mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-07 16:11:46 +02:00
a17bee98f2b7f4e7d319ddbf313fd3d3119e4e65
3228
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a17bee98f2 |
Allow trust-gated direct-parent issue reports (#10098)
## Thinking Path > - Paperclip is the control plane used to coordinate and govern AI-agent companies. > - Agent issue access must preserve company boundaries and trust-policy containment without preventing legitimate task coordination. > - Checked-out standard-trust child runs need a narrow way to report progress directly to their parent issue, but existing authorization treated that report like an arbitrary cross-boundary write. > - Low-trust review runs must remain contained, and stop propagation must not copy potentially untrusted child prose into a higher-trust parent context. > - This pull request adds an audited, one-hop direct-parent comment grant only for standard checked-out runs and a sanitized, idempotent relay for blocked or cancelled child stops. > - The benefit is restored parent/child liveness while retaining least privilege, complete mediation, and low-trust output quarantine. ## Linked Issues or Issue Description ### What happened? A standard-trust agent running a checked-out child issue could not post a progress comment to the direct parent issue because the authorization boundary treated it as an arbitrary cross-issue write. This could stall parent/child coordination. Low-trust review runs also need stop propagation without exposing quarantined child-authored prose. ### Expected behavior A standard checked-out child run may add a comment only to its direct parent issue. The grant must not allow grandparent or sibling access, issue mutation, document writes, reopening, or resuming. Low-trust runs remain denied unless separately mentioned, while blocked/cancelled stops relay only sanitized system metadata once. ### Steps to reproduce 1. Create a parent issue and a child issue assigned to different standard-trust agents. 2. Check out the child issue in a heartbeat run and authenticate as that run. 3. Post a comment to the parent issue and observe the authorization denial before this change. 4. Mark a low-trust child blocked or cancelled and observe that no bounded sanitized parent notification preserves liveness before this change. ### Paperclip version or commit Reproduces on `master` before this PR, including base commit `d36ea13e08`. ### Deployment mode Local dev (`pnpm dev`). ### Installation method Built from source (`pnpm dev` / `pnpm build`). ### Agent adapter(s) involved Not adapter-specific (core authorization and issue-routing behavior). ### Database mode External Postgres in the focused route regression suite; behavior is database-mode independent. ### Access context Agent (bearer API key associated with a checked-out heartbeat run). ### Additional context The implementation deliberately distinguishes a direct-parent report decision from general issue mutation permission and records successful grants in the activity log. ### Privacy checklist - [x] I have reviewed all pasted output for PII, API keys, tokens, company names, and private instance references. ## What Changed - Adds a distinct authorization decision for standard checked-out runs commenting on their direct parent issue. - Keeps low-trust direct-parent reports denied unless an existing explicit mention grant applies. - Forces direct-parent grants to remain comment-only even when a closed parent is unassigned or assigned to the reporting agent. - Audits successful direct-parent report grants in issue activity details. - Adds sanitized, parent-scoped, idempotent system comments and parent wakeups for blocked or cancelled child stops. - Extends the low-trust red-team route suite for allowed parent reports, forbidden upward/sibling writes, closed-parent mutation suppression, and non-laundering stop relays. ## Verification - `pnpm exec vitest run server/src/__tests__/low-trust-red-team-routes.test.ts` — 11 tests passed after the review fix. - `pnpm --filter @paperclipai/server typecheck` — passed after the review fix. - Confirmed the PR changes four files and excludes `pnpm-lock.yaml`, workflow changes, migrations, and unrelated branch commits. ## Risks - This is an authorization behavior change. An overly broad grant could enable cross-boundary writes, while an overly narrow grant could preserve the liveness failure. - The implementation constrains the grant to a standard-trust checked-out run, a direct parent target, and comments only; activity auditing and red-team coverage make regressions observable. - Stop relays intentionally contain only system-generated child identity/status metadata and are deduplicated; child-authored prose is not copied. - SecurityEngineer approval is mandatory before merge. > 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.5 with reasoning, repository tool use, shell execution, and test execution. The runtime 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> |
||
|
|
429792f1f3 |
fix(interactions): stop wedging confirmation accept on a terminal workspace_finalize (#10099)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - When an agent finishes work in an execution workspace, the board can confirm the result through an issue-thread interaction (e.g. the "Merged" / mark-done confirmation button on a `request_confirmation`). > - That accept action is gated: it must not race a worktree sync-back (`workspace_finalize`) that is still copying the agent's commits out of the sandbox, or the board could act on a base that hasn't received them yet. > - The gate (`runWorkspaceIsFinalized`) treated the sync-back as "settled" only when the latest `workspace_finalize` op was `succeeded` — so a run whose finalize reached a terminal `failed` state, or died leaving a stale `running` op, was treated as "still syncing" forever. > - Users hit a permanent, misleading `... has not finished syncing its workspace` error and could never click "Merged", even though nothing was syncing and the run had long since ended. > - This PR fixes the settle semantics so the gate blocks only while a sync-back is genuinely pending or in flight, and treats any terminal (or stale-orphaned) finalize as done. > - The benefit is that a failed or abandoned sync-back no longer wedges the human confirmation, while a genuinely in-flight sync-back on a live run still blocks correctly. ## Linked Issues or Issue Description No public GitHub issue exists for this. Describing the bug in-PR (bug report): **What happened** Clicking the "Merged" / mark-done confirmation at the bottom of an issue thread returns an error that the workspace "has not finished syncing its workspace" — but nothing is actually syncing, and the run that created the interaction has already ended. The confirmation is permanently stuck; the only workaround is to merge and mark the task done manually. **Expected behavior** Once the source run's worktree sync-back has finished — whether it succeeded, failed, or was skipped — the confirmation should be acceptable. The gate should block only while a sync-back is genuinely still running on a live run. **Steps to reproduce** Have an agent run reach `workspace_finalize` and end without a `succeeded` finalize (e.g. the sync-back fails, or the run process dies mid-finalize leaving a `running` op). Then attempt to accept the `request_confirmation` interaction it created → 409 "... has not finished syncing its workspace" with no way to proceed. **Paperclip version or commit** Reproduced on the current `master` line (server service); root cause is in `runWorkspaceIsFinalized` in `server/src/services/issues.ts`. **Deployment mode** Local / self-hosted instance (server service). **Root cause** `runWorkspaceIsFinalized` returned `true` only when the latest `workspace_finalize` operation was `succeeded`. A terminal `failed` finalize (the sync-back ran and failed; it will not retry within that run) and a `running` finalize left behind by a dead run both left the gate closed forever. ## What Changed - `runWorkspaceIsFinalized` (server/src/services/issues.ts) now treats a sync-back as **settled** when the latest `workspace_finalize` op reached any terminal status (`succeeded`, `failed`, or `skipped`), instead of only `succeeded`. - A `workspace_finalize` still marked `running` blocks only while its owning run is alive; a `running` record left behind by a terminal/missing run is treated as stale (settled), so a dead run can no longer wedge the gate. - Preserved existing behavior for the other cases: no operations recorded at all → settled; earlier phases recorded but no `workspace_finalize` yet → still blocks (the sync-back hasn't been attempted). - Extracted the run-liveness check into a shared exported helper `heartbeatRunIsTerminalOrMissing` and reused it from the existing `isTerminalOrMissingHeartbeatRun` closure (no behavior change there). - Added a short comment at the confirmation-accept gate (server/src/services/issue-thread-interactions.ts) documenting the relaxed settle semantics. - The dependency-readiness / blocker barrier (`listPendingFinalizeBlockerIssueIds`) is deliberately left unchanged: an automated dependent must not proceed onto a base that never received a blocker's synced-back commits, so a failed finalize keeps that gate closed. Only the human-driven confirmation accept is relaxed. - Added regression tests for: failed finalize, stale `running` finalize on a dead run, and a genuinely `running` finalize on a live run (must still block). ## Verification - `cd server && node_modules/.bin/vitest run src/__tests__/issue-thread-interactions-service.test.ts -t "accept"` → 17 passed (includes the 3 new regression tests), 21 unrelated tests skipped by the name filter. - Manual reasoning walkthrough of `runWorkspaceIsFinalized` for each op-history shape (no ops / earlier-phase-only / terminal finalize / running-on-dead-run / running-on-live-run) confirms the intended block-vs-settle outcome. ## Risks - Low risk and narrowly scoped to the human confirmation-accept gate. The only behavioral change is that a terminal (`failed`/`skipped`) or stale-orphaned `running` finalize now settles the gate instead of blocking forever. - A genuinely in-flight sync-back on a live run still blocks (covered by a regression test), so the accept cannot race commits that are actively being synced back. - The blocker/dependency barrier for automated dependents is unchanged, so no dependent will be advanced onto a base missing a failed blocker's commits. ## Model Used - Provider/model: Claude (Anthropic), **Opus 4.8**, model ID `claude-opus-4-8`, 1M context window. - Capabilities used: extended thinking, tool use (repo inspection, local 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 - [ ] 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 mergecanary/v2026.723.0-canary.11 |
||
|
|
d36ea13e08 |
feat(acpx): stage once per remote ACP session - compatible resume reuses staged runtime, no cross-session credential reuse (#10089)
## Thinking Path > - Paperclip coordinates autonomous agent work across isolated company-scoped sessions > - The ACP remote lane stages workspaces and managed home state inside the sandbox so sessions can resume safely > - If a compatible resume re-staged everything every time, it would waste work and risk inconsistent session reuse behavior > - If an incompatible resume reused the wrong staged runtime, it could cross session boundaries or leak credentials > - This pull request keeps the session fingerprint as the scoping key and adds a staged-runtime cache keyed to that fingerprint > - Compatible resumes now reuse the already staged runtime while incompatible fingerprints stage fresh > - The benefit is faster safe resumes without weakening session isolation or credential separation ## Linked Issues or Issue Description ### Problem or motivation The ACP remote lane currently needs to preserve safe resume behavior without repeatedly re-staging work that is already valid for the same session. The failure mode to avoid is letting one session reuse another session's staged workspace or credentials. ### Proposed solution Keep the session fingerprint as the scoping key and add a staged-runtime cache keyed to that fingerprint. When the fingerprint matches, reuse the already staged workspace and managed home. When the fingerprint changes, stage fresh. ### Alternatives considered - Always restage on resume: safest mechanically, but wastes work and breaks the compatible-resume optimization. - Reuse without fingerprint scoping: too risky because it could cross session boundaries. ### Roadmap alignment This is a narrow implementation change for the ACP remote lane and does not duplicate any broader roadmap item I could find in `ROADMAP.md`. ### Additional context The change preserves the existing session fingerprint contents and codex auth copy-back cadence while adding tests for compatible reuse, incompatible fresh staging, no cross-session credential reuse, and failed-turn eviction. ## What Changed - Added a staged-runtime cache in the ACP remote execution path keyed by the session fingerprint. - Reused the existing staged workspace and managed home for compatible resumes. - Kept incompatible fingerprints on the fresh staging path. - Preserved the existing session fingerprint contents and codex auth copy-back cadence. - Added tests for compatible reuse, incompatible fresh staging, no cross-session credential reuse, and failed-turn eviction. ## Verification - `pnpm exec vitest run packages/adapter-utils/src/acpx-engine/execute.test.ts` (74/74 pass, including the active-turn lease regression) - `pnpm exec tsc -p packages/adapter-utils/tsconfig.json --noEmit` - Verified the PR touches only `packages/adapter-utils/src/acpx-engine/execute.ts` and `packages/adapter-utils/src/acpx-engine/execute.test.ts` ## Risks - A cache eviction bug could cause an unavailable or partially staged runtime to be reused. - If the fingerprint scoping regressed, a session could incorrectly reuse another session's state. - The change is isolated to the ACP remote lane, but it still affects resume behavior for that path. ## Model Used OpenAI Codex, GPT-5, reasoning-capable coding agent, tool-enabled session. ## 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 - [ ] 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>canary/v2026.723.0-canary.10 |
||
|
|
a7186dce4b |
fix(plugin-sdk): thread companyId through configChanged + fail-closed cross-tenant guard (LOOA-687) (#10096)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - First-party **plugins** run as isolated workers spawned by the host
`plugin-loader`, reading company-scoped config through a governed
`ctx.config.get(companyId)` channel.
> - The host→worker `configChanged` RPC carries `{ config, companyId }`,
but the SDK dispatch dropped the scope — `onConfigChanged(newConfig)`
was companyId-blind by design — so a **proactive** worker kept a single
worker-global config.
> - #10092 added a startup replay that fans out **every** stored
company's config through `configChanged`. With no deterministic
ordering, a plugin configured for more than one distinct company ends up
running as whichever DB row was delivered last.
> - That is a latent cross-tenant identity/secret confusion bug: one
company's bot token could be applied to another company's traffic.
> - This pull request threads `companyId` through `onConfigChanged` and
adds a fail-closed cross-tenant guard at the SDK layer, so a
single-tenant worker can never silently collapse to a second company's
config.
> - The benefit is that the config-delivery class is fixed at the SDK
boundary — before any genuinely multi-company proactive plugin ships —
without changing today's single-tenant behavior.
## Linked Issues or Issue Description
No public GitHub issue — describing in-PR (hardening / latent security):
**Latent cross-tenant config collapse.** The worker-side `configChanged`
dispatch forwarded only `config` and dropped `companyId`, so a proactive
plugin kept a single worker-global config. #10092's startup replay
delivers every configured company's config sequentially with no `ORDER
BY`, so a plugin with configs for more than one distinct company would
apply a nondeterministic last-write-wins global config (one tenant's
credential applied to another's traffic).
- Builds on and must merge after #10092.
- Not exploitable today: the only proactive consumer (the chat gateway)
has single-tenant config rows, so last-write-wins is a no-op. This is a
hardening pre-condition before any multi-company proactive plugin ships.
## What Changed
- **Thread scope through:** `onConfigChanged(newConfig, context)` with a
new exported `PluginConfigChangeContext { companyId }`. Backward
compatible — the second arg is optional; existing single-arg
implementations are unaffected.
- **Fail-closed cross-tenant guard** (`worker-rpc-host.ts`): a
single-tenant plugin that receives `configChanged` for a second,
distinct company with a *different* config is rejected with the new
`PLUGIN_RPC_ERROR_CODES.CROSS_TENANT_CONFIG` instead of silently
overwriting the applied tenant's config. Idempotent replays of the
*same* config under a different scope row remain allowed.
- **Opt-in `multiCompanyConfig: true`** on the plugin definition for
plugins that genuinely serve multiple companies from one worker (keying
per-company state on `context.companyId`); the guard is bypassed for
those.
- **Deterministic `ORDER BY companyId`** on `registry.listConfigs`, so
the startup replay binds a single-tenant worker to a stable company
across restarts.
- **Loader visibility:** a `CROSS_TENANT_CONFIG` rejection is logged at
`warn` (was best-effort `debug`) so the misconfiguration is surfaced.
- **Regression test**
(`packages/plugins/sdk/tests/worker-rpc-host.test.ts`): two distinct
companies delivered via the startup-replay path fail closed and stay
bound to the first company; an idempotent same-config replay under a
different scope row is allowed; a `multiCompanyConfig` plugin receives
per-company config with the correct `context.companyId`.
## Verification
- SDK `tsc --noEmit`: clean.
- SDK vitest `worker-rpc-host.test.ts`: 7/7 pass (incl. 3 new). The
two-distinct-company case **fails against pre-fix code** and passes
after the fix.
- #10092 embedded-postgres `plugin-config-startup-delivery.test.ts`: 3/3
pass (unaffected by the new `ORDER BY`).
- Full server `tsc --noEmit` against this SDK: clean.
## Risks
- **Low functional risk.** The second `onConfigChanged` arg is optional
and existing implementations are unchanged. Today's single-tenant
gateway keeps working — idempotent same-config replays are explicitly
allowed, so the go-live is preserved.
- **Behavioral shift on misconfig:** a genuinely multi-company plugin
that has NOT opted into `multiCompanyConfig` now fails closed
(`CROSS_TENANT_CONFIG`) rather than silently collapsing to one tenant.
This is the intended safer default; opt in with `multiCompanyConfig:
true` to serve multiple companies from one worker.
- **Not in scope (residual).** Per-company workers/connections for a
genuinely multi-company gateway increase resource use and are tracked
separately (ties into the #10092 fan-out/timeout follow-up). This PR
fixes the class and fails closed; it does not build multi-tenant
connection management.
## Model Used
Claude — Anthropic `claude-opus-4-8` (Opus 4.8), extended thinking, with
tool use / code execution via Claude Code.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or 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 and contains no internal
Paperclip ticket id — branch predates this rule; not renaming an open PR
mid-review
- [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 — no
doc surface; internal SDK/host behavior only
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: anicca <annica@Michaels-Mac-Studio.local>
Co-authored-by: Paperclip <noreply@paperclip.ing>
canary/v2026.723.0-canary.9
|
||
|
|
1ebf5254b6 |
fix(plugin-loader): deliver stored config to freshly-started plugin workers (#10092)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - One capability is first-party **plugins** that run as isolated workers spawned by the host `plugin-loader`, reading company-scoped config through a governed `ctx.config.get(companyId)` channel. > - A **proactive** plugin (e.g. a chat gateway that opens a Slack Socket Mode connection at startup) does its company work from `setup()`, where there is **no company-scoped invocation** — so `ctx.config.get()` is rejected with `company context is required`. > - The worker swallows that error and falls back to its default (feature-off) config, so the plugin comes up **inert** even though correct config exists in the database. > - This is a regression from #9557 ("governed access contracts"), which changed `plugin-loader.ts` `activatePlugin` from loading stored config into the worker bootstrap to `const config = {}`. > - This pull request replays each configured company's stored config to the freshly-started worker over the **same `configChanged` host→worker path an operator config-save already uses**. > - The benefit is that proactive plugins receive their config on worker start (both server boot and operator enable) without weakening the governed-access surface. ## Linked Issues or Issue Description No public GitHub issue — describing in-PR (bug): **Bug.** After a proactive plugin's worker spawns, it never receives its stored config. Governed access (`packages/plugins/sdk/src/host-client-factory.ts`) only resolves `config.get` inside a company-scoped invocation (event/action/tool, or explicit `params.companyId`). Proactive plugins operate from `setup()` where no such scope exists, so `config.get()` fails with `company context is required`, the worker falls back to defaults, and the feature stays disabled despite valid DB config. - Regression introduced by #9557. - Related follow-up (latent multi-company hardening): #10096. ## What Changed - `plugin-registry.ts`: add read-only `listConfigs(pluginId)` returning all stored company config rows for a plugin (scoped `where eq(pluginConfig.pluginId, pluginId)`). - `plugin-loader.ts`: after the worker starts in `activatePlugin`, replay each company's stored config through the existing `configChanged` host→worker RPC — one `{ config, companyId }` per row, the same payload shape as the operator config-save path in `routes/plugins.ts`. Best-effort and idempotent; covers both server-boot `loadAll` and operator enable. - test: DB-backed `plugin-config-startup-delivery.test.ts` covering `registry.listConfigs` completeness and cross-plugin isolation. ## Verification - `tsc --noEmit` on `@paperclipai/server` — clean. - New `plugin-config-startup-delivery.test.ts` (embedded-postgres, 3 cases) — pass. - Full PR CI green: typecheck, all server/e2e/serialized test shards, build, canary dry-run, verify, and the security scanners (Snyk, Socket, Superagent, Greptile). ## Risks - **Low functional risk.** Adds an outbound host→worker push that mirrors the already-shipped operator-save path. A worker without an `onConfigChanged` handler (or momentarily unavailable) simply keeps the runtime `ctx.config.get(companyId)` model. - **Startup fan-out.** One `configChanged` per configured company at activation (sequential, default RPC timeout). `plugin_config` rows are writable only by instance-admins, so fan-out size is operator-controlled — not a remote surface. - **No secret-handling change.** `configJson` is delivered as-is, exactly as `config.get`/operator-save already deliver it. No new secret sink; catch-blocks log only ids + `err.message` at debug, never `configJson`. - **Latent multi-company behavior (pre-existing, not introduced here).** The worker-side `configChanged` dispatch forwards only `config` (drops `companyId`), and `listConfigs` has no `ORDER BY`, so a plugin configured for **more than one** company would apply a nondeterministic last-write-wins global config. This is existing SDK behavior — operator-save already pushes into the same handler — and is **not reachable by the single-company consumer this fix targets**. Greptile flagged this shape (4/5). It is tracked and fixed as a separate, non-blocking hardening PR (#10096): thread `companyId` through `onConfigChanged`, deterministic ordering, bounded fan-out. ## Model Used Claude — Anthropic `claude-opus-4-8` (Opus 4.8), extended thinking, with tool use / code execution via Claude Code. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or 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 and contains no internal Paperclip ticket id — branch predates this rule; not renaming an open PR mid-review - [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 — no doc surface; internal SDK/host behavior only - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups — 4/5; two latent multi-company items triaged as non-blocking and fixed in follow-up #10096 (see Risks) - [x] I will address all Greptile and reviewer comments before requesting merge — addressed: triaged as non-blocking follow-up in #10096 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Paperclip <noreply@paperclip.ing>canary/v2026.723.0-canary.8 |
||
|
|
e1cc63328a |
feat(acpx): per-adapter managed-home seed (x3) + Codex auth copy-back for remote ACP lane (#10073)
## Thinking Path > - Paperclip is a control plane that orchestrates AI agents and adapter execution for human operators. > - Agents run across local and remote execution contexts, and reliability in remote sessions depends on consistent adapter bootstrapping. > - The ACP path must prepare per-adapter runtime homes so managed credentials and config are available in sandboxed runner environments. > - Before this change, the new remote ACP lane did not yet consistently stage managed-home paths for all affected adapters or restore Codex auth state on teardown. > - We added a shared per-adapter seam in the ACP engine, then wired Codex, Claude, and Gemini remote lanes to seed managed homes and remap to in-sandbox locations. > - Codex additionally reuses the existing atomic auth restore flow to copy auth back on teardown, matching CLI behavior. > - This improves remote runner parity with existing CLI behavior and avoids credential drift in shared-code-path executions. ## Linked Issues or Issue Description - This change continues the ACP remote managed-home work by completing per-adapter remote bootstrapping and Codex auth restore behavior for the remote ACP lane. - It specifically covers: `acpx-engine`, `codex-local`, `claude-local`, and `gemini-local`. - Related prior work in this repo: PR #10070. ## What Changed - Add a per-adapter remote managed-home seam (`prepareRemoteManagedHome`) in `acpx-engine` and thread it through ACP execution options. - Implement Codex ACP preparation to: - stage `CODEX_HOME` (auth/config/skills) into a sandboxed remote home, - repoint `CODEX_HOME` to the in-sandbox path, - and wire teardown copy-back through the existing managed-auth restore path. - Implement Claude ACP preparation to seed a sanitized `config-seed` into `CLAUDE_CONFIG_DIR` and remap that directory to the sandbox root. - Implement Gemini ACP preparation to seed `~/.gemini/skills`, set `HOME` to managed runtime root, and preselect API-key auth in `settings.json`. - Keep local and runner-less ACP→CLI behavior unchanged by only invoking the remote managed-home seam when running in remote ACP mode. - Preserve existing authorization and activity boundaries in the shared engine and adapter layers. ## Verification - `git log --oneline origin/master..origin/feat/acp-remote-managed-home-seed` confirms only the expected 4 commits. - `tsc --noEmit` is clean in `adapter-utils`, `codex-local`, `claude-local`, and `gemini-local`. - Vitest selection used during validation passed (120 tests across the ACP-related suites). ## Risks - If remote sandbox teardown occurs after token rotation but before restore timing, Codex credentials can become stale and require re-auth on next startup. - Partial provisioning of managed-home assets would cause adapter bootstrap failures in runner-backed ACP sessions. - This change is scoped to execution-path behavior; it should not affect CLI behavior. ## Model Used None — human-authored. ## 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: Harold Kim <harold@paperclip.ing> Co-authored-by: Paperclip <noreply@paperclip.ing>canary/v2026.723.0-canary.7 |
||
|
|
8a3cc86531 |
feat(acpx): stage workspace + route in-sandbox cwd for remote ACP lane (#10070)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The shared ACP engine
(`packages/adapter-utils/src/acpx-engine/execute.ts`) is responsible for
launching local and remote agent processes via the ACP protocol
> - On runner-backed remote sandbox (Daytona) targets, `buildRuntime`
never crossed the CLI's staging seam: it never called
`prepareAdapterExecutionTargetRuntime`, left `runtimeRootDir: null` in
both the paperclip and process-session bridges, and handed the agent the
**HOST filesystem path** as the `session/new` cwd — meaning
Claude/Gemini silently operated on a path that does not exist inside the
sandbox (Codex additionally crashes on its HOST home path, addressed in
a follow-up PR)
> - The fix must cross the staging seam for remote sandboxes, thread the
real `runtimeRootDir` through both bridges, and bind the in-sandbox
workspace path as the session cwd — without touching local ACP runs or
the runner-less ACP→CLI fallback
> - This pull request introduces a `stageAcpRemoteRuntime` helper that
calls `prepareAdapterExecutionTargetRuntime` for runner-backed remote
runs, captures `{ workspaceRemoteDir, runtimeRootDir, assetDirs,
restoreWorkspace }`, and reuses the in-sandbox `workspaceRemoteDir` as
the single `sessionCwd` across `session/new`, the fingerprint,
compatibility check, persistence, `ensureSession`, the process-session
bridge cwd, and the error path
> - The benefit is that remote ACP runs now operate in the correct
in-sandbox cwd and receive a non-null `runtimeRootDir` in both bridges —
fixing silent wrong-cwd degradation for Claude/Gemini on Daytona
targets; this is PR 1 of 3 and seeds no credential material
## Linked Issues or Issue Description
No public GitHub issue exists for this change. Inline description
follows the feature request template:
### Subsystem affected
packages/adapters — agent adapter implementations
### Problem or motivation
The shared ACP engine
(`packages/adapter-utils/src/acpx-engine/execute.ts`) never crossed the
CLI's staging seam on runner-backed remote sandbox targets. It never
called `prepareAdapterExecutionTargetRuntime`, always passed
`runtimeRootDir: null` to both the paperclip and process-session
bridges, and handed the agent the **HOST filesystem path** as the ACP
`session/new` cwd. As a result, Claude and Gemini silently operated on a
cwd that does not exist inside the sandbox; Codex crashed with a fatal
error on the HOST `CODEX_HOME` path (that crash is in a follow-up PR).
### Proposed solution
Gate on `usesRunnerBackedSandbox` (`kind === "remote" && transport ===
"sandbox" && runner`). For runs that pass the gate, call
`prepareAdapterExecutionTargetRuntime` via a new `stageAcpRemoteRuntime`
helper that ships the workspace into the sandbox and captures `{
workspaceRemoteDir, runtimeRootDir, assetDirs, restoreWorkspace }`.
Thread the real `runtimeRootDir` into both bridges. Bind a single
`sessionCwd` (the in-sandbox `workspaceRemoteDir`) and use it at every
cwd-keyed session site (`session/new`, fingerprint, compat, persist,
`ensureSession`, process-session bridge, error path) so a warm/resumable
session is reused rather than invalidated. For local runs and the
runner-less ACP→CLI fallback, `sessionCwd` resolves to the HOST cwd —
byte-identical to the previous behavior.
### Alternatives considered
Patching each per-adapter bridge individually — rejected because the bug
is in the shared engine layer and the fix belongs there so all three
adapters (Codex, Claude, Gemini) benefit without per-adapter
duplication.
### Roadmap alignment
Internal correctness fix enabling remote ACP to work as designed; no new
user-facing features. This is PR 1 of 3 in a sequential chain: PR 1
(this PR) stages the workspace and routes the cwd; PR 2 adds per-adapter
home seeding and copy-back; PR 3 wires session-lifecycle restore.
## What Changed
- **`packages/adapter-utils/src/acpx-engine/execute.ts`** — added
`stageAcpRemoteRuntime` helper that calls
`prepareAdapterExecutionTargetRuntime` for runner-backed remote
sandboxes and returns `{ sessionCwd, runtimeRootDir, stagedRuntime }`;
`buildRuntime` now uses this helper to derive `sessionCwd` (in-sandbox
`workspaceRemoteDir` for remote; HOST cwd unchanged for
local/runner-less) and threads the real `runtimeRootDir` to both the
paperclip bridge and process-session bridge; `stagedRuntime` is stashed
for the follow-up credential PR
- **`packages/adapter-utils/src/acpx-engine/execute.test.ts`** — new
engine-level unit tests: staging seam crossed with no credential asset,
non-null `runtimeRootDir` in both bridges, in-sandbox `session/new` cwd,
warm-handle reuse after the cwd change, local-unchanged; 60 tests green
- **`packages/adapters/codex-local/src/server/acp.test.ts`** — new
per-adapter test: runner-backed remote asserts `ensureSession` cwd ==
`workspaceRemoteDir`; runner-less sandbox falls back to CLI
- **`packages/adapters/claude-local/src/server/acp.test.ts`** — same
per-adapter coverage for Claude
- **`packages/adapters/gemini-local/src/server/acp.test.ts`** — same
per-adapter coverage for Gemini
## Verification
- `adapter-utils` typecheck clean; `codex/claude/gemini-local` typecheck
clean
- Engine units (`acpx-engine/execute.test.ts`): 60/60 green — staging
seam crossed with no credential asset, non-null `runtimeRootDir` to both
bridges, in-sandbox `session/new` cwd, warm-handle reuse after cwd
change, local unchanged
- Per-adapter ACP test suites (`codex/claude/gemini-local`
`acp.test.ts`): 103 tests green — runner-backed remote asserts
`ensureSession` cwd == `workspaceRemoteDir`; runner-less sandbox falls
back to CLI
- CI green on PR (in progress)
## Risks
This is PR 1 of 3 in a strictly sequential chain; it seeds **no
credential material** (no `assets`, no `installCommand`). The
per-adapter home seeding is deferred to PR 2, which consumes the
`stagedRuntime` object stashed here. The `restoreWorkspace` callback is
carried on `stagedRuntime` for PR 3's session-lifecycle wiring (see the
`stageAcpRemoteRuntime` function comment). Local ACP runs and the
runner-less ACP→CLI fallback are untouched — `sessionCwd` resolves to
the HOST cwd for those paths, preserving existing behavior. The
`stageAcpRemoteRuntime` helper is gated on `usesRunnerBackedSandbox`, so
there is no regression risk for local or CLI-lane runs.
## Model Used
Claude Sonnet 4.6 (`claude-sonnet-4-6`) via Paperclip ACPX engine —
extended context, tool use enabled, co-authored with Paperclip agent
orchestration.
## 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/...`,
`feat/...`) 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
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Harold Kim <harold@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
canary/v2026.723.0-canary.6
|
||
|
|
5a00040c4c |
feat(plugin-sdk): interactions/approvals respond + attachment read capabilities for the chat gateway (#10066)
Adds the remaining 5 plugin capabilities + 7 worker→host RPC methods (interactions read/respond, approvals read/respond, attachment read) needed by the Slack chat gateway plugin (v0.5.0) to pass manifest capability validation and load. - Security review: PASS (LOOA-642) after the viewer-role privilege-escalation blocker (LOOA-648) was fixed on this branch (requireActiveHumanMember now rejects viewer on impersonation write-paths, matching assertCompanyAccess). - CI: Build, Typecheck, all server suites (3/3 + serialized 4/4), workspaces, e2e shard 2/2, and all security scanners (Snyk/Socket/Superagent/Greptile/security-review) green. - One e2e flake (signoff-policy 'non-participant cannot advance stage') is unrelated: it exercises execution-policy stage advancement (routes/issues.ts, untouched by this PR) and failed on a heartbeat_run_events FK race + 409 checkout conflict. Unblocks LOOA-629 (Slack gateway go-live) and the interview-ask feature. Co-Authored-By: Paperclip <noreply@paperclip.ing>canary/v2026.723.0-canary.5 |
||
|
|
0ef3b320c7 |
Harden POST /plugins/install: canonicalize localPath for all instances; bundled-only floor for managed instances (#10067)
**Builds on** #10058 — managed detection keys off the *presence* of the `PAPERCLIP_MANAGED_CONFIG` env var that PR introduces, deliberately never its parsed body. **Summary.** Two layered hardenings of the plugin install route. (1) For **all** instances: `localPath` installs previously skipped the package-name validation entirely; the path is now null-byte-checked, resolved absolute, `realpath`'d (collapsing `..` traversal and symlinks), and required to be an existing directory before the loader ever sees it. (2) For instances running under a managed hosting control plane (detected by the *presence* of `PAPERCLIP_MANAGED_CONFIG` — deliberately never its body, so a corrupted document cannot widen the surface): registry/npm installs return 403, and `localPath` installs must canonicalize to inside the bundled plugin catalog root (`packages/plugins`) — a positive allowlist enforced in code at the route, independent of any flag value. Self-hosted behavior is otherwise unchanged. ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The plugin system lets instance admins install plugins from a registry or from a local filesystem path, and plugin installation is code execution on the host > - The `localPath` branch of `POST /plugins/install` skips the validation applied to registry installs; the raw path reaches the plugin loader without canonicalization > - Separately, instances operated by a managed hosting control plane must constrain installs to the bundled plugin catalog, because there the host belongs to the operator, not the tenant > - This pull request canonicalizes and validates `localPath` for all instances, and adds a bundled-only install floor for managed instances > - The benefit is a smaller install-route attack surface everywhere, and a positive code-enforced allowlist where the operator owns the machine ## Linked Issues or Issue Description No public issue exists; `bug_report` template fields for the validation gap this PR fixes: - **What happened:** `POST /plugins/install` with `localPath` set bypasses the package-name validation entirely; the un-canonicalized path (relative segments, symlinks, no existence check) is handed straight to the plugin loader. - **Expected behavior:** path installs are validated like registry installs — null-byte-checked, resolved absolute, `realpath`'d, and required to be an existing directory before the loader sees them. - **Steps to reproduce:** as an instance admin, call `POST /plugins/install` with a `localPath` containing `..` traversal or a symlink pointing outside any plugin directory; observe the loader receives the raw path. Exploitability is bounded (the route already requires instance admin), so this is hardening of an admin-only surface rather than an open exploit. - **Version:** current `master`. The managed-instance bundled-only floor layered on top is new behavior (motivation: on managed hosting, arbitrary plugin install is arbitrary code execution on operator infrastructure), aligned with the in-progress "Cloud deployments" milestone in `ROADMAP.md`. ## What Changed - New `server/src/services/plugin-install-guard.ts` — three pure primitives: managed detection (presence-based), path canonicalization (null-byte check → absolute resolve → `realpath` → must be an existing directory), and segment-based containment in the bundled plugin catalog root. - Route enforcement in `server/src/routes/plugins.ts`: npm/registry installs return 403 on managed instances; `localPath` installs are canonicalized on every instance and, on managed instances, must land inside the bundled catalog root. - The plugin loader now receives the canonical path instead of the raw request string. ## Verification - 15 guard unit tests (`server/src/__tests__/plugin-install-guard.test.ts`): traversal, symlink escape, null byte, file-vs-directory, string-prefix sibling root. - 13 route security tests (`server/src/__tests__/plugin-install-route-security.test.ts`): 403 matrix on managed instances + self-hosted happy paths. - 36 existing plugin route authz tests green (`server/src/__tests__/plugin-routes-authz.test.ts`). - Server `tsc --noEmit` clean. ```bash cd server pnpm vitest run src/__tests__/plugin-install-guard.test.ts src/__tests__/plugin-install-route-security.test.ts src/__tests__/plugin-routes-authz.test.ts pnpm exec tsc --noEmit ``` ## Risks - Managed instances: npm/registry installs and out-of-catalog `localPath` installs now return 403 — intended new behavior, enforced in code rather than configuration. - All instances: `localPath` installs that previously pointed at nonexistent paths or non-directories now fail with 400 before reaching the loader (previously the loader failed later, less safely). Symlinked deployment layouts are handled by canonicalizing both sides of the containment check. - Self-hosted npm install path is unchanged. Low residual risk. ## Model Used Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use; independently peer-reviewed by a second AI agent before push. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>canary/v2026.723.0-canary.4 |
||
|
|
04e070bf45 |
docs(skill): require agents to claim only monitors they actually scheduled (#10064)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work, and the shared `skills/paperclip/SKILL.md` is the behavioral
contract every managed agent follows each heartbeat.
> - Issue continuation between heartbeats depends on real, persisted
state: an issue only auto-resumes when it has a scheduled **issue
monitor** (`monitorNextCheckAt` + an execution-policy `monitor` block)
that the server's `tickDueIssueMonitors` scheduler polls and re-wakes
via `issue_monitor_due`.
> - A run/heartbeat is an ephemeral execution window — nothing keeps
"watching" after it exits — but the skill never said this, so agents
narrated a "watcher in this run" as if a live subscription existed.
> - That gap produced a concrete user-facing failure: an agent claimed
"a watcher in this run wakes me when CI + Greptile complete," then the
run ended with no monitor scheduled and nothing ever resumed, leaving
the user unsure whether a watcher existed at all.
> - This PR closes the gap by documenting what a monitor actually is and
adding hard rules so agents only claim a watcher they have actually
scheduled, describe it in checkable terms, and never imply a live
watcher on a task they mark `done`.
> - The benefit is that agent narration stays consistent with the
disposition guard and recovery classifier that already enforce these
paths in state, so users get accurate expectations about whether and
when a task will resume.
## Linked Issues or Issue Description
This is a documentation-only change to a shared agent skill, so no code
issue is required. The underlying problem it addresses:
**Problem or motivation** — Agents were telling users that a "watcher in
this run" would wake them when external checks (CI, Greptile) finished,
when no persisted issue monitor had been scheduled. Because a heartbeat
is ephemeral, no such watcher exists after the run exits, so the task
silently never resumed and the user was left confused about what would
happen next.
**Proposed solution** — Document, in the shared skill, exactly what an
issue monitor is (durable `monitorNextCheckAt` + execution-policy
`monitor` block, polled by `tickDueIssueMonitors`, re-woken via
`issue_monitor_due`) and add rules that agents may only claim a
watcher/monitor after actually scheduling one, must describe it in
checkable terms (kind / next check / timeout / attempts), and must never
imply a live watcher on a task being marked `done`.
**Alternatives considered** — Enforcing purely in server state (the
disposition guard and recovery classifier already reject
`in_review`/parked issues without a real wake path). That enforcement
exists but does not stop an agent from *narrating* a non-existent
watcher in a comment; aligning the skill guidance with the existing
state enforcement is the missing piece.
## What Changed
- Added a **"Monitors and Watchers (say only what you actually
scheduled)"** subsection to `skills/paperclip/SKILL.md` explaining that
a watcher does not live inside a run, and that only a persisted issue
monitor can auto-resume an issue (with the concrete fields and the
`tickDueIssueMonitors` / `issue_monitor_due` polling path).
- Added three behavioral rules: only claim a monitor after scheduling
one (and how to schedule/confirm it via `PATCH /api/issues/{id}` and
`monitor/check-now`); describe monitors in checkable terms; never imply
a live watcher on a task marked `done`.
- Cross-referenced the rule from the **Critical Rules** list.
- Tightened the final-disposition checklist so `in_review` /
`in_progress` continuation requires a real, non-null
`monitorNextCheckAt` rather than a merely described one.
## Verification
- Docs-only change to `skills/paperclip/SKILL.md`; no code paths are
affected.
- Confirmed every identifier referenced in the new text is real in the
codebase: `monitorNextCheckAt`, `monitorScheduledBy`,
`executionPolicy.monitor`, `tickDueIssueMonitors`, and the
`issue_monitor_due` wake reason.
- Rendered the Markdown to confirm the new subsection and the Critical
Rules bullet display correctly and links resolve within the document.
- `git diff` confirms the change is limited to the single skill file (14
insertions, 2 deletions).
## Risks
Low risk. This is guidance text in a shared agent skill with no runtime
or schema impact. Worst case is stylistic wording that can be refined in
a follow-up; it cannot break builds, migrations, or behavior. It
strengthens (never loosens) the existing disposition guarantees.
## Model Used
Claude Opus 4.8 (model id `claude-opus-4-8`, 1M-context variant) running
in an agent harness with extended thinking and tool use (file edit,
shell, git, GitHub CLI).
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [ ] 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
|
||
|
|
62e367c1b2 |
fix(ui): rewrite recovery and blocked-notice copy in plain language (#10065)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The issue-detail UI shows recovery cards and blocked/parked notices when a task loses its next step — a run finished with no disposition, a task is stranded, work is blocked behind other tasks, or an assigned item sits in the backlog > - That copy was written in the scheduler's internal vocabulary — "Corrective wake queued", "Graph Liveness", "lost a live action path", "the responsible" — which describes Paperclip's internals rather than the user's situation > - Users seeing these cards report having no idea what the card means or what they are supposed to do > - This pull request rewrites the user-facing copy in plain language and adds explicit calls to action that match the options in the card's Resolve menu > - The benefit is that a non-expert operator can read a recovery or blocked notice and immediately understand what happened and which action to take next ## Linked Issues or Issue Description No public GitHub issue exists for this; describing the problem here (bug-report format): - **What happened:** Recovery action cards and blocked notices render internal jargon, e.g. the headline "Paperclip detected this task lost a live action path. A recovery owner needs to act.", the chip "Corrective wake queued", the kind label "Graph Liveness", and phrases like "Comments still wake the responsible". Status values also appear as raw code literals (`in_progress`, `todo`). - **Expected behavior:** These notices should tell a normal user, in plain language, what happened and what to do next (retry the task, mark it done, send it for review, or record a blocker). - **Impact:** Operators stall on tasks that only need a simple disposition because the UI doesn't tell them that's what is being asked. Related prior work: #9417 (merged) made the reopen-suppressed blocked message explicit; this PR extends the same plain-language treatment to the rest of the recovery and blocked-notice copy. ## What Changed - Recovery card headlines for `missing_disposition`, `stranded_assigned_issue`, and `issue_graph_liveness` now say what Paperclip found and name the concrete next steps ("try the task again, mark it done, or send it for review") matching the card's Resolve menu. - The `issue_graph_liveness` kind label "Graph Liveness" is now "Task Needs Next Step", and the "Wake" metadata row is now "Follow-up". - Wake-policy chips describe actual behavior: "An agent will be asked to choose the next step" (was "Corrective wake queued"), "Board will decide", "Manual follow-up needed", "Repair needed before retry", "Check scheduled". - Blocked/waiting/parked notices say "the assignee" instead of "the responsible" / "responsible agent", and "notify" instead of "wake". - The still-needs-a-next-step notice drops raw `in_progress` code literals and keeps a plain-language option list (mark done or cancelled, send for review, record what is blocking it, delegate follow-up). - Parked-backlog notice renders "To do / In progress" as plain labels instead of code literals. - Component tests updated to pin the new copy and the successful-run example options. ## Verification - `cd ui && npx vitest run src/components/IssueRecoveryActionCard.test.tsx src/components/IssueBlockedNotice.test.tsx src/components/IssueAssignedBacklogNotice.test.tsx src/components/IssueChatThread.test.tsx` — 4 files, 126 tests, all passing. - Copy-only review: the diff touches display strings, one label map entry, and test assertions; no control flow, props, or identifiers change. ## Risks - Low risk — user-facing strings and test updates only. No behavior, API, or schema changes. The only functional surface is that anything keying off the displayed text (e.g. screenshots, external docs) will show the new wording. ## Model Used - Claude (Anthropic) — Claude Fable 5, model ID `claude-fable-5`, extended thinking enabled, running in Claude Code with 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 (no docs reference this copy) - [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 |
||
|
|
0f898f95af |
Render managed experimental settings as locked, with a 'Managed by Paperclip Cloud' badge (#10061)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The experimental settings page renders one interactive toggle per feature from the settings API > - On managed instances some values are enforced by the hosting control plane, and the API now reports those keys as managed > - Rendering enforced values as live toggles misleads users: the click appears to work, and the value silently snaps back > - This pull request renders managed keys as locked toggles with a "Managed by Paperclip Cloud" badge and guards the handlers so no PATCH can be emitted > - The benefit is UI honesty on managed instances, with self-hosted responses rendering exactly as before ## Linked Issues or Issue Description Builds on #10058 — renders the per-key `managedKeys` metadata #10058 adds to settings responses (typing shared from #10058 at rebase). No public issue exists; `feature_request` template fields: - **Problem or motivation:** on managed instances users see fully interactive toggles for settings the control plane enforces; changes appear to apply and never do, with no explanation. - **Proposed solution:** disabled toggle + badge + guarded handler driven by the settings response's managed-key metadata; the ~17 uniform setting cards are extracted into one shared component with copy, aria-labels, and patch payloads preserved verbatim. - **Alternatives considered:** hiding managed settings entirely (users lose sight of the effective value and why it is fixed); tooltip-only hints on still-active toggles (doomed PATCHes are still emitted and stripped server-side). - **Roadmap alignment:** supports the in-progress "Cloud deployments" milestone in `ROADMAP.md`. ## What Changed - When the settings API reports a feature key as managed (`managedKeys` from the managed-config overlay), the experimental settings page renders that toggle disabled with a badge and a guarded handler, so a click can never emit a PATCH. Previously, managed-instance users saw fully interactive toggles they could never actually change. Self-hosted responses (no `managedKeys`) render exactly as before. - The ~17 copy-pasted uniform setting cards are extracted into one `ExperimentalToggleCard` component with titles, descriptions, footnotes, aria-labels, and patch payloads preserved verbatim; the two bespoke cards get inline managed handling (the managed auto-recovery toggle also cannot open its preview dialog). - `ui/src/api/instanceSettings.ts` response typing now uses the shared `InstanceExperimentalSettingsWithManaged` / `ManagedSettingMetadata` types from #10058; `ui/src/pages/InstanceExperimentalSettings.tsx` locked rendering + card extraction; tests. ## Verification - 24 page tests (20 existing unmodified + 4 new: locked badge with no PATCH while unmanaged keys stay editable; managed auto-recovery opens no dialog; an open recovery preview closes with no PATCH when a refresh marks auto-recovery managed; self-hosted unaffected): `pnpm --filter @paperclipai/ui exec vitest run src/pages/InstanceExperimentalSettings.test.tsx` - `pnpm --filter @paperclipai/ui typecheck` clean ## Risks - Low risk. UI-only change; no server or API behavior changes. Self-hosted responses carry no `managedKeys`, so the page renders exactly as before there. The card extraction preserves copy, aria-labels, and patch payloads verbatim, covered by the 20 pre-existing page tests passing unmodified. ## Model Used Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use; independently peer-reviewed by a second AI agent before push ## 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 - [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> |
||
|
|
c4cdcc4826 |
Generalize bundled plugin provisioning: ensureBundledKubernetesPlugin → ensureBundledPlugins (#10063)
**Builds on** #10058 — reads `plugins.autoInstall` from the parsed managed-config contract #10058 introduces (the interim `readManagedPluginAutoInstall` shim is retired at rebase). **Summary.** Boot-time bundled-plugin provisioning becomes catalog-driven. A new bundled-plugin catalog lists the sandbox providers shipped in-tree (keys like `kubernetes`, `daytona` → plugin key + path under the catalog root). Managed instances read `plugins.autoInstall` from `PAPERCLIP_MANAGED_CONFIG`; unknown keys or paths escaping the catalog root (symlinks resolved) **throw before listen** — a managed instance refuses to start rather than boot half-provisioned. Installation keeps today's mechanism: an in-process, fail-safe `loader.installPlugin({ localPath })` under a system actor — no HTTP route, no user, no role widening. Self-hosted boot is unchanged (kubernetes bundle only, existing env override honored, install failures still log-and-continue). **Semantics.** A plugin already present in any non-uninstalled state is skipped, so an operator-disabled plugin is never silently re-enabled; managed mode reinstalls soft-uninstalled bundles (the control plane owns provisioning); removal from the autoInstall list never auto-uninstalls. ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Sandbox-provider plugins ship in-tree, but boot-time provisioning is hard-coded to exactly one of them (Kubernetes) via a bespoke function > - On managed hosting, tenant users have no install privileges, so any bundled plugin that is not provisioned at boot is unusable > - Widening install routes or granting roles to fix that would trade a provisioning gap for a security regression > - This pull request generalizes the existing boot installer into a catalog-driven `ensureBundledPlugins`, fed by `plugins.autoInstall` from `PAPERCLIP_MANAGED_CONFIG` > - The benefit is that managed tenants get working bundled plugins out of the box, through the same in-process, role-free mechanism the codebase already trusts, while self-hosted boot is unchanged ## Linked Issues or Issue Description No public issue exists; `feature_request` template fields: - **Problem or motivation:** on managed instances tenant users cannot install plugins (by design they never hold instance admin), so even plugins shipped with the product are unusable; boot provisioning currently knows only the Kubernetes bundle. - **Proposed solution:** a bundled-plugin catalog plus `ensureBundledPlugins(keys)` driven by the managed config; same in-process `loader.installPlugin({ localPath })` under a system actor; unknown keys or catalog-escaping paths fail startup; already-present plugins are skipped so operator-disabled plugins are never silently re-enabled. - **Alternatives considered:** granting tenant users install privileges (widens secrets/adapters/settings access to solve a one-button problem); a separate non-admin install route for bundled plugins (new authz surface; provisioning removes the need for any install action at all). - **Roadmap alignment:** supports the in-progress "Cloud deployments" milestone and builds on the shipped sandbox-provider milestone in `ROADMAP.md`. Refs #10058. ## What Changed - New `server/src/services/bundled-plugins.ts`: the bundled-plugin catalog, the fail-to-start resolver (`resolveBundledPluginInstalls`, positive allowlist + catalog-root containment with symlinks resolved), and the fail-safe installer (`ensureBundledPlugins`). - `server/src/app.ts`: replaces the hard-coded `ensureBundledKubernetesPlugin` boot hook with resolver + installer wiring, with test hooks (`managedPluginAutoInstall`, `bundledPluginCatalogRoot` options). - `server/src/index.ts`: passes `plugins.autoInstall` from the single fail-closed `PAPERCLIP_MANAGED_CONFIG` startup parse (#10058) into `createApp`; absent env means self-hosted and changes nothing. ## Verification - 24 new tests in `server/src/__tests__/bundled-plugins.test.ts` (catalog resolution, containment incl. symlink and `..` escapes, skip/reinstall matrix, self-hosted invariants, installer error paths) — all green. - 85 adjacent startup/plugin-route/auto-build/managed-config tests green (`managed-config`, `instance-settings-managed-overlay`, `plugin-install-autobuild`, `plugin-routes-authz`, `server-startup-feedback-export`). - Server `tsc --noEmit` clean. ```bash cd server npx vitest run src/__tests__/bundled-plugins.test.ts npx vitest run src/__tests__/managed-config.test.ts src/__tests__/instance-settings-managed-overlay.test.ts src/__tests__/plugin-install-autobuild.test.ts src/__tests__/plugin-routes-authz.test.ts src/__tests__/server-startup-feedback-export.test.ts npx tsc --noEmit ``` ## Risks - Managed instances with a malformed or unknown `plugins.autoInstall` entry now **refuse to start** (fail closed, by design) instead of booting half-provisioned; harness misconfiguration surfaces as a precise startup error. - Self-hosted behavior is unchanged (kubernetes bundle only, `PAPERCLIP_KUBERNETES_PLUGIN_PATH` honored without containment, install failures log-and-continue), so the default deployment path carries low risk. - No uninstall path exists in this module; removal from the autoInstall list can leave a previously provisioned plugin installed (intentional v1 semantics, documented in code). ## Model Used Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use; independently peer-reviewed by a second AI agent before push. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>canary/v2026.723.0-canary.3 |
||
|
|
216d3d2680 |
Managed-instance config: fail-closed PAPERCLIP_MANAGED_CONFIG parsing and read-time settings overlay (#10058)
**Builds on.** #10055 — the `catalogVersion` this config document pins is the feature-catalog artifact #10055 emits. **Summary.** Instances operated by a managed hosting control plane can now receive instance configuration through a single environment variable, `PAPERCLIP_MANAGED_CONFIG` (versioned JSON: `mode`, `catalogVersion`, `features`, `plugins.autoInstall`). When the variable is absent the instance is self-hosted and nothing changes. When present, parsing is strict and **fail-closed**: blank value, malformed JSON, unknown feature key, a feature key this build's feature catalog does not mark tier `managed`, missing required section, or unsupported version refuses startup with a precise error — a typo that silently does nothing is how a security control quietly fails. Managed feature values are overlaid **at read time** inside the instance settings service (never persisted), so a DB restore or manual row edit cannot resurrect a disabled capability; responses expose per-key `managedKeys` metadata (`managed: true`, `managedBy`) so clients can render locked state. ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Paperclip runs both self-hosted and under managed hosting, where an operator's control plane owns instance configuration > - Today instance feature settings live only in the tenant database; a hosting control plane has no way to enforce a configuration that tenant-side writes or restores cannot undo > - Managed configuration will carry security posture, so delivery must be atomic and parsing must fail closed — a typo that silently does nothing is how a security control quietly fails > - This pull request adds strict parsing of one `PAPERCLIP_MANAGED_CONFIG` env var and overlays its feature values at read time inside the settings service, never persisting them > - The benefit is a minimal, auditable managed-hosting contract: absent var ⇒ self-hosted instances are byte-for-byte unchanged; present ⇒ deterministic, locked configuration surfaced to clients via per-key managed metadata ## Linked Issues or Issue Description Refs #966 — this PR delivers that issue's "managed config injection" hook, via a strict env-var contract rather than the config-file path it sketches; the issue's other hooks (identity header, health, usage webhook, lifecycle, external secrets, IAM auth) are out of scope, so the PR refs rather than closes it. *Mechanism differs from #966's proposal, so the `feature_request` fields are also filled in:* - **Problem or motivation:** managed hosting deployments need to centrally enable/disable instance features; DB-stored settings can be edited, restored, or migrated back to permissive values, and nothing marks a value as operator-enforced. - **Proposed solution:** one versioned JSON env var; fail-closed parse at startup; read-time overlay in the settings service (precedence: managed value over stored value over schema default); `managedKeys` metadata in settings responses so clients can render locked state. - **Alternatives considered:** per-feature env vars (non-atomic across a half-updated env set, unbounded env surface); seeding the DB at boot (persisted values can be edited or restored over, and cannot express "forced"); lenient warn-and-drop parsing (fails open — unacceptable for a security-bearing control). - **Roadmap alignment:** supports the in-progress "Cloud deployments" milestone in `ROADMAP.md`. ## What Changed - New `server/src/services/managed-config.ts` (pure parser over the env record) - Startup parse ordered before the first `instanceSettingsService` construction in `server/src/index.ts` - Read-time merge + `managedKeys` in the settings service - Shared validator updates ## Verification - 29 parser/overlay tests (fail-closed matrix incl. blank/whitespace env, missing sections, catalog-tier mismatch, empty-section happy path): `pnpm vitest run src/__tests__/managed-config.test.ts src/__tests__/instance-settings-managed-overlay.test.ts` (from `server/`) - 40 existing settings route/service tests green: `pnpm vitest run src/__tests__/instance-settings-routes.test.ts src/__tests__/instance-settings-service.test.ts` (from `server/`) - 15 shared validator tests: `pnpm vitest run src/validators/instance.test.ts` (from `packages/shared/`) - Server `tsc --noEmit` clean: `pnpm typecheck` (from `server/`) ## Risks - Self-hosted instances (no `PAPERCLIP_MANAGED_CONFIG` set) are byte-for-byte unchanged — the parser only runs when the variable is present. - For managed instances, a malformed document now refuses startup by design (fail-closed). This is an intentional behavioral guarantee, not a regression: the control plane owns the variable and a precise startup error is the contract. - Overlay values are never persisted, so no migration or data-shape risk. ## Model Used Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use; independently peer-reviewed by a second AI agent before push. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>canary/v2026.723.0-canary.2 |
||
|
|
9edde68373 |
feat(kubernetes): native file-sync lifecycle hooks over pod exec (#10053)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - AI agents run in sandboxed execution environments (Kubernetes pods, Daytona workspaces, etc.) and need to sync files between the host and those environments — for workspace setup, asset delivery, and output retrieval > - The existing sync path for Kubernetes uses a base64-over-exec chunk loop: each ~4 MB chunk requires its own `execInPod` round-trip, so large syncs balloon into many exec calls with corresponding overhead > - `execInPod` supports piped stdin/stdout, meaning the full transfer can be done as a single exec that streams a raw `tar` archive over the data channel — one round-trip regardless of file size, with nothing base64-encoded and nothing buffered whole in memory on either side > - PR-1 (#10013, merged) added the `onEnvironmentSyncIn`/`onEnvironmentSyncOut` opt-in hook API to the sandbox provider interface and documented the protocol; PR-2 (#10028, merged) implemented these hooks for the Daytona provider > - This pull request implements the same two lifecycle hooks in the Kubernetes sandbox provider, so workspace/asset file sync streams through one `execInPod` per operation instead of the chunk loop > - The benefit is significantly fewer exec round-trips for large syncs and flat memory use on both host and pod, with security properties preserved: atomic replace, secret-mode enforcement, path confinement, TOCTOU-safe snapshot, and member-confinement on host-assembled archives from sandbox-authored tar output ## Linked Issues or Issue Description This is the third and final PR in a sequential series: - Refs #10013 — PR-1: opt-in sync hook API + provider docs (merged) - Refs #10028 — PR-2: native file-sync lifecycle hooks for Daytona provider (merged) **Feature:** Native single-exec file-sync lifecycle hooks for the Kubernetes sandbox provider. *Motivation:* The existing Kubernetes sync path encodes files as base64 and loops over `execInPod` one chunk at a time (~4 MB per exec). For large workspaces or asset sets this is slow and resource-intensive. The Kubernetes `execInPod` API supports piped stdin/stdout, enabling a raw-`tar` streaming transfer that needs only one exec regardless of file count or size and never buffers the whole payload in memory. *Proposed solution:* Implement `onEnvironmentSyncIn` and `onEnvironmentSyncOut` in the Kubernetes provider using a streaming `execInPod` with a tar pipeline — for syncIn the host builds the archive on disk and streams its raw bytes into the pod's stdin (`head -c <exact-size> | tar -x`, no base64); for syncOut in-pod `tar` writes to the exec's stdout and the host streams those bytes straight to a file. Path confinement, atomic replace, secret-mode enforcement, TOCTOU protection, and a streamed-bytes fail-closed guard are all enforced. ## What Changed - **New `src/file-sync.ts`** in `packages/plugins/sandbox-providers/kubernetes/` — `performSyncIn` and `performSyncOut` over an injected pod-exec closure, keeping transfer logic hermetically unit-testable - **New `execInPodStreaming` in `src/pod-exec.ts`** — a streaming exec primitive that binds a caller-supplied stdin readable and a stdout writable to the exec WebSocket data channel, added alongside the existing `execInPod` (which is unchanged). This lets a transfer stream raw bytes to/from disk instead of buffering the payload as a single string - **Updated `src/plugin.ts`** — registers `onEnvironmentSyncIn`/`onEnvironmentSyncOut`; resolves the `sandbox-cr` pod exactly like `onEnvironmentExecute` and delegates; `job` backend rejects file-sync calls explicitly (out of scope) - **syncIn path:** host builds the tarball to a temp file → streams its raw bytes over exec stdin, bounded in-pod by `head -c <exact-archive-size> | tar -x` (no base64 anywhere) → extract into a `/proc/self/fd`-pinned reserved `0700` staging dir → `chmod`-before-`mv -f` atomic replace per file (directory mappings use `followSymlinks`→`-h`) - **syncOut path:** in-pod validate + realpath-snapshot each source (closes the validation→copy TOCTOU window) → single-exec `tar -c` streamed over exec stdout → host streams that stdout straight to a temp file through a byte-counting transform → member-confined extraction of the sandbox-authored archive - **Security properties:** secret files land at requested mode with no widened window; every interpolated path is shell-quoted and confined lexically plus via in-pod `realpath`; the outbound stream is bounded by a **streamed-bytes disk guard** (`MAX_SYNC_OUTPUT_BYTES`, 8 GiB default, per-call overridable) that fails the transfer closed — writing no target file — if an untrusted pod emits more bytes than allowed. Neither host nor pod buffers the whole payload, so there is no in-memory size cap on the transfer - **No changes** to `execInPod`, `wrapCommandWithEnv`, or `FastUploadInterceptor` (the `environmentExecute` path is untouched) - **No dependency or lockfile changes** - **New tests** in `test/unit/file-sync.test.ts` (atomic-replace, `0600` secret mode, symlink preserve/deref, dir-mapping, exclude, path-confinement rejection, streamed-output guard fail-closed) and `test/unit/pod-exec.test.ts` (streaming stdin/stdout, caller-sink error fail-closed), plus extended `test/unit/plugin.test.ts` ## Follow-up: Legacy Job-Lease Base64 Fallback Fix Addresses the Greptile 4/5 blocking finding ("Handle existing job leases", `server/src/services/environment-runtime.ts`). Job leases provisioned before the `nativeFileSyncUnsupported` metadata flag existed carry `backend: "job"` but no flag, so `supportsSync()` treated them as native-capable and routed their sync to the pod-exec hook — which the job backend rejects (it has no exec channel) instead of using the byte-identical base64 fallback. The fix adds a belt-and-suspenders gate on the persisted `backend === "job"` field alongside the existing `nativeFileSyncUnsupported` flag check, so pre-existing job leases continue syncing via the base64 fallback after deployment. No behaviour change for `sandbox-cr` leases. ## Verification - `pnpm --filter @paperclipai/sandbox-provider-kubernetes test` — 19 files / 182 tests green, including the existing `upload-interceptor` and `pod-exec` suites - `tsc --noEmit` in the kubernetes package — 0 errors - The sync hooks are opt-in; existing `environmentExecute` behaviour is unaffected and tested by the unchanged existing suites ## Risks - **Opt-in only:** `onEnvironmentSyncIn`/`onEnvironmentSyncOut` are registered conditionally; providers that do not register them fall back to the existing chunk loop. No regression risk on the existing path. - **Shell-injection surface:** all path interpolation uses shell-quoting; paths are additionally confined lexically and via in-pod `realpath` before use. - **TOCTOU on syncOut:** the in-pod snapshot validates and records file metadata before the tar call, closing the window between validation and copy. - **Archive member confinement:** host-side reassembly rejects any tar member whose resolved path escapes the target directory, preventing a malicious in-pod tar from writing outside the intended destination. - **Untrusted-output volume:** an over-large outbound stream trips the streamed-bytes disk guard and fails closed (no target written and the temp sink is swept) rather than filling host disk or memory; the guard bounds disk unconditionally and bounds memory insofar as WebSocket write-backpressure holds. ## Model Used Anthropic Claude Sonnet 4.6 (`claude-sonnet-4-6`) — produced by a Claude-based AI agent using agentic tool use and multi-step code generation. 200K context window, extended reasoning, code execution and verification capabilities. ## 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: Harold Kim <harold@paperclip.ing> Co-authored-by: Paperclip <noreply@paperclip.ing>canary/v2026.723.0-canary.1 |
||
|
|
ad74fb5450 |
Add a feature catalog build artifact derived from the experimental settings schema (#10055)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Instances expose ~23 experimental feature settings, all declared in one shared zod schema and toggled per instance > - Deployment tooling and hosting control planes have no machine-readable list of those feature keys for a given release — the schema is only reachable from code that imports the package > - Any external system that references feature keys therefore does so as free text, and typos drift silently > - This pull request derives a versioned `feature-catalog.json` build artifact from the schema, with a compiler-checked metadata map so the schema stays the single source of truth > - The benefit is a stable contract external tooling can validate feature-key references against, with zero runtime behavior change ## Linked Issues or Issue Description No public issue exists; `feature_request` template fields: **Problem or motivation:** External deployment tooling cannot enumerate or validate an instance's feature keys per release; free-text references fail silently when keys are renamed or removed. **Proposed solution:** A metadata map keyed by the settings schema's own keys (compiler flags drift) plus a build step emitting `feature-catalog.json` (keys, tiers, defaults, `catalogVersion`) as a release artifact. **Alternatives considered:** A hand-maintained catalog file (drifts from the schema); serving the schema from a runtime API (requires a running instance at validation time — a build artifact works offline and pins to a release). **Roadmap alignment:** Supports the in-progress "Cloud deployments" milestone in `ROADMAP.md`. ## What Changed Adds a metadata map (title, description, tier, cloud/self-hosted defaults) keyed by the keys of `instanceExperimentalSettingsSchema`, so the schema stays the single source of truth and the compiler flags any drift. A new build step (`build:feature-catalog --version <v>`) emits `feature-catalog.json` — all 23 feature keys, their tiers, and a `catalogVersion` — as a release artifact that managed-hosting control planes can validate feature-flag writes against. No runtime behavior changes. - New `packages/shared/src/feature-catalog.ts`: per-flag metadata map keyed by a type derived from the settings schema (adding/removing/renaming a flag without updating the map is a compile error), plus `featureCatalogArtifactSchema` and `buildFeatureCatalogArtifact`/`renderFeatureCatalogArtifact` for the artifact - New `scripts/generate-feature-catalog.ts` wired as `pnpm build:feature-catalog --version <v>` - `scripts/create-github-release.sh` generates the artifact and uploads it as a GitHub Release asset (with a dry-run preview line) - Tests in `packages/shared/src/feature-catalog.test.ts` ## Verification - `vitest run packages/shared/src/feature-catalog.test.ts` — 9 tests: schema-key coverage, drift detection, artifact shape - `pnpm --filter @paperclipai/shared typecheck` - Artifact generation run end-to-end: `pnpm build:feature-catalog --version 0.0.0-test` emits 23 keys with `catalogVersion` ## Risks Low risk — no runtime behavior changes; the change is metadata, a build script, and a release-artifact emission step only. ## Model Used Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use; independently peer-reviewed by a second AI agent before push ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>canary/v2026.723.0-canary.0 |
||
|
|
e55d702916 |
feat(plugin-sdk): human-attributed issue comments for chat gateway plugins (#10050)
Adds the `issue.comments.create_human_attributed` capability and `ctx.issues.createComment` `actorUserId` option, with host-side active-human-member verification. LOOA-627. Co-Authored-By: Paperclip <noreply@paperclip.ing>v2026.722.0 canary/v2026.722.1-canary.0 |
||
|
|
1b8738da5c |
feat(cli): add --force to company export for non-interactive runs (#10054)
## Thinking Path
- `company export` writes into a target directory but aborts
non-interactively when that directory is non-empty ("already contains
files. Re-run interactively or choose an empty directory"), and there is
no override flag. Any automated caller that exports into a pre-existing
directory (for example a git clone used as a backup target) is therefore
stuck.
- The interactive confirmation is the right default for humans, but
automation needs an explicit opt-out rather than being forced to export
into a throwaway empty directory and copy the tree afterward.
- A `--force` flag that skips only the non-empty-directory confirmation
is the minimal change: it does not alter what gets written (existing
files are overwritten in place, nothing is bulk-deleted), so callers
keep full control over cleanup via their own VCS.
## What Changed
- Added a `--force` option to `company export` that skips the non-empty
output-directory confirmation for non-interactive/automated runs.
- Threaded the flag into `confirmOverwriteExportDirectory(outDir, {
force })`; behavior is unchanged when the flag is absent.
- Updated the non-interactive error message to also mention `--force`.
- Added focused unit tests covering: missing dir (resolves), empty dir
(resolves), non-empty dir without force (throws), non-empty dir with
force (resolves), and a path that exists but is a file (throws).
## Verification
- `pnpm --filter @paperclipai/cli exec vitest run
src/__tests__/company-export-force.test.ts` → 5/5 pass.
- `tsc --noEmit` over the CLI sources: no new type errors (the only
errors are pre-existing `@paperclipai/plugin-sdk` module-not-found in
`server/` from an unbuilt plugin sdk in the sandbox, unrelated to this
change).
- End-to-end against a local server: exporting into a non-empty
directory fails without `--force` and succeeds with it; a `.git`
directory and a sentinel file in the target were preserved; 128 files
written.
## Risks
- Low. The flag is opt-in and defaults to false; interactive and
empty-directory behavior is untouched. `--force` overwrites matching
files in place but never deletes unrelated files, so it cannot silently
wipe a directory.
## Model Used
Claude Opus 4.8 (claude-opus-4-8)
---
**Problem or motivation**
`company export` aborts when the `--out` directory is non-empty and
stdin/stdout are not a TTY, and there is no override flag. This makes it
impossible to run `company export` unattended into a pre-existing
directory such as a git clone.
**Proposed solution**
Add a `--force` flag that skips the non-empty-directory confirmation for
non-interactive callers. Files are still written on top of existing
content with no bulk delete, so unrelated files such as `.git` are
preserved.
**Alternatives considered**
Exporting into a fresh temp directory and copying the tree into the real
target afterward works but is clumsy and error-prone for automation;
broadening or removing the guard entirely would remove a useful safety
net for interactive users.
**Roadmap alignment**
Hardens the automated/unattended export path used by scheduled
company-backup routines.
---
- [x] I searched the repository and open pull requests for similar or
duplicate PRs and found none.
Co-authored-by: anicca <annica@MichaelacStudio.localdomain>
Co-authored-by: Paperclip <noreply@paperclip.ing>
canary/v2026.722.0-canary.4
|
||
|
|
b431d4eca1 |
fix(a11y): add ARIA progressbar to QuotaBar component (#1878)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Operators run those agents against paid model providers, so the web UI has a Costs surface that reports spend and quota utilisation > - Those figures are drawn as horizontal bars by the shared `QuotaBar` component (`ui/src/components/QuotaBar.tsx`), consumed by `BillerSpendCard` and `ProviderQuotaCard` > - `QuotaBar` renders its fill as a plain `<div>` whose CSS width is the only encoding of the percentage — no `role`, no value attributes, no accessible name > - A screen reader therefore announces nothing at all for these bars, so the spend and quota numbers they convey are unavailable to assistive-technology users (WCAG 2.1 SC 4.1.2, Name/Role/Value) > - The same gap is being closed for the other bars in this area by #1805 (BudgetPolicyCard) and #1869 (ProviderQuotaCard's inline bars); `QuotaBar` is the remaining shared component with no ARIA semantics > - This pull request adds the standard ARIA progressbar attributes to `QuotaBar`'s fill element, reusing the `label` prop the component already takes > - The benefit is that every progress bar on the Costs screen exposes its name and current value to assistive technology, with no visual or behavioural change for sighted users ## Linked Issues or Issue Description No existing GitHub issue — the problem is described in-PR below, following [`bug_report.yml`](.github/ISSUE_TEMPLATE/bug_report.yml). Related PRs from the same accessibility sweep (each covers a *different* component, so these are companions rather than duplicates — all three are currently open): - Refs #1805 — ARIA attributes for the BudgetPolicyCard progress bar - Refs #1869 — ARIA attributes for the ProviderQuotaCard inline bars **What happened?** On the Costs screen, the spend/quota bars rendered by `QuotaBar` (via `BillerSpendCard` and `ProviderQuotaCard`) are non-semantic `<div>` elements. Screen readers skip them entirely: no role, no value, no label is announced, so the percentage information is available only visually. **Expected behavior** Each bar should be exposed as a progress bar with an accessible name and its current value — e.g. announced as "Weekly spend: 45%, progress bar". **Steps to reproduce** 1. Run the app and open the Costs page. 2. Expand any provider or biller card so a quota/spend bar is visible. 3. Navigate to the bar with a screen reader (VoiceOver, NVDA, or Chrome DevTools → Accessibility pane). 4. Observe that the fill element has no role, no value, and no accessible name. **Paperclip version or commit** Reproduces on `master`; `ui/src/components/QuotaBar.tsx` has carried no ARIA attributes since the component was introduced. **Deployment mode** Not deployment-specific — the missing markup is in the shipped component. Verified in local dev (`pnpm dev`). **Agent adapter(s) involved** Not adapter-specific (core UI). ## What Changed - `ui/src/components/QuotaBar.tsx`: added `role="progressbar"` to the fill `<div>`. - Added `aria-valuenow={Math.round(clampedPct)}` with `aria-valuemin={0}` / `aria-valuemax={100}`, using the already-clamped percentage so the reported value can never fall outside 0–100. - Added an `aria-label` of the form `<label>: <pct>%`, reusing the existing `label` prop for the accessible name. - No changes to props, styling, layout, or rendering logic: 1 file, 5 added lines, 0 deleted. ## Verification - Manual: open Costs → expand a provider/biller card, inspect the bar in Chrome DevTools → Accessibility pane. The fill node now reports role `progressbar`, value `45`, min `0`, max `100`, and name "Weekly spend: 45%". - Manual: with VoiceOver/NVDA, the bar announces "Weekly spend: 45%, progress bar" instead of being skipped. - Visual regression check: the bar is unchanged for sighted users — only ARIA attributes were added, no class or style changes. - CI (lint, typecheck, build, tests) is green on this branch. - No unit test is added: the change is a set of static ARIA attributes on one element, and `QuotaBar` currently has no test file. Happy to add one if maintainers would like coverage here. ## Risks Low risk. Presentation-only accessibility metadata on a single element; no props, state, or styling change, and no other component is touched. The one debatable point is that the percentage appears in both `aria-label` and `aria-valuenow`, so some screen readers may announce it twice; both forms are valid, and the label is kept because it carries the bar's name alongside the value. Happy to drop the percentage from the label if reviewers prefer the terser announcement. ## Model Used <!-- @bluzername: please replace this line with the provider + exact model ID (and context window / reasoning mode if relevant), or "None — human-authored". Required by CONTRIBUTING.md. --> ## Checklist - [x] I have included a thinking path that traces from project context to this change - [ ] 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 - [ ] I have run tests locally and they pass - [ ] I have added or updated tests where applicable - [ ] I have updated relevant documentation to reflect my changes (no docs cover this component's markup) - [x] I have considered and documented any risks above - [x] 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 mergecanary/v2026.722.0-canary.3 |
||
|
|
204c416478 |
fix(release): publish bundled packages with trusted npm staging (#10047)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Paperclip publishes its CLI, server, adapters, and shared packages through automated canary and stable release workflows. > - `@paperclipai/adapter-utils` bundles the patched `acpx` runtime, so it must use npm 11 for OIDC trusted publishing. > - The prior staging directory contained pnpm's `.pnpm` symlink forest, which crashes npm 11's directory-pack step on GitHub runners and produces a consumer-broken bundled dependency tree. > - This pull request rebuilds staged production dependencies as a physical npm tree, reapplies repository patches, and publishes that clean directory directly with npm 11 trusted publishing. > - The benefit is a release path that retains GitHub Actions OIDC trusted publishing while shipping a working patched acpx runtime to consumers. ## Linked Issues or Issue Description Refs: #9980, #10030, #10041 No public GitHub issue exists for this release failure. ### What happened? Canary and stable publishing began routing `@paperclipai/adapter-utils` through npm after it declared `bundleDependencies: ["acpx"]`. Publishing the pnpm-deployed directory with npm 11 crashes during npm's directory-pack phase on GitHub runners with `Exit handler never called!`. The same staged shape also produces a broken consumer artifact because acpx cannot resolve transitive runtime dependencies after installation. ### Expected behavior Bundled packages publish directly from a self-contained staging directory through npm 11 OIDC trusted publishing, and consumers receive a working patched acpx runtime with its transitive dependencies. ### Steps to reproduce 1. Stage `packages/adapter-utils` using the old `pnpm deploy`-only shape. 2. Publish that directory with npm 11 on a GitHub runner. 3. npm crashes before registry/OIDC activity while walking the `.pnpm` symlink forest. 4. Install an artifact packed from that old shape into a fresh npm project and run acpx; its runtime dependency resolution fails. ### Deployment mode GitHub Actions canary/stable release workflow. ### Relevant logs or output ```text npm error Exit handler never called! ``` ## What Changed - After `pnpm deploy`, remove the staged pnpm `node_modules` tree and run `npm install --omit=dev --ignore-scripts --no-audit --no-fund` to create a physical hoisted production tree. - Apply every root `pnpm.patchedDependencies` patch whose package is declared in the staged package's bundled dependencies, failing staging if any patch cannot apply. - Assert the staged acpx runtime contains the required `onAgentStderr` patch marker. - Publish the clean staging directory directly with pinned npm 11.18.0, retaining GitHub Actions OIDC trusted publishing, verbose diagnostics, and the duplicate-transparency-log retry without provenance. - Keep pinned npm 10.9.7 packing only for local/dry-run payload verification; registry publishing does not use a tarball argument. - Add focused coverage for npm-tree staging, patch application, direct directory publish arguments, and bundled tlog retries. ## Verification - `bash -n scripts/release-lib.sh scripts/release.sh` - `node --test scripts/release-lib.test.mjs scripts/acpx-patch-packaging.test.mjs` — 12/12 passed. - `pnpm test:release-registry` — 68/68 passed. - Real staging smoke: `node scripts/prepare-bundled-package.mjs packages/adapter-utils <stage>` produced a real `node_modules/acpx` directory, no `.pnpm` directory, and the `onAgentStderr` patch marker. - Real npm 11 directory-publish smoke: `npx --yes npm@11.18.0 publish --dry-run --tag canary --access public --loglevel verbose` packed 26 bundled dependencies and reached the expected existing-version registry rejection without `Exit handler never called!`. - The merge-triggered `publish_canary` workflow remains the live OIDC trusted-publishing verification. ## Risks - The live GitHub Actions trusted-publishing path can only be fully proven by the merge-triggered canary run; npm debug-log upload remains available if it fails. - Bundling acpx continues to freeze platform-specific transitive artifacts such as esbuild binaries from the Linux release runner. This is a pre-existing consequence of the bundling decision in #9980 and is not expanded here. - Rebuilding dependencies with npm depends on the exact bundled dependency versions in the staged manifest; staging fails hard if repository patches no longer apply. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, exact model ID `gpt-5.6-sol`, high reasoning mode, with repository tool use and code execution. The harness did not expose a model 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>canary/v2026.722.0-canary.2 |
||
|
|
8f7509c28b |
fix(a11y): add aria-label to mobile tab selector in PageTabBar (#1871)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - People increasingly drive Paperclip from a phone, so the UI ships a mobile layout alongside the desktop one > - `PageTabBar` is the shared component behind the tab strip on nearly every detail page — AgentDetail, ProjectDetail, RoutineDetail, IssueDetail, Inbox, Costs > - On desktop it renders a Radix `TabsList`, whose `TabsTrigger`s carry their own accessible names; on mobile it swaps to a native `<select>` > - That `<select>` had no accessible name at all, so screen readers announced it only as "popup button" — a user could not tell what the control switches between > - Because the component is shared, the gap reproduced on every mobile page that uses tabs rather than on one screen > - This pull request adds `aria-label="Page section"` to the mobile `<select>` > - The benefit is that mobile screen-reader users get the same orientation desktop users already get from the tab triggers, from a one-line change with no visual or behavioral impact ## Linked Issues or Issue Description No existing public issue covers this, so the problem is described in-PR following `.github/ISSUE_TEMPLATE/bug_report.yml`: - **What happened** — On a mobile viewport, the `PageTabBar` `<select>` had no `aria-label`, no `<label>` association, and no visible text of its own. VoiceOver/TalkBack announce it as an unlabeled "popup button". - **Expected behavior** — The control announces what it switches between, matching the accessible naming the desktop `TabsTrigger`s already provide. - **Steps to reproduce** — 1. Open any detail page with tabs (agent, project, routine, issue). 2. Narrow the viewport to mobile width so the tab strip collapses to a `<select>`. 3. Focus the `<select>` with a screen reader. 4. Observe that no purpose is announced. - **Version / commit** — head `ef92d1c`, branch `fix/page-tab-bar-mobile-a11y`. - **Deployment mode** — Any. The change is UI-only and client-side. **Related prior PR:** #1532 (closed unmerged on 2026-03-23) made this same one-line change to `ui/src/components/PageTabBar.tsx` as part of a ~100-file batch. This PR is the focused standalone version of that fix. ## What Changed - Added `aria-label="Page section"` to the mobile `<select>` in `ui/src/components/PageTabBar.tsx`. One line added; no other files touched. ## Verification - **Automated:** `pnpm -C ui test` and the repo CI gates (lint, typecheck, build) — CI is currently green on `ef92d1c`. - **Manual:** Open any tabbed detail page, narrow the viewport until the tab strip becomes a `<select>`, and focus it with VoiceOver (macOS/iOS) or TalkBack (Android). It now announces "Page section, popup button" instead of an unlabeled "popup button". - **Inspector check:** In devtools, the `<select>` node's computed accessible name is "Page section" (previously empty). ## Risks Low risk. `aria-label` on a `<select>` is a presentation-free attribute: it changes nothing about layout, styling, DOM structure, event handling, or the desktop code path, which is untouched. No migration, no API change, no new dependency. The only debatable point is wording — "Page section" is a generic name shared by every call-site (see the note below). ## Model Used Not specified by the original author, and not recoverable from the commit metadata (no `Co-Authored-By` or model trailer on `ef92d1c`). @bluzername — please replace this line with the provider, model ID/version, and any relevant capability details, or "None — human-authored". ## Checklist - [x] I have included a thinking path that traces from project context to this change - [ ] I have specified the model used (with version and capability details) — see above; needs the author - [ ] 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 (`fix/page-tab-bar-mobile-a11y`) and contains no internal Paperclip ticket id or instance-derived details - [ ] I have run tests locally and they pass - [ ] I have added or updated tests where applicable — no test added; the change is a static attribute with no branching behavior - [ ] I have updated relevant documentation to reflect my changes — not applicable - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups — currently 4/5, see below - [ ] I will address all Greptile and reviewer comments before requesting merge --- ### Maintainer note on the open Greptile comment This description was restructured to the repository PR template by a maintainer; the code and the author's intent are unchanged. Unchecked boxes above are ones only @bluzername can attest to. Greptile's one remaining comment asks for a `selectAriaLabel` prop so call-sites could override the label. We think the hardcoded label is correct here and match existing practice: shared components whose meaning is fixed own their label internally (`ThemeToggle.tsx`), while components whose label depends on the data they render take it as a prop (`CopyText.tsx`'s `ariaLabel`). This `<select>` always means "which page section", at every call-site, so a prop no caller would set would be unused API surface. |
||
|
|
f215444919 | feat(daytona): native file-sync lifecycle hooks over batch transfer (#10028) | ||
|
|
39b94d0e7a | fix(test): gate interaction-continuation retry barrier on terminal write (#10049) | ||
|
|
4e8cd757eb |
fix(release): add npm publish crash diagnostics (#10041)
## Thinking Path > - Paperclip publishes a coordinated set of packages through its release workflows > - Bundled packages use a pinned npm CLI so trusted publishing works consistently > - The canary publisher now crashes deterministically inside npm before useful output reaches the workflow log > - The npm debug log that contains the underlying failure disappears with the hosted runner > - This pull request upgrades the pinned publish CLI and preserves both verbose HTTP activity and npm debug logs on failure > - The benefit is that the plausible HTTP-layer fix ships immediately, while any remaining CI-only failure becomes diagnosable ## Linked Issues or Issue Description - **Problem:** The canary release workflow fails on the first bundled package with `npm error Exit handler never called!` and no preceding diagnostic output. - **Expected behavior:** Bundled packages publish through trusted publishing, or the workflow retains enough npm diagnostics to identify the actual failure. - **Reproduction:** Run the canary release workflow in GitHub Actions; the failure reproduced on both attempts of run 29948506814. - **Version/commit:** Current `master` after #10024 and #10030. - **Deployment mode:** GitHub-hosted release workflow using Node.js 24 and npm trusted publishing. - Related: #10024, #10030. ## What Changed - Bumped the bundled publish CLI from npm 11.16.0 to npm 11.18.0. - Added `--loglevel verbose` to bundled npm publish invocations. - Dumped the last 300 lines of every npm debug log after failed canary or stable publishes, with common registry credential forms redacted. - Updated release assertions to pin npm 11.18.0 and verify verbose logging. ## Verification - `pnpm test:release-registry` — 66 tests passed. - `bash -n scripts/release-lib.sh`. - Parsed `.github/workflows/release.yml` with Python/PyYAML. - Smoke-tested npm log redaction with representative Authorization, `_authToken`, and token environment values. - Smoke-tested npm debug-log redaction against Authorization, `_authToken`, and `npm_token` examples. - `git diff --check origin/master...HEAD`. - The merge-triggered canary workflow remains the live trusted-publishing verification. ## Risks - Low code risk: changes are isolated to the release publisher and its workflow diagnostics. - npm 11.18.0 could expose a different registry/runtime regression; failure-time debug log dumping makes that actionable. - Verbose npm output increases release log volume but does not change package contents or dist-tags; common credential forms are redacted before debug logs are printed. > 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.3 Codex, tool-enabled coding agent with repository and shell execution; context window size is not exposed in this environment. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
8af70b9fae |
fix(test): drain in-flight heartbeat runs before liveness teardown (#10040)
## Thinking Path > - Paperclip runs AI agent heartbeats to manage work; each heartbeat dispatches `executeRun` fire-and-forget, which is intentional for concurrency > - The server escalation test suite (`heartbeat-issue-liveness-escalation.test.ts`) exercises `reconcileIssueGraphLiveness`, which heals a resolved-dependency wake by enqueuing an on-demand heartbeat run > - `enqueueWakeup` → `startNextQueuedRunForAgent` dispatches the run fire-and-forget (`void executeRun(...)`), so the background run outlives the awaited reconcile call > - The test's `afterEach` polled `heartbeat_runs.status` to wait for idle, but that flips to `completed` while `executeRun`'s finally block is still flushing events — the escaping `heartbeat_run_events` insert could land between the events delete and the runs delete, tripping the FK constraint > - This PR fixes the race deterministically by tracking in-flight `executeRun` promises and exposing `heartbeatService.drainActiveRunExecutions()`, which the suite awaits before clearing tables > - The benefit is a permanently reliable escalation test suite with no sleeps, no retry bumps, and no production behavior change ## Linked Issues or Issue Description **What happened?** The `heartbeat-issue-liveness-escalation.test.ts` suite intermittently failed in CI with: ``` delete on table "heartbeat_runs" violates foreign key constraint "heartbeat_run_events_run_id_heartbeat_runs_id_fk" ``` **Expected behavior** `afterEach` cleanup should complete without FK violations. **Steps to reproduce** The race is timing-dependent but surfaces reliably when the teardown window is artificially widened. `reconcileIssueGraphLiveness()` heals resolved-dependency wakes by dispatching a heartbeat run fire-and-forget (`void executeRun(...)`). The old `afterEach` polled `heartbeat_runs.status` — but that flips to `completed` while `executeRun`'s finally block still has pending `heartbeat_run_events` row writes. The escaping insert can land between the events delete and the runs delete. **Paperclip version or commit** Reproducible on current `master` (commit `b57aa9950c707a024156c34b79326a82b2dcca31`) ## What Changed - **`server/src/services/heartbeat.ts`** — tracks all in-flight `executeRun` promises in a module-level `Set`; exposes `heartbeatService(db).drainActiveRunExecutions()`, which loops until the set drains (a completing run can enqueue the next queued run in its finally, so a single `await` is not enough) - **`server/src/server-suites/heartbeat-issue-liveness-escalation.test.ts`** — replaces the poll-on-`heartbeat_runs.status` teardown with `await heartbeatService(db).drainActiveRunExecutions()` before clearing tables; removes the now-unnecessary `waitForHeartbeatRunToComplete` helper ## Verification ```bash # Full file (22 tests) npx vitest run server/src/server-suites/heartbeat-issue-liveness-escalation.test.ts # 12x stress loop (264 test-runs, 0 failures) for i in $(seq 1 12); do npx vitest run server/src/server-suites/heartbeat-issue-liveness-escalation.test.ts || break done # Type check the changed files npx tsc --noEmit ``` - 22/22 tests green locally - 12/12 full-file loop iterations: 264 test-runs / 264 afterEach cycles, 0 failures - Widened-teardown stress variant (failed deterministically before the fix) now passes with the drain ## Risks Low risk. The drain mechanism is additive — it only affects test teardown and could also be wired into graceful shutdown. The fire-and-forget dispatch in production is unchanged. The `Set`-based tracking adds negligible overhead per run dispatch (insert on dispatch, delete on completion). ## Model Used - **Provider:** Anthropic - **Model:** Claude Sonnet 4.6 (`claude-sonnet-4-6`) - **Context window:** 200K tokens - **Mode:** Tool use, code execution, extended reasoning ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes: #` / `Refs: #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] 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: Harold Kim <harold@paperclip.ing> Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
b57aa9950c |
fix(test): stop flaky server-suite afterAll hook timeouts (#10024)
## Thinking Path > - Paperclip is an open-source AI agent management platform; its test suite spans a `server` package that mounts real embedded Postgres databases in `beforeAll`/`afterAll` hooks > - The `server` package CI shard runs all ~93 suites serially (`maxWorkers=1`) on a loaded CI host; each suite boots and tears down its own embedded Postgres in hook callbacks > - vitest's default `hookTimeout` is 10 seconds; under load, graceful embedded-Postgres shutdown occasionally crosses that threshold > - This produces intermittent `Error: Hook timed out in 10000ms` failures in `afterAll` hooks — not test assertion failures — and the suites pass on re-run, making them textbook flaky tests > - Inspecting `embedded-postgres@18.1.0-beta.16` shows that `stop()` takes no argument (no fast-shutdown mode), SIGINTs postgres (already PostgreSQL "fast shutdown"), and resolves only on the child's `exit` event with no internal time bound > - Two targeted fixes: (1) raise `hookTimeout` and `teardownTimeout` to 30 s in `server/vitest.config.ts` — one config change that eliminates the flake for all ~93 suites at once; (2) wrap `stop()` in a 5 s bounded `Promise.race` in the test helper so a slow shutdown can never hang the hook regardless of OS scheduling variance > - This PR changes only test-infra and test-config; no production-code behavior changes ## Linked Issues or Issue Description No public GitHub issue exists for this flake. Inline bug description (bug report template): **What happened?** The `General tests (server (N/3))` CI shards intermittently fail with `Error: Hook timed out in 10000ms` in `afterAll` hooks and pass on re-run. Every test assertion passes; only the teardown hook exceeds vitest's default timeout. **Expected behavior** CI passes reliably. Teardown timeouts should not be a source of flake. **Steps to reproduce** Run the server test suite repeatedly on a loaded host or in CI with `maxWorkers=1` — the shard occasionally crosses 10 s in `afterAll` during embedded-Postgres shutdown. **Paperclip version** `master`, any build that includes `server/vitest.config.ts` without an explicit `hookTimeout`. **Deployment mode** Self-hosted (CI). ## What Changed - **`server/vitest.config.ts`** — added `hookTimeout: 30000` and `teardownTimeout: 30000`. Removes flake across all ~93 server suites at once. 30 s gives generous headroom over observed worst-case teardown while still catching a genuinely hung hook. - **`packages/db/src/test-embedded-postgres.ts`** — added `stopEmbeddedPostgresBounded()`, a 5 s `Promise.race` wrapper around `stop()`. Applied at all three call sites inside `cleanup()`. Data dir is still removed unconditionally; errors are still swallowed; the null-instance guard is preserved. Existing behavior unchanged except the shutdown can no longer block indefinitely. ## Verification - `tsc --noEmit` clean on `packages/db` (built against worktree-local `shared`) - `packages/db` `client.test.ts` passes 14/14 — boots embedded Postgres and exercises the bounded teardown via `cleanup()` in `afterEach` - Standalone bounded-race semantics verified: hang resolves at the 5 s bound; late or immediate `stop()` rejection swallowed; no unhandled rejection; null-instance path safe - CI: all 3 server shards + split-verify lane (Async-Verification Gate) expected green after this PR ```bash # Reproduce the teardown test locally: cd packages/db && npx vitest run src/client.test.ts # Type-check packages/db: npx tsc --noEmit -p packages/db/tsconfig.json ``` ## Risks Low risk. No product-code changes — test-infra and test-config only. The vitest timeout increase is additive (raises the ceiling; never lowers it). The bounded race wrapper preserves prior teardown behavior exactly: data dir always removed, errors always swallowed, stop is still attempted. A worst-case outcome is that a genuinely hung `stop()` now surfaces as a test timeout at 30 s instead of 10 s — still caught, just later. ## Model Used - **Provider:** Anthropic - **Model:** Claude Sonnet 4.6 (`claude-sonnet-4-6`) - **Context window:** 200 k tokens - **Capabilities:** tool use, code execution, extended reasoning - **Mode:** Paperclip agent heartbeat (autonomous execution with human board oversight) ## 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: Harold Kim <harold@paperclip.ing> Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
47c38777d8 |
fix(release): use trusted publishing npm for bundled packages (#10030)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Paperclip publishes canary and stable packages through a shared release script > - GitHub Actions authenticates those publishes through npm trusted publishing and an OIDC identity token > - Bundled-dependency packages recently moved from pnpm publish to a pinned npm CLI to preserve their bundled files > - That pin selected npm 10, which cannot use trusted publishing, so the first bundled package failed with `ENEEDAUTH` > - This pull request keeps bundled-package packing on npm 10 while routing actual publishing through the trusted-publishing-capable npm 11 version > - The benefit is bundled packages keep their required npm packaging behavior while canary and stable releases authenticate successfully ## Linked Issues or Issue Description **What happened** The canary release job failed while publishing `@paperclipai/adapter-utils` with `ENEEDAUTH`. The package has bundled dependencies, so the release helper selected pinned `npm@10.9.7`; the workflow provides OIDC trusted publishing rather than an npm token, and npm 10 cannot use that authentication path. Because this is the first package attempted, the release exited before trying the remaining packages. **Expected behavior** Bundled-dependency packages should publish with an npm CLI that both preserves bundled dependencies and supports GitHub Actions trusted publishing. **Steps to reproduce** Run the canary release workflow from master after PR #9980. The `publish_canary` job reaches `@paperclipai/adapter-utils`, invokes `npx npm@10.9.7 publish`, and fails with `ENEEDAUTH`. **Deployment mode** GitHub Actions canary and stable npm release workflows. Refs #9980. ## What Changed - Kept bundled-package dry-run packing on npm `10.9.7`, which successfully produces the staged tarball. - Routed bundled-package publishing through npm `11.16.0`, which supports GitHub Actions trusted publishing. - Split the pack and publish helpers so future npm changes cannot silently couple the two compatibility requirements. - Updated focused release and ACPX packaging tests to enforce both versions and call paths. ## Verification - `pnpm test:release-registry` — 67 passed locally. - Initial all-npm-11 PR head: Canary Dry Run reproduced an npm-internal crash during bundled `pack`. - Current head `5b3961ed13dd26ed2d6b1096ea23fd91b32e4353`: Canary Dry Run passed with split npm pack/publish helpers. - All PR checks passed, including build, typecheck + release registry, server/workspace suites, both e2e shards, security gates, and Greptile. - Greptile reviewed the current head at 5/5 confidence with no blocking issues. ## Risks - Low risk: the change only separates the npm CLI used for bundled-package packing from the CLI used for publishing. - The versions remain explicitly pinned because npm 11.16.0 currently crashes on the bundled pack payload, while npm 10.9.7 cannot perform trusted publishing. - Focused tests assert both pins and both helper call paths, and the full Canary Dry Run passes on the current head. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, exact model ID `gpt-5.6-sol`, high reasoning mode, with repository, shell, GitHub CLI, and code-execution 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> |
||
|
|
e1e35881af |
feat(cost-events): propagate issue.billing_code at heartbeat record time (#6821)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agent runs report progress through the heartbeat service, which writes the cost ledger (`cost_events`) as usage accrues > - `cost_events` already has a `billing_code` column, but nothing populates it — the heartbeat writes `issueId`/`projectId` and leaves `billing_code` NULL > - Issues carry a `billing_code`, so the attribution data sits one join away but never reaches the ledger rows > - Reporting therefore has to reconstruct attribution by joining back to `issues` at query time, which reflects the issue's *current* billing code rather than the one in effect when the cost was incurred > - This pull request threads `billingCode` through `resolveLedgerScopeForRun` so the heartbeat stamps it onto each `cost_events` row at record time > - The benefit is that attribution is captured at write time and stays correct if an issue's billing code later changes ## Linked Issues or Issue Description No existing public GitHub issue. Describing the problem in-PR: **Problem.** `cost_events` has a `billing_code` column that is never written. The heartbeat's cost-ledger insert records `issueId` and `projectId` but not the billing code of the issue the run belongs to, so every row lands with `billing_code` NULL. **Impact.** Cost-per-billing-code reporting has to derive attribution by joining `cost_events` back to `issues` at query time. That join returns the issue's billing code *as of the query*, not as of when the cost was incurred, so historical cost reports shift retroactively whenever an issue is re-coded. **Desired behaviour.** The billing code in effect at record time is stored on the `cost_events` row itself. **Related PRs.** #6820 — same change to the same file by the same author, opened separately. These are duplicates; only one should land. ## What Changed - `resolveLedgerScopeForRun` now selects `issues.billingCode` alongside `id` and `projectId`. - The scope object it returns gained a `billingCode` field, populated with `issue?.billingCode ?? null`. - The early-return path for runs with no issue in context returns `billingCode: null`. - The `costs.createEvent` call in `heartbeatService` passes `billingCode: ledgerScope.billingCode` alongside `issueId`/`projectId`. No schema migration: `cost_events.billing_code` already exists. ## Verification **No automated test accompanies this change.** There is currently no test asserting that a `cost_events` row carries the issue's billing code when an issue is in scope, or `null` when there is not. A reviewer should treat the checks below as manual verification only. Manual verification against a running instance: ```sql -- Non-NULL billing_code for recent runs on billed issues SELECT billing_code, COUNT(*) FROM cost_events WHERE created_at > NOW() - INTERVAL '1 hour' GROUP BY billing_code; -- Cost attribution query this change is intended to enable SELECT billing_code, SUM(cost_cents) FROM cost_events GROUP BY billing_code; ``` Expected: rows for runs attached to an issue with a billing code now carry that code; runs with no issue in context remain NULL. ## Risks Low risk in blast radius, with two things worth a reviewer's attention: - **Behavioural shift for consumers.** `cost_events.billing_code` was uniformly NULL and now starts arriving populated. Anything downstream that groups, filters, or dedupes on that column will see new values and new cardinality. Existing rows are not backfilled, so the column is mixed NULL/non-NULL across the historical boundary. - **No test coverage.** The null-fallback behaviour on both paths is asserted only by reading the code, not by a test. - **Migration safety:** not applicable — no schema change; the column already exists. - **Failure mode:** if `billingCode` were absent from the `issues` selection the value would silently be `undefined` rather than erroring, so the field is worth confirming in review. ## Model Used **TODO (author):** this section is required and cannot be completed on your behalf. Please state the provider and model name, the exact model ID/version, and the reasoning/thinking mode used — or "None — human-authored" if no AI model was involved. Per the template, the "Generated with Claude Code" footer is not a substitute for this section. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [ ] I have specified the model used (with version and capability details) — **pending author input, see above** - [ ] 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 — #6820 is a duplicate of this PR - [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 - [ ] I have run tests locally and they pass - [ ] I have added or updated tests where applicable — **no test added for the new field** - [x] I have updated relevant documentation to reflect my changes — not applicable, no user-facing or documented behaviour changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green — **`e2e` did not complete on `5ca5fde` (Playwright install timed out at 30m and the run was cancelled); all other checks pass** - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups — **currently 4/5, sole finding being this description** - [ ] I will address all Greptile and reviewer comments before requesting merge --- <sub>This description was reformatted to `.github/PULL_REQUEST_TEMPLATE.md` by the Paperclip PR triage bot. The code was not modified. Checklist boxes reflect the PR's verifiable state at commit `5ca5fde`; unchecked items are genuinely outstanding, not oversights. The **Model Used** section requires input from the author. The previous description's `LEG-` reference was removed as an internal, instance-local identifier that the template prohibits.</sub> --------- Co-authored-by: Lead Backend Engineer Agent <backend1@legacykeeper.io> Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com> |
||
|
|
b247bf7150 |
feat(runtime): opt-in sandbox file-sync lifecycle hooks (API + provider docs) (#10013)
## Thinking Path > - Paperclip is an open-source AI-agent management platform; agents run tasks inside sandboxed environments (Daytona, Kubernetes, E2B, etc.) > - The control-plane ↔ sandbox file-transfer path flows through the `environmentExecute` seam in `protocol.ts` — the only verb available to plugins — which forces a base64-over-exec chunked loop for every file move: workspace files, assets, Codex home sync > - This transport is correct and safe, but it bypasses provider-native bulk/streaming APIs (Daytona `uploadFiles`, K8s `FastUploadInterceptor` / volume mounts), leaving significant throughput on the table for large workspaces > - The right fix is an opt-in seam extension: providers with faster native transfer declare two optional verbs; providers that do not opt in stay on the existing fallback with zero code or behavior change required > - This PR adds the first layer of that extension — two optional verbs (`environmentSyncIn` / `environmentSyncOut`) in the plugin SDK, the runtime plumbing to prefer the native path for the two clean destroy-then-replace cases, and a doc for the contract > - The core correctness invariant is byte-identical fallback: if no provider opts in, execution is exactly what ships today; `assertSyncOperationsConfined` enforces host-side path confinement for providers that do opt in > - No provider advertises the verbs yet → zero production behavior change; future PRs wire up Daytona and K8s providers against this contract ## Linked Issues or Issue Description No public GitHub issue exists for this feature. Description follows the `feature_request` issue template: **Subsystem affected:** packages/plugins — plugin system; packages/adapter-utils — adapter runtime; server/ — EnvironmentRuntimeService **Problem or motivation:** Sandbox file transfers currently always use a base64-over-exec chunked loop regardless of what the underlying provider supports. For workspaces larger than a few MB this becomes the dominant wall-clock cost of every sandbox run, and it bypasses bulk/stream APIs that providers like Daytona already expose natively. **Proposed solution:** Add two optional, opt-in plugin hooks — `onEnvironmentSyncIn` / `onEnvironmentSyncOut` — to the plugin SDK. When a provider defines both hooks and both are advertised via the existing `supportedMethods` negotiation, the runtime prefers the native path for the two clean destroy-then-replace transfer cases; all other cases fall back to the existing byte-identical base64 transport. **Alternatives considered:** An unconditional verb would require every provider to implement or stub the verb. The opt-in / `METHOD_NOT_IMPLEMENTED` pattern (already used by `environmentExecute`) preserves backward compatibility with zero provider changes required. **Roadmap alignment:** Consistent with the ✅ "Cloud / Sandbox agents" and ✅ "Plugin system" milestones; extends the plugin seam rather than adding control-plane-level logic. **Additional context:** Searched open pull requests and issues for duplicate sandbox file-sync / native-transfer work; none found. ## What Changed - **`packages/plugins/sdk`** - `protocol.ts`: two new optional `HostToWorkerMethods` — `environmentSyncIn` / `environmentSyncOut` — plus generic `SyncOperation`, `SyncFileMapping`, and `SyncOutcome` types - `define-plugin.ts`: optional `onEnvironmentSyncIn` / `onEnvironmentSyncOut` fields on `PluginDefinition`; worker advertises each verb only when its hook is defined (else `METHOD_NOT_IMPLEMENTED`, mirroring `environmentExecute`) - `worker-rpc-host.ts`: route new verbs to plugin hooks - `index.ts`: re-export new public types - **`packages/adapter-utils`** - `command-managed-runtime.ts`: expose optional `syncIn` / `syncOut` on `CommandManagedRuntimeRunner` (available only when both verbs are advertised); add `assertSyncOperationsConfined` host-side path-confinement guard - `sandbox-managed-runtime.ts`: `SandboxManagedRuntimeClient` gains optional `syncIn` / `syncOut`; orchestrator prefers native path for default-provision asset inbound and workspace-download-into-fresh-dir outbound; all other paths keep the existing base64 fallback - `sandbox-file-sync.test.ts` (new): 234-line characterization suite — native-opt-in branch, fallback branch, `assertSyncOperationsConfined` escape-path rejection, `followSymlinks` → tar `-h` - `command-managed-runtime.test.ts`: negotiation + native-sync + confinement tests - **`server/src/services/environment-runtime.ts`**: `EnvironmentRuntimeService` delegates to `syncIn` / `syncOut`, gated on advertised support - **`server/src/services/environment-execution-target.ts`**: minor typing fix alongside the new verbs - **`doc/plugins/SANDBOX_FILE_SYNC_HOOKS.md`** (new): documents the full contract — opt-in / no-op guarantee, operation ordering, provider-may-tar, atomicity, `followSymlinks`, secret modes (0600, no window), path confinement, `operationId` opacity, resource bounds, shell-quoting ## Verification ```bash # SDK suite pnpm --filter packages/plugins/sdk test # Adapter-utils suite (includes new sandbox-file-sync characterization tests) pnpm --filter packages/adapter-utils test # Expected: 255 pass / 4 skip # Type-check across affected packages pnpm --filter packages/plugins/sdk typecheck pnpm --filter packages/adapter-utils typecheck # Server changed-file spot check: cd server && npx tsc --noEmit --skipLibCheck 2>&1 | grep -E "environment-(runtime|execution-target)" | head -20 ``` Key behavioral invariant to spot-check: with no provider opting in (the current state), run any sandbox task and confirm file-transfer behavior is byte-for-byte identical to what the pre-PR code produces. The characterization tests assert this at the unit level. ## Risks - **Zero production risk today**: no provider advertises `environmentSyncIn` / `environmentSyncOut`, so the new code paths are unreachable in production; all real traffic stays on the existing base64 fallback - **Path confinement**: `assertSyncOperationsConfined` rejects any `targetPath` that escapes the declared root — this is the primary security boundary for future providers. The test suite covers escape-path rejection - **Atomicity**: the contract delegates atomicity to providers; the doc explicitly calls out that directory-level ops are not guaranteed atomic - **Secret transport**: credential assets (e.g., Codex `auth.json`, directory mappings) continue to use the existing tar path — they do not go through the new verbs in any current provider > 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 Provider: Anthropic Model: `claude-sonnet-4-6` (Claude Sonnet 4.6) Context window: 200 K tokens Capabilities: extended tool use, multi-file code generation, agentic reasoning via the Paperclip agent framework ## 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: Harold Kim <harold@paperclip.ing> Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
d54ff52fc3 |
test(heartbeat): await execution drain before cleanup (#10023)
## Thinking Path > - Paperclip is the open source control plane people use to manage AI agents and their work > - Heartbeat scheduling tests protect the orchestration rules that serialize an agent's runs > - The dependency scheduling suite waits for run rows to become terminal before deleting shared database fixtures > - A terminal row is persisted before asynchronous execution finalization and successful-run handoff work fully drain > - The test then clears process tracking and deletes heartbeat events while finalization can still append another event > - This pull request waits for each tracked run's execution promise to drain before resetting mocks or deleting fixtures > - The benefit is deterministic cleanup that preserves the production lifecycle ordering and prevents release CI flakes ## Linked Issues or Issue Description ### What happened? Release run `29936031931` failed in `heartbeat-dependency-scheduling.test.ts` while deleting `heartbeat_runs`. Asynchronous heartbeat finalization inserted a new `heartbeat_run_events` row after the test had already deleted existing events, causing the run-row delete to violate the event foreign key. ### Expected behavior The serialized heartbeat test suite should finish all asynchronous run execution work before destructive fixture cleanup. ### Steps to reproduce 1. Check out commit `2aef4641b48e88f5ce7e75ce69fbe3bf6bbfc60d`. 2. Run `pnpm exec vitest run --project @paperclipai/server server/src/__tests__/heartbeat-dependency-scheduling.test.ts --pool=forks --isolate` repeatedly with PostgreSQL test support enabled. 3. Observe that teardown can delete heartbeat events while execution finalization is still able to append another event, causing a foreign-key failure when heartbeat runs are deleted. ### Paperclip version or commit `2aef4641b48e88f5ce7e75ce69fbe3bf6bbfc60d` ### Deployment mode Other — GitHub Actions release verification. ### Installation method Built from source with pnpm. ### Agent adapter(s) involved Not adapter-specific (core heartbeat test lifecycle). ### Database mode External PostgreSQL test database. ### Relevant logs or output `delete from "heartbeat_runs"` failed because the run remained referenced by `heartbeat_run_events_run_id_heartbeat_runs_id_fk`. ## What Changed - Collect heartbeat run IDs after queued/running rows settle and await `heartbeat.waitForRunExecutionDrain()` for each run. - Reset the adapter mock and clear process tracking only after asynchronous heartbeat finalization has completed. ## Verification - Ran `pnpm exec vitest run --project @paperclipai/server server/src/__tests__/heartbeat-dependency-scheduling.test.ts --pool=forks --isolate` 10 consecutive times; all 10 runs passed with 6/6 tests. ## Risks - Low risk: test-only cleanup ordering change using an existing heartbeat service drain API. Production behavior is unchanged. > 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 exact model IDs `gpt-5.5` for this heartbeat and `gpt-5.6-sol` for the recovered initial implementation run; tool-enabled code inspection, GitHub diagnostics, and shell test execution. Runtime context-window sizes were not exposed. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
2aef4641b4 |
docs(release): add v2026.722.0 changelog (#10008)
## Release changelog: Paperclip v2026.722.0 Adds `releases/v2026.722.0.md`, the user-facing stable changelog for the `v2026.722.0` release, generated per `.agents/skills/release-changelog/SKILL.md`. - **Range:** `v2026.720.0..origin/master` — 44 commits from 13 contributors. - **Version:** confirmed via `scripts/release.sh stable --date 2026-07-22 --print-version` → `2026.722.0`. - **Breaking changes:** none. Two additive DB migrations (Connections v3 schema core + connection user authorization state); no destructive migrations. - **Sections:** Highlights (run-bound agent secret access; Windows local-agent spawning), Improvements, Experimental (Connections v3 foundation, gated behind the Apps experimental setting), Fixes, Upgrade Guide, Contributors. Contributor count/names follow the skill rules (bots and Paperclip founders excluded from the listed names but counted in the total). Related issue: PAP-14983. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
486ecac5e9 |
chore(lockfile): refresh pnpm-lock.yaml (#9964)
Auto-generated lockfile refresh after dependencies changed on master. This PR only updates pnpm-lock.yaml. Co-authored-by: lockfile-bot <lockfile-bot@users.noreply.github.com> |
||
|
|
d31a28828b |
fix(acpx): support Windows agent spawning (#9980)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Local Claude, Codex, Gemini, and custom ACP adapters run through the shared embedded ACPX engine > - That engine wrapped every local agent command in a generated Bash script to inject environment variables and filter child stderr > - Windows cannot directly spawn that Bash wrapper, and npm/pnpm ACP binaries are exposed through `.cmd` shims there > - ACPX 0.12 already supports per-session child environment variables, so the wrapper is unnecessary > - This pull request registers agent commands directly, injects env through ACPX session options, captures child stderr in-process, and adds a real Node ACP spawn smoke on Ubuntu and Windows > - The benefit is one cross-platform spawn path with a reusable smoke test instead of parallel shell-wrapper implementations ## Linked Issues or Issue Description Fixes #9941. Refs #9428 and #9771. **What happened** ACPX-backed local agents failed to start on Windows because Paperclip registered a generated POSIX `.sh` wrapper as the agent command. Windows also needs the `.cmd` npm/pnpm shim when resolving built-in ACP binaries, and symlink creation can fail with `EPERM` for seeded auth/skill files. **Expected behavior** The same ACPX engine path should spawn a real ACP agent on Windows and Linux, forward Paperclip/runtime env without mutating `process.env`, preserve filtered/unfiltered child stderr behavior, and fall back to copies where Windows symlinks are unavailable. **Steps to reproduce** Run a local ACPX adapter on Windows with the prior wrapper path. ACPX attempts to spawn the generated `.sh` file and the agent never initializes. **Deployment mode** Local Paperclip adapters using `packages/adapter-utils/src/acpx-engine/`. ## What Changed - Removed generated Bash agent/env wrappers and registered local commands directly with ACPX. - Passed the resolved child environment through ACPX `sessionOptions.env`, including resume retry paths. - Added a minimal `acpx@0.12.0` package patch exposing child stderr callbacks and allowing documented uppercase env-map keys in persisted session options. - Moved stderr tee/filter behavior in-process: raw stderr remains in the per-run file while benign `nes/close` noise is omitted from live stderr. - Preferred `.cmd` ancestor binaries on Windows and added `EPERM` copy fallbacks for Codex auth seeding and Gemini skill materialization. - Added a real Node ACP echo-agent spawn smoke that can run directly on any supported platform. ## Verification - `pnpm exec vitest run packages/adapter-utils/src/acpx-engine/execute.test.ts packages/adapter-utils/src/acpx-engine/spawn-smoke.test.ts` — 57 passed. - `pnpm --filter @paperclipai/adapter-utils typecheck` — passed. - `node --test scripts/acpx-patch-packaging.test.mjs scripts/release-lib.test.mjs` — 10 passed. - Full canary release dry run under Node 24.18.0 / npm 11.16.0 — passed in an isolated scratch clone. - `git diff --check` — passed during implementation verification. - One-time GitHub Actions proof: [Ubuntu ACPX spawn smoke](https://github.com/paperclipai/paperclip/actions/runs/29924348927/job/88937774579), [Windows ACPX spawn smoke](https://github.com/paperclipai/paperclip/actions/runs/29924348927/job/88937774558), and [Canary Dry Run](https://github.com/paperclipai/paperclip/actions/runs/29924348927/job/88937774497) passed on head `f345ac69f2`; the dedicated smoke jobs are intentionally not retained in the recurring PR workflow. ## Risks - The ACPX stderr callback and env persistence exemption are carried as a pnpm dependency patch until ACPX exposes/fixes those behaviors upstream. - Child stderr is synchronously appended to preserve ordering and failure diagnostics; unusually high-volume agent stderr could briefly block the Node event loop. - The Windows-specific `.cmd` resolution and symlink `EPERM` branches are proven by the standalone smoke test and the linked one-time `windows-latest` run rather than a permanent CI gate. > 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 GPT-5.4 via Codex CLI, medium reasoning, repository/tool execution enabled; context-window size is not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
0b496c9c03 |
feat(secrets): add run-bound agent secret access (#9921)
## Thinking Path > - Paperclip is the open source control plane people use to manage AI-agent companies. > - Agents already receive selected company secrets through `env.*` bindings at run launch, but environment injection is ambient, long-lived, and not suitable for every secret consumer. > - The existing binding and secret-access-event models already provide company-scoped authorization and per-resolution audit seams. > - Agents need an explicit way to discover only the secrets granted to them and fetch a value on demand without exposing the wider company catalog. > - That capability must remain run-bound, preserve low-trust token carve-outs, and make every value read visible in both security and operator audit trails. > - This pull request adds an `access.*` delivery namespace, two run-bound agent routes, dual audit logging, documentation, and an operator grants editor. > - The benefit is least-privilege, revocable, auditable secret access while preserving existing env injection behavior. ## Linked Issues or Issue Description No pre-existing public issue. Related work: - Refs #9797 — existing in-sheet agent access UI that this PR extends to distinguish env and API delivery. - Refs #9918 — complementary searchable-agent picker improvement for the same secrets sheet. - Refs #9530 — related company-wide metadata catalog proposal; this PR intentionally exposes only the authenticated run's granted aliases and values. **Problem / motivation:** Agents can currently consume secrets only through process environment injection. This keeps values resident for the run, does not support on-demand consumers, and cannot provide a discrete operator-visible activity event for each agent-initiated read. **Proposed solution:** Treat `company_secret_bindings` as the source of truth for agent secret grants. Keep `env.KEY` as env delivery and add `access.ALIAS` for API-only delivery; an env binding also implies read access because the value is already present in the agent process. Add run-bound list/fetch endpoints that derive scope from the authenticated heartbeat run and never accept caller-selected overlays. **Alternatives considered:** A company-wide agent-readable catalog was rejected for this value path because it increases reconnaissance and does not prove a per-secret grant. Reusing the ephemeral environment-probe resolver was rejected because it lacks binding enforcement. Approval-gated reads and user-scoped secrets remain deferred beyond v1. **Roadmap alignment:** This extends the completed **Secrets Manager with per-agent access** roadmap capability from launch-time env injection to explicit run-bound API delivery without duplicating a separate planned initiative. ## What Changed - Added `access.*` agent binding validation and a dedicated run-bound resolver that combines `secrets:read` authorization with binding-context enforcement. - Added `GET /api/agents/me/secrets` for minimal granted metadata and `POST /api/agents/me/secrets/:key/value` for on-demand value fetches with `Cache-Control: no-store`. - Preserved the existing denials for low-trust review agents, task-bridge credentials, and skill-test tokens; standard long-lived agent API keys cannot call the run-bound routes. - Added dual audit behavior: value attempts write `secret_access_events` and `activity_log` (`secret.value.read`), while metadata listing writes the lighter `secret.access.listed` activity event. - Kept env compatibility: `env.*` remains injected at launch and also implies API read for the same bound agent; `access.*` never becomes an environment variable. - Added the agent-settings **Secret access** editor plus delivery-mode/alias surfacing on the Secrets page, with focused UI tests and tokenized layout styles. - Updated OpenAPI, shared types, agent-facing skill documentation, and API reference documentation. ### UI Screenshots P3 produced and reviewed three screenshots using mock data; images are intentionally not committed to the repository: - `secret-access-editor.png` — agent settings grant editor. - `secret-access-light.png` — Secrets-page delivery surfacing in light mode. - `secret-access-dark.png` — Secrets-page delivery surfacing in dark mode. The source attachments are retained with the implementation task and linked in the internal handoff; the public page publisher was unavailable in the PR-prep runtime. ## Verification - `pnpm exec vitest run server/src/__tests__/agent-secrets-routes.test.ts server/src/__tests__/secrets-service.test.ts server/src/__tests__/secrets-routes.test.ts ui/src/lib/secret-delivery.test.ts ui/src/components/AgentSecretAccessEditor.test.tsx` — 5 files, 122 tests passed. - Security follow-up: `pnpm exec vitest run server/src/__tests__/agent-secrets-routes.test.ts server/src/__tests__/secrets-service.test.ts` — 2 files, 73 tests passed after active-run and version-consistency fixes. - Final-head CI: all feature, typecheck, build, e2e, security, and review gates pass; `General tests (server (1/3))` remains red after one rerun because unrelated `heartbeat-retry-scheduling.test.ts` cleanup deletes `heartbeat_runs` before referenced `activity_log` rows. - `pnpm --filter @paperclipai/server typecheck` — passed. - `pnpm --filter @paperclipai/shared typecheck` — passed. - `pnpm --filter @paperclipai/ui typecheck` — passed. - `pnpm check:token-gates` — feature-local arbitrary-value violations fixed; command still reports five unchanged `#9627` literals outside this PR. - End-to-end QA passed all eight acceptance criteria: grant/list, fetch, dual audit, env-implies-read, denial matrix, revocation, UI rendering, and env-injection regression. Evidence: https://github.com/paperclipai/paperclip/pull/9921#issuecomment-5027455492 - Security review returned PASS-with-required-changes; the implementation uses the required dedicated binding-enforcing resolver, run-bound JWT restriction, run-derived overlays, minimal metadata, and a resolver redaction-registration hook. Evidence: https://github.com/paperclipai/paperclip/pull/9921#issuecomment-5027455382 ## Risks - A compromised agent can exfiltrate any secret explicitly granted to it; explicit company-scoped/run-scoped grants, revocation, and audit reduce but cannot remove that inherent capability risk. - The resolver invokes a redaction-registration hook before returning values, but the current route has no persistent cross-request per-run redaction registry. Paperclip-owned later comments/events therefore cannot yet guarantee automatic scrubbing of a deliberately copied fetched value; QA classified this as non-blocking residual hardening. - Audit-event insertion currently fails open if the security-event insert itself fails; the operator activity event provides partial redundancy, but a future hardening change should define fail-closed behavior for value delivery. - This PR overlaps `ui/src/pages/Secrets.tsx` with #9918 and may require a straightforward rebase after that PR moves. > 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.3-codex`, with reasoning, repository tool use, terminal execution, Paperclip API access, and GitHub CLI capabilities. Context-window size is not exposed by the runtime. - Anthropic Claude Opus 4.8 with 1M context and tool use assisted with the UI implementation commit. ## 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>canary/v2026.722.0-canary.1 |
||
|
|
5ed0b74b34 |
fix(runtime): scope PAPERCLIP_ env-binding strip to reserved keys (#9974)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agent runs get their environment from user/adapter/project/routine env bindings resolved by the server heartbeat, plus `PAPERCLIP_*` runtime vars (identity, wake, workspace, API access) injected by the harness > - The heartbeat stripped **every** `PAPERCLIP_`-prefixed binding before resolution, so legitimately user-named keys (e.g. cloud provider token bindings like `PAPERCLIP_CLOUD_PROD_PROVIDER_RAILWAY_*`) were silently dropped and never reached the run env > - At the same time, several adapters honored an explicitly configured `PAPERCLIP_API_KEY` over the harness-minted run token, which is exactly the one key config must never control > - This pull request replaces the blanket prefix strip with a precise three-rule policy: never accept `PAPERCLIP_API_KEY` from config, always let harness-assigned runtime vars win, and let every other `PAPERCLIP_*`-named user binding flow through > - The benefit is that user secrets with a `PAPERCLIP_`-style name work like any other binding, while runtime identity and API credentials stay fully harness-controlled ## Linked Issues or Issue Description **Bug description** (no public issue exists): - **What happened:** Env bindings whose key starts with `PAPERCLIP_` (e.g. a cloud provider token a user deliberately named `PAPERCLIP_CLOUD_PROD_PROVIDER_RAILWAY_TOKEN`) were silently stripped by the server before secret resolution, so the spawned agent never received them. No error, no access event — the variable just never appeared. - **Expected behavior:** A user-named `PAPERCLIP_*` binding should reach the run env unless the harness itself uses that key. Only `PAPERCLIP_API_KEY` should be categorically rejected, and harness-assigned runtime vars (`PAPERCLIP_RUN_ID`, `PAPERCLIP_AGENT_ID`, wake/workspace vars, …) should always win over config. - **Steps to reproduce:** Configure an agent/project env binding named `PAPERCLIP_<ANYTHING>` (plain or secret_ref), run a heartbeat, and inspect the spawned process env — the key is absent. - **Deployment mode:** local server, any local adapter. Related prior PRs (different, save-time/API-layer blanket-ban approach; this PR supersedes that direction with a runtime allow-except-reserved policy): Refs #8239, Refs #8439. ## What Changed - `server/src/services/heartbeat.ts`: the pre-resolution strip now removes only `PAPERCLIP_API_KEY` (hard denylist) instead of every `PAPERCLIP_`-prefixed binding; other `PAPERCLIP_*` keys flow into binding resolution. Low-trust inline-sensitive-env checks now also cover those keys. - `packages/adapter-utils/src/server-utils.ts`: new `isForbiddenConfigEnvKey()` helper; the shared `refreshPaperclipWorkspaceEnvForExecution` merge drops `PAPERCLIP_API_KEY` from config and keeps harness-assigned `PAPERCLIP_*` keys authoritative. - `packages/adapter-utils/src/acpx-engine/execute.ts`: removed the explicit-`PAPERCLIP_API_KEY`-from-config allowance; the run token (`authToken`) is now always applied; config `PAPERCLIP_API_KEY` is ignored. - All local adapters (`claude-local`, `codex-local`, `cursor-local`, `gemini-local`, `grok-local`, `opencode-local`, `pi-local`) plus `cursor-cloud`, `hermes`, and the server `process` adapter: removed `hasExplicitApiKey`-style allowances so the harness token always wins, and guarded the remaining unguarded env-merge loops (claude-local inline loop, process adapter) with the same policy. - Tests updated/added: heartbeat binding-strip test now asserts the three-rule policy; adapter-utils merge tests assert the `PAPERCLIP_API_KEY` ban and `PAPERCLIP_*` pass-through; acpx engine tests moved credential fixtures to `authToken` and assert config `PAPERCLIP_API_KEY` is ignored while other `PAPERCLIP_*` config keys forward and still bust the session fingerprint on rotation. ## Verification - `pnpm vitest run packages/adapter-utils/src/server-utils.test.ts packages/adapter-utils/src/acpx-engine/execute.test.ts` — 127 passed - `pnpm vitest run server/src/__tests__/heartbeat-project-env.test.ts server/src/__tests__/heartbeat-local-environment.test.ts server/src/__tests__/claude-local-execute.test.ts server/src/__tests__/codex-local-execute.test.ts server/src/__tests__/cursor-local-execute.test.ts server/src/__tests__/gemini-local-execute.test.ts` — 68 passed - Adapter package execute suites and the server tests touching API-key fixtures (`heartbeat-run-log`, `redaction`, `effective-run-config-fingerprints`, `agent-permissions-routes`) — green. Three pre-existing sandbox/SSH fixture failures reproduce identically on clean `master` on this host and are unrelated. - `pnpm --filter <pkg> typecheck` for server, adapter-utils, and all nine touched adapter packages — all pass. ## Risks - Behavioral change: a deployment that relied on configuring a static `PAPERCLIP_API_KEY` in adapter config env loses that override — by design; the harness-minted run token is now the only source. When no run token exists, no API key is injected at all. - `PAPERCLIP_*`-named user bindings now reach binding resolution and run envs; a key that collides with a harness runtime var is still discarded at merge time, so runtime identity/wake/workspace vars cannot be spoofed. - Low risk otherwise: no migrations, no API surface changes. ## Model Used - Claude Fable 5 (`claude-fable-5`, Anthropic Claude 5 family, Mythos-class tier), extended thinking enabled, agentic tool use (file edits, shell, test runner) via 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 - [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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7985c1e000 |
refactor(codex-local): stage an allowlist for sandbox CODEX_HOME sync (#9972)
## Thinking Path
> - Paperclip manages AI agents for work; agents can run in remote
sandboxes via the codex-local adapter
> - When a remote sandbox run starts, the codex-local adapter syncs the
host CODEX_HOME directory into the sandbox so Codex can authenticate and
load its configuration
> - The old approach staged the entire CODEX_HOME and relied on a 4-name
denylist (`tmp`, `.tmp`, `sessions`, `shell_snapshots`) to exclude
unnecessary state
> - The denylist was too narrow: large host-local runtime files
(`logs_2.sqlite`, `plugins/`, etc.) still uploaded on every remote run —
state the sandbox never uses
> - Additionally, `config.toml` (which embeds the managed MCP
`Authorization: Bearer …` header) was staged at 0644, relying solely on
the staging directory's 0700 permission for protection — a
credential-bearing file without per-file least-privilege
> - This pull request replaces the denylist with an explicit allowlist:
only `{auth.json, config.toml, config.json, instructions.md, skills/}`
are staged into a private `0700` temp dir; all staged regular files are
written `0600` (fail-closed on any staging error)
> - The fix is adapter-local to `packages/adapters/codex-local` with
zero changes to the shared `packages/adapter-utils` seam; inbound
auth-merge and outbound copy-back are preserved unchanged
## Linked Issues or Issue Description
No public GitHub issue for this specific bug. Describing the problem
directly:
**Bug:** The codex-local remote sandbox sync uploads more CODEX_HOME
content than the sandbox uses, and stages a credential-bearing file with
an over-permissive mode.
- **What happened:** The codex-local adapter synced the entire
CODEX_HOME directory minus four directory names (`tmp`, `.tmp`,
`sessions`, `shell_snapshots`). Large host-local runtime artifacts — a
SQLite write-ahead log that can reach 420 MB, plugin directories, crash
logs — uploaded on every remote run. `config.toml` (which embeds the
managed MCP `Authorization: Bearer …` header) was staged at `0644`; only
the staging temp dir's `0700` mode prevented broader exposure.
- **Expected behavior:** Only the files Codex needs inside the sandbox
should sync: `auth.json`, `config.toml`, `config.json`,
`instructions.md`, and `skills/`. All staged regular files should use
mode `0600` (single-layer credential protection is insufficient for
secret-bearing files).
- **Steps to reproduce:** Run a remote codex sandbox from a host with a
mature CODEX_HOME. Observe the sync payload includes `sessions/`,
`*.sqlite`, `plugins/`, etc.
Related work: Refs #9621 (outbound auth sync-back in the same
codex-local adapter area; this PR covers the inbound staging direction)
## What Changed
- New `stageCodexHomeForSync(codexHomeDir)` helper in
`packages/adapters/codex-local/src/server/codex-home.ts` stages only
allowlisted files into a fresh `fs.mkdtemp`-created `0700` temp dir,
then registers that dir as the `home` asset (dropping the old `exclude`
field entirely)
- All staged regular files written with mode `0600` — auth.json holds
single-use OAuth tokens; config.toml embeds MCP bearer headers; least
privilege applies throughout
- Symlinks dereferenced to resolved file bytes (`auth.json` single-use
token, each `skills/` entry land as real files)
- Missing-but-optional allowlist entries skipped silently (keyring-only
mode with no `auth.json`; absent `config.json`)
- Staged temp dir removed on teardown AND on error; staging is
fail-closed — a staging error aborts the run rather than shipping a
partial CODEX_HOME
- `execute.ts` updated to call `stageCodexHomeForSync` instead of the
old whole-dir + denylist path; the `exclude` field is removed from the
`home` asset
- Adapter-local fix: zero diff to `packages/adapter-utils` (shared
managed-runtime seam untouched); inbound auth-merge (`provision`) and
outbound copy-back (`restore`) preserved unchanged
## Verification
```sh
# Full codex-local test suite
npx vitest run packages/adapters/codex-local
# 174/174 pass
# Shared seam regression — must be zero diff to packages/adapter-utils
npx vitest run packages/adapter-utils
# 14/14 pass; zero changes to packages/adapter-utils source
# TypeScript (both packages)
npx tsc --noEmit -p packages/adapters/codex-local/tsconfig.json
npx tsc --noEmit -p packages/adapter-utils/tsconfig.json
# Both clean
```
Key new test coverage
(`packages/adapters/codex-local/src/server/codex-home.test.ts`):
- Stager copies exactly the allowlisted entries and nothing else
- Staged `auth.json` dereferenced from symlink and written mode `0600`
- Staged temp dir created with mode `0700`
- ALL staged regular files mode `0600`, including `config.toml` carrying
an MCP bearer token (regression test fails against the pre-fix `0644`
path)
- Fail-closed: I/O error during staging removes the temp dir and
re-throws
- Missing-but-optional entries (`config.json`) skipped without error
- Dangling symlink entries skipped without error
Execute-path integration test (`execute.remote.test.ts`): home asset
uses staged dir; staged config preserves provider block; skills survive;
staged temp dir removed after run teardown.
## Risks
- **Allowlist narrower than denylist:** Any CODEX_HOME file not in
`{auth.json, config.toml, config.json, instructions.md, skills/}` is now
excluded. If a codex-local configuration stores additional files in
CODEX_HOME that the sandbox requires, those runs will miss that state.
This tradeoff is intentional — only known-necessary files sync.
- **Permission change:** `config.toml` previously staged at `0644`, now
staged at `0600`. Strictly more restrictive; no known behavioral impact
(the sandbox reads as the file owner). This was the security gap that
motivated the fix.
- **Fail-closed staging:** A staging error (disk full, temp dir creation
failed) now aborts the run rather than falling back to the raw
CODEX_HOME. This is the safer behavior, but it changes the failure mode
from "run with potentially wrong state" to "run aborted cleanly."
## Model Used
Claude Opus 4.8 (claude-opus-4-8), Anthropic. Reasoning/agentic tool-use
mode (extended thinking, multi-step tool calls).
## 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
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Harold Kim <harold@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
canary/v2026.722.0-canary.0
|
||
|
|
cac3c0fa1a |
feat(connections): add runtime subjects and grants (#9982)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Connections is the subsystem that lets operators connect external apps and govern which subjects may use those credentials > - #9958 established the v3 schema foundation and #9981 adds the AppDefinition catalog layer > - The runtime still needs subject-aware authorization state, scoped key handling, and API/OpenAPI routes so connected apps can actually be granted and used safely > - This pull request adds the runtime grants/authorization behavior on top of the catalog branch, while keeping unrelated dependency and workflow sync commits out of the stack > - The benefit is a reviewable runtime layer that can land after the catalog PR, then unblock the wizard and orchestrator cutover work ## Linked Issues or Issue Description Refs #9958 and #9981. Refs #9981. No public GitHub issue exists for this branch. This is the runtime layer for the Connections v3 stack and is rebased onto `master` after #9981 landed. ## What Changed - Adds the connection user authorization state migration and schema wiring. - Adds shared runtime subject/grant types and validators. - Adds runtime grant and scoped key behavior in the tool-access service. - Adds runtime route coverage and registers the routes in OpenAPI. - Replays only the Connections runtime commits on top of the catalog branch, dropping unrelated sync/dependency history from the prior closed runtime PR. ## Verification - `pnpm run preflight:workspace-links` - `pnpm exec vitest run packages/shared/src/validators/tool-access.test.ts server/src/__tests__/tool-access-service.test.ts` ## Risks - Medium: runtime grant enforcement is security-sensitive and must fail closed for unknown key scopes. - Migration ordering depends on the schema and catalog layers already merged through #9958 and #9981. - This PR is rebased and retargeted to `master` with runtime-only commits. > 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 GPT-5 Codex coding agent with repository 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 - [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>canary/v2026.721.0-canary.14 |
||
|
|
d23fbf8ae4 |
feat(connections): add AppDefinition Wave 1 catalog (#9981)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Connections is the subsystem that defines which external apps and MCP-style integrations operators can browse, configure, and run > - The v3 schema core in #9958 added stable connection identities, auth metadata, and grant-aware contracts, but the app catalog still used the older gallery shape > - The product needs a richer, typed AppDefinition catalog so browsing and setup can render provider-specific auth and configuration requirements consistently > - This pull request moves the Wave 1 app catalog onto generated AppDefinition data and carries that shape through shared types, server lookup paths, and app connection UI > - The benefit is that follow-up runtime and wizard work can build against one catalog contract instead of local-only mock/gallery data ## Linked Issues or Issue Description Refs #9958. No public GitHub issue exists for this branch. This is the catalog layer for the Connections v3 stack after the schema-core foundation in #9958. ## What Changed - Adds generated AppDefinition data for the Wave 1 catalog and ingestion reporting. - Replaces the legacy tool app gallery exports with AppDefinition-centered shared contracts, validators, and tests. - Updates server tool-access lookup behavior to use the AppDefinition catalog. - Updates app connection UI surfaces and tests to consume AppDefinition-backed catalog data. - Documents the catalog ingestion workflow in the connector playbook. ## Verification - `pnpm run preflight:workspace-links` - `pnpm exec vitest run packages/shared/src/app-definitions.test.ts packages/shared/src/app-definitions-url.test.ts ui/src/pages/apps/AppsConnect.test.tsx server/src/__tests__/tool-access-service.test.ts` ## Risks - Medium: this changes the catalog contract used by shared, server, and UI app connection surfaces. - Catalog data quality matters because generated definitions now drive browse/setup display. - Follow-up runtime and wizard PRs must rebase on this branch or on master after this lands. > 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 GPT-5 Codex coding agent with repository 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 - [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>canary/v2026.721.0-canary.13 |
||
|
|
7e00f67138 |
feat(connections): add v3 schema core (#9958)
## Thinking Path > - Paperclip is the open source control plane people use to manage AI-agent companies and their governed access to external systems. > - Connected Apps build on the existing Apps and MCP gateway substrate so companies can configure reusable, auditable integrations. > - The current connection record does not yet have a stable public address, explicit ownership/auth method fields, or subject-specific credential grants. > - Without that schema core, later OAuth, per-user authorization, token brokering, triggers, and connector-service phases cannot enforce tenant and subject boundaries consistently. > - This pull request adds the forward-compatible Connections v3 schema core while preserving the existing connection lifecycle and directly migrating the remote MCP transport name. > - The benefit is a company-scoped, least-privilege foundation for one-click integrations without bypassing Paperclip secrets, profiles, rules, or audit controls. ## Linked Issues or Issue Description No matching public issue was found. **Problem** Paperclip's current app connections need a durable identity and authorization substrate before Connected Apps can safely support multiple setup methods, per-user credentials, provider tenants, and managed connector services. The existing schema only models a single connection-level credential set and uses legacy transport terminology. **Proposed solution** Add a stable company-scoped connection UID, explicit ownership/auth/transport fields, a subject-aware `connection_grants` table, and multi-key credential annotations. Backfill existing connections and workspace grants in a reversible migration, then update shared/server/UI contracts to the new `mcp_remote` transport name. **Related work** - Related foundation: #9534 - Roadmap: Connected Apps (one-click integrations) ## What Changed - Added company-scoped connection `uid`, `ownership`, `authKind`, and canonical transport fields across database, shared contracts, validators, services, and UI fixtures. - Added `connection_grants` with workspace/user subject rules, provider tenant metadata, credential secret refs, revocation state, company scoping, and uniqueness constraints. - Added migration `0182_connections_v3_schema_core` to backfill stable UIDs, rename `remote_http` to `mcp_remote`, infer auth kinds, create default workspace grants, and support rollback coverage. - Added multi-key credential annotations and updated gateway/access services without changing the existing lifecycle behavior. - Updated the connection glossary, connector playbook, and security threat model for the new identity, grant, and relay boundaries. - Added explicit test UIDs to direct database fixtures so the new non-null invariant is exercised across affected server suites. ## Verification - `pnpm --filter @paperclipai/shared typecheck` - `pnpm --filter @paperclipai/db typecheck` - `pnpm --filter @paperclipai/server typecheck` - `pnpm --filter @paperclipai/ui typecheck` - `pnpm exec vitest run server/src/__tests__/tool-access-service.test.ts server/src/__tests__/tool-gateway-service.test.ts server/src/__tests__/tool-gateway.test.ts server/src/__tests__/heartbeat-runtime-skills.test.ts server/src/__tests__/tool-oauth-legacy-backfill.test.ts server/src/__tests__/tool-access-policy-service.test.ts server/src/__tests__/heartbeat-runtime-mcp-servers.test.ts packages/db/src/connections-v3-schema-core-migration.test.ts packages/shared/src/validators/tool-access.test.ts --config vitest.config.ts` — 9 files, 218 tests passed. - Latest-head GitHub Actions: build, typecheck, general/serialized suites, backup/worktree restore coverage, both e2e shards, canary, policy, and security scans pass. - Greptile: 5/5 with zero unresolved threads. - `pnpm check:token-gates` remains red only on five pre-existing `#9627` color literals outside this change. ## Risks - **Migration risk:** UID backfill and default-grant creation touch every existing connection. The migration uses company-scoped uniqueness, deterministic legacy UIDs with ID suffixes, and seeded up/rollback coverage. - **Authorization risk:** Grant rows carry credential references. Constraints enforce workspace-vs-user subject shape, company/connection lookup indexes, one default grant per connection, and one user grant per connection/subject. Security review is requested specifically for this design. - **Compatibility risk:** `remote_http` is renamed directly to `mcp_remote`; all repository call sites and fixtures are updated in the same change. - **Future-phase risk:** Subject-bound token issuance, triggers, and connector-service relay verification remain fail-closed requirements documented for later phases; this PR does not expose those capabilities. > 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 coding agent. The runtime did not expose an exact underlying model ID or context-window size; capabilities used include repository inspection, code editing, shell execution, test execution, Git/GitHub CLI operations, and structured reasoning. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>canary/v2026.721.0-canary.12 |
||
|
|
98608dd71c |
fix(cli): add missing company manifest fields to import selection test fixture (#9978)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The CLI's company import/export (portability) subsystem has a typed manifest, `CompanyPortabilityCompanyManifestEntry`, that test fixtures must satisfy > - PR #2379 was authored in April and merged on 2026-07-21 without a rebase; in the interim the manifest type gained five required fields > - Its new test fixture predates those fields, so `tsc --noEmit` now fails on master (TS2739), which fails the Release workflow's `verify_canary / Typecheck` job and blocks canary publishing > - This pull request adds the five missing fields to that one fixture, matching the sibling fixtures in the same file > - The benefit is a green typecheck on master, unblocking the Release pipeline ## Linked Issues or Issue Description No open issue exists; the problem is described here (bug path): - **What happens:** `pnpm typecheck` fails on master at `cli/src/__tests__/company.test.ts:744` with `error TS2739: Type '{...}' is missing the following properties from type 'CompanyPortabilityCompanyManifestEntry': attachmentMaxBytes, feedbackDataSharingEnabled, feedbackDataSharingConsentAt, feedbackDataSharingConsentByUserId, feedbackDataSharingTermsVersion`. See the failing Release run: https://github.com/paperclipai/paperclip/actions/runs/29855224743/job/88718044096 - **Expected:** master typechecks cleanly and the Release workflow publishes the canary - **Cause:** semantic conflict — Refs #2379 (merged with pre-existing green checks from April, before the manifest type gained the five required fields) ## What Changed - Added `attachmentMaxBytes: null`, `feedbackDataSharingEnabled: false`, `feedbackDataSharingConsentAt: null`, `feedbackDataSharingConsentByUserId: null`, and `feedbackDataSharingTermsVersion: null` to the company manifest fixture in the test "includes extension file even when all entities are deselected" (`cli/src/__tests__/company.test.ts`), using the same values and field order as the two sibling fixtures in the same file ## Verification - `cd cli && pnpm typecheck` — fails on master with TS2739 at `src/__tests__/company.test.ts:744`, passes with this change - `cd cli && pnpm vitest run src/__tests__/company.test.ts` — all tests pass (runtime behavior unchanged; the fixture only gains fields the code under test does not read) - CI: the `verify / Typecheck` job on this PR exercises the same gate that is currently red on master ## Risks - Low risk — a 5-line, test-only fixture change; no runtime code touched. Worst case is a still-failing typecheck, which CI on this PR verifies before merge. ## Model Used - Claude Fable 5 (Anthropic, model ID `claude-fable-5`), via Claude Code with extended thinking and tool use (GitHub CLI/API for investigation, diff authored by the model) ## 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 - [ ] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01T5W8yjizAewpLtmsBHKPuA Co-authored-by: Claude Fable 5 <noreply@anthropic.com>canary/v2026.721.0-canary.11 |
||
|
|
f4b65c122a |
fix(cli): preserve extension file when import selections are cleared (#2379)
## Summary - always include the extension file in interactive import selection when it exists - add a regression test for the all-deselected case ## Why In the interactive company import flow, clearing every entity selection also drops `.paperclip.yaml`, which makes configuration-only imports impossible even when the extension file is present. ## Testing - pnpm test:run cli/src/__tests__/company.test.ts |
||
|
|
0d2bfee972 |
build(deps): bump radix-ui from 1.6.0 to 1.6.4 (#9895)
Bumps [radix-ui](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radix-ui) from 1.6.0 to 1.6.4. <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/radix-ui/primitives/blob/main/packages/react/radix-ui/CHANGELOG.md">radix-ui's changelog</a>.</em></p> <blockquote> <h2>1.6.4</h2> <ul> <li>Fixed a regression where importing primitives from the root <code>radix-ui</code> entry point erased every primitive's types to <code>any</code>.</li> </ul> <h2>1.6.3</h2> <h3>Dialog</h3> <ul> <li>Fixed broken ARIA references in Dialogs where title or description elements are not rendered.</li> </ul> <h3>Slider</h3> <ul> <li>Fixed a bug where <code>onValueCommit</code> was not called when a slider thumb was dragged across another thumb.</li> </ul> <h3>Toast</h3> <ul> <li>Fixed <code>Toast</code> removing non-focused toasts when pressing <code>Escape</code>.</li> </ul> <h3>Tooltip</h3> <ul> <li>Fixed a bug where <code>Tooltip.Content</code> children were mounted to the DOM twice.</li> </ul> <h3>Other updates</h3> <ul> <li>Fixed overriding inline animation style in <code>Popper.Content</code>.</li> <li>Improved tree-shaking so bundlers can drop unused components. Component parts are now marked <code>/* @__PURE__ */</code> and use named render functions instead of <code>Component.displayName = ...</code> assignments, which previously prevented dead-code elimination with some bundlers.</li> <li>Widened <code>virtualRef</code> prop type to allow <code>RefObject<Measurable | null></code> in popover components.</li> <li>Fixed dev-only checks with conditional exports to drop dev-warnings from production builds.</li> <li>Added per-primitive subpath entry points so each primitive can be imported directly, eg. <code>import { Accordion } from 'radix-ui/accordion'</code> or <code>import * as Accordion from 'radix-ui/accordion'</code>. This mirrors the namespaced exports available from the root <code>radix-ui</code> entry point.</li> <li>Fixed a bug where updating a <code>Checkbox</code>, <code>Switch</code>, or <code>RadioGroup</code> value programmatically (eg. a "select all" control) while inside a <code><form></code> would dispatch a <code>click</code> event from the hidden bubble input that propagated to ancestor <code>onClick</code> handlers.</li> </ul> <h2>1.6.2</h2> <h3>Other updates</h3> <ul> <li>Added CSS custom properties for Navigation Menu item indicators' translate values.</li> <li>Fixed a bug in Dismissable Layer causing background nested popovers to close all layers on outside click</li> <li>Fixed runtime errors for <code>Form.Message</code>, <code>Form.Control</code>, <code>Form.Label</code> and <code>Form.ValidityState</code> that are correctly rendered outside of <code>Form.Field</code> components</li> <li>Fixed a bug in form control components to ensure their values are updated when their associated form's is reset. This affects <code>RadioGroup</code>, <code>Slider</code>, <code>Select</code>, and <code>Switch</code>.</li> <li>Fixed menu items, tab triggers, toolbar links, and select items intercepting <code>Space</code>/<code>Enter</code> keys that originate from focusable descendants.</li> <li>Fixed a bug where calling an event handler without an argument would throw, preventing successive event handlers from being called. This affected all components that accept event handlers with internal implementations.</li> <li>Fixed a bug in Context Menu to ensure that the menu properly re-anchors to the latest pointer position when re-triggered in its open state.</li> <li>Fixed stale <code>onEscapeKeyDown</code>/<code>onDismiss</code> handlers on React 19.2.</li> <li>Fixed items in a Roving Focus Group not being auto-focused on mount within a Focus Scope component.</li> <li>Fixed a regression in Dismissable Layer originating from a <a href="https://redirect.github.com/react/react/pull/34831">bug in React's <code>useEffectEvent</code></a>.</li> <li>Fixed <code>--radix-scroll-area-corner-width</code> and <code>--radix-scroll-area-corner-height</code> not resetting to <code>0</code> when a corner is removed. Previously these values would stick around and leave a permanent gap on the remaining scrollbar.</li> <li>Fixed a bug in Slider where stepping with the keyboard would skip a valid value when the current value is off the step grid. Stepping now snaps to the next step-aligned value in the direction of travel, matching native <code><input type="range"></code> behavior.</li> </ul> <h2>1.6.1</h2> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li>See full diff in <a href="https://github.com/radix-ui/primitives/commits/HEAD/packages/react/radix-ui">compare view</a></li> </ul> </details> <details> <summary>Attestation changes</summary> <p>This version has no provenance attestation, while the previous version (1.6.0) was attested. Review the <a href="https://www.npmjs.com/package/radix-ui?activeTab=versions">package versions</a> before updating.</p> </details> <br /> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
6439572ecd |
fix(ui): sync cron validity + settle branch test for radix-ui 1.6.4 (#9966)
## Thinking Path > - Paperclip is the open-source app people use to manage AI agents for work > - The `ui` package contains React components that drive the agent scheduling and workspace configuration UX > - Dependabot PR #9895 bumped `@radix-ui/react-*` from 1.6.0 → 1.6.4, which changed the internal effect-scheduling order inside Dialog and Select primitives > - Two `workspaces-a` CI tests started failing: `RoutineRunVariablesDialog` and `editable-sections / TriggersSection` > - Root cause for both: radix 1.6.4's changed scheduling pushes a cascaded state update one render-tick later, and each test asserted before that tick landed > - This PR fixes the two affected surfaces at the source (one production fix, one test fix) so the radix bump can land cleanly > - The benefit is unblocking PR #9895 without compromising test fidelity or production correctness ## Linked Issues or Issue Description Refs #9895 — this PR fixes the two `workspaces-a` test failures that blocked the radix-ui 1.6.0 → 1.6.4 Dependabot bump. **Root cause:** radix-ui 1.6.4 changed the internal effect-scheduling order inside its Dialog and Select primitives, pushing certain cascaded state updates one render-tick later than before. Two tests each asserted against the intermediate state, before the deferred tick landed. **Affected tests (both now pass at radix 1.6.0 AND 1.6.4):** 1. `editable-sections / TriggersSection` — `ScheduleEditor` disabled-button assertion 2. `RoutineRunVariablesDialog` — workspace branch propagation assertion Production behavior is unchanged and correct in both cases (verified via tracing). ## What Changed - **`ui/src/components/ScheduleEditor.tsx`** — `onValidityChange` is now called synchronously inside the custom-cron `onChange` handler, not only via the passive `useEffect` below. Previously there was a one-render window where an invalid draft still read as valid to the parent (button enabled); this closes that window. The effect call is preserved as a safety net for other entry paths; only the `onChange` path is new. - **`ui/src/components/RoutineRunVariablesDialog.test.tsx`** — the post-mount settle loop now waits for the branch value to actually appear in an `<input>` (`value === "pap-1634-routine-branch"`) rather than exiting as soon as the workspace card mounts. The card reports its branch name through an effect callback that triggers a follow-up render; the old loop exited one tick too early. Iteration cap raised from 10 → 20 to give the extra tick room. This PR intentionally does **not** bump radix-ui — that stays in #9895. ## Verification ```sh # TypeScript — clean at radix 1.6.0 (master): tsc -p ui/tsconfig.json # Targeted vitest: npx vitest run ui/src/components/RoutineRunVariablesDialog.test.tsx npx vitest run ui/src/components/editable-sections npx vitest run ui/src/components/ScheduleEditor # Full ui suite — green with radix 1.6.4 installed locally (371 files / 3035 tests): npx vitest run --project ui # check-forbidden-tokens — clean ``` All of the above pass at **both** radix 1.6.0 (current master) and 1.6.4. ## Risks Low risk. The production change (`ScheduleEditor.tsx`) adds a synchronous call to an already-injected `onValidityChange` prop — same value, earlier in the same event cycle. No new state, no new effects, no API changes. The test change tightens an assertion (waits longer, checks a more specific condition) rather than relaxing one. ## Model Used Claude Sonnet (Anthropic) — Paperclip agent workflow; model family claude-sonnet-4-x with tool use and extended reasoning 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: Harold Kim <harold@paperclip.ing> Co-authored-by: Paperclip <noreply@paperclip.ing> |
||
|
|
b565603a86 |
fix(server): accept Office issue attachments (#8562)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents and board users can attach files to issues so context and deliverables stay with the task > - Some clients upload Microsoft Office files with generic binary MIME types such as `application/octet-stream` > - Current `master` now accepts arbitrary issue attachment MIME types, so the upload should keep working for unknown binary files > - Office files still benefit from being stored with a specific Office MIME type when the filename makes that inference safe > - Shared attachment allow-list defaults should also include common Office MIME types for routes that still use that allow-list > - This pull request keeps the current arbitrary-MIME issue upload behavior and only narrows generic binary uploads to Office MIME types for known Office filename extensions ## Linked Issues or Issue Description Fixes #8243 Duplicate search performed before implementation: - No matching open or closed PR found for `8243`, `Office document`, `attachment MIME`, or `openxmlformats`. ## What Changed - Added common Office MIME types to the default shared attachment allow-list. - Added upload content-type normalization that maps generic binary uploads to a specific Office MIME type only for known Office filename extensions. - Added an optional helper-level allow-list gate so callers that still validate against an effective allow-list can keep generic binary uploads generic when the inferred Office MIME type is not allowed. - Reused the shared generic attachment content-type list for response handling. - Preserved current `master` behavior for issue uploads that use unknown or arbitrary MIME types. - Added regression coverage for default Office allow-list matching, filename inference, optional allow-list fallback, official Office MIME uploads, inferred generic Office uploads, and preservation of unknown generic binary uploads. ## Verification - `env CI=true corepack pnpm install --frozen-lockfile --force` - `env CI=true corepack pnpm --filter @paperclipai/server exec vitest run src/__tests__/attachment-types.test.ts src/__tests__/issue-attachment-routes.test.ts` - `env CI=true corepack pnpm --filter @paperclipai/plugin-sdk ensure-build-deps` - `env CI=true corepack pnpm --filter @paperclipai/server exec tsc --noEmit` - `git diff --check origin/master...HEAD` GitHub CI, security checks, and Greptile pass on rebased head `acc364cfbe3440a59db6570bb907818046649eb4`. ## Risks Low risk. The issue attachment route continues to accept arbitrary MIME types as current `master` does; this change only stores a more specific Office MIME type for generic binary uploads when the filename has a known Office extension. Unknown generic binary uploads remain generic. For callers that use an allow-list before storing uploads, `normalizeUploadAttachmentContentType` supports an optional gate so inference can be limited to MIME types that are already allowed. No docs change included because this is a default upload compatibility fix covered by server tests. > 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`. This is a narrow bug fix, not roadmap-level core feature work. `ROADMAP.md` was checked. ## Model Used OpenAI Codex using GPT-5, tool-enabled coding agent. Context window details are not exposed in this environment. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Sami Rusani <sr@samirusani>canary/v2026.721.0-canary.10 |
||
|
|
3e1dc90bf2 |
fix(execution-policy): final-stage approval terminates the policy instead of rewinding to stage 1 (#7893) (#7936)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Issues can carry an embedded multi-stage `executionPolicy` (e.g. QA → CodeReviewer → CodePusher) driven by `applyIssueExecutionStageTransition` in `server/src/services/issue-execution-policy.ts` > - On approval, the next stage was picked with `nextPendingStage()`, which scans the **whole** stage list from index 0 for the first id not in `completedStageIds` > - Stage ids are regenerated whenever the embedded policy is re-sent or edited mid-flow (a supported operation — the existing "reassigns the active stage when the current participant is removed" test depends on it), so earlier `completedStageIds` can stop matching the current policy; a final-stage approve then "finds" stage 1 pending again and rebuilds a first-stage review (#7893) — an endless re-review loop that can recycle indefinitely against a moving main tip > - This pull request makes approvals advance with a forward-only scan (only stages *after* the one being approved), so approving the last stage always terminates the policy, and adds a guard so an already-completed execution state is terminal for `status=done` > - The benefit is final-stage approvals close the issue as the policy intends, with no behavior change for non-final advancement or reject/changes_requested verdicts ## Linked Issues or Issue Description Fixes #7893 ## What Changed - `server/src/services/issue-execution-policy.ts`: - New `nextPendingStageAfter(policy, completedStage, state)` helper — forward-only scan from the approved stage's index; the approval path uses it instead of `nextPendingStage()`. Approving the final stage therefore always yields `nextStage === null` → completed state → the caller's `done` flows through. - New guard: `requestedStatus === "done"` with an already-`completed` execution state returns without restarting the chain at stage 1 (closes the same loop when a stale completed state lingers). - Reject/`changes_requested` verdicts and intact-state forward advancement are untouched. - `server/src/__tests__/issue-execution-policy.test.ts`: 4 regression tests, including one that reproduces the exact rewind (regenerated stage ids + final-stage approve → previously reassigned QA at `currentStageIndex 0`; now terminal completed) and an explicit final-stage rejection test pinning the unchanged path. ## Verification - `npx vitest run server/src/__tests__/issue-execution-policy.test.ts` → 54 passed (50 pre-existing + 4 new). - `pnpm --filter @paperclipai/server typecheck` → clean. - The rewind was confirmed empirically against unmodified code first (a test asserting the buggy output passed pre-fix and flips post-fix), plus brute-forced realistic operation sequences (checkout dances, status round-trips, interim comments per the agent flow documented around #4889) to verify intact-state flows are unaffected. - Related suites (`issue-execution-policy-routes`, `issue-comment-reopen-routes`, `issues-service`, `issue-thread-interaction-routes`, `issue-agent-mutation-ownership-routes`) also pass locally. ## Risks - Behavior deliberately preserved: non-final approvals (forward scan is identical when state is intact), rejections at any stage, reopen-from-done (state cleared on reopen, fresh chain still starts at stage 1), and explicit `in_review` restarts. - The policy schema has no terminal-state field, so per the issue's Ask the policy simply terminates and the requested `done` status flows through. ## Model Used - Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code, agentic mode with tool use (subagent implementation + independent adversarial review subagent), extended thinking 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 (none found for #7893) - [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 run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots (N/A — server-only change) - [x] I have updated relevant documentation to reflect my changes (N/A — internal stage-advance semantics) - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green (will confirm once CI runs on this PR) - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups (pending first review) - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Claude Fable 5 <noreply@anthropic.com>canary/v2026.721.0-canary.9 |
||
|
|
31c59f8822 |
build(deps-dev): bump @playwright/test from 1.58.2 to 1.61.1 (#9887)
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.58.2 to 1.61.1. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/microsoft/playwright/releases">@playwright/test's releases</a>.</em></p> <blockquote> <h2>v1.61.1</h2> <h3>Bug Fixes</h3> <ul> <li><a href="https://redirect.github.com/microsoft/playwright/issues/41365">#41365</a> [Bug]: Expect.Extend matcher with same name as default matcher in same expect instance overrides default matchers implementation to custom matcher</li> <li><a href="https://redirect.github.com/microsoft/playwright/issues/41351">#41351</a> [Bug]: Playwright UI mode: apiRequestContext._wrapApiCall reports unexpected number of bytes (same test passes in headed mode)</li> <li><a href="https://redirect.github.com/microsoft/playwright/issues/41360">#41360</a> [Bug]: Trace viewer: message times in websockets are downscaled by 1000</li> <li><a href="https://redirect.github.com/microsoft/playwright/issues/41311">#41311</a> [Bug]: [Regression]: Sync loader throws "context.conditions?.includes is not a function" on Node 22.15</li> <li><a href="https://redirect.github.com/microsoft/playwright/issues/41371">#41371</a> [Regression]: Sync ESM loader (registerHooks) fails to resolve extensionless .ts subpath imports across pnpm workspace symlinks</li> </ul> <h2>v1.61.0</h2> <h2>🔑 WebAuthn passkeys</h2> <p>New <a href="https://playwright.dev/docs/api/class-credentials">Credentials</a> virtual authenticator, available via <a href="https://playwright.dev/docs/api/class-browsercontext#browser-context-credentials">browserContext.credentials</a>, lets tests register passkeys and answer <code>navigator.credentials.create()</code> / <code>navigator.credentials.get()</code> ceremonies in the page — no real hardware key required, works in all browsers:</p> <pre lang="js"><code>const context = await browser.newContext(); <p>// Seed a passkey your backend provisioned for a test user. await context.credentials.create('example.com', { id: credentialId, userHandle, privateKey, publicKey, }); await context.credentials.install();</p> <p>const page = await context.newPage(); await page.goto('<a href="https://example.com/login">https://example.com/login</a>'); // The page's navigator.credentials.get() is answered with the seeded passkey. </code></pre></p> <p>You can also let the app register a passkey once in a setup test, read it back with <a href="https://playwright.dev/docs/api/class-credentials#credentials-get">credentials.get()</a>, and seed it into later tests — see <a href="https://playwright.dev/docs/api/class-credentials">Credentials</a> for details.</p> <h2>🗃️ Web Storage</h2> <p>New <a href="https://playwright.dev/docs/api/class-webstorage">WebStorage</a> API, available via <a href="https://playwright.dev/docs/api/class-page#page-local-storage">page.localStorage</a> and <a href="https://playwright.dev/docs/api/class-page#page-session-storage">page.sessionStorage</a>, reads and writes the page's storage for the current origin:</p> <pre lang="js"><code>await page.localStorage.setItem('token', 'abc'); const token = await page.localStorage.getItem('token'); const items = await page.sessionStorage.items(); </code></pre> <h2>New APIs</h2> <h3>Network</h3> <ul> <li><a href="https://playwright.dev/docs/api/class-apiresponse#api-response-security-details">apiResponse.securityDetails()</a> and <a href="https://playwright.dev/docs/api/class-apiresponse#api-response-server-addr">apiResponse.serverAddr()</a> mirror the browser-side <a href="https://playwright.dev/docs/api/class-response#response-security-details">response.securityDetails()</a> and <a href="https://playwright.dev/docs/api/class-response#response-server-addr">response.serverAddr()</a>.</li> </ul> <h3>Browser and Screencast</h3> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/microsoft/playwright/commit/39e3553a4f283a41134d75d7e404484bd9e6865a"><code>39e3553</code></a> cherry-pick(<a href="https://redirect.github.com/microsoft/playwright/issues/41399">#41399</a>): fix(test): load require-reached files as commonjs in syn...</li> <li><a href="https://github.com/microsoft/playwright/commit/4328122a0fa91df1be287f12d26f272f598ccca7"><code>4328122</code></a> chore: mark v1.61.1 (<a href="https://redirect.github.com/microsoft/playwright/issues/41404">#41404</a>)</li> <li><a href="https://github.com/microsoft/playwright/commit/2c29a94ed59a2dbb2cb2553ee7d1ba429f027826"><code>2c29a94</code></a> fix(tracing): stop recording websocket frames outside of chunks (<a href="https://redirect.github.com/microsoft/playwright/issues/41398">#41398</a>)</li> <li><a href="https://github.com/microsoft/playwright/commit/4324b1904199c58ae56d864390f5210df18e33f6"><code>4324b19</code></a> cherry-pick(<a href="https://redirect.github.com/microsoft/playwright/issues/41367">#41367</a>): fix(test): keep builtin expect matchers on base extend</li> <li><a href="https://github.com/microsoft/playwright/commit/041e7e30002e7c384e1918c29720b34c435145f4"><code>041e7e3</code></a> cherry-pick(<a href="https://redirect.github.com/microsoft/playwright/issues/41364">#41364</a>): fix(har): <code>WebSocket</code> message timestamps should be in mi...</li> <li><a href="https://github.com/microsoft/playwright/commit/b8a0fc33932399fc5cfcd211165cf16f8ca01d71"><code>b8a0fc3</code></a> cherry-pick(<a href="https://redirect.github.com/microsoft/playwright/issues/41309">#41309</a>, <a href="https://redirect.github.com/microsoft/playwright/issues/43149">#43149</a>): Revert "fix(firefox): treat `navigationCommitted...</li> <li><a href="https://github.com/microsoft/playwright/commit/b5a31759e6611397bf3afaaa6049a420a5f082bd"><code>b5a3175</code></a> cherry-pick(<a href="https://redirect.github.com/microsoft/playwright/issues/41319">#41319</a>): fix(loader): support other node versions</li> <li><a href="https://github.com/microsoft/playwright/commit/d4724a91b280ae1ee9a87c426e9d6a953c59756e"><code>d4724a9</code></a> cherry-pick(<a href="https://redirect.github.com/microsoft/playwright/issues/41290">#41290</a>): feat(docker): add Ubuntu 26.04 (Resolute Raccoon) image</li> <li><a href="https://github.com/microsoft/playwright/commit/1cc5a90cfa3eaa430b1a991963100f95126caa47"><code>1cc5a90</code></a> cherry-pick(<a href="https://redirect.github.com/microsoft/playwright/issues/41295">#41295</a>): chore: PLAYWRIGHT_TRACING_NO_WEBSOCKET_FRAMES and PLAYWR...</li> <li><a href="https://github.com/microsoft/playwright/commit/a6772bdede34028cbbd417a3b3d778801899e870"><code>a6772bd</code></a> cherry-pick(<a href="https://redirect.github.com/microsoft/playwright/issues/41280">#41280</a>): Revert "fix(trace-viewer): add keyboard navigation to `N...</li> <li>Additional commits viewable in <a href="https://github.com/microsoft/playwright/compare/v1.58.2...v1.61.1">compare view</a></li> </ul> </details> <br /> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
c1f6698ec5 |
fix(ui/invite): make pending-approval admin guidance non-clickable (#6786)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies, and
humans onboard into companies via invite links.
> - Some invite types (`company_join` with
`requires_company_admin_approval`) require an admin to approve the join
request after the invitee submits it.
> - While the requester waits, the invitee sees the
`AwaitingJoinApprovalPanel` in `InviteLanding`, which describes where
the admin needs to go to approve the request.
> - That panel rendered the destination — "Company Settings → Access" —
as two clickable `<a href="/company/settings/access">` links, even
though the surrounding copy is plainly addressed to the admin ("Ask
**them** to visit ..."), not the requester.
> - First-time invitees naturally click the only underlined link on the
screen, are sent to `/company/settings/access`, hit the "No company
access" panel (they have no membership yet), and conclude the invite
flow is broken.
> - This PR removes the navigation by rendering both "Company Settings →
Access" references as plain styled text (`<p>` / `<span>`), so the
guidance stays visible but cannot be followed by the requester.
> - The benefit is that the post-submit invite experience matches the
copy's intent — guidance for the admin, not navigation for the
requester.
Fixes #6784.
## What Changed
- `ui/src/pages/InviteLanding.tsx` — In `AwaitingJoinApprovalPanel`,
replace the two `<a href={approvalUrl}>Company Settings → Access</a>`
elements with `<p>` and `<span>` containing the same text. Remove the
now-unused `approvalUrl` constant.
- `ui/src/pages/InviteLanding.test.tsx` — Update the existing "pending
approval page" test: it previously asserted two anchor tags pointing at
`/company/settings/access`; it now asserts **zero** anchors while the
text "Company Settings → Access" still appears twice (in the "Approval
page" box and inline in the "Ask them to visit ..." sentence). Renamed
the test description from "...linked access instructions" to
"...non-clickable access instructions" to reflect the contract.
## Verification
```
pnpm vitest run ui/src/pages/InviteLanding.test.tsx
```
Result: 8 / 8 pass, including the updated "shows the pending approval
page with the company icon and non-clickable access instructions" case.
Manual reproduction (master @ `242a2c2f`,
`deploymentMode=authenticated`, `bind=lan`, embedded Postgres):
1. As instance admin, generate a `company_join` invite that requires
admin approval.
2. In a fresh browser profile, open the invite link.
3. Fill **Create your account** and submit.
4. The "Request to join \<company\>" panel appears.
5. Hover the "Company Settings → Access" mentions — no underline, no
link cursor; clicking does nothing. The text is still readable and the
surrounding copy ("Ask them to visit ...") still conveys the instruction
to the requester.
Before / after screenshots: see issue #6784 — the "before" state lands
users on `/company/settings/access` which renders "No company access".
After this PR the guidance is informational only.
## Risks
Low. UI-only change confined to one function in `InviteLanding.tsx` plus
its matching test. No API contracts, routes, or data shapes are
modified. The removed `approvalUrl` constant was only referenced by the
two anchor elements.
## Model Used
- Anthropic Claude Opus 4.7 (`claude-opus-4-7`, 1M context, extended
thinking enabled).
- Tools: file editing, Bash, Playwright reproduction against a
self-hosted Paperclip instance running master @ `242a2c2f`, and the
Paperclip monorepo's own Vitest suite for verifying the test update.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots (will attach in PR thread)
- [x] I have updated relevant documentation to reflect my changes (no
docs files needed updating)
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
|
||
|
|
68ba7ccae6 |
Fail loudly on invalid config files (#9041)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The server config loader reads `.paperclip/config.json` and feeds it into the shared Paperclip config schema. > - When a config file exists but cannot be parsed or fails schema validation, Paperclip should not silently ignore it. > - The current `readConfigFile()` catch block treats invalid files the same as missing files, so startup falls back to defaults while the banner can still point at the ignored config path. > - This pull request keeps the missing-file fallback, but makes present invalid config files fail with a path-specific error. > - The benefit is safer startup behavior and a clear diagnostic that points at the invalid config field. ## Linked Issues or Issue Description Fixes #8908 ## What Changed - Changed `readConfigFile()` to return `null` only when the config file is absent. - Added explicit errors for unreadable/invalid JSON config files. - Added explicit Zod validation errors that include the config path and invalid field path without printing config contents. - Added server tests for missing config, invalid JSON, schema validation failure, and valid config parsing. ## Verification - `pnpm exec vitest run server/src/__tests__/config-file.test.ts` - `pnpm --filter @paperclipai/server typecheck` ## Risks Low risk for valid configs and missing configs. This intentionally changes behavior for present invalid config files from silent fallback to startup failure, which is the issue being fixed. > 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, with repository file inspection, GitHub CLI, and local command 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 - [ ] 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 |
||
|
|
a3f583b5a9 |
build(deps-dev): bump @tailwindcss/vite from 4.3.0 to 4.3.3 (#9892)
Bumps [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) from 4.3.0 to 4.3.3. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/tailwindlabs/tailwindcss/releases">@tailwindcss/vite's releases</a>.</em></p> <blockquote> <h2>v4.3.3</h2> <h3>Fixed</h3> <ul> <li>Support <code>--watch --poll[=ms]</code> in <code>@tailwindcss/cli</code> when filesystem events are unreliable or unavailable (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20297">#20297</a>)</li> <li>Canonicalization: match arbitrary hex colors against theme colors case-insensitively (e.g. <code>bg-[#fff]</code> and <code>bg-[#FFF]</code> → <code>bg-white</code>) (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20298">#20298</a>)</li> <li>Prevent Preflight from overriding Firefox's native <code>iframe:focus-visible</code> outline styles (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20292">#20292</a>)</li> <li>Ensure <code>theme('colors.foo')</code> in JS plugins resolves correctly when both <code>--color-foo</code> and <code>--color-foo-bar</code> exist (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20299">#20299</a>)</li> <li>Ensure fractional opacity modifiers work with named shadow sizes like <code>shadow-sm/12.5</code>, <code>text-shadow-sm/12.5</code>, <code>drop-shadow-sm/12.5</code>, and <code>inset-shadow-sm/12.5</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20302">#20302</a>)</li> <li>Parse selectors like <code>[data-foo]div</code> as two selectors instead of one (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20303">#20303</a>)</li> <li>Ensure <code>@tailwindcss/postcss</code> rebuilds when a preprocessor like Sass changes the input CSS without changing the input file on disk (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20310">#20310</a>)</li> <li>Ensure CSS nesting is handled even when Lightning CSS isn't run, such as in <code>@tailwindcss/browser</code> and Tailwind Play (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20124">#20124</a>)</li> <li>Prevent achromatic theme colors from shifting hue when mixed in polar color spaces like <code>oklch</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20314">#20314</a>)</li> <li>Ensure <code>--spacing(0)</code> is optimized to <code>0px</code> instead of <code>0</code> so it remains a <code><length></code> when used in <code>calc(…)</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20319">#20319</a>)</li> <li>Load <code>@parcel/watcher</code> only when needed in <code>@tailwindcss/cli --watch</code> mode, so one-off builds and <code>--watch --poll</code> work when <code>@parcel/watcher</code> can't be loaded (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20325">#20325</a>)</li> <li>Use explicit platform fonts instead of <code>system-ui</code> and <code>ui-sans-serif</code> so CJK text respects the page's <code>lang</code> attribute on Windows (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20318">#20318</a>)</li> <li>Prevent <code>@tailwindcss/upgrade</code> from rewriting ignored files when run from a subdirectory (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20329">#20329</a>)</li> <li>Ensure earlier <code>@source</code> rules pointing to nested files are scanned when later <code>@source</code> rules point to files in parent folders (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20335">#20335</a>)</li> <li>Prevent <code>@tailwindcss/vite</code> from triggering full page reloads when scanned files are processed by Vite but haven't been loaded as modules yet (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20336">#20336</a>)</li> </ul> <h2>v4.3.2</h2> <h3>Fixed</h3> <ul> <li>Support bare spacing values for <code>auto-rows-*</code> and <code>auto-cols-*</code> utilities (e.g. <code>auto-rows-12</code> and <code>auto-cols-16</code>) (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20229">#20229</a>)</li> <li>Prevent <code>@tailwindcss/cli</code> in <code>--watch</code> mode from crashing on Windows when <code>@source</code> points to a directory that doesn't exist (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20242">#20242</a>)</li> <li>Prevent <code>@tailwindcss/vite</code> from crashing in Deno v2.8.x when <code>context.parentURL</code> is not a valid URL (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20245">#20245</a>)</li> <li>Ensure <code>@tailwindcss/cli</code> in <code>--watch</code> mode rebuilds when the input CSS file changes in an ignored directory (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20246">#20246</a>)</li> <li>Allow <code>@variant</code> rules used in <code>addBase(…)</code> to use custom variants defined later (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20247">#20247</a>)</li> <li>Prevent <code>@tailwindcss/vite</code> from crashing during HMR when scanned files or directories are deleted (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20259">#20259</a>)</li> <li>Generate <code>font-size</code> instead of <code>color</code> declarations for <code>text-[--spacing(…)]</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20260">#20260</a>)</li> <li>Prevent <code>@source</code> patterns from scanning unrelated sibling files and folders (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20263">#20263</a>)</li> <li>Extract class candidates adjacent to Template Toolkit delimiters like <code>%]…[%</code> in <code>.tt</code>, <code>.tt2</code>, and <code>.tx</code> files (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20269">#20269</a>)</li> <li>Extract class candidates from conditional Maud syntax like <code>p.text-black[condition]</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20269">#20269</a>)</li> <li>Prevent <code>@position-try</code> rules from triggering unknown at-rule warnings when optimizing CSS (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20277">#20277</a>)</li> <li>Support class suggestions for named opacity modifiers from <code>--opacity</code> theme values (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20287">#20287</a>)</li> <li>Prevent type errors in <code>@tailwindcss/postcss</code> when used with newer PostCSS patch releases (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20289">#20289</a>)</li> </ul> <h2>v4.3.1</h2> <h3>Added</h3> <ul> <li>Add <code>--silent</code> option to suppress output in <code>@tailwindcss/cli</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20100">#20100</a>)</li> </ul> <h3>Fixed</h3> <ul> <li>Remove deprecation warnings by using <code>Module#registerHooks</code> instead of <code>Module#register</code> on Node 26+ (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20028">#20028</a>)</li> <li>Canonicalization: don't crash when plugin utilities throw for unsupported values (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20052">#20052</a>)</li> <li>Allow <code>@apply</code> to be used with CSS mixins (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19427">#19427</a>)</li> <li>Ensure <code>not-*</code> correctly negates <code>@container</code> queries, including <code>style(…)</code> queries (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20059">#20059</a>)</li> <li>Ensure <code>drop-shadow-*</code> color utilities work with custom shadow values containing <code>calc(…)</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20080">#20080</a>)</li> <li>Fix 'Sourcemap is likely to be incorrect' warnings when using <code>@tailwindcss/vite</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20103">#20103</a>)</li> <li>Ensure <code>@tailwindcss/webpack</code> can be installed in Rspack projects without requiring <code>webpack</code> as a peer dependency (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20027">#20027</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md">@tailwindcss/vite's changelog</a>.</em></p> <blockquote> <h2>[4.3.3] - 2026-07-16</h2> <h3>Fixed</h3> <ul> <li>Support <code>--watch --poll[=ms]</code> in <code>@tailwindcss/cli</code> when filesystem events are unreliable or unavailable (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20297">#20297</a>)</li> <li>Canonicalization: match arbitrary hex colors against theme colors case-insensitively (e.g. <code>bg-[#fff]</code> and <code>bg-[#FFF]</code> → <code>bg-white</code>) (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20298">#20298</a>)</li> <li>Prevent Preflight from overriding Firefox's native <code>iframe:focus-visible</code> outline styles (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20292">#20292</a>)</li> <li>Ensure <code>theme('colors.foo')</code> in JS plugins resolves correctly when both <code>--color-foo</code> and <code>--color-foo-bar</code> exist (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20299">#20299</a>)</li> <li>Ensure fractional opacity modifiers work with named shadow sizes like <code>shadow-sm/12.5</code>, <code>text-shadow-sm/12.5</code>, <code>drop-shadow-sm/12.5</code>, and <code>inset-shadow-sm/12.5</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20302">#20302</a>)</li> <li>Parse selectors like <code>[data-foo]div</code> as two selectors instead of one (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20303">#20303</a>)</li> <li>Ensure <code>@tailwindcss/postcss</code> rebuilds when a preprocessor like Sass changes the input CSS without changing the input file on disk (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20310">#20310</a>)</li> <li>Ensure CSS nesting is handled even when Lightning CSS isn't run, such as in <code>@tailwindcss/browser</code> and Tailwind Play (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20124">#20124</a>)</li> <li>Prevent achromatic theme colors from shifting hue when mixed in polar color spaces like <code>oklch</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20314">#20314</a>)</li> <li>Ensure <code>--spacing(0)</code> is optimized to <code>0px</code> instead of <code>0</code> so it remains a <code><length></code> when used in <code>calc(…)</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20319">#20319</a>)</li> <li>Load <code>@parcel/watcher</code> only when needed in <code>@tailwindcss/cli --watch</code> mode, so one-off builds and <code>--watch --poll</code> work when <code>@parcel/watcher</code> can't be loaded (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20325">#20325</a>)</li> <li>Use explicit platform fonts instead of <code>system-ui</code> and <code>ui-sans-serif</code> so CJK text respects the page's <code>lang</code> attribute on Windows (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20318">#20318</a>)</li> <li>Prevent <code>@tailwindcss/upgrade</code> from rewriting ignored files when run from a subdirectory (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20329">#20329</a>)</li> <li>Ensure earlier <code>@source</code> rules pointing to nested files are scanned when later <code>@source</code> rules point to files in parent folders (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20335">#20335</a>)</li> <li>Prevent <code>@tailwindcss/vite</code> from triggering full page reloads when scanned files are processed by Vite but haven't been loaded as modules yet (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20336">#20336</a>)</li> </ul> <h2>[4.3.2] - 2026-06-26</h2> <h3>Fixed</h3> <ul> <li>Support bare spacing values for <code>auto-rows-*</code> and <code>auto-cols-*</code> utilities (e.g. <code>auto-rows-12</code> and <code>auto-cols-16</code>) (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20229">#20229</a>)</li> <li>Prevent <code>@tailwindcss/cli</code> in <code>--watch</code> mode from crashing on Windows when <code>@source</code> points to a directory that doesn't exist (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20242">#20242</a>)</li> <li>Prevent <code>@tailwindcss/vite</code> from crashing in Deno v2.8.x when <code>context.parentURL</code> is not a valid URL (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20245">#20245</a>)</li> <li>Ensure <code>@tailwindcss/cli</code> in <code>--watch</code> mode rebuilds when the input CSS file changes in an ignored directory (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20246">#20246</a>)</li> <li>Allow <code>@variant</code> rules used in <code>addBase(…)</code> to use custom variants defined later (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20247">#20247</a>)</li> <li>Prevent <code>@tailwindcss/vite</code> from crashing during HMR when scanned files or directories are deleted (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20259">#20259</a>)</li> <li>Generate <code>font-size</code> instead of <code>color</code> declarations for <code>text-[--spacing(…)]</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20260">#20260</a>)</li> <li>Prevent <code>@source</code> patterns from scanning unrelated sibling files and folders (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20263">#20263</a>)</li> <li>Extract class candidates adjacent to Template Toolkit delimiters like <code>%]…[%</code> in <code>.tt</code>, <code>.tt2</code>, and <code>.tx</code> files (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20269">#20269</a>)</li> <li>Extract class candidates from conditional Maud syntax like <code>p.text-black[condition]</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20269">#20269</a>)</li> <li>Prevent <code>@position-try</code> rules from triggering unknown at-rule warnings when optimizing CSS (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20277">#20277</a>)</li> <li>Support class suggestions for named opacity modifiers from <code>--opacity</code> theme values (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20287">#20287</a>)</li> <li>Prevent type errors in <code>@tailwindcss/postcss</code> when used with newer PostCSS patch releases (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20289">#20289</a>)</li> </ul> <h2>[4.3.1] - 2026-06-12</h2> <h3>Added</h3> <ul> <li>Add <code>--silent</code> option to suppress output in <code>@tailwindcss/cli</code> (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20100">#20100</a>)</li> </ul> <h3>Fixed</h3> <ul> <li>Remove deprecation warnings by using <code>Module#registerHooks</code> instead of <code>Module#register</code> on Node 26+ (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20028">#20028</a>)</li> <li>Canonicalization: don't crash when plugin utilities throw for unsupported values (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20052">#20052</a>)</li> <li>Allow <code>@apply</code> to be used with CSS mixins (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/19427">#19427</a>)</li> <li>Ensure <code>not-*</code> correctly negates <code>@container</code> queries, including <code>style(…)</code> queries (<a href="https://redirect.github.com/tailwindlabs/tailwindcss/pull/20059">#20059</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/tailwindlabs/tailwindcss/commit/c2b24dd15fed1c59dd521bd86082f520c9f5ad0d"><code>c2b24dd</code></a> 4.3.3 (<a href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite/issues/20334">#20334</a>)</li> <li><a href="https://github.com/tailwindlabs/tailwindcss/commit/bdcd7087b332d263353d46ed366b7b08040ded7a"><code>bdcd708</code></a> Don't trigger a full page reload for scanned files that Vite processes as mod...</li> <li><a href="https://github.com/tailwindlabs/tailwindcss/commit/056a1550721d4bf79ff732d5ab9414fa83f7064f"><code>056a155</code></a> 4.3.2 (<a href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite/issues/20281">#20281</a>)</li> <li><a href="https://github.com/tailwindlabs/tailwindcss/commit/bb6a10937c7e1434db39919fbb4df4a8982dba7f"><code>bb6a109</code></a> use <code>.ts</code> instead of <code>.css</code></li> <li><a href="https://github.com/tailwindlabs/tailwindcss/commit/8a14a710102cae195f6811e8578bef9477bc6be9"><code>8a14a71</code></a> 4.3.1 (<a href="https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite/issues/20226">#20226</a>)</li> <li><a href="https://github.com/tailwindlabs/tailwindcss/commit/73983e1cf5bc0ae721f4568cc24a5b5067b6b90b"><code>73983e1</code></a> Fix 'Sourcemap is likely to be incorrect' warnings when using `@tailwindcss/v...</li> <li>See full diff in <a href="https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-vite">compare view</a></li> </ul> </details> <br /> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>canary/v2026.721.0-canary.8 |
||
|
|
1ba79d82a5 |
fix(server): preserve terminal status on issue release (#7524)
Fixes #4206 ## Thinking Path > - Paperclip orchestrates AI agents on issues with checkout/release semantics for execution locks > - `POST /api/issues/:id/release` clears checkout and execution locks when a heartbeat ends without finishing the issue > - `issues.release()` unconditionally set `status: "todo"`, undoing terminal and waiting states (`done`, `cancelled`, `in_review`, `blocked`) set during the session > - Agents reported status drift after release (e.g. `in_review` → `todo`, `done` → `todo`), forcing manual PATCH recovery and risking silent stalls > - This pull request gates the `todo` re-queue to `in_progress` issues only and preserves all other statuses on release > - The benefit is lock cleanup without destroying workflow state agents already recorded ## Linked Issues or Issue Description - Fixes #4206 — `issues.release()` must not downgrade terminal/waiting statuses - Related internal incident: AIT-114 status drift on terminal issue release (AI Trading Council) ## What Changed - `server/src/services/issues.ts` — `releaseStatus` is `todo` only when `existing.status === "in_progress"`; otherwise preserves `existing.status` - `server/src/__tests__/issue-stale-execution-lock-routes.test.ts` — regression tests: release preserves done, cancelled, in_review, blocked keeps `done` and clears lock fields - `server/package.json` — patch bump `0.3.1` → `0.3.2` - `server/CHANGELOG.md` — documents the fix ## Verification ```sh pnpm --filter @paperclipai/server test issue-stale-execution-lock-routes ``` - 7/7 tests pass (parametrized done, cancelled, in_review, blocked) (includes new `preserves terminal status when releasing a done issue` and existing `in_progress` → `todo` on release) - CI: Build, Typecheck, serialized server suites, e2e, Canary Dry Run green on latest head `f31b55f` ## Risks Low risk. Behaviour change is intentional: non-`in_progress` releases no longer force `todo`. Agents that relied on release to re-queue `in_review`/`blocked` work must PATCH status explicitly (documented in agent lifecycle guidance). Rollback: revert this commit and redeploy `@paperclipai/server` 0.3.1. ## Model Used Anthropic Claude Opus 4.6 (extended thinking mode) — 200K context window, tool use enabled. Assisted implementation and PR packaging for AI Trading Council upstream port from local hotfix. ## 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 run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots (N/A) - [x] I have updated relevant documentation to reflect my changes (CHANGELOG) - [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 (re-review requested on head `f31b55f`) - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: brandon <brandonburr@gmail.com> |