mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-03 17:51:58 +01:00
de5a1bdbe73e47adee904660ab5e4101efdc1c8f
923
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
63df7ad2b3 |
feat(login): use the login pseudo-terminal for Codex device login and de-Claude the shared channel (#12020)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agent adapters use provider-specific login flows > - Codex device login needs a live pseudo-terminal (PTY), while the shared channel still uses Claude-specific names > - The old streamed-exec path does not provide the prompt transport that Codex needs > - This pull request moves Codex device login to the shared login PTY and removes the dead streamed-exec path > - The benefit is one controlled login transport with fail-closed capability checks and safer credential reads ## Linked Issues or Issue Description **Problem or motivation** Codex device login used a streamed-exec path that did not provide the required prompt transport. The shared login channel also exposed Claude-specific names outside Claude code. **Expected behavior** The host selects a fixed login command from trusted adapter data. Codex login uses the provider login PTY. Providers without that capability fail closed. **Proposed solution** Use a server-controlled session home, create and validate it as a fresh 0700 directory, read credentials from one validated descriptor, and rename shared channel names to the neutral login PTY family. **Alternatives considered** Keep the shared login PTY as the single transport. Do not keep the removed streamed-exec path because it cannot provide the required prompt transport. **Roadmap alignment** This change supports the planned login transport work. It does not add a separate roadmap item. ## What Changed - Route Codex device login through the shared login PTY transport. - Select the login command from a closed internal command key. - Carry a server-controlled session home through the launch contract. - Create and validate the session home as a fresh 0700 directory owned by the login user. - Read the credential file with descriptor-relative, no-follow path walking and final descriptor checks. - Gate the login route and run lease on the provider login PTY capability. - Rename shared channel names to the neutral login PTY family. - Remove the streamed-exec transport value, selector field, driver branch, and related tests. - Hide Codex login in the user interface when the provider lacks the login PTY capability. ## Verification - Server unit suites pass: 89/89. - Adapter-utils suites pass: 262/262. - Codex-local suites pass: 326/326. - Credential-read reader suite passes: 20/20. - Daytona login PTY suite passes: 30/30. - Device-login suites pass: 56/56. - TypeScript checks pass for server, adapter-utils, and UI. - GitHub Actions must pass after pull request creation. - Greptile review must reach 5/5 with no open P2 findings, recommendations, or follow-ups. ## Risks - Providers without a login PTY capability lose Codex login support by design. - The credential read rejects invalid ownership, mode, type, path, and size. - The launch-time sandbox directory race remains outside the threat model because the login runs inside the sandbox and a hostile sandbox already controls its credential. ## Model Used OpenAI Codex, GPT-5, tool use and code review assistance. The exact context window and reasoning mode are 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 - [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> |
||
|
|
f572e08678 |
fix(recovery): stop automatic stranded-task takeovers (#11961)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The recovery service restores execution when a task loses its live path. > - The service retries the original agent for a limited number of attempts. > - The old fallback could select a manager or an executive and wake that agent. > - That fallback changed the effective recovery owner without a board decision. > - This pull request keeps the source owner and gives the exhausted recovery decision to the board. > - The benefit is a clear ownership rule with no automatic task takeover. ## Linked Issues or Issue Description Refs: #11807 Refs: #11817 **What existing behavior does this improve?** This improves stranded-task recovery in the server and the recovery action card in the board UI. **Subsystem affected** Cross-cutting: server recovery orchestration, recovery observability, board UI, and execution documentation. **Current behavior** Paperclip retries the original agent for a limited number of attempts. After the retry limit, it can select a manager, task creator, CTO, or CEO as a recovery owner. It can then wake that substitute agent. The source task keeps its assignee, but the automatic substitute wake creates an implicit takeover path. **Proposed behavior** Paperclip keeps the limited retry path for the original agent. If recovery is exhausted or unsafe, Paperclip creates one board-owned source recovery action. It keeps both source assignee fields. It does not wake a substitute agent. The board can repair, retry the original owner, explicitly reassign, or resolve the task. **Reason and benefit** Source task ownership must remain stable until a person or an approved policy changes it. The new rule removes implicit manager and executive takeover. It also gives operators clear evidence through the `board_escalation_no_takeover_v1` routing marker. **Breaking changes** Automatic recovery no longer wakes a manager or executive after the original-agent retry limit. Existing active agent-owned recovery actions remain visible and can resolve. Paperclip does not schedule a new takeover wake for those legacy actions. ## What Changed - Route exhausted and unsafe stranded recovery to a board-owned source action. - Preserve agent and user assignee fields during automatic escalation. - Keep limited same-agent continuity repair and provider quota monitoring. - Stop new manager, creator, CTO, and CEO recovery wakes. - Keep legacy agent-owned recovery actions readable and resolvable. - Add the routing marker to new board escalation evidence and observability. - Update recovery notices, the board UI card, tests, and execution documentation. ## Verification - Run `pnpm -r typecheck`. - Run `pnpm build`. - Run `pnpm check:token-gates`. - Run `pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-process-recovery.test.ts`. - Run `pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-workspace-branch-containment.test.ts`. - Run the focused recovery and UI Vitest files changed by this pull request. - Confirm that a paused or over-budget source owner creates one board action, keeps the source assignee, and creates no substitute wake. ## Risks - Operators must now make the final recovery decision after the original-agent limit. - Legacy agent-owned actions use their stored contract. This avoids a rollout-time ownership rewrite. - No database migration or API response shape changes are included. - The tests cover concurrent escalation, paused and over-budget owners, legacy actions, provider quota monitoring, and UI presentation. > 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 with GPT-5. The hosted exact model revision and context window are not exposed. Reasoning, tool use, and code execution were enabled. ## 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> |
||
|
|
10d2781a29 |
feat(sandbox): add the duplex bridge broker, gated transport selection, and fixed observability (#11769)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Sandbox adapters provide controlled execution for untrusted provider environments. > - The sandbox channel needs one persistent duplex transport with strict host control. > - The transport must remain off unless the instance setting and provider capability both allow it. > - The host must detect loss, bound resource use, and expose only safe telemetry. > - This pull request adds the broker, gated selection, kill-switch wiring, fixed observability, and real-process proof. > - The benefit is safer sandbox execution with bounded failure behavior and inspectable transport results. ## Linked Issues or Issue Description No public issue exists for this change. The related pull requests are #11738 and #11750. **Problem or motivation** The sandbox duplex channel needs a host-controlled broker, strict transport gates, bounded provider input, and safe loss telemetry. Without these controls, a provider can cause replay, resource growth, unsafe endpoint selection, or data exposure through telemetry. **Proposed solution** Add a host broker with nested time limits, request limits, one-shot loss, and per-id deduplication. Select duplex transport only when the instance setting and provider capability both equal true. Assign the endpoint and nonce on the host. Reject invalid readiness data and use the file bridge on failure. Add fixed redacted telemetry and a real-process end-to-end test harness. **Alternatives considered** Keep the file bridge as the only transport. This avoids new channel behavior but does not provide persistent duplex operation for supported sandbox providers. **Roadmap alignment** This change supports the Cloud / Sandbox agents section in ROADMAP.md. ## What Changed - Add the duplex bridge broker with bounded forward, response, and gateway wait budgets. - Bound concurrent requests, lifetime requests, and request-id bytes before retention or forwarding. - Select duplex transport only when both required gates are true. - Assign the loopback port and nonce on the host and enforce a liveness-only READY frame. - Fall back to the file bridge after invalid readiness, contamination, bind failure, or timeout. - Carry the kill switch through the server, acpx engine, and six local adapters. - Add fixed, redacted duplex telemetry with a provider allowlist. - Add a real-process end-to-end harness for readiness, round trips, loss, and teardown. - Add regression coverage for limits, loss, UTF-8 splits, concurrency, and telemetry dimensions. ## Verification - Adapter-utils, server, and Daytona typechecks pass locally. - Adapter-utils tests pass, including the codec, broker, execution-target sandbox, and real-process harness. - Server kill-switch tests pass. - Live Daytona tests pass with the required provider key and skip without that key. - The root pnpm-lock.yaml file has no diff. - The branch contains ten commits after origin/master. ## Risks - Duplex transport remains disabled unless both gates equal true. - A provider remains an untrusted boundary and needs least-privilege credentials and quotas. - The server telemetry recorder stays deferred; the default recorder does nothing. - A provider that pre-binds the host port causes a fail-closed fallback to the file bridge. - The change adds no database migration and changes no root lockfile. ## Model Used OpenAI GPT-5, exact model family GPT-5, large context window, reasoning, and tool use. The model assisted with Git handoff validation and PR preparation. The implementation commits came from the engineering worktree. ## 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/... or 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 - [x] I searched the GitHub PR list for similar PRs and confirmed this is not a duplicate --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
3ff636bc48 |
Drop the mission step from the wizard arc (#11935)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - New customers arrive through an onboarding arc that spans Paperclip Cloud and the tenant app > - Cloud's naming screen stopped asking for the company mission, but the tenant wizard still decided its first step by asking whether the company had one > - Every Cloud-created company therefore looked mission-less on arrival, so every walk detoured through a "Define your mission" screen the design had already removed > - This pull request removes that step from the arc, and makes step 1 create the company itself > - The benefit is a shorter arc that matches the design, and a three-step progress strip that now counts the steps that exist ## Linked Issues or Issue Description No public issue exists. The problem was found by walking staging end to end. **What happened:** A new customer signs in, names their organization, and waits for it to build. The tenant wizard then asks "Define your mission" before it asks for the first agent. Cloud no longer collects a mission, so this screen appears for every new customer. **Expected behavior:** The wizard asks for the first agent, the model, and a review. The progress strip counts three steps. **Actual behavior:** The wizard asks for the mission first. The progress strip counts five segments, because the run does not enter on the agent arc. **Additional context:** Three merged pull requests built the mission-based step choice this change removes: #11352, #11416 and #11429. The mission is now collected later, inside the tenant app, so onboarding does not ask for it at all. ## What Changed - `onboardingStepForCompany` always returns the agent step. The `companyHasMission` parameter is removed, because it cannot change the answer. - `resolveRouteOnboardingOptions` no longer accepts `companyHasMission`. - The dashboard no longer waits for the goal lookup before it opens the wizard. That wait only chose a step, and the step is now fixed. - Step 1 creates the company in a new `handleCreateCompany`. Company creation used to sit at the end of `handleConfirmMission`. - No company goal is written during onboarding. - The three-step strip now shows on the agent, model and review steps, because every Cloud-first run enters on the arc. - The full-length bar drops its second segment. No run can fill it. - The grow path keeps its step 2 questionnaire. Only the create path skips ahead. - Back from the agent step goes to the screen the run came from. - Four end-to-end specs no longer drive the wizard through the mission step. ## Verification Run the tenant test suite: ``` cd ui && npx vitest run ``` - 4356 tests pass. 471 files pass. - `npx tsc --noEmit` reports no errors. - Fault injection: forcing `skipsMissionStep` to `true` fails the grow questionnaire test. Removing the Back rule fails the Back test. Both tests fail on the exact defect they guard. - The three-step strip is asserted by an existing test. It checks `Step 1 of 3` and `aria-label="Create your first agent"`. Manual check on staging after the paired Cloud change: 1. Open a new incognito window. 2. Sign in with a new account. 3. Name the organization. 4. Confirm the wizard shows "Create your first agent" and "Step 1 of 3". ## Risks - **Behavioral change.** Onboarding no longer writes a company goal. An agent hired during onboarding starts without a seeded mission. This is intended. The mission moves to the tenant app. - **Dead code.** `ONBOARDING_MISSION_STEP` and the mission screen stay in the codebase, but nothing in the app opens them. They wait for the surface that collects the mission later. - **Grow path.** The grow path is unchanged, but it shares step 2 with the removed screen. New tests cover it. - **Superseded work.** #11352, #11416 and #11429 tuned the mission-based step choice. This change removes the branch they tuned. ## Model Used Claude Opus 5 (`claude-opus-5`), extended thinking, with tool use and code execution through Claude Code. ## 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 - [x] My branch name describes the change and contains no internal Paperclip ticket id - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] 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: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
adfbe2d4b9 |
feat(environments): refer to the managed default environment by name, not the sandbox driver key (#11838)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Managed deployments provision a platform-managed default environment
for agent runs; the UI shows this environment in selectors, the agent
form, run details, and the environments page
> - Those surfaces append the raw driver key to the environment name, so
users see labels like "Paperclip Computer (sandbox)", "Paperclip
Computer · sandbox", and fallback copy such as "Managed sandbox" and
"The sandbox has no ready authentication"
> - "sandbox" is infrastructure vocabulary, not the product name of the
environment; showing it next to the managed environment's name is
confusing and off-brand
> - This pull request renders platform-managed environments by name
alone and rewords the sandbox-phrased copy, while user-created
environments keep the driver suffix so mixed lists stay distinguishable
> - The benefit is that the default environment reads as one clear
product name everywhere, and self-hosted users lose nothing: their own
environments still show the driver
## Linked Issues or Issue Description
**What existing behavior does this improve?**
Display of the platform-managed default environment across the UI.
**Subsystem affected**
UI (environment selectors, agent config form, environments page, agents
page, run details) and the claude-local/codex-local adapter auth checks.
**Current behavior**
The agent form labels the inherited default environment as "Name
(sandbox)". Environment selectors and the environments list render "Name
· sandbox". The agents page describes the environment as "<provider>
sandbox provider". The agent form's fallback label is "Managed sandbox".
Adapter auth checks say "The sandbox has no ready authentication for
this adapter."
**Proposed behavior**
Platform-managed environment rows (`metadata.managedByPaperclip`) render
their name alone. The fallback label is "Paperclip Computer". The agents
page describes managed environments as "Managed by Paperclip". Run
details omit the driver suffix for sandbox-driver environments (the
adjacent Provider entry already identifies the mechanism). Adapter auth
checks say "This environment has no ready authentication for this
adapter."
**Reason and benefit**
The managed environment carries a product name. Appending the raw driver
key ("sandbox") to it is noise and contradicts the product naming.
User-created environments keep the driver suffix, so mixed lists stay
distinguishable.
**Breaking changes**
None. Message text of the auth check is not read programmatically; the
UI keys off `ADAPTER_AUTH_MISSING_CHECK_CODE`. Rows without the managed
marker render exactly as before.
## What Changed
- New `environmentDisplayLabel` helper in
`ui/src/lib/managed-sandbox-environment.ts`: managed rows → name alone;
other rows → "Name · driver".
- `AgentConfigForm`: inherited-default label uses the helper; fallback
copy "Managed sandbox" → "Paperclip Computer"; environment options use
the helper.
- `ProjectProperties`, `CompanyEnvironments`: environment selector
options use the helper; the environments-list row hides the driver
suffix on managed rows; the managed detail page's fallback description
no longer says "sandbox".
- `Agents` page: managed environments are described as "Managed by
Paperclip" instead of "<provider> sandbox provider".
- `CommentThread` run details: the driver suffix is omitted for
sandbox-driver environments.
- claude-local and codex-local adapters: auth-missing check message/hint
reworded from "sandbox" to "environment" (ACP and environment-test
paths); claude-local probe/effort/login hints reworded the same way.
- Run status lines: "Syncing workspace to sandbox", "Exporting git
changes from sandbox", "Starting adapter in sandbox", and friends now
say "environment"; "Finalizing sandbox workspace" → "Finalizing
workspace". Templated transfer-progress lines map the `sandbox`
transport key to "environment" for display (`runtime-progress.ts`).
- Agent form sign-in panel: "Sign in to the sandbox" → "Sign in to the
environment"; "Authenticated. The sandbox has credentials now." → "…The
environment has credentials now."
- Feature catalog + instance settings card: "Managed Sandbox Only" →
"Managed Environment Only" (setting key unchanged; the card keeps its
alphabetical slot).
- Server agents routes: execution-target failure and test-identity copy
no longer say "sandbox"; workspace-mode label "Cloud sandbox" → "Cloud
environment".
- Tests: new `environmentDisplayLabel` unit cases; new `AgentConfigForm`
render case asserting the managed default renders without "(sandbox)" or
"· sandbox"; status-line assertions updated across adapter-utils, server
heartbeat/live-run, and UI chat suites.
## Verification
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `pnpm --filter @paperclipai/adapter-claude-local typecheck` and
`--filter @paperclipai/adapter-codex-local typecheck` — clean.
- `vitest run` for `managed-sandbox-environment.test.ts`,
`AgentConfigForm.render.test.tsx`, `CompanyEnvironments.test.tsx`,
`Agents.test.tsx`, `CommentThread.test.tsx`, `NewAgent.test.tsx` — all
green (118 tests across the two runs).
## Risks
Low risk. Cosmetic label changes only; no data or API changes. Rows
without `metadata.managedByPaperclip` render exactly as before, so
self-hosted deployments with their own environments see no change. The
only self-hosted-visible wording changes are the adapter auth-check
message and the driver suffix omission on sandbox-driver rows in run
details.
## Model Used
- Claude (Anthropic) — claude-fable-5 (Claude Fable 5), Claude Code CLI,
extended thinking, tool use.
## 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 (no
docs reference these labels)
- [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
|
||
|
|
773a9dade6 |
fix: preserve plugin detail-tab deep links and bridge the real JSX runtime (#11826)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Plugins can contribute detail tabs to project pages, and plugin UI
bundles render through a host-provided React bridge
> - A cold load of a plugin-tab deep link redirected to the Issues tab,
because the plugin-slots query is disabled until the project's company
resolves and a disabled query reports `isLoading: false`
> - The redirect made shareable plugin-tab URLs and browser
reload/back/forward unreliable for every detail-tab plugin
> - Separately, the bridge shim rebuilt `jsx`/`jsxs` on top of
`createElement(type, { children })`, which drops React's static-children
marking and floods the dev console with "unique key" warnings from
plugin components
> - This pull request holds the tab decision on a skeleton until
contributions actually load, and exposes the host's real
`react/jsx-runtime` on the plugin bridge with the old shim as fallback
> - The benefit is that plugin detail tabs survive direct open, reload,
back, and forward, and plugin surfaces stop emitting spurious React key
warnings
## Linked Issues or Issue Description
**What happened**
Opening a project URL with `?tab=plugin:<publisher>.<plugin>:<tab>`
directly, or reloading while on such a tab, redirected to the project's
Issues tab even though the plugin contribution was registered and
available. The dev console also showed "each child in a list should have
a unique key" warnings pointing at plugin-rendered components.
**Expected behavior**
Direct open, reload, back, and forward keep the requested plugin tab
when its contribution is available. When the contribution truly does not
exist, the page still falls back safely to the Issues tab. Plugin
components with static multi-child JSX render without React key
warnings.
**Steps to reproduce**
1. Install a plugin that contributes a project detail tab.
2. Open the tab, copy the URL, and open it in a new browser tab (or
press reload).
3. Observe the redirect to `/projects/<ref>/issues` before the
plugin-slots query has ever run.
4. With React in development mode, open any plugin tab that renders
sibling elements and observe key warnings in the console.
**Version or commit**
Reproduced on `master` at `733ffbf7c`.
**Deployment mode**
Local development instance (managed runtime).
## What Changed
- `ui/src/pages/ProjectDetail.tsx`: the plugin-tab fallback now waits
until the company is resolved and the plugin-slots query has finished
before it decides. While the decision is pending it renders the detail
`PageSkeleton` instead of navigating away. Loading a project that fails
to load still shows the error state.
- `ui/src/plugins/bridge-init.ts`: the plugin bridge registry now
exposes the host's real `react/jsx-runtime` module as `reactJsxRuntime`.
- `ui/src/plugins/slots.tsx`: the `react/jsx-runtime` shim served to
plugin bundles prefers the bridged runtime's `jsx`/`jsxs`/`Fragment` and
keeps the previous `createElement`-based implementation as a fallback.
- `ui/src/pages/ProjectDetail.test.tsx`: new tests for cold deep links —
pending slots query, disabled query before company resolution,
registered tab render, and fallback when the tab is not contributed.
- `ui/src/plugins/bridge.test.ts`: new tests that the bridge exposes the
real runtime and that unkeyed static children render through it without
key warnings.
## Verification
- `npx vitest run src/pages/ProjectDetail.test.tsx
src/plugins/bridge.test.ts` in `ui/` — 2 files, 18 tests, all pass.
- `pnpm check:token-gates` — all gates clean.
- Manual: open a project with a plugin detail tab, switch to the tab,
reload the browser, use back/forward, and open the URL in a fresh tab.
The tab persists. Remove the plugin and open the same URL. The page
falls back to the Issues tab.
## Risks
- Low risk. The fallback redirect still happens whenever contributions
finish loading without the requested tab; the change only defers it
until the answer is known, so an unavailable contribution cannot get
stuck on the skeleton.
- The jsx-runtime shim keeps the old `createElement` path as a fallback
when the bridged runtime is absent, so older bridge registries keep
working.
## Model Used
- Claude (Anthropic) — `claude-fable-5`, extended thinking enabled,
agentic tool use (file edits, shell, test execution) via Claude Code.
## 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 similar PRs and linked
related PRs above where they exist
- [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)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
— *the branch predates this rule; squash-merge keeps the branch name off
`master`*
- [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
(code-level docs; no user-facing docs affected)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green — *pending first CI run on this
PR*
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
*pending first review*
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
db4defdfbf |
feat: operator-configurable settings visibility via PAPERCLIP_HIDDEN_SETTINGS (#11823)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The instance settings surface (Access, Plugins, Adapters, General,
Experimental) assumes the person at the keyboard operates the whole
instance
> - Operators who host Paperclip for others — a managed cloud or an
internal shared server — expose settings pages and toggles that do not
apply to their deployment, and the related mutation APIs stay open
> - A hosted tenant can open Plugins or Adapters, try an action, and hit
a confusing failure, because only a few hardcoded platform floors exist
> - This pull request adds a generic, operator-configured visibility
mechanism: one env var hides declared settings surfaces in the UI and
floors their mutation routes with a stable 403 code
> - The benefit is a clean hosted-tenant settings surface for any
operator, with zero behavior change for normal self-hosted instances
## Linked Issues or Issue Description
**Subsystem affected**
Instance settings (server routes and UI), the shared settings registry
in `packages/shared`, and the `/api/health` bootstrap payload.
**Problem or motivation**
An operator who hosts Paperclip for other people cannot hide settings
surfaces that the platform manages. Tenants see Access, Plugins, and
Adapters pages, backup retention, and host-level experimental toggles
that do nothing useful for them. The mutation APIs behind these surfaces
also stay open, so a tenant admin can attempt actions the platform must
control. ROADMAP.md names a cleaner shared deployment story as a goal
("Teams should be able to run the same product in hosted or semi-hosted
environments without changing the mental model").
**Proposed solution**
Add a declarative registry of hideable settings surfaces and one env
var, `PAPERCLIP_HIDDEN_SETTINGS`. The server parses the list at boot,
reports it on `/api/health`, and rejects value-changing writes to hidden
surfaces with a stable `settings_operator_managed` 403 code. The UI
reads the list from the health payload and removes the hidden pages,
sections, and toggles from navigation, routes, and page content. Unknown
keys warn and are ignored, so one list can roll across a fleet with
mixed app versions. With the variable unset, behavior is byte-identical
to today.
**Alternatives considered**
- Hardcode the hidden set for cloud instances in this repo: rejected,
because each hosting operator needs a different policy, and policy does
not belong in shared code.
- Deliver the hidden set through the managed-config document: rejected,
because that channel is cloud-specific and fail-closed on unknown
fields; a plain env var works for any operator, including self-hosted
shared servers.
- Lock the controls with a badge instead of hiding them: rejected for
these surfaces, because they are meaningless to tenants, not merely
platform-controlled; the existing managed-overlay lock stays the right
tool for controlled flags.
**Roadmap alignment**
Supports the "shared deployment story" item in ROADMAP.md: hosted and
semi-hosted deployments keep the same product with a settings surface
that matches what the tenant can actually do.
## What Changed
- New `packages/shared/src/settings-visibility.ts`: registry of hideable
surfaces (every instance settings page — profile, environments, access,
heartbeats, experimental, plugins, adapters; every Instance → General
section; every experimental flag as `instance.experimental.<key>`), the
`PAPERCLIP_HIDDEN_SETTINGS` parser, and the `settings_operator_managed`
error code. The General page stays visible as the settings root and
redirect target.
- New `server/src/services/settings-visibility.ts`: parse-once accessor;
unknown keys log one warning and are ignored.
- `/api/health` reports `hiddenSettings` on every response shape; the
field is omitted when nothing is hidden.
- Server floors on hidden surfaces, with same-value echo tolerance (the
`executionMode` precedent): field-backed general sections and
experimental keys reject value-changing PATCHes, and hiding the whole
Experimental page floors every toggle; plugin lifecycle and config
writes, adapter management writes, and the Access admin routes (reads
included) return 403 `settings_operator_managed`. Reads the app itself
needs (plugin `ui-contributions`, adapter metadata, plugin job trigger)
stay open. Pages without instance-scoped mutation routes are hidden in
the UI only.
- UI: new `useHiddenSettings` hook and `HiddenSettingsPageGate` route
gate (hidden pages redirect to the settings root); the settings sidebar
and tab bar drop hidden entries; remembered settings paths remap to the
default page; `InstanceGeneralSettings` skips hidden sections; every
`ExperimentalToggleCard` now carries its flag key and renders nothing
when hidden.
- Removed the dead `InstanceSidebar` component (referenced only by its
own test).
- Docs: `docs/deploy/environment-variables.md` documents the variable
and the key registry.
## Verification
- `pnpm vitest run` over the new and extended suites: shared registry
and parser, representative floor tests per route class (changed-value
403, same-value echo 200, unset env 200, page-level Experimental
hiding), the health field, the route gate, nav filtering, and
section/card hiding with one hidden example per surface kind — 168 tests
pass.
- Full root `pnpm typecheck` passes.
- Manual: booted a server with the variable set. `/api/health` lists the
keys; an unknown key logs one warning and the server boots; hidden pages
redirect; hidden sections and cards do not render; hidden-field PATCH
returns 403 with `details.code = "settings_operator_managed"`; a
same-value echo returns 200. Unset the variable: the full settings
surface returns and responses are byte-identical to master.
## Risks
- Low risk for self-hosted instances: with the variable unset, the
hidden set is empty, the health field is omitted, and no floor
activates.
- Flooring plugin config writes assumes hosted deployments configure
plugins through the platform. If a future bundled plugin needs
tenant-entered config, the floor needs a narrow carve-out.
- Hidden-key floors tolerate same-value echoes, so API clients that
round-trip full GET responses keep working.
- Hiding a toggle does not change its value; operators pair hiding with
the desired default where the value matters.
## Model Used
Claude Fable 5 (Anthropic, `claude-fable-5`) with extended thinking and
agentic tool use, driven through the Claude Code CLI (file edits, test
execution, and live-server verification loops).
## 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
|
||
|
|
233c12f029 |
feat: add kimi-local adapter for Kimi Code CLI (CLI + ACP engines) (#9967)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Local agent adapters (`claude_local`, `gemini_local`, `grok_local`, …) are the integration surface that lets Paperclip run coding CLIs on the host machine > - The Kimi Code CLI (`kimi`, Moonshot AI) has a documented non-interactive mode, `kimi -p --output-format stream-json` with session resume via `kimi -r`, but Paperclip has no built-in adapter for it > - So Kimi users (especially Kimi membership / OAuth subscribers) cannot onboard their CLI to Paperclip agent teams > - This pull request adds a complete built-in `kimi_local` adapter (both execution engines, session management, instructions + skills delivery, thinking-effort control, environment test, UI and CLI modules, docs) following the established `gemini_local`/`grok_local` package pattern > - Kimi Code ships an ACP server (`kimi acp`), so the adapter runs on Paperclip's shared acpx engine by default (streaming transcript with live tool status, like `claude_local`/`gemini_local`) and falls back to a headless CLI lane (`kimi -p --output-format stream-json`) when ACP prerequisites are unavailable > - The benefit is that Kimi Code becomes a first-class Paperclip agent lane: selectable in the UI, resumable across heartbeats, with the same operating context (instruction bundle, skills, effort) and streaming transcript the other local adapters get ## Linked Issues or Issue Description - Supersedes #9880 (same branch; expanded from the CLI-only lane into a complete adapter with the default ACP engine lane, control-plane skill install, and live transcript wiring) - Refs #9879 (adapter request for Kimi Code CLI, filed with this PR) - Refs #163 (original Kimi support request) Duplicate/related prior PRs, per the dedup search (both appear stale: no updates or maintainer review since May 2026, and both target an older Kimi CLI interface; calling them out for reviewer context per CONTRIBUTING.md): - Refs #6276 (`feat: add kimi-local adapter`): targets an older array-based content format (`{type: think}`/`{type: text}` blocks), not the current documented stream-json schema - Refs #5202 (`feat(adapter): add Kimi CLI local adapter with Wire protocol support`): builds on a `--wire` JSON-RPC interface that current Kimi Code CLI (0.27.0) no longer documents; the current documented headless interface is `-p --output-format stream-json` This PR is a fresh implementation against current master and the currently documented/verified Kimi CLI behavior (see Verification). Happy to fold in anything useful from the earlier attempts if a reviewer prefers. ## What Changed - **New adapter package** `packages/adapters/kimi-local` (`@paperclipai/adapter-kimi-local`), modeled on `gemini-local`/`grok-local`: - `src/server/execute.ts`: spawns `kimi -p <prompt> --output-format stream-json` (argv array, no shell), `-m <model>` only when configured, `-r <sessionId>` when the stored session cwd matches the run cwd, automatic fresh-session retry on unrecoverable-session errors, headless-safe env (`CI=1`, `NO_COLOR=1`, `KIMI_CODE_NO_AUTO_UPDATE=1`, `TERM=dumb`; user-configured values win), full remote (ssh/sandbox) execution lane with runtime install via `@moonshot-ai/kimi-code` - **Instruction bundle delivery**: the prompt path directive now names the sibling instruction files (`./HEARTBEAT.md`, `./SOUL.md`, `./TOOLS.md`) alongside the prepended entry file, and local runs pass `--add-dir <instructions-dir>` so Kimi can actually open them (matching `claude_local`). Without this, only the entry file reached Kimi and agents improvised the operating workflow that `HEARTBEAT.md` documents - **Thinking effort**: a configured `effort` is forwarded as the `KIMI_MODEL_THINKING_EFFORT` operational override (Kimi has no per-invocation effort flag). It is only sent for models that advertise `support_efforts` (currently `kimi-code/k3`) to avoid provider rejections, and `medium` maps to `high` since Kimi has no medium tier (`low`/`high`/`max` pass through) - **Skills delivery**: desired Paperclip skills are delivered via Kimi's `--skills-dir` flag from a dedicated per-run directory (a local snapshot, or the synced snapshot on remote targets), so skills load reliably and in isolation. Paperclip never overwrites the shared `$KIMI_CODE_HOME/skills` home, so skills installed by the operator or other agents are left intact. `--skills-dir` is only passed when at least one skill is desired, so unconfigured agents keep Kimi's default skill discovery - **Live run status**: the adapter now forwards each streamed stream-json line to `onEvent` (assistant `content` as an assistant snippet, `tool_calls` as tool-name events), which drives the issue-thread activity indicator (`currentToolName` / `lastAssistantSnippet` / `lastEventAt`). Previously the adapter only wrote the raw run log, so the issue thread showed a stale "no output for N s" line with no tool or reasoning context while Kimi worked. Tool results are omitted so the last meaningful "Using X" / snippet is not overwritten by a generic label - `src/server/parse.ts`: parses the verified Kimi stream-json event shapes (`assistant` text, `assistant.tool_calls` with JSON-string arguments, `tool` results, trailing `meta.session.resume_hint` for session-id capture) plus failure classifiers (`kimi_auth_required`, transient network, unrecoverable session). A signaled exit (null exit code, not a timeout) is now reported as a failure rather than coalesced to success, and the error message names the terminating signal - `src/server/skills.ts`: lists/syncs Paperclip skills for the adapter's skill-management surface - `src/server/test.ts`: environment test covering CLI resolution + `kimi --version`, cwd check, auth detection (OAuth credential dirs, keyed `[providers.*]` in config.toml, or the `KIMI_MODEL_NAME` + `KIMI_MODEL_API_KEY` env pair), and a live hello probe - `src/ui/` (stdout-line parser for transcripts, config builder) and `src/cli/` (stream event formatter) modules - Root metadata: three managed model aliases (`kimi-code/kimi-for-coding`, `kimi-code/kimi-for-coding-highspeed`, `kimi-code/k3`), effort-capable-model metadata (`EFFORT_CAPABLE_MODELS`, effort mapping helpers), `agentConfigurationDoc` - Tests: 101 tests across parse, execute (args building, resume gating, retry, auth error code, timeout, signaled-exit failure, effort forwarding/gating/mapping, `--add-dir` instructions directive, `--skills-dir` gating, `onEvent` runtime-event forwarding), ACP engine (engine resolution, acpx config build, node-version gate), ACP transcript delegation, environment test, UI parse/build-config - **ACP engine lane (default)** (`src/server/acp.ts` + shared `adapter-utils/acpx-engine`): Kimi Code ships an ACP server (`kimi acp`), so `kimi_local` now runs on Paperclip's shared acpx engine by default, matching `claude_local`/`codex_local`/`gemini_local`. The issue-thread transcript streams live (assistant text deltas, tool calls with a `pending`->`completed` status lifecycle) instead of the CLI lane's bursty complete-message output. Registered `kimi_local -> "kimi"` in `ACPX_ADAPTER_AGENT_IDS` and resolved the built-in agent command to `kimi acp`; `execute.ts` dispatches to the ACP executor first with an automatic CLI fallback when ACP prerequisites fail (`engine=acp` requires ACP, `engine=cli` pins the headless lane); `index.ts` falls back to the shared acpx session codec; the UI/CLI delegate `acpx.*` events to the shared acpx transcript parser and event formatter. The headless CLI lane (above) remains as the fallback - **Registration** (one entry each, mirroring existing adapters): server adapter registry + `BUILTIN_ADAPTER_TYPES`, `AGENT_ADAPTER_TYPES` (shared), UI adapter registry + display registry (`Kimi Code`, Moon icon) + capabilities defaults, CLI adapter registry, `Dockerfile` (package copy + `npm install --global @moonshot-ai/kimi-code@latest`), `vitest.config.ts` workspace, `scripts/release-package-manifest.json` - **Behavioral sets** mirroring `gemini_local` (Kimi resumes sessions the same way): `GIT_SENSITIVE_LOCAL_ADAPTER_TYPES`, `SESSIONED_LOCAL_ADAPTERS` (heartbeat + recovery), `REMOTE_MANAGED_ADAPTERS`, ssh/sandbox execution-target allow-lists, `ADAPTER_DEFAULT_RULES_BY_TYPE` (`timeoutSec: 0`, `graceSec: 15`), and `LEGACY_SESSIONED_ADAPTER_TYPES` + `ADAPTER_SESSION_MANAGEMENT` in adapter-utils - **UI touch-points**: New Agent default-model branch, AgentConfigForm command map (`kimi_local: "kimi"`) + model defaults + a Kimi-specific thinking-effort option list (`Low`/`High`/`Max`, reflecting Kimi's tiers rather than borrowing Claude's), OnboardingWizard (command map, model default, `kimi login` / `KIMI_MODEL_NAME + KIMI_MODEL_API_KEY` auth hints, manual-debug command line), InviteLanding enabled adapters - **Control-plane skill install** (`cli/src/commands/client/agent.ts`): `paperclipai agent local-cli` seeded the Paperclip control-plane skills into `~/.codex/skills` and `~/.claude/skills` so Codex/Claude agents auto-discover the API reference every run. Kimi had no equivalent target, so `kimi_local` agents began each session without the control-plane skill and rediscovered routes (e.g. the company-scoped `POST /api/companies/{companyId}/issues`) by trial and error. Added `~/.kimi-code/skills` (honoring `KIMI_CODE_HOME`) as a third install target for parity. Independent of the per-run `--skills-dir` delivery, which only applies to explicitly configured skills. - **Docs**: `docs/adapters/kimi-local.md` (prerequisites, auth options, config fields including `effort`, session resume, instruction bundle, skills delivery, control-plane skill install) + a row in `docs/adapters/overview.md` Out of scope (deliberately): model profiles, built-in agent `allowedAdapterTypes` additions. ## Verification\n\nCurrent-master rebase verification (OpenAI Codex, 2026-08-03): 13 focused files / 231 tests pass; adapter-utils, server, UI, CLI, and Kimi adapter typechecks pass; full repository build and UI token gates pass. The branch is conflict-free against master at head `1249df117c5e12e5771b9a570a6340866450619e`.\n\nAutomated (all from repo root, pnpm 9.15.4, Node 22): - `vitest run packages/adapters/kimi-local`: 89/89 pass (includes coverage for the instruction `--add-dir` directive, effort forwarding/gating/mapping, `--skills-dir` gating, the signaled-exit failure path, and `onEvent` runtime-event forwarding with cross-chunk line buffering) - `vitest run server/src/__tests__/adapter-registry.test.ts server/src/__tests__/adapter-routes.test.ts server/src/services/heartbeat-stop-metadata.test.ts ui/src/adapters/adapter-display-registry.test.ts`: 37/37 pass - `vitest run cli/src/__tests__/skills.test.ts`: 13/13 pass (the control-plane skill install target follows the existing Codex/Claude install path, whose symlink logic is unchanged) - `vitest run packages/shared`: 307/307 pass; `vitest run packages/adapter-utils`: pass except one pre-existing, unrelated failure (`mcp-isolation.integration.test.ts` requires Claude CLI ≥ 2.1.207; host has 2.1.185, fails identically on unmodified master) - `pnpm --filter @paperclipai/adapter-kimi-local typecheck|build`, plus typecheck of `server`, `ui`, `cli`, `adapter-utils`: all clean - `pnpm install --frozen-lockfile`: passes (the PR diff itself contains no lockfile changes, per repo policy; verified against a locally regenerated lockfile) - `node scripts/check-no-git-push.mjs` and `node scripts/check-forbidden-tokens.mjs`: pass - CI note: the `policy` job's release-bootstrap step is expected to stay red until a maintainer bootstraps the first npm publish of `@paperclipai/adapter-kimi-local`; see the CI Note for Maintainers comment. All other contributor-actionable checks are green. Manual end-to-end (real Kimi CLI 0.27.0, OAuth login, dev server on an isolated instance): 1. Server `GET /api/adapters` lists `kimi_local` as builtin with correct capability flags; models endpoint returns the three Kimi models 2. `POST .../adapters/kimi_local/test-environment`: all checks pass, including a live `kimi -p` hello probe 3. Created a `kimi_local` agent and invoked two heartbeats: run 1 spawned `kimi -p ... --output-format stream-json`, Kimi used its `Read` tool, produced the expected answer, and the session id was captured from the `session.resume_hint` meta event; run 2 resumed the **same** Kimi session (`sessionIdBefore == sessionIdAfter`) via `-r` 4. UI: adapter appears in the New Agent dropdown; selecting it shows the Kimi command placeholder, the three models, and the Kimi config fields; the run transcript renders Kimi tool calls via the adapter's stdout parser The instruction-bundle, thinking-effort, and `--skills-dir` changes landed after the manual run above. They are covered by the unit tests listed under Automated, and the Kimi CLI flags they rely on (`--add-dir`, `--skills-dir`, `KIMI_MODEL_THINKING_EFFORT`) were confirmed against the installed Kimi Code CLI 0.27.0 (`kimi --help`, config-file thinking-effort docs). Screenshots (assets branch on the fork, not part of the diff):       ## Risks - Low risk to existing behavior: the change is additive, one new workspace package plus single-entry registrations alongside existing adapters; no existing adapter code paths are modified. - The adapter invokes the locally installed `kimi` CLI; like other local adapters, run behavior depends on the host's Kimi version. The parser is written against the documented/verified 0.27.0 stream-json schema and degrades gracefully (malformed lines are skipped, failures surface as run errors). - `--skills-dir` overrides Kimi's auto-discovery of user and project skills for the run. This is intentional (paperclip-managed agents get a reproducible, isolated skill set), and it is only passed when at least one Paperclip skill is desired, so unconfigured agents keep default discovery. - Thinking effort is only forwarded to models that advertise `support_efforts` (currently `kimi-code/k3`); `EFFORT_CAPABLE_MODELS` must be extended when more Kimi models gain support, otherwise a configured effort is silently ignored for them. - `Dockerfile` now installs `@moonshot-ai/kimi-code@latest` globally alongside the other agent CLIs, so image size increases slightly. - Maintainer action needed for the npm bootstrap gate: the `policy` job's release-bootstrap step fails until the first npm publish of `@paperclipai/adapter-kimi-local` (the gate from #5146 that every new adapter package has passed through). Enrollment with `publishFromCi: true` is required by the manifest validator (dropping the entry, `false`, or `private` are all rejected), so this is intentionally left to a maintainer. Remaining CI lanes are expected to run once it is done. ## Model Used\n\n- **Current-master rebase, conflict adaptation, and registry-parity coverage:** OpenAI, **GPT-5 Codex** (Codex agent; exact serving model ID and context-window size were not exposed to the runtime), with repository, shell, Git, and GitHub tooling. It preserved Hawik’s commit authorship, reconciled ACPX and environment-capability changes, added current registry tests, and ran the verification above.\n- **Adapter implementation and initial review:** Moonshot AI, **Kimi K3 Coding** (latest), via **Kimi Code CLI v0.27.0** (`kimi-code/k3` alias, 1M-token context window, thinking mode, agentic tool use). The CLI agent explored the repo, wrote the adapter implementation (delegated to a coder sub-agent of the same model), ran tests, and drafted the first version of this PR body. A second model-driven review pass (read-only, same model) audited the diff for security/correctness before submission; its findings (shell-quoting hardening, auth-detection false positive, session-compaction registration, test gaps) were fixed and are included. - **Harness-context fixes and review responses:** Anthropic, **Claude Opus 4.8** (`claude-opus-4-8`) via Claude Code. Diagnosed from run logs that Kimi received only the entry instructions file (not the `HEARTBEAT.md`/`SOUL.md`/`TOOLS.md` bundle) and that `effort` was never wired, then implemented the instruction `--add-dir` delivery, `KIMI_MODEL_THINKING_EFFORT` forwarding, and `--skills-dir` skill delivery, added the accompanying tests and docs, and addressed the automated review comments (preserving external skills on remote sync, treating a signaled exit as a failure). Also extended the `paperclipai agent local-cli` installer to seed the control-plane skills into `~/.kimi-code/skills` for Codex/Claude parity, wired `onEvent` runtime events so the issue-thread activity indicator reflects Kimi's tool and reasoning output live, and built the ACP engine lane (`kimi acp` via the shared acpx engine, default) so the transcript streams with live tool status like the other ACP adapters. The Kimi CLI flags, subcommand, and env var relied on here were verified against the installed Kimi Code CLI 0.27.0. - All CLI behaviors claimed here (`-p`, `--output-format stream-json`, `-r` resume, event shapes, `--add-dir`, `--skills-dir`, `KIMI_MODEL_THINKING_EFFORT`) were verified empirically against the installed Kimi CLI, not assumed. ## 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 *(only the release-bootstrap step remains red, pending the maintainer npm publish described in Risks)* - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups *(will address all Greptile comments as they arrive)* - [x] I will address all Greptile and reviewer comments before requesting merge --- ## Maintainer Addendum (2026-08-20) The shared acpx-engine and issue-chat changes (run-summary segmentation, placeholder tool-event coalescing, `ISSUE_CHAT_TRANSCRIPT_MAX_VISIBLE_ENTRIES` 30 → 400, live-reasoning UI) have been **extracted to #11761** so the cross-adapter behavior changes review and revert independently — both commits there preserve @hawikk's authorship. This PR is now the kimi-specific adapter only (60 files, +3,793/−8, essentially pure addition); the only shared-engine touch left is the `kimi acp` command resolution. `publishFromCi` is `true` — the package name is bootstrapped on npm. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Dotta <bippadotta@protonmail.com> Co-authored-by: Devin Foley <devin@paperclip.ing> |
||
|
|
e826188e82 |
refactor(settings): unify settings and speed up exports (#11789)
## Thinking Path > - Paperclip is the open source app that people use to manage AI agents for work. > - Operators use the settings area to control a company and its Paperclip instance. > - The current navigation separates related settings and uses duplicate instance pages. > - Company exports also do independent reads in sequence and do extra work for previews. > - Hardened workspace commands can differ from their saved command after loopback binding. > - This pull request makes these related operator workflows consistent and faster. > - The benefit is one clear settings area, faster exports, and stable runtime command matching. ## Linked Issues or Issue Description Refs #338 Related: #9834 **What existing behavior does this improve?** This improves the company settings UI, company export preparation, and workspace runtime command matching. **Current behavior** Company and instance settings use separate navigation and duplicate pages. Export preparation reads many independent records in sequence. Preview generation can also build an unused organization image. A command with a forced loopback bind can fail to match its saved runtime command. **Proposed behavior** Use one settings navigation and put general instance controls on the company General page. Load independent export data with bounded concurrency, skip unused preview image work, and load the export page only when it is needed. Treat the loopback-bound form of a command as the same runtime command. **Reason and benefit** Operators get one clear settings area. Large company exports need fewer serialized reads. Export previews and initial UI loads do less work. Hardened runtime services remain linked to their saved command definitions. **Breaking changes** The obsolete instance General URL redirects to the unified settings page. Access and Heartbeats remain available, and legacy bookmarks keep their destinations. No API response shape or database schema changes. ## What Changed - Unified company and instance settings navigation and removed duplicate instance settings pages. - Embedded general instance controls in the company General page and kept access-sensitive navigation behavior. - Preserved instance Access and Heartbeats controls in the unified navigation and normalized old bookmarks to those destinations. - Improved environment and access-state handling when workspace seed requests overlap. - Added bounded export reads, a lighter preview path, deferred export preparation, and lazy export-page loading. - Matched loopback-bound runtime commands to their saved command definitions. - Added focused shared, server, and UI regression tests. ## Verification - `pnpm exec vitest run <18 changed test files>`: 18 files and 256 tests passed. - `pnpm check:token-gates`: passed all four token gates. - `pnpm -r typecheck`: passed for all workspace projects. - `pnpm build`: passed for all workspace projects. - `pnpm test:run`: tests ran without a reported failure, but the runner did not close after the server handoff tests. The process closed with status 0 after an interrupt. - Focused latest-head route tests: 2 files and 4 tests passed. - GitHub latest-head checks: all completed without failure. - Greptile: 5/5 with no unresolved review threads. ## Risks - Medium risk: settings routes and navigation changed across several operator roles. - Medium risk: bounded export concurrency increases simultaneous database reads. The limits stay below the normal pool size. - Low risk: runtime command matching accepts only the known Tailscale HTTPS loopback transformation. - No 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 with a GPT-5-family coding model. The runtime does not expose the exact deployed model ID or context-window size. Reasoning, tool use, and local code execution were enabled. ## 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) - [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details Exception: This task requires the existing execution branch. The harness does not permit a branch rename. - [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> |
||
|
|
faab2620ad |
feat(sandbox): add a duplex transport for Daytona behind a default-off kill switch (#11750)
## Thinking Path > - Paperclip is an open source app that manages AI agents for work > - Paperclip runs agents in local and remote sandbox environments > - A sandbox needs a bounded channel for commands and asynchronous input > - Daytona needs a real pseudo-terminal transport for this channel > - The sandbox gateway also needs a mode that handles channel loss safely > - This pull request adds the Daytona transport and gateway mode behind a default-off kill switch > - The benefit is a tested foundation for later transport selection ## Linked Issues or Issue Description **Subsystem affected** Cross-cutting (multiple of the above): sandbox providers, plugin SDK, server settings, and shared types. **Problem or motivation** The merged sandbox protocol has no runtime transport for Daytona. The generated sandbox gateway also has no duplex mode. A later transport-selection change needs both parts and a safe per-run gate. **Proposed solution** Add a Daytona `duplexCommandStream` transport over a raw pseudo-terminal. Add a generated gateway mode named `duplex_v1`. Add the `enableSandboxDuplexBridge` setting with a default value of `false`. Keep transport selection disabled until a later pull request. **Alternatives considered** Keep the protocol unused until the transport-selection change. This would delay provider tests and leave the gateway path without direct coverage. **Roadmap alignment** This change supports the completed Roadmap item for cloud and sandbox agents. It extends the merged sandbox channel foundation in pull request #11738. **Additional context** The Daytona provider remains an untrusted boundary. Deployments must use least-privilege provider credentials and provider-side quota controls. Operators must name an owner for duplex telemetry retention before rollout. ## What Changed - Add the Daytona `duplexCommandStream` capability over a raw pseudo-terminal. - Add a launch wrapper that disables echo and newline translation for NDJSON frames. - Close channels on lease release, destroy, resume of a stopped worker, and worker shutdown. - Declare the capability in the Daytona manifest and set `PLUGIN_VERSION` to `0.1.5`. - Add the worker-to-host notification sink at `ctx.duplexChannel.data` and `ctx.duplexChannel.exit`. - Add the generated sandbox gateway mode `PAPERCLIP_API_BRIDGE_MODE=duplex_v1`. - Add channel-loss results of `409 outcome_indeterminate` and `503 bridge_unavailable`. - Add the per-run setting `enableSandboxDuplexBridge`, with a default value of `false`. - Add unit tests, generated-source codec tests, lifecycle tests, and a credential-gated live Daytona test. ## Verification - Daytona suite: 185 tests pass. - Adapter utilities: 754 tests pass and 4 tests skip. - Plugin SDK: 62 tests pass. - Shared package: 28 tests pass. - Server duplex tests pass. - Shared, plugin SDK, server, and Daytona TypeScript checks pass. - The live Daytona test passes 3 cases when `DAYTONA_API_KEY` is set. - The live Daytona test skips 3 cases without `DAYTONA_API_KEY`. - CI must run the full workspace typecheck, test, and build gates after PR creation. ## Risks - The Daytona control plane and pseudo-terminal remain untrusted boundaries. - The duplex gateway changes behavior only when the mode and per-run setting enable it. - A lost channel fails requests without replay, so callers must handle indeterminate outcomes. - The transport-selection change must require both `duplexCommandStream === true` and `enableSandboxDuplexBridge === true`. - The provider credential and quota limits need operator control before rollout. ## Model Used OpenAI Codex, GPT-5, tool use and code execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [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> |
||
|
|
b8a76081ec |
feat(environments): expose boot-relevant drift attribution in the custom-image overview (#11751)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Paperclip lets operators prepare and use custom images for sandbox environments > - The custom-image overview detected drift but did not show which boot source changed > - Operators need the changed field and values to understand why a template no longer matches > - This pull request adds safe drift attribution to the overview API and the out-of-sync banner > - The benefit is faster diagnosis without exposing secrets or internal snapshot data ## Linked Issues or Issue Description **Subsystem affected** Cross-cutting (server and UI). **Problem or motivation** The custom-image overview reported drift without identifying the changed boot source. Operators had to inspect other data to find the cause. **Proposed solution** Return a classified drift summary with changed paths and their prior and current values. Show the boot-source field in the UI banner. Keep legacy templates and unclassified drift on the generic message. **Alternatives considered** The change does not expose the full snapshot or fingerprint. This keeps the overview contract small and avoids secret disclosure. **Roadmap alignment** The change supports the existing custom-image environment workflow and does not duplicate a roadmap item. ## What Changed - Add `activeTemplateDrift` to the custom-image overview response. - Classify drift as `boot_source_drift`, `knob_only`, or `unclassified`. - Return drifted paths with safe `from` and `to` values. - Show the changed boot-source field and values in the out-of-sync banner. - Keep legacy templates fail-closed and exclude secrets, fingerprints, and raw snapshots. - Add server and UI tests for the new behavior. ## Verification - `npx vitest run server/src/__tests__/environment-custom-images-service.test.ts` passes with 27 tests. - `npx vitest run ui/src/pages/CompanyEnvironments.test.tsx` passes with 27 tests. - `pnpm --filter @paperclipai/ui exec tsc --noEmit` passes. - Review the overview response and banner cases for boot-source, knob-only, and legacy drift. ## Risks The overview response gains one optional field. Legacy templates remain compatible because they return `unclassified` and keep the generic banner. The service excludes secret values, fingerprints, and raw snapshots. ## Model Used OpenAI Codex, GPT-5, tool use and code execution enabled. The model reviewed the handoff and managed the pull request. ## 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] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [ ] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
e0b64529b3 |
feat(auth): normalize agent login in the sandbox onto one session table and a capability contract (#11730)
## 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> |
||
|
|
e1df4c6068 |
fix(workspaces): keep deferred seed databases reliable (#11706)
## Thinking Path > - Paperclip manages agent work in isolated execution workspaces. > - A workspace depends on a valid database seed before it can run. > - Deferred seed failures were hidden behind a successful provision status. > - The seed restore also had two possible owners for the embedded PostgreSQL process. > - That allowed the target database to stop while the restore was still running. > - This pull request makes seed failures visible and gives the seed process sole lifecycle ownership. > - The benefit is that workspace provisioning reports the real result and does not stop its own target database. ## Linked Issues or Issue Description Related: #11684 **What happened?** Initial worktree provisioning could report success before its deferred database seed completed. The seed restore could also reuse a target embedded PostgreSQL process with another shutdown owner. This could stop the target database during the restore. **Expected behavior** Workspace status must show a failed deferred seed as a failure. The seed restore must own the target embedded PostgreSQL process until restore, migration, and validation finish. **Steps to reproduce** 1. Provision a worktree with deferred database seeding. 2. Make the seed manifest end in a failed state while the command exits with code 0. 3. Observe that the provision status remains successful on `master`. 4. Start a seed restore against an already-running target embedded PostgreSQL process. 5. Observe that another lifecycle owner can stop the target during restore. **Paperclip version or commit** `51a843e135` **Deployment mode** Local dev with execution workspaces and embedded PostgreSQL. ## What Changed - Add a first-class `workspace_seed` operation for deferred database seeds. - Require terminal, verified seed evidence before the seed operation succeeds. - Surface the seed phase and failure metadata in workspace status and UI state. - Give the seed process exclusive lifecycle ownership of the target embedded PostgreSQL process. - Suppress imported embedded-Postgres exit hooks without removing existing host listeners. - Record a credential-safe shutdown diagnostic in failed seed manifests. ## Verification - The original deferred-seed commit passed 4 server tests, 24 workspace-status UI tests, shared/server/UI typechecks, and the UI token gate. - The original PostgreSQL-lifecycle commit passed 3 lifecycle tests, 3 ownership/diagnostic tests, 1 real embedded-Postgres seed integration, and the affected package typechecks. - No local tests were rerun after the clean cherry-pick because the operator requested the shortest landing path. - Review the automatic PR checks for the clean `origin/master` replay. ## Risks - A live target database now causes an early error instead of being reused. The error includes recovery guidance. - Workspace consumers must handle the new `workspace_seed` operation type. Shared types and UI state handling are updated in this pull request. > 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, high-reasoning mode, with repository, shell, and GitHub tool use. The runtime does not expose a more specific deployment suffix or context-window value. ## 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> |
||
|
|
a2bf936f9a |
feat(workspaces): sign the workspace login handoff and gate readiness (#11671)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Managed worktree services run isolated Paperclip instances with cloned databases. > - A reachable service was reported as ready even when its database, runtime identity, or login path was not usable. > - The first candidate added verified database seeding and managed repair in #11665. > - This pull request consolidates that candidate with signed login handoff and a complete readiness contract. > - Post-QA fixes close five defects in repair identity, repair responses, UI retry, seed journal handling, and seed-source trust. > - The benefit is a workspace that either opens safely or reports one accurate recovery action. ## Linked Issues or Issue Description No public GitHub issue exists for this work, so the problem is described here. **What happened** Managed workspace URLs could return HTTP 200 and report ready while login failed. QA also found cases where repair used the wrong instance identity, returned a generic error, left the UI stuck, rejected a safe journal lag, or trusted a mutable workspace manifest. **Expected behavior** Opening a ready workspace signs the board user in to the correct isolated instance. Provisioning and repair use a registered source and report a structured recovery state. **Actual behavior** Entry depended on a password copied into the clone. Several failure paths could publish stale readiness, hide the repair precondition, or trust state that the workspace could modify. **Additional context** This pull request includes the commits first published in #11665. That pull request keeps the original base head for review history. This consolidated pull request is the merge candidate. Related open readiness work includes #11575 and #11621. ## What Changed - Adds a short-lived, signed, single-use login ticket. It binds the user, workspace, instance, and runtime origin. - Exchanges the ticket through Better Auth. It creates the session and cookie through the supported adapter path. - Adds protected workspace readiness fields for the database, clone data, login handoff, seed phase, and runtime identity. - Fails readiness closed when the guest has no company or execution-workspace binding. - Binds ticket issuance to the exact cloned user and active company membership selected for the handoff. - Verifies every current active board identity through the exact-user handoff before publication or reuse. - Gates managed runtime publication on the readiness contract and the recorded worktree instance identity. - Refreshes runtime work products from the live runtime row after a port change. - Adds one workspace access card with ready, degraded, repairing, and failed states. - Uses the runtime response identity for repair. It returns structured repair precondition errors. - Lets a valid source journal lag converge during provisioning. - Binds seed and repair manifests to a source registered outside the agent-writable worktree. - Clears recovered UI errors so a successful retry can open the workspace. - Makes runtime tests register canonical sources and avoid ports owned by live host listeners. - Keeps Vitest on source suites when compiled `dist` trees exist. - Isolates CLI and adapter tests from ambient AWS and runtime API environment variables. - Preserves a 404 response for cross-company workspace ID lookups before runtime authorization. - Makes concurrent single-flight coverage independent of path-canonicalization scheduling order. ## Verification The following checks passed on the integrated head: ```sh pnpm -r typecheck pnpm build pnpm check:token-gates pnpm --filter @paperclipai/db check:migrations ``` - The server source lane passed 420 files and 4,953 tests. Five tests were skipped. - The CLI lane passed 57 files and 385 tests. - The database lane passed 26 files and 97 tests. - The shared package passed 58 files and 506 tests. - The adapter utility lane passed 640 tests. Four tests were skipped. - The Claude adapter passed 220 tests. One test was skipped. - The Codex adapter passed 323 tests. - The OpenClaw adapter passed 13 tests. - The OpenCode adapter passed 42 tests. - The plugin SDK passed 45 tests. - The workspace runtime suite passed 124 tests. - The caller-scoped readiness and handoff suite passed 52 tests. - The workspace provisioning shell suite passed 7 tests. - The runtime exposure suite passed 17 tests while live host mappings occupied fixed test ports. - `git diff --check` passed and the worktree is clean. The serialized route lane will run in GitHub CI with its normal shards. No deployment or active-workspace migration was performed. ## Risks - This is a medium-risk authentication and runtime-readiness change. - The login ticket uses exact origin, workspace, instance, and user binding. It has a short expiry and a one-time nonce. - Runtime publication is stricter. A real readiness, identity, per-user handoff, or control-plane database disagreement now blocks publication. - This pull request supersedes #11665 as the merge candidate. Close #11665 after this pull request merges. - No new database migration is included. The lockfile and workflow files are unchanged. - Deployment and active-workspace migration are intentionally outside this pull request. > 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 Claude Opus 5 (`claude-opus-5[1m]`), 1M context, extended thinking, tool use, and code execution produced the main candidate. OpenAI GPT-5 (`gpt-5`) through Codex, with agentic reasoning, tool use, and code execution, integrated the post-QA fixes and hardened the test gates. The Codex context-window size was not exposed. ## 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> |
||
|
|
d1cd9c37f4 |
fix(ui): keep Archive available on every inbox item (#11636)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The Mine inbox combines tasks, failed runs, approvals, and access requests > - Each item needs a consistent action that removes it from the inbox > - Task rows keep the Archive action separate from the unread marker > - Other rows used one slot for both actions, so an unread marker hid Archive > - This pull request gives every Mine inbox row the same Archive action > - The benefit is consistent hover and swipe cleanup for every inbox item type ## Linked Issues or Issue Description **What happened?** Unread failed runs, approvals, and join requests showed a blue unread marker but no Archive button. The user had to mark the item as read before the old leading Archive control became available. **Expected behavior** Every Mine inbox item shows Archive on hover. Every item also keeps its swipe-to-archive behavior. **Steps to reproduce** 1. Open the Mine inbox. 2. Add an unread failed run, approval, or join request. 3. Hover the new item. 4. Observe that Archive is missing before this change. **Paperclip version or commit** Current `master` at `0e9b03832d`. **Deployment mode** Local development from source. **Additional context** PR #1860 improves the accessibility of the existing swipe gesture. PR #9704 changes server-side archive resurfacing rules. Neither PR adds the missing hover action to unread non-task rows. ## What Changed - Reused the task-row Archive action for failed runs, approvals, and join requests. - Kept the unread marker visible in its own leading slot. - Kept retry, approve, and reject actions beside the new trailing Archive action. - Added regression coverage for hover Archive and swipe wrappers on every non-task Mine row type. - Updated a test comment so the repository token gate does not treat documented color literals as UI source. ## Verification - `pnpm --filter @paperclipai/ui exec vitest run src/pages/Inbox.test.tsx src/components/IssueRow.test.tsx src/components/SwipeToArchive.test.tsx` — 46 tests passed. - `pnpm --filter @paperclipai/ui typecheck` — passed. - `pnpm check:token-gates` — passed. - `pnpm -r typecheck` — passed. - `pnpm test:run` — 4,284 tests passed. Ten unrelated runtime-exposure tests cannot claim fixed ports 42000 and 52000 because host Tailscale listeners already own them. - `pnpm build` — passed. - GitHub CI on the latest head — all required checks passed. - Greptile — 5/5 confidence with no open review threads. ## Risks - Low risk. The change only affects Mine inbox row actions and regression tests. - The server archive and dismiss APIs do not change. - Retry, approve, and reject behavior does not change. - No documentation change is required because this fix restores the expected inbox behavior. > 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 based on GPT-5. The runtime did not expose a dated model ID or context-window size. Reasoning, terminal tool use, code execution, and GitHub integration were enabled. ## 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> |
||
|
|
120ae5428f |
feat(server): add a one-click relink action for detached custom-image templates (#11641)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Paperclip environments can use captured custom images for agent runs > - A configuration fingerprint change can detach a valid custom-image template > - Operators need a safe way to confirm that the image still matches the boot source > - This pull request adds a guarded relink action with drift classification and audit logging > - The benefit is a deliberate relink without a new sandbox boot or provider snapshot ## Linked Issues or Issue Description **Subsystem affected** Cross-cutting environment, server, and UI behavior. **Problem or motivation** A custom-image template detaches when the environment configuration fingerprint changes. The runtime then uses the base image, even when the boot source did not change. The only prior remedy required a full re-capture. **Proposed solution** Add an operator-triggered relink action. Classify configuration drift from a server-owned boot-relevant snapshot. Relink knob-only drift without confirmation. Require explicit confirmation for boot-source or unclassified drift. Guard the route for instance administrators and record a safe activity event. **Alternatives considered** Keep requiring a full re-capture. This adds a sandbox boot and provider snapshot for cases where the image remains correct. **Roadmap alignment** The roadmap has no matching custom-image relink item. This change addresses an environment operation gap. **Additional context** The relink response exposes raw drift values only in the transient 409 response to the instance administrator. The service never persists or logs fingerprints or configuration values. Reserved identity-path segments fail closed. ## What Changed - Add `relinkActiveTemplate` with drift classification and conditional fingerprint update. - Persist a server-owned boot-relevant configuration snapshot during capture. - Add the guarded relink route with strict request validation and activity logging. - Add the relink action and confirmation flow to the environment page. - Add service, route, UI, and OpenAPI coverage. ## Verification - Run the focused service suite: `pnpm vitest run server/src/services/environment-custom-images-service.test.ts`. - Run the focused route suite: `pnpm vitest run server/src/routes/environment-custom-image-routes.test.ts`. - Run the focused UI suite: `pnpm vitest run ui/src/pages/CompanyEnvironments.test.tsx`. - Run server and UI TypeScript checks. - Confirm the OpenAPI snapshot matches the new route. - Confirm all required GitHub checks pass on commit `e46fdcfe94a719be854adf8849d30714e5b70b93`. - Confirm Greptile reports 5/5 with no unresolved review threads. ## Risks The relink action can keep an image after configuration drift. The service requires explicit confirmation for boot-source or unclassified drift. Reserved path segments produce a safe unresolved marker and never enter stored values. ## Model Used OpenAI GPT-5 Codex. The model used repository inspection, GitHub operations, and PR preparation with tool use and code execution. The runtime did not expose a context-window value or a separate reasoning-mode value. ## 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> |
||
|
|
c1c46f1e4e |
feat: Claude login on the new-agent page before agent creation (#11347)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The Claude local adapter supports subscription login through a sandbox > - The new-agent page must show login before the user creates an agent > - Test results must not expose raw sandbox diagnostics or secret values > - This pull request adds the login UI to both Test lanes and closes the diagnostic boundary > - The branch also adds durable cleanup recovery for failed sandbox teardown > - Reusable sandboxes must retain both their recorded teardown configuration and a valid lifecycle path until destruction succeeds > - The benefit is a usable login flow with fixed public checks, redacted server logs, and recoverable sandbox cleanup ## Linked Issues or Issue Description Related public work: [#9488](https://github.com/paperclipai/paperclip/pull/9488) adds first-class recognition for `CLAUDE_CODE_OAUTH_TOKEN` in headless and remote runs. Related public issue: [#2681](https://github.com/paperclipai/paperclip/issues/2681) requests Claude Code subscription support. This pull request adds the login transport and new-agent UI flow that those changes do not provide. **Subsystem affected:** Claude local adapter, server login probes, sandbox provider setup, cleanup recovery, and the new-agent UI. **Problem or motivation:** The Test lanes did not show the sandbox login panel in all supported cases. Test results also exposed raw probe diagnostics, and JSON escapes could end secret redaction early. **Proposed solution:** Surface the login capability through the bundled provider manifest. Prepare the same probe runtime in the ACP lane. Send diagnostics only to redacted server logs. Keep Test checks on fixed public messages. Normalize login URL hints to allowlisted HTTPS Claude and Anthropic hosts. Consume JSON escapes during redaction. Preserve failed sandbox cleanup state across retries and restarts, and prevent deletion from severing the lifecycle context of a live reusable sandbox. **Alternatives considered:** Keep raw diagnostics in Test checks or trust login URL text from the sandbox. Both choices increase information exposure. Keep separate probe behavior in the ACP lane. That choice would leave the two Test lanes inconsistent. ## What Changed - Surface the sandbox login panel on both Test lanes. - Reconcile the bundled Daytona plugin manifest so `supportsSetupTokenLogin` reaches the UI capability gate. - Prepare the ACP Test lane with the same probe runtime as the CLI Test lane. - Add the `claude_acp_login_probe_unavailable` warning when the ACP probe cannot run. - Send raw sandbox diagnostics only to redacted server logs. - Keep Test checks on fixed public messages in the ACP, managed-config, and CLI paths. - Normalize login URL hints to allowlisted HTTPS Claude and Anthropic hosts. - Redact JSON and escaped-JSON secret values, including escaped quotes and backslashes. - Preserve orphan cleanup records across provider failures, restarts, and unavailable plugins. - Atomically block environment deletion while a live reusable sandbox lease still depends on it. - Verify pending cleanup destroys plugin sandboxes with the provider configuration recorded on the lease, even after the current environment configuration changes. ## Verification - Head under review: `506b7fa2d83c36bfa5fd722ee9d95b0c7431c241`. - Focused environment route/service/runtime coverage passes: 196 tests across 3 files. - `pnpm -r typecheck` passes. - `pnpm build` passes. - The full Vitest run completed with 4,754 passing and 28 failing tests. All 23 source-test failures reproduce unchanged on parent head `58cfe61a33191ce03d965d65085d26064b4888ba`; the other 5 are duplicate executions from stale `server/dist` output. The failures are unrelated macOS path/listener and scheduler-fixture failures, so there is no new bad commit for bisect to localize. - All required CI checks pass for the current head, including build, typecheck/release registry, all server and workspace shards, serialized server suites, canary, and e2e. - A fresh Greptile review for `506b7fa2d83c36bfa5fd722ee9d95b0c7431c241` reports 5/5, “safe to merge,” with no blocking failure remaining. ## Risks - A probe or redaction change could hide useful server diagnostics. - An allowlist change could reject a valid Claude login URL. - Cleanup recovery changes could affect provider teardown ordering. - An environment with a live reusable sandbox can no longer be deleted until the owning issue or execution workspace completes teardown. - The implementation keeps public Test messages fixed and sends detail to redacted server logs. ## Model Used OpenAI GPT-5 via Codex — exact model ID: GPT-5; tool use and code execution enabled; extended reasoning enabled. The implementation author used AI-assisted development. ## 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 documented the result - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation or confirmed no separate documentation change is needed - [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> |
||
|
|
d2fb05d225 | Fix issue document deep-link routing (#11551) | ||
|
|
55464204a6 |
test(ui): wait for conditions in the last four fixed-turn loops (#11523)
The remaining instances of the pattern #11499 and #11521 replaced in the routing tests, found by grepping `attempt < N` across the suite. Budgets of 20, 25 and 30 turns rather than 3 and 5, which is why they surfaced far less often - SkillStudio was among the failures seen while verifying the earlier PRs. Three of the four were reimplementations of `vi.waitFor` down to rethrowing the last error, differing only in bounding on turns rather than on time. The fourth was the text variant. Two `flushReact` helpers became dead with the loops that used them and are removed. Verified on the mechanism, since budgets this large pass until the machine is loaded and so prove nothing by passing: a throwaway probe drove a 30-turn loop and `vi.waitFor` against a value landing at turn 60. The loop throws, `vi.waitFor` reaches it. Not a lateral move between arbitrary bounds. This closes one spelling of the pattern, not the class, and the sweep that found these was too narrow. Two other shapes do the same thing and a grep for `attempt < N` cannot see either: a fixed-cycle helper, `flushReact(cycles = 4)` in AgentToolsTab.test.tsx, and fixed-duration sleeps in AgentToolsTab, CompanyContext, AgentConfigForm.render, Artifacts, Search and ImportFromVaultDialog. `AgentToolsTab > autosaves installed apps for the current agent` failed one of three full-suite runs here, holds both shapes, and is untouched by this change. Refs #11484. ui typecheck clean; the four files pass; full suite passes two of three runs, the third failing only on that pre-existing instance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6d0adbfb5d |
refactor(ui): retire the shared-cache reasoning the account key made obsolete (#11507)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Three places in the UI turn the company list into an authorization verdict: the invite landing page, the onboarding draft gate, and company auto-selection > - Each grew a defense when one `["companies"]` cache entry answered for every account, and each documented that hazard at length > - #11488 keyed the entry by account, so the hazard those comments describe can no longer happen > - The comments stayed, and a comment that describes a trap that no longer exists is how the next reader removes a mechanism that is still holding something up > - This pull request replaces that reasoning with what the mechanisms actually do now, and removes the one condition that genuinely went dead > - The benefit is that the next person to simplify these gates has accurate reasons to work from ## Linked Issues or Issue Description No public issue exists. Follow-up to #11488, #11430 and #11417. The problem follows. **What happened?** The three gates were written against a shared, account-less company cache. #11488 keyed that entry by account, which made the documented hazard impossible — but the documentation stayed. Each gate now carries a long explanation of a cross-account leak that the key prevents, while the mechanism it explains is in fact still required for a different and unrelated reason. That is a maintenance hazard in a specific direction: a reader who checks the comment against the code concludes the mechanism is obsolete, removes it, and reintroduces a failure the comment never mentioned. **Expected behavior** The reasoning next to each gate describes why the gate is there now. **Steps to reproduce** Read the comment above `ownershipDecidable` in `OnboardingWizard.tsx` against `master`. It justifies `isSuccess` on the grounds that "after an account switch the retained value is the previous account's list", which the account-keyed entry makes impossible. **Paperclip version or commit** `master` at `0817fbad9`. ## What Changed - `ui/src/pages/InviteLanding.tsx` — dropped the `Boolean(sessionQuery.data)` conjunct from `membershipListIsCurrent`; rewrote the comment. - `ui/src/components/OnboardingWizard.tsx` — replaced the shared-cache explanation above the ownership gate with the reason the gate still exists. - `ui/src/hooks/useSignOut.ts` — corrected the sweep's rationale, which cited the company list as its example of data the next account could read. ### The one dead condition `membershipListIsCurrent` tested `Boolean(sessionQuery.data) && companiesQuery.isFetchedAfterMount`. The first term cannot be false when the second is true: the query is `enabled` only while a session exists, so the flag cannot be set without one. The lapsed-session case it looked like it covered is covered by the keying instead — the observer re-keys to the anonymous entry and holds no data to leak. Tests pass with it removed, but that only shows no test distinguishes it, which is why the reasoning above is recorded in the code rather than left for the next reader to redo. ### What is deliberately kept Each gate turned out to be load-bearing for a reason that has nothing to do with accounts: - **InviteLanding** still waits for a list fetched this mount. A pending query reads as an empty list, which reads as "not a member", which auto-accepts an invite the customer may already hold. - **OnboardingWizard** still forces a fetch with `staleTime: 0`. A cached list is the right account's but can be thirty seconds old, so a company created moments ago in another tab is missing from it — and missing reads as "you do not own this", which *deletes* the draft rather than withholding it. - **CompanyProvider** still clears the live selection on an account change. That is component state and does not change key with the query. Removing them as redundant is the mistake the stale comments invited; this change is what makes that argument harder to make by accident. ## Verification - `pnpm tsc -b` in `ui`: clean. - `InviteLanding.test.tsx`, `OnboardingWizard.test.tsx`, `CompanyContext.test.tsx`, `useSignOut.test.tsx`, `companies-query.test.ts`: **67 passed**, run twice. No behaviour change is claimed and none is intended: the only non-comment edit is the removal of a condition that cannot alter the expression's value. **Not done:** no browser run. Nothing here is observable at runtime. ## Risks Low. Comments, plus one condition shown to be unreachable-false. The risk that remains is a documentation risk in the other direction: if the keying is ever reverted or bypassed, these comments will understate what the gates protect against. They name #11488 so that connection is findable. **This does not close the class.** Account-scoped entries other than the list — `["companies", id]`, stats, and the rest — still survive an account change that skips the sign-out button. That is unclaimed work, and larger than this. ## Model Used Claude Opus 5 (`claude-opus-5`), through Claude Code. Extended thinking enabled. Tool use enabled: file read and edit, shell for typecheck and test runs. ## 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 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
40e7add71c |
test(ui): make the suite independent of the machine timezone (#11478)
Several suites asserted local-time renders from UTC instants, or built date fixtures from the real clock, so they only held where local time happened to match. CI runs in UTC and never reported it; a contributor anywhere else saw failures on a clean checkout. Seven files, anchored to the clock under test rather than to the machine's. The set grew twice while being fixed: the two tests the issue named surfaced three more at UTC+9, and those surfaced two more at UTC+14. `StatusCards/format` is the interesting one. `rollupUpdatesToday` filters on the UTC calendar day to match the server token cap, while the fixtures were built on the local day. West of UTC that lands `iso(0)` in the previous UTC day for the stretch between UTC midnight and local midnight - about seven hours a day at UTC-7 - and east of UTC+12 "today at local noon" is already yesterday in UTC outright. Either way the rows it means to count drop out. A run crossing midnight UTC splits the same way. Fixes #11476. Deliberately left: IssueProperties.test.tsx:1515-1517 still pin the minute of three timestamps against a UTC fixture. They pass at every offset tried, including UTC+5:45, and the minute there is load-bearing - it distinguishes Created from Started from Completed - so it wants more care than mechanical anchoring. Full ui suite 4113 pass. The TZ pin added by #11480 is still in place here and is now redundant; #11508 removes it, stacked on this branch so it cannot land without the anchoring it depends on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0817fbad92 |
fix(ui): scope invite membership checks to the signed-in account (#11417)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Which companies a person belongs to is an authorization fact the server owns, and the UI caches the answer under a single `["companies"]` key > - That cache entry carries no account identity, and `main.tsx` sets `staleTime: 30_000` for every query, so for thirty seconds after a sign-in the previous account's list is served with no request at all > - The invite landing page reads that list to decide whether the person is already a member of the inviting company > - A list that arrives with no loading state and no error therefore looks authoritative while describing somebody else > - This pull request makes the page trust only a list it fetched itself, for the account signed in now > - The benefit is that a membership decision stops depending on cache freshness, which nothing in the app guarantees ## Linked Issues or Issue Description No public issue exists. Refs #11380, #11382. The problem follows. **What happened?** `InviteLanding` read the shared `["companies"]` cache entry as proof of membership in two places: - The post-sign-in redirect called `fetchQuery(companiesListQueryOptions)`, which returns the cached entry without a request while it is inside the app-wide `staleTime`. - An effect cleared the pending invite token whenever the cached list contained the invited company. Neither checked that the list belonged to the account signed in now. A second account signing in on a warm tab, or a session that lapses server-side, is enough to reach both. **Expected behavior** The page decides membership from a company list fetched for the current session. **Steps to reproduce** 1. On a self-hosted instance in `authenticated` mode, sign in as account A, which belongs to company X. 2. Within thirty seconds, open an invite link for company X and sign in as account B, which does not belong to it. 3. The page reads A's cached list, finds company X, and treats B as already a member. **Paperclip version or commit** `master` at `2a4b4bc63`. ## What Changed - `ui/src/pages/InviteLanding.tsx` — the membership query sets `staleTime: 0` so it revalidates on mount, and the verdict is withheld until that fetch lands, keyed on `isFetchedAfterMount`. The token-clearing effect and the "already a member" branch both read through that gate. - `ui/src/pages/InviteLanding.tsx` — the post-sign-in path cancels anything still in flight for the previous session, then forces a fetch for the new one with `staleTime: 0`. - `ui/src/pages/Auth.tsx` — sign-in resets the companies query instead of invalidating it. Invalidation leaves the previous account's list readable, and its fetch running, until the refetch returns. - `ui/src/pages/InviteLanding.test.tsx` — coverage for the warm-cache case, the token-clearing effect, and the `local_trusted` exemption. ### `local_trusted` is exempt Those instances have no accounts, so the shared list is the only identity there is. `membershipIsAccountScoped` is false there and the gate stays open. ### Rebased onto the account-keyed cache #11488 landed while this was open and keys the company list by account, so the page can no longer reach another account's list at all. Two things changed here as a result: - The post-sign-in read now calls `fetchCompanyListForCurrentAccount`, which replaces the `cancelQueries` plus forced `fetchQuery` this PR originally carried. The helper is strictly stronger: it detaches the in-flight `/companies` request inside the query function, and it resolves the account identity past the session invalidation immediately above rather than trusting the session entry still in the cache. - The observer reads through `useCompanyListQuery`. The mount-scoped `isFetchedAfterMount` gate is **kept**, not removed. Its purpose has narrowed — cross-account leakage is now structurally impossible, so what remains is holding the verdict until this page has a list rather than acting on a pending one. It is still load-bearing: disabling it fails two tests here. Removing a defense in the same change that rebases onto a new foundation is the wrong order; that is a follow-up once the keying has proven itself. `Auth.tsx` can safely reset, because it navigates away on success and `InviteLanding` mounts fresh afterward. Measurements of exactly when that rewind does and does not bite are in [#11380](https://github.com/paperclipai/paperclip/pull/11380#issuecomment-5300984911). ## Verification - `InviteLanding.test.tsx`, `Auth.test.tsx`, `companies-query.test.ts`, `CompanyContext.test.tsx` together: **51 passed**, run twice. - `pnpm tsc -b`: clean. - `InviteLanding.test.tsx` and `Auth.test.tsx` together: 21 passed. Both failures are pre-existing and unrelated. Each reproduces on a tree that does not contain this change, in files this change does not touch: | Failure | Why it fails | | --- | --- | | `IssueProperties.test.tsx` | Timezone-dependent: expects `4:08 PM`, gets `9:08 AM` | | `StatusCards/format.test.ts` | Time-of-day dependent: "only counts updates started today" breaks near midnight | **Not done:** no manual two-account run in a browser. The path needs two accounts on an `authenticated` instance, which a local dev instance cannot exercise. ## Risks Low. The failure direction is a membership verdict withheld for one extra round trip, which resolves itself; the direction it removes is one account's membership granted to another, which does not. It adds one request per invite-page mount, on a query key the app already uses. **This does not close the class.** The shared list is still unscoped for every other consumer. #11380 clears it on sign-out and #11382 handles the onboarding draft gate; all three are needed, because an account can change without passing through any one of those paths. ## Model Used Claude Opus 5 (`claude-opus-5`), through Claude Code. Extended thinking enabled. Tool use enabled: file read and edit, shell for typecheck and test runs. ## 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 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
327f59cac2 |
refactor(ui): key the company list by account (#11488)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Which companies a person belongs to is an authorization fact the server owns, and the UI caches the answer for speed > - It cached that answer under one `["companies"]` key with no account attached, while `main.tsx` sets `staleTime: 30_000` for every query > - So for thirty seconds after an account change, one person's list answered questions asked about another, arriving with no loading state and no error > - Three separate consumers each grew their own defense against this, and each was a place to forget one > - This pull request keys the entry by account, so a list belonging to someone else is not distrusted but unreachable > - The benefit is that the protection stops depending on every future consumer remembering to defend itself ## Linked Issues or Issue Description No public issue exists. Refs #11380, #11382, #11417, #11430. The problem follows. **What happened?** The company list lived in a single cache entry, `["companies"]`, carrying no record of which account it was fetched for. Combined with the app-wide 30s `staleTime`, any read within that window after an account change returned the previous account's list — from cache, with no request, no loading state and no error. Every consumer that treats the list as an authorization fact had to know this and defend itself: - `InviteLanding` reads it to decide whether you already belong to the inviting company (#11417). - `OnboardingWizard` reads it to decide whether a saved draft belongs to you (#11382, merged). - `CompanyProvider` reads it to pick and persist your active company (#11430). All three defenses are correct. The problem is structural: the fourth consumer has to invent a fourth one. **Expected behavior** A cached company list can only answer questions about the account it was fetched for. **Steps to reproduce** 1. On a self-hosted instance in `authenticated` mode, sign in as account A, which belongs to company X. 2. Within thirty seconds, have account B become the session in that tab — a second tab signing in, or A's session lapsing server-side. 3. Any consumer reading the company list receives A's list, and nothing in the query result indicates it is not B's. **Paperclip version or commit** `master` at `ac91b7f3b`, which includes #11430. ## What Changed - `ui/src/lib/queryKeys.ts` — `companies.list(userId)` replaces `companies.all` as the list's entry. `companies.all` remains the prefix, so it still matches for invalidation. - `ui/src/api/companies-query.ts` — `companyListQueryOptions(userId)` builds the keyed options; `useCompanyListQuery()` is the only observer entry point and holds until the session settles, because the key cannot be built before then; `fetchCompanyListForCurrentAccount(queryClient)` covers imperative paths; `useAccountIdentity()` exposes the session identity the key is built from. - `ui/src/api/companies-query.ts` — the `/companies` detach moved into the query function. - `ui/src/context/CompanyContext.tsx` — drops the session-watching refetch machinery the key now makes unnecessary (`removeQueries`, the explicit replacement fetch, the awaiting gate). It still clears the live selection on an account change, because that is component state and does not change key with the query. - `ui/src/pages/InviteLanding.tsx`, `ui/src/components/OnboardingWizard.tsx` — read through the account-aware API. - Tests — the account-keyed guarantee, prefix invalidation still reaching the list, the detach inside the query function, and updates where suites seeded the old shared key. ### The existing defenses are deliberately left in place The per-consumer gates in #11382, #11417 and #11430 are now belt and braces. They are also what will catch this refactor if it is wrong somewhere, so removing them in the same change that moves the foundation would be the wrong order. Simplifying them is a follow-up, once this has proven itself. ### Why `retry: 1` appears in CompanyProvider An earlier measurement on #11430 found a retry on the replacement fetch changed no outcome, because `removeQueries` made the observer rebind and issue a second request for free. Keying by account removes that mechanism and the free attempt with it. The retry now carries the property the incidental refetch used to — a single blip during an account change should not leave the customer with no companies until they find "Try again". #11430's test for that property is unchanged and still passes, which is how the gap was caught. ### A regression this went through, kept for the record Gating the query on the session settling meant that while the account was unknown the query was *disabled*, and a disabled query reports `isLoading: false` with no data — which the provider defaults to an empty list and reads as "asked, and owns nothing". That is the destructive branch #11477 had just fixed, reached through a different door: it would have cleared the customer's stored company on every cold boot. #11477's test caught it during the rebase. `useCompanyListQuery` now reports the wait for the account as part of the wait for the list. ### What this does not do It does not scope the rest of the per-account cache. `["companies", id]`, stats, and every other account-scoped entry still survive an account change; that is the cache-lifetime work in #11380. ## Verification - `pnpm vitest run` in `ui`: **4018 passed, 1 failed**. - `pnpm tsc -b` in `ui`: clean. - `companies-query.test.ts`: 6 passed. `CompanyContext.test.tsx`: 17 passed. `OnboardingWizard.test.tsx`: 13 passed. `InviteLanding.test.tsx`: 13 passed. The failure is the pre-existing timezone-dependent `IssueProperties.test.tsx`, fixed by #11478. Two behaviours are asserted rather than assumed, because the refactor is only safe if they hold: that invalidating the `companies` prefix still marks the account-keyed list stale (19 call sites depend on it), and that the query function detaches the in-flight `/companies` request before fetching. **Not done:** no manual two-account run in a browser. The path needs two accounts on an `authenticated` instance, which a local dev instance cannot exercise. ## Risks Moderate, and worth reading before approving. **It touches `InviteLanding.tsx`, which #11417 also modifies**, so one of the two will need a rebase — the conflict is mechanical (both change how the same query is read). This was #11481, which GitHub closed automatically when its base branch (#11430's) was deleted on merge; reopening a pull request whose base branch is gone is not permitted, so it continues here against `master` with the same head and the same review already recorded on #11481. **The list now waits for the session query.** The key cannot be built before the account is known. In the app the session is already fetched at boot by many components, so this is a dependency rather than an extra request, but it does serialize: on a cold boot the list waits for the session to land. Every test that renders a company-list consumer now needs a session in the cache, which is why several suites gained a seed. **A missing mock surfaces as a passing gate rather than an error.** The detach inside the query function meant suites whose `companiesApi` mock lacked `detachInflightList` had their query function throw, which read as "decided" in the onboarding gate and mounted the wizard early. Fixed in the affected suites; worth knowing as a failure mode. ## Model Used Claude Opus 5 (`claude-opus-5`), through Claude Code. Extended thinking enabled. Tool use enabled: file read and edit, shell for typecheck and test runs. ## 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 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
10d0555189 |
fix(interactions): authorize resolvers consistently (#11376)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Issue interactions give agents and people a structured decision record. > - Resolver routes used different authorization rules. > - Some routes blocked valid agents, including task watchdogs with normal issue access. > - The API did not show who could resolve a pending interaction. > - This pull request gives every interaction kind one resolver policy evaluator. > - The benefit is a clear decision path with consistent governance and company isolation. ## Linked Issues or Issue Description Fixes: #8087 Refs: #7403 Related PR: #11082 proposes board-only confirmation rules. This change keeps human-only review as an explicit policy. **What happened?** Agents could create issue interactions. Some resolver routes still required board access. This left valid agent confirmations pending. Task watchdogs could see the same problem without board identity. **Expected behavior** Every interaction kind must use one resolver policy contract. The contract must support `anyone`, `not_creator`, and `human_only`. It must also apply all normal governance controls. **Steps to reproduce** 1. Create a `request_confirmation` interaction as an agent. 2. Resolve it with another authorized agent. 3. Observe the board-only denial. **Paperclip version or commit** The problem exists on `master` before this change. **Deployment mode** Local development with `pnpm dev`. ## What Changed - Add canonical policies for `anyone`, `not_creator`, and `human_only`. - Use one server evaluator for every interaction kind. - Apply named addressees, company limits, review rules, and task watchdog scope. - Charge cross-issue resolutions to the existing per-run action limit. - Return the effective resolver audience in attention and interaction data. - Show the audience, governance choices, and denial reasons in the board UI. - Add telemetry, API documents, product documents, and regression fixtures. - Add migration provenance for safe legacy behavior. - Make migration `0218` safe for complete replays and partial prior runs. ## Product Rules - An interaction records a response. It does not grant authority for the next action. - `anyone` lets any authorized issue participant respond. - `not_creator` requires a responder other than the interaction creator. - `human_only` requires an authorized person. - A named addressee, company policy, or governed action can narrow the audience. - These controls cannot widen the audience. - A task watchdog uses the same rules as an ordinary agent. - A task watchdog does not receive board authority. - An agent resolution on another issue uses the shared cross-issue action limit. - Legacy pending interactions keep their earlier restrictions. - The UI shows the effective audience and a permanent denial reason. ## Verification - `pnpm --filter @paperclipai/db check:migrations` - `pnpm --filter @paperclipai/db typecheck` - `pnpm exec vitest run packages/db/src/issue-thread-interaction-resolver-policy-migration.test.ts` - The focused PostgreSQL test applies migration `0218` twice. - The test also completes a partial prior run and preserves existing provenance. - The latest GitHub head has 29 successful checks. - The opt-in Storybook visual check skipped as expected. - Greptile reports 5/5 with no open comments. ## Risks - New interaction writes use `anyone` by default. - Callers must select `not_creator` or `human_only` when they need stricter review. - Legacy pending interactions keep the old creator and human restrictions. - Migration `0218` fills only missing provenance fields during recovery. - Cross-issue resolutions can reach the existing action limit. - The shared evaluator affects every interaction kind. - Route, service, database, shared contract, and UI tests cover these rules. > This work matches the Agent Reviews and Approvals direction in `ROADMAP.md`. It does not duplicate a planned item. ## Model Used OpenAI Codex, GPT-5. The runtime does not expose the exact deployment ID or context window. The agent used reasoning, repository tools, shell commands, and test execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either linked public issues or described the issue with the required labels - [x] I have not referenced internal Paperclip issues or links - [x] My branch name describes the change and contains no internal ticket id - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation - [x] I have considered and documented the risks - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open comments - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
add65ba4a2 |
test(ui): make the suite pass outside UTC (#11480)
The ui suite was green in CI and red on a clean checkout in any other
timezone. GitHub's runners default to UTC, so nothing ever reported it. A
contributor elsewhere sees two failures on their first run, which reads as
"this project is broken" rather than "your clock differs from the runner's".
Two independent causes.
`IssueProperties` supplies a UTC instant and asserts on the local-time string
the UI renders from it - "2026-07-17T16:08:00.000Z" is expected to read
"Today, 4:08 PM". That holds only where local time is UTC. Pinned with
`env: { TZ: "UTC" }` rather than rewritten: those assertions are about what a
person sees, and "4:08 PM" is worth more to a reader than an expectation
computed from the same formatter the component uses, which would pass whatever
that formatter did.
`StatusCards/format` was wrong in two ways at once, and the pin hides only one,
so it is fixed directly. `rollupUpdatesToday` filters on the *UTC* day
boundary, while the test built fixtures from local noon on the real clock.
East of UTC+12, "today at local noon" is already yesterday in UTC and the rows
the test means to count are filtered out; and any run crossing midnight UTC
lands `iso(0)` and the function's default `now` on different days. The
fixtures now come from a fixed instant, passed as `now` - the parameter exists
for this, and the sibling test already used it.
Each fix was confirmed load-bearing by removing it under TZ=Pacific/Auckland.
Without the pin, IssueProperties fails; without the fixed instant, StatusCards
fails even with the pin removed, so neither rides on the other.
Full ui suite 4017 pass, 0 fail, in UTC, Pacific/Auckland and Asia/Kolkata.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
38752c4e5e |
fix(ui): clear account-scoped query caches on sign-out (#11380)
Sign-out invalidated two query keys - the auth session and health - and left everything else in the cache. `invalidateQueries` also keeps serving the old data while it refetches, so it was insufficient even for the two it did touch. The company list was never touched at all, which is how one account's companies could still be in hand when the next account signed in. The self-hosted path now resets every account-scoped entry. Scoping is an allowlist of instance-scoped roots rather than a list of things to clear, so a query key added later is account-scoped unless someone deliberately says otherwise - forgetting this file fails closed. `health` is the only exemption. It carries deployment mode, bootstrap state and Cloud metadata, nothing account-scoped, and `useCloudInstance` observes it with `enabled: false` and leaves the fetch to CloudAccessGate. Dropping the entry would strand every such observer on `null` until the gate happened to refetch, flipping Cloud instances into their self-hosted rendering mid sign-out. It is refreshed in place instead. `resetQueries` rather than `removeQueries`: removal empties the cache without notifying the observers already subscribed, so a mounted `useQuery` keeps returning its last result until an unrelated re-render rebuilds it. CompanyProvider sits above the router and stays mounted across the whole sign-out and sign-in cycle, so that is the common case here rather than a corner one. Reset notifies them, so the old data leaves the cache and everything reading it. The cloud path is untouched: it is a top-level navigation, and the document reload builds a new QueryClient with nothing left to clear. This is the root cause behind the onboarding draft-ownership gate added in #11382. That gate stays, and its comment now says why: this fix covers the sign-out button, not the question. An account can change without it - a session lapsing server-side, a second account signing in on a warm tab, a caller supplying the company context from somewhere else - so the gate stays independent rather than deferring to this. ui typecheck clean; full ui suite 4014 pass, with only the timezone-dependent IssueProperties failure already present on master. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
fdb9a4880d |
fix(security): route paperclipai CLI guidance through safe npx form (CWE-78) (#11400)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Paperclip provides CLI commands and guidance for operators and agents > - The `pnpm paperclipai` script can pass argument values through a shell > - Shell re-parsing can execute command substitutions inside quoted values > - This pull request routes guidance through inert-argv `npx paperclipai` commands and adds regression coverage > - The benefit is safer operator guidance across documentation and runtime hints ## Linked Issues or Issue Description This pull request fixes a command-injection-class defect in Paperclip CLI guidance. **What happened?** The `pnpm paperclipai <sub> --flag "$VALUE"` form can re-parse argument values through a shell. A command substitution inside a quoted value can execute on the host. **Expected behavior** Paperclip guidance must pass CLI values as inert argument values. Host-derived values must not appear in copyable commands. **Steps to reproduce** 1. Run a Paperclip guidance command that uses the `pnpm paperclipai` script. 2. Provide a quoted value that contains a command substitution. 3. Observe that the shell can evaluate the substitution before the CLI starts. 4. Compare the result with the `npx paperclipai` form. **Paperclip version or commit** `5670984b75d109950c968542a0111ebb6967f4da` **Deployment mode** All deployment modes that show or use the affected CLI guidance. **Installation method** Built from source and installed CLI guidance. **Agent adapter(s) involved** Not adapter-specific (core bug). **Database mode** Not database-related. **Access context** Both. **Additional context** The earlier merged PR [#11343](https://github.com/paperclipai/paperclip/pull/11343) used the unsafe `pnpm exec paperclipai` form. This fresh PR replaces that guidance with the safe `npx paperclipai` form. ## What Changed - Standardize documentation and runtime hints on `npx paperclipai`. - Remove the broken `pnpm exec paperclipai` guidance. - Use a static `<host>` placeholder in private-hostname guidance. - Add regression tests for unsafe forms, continued lines, static hosts, and offline guidance. ## Verification - `git diff --check origin/master...origin/fix/paperclipai-cli-npx-safe-invocation` passes. - The branch adds `server/src/__tests__/cli-invocation-safety.test.ts` and updates private-hostname tests. - CI must run the new tests, typecheck, lint, and build checks. - Local Vitest execution was not available because this worktree has no installed Vitest binary. ## Risks - The change affects operator and agent documentation text. - The runtime hints now show `<host>` instead of a request-derived host value. - No database schema or migration changes exist. - CI will detect any missed unsafe invocation or type error. ## Model Used OpenAI GPT-5, exact model ID `gpt-5`, with tool use and code-review assistance. The model used repository inspection, Git operations, and PR preparation. ## 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] CI ran the test suites and they pass; local test execution was unavailable in this worktree - [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 addressed all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
8cb0ce0de5 |
fix(ui): restore queued message interrupt action (#11374)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The task thread lets an operator add guidance while an agent run is active > - A new message can wait behind that active run as a queued message > - The classic task view lets the operator interrupt the target run from that queued message > - The redesigned task view did not expose the same action > - This pull request restores the action and keeps it bound to the exact target run > - The benefit is that operators can apply urgent guidance without switching task views ## Linked Issues or Issue Description **What happened?** The redesigned task view showed `Queued` for a queued operator message, but it did not show the existing interrupt action. **Expected behavior** The queued message must show `Interrupt` next to `Queued`. The action must stop the exact run that the message is waiting behind. **Steps to reproduce** 1. Open a task in the redesigned task view while an agent run is active. 2. Send a new operator message so it enters the queued state. 3. Observe that the queued message has no interrupt action. **Paperclip version or commit** `bc0b5a1642` **Deployment mode** All deployment modes that use the redesigned task view. ## What Changed - Preserve persisted queued state and the target run ID in the redesigned thread model. - Render a token-compliant `Interrupt` action beside the queued state. - Reuse the existing exact-run interrupt callback and show a disabled `Interrupting…` state during the request. - Keep an assigned queue target immutable so an in-flight comment cannot rebind its interrupt action to a replacement run. - Add regression tests for persisted queued messages, replacement-run races, and the in-progress action state. - No documentation update was required because this restores existing behavior. ## Verification - `pnpm exec vitest run ui/src/components/TaskChatThread.test.tsx` - `pnpm exec vitest run ui/src/components/TaskChatThread.test.tsx ui/src/components/task-chat/task-chat-adapter.test.ts ui/src/pages/IssueDetail.test.tsx -t 'queued message actions|queues messages against a queued live run and interrupts that exact run|commentsToTaskChatItems'` - `pnpm exec vitest run ui/src/pages/IssueDetail.test.tsx -t 'queued message|queues messages'` - `pnpm exec vitest run ui/src/pages/IssueDetail.test.tsx` (52 passed) - `pnpm -r typecheck` - `pnpm test:run` (all server and UI groups passed; the CLI group passed after inherited static AWS credential variables were omitted) - `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm exec vitest run cli/src/__tests__/secrets.test.ts` - `pnpm build` - `pnpm check:token-gates` ## Risks Low risk. The change only adds an action to queued messages that have a target run and an interrupt callback. Messages without both values keep the current rendering. > 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 with GPT-5. The runtime does not expose a more specific deployment ID or context-window size. The model used reasoning, repository tools, code execution, and test execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [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> |
||
|
|
d95340b0b8 |
feat(ui): send a company with no agent into onboarding, at the right step (#11352)
A company with no agent cannot do anything: no runs, no tasks, nothing to show. The dashboard says so in a banner with a link, which asks the customer to notice a problem the product can fix for them. It is worse for a company created by Paperclip Cloud: Cloud creates the company before the tenant boots, so the companyless redirect never runs, and the customer arrives on an empty dashboard straight out of a signup flow that already asked for a mission. The dashboard now opens onboarding when the agent list has loaded and is empty, and onboarding opens on the agent step when the company already has its mission — read from the company-level goal the seed writes, under the query key the launch path already uses, so it shares a cache entry rather than adding a fetch. The step is decided once. `initialStep` is derived from the company list and the goal list, so it changes on any retry, refetch or cache invalidation. An effect that took it as a dependency called `setStep` on every one of those and moved a customer who was already mid-flow. Gating the input only narrowed that window; it could not close it. The step now belongs to the request that opened the wizard: the effect reads it through a ref and is keyed on the wizard opening or the company changing. `createdCompanyIdRef` beside it already used this pattern for the same reason. That exposed a path nothing had ever taken. A company reached the mission step only by creating itself on step 1, so opening an existing company there found code that had never run: `companyName` is only typed on step 1, and both ways forward require it, so the step could not be completed at all; and confirming advanced without writing anything, so the mission the customer typed was discarded. Both fixed, and the write now reconciles against the goal list rather than adding a second company goal, since the mission lookup fails open and can send a company that has one back to that step. Company-scoped state now stays with its company. `clearCompanyScopedState` runs when the route replaces a company and when it withdraws one — the same event, and clearing half of it left a goal id that made the next company skip a mission it had never given. `stillTheSameCompany` guards all five async writes, after the server work rather than before it, so a company switch mid-flight cannot hand the new company the old one's goal, project, issue or agent, and cannot leave a hired agent without its instructions file. The keyboard path honours `loading` like every button already did. `claimOnboardingOffer` makes onboarding an offer that stays declined for the visit. Route ownership is now recorded whenever the route names a company, including one the wizard already holds. This changes a documented rule deliberately: without it a self-created company was never withdrawn, so `/onboarding` would show "create a company" while still holding the previous one and write the customer's new mission into it. Tested at the seam, because every defect on this branch lived between a value and its consumer and the predicate tests passed at every stage. `OnboardingWizard.step.test.tsx` renders the real wizard against the real resolver and the real mission hook across 18 cases, and each was fault-injected against the code it replaces rather than trusted on a green run. That caught a case that passed against the broken code, and a race in one of the guards. ui typecheck clean; full ui suite 3923 pass, with one timezone-dependent IssueProperties failure present on this branch's base in a file this change does not touch. All CI gates green; Greptile 5/5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dc6fcd1ff1 |
fix(ui): move agent secret access to searchable secrets tab (#11283)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The agent configuration UI controls each agent and its allowed secrets. > - The environment variable editor already has a secret selector with search and folder navigation. > - The secret access editor used a basic list and made large secret stores hard to use. > - The secret access controls also occupied the main Configuration tab. > - This pull request reuses the rich selector and moves secret access to a dedicated Secrets tab. > - The benefit is one consistent secret selection workflow with clearer agent configuration navigation. ## Linked Issues or Issue Description **What existing behavior does this improve?** The agent detail configuration view and its secret access editor. **Subsystem affected** `ui/` — React and Vite board UI. **Current behavior** The secret access editor uses a basic select control. It does not provide the search and folder navigation available in the environment variable editor. The editor also appears inside the Configuration tab. **Proposed behavior** The secret access editor uses the shared secret picker. Users can search secrets and browse slash-delimited folders. Agent details provide a dedicated Secrets tab for this editor. **Reason and benefit** Large secret stores are slow to scan in a flat list. Reusing one selector reduces UI differences and makes scoped secret access easier to manage. **Breaking changes** None. The API and saved secret access data do not change. ## What Changed - Reused the environment variable secret picker in the agent secret access editor. - Preserved secret version selection and the create-secret action, including nested-popover focus handling. - Added a route-backed Secrets tab to agent details and removed secret access controls from Configuration. - Guarded unsaved configuration across tab, link, browser-history, and action-triggered navigation. - Rechecked dirty state when navigation-producing agent actions finish, covering edits made while a request is pending. - Added component, page, and Storybook coverage for the workflow. ## Verification - `pnpm --filter @paperclipai/ui exec vitest run src/components/AgentActionButtons.test.tsx src/components/AgentConfigForm.render.test.tsx src/components/AgentSecretAccessEditor.test.tsx src/components/environment-variables-editor/EnvironmentVariablesEditor.test.tsx src/pages/AgentDetail.progress.test.ts` — 82 tests passed. - `pnpm --filter @paperclipai/ui typecheck` - `pnpm check:token-gates` - All GitHub PR checks passed on `5209c5b787`, including build, canary, general and serialized tests, and all three e2e shards. - Greptile completed at 5/5 with zero unresolved review threads. ## Risks - Low risk. The API and persisted binding format are unchanged; this changes agent configuration navigation and secret selection UI. - Dirty-state guards now cover direct navigation, Back/Forward history, and navigation-producing agent actions, including pending-request races. - Tests cover tab separation, secret access updates, search, folder navigation, focus restoration, and navigation rejection. - No documentation change is required because commands, contracts, and setup steps do not change. > 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 with GPT-5. This runtime did not expose a more specific model ID or context window. The model used agentic reasoning, repository tools, and code execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [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> |
||
|
|
5ca7b4c1fe |
fix(security): standardize paperclipai CLI guidance on safe npx path (#11343)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Paperclip provides CLI guidance to agents and operators through documentation and runtime messages. > - Content-bearing `pnpm paperclipai` examples send arguments through a shell. > - Shell evaluation can execute command substitutions in untrusted argument content. > - Runtime hostname guidance can also place request-derived content inside a shell command. > - This pull request uses `npx paperclipai` for content-bearing guidance and uses a static hostname placeholder. > - The benefit is safer copy-paste guidance for agents and operators. ## Linked Issues or Issue Description **Issue type** Incorrect information **Where is the issue?** CLI guidance in `doc/CLI.md`, `skills/paperclip/SKILL.md`, documentation, and runtime-generated hints. **What's wrong?** Content-bearing `pnpm paperclipai` commands can pass argument text through `/bin/sh`. Shell command substitution in an argument can execute before the CLI receives the value. **Suggested fix** Use `npx paperclipai` for content-bearing commands. Use a static `<host>` placeholder when runtime guidance displays the allowed-hostname command. ## What Changed - Replace content-bearing `pnpm paperclipai` examples with `npx paperclipai` across the documentation and agent-facing guidance. - Update runtime-generated CLI hints to use a static `<host>` placeholder. - Add safety notes to `doc/CLI.md` and `skills/paperclip/SKILL.md`. - Add scans and regression tests for unsafe invocation and hostile hostname headers. - Keep fixed lifecycle commands and `pnpm --filter @paperclipai/*` build commands unchanged. ## Verification - Run `tsc --noEmit` for the changed server files. - Run `cli-invocation-safety.test.ts`. - Run `private-hostname-guard.test.ts`. - Confirm that hostile hostname headers do not enter shown shell commands. - Confirm that the three commits contain the required Paperclip co-author trailer. ## Risks - This change updates documentation and diagnostic text across many surfaces. - Fixed lifecycle and setup commands remain unchanged. - The tests fail if content-bearing `pnpm paperclipai` guidance returns. - The change does not alter the CLI argument parser. ## Model Used OpenAI Codex, GPT-5, tool use, code execution, and repository review 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> |
||
|
|
d0d242e843 |
feat(server): reopen an archived isolated execution workspace in place (#11322)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Issue execution uses isolated workspaces that hold the issue
worktree
> - A terminal issue can leave its isolated workspace archived and
unable to resume
> - The existing closed-workspace guards returned a conflict and gave
the user no self-serve recovery
> - This pull request reopens the same isolated workspace row and
rebuilds its worktree
> - The benefit is that resume, checkout, and comment actions can
continue without a new workspace row
## Linked Issues or Issue Description
**Problem or motivation**
A terminal issue can point to an archived isolated execution workspace.
Resume, checkout, and comment actions then stop with a conflict.
**What happened?**
A terminal issue kept its issue-to-workspace link after the isolated
workspace reached a closed status. The guarded actions returned HTTP 409
instead of restoring access.
**Expected behavior**
The next authorized resume, checkout, or comment action reopens the same
isolated workspace row. The action rebuilds the worktree and then
continues.
**Steps to reproduce**
1. Create an issue that uses an isolated execution workspace.
2. Move the issue to a terminal state and let the workspace archive.
3. Try to resume the issue or add a comment.
4. Observe the closed-workspace conflict.
**Paperclip version or commit**
|
||
|
|
a09d7dcc06 |
feat(ui): bounce cold arrivals off archived company URLs, add Unarchive (#11302)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Archiving a company hides it from the sidebar switcher, but remembered last-visited paths, browser history, bookmarks, and restored tabs keep depositing users onto its URLs long after archiving > - Since the selection ping-pong fix (#11300) those arrivals render, but the user is stranded inside a workspace the sidebar refuses to show — and unarchiving had no UI anywhere, so the only way back was a hand-typed settings URL > - This pull request bounces cold arrivals at archived company URLs to an active company (with a toast naming why), lets deliberate visits stick, and adds an Unarchive action to the companies list > - The benefit is that stale URLs stop stranding users in retired workspaces, and archived companies become restorable from the one page that still lists them ## Linked Issues or Issue Description Follow-up to #11300. No existing issue for the remaining gap; description follows the enhancement template: **What happened?** After #11300, opening an archived company's URL (stale tab, history, bookmark, remembered path) renders that company's pages — but the sidebar switcher does not list it, so the user is stranded in a workspace they retired, and every stale URL pulls them back in. Separately, unarchiving a company has no UI: the archive button lives in company settings, which becomes unreachable through normal navigation once the company is archived. **Expected behavior** Arriving cold at an archived company's URL lands the user in an active workspace, with a toast explaining the redirect. Explicitly choosing the archived company (from the companies list) still works, so its pages remain reachable. Archived companies can be restored from the companies list. **Steps to reproduce** 1. Create two companies; archive one. 2. Open `/{archivedPrefix}/dashboard` directly — before: renders the archived workspace with no sidebar presence; after: bounces to the active company's dashboard with a toast. 3. On the companies list, open the archived company's row menu — before: no restore action anywhere; after: Unarchive. ## What Changed - `ui/src/lib/company-selection.ts`: `resolveArchivedCompanyBounce` — pure policy: bounce when the URL names an archived company that is not the current selection and an active company exists; prefer the currently selected active company as the destination. - `ui/src/components/Layout.tsx`: the route-sync effect applies the bounce (toast + selection + `replace` navigation) before syncing selection from the route. - `ui/src/pages/Companies.tsx`: Unarchive action (`PATCH status: "active"`) in the row menu for archived companies. - Tests: unit cases for the bounce policy; the e2e now drives all three behaviors (direct-load bounce with toast, re-arrival bounce, deliberate visit sticks) on top of the existing crash regression. ## Verification - `pnpm vitest run src/lib/company-selection.test.ts src/context/CompanyContext.test.tsx src/pages/Companies.test.tsx` in `ui/` — 20 tests pass. - `npx playwright test --config tests/e2e/playwright.config.ts archived-company-url` — passes, covering bounce, toast, and deliberate-visit paths. - `pnpm typecheck` in `ui/` — clean. ## Risks Low risk. The bounce only fires for archived-company URLs when the archived company is not already selected and an active company exists; all-archived instances render as before. Deliberate selection from the companies list is unaffected (selection equals the matched company, so no bounce). Unarchive reuses the existing `PATCH /api/companies/:id` status transition the server already supports. ## Model Used - Claude (Anthropic), Claude Fable 5 (`claude-fable-5`), via Claude Code CLI with extended thinking and tool use (code search, edit, test execution, Playwright e2e). ## 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 |
||
|
|
68c699687e |
fix(ui): keep selected run lifecycle and logs current (#10169)
## Thinking Path > - Paperclip's agent-run detail view combines live lifecycle events, detail queries, and persisted shell logs. > - Queued runs do not have a log reference yet, so polling the log endpoint returns expected-but-noisy 404s. > - Simply disabling queued log polling can strand the selected detail cache at `queued` or leave terminal lifecycle fields stale. > - The detail cache therefore needs both live lifecycle patches and an HTTP fallback while the run is active. > - Both the selected-run query and the shared transcript hook must avoid reading persisted logs before execution starts. > - This pull request disables queued log polling in both paths, patches lifecycle fields, invalidates authoritative detail data, and polls run state only while active. > - The benefit is quiet queued runs, reliable queued-to-running transitions, and complete final log handoff. ## Linked Issues or Issue Description No exact duplicate found. I searched open PRs for `queued run`, `log polling`, and `run lifecycle`; related results addressed stale queued execution or dashboards rather than selected-run log/detail synchronization. **What happened?** Opening or rendering a queued run caused repeated `GET /api/heartbeat-runs/:id/log` 404s from both the selected detail and shared transcript hydration paths. A queued-only detail guard could also leave the selected run stuck at `queued` when live delivery was missed, or stop before fetching final `logRef` and terminal fields. **Expected behavior** Queued runs should not request unavailable logs. The selected detail should transition through running to terminal via live events or bounded HTTP fallback, refresh authoritative detail data, fetch final logs, and then stop polling. **Steps to reproduce** 1. Open the detail page for a run waiting behind the agent concurrency limit. 2. Observe repeated log-endpoint 404s while status is queued. 3. Let the run start with WebSocket delivery unavailable or delayed. 4. Observe stale detail state without a run-detail fallback. **Environment** - Paperclip base: `14f20be92b86a49ff2c35495e5b0fa4d719998ef` - Deployment: self-hosted, built from source - Adapter scope: visible with Hermes-backed agents but not adapter-specific - [x] I searched open PRs for queued-run log polling and selected-run lifecycle synchronization; no exact duplicate was found. ## What Changed - Poll shell logs only while a run is `running`, never while `queued`. - Defer shared persisted-transcript hydration and its live WebSocket until a queued run becomes `running`; terminal runs still receive one persisted-log hydration attempt. - Patch selected run status, invocation metadata, errors, and start/finish times from lifecycle events. - Invalidate selected run detail on lifecycle events to hydrate authoritative `logRef`, result, usage, and excerpts. - Poll run detail every 5 seconds while queued and every 15 seconds while running, then stop at terminal status. - Add regression coverage for queued/running/terminal polling and lifecycle cache handoff. ## Verification - `pnpm exec vitest run ui/src/context/LiveUpdatesProvider.test.ts ui/src/pages/AgentDetail.progress.test.ts` — 42 passed. - `pnpm exec vitest run ui/src/components/transcript/useLiveRunTranscripts.test.tsx` — 11 passed. - `pnpm --filter @paperclipai/ui typecheck` - `pnpm --filter @paperclipai/ui build` - Exact final diff independently reviewed: **APPROVE**, no blocking findings. - UI-only local cutover completed with the Paperclip server PID unchanged. ## Risks - The selected run performs one lightweight detail GET every 5 seconds while queued and every 15 seconds while running if it remains open. Polling stops at terminal status. - Lifecycle events still patch immediately; polling is only the fallback and authoritative hydration path. - No API, schema, or migration changes. > This is a bug fix, not roadmap feature work. ## Model Used OpenAI Codex `gpt-5.6-sol`, with tool use, code execution, repository inspection, and independent read-only review agents. ## 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/described the search above - [x] I have described the issue in-PR following the bug template - [x] I have not referenced internal/instance-local Paperclip issues or links - [x] My branch name describes the change and contains no internal ticket id - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have considered documentation; no user-facing documentation change is required - [x] I have considered and documented 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: cucurigoo <cucurigoo@users.noreply.github.com> |
||
|
|
0ee0543e5b |
feat(ui): refine the chat-style task workflow (#11263)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The issue detail view is the main place where people read work and guide agents. > - The chat-style task view needs clear messages, controls, properties, and document feedback. > - Dense metadata and disconnected controls make active work harder to scan. > - This pull request refines the existing chat-style task workflow across desktop and mobile layouts. > - The benefit is a clearer issue thread with faster access to the controls that guide work. ## Linked Issues or Issue Description **What existing behavior does this improve?** The change improves the chat-style issue detail view that was introduced in [#10606](https://github.com/paperclipai/paperclip/pull/10606) and expanded in [#10707](https://github.com/paperclipai/paperclip/pull/10707). **Current behavior** The issue thread spreads task controls across the page. Agent-turn metadata competes with the message content. Document annotation comments use inline placement that limits the document reading area. The mobile composer can overlap the bottom navigation. **Proposed behavior** The issue view keeps the thread focused on message content. It moves supporting controls into the properties area, adds searchable assignment, restores the sub-task tree, docks document comments in a side gutter, and keeps the mobile composer clear of navigation. **Reason and benefit** People can scan active work faster and find task controls without leaving the issue. The layout also gives documents and mobile conversations more usable space. **Breaking changes** None. The change updates presentation and interaction behavior in the existing issue UI. ## What Changed - Refined task-chat message spacing, metadata, agent bubbles, and composer alignment. - Added searchable assignment and restored sub-task navigation in the properties pane. - Moved document annotation comments into a right-side gutter. - Kept the mobile composer above the auto-hiding bottom navigation. - Added and updated focused component tests for the changed interactions. ## Verification - `pnpm check:token-gates` - `TZ=UTC pnpm --filter @paperclipai/ui exec vitest run src/components/InlineEntitySelector.test.tsx src/components/IssueDocumentAnnotations.test.tsx src/components/IssueProperties.test.tsx src/components/TaskChatThread.test.tsx src/pages/IssueDetail.test.tsx` - The token gates report 3/3 clean. - The focused test run passes 124 tests in 5 files. - Visual snapshot baselines were not updated. This follows the `doc/design/DECISION-SHEET.md` entry "Per-change snapshot verification demoted to dormant (Jul 13 2026)." ## Risks - The changes affect several related issue-detail layouts. A browser review should cover desktop and mobile widths before merge. - The monitor-row test formats time in the host timezone. The verification command sets `TZ=UTC` to match CI. > 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 with GPT-5. The context-window size is not exposed in this environment. The model used reasoning, repository tools, code execution, and GitHub tools. ## 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: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
ff5fd62d07 |
Resolve the environment secret companyId context on first save (#11291)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Environments give agent runs an execution target, and their env vars can carry secrets, so writes need a company scope for secret bindings > - `PATCH /environments/:id` resolves that scope from a query param, existing bindings, or a single-membership actor — and fails closed otherwise > - A fresh environment has no bindings yet, so an admin whose memberships cannot pin one company gets a 422 on the very first env var save and can never create the first binding > - #11200 made this reachable from the UI: it opened the envVars-only edit on the managed sandbox row, but the UI never sends the company it already knows > - This pull request sends the page's selected company on environment updates and adds a server fallback to the instance's only company > - The benefit is that the first env var save works on fresh environments, while multi-company ambiguity still fails closed ## Linked Issues or Issue Description Refs #11200 **What happened?** On a fresh platform-managed sandbox environment, the first "Save environment variables" in the UI fails with HTTP 422: "Environment secret management requires a companyId context during the instance-scoped transition." The same failure hits any environment update that touches `envVars` or `config` when the environment has no secret bindings yet and the actor's memberships do not name exactly one company (for example an instance admin provisioned without a membership row, or a user in two companies). **Expected behavior** The save succeeds. The client tells the server which company scopes the secret bindings, and on a single-company instance the server can resolve the only possible scope by itself. **Steps to reproduce** 1. Provision a platform-managed sandbox environment (managed-config `environments` entry) so the row exists with no secret bindings. 2. Sign in as an instance admin whose memberships do not resolve to exactly one company. 3. Open Environments, edit the managed row, add an env var, and save. 4. The PATCH returns 422 with the companyId-context error. ## What Changed - `ui/src/api/environments.ts`: `environmentsApi.update` accepts an optional `companyId` and sends it as the `companyId` query param the server already reads. Renamed the `customImageCompanyQuery` helper to `companyIdQuery` since it now serves plain updates and probes too. - `ui/src/pages/CompanyEnvironments.tsx`: both the managed envVars-only save and the general environment save pass `selectedCompanyId`. - `server/src/routes/environments.ts`: `resolveEnvironmentSecretContextCompanyId` falls back to the instance's only company when exactly one exists, mirroring the existing fallback in `resolveCustomImageCompanyId`. Multi-company instances still fail closed. - Tests: three new route tests (explicit query context for a multi-company actor, single-company-instance fallback for a membership-less admin, fail-closed regression on a multi-company instance), and updated the SSH-probe and probe-ambiguity tests for the new fallback. ## Verification - `cd server && pnpm vitest run src/__tests__/environment-routes.test.ts` — 78 pass. - `cd ui && pnpm vitest run src/pages/CompanyEnvironments.test.tsx` — 22 pass. - Full `server` and `ui` vitest suites and `tsc --noEmit` on both packages pass locally. - Manual: on a managed instance, edit the managed sandbox environment, add an env var, save. Before: 422. After: the save persists and the PATCH carries `?companyId=`. ## Risks - Low risk. The explicit query param path already existed on the server; the UI now uses it. - Behavioral shift: on single-company instances, environment secret-context resolution (including `POST /environments/:id/probe`) now resolves the only company instead of returning no context. That lets probes resolve secret-backed config where they previously returned a 422 asking for an explicit companyId. Multi-company instances keep the fail-closed behavior, covered by a regression test. - Self-hosted single-company instances gain the same first-save fix; no schema or config changes. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use. ## 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 |
||
|
|
e5a7fd7038 |
Add sandbox device-login for the Codex adapter (#11237)
## Thinking Path > - Paperclip helps people manage AI agents for work. > - Agent adapters connect Paperclip to tools such as the Codex command line tool. > - A sandboxed Codex agent may start without a credential. > - The operator needs a safe sign-in flow that does not expose credentials to the shared package or the sandbox. > - This pull request adds a company-scoped device-login flow with a temporary Daytona sandbox. > - The flow promotes the credential only after readiness checks pass and removes the temporary sandbox after use. > - The result lets an operator sign in to a sandboxed Codex agent from the agent form. ## Linked Issues or Issue Description **Subsystem affected** Cross-cutting (multiple of the above) **Problem or motivation** A Codex adapter that runs in a sandbox cannot authenticate when the company has no pre-provisioned Codex credential. **Proposed solution** Add a company-scoped device-login session. Start a temporary sandbox, run `codex login --device-auth`, stream the code and URL, verify readiness, promote the credential, and delete the sandbox. **Alternatives considered** Pre-provisioning a credential does not support first-time sandbox login. Keeping the credential in the login sandbox does not provide a durable company credential. **Roadmap alignment** This supports the roadmap item for cloud and sandbox agents. **Additional context** The flow uses a five-minute cleanup reaper, compare-and-set status changes, and a PostgreSQL advisory lock to protect promotion and cleanup. ## What Changed - Add the adapter login-session contract, database table, and migration. - Add company-scoped server routes and a service for sandbox device login. - Add credential promotion, readiness checks, and cleanup after login. - Add restart-safe cleanup for abandoned login sandboxes. - Add sandbox login controls to the agent creation and edit forms. - Keep device-login and vendor identifiers out of public shared and adapter UI symbols. ## Verification - `pnpm --filter @paperclipai/adapter-codex-local exec vitest run` passed with 310 tests at the submitted commit. - The server login route, service, and reaper tests passed with 45 tests at the submitted commit. - The agent form render tests passed with 26 tests at the submitted commit. - The public-symbol leak check passed at the submitted commit. - A live Daytona sign-in flow still requires confirmation by a user with a live sandbox. ## Risks The migration adds a new company-scoped table. A promotion or cleanup race could remove a credential or leave a sandbox active, so the service uses claims, compare-and-set transitions, and an advisory lock. The live Daytona flow needs operator confirmation because local tests do not provide a real browser sign-in. ## Model Used OpenAI Codex, GPT-5, tool use and code execution, extended reasoning. ## 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> |
||
|
|
d5bb396518 |
fix: pass sandbox provider credential env vars to plugin workers; hide Local default under managed-sandbox-only (#11244)
<!-- Write all pull request text in Simplified Technical English (ASD-STE100): short sentences, one instruction per sentence, simple approved vocabulary, and the active voice. --> ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Environments give each agent run an execution target, and sandbox providers (Daytona, E2B, Novita, exe.dev) run as plugin workers > - A managed deployment provisions one platform-managed sandbox row with no credential in config; the provider is documented to fall back to its process env var (for example `DAYTONA_API_KEY`) > - Plugin workers spawn with a scrubbed environment, so that fallback never sees the host env var — probe and lease acquisition fail with "require an API key in config or DAYTONA_API_KEY" even when the deployment sets the var > - Separately, the managed-sandbox-only mode hides local rows from every list, but the instance Default picker renders a hardcoded synthetic "Local" option that no filter touches > - This pull request forwards each bundled provider's documented credential env var to its own plugin worker, and gates the synthetic Local option on the flag > - The benefit is that the documented host-env credential fallback works for plugin-backed providers, and managed-sandbox-only instances no longer offer Local anywhere ## Linked Issues or Issue Description **Subsystem affected** Plugin worker environment construction (`server/src/services/plugin-loader.ts`) and the environments UI (instance Default picker, agent form inherited-environment label). **Problem or motivation** Two follow-ups to the managed-sandbox-only mode (#11200), both found on a live managed deployment: 1. The deployment sets `DAYTONA_API_KEY` as a server env var and the managed sandbox row omits `config.apiKey` by contract. "Test Connection" fails with `Sandbox environment probe failed for provider "daytona". Daytona sandbox environments require an API key in config or DAYTONA_API_KEY.` A real agent run fails the same way at lease acquisition. The cause: sandbox providers run as plugin workers, and `buildPluginWorkerEnv` passes only model-provider keys and in-cluster Kubernetes vars. The provider's own documented credential env var never reaches the worker, so the in-plugin `process.env` fallback reads nothing. The self-hosted path has the same gap: the Daytona plugin README documents `DAYTONA_API_KEY` as a host-level fallback, and it does not work today. 2. With `enableManagedSandboxOnly` on, the instance Default environment picker still shows "Local". The server filters local *rows* out of the list, and the client filter mirrors that for cached lists, but this option is a hardcoded `<option value="">Local</option>` — not a list row — so no filter removes it. Selecting it writes a null default, which run selection then rejects fail-closed. **Proposed solution** Forward each bundled sandbox provider's documented credential env var into its plugin worker, keyed by the manifest's declared `environmentDrivers[].driverKey` so a worker only receives its own provider's credential (daytona → `DAYTONA_API_KEY`, e2b → `E2B_API_KEY`, exe-dev → `EXE_API_KEY`, novita → `NOVITA_API_KEY`). Keep the existing gate: only plugins that declare `environment.drivers.register` receive any passthrough. In the UI, render the synthetic Local option only when managed-sandbox-only is off; under the flag show a disabled "Select environment" placeholder only while no default is stamped yet, and stop the agent form's inherited label from reading "Local". **Alternatives considered** Adding `DAYTONA_API_KEY` to the existing `ADAPTER_ENV_PASSTHROUGH` list was rejected: that list goes to every environment-driver plugin, so each provider would receive every other provider's credential. A manifest schema field for declared credential env vars was rejected as heavier than needed: the bundled providers are known, and the mapping lives next to the two existing passthrough lists. ## What Changed - `server/src/services/plugin-loader.ts`: new `SANDBOX_PROVIDER_CREDENTIAL_ENV_PASSTHROUGH` map (driverKey → documented credential env vars). `buildPluginWorkerEnv` reads the manifest's `environmentDrivers` and forwards only the matching vars, after the existing `environment.drivers.register` gate. Blank values stay excluded. - `server/src/__tests__/plugin-database.test.ts`: the daytona worker receives `DAYTONA_API_KEY` and not another provider's key; a plugin whose drivers have no mapping (kubernetes) receives no credential var. - `ui/src/pages/CompanyEnvironments.tsx`: the Default picker's synthetic Local option renders only when managed-sandbox-only is off. Under the flag, a disabled "Select environment" placeholder renders only while the default is unset. - `ui/src/pages/CompanyEnvironments.test.tsx`: the Local option is present by default and absent under the flag; saved non-local environments stay selectable. - `ui/src/components/AgentConfigForm.tsx`: the inherited-environment label falls back to "Managed sandbox" instead of "Local" under the flag. ## Verification - `server`: `npx vitest run src/__tests__/plugin-database.test.ts -t buildPluginWorkerEnv` — 5 passed (3 existing, 2 new). - `ui`: `npx vitest run src/pages/CompanyEnvironments.test.tsx` — 22 passed (2 new); `npx vitest run src/components/AgentConfigForm.render.test.tsx` — 10 passed. - `tsc --noEmit` clean in `server` and `ui`. - Live managed deployment: confirmed the tenant service env carries `DAYTONA_API_KEY` while the probe fails with the exact message above, which pins the root cause to the worker env, not delivery. ## Risks - The worker env grows by exactly one var per matching bundled provider, only when the deployment sets it and only for plugins that declare a matching environment driver. Plugins without a mapping see no change. - Self-hosted behavioral shift is the fix itself: a host-level `DAYTONA_API_KEY` (or E2B/EXE/NOVITA equivalent) now reaches the provider as its README documents. Deployments that set the var but expected it to stay inert had no working configuration to preserve — the provider errored on every keyless probe and run. - UI change is inert unless `enableManagedSandboxOnly` is on (default false everywhere). ## Model Used Claude Fable 5 (`claude-fable-5`) via Claude Code — extended thinking, tool use, parallel read-only subagents for the two root-cause traces. ## 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 |
||
|
|
0a95ada1be |
feat(server): chunked import preview endpoint and resumable upload in the Import page and CLI (#11224)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The previous pull request added server-side chunked resumable import transfers; without clients, large imports still ride the single fragile upload > - The Import page and the CLI need to slice large packages, upload parts with retry and progress, resume after interruptions, and preview before applying > - Preview is the missing server piece: the browser flow is preview-then-import, so a completed spool must be previewable without re-uploading > - This pull request adds the transfer preview endpoint, switches the Import page to the chunked path for zips over 48 MB, and teaches the CLI the same for oversized local imports > - The benefit is that large imports get progress, per-part retry, and resume in both clients, while small imports keep the exact single-shot path they have today ## Linked Issues or Issue Description **What happened?** With only the server transfer routes in place, users still upload large company packages as one request from the Import page and the CLI: no progress indication, no retry below the whole file, and no resume after a dropped connection or refresh. The preview-then-import flow also cannot run against an uploaded transfer, forcing a second full upload. **Expected behavior** A large package uploads once as verified parts with visible progress; preview and import both run against the uploaded spool; an interrupted upload resumes with only the missing parts re-sent; packages at or below 48 MB behave exactly as before. **Steps to reproduce** 1. Select a 500 MB zip on the Import page over an unreliable connection. 2. Watch the single upload fail near the end and restart from zero, twice — once for preview, once for import. 3. Same story headless via the CLI. ## What Changed - Server: `POST /import/transfers/:id/preview` runs the existing preview logic against the completed spool (shared assembly + whole-file verification helper with apply); preview neither completes the run nor deletes the spool, so the subsequent apply reuses it. Missing parts respond with the missing list. - UI: zips over 48 MB take the chunked path in both preview and import — the file is sliced into 32 MB parts hashed with WebCrypto (single ArrayBuffer, no second copy), the transfer is created or resumed (the create response's missing-parts list drives what uploads), parts upload sequentially with three attempts each and visible progress, then transfer preview/apply replace the multipart calls. The existing preview pane, collision handling, adapter overrides, and async job polling are unchanged; ≤ 48 MB keeps the single-shot path. - CLI: oversized local `.zip` or folder imports zip/slice/hash with node crypto, upload with resume and per-part retry and progress lines, and use transfer preview/apply. Small packages keep the inline path byte-identical. - Failure honesty: adapters/API errors fail open to existing behavior; a part failing all attempts surfaces a durable error panel with resume intact. ## Verification - Server: preview-then-apply on one spool (run stays open, spool intact, then apply completes), preview with missing parts rejected — added to the transfer route suite (embedded Postgres). - UI suite: large file takes the chunked path (manifest shape, part uploads, progress, apply on a resumed transfer, single-shot endpoints never called), small file stays single-shot, part failure after three attempts surfaces the error panel without running preview, resume re-uploads only the missing part. - CLI: manifest slicing/hashing, threshold behavior for zip and folder sources, folder-zip round-trip through the real zip reader, upload resume/retry/exhaustion/already-completed, full-command chunked and small-zip inline flows. - Server, ui, cli typechecks clean. Exact counts in the PR checks. ## Risks - The 48 MB threshold only routes between two verified paths; behavior below it is untouched. - Chunked CLI imports use the board-scoped transfer routes, so oversized CLI imports need board credentials (agent tokens keep the agent-safe small-file path). No privilege change — board actors already had the generic routes — but the two size regimes differ semantically; called out for review. - A CLI dry-run over the threshold uploads parts before previewing; the spool persists (24 h sweep) and a later apply resumes without re-upload — inherent to preview-against-spool. Stacked on #11223 — merge that first; this PR then shows only the preview endpoint and client changes. ## Model Used - Claude Fable 5 (`claude-fable-5`) via Claude Code CLI, with extended thinking and tool use (multi-agent implementation with independent verification). ## 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 |
||
|
|
2494a2a0fe |
perf: add repeatable issue-detail baseline rig (#10409)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The issue detail page is a core operator surface where perceived latency directly affects task navigation > - Performance work needs repeatable evidence so later optimizations can be compared against the same scenarios > - The page did not expose stable user-timing marks for its header or first useful content > - There was also no isolated seeded browser rig that measured warm navigation, cold deep links, waterfalls, or server time > - This pull request adds the instrumentation and a one-command Playwright baseline harness > - The benefit is that issue-page performance changes can be validated with reproducible median measurements instead of anecdotes ## Linked Issues or Issue Description **Subsystem affected** Cross-cutting: `ui/`, `server/`, and browser performance tooling. **Problem or motivation** The issue detail page performs a large client bootstrap and request fan-out, but the repository lacks stable user-timing boundaries and a repeatable benchmark. That makes performance changes difficult to compare and allows regressions to be judged from anecdotes instead of consistent evidence. **Proposed solution** Add stable header/content paint measures, development/QA-only lifecycle vital reporting, aggregate server timing for the issue endpoint, and a seeded Playwright command that runs warm/cold scenarios under throttled and unthrottled profiles with N≥5 median reporting. **Alternatives considered** Ad hoc DevTools recordings were rejected because they are not repeatable or reviewable. Production telemetry was rejected because this baseline should not change production data collection. A unit-only harness was rejected because it cannot capture browser bootstrap, rendering, and network waterfall costs. **Roadmap alignment** The roadmap calls for agent performance to be measurable over time. This change applies that evidence-first principle to a core operator page and does not duplicate a listed roadmap deliverable. **Additional context** The generated report includes warm and cold medians, TTFB/FCP/LCP where applicable, request and byte totals before first useful content, JavaScript bytes, and issue endpoint server timing. ## What Changed - Added `issue-detail:navigate→header-paint` and `issue-detail:navigate→content-paint` user-timing measures to the issue detail page. - Added development/QA-only TTFB, LCP, and INP console reporting without production telemetry delivery. - Added `Server-Timing` for `GET /api/issues/:id`. - Added `pnpm exec playwright test --config tests/perf/issue-detail/playwright.config.ts`, which seeds an isolated instance and runs N≥5 warm/cold samples under unthrottled and Fast 4G/4x CPU profiles. - Added Markdown, raw JSON, and Chrome-trace outputs with median baseline tables and waterfall data. ## Verification - `pnpm --filter @paperclipai/ui typecheck` - `pnpm --filter @paperclipai/server typecheck` - `pnpm check:token-gates` - `npx playwright test --config tests/perf/issue-detail/playwright.config.ts --list` - `pnpm exec playwright test --config tests/perf/issue-detail/playwright.config.ts` — passed 20 samples in 9.4 minutes (5 runs × 2 scenarios × 2 profiles) for the baseline; post-review integrity reruns also exercised the corrected paths, while this shared runner intermittently killed Chromium processes, so the rig now performs one bounded browser-crash retry per sample. - Baseline medians: warm unthrottled 278/447 ms header/content; cold unthrottled 646/646 ms; warm throttled 1240/2060 ms; cold throttled 3932/3933 ms. ## Risks - Low product risk: the new browser measurements are development/QA tooling and the UI timing work does not change visible layout. - `Server-Timing` exposes only aggregate handler duration, not query contents or private identifiers. - Native INP reporting uses supported browser event timing entries and silently no-ops where unsupported. > 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-assisted coding and browser execution with reasoning enabled; context-window size is not exposed in this environment. ## 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> Co-authored-by: Dev Agent <dev@paperclip.ing> |
||
|
|
0044fa8904 |
Let tenants edit env vars on managed sandbox environments; add managed-sandbox-only mode (#11200)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Environments give each agent run an execution target: the local host, SSH, or a sandbox provider > - A managed deployment can provision one platform-managed sandbox environment through the `PAPERCLIP_MANAGED_CONFIG` `environments` section > - That row is fully locked today. A tenant cannot add environment variables for their agents. There is also no way to hide local execution — run selection falls back to the local row > - A platform that manages the sandbox for its tenants needs both: the tenant adds env vars (and nothing else), and local execution is neither visible nor reachable > - This pull request opens exactly one tenant edit (env vars) on the managed sandbox row, and adds an `enableManagedSandboxOnly` mode that hides local and makes run selection fail closed > - The benefit is a complete managed-sandbox experience with no change for self-hosted instances ## Linked Issues or Issue Description **Subsystem affected** Environments (managed sandbox provisioning, environment routes, run environment selection) and the environments UI. **Problem or motivation** Platform-provisioned sandbox environments (`metadata.managedByPaperclip`) reject every write on cloud-managed instances. Agents often need environment variables inside their sandbox. The tenant has no way to set them on the managed row. Separately, an operator cannot remove local execution: the environment list always shows the local row, and run selection falls back to it when no default is set. **Proposed solution** Allow an envVars-only PATCH on the managed sandbox row, and echo those env vars back for editing. Add a managed-tier feature (`enableManagedSandboxOnly`) that hides the local environment from all read surfaces and redirects local-landing run selection to the managed sandbox environment, failing closed when it is unavailable. **Alternatives considered** UI-only hiding of the local row. This was rejected: it does not stop a run from resolving to local, so it is presentation without enforcement. Full unlock of the managed row was also rejected: name, driver, and config stay platform-owned so boot reconciliation cannot fight tenant edits. ## What Changed - `server/src/routes/environments.ts`: the platform-provisioned write floor admits an envVars-only PATCH on the generalized managed sandbox row (sandbox driver, `managedByPaperclip`, not legacy kubernetes-marker rows). Name, driver, config, status, metadata, and DELETE stay rejected. The read floor stops blanking env vars on that row; credential-shaped config keys stay redacted for every actor. Legacy kubernetes-marker rows keep the full floor. - Same file: under `enableManagedSandboxOnly`, the environments list and the by-id read omit the local row for every actor, including instance admins. - `server/src/services/execution-workspace-policy.ts`: `resolveExecutionWorkspaceEnvironmentId` gains the managed-sandbox-only inputs. A selection that lands on the local environment is redirected to the managed sandbox environment. With no active managed row it throws `ManagedSandboxUnavailableError` — never local. Non-local selections (ssh, user-created sandboxes) are untouched. - `server/src/services/heartbeat.ts`: the run path reads the flag, looks up the managed row (`findManagedSandboxEnvironment`, new read-only finder in `environments.ts`), and passes both to the resolver. Mirrors the forced-kubernetes precedent, which keeps precedence when both regimes are on. - `server/src/services/managed-environments.ts`: after a successful reconcile, the instance default environment moves to the managed sandbox row when the current default is unset, local, or dangling. A tenant-chosen custom environment is never overridden. - `packages/shared`: new `enableManagedSandboxOnly` key (schema default false, catalog tier `managed`, cloudDefault false, selfHostedDefault false) and the matching interface field. - UI: managed rows show a "Managed by Paperclip" lock badge; editing one opens a dedicated env-vars-only editor that sends the one PATCH shape the server admits (the old full form failed with a 403 on save). New `ui/src/lib/managed-sandbox-environment.ts` mirrors the local filter for cached lists (applied in the project picker; the agent picker already excluded local). The experimental settings page gains the toggle at its alphabetical card position. `environmentsApi.update` now declares the `envVars` field it already sent. - Tests: environment route floor coverage (envVars-only accepted, mixed bodies rejected, legacy rows still blanked and locked, local hidden and 404 under the flag, self-hosted unchanged), an embedded-postgres service test pinning that boot reconciliation never touches tenant env vars, resolver redirect/fail-closed cases, managed-environments default-stamping cases, and UI lib/settings tests. ## Verification - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/environment-routes.test.ts src/__tests__/environment-service.test.ts src/__tests__/execution-workspace-policy.test.ts src/services/managed-environments.test.ts` — all pass. - `pnpm --filter @paperclipai/shared exec vitest run` — 425 pass (catalog/schema default parity is pinned by an existing test). - `pnpm --filter @paperclipai/ui exec tsc --noEmit` and the affected UI suites (CompanyEnvironments, InstanceExperimentalSettings incl. card-order test, new lib test) — all pass. - Full workspace `pnpm test`: 3,414 passed. 17 files report failures on this machine; the identical 17 fail on a clean `origin/master` worktree in the same environment (git-worktree/skills/embedded-postgres environment dependencies and plugin-SDK zero-test collections). One additional file (`issue-monitor-scheduler.test.ts`) failed one timing-sensitive test in one of two full-suite runs and passes 7/7 in isolation on this branch — a flake in a domain this diff does not touch. The branch introduces no new failures. - Self-hosted zero-delta: every new behavior is gated on the cloud-managed instance check or the new flag, which defaults to false in schema and catalog; pinned by the "does not floor platform-marked rows on self-hosted instances" and flag-off tests. ## Risks - Behavior is opt-in twice over: the write-floor exception applies only to rows the managed-config provisioner stamps, and the hiding/forcing applies only when `enableManagedSandboxOnly` is on (default false everywhere). Self-hosted instances see no change. - The env-vars echo is scoped to the generalized managed sandbox row; legacy kubernetes-marker rows keep the blanket floor because pre-generalization builds may have written platform values there. - Fail-closed run selection means a managed instance with the flag on and an archived managed row (provider plugin down) refuses runs with a precise error instead of running locally. That is the intended posture. ## Model Used Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use via Claude Code CLI. ## 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 |
||
|
|
145d86911b |
Remove decision training UI (#11225)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The Decisions desk shows work that needs an operator response. > - It also exposed decision-training actions and a separate training library. > - Paperclip does not plan to use these training surfaces now. > - Keeping inactive controls makes the Decisions workflow harder to scan. > - This pull request removes the training UI and keeps the backend snapshot contract unchanged. > - The benefit is a smaller and clearer Decisions workflow without a data migration. ## Linked Issues or Issue Description **What existing behavior does this improve?** The Decisions desk currently exposes training controls, training state, and a separate training library route. **Subsystem affected** `ui/` — React and Vite board UI. **Current behavior** Operators can open a training library from the Decisions toolbar. They can also mark a decision for training from rows and inspect the result in a drawer. **Proposed behavior** Remove the training controls, badges, drawer, library pages, and routes from the Decisions UI. Keep the server APIs and stored training examples unchanged. **Reason and benefit** The product does not plan to use decision training now. Removing the unused surfaces reduces Decisions UI noise and avoids presenting a workflow that operators should not use. **Breaking changes** The `/decisions/training` UI routes are no longer registered. Existing server endpoints and stored decision-training data remain compatible. ## What Changed - Removed decision-training controls and state from Decisions toolbars, rows, queue pages, and shelves. - Removed the training drawer, library, inspector, API client, helpers, query keys, and routes. - Added route and row regressions that assert training UI does not return. - Updated the Decisions Storybook description to match the available controls. ## Verification - `pnpm exec vitest run ui/src/App.test.tsx ui/src/components/AttentionQueueRow.test.tsx` — 32 tests passed. - `pnpm check:token-gates` — all gates clean. - `pnpm -r typecheck` — passed. - `pnpm build` — passed. - `pnpm test:run` — server and UI partitions passed. The CLI partition had one environment-only failure because this agent runtime injects static AWS credentials. The exact CLI file passed all 8 tests when those credential variables were unset. - Searched `ui/src` and `ui/storybook` for the removed training routes, drawer, library, badges, and actions. Only negative regression assertions remain. ## Risks - Low implementation risk. This change deletes UI-only entry points and does not change the database or server APIs. - Saved training-page URLs no longer render a board route. This is the intended behavior. > 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, model `gpt-5.6-sol`, with `xhigh` reasoning. The runtime did not expose the context-window size. The agent used repository tools, shell execution, and automated tests. ## 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> |
||
|
|
45dfb183b6 |
fix(ui): add undo action to inbox archive toast (#11220)
## Thinking Path > - Paperclip helps operators supervise AI-agent work. > - The Mine inbox keeps tasks that need an operator's attention in one place. > - Operators can archive a task from its detail page after they finish triage. > - That action is easy to select accidentally and did not offer immediate recovery. > - This pull request adds Undo to the archive success toast and keeps inbox caches consistent. > - The benefit is fast recovery without searching for or reopening the task. ## Linked Issues or Issue Description **What happened?** Archiving a task from the Mine inbox removed it and showed a success toast with no recovery action. **Expected behavior** The success toast should offer Undo. Selecting Undo should restore the task through the existing unarchive API while preserving a consistent inbox view. **Steps to reproduce** 1. Open a task from the Mine inbox. 2. Select the archive action. 3. Observe that the task leaves the inbox and the success toast has no Undo action. **Paperclip version or commit** Reproduced on `master` before this change. **Deployment mode** Local dev, built from source. Related prior work: #9931 and #10668. ## What Changed - Add an Undo action to the successful inbox archive toast. - Optimistically restore the task in captured inbox query caches before the unarchive request completes. - Cancel in-flight inbox fetches and clear the local archive guard so stale responses cannot hide the restored task. - Reapply the archive guard and remove the cached task if the unarchive request fails. - Add regression tests for successful Undo, the in-flight cache race, and failed Undo rollback behavior. ## Verification - `pnpm exec vitest run ui/src/pages/IssueDetail.test.tsx ui/src/lib/inboxArchiveCache.test.ts` — 51 tests passed on the final head. - `pnpm check:token-gates` — passed. - `pnpm -r typecheck` — passed. - `pnpm build` — passed. - `pnpm test:run` — the general server and UI groups passed. One CLI doctor assertion detected injected host AWS credentials and passed all 8 tests with those unrelated variables unset. A task-watchdog scheduler test also passed all 18 tests in isolation after one full-suite timing failure. ## Risks Low risk. The change uses the existing unarchive endpoint and inbox cache helpers. Undo failure returns the task to its archived state, shows an error toast, and invalidates the inbox queries for server reconciliation. > 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, model ID GPT-5. The service manages the context window. Reasoning, tool use, and code execution were enabled. ## 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> |
||
|
|
815e49bb7c | feat: make chat-style tasks the default experience (#11101) | ||
|
|
c4abecb2c4 |
fix(skills): refresh project folders in place (#11066)
## Thinking Path > - Paperclip helps operators manage agent skills across a company. > - The installed skills view groups project-backed skills into folders. > - The view showed two folder creation controls and only offered a global project scan. > - Operators need one clear folder action and a refresh action for the selected project. > - This pull request keeps folder creation in the folder rail and adds a scoped project refresh. > - The benefit is a calmer skills view and faster, more precise project skill updates. ## Linked Issues or Issue Description No public GitHub issue exists for this focused UI bug. **What happened?** The installed skills view repeated the folder creation action in the toolbar. A selected project folder also had no way to refresh only its own project skills. **Expected behavior** The folder rail must own folder creation. A selected project-backed folder must offer a refresh action that scans only that project and refreshes the skill and folder queries. **Steps to reproduce** 1. Open the installed skills view for a company with project-backed skill folders. 2. Select a project folder. 3. Observe the duplicate folder action and the absence of a project-scoped refresh action. **Paperclip version or commit** Reproduced before this two-commit fix on `master`. **Deployment mode** Local development with `pnpm dev`. ## What Changed - Removed the duplicate toolbar folder creation button when the folder rail exists. - Preserved the toolbar folder action when no folder rail exists. - Added a refresh action beside the breadcrumb for a selected project-backed folder. - Passed the selected project ID to the project scan API. - Refreshed both the installed skill list and skill folder data after scans. - Added component tests for compact folder creation, the empty-folder fallback, and scoped project refresh. ## Verification - `pnpm exec vitest run ui/src/pages/CompanySkills.test.tsx` — 20 tests passed. - `pnpm check:token-gates` — passed with all three gates clean. - `pnpm -r typecheck` — passed. - `pnpm build` — passed. - `pnpm test:run` — the server and UI stages passed 7,475 tests. The CLI stage then found one environment-sensitive AWS doctor assertion because this agent runtime injects static AWS credentials. - `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm exec vitest run cli/src/__tests__/secrets.test.ts --project paperclipai` — all 8 tests passed. - GitHub CI — all latest-head checks passed. ## Risks - Low risk. The scoped refresh depends on the existing `project:<id>` folder system key. - The global scan path is unchanged. - There are no schema, migration, API contract, dependency, workflow, or documentation changes. > 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 family. The runtime did not expose a more specific model ID or context-window size. The agent used high-reasoning mode, repository tools, GitHub tools, and code execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [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> |
||
|
|
b58ce27a02 |
fix: isolate execution workspace summaries (#10790)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Paperclip gives operators a summary for each workspace. > - An execution workspace detail page used the parent project-workspace summary slot. > - Two execution workspaces under one project workspace could therefore show the same summary. > - This pull request gives each execution workspace its own summary scope. > - It also limits the summary snapshot and generated issue to that execution workspace. > - The benefit is that a new or parallel execution workspace cannot inherit unrelated status. ## Linked Issues or Issue Description **What happened?** An execution workspace detail page read and refreshed the summary slot for its parent project workspace. Parallel execution workspaces could show the same status and include issues from each other. **Expected behavior** Each execution workspace must have one isolated summary slot. Its generated snapshot must include only issues assigned to that execution workspace. **Steps to reproduce** 1. Create two execution workspaces under one project workspace. 2. Add different issues to each execution workspace. 3. Generate the summary in the first execution workspace. 4. Open the second execution workspace. 5. Observe that the old implementation could reuse the first summary. **Paperclip version or commit** The problem exists on `master` before this pull request. **Deployment mode** The issue affects both local trusted and authenticated deployments. ## What Changed - Added `execution_workspace` to the shared summary-slot scope contract. - Validated execution-workspace ownership and stored generated summary issues on the correct execution workspace. - Limited execution-workspace snapshots to issues with the matching execution workspace ID. - Updated the execution workspace page to use its own summary slot. - Updated Summarizer instructions, routine options, catalog metadata, documentation, and regression tests. ## Verification - `NODE_ENV=test pnpm exec vitest run packages/shared/src/summary-slot.test.ts server/src/__tests__/summary-slots.test.ts ui/src/pages/ExecutionWorkspaceDetail.test.tsx` — 30 focused tests passed; the embedded-Postgres server tests were run outside the process-restricted sandbox. - `pnpm check:token-gates` — passed. - `pnpm --filter @paperclipai/skills-catalog validate` — passed with 17 catalog skills. - [Latest-head GitHub Actions](https://github.com/paperclipai/paperclip/actions/runs/31491475405) — all 22 jobs passed on `beea14cbaf`, including typecheck, build, server/workspace tests, serialized suites, e2e, canary, and aggregate verification. One unrelated adapter cleanup test initially hit an `ENOTEMPTY` temp-directory race; its single permitted rerun passed. - Greptile — 5/5 confidence on `beea14cbaf`, 12 files reviewed, zero comments added, and zero unresolved threads. ## Risks - Low risk. The new scope is additive. - Existing project and project-workspace summary slots keep their current keys and behavior. - A summary generated for an execution workspace now excludes sibling workspace issues by design. > 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 with GPT-5. The deployment does not expose a more specific model ID or context-window value. It used agentic reasoning, repository tools, code execution, and GitHub tooling. ## 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> |
||
|
|
9cdaa5416e |
fix(ui): remember folded inbox subtasks (#11069)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The inbox helps operators scan parent tasks and their sub-tasks > - Operators can fold a parent task to hide its sub-tasks > - The inbox previously forgot that fold state after a page refresh > - This pull request stores the fold state for each company and restores it when the inbox loads > - The benefit is that the inbox keeps the operator's chosen task layout across page refreshes ## Linked Issues or Issue Description **What happened?** The inbox reset every folded parent task after a page refresh. This made all nested sub-tasks visible again. **Expected behavior** The inbox must keep each folded or unfolded parent state after a page refresh. The state must remain separate for each company. **Steps to reproduce** 1. Open the inbox with parent and child tasks. 2. Fold one parent task. 3. Refresh the page. 4. Observe that the child task is visible again without this fix. **Paperclip version or commit** Current `master` before this pull request. **Deployment mode** Local dev and built-from-source deployments. ## What Changed - Added company-scoped local storage helpers for collapsed inbox parent IDs. - Restored the stored parent fold state when the inbox mounts or the selected company changes. - Saved both direct toggle changes and explicit collapse changes. - Added helper tests and an inbox remount regression test for both folded and unfolded states. ## Verification - `pnpm exec vitest run ui/src/lib/inbox.test.ts ui/src/pages/Inbox.test.tsx` — 77 tests passed. - `pnpm check:token-gates` — all gates passed. - `pnpm --filter @paperclipai/ui typecheck` — passed. - `pnpm --filter @paperclipai/ui build` — passed. - `pnpm -r typecheck` — passed. - `pnpm build` — passed. - `pnpm test:run` — 3,521 tests passed and four skipped. One unrelated server test on the current base fails because it reads `heartbeat.scheduling_suppressed` instead of `issue_commented`; the same test fails alone and this pull request changes only inbox UI files. ## Risks - Low risk. The state is local to the browser and scoped by company ID. - Old parent IDs can remain in local storage after tasks are deleted, but they do not affect visible tasks. > 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, exact model ID `gpt-5.6-sol`, with reasoning, tool use, and code execution. The runtime does not expose its context-window size. ## 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> |
||
|
|
4c062a0eb2 |
fix(ui): stop coercing imported agents to the destination CEO adapter (#11192)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Company import lets an operator bring a package of agents into an instance, and each agent declares which adapter runs it (Claude Code, Codex, and so on) > - The export and the server importer preserve each agent's adapter faithfully, but the Import page seeds an adapter override for every agent with the destination CEO's adapter before the user touches anything > - Every imported agent therefore arrives as the CEO's adapter (usually Claude Code) even when the source package holds a mix, and the picker shows the coerced value as if it were the source's, so nothing looks wrong > - This pull request makes the manifest adapter the default, sends overrides only for agents the user actually changed, and replaces the silent coercion with an explicit per-agent fallback warning when the destination truly lacks the source adapter > - The benefit is that a mixed Claude/Codex team imports as a mixed Claude/Codex team, and any real adapter gap is visible instead of silent ## Linked Issues or Issue Description **What happened?** A user imported a company package whose agents were a mix of Claude Code and Codex on the source instance. After the import, every agent was configured as Claude Code. The import preview showed no sign that anything had been changed. Cause: the Import page initializes its adapter-override map by assigning every agent the destination CEO's adapter type and sends that override for every agent, overriding the manifest's per-agent adapter server-side. For imports into a new company, the "CEO adapter" is read from whichever unrelated company is currently selected. **Expected behavior** Imported agents keep the adapter declared in the package. An override is sent only when the operator explicitly picks a different adapter, or when the source adapter is not installed on the destination — and in that case the page must say so per agent, not silently substitute. **Steps to reproduce** 1. On a source instance, create a company with one Claude Code agent and one Codex agent, and export it. 2. Import the package on another instance whose CEO uses Claude Code, changing nothing in the import dialog. 3. Both agents arrive configured as Claude Code; the Codex identity is gone. ## What Changed - The preview no longer seeds adapter overrides; the override map starts empty, and the picker displays each agent's manifest adapter (`ui/src/pages/CompanyImport.tsx`). - `buildFinalAdapterOverrides` sends an entry only when the effective adapter differs from the manifest or the agent's adapter config was edited — untouched agents flow through with no override. - The page fetches the destination's installed adapters (existing `adaptersApi.list()` client). When a manifest adapter is missing or disabled on the destination, only that agent defaults to the CEO's adapter, with a visible amber warning naming both adapters. If the adapters request fails, the page fails open: manifest adapters are kept and no coercion happens. - Tests: untouched mixed-adapter import sends no overrides; a user-changed agent sends exactly one; a missing destination adapter produces the fallback plus rendered warning for that agent only; an adapters-endpoint failure produces no coercion. ## Verification - `npx vitest run ui/src/pages/CompanyImport.test.tsx` — 19 passed (15 pre-existing + 4 new). - `pnpm --filter ./ui typecheck` (`tsc -b`) — clean. ## Risks - Behavior change: users who previously relied on the silent conversion (importing packages that reference adapters they don't have) now get an explicit per-agent fallback with a warning — same outcome, visible. The server's hard rejection of unknown adapter types remains the backstop for API callers. - UI-only change; no server or schema impact. ## Model Used - Claude Fable 5 (`claude-fable-5`) via Claude Code CLI, with extended thinking and tool use (multi-agent implementation with independent verification). ## 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 |
||
|
|
5ca752dc81 |
fix(server): raise company import zip upload limit to 1 GB and make it operator-configurable (#11184)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Company import/export lets an operator move a full company package between instances, with the Import page uploading the package as one compressed `.zip` > - The server caps that upload at 128 MB, and real company packages with attachments now exceed it — imports fail at the preview step > - The failure message tells the user to use the CLI folder import, but that path posts inline JSON capped at 64 MB, so the advice is a dead end for exactly these packages > - This pull request raises the zip upload cap to a 1 GB default, makes it operator-configurable through an environment variable, scales the decompression-bomb guards from the cap in effect, and replaces the misleading hint > - The benefit is that large real-world company packages import successfully, and operators with unusual needs can tune the cap without a code change ## Linked Issues or Issue Description **What happened?** A company import fails at the preview step with `Preview failed: Import package exceeds 134217728 bytes`. The package is a valid Paperclip export. Its compressed size is larger than the 128 MB server cap (one reported package is 257 MB). The error panel suggests the CLI folder import, but that path sends the package as one inline JSON body capped at 64 MB, so it also fails. **Expected behavior** A valid company package of realistic size imports successfully through the Import page. If a package is too large, the error must state the limit clearly and suggest a step that can work. **Steps to reproduce** 1. Export a company with enough attachments to make the compressed package larger than 128 MB. 2. Open the Import page and upload the `.zip`. 3. Click "Preview import". 4. The preview fails with `Import package exceeds 134217728 bytes`. **Deployment mode** Reported from a managed deployment; the limit applies to all deployment modes. ## What Changed - Raise `PORTABLE_ZIP_UPLOAD_LIMIT_BYTES` from 128 MB to a 1 GB default (`server/src/http/body-limits.ts`). - Add the `PAPERCLIP_IMPORT_ZIP_MAX_BYTES` environment override. Invalid or non-positive values fall back to the default. - Scale the zip decompression-bomb guard from the configured cap at the import route: the aggregate inflated ceiling is 4x the cap. The per-entry ceiling stays at 512 MB because V8's string length limit applies to an entry regardless (`server/src/routes/companies.ts`, `packages/shared/src/portability-zip.ts`). - Report the 422 limit error in MB instead of raw bytes. - Replace the "use the CLI folder import for very large packages" hint on preview failure with advice that works: re-export the package without large attachments (`ui/src/pages/CompanyImport.tsx`). - Update the stale comment in `ui/src/lib/import-preflight.ts` that made the same CLI claim. - Add tests for the new default, the env override, and the invalid-override fallback. ## Verification - `pnpm vitest run server/src/__tests__/body-limits.test.ts packages/shared/src/portability-zip.test.ts server/src/__tests__/company-portability-routes.test.ts server/src/__tests__/company-portability.test.ts server/src/__tests__/company-portability-import-batching.test.ts` — all pass. - `pnpm vitest run ui/src/pages/CompanyImport.test.tsx` — passes, including the updated failure-panel copy assertion. - `pnpm typecheck` — clean across the workspace. - Manual: upload a `.zip` larger than the configured cap; the preview fails with `Import package exceeds the 1024 MB upload limit` and the new hint. A package between 128 MB and 1 GB now previews and imports. ## Risks - Peak per-import memory rises with the cap: the upload is buffered in memory and unzipped in one pass. A 1 GB compressed package can use several GB transiently. Imports are instance-admin actions, so the exposure is a deliberate operator action, not anonymous traffic. Operators on small hosts can lower the cap with `PAPERCLIP_IMPORT_ZIP_MAX_BYTES`. - The aggregate bomb guard moves from a fixed 512 MB to 4x the configured cap. It still bounds expansion far below what a decompression bomb needs. - No migration and no API shape change. The 422 message text changes; no code matches on the old text. ## Model Used - Claude Fable 5 (`claude-fable-5`) via Claude Code CLI, with extended thinking and tool use (file edits, local test runs, live-instance inspection). ## 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 |
||
|
|
19be4cf927 |
refactor(a11y): add scope=col to the agent costs table headers (#1789)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The agent detail page has a Costs section. That section renders a data table of per-run spend. > - The table has a header row, but its `<th>` elements carry no `scope` attribute. > - A screen reader uses `scope="col"` to bind each data cell to its column header. Without it, the reader announces a number without telling the user which column it belongs to. > - A table of costs is exactly the case where that hurts. Every cell is a bare figure. > - This pull request adds `scope="col"` to the five header cells in that table. > - The benefit is that assistive technology announces the cost table correctly. The change is markup only, so sighted users see no difference. ## Linked Issues or Issue Description No public GitHub issue covers this. The problem is described in-PR, following the enhancement template. **What existing behavior does this improve?** The Costs table rendered by `CostsSection` in `ui/src/pages/AgentDetail.tsx`. **Subsystem affected** ui/ — React + Vite board UI **Current behavior** The table renders five header cells: Date, Run, Input, Output, and Cost. None of them set `scope`. A screen reader must guess the header-to-cell relationship, so a user hears a value with no column name attached to it. **Proposed behavior** Each header cell sets `scope="col"`. A screen reader then announces the column name together with each cell, so a cost figure is read as part of the Cost column. **Reason and benefit** `scope` is the standard way to associate header cells with data cells in an HTML table. The attribute has no visual effect, so the fix carries no design cost and makes the table usable with a screen reader. **Breaking changes** None. `scope` is a presentational-neutral HTML attribute. No component API, no styling, and no test changes. **Related pull requests** - #2215 proposed the same attribute for the Routines table. It is closed, because that table no longer exists on master. - #1524 and #1522 applied `scope="col"` to other tables. Both are closed. ## What Changed - Added `scope="col"` to the five `<th>` elements in the `CostsSection` table in `ui/src/pages/AgentDetail.tsx`. - Rebased the branch onto current master. - Dropped the original `ui/src/pages/Routines.tsx` hunks. Master rebuilt the Routines page around folder-grouped rows, so the table those hunks targeted no longer exists. - Dropped the original `HintIcon` opacity change. It altered a visible colour, which is out of scope for a markup-only accessibility fix. ## Verification - Run `pnpm --filter @paperclipai/ui typecheck` and `pnpm --filter @paperclipai/ui build`. This is a markup-only change, so a clean type-check and build is the relevant automated signal. - Open an agent detail page and go to the Costs section. Inspect the header row. Each `<th>` now carries `scope="col"`. - Navigate the same table with a screen reader, cell by cell. Each cell is announced with its column name. - Compare the rendered page before and after. It is unchanged, because `scope` has no styling effect. ## Risks Low risk. The change adds one standard HTML attribute to five header cells in a single table. It introduces no code path, changes no component API, and has no visual effect. The worst case is that the attribute is redundant for a reader that already infers the column, which is harmless. ## Model Used Anthropic Claude Opus 5, exact model ID `claude-opus-5`. It ran with extended thinking and repository read/write tools, inside a maintainer-operated triage agent. The model rebased the branch, dropped the two out-of-scope hunks, and wrote this description. The original change was authored by @bluzername, and the model used for that work is not recorded here. ## 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 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` references) - [x] I have considered and documented any risks above - [x] My branch name describes the change and contains no internal ticket id - [ ] I have run tests locally and they pass — not run. This is a markup-only change and the package has no test covering this table. - [ ] I have added or updated tests where applicable — no test added, which is why this PR is titled `refactor:`. - [ ] I have updated relevant documentation to reflect my changes — no documentation describes this markup. - [ ] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work — not checked by the maintainer who rebased this. - [ ] All Paperclip CI gates are green — CI re-runs on this push. - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups — Greptile re-reviews on this push. --------- Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com> |