mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-07 16:11:46 +02:00
0a09e4d975e1e1cd7de65fbd1ca730b9cd9f97ea
1307
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0a09e4d975 |
feat(decisions): add desk workflow and retention (#10672)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The Decisions desk shows work that needs a human decision > - The queue foundation can group and rank decision work > - Operators also need a focused daily view and a safe way to handle old work > - This pull request adds the desk controls, the aging shelf, and reversible retention > - It also binds bulk archive decisions to the exact reviewed item set > - The benefit is a smaller daily queue without lost or orphaned work ## Linked Issues or Issue Description **Subsystem affected** Cross-cutting: server, UI, database, and shared contracts. **Problem or motivation** Decision work can grow into one large company list. Operators need quick queue and date controls. Old items also need a safe retention path that does not delete work. **Proposed solution** Add queue and date controls to the Decisions desk. Compute the aging shelf on the server. Archive idle items after 90 days unless an operator keeps them. Keep archived items searchable and revivable. Notify origin agents in one batch per sweep. Bind bulk archive proposals to a signed, exact item manifest. **Alternatives considered** Client-only aging can drift across browsers and source kinds. Deleting old rows removes audit and recovery paths. An unsigned dynamic bulk query can archive items that the reviewer did not inspect. **Roadmap alignment** This work supports the Work Queues and decision-memory directions in `ROADMAP.md`. It extends the Decisions and attention-feed foundation from #10651. Related earlier work includes #9380, #10010, and #10474. ## What Changed - Added the queue rail, date chips, decide split, triage strip, queue page, and aging shelf UI. - Added server-owned shelf state with per-queue retention overrides. - Added reversible retention state, archive history, and an idempotent notification outbox. - Added the 90-day archive sweeper, Keep exemption, archived feed query, and revive actions. - Added one origin-agent notification per agent and sweep. - Added signed bulk archive proposals with exact-set and version checks. - Persisted queue-exclusion reasons atomically and kept cross-domain source resolution per-item until it has an exact-set transaction contract. - Added API contracts, OpenAPI entries, migration coverage, focused tests, and Storybook screens. ## Verification - `pnpm -r typecheck` - `pnpm test:run` (server: 329 files and 3,453 tests passed; UI: 408 files and 3,362 tests passed) - `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` - Focused retention, attention, decisions, migration replay, startup, and UI API tests. - Complete queue snapshot regression with 51 items across the normal 50-item page boundary. The unmodified CLI test reports one warning assertion in this runtime because the harness injects static AWS credential variables. The isolated test passes when those two variables are removed. ## Risks - The migration adds retention and notification outbox tables. It uses idempotent table, index, and foreign-key creation. - Retention runs on the heartbeat scheduler interval. Compare-and-set version checks prevent stale archive writes. - Bulk archive acceptance fails closed when authority, activity, version, or the reviewed set changes. - Archive is reversible and does not delete source records. > 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.6-sol`. The model used tool calls, code execution, database migration generation, and test execution. The context-window size is not exposed in this 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: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
95d33e1788 |
fix(inbox): archive tasks completed by human users (#10668)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The Mine inbox gives each human user a personal work queue. > - A human user can complete a task from the board. > - The status update did not update the user's inbox archive state. > - The completed task therefore stayed in the user's Mine inbox. > - This pull request archives the task for the human user who completes it. > - Agent completion does not change another user's inbox archive state. > - The benefit is that manual completion removes the task from Mine without an extra action or comment. ## Linked Issues or Issue Description **What happened?** When a human user changed a task status to `done`, the task stayed in that user's Mine inbox. **Expected behavior** The status change should archive the task from that user's Mine inbox. The operation should not add a task comment. **Steps to reproduce** 1. Open a task that appears in Mine. 2. Change the task status to `done`. 3. Return to Mine. 4. Observe that the completed task is still present. **Paperclip version or commit** `master` before this change. **Deployment mode** All deployment modes with a board user and the Mine inbox. **Access context** Board (human operator). ## What Changed - Archive the completed task for the board user who changes its status to `done`. - Persist the status update, inbox archive, and archive audit atomically. - Publish live and plugin activity only after the owning transaction commits, including recovery, decision, and approval completion paths. - Keep agent-driven completion from changing a human user's inbox archive state. - Add database-backed regression tests for the archive, audit, Mine filter, rollback, event publication, recovery, and agent paths. ## Verification - `pnpm exec vitest run server/src/__tests__/inbox-archive-routes.test.ts` passed all 7 tests. - `pnpm exec vitest run server/src/__tests__/issue-recovery-actions.test.ts` passed all 44 tests. - Five directly affected server test files passed all 74 tests; decision and comment-route suites passed all 105 tests. - `pnpm --filter @paperclipai/server typecheck` passed after the final fix. - `pnpm -r typecheck` and `pnpm build` passed. - The initial repo-wide `pnpm test:run` passed 3,223 tests; one unrelated plugin orchestration wake-reason test failed and reproduced in isolation. - All latest-head GitHub Actions gates are green, including all server and E2E shards. - Greptile reviewed the latest commit at 5/5 with no unresolved review threads. - Public GitHub search found no duplicate issue or pull request. ## Risks - Low risk. The behavior only runs on a board user's transition into `done`. - Reopening and completing the task again updates the existing per-user archive row. - Agent status updates do not archive a human user's inbox. - Transactional callers must supply a post-commit activity queue; covered completion paths do so and tests exercise rollback and publication order. - This bug fix does not duplicate planned core work in `ROADMAP.md`. > 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 from the GPT-5 model family assisted with this change. The runtime does not expose the exact serving model ID or context window. Reasoning, repository tools, code execution, GitHub CLI, and Paperclip API access 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> |
||
|
|
dcac49a4fd |
feat(workspaces): defer isolated setup until runtime start (#10653)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Isolated workspaces give each task a safe and reproducible checkout. > - The existing setup cloned the development database before an agent needed to run the app. > - This made worktree creation slower and heavier for tasks that never start a service. > - Runtime services already use one server start path for heartbeat, operator, and startup recovery flows. > - This pull request moves heavy setup to that start path and keeps worktree creation lean. > - The benefit is faster isolated workspace creation with the same reliable runtime setup when a service starts. ## Linked Issues or Issue Description Related pull request: #10652 covers the initial deferred database-seeding slice. This pull request supersedes it with end-to-end runtime provisioning and safe cleanup. **What existing behavior does this improve?** This improves isolated worktree creation, runtime service startup, and isolated instance cleanup. **Subsystem affected** Cross-cutting: CLI worktree setup, server runtime orchestration, shared workspace contracts, and development scripts. **Current behavior** Paperclip seeds an isolated development database during worktree creation. It can also leave an isolated instance directory after workspace teardown. This work happens even when no runtime service starts. **Proposed behavior** Paperclip creates the worktree with a lean eager setup. It runs an idempotent runtime provision command before the first managed service spawn. Concurrent starts share one provision attempt. Teardown removes the isolated instance safely. **Reason and benefit** Many agent tasks only edit and test code. They do not need a running Paperclip instance. Deferring the database seed reduces workspace startup cost while preserving automatic setup for tasks that start the app. **Breaking changes** None. The new runtime provision command is optional. Existing workspace behavior is unchanged when it is absent. ## What Changed - Split Paperclip worktree setup into a lean eager script and an idempotent runtime provision script. - Added `runtimeProvisionCommand` to project, issue, realized workspace, and persisted workspace contracts. - Added a per-workspace provision mutex before local service spawn for heartbeat, operator, and startup recovery flows. - Added a persisted `provisioning` service state and the `workspace_runtime_provision` operation phase. - Kept provision time outside the service readiness timeout and made failed attempts visible and retryable. - Reclaimed isolated instance data during safe workspace teardown. - Serialized deferred database seeding across processes and bound teardown to the instance root captured in persisted workspace metadata. - Added tests for config flow, concurrency, retry, no-op behavior, readiness timing, scripts, CLI commands, and cleanup. - Documented the eager and runtime provisioning contracts. ## Verification - `pnpm -r typecheck` - `pnpm build` - `pnpm test:run` (server: 3,201 passed; UI: 3,345 passed; the CLI phase exposed one environment-sensitive AWS doctor assertion because the 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 -t 'passes AWS doctor checks when non-secret provider config is present'` - Focused runtime tests cover serialized provisioning, retry after stderr failure, absent-command no-op behavior, operation logging, persisted state order, and readiness timeout exclusion. - Focused CLI and cleanup tests cover concurrent seed serialization, stale-lock fail-closed behavior, persisted instance ownership, and rewritten sibling pointers. ## Risks - A faulty runtime provision script blocks service startup. Paperclip records stderr, marks the service failed, and retries on the next start. - Concurrent service requests share an in-process provision attempt, while the seed command uses an atomic filesystem lock across processes. A stale lock fails closed and requires an operator to verify no seed is running before removing it. - Isolated instance cleanup is destructive. The cleanup service validates ownership and path containment before removal. > 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.6-sol`, with agentic reasoning, tool use, and code execution. The service does not expose the 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> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
173d6d2a71 |
Reclaim isolated worktree instances during teardown (#10649)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Paperclip creates isolated instances for server-managed git worktrees. > - The worktree teardown path removes the git worktree but leaves its isolated instance directory behind. > - The leaked directory can retain an embedded PostgreSQL process and database files. > - Teardown must remove only the collision-resistant instance assigned to that exact worktree path. > - This pull request stops the verified embedded PostgreSQL process and removes the guarded instance directory. > - The benefit is complete worktree cleanup without risk to another, default, or live Paperclip instance. ## Linked Issues or Issue Description **What happened?** Closing a server-managed git worktree removed the git worktree and branch, but it left the isolated Paperclip instance directory behind. A live embedded PostgreSQL process could also keep running against that directory. **Expected behavior** Worktree teardown must stop the isolated embedded PostgreSQL process and remove only the instance assigned to that exact worktree. It must refuse mismatched instance IDs and all paths outside `PAPERCLIP_WORKTREES_DIR/instances/`. **Steps to reproduce** 1. Create a server-managed git worktree with a repo-local `.paperclip/.env` file. 2. Start its isolated embedded PostgreSQL instance. 3. Close the execution workspace. 4. Observe that the git worktree is removed but the isolated instance directory remains. **Paperclip version or commit** The bug reproduces on `master` before this change. **Deployment mode** Local development with a server-managed git worktree and embedded PostgreSQL. ## What Changed - Give server-managed worktrees collision-resistant instance IDs derived from their resolved absolute paths. - Capture the repo-local instance pointer before custom teardown commands can remove it. - Require the pointer's instance ID to match the exact worktree-derived ID. - Resolve and validate the instance path against the canonical managed worktree instance root. - Verify and stop the matching embedded PostgreSQL process before directory removal, including process-exit races. - Record successful and refused cleanup operations in the workspace operation log. - Add focused ownership, process-race, path-safety, and runtime integration tests. - Document automatic isolated-instance cleanup for server-managed worktrees. ## Verification - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/workspace-instance-cleanup.test.ts` — 9 tests passed. - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/workspace-runtime.test.ts -t "records teardown and cleanup operations when a recorder is provided"` — 1 test passed and 99 tests skipped. - `node scripts/__tests__/provision-worktree-self-heal.test.mjs` — 4 tests passed. - `bash -n scripts/provision-worktree.sh` — passed. - `pnpm --filter @paperclipai/server build` — passed. - `git diff --check` — passed. ## Risks The main risk is removal of the wrong instance directory. Provisioning assigns a path-derived ID with a SHA-256 suffix, and cleanup requires that exact ID in addition to a safe instance identifier, an absolute configured home, a strict child path, canonical path checks, and a second canonical path check immediately before removal. It refuses legacy or mismatched IDs, symlink escapes, and all paths outside the managed worktree instance root. Cleanup failures become visible warnings and do not delete an unverified path. > 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 the exact model snapshot or context-window size. The agent used reasoning, 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> Co-authored-by: Paperclip <paperclip@paperclip.ing> |
||
|
|
30c49c8327 |
feat(decisions): add queues and prioritized attention feed (#10651)
<!-- 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 control plane for companies of AI agents. > - Operators use the attention feed to find decisions that need action. > - The feed has eleven source kinds, but it has no durable queue or triage state. > - The feed also returns every item and lacks decision deadlines, snooze state, and decision-focused ordering. > - This pull request adds secure queue sidecars and enriches the attention feed with triage data, filters, cursor pagination, and decide-now ranking. > - The benefit is a bounded feed that can show the most urgent decisions first without weakening source visibility rules. ## Linked Issues or Issue Description This pull request replaces the closed [#10634](https://github.com/paperclipai/paperclip/pull/10634). It combines that queue foundation with the dependent attention-feed change as one review unit. **Subsystem affected** Database schema, shared contracts, server authorization and REST APIs, and the UI attention client library. **Problem or motivation** The attention feed can contain hundreds of mixed decision items. Operators cannot group them into durable queues, set a decision deadline, snooze an item, or request a bounded page ordered by urgency. The current client must download the full feed on each refresh. **Proposed solution** Store queue membership and triage state by stable attention identity. Re-authorize each source during queue reads and writes. Enrich attention items with queue, deadline, snooze, expiry, rule, and origin data. Add activity and queue filters, opaque cursor pagination, decide-focused ordering, and a decide-now count. **Alternatives considered** Adding queue fields to every source would duplicate schema and authorization logic across eleven source kinds. Client-only filtering and sorting would still transfer the full feed and would make pagination unstable. **Roadmap alignment** This change improves the core decision-attention surface and operator oversight. It does not implement the separate general-purpose work queue milestone in `ROADMAP.md`. ## What Changed - Added company-scoped queue, membership, triage, and append-only event tables with actor and run provenance. - Added queue CRUD, item membership, starter-rule discovery, and decide-by and snooze endpoints. - Kept source authorization on each queue mutation, read, and count. - Added attention fields for expiry, rule, origin agent, queues, decide-by attribution, and snooze state. - Added activity date filters, queue filters, opaque cursor pagination, and configurable page limits. - Added decide-now ordering by deadline, expiry, severity, and activity. - Added `decideNowCount` and excluded actively snoozed items from the default feed. - Updated the shared and UI client contracts. - Added focused server, route, OpenAPI, and UI client tests. ## Verification - `pnpm exec vitest run server/src/__tests__/attention-service.test.ts server/src/__tests__/decision-queues-routes.test.ts server/src/__tests__/openapi-routes.test.ts ui/src/api/attention.test.ts ui/src/lib/attention.test.ts` (72 tests passed) - `pnpm --filter @paperclipai/db check:migrations` - `pnpm -r --filter @paperclipai/db --filter @paperclipai/shared --filter @paperclipai/server --filter @paperclipai/ui typecheck` - `git diff --check origin/master...HEAD` ## Risks - The migration adds four company-scoped tables and provenance foreign keys. Migration numbering and safety checks pass. - Attention reads can lazily create starter queues and memberships. Inserts are idempotent, audited, and transactional. - Cursor validity depends on the filtered feed. The API returns a clear validation error when the cursor item no longer exists in that feed. - Queue reads re-check source visibility. This favors correct authorization over fewer queries. > 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`. The runtime used agentic reasoning, repository tools, code execution, and test execution. The runtime did not expose the 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> |
||
|
|
14d4db6330 |
fix(inbox): keep passive issue views out of Mine (#10581)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The inbox shows work that a board user owns or has joined > - Issue detail views update a per-user read receipt > - The Mine query treated that read receipt as user participation > - A passive view therefore added the issue to Mine > - This pull request separates passive reads from audited user mutations > - The benefit is a Mine inbox that reflects ownership and real participation ## Linked Issues or Issue Description **What happened?** Opening an issue detail marks the issue as read. The Mine query treated the read receipt as participation. The viewed issue then appeared in Mine even when the user did not change it. **Expected behavior** Viewing an issue can update unread state. A view alone must not add the issue to Mine. Issue creation, assignment, comments, and audited user mutations must add it. **Steps to reproduce** 1. Open an issue that you did not create and that is not assigned to you. 2. Do not comment or change the issue. 3. Open the Mine inbox. 4. Observe that the issue appears in Mine on the previous implementation. **Paperclip version or commit** `90ead239a8` **Deployment mode** Local dev from source. **Additional context** Related approach: #3421 changes Mine to an assignee-only filter. This change keeps participation-based Mine behavior and corrects the participation signal. ## What Changed - Use an explicit audited user-mutation allowlist for Mine participation, including comment cancellation. - Keep passive reads, previews, denied resource requests, and archive bookkeeping out of Mine participation. - Record manual routine reuse as an explicit audited inbox touch instead of a read receipt. - Keep that inbox bookkeeping from satisfying routine activity gates. - Add focused regression coverage for passive views, real mutations, comment cancellation, and manual routine runs. - Document the Mine participation contract. ## Verification - `pnpm exec vitest run server/src/__tests__/issues-service.test.ts -t "does not treat passive issue activity"` - `pnpm exec vitest run server/src/__tests__/routines-service.test.ts -t "touches a (coalesced|skipped active) routine issue|ignores inbox bookkeeping activity"` - `pnpm --filter @paperclipai/server typecheck` - GitHub latest-head CI: all required checks passed, including build, typecheck, server suites, and e2e shards. ## Risks - Low risk. The change affects only the server query that defines user participation in Mine and the manual routine touch signal. - Historical audited user mutations can now qualify an issue for Mine. Passive read and archive actions remain excluded. > 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 the exact deployment ID or context window. Agentic 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> |
||
|
|
e4b0152ca3 |
fix(server): keep the agent invokable when a run fails on a workspace sync conflict (#10660)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents on the same project can share one `shared_workspace` clone, and each sandbox run reconciles its git history back into it > - When histories written by different runs genuinely diverge, reconciliation can hit a real merge conflict — a property of the *workspace*, not of whichever agent happened to run last > - That agent nonetheless finalized into a sticky `error` state, removing a healthy agent from rotation while the workspace stayed broken — and on a shared workspace this serially knocks out every agent that touches it > - This pull request classifies workspace-reconciliation failure signatures as workspace-scoped, so the run still fails with the full message but the agent stays invokable > - The benefit is that one bad workspace state no longer disables agents one by one ## Linked Issues or Issue Description Refs #10645 — this addresses the sticky-agent-error clause of that issue. Workspace run serialization / per-agent worktrees remain tracked there (design sketch on the issue). ## What Changed - New exported `isWorkspaceSyncConflictFailure(message)` matching the reconciliation failure signatures: `merge-tree` conflict ("Failed to merge concurrent remote git histories"), integrate-retry exhaustion ("Failed to integrate concurrent remote git history"), and bundle prerequisite failures ("did not send all necessary objects", "lacks these prerequisite commits"). - Both run-failure finalization paths (adapter returned a failed result; adapter threw) pass `keepIdleOnFailure` for these signatures — the same mechanism already used for provider-quota failures — so the agent finalizes to `idle` instead of `error`. The run itself still fails and carries the full message; nothing about run reporting changes. ## Verification - `pnpm vitest run server/src/__tests__/heartbeat-workspace-session.test.ts` — new signature matrix (4 positive signatures, negatives for unrelated adapter failures and null/empty); 121 tests total. - `cd server && pnpm run typecheck`. ## Risks - Low. The only change is which failure families put the agent into `error`; behavior for every other failure is untouched. A workspace stuck in conflict still fails every run against it (visible on the runs surface) — it just no longer takes agents down with it. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use. No other models involved. ## 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 |
||
|
|
592cade5a6 |
feat(server): trigger the push-capability preflight from the issue's stated PR deliverable (#10659)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Runs that must push to GitHub have a pre-dispatch credential preflight (`push_write_credential_missing`) so the missing token surfaces as a configuration-incomplete blocker instead of a late runtime failure > - The preflight only triggers when the issue mentions the GitHub PR workflow *skill* — routine-created issues and agent-to-agent handoffs rarely do, even when their text literally says "push the branch and open a PR" > - In practice the credential gap then surfaced only after implementation and review were complete, stranding finished work > - This pull request adds a conservative, verb-anchored text heuristic over the issue title and description as a second preflight trigger > - The benefit is that the credential ask reaches the human before any work is burned ## Linked Issues or Issue Description Fixes #10644 (completes the prevention set with #10648, #10650, #10658) ## What Changed - `issueTextImpliesPrDeliverable(text)`: matches verb-anchored deliverable statements — "open/create/raise/submit a (draft) pull request/PR", "push … branch/remote/origin/upstream". Verb anchoring deliberately ignores passing mentions ("the PR merged yesterday", "PR feedback addressed"). - `requiresPushCapabilityPreflight` takes the issue's title+description and ORs the text heuristic with the existing skill-mention trigger; adapter-type and issue gating are unchanged. The run-dispatch call site threads the already-loaded issue text — no extra query. ## Verification - `pnpm vitest run server/src/__tests__/heartbeat-workspace-session.test.ts` — new heuristic matrix (4 positive, 6 negative including null/empty) and preflight-by-text cases (text triggers, passing mention does not, no issue → no preflight); 122 tests total. - `cd server && pnpm run typecheck`. ## Risks - A false positive turns into a configuration-incomplete blocker asking for a GitHub token on an issue that didn't need one — the heuristic is intentionally conservative (verb-anchored) to keep that rare, and the blocker names the exact remediation. - No behavior change for issues that neither mention the skill nor state a PR deliverable. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use. No other models involved. ## 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 |
||
|
|
ddbcf53e31 |
fix(server): refuse agent delegation cycles back to an open ancestor's creator (#10658)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents decompose work by creating child issues assigned to other agents > - When two agents each lack a capability the other assumed (e.g. neither can push to GitHub), each can "resolve" its blocker by delegating the same step to the other: A creates a child for B, B creates a grandchild back for A > - Nothing detects the cycle; the chain of blocked issues grows and no signal reaches the human who could actually fix the capability gap > - This pull request refuses agent-initiated child creation when the child's assignee is the creator of a still-open ancestor in the same chain — a mechanical, semantics-free cycle signal > - The benefit is that the hot-potato dies at creation time with an actionable error instead of growing a dead chain ## Linked Issues or Issue Description Fixes #10642 (write-time counterpart: #10648 refuses assignment to paused agents; the credential-gap *preflight* side is tracked separately in #10644) ## What Changed - `issueService.findOpenAncestorCreatedByAgent(parentIssueId, agentId, {maxDepth})`: bounded walk up the parent chain looking for a still-open (not done/cancelled) ancestor created by the given agent. - Agent-initiated issue creation with a parent (both the create-with-`parentId` route and `POST /issues/:id/children`) now refuses with a structured 409 (`code: delegation_cycle`, naming the ancestor) when the new child would be assigned to the agent that created a still-open ancestor: that agent delegated the work into this chain, so assigning it back is a cycle. The message states the alternatives — complete the work, leave the child unassigned, or escalate to a board operator. - Deliberately unaffected: human actors (deliberate re-routing is their call), closed ancestors (re-engaging the creator of finished work is normal), and accepted-plan decomposition (its children come from a human-approved plan). ## Verification - `pnpm vitest run server/src/__tests__/issue-assignee-invokability-routes.test.ts` — cycle refused with 409 and no create call; the same child allowed when no open ancestor matches; board actors never consult the guard. - `pnpm vitest run server/src/__tests__/issues-service.test.ts` — new embedded-Postgres coverage: ancestor found through the chain, closed ancestors ignored, depth bound honored (114 total). - `pnpm vitest run server/src/__tests__/issue-create-deduplication-routes.test.ts server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts` — unchanged (79). - `cd server && pnpm run typecheck`. ## Risks - Low-to-moderate: a new 409 for a creation shape that previously succeeded. The blocked shape (agent assigns new work to the creator of an open ancestor) is the cycle signature; the legitimate "hand a subtask to the parent's assignee" pattern is unaffected because it keys on assignee, not creator. Watchdog and plan-decomposition flows are exempt or unaffected as described. - The walk adds at most `maxDepth` (10) single-row lookups per agent child creation with an assignee. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use. No other models involved. ## 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 |
||
|
|
a0dbe21045 |
fix(server): stand down recovery while an operator-cancelled run is the latest activity (#10656)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Operators can cancel a running agent from the board when a run is unwanted — most acutely while cleaning up a runaway loop > - The recovery machinery treats a cancelled run like any other unsuccessful terminal run: the stranded-issue sweep classifies the issue as stranded, creates a recovery action, and wakes the agent again > - So cancelling runs to stop a loop *fed* the loop: each operator cancel spawned a recovery action that re-woke the agent the operator had just stopped > - This pull request stamps board-initiated cancellations with operator attribution and makes the sweep stand down while such a run is the issue's latest activity > - The benefit is that an operator's cancel is final until something new happens, instead of being fought by automation ## Linked Issues or Issue Description Fixes #10646 ## What Changed - `POST /heartbeat-runs/:runId/cancel` (board-only) now cancels with an explicit reason ("Cancelled by a board operator") and stamps `resultJson.cancelledByActorType: "user"` / `cancelledByUserId`. - `reconcileStrandedAssignedIssues` gains an early stand-down: when the issue's latest run is operator-cancelled (the new stamp, or the existing `operator_interrupted` error code from interrupt-by-comment), the issue is skipped entirely — no recovery action, no wake — and counted in a new `operatorCancelExempted` result field. The exemption is inherently self-limiting: any newer run or wake supersedes it because the gate only looks at the *latest* run. - System cancellations without operator attribution (lease expiry, assignee changes, terminal-status cancels, pause holds) keep today's recovery behavior unchanged. ## Verification - `pnpm vitest run server/src/__tests__/issue-recovery-actions.test.ts` (embedded Postgres) — 3 new cases: a stamped operator cancel produces zero recovery actions and zero wakes; an `operator_interrupted` cancel likewise; an unattributed system cancel still flows into pre-existing recovery (wake observed), proving the stand-down is scoped to operator attribution. - `pnpm vitest run server/src/__tests__/heartbeat-process-recovery.test.ts server/src/__tests__/issue-scheduled-retry-routes.test.ts` — unchanged (109 tests). - `cd server && pnpm run typecheck`. ## Risks - Low. The only suppressed behavior is recovery of runs a human explicitly cancelled from the board; everything else is byte-identical. If an operator cancels and walks away, the issue stays quiet until any new activity — which is the intent (the operator owns the next step). ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use. No other models involved. ## 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 |
||
|
|
0f12721ee9 |
fix(server): let board users cancel issues with an active review stage (#10655)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The server enforces an issue execution policy. It gates status changes while a review or approval stage is active. > - A board user could not cancel a task while an agent reviewer held the active stage. The API returned "Only the active reviewer or approver can advance the current execution stage". > - Board users own the board. They must always be able to edit and cancel any task. > - This pull request adds a board override to the execution stage transition. A board cancel clears the pending stage state and proceeds instead of raising an error. > - The benefit is that board users can always stop work, even while a review is pending or the stored stage state has drifted. ## Linked Issues or Issue Description No public GitHub issue exists for this bug. Description follows `bug_report.yml`: **What happened?** A board user set a task to `cancelled` while the task had an active reviewer stage held by an agent. The PATCH failed with "Task Update Failed... only the assigned approver or reviewer...". The same failure occurred when the stored stage state had drifted: the server silently forced the task back to `in_review` instead of honoring the cancel. **Expected behavior** A board user can always edit and cancel any task. A board cancel must clear the pending review stage and apply the requested status. **Steps to reproduce** 1. Create a task assigned to agent A with agent B configured as reviewer in the execution policy. 2. Let agent A hand the task off so the review stage becomes active. 3. As the board user, set the task status to Cancelled. 4. The update fails with the reviewer-only error. Related work: #5487 touches the execution-policy approver UI. It does not address the board cancel path. ## What Changed - `server/src/routes/issues.ts`: the issue PATCH route now passes `allowBoardOverride` when the actor is a board user. - `server/src/services/issue-execution-policy.ts`: when `allowBoardOverride` is set and the requested status is not `in_review` or `in_progress`, the transition clears `executionState` and proceeds. This applies both while a stage decision is pending and when the stage state has drifted, so a board cancel is no longer rejected or silently flipped back to `in_review`. - Reviewer gating is unchanged for everyone else: a board user who is the active participant still uses the normal approve / request-changes flow, and non-participant agents still receive the 422 guard. - Assignee-only board updates on an `in_review` task keep the stage state coherent: reassigning to an eligible stage participant re-pends the stage with them as the current participant, while reassigning to a non-participant (or unassigning) dissolves the review back to `in_progress` instead of persisting an `in_review` issue with no execution state or an ineligible participant. - New unit tests and route tests cover board cancellation of an active review stage and of a drifted pending review, plus reviewer swap, non-participant reassignment, and unassignment during an active review. ## Verification - In `server/`: `pnpm exec vitest run src/__tests__/issue-execution-policy.test.ts src/__tests__/issue-execution-policy-routes.test.ts` — 2 files, 73/73 tests pass on top of current `master`. - In `server/`: `pnpm run typecheck` passes. ## Risks - Low risk. The override branch runs only for board actors and only for target statuses other than `in_review` and `in_progress`. Cancelling clears `executionState`, so a later reopen starts from a fresh stage state. Agent-facing flows and reviewer gating are unchanged. ## Model Used - Claude Fable 5 (Anthropic), model ID `claude-fable-5`, running in Claude Code (Claude Agent SDK) with extended thinking and 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 |
||
|
|
ada47be764 |
fix(server): refuse agent-initiated issue assignment to paused agents (#10648)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents can create and assign issues to other agents, and commonly escalate to their org-chart manager (`reports_to`) when they hit something outside their authority > - Issue assignment already refuses terminated and pending-approval assignees, but accepts paused assignees from any actor > - A paused agent never runs, so agent-initiated escalations to a paused manager become invisible dead letters — accepted silently, never picked up, never surfaced > - This pull request refuses paused assignees when the assigning actor is an agent, at the single normalization helper all four assignment paths flow through > - The benefit is that agent-routed work can no longer silently vanish into a paused agent's queue ## Linked Issues or Issue Description Fixes #10641 ## What Changed - `normalizeIssueAssigneeAgentReference` (used by issue create, both child-create routes, and issue update) now throws a 409 when an **agent** actor assigns to a **paused** agent, with a message naming the alternatives: assign an invokable agent, leave the issue unassigned, or escalate to a board operator. - Board/user actors are unchanged and may still assign to paused agents deliberately — the pause state is visible in the UI, and staging work for a later unpause is a legitimate workflow. Terminated / pending-approval / invalid-org-chain refusals are unchanged for all actors. - This matches the existing precedent for watchdogs ("Cannot assign watchdog to an agent that is not invokable") using the same conflict-error shape. ## Verification - `pnpm vitest run server/src/__tests__/issue-assignee-invokability-routes.test.ts` — new coverage: agent PATCH → paused assignee 409 (no update call), agent child-create → paused assignee 409 (no create call), agent assignment to an invokable agent still 200, board assignment to a paused agent still 200. - Neighboring suites unchanged: `issue-update-comment-wakeup-routes`, `issue-agent-mutation-ownership-routes`, `issue-create-deduplication-routes`, `issue-watchdogs-routes` (97 tests). - `cd server && pnpm run typecheck`. ## Risks - Low. The only behavior change is a new 409 for agent actors assigning to paused agents — previously a silent dead-letter. Agents that relied on this (escalation flows) now get an actionable error instead; human workflows are untouched. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use. No other models involved. ## 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 |
||
|
|
27f8c8dbcf |
feat(server): cap agent review rounds and escalate exhausted reviews to the responsible human (#10650)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Execution policies let one agent implement and another review, cycling through changes-requested → addressed rounds > - Nothing bounds that cycle: no round counter, no escalation, no termination signal — two agents can ping-pong indefinitely, especially when the review's success criteria drift to something the implementer cannot satisfy > - On a real multi-agent instance this produced 6+ unattended rounds (~8 runs) that continued even after the human had merged the PR under review > - This pull request counts consecutive agent-initiated changes-requested rounds and, at a configurable cap, hands the still-pending review to the responsible human instead of bouncing back to the implementer > - The benefit is that unattended review loops terminate in a human decision instead of burning runs forever ## Linked Issues or Issue Description Fixes #10643 ## What Changed - `IssueExecutionState.changesRequestedCount` (schema + type, default 0): consecutive agent-initiated changes-requested rounds on the current stage. Carries through executor resubmissions, resets to 0 on approval, and resets when a **human** makes the changes-requested decision — the cap targets unattended agent↔agent ping-pong, never human review. - `IssueExecutionPolicy.maxReviewRounds` (optional, 1–50, default null → server default `DEFAULT_MAX_REVIEW_ROUNDS = 3`). - At the cap, the transition records the reviewer's changes-requested decision as usual but keeps the stage **pending** with the responsible human (`responsibleUserId`, falling back to `createdByUserId`) as the participant: the issue is assigned to that human and the pending review surfaces through the existing attention/review UI. The human then approves, requests changes (resetting the counter and handing back to the implementer), or re-scopes. - The escalated hold is sticky: transitions from anyone other than the escalated human no longer re-select a configured agent participant for the stage (which would have silently undone the escalation on the next unrelated PATCH). The escalated human's own decisions flow through the normal participant decision branch. - Issues with no responsible human keep today's hand-back behavior; the counter still accumulates so operators can see the churn. ## Verification - `pnpm vitest run server/src/__tests__/issue-execution-policy.test.ts` — 8 new cases: round counting on hand-back, count carried through resubmission, escalation at the default cap, sticky hold across unrelated transitions, human changes-requested resets the counter, human approval completes the stage, no-responsible-human fallback, and a `maxReviewRounds: 1` policy override. - `pnpm vitest run server/src/__tests__/issue-execution-policy-routes.test.ts` and the full `@paperclipai/shared` suite (387 tests) — schema additions are backward compatible (both fields optional with defaults; persisted states without the counter parse as 0). - `pnpm --filter @paperclipai/shared exec tsc --noEmit` and `cd server && pnpm run typecheck`. ## Risks - Behavior change: an agent-only review loop that previously ran forever now escalates to a human after 3 agent rounds by default. Instances that want longer loops can set `maxReviewRounds` per policy. Flows where a human participates are unaffected (human decisions reset the counter). - Escalation requires a `responsibleUserId`/`createdByUserId` on the issue; without one, behavior is unchanged. - Persisted execution states from before this change parse with `changesRequestedCount: 0` — no migration needed. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use. No other models involved. ## 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 |
||
|
|
a388ea1cac |
Render workspace-ready comments as compact system notices (#10636)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The server posts a workspace-ready comment after it prepares an execution workspace or runtime service. > - The full Markdown card uses too much space in the task thread. > - The existing system-notice presentation can show the same comment as a compact row. > - The server must keep the original body for API clients and expanded details. > - This pull request adds structured presentation data at both workspace-ready call sites. > - The benefit is a quieter thread with no data loss and no migration. ## Linked Issues or Issue Description **What existing behavior does this improve?** The server posts the workspace-ready task comment after workspace provisioning and adapter-managed runtime startup. **Current behavior** The task thread shows a full Markdown comment with strategy, branch, working directory, services, and warnings. Long branch names can make this card dominate the thread. **Proposed behavior** Show the comment as a compact system-notice row. Expand the row in place to show the original Markdown body and structured workspace, service, and warning details. Use a warning tone and open the details by default when warnings exist. **Reason and benefit** The same workspace data is available in the task properties. The compact row keeps the thread easy to scan while it preserves the full comment for API consumers and expanded inspection. **Breaking changes** None. The comment body stays unchanged. Existing comments without presentation data keep their current rendering. ## What Changed - Added workspace-ready presentation and metadata builders. - Added structured workspace, service, reuse, and warning details. - Wired both workspace-ready comment paths to send presentation and metadata options. - Added focused unit and heartbeat-level tests. Collapsed notice:  Expanded notice:  ## Verification - `pnpm exec vitest run server/src/services/workspace-runtime-ready-comment.test.ts server/src/__tests__/heartbeat-workspace-ready-comment.test.ts` — 8 tests passed. - `pnpm --filter @paperclipai/server typecheck` — passed. - `pnpm -r typecheck` — passed. - `pnpm check:token-gates` — passed. - `pnpm build` — passed. - `pnpm test:run` — 300 server files and 405 UI files passed. One unrelated CLI AWS doctor test detected static credentials from the runner environment. The same test passed with `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` removed. - Built the existing system-notice Storybook story and captured both compact and expanded states. - GitHub CI — all latest-head gates passed. One signoff-policy e2e shard hit a transient checkout-state race and passed on its single rerun. ## Risks Low risk. This change only adds optional comment presentation data in two server paths. The body, database schema, API contract, and old comments remain unchanged. Incorrect metadata would affect only expanded structured details; focused tests cover the shape and both warning states. > 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 (`gpt-5`, the exact snapshot and context-window size are not exposed by this runtime). The agent used reasoning, repository tools, code execution, and visual 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 - [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> |
||
|
|
ee851fc364 |
feat(heartbeat): expose cache-adjusted run cost (#10349)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The heartbeat service is the control plane that runs agents through adapters and records each run's usage and cost in the finance/cost ledger > - Adapters report a provider cost (`costUsd`), but there is no way to represent the provider-billed cost *after* prompt-cache discounts, so cache-heavy runs are priced wrong and some paid runs end up exported with a zero/null cost > - A benchmark comparing Paperclip-orchestrated runs against direct harness invocation of the same tasks measured 1.5–3.1× higher apparent USD per pair, largely because cache-discounted billing was not represented in the exported cost data > - This pull request adds an optional `cacheAdjustedCostUsd` field to `AdapterExecutionResult` and a `resolveCacheAdjustedCostUsd` helper in the heartbeat service that prefers the explicit cache-adjusted figure and falls back to the reported `costUsd`, persisting it into the run's usage/ledger JSON > - The benefit is that paid runs are no longer exported as zero/null cost and cache-heavy runs can be priced correctly, so operators comparing orchestrated vs. direct costs see real numbers ## Linked Issues or Issue Description No single existing public issue covers this exactly; closely related cost-reporting issues: - Refs #8947 — hermes adapter never reports usage/cost to Paperclip, so budget limits never trigger - Refs #6716 — hermes_local cost/usage capture returns zero - Refs #3320 — expose per-run token counts in activity log and dashboard **Problem (feature-request form):** Adapters can only report a single `costUsd`. Providers with prompt caching bill less than the nominal token cost, and the heartbeat cost accounting has no field for the cache-adjusted billed amount. As a result, cache-heavy paid runs are either priced at the undiscounted figure or, when the adapter withholds the ambiguous number, exported as zero/null. **Proposed solution:** an explicit optional `cacheAdjustedCostUsd` on the adapter execution result, resolved server-side with a safe fallback to `costUsd`. **Alternatives considered:** recomputing cache discounts server-side from token counts (rejected: provider pricing tables drift and cache billing rules are provider-specific; the adapter is the source of truth). ## What Changed - `packages/adapter-utils/src/types.ts`: added optional `cacheAdjustedCostUsd?: number | null` to `AdapterExecutionResult`, with a doc comment on adapter expectations - `server/src/services/heartbeat.ts`: added exported `resolveCacheAdjustedCostUsd()` (explicit cache-adjusted value wins when a finite non-negative number; otherwise falls back to a finite non-negative `costUsd`; otherwise `null`), and consistently uses the resolved billed value for ledger cents, cost status, and run usage JSON - `server/src/__tests__/heartbeat-cost-accounting.test.ts`: added unit coverage for explicit precedence, fallback, invalid values, adjusted-only pricing, and discounted ledger billing ## Verification - `pnpm exec vitest run server/src/__tests__/heartbeat-cost-accounting.test.ts` — 1 file, 7 tests passed - `pnpm --filter @paperclipai/adapter-utils typecheck` — passed - `pnpm --filter @paperclipai/server typecheck` — passed - GitHub Actions on head `dc9c3830bf694b9afb3b27c5c8c36bff38e7fdcb` — all 26 checks clean/skipped; one unrelated E2E checkout-contention flake passed on its single failed-job rerun - Greptile review on the same head — 5/5 with zero unresolved threads ## Risks - Low risk: the field is optional and additive; when absent, behavior falls back to the existing `costUsd` path - Ledger/usage JSON gains a new optional `cacheAdjustedCostUsd` key — consumers that strictly validate keys would need to tolerate it (usage JSON is already open-shaped) - No migrations, no API-breaking 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 - Implementation: Anthropic Claude Fable 5, model ID `claude-fable-5`, standard context window, agentic coding mode with shell/file tool use - PR preparation and verification: OpenAI Codex on GPT-5 (the runtime did not expose a more specific serving snapshot or context-window value), reasoning mode with shell and GitHub 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 (doc comment on the new field; no user-facing docs affected) - [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> |
||
|
|
c185e64b77 |
feat(ui): chat-style task view behind an experimental flag (#10606)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Operators spend most of their time on the issue detail page. They talk to the assigned agent there through comments. > - The current page reads as a ticket form. The thread sits below properties, the composer sits mid-page, and live agent activity renders as dense transcript logs. > - Talking to an agent is a conversation. A chat-first layout matches that mental model better than a ticket form. > - A layout change this large must not disrupt current users. It needs a safe opt-in path and full parity with the existing thread features. > - This pull request adds a chat-style task view behind a new "Chat-Style Tasks" experiment toggle. The flag is off by default and the existing page is unchanged when it is off. > - The benefit is a focused, readable conversation with the agent: live tool activity folds into compact summaries, the composer stays at the bottom, and properties, plan, and artifacts move into header tabs. ## Linked Issues or Issue Description Refs #49 (chat with agents is a much-wanted feature). Related PRs found in the dedup search: - #4489 — an earlier, closed attempt to promote the conversation to the primary surface on issue detail. This PR is a fresh, flag-gated take on the same goal. - #8837 — an open PR that proposes a two-column task layout. It restructures the same page but keeps the ticket paradigm; this PR is orthogonal because it is opt-in and chat-first. **Subsystem affected** UI (issue detail page). **Problem or motivation** The issue detail page presents agent conversations as a ticket: properties first, thread below, composer in the middle of the page, and raw transcript noise during live runs. Users who mainly converse with their agents must scroll past chrome to follow the conversation, and live activity is hard to read. **Proposed solution** An opt-in chat-style view of the issue detail page, gated by a new "Chat-Style Tasks" experiment toggle in Settings → Experimental. With the flag on, the thread fills the center pane, the composer docks to the bottom of the viewport, Properties / Plan / Artifacts become header tabs, live turns show a status pill with the current tool action and elapsed time, and settled turns collapse to a "Worked · N tools" summary that expands into per-tool rows. With the flag off, nothing changes. **Alternatives considered** Restyling the existing layout in place (rejected: too disruptive without an opt-out), and a separate chat page beside the issue page (rejected: splits the task's single source of truth). A per-request lab page (`/task-chat-lab`, dev-only) was kept for design iteration instead. **Roadmap alignment** ROADMAP.md "CEO Chat" wants lighter conversations that still resolve to real work objects. This PR keeps the core task-and-comments model — it only changes presentation, opt-in — so it does not duplicate that planned work. ## What Changed - New `enableTaskChatRedesign` instance setting, exposed as a "Chat-Style Tasks" experiment card in Settings → Experimental (shared feature catalog, validators, server instance-settings service, and UI settings page). - New `ui/src/components/task-chat/` component family: chat thread with turn grouping, agent reply bubbles, live status pill, collapsible turn summaries with per-tool rows, plan tab with a sticky CTA action bar, inline interaction cards, per-request mode chips, and a bottom-docked composer. - A shared tool taxonomy (`tool-taxonomy.ts`) maps tool names to verbs and icons; the status pill, tool rows, and the classic transcript view all use it. - A transcript adapter converts stored run logs into chat turns; it dedupes tool-call updates by `toolUseId` so tool counts match the expanded rows, and it keeps a tool row's first real name when later generic updates arrive. - Composer: posts on Cmd/Ctrl+Enter, supports image paste with object-URL thumbnail previews (revoked on clear/unmount), and uploads through the issue attachments route. - `IssueDetail.tsx`: with the flag on, pane tabs move to the header bar, the header is not sticky, and the chat fills the center; with the flag off, the previous layout renders unchanged. - Motion tokens for the new animations live in `ui/src/index.css` with a `motion-tokens.ts` catalog and a test that keeps the two in sync (the catalog now also covers the shared enter/exit/swap tokens that the decision/quicklook block declares). - A dev-only `/task-chat-lab` page with fixtures and a tweak panel for motion tuning. ## Verification - `pnpm typecheck` — clean across the workspace. - `pnpm check:token-gates` — 3/3 CLEAN. - `cd ui && pnpm vitest run` — 3,344 of 3,345 tests pass locally. The one failure is `IssueProperties.test.tsx` monitor-row time formatting, which is timezone-sensitive: it also fails on unmodified `origin/master` in a non-UTC timezone and passes with `TZ=UTC`. It is not related to this change. - `cd server && pnpm vitest run src/__tests__/instance-settings-service.test.ts` — 21/21 pass (covers the new setting). - Manual: start the dev server, open Settings → Experimental, enable "Chat-Style Tasks", and open any issue. The thread fills the page, the composer docks to the bottom, and Properties / Plan / Artifacts appear as header tabs. Assign an agent and comment to watch a live run: the status pill shows the current tool action with elapsed time, and the finished turn folds into a "Worked · N tools" summary. Disable the toggle and confirm the classic page is unchanged. - Visual snapshot baselines are intentionally not updated: per `doc/design/DECISION-SHEET.md`, "Per-change snapshot verification demoted to dormant (Jul 13 2026)". ## Risks - The flag-off path goes through the same `IssueDetail.tsx` file, so a regression there would affect current users. Mitigation: the classic markup renders through the same components as before behind explicit flag conditionals, and the full UI suite passes. - The transcript adapter interprets stored run-log formats, including legacy entries without `toolUseId`. Malformed logs degrade to generic tool rows rather than crashing. - The new view changes no server behavior other than one additive instance setting; it is additive and default-off. Overall risk with the flag off is low. ## Model Used - Claude (Anthropic), model id `claude-fable-5` (Claude Fable 5), extended thinking enabled, agentic tool use (file editing, shell, test execution) via Claude Code / Claude Agent SDK. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
92c3d9f0d9 |
fix(server): stop requiring PAPERCLIP_DECISION_SIGNING_SECRET at startup (#10594)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The server signs decision specifications with an HMAC > - PR #10010 made `PAPERCLIP_DECISION_SIGNING_SECRET` a hard startup requirement > - Existing installs do not have this new environment variable > - Those installs now stop during startup > - This pull request uses a secure persisted instance key when the override is absent > - The benefit is that existing installs start without new configuration and decision signing remains fail-closed ## Linked Issues or Issue Description **What happened?** After #10010, `startServer()` throws when `PAPERCLIP_DECISION_SIGNING_SECRET` is unset or shorter than 32 characters. Existing installs without the new environment variable stop at startup. **Expected behavior** The server starts without manual configuration. A new optional feature must not add a required environment variable for existing installs. **Steps to reproduce** 1. Check out `master` at |
||
|
|
b163c6c473 |
fix(server): preserve hot restart intent across path upgrade (#10593)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The server can preserve eligible agent runs during a controlled hot restart. > - A path change moved restart state from the Paperclip home root to the instance root. > - A staged update can therefore make the old server and the new server read different intent files. > - The old server then drains live runs, while the new server can start without a shutdown snapshot. > - This pull request adds a correlated compatibility handoff and records the live preflight set. > - It also verifies the target process instance on Linux, macOS, and Windows. > - The benefit is complete and safe run classification across the path upgrade. ## Linked Issues or Issue Description No public GitHub issue covers this defect. **What happened?** A staged hot restart can run an older server that reads `hot-restart-intent.json` from the Paperclip home root and a new server that writes the file under the instance root. The old server misses the request and uses graceful drain. The new server later finds its marker without a shutdown snapshot. Before this change, that state could produce an empty loss list even when live runs existed before restart. **Expected behavior** The old server must receive the PID-targeted restart request at its legacy path. The new server must correlate the legacy shutdown snapshot with its instance-scoped request. Every run that was live during preflight must appear as adopted, finalized while down, or lost. A reused PID must not let a stale marker claim a different process instance. **Steps to reproduce** 1. Start a server version from before the instance-root marker change. 2. Keep one or more local-agent heartbeat runs active. 3. Stage a current build and request a hot restart from that build. 4. Observe that the old server reads only the home-root path while the staged build writes only the instance-root path. 5. Observe graceful drain and a new-server intent that has no shutdown snapshot. **Paperclip version or commit** The path transition entered `master` in #10045. The hot-restart adoption flow came from #9647. This fix targets current `master` and compatibility with the immediately preceding home-root behavior. **Deployment mode** Self-hosted server built from source with controlled service hot restarts. Related work: #9628 is the original broader hot-restart feature PR. #10556 addresses embedded PostgreSQL lifecycle behavior and does not address marker-path compatibility. ## What Changed - Write an authoritative instance-scoped intent and a correlated legacy home-root handoff marker. - Merge a legacy shutdown snapshot only when immutable request identity fields match. - Prevent a non-default instance from consuming an uncorrelated legacy-only marker. - Record preflight running heartbeat IDs and reconcile snapshot omissions from current database state. - Serialize marker claims, snapshot writes, stale recovery, and matching cleanup with recoverable per-path filesystem leases. - Read process start identity on Linux, macOS, and Windows to distinguish a reused PID from the original server. - Require identity for new restart requests and fail closed when a supported platform cannot provide it. - Classify older markers by comparing the replacement server boot time or operating-system process start time with the request time. - Close the preflight database client explicitly and use a root-safe SQL query. - Add focused unit, platform-branch, database-backed, and CI regression coverage. - Document the compatibility handoff, process identity probes, and instance-scoped report path. ## Verification - `pnpm exec vitest run server/src/services/hot-restart.test.ts server/src/__tests__/heartbeat-process-recovery.test.ts -t "hot-restart|old-server legacy|preflight live|preflight run|spawn identity before hot restart"` — 24 tests passed and 90 tests were skipped across 2 files. - `pnpm exec vitest run server/src/services/hot-restart.test.ts` — 17 tests passed. - `pnpm exec vitest run server/src/__tests__/issue-watchdogs-routes.test.ts -t "restarts a stalled claimed run"` — 1 test passed and 10 tests were skipped. - `pnpm exec vitest run server/src/__tests__/agent-action-audit-routes.test.ts -t "allows an agent with issue:delegate"` — 1 test passed and 7 tests were skipped. - `pnpm --filter @paperclipai/server typecheck` — passed. - `git diff --check` — passed. - GitHub Actions — 26 of 26 checks passed at `55a79cb029be8b1dc89926d9d89ccd2181266d5c`. - Greptile — 5/5 at the same head with no unresolved current-head review threads. ## Risks - The legacy handoff path is shared across instances. Exclusive claims and per-path leases prevent overwrite and match-before-delete races. - Process identity uses platform commands as a fallback when the health endpoint has no identity. Linux reads `/proc`, macOS and BSD use `ps`, and Windows uses PowerShell. - A supported-platform identity probe failure aborts the restart. This fails closed instead of replacing an unknown live process. - Older intent files do not contain process identity. The server compares the replacement boot or process start time with the request time when those values are available. - A preflight database read can fail before the marker is written. The command fails closed instead of claiming a restart whose live-run set is unknown. > 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 exact deployment model ID and context-window size were not exposed by this runtime. Reasoning, repository editing, shell execution, test execution, GitHub CLI, and Paperclip API capabilities 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 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> |
||
|
|
9c1f8e7887 |
feat(decisions): add first-class propose mode (#10010)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents can currently perform many mutations directly, while humans often need a durable review point before cross-issue or destructive actions occur > - Existing approvals and issue-thread interactions do not provide a standalone, reusable object for presenting options, collecting typed inputs, detecting stale targets, and auditing effect execution > - The control plane therefore needs a first-class propose mode that separates an agent's recommendation from the governed mutation it may cause > - This pull request adds Decisions v1 across the database, shared contracts, server execution and telemetry, agent skill guidance, and operator UI > - The benefit is that agents can propose multi-option actions safely while operators get explicit provenance, fail-closed execution, per-effect results, and a focused attention workflow ## Linked Issues or Issue Description ### Subsystem affected Cross-cutting: `packages/db`, `packages/shared`, `server`, and `ui`. ### Problem or motivation Agents need a governed way to propose consequential work without immediately mutating issues, especially when one choice can affect several issue trees. Existing approvals and issue-thread interactions do not provide a standalone object with typed options, target snapshots, effect-level authorization, expiration, execution outcomes, and reusable attention-feed presentation. ### Proposed solution Add first-class Decisions that store options and typed inputs, surface open proposals in the operator attention feed, validate target freshness and the origin-agent/operator authorization intersection at decision time, execute a bounded set of auditable effects, and retain terminal outcomes. Decisions v1 supports comments, status and assignee changes, follow-up issue creation, blocker resolution, and issue-tree cancellation, plus bundle grouping, expiration/dismissal, rule-key telemetry, and agent-facing API guidance. ### Alternatives considered - Extend approvals with arbitrary effects: rejected because approvals represent governed yes/no actions and would become an unsafe generic mutation envelope. - Model every proposal as an issue-thread interaction: rejected because decisions can span several targets and need independent lifecycle, telemetry, idempotency, and effect results. - Let agents perform the mutation and ask for retrospective review: rejected because it removes the pre-execution governance boundary this feature is meant to provide. ### Roadmap alignment Aligns with `ROADMAP.md` sections **Agent Reviews and Approvals**, **Enforced Outcomes**, **MCP Tool Gateway & Apps (governed tool access)**, and **Activity History** by making explicit decisions, authorization gates, auditable execution, and terminal outcomes first-class control-plane objects. ### Additional context This does not replace existing approvals or issue-thread interactions, and it does not add an unrestricted generic mutation effect. ## What Changed - Added company-scoped decision, option, target, and effect-execution schema plus migration and shared TypeScript/Zod contracts. - Added decision routes and services for propose, list/get, decide, dismiss, cancel, target freshness checks, authorization intersection, idempotency, activity logging, and execution auditing. - Added rule-key decision telemetry and attention-feed metadata so open decisions are visible and measurable. - Added agent skill documentation for proposing and resolving decisions through the Paperclip API. - Added the Decisions UI: API client, query keys, inline attention resolver, bundle grouping, target-issue strip, terminal history, destructive confirmation, and per-effect result rendering. - Added server service coverage, DecisionCard state tests, and Storybook stories for the supported visual states. ## Verification - `pnpm -r typecheck` — passed. - `pnpm test:run` — 2,876 passed, 1 skipped, with one unrelated cross-suite cleanup-order failure in `heartbeat-responsible-user-invariant.test.ts`; the failing file passes in isolation (`6/6`). - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-responsible-user-invariant.test.ts` — passed. - `pnpm --filter @paperclipai/ui exec vitest run src/components/DecisionCard.test.tsx` — passed (`9/9`). - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/authz-existence-oracle-guard.test.ts src/__tests__/openapi-routes.test.ts` — passed (`5/5`). - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/decisions-service.test.ts` — passed (`16/16`). - `pnpm --filter paperclipai exec vitest run src/__tests__/company-import-export-e2e.test.ts` — passed (`1/1`). - `pnpm --filter @paperclipai/server typecheck` and `pnpm --filter paperclipai typecheck` — passed. - `pnpm build` — passed. - Rebased-head focused suite — passed (`6` files, `88` tests): shared decision contracts, Decisions service, OpenAPI routes, startup feedback export, DecisionCard states, and attention helpers. The follow-up stale-secondary-target regression passes in the DecisionCard suite (`10/10`). - Rebased-head scoped typechecks — passed for `@paperclipai/shared`, `@paperclipai/db`, `@paperclipai/server`, and `@paperclipai/ui`. - Rebased-head migration numbering and safety checks — passed after renumbering the additive migration to `0193` and making it replay-safe for environments that applied the earlier feature-branch number. - `pnpm check:token-gates` — passed with all gates clean. - GitHub PR workflow and Greptile review for `1f9f7645882d05dfdd9c99377c03a1f53f20e8be` — running after the stale-secondary-target fix and PR metadata refresh on July 27, 2026. - `pnpm --filter @paperclipai/ui build-storybook` exposes an existing Storybook version mismatch (`storybook` 10.4.6 vs `@storybook/addon-docs` 10.5.0); Decisions stories were validated with the docs addon temporarily disabled and the tracked config remains unchanged. ## Risks - **Migration:** Adds replay-safe migration `0193`; migration numbering and safety checks pass. The new tables and indexes are additive. - **Authorization:** Effect execution intersects the proposing agent's permissions with the responsible user context and fails closed; mistakes could reject a valid proposal rather than silently over-authorize it. - **Concurrency:** Target snapshots and idempotency keys protect against stale or duplicate execution, but reviewers should focus on mixed-effect partial outcomes and retry behavior. - **UI:** Decisions are integrated into the existing attention feed rather than a separate navigation surface, reducing routing risk but increasing the importance of attention-item metadata compatibility. > 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 CLI using `gpt-5.6-sol` for final PR preparation, review fixes, and verification; repository tools and code execution were enabled, and context-window size is not exposed in this runtime. - Anthropic Claude Opus 4.8 with 1M context assisted with the Decisions UI implementation, as recorded in the relevant commits. ## 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: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
71231dfa38 |
feat(audit): agent audit UI — company page + per-agent tab (#9744)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Operators need an audit record of agent actions across tasks, comments, documents, approvals, and runs > - The permission-gated audit read API provides that record, but operators cannot inspect it in the product > - A readable UI must preserve company boundaries, server-side permission decisions, and redaction > - Audit exports must also be safe to open in spreadsheet software and must record the export itself > - This pull request adds company and per-agent audit views plus a guarded CSV export > - The benefit is a searchable, filterable, and reviewable agent action history with direct links back to work ## Linked Issues or Issue Description **Feature.** This change adds the frontend and CSV export for the agent action audit log. Refs #9731 and #9735. - Problem: agent actions are recorded, but operators have no readable product surface to inspect or export them. - Solution: add a company audit page and a per-agent Audit tab that use the permission-gated audit API. - Alternative: build a separate plugin-only surface. This was rejected because the existing permission model already supports a unified, server-authoritative view. This pull request targets the audit epic branch, which contains the merged #9735 audit API. ## What Changed - Added a company Audit page and sidebar entry. - Added a per-agent Audit tab with a fixed agent filter. - Added filters for agent, responsible user, action domain, entity type, and date range. - Added task and run links, responsible-user context, cursor pagination, and readable action text. - Added a permission-denied Enterprise card for callers without `audit:view_agent_actions`. - Added a CSV export that is permission-gated, capped, self-audited, CSV-escaped, and protected against spreadsheet formula injection. - Preserved the merged audit API cursor validation, redaction, and sub-millisecond pagination behavior. ## Verification - `pnpm exec vitest run ui/src/pages/audit/AuditFeed.test.tsx` — 6 passed. - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/agent-action-audit-routes.test.ts` — 8 passed with embedded PostgreSQL. - `pnpm -r typecheck` — passed across all workspaces. - `pnpm build` — passed across all workspaces. - `pnpm test:run` — all completed shards passed except one environment-sensitive CLI assertion caused by injected static AWS credential variables; the exact test passes 8/8 with those variables unset. - Manual Chromium QA exercised the populated feed, active filters, permission-denied card, per-agent tab, and CSV export. ## Screenshots and Manual QA - [All audit states exercised in Chromium](https://github.com/paperclipai/paperclip/pull/9744#issuecomment-4998997001) - [Detailed browser report and per-agent tab root cause](https://github.com/paperclipai/paperclip/pull/9744#issuecomment-4998771061) The per-agent redirect defect found during QA is fixed in this branch. ## Risks Low to moderate risk. The UI and export route are additive and use the existing company-scoped permission gate. The main risks are large exports and spreadsheet interpretation. The export is capped at 10,000 rows, records truncation accurately, and prefixes formula-like cells as text. There are no schema changes or migrations. > 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 - Anthropic Claude Opus 4.8, 1M context, extended thinking, tool use, and code execution produced the original implementation. - OpenAI Codex, GPT-5 (deployment ID and context window not exposed), reasoning, tool use, code execution, browser-test orchestration, and GitHub review tooling repaired and verified 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) - [ ] 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: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a29b10510c |
fix(server): bound inherited workspace runtime services (#10589)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Execution workspaces can inherit runtime services from a project workspace > - A project workspace keeps current and historical runtime service rows > - The execution workspace read path returned all current rows, including services removed from the current configuration > - This pull request matches inherited rows to the current service definitions > - The benefit is bounded workspace payloads and accurate service summaries ## Linked Issues or Issue Description **What happened?** Shared execution workspaces returned current historical service rows that no longer matched the project workspace configuration. The response size multiplied across every shared execution workspace. **Expected behavior** Shared execution workspaces must return only the newest runtime service row for each service in the current project workspace configuration. **Steps to reproduce** 1. Create one project workspace with many historical runtime service rows. 2. Create many shared execution workspaces that inherit that project workspace. 3. List the execution workspaces and inspect each `runtimeServices` array. **Paperclip version or commit** `7301fae942c3d5826974335cb40d6f1e0d95d1e0` **Deployment mode** Built from source. The defect is in the server read model and is not deployment-specific. No duplicate or related public issue or pull request was found. ## What Changed - Select only runtime service rows that match the current project workspace service definitions. - Preserve each matched service definition index in the API result. - Avoid loading direct execution service rows for workspaces that inherit project services. - Add unit, integration, and volume regression coverage. ## Verification - `pnpm --dir server exec vitest run src/services/workspace-runtime-read-model.test.ts src/__tests__/execution-workspaces-service.test.ts -t 'selectConfiguredRuntimeServiceRows|returns full details at the observed volume|inherits only runtime-service rows'` - `pnpm --filter @paperclipai/server typecheck` The focused test run passed 4 tests and skipped 27 unrelated tests. ## Risks The read path now omits service rows that do not match the current configuration. This is the intended behavior for inherited runtime services. The change does not alter service persistence or lifecycle transitions. > 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 model ID `gpt-5`. The context-window size is not exposed to this run. The run 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 - [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> |
||
|
|
2e4774ac90 |
fix(adapter-utils): correct confirmation wake semantics (#10588)
<!-- 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 > - Local agent wakes include a default execution contract > - That contract tells agents how issue-thread continuation policies behave > - The current text says `wake_assignee` resumes a confirmation only after acceptance > - The server actually wakes for every non-expired resolution and reserves acceptance-only behavior for `wake_assignee_on_accept` > - This pull request makes the default prompt match the server contract and strengthens the recovery follow-up regression case > - The benefit is that agents choose the correct continuation policy and recovery tests cover normalized agent name keys ## Linked Issues or Issue Description Related work: Refs #5473, Refs #5060, and Refs #10562. **What happened?** The default local-agent prompt described `wake_assignee` as acceptance-only for `request_confirmation`. This conflicts with the server. The server wakes on every non-expired resolution. A recovery follow-up test also used an already-normalized execution agent name key, so it did not exercise the normalization seam. **Expected behavior** The prompt must state that `wake_assignee` resumes after acceptance or rejection. It must direct acceptance-only flows to `wake_assignee_on_accept`. The recovery regression must use a display-style agent name key and prove that the follow-up path still works after normalization. **Steps to reproduce** 1. Read the default local-agent prompt in `packages/adapter-utils/src/server-utils.ts`. 2. Compare its confirmation continuation text with `queueResolvedInteractionContinuationWakeup` in `server/src/routes/issues.ts`. 3. Observe that the prompt gives acceptance-only semantics to `wake_assignee`. 4. Inspect the recovery hand-back test and observe that its execution name key is already normalized. **Paperclip version or commit** `7301fae942` **Deployment mode** Local dev. The prompt and test behavior are not deployment-specific. ## What Changed - Corrected the default agent prompt for `wake_assignee` and `wake_assignee_on_accept`. - Added focused prompt assertions for both the required and obsolete text. - Changed the recovery follow-up fixture to use a display-style agent name key. ## Verification - `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts -t 'keeps the default local-agent prompt action-oriented'` passed: 1 test. - `pnpm exec vitest run server/src/__tests__/heartbeat-comment-wake-batching.test.ts -t 'defers recovery hand-back wakes until the resolving run exits'` passed: 1 test. - `pnpm --filter @paperclipai/adapter-utils typecheck` passed. - `pnpm --filter @paperclipai/server typecheck` passed. - `git diff --check origin/master...HEAD` passed. ## Risks - Low risk. The production change updates prompt text only. - Agents that followed the old text may now choose `wake_assignee_on_accept` for acceptance-only flows. - The server test change only broadens an existing regression fixture. > 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 exact serving model ID and context-window size are not exposed to the agent. The model used reasoning, repository tools, tests, Git, and GitHub CLI access. ## 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 - [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> |
||
|
|
627728bdde |
feat: add authoritative issue PATCH receipts (#10478)
<!-- 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. > - Agents update tasks through the issue API. > - The update response did not state which values changed. > - Blocker updates also did not echo the scalar blocker IDs. > - Agents therefore used an extra GET request to confirm a successful write. > - This pull request adds an authoritative change receipt and an optional small response. > - The benefit is fewer API calls with a clear and compatible write contract. ## Linked Issues or Issue Description No public GitHub issue exists for this change. ### Subsystem affected Cross-cutting: `server/`, `packages/shared`, and the UI issue cache. ### Problem or motivation A successful issue PATCH returned the updated issue, but it did not identify the effective changes. Blocker writes returned relation summaries without the scalar IDs. Agents could not distinguish a confirmed clear operation from missing data. The response must confirm committed field and blocker changes while existing UI clients continue to receive the full issue by default. ### Proposed solution Add a `changes` receipt. Add a conditional `blockedByIssueIds` echo. Support `Prefer: return=minimal`. Keep the full response as the default. ### Alternatives considered Make the small response the default for agent tokens. This would create different response contracts by actor type, so this pull request does not use that design. ### Roadmap alignment This is a focused control-plane reliability improvement. It does not duplicate an open roadmap milestone. ## What Changed - Compute committed issue row and relation changes in the issue service. - Omit no-op fields and truncate changed long text values to 200 characters. - Echo blocker ID arrays for blocker set and clear requests. - Add the opt-in `Prefer: return=minimal` response and `Preference-Applied` header. - Keep receipt metadata out of React Query issue caches. - Add route and embedded Postgres tests for the new contract. ## Verification - `pnpm exec vitest run server/src/__tests__/issue-activity-events-routes.test.ts` - `pnpm exec vitest run server/src/__tests__/issues-service.test.ts -t "returns authoritative update receipts for row fields and blocker relations"` - `pnpm --filter @paperclipai/shared typecheck` - `pnpm --filter @paperclipai/server typecheck` - `pnpm --filter @paperclipai/ui typecheck` - `pnpm check:token-gates` - `git diff --check` ## Risks - Low compatibility risk. The default response only adds receipt fields. - Minimal mode is opt-in. Existing clients do not receive a smaller body. - The receipt excludes `updatedAt` because the response already returns it as the freshness anchor. - Prose API and agent workflow guidance will follow after the server contract is available. > 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 exact deployment ID, context window size, and reasoning mode are not exposed to the agent. The agent used 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> |
||
|
|
fc5a30805e |
feat(cli): add managed install, update, and service lifecycle (#10045)
## Thinking Path > - Paperclip is the open source control plane people use to manage AI-agent companies > - Operators need a predictable installation path that survives beyond an ephemeral `npx` process > - A durable installation needs an owned per-user payload store, stable command shim, safe shell integration, and supported service lifecycle > - Updates must preserve recoverability by backing up data, installing side-by-side, verifying the new payload, and retaining rollback state > - Bootstrap scripts and privileged service operations must fail closed across download, filesystem, ownership, and consent boundaries > - This pull request integrates managed install, update, rollback, service, uninstall, doctor, bootstrap-installer, and runtime-serving support into one workflow > - The benefit is a recoverable, inspectable, and documented installation lifecycle with explicit safety boundaries across Linux, macOS, containers, WSL, npm, npx, and source checkouts ## Linked Issues or Issue Description ### Problem Paperclip lacks a first-class durable installation and lifecycle workflow. Operators currently have to assemble npm/npx installation, PATH setup, background-service management, updates, rollback, diagnostics, and uninstall behavior themselves. That makes upgrades harder to recover, creates inconsistent behavior across platforms, and leaves shell/download/service trust boundaries without one documented implementation. ### Proposed Solution Add a managed per-user install store and stable shim, a verified shell bootstrap installer, service lifecycle commands, install-mode-aware update/rollback behavior, doctor checks, and documentation. Managed updates back up the database, install and smoke-test a side-by-side payload, atomically switch `current`, and retain prior payloads. The shell installer pins registry/download trust boundaries and requires explicit consent for non-interactive privileged actions. ### Alternatives Considered - Keep recommending `npx`: simple for evaluation, but ephemeral and unsuitable for stable services, atomic updates, or rollback. - Require global npm installation only: familiar, but cannot provide the owned side-by-side payload store and retained rollback semantics. - Split the capability across multiple PRs: rejected because install, update, service, uninstall, bootstrap, and serving behavior share contracts and security boundaries that need review together. ### Related Pull Requests - Supersedes #10042 and #10044 with one integrated final diff. - Incorporates and replaces the closed preparatory work in #10032 and #10034. ## What Changed - Added `paperclipai install`, `update`/`upgrade`, rollback, uninstall, service lifecycle, onboarding integration, and managed-install doctor checks. - Added a private managed payload store, verified manifest/marker ownership, exclusive mutation locks, atomic manifest/current/shim writes, retained previous payloads, and provenance validation. - Added npm and GitHub-ref install sources with exact target resolution, registry isolation, database backup, side-by-side verification, atomic activation, service restart coordination, and failure rollback. - Made managed-update backups report actionable service-start and `--no-backup` recovery guidance for unreachable databases, while clean never-onboarded instances skip an empty backup. - Added systemd user and launchd service definitions, status/health/log commands, single-instance coordination, stale-port recovery, and explicit sudo/lingering consent handling. - Added the `scripts/install.sh` bootstrap path with checked two-stage downloads, pinned public npm registry usage, platform checks, dry-run/non-interactive controls, and Docker fixtures. - Added embedded Postgres/native bootstrap integration, hot-restart/systemd-notify serving support, passive update notices, configuration contracts, README/CLI/install documentation, and focused regression tests. - Security re-review should explicitly re-verify: (1) `addManagedPathBlock`/`removeManagedPathBlock` reject symlinked or non-regular rc files, assert current-user ownership, preserve restrictive modes, and replace atomically; (2) managed shim replacement rejects unsafe parents, foreign-owned or multiply linked files, and uses checked atomic replacement; (3) the shell installer and sudo path preserve explicit consent and checked downloads; and (4) installed service/runtime serving remains bound to the validated managed shim and instance configuration. ## Verification - `bash -n scripts/install.sh scripts/clean-install-git.sh scripts/clean-install-npm.sh scripts/test-install-sh-docker.sh` - `pnpm exec vitest run cli/src/__tests__/install-store.test.ts cli/src/__tests__/install-command.test.ts cli/src/__tests__/managed-install-check.test.ts cli/src/__tests__/onboard-service.test.ts cli/src/__tests__/service-health-check.test.ts cli/src/__tests__/service-manager.test.ts cli/src/__tests__/update-command.test.ts cli/src/__tests__/update-notice.test.ts packages/db/src/embedded-postgres-native.test.ts` — 9 files, 66 tests passed - `pnpm --dir cli typecheck` - `pnpm --dir cli build` - Follow-up verification: `pnpm exec vitest run cli/src/__tests__/update-command.test.ts` (14/14), `pnpm --dir cli typecheck`, `pnpm --dir cli build`, and `pnpm --filter @paperclipai/server typecheck`. - `pnpm -r typecheck` - `pnpm build` - Full `pnpm test:run` exercised all suites; an injected static AWS credential changed one unrelated doctor expectation, which passed when those credentials were removed. A second run cleared that case and exposed stale pre-existing adapter-utils `dist` output; rebuilding `@paperclipai/adapter-utils` made the isolated test pass. The updated PR CI is the authoritative clean-workspace full-suite run. ## Risks - Installer/update code writes executable shims, symlinks, shell rc blocks, service definitions, and managed payloads; ownership, regular-file, symlink, hard-link, marker, and path-containment checks fail closed before destructive changes. - The bootstrap installer executes downloaded tooling; downloads are staged and checked before execution, npm traffic is pinned to the public registry, and non-interactive privileged behavior requires explicit consent. - Linux lingering may invoke `sudo`; the command is surfaced and confirmed before execution, and unsupported service managers fall back to foreground-run guidance. - Database migrations remain forward-only; payload rollback does not reverse migrations, so managed updates create a backup before activation unless explicitly disabled. - Service restart and runtime serving touch process/port ownership; lifecycle locks, health/version checks, and stable-shim service definitions reduce split-brain and stale-process risk. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex coding agents using GPT-5.5 and GPT-5.6-sol, with reasoning, repository/API access, shell execution, and test tooling. The runtime did not expose a reliable 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> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
492555aaf9 |
design(decisions): flatten decision cards to two task-borrowed types (#10474)
The Decisions queue ran five parallel colour/icon vocabularies chosen by source kind, plus a separate severity badge, so two rows needing the same response could look unrelated and none of it matched the task list. Every row now resolves to one of two kinds, each borrowing the task status it corresponds to: blocking renders as `blocked`, review as `in_review`, both through StatusGlyph and the existing --status-task-icon-* tokens. Source kinds keep their own wording; only colour and icon merge. Card anatomy follows the design mock: no left accent rail, rounded cards 16px apart, a "/"-separated meta breadcrumb, a named See more / See less control, and no separately tinted drawer when expanded. Verb order is fixed across both states. Severity moves from chrome to a toolbar filter. Four defects fixed along the way: - blocked rows reported themselves as their own blocker (server-side) - the task key was missing wherever the row's subject IS the task - the task quicklook stuck open, because closing handed focus back to a trigger that opens on focus - the card ring appeared on click, and only on cards with a toggle Also: the standard task preview is aligned to its trigger's text and scales out of it, the task eyebrow renders its project as a tile, and the first motion tokens land alongside the disclosure and crossfade. Supersedes #9574 and #9575. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c0b875c46c |
fix(codex): let sandbox runs use the sandbox image's own Codex login (#10582)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Codex agents can run inside sandbox environments, and operators can bake a Codex login into the sandbox image during interactive image setup > - Two credential gates (the control plane's pre-dispatch configuration-incomplete gate and the adapter's execute-time fail-fast) required host-side Codex credentials — a usable `auth.json` in the managed home or a configured `OPENAI_API_KEY` — regardless of where the run executes > - On managed cloud hosts a local Codex login never exists, so every sandbox run of a Codex agent failed immediately with "configuration incomplete: no Codex credentials available for managed home …", even though the adapter's inbound auth merge already supports the image-login case end to end > - This pull request makes the execute-time gate probe the sandbox for its own `~/.codex/auth.json` before failing, and exempts sandbox-destined runs from the pre-dispatch host check > - The benefit is that a sandbox image signed in to Codex is a first-class credential source, matching what the auth-merge, precedence-warning, and copy-back machinery were already built for ## Linked Issues or Issue Description **What happened?** Running a `codex_local` agent in a sandbox environment whose image carries a Codex login failed instantly with `configuration incomplete: no Codex credentials available for managed home "…/codex-home". Sign in to Codex on the host with a ChatGPT subscription, or bind a per-agent OPENAI_API_KEY secret for this agent.` The host has no Codex login and never will on a managed cloud deployment; the sandbox's own login was never consulted. **Steps to reproduce** 1. Configure a sandbox environment and capture a custom image after signing in to Codex inside the interactive image setup. 2. Create a `codex_local` agent that uses that environment, on a host with no Codex login and no `OPENAI_API_KEY` bound. 3. Start a run: it fails pre-dispatch with the configuration-incomplete blocker above. **Expected behavior** The run launches and Codex authenticates with the sandbox image's own login, the same way the adapter's host↔sandbox auth merge already keeps the sandbox credential when the host ships none. A run should only fail fast when neither the host, a bound `OPENAI_API_KEY`, nor the sandbox has credentials. **Paperclip version** Current `master` (cloud image deployments). **Deployment mode** Managed cloud stacks (any deployment where the server host has no local Codex login). ## What Changed - Extracted the adapter's execute-time gate into `assertCodexCredentialsLaunchable`: when host readiness fails and the target is a sandbox, it probes `~/.codex/auth.json` in the sandbox (same command the auth-precedence warning uses) and proceeds with a log line naming the credential source; when the sandbox has no login either, the error now names all three remediation options (sandbox image sign-in, per-agent `OPENAI_API_KEY`, host sign-in). Non-sandbox targets keep today's strict behavior byte-for-byte. - The control plane's pre-dispatch gate in `resolveExecutionRunAdapterConfig` now takes the selected environment's driver and skips the host-credential check for sandbox-destined runs — only the adapter can probe the sandbox once it is up, so the execute-time gate is the authority there. Non-sandbox runs keep the early, well-attributed configuration-incomplete blocker. - The codex Test flow needed no change: it already seeds host credentials only when they exist and otherwise leaves the sandbox's `CODEX_HOME` alone; this aligns the run path with it. ## Verification - `cd packages/adapters/codex-local && pnpm vitest run` — 210 tests, including new gate cases: sandbox login present (proceeds + logs source), sandbox and host both credential-less (fails with the extended message), non-sandbox target (strict host requirement kept, no sandbox probe), per-agent API key (no probe at all). - `cd server && pnpm vitest run src/__tests__/heartbeat-project-env.test.ts src/__tests__/codex-local-adapter-environment.test.ts` — includes the new sandbox-exemption case next to the existing blocker tests. - `pnpm run typecheck` in `server` and `packages/adapters/codex-local`. ## Risks - Sandbox-destined misconfigurations (no credentials anywhere) now surface at adapter execute time instead of pre-dispatch, so they read as an adapter failure with a precise message rather than a configuration-incomplete blocker. The trade-off is deliberate: the sandbox must be up to know whether credentials exist, and the failure message names the exact remediations. - The sandbox probe adds one short (5s-capped) shell command to sandbox runs whose host has no credentials; runs with host credentials or a bound key are untouched. - Self-hosted behavior is unchanged for local and SSH targets. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use (file edits, vitest/tsc runs). No other models involved. ## 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 |
||
|
|
7301fae942 |
fix(heartbeat): atomically claim due timer intervals (#10584)
<!-- 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 coordinates AI agents through scheduled heartbeat runs. > - The heartbeat scheduler can call `tickTimers()` again before an earlier tick has finished. > - Each overlapping tick can read the same old `lastHeartbeatAt` value and decide that the same agent is due. > - The existing queue checks do not make that due-time decision atomic. > - This pull request atomically advances the timer baseline before it enqueues the wake. > - The benefit is that one timer interval can create at most one scheduled run for an agent. ## Linked Issues or Issue Description No public GitHub issue describes this exact scheduler race. Related pull requests address active-run overlap or queued-run buildup, but they do not atomically claim a due timer interval: #9457, #8416, and #3858. **What happened?** Two overlapping calls to `tickTimers()` could both read the same due timer baseline. Both calls could enqueue a timer run for the same agent and interval. **Expected behavior** Only one scheduler tick must claim a due timer interval. A second overlapping tick must observe that the interval was already claimed and skip it. **Steps to reproduce** 1. Create an active agent with a 60-second timer interval. 2. Set `lastHeartbeatAt` to more than 60 seconds in the past. 3. Call `tickTimers(now)` twice with `Promise.all()`. 4. Observe that the old code can enqueue two runs for the same interval. **Paperclip version or commit** Reproduced on `master` before this branch. **Deployment mode** Local development with embedded PostgreSQL. ## What Changed - Added an atomic conditional update that claims a due timer interval by advancing `lastHeartbeatAt`. - Made `tickTimers()` enqueue only after that conditional update succeeds. - Preserved first-heartbeat telemetry when the timer claim advances `lastHeartbeatAt` before run completion. - Added regression tests for concurrent claims and first-heartbeat telemetry. ## Verification - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-stale-queue-invalidation.test.ts` — 24 tests passed on the final rebased commit. - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-process-recovery.test.ts -t "preserves first-heartbeat telemetry after a timer interval claim|tracks the first heartbeat with the agent role"` — 2 tests passed. - `pnpm --filter @paperclipai/server typecheck` — passed after the review fix. - `pnpm -r typecheck` — passed. - `pnpm build` — passed. - `pnpm test:run` — 3,121 tests passed and 2 tests skipped. One unrelated runtime-skills test exceeded its 5-second limit under full-suite load. - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-runtime-skills.test.ts` — the timed-out file passed in isolation, 2 tests passed. - `pnpm --filter @paperclipai/db exec vitest run src/status-card-migrations.test.ts` — the unrelated CI timeout passed in isolation. - The full PR CI matrix passed after one rerun of that unrelated timeout. - Greptile passed with zero new comments and no unresolved review threads. ## Risks - Low risk. The change only affects due timer claims. - If enqueue fails after the claim, the next timer attempt waits for one interval. This is safer than duplicate agent execution. - No schema, migration, API, UI, or dependency changes 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 based on GPT-5. The exact deployment ID and context-window size are not exposed to this runtime. Agentic reasoning, shell tools, code execution, and GitHub operations 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> |
||
|
|
90ead239a8 |
feat(ui/server): name cross-company environment secret refs instead of calling them missing (#10577)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Environment configs (sandbox providers, SSH) can bind stored company secrets through `format: "secret-ref"` fields, picked in the environment editor's secret picker > - Environments are instance-scoped and shared by every company on an instance, but the picker lists only the current company's secrets, so a ref pointing at another company's secret renders as "Missing secret (…)" in destructive styling > - That state is indistinguishable from a genuinely deleted secret, so operators "fix" a healthy binding by creating a duplicate secret in their own company — the exact sequence that used to corrupt bindings before #10576 > - This pull request adds an instance-gated metadata endpoint for an environment's secret refs and teaches the picker to name a cross-company secret and its owner honestly > - The benefit is that operators can tell a healthy cross-company binding from a broken one, and stop creating duplicate secrets ## Linked Issues or Issue Description **Is your feature request related to a problem? Please describe.** In the environment editor, a secret-ref field that points at a secret owned by a different company shows "Missing secret (22095402…)" in red, with "The previously selected secret is no longer available. Pick another or remove the binding." The binding is actually healthy — the current company's picker just cannot list the other company's secrets. Operators react by creating a duplicate secret and re-pointing the field. **Describe the solution you'd like** The editor should know the referenced secret's name, status, and owning company (metadata only, never the value) and present a cross-company ref neutrally, a deleted secret as deleted, and only an unknown id as missing. Related: #10576 (fixes the binding corruption this UI state used to trigger). ## What Changed - New `GET /environments/:id/secret-refs` returns `{ refs: [{ configPath, secretId, name, status, companyId, companyName }] }` for the environment's config-derived secret refs. Values are never returned. The route sits behind `assertCanAccessInstanceEnvironments`, the same gate as environment editing. - New `secretService.describeSecretRefs` loads that metadata across companies; unknown ids are omitted. - `SecretBindingPicker` reads an optional `SecretRefHintsContext` (keyed by secret id). With a hint, a ref the company list cannot show renders as `NAME — Owning Company` with neutral styling and the note "Owned by the … company. The binding keeps working; selecting a secret from this list re-points it here." A hint with `status: "deleted"` reports the secret as deleted. Without hints, behavior is byte-identical to before — agent editors and other picker users are unaffected. - `CompanyEnvironments` fetches descriptors for the environment being edited and provides them through the context. ## Verification - `cd server && pnpm vitest run src/__tests__/environment-routes.test.ts src/__tests__/secrets-service.test.ts` — new endpoint happy path, agent 403 (descriptors never computed), and embedded-Postgres coverage proving cross-company names resolve and unknown ids drop out. - `cd ui && pnpm vitest run src/components/SecretBindingPicker.test.tsx src/components/JsonSchemaForm.test.tsx src/pages/CompanyEnvironments.test.tsx` — hinted cross-company rendering, hinted deleted secret, and unchanged no-hint fallback. - `pnpm run typecheck` in `server` and `ui`. - Manual: edit an environment whose secret-ref field references another company's secret; the field names the secret and its owning company instead of "Missing secret". ## Risks - The endpoint exposes secret names and company names across companies to instance-level environment editors. Those actors already manage instance-shared environments (and instance admins are implicit members of every company), so this reveals no secret material and no new reach; the service method documents that callers must sit behind an instance-level gate. - UI change is additive and context-gated; pickers without a provider render exactly as before. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use (file edits, vitest/tsc runs). No other models involved. ## 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 |
||
|
|
f51cba33fa |
fix(server): keep environment secret bindings consistent when re-pointing config secrets (#10576)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents can run inside environments (SSH boxes, sandbox providers); a sandbox environment's config can reference stored company secrets (for example a provider API key) through `format: "secret-ref"` fields > - Environments are instance-scoped and shared by every company on an instance, but `company_secret_bindings` rows are company-scoped, and the environment routes synced config-derived bindings under one guessed "context company" resolved from the environment's existing bindings > - When a save re-pointed a secret-ref field at a secret owned by a different company, the binding sync threw after the config row had already been persisted: the config referenced the new secret, the binding still pointed at the old one, every later lease acquisition failed with `Secret is not bound to environment:<id> at apiKey`, and the stale cross-company binding made every later save fail with a company-context conflict — with no route-level way to recover > - This pull request makes config-derived bindings follow the company that owns each referenced secret, and makes the environment write and its binding syncs atomic > - The benefit is that environment saves can no longer strand an environment in a half-updated state that breaks all of its runs ## Linked Issues or Issue Description Refs #10577 (companion UX change: the editor state that nudges operators into this sequence). **What happened?** Saving an environment whose secret-ref config field points at a secret owned by a different company than the environment's existing binding partially applied: the config row updated, the binding sync failed server-side, and the environment was left referencing a secret it has no binding for. Every run that leased the environment then failed with `lease_acquire_failed: ... Secret is not bound to environment:<id> at apiKey`, and every later save of the environment returned 409 `Environment secret bindings already use a different company context.` — with no route-level way to recover. **Steps to reproduce** 1. On an instance with two companies, create a sandbox environment from company A with a picker-bound API-key secret owned by A (the binding lands in A). 2. From company B, create a new secret and re-point the environment's API-key field at it, then save. 3. The save persists the config but the binding sync throws, so no binding for B's secret exists. 4. Run any agent that uses the environment, or try to save the environment again. **Expected behavior** The save either fully applies (config and bindings consistent) or fully fails. Re-pointing a config secret ref to a secret owned by another company moves the binding with the secret. **Paperclip version** Reproduced on current `master` (also present on recent release images). **Deployment mode** Multi-company server deployment (any mode with more than one company on the instance). ## What Changed - New `secretService.replaceSecretRefsForInstanceTarget`: writes each config-derived binding under the company that owns the referenced secret, replaces all non-`env.*` bindings of the target across every company, and validates every ref (secret exists, not deleted, config-path and projection-class rules) before any row is written. `env.*` env-var bindings stay company-scoped and untouched. - The environment create and update routes now run the environment write and its binding syncs inside one `db.transaction`, threading the transaction through new optional executor seams on `environmentService.create/update` and the existing `SecretBindingDb` seam pattern, so an invalid ref rolls the whole save back instead of leaving a half-updated environment. - `resolveEnvironmentSecretContextCompanyId` no longer lets existing bindings veto the caller's context (the 409s above); it now only picks where new raw-pasted secrets are created and how env-var bindings and probes resolve: explicit route/query company first, then the single company the bindings live in, then the actor's company. ## Verification - `cd server && pnpm vitest run src/__tests__/environment-routes.test.ts src/__tests__/environment-instance-routes.test.ts src/__tests__/secrets-service.test.ts src/__tests__/environment-custom-image-routes.test.ts` (165 tests, includes new coverage below) - New embedded-Postgres tests prove: a re-point moves the binding to the new secret's company and deletes the stale row; refs across several companies each bind under their own secret's company; an unknown secret ref rejects without touching existing bindings; `env.*` rows survive config-ref replacement. - New route tests prove: a cross-company re-point that previously 409'd now saves, with the update and binding replacement on the same transaction executor; a failing ref surfaces as 422. - `cd server && pnpm run typecheck` ## Risks - Behavioral shift: environment saves no longer 409 on a company-context mismatch between the caller and existing bindings; bindings follow the referenced secret's company instead. Environment routes are instance-admin gated, and instance admins already had access to every company's secrets by passing the company explicitly, so this removes an ordering trap rather than widening access. - Runtime lease resolution is unchanged: a run still resolves environment secrets under the run's own company, so an environment referencing company B's secret still only leases for company B runs (fail-closed as before). - The delete route's per-company binding cleanup is unchanged. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended thinking, agentic tool use (file edits, vitest/tsc runs). No other models involved. ## 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 |
||
|
|
32c1a8576c |
fix(server): self-heal execution workspaces whose recorded branch no longer exists (#10578)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Each agent task gets an execution workspace (a git worktree) with a recorded branch name; workspace validation compares that record to the worktree before every run > - Agents sometimes rename their task branch (for example to a `feat/*` PR branch), so the recorded branch never existed or was deleted > - Validation then fails every run with "expected branch does not exist" — a deterministic `workspace_validation_failed` loop with no self-heal path > - A recorded branch with no resolvable commit has nothing to lose, so adopting a clean, registered checked-out branch is trivially forward-only > - This pull request routes that exact case through the existing audited forward-reconciliation path, in both the runtime and the manual board reconcile endpoint > - The benefit is that these stranded workspaces heal themselves while dirty worktrees, detached HEADs, unregistered paths, and ambiguous git states all stay fail-closed ## Linked Issues or Issue Description No public GitHub issue exists; the underlying bug is described here per `bug_report.yml`. Related PR: #10574 self-heals the sibling provisioning failure loop uncovered by the same incident diagnosis. **What happened?** An execution workspace whose recorded branch was renamed away failed every subsequent run with `workspace_validation_failed` ("expected branch does not exist"). The safe-repair matrix refused the case, so the task stayed blocked until a human intervened. **Expected behavior** When the recorded branch is confirmed absent and the worktree is clean and registered with its checked-out branch matching HEAD, Paperclip adopts the checked-out branch through the audited forward-reconciliation path and the next run proceeds. **Steps to reproduce** In an isolated workspace, rename the task branch (`git branch -m <recorded> feat/something`) or delete the recorded branch, leave the worktree clean, then start a new run on the task. Validation fails on every retry. **Paperclip version or commit** master as of the branch point of this PR. **Deployment mode** Local trusted deployment with git-worktree isolated workspaces. ## What Changed - `ensureGitWorktreeBranchCoherent` (workspace runtime): a missing recorded branch with a clean worktree, an existing checked-out branch, and a registered branch matching HEAD now goes through audited forward reconciliation instead of failing closed. Gated behind `enableWorkspaceBranchReconcileForward`. - `reconcileExecutionWorkspaceBranch` mode `forward` (service): accepts the same case so the board reconcile endpoint can repair it manually. - The service inspection now classifies each branch ref as `resolved` / `missing` / `error` (`git rev-parse --verify --quiet`, distinguishing an absent ref from git failing to inspect the repo). Adoption requires a confirmed-missing recorded ref **and** a resolved target ref, so a git error can never bypass ancestry validation and a nonexistent branch name is never persisted. - Removed the test that asserted this case fails closed; it is superseded by tests that assert the new behavior. - New tests: successful adoption, dirty-worktree refusal, refusal when the checked-out branch ref does not resolve either, and disabled-flag behavior. ## Verification - `server`: `npx vitest run src/__tests__/execution-workspaces-service.test.ts -t "reconcil"` — 11 passed. - `server`: `npx vitest run src/__tests__/workspace-runtime.test.ts -t "adopt"` — 7 passed. - `npx tsc --noEmit` in `server/` is clean. - Manually validated the underlying repair on a live stranded workspace before automating it: creating the recorded branch at the clean HEAD ended the validation-failure loop without touching the agent's PR branch. ## Risks - The change relaxes a fail-closed gate, so the main risk is over-adoption. Mitigations: the exception requires flag-on, clean worktree, registered worktree path, registered branch matching HEAD, a confirmed-missing (not merely unreadable) recorded ref, and a resolvable target ref; everything else still fails closed. Every adoption goes through the audited reconcile path with an issue comment trail. - No migrations, no API surface changes (the reconcile route returns the same hand-picked fields). ## Model Used Claude Fable 5 (Anthropic, model ID `claude-fable-5`), extended thinking, agentic tool use (Claude Code harness). ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have 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 Fable 5 <noreply@anthropic.com> Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
1ee1275f11 |
fix(adapters): persist ACPX process identity for hot restart (#9838)
## Thinking Path > - Paperclip is the open source control plane people use to manage AI agents for work. > - Local agent heartbeats need durable process identity so the server can supervise them. > - The ACPX runtime owns the child process used by `codex_local` sessions. > - ACPX did not expose the child PID and start time to the Paperclip adapter. > - Warm ACPX runtimes can also serve a later heartbeat without a new spawn event. > - A hot restart could therefore classify a live Codex run as lost because its heartbeat row had no process identity. > - This pull request forwards ACPX spawn identity, reuses it for compatible warm heartbeats, and fails closed when identity cannot be persisted. > - The benefit is reliable hot-restart adoption for eligible local Codex runs. ## Linked Issues or Issue Description No matching public GitHub issue was found. **What happened?** A `codex_local` heartbeat could run through ACPX without a persisted `processPid` or `processStartedAt`. A Paperclip hot restart then had no durable identity for the live ACP child. Recovery could classify the run as `process_lost` even while the child was still alive. **Expected behavior** ACPX reports the real child PID and start time before the first prompt. A compatible warm runtime reports the same known identity to each later heartbeat that reuses the child. ACPX stops the child if the identity is invalid or persistence fails. Hot-restart recovery can then adopt the live run. **Steps to reproduce** 1. Start a `codex_local` heartbeat through the ACPX execution lane. 2. Keep the run active during a Paperclip hot restart. 3. Inspect the heartbeat row before this change. 4. Observe that the process identity can be null and recovery cannot adopt the live child. **Reproduced on** - Paperclip `master` before this change. - Linux source deployment. - `codex_local` with ACPX `0.12.0`. ## What Changed - Add an awaited `onAgentSpawn` lifecycle hook to the patched ACPX runtime. - Forward the ACP child PID and start time through the adapter `onSpawn` callback. - Keep a mutable callback sink for cached runtimes so a later respawn updates the current heartbeat. - Reuse the last known process identity when a compatible warm heartbeat reuses the existing child. - Kill the ACP child and fail session startup when the PID is invalid or identity persistence rejects. - Add ACPX and heartbeat recovery tests for callback ordering, warm reuse, failure cleanup, durable row identity, and hot-restart adoption. - Document the one-time drain required when an installed pre-fix run already lacks process metadata. ## Verification - `PAPERCLIP_HOME="$PAPERCLIP_RUN_SCRATCH_DIR/test-home-execute-escalated" pnpm exec vitest run packages/adapter-utils/src/acpx-engine/execute.test.ts` — 89 passed. - `PAPERCLIP_HOME="$PAPERCLIP_RUN_SCRATCH_DIR/test-home-recovery-escalated" pnpm exec vitest run server/src/__tests__/heartbeat-process-recovery.test.ts` — 92 passed. - `PAPERCLIP_HOME="$PAPERCLIP_RUN_SCRATCH_DIR/test-home-remote-smoke-escalated" pnpm exec vitest run packages/adapter-utils/src/acpx-engine/remote-spawn-smoke.test.ts` — 3 passed. - `PAPERCLIP_HOME="$PAPERCLIP_RUN_SCRATCH_DIR/test-home-ci-repro-escalated" pnpm exec vitest run server/src/__tests__/heartbeat-dependency-scheduling.test.ts` — 6 passed. - `pnpm --filter @paperclipai/adapter-utils typecheck` — passed. - Reverse and forward dry-run application of `patches/acpx@0.12.0.patch` — passed. - `git diff --check` — passed. - `git diff --exit-code origin/master...HEAD -- pnpm-lock.yaml` — passed. - `git diff --exit-code origin/master...HEAD -- .github/workflows` — passed. ## Risks - Runtime risk is low to moderate. ACPX now awaits process-identity persistence during child startup. - ACPX kills the child when persistence fails. This prevents an unsupervised process, but it makes that heartbeat fail visibly. - A compatible warm heartbeat reuses the identity of the existing ACP child. Regression tests verify that identity is persisted before the next prompt. - The change updates the vendored ACPX patch. Package installation must apply that patch. - There are no schema, migration, public API, UI, workflow, or lockfile 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 used GPT-5.3-Codex for the earlier implementation. - OpenAI Codex used GPT-5 for the lifecycle-hook revision and the current fail-closed review fix. The runtime did not expose a more specific snapshot ID or context-window size. Both runs used 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> |
||
|
|
53bcf3897f |
feat: sync @-mentioned projects into remote sandboxes (confined sandbox transport, flag ON) (#10564)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The run layer must move project context into the sandbox that executes the agent > - Local sandboxes already stage referenced projects for @-mentions > - Remote confined sandboxes dropped the whole referenced set, so the agent lost needed files and paths > - This pull request keeps the confined sandbox transport aligned with the local behavior for referenced projects > - It does this behind a remote-only flag that defaults on, while SSH keeps the old drop-only path > - The benefit is that remote runs can read the same referenced project context that local runs already provide ## Linked Issues or Issue Description **Subsystem affected** Cross-cutting. This change touches server orchestration, sandbox transport, and observability. **Problem or motivation** A run can @-mention another project. Local targets stage each referenced project and give the agent a path. Remote confined sandboxes dropped the full referenced set, so the agent could not read those project files or paths. **Proposed solution** Enable referenced-project sync for the confined sandbox transport behind `PAPERCLIP_MULTI_PROJECT_WORKSPACE_SYNC_REMOTE`, which defaults on. Keep the SSH transport out of scope and keep it dropping referenced projects. Repoint each referenced workspace hint at its staged `project-<projectId>` sandbox directory. Publish `PAPERCLIP_WORKSPACES_JSON` on the confined sandbox lane. Count each per-project remote staging failure as a `staging` failure in the requested-vs-synced metrics. **Alternatives considered** Keep the remote path drop-only. That keeps the gap open. Move the change into SSH too. That expands scope beyond the target transport and adds risk. **Roadmap alignment** No matching item in `ROADMAP.md` showed up in this review. **Additional context** The change lands in three commits. The first commit opens the gate for the confined sandbox transport. The second commit repoints the workspace hints and publishes the workspace map. The third commit records per-project staging failure data. ## What Changed - Opened remote referenced-project sync for the confined sandbox transport behind `PAPERCLIP_MULTI_PROJECT_WORKSPACE_SYNC_REMOTE`. - Repointed referenced workspace hints to the staged `project-<projectId>` sandbox directories and published `PAPERCLIP_WORKSPACES_JSON`. - Counted per-project remote staging failures as first-class `staging` failures in the requested-vs-synced observability. ## Verification - The pushed ref `refs/heads/feat/sync-referenced-projects-remote-sandbox` resolves to the authorized submit SHA. - `git log --oneline origin/master..origin/feat/sync-referenced-projects-remote-sandbox` shows exactly the three expected commits. - The handoff reports server typecheck clean, adapter-utils typecheck clean, and the listed unit tests passing. - The handoff also reports no open review comments and no Greptile score yet. ## Risks - The change touches authorization and sandbox path handling, so regressions could block remote runs or expose the wrong project context. - The new flag defaults on, so any bug in the remote path affects normal remote use. - SSH stays out of scope, so the two transport paths must remain distinct. ## Model Used OpenAI GPT-5 via Codex. Tool use enabled. Context window not reported in this run. ## 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> |
||
|
|
b01f423cd7 |
fix(server): export manual OpenTelemetry spans (#10565)
## Thinking Path > - Paperclip uses the server to coordinate agent work. > - The server emits manual OpenTelemetry spans for startup, heartbeat, and sandbox execution. > - Those spans need the shared OpenTelemetry API package and a type-safe exporter path. > - Without the direct API dependency, the tracer stays no-op and the spans do not reach the collector. > - This pull request adds the direct dependency and the exporter cast. > - The benefit is that the manual spans can export cleanly at runtime. ## Linked Issues or Issue Description **What happened?** The server resolved the tracer with a runtime import, but `server` did not declare `@opentelemetry/api`. The manual spans stayed no-op, so the collector did not receive them. **Expected behavior** The server should load the shared OpenTelemetry API package, create the manual spans, and export them. **Steps to reproduce** 1. Start the server with telemetry enabled. 2. Run startup, heartbeat, or sandbox execution paths. 3. Observe that the manual spans do not export before this change. **Paperclip version or commit** `f91df236dfd8e5e6210941c80efeb0a7953bbe50` **Deployment mode** Built from source with `pnpm dev` or `pnpm build`. ## What Changed - Added `@opentelemetry/api` as a direct `server` dependency. - Cast the `traceExporter` value to `never` so the type check passes without a static `SpanExporter` import. - Kept the optional OTLP and SDK packages behind dynamic import. ## Verification - `pnpm build` in `server/` passed. - `server/src/instrumentation.ts` does not import `SpanExporter`. - `server/package.json` lists `@opentelemetry/api` at `^1.9.0`. ## Risks - Low risk. The change touches dependency metadata and one type cast. - Runtime telemetry still needs live collector QA. ## Model Used - OpenAI Codex, GPT-5, tool use 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] 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> |
||
|
|
51bb41c7e3 |
Expose the running build commit on the unauthenticated health response (#10563)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Instances run self-hosted or under hosting/deploy tooling, and operators need to observe what build a server is actually running > - `/api/health` carries the git SHA only inside `serverInfo`, which is gated to board/agent actors — anonymous callers get a redacted body with no version signal at all > - Deploy tooling that manages instances from outside (fleet rollouts, hosting providers, upgrade scripts) therefore cannot ground-truth that a deploy actually shipped without holding credentials > - A build commit is a plain git SHA of this public repository — it is not a secret, and gating it buys no security while blocking legitimate verification > - This pull request surfaces the running build commit as a top-level `commit` field on every `/api/health` response, including the redacted anonymous one > - The benefit is credential-free deploy verification: any operator or tool can confirm which commit an instance serves, while the fuller `serverInfo` block stays access-controlled as before ## Linked Issues or Issue Description No existing public issue — inline description following the feature request template: **Subsystem affected** Server (API, runs, routes) **Problem or motivation** An anonymous `GET /api/health` returns a redacted body with no version information; the running git SHA exists only in `serverInfo.git.fullSha`, which requires a board/agent actor. External deploy tooling (fleet rollouts, hosting providers, upgrade scripts) therefore cannot verify that an instance is actually serving the build it was just upgraded to — a rollout that silently keeps running the old image is indistinguishable from a successful one at the health endpoint. **Proposed solution** Surface the running build commit as a top-level nullable `commit` field on every `/api/health` response shape, including the redacted anonymous one, while keeping the fuller `serverInfo` block access-controlled as before. A build commit is a plain git SHA of this public repository — exposing it costs nothing and enables credential-free deploy verification, like the `version` endpoints on most server software. **Alternatives considered** Authenticating deploy tooling as a board actor to read `serverInfo` — rejected: it forces credential plumbing into infrastructure that only needs a public SHA, and adds a whole class of auth-misconfiguration failure to deploy verification. **Roadmap alignment** Not on ROADMAP.md; a small operational observability improvement, no overlap with planned core work. ## What Changed - `server/src/routes/health.ts`: derive `commit` from the server info snapshot (`serverInfo.git.fullSha` when git metadata is available, else `null`) and include it as a top-level field on every `/api/health` response shape — the redacted anonymous body, the full-details body, the no-db body, and the 503 database-unreachable body. - `serverInfo` itself remains gated to full-details responses exactly as before; only the bare commit is newly public. - `server/src/__tests__/health.test.ts`: updated exact-shape assertions to include `commit`, and added an assertion that `commit` is `null` (not omitted) when git metadata is unavailable. The redacted-response tests now pin that anonymous callers receive the commit. ## Verification - `pnpm vitest run src/__tests__/health.test.ts` in `server/` — 13 tests pass, including the redacted-anonymous shapes (which now pin the `commit` field) and the git-unavailable `null` case. - `tsc -p server/tsconfig.json --noEmit` — clean. - Manual: `curl -s https://<instance>/api/health` as an anonymous caller returns `"commit": "<full sha>"` alongside the existing redacted fields. ## Risks - **Version disclosure:** anonymous callers can now fingerprint the exact running commit. This is a deliberate trade-off: the builds are of a public repository (the SHA reveals no private code), the endpoint already responds to anonymous callers, and the operational value — verifying deploys actually shipped — outweighs the marginal fingerprinting surface. Operators who consider this sensitive are typically fronting `/api` with their own access controls already. - Otherwise low risk: no behavioral change to any gated field, no schema or API-surface removal; `commit: null` keeps the field shape stable when git metadata is absent (e.g. non-git installs). ## Model Used Claude Opus 4.8 (`claude-opus-4-8`, extended thinking, via Claude Code with tool use and code execution) authored the change and tests; finalized and PR'd under Claude Fable 5 (`claude-fable-5`). ## 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 (none needed beyond code comments — health endpoint has no standalone doc) - [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 |
||
|
|
d295251550 |
feat(adapter-claude): add Claude Sonnet 5 to the static model fallback (#10280)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents pick their model from a dropdown in agent config, populated
per-adapter by `listAdapterModels()` → each adapter's live provider
catalog merged over a static fallback list
> - For `claude_local`, newer model ids only reach the dropdown via the
live Anthropic `/v1/models` fetch, which needs a server
`ANTHROPIC_API_KEY`, a <5s round-trip, non-Bedrock mode, and account
entitlement; on any miss it silently falls back to the static `models`
array
> - Claude Sonnet 5 (`claude-sonnet-5`) is a current flagship but was
absent from that static fallback, so it appeared only when live
discovery happened to succeed — i.e. "the newest model doesn't
consistently show up"
> - This pull request adds `claude-sonnet-5` to the `claude_local`
static model list so it is selectable regardless of the live-discovery
path
> - The benefit is a consistent, reliable dropdown that no longer
depends on a flaky live fetch to surface a shipped flagship model
## Linked Issues or Issue Description
No public GitHub issue. The bug is described inline following the
bug-report template:
**What happened**
The `claude_local` agent-config model dropdown intermittently omitted
Claude Sonnet 5. `claude-sonnet-5` was missing from the adapter's static
fallback `models` array (`packages/adapters/claude-local/src/index.ts`),
so it only surfaced when the live Anthropic `/v1/models` discovery
happened to succeed.
**Expected behavior**
Claude Sonnet 5 is a shipped flagship model and should always be
selectable in the dropdown, independent of whether live discovery
succeeds.
**Steps to reproduce**
1. Run the server without a working live Anthropic `/v1/models` path (no
`ANTHROPIC_API_KEY`, Bedrock mode, a discovery timeout, or a cache
miss).
2. Open agent config for a `claude_local` agent and inspect the model
dropdown.
3. Observe that `claude-sonnet-5` is absent because the static fallback
list omitted it.
**Deployment mode**
Self-hosted / local adapter (`claude_local`); the server process reads
`ANTHROPIC_API_KEY` from its environment.
## What Changed
- Added `{ id: "claude-sonnet-5", label: "Claude Sonnet 5" }` to the
`claude_local` static `models` fallback, immediately after
`claude-opus-4-8` (so Opus 4.8 stays the default first option).
- Added an explicit regression assertion in
`server/src/__tests__/adapter-models.test.ts` that `claude-sonnet-5` is
present in the `claude_local` fallback when live discovery is
unavailable.
## Verification
- `pnpm -C server exec vitest run src/__tests__/adapter-models.test.ts
-t "claude fallback"` — **passes** (the new `claude-sonnet-5` assertion
included).
- Reviewed the consuming tests: the fallback test also asserts
`models[0]?.id === "claude-opus-4-8"` (still index 0 — Sonnet 5 is index
1, unaffected); `adapter-registry.test.ts` reads `builtIn?.models`
dynamically, so no exact-array snapshot breaks.
- Change is a single static-data addition plus a test assertion; no
control-flow change.
## Risks
- Low risk. Pure additive change to a fallback list; no control-flow
change. Worst case is an id that a given account isn't entitled to,
which the existing "current"/manual-model UI paths already tolerate.
## Model Used
Claude (Anthropic), model id `claude-opus-4-8` (Opus 4.8), extended
thinking + tool use, run as the Paperclip CTO agent.
## 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 (branch is the assigned
execution-workspace branch and cannot be renamed this run)
- [x] I have run tests locally and they pass (server adapter-models
"claude fallback" case)
- [x] I have added or updated tests where applicable (explicit
`claude-sonnet-5` fallback assertion)
- [x] I have updated relevant documentation to reflect my changes (n/a —
no docs reference this list)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending CI)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review)
- [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>
|
||
|
|
cd7f84965c |
fix(recovery): preserve hand-back wake liveness (#10562)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The heartbeat service delivers issue work to assigned agents. > - Recovery can hand an issue back to its agent while the recovery run is still active. > - The hand-back wake can merge into that active run and disappear when the run exits. > - The stranded-work scan also treats the successful recovery run as proof that the handed-back issue is live. > - This pull request keeps the hand-back wake for follow-up delivery and lets the scan repair a lost wake. > - The benefit is that an assigned issue continues after recovery without manual operator action. ## Linked Issues or Issue Description No public issue exists. This is related to the wake reconciliation work in #8943. **What happened?** A recovery action could hand an assigned issue back from `blocked` to `todo`. The `issue_recovery_action_restored` wake then merged into the recovery run that made the change. The wake disappeared when that run exited. The stranded-work scan did not repair the issue because it treated the successful recovery run as current liveness. **Expected behavior** Paperclip must dispatch the hand-back wake after the recovery run exits. If that delivery is lost, the stranded-work scan must enqueue the assigned `todo` issue again. **Steps to reproduce** 1. Start a recovery run for an assigned blocked issue. 2. Resolve a recovery action with the `handed_back` outcome. 3. Move the issue to `todo` while the recovery run is still active. 4. Observe that the wake merges into the active run and no new run starts after it exits. 5. Run the stranded-work scan and observe that the successful latest run prevents repair. **Paperclip version or commit** `131d476a7e` **Deployment mode** Local dev (`pnpm dev`). The defect is in the core server and is not deployment-specific. **Agent adapter(s) involved** Not adapter-specific. This is a core heartbeat and recovery defect. ## What Changed - Added `issue_recovery_action_restored` to the wake reasons that require follow-up delivery when an issue run is active. - Made the stranded-work scan detect a resolved hand-back that occurred during or after the latest successful run. - Added focused regression tests for the heartbeat coalescing seam and the stranded-work repair shape. - Documented the hand-back liveness guarantee in execution semantics section 9.1. ## Verification - `pnpm exec vitest run server/src/__tests__/heartbeat-comment-wake-batching.test.ts server/src/__tests__/heartbeat-process-recovery.test.ts` passed: 104 tests. - `pnpm --filter @paperclipai/server typecheck` passed. - `pnpm -r typecheck` passed. - `pnpm build` passed. - `pnpm test:run` passed the server shard (3,095 passed, 2 skipped) and UI shard (3,182 passed). One unrelated CLI test failed because the agent environment exports static AWS credentials. `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY -u AWS_SESSION_TOKEN pnpm exec vitest run cli/src/__tests__/secrets.test.ts` passed all 8 tests. - `git diff --check` passed. - All GitHub checks passed on commit `8b380e67e6`. - Greptile gave 5/5 confidence with no comments or unresolved threads. ## Risks - Low risk. The follow-up rule affects only a recovery hand-back wake that arrives while the same issue already has an active run. - The backstop adds one indexed recovery-action lookup for an assigned `todo` issue whose latest run succeeded. - The timestamp check uses the latest run start time. This includes hand-backs made by that run and later hand-backs, but excludes older resolved actions. ## Model Used - OpenAI Codex with GPT-5 (`gpt-5`), agentic reasoning, tool use, and code execution. The serving context-window size is not exposed to the agent. ## 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> |
||
|
|
b4a7a12985 |
feat: make recovery updates quieter (#10542)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The recovery subsystem restores work after an agent run stops or loses state > - Recovery notices currently use the same visual weight as normal work comments > - Recovery agents can also post long narratives that obscure the useful hand-off > - The server must identify recovery output because agents cannot set presentation controls > - This pull request adds compact recovery notices, structured action references, and brief recovery prompts > - The benefit is a quieter issue thread that still keeps recovery state inspectable ## Linked Issues or Issue Description **Subsystem affected** Cross-cutting: `server/`, `packages/shared`, and `packages/adapter-utils`. **Problem or motivation** Recovery notices and recovery-run comments can dominate an issue thread. Operators must scan routine recovery narration before they find the work hand-off. **Proposed solution** Give routine recovery output a compact system-notice presentation. Derive the presentation on the server so agents cannot hide arbitrary comments. Keep the successful missing-state summary fully visible because that comment is the recovery deliverable. **Alternatives considered** The UI could detect recovery text. That approach is fragile and does not provide structured action references. Agents could also set presentation directly, but that would weaken the current board-only security boundary. **Roadmap alignment** This change refines the completed “Self-healing runs & automatic recovery” and “Enforced Outcomes” roadmap areas. It does not add a competing roadmap capability. **Additional context** The scope covers shared comment validation, server recovery notices, agent-comment derivation, and recovery prompt text. No database migration is needed because presentation data already uses JSON. ## What Changed - Add the `compact` issue-comment presentation density to shared constants, types, and validation. - Give recovery escalation, waiting, and in-place notices compact titles and structured recovery-action metadata. - Use recovery-action metadata for notice deduplication, with the legacy text marker as a compatibility fallback. - Derive compact presentation for comments from recovery-scoped runs while preserving the board-only presentation boundary. - Keep successful missing-state recovery summaries fully visible. - Ask recovery participants to record outcomes in `resolutionNote` and keep source-issue comments brief. - Add shared, route, service, and prompt tests for the new behavior and exceptions. ## Verification - `pnpm -r typecheck` - Focused Vitest coverage: 320 tests passed across shared validators, adapter prompts, issue comments, recovery actions, and heartbeat recovery. - Full server phase: 292 files passed, 3,094 tests passed, and 2 tests skipped. - Full UI phase: 386 files passed and 3,182 tests passed. - `pnpm build` - Known master baseline: `cli/src/__tests__/secrets.test.ts` expects `pass`, but the current implementation returns `warn` when strict secret mode is disabled for Postgres. This branch does not change CLI secrets code. ## Risks - Low migration risk. The presentation column is JSON and needs no database migration. - Recovery-run detection depends on the persisted run context snapshot. - Structured metadata becomes the primary deduplication key. The existing body marker remains as a fallback for older comments. > 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.6-sol`. The runtime did not expose the context-window size. The model used agentic 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> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
6a3cbe1c58 |
fix(ui): load the full selected timeline window (#9576)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The timeline page visualizes company activity across a selected date window > - The UI requested only the first paginated issue batch even when the selected zoom covered seven or thirty days > - A busy company could therefore render an incomplete timeline while the controls implied the full window was loaded > - The timeline query needs to exhaust the API pagination for the selected date range and combine each page without duplicating shared timeline records > - This pull request adds a paginated window loader, merges the returned timeline data, and covers the multi-page behavior with a regression test > - The benefit is that the visible timeline matches the selected zoom window instead of silently omitting later issues ## Linked Issues or Issue Description ### Pre-submission checklist - [x] I searched existing open and closed issues and pull requests; no matching report or implementation was found. - [x] I reproduced the behavior against the pre-change `master` implementation. - [x] I confirmed the error originates in Paperclip's core timeline UI, not an adapter, provider, or local configuration. ### What happened? Selecting the default seven-day timeline range loaded only the first API page (up to 500 issues). Companies with more activity therefore displayed incomplete data even though the controls showed the full selected window. ### Expected behavior The timeline should load all issue pages that fall within the selected date window. ### Steps to reproduce 1. Open the company timeline for a date range containing more than 500 issues. 2. Keep the default seven-day range or select another multi-day preset. 3. Observe that only the first page of issue-backed timeline data is shown. ### Paperclip version or commit Pre-change `master`. ### Deployment mode Local dev source build. The behavior is not adapter-specific and is independent of database mode and access context. ### Privacy checklist - [x] No logs, configuration, personally identifiable information, or user data are included. ## What Changed - Added pagination parameters to the timeline API client contract. - Added a timeline window loader that requests every issue page and deduplicates actors, spans, events, and edges while preserving pagination metadata. - Switched the timeline query to use the complete-window loader. - Added a regression test proving a 501-issue window loads both API pages and combines their records. - Preserved delegation events and edges when parent and child issues fall on different API pages, with a server regression test. ## Verification - `pnpm exec vitest run server/src/__tests__/work-timeline-service.test.ts ui/src/pages/Timeline.test.tsx` — 16 tests passed. - `pnpm --filter @paperclipai/ui typecheck` — passed. - `pnpm --filter @paperclipai/server typecheck` — passed. - `pnpm check:token-gates` — all gates clean. - `git diff --check origin/master...HEAD` — passed. - Remote CI: build, typecheck, both e2e shards, canary, policy, security, every general/serialized test shard, and the aggregate `verify` gate passed on head `24784b28e9`. ## Risks - Low risk: the change is isolated to timeline data loading and has no schema or API endpoint changes. - Large date windows now make sequential requests for all issue pages, increasing request count for very active companies; the 500-item page size bounds each response. - Merged records rely on stable identifiers or composite event/edge keys; the regression test covers cross-page combination and deduplication 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 using GPT-5.4 with reasoning, repository tool use, shell execution, and test execution. The runtime does not expose the exact 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> |
||
|
|
dd1a7f5290 |
Ensure app-home ownership before the privilege drop, not only on remap (#10530)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The Docker image persists all instance state (project checkouts, worktrees, run logs, uploads) under `PAPERCLIP_HOME`, and deployments mount a volume there for durability > - The entrypoint starts as root and drops privileges to the `node` user, but it fixes `PAPERCLIP_HOME` ownership only when it remaps the user's UID/GID > - A freshly mounted volume arrives root-owned and shadows the image's build-time `chown`, so a default-UID boot drops privileges onto an unwritable home and the server crashes on its first `mkdir` > - This pull request makes the entrypoint probe the home's ownership and chown whenever it does not match the runtime user, before the privilege drop > - The benefit is that the image works out of the box on any platform-managed volume, with the common already-correct boot staying chown-free ## Linked Issues or Issue Description No public issue exists — describing the bug inline (per the bug report template). **What happened?** Running the image with a freshly created volume mounted at `/paperclip` (a Docker named volume, a Kubernetes PV, or any platform-managed volume) and the default `USER_UID`/`USER_GID` crashes on boot: `Error: EACCES: permission denied, mkdir '/paperclip/instances/default/logs'`. **Expected behavior** The container boots and initializes its instance tree on the mounted volume, exactly as it does when `/paperclip` is the image's own (build-time chowned) directory. **Steps to reproduce** 1. `docker volume create paperclip-data` 2. `docker run -v paperclip-data:/paperclip ghcr.io/paperclipai/paperclip:<any current tag>` 3. Observe the EACCES crash on the first `mkdir` under `/paperclip`. **Root cause** `scripts/docker-entrypoint.sh` chowns `/paperclip` only inside its UID/GID remap branch (`changed=1`). A fresh volume mount is root-owned and shadows the image's build-time `chown node:node /paperclip`; with the default 1000:1000 no remap happens, so no chown happens, and `gosu node` drops onto an unwritable home. **Paperclip version or commit:** reproduces on `master` and any published image. **Deployment mode:** any; observed on managed-cloud volume mounts and reproducible with plain Docker named volumes. **Installation method:** Docker image (`ghcr.io/paperclipai/paperclip`). **Related PRs (dedup search):** no open or merged PR touches the entrypoint ownership logic; the entrypoint's privilege-handling tests were added previously and this extends them. No duplicate found. ## What Changed - `scripts/docker-entrypoint.sh`: the remap-conditional `chown` is replaced by an ownership probe — after any UID/GID remap, the entrypoint stats `PAPERCLIP_HOME` (default `/paperclip`) and runs `chown -R node:node` only when the owner does not match the runtime user, before `exec gosu node`. Covers fresh root-owned mounts and trees written under a previous UID mapping; the already-correct boot performs no chown. The unprivileged (non-root start) branch is unchanged. - `server/src/__tests__/docker-entrypoint.test.ts`: `stat` stub added to the harness; new cases for the fresh root-owned mount with default UID/GID and for `PAPERCLIP_HOME`-relative probing; the remap case now models the post-remap ownership mismatch. ## Verification - `pnpm vitest run server/src/__tests__/docker-entrypoint.test.ts` — 7 passed (5 existing behaviors unchanged, 2 new). - Live on a managed deployment: a container that crash-looped with the EACCES above boots cleanly once the home is chowned before the drop (the same effect this entrypoint change produces; forced there by a UID remap as an interim workaround). ## Risks - Low. Behavior changes only for boots where `PAPERCLIP_HOME` exists with mismatched ownership — exactly the boots that crash today. `chown -R` on a large previously-mismatched tree adds one-time boot latency; correctly-owned homes skip it entirely. Kubernetes restricted / OpenShift non-root starts keep the existing exec-directly path untouched. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic; Claude Code CLI with extended thinking and tool use; tests executed locally via Vitest). ## 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 (no duplicates; extends the existing entrypoint privilege tests) - [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 |
||
|
|
075951f6bd |
Fix import completion UX: inbox flood, false-failure message, stale company list (#10538)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Company Import/Export (#10507, hardened in #10523 and #10531) now imports a large company end to end via an async job > - A real 1,418-issue import succeeded, but three rough edges showed up in that success > - Imported issues flooded the inbox, a completed import surfaced a false "failed" message after its in-memory result expired, and the new company didn't appear in the switcher until a manual refresh > - This pull request keeps imported issues out of the inbox, treats an expired-but-completed import as success, and refreshes the company list on completion > - The benefit is that a successful import looks and feels successful, and doesn't bury the user's inbox in historical tasks ## Linked Issues or Issue Description - Refs #10507 / #10523 / #10531 (Import/Export and its hardening). No open issue; three post-import bugs described above. ## What Changed - **Imported issues no longer flood the inbox.** The inbox "mine" tab is a query: an issue is "touched" if the user authored a comment on it, and import re-attributes bundled user comments to the importing user — so every imported issue appeared. Import now seeds a per-user `issue_inbox_archives` row for each imported issue (via a batched `issues.archiveImportedInbox`), the exact table the inbox visibility query excludes. Gated on an actor user id, so agent/system imports and normal issue creation are untouched; genuine new activity still resurfaces the issue. - **A completed import no longer shows a false failure.** The in-memory job's terminal retention was 5 minutes, so a poll after that 404'd and the UI showed "failed." Retention is extended to 60 minutes — the real mitigation for a user who steps away during a long import. `watchImportJob` additionally treats a *server-confirmed* success whose full result is no longer retained (a `succeeded` status carrying only the compact summary — a cloud tenant job, or a board job whose full in-memory result aged out) as a soft success ("import completed — open the company"), navigating by the summary's company id. A 404 while the job is still being watched is *not* treated as success: a running job is never dropped by the retention sweep, so its disappearance means a restart mid-import that may not have finished, and it surfaces the honest "may have restarted while the import ran" error. A first-poll 404 (the id never existed) is likewise a real error. - **The imported company appears without a refresh.** `onSuccess` now invalidates the companies/switcher query unconditionally (covering both the full-result and expired-but-completed paths) and navigates by the job's company id. ## Verification - shared/server/ui typechecks clean; 15 UI tests in the touched spec green, plus the embedded-Postgres import batching and portability-routes suites. - New tests: embedded-Postgres test that imported touched issues are archived for the actor and excluded from the inbox query while a normally-created issue still appears; job resolvable at the old window+1 and only 404s past 60 min; UI soft success on a server-confirmed `succeeded` job without a retained full result (no error, list invalidated, navigates by company id), a running-then-gone job → honest error (restart mid-import), and a first-poll 404 → error. ## Risks - Low and import-scoped: the inbox archive only affects imported issues for the importing user; normal issue creation and non-user (agent/system) imports are unchanged. Retention extension is a constant; the async job store remains in-memory by design. A restart mid-import still 404s and is surfaced honestly as a possible failure (never masked as success); only a server-confirmed success whose full result has expired is reported as a soft success. ## Model Used - Implementation: Claude Fable 5 (`claude-fable-5`, Anthropic). Review hardening (the confirmed-success narrowing): Claude Opus 4.8 (`claude-opus-4-8`, Anthropic). Both via the Claude Code CLI with extended thinking + tool use; root-caused against the live import. ## 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 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 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 |
||
|
|
9f7565f4ce |
feat: activate OpenTelemetry spans on the sandbox start path (#10536)
## Thinking Path > - Paperclip moves agent work through sandboxed execution and control-plane services. > - The sandbox start path now has a no-op span seam. > - This change turns that seam on when OTLP export is configured. > - It keeps the default path unchanged when export is off. > - The result is structured startup traces with low-cardinality attributes and explicit parent links. > - The benefit is better observability without changing normal behavior. ## Linked Issues or Issue Description No public GitHub issue exists for this change. ### Problem The sandbox start path has a tracer seam, but it stays a no-op unless the OTLP export path is active. ### Proposed solution Enable the server tracer on sandbox bring-up, open a root span, parent each startup boundary to that root, and keep the export path opt-in behind `OTEL_EXPORTER_OTLP_ENDPOINT`. ### Alternatives considered - Keep the start path as a no-op. I rejected that path because it leaves sandbox start opaque when OTLP export is already configured. - Add broad attributes for commands and paths. I rejected that path because the span allowlist must stay low-cardinality. ### Roadmap alignment This follows the current OTel sandbox-start work and keeps the default path unchanged. ## What Changed - Add a root sandbox startup span and child spans for each named startup boundary. - Keep concurrent bridge spans parented to the root span. - Inject the server tracer through the adapter deps without OpenTelemetry imports in the engine. - Attach host-received provider duration attributes only when the values are finite. - Keep span attributes inside the allowlist and keep command, path, id, and error text out of span data. ## Verification - The pushed branch already passed `pnpm --filter @paperclipai/adapter-utils exec tsc --noEmit`. - The pushed branch already passed `pnpm exec vitest run packages/adapter-utils/src/acpx-engine/`. - The pushed branch already passed `pnpm --filter @paperclipai/server exec vitest run src/__tests__/environment-execution-target.test.ts src/__tests__/instrumentation.test.ts`. - The pushed branch already passed `pnpm --filter @paperclipai/server exec tsc --noEmit`. ## Risks - OTel export changes trace volume when the endpoint is set. - The allowlist limits trace detail, so new fields need care. - The change stays no-op when OTLP export is off. ## Model Used - OpenAI GPT-5, tool use 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 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 existing issues or described the issue in-PR - [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 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> |
||
|
|
5ec7ce76e5 |
Upload company import packages as compressed zip uploads (fix large-company imports through Cloud) (#10531)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Company Import (#10507, hardened in #10523) lets a user upload a company package on the Import page > - The page expanded the user's `.zip` into a files map and POSTed it as ONE inline JSON body — ~40MB for a real company because attachment blobs get base64-inflated > - On Paperclip Cloud that body travels browser → harness proxy → tenant, where it truncated in transit → body-parser 400 → the browser saw "Failed to fetch", and nothing imported > - Two compounding causes: the giant inline body itself, and the board async opt-in riding an `x-paperclip-cloud-*` header that the Cloud harness strips as anti-spoofing (so async never engaged and the import held one fragile synchronous connection) > - This pull request uploads the raw compressed `.zip` as a multipart request (about a third the size, already compressed) parsed server-side into the same bundle the importer consumes, and moves the async opt-in to a proxy-safe `?async=1` > - The benefit is that a large-company import actually completes through Cloud: a small compressed upload, a real async job that survives dropped connections ## Linked Issues or Issue Description - Refs #10507 / #10523 (Import/Export and its hardening). No open issue; problem described above (large-company browser import through a proxy: inline JSON body truncates → 400 → "Failed to fetch"; async opt-in header stripped by the front door → async never engages). ## What Changed - **Multipart zip transport.** The Import page uploads the raw `File` as `multipart/form-data` (field `package`, import options in a JSON `meta` field); the server unzips it into `{ rootPath, files }` and runs the exact existing preview/import logic. The `application/json` inline path is byte-identical for CLI/programmatic callers. Bare `application/zip` (meta via `?meta=`) is also accepted for programmatic use. - **Shared node zip reader.** `packages/shared/src/portability-zip.ts` (node-only subpath, not re-exported to the browser bundle — same pattern as `portability-hash.ts`); the CLI's `zip.ts` becomes a thin re-export. Identical codec (STORE + DEFLATE via `inflateRawSync`, rejects data descriptors/zip64). - **Proxy-safe async signal.** `wantsAsyncImport` = `?async=1` (board browsers, survives the harness) OR the existing `x-paperclip-cloud-async-import` header (cloud tenants, set server-side). The UI async client now uses `?async=1`. Backward compatible. - **Size + preflight.** New `PORTABLE_ZIP_UPLOAD_LIMIT_BYTES = 128MB`; the inline 56MB preflight no longer gates the zip path (it shows the compressed size instead). Async submit/poll/resume, the duplicate-guard fingerprint (now over the resolved bundle), pause-on-import, progress/error panels, and activation all apply to the multipart path. - OpenAPI documents json + multipart + zip bodies and the `async` query param. ## Verification - Full typecheck chain (shared, server, ui, cli) clean. - 152 tests across 8 files: new `portability-zip.test.ts` (STORE/DEFLATE/base64-blob byte-exact round-trip, truncation throws, data-descriptor rejection); `company-portability-routes.test.ts` +7 (multipart import+preview equals the inline bundle; async multipart 202→poll→success; board async via `?async=1` with no cloud header; cloud-tenant async via header; sync fallback with neither; truncated-zip 400, nothing imported); `CompanyImport.test.tsx` asserts the local zip sends the raw File and the inline preflight no longer blocks; `openapi-routes.test.ts` green. - NOT yet measured: the end-to-end browser upload through the live Cloud harness — verified on staging after deploy before closing out. ## Risks - Import semantics unchanged — only transport changed; the JSON inline path is byte-identical, the cloud-tenant header async path untouched. Multipart parsing is server-side (memory-bound: a ~13MB zip → ~30MB files map, fine on the server). - The bare `application/zip` path is programmatic-only and covered by content-type dispatch but not a dedicated route test (the multipart path is). ## Model Used - Claude Fable 5 (`claude-fable-5`, Anthropic), Claude Code CLI, extended thinking + tool use; root-caused against live logs/DB and the harness proxy source. ## 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 |
||
|
|
740554acc6 |
feat: add a no-op OpenTelemetry span seam for the sandbox startup path (#10522)
## Thinking Path > - Paperclip helps people run and govern AI agent work > - Sandbox startup needs a safe place to add telemetry spans without forcing OpenTelemetry on every run > - This change adds a no-op span seam, so the startup path can accept a tracer later and still stay inert now > - The server gets a lazy tracer accessor, and the adapter timing helper gets an injected tracer hook > - The change keeps the default path free of OpenTelemetry and keeps the existing startup event path unchanged > - The benefit is a future-safe seam with no runtime change today ## Linked Issues or Issue Description This PR addresses a feature gap in the sandbox startup path. ### Problem Sandbox startup has no safe span seam. A direct OpenTelemetry import would load telemetry packages on every run. ### Proposed Solution Add a lazy tracer accessor in the server. Add an injected no-op tracer seam in startup timing. ### Alternatives Import OpenTelemetry directly in the startup path. Reject that path because the default startup flow must stay inert. ## What Changed - Added a lazy startup tracer accessor in `server/src/instrumentation.ts`. - Added an injected startup tracer seam in `packages/adapter-utils/src/acpx-engine/startup-timing.ts`. - Kept the startup event path unchanged. - Kept `adapter-utils` free of OpenTelemetry imports. ## Verification - `pnpm exec vitest run packages/adapter-utils/src/acpx-engine/startup-timing.test.ts` - `pnpm exec vitest run server/src/__tests__/instrumentation.test.ts` - `tsc --noEmit` for `@paperclipai/adapter-utils` and `@paperclipai/server` ## Risks Low risk. The default tracer is a no-op, so the runtime path stays inert until a later change injects a real tracer. ## Model Used OpenAI 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 found none - [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 or instance-local Paperclip issues or links - [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 - [ ] 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 - [ ] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
7cfb655f60 |
fix(worktree): disable automatic database backups (#10520)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Paperclip creates isolated instances for linked git worktrees so development does not affect the primary instance > - Those instances inherited the source instance's automatic database-backup setting and also repaired older configs without overriding it > - As worktrees accumulated, each isolated instance could schedule its own backup stream, producing redundant backup churn for disposable database clones > - This pull request makes backup disablement an invariant of worktree config creation and repair > - The benefit is that automatic backups remain focused on the durable primary instance while isolated development instances stop accumulating redundant backup files ## Linked Issues or Issue Description No public GitHub issue exists for this bug, so the report is included here. The closest related open change is Refs #10266, which hardens where worktree config repair may write; this PR changes the backup policy applied by that repair and by worktree initialization. ### What happened? Isolated worktree instances copied `database.backup.enabled` from their source config. When the source instance enabled automatic backups (the normal default), every linked worktree also enabled a scheduled backup stream. Existing worktree configs kept that state during startup repair, so the redundant backups continued after the policy changed. ### Expected behavior Automatic database backups are disabled for isolated worktree instances created by `paperclipai worktree init` or `paperclipai worktree:make`, and legacy worktree configs are migrated to that policy during normal startup repair. The durable primary/default instance keeps its existing backup behavior. ### Steps to reproduce 1. Start from a Paperclip instance whose database backup setting is enabled. 2. Create or initialize a linked worktree with `paperclipai worktree init`. 3. Inspect the generated worktree config and environment. 4. Before this change, the config retained `database.backup.enabled: true` and the environment had no disabling override; after this change, the config is false and `PAPERCLIP_DB_BACKUP_ENABLED=false` is persisted. ### Paperclip version, deployment mode, and environment - Reproduced against `master` before commit `ea5e0a0269`. - Deployment mode: local trusted development with linked git worktrees and embedded PostgreSQL. - Environment: Node.js 22, pnpm workspace install. ## What Changed - Always generate isolated worktree configs with automatic backups disabled. - Persist `PAPERCLIP_DB_BACKUP_ENABLED=false` in generated worktree environments. - Repair existing isolated worktree configs and environments that still enable backups. - Add CLI and server regression coverage for creation and legacy repair paths. - Document the worktree-specific backup policy and primary-instance exception. ## Verification - `pnpm exec vitest run cli/src/__tests__/worktree.test.ts server/src/__tests__/worktree-config.test.ts` — 52 tests passed. - `pnpm -r typecheck` — passed. - `pnpm build` — passed. - All repository commands above were run with inherited worktree runtime identity variables removed. ## Risks - Low operational risk: the change is limited to explicitly isolated worktree instances. - Operators who intentionally relied on automatic backups of disposable worktree databases will now need to run a manual backup or explicitly manage those files outside the scheduled worktree runtime. - No schema, migration, API, UI, lockfile, or workflow 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 based on GPT-5 (the runtime does not expose a more specific snapshot ID or context-window value), using reasoning, tool use, local code execution, and GitHub CLI integration. ## 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> |
||
|
|
276ae3a75d |
Harden company import: durable UI, async jobs, integrity guard, batched inserts (#10523)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Company Import/Export (#10507) moves whole companies between instances as portability bundles > - Real-world use on a large company (1,418 issues, ~10.6k comments) surfaced a cluster of related failures: the import took hours and the browser connection died while the server kept running, a retry silently produced a second partial import, the progress/error UI gave no durable signal, and a cloud-tenant user couldn't even open the companies afterward > - Root cause of the slowness: importBundle inserted every issue, comment, and document as a separate round-trip to a network Postgres — an N+1-over-network pattern > - This pull request hardens the whole import path: durable progress/error UI, an async server-side job so imports survive dropped connections (with a duplicate-submit guard), a fail-closed guard against incomplete payloads, and batched inserts that cut a large import from hours to minutes > - The benefit is that migrating a real, large company actually completes, is legible while it runs, and can't half-import twice ## Linked Issues or Issue Description - Refs #10507 (the Import/Export feature this hardens). Supersedes #10513 (the progress/error-UI piece, folded in here). No open issue; problem described above (large-company import: slow, connection-fragile, silently duplicable, opaque UI). ## What Changed - **Batched inserts (perf):** importBundle pre-generates entity ids in JS and inserts in chunked multi-row statements, so children no longer wait on parents' generated ids. A 1,418-issue import drops from ~15,600 insert statements to **82** (190×); benchmark below. Import semantics — collision handling, pause-on-import, label/blocker/monitor/attachment/embedded-asset handling, blob sha verification — are unchanged (full portability suite green). - **Async import jobs for board sessions:** the existing cloud-tenant async job path opens to board sessions with per-actor job keys; the import page submits, polls, and resumes watching after a reload or dropped connection instead of holding one fragile request. A non-terminal job blocks a duplicate submit (409 returns the running job), preventing the double-import. - **Fail-closed completeness guard:** an optional `expectedFileCount` on inline imports; the server rejects (422 `import_payload_incomplete`) a body carrying fewer files than declared, so a re-framed/short payload fails loudly instead of half-importing. - **Durable progress/error UI (was #10513):** persistent progress panels with size-aware copy, persistent error panels with retry guidance, and inline explanation when the preview button is disabled; request-lifecycle guards so stale previews/imports can't publish or detach. ## Verification - `pnpm -r` typechecks (shared, server, ui) clean. - `company-portability.test.ts` (76) + `company-portability-routes.test.ts` (30) green — the import correctness net — plus new `CompanyImport.test.tsx` async/resume/409 coverage and a new batching regression test (a 50-issue import issues <50 issue-insert statements; rows land unchanged). - **Batching benchmark (embedded Postgres):** at 1,418 issues × 7 comments × 1 doc — 82 insert statements vs ~15,598 one-per-row (190×), ~1s wall-clock; a row-verifying run at that scale imports all 1,418 issues / 9,926 comments / 1,418 documents with unique identifiers and no warnings (no rows dropped by chunking). Over a network DB the round-trip reduction is the hours→minutes lever. - What is NOT directly measured here: wall-clock against a real network Postgres (that happens on a staging deploy); the local timing is network-free. ## Risks - Batching is the load-bearing change: it rewrites the import write path. Mitigated by the unchanged 106-test correctness suite, a new scale/row-integrity test, and per-writer transactions (a failure rolls back its table group; not a single outer transaction across writers — noted, correctness preserved). - Async jobs are in-memory (lost on server restart → pollers 404 and can resubmit); matches the pre-existing cloud-tenant job semantics. - `expectedFileCount` is optional (older callers unaffected); over-count is allowed, only under-count fails closed. ## Model Used - Claude Fable 5 (`claude-fable-5`, Anthropic), Claude Code CLI, extended thinking + tool use; implementation across Fable 5 subagents with live diagnosis against a running instance. ## 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 |
||
|
|
1c52f02d34 |
Let cloud tenant sessions reach companies they hold memberships in (#10524)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - On Paperclip Cloud, each stack authenticates its users to the tenant app through trusted headers (`resolveCloudTenantActor`), which seed a primary company for the stack > - That actor was pinned to exactly one company — the seeded primary — regardless of any other companies the user actually holds a membership in > - Companies created later (via the import flow, or company creation) write real membership rows for the user, but the pinned actor ignored them, so those companies showed up in listings yet returned "User does not have access to this company" when opened > - This pull request unions the pinned primary with the user's own active membership rows, exactly as a locally authenticated session already does > - The benefit is that a Cloud user can reach every company they belong to — most visibly, a company they just imported ## Linked Issues or Issue Description - Refs #10507 (Import/Export — imported companies were unreachable on Cloud stacks). No open issue; bug described above (companies visible in listing but unreachable; expected: reachable when the user holds an active membership). ## What Changed - Extracted the session path's own active-membership query into `loadActiveUserCompanyMemberships(db, userId)` (single-sourced; the session path now calls it too). - `resolveCloudTenantActor` unions its result with the pinned primary: `companyIds = [primary, ...others]`, memberships likewise, primary first. Strictly per-user; a membership-read failure degrades to primary-only (mirrors the existing fail-closed owner-elevation pattern). No change to owner instance-admin elevation, grant seeding, the stale instance-admin purge, or trusted-header validation. - Grants are seeded at membership creation across all flows (company create, invite/join, import), not per request — so no extra seeding was added here. ## Verification - `@paperclipai/server` typecheck clean. - `cloud-tenant-actor.test.ts` (+ union / other-user-excluded / inactive-excluded / no-rows-identical cases), `auth-session-route.test.ts` (route-level: trusted headers reach a unioned company through `assertCompanyAccess`), plus agent-auth, authz-company-access, cross-company-authz, portability-routes — 83 tests green. ## Risks - Low and tightly scoped: only widens a Cloud actor's reachable companies to those it already holds active memberships in; users without extra memberships, other users' rows, and owner elevation are all unaffected. Read failure fails closed to primary-only. ## Model Used - Claude Fable 5 (`claude-fable-5`, Anthropic), 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 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 |
||
|
|
39666aa906 |
fix(server): clarify execution policy decision comments (#9105)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Execution policies let issues move through enforced review and
approval stages before work is considered done.
> - Active reviewers and approvers must include decision rationale when
approving or requesting changes.
> - The server already requires the decision comment to arrive in the
same `PATCH /api/issues/:id` request as the status change.
> - When callers post a normal comment first and then send a status-only
`PATCH`, the existing 422 only says a comment is required.
> - This pull request keeps the atomic decision behavior but makes the
API error and docs explicit about the same-request requirement.
> - The benefit is that agents and API clients can recover immediately
by sending `{ status, comment }` together instead of dead-ending on an
ambiguous validation error.
## Linked Issues or Issue Description
Fixes #9049.
Duplicate/related search completed before implementation: searched open
PRs for `9049`, `Approving a review or approval stage requires a
comment`, `Requesting changes requires a comment`, and `same PATCH`. I
did not find a direct open PR for the same error-message/docs fix.
Related PRs found but not duplicates: #8302 documents cross-agent review
gates in the skill API reference, and #5487 covers human approval UI.
## What Changed
- Expanded execution-policy 422 messages for approve and request-changes
decisions to say the comment must be included in the same `PATCH`
request and prior comments are not considered.
- Tightened unit coverage so the comment-required branches assert the
new actionable guidance.
- Documented the same-request `{ status, comment }` decision shape in
the execution policy guide, issue API reference, and agent communication
guide.
## Verification
- `CI=true corepack pnpm --filter @paperclipai/server exec vitest run
src/__tests__/issue-execution-policy.test.ts --reporter verbose`
- `CI=true corepack pnpm --filter @paperclipai/plugin-sdk exec node
../../../scripts/ensure-plugin-build-deps.mjs`
- `CI=true corepack pnpm --filter @paperclipai/server exec tsc --noEmit`
- `git diff --check`
## Risks
Low risk. This does not change execution-policy state transitions or
relax the atomic decision guard; it only makes the existing requirement
explicit in errors and docs.
> 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 Codex), operating in Codex desktop with repository
file access, shell validation, and GitHub CLI workflow. Exact context
window size is not surfaced by 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
- [ ] 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: Sami Rusani <sr@samirusani>
|
||
|
|
fcf66f3a91 |
feat(skills): add managed skill rename API (#9688)
## Thinking Path > - Paperclip is the open source control plane people use to manage AI-agent companies. > - Company skills are reusable capabilities that operators install, edit, assign, and materialize for agents. > - Managed local skills currently lack a safe backend operation for changing their display name and canonical slug/key together. > - Treating rename as an ordinary save can leave duplicate records, stale runtime materializations, or agent assignments pointing at the old key. > - This pull request adds a company-scoped managed-skill rename contract, service operation, and REST endpoint with focused authorization and activity logging. > - The benefit is an atomic-enough, recoverable rename path that keeps disk state, database identity, and agent skill assignments synchronized. ## Linked Issues or Issue Description - Refs #2121 - Problem: managed company skills need a dedicated rename operation rather than save-time duplication behavior. - Expected behavior: renaming a managed skill updates its name, slug, key, source directory, frontmatter, runtime materialization, and assigned-agent references while preserving version pins. ## What Changed - Added shared request/result types and Zod validation for managed skill rename requests. - Added `POST /api/companies/:companyId/skills/:skillId/rename` with `skills.edit` policy checks and `company.skill_renamed` activity logging. - Restricted renames to Paperclip-managed local skills and added slug, key, and target-directory conflict handling. - Moved the managed directory, rewrote only the `SKILL.md` frontmatter name, updated the database row, and rolled filesystem changes back when persistence fails. - Rewrote assigned agents' desired-skill keys while preserving pinned version IDs and removed stale runtime materialization. - Added focused route and service coverage for success, no-op, name-only changes, conflicts, unsupported sources, assignment rewrites, rollback-sensitive behavior, and runtime cleanup. - Rejected multiline rename names before they can inject extra `SKILL.md` frontmatter fields. ## Verification - `pnpm exec vitest run server/src/__tests__/company-skills-service.test.ts server/src/__tests__/company-skills-routes.test.ts` — 106 tests passed. - `pnpm --filter @paperclipai/shared typecheck` — passed. - `pnpm --filter @paperclipai/server typecheck` — passed. ## Risks - Filesystem and database updates cannot share one native transaction; the service stages filesystem changes and explicitly restores the original directory and markdown when the database transaction fails. - Renames intentionally reject catalog, remote, project-scanned, and unmanaged local skills to avoid changing identities owned by external sources. - No database migration is required; the endpoint updates existing company-skill and agent configuration fields. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex coding agent (exact underlying model ID and context-window size were not exposed to this runtime), with reasoning, repository tool use, code execution, and test execution. The rescued source commit also records assistance from Claude Opus 4.8. ## 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> |
||
|
|
187a90b7bc |
Add opt-in in-flight run-log mirroring with graceful-shutdown flush (#10512)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The run-log store records each agent run's output and can mirror completed logs to S3-compatible object storage > - The mirror uploads only on finalize, so a server restart mid-run loses the whole in-flight log > - Deployments and crashes are routine on ephemeral hosts, and lost run output makes failed runs impossible to debug > - This pull request adds an opt-in throttled mirror for still-running logs plus a graceful-shutdown flush > - The benefit is that a restart mid-run keeps the log tail up to the last mirror interval, and an orderly restart keeps everything ## Linked Issues or Issue Description No public issue exists — describing the feature inline (per the feature request template). **Subsystem affected** server/ — REST API & orchestration services **Problem or motivation** `RUN_LOG_S3_BUCKET` gives finished run logs durability, but the mirror uploads only on finalize. A run that is still writing when the server restarts leaves nothing in object storage. On hosts with ephemeral disks the local file is gone too, so the run's output is lost end to end and failed runs cannot be debugged. **Proposed solution** Mirror the in-flight log to the same object key on a throttled cadence (`RUN_LOG_S3_INFLIGHT_MIRROR_SECONDS`), and flush dirty tails during graceful shutdown. Keep it opt-in so existing deployments see zero new upload traffic unless they ask for it. **Alternatives considered** Per-append uploads (rejected: one PUT per output chunk is hostile to S3 endpoints and run latency). Chunked part objects with read-time stitching (rejected: complicates the read path, and S3 multipart minimum part sizes do not fit small tails). Persistent volumes (rejected upstream already: the data dir is deliberately an emptyDir in hardened cloud_tenant deployments). **Roadmap alignment** Not on ROADMAP.md; extends the existing run-log durability mirror without changing any default behavior. **Additional context** Ranged reads already serve partial objects like a live tail, so the read path needs no change; finalize overwrites the mirror with the complete file. **Related PRs (dedup search):** the finalize-only S3 mirror landed previously and this extends it; no duplicate or competing PR found for in-flight run-log mirroring. ## What Changed - `server/src/services/run-log-store.ts`: new opt-in `inflightMirrorMs` on the S3 options (`RUN_LOG_S3_INFLIGHT_MIRROR_SECONDS` env). When set, appends schedule at most one upload of the current file per interval, to the same key finalize uses. Ranged reads already serve that key, so a partial object behaves like a live tail and needs no read-path change. Finalize retires the in-flight bookkeeping and waits out an upload already on the wire, so a stale partial can never overwrite a finalized log. Upload failures warn, re-mark the tail dirty, and retry at most once per interval. - `server/src/services/run-log-store.ts`: new `flushInflightMirrors()` on the store and a module-level `flushInFlightRunLogMirrors()` for the shutdown path. Both are no-ops when the mirror is off. - `server/src/index.ts`: graceful shutdown flushes dirty in-flight tails after the heartbeat run drain, so runs the drain did not finalize (timeouts, the hot-restart skip path) still persist their output. - `server/src/services/run-log-store.test.ts`: five new tests — off-by-default (no uploads before finalize), tail preserved after a wipe without finalize, throttle coalescing with a single flush upload, finalize superseding the in-flight mirror and retiring its timer, and upload failures never breaking appends with recovery on the next flush. ## Verification - `pnpm vitest run server/src/services/run-log-store.test.ts` — 13 passed (8 existing + 5 new). - `pnpm vitest run server/src/__tests__/heartbeat-run-log.test.ts server/src/__tests__/heartbeat-active-run-output-watchdog.test.ts` — 21 passed (consumers of the store, unchanged behavior). - `pnpm -C server run typecheck` — clean. - Self-hosted behavior is unchanged unless `RUN_LOG_S3_INFLIGHT_MIRROR_SECONDS` is set: with the variable unset there are zero new uploads and the finalize-only mirroring is byte-identical (asserted by the off-by-default test). ## Risks - Low. The feature is opt-in; unset env preserves today's behavior exactly. When enabled, worst case is one extra PUT per interval per active run, and every upload is best-effort — a failing endpoint warns and never breaks appends, finalization, or shutdown. The finalize path awaits any in-flight upload before writing the complete file, closing the only overwrite race the design introduces. Timers are `unref`ed so the mirror never keeps the process alive. ## Model Used Claude Fable 5 (`claude-fable-5`, Anthropic; Claude Code CLI with extended thinking and tool use; tests executed locally via Vitest). ## 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 (no duplicates found for in-flight run-log mirroring; the finalize-only mirror landed previously and this extends it) - [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 |
||
|
|
916c13501f |
Replace host-to-host Cloud Sync with full-fidelity company Import/Export (#10507)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A company accumulates real state — issues, labels, blockers, documents, work products, monitors, attachments, agents, routines — and people need to move that state between instances: self-hosted to cloud, cloud back to self-hosted, or plain backups > - The experimental, flag-gated Cloud Sync transport (#6548) tried to solve this host-to-host: the source pushed into a receiver over HTTPS with a cross-instance consent/token handshake, which required the destination to be publicly reachable and broke for common self-hosted topologies (plain-HTTP LAN/VPN origins); the receiver half never landed upstream at all > - Meanwhile the portability bundle and the existing export/import pages already move companies offline with none of those networking constraints — but silently dropped labels, blockers, issue documents, work products, monitors, and every attachment > - This pull request removes the host-to-host transport and makes Import/Export the single data-movement path: the pages become first-class company-settings destinations, exports declare exactly what they do not carry, and bundle schemaVersion 6 now carries all of the above, with attachments as content-addressed sha256 blobs verified before a single row is written > - The benefit is a migration and backup flow that works between any two instances with no reachability requirements, no cross-instance auth, and no silent data loss ## Linked Issues or Issue Description - Refs #6548 — the original Cloud Sync sender this PR supersedes and removes. - Related, not duplicates: #1697 (goals in the portability manifest — orthogonal field addition), #954 (an earlier import/export + skill-visibility proposal predating the current portability bundle). - No open issue describes this directly, so in brief (feature-request shape): **Problem** — moving a company between instances silently lost labels (imports with label references actually hard-failed), blocker relations, issue documents, work products, monitor state, and all attachments, and the alternative Cloud Sync transport required the destination to be publicly reachable over HTTPS plus a consent handshake, which failed for typical self-hosted setups. **Desired behavior** — one Import/Export flow in company settings that produces a portable bundle carrying all of that data, tells the operator up front what it cannot carry, imports with automations paused, and offers real one-click activation afterwards. ## What Changed - New export fidelity report (`GET /api/companies/:companyId/export/fidelity`) + an "Export fidelity" panel on the Export page listing anything a bundle will not include (now only: approvals, cost history, activity history) - Imports accept `pauseAutomations`; imported agents and routines land paused, the import result reports created routines, and the Import page ends in an activation panel that actually resumes selected agents/activates routines - Export and Import pages promoted into the company-settings nav; the Cloud Upstream wizard, ux-lab page, and API client removed; the old settings route redirects to Export - Host-to-host transport removed: upstream-sync/receiver-client routes and services, CLI `cloud connect`/`cloud push` + keypair store, the shared upstream transfer contract, and the `enableCloudSync` flag; migration `0196` drops the two experimental `cloud_upstream_*` sender tables - Bundle schemaVersion 6: labels (definitions + per-task names, remapped by name on import), blocker relations (`blockedBy` slugs, cycle-tolerant), issue documents (`tasks/<slug>/documents/<key>.md`), work products (system refs nulled), monitors (notes/scheduledBy restored, imported un-armed) - Attachments travel as content-addressed `blobs/<sha256>` entries (deduped; comment-scoped attachments re-link via comment index); every blob is hash-verified **before any write**, so a corrupted bundle cannot leave a partially imported company; both zip codecs now round-trip extensionless/binary entries byte-exactly; the Import page preflights the inline body limit and offers continue-without-attachments - v5 (and older) bundles still import, with an informational warning; bundles newer than v6 are rejected cleanly - Docs: board-operator import/export guide, CLI README, README/ROADMAP updated ## Verification - `pnpm -r` typechecks (shared, db incl. migration numbering/safety checks, server, ui, cli) and `pnpm check:token-gates` — clean - Vitest: full server + shared sweep 4,888 passed / 1 skipped, with the only 3 failures being pre-existing on `master` (2× heartbeat-workspace-branch-containment, 1× workspace-runtime auto-port; reproduced identically with this change stashed); ui + cli suites green; the embedded-Postgres export-fidelity suite applies the full migration chain including the new `0196` against a fresh database - Live end-to-end on a scratch instance: seeded a company with labels, a blocker pair, an issue document, a work product, a monitor, an agent, a routine, and two binary attachments (one comment-scoped) → export → import into a fresh company → labels remapped to new ids, blocker edge and document restored, monitor un-armed with notes intact, attachments byte-identical (sha256-compared through the API), agents/routines paused → activation panel resumed them; a v5-shaped bundle imported with only the info warning; flipping one byte in a blob made the import 422 with **zero** rows created - Reviewer repro: create a company with a labeled issue + attachment → Settings → Export → download → Settings → Import on another company/instance → watch the preview, apply with "start paused", then activate ## Risks - Migration `0196` drops `cloud_upstream_connections`/`cloud_upstream_runs` — experimental tables behind a default-off flag; their connection/run history is intentionally discarded - Breaking removals are all of experimental, flag-gated surface: `/api/upstream-sync/*` + `/api/cloud-upstreams/*` routes, `paperclipai cloud connect|push`, and the `enableCloudSync` flag (stale keys in stored instance settings parse harmlessly) - Import remains non-atomic on mid-apply errors generally (pre-existing behavior); the new blob verification specifically moved ahead of all writes so tampered bundles cannot create partial state - GitHub-sourced imports do not fetch `blobs/*` and skip attachments with a warning ## Model Used - Claude Fable 5 (`claude-fable-5`, Anthropic), via Claude Code CLI with extended thinking, tool use, and subagent orchestration; implementation and review split across Fable 5 subagents, with live end-to-end verification against a running instance ## 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 |