Commit Graph

698 Commits

Author SHA1 Message Date
Devin Foley c0743482bc fix(claude-local): tolerate sandboxes whose Claude CLI lacks --effort (#8393)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The claude-local adapter launches the Claude CLI inside execution
environments (local, SSH, and ephemeral sandboxes such as Daytona)
> - Newer adapter code passes `--effort` to the CLI, but the Claude
binary baked into some sandbox images is older and rejects it with
`error: unknown option '--effort'`, so every run in those environments
fails
> - This needs addressing because the failure is environment-dependent
and silent from the operator's perspective — the run just dies with a
CLI usage error
> - This pull request probes the in-sandbox CLI for `--effort` support
once, caches the result per environment, and strips the flag (with a
warning) when the CLI does not support it
> - The benefit is that sandboxes with older Claude CLIs keep working
instead of failing, with negligible probe overhead because the
capability check is cached and reused across ephemeral leases

## Linked Issues or Issue Description

No public GitHub issue exists. Describing the bug inline following the
bug report template:

### What happened?

Runs using the `claude-local` adapter inside certain sandbox/execution
environments fail with `error: unknown option '--effort'`. The adapter
unconditionally appends `--effort` to the Claude CLI invocation, but the
Claude CLI version present in some sandbox base images predates that
flag, so the process exits with a usage error and the run dies.

### Expected behavior

The adapter should detect that the target environment's Claude CLI does
not support `--effort` and degrade gracefully — drop the flag and emit a
warning — rather than failing the run.

### Steps to reproduce

1. Configure an execution environment (e.g. a sandbox image) whose
bundled Claude CLI is old enough to predate the `--effort` option.
2. Run any claude-local task that resolves to an effort level (so
`--effort` is appended).
3. Observe the run fail immediately with `error: unknown option
'--effort'`.

### Paperclip version or commit

`master` at the time of this PR (branch forked from current `master`).

### Deployment mode

Self-hosted / local instance using execution environments (reproducible
with ephemeral sandbox providers such as Daytona where `reuseLease:
false`).

### Agent adapter(s) involved

Claude Code (`claude-local`).

## What Changed

- Add a CLI capability probe (`cli-capabilities.ts`) that runs the
target Claude binary's `--help` inside the execution environment to
detect `--effort` support.
- Strip `--effort` from the CLI args (emitting a warning) when the probe
reports the flag is unsupported; keep it otherwise.
- Cache probe results keyed by
`sandbox:providerKey:environmentId:command` (no lease id) so the probe
is reused across ephemeral leases — important for `reuseLease: false`
sandbox configs like Daytona, which would otherwise re-probe on every
run.
- Conservative fallback: if the probe itself can't run/parse, assume the
flag is supported (preserves prior behavior).

## Verification

- `node_modules/.bin/vitest run
src/__tests__/claude-local-execute.test.ts
src/__tests__/claude-local-adapter-environment.test.ts` → **2 files, 30
tests passed**.
- Regression test issues two `execute()` calls with distinct lease ids
and asserts the in-sandbox `--help` probe runs exactly once (cache reuse
across leases).
- Added tests covering: flag stripped when unsupported, flag retained
when supported, warning emitted, and conservative fallback when the
probe fails.

## Risks

Low risk. The change is additive and gated behind a probe with a
conservative default (assume supported on probe failure), so existing
environments that support `--effort` are unaffected. Worst case for an
environment where the probe is unreliable is the prior behavior (flag
passed through).

## Model Used

Claude Opus 4.8 (claude-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)
- [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
- [ ] If this change affects the UI, I have included before/after
screenshots (N/A — no UI change)
- [ ] I have updated relevant documentation to reflect my changes (N/A —
no doc-facing behavior change)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending CI)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review)
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-06-20 13:05:10 -07:00
Devin Foley 07e98d2b2c feat(adapter-utils): add observable sandbox sync progress (#8395)
## Thinking Path

> - Paperclip is the control plane for running AI-agent companies, so
long-running remote work needs to stay observable to human operators.
> - Cloud / sandbox agents are an active roadmap area, and their
workspace sync path is part of the runtime substrate every remote coding
run depends on.
> - In the sandbox and SSH execution-target flows, Paperclip logged that
sync had started, then often went silent for the full transfer window.
> - That made large remote syncs feel stalled and also hid a real
performance problem in the command-managed sandbox upload path.
> - The first part of this pull request threads a throttled
progress-reporting surface through the adapter execution-target stack so
sync and restore work can emit meaningful updates.
> - The second part fixes the command-managed sandbox transport itself:
it removes the old serial 32KB append bottleneck, but also falls back
away from the single-stream path when a provider-backed sandbox runner
cannot surface mid-flight stdin progress.
> - The result is that sandbox and SSH transfers are both faster and
more observable, including the live Daytona-style sandbox case that
previously only emitted `0%` and `100%`.

## Linked Issues or Issue Description

No public GitHub issue exists for this bug, so it is described inline
below following the bug report template.

### What happened

- Remote sandbox and SSH workspace syncs could spend a long time
transferring data while only logging a start line (`Syncing workspace
and runtime assets to sandbox environment`) and, at best, a terminal
line.
- In the command-managed sandbox path, the original upload
implementation also paid a large performance cost by appending base64
data in many small sequential remote writes (thousands of serial 32KB
round-trips on a large workspace).
- After the initial transport rewrite, live provider-backed sandbox runs
still only emitted `0%` and `100%` because the single-stream stdin RPC
buffered progress until completion.

### Expected behavior

- Long-running sandbox and SSH syncs should periodically report how much
of the transfer is complete (a percentage and/or MB transferred) so an
operator can tell the run is healthy and making progress rather than
stuck.
- The main sandbox upload path should not be artificially slow.
- A transfer that fails partway should leave an explicit failure marker
in the log rather than a dangling intermediate percentage.

### Steps to reproduce

1. Run an agent against a sandbox (command-managed) or SSH
(remote-managed) execution target with a non-trivial workspace.
2. Watch the run log during the workspace/runtime asset sync phase.
3. Observe that the log shows the sync start line and then stays silent
for the full transfer (live provider-backed sandbox runs only show `0%`
then `100%`).

### Paperclip version or commit

- Branch `PAPA-825-provide-status-updates-when-syncing-sandboxes` off
`master`.

### Deployment mode

- Self-hosted / local instance using sandbox (command-managed) and SSH
(remote-managed) execution targets, including provider-backed sandbox
runners.

## What Changed

- Added shared throttled runtime progress reporting and threaded
`onProgress` through the adapter execution-target surface and adapter
`execute.ts` entrypoints.
- Added sync and restore progress reporting for the command-managed
sandbox path and the SSH/remote-managed path, including git
import/export progress where totals are known.
- Reworked command-managed sandbox transfer behavior so uploads use the
faster single-stream path when appropriate, but fall back to chunked
progress-emitting writes when the runner cannot expose mid-stream stdin
progress.
- Marked provider-backed environment sandbox runners as not supporting
single-stream stdin progress so live sandbox runs emit meaningful
intermediate updates instead of only `0%` and `100%`.
- Emit an explicit terminal failure marker (`failed at NN% (x/y MB)`)
when an SSH/tar transfer rejects, so a failed sync no longer leaves a
dangling intermediate percentage in the log.
- Run the SSH sync/restore size estimate (local directory walk / remote
`du` probe) concurrently with the transfer instead of awaiting it before
opening the pipe, so progress instrumentation no longer adds startup
latency proportional to workspace file count.
- Added and extended focused regression coverage for runtime progress
throttling and the new failure marker, command-managed sandbox
transfers, sandbox orchestration, SSH transfer progress, and environment
execution-target wiring.

## Verification

- `pnpm exec vitest run
packages/adapter-utils/src/runtime-progress.test.ts
packages/adapter-utils/src/ssh-fixture.test.ts
packages/adapter-utils/src/command-managed-runtime.test.ts
packages/adapter-utils/src/sandbox-managed-runtime.test.ts`
- `pnpm exec vitest run
packages/adapter-utils/src/command-managed-runtime.test.ts
server/src/__tests__/environment-execution-target.test.ts`
- `npx tsc --noEmit` for `packages/adapter-utils`

## Risks

- The provider-backed sandbox fallback now prefers chunked
command-managed writes when progress hooks are active, so
small-to-medium uploads may trade some raw throughput for observable
intermediate progress on runtimes that cannot surface true mid-stream
stdin progress.
- Progress percentages on tar-based transfers still depend on estimates
in some cases, so operators may briefly see MB-only lines before the
estimate resolves, then near-final clamping before the terminal `100%`
line.
- This PR changes shared execution-target behavior used by multiple
adapters, so regressions would most likely appear in remote runtime
setup/teardown flows rather than in a single adapter.

## Model Used

- Initial implementation: OpenAI GPT-5.4 via Codex local agent
(`codex_local`), high reasoning mode.
- Observability follow-ups (failure marker, concurrent size estimate,
added tests): Claude Opus 4.8 via Claude Code (`claude_local`).

## 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] 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
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-20 13:03:42 -07:00
Devin Foley 547463d3a2 refactor(environments): make execution environments instance-scoped (#8375)
## Thinking Path

> - Paperclip is the control plane for AI-agent companies, so execution
environment selection has to stay inspectable and predictable across
companies, agents, and runs.
> - The environment subsystem decides where an agent heartbeat actually
runs and how remote sandbox state is realized and restored.
> - That subsystem previously mixed company-scoped environment catalogs
with issue-level environment stamping, so a reassigned issue could keep
executing in the previous assignee's sandbox.
> - That behavior breaks the control-plane contract: changing the
assignee should change the executing agent/environment path unless there
is an explicit current override.
> - Fixing it cleanly required more than a narrow patch; the environment
model had to move to instance scope with a single inherited default and
per-agent override semantics.
> - This pull request rewires the schema, server/API surface, runtime
resolution, and UI around that model, then adds regression coverage for
cross-company inheritance and per-agent isolation.
> - The benefit is that environment choice now follows the approved
instance/agent configuration path instead of stale issue state, while
shared environments only need to be configured once per instance.

## Linked Issues or Issue Description

- No directly matching public GitHub issue or PR was found while
searching for this refactor.

### What happened?

Reassigning work between agents with different execution environments
could keep running in the previous sandbox because environment choice
was stamped onto the issue and outranked the current assignee. The same
subsystem also forced environment catalogs to be duplicated per company
even though the underlying execution environments were instance-wide
resources.

### Expected behavior

Execution should resolve through the current instance and agent
configuration path, with one instance-scoped environment catalog, one
instance default, optional per-agent override, and no stale issue-level
environment authority surviving reassignment.

### Steps to reproduce

1. Configure two agents to use different execution environments.
2. Assign an issue to the first agent so the issue records execution
state in that environment.
3. Reassign the same issue to the second agent and run another
heartbeat.
4. Observe that the pre-fix runtime can still sync or execute in the
original sandbox instead of the second agent's environment.

### Paperclip version or commit

Current `master` before this PR.

### Deployment mode

Self-hosted server.

### Installation method

Built from source (`pnpm dev` / `pnpm build`).

### Agent adapter(s) involved

- Claude Code
- Not adapter-specific (core bug in environment authority / resolution)

### Database mode

External Postgres.

### Access context

Both board reassignment and agent heartbeats were involved.

## What Changed

- Moved environments and their default selection contract to instance
scope in DB/shared types, including the migration that dedupes legacy
per-company environments and seeds the instance local default.
- Reworked environment CRUD/auth flows to use instance-scoped APIs and
added route/service coverage for instance-level environment management.
- Changed runtime resolution to prefer `agent default -> instance
default -> built-in local`, removed issue-level environment stamping
from the active execution path, and isolated sandbox/plugin leases by
`(executionWorkspaceId, agentId)`.
- Added environment env-var runtime precedence so environment-provided
values act as the baseline for agent execution.
- Moved the environment UI into instance settings and updated agent
configuration surfaces to reflect inherit/override behavior.
- Added regression coverage for instance-default inheritance across
companies and for the new runtime resolution behavior.
- Fixed a rebase-only duplicate `enableTaskWatchdogs` flag regression in
instance settings types/validators/services so the branch typechecks
cleanly on current `master`.
- Updated stale server tests so CI matches the shipped instance-scoped
environment contract.

## Verification

- `git diff --check`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run
server/src/__tests__/environment-runtime-driver-contract.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/environment-routes.test.ts
server/src/__tests__/environment-instance-routes.test.ts
server/src/__tests__/execution-workspace-policy.test.ts
server/src/__tests__/heartbeat-plugin-environment.test.ts
server/src/__tests__/instance-settings-routes.test.ts`

## Risks

- The migration changes environment scope and dedupes existing rows, so
installs with unusual legacy environment combinations should be reviewed
carefully during upgrade.
- Remote execution behavior now depends on instance-default inheritance
semantics instead of issue-level stamping, so any remaining code paths
that still assume issue-scoped environment authority would surface as
follow-up bugs.
- This PR includes both server/runtime behavior and UI relocation, so
reviewers should watch for authorization edge cases around instance
settings and environment management.

> I checked [`ROADMAP.md`](ROADMAP.md). This work fits the existing
Cloud / Sandbox agents direction as a bug-fix/refactor to current
behavior, not a new parallel product surface.

## Model Used

- OpenAI Codex coding agent in this Paperclip/Codex session; GPT-5-class
tool-using model with code execution and shell access. The exact backend
model ID is not exposed to the session runtime.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [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
2026-06-20 09:42:53 -07:00
uky333 eb68198c42 feat(adapter-claude-local): emit errorCode claude_refusal on stop_reason refusal (#8314)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run through adapters; `@paperclipai/adapter-claude-local`
shells out to the Claude CLI and maps its result JSON into Paperclip's
run outcome (`errorCode` / `errorFamily`)
> - A Fable 5 policy refusal exits the CLI cleanly (`exitCode=0`,
`is_error=false`) with `stop_reason: "refusal"`, which the adapter
mapped to `errorCode: null`
> - Paperclip therefore recorded the run as a silent success, so the
agent's heartbeat stalled with no signal for operators and no hook to
retry or alert
> - This pull request adds a refusal detector to the adapter so a
refusal resolves to a distinct `errorCode: "claude_refusal"`
(`errorFamily: "model_refusal"`)
> - The benefit is that policy refusals become observable: the server
persists the code, operators can see it on the run, and downstream
retry/alert policy can key off it

## Linked Issues or Issue Description

No public GitHub issue exists. Describing the underlying bug in-PR,
following `.github/ISSUE_TEMPLATE/bug_report.yml`:

**What happened?**
When the Claude CLI returns `stop_reason: "refusal"` (a model policy
refusal), it still exits cleanly (`exitCode=0`, `is_error=false`).
`@paperclipai/adapter-claude-local` keyed refusal detection off the
failure flag during error-code resolution, so it returned `errorCode:
null` — a refused run was indistinguishable from a successful one,
recorded by Paperclip as a silent success with no signal to retry or
alert.

**Expected behavior**
A refusal should resolve to a distinct, non-null error code (`errorCode:
"claude_refusal"`, `errorFamily: "model_refusal"`) so the server can
surface it to operators and downstream policy can react.

**Steps to reproduce**
1. Run any agent on the `claude-local` adapter with a prompt the model
refuses on policy grounds.
2. The Claude CLI exits `0` with `is_error=false` and `stop_reason:
"refusal"`.
3. Observe the run resolves `errorCode: null` (pre-fix) instead of a
refusal-specific code.

**Paperclip version or commit**
`adapter-claude-local` v2026.609.0 (`dist/server/execute.js` error-code
resolution block); reproduced on `master`.

**Deployment mode**
Local / self-hosted.

**Agent adapter(s) involved**
`@paperclipai/adapter-claude-local` (Claude Code, local).

## What Changed

- **`packages/adapters/claude-local/src/server/parse.ts`** — new
`isClaudeRefusalResult()` helper, parallel to
`isClaudeMaxTurnsResult()`. Detects `stop_reason` / `stopReason` /
`error_code` / `errorCode` == `refusal` and `subtype` ==
`model_refusal`, case/whitespace tolerant.
- **`packages/adapters/claude-local/src/server/execute.ts`** — compute
the refusal flag independent of the `failed` flag (a refusal exits
cleanly, so keying off `failed` would miss it); resolve `errorCode:
"claude_refusal"` and `errorFamily: "model_refusal"`; surface
`stopReason: "refusal"` in `resultJson`.
- **`packages/adapter-utils/src/types.ts`** — widen
`AdapterExecutionErrorFamily` with `"model_refusal"`.
- **`packages/adapters/claude-local/src/server/index.ts`** — export the
new helper.
- **`packages/adapters/claude-local/src/server/parse.test.ts`** — 7 new
unit tests.

## Verification

```
npx vitest run packages/adapters/claude-local           # 35 passed (7 new)
npx tsc --noEmit -p packages/adapter-utils              # clean
npx tsc --noEmit -p packages/adapters/claude-local      # clean
```

Manual: a result JSON with `stop_reason: "refusal"` on a clean exit now
resolves `errorCode: "claude_refusal"` and `errorFamily:
"model_refusal"`; non-refusal results are unaffected.

## Risks

Low risk. Additive only — it introduces a new error code on a path that
previously returned `null`; no existing error code or success path
changes. `claude_refusal` is deliberately **not** added to the
transient-upstream retry set: a refusal is deterministic, so retrying
the same prompt yields the same refusal. It surfaces as a distinct
non-transient code for operators; downstream retry/alert policy can key
off `claude_refusal` / `errorFamily: model_refusal` later.

## Model Used

Claude Opus 4.8 (`claude-opus-4-8`), extended-thinking mode, run via
Claude Code with 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 (no related PRs found)
- [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] 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 — no UI changes)
- [x] I have updated relevant documentation to reflect my changes (N/A —
internal adapter error-code addition; no user-facing docs)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (this push re-runs the gates;
will confirm once green)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(prior run was 4/5 on a PR-description note now addressed; will confirm
on re-run)
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Full-Stack Engineer <noreply@paperclip.ing>
2026-06-19 23:24:06 -07:00
Sergio Sánchez Zavala 323b6220cc Fix Gemini headless invocation stalls (#8368)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Local adapters turn Paperclip runs into unattended CLI invocations.
> - The `gemini_local` adapter depends on Gemini CLI behaving
non-interactively in headless worker sessions.
> - When Gemini CLI falls back to browser-based auth, the process can
stall at startup instead of producing stream-json output.
> - The adapter should make headless intent explicit and turn missing
auth into a classified, fast failure.
> - This pull request hardens the Gemini child-process environment and
parser classification around that failure mode.
> - The benefit is that operators get actionable `gemini_auth_required`
failures instead of silent hung runs.

## Linked Issues or Issue Description

No exact public issue exists for this runtime stall.

Related: #2344 covers a Gemini CLI adapter environment/auth probe
failure. This PR addresses a different runtime path: unattended
`gemini_local` executions that can stall when Gemini CLI attempts
interactive browser auth in a headless session.

Bug summary:
- Adapter: `gemini_local`
- Symptom: child Gemini CLI process starts but produces no stream-json
output when auth requires interactive browser flow
- Expected: unattended runs either produce stream-json output or fail
quickly with a classified auth error
- Actual: the run can hang at invocation until an external watchdog
kills it
- Scope: Gemini adapter process env, auth-required parsing, and adapter
docs/tests

Duplicate search performed:
- `gh pr list --state all --search "gemini headless invocation stall"`
found only this PR.
- `gh pr list --state all --search "gemini NO_BROWSER NO_COLOR"` found
only this PR.
- `gh issue list --state all --search "gemini headless authentication"`
found related issue #2344 but no exact duplicate.

## What Changed

- Set a headless-safe Gemini invocation env at the final child-process
boundary: `TERM=xterm-256color`, `COLORTERM=truecolor`, and
`NO_BROWSER=1`.
- Delete inherited `NO_COLOR` from the child env so Gemini CLI keeps
color-capable terminal behavior when deciding whether it can run
non-interactively.
- Classify Gemini `FatalAuthenticationError: Manual authorization is
required...` failures as `gemini_auth_required`.
- Update Gemini adapter docs to describe the non-interactive `--prompt`
path and headless env behavior.
- Add/extend focused parser and remote execution tests for the
auth-required and env invariants.

## Verification

- `pnpm exec vitest run
packages/adapters/gemini-local/src/server/parse.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts` — 23
tests passed.
- `pnpm --filter @paperclipai/adapter-gemini-local typecheck` — passed.
- Local Gemini CLI probe confirmed `NO_BROWSER=1` turns the browser-auth
stall into a fast auth error instead of an interactive wait.

## Risks

Low risk. The change is limited to `gemini_local` invocation env,
parsing, docs, and tests. It does not touch schemas, API routes,
persisted data, or other adapters.

Operational risk: environments that intentionally rely on `NO_COLOR`
being inherited by Gemini child processes will no longer pass that
variable through. That is intentional here because Gemini CLI
auth/headless behavior should not depend on inherited color suppression.

> 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 via Codex CLI, with shell/file-editing tool use for
repository inspection, code edits, tests, and GitHub PR maintenance.

## 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
- [ ] If this change affects the UI, I have included before/after
screenshots
- [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

/cc @codex — please review.
2026-06-19 21:31:36 -07:00
Nicky Leach 364f0f5a8d fix(server): enforce issue read for issue thread lists (#8331)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents coordinate through issue threads, so issue comments and
interactions are part of the task authorization surface.
> - Low-trust and boundary-limited agents can receive narrow
mention-scoped access, but that must not turn into broad same-company
issue-thread reads.
> - The comment creation path was narrowed to allow explicit mention
replies without granting mutation access.
> - The surrounding list/read routes still needed to enforce the same
`issue:read` boundary before returning thread data.
> - This pull request applies the issue read check to issue comment and
interaction listing routes, and locks that behavior with server
regressions.
> - The benefit is that narrow cross-agent collaboration remains
possible without exposing unrelated issue-thread history.

## Linked Issues or Issue Description

Bug fix:
- What happened: same-company agents outside an issue read boundary
could still hit issue-thread listing routes and receive thread data.
- Expected behavior: issue comments and issue-thread interactions should
only be listed after the actor is allowed to read the issue.
- Steps to reproduce: configure a peer agent denied by the issue read
boundary, then request `GET /api/issues/:id/comments` or the issue
interaction listing route.
- Paperclip version/commit: current `master` before this branch.
- Deployment mode: applies to server authorization in all modes.

Related public context: #7389, #7863, #8024.

## What Changed

- Added issue read enforcement before listing issue comments.
- Added issue read enforcement before listing issue-thread interactions.
- Added server regressions for denied peer-agent issue-thread access
while preserving mention-scoped collaboration behavior.
- Removed an avoidable per-mentioned-comment issue reload in
mention-grant authorization by passing the already-loaded issue assignee
through the helper.
- Documented cross-agent issue read/comment authorization behavior in
the Paperclip API reference.
- Added a resilient fallback for pinned external skills when GitHub tree
fetches are temporarily unavailable during catalog builds.

## Verification

- `pnpm vitest run
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/authorization-service.test.ts`
  - 2 test files passed
  - 84 tests passed
- Existing mocked recovery revalidation warnings were emitted by the
route suite and the command exited 0
- Greptile: 5/5 on commit `b73fc323f192dc44f88374c16865008d4348923b`; no
unresolved review threads.
- `pnpm vitest run packages/skills-catalog/src/catalog-builder.test.ts`
  - 1 test file passed
  - 6 tests passed
- CI: all visible PR checks are terminal green on commit
`b73fc323f192dc44f88374c16865008d4348923b`.

## Risks

Low risk. This tightens read authorization on issue-thread listing
routes; any caller that depended on same-company access without
`issue:read` will now receive 403 and must use an explicit grant or
valid issue read path.

## Model Used

OpenAI GPT-5 Codex, Codex coding agent environment, tool use and local
command execution enabled, reasoning mode active. Context window not
exposed by the runtime.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] 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
- [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>
2026-06-19 15:16:17 -07:00
Dotta a71c4b6782 [codex] feat(watchdog): add task watchdog control plane (#8339)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task lifecycle and recovery subsystems decide when agent work is
still productive, stalled, or ready for review.
> - Existing recovery paths can observe stopped or incomplete work, but
there was no first-class per-task watchdog model with scoped review
permissions.
> - Watchdog follow-ups also need strict boundaries so
recovery/status-only runs cannot mutate approvals or perform deliverable
work.
> - This pull request adds the task watchdog data model, API/service
layer, scheduler/review flow, adapter wake context, UI configuration
surfaces, and docs.
> - The branch has been rebased onto current `paperclipai/paperclip`
`master`; the watchdog migration is now ordered after master's latest
migrations as `0104_issue_watchdogs`.
> - The benefit is a more explicit task-review loop that preserves
Paperclip's single-assignee and governance invariants while making
stalled work easier to route.

## Linked Issues or Issue Description

No linked GitHub issue. Paperclip task:
[PAP-11275](/PAP/issues/PAP-11275).

## Problem or motivation

Task recovery needs a first-class watchdog path that can inspect stopped
work and create scoped follow-ups without bypassing normal task
ownership. Board/UI users need a way to configure watchdogs on tasks and
see watchdog-related live work. Recovery/status-only runs must remain
limited to status reporting and must not create approvals, link
approvals, or submit approval comments.

## Proposed solution

Add a task-watchdog data model, scheduler/classifier, scoped mutation
guard, adapter wake context, API/UI configuration surfaces, and
documentation so watchdog agents can review stopped task subtrees under
explicit boundaries.

## Alternatives considered

Reuse the existing recovery-action flow only. That would keep
stopped-work detection implicit, make per-task watchdog assignment
harder to expose in the UI, and would not provide a durable
scoped-review issue for stalled task trees.

## Roadmap alignment

This is Paperclip control-plane lifecycle infrastructure for task
execution and recovery. I checked `ROADMAP.md`; this PR does not
duplicate an existing planned core item.

## What Changed

- Added issue watchdog schema, migration, shared contracts, validators,
CRUD API, and service support.
- Added task watchdog scheduler/classifier behavior, scoped mutation
enforcement, adapter wake context, and default watchdog mandate
guidance.
- Added UI surfaces for configuring watchdogs on new/existing tasks,
viewing watchdog activity, and exposing the experimental setting.
- Added docs for the user-facing task watchdog workflow and
implementation semantics.
- Gated new-task watchdog setup behind `enableTaskWatchdogs` and blocked
cheap status-only recovery runs from approval mutations.
- Rebased onto current `master` and renumbered the idempotent watchdog
migration from the branch-local `0102_issue_watchdogs` slot to
`0104_issue_watchdogs`.
- Addressed Greptile feedback by loading watchdog classifier input with
a recursive subtree query and centralizing the watchdog origin-kind
constant.
- Added and updated focused server/UI tests for watchdog routes,
scheduler/classifier behavior, scope boundaries, live task visibility,
settings, and new issue dialog behavior.

## Verification

- `pnpm vitest run server/src/__tests__/task-watchdogs-scheduler.test.ts
server/src/__tests__/task-watchdogs-classifier.test.ts`
- `pnpm vitest run
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts`
- `pnpm vitest run ui/src/components/NewIssueDialog.test.tsx`
- `pnpm --filter @paperclipai/server typecheck`
- `git diff --check`
- Verified the PR diff does not include `pnpm-lock.yaml` or
`.github/workflows`.

## Risks

- Medium risk: this introduces a new task lifecycle surface touching DB
schema, server routes/services, adapter wake context, and UI task
configuration.
- Watchdog scheduling behavior depends on the new experimental setting
and runtime context checks behaving consistently across local and
production agents.
- The watchdog migration is idempotent (`IF NOT EXISTS` /
duplicate-object guards) so users who tried the previous branch-local
migration number should not get duplicate-object failures.
- CI and the second Greptile pass are pending after the latest
review-fix push.

> 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-class coding agent in the Paperclip workspace. Exact
runtime model id and context window were not exposed to the agent; tool
use and local command execution were enabled.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have 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 per Paperclip task instruction: do not add
screenshots/images to this PR unless they are specifically part of the
work.
- [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>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:38:52 -05:00
Dotta 7069053a1f [codex] Add ask issue work mode (#8334)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Issue work mode controls how a task starts and how the conversation
composer frames the operator's intent.
> - Paperclip already supports standard agent execution and planning
mode, but there is no lightweight mode for asking a question without
immediately implying execution or plan drafting.
> - That gap makes low-commitment clarification workflows look like
normal task execution.
> - This pull request adds an explicit Ask mode and threads it through
shared contracts, server heartbeat context, and the issue composer UI.
> - The benefit is that operators can create or switch a task into a
question-oriented mode while preserving existing agent and planning
flows.

## Linked Issues or Issue Description

No public GitHub issue exists for this change. Inline feature request
follows the repository feature request template.

### Subsystem affected

Cross-cutting: `packages/shared`, `server/`, and `ui/`.

### Problem or motivation

Issue conversations currently distinguish standard agent work from
planning work, but question-first conversations do not have a clear
public mode in the shared contract or UI. Operators who want to ask an
agent a focused question have to use standard mode, which can imply
normal task execution, or planning mode, which asks for a plan rather
than an answer.

### Proposed solution

Add Ask as a first-class issue work mode. It should be selectable from
issue creation and issue chat, cycle alongside Standard and Planning
from the keyboard shortcut/menu, appear distinctly in composer styling,
and be included in heartbeat context so agents know to answer directly
instead of executing or drafting a plan.

### Alternatives considered

- Keep using standard mode for questions: rejected because it does not
communicate answer-only intent to the agent or the UI.
- Reuse planning mode for questions: rejected because planning mode asks
for a plan and is semantically different from asking a question.
- Add only local UI copy: rejected because the mode needs to be
represented in the shared contract and server heartbeat context to be
reliable.

### Roadmap alignment

This is a focused issue-workflow improvement. `ROADMAP.md` was checked
and no duplicate planned core work was found.

### Additional context

Related public searches performed before opening this PR:

- GitHub PR search for `"ask mode" repo:paperclipai/paperclip`
- GitHub issue search for `"ask mode" repo:paperclipai/paperclip`
- GitHub PR search for `"work mode" "ask" repo:paperclipai/paperclip`

No duplicate PR was found.

## What Changed

- Added `ask` to the shared issue work-mode contract and validation
coverage.
- Included issue work mode in heartbeat context summaries so agents can
see standard, planning, and ask state.
- Added Ask mode metadata, styling, composer tone handling, and
selection/cycling behavior in the issue chat/new issue UI.
- Updated focused tests for shared validators, heartbeat context, and
affected UI work-mode flows.

## Verification

- `NODE_ENV=test pnpm exec vitest run
ui/src/components/ChatComposer.test.tsx
ui/src/components/IssueChatThread.test.tsx
ui/src/components/NewIssueDialog.test.tsx
ui/src/lib/work-mode-meta.test.ts`
- `NODE_ENV=test pnpm exec vitest run
packages/shared/src/validators/issue.test.ts
server/src/__tests__/heartbeat-context-summary.test.ts
server/src/__tests__/issues-service.test.ts
ui/src/components/ChatComposer.test.tsx
ui/src/components/IssueChatThread.test.tsx
ui/src/components/NewIssueDialog.test.tsx
ui/src/lib/work-mode-meta.test.ts ui/src/pages/IssueDetail.test.tsx`

The broader targeted command passed 8 test files / 245 tests.

Visual reference for Standard/Planning/Ask composer states:
https://gist.github.com/cryppadotta/714d8590bac55500a65e7e16de5bb4b8

It emitted an expected warning from an existing server test fixture
about a missing run-log fixture while verifying derived issue comment
metadata.

## Risks

Low to moderate risk. This adds a new enum value that crosses shared,
server, and UI contracts. Existing standard and planning modes are
preserved, but any downstream code assuming only two non-terminal work
modes may need to handle `ask`.

> 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 in Paperclip CodexCoder mode, with
shell, git, GitHub connector, and local test execution tools. Context
window and exact hosted model snapshot are not exposed in this runtime.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] 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
- [x] If this change affects the UI, I have included before/after
screenshots
- [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>
2026-06-19 13:06:04 -05:00
Devin Foley 5e086cb828 fix(server): resolve published skills catalog package root and fallback (#8327)
## Thinking Path

> - Paperclip is the open source control plane people use to run and
supervise AI-agent companies.
> - The skills system is part of that core operator experience because
agents and humans both depend on the catalog-backed Skills Manager
surfaces.
> - In source checkouts, the server can find the catalog manifest and
bundled skill files through monorepo-relative paths, but published
installs do not preserve that layout.
> - That mismatch makes `GET /api/skills/catalog` fail in npm/pnpm
installs even though the catalog package itself is present.
> - The server therefore needs to resolve the catalog from the published
`@paperclipai/skills-catalog` package first, while still keeping a
monorepo fallback for local development.
> - This pull request makes the published package the primary resolution
path, uses the same resolved package root for bundled skill file reads,
and degrades the list route safely when the manifest is unavailable.
> - The benefit is that Skills Manager catalog reads behave correctly in
packaged installs instead of only in repo-local development layouts.

## Linked Issues or Issue Description

Fixes #8316
Refs #7281
Refs #7313
Refs #7350
Refs #7860
Refs #8223
Refs #8227

## What Changed

- Added `@paperclipai/skills-catalog` as a runtime dependency of
`@paperclipai/server`.
- Exported `./package.json` from `@paperclipai/skills-catalog` so the
server can resolve the published package root directly.
- Updated `server/src/services/skills-catalog.ts` to resolve the
manifest and package root from the published package first, with the
monorepo path retained only as a development fallback.
- Applied that resolved package root to bundled catalog file reads so
manifest lookup and skill-file reads use the same published layout.
- Added `listCatalogSkillsOrEmpty()` so `GET /api/skills/catalog`
returns `[]` and logs a warning when the manifest is unavailable instead
of surfacing a 500.
- Added targeted server tests for published-package resolution and
missing-manifest fallback handling.

## Verification

- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/skills-catalog-service.test.ts
src/__tests__/company-skills-routes.test.ts`
- `pnpm --filter @paperclipai/server build`
- Packaging smoke:
- pack `@paperclipai/shared` and `@paperclipai/skills-catalog` from this
checkout
  - mount those packed artifacts under `server/dist/node_modules`
  - import `server/dist/services/skills-catalog.js`
- verify a bundled catalog `SKILL.md` resolves and reads successfully
from the packed package layout

## Risks

- Low risk: the change is narrowly scoped to catalog package resolution
and fallback behavior.
- The new `./package.json` export slightly broadens the catalog
package's public surface, so reviewers should confirm that is an
acceptable runtime contract.
- The empty-array fallback intentionally changes failure mode for a
missing manifest from `500` to a warning + empty payload, which is safer
for packaged installs but could hide packaging regressions if logs are
not monitored.

## Model Used

- OpenAI Codex via Paperclip `codex_local` (GPT-5-based coding agent;
exact backend model ID/context window not exposed in this harness), with
tool use, shell execution, git, and local test/build verification.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] 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
- [ ] 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
2026-06-19 09:14:41 -07:00
Devin Foley fc95699fde fix(server): enforce agent secret binding sync across lifecycle flows (#8307)
## Thinking Path

> - Paperclip is the control plane people use to create, configure, and
run AI agents for work.
> - This change sits in the server-side agent lifecycle and
secret-binding subsystem, where adapter config `env` entries can
reference company secrets.
> - An incident (while trying to configure a Novita sandbox) showed that
an agent can reach a broken runtime state if `adapterConfig.env`
contains `secret_ref` entries but the matching `company_secret_bindings`
rows are missing.
> - The immediate run-path guard and error-surfacing work made the
failure diagnosable, but they did not fully prevent new broken agents
from being created.
> - The risk came from create and approval flows being responsible for
remembering to sync bindings at each call site, which is easy to miss as
new flows are added.
> - This pull request moves the invariant into `agentService`
create/update/activate paths, keeps the existing hire-flow fix, and adds
regression coverage for create, update, and legacy pending-approval
recovery.
> - The benefit is that agent secret binding integrity is enforced
closer to the data mutation point, so future callers inherit the
protection automatically.

## Linked Issues or Issue Description

Refs #8309

### What happened?
A Paperclip agent could persist `adapterConfig.env` `secret_ref` entries
without matching agent-scoped `company_secret_bindings` rows. When that
happened, the config UI could still look configured, but the real run
path failed pre-dispatch because the secret was not actually bound to
that agent.

### Expected behavior
Every normal agent create, config-update, and pending-approval
activation flow should leave the agent with secret bindings that match
its persisted secret-ref env config.

### Steps to reproduce
1. Create or activate an agent through a flow that persists
`adapterConfig.env` secret refs without synchronizing
`company_secret_bindings`.
2. Observe that the config state can still appear populated.
3. Start a run for that agent.
4. Observe that pre-dispatch binding validation fails because the secret
reference exists but the agent binding does not.

### Deployment mode
Local dev (`pnpm dev`)

### Installation method
Built from source (`pnpm dev` / `pnpm build`)

### Agent adapter(s) involved
- Claude Code
- Not adapter-specific (core bug)

### Database mode
Embedded PGlite / embedded local dev database flow

### Access context
Board (human operator) created or approved the agent; agent runtime
later consumed the config.

### Additional context
This PR focuses on preventing new broken states from normal service
flows and on backfilling the covered legacy pending-approval activation
path.

## What Changed

- Kept the existing branch-local hire-flow fix that synchronized
bindings for route and approval paths.
- Moved the binding integrity invariant into `agentService.create()`,
`agentService.update()` when `adapterConfig` changes, and
`agentService.activatePendingApproval()`.
- Added `server/src/__tests__/agents-service-secret-bindings.test.ts`
covering create-time sync, update-time resync, and backfill for legacy
pending-approval agents.
- Removed now-redundant route-layer and approval-layer binding sync
calls once the service layer became authoritative.
- Simplified the affected unit tests so route/approval tests no longer
assert service-owned binding writes directly.

## Verification

- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run
server/src/__tests__/agents-service-secret-bindings.test.ts
server/src/__tests__/approvals-service.test.ts
server/src/__tests__/agent-skills-routes.test.ts`

## Risks

- Low to medium risk.
- This changes where secret-binding synchronization is enforced, so any
unexpected caller that relied on upper-layer manual sync behavior could
behave differently.
- Agent create/update/activation flows now perform binding
synchronization consistently, which adds binding-table writes at those
mutation points.
- This PR does not retroactively scan and heal every already-broken
historical agent row; it prevents and backfills through the covered
service flows.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex / GPT-5 Codex class model via `codex_local`
- Session model family: GPT-5 Codex
- Tool-assisted coding with shell, git, HTTP, and local test execution
- Reasoning mode: medium interactive tool-use workflow

## 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
- [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>
2026-06-18 21:26:36 -07:00
dependabot[bot] 5c11725784 build(deps-dev): bump tsx from 4.21.0 to 4.22.4 (#7754)
Bumps [tsx](https://github.com/privatenumber/tsx) from 4.21.0 to 4.22.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/privatenumber/tsx/releases">tsx's
releases</a>.</em></p>
<blockquote>
<h2>v4.22.4</h2>
<h2><a
href="https://github.com/privatenumber/tsx/compare/v4.22.3...v4.22.4">4.22.4</a>
(2026-05-31)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>resolve CommonJS directory requires inside dependencies (<a
href="https://redirect.github.com/privatenumber/tsx/issues/803">#803</a>)
(<a
href="https://github.com/privatenumber/tsx/commit/1ce846335b7c445a3328c7d27f06424949356d97">1ce8463</a>)</li>
</ul>
<hr />
<p>This release is also available on:</p>
<ul>
<li><a href="https://www.npmjs.com/package/tsx/v/4.22.4"><code>npm
package (@​latest dist-tag)</code></a></li>
</ul>
<h2>v4.22.3</h2>
<h2><a
href="https://github.com/privatenumber/tsx/compare/v4.22.2...v4.22.3">4.22.3</a>
(2026-05-19)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>decode typed loader source (<a
href="https://github.com/privatenumber/tsx/commit/dce02fc3b8b64a58d24560714902b16f89332f1f">dce02fc</a>)</li>
<li>preserve entrypoint with TypeScript preload hooks (<a
href="https://github.com/privatenumber/tsx/commit/68f72f3304d8c3ff7048bde8571af9c163fcefa2">68f72f3</a>)</li>
</ul>
<hr />
<p>This release is also available on:</p>
<ul>
<li><a href="https://www.npmjs.com/package/tsx/v/4.22.3"><code>npm
package (@​latest dist-tag)</code></a></li>
</ul>
<h2>v4.22.2</h2>
<h2><a
href="https://github.com/privatenumber/tsx/compare/v4.22.1...v4.22.2">4.22.2</a>
(2026-05-18)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>preserve CJS JSON require in ESM hooks (<a
href="https://github.com/privatenumber/tsx/commit/35b700bd8620696df03827068af29dcd0d091a60">35b700b</a>)</li>
<li>preserve named exports from CommonJS TypeScript (<a
href="https://github.com/privatenumber/tsx/commit/11de737dae1fb9dae28db3716df5b1a7e1a6a089">11de737</a>)</li>
<li>support module.exports require(esm) interop (<a
href="https://github.com/privatenumber/tsx/commit/cf8f19918e4e0a0dc5ee5c52d8cc15e5e22d7c49">cf8f199</a>)</li>
</ul>
<hr />
<p>This release is also available on:</p>
<ul>
<li><a href="https://www.npmjs.com/package/tsx/v/4.22.2"><code>npm
package (@​latest dist-tag)</code></a></li>
</ul>
<h2>v4.22.1</h2>
<h2><a
href="https://github.com/privatenumber/tsx/compare/v4.22.0...v4.22.1">4.22.1</a>
(2026-05-17)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>resolve tsconfig path aliases containing a colon (<a
href="https://redirect.github.com/privatenumber/tsx/issues/780">#780</a>)
(<a
href="https://github.com/privatenumber/tsx/commit/6979f28810829dc79ec9baf406e162a18b65ab4b">6979f28</a>)</li>
</ul>
<hr />
<p>This release is also available on:</p>
<ul>
<li><a href="https://www.npmjs.com/package/tsx/v/4.22.1"><code>npm
package (@​latest dist-tag)</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/privatenumber/tsx/commit/1ce846335b7c445a3328c7d27f06424949356d97"><code>1ce8463</code></a>
fix: resolve CommonJS directory requires inside dependencies (<a
href="https://redirect.github.com/privatenumber/tsx/issues/803">#803</a>)</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/dce02fc3b8b64a58d24560714902b16f89332f1f"><code>dce02fc</code></a>
fix: decode typed loader source</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/68f72f3304d8c3ff7048bde8571af9c163fcefa2"><code>68f72f3</code></a>
fix: preserve entrypoint with TypeScript preload hooks</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/69455cfefbfe71100a3c58d3ce7cea42445d9113"><code>69455cf</code></a>
test: cover package exports for ambiguous ESM reexports</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/35b700bd8620696df03827068af29dcd0d091a60"><code>35b700b</code></a>
fix: preserve CJS JSON require in ESM hooks</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/ef807dba6832260fb4cafd78d81f5469a733966b"><code>ef807db</code></a>
chore: update testing dependencies</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/3917090d4f61863ea6ea16e4a9a3722a112cc3f7"><code>3917090</code></a>
test: document compatibility test taxonomy</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/de8113ffa8edbcd4e05fa218324c3e8c2a4afdbe"><code>de8113f</code></a>
refactor: centralize Node capability facts</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/c1f62db45ada60b24ceb3dfdf7f64173d9a15396"><code>c1f62db</code></a>
test: consolidate tsconfig path edge coverage</li>
<li><a
href="https://github.com/privatenumber/tsx/commit/4e08174ec10276ac71c9a69eb28426ad702d0c76"><code>4e08174</code></a>
test: consolidate loader hook coverage</li>
<li>Additional commits viewable in <a
href="https://github.com/privatenumber/tsx/compare/v4.21.0...v4.22.4">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for tsx since your current version.</p>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 17:56:49 -05:00
dependabot[bot] 6f142a60ce build(deps-dev): bump @types/node from 22.19.11 to 22.19.21 (#7748)
Bumps
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
from 22.19.11 to 22.19.21.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">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>
2026-06-18 15:46:06 -07:00
dependabot[bot] dd92c7d2dd build(deps): bump @agentclientprotocol/claude-agent-acp from 0.31.4 to 0.47.0 (#7744)
Bumps
[@agentclientprotocol/claude-agent-acp](https://github.com/agentclientprotocol/claude-agent-acp)
from 0.31.4 to 0.47.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/agentclientprotocol/claude-agent-acp/releases">@​agentclientprotocol/claude-agent-acp's
releases</a>.</em></p>
<blockquote>
<h2>v0.47.0</h2>
<h2><a
href="https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.46.0...v0.47.0">0.47.0</a>
(2026-06-17)</h2>
<h3>Features</h3>
<ul>
<li>Update to claude-agent-sdk 0.3.179 (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/783">#783</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/59a098c2b530bbae034e9a2dfbd31f8b4ef2a4d0">59a098c</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>Duplicate assistant messages in feed (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/785">#785</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/12d34e64e53564602ac1c38a30127e234c5c25ff">12d34e6</a>)</li>
</ul>
<h2>v0.46.0</h2>
<h2><a
href="https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.45.1...v0.46.0">0.46.0</a>
(2026-06-16)</h2>
<h3>Features</h3>
<ul>
<li>Update to claude-agent-sdk 0.3.178 (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/777">#777</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/58549ffe6a8b02ce59894e567407bd4299c11428">58549ff</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>Better handle out of turn events (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/780">#780</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/4f273a20d870c9c69f71556b8e0519f1de30f285">4f273a2</a>)</li>
<li>Forward option details in elicitation meta (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/779">#779</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/b3640599ae685beecacd93e012d5bbc9dac716f7">b364059</a>),
closes <a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/764">#764</a></li>
</ul>
<h2>v0.45.1</h2>
<h2><a
href="https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.45.0...v0.45.1">0.45.1</a>
(2026-06-16)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Fix terminal error printing as text instead of terminal output (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/776">#776</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/db6eaaf71484a321e47093ad65bcf8994943cb31">db6eaaf</a>)</li>
<li>Scope custom answers per question (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/774">#774</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/d58004a34880e0a76833697319eb2a9efa6a43c7">d58004a</a>)</li>
</ul>
<h2>v0.45.0</h2>
<h2><a
href="https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.44.0...v0.45.0">0.45.0</a>
(2026-06-15)</h2>
<h3>Features</h3>
<ul>
<li><strong>deps-dev:</strong> bump the minor group with 3 updates (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/763">#763</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/7de5e4bcca9bfea70593092060f82bc8abe33e0e">7de5e4b</a>)</li>
<li><strong>deps:</strong> bump
<code>@​anthropic-ai/claude-agent-sdk</code> to 0.3.177 (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/771">#771</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/1be5ca57ee772fe90e41126365dc4186a18ad257">1be5ca5</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>preserve ANTHROPIC_CUSTOM_MODEL_OPTION when availableModels is set
(<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/768">#768</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/cc2885f6a9993cf61e759c3c770015f94c218627">cc2885f</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/agentclientprotocol/claude-agent-acp/blob/main/CHANGELOG.md">@​agentclientprotocol/claude-agent-acp's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.46.0...v0.47.0">0.47.0</a>
(2026-06-17)</h2>
<h3>Features</h3>
<ul>
<li>Update to claude-agent-sdk 0.3.179 (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/783">#783</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/59a098c2b530bbae034e9a2dfbd31f8b4ef2a4d0">59a098c</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>Duplicate assistant messages in feed (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/785">#785</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/12d34e64e53564602ac1c38a30127e234c5c25ff">12d34e6</a>)</li>
</ul>
<h2><a
href="https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.45.1...v0.46.0">0.46.0</a>
(2026-06-16)</h2>
<h3>Features</h3>
<ul>
<li>Update to claude-agent-sdk 0.3.178 (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/777">#777</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/58549ffe6a8b02ce59894e567407bd4299c11428">58549ff</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>Better handle out of turn events (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/780">#780</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/4f273a20d870c9c69f71556b8e0519f1de30f285">4f273a2</a>)</li>
<li>Forward option details in elicitation meta (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/779">#779</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/b3640599ae685beecacd93e012d5bbc9dac716f7">b364059</a>),
closes <a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/764">#764</a></li>
</ul>
<h2><a
href="https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.45.0...v0.45.1">0.45.1</a>
(2026-06-16)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Fix terminal error printing as text instead of terminal output (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/776">#776</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/db6eaaf71484a321e47093ad65bcf8994943cb31">db6eaaf</a>)</li>
<li>Scope custom answers per question (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/774">#774</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/d58004a34880e0a76833697319eb2a9efa6a43c7">d58004a</a>)</li>
</ul>
<h2><a
href="https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.44.0...v0.45.0">0.45.0</a>
(2026-06-15)</h2>
<h3>Features</h3>
<ul>
<li><strong>deps-dev:</strong> bump the minor group with 3 updates (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/763">#763</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/7de5e4bcca9bfea70593092060f82bc8abe33e0e">7de5e4b</a>)</li>
<li><strong>deps:</strong> bump
<code>@​anthropic-ai/claude-agent-sdk</code> to 0.3.177 (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/771">#771</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/1be5ca57ee772fe90e41126365dc4186a18ad257">1be5ca5</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>preserve ANTHROPIC_CUSTOM_MODEL_OPTION when availableModels is set
(<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/768">#768</a>)
(<a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/cc2885f6a9993cf61e759c3c770015f94c218627">cc2885f</a>)</li>
</ul>
<h2><a
href="https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.43.0...v0.44.0">0.44.0</a>
(2026-06-09)</h2>
<h3>Features</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/794aa846844a2fe8a8574c2539e2c4107e9182d1"><code>794aa84</code></a>
chore(main): release 0.47.0 (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/784">#784</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/12d34e64e53564602ac1c38a30127e234c5c25ff"><code>12d34e6</code></a>
fix: Duplicate assistant messages in feed (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/785">#785</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/59a098c2b530bbae034e9a2dfbd31f8b4ef2a4d0"><code>59a098c</code></a>
feat: Update to claude-agent-sdk 0.3.179 (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/783">#783</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/c27e6aec9059a920f9cd768f492e25934653b3ff"><code>c27e6ae</code></a>
chore(main): release 0.46.0 (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/778">#778</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/4f273a20d870c9c69f71556b8e0519f1de30f285"><code>4f273a2</code></a>
fix: Better handle out of turn events (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/780">#780</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/b3640599ae685beecacd93e012d5bbc9dac716f7"><code>b364059</code></a>
fix: Forward option details in elicitation meta (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/779">#779</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/58549ffe6a8b02ce59894e567407bd4299c11428"><code>58549ff</code></a>
feat: Update to claude-agent-sdk 0.3.178 (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/777">#777</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/116f06eab178fe50857f7a4150deb7f5c4cfee28"><code>116f06e</code></a>
chore(main): release 0.45.1 (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/775">#775</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/db6eaaf71484a321e47093ad65bcf8994943cb31"><code>db6eaaf</code></a>
fix: Fix terminal error printing as text instead of terminal output (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/776">#776</a>)</li>
<li><a
href="https://github.com/agentclientprotocol/claude-agent-acp/commit/d58004a34880e0a76833697319eb2a9efa6a43c7"><code>d58004a</code></a>
fix: Scope custom answers per question (<a
href="https://redirect.github.com/agentclientprotocol/claude-agent-acp/issues/774">#774</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.31.4...v0.47.0">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>
2026-06-18 15:32:15 -07:00
MrBob 5f16efb3d0 fix: parse YAML block scalar skill descriptions (#5046)
## Thinking Path

> - Paperclip is the open source control plane teams use to manage AI
agents for work.
> - Company skills are imported from `SKILL.md` files and rely on YAML
frontmatter to describe what each skill does.
> - Multi-line descriptions commonly use YAML block scalars (`>` and
`|`), but the broken parser path behind #4989 reduced those descriptions
to a literal `>` or `|`.
> - The earliest contributor fix for that bug was PR #5046, so this
branch keeps that PR as the canonical merge target instead of replacing
it.
> - Follow-up work from #5071 and #8258 was then transplanted onto this
earlier branch so the final PR preserves contributor credit while still
shipping the strongest complete fix.
> - The resulting change fixes block-scalar parsing in the shared
frontmatter path, aligns server company-skill imports with that shared
parser, and prevents already-stale stored markers from rendering as junk
in the UI.

## Linked Issues or Issue Description

- Fixes #4989
- Refs #2863
- Refs #788
- Related superseded PRs: #5071, #8258

## What Changed

- Kept the original PR #5046 server-side company-skill fix and
regression coverage as the base branch history.
- Added the missing YAML chomping and indicator hardening explored
further in #5071.
- Moved frontmatter parsing to the shared parser path so
`packages/shared`, `packages/skills-catalog`, and server company-skill
imports stay aligned.
- Added UI summary sanitization and fallback handling so stale stored
`>` / `|` values no longer render as visible junk in company-skill
cards.
- Added regression coverage for shared frontmatter parsing,
skills-catalog parsing, company-skill imports, and stale-summary
fallback behavior.

## Verification

- Passed locally: `pnpm exec vitest run
packages/shared/src/frontmatter.test.ts
packages/skills-catalog/src/frontmatter.test.ts
server/src/__tests__/company-skills.test.ts
ui/src/lib/company-skill-summary.test.ts`
- Passed locally: `pnpm --filter @paperclipai/shared typecheck`
- Passed locally: `pnpm --filter @paperclipai/skills-catalog typecheck`
- Not fully runnable in this worktree: `pnpm --filter
@paperclipai/server typecheck` currently fails in `packages/plugins/sdk`
before reaching server code because local workspace `node_modules` type
deps are missing (`TS2688` for `node` / `react`).
- GitHub Actions / PR checks are rerunning on PR #5046 head
`005290b7557725abf748d00f36dd24ea0d919aba`.

## Risks

- Medium-low risk: the fix now touches shared parser code, server
company-skill imports, and UI fallback display rather than only the
server import path.
- The parser is still intentionally narrower than a full YAML
implementation; this change focuses on block-scalar correctness and the
stale-description rendering path relevant to #4989 / #2863.
- This branch intentionally supersedes narrower overlapping work from
#5071 and duplicate work from #8258 once the survivor PR is green.

> 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-based coding agent with local shell and
code-editing tools 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 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
- [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: Devin Foley <devin@paperclip.ing>
2026-06-18 12:01:11 -07:00
Svetlana Zolotenkova 5320a44088 Guard codex_local agents from shared OpenAI key (#8272)
## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies.
> - The `codex_local` adapter runs local Codex CLI processes and builds
their environment from persisted agent config plus host process env.
> - A host-level `OPENAI_API_KEY` or shared Codex auth home can silently
make new agents spend through shared credentials.
> - Existing agents can be repaired manually, but new and updated agents
need a persistent guard at the agent configuration boundary.
> - This pull request isolates new and updated `codex_local` agents with
per-agent `CODEX_HOME` and an empty `OPENAI_API_KEY` override.
> - The benefit is that future agent creation or adapter updates cannot
silently fall back to shared OpenAI credentials.

## Linked Issues or Issue Description

Paperclip work item: [ZOL-5477](/ZOL/issues/ZOL-5477).

No matching GitHub issue exists, so the bug is described inline
following `.github/ISSUE_TEMPLATE/bug_report.yml`.

**Pre-submission checklist**

- [x] I have searched existing open and closed issues and this is not a
duplicate.
- [x] I am on the latest `master` commit for this PR branch.
- [x] I have confirmed the error originates in Paperclip's `codex_local`
adapter configuration boundary, not in a provider outage.

**What happened?**

New or updated `codex_local` agents could inherit a host-level
`OPENAI_API_KEY` or use a shared Codex home when their adapter config
did not explicitly isolate those values. That made it possible for
future agents or manual adapter edits to silently fall back to shared
OpenAI credentials.

**Expected behavior**

Creating, hiring, or updating a `codex_local` agent should either
persist isolated per-agent configuration or reject unsafe shared Codex
home configuration with a clear 422 response. The guard must not print
secret values.

**Steps to reproduce**

1. Create or update a `codex_local` agent without an explicit
`adapterConfig.env.OPENAI_API_KEY` override.
2. Run it on a host where the Paperclip server process has
`OPENAI_API_KEY` set.
3. Observe that the adapter process can inherit the host key unless
Paperclip persists a blocking empty override.
4. Set `adapterConfig.env.CODEX_HOME` to a shared path such as
`~/.codex` or the company-level `codex-home`.
5. Observe that the old code allowed the shared auth home instead of
returning a validation error.

**Paperclip version or commit**

- Reproduced by inspection against `master` before this PR.

**Deployment mode**

- Local dev / self-hosted server with `codex_local` agents.

**Installation method**

- Built from source.

**Agent adapter(s) involved**

- Codex.

**Database mode**

- Not database-related.

**Access context**

- Board and agent configuration paths.

**Relevant logs or output**

- No secret-bearing logs included.

**Relevant config**

- Unsafe shape: missing `adapterConfig.env.OPENAI_API_KEY`, or shared
`adapterConfig.env.CODEX_HOME`.
- Fixed shape: per-agent `CODEX_HOME` plus empty `OPENAI_API_KEY`
override.

**Additional context**

Related PR search for `codex_local OPENAI_API_KEY CODEX_HOME` found:

- #3681 `fix: preserve managed Codex auth and repo-root env loading`
- #5621 `fix: copy worktree codex auth locally`

Those are adjacent auth-handling changes, but they do not add the agent
create/update guard implemented here.

**Privacy checklist**

- [x] I have reviewed all pasted output for PII, usernames, file paths,
API keys, tokens, company names, and redacted where necessary.

## What Changed

- Added a `codex_local` config guard in agent create, hire, and update
routes.
- The guard assigns `adapterConfig.env.CODEX_HOME` to
`companies/<companyId>/agents/<agentId>/codex-home` when missing.
- The guard persists `adapterConfig.env.OPENAI_API_KEY = ""` when
missing, preventing host env inheritance.
- Shared `CODEX_HOME` values for the company codex-home, host
`$CODEX_HOME`, or `~/.codex` now fail with a 422 error.
- Added route tests for create, hire, update, and rejected shared host
Codex home.
- Updated `codex_local` and development docs to describe the per-agent
home contract.

## Verification

- `pnpm exec vitest run
server/src/__tests__/agent-adapter-validation-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/agent-skills-routes.test.ts`
- `pnpm typecheck`
- `git diff --check upstream/master...HEAD`
- `gh pr list --repo paperclipai/paperclip --state all --search
"codex_local OPENAI_API_KEY CODEX_HOME" --limit 20 --json
number,title,state,url`
- `rg -n "codex|OPENAI_API_KEY|CODEX_HOME|adapter" ROADMAP.md` returned
no roadmap overlap.

## Risks

- Existing legacy `codex_local` agents with shared `CODEX_HOME` will get
a clear 422 when their adapter config is updated until the shared path
is replaced. This is intentional because silent fallback is the bug
being guarded.
- Low migration risk: no database migration and no secret values are
printed or persisted beyond the empty override.

## Model Used

- OpenAI GPT-5.5 Codex, Codex coding-agent session 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 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
- [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

## Paperclip

- Issue: [ZOL-5477](/ZOL/issues/ZOL-5477)
- Owner: Разработчик (`6625498c-66c9-429f-b578-4463ddc3ba16`)
- Status: waiting reviewer
- Next action: merge after approval and green CI

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-18 10:41:00 -07:00
Devin Foley 04173b341d fix: resolve secret refs before sandbox draft probes (#8256)
## Thinking Path

> - Paperclip is the control plane operators use to manage agent
execution environments, including plugin-declared sandbox providers.
> - The failing user path here was `Test draft` for an unsaved sandbox
environment using a schema field marked `format: "secret-ref"`.
> - Saved environments already resolve secret refs before provider use,
but the unsaved probe path was forwarding the selected secret UUID
directly to the provider, which made Novita draft probes fail.
> - Fixing that safely required a probe-only secret resolution path with
explicit actor authorization and audit context, because an unsaved draft
has no persisted environment binding to authorize against.
> - Once that was fixed, CI and review surfaced follow-up hardening
work: preserve actor source through the draft-probe path, prevent late
heartbeat finalization from overwriting already-terminal runs, avoid
duplicate successful-run handoff wakes for comment-driven runs, make SSH
git ref updates tolerate concurrent managed-runtime restores, and keep
the skills catalog build from failing on transient GitHub errors for
pinned references.
> - The result is that Novita draft probes now behave like saved
environments, the new secret access path is constrained and audited, and
the PR is green end-to-end with Greptile at 5/5.

## Linked Issues or Issue Description

No matching public GitHub issue was found after searching open and
closed Paperclip issues for `novita`. Related PR search found
[#8255](https://github.com/paperclipai/paperclip/pull/8255), but it
addresses Novita/dev-SDK linking rather than this draft probe bug.

Bug summary:
- What happened?
When a board user configured a sandbox environment backed by a
schema-driven plugin provider such as Novita, selecting an existing
company secret for `apiKey` and clicking `Test draft` failed because the
probe received the secret UUID instead of the resolved secret value.
- Expected behavior
`Test draft` should resolve secret-ref fields before calling the
provider probe, just like the saved runtime path does.
- Steps to reproduce
  1. Open `Company Settings -> Environments`.
2. Create or edit a `Sandbox` environment using a provider with a
`format: "secret-ref"` field such as `Novita Agent Sandbox`.
  3. Select an existing company secret for `apiKey`.
  4. Click `Test draft`.
  5. Observe the probe failure before this patch.
- Paperclip version or commit
Reproduced on a local `master` dev checkout; fixed and verified on
branch commit `ed982d0c0`.
- Deployment mode
  Local dev (`pnpm dev`).
- Installation method
  Built from source (`pnpm dev` / `pnpm build`).
- Agent adapter(s) involved
Not adapter-specific in the core bug path; affects schema-driven sandbox
provider plugins such as Novita.
- Database mode
  Not database-related.
- Access context
  Board (human operator).
- Node.js version
  `v25.6.1`.
- Operating system
  `macOS 15.7.4`.
- Relevant logs or output
The user-visible failure was `Novita sandbox probe failed` during `Test
draft`.

## What Changed

- Resolved schema-marked secret-ref fields during unsaved sandbox
environment probes by adding a dedicated probe-time secret resolution
path in `environment-config.ts`.
- Passed `companyId` plus the full authenticated actor context into the
draft probe normalization route so secret resolution stays
company-scoped, authorized, and auditable.
- Hardened ephemeral secret resolution so unsaved probes require
`secrets:read`, preserve the original actor source (`local_implicit`,
`agent_jwt`, etc.), and emit usable audit metadata.
- Added a conditional heartbeat run-status update so late adapter
completions cannot overwrite runs that were already cancelled or
otherwise terminal.
- Skipped successful-run handoff synthesis for comment-driven wakes,
which removes the extra wake/run that was breaking
`heartbeat-comment-wake-batching`.
- Retried managed-runtime SSH git ref updates on concurrent ref-lock
races instead of failing the restore path.
- Reused the previous skills-catalog manifest entry when a pinned GitHub
reference fails with a recoverable transient error during CI catalog
generation.
- Added focused regression coverage for the draft probe, ephemeral
secret access, heartbeat handoff behavior, SSH ref-lock races, and
catalog fallback behavior.

## Verification

- `pnpm vitest run server/src/__tests__/environment-routes.test.ts`
- `pnpm vitest run server/src/__tests__/secrets-service.test.ts`
- `pnpm vitest run
server/src/__tests__/heartbeat-comment-wake-batching.test.ts`
- `pnpm vitest run
server/src/services/recovery/successful-run-handoff.test.ts`
- `pnpm vitest run
server/src/__tests__/openclaw-gateway-adapter.test.ts`
- `pnpm exec vitest run packages/adapter-utils/src/ssh-fixture.test.ts
-t "merges concurrent remote commits through the managed runtime restore
path"`
- `pnpm exec vitest run
packages/skills-catalog/src/catalog-builder.test.ts`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/skills-catalog build`
- `pnpm --filter @paperclipai/adapter-utils build`
- `gh pr checks 8256`
- Manual/live validation: the same fix was cherry-picked into the
running local dev checkout and the user re-tested the Novita `Test
draft` flow successfully after the server restart.

## Risks

- Low risk: the Novita-specific user-facing fix is isolated to unsaved
sandbox draft probes for plugin schema fields marked `format:
"secret-ref"`.
- The new ephemeral secret resolution path is intentionally stricter
than the original broken behavior; regressions would most likely show up
as denied draft probes rather than accidental secret exposure.
- The heartbeat, SSH, and catalog changes are all defensive; if they
regress, they should affect test/CI orchestration paths rather than
persisted company data.

## Model Used

- OpenAI Codex Local (`codex_local` in Paperclip). The runtime does not
expose the exact backend model ID in agent metadata. GPT-5-class coding
model with shell/tool use, repository editing, test execution, GitHub
review handling, and issue-thread coordination.

## 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
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] 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>
2026-06-18 07:48:09 -07:00
Devin Foley 93291df5c8 fix(plugins): move dev SDK linking out of plugin postinstall scripts (#8255)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandbox-provider plugins (cloudflare, daytona, e2b, exe-dev,
kubernetes, modal, novita) and `plugin-workspace-diff` are published as
standalone npm packages, but during local dev they need the in-repo
`@paperclipai/plugin-sdk` symlinked in
> - Each of these plugins shipped a `postinstall` lifecycle script that
traversed *out* of its own package directory (`node
../../../../scripts/link-plugin-dev-sdk.mjs`) to do that linking
> - The publishable manifest is built by a `prepack` whitelist that
drops the `scripts` field, so npm consumers don't see the postinstall
today — but that safety property depends entirely on `prepack` running
on every publish. A publish that skips lifecycle scripts would ship a
tarball whose postinstall escapes its package directory at consumer
install time
> - This pull request removes the escape-the-package-dir lifecycle
script from every plugin source manifest and moves the dev linking to a
single root-level postinstall that iterates the excluded plugin
directories itself
> - The benefit is that plugin tarballs can no longer carry an
install-time script that reaches outside their own directory, regardless
of whether `prepack` runs

## Linked Issues or Issue Description

This is a follow-up hardening change flagged during review of the Novita
sandbox provider PR (#7595).

**Problem (security):** Excluded plugin packages each carried
`"postinstall": "node ../../../../scripts/link-plugin-dev-sdk.mjs"`. The
relative path traverses outside the package root. Today the published
manifest is sanitized by a `prepack` whitelist that drops `scripts`, so
consumers are unaffected in the normal publish path. The risk is that
this is a defense-in-depth gap: if a publish ever skips lifecycle
scripts (e.g. `npm publish --ignore-scripts` is *not* used, or a tool
publishes the raw manifest), the tarball would ship a postinstall that
runs out-of-tree code at the consumer's install time.

## What Changed

- Added a single root `package.json` `postinstall`: `node
scripts/link-plugin-dev-sdk.mjs`.
- Rewrote `scripts/link-plugin-dev-sdk.mjs` to iterate the excluded
plugin directories itself (`packages/plugins/sandbox-providers/*` + the
orchestration smoke example) instead of relying on each plugin to invoke
it from its own cwd. Preserves both prior behaviors: leave a real
installed SDK dir alone, and skip when already correctly symlinked
(idempotent).
- Removed `scripts.postinstall` from all 7 sandbox-provider plugins
(cloudflare, daytona, e2b, exe-dev, kubernetes, modal, novita).
- Removed `scripts.postinstall` from `plugin-workspace-diff` (a pnpm
workspace member — pnpm already links the SDK, so the script was a no-op
there).

## Verification

- `node scripts/link-plugin-dev-sdk.mjs` from repo root: links the SDK
into the excluded plugins and reports skipped (already-linked) dirs;
re-running is idempotent.
- `grep -r "link-plugin-dev-sdk" packages/plugins/*/package.json
packages/plugins/sandbox-providers/*/package.json` returns no matches —
no plugin source manifest references the linker any longer.
- All affected `package.json` files re-validated as parseable JSON.

## Risks

Low risk. Dev-only tooling: the linker only runs at the repo root during
local install and only touches `node_modules/@paperclipai/plugin-sdk`
symlinks inside excluded plugin dirs. No change to published plugin
behavior or runtime code. Worst case if the root postinstall failed to
run, local dev of an excluded plugin would not find the SDK symlink —
easily re-run manually.

## Model Used

Claude Opus (claude-opus-4-8), extended reasoning, with tool use / code
execution in an agentic coding harness.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues 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 (added
`scripts/link-plugin-dev-sdk.test.js`, wired into
`test:release-registry`)
- [ ] If this change affects the UI, I have included before/after
screenshots (N/A — no UI change)
- [ ] I have updated relevant documentation to reflect my changes (N/A)
- [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>
2026-06-18 07:45:53 -07:00
Alex b18669452f Add Novita sandbox provider plugin (#7595)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip already separates agent adapters from execution
environments, so agents can run locally, over SSH, or through sandbox
providers.
> - Sandbox provider plugins let Paperclip add new cloud runtimes
without changing each agent adapter.
> - Novita Agent Sandbox is a cloud runtime for AI agent workloads with
isolated filesystems, command execution, templates, timeout controls,
and pause/resume behavior.
> - Paperclip currently has sandbox provider examples for Daytona and
Cloudflare, but not Novita.
> - This pull request adds a Novita sandbox provider plugin using the
existing provider-plugin lifecycle.
> - The benefit is that Paperclip users can run existing adapters such
as Codex, Claude, Gemini, OpenCode, Cursor, or ACPX inside Novita Agent
Sandbox environments.

## Linked Issues or Issue Description

Fixes #7596

## What Changed

- Added `packages/plugins/sandbox-providers/novita` as a standalone
sandbox provider plugin package.
- Registered provider key `novita` with `kind: "sandbox_provider"` and
`environment.drivers.register` capability.
- Implemented Novita environment lifecycle hooks: validate config,
probe, acquire lease, resume lease, release lease, destroy lease,
realize workspace, and execute commands.
- Added config support for `apiKey`, `domain`, `template`,
`requestedCwd`, `timeoutMs`, `requestTimeoutMs`, `secure`, `autoPause`,
and `reuseLease`.
- Added README documentation for setup, configuration, and lifecycle
behavior.
- Added tests for manifest shape, config parsing, safe shell command
wrapping, stdin delimiter safety, and env-key validation.

## Verification

From `packages/plugins/sandbox-providers/novita`:

- `pnpm typecheck`
- `pnpm test`

The tests avoid live Novita API calls and cover the provider's static
contract and command-wrapping behavior. Live end-to-end verification
requires a Paperclip instance with the plugin installed and a Novita API
key configured as either a Paperclip secret or `NOVITA_API_KEY` in the
worker environment.

## Risks

- This adds a new direct dependency on the Novita Sandbox JS SDK
(`novita-sandbox`). Socket/Snyk should review the package as part of
normal dependency checks.
- The implementation relies on Novita SDK command execution semantics;
live provider behavior should be verified with a real Novita sandbox
before marking the plugin production-ready.
- `reuseLease` maps Paperclip release behavior to Novita `betaPause()`.
If pause is unavailable for a selected template, the plugin falls back
to best-effort kill during release.
- Low migration risk for existing users because this is a new standalone
provider plugin and does not change existing adapters or built-in
providers.

> 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 via Codex CLI, with repository file access, shell
command execution, GitHub CLI/API usage, and local TypeScript/Vitest
verification. Web and local documentation context were used for Novita
Sandbox SDK/API behavior.

## 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
- [ ] If this change affects the UI, I have included before/after
screenshots
- [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>
Co-authored-by: Devin Foley <devin@paperclip.ing>
2026-06-17 18:34:12 -07:00
Devin Foley f3e01c63bd fix(environments): partial unique index to dedup managed sandbox rows (#8247)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The `server/services/environments.ts` module lazily provisions a
managed Kubernetes sandbox environment for each company on first
heartbeat. Idempotency relies on `ensureKubernetesEnvironment` returning
the single managed row per company.
> - The `(company_id, driver)` unique index in the `environments` schema
is partial on `driver='local'` only, so two concurrent callers (e.g.
simultaneous first heartbeats from a freshly synced tenant) can both
insert a `driver='sandbox'` row before either sees the other.
> - The function tried to converge after the race by re-reading, picking
the oldest managed row as winner, and deleting the loser. Under
autocommit + read-committed, each post-insert SELECT is a fresh snapshot
— A may not see B's row, B may not see A's, so both pick their own and
neither deletes. Two rows survive.
> - The same race fired in CI as `expected 2 to be 1` at
`environment-service.test.ts:293`, gating multiple unrelated PRs on
retries.
> - This pull request encodes the operator-level invariant ("at most one
Paperclip-managed sandbox row per company") at the DB layer with a
partial unique index, then switches `ensureKubernetesEnvironment` to
`INSERT … ON CONFLICT DO NOTHING` keyed on that index. Losers re-read
the surviving row.
> - The benefit is the race is impossible by construction — no
application-side convergence loop, no test flake, and any future
`ensureXyzSandboxEnvironment` that sets `managedByPaperclip=true`
inherits the invariant for free.

## Linked Issues or Issue Description

Paperclip issue: PAPA-783 — implement managed-sandbox dedup fix (phase 2
of the approved plan on the parent flake-investigation issue).

This is the phase-2 fix for the flaky `environmentService > deduplicates
concurrent managed Kubernetes environment creation` test introduced by
`4ad94d0bd` (PR #7938). Failing CI runs since then on at least PRs
#7595, #8233, #8215, #8212. The plan was reviewed and approved on the
parent issue before implementation.

Closely related (not duplicates):
- PR #7938 — introduced the test and the in-process convergence loop
being replaced here.
- PR #7595, PR #8233, PR #8215, PR #8212 — downstream PRs affected by
the flake; one of them will be rebased onto this fix as the acceptance
gate.

## What Changed

- `packages/db/src/schema/environments.ts`: added
`environments_company_managed_sandbox_idx`, a partial unique index on
`(company_id) WHERE driver='sandbox' AND
(metadata->>'managedByPaperclip')::boolean = true`. The umbrella
`managedByPaperclip` predicate covers any current or future
Paperclip-managed sandbox flavor without needing a new index per
provider.
- `packages/db/src/migrations/0102_managed_sandbox_dedup_index.sql`:
one-shot dedup `DELETE` keeping the oldest managed-sandbox row per
`company_id` (scoped to `driver='sandbox' AND managedByPaperclip=true`),
`RAISE NOTICE` if any duplicates were removed, then `CREATE UNIQUE INDEX
IF NOT EXISTS environments_company_managed_sandbox_idx`. `CONCURRENTLY`
is omitted because the codebase's migration runner wraps each file in a
transaction (see `applyPendingMigrationsManually`); the table holds 1–3
rows per company, so the short lock is acceptable and consistent with
every other migration in the repo.
- `server/src/services/environments.ts`: `ensureKubernetesEnvironment`
now uses `INSERT … ON CONFLICT DO NOTHING` keyed on the new index. On
conflict it re-reads the surviving managed-sandbox row and returns it.
Drops the post-insert convergence (re-read by `createdAt ASC, id ASC`,
delete the loser) and the trailing comment that flagged "until a partial
unique index is added via migration" as the proper long-term fix.
- Unused `asc` import removed from
`server/src/services/environments.ts`.

## Verification

Local (matches the success criteria in the issue body):

```
$ cd server
$ passes=0; for i in $(seq 1 20); do
    pnpm vitest run src/__tests__/environment-service.test.ts -t "deduplicates concurrent" \
      && passes=$((passes+1)) || break
  done; echo "$passes/20"
20/20

$ passes=0; for i in $(seq 1 10); do
    pnpm vitest run src/__tests__/environment-service.test.ts \
      && passes=$((passes+1)) || break
  done; echo "$passes/10"
10/10
```

Adversarial fan-out stress (temporarily bumped `Array.from({ length: 8
}, …)` to `length: 32` on both dedup tests; reverted before commit):

```
$ # both dedup tests fan-out-of-32, 10 iterations
10/10
```

Two ensure paths exist in the parent plan, but only
`ensureKubernetesEnvironment` is on `master`.
`ensureManagedSandboxEnvironment` (referenced by the approved plan as
commit `dce9a9622`) lives only on an unmerged feature branch, not
master. The plan's helper-extraction and symmetric dedup test for that
path are deferred to whichever PR lands the second ensure path — it
inherits the same DB invariant by setting `managedByPaperclip=true`.
Discrepancy was flagged on the issue thread before implementation.

Typecheck:

```
$ pnpm -C server typecheck
ok
```

## Risks

Low risk.

- **Migration safety.** `IF NOT EXISTS` on the index makes the migration
idempotent. The dedup `DELETE` is bounded to rows matching the
managed-sandbox predicate; in production this should be a no-op (no race
has been reported in the wild — only in CI). On dev/CI DBs that already
accumulated duplicates, the migration emits a `NOTICE` reporting the
count.
- **No `CONCURRENTLY`.** The migration runner wraps each `.sql` file in
a transaction, which is incompatible with `CREATE INDEX CONCURRENTLY`.
The `environments` table holds 1–3 rows per company and the row count is
bounded by company count; the short ACCESS EXCLUSIVE during `CREATE
UNIQUE INDEX` is acceptable here and matches every other index migration
in the repo.
- **Predicate scope.** The partial index predicate matches exactly the
rows that `ensureKubernetesEnvironment` writes (`driver='sandbox'` with
`metadata.managedByPaperclip=true`). Tenant-created sandbox envs (via
`svc.create`) do not set this marker and are not covered — no false
positives, no surprise constraint violations on unrelated inserts.

## Model Used

Claude (Anthropic), `claude-opus-4-7`. Tool use: code edit + bash +
filesystem search; no extended-thinking mode.

## 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
- [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: Paperclip <noreply@paperclip.ing>
2026-06-17 16:09:45 -07:00
Devin Foley 4c26b984a7 fix(routines): detect variables when underscores are markdown-escaped (#8056)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Routines let users templatize titles and instructions with
`{{name}}` placeholders that get prompted for at run time
> - A user reported that `{{pr_url}}` typed into a routine description
was never detected as a variable, while camelCase placeholders worked
> - Root cause: the routine description is edited via MDXEditor (WYSIWYG
markdown). On save its serializer (`mdast-util-to-markdown`) defensively
escapes intraword underscores, so a user-typed `{{pr_url}}` is stored as
`{{pr\_url}}`. The variable matcher only accepted `[A-Za-z0-9_]` inside
the placeholder, so the backslash broke the match and the variable was
silently dropped
> - This PR widens the matcher to tolerate `\_` and unescapes it back to
`_` on capture, in both extraction and interpolation, so a single name
is detected and resolved regardless of whether the source markdown was
hand-typed or round-tripped through a WYSIWYG editor
> - The benefit is that snake_case placeholders behave the same in the
routine UI and the executor, removing a silent failure mode

## Linked Issues or Issue Description

Refs PAPA-771 — "Fix example for variable_name". Initially scoped as a
docs-copy fix (change the example to camelCase), but investigation
showed the underlying bug was that the parser could not see the variable
when it was authored as `{{pr_url}}` in a WYSIWYG-edited description.
This PR addresses the bug directly so snake_case names work as users
expect. (Supersedes the now-closed PR #8054 — same commit, fresh branch
so the diff is visible.)

## What Changed

- `packages/shared/src/routine-variables.ts`:
- Widened the `ROUTINE_VARIABLE_MATCHER` regex to accept `\_` inside
placeholder names (in addition to `[A-Za-z0-9_]`)
- Added `unescapeRoutineVariableName` and applied it on capture in both
`extractRoutineVariableNames` and `interpolateRoutineTemplate`, so the
looked-up variable name is normalized regardless of the markdown escape
- `packages/shared/src/routine-variables.test.ts`:
- Added tests covering plain snake_case (`{{pr_url}}`), markdown-escaped
(`{{pr\_url}}`), multi-underscore (`{{pr\_url\_v2}}`),
sync-with-template, and interpolation

## Verification

- `pnpm --filter @paperclipai/shared exec vitest run routine-variables`
→ 13 passed (4 new), confirming both plain and escaped underscore
placeholders are detected and interpolated as the same variable name
- Reproduced the root cause out-of-band with `mdast-util-from-markdown`
+ `mdast-util-to-markdown` to confirm the editor's serializer is the
source of the `\_` escape

## Risks

- Low. Change is confined to `packages/shared/src/routine-variables.ts`;
the regex is strictly more permissive in a tightly bounded way (only
`\_` is newly allowed) and the captured name is normalized.
`isValidRoutineVariableName` is unchanged, so stored names are still
strict identifiers.

## Model Used

- Claude (Anthropic), `claude-opus-4-7` (Opus 4.7), 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 — only my own closed #8054 supersedes/duplicates this one.
Open PRs touching `routine-variables.ts` (#7184, #7186, #6993, #7187)
are all for unrelated concerns (server-side PATCH persistence, GH#6525)
- [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 — `routine-variables` suite
passes (13/13)
- [x] I have added or updated tests where applicable — 4 new tests
covering snake_case + escaped forms
- [ ] If this change affects the UI, I have included before/after
screenshots — no UI change in this PR
- [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 — pending CI re-run after the
push for the Greptile blank-line nit
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
initial review was 5/5; the one P2 (missing blank line) is addressed in
the latest push
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-12 13:02:12 -07:00
Jannes Stubbemann 05bcd3ce84 feat(security): plugin tables get company_id FK for tenant isolation (#5865)
## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies
> - The plugin subsystem persists state into four tables
(`plugin_entities`, `plugin_job_runs`, `plugin_logs`,
`plugin_webhook_deliveries`) and those rows currently have no notion of
an owning tenant — so company-deletion doesn't cascade plugin state, and
operators have no way to query "what does this plugin own for company
X?"
> - The fix is one thin slice of tenant-isolation hygiene that doesn't
change any external API: add a nullable `company_id` FK with `ON DELETE
CASCADE` to the four plugin tables, index it, and scope the
`plugin_entities` external-id uniqueness per-tenant
> - The benefit is plugin-row tenant attribution + cascade cleanup, with
zero impact on single-tenant local-first deploys (`NULL` continues to
mean instance-scope)

> **Rebase note (scope narrowed):** This PR originally also hardened the
schedulers (`heartbeat.tickTimers` / `resumeQueuedRuns` /
`enqueueWakeup` and `routines.tickScheduledTriggers`) to skip archived
companies. That half has since landed on `master` via #7478 (`93206f73`,
"Stop archived companies from waking agents") with a stricter
implementation (`status != 'active'` plus a skipped-request audit row).
On rebase those changes were dropped as redundant — `heartbeat.ts` and
`routines.ts` are now identical to `master`, and the scheduler-specific
tests were removed. **This PR is now DB-only.**

## Linked Issues or Issue Description

No existing issue covers this directly — problem described in-PR:

- Four plugin tables (`plugin_entities`, `plugin_job_runs`,
`plugin_logs`, `plugin_webhook_deliveries`) persist rows with no notion
of an owning tenant.
- Company deletion therefore does not cascade plugin state, and
operators have no way to query "what does this plugin own for company
X?"
- One thin slice of tenant-isolation hygiene fixes this without changing
any external API: a nullable `company_id` FK with `ON DELETE CASCADE`,
an index, and per-tenant scoping of the `plugin_entities` external-id
uniqueness.
- Part of the multi-tenant hardening initiative alongside #3967
(cross-tenant 404 oracle) and #5864 (per-company JWT keys).

## What Changed

**Schema (`packages/db/src/schema/plugin_*.ts`):**
- Nullable `companyId` FK with `onDelete: "cascade"` added to
`plugin_entities`, `plugin_job_runs`, `plugin_logs`,
`plugin_webhook_deliveries`.
- A btree index on each new `company_id` column (`<table>_company_idx`).
- `plugin_entities_external_idx` rescoped from `(plugin_id, entity_type,
external_id)` to `(company_id, plugin_id, entity_type, external_id)` and
switched to `UNIQUE … NULLS NOT DISTINCT` so instance-scope rows
(`company_id IS NULL`) keep their dedup guarantee while tenants get
their own namespace.

**Migration:**
- `0095_plugin_company_id_tenant_isolation.sql` — 14 statements: 4
column adds + 4 FK constraints (`ON DELETE CASCADE`) + 4 indexes +
drop/recreate of the external-id unique constraint.
- Journal entry + regenerated `0095_snapshot.json`.

**Tests:**
- `server/src/__tests__/plugin-tenant-isolation.test.ts` — `NULL`
preserves backward compat; `CASCADE` on company delete across all four
tables; per-tenant external-id namespacing; NULL-NULL collision rejected
(`NULLS NOT DISTINCT`).

## Verification

- `pnpm --filter @paperclipai/db run check:migrations` — pass.
- `pnpm --filter @paperclipai/db typecheck` (`tsc`) — pass.
- `vitest run plugin-tenant-isolation` — **4/4 pass** (embedded Postgres
applies `0095` and exercises cascade + NULLS NOT DISTINCT).

## Notes

- **Clean snapshot, no drift.** The earlier revision of this PR shipped
a ~17.6k-line meta snapshot that was almost entirely pre-existing drift.
On rebase the migration was renumbered (the old `0090_brainy_darkhawk`
collided with `master`'s `0090_resource_memberships … 0094`) and
regenerated from the current `master` baseline via `drizzle-kit
generate`. The result is a 14-statement migration containing **only**
the plugin-table changes — no unrelated drift.
- **Backward-compatible.** `NULL company_id` continues to mean
instance-scope (cron jobs, public webhooks). No new env vars, no API
surface change. Single-tenant local-first deploys unaffected.

## Risks

- **Migration is additive and nullable** — `0095` adds nullable
`company_id` columns, FK constraints, and indexes; existing rows stay
valid (`NULL` keeps meaning instance-scope) and no backfill is required.
- **`ON DELETE CASCADE` is a behavioral change**: deleting a company now
removes its plugin rows across all four tables. Intended (it is the
point of the PR), but operators relying on plugin rows surviving company
deletion would be affected. Covered by the cascade tests.
- **Uniqueness semantics change on `plugin_entities`**: the external-id
constraint is rescoped per-tenant and switched to `UNIQUE … NULLS NOT
DISTINCT`, so two instance-scope rows (`company_id IS NULL`) with the
same external id are now rejected instead of coexisting. Covered by the
NULL-NULL collision test.
- **No API surface change, no new env vars.** Single-tenant local-first
deploys unaffected.

(Section added retroactively to match the PR template; distilled from
the What Changed / Notes sections above.)

## Model Used

Same authoring setup as #5864 (same series, same day): Claude Opus 4.7
(1M context), extended thinking mode. (Section added retroactively.)

## Checklist

- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] Tests run locally and pass (plugin-tenant-isolation 4/4)
- [x] `check:migrations` + db typecheck pass
- [x] No UI changes
- [x] Migration carries only the intended changes (no snapshot drift)
- [x] Scheduler half dropped as superseded by #7478

Part of the multi-tenant hardening initiative — see also #3967
(cross-tenant 404 oracle) and #5864 (per-company JWT keys).

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-12 10:17:19 -07:00
Ismaël O. 3b7c42be86 fix(openclaw-gateway): complete and stabilize OpenClaw Gateway integration (#2322)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The `openclaw_gateway` adapter is how operators wire Paperclip
agents to an OpenClaw gateway over WebSocket
> - The adapter UI previously only exposed a handful of config fields in
edit mode; many timeout / auth / session-routing knobs were unreachable
through the form
> - The serializer also forgot to inject the configured `authToken` into
the `x-openclaw-token` header, and the server-side execute path lacked
retries on transient gateway errors and an `OPENCLAW_TOKEN` env fallback
> - This pull request exposes the full set of config fields in both
create and edit modes, fixes the serializer, hardens the server-side
execute path, and pins the existing default request timeouts (120s /
120000ms) — see the dedicated commit and the new unit tests
> - The benefit is operators can configure and reconfigure an
`openclaw_gateway` agent end-to-end through the UI, with no silent
change to the defaults documented in the adapter README and
`doc/ONBOARDING_AND_TEST_PLAN.md`

## Linked Issues or Issue Description

Closes #414
Closes #1901
Closes #2309

## What Changed

- **UI**: Removed the `!isCreate` guard so all `openclaw_gateway` config
fields are visible in both create and edit modes (`authToken`,
`agentId`, `sessionKeyStrategy`, `sessionKey`, `timeoutSec`,
`waitTimeoutMs`, `disableDeviceAuth`, `autoPairOnFirstConnect`, `role`,
`scopes`, `paperclipApiUrl`, `headersJson`, `payloadTemplate`,
`runtimeServices`).
- **Serialization**
(`packages/adapters/openclaw-gateway/src/ui/build-config.ts`): inject
`authToken` into headers as `x-openclaw-token`; apply safe defaults on
create (`timeoutSec=120`, `waitTimeoutMs=120000`,
`sessionKeyStrategy="issue"`, `role="operator"`,
`scopes=["operator.admin"]`).
- **Backend**
(`packages/adapters/openclaw-gateway/src/server/execute.ts`): add
`OPENCLAW_TOKEN` env-var fallback for `authToken`, retry logic (max 2
retries with backoff for transient gateway errors), session-key prefix
`agent:{agentId}:{sessionId}` when `agentId` is configured.
- **Defaults restoration** (dedicated commit): an earlier revision of
this PR lowered the default request timeouts to `60` / `30000`. The
current branch restores the historical `timeoutSec=120` /
`waitTimeoutMs=120000` defaults that match the values documented in
`packages/adapters/openclaw-gateway/src/index.ts`,
`src/server/execute.ts` on master, and the worked example in
`doc/ONBOARDING_AND_TEST_PLAN.md`.
- **Tests** (new):
`packages/adapters/openclaw-gateway/src/ui/build-config.test.ts` pins
the documented timeout and identity defaults so the silent-halve
regression cannot recur.

## Verification

- `pnpm --filter @paperclipai/adapter-openclaw-gateway typecheck`
- `pnpm typecheck` (root)
- Manual: create a new `openclaw_gateway` agent — all fields visible,
defaults populate as documented.
- Manual: edit an existing `openclaw_gateway` agent — every field
round-trips correctly and saves.
- Manual: unset `authToken` in the form and set `OPENCLAW_TOKEN` env var
— adapter picks up the env-var fallback.
- Manual: simulate a transient gateway error — execute retries up to 2
times with backoff before failing.

## Risks

- Low risk. Surface area is one adapter, behind explicit operator
configuration. The defaults change in this PR is a restoration of values
that already exist on master and in the adapter docs, so no production
agent sees a behavioral shift relative to the prior release. Field
exposure in edit mode is purely additive — existing values are preserved
on save.

## Model Used

- Provider/model: Claude (Anthropic) — `claude-opus-4-7`
- Mode: standard tool use, no extended thinking
- Capability notes: code execution + repository file edits via Claude
Code

## Cross-references and status (maintainer)

Closes #414
Closes #1901
Closes #2309

## 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
- [ ] If this change affects the UI, I have included before/after
screenshots
- [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 Bot <bot@paperclip.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-11 23:31:39 -07:00
Jannes Stubbemann 482f64e343 fix(plugin-kubernetes): resolve sandbox pod by exact name (controller labels pods with sandbox-name-hash, not sandbox-name) (#7982)
## Thinking Path

Production e2e on the merged #5790 plugin failed on every fresh lease
with "Failed to install the adapter runtime command" for a harness that
was present in the runtime image. Tracing the lease showed the first
exec resolved no pod: the exact-label fallback added during the #5790
review queries `agents.x-k8s.io/sandbox-name=<name>`, but the
kubernetes-sigs agent-sandbox controller labels pods only with
`agents.x-k8s.io/sandbox-name-hash` (see `sandboxLabel` in its
`controllers/sandbox_controller.go`) and NAMES the backing pod exactly
after the Sandbox CR. The selector matches nothing, `findPodForSandbox`
returns null, execute returns "podName could not be resolved", and
adapter-utils misreports it as a missing runtime command.

## What Changed

Between the `status.podName` read and the label fallback, try an
exact-name pod GET (`readNamespacedPod({namespace, name})`). This is
collision-free, so the original review concern (name-prefix matching
execing into a concurrent sandbox's pod) stays honored. A 404 falls
through to the existing full-name label selector for controller versions
that do set such a label. Non-404 errors propagate unchanged.

## Verification

- New unit test pins the controller reality: pod named exactly like the
sandbox, only a `sandbox-name-hash` label, no full-name label; fails
before the fix, passes after.
- Review-feedback round: the primary-path test now asserts the
exact-name GET is never called, and a new test covers non-404 error
propagation (403 rejects, no fallback). 153/153 plugin tests green, tsc
clean.
- Production-verified on our deployment: agent runs were broken on every
fresh lease before this patch and complete end-to-end after it (gVisor
sandbox pool, agent-sandbox controller v0.4.6; verified run with cost
event and agent reply on a fresh tenant).

## Risks

Low: one additional pod GET per first-exec on a fresh lease, only when
`status.podName` is unset. Non-404 errors from the GET propagate
unchanged (now test-pinned).

## Issue

No existing issue; the defect is described in full under Thinking Path
(introduced by the review-round fallback change in #5790, first hit in
production e2e on 2026-06-11).

## Model Used

Claude Fable 5 (claude-fable-5, Claude Code CLI, extended reasoning,
tool use)

## Duplicate search

Searched open and closed PRs for `findPodForSandbox`,
`sandbox-name-hash`, and pod-resolution fixes; no duplicate found.
Related parent: #5790 (introduced the fallback this PR repairs).

## 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 (no UI change)
- [x] I have updated relevant documentation to reflect my changes (code
comments; no doc surface affected)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-11 17:47:17 -07:00
scotttong 6f9801a46b feat(ui): NUX rework behind enableConferenceRoomChat experimental flag — capsule onboarding, conference-room chat, unified composer (#8000)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The first-run experience (onboarding wizard) and the chat surfaces
(conference-room/board chat, task threads, composers) are the product's
front door — they decide whether a new operator understands "hire
agents, give them work, review results" in the first five minutes
> - Today those surfaces feel ticket-y and form-like: the wizard is a
static multi-step form that ends in an anticlimactic "Launch" screen,
the task composer and board chat behave differently from each other, and
agent-feed issue quicklooks misbehave (multiple flyouts open at once,
cards jump on hover)
> - We wanted to iterate toward a conversational, team-centric NUX — but
without risking the workflows of everyone already running Paperclip
> - This PR reworks the NUX behind a new default-OFF
`enableConferenceRoomChat` experimental flag: a capsule-motif onboarding
wizard that builds your team as you answer, a conference-room chat
surface, one shared ChatComposer across surfaces, brand-accurate status
chips, and feed-quicklook fixes — with the pre-existing UI
fork-and-frozen as `*Classic` components that flag-OFF users keep
> - The benefit is a complete, testable modern NUX that anyone can opt
into from Settings → Experimental, with zero default behavior change and
a clean path to either graduate or drop the experiment

## Linked Issues or Issue Description

No pre-existing GitHub issue — feature description per
`feature_request.yml`:

- **Problem / motivation:** Paperclip's onboarding wizard and chat
surfaces grew up as separate ticket-centric forms. New users get a
form-filling experience rather than the feeling of standing up a team;
the board chat and task threads use different composers with different
affordances; the agent feed's issue quicklook can stack multiple
popovers and shifts cards on hover.
- **Proposed solution:** A coherent NUX experiment behind one
experimental flag (`enableConferenceRoomChat`, Settings → Experimental,
default OFF): capsule onboarding wizard with an evolving team capsule,
conference-room chat, unified `ChatComposer`, team-centric copy, brand
status chips, quicklook single-flight fix. Flag-OFF users get the exact
pre-experiment UI via frozen `*Classic` forks, verified by an on/off
parity test matrix.
- **Alternatives considered:** (a) incremental unflagged restyling —
rejected: the changes interlock across surfaces and would drip risk into
every release; (b) a separate app shell / route for the new NUX —
rejected: too much divergence, the flag + classic-fork pattern keeps the
diff reviewable and reversible.
- **Roadmap alignment:** `ROADMAP.md` lists **CEO Chat** ("a
lighter-weight way to talk to leadership agents... should still resolve
to real work objects"). This experiment is groundwork in that direction
(conference-room chat resolves to issues/tasks via the same composer
used in task threads) and does not change the core task-and-comments
model.

Related PRs found in the dedup search (same area, none duplicate this
work — they target the classic wizard, which this PR intentionally
leaves intact and mergeable):

- #5385 — Coach-driven onboarding: conversational entry +
agent-companies package import
- #5378 — Onboarding wizard: reusable adapter picker + probe card
- #6636 — ui(onboarding): friendly error surface + retry for the wizard
- #7005 — fix(onboarding): explicitly await first-task wake
- #2616 — fix: restore workspace directory config in onboarding wizard

## What Changed

- **Experimental flag plumbing** — `enableConferenceRoomChat` in shared
types/validators, server instance-settings service + API, Settings →
Experimental card with explicit enable/disable copy
- **Onboarding wizard** — classic wizard forked and frozen
(`OnboardingWizardClassic`); flag-ON variant is a 5-step capsule wizard
with a persistent evolving `AgentCapsule` (gradient/glow motif),
team-centric reframed copy, and a typing-dots intro (hardened with
fake-timer tests)
- **Conference-room chat** — flag-ON board-chat surface with agent
bubble name/icon headers and copy/vote/timestamp action rows
(`AgentBubbleActionRow`)
- **Unified composer** — shared `ChatComposer` adopted across surfaces;
translucent surface + scroll-mask removal; "Agent mode"/"Plan mode"
relabels; no-assignee confirmation `AlertDialog` (new
`ui/alert-dialog.tsx` primitive); `@task` reference picker +
linkification in mentions
- **Agent feed** — single-flight issue-quicklook store (one popover at a
time), flyouts open to the left, removed hover translate-y jitter
- **Status chips** — brand-accurate task status chips behind the flag
(light/dark, 1px borders per paperclip.ing/brand)
- **Tests** — flag on/off parity matrix across IssueDetail,
NewIssueDialog, Sidebar, wizard, gate components; component tests for
all new pieces
- **Merge with `master`** — one conflict in
`ui/src/components/IssueChatThread.tsx`, resolved by keeping master's
new `AssigneeChip`/`HandoffWakeRow`/`RunStatusBadge` components inside
the flag-gated metadata-row chrome (details in commit `21a5642a`);
post-merge fixes: vitest 4 mock typing in `MarkdownEditor.test.tsx`,
flag hook made safe for provider-less mounts (master's new isolated
component tests)
- **Branch hygiene** — internal design wireframes/mockups stripped
before the PR (they live in the Paperclip issue threads)
- No user-facing documentation changes required: the flag is
intentionally experimental and self-described in the Settings card; no
existing docs reference the affected surfaces

## Verification

- `pnpm run typecheck` — green across the workspace (ui, server, shared,
plugins)
- Full UI suite (`vitest run` in `ui/`, clean worktree at this HEAD):
**1593/1595 passing, 223/224 files** — the 2 remaining failures are in
`src/components/artifacts/ArtifactCard.test.tsx` and **fail identically
on pristine `origin/master`** (pre-existing upstream, unrelated to this
branch)
- Full server suite (`vitest run` in `server/`, same clean worktree):
results in PR checks; flag plumbing covered by instance-settings tests
- Targeted post-merge resolution check: `IssueChatThread`,
`IssueChatThreadSystemNotice`, `IssueDetail`, `Sidebar`,
`ConferenceRoomChatGate`, `OnboardingWizardVariant`, `NewIssueDialog`,
`InstanceExperimentalSettings`, `MarkdownEditor` — 172/172 passing
- Manual walkthrough: flag OFF (default) → onboarding wizard, task
thread, board chat, composer all render the classic UI; flag ON via
Settings → Experimental → capsule wizard, conference-room chat, unified
composer, status chips active
- Screenshots: see below

**Flag on/off screenshots** (committed on this branch under
`screenshots/PR-8000-*`):

| Surface | Flag OFF (classic, default) | Flag ON (experimental) |
| --- | --- | --- |
| Settings → Experimental | ![settings
off](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-settings-experimental-flag-off.png)
| ![settings
on](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-settings-experimental-flag-on.png)
|
| Task thread | ![thread
off](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-task-thread-flag-off.png)
| ![thread
on](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-task-thread-flag-on.png)
|
| Home / nav | ![home
off](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-home-flag-off.png)
| ![home
on](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-home-flag-on.png)
|
| Conference Room (flag-ON only surface) | — | ![conference
room](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-conference-room-flag-on.png)
|

Capsule onboarding wizard walkthrough screenshots (flag ON) are attached
to the Paperclip design/implementation threads; the wizard requires a
fresh instance so it is captured via the e2e harness
(`tests/e2e/nux-phase4-screenshots.spec.ts`).


## Risks

- **Large surface, but gated:** all new behavior sits behind
`enableConferenceRoomChat`, default OFF; flag-OFF rendering is locked by
frozen `*Classic` forks plus an on/off parity test suite
- **Classic forks are frozen at the fork point (`e3aada1d`):** master
features added to the live thread component after that point (assignee
handoff chips, run status badge, composer mention coach) render in the
flag-ON path; the flag-OFF task thread keeps the fork-point behavior
until the experiment graduates (forks deleted) or is dropped (forks
restored as canonical). Called out for reviewer attention.
- **Merge-conflict resolution in `IssueChatThread.tsx`** (commit
`21a5642a`) deserves reviewer eyes: master's new handoff/run-status
components were kept; the base toast-style no-assignee flow remains
replaced by the AlertDialog flow introduced on this branch
- Schema/server changes are additive (one optional boolean instance
setting); no migrations of existing data

## Model Used

- Claude (Anthropic) via Claude Code running in the Paperclip agent
harness (agent: ClaudeCoder)
- Branch implemented across multiple agent sessions on Claude Opus-class
models with extended thinking + tool use (file edits, shell, Playwright
screenshots); merge/PR session model ID as reported by the harness:
`claude-fable-5` (Claude Code CLI)
- All code was agent-authored and board-reviewed through Paperclip issue
threads (plans, wireframes, confirmations) before merging

## 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
- [x] I have updated relevant documentation to reflect my changes (none
required — experimental flag, self-documenting Settings card; noted
above)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green (run 3 on `8af3041a`: all 16
gates SUCCESS, incl. e2e and all 4 serialized-suite shards)
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(re-review verdict: Confidence 5/5, “Safe to merge”; all 4 round-1
findings fixed + confirmed resolved; both summary notes addressed in
`8af3041a`)
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 16:32:55 -05:00
Dotta 1413729a06 Build the Skills Store (#7990)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents increasingly depend on reusable skills, so the control plane
needs a first-class way to browse, inspect, install, version, and attach
those skills.
> - The old skills surface was mostly operational plumbing; it did not
give operators a store-like discovery flow, canonical detail URLs, rich
source/version context, or creation paths.
> - The backend also needed stronger contracts around company skill
metadata, versions, install counts, runtime materialization, and adapter
skill preferences.
> - This pull request builds the Skills Store foundation across DB,
shared contracts, server routes/services, UI, and Storybook.
> - The benefit is a more inspectable, operator-friendly skill workflow
that still preserves company-scoped control-plane boundaries and agent
runtime behavior.

## Linked Issues or Issue Description

No GitHub issue exists for this Paperclip work item. Paperclip task
refs: PAP-10846 and PAP-10921.

Feature request:
Paperclip operators need a single Skills Store experience where company
skills can be discovered, inspected, created, versioned, installed, and
attached to agents without relying on scattered operational screens or
implicit runtime state.

Related PR search:
- Searched GitHub for `Skills Store`, `company skills`, and `skill
detail`.
- Found several open skills-related PRs such as #7809 and #4409, but no
duplicate PR for this end-to-end Skills Store branch.

## What Changed

- Added the Skills Store backend foundation: company skill schema
fields, migrations, shared types/validators, and expanded server skill
routes/services.
- Added skill discovery, category navigation, canonical skill detail
routes, tabs, source attribution, version snapshots/diffs, install count
backfill, and creation flows.
- Updated agent skill preference handling so version selections survive
runtime mention injection and runtime skill materialization honors
pinned versions.
- Preserved unversioned skill assignments as live/current selections
instead of silently pinning them to the current version at assignment
time.
- Added focused regression coverage for company skill routes/services,
route helpers, UI behavior, skill version diffs, and runtime skill
version pins.
- Added Storybook coverage for Skills Store discovery/detail states and
updated the main layout navigation.
- Addressed Greptile findings around version creation races,
soft-deleted comments, fork metadata scoping, GitHub skill directory
fallback, runtime snapshot materialization, shared runtime
skill-selection helpers, and version-assignment semantics.

## Verification

- `pnpm exec vitest run
server/src/__tests__/heartbeat-project-env.test.ts
server/src/__tests__/heartbeat-runtime-skills.test.ts`
- `pnpm exec vitest run
packages/shared/src/validators/company-skill.test.ts`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/company-skills-service.test.ts`
- `pnpm exec vitest run
cli/src/__tests__/company-import-export-e2e.test.ts`
- `pnpm exec vitest run
server/src/__tests__/agent-skills-routes.test.ts`
- `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/company-skills-routes.test.ts
server/src/__tests__/heartbeat-runtime-skills.test.ts`
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts`
- `pnpm exec vitest run ui/src/components/IssueProperties.test.tsx -t
"edits existing custom assignee model options from the properties pane"`
- `pnpm --filter @paperclipai/server typecheck`
- GitHub checks are green on `0823957a2`: Build, Canary Dry Run, General
tests, Typecheck + Release Registry, serialized server suites, e2e,
policy/review, Socket, Snyk, and aggregate `verify`.
- Greptile Review succeeded on `0823957a2` with `40 files reviewed, 0
comments added`; GitHub unresolved review threads: 0.

Not run in this heartbeat:
- Browser screenshot capture for the UI changes. This PR intentionally
omits screenshots per the Paperclip task direction not to add design
screenshots/images.

## Risks

- Broad feature branch touching DB, shared contracts, server, and UI;
reviewers should still scan merge conflicts carefully if `master` moves
again before landing.
- Skill version/runtime behavior is sensitive: pinned skill versions
must stay pinned while default selections should continue following the
current version.
- UI polish should get normal reviewer/browser attention before merge
because this PR includes a large Skills Store surface and screenshots
were intentionally omitted.

> 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-based coding agent with tool use 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 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 (intentionally omitted per PAP-10921 direction)
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 14:02:09 -05:00
Jannes Stubbemann 69a368ed51 fix(gemini-local): pre-select gemini-api-key auth in managed-HOME settings.json for headless runs (#7918)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The gemini-local adapter runs gemini-cli headlessly, including on
remote/sandboxed execution targets where the adapter manages a dedicated
HOME under the runtime root
> - gemini-cli hard-refuses headless runs with "Invalid auth method
selected." unless `$HOME/.gemini/settings.json` persists an auth
selection; setting `GEMINI_DEFAULT_AUTH_TYPE` alone does NOT satisfy it
(proven in an isolated pod)
> - With a managed HOME the runtime root replaces the image home, so any
settings.json baked into the agent image (or the user's real home) is
invisible to the CLI, and every sandboxed gemini run dies before doing
any work
> - This affects any sandbox provider that runs gemini with API-key auth
through the managed-HOME path (SSH, E2B, Daytona, Kubernetes, or any
other remote execution target); it is a headless-execution bug fix, not
gateway- or deployment-specific behavior
> - This pull request makes the adapter pre-select the `gemini-api-key`
auth type in the managed `$HOME/.gemini/settings.json` whenever a
Gemini/Google API key is present, writing both settings schema
generations and never touching an existing settings.json
> - The benefit is that gemini agents actually run headlessly on remote
and sandboxed execution targets without any manual settings provisioning

## Linked Issues or Issue Description

No existing issue; describing the bug in-PR (bug template fields):

- **What happened:** Headless gemini-local runs on remote/sandboxed
execution targets fail immediately with `Invalid auth method selected.`
even though `GEMINI_API_KEY` is provided.
- **Expected:** Providing the API key should be enough for a headless
run to authenticate and proceed.
- **Root cause:** gemini-cli requires an auth selection persisted in
`$HOME/.gemini/settings.json` for non-interactive runs; the
`GEMINI_DEFAULT_AUTH_TYPE` env var does not substitute for it (verified
in an isolated pod with only the env var set). The adapter's
managed-HOME execution path points HOME at the runtime root, so any
pre-existing settings.json (image-baked or user home) is hidden and the
CLI finds no auth selection.
- **Reproduction:** Run the gemini-local adapter against a
remote/sandboxed execution target with `GEMINI_API_KEY` set and no
settings.json under the managed HOME; the run aborts with the error
above.
- Duplicate/related search: no existing PR or issue addresses this;
closest related is #7693 (bundles gemini-cli in the Docker image), which
makes the CLI available but does not fix headless auth selection.

## What Changed

- `packages/adapters/gemini-local/src/server/execute.ts`: after
provisioning the managed HOME, when a Gemini/Google API key is present,
write `$HOME/.gemini/settings.json` pre-selecting `gemini-api-key` auth.
Both settings schema generations are written (legacy top-level
`selectedAuthType` and current `security.auth.selectedType`) so old and
new gemini-cli versions are covered.
- The write is strictly scoped to the managed HOME (the per-run runtime
root on sandbox transports). On non-managed remote targets (SSH), where
the remote home is the user's real home and existing settings remain
visible to the CLI, the adapter creates nothing (review feedback, P1).
- The write is guarded by `[ -f ... ] ||` so a user-shipped
settings.json (e.g. via workspace) is never overwritten.
- The key-presence gate checks the run env AND the host process env
(`GEMINI_API_KEY` / `GOOGLE_API_KEY`): in sandboxed paths the key never
enters the adapter's run env; it reaches the agent pod via the sandbox
provider's per-run secret (env passthrough from the host env), so the
host env is the correct signal there.
- `packages/adapters/gemini-local/src/server/execute.remote.test.ts`: a
new sandbox-transport test asserts the settings.json write lands under
the per-run runtime root (path + `gemini-api-key` content), and the SSH
test asserts no settings.json is created on a non-managed home.

## Verification

- `npx vitest run packages/adapters/gemini-local`: 3 files, 17 tests,
all pass.
- `pnpm --filter @paperclipai/adapter-gemini-local typecheck` and
`build`: clean (test file is covered by the package tsconfig `include`).
- Negative control: in an isolated pod, gemini-cli with `GEMINI_API_KEY`
+ `GEMINI_DEFAULT_AUTH_TYPE` set but no settings.json still fails with
`Invalid auth method selected.`; with the settings.json written by this
change, the run proceeds.
- Verified end-to-end: a gemini agent in a hardened Kubernetes (gVisor)
sandbox completed a real task (with `GOOGLE_GEMINI_BASE_URL` pointing at
a GenAI-compatible endpoint), producing a billed usage row. That
deployment supplies the verification evidence; the fix applies to any
sandbox provider running gemini with API-key auth.

## Risks

- Low risk. The new write only fires on the managed-HOME path (per-run
runtime root) when an API key is present, and only when no settings.json
exists yet, so existing setups, real user homes on SSH targets, and
user-provided settings are unaffected.
- If a future gemini-cli changes the settings schema again, the file may
need a third generation key; both current generations are written today.

## Model Used

- Claude (Anthropic), Claude Opus 4.8, 1M context, extended thinking,
with tool use (code execution / shell) 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 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 (no UI change)
- [x] I have updated relevant documentation to reflect my changes (code
comments document the behavior; no doc pages cover managed-home auth)
- [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
(review requested)
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 21:14:05 -07:00
Jannes Stubbemann 9e750d3e92 feat(codex-local): env-driven gateway routing via PAPERCLIP_CODEX_PROVIDERS config.toml (#7919)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The `codex-local` adapter runs the OpenAI Codex CLI; Paperclip
already maintains a managed `CODEX_HOME` per company and ships it to
remote/sandboxed execution targets
> - Deployments increasingly put an OpenAI-compatible LLM gateway
between the harness and the model for cost, governance, or
data-residency reasons: LiteLLM, OpenRouter, Portkey, Kong, a corporate
proxy, self-hosted models (vLLM/Ollama), or region-pinned/sovereign
endpoints. But Codex has no CLI flag or env var for a custom endpoint:
its only mechanism is `[model_providers.<id>]` tables (with `base_url`,
`env_key`, `wire_api`) in `$CODEX_HOME/config.toml`, selected by a
root-level `model_provider` key
> - Today there is no supported way to get such provider config into the
managed `CODEX_HOME`, so gateway routing requires hand-editing files the
adapter owns and regenerates
> - This pull request adds the codex analogue of #7837's opencode
mechanism: a `PAPERCLIP_CODEX_PROVIDERS` JSON env var whose shape maps
1:1 onto codex's TOML schema, merged into the managed `config.toml` so
the existing asset-shipping + `env.CODEX_HOME` mechanics deliver it to
local and sandboxed runs alike; nothing here is specific to one hosting
setup
> - The benefit is Codex works behind any OpenAI-compatible gateway with
config only; with no env set, behavior is unchanged

## Linked Issues or Issue Description

No existing issue; describing in-PR (feature / adapter enhancement).

- **Gap:** there is no supported way to register a custom/gateway
`[model_providers.*]` endpoint for `codex-local`. Codex's only
custom-endpoint mechanism is `config.toml` (`base_url` + `env_key` +
`wire_api`, selected via the root `model_provider` key), and the adapter
owns/regenerates the managed `CODEX_HOME`, so operators cannot durably
hand-edit it.
- Related: #7837 (the opencode-local analogue of this change, same
env-driven gateway-routing pattern). Searched for duplicate/related PRs:
no existing codex-local gateway/provider-routing PR found.

> Note on ROADMAP: this is adapter-level, opt-in config (defaults
unchanged) that *enables* gateway routing for one harness; it is not the
core "Cloud / Sandbox agents" platform work itself.

## What Changed

- New `prepareCodexRuntimeConfig()`
(`packages/adapters/codex-local/src/server/runtime-config.ts`): reads
`PAPERCLIP_CODEX_PROVIDERS` (run env first, then `process.env`), shaped
as `{"providers": {"<id>": {base_url, env_key, wire_api, ...}},
"model_provider": "<id>"}`, and merges it into the managed
`CODEX_HOME`'s `config.toml`. No-op when unset or empty.
- A malformed value (invalid JSON, not a JSON object, no `providers`
object, no usable provider entries, or individual entries with empty
names or non-object values, which are skipped by name) is never silently
dropped: each case surfaces a distinct, user-visible note (via the
prepare notes, which flow into command notes + `onLog`) and unusable
input leaves `config.toml` untouched.
- Merge is marker-delimited and TOML-correct: existing `config.toml`
content is preserved between two managed blocks. Root keys (e.g.
`model_provider`) are prepended **before the first table header** (TOML
root-region rule), `[model_providers.*]` tables are appended.
Pre-existing same-name provider sections and root `model_provider` keys
are excised so the managed definitions win without duplicate-table parse
errors.
- `{env:VAR}` placeholders are expanded server-side for
literal-credential fields; `env_key` indirection remains the preferred
path.
- Crash-safe restore: prepare writes a pre-run backup
(`config.toml.paperclip-backup`) before the merged file; `cleanup()`
restores the original in the execute `finally` and removes the backup.
If a run never reaches `cleanup()` (a throw during the setup between
prepare and execution, or SIGKILL), the next prepare restores the
original from the backup with full fidelity, including user
`[model_providers.*]` sections the merge excised (review feedback, P2);
plain block-stripping remains the fallback for pre-backup state.
- An explicit adapter-config `env.CODEX_HOME` override is treated as
user-managed: no merge, surfaced as a command note.
- Dependency-free hand-emitted TOML (strings/numbers/booleans, arrays of
scalars, plain objects as inline tables); basic strings escape
U+0000-U+001F and U+007F per TOML 1.0 (review feedback, P2). Merged
output was additionally validated locally with python tomllib during
development; the committed tests assert the structural invariants.
- `execute.ts` wiring: `prepareCodexRuntimeConfig` runs after
`prepareManagedCodexHome` (before the home ships to the remote target),
notes surface via `onLog` + command notes, and the `finally` calls
`cleanup()`.

**Note for reviewers:** current codex removed `wire_api = "chat"`
(openai/codex#10157, Feb 2026), so gateway provider configs must use
`wire_api = "responses"`, i.e. the gateway must speak `/v1/responses`.
The adapter passes the value through verbatim; this is a codex-side
constraint worth knowing when configuring it.

## Verification

- `pnpm --filter @paperclipai/adapter-codex-local build` and
`typecheck`: tsc clean against current `master`
- `pnpm exec vitest run packages/adapters/codex-local`: 45 passing
(incl. 17 `runtime-config` tests: fresh-merge + cleanup restore,
root-region placement, same-name provider override, inline
tables/arrays, DEL escaping, `{env:}` expansion from run env +
`process.env`, per-case malformed-input notes with `config.toml`
untouched, skipped-entry notes alongside a successful merge, silent
no-op when unset/empty, explicit-`CODEX_HOME` skip note, backup restore
of excised user sections after an interrupted run, backup removal on
cleanup, stale-block self-heal, re-run replacement)
- Verified end-to-end: a codex agent in a hardened Kubernetes (gVisor)
sandbox completed a real task routed through an OpenAI-compatible
gateway's `/v1/responses`, with a billed usage row recorded on the
gateway. That deployment supplies the verification evidence; the
mechanism is gateway-agnostic.

## Risks

Low. Entirely env-driven and opt-in; with `PAPERCLIP_CODEX_PROVIDERS`
unset the adapter never touches `config.toml` and behavior is
byte-identical to before. The merge preserves user content, restores the
original file on cleanup, and survives interrupted runs via the pre-run
backup; malformed input surfaces a visible note and is ignored without
touching `config.toml`. No migration/UI impact.

## Model Used

Claude Opus 4.8 (`claude-opus-4-8`, 1M context), extended thinking +
tool use, 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 (adapter-level opt-in config enabling
gateway routing; not the core sandbox-platform work, noted above)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (#7837 is the opencode analogue; no codex-local duplicate
found)
- [x] I have either (a) linked existing issues 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
- [ ] If this change affects the UI, I have included before/after
screenshots (n/a, no UI)
- [ ] I have updated relevant documentation to reflect my changes (env
var documented inline; no central doc references the adapter env yet)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green (green on the previous head;
re-running on the final note-copy polish commit)
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(both review P2s are fixed at head: the interrupted-run restore via the
pre-run backup and the U+007F escaping; a re-review is requested for the
note-copy polish)
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 21:13:31 -07:00
Jannes Stubbemann 6e4aca9c67 feat(pi-local): env-driven gateway routing via PAPERCLIP_PI_PROVIDERS models.json (#7920)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The `pi-local` adapter runs the Pi coding agent, including inside
remote/sandboxed execution targets; Pi resolves `--provider P --model M`
by an exact (provider, id) match against its model registry, and it has
no base-url CLI flag or env var: a `models.json` in its agent config dir
(`$PI_CODING_AGENT_DIR`, falling back to `$HOME/.pi/agent`) is its only
mechanism for custom or OpenAI/Anthropic-compatible endpoints
> - Deployments increasingly put an LLM gateway between the harness and
the model for cost, governance, or data-residency reasons: LiteLLM,
OpenRouter, Portkey, Kong, a corporate proxy, self-hosted models
(vLLM/Ollama), or region-pinned/sovereign endpoints. Today there is no
supported way to get such provider config into Pi's registry for
orchestrated runs
> - The opencode adapter gained the equivalent capability in #7837 and
codex in #7919; this pull request is the Pi analogue, so the harness
layer stays gateway-agnostic regardless of which CLI an agent uses;
nothing here is specific to one hosting setup
> - This pull request reads `PAPERCLIP_PI_PROVIDERS` (Pi's `models.json`
`providers` shape), materialises a managed `models.json` in a temp
agent-config dir, points `PI_CODING_AGENT_DIR` at it, and ships it to
remote execution targets with the run
> - The benefit is Pi works behind any compatible gateway with config
only; with no env set, behavior is unchanged

## Linked Issues or Issue Description

No existing issue; describing in-PR (feature / adapter enhancement).

- **Gap:** there is no supported way to register custom/gateway
providers + models for `pi-local`. Pi's only custom-endpoint mechanism
is a `models.json` in its agent config dir, and orchestrated (especially
sandboxed) runs have no way to provision one declaratively.
- Related: #7837 (the opencode-local analogue, same env-driven
gateway-routing pattern) and #7919 (the codex-local analogue). Searched
for duplicate or related PRs: no existing pi-local
gateway/provider-routing PR found.

> Note on ROADMAP: this is adapter-level, opt-in config (defaults
unchanged) that *enables* gateway routing for one harness; it is not the
core "Cloud / Sandbox agents" platform work itself.

## What Changed

- New `packages/adapters/pi-local/src/server/runtime-config.ts`:
`preparePiRuntimeConfig()` reads `PAPERCLIP_PI_PROVIDERS` (a JSON object
in pi's `models.json` `providers` shape) from the run env, then
`process.env`. When set, it expands `{env:VAR}` placeholders (run env
first, then process env; unresolvable placeholders left intact), writes
`{"providers": ...}` to a managed temp dir as `models.json`, and returns
env with `PI_CODING_AGENT_DIR` pointing at it plus a cleanup handle.
- `execute.ts`: the prepared dir ships to remote execution targets as
the managed-runtime asset `agentConfig` (same mechanism as opencode's
`xdgConfig`), and `PI_CODING_AGENT_DIR` is repointed to the in-target
path; cleanup runs in `finally`.
- Misconfiguration is visible, not silent: a set-but-unusable
`PAPERCLIP_PI_PROVIDERS` (invalid JSON, not an object, no provider
objects) surfaces an explanatory note instead of proceeding unconfigured
into an opaque model-not-found failure later, and provider entries with
non-object values are skipped with a note naming them. Unset/empty stays
a silent no-op (feature off).
- Defaults unchanged: with `PAPERCLIP_PI_PROVIDERS` unset, the adapter
behaves byte-for-byte as before, for local runs and for every existing
sandbox provider.

## Verification

- All pi-local tests green against this base (new: providers written
verbatim, `{env:VAR}` expansion from run env/process env/unresolvable,
no-op when unset, `PI_CODING_AGENT_DIR` set and shipped, the
misconfiguration notes incl. skipped non-object entries, remote asset
sync + env repoint). Typecheck and build clean.
- Production end-to-end evidence (our deployment, used as verification,
not as the scope of the change): a pi agent in a Kubernetes gVisor
sandbox resolved a custom provider from the shipped `models.json`,
completed an assigned issue through an Anthropic-compatible gateway, and
landed a billed usage row.

## Risks

Low. The entire feature is opt-in behind one env var; the only behavior
change when it is set is the intended one. The managed dir replaces the
host agent dir for the run by design (credentials travel inside the
provider config or via env-key indirection), which is the correct
posture for orchestrated runs. No migration/UI impact.

## Model Used

Claude Opus 4.8 (`claude-opus-4-8`, 1M context), extended thinking +
tool use, 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 (adapter-level opt-in config enabling
gateway routing; not the core sandbox-platform work, noted above)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (#7837 and #7919 are the opencode/codex analogues; no
pi-local duplicate found)
- [x] I have either (a) linked existing issues 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
- [ ] If this change affects the UI, I have included before/after
screenshots (n/a, no UI)
- [ ] I have updated relevant documentation to reflect my changes (env
var documented inline; no central doc references the adapter env yet)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green (green on the previous head;
re-running on the final note-copy polish commit)
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(both prior review findings are fixed at head: the indirect notes-based
guard is now an explicit `agentConfigDir` handle, and a failed
`models.json` write no longer leaks the temp dir; a re-review is
requested for the note-copy polish)
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 21:12:23 -07:00
Jannes Stubbemann 1ac1ba5442 feat(opencode-local): env-driven gateway routing (custom providers, small/cheap model, remote allow-all) (#7837)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The `opencode-local` adapter runs the OpenCode harness; its
model/provider routing assumes built-in providers (anthropic/openai/...)
and their default models
> - Deployments increasingly put an OpenAI/Anthropic-compatible LLM
gateway between the harness and the model for cost, governance, or
data-residency reasons: LiteLLM, OpenRouter, Portkey, Kong, a corporate
proxy, self-hosted models (vLLM/Ollama), or region-pinned/sovereign
endpoints. But OpenCode only resolves `--model provider/model` when the
model is registered in a provider's `models` map, and
`OPENCODE_ALLOW_ALL_MODELS` does NOT bypass its internal `getModel()`
> - Several lanes also fall back to built-in default models the gateway
may not serve: the auxiliary/title model (e.g. `claude-haiku-*`) and the
budget/recovery "cheap" lane (`openai/gpt-5.1-codex-mini`); these abort
runs with "no keys found that support model"
> - This pull request makes the adapter's provider/model wiring
declarative via env, so any such deployment can register gateway models
+ pin the auxiliary/budget lanes without code changes; nothing here is
specific to one hosting setup
> - The benefit is OpenCode works behind any compatible gateway with
config only; with no env set, behavior is unchanged

## Linked Issues or Issue Description

No existing issue; describing in-PR (feature / adapter enhancement).

- **Gap:** there is no supported way to register custom/gateway
providers + models for `opencode-local`, nor to pin the auxiliary
(title-gen) and budget (recovery) model lanes, so routing OpenCode
through a gateway fails at `getModel()` or on the default helper models.
- Related: #5737 (exe.dev sandbox installs for gemini/opencode local),
#5823 (unblock claude_local on remote sandbox providers).

> Note on ROADMAP: this is adapter-level, opt-in config (defaults
unchanged) that *enables* gateway routing for one harness; it is not the
core "Cloud / Sandbox agents" platform work itself. Happy to
redirect/discuss in #dev if preferred.

## What Changed

- `PAPERCLIP_OPENCODE_PROVIDERS`: merge custom/extended providers
(OpenCode `provider` shape) into the runtime `opencode.json`, so gateway
models are registered and `--model provider/model` resolves. `{env:VAR}`
placeholders are expanded server-side (so a key need not depend on the
sandbox run env).
- A malformed `PAPERCLIP_OPENCODE_PROVIDERS` is no longer silently
ignored: invalid JSON, a non-object value, and individual provider
entries with non-object values (which are skipped by name) each append a
visible note to the run notes so the misconfiguration is diagnosable
(addresses both review P1s).
- `PAPERCLIP_OPENCODE_SMALL_MODEL` / `PAPERCLIP_OPENCODE_CHEAP_MODEL`:
pin the auxiliary (title-generation) and budget (recovery-retry) lanes
to gateway-served models; defaults unchanged.
- Honour `OPENCODE_ALLOW_ALL_MODELS` on the **remote** execution path
too (was local-only, a parity gap).
- `PAPERCLIP_OPENCODE_PRINT_LOGS`: optional toggle adding `--print-logs`
so OpenCode logs surface on stderr for diagnosing remote/sandbox runs.
- `buildOpenCodeModelProfiles()` guards its `process.env` default with
`typeof process` so the shared client/server module stays browser-safe
(a bare `process.env` at module load threw ReferenceError in the browser
under Vite dev middleware and broke UI rendering in the e2e lane).

## Verification

- `pnpm --filter @paperclipai/adapter-opencode-local build` and
`typecheck` (tsc clean)
- `pnpm exec vitest run packages/adapters/opencode-local/src` shows 33
passing (incl. new tests for the provider merge, `{env:}` expansion, the
malformed/non-object/skipped-entry provider notes, small/cheap-model
resolution, and the remote allow-all bypass)
- Manually verified end-to-end against a real
OpenAI-/Anthropic-compatible gateway: with the providers + small/cheap
model set, both the title-gen and main task route to the configured
gateway model and the agent completes (a real completion is returned and
billed). That deployment supplies the verification evidence; the
mechanism is gateway-agnostic.

## Risks

Low. Everything is env-driven and opt-in; with no env set the generated
config output is unchanged, and the cheap model profile keeps its model
(the only difference is its updated human-readable description).
Defaults preserved: built-in providers, Codex-mini cheap lane with
`variant: low`, no `--print-logs`. No migration/UI impact.

## Model Used

Claude Opus 4.8 (`claude-opus-4-8`, 1M context), extended thinking +
tool use, 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 (adapter-level opt-in config enabling
gateway routing; not the core sandbox-platform work, noted above)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (#5737, #5823)
- [x] I have either (a) linked existing issues 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
- [ ] If this change affects the UI, I have included before/after
screenshots (n/a, no UI)
- [ ] I have updated relevant documentation to reflect my changes (env
vars documented inline via comments; no central doc references the
adapter env yet)
- [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
(the P1 about silently dropped malformed providers JSON is addressed in
6eeb803, the follow-up P1 about silently skipped non-object entries in
2f4045a; the latest review has no further findings, and a re-review is
requested for the final note-copy/test-fixture polish at head)
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 21:11:49 -07:00
Jannes Stubbemann 4ad94d0bde feat(server): kubernetes execution integration for sandbox-provider plugins (stage 2/3) (#7938)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The execution subsystem runs those agents in environments (local,
ssh, sandbox), and sandbox-provider plugins let an environment
materialize per-run sandboxes
> - Stage 1 (#5790) contributed a first-party Kubernetes
sandbox-provider plugin, but the server core has no way to adopt it
operationally: no per-run adapter selection, no way to force an instance
onto sandboxed execution, no declarative adapter/model configuration,
and the plugin must be installed by hand
> - Without this, a multi-tenant or security-conscious deployment cannot
guarantee that agent runs never execute on the host, and a single
environment cannot serve agents with different harnesses
> - This pull request adds the server + SDK integration: per-run
adapterType on the lease protocol, an env-gated forced-Kubernetes
execution policy with provisioning and a per-run allowlist guard, a
declarative adapter registry and model list, in-cluster env passthrough
for sandbox plugin workers, fail-safe auto-install of the bundled
plugin, and the matching UI affordance
> - The benefit is that sandbox-provider plugins become fully usable for
Kubernetes execution: operators configure everything via environment
variables and GitOps, while self-hosters who set none of the variables
see exactly the behavior they have today

## Linked Issues or Issue Description

Refs #5790 (stage 1 of 3: the Kubernetes sandbox-provider plugin
package).

No existing issue. Feature description: the server core lacks the
integration seams to operate a sandbox-provider plugin as the mandatory
execution path of an instance. This PR is stage 2 of 3 of the staged
Kubernetes contribution; stage 3 will contribute the agent runtime
images and their build pipeline.

## What Changed

One line per piece:

- `packages/plugins/sdk/protocol.ts`: optional `adapterType` on
`PluginEnvironmentAcquireLeaseParams` so a provider can select the
runtime image per run; existing providers simply ignore it
- `server/services/environment-runtime.ts` +
`environment-run-orchestrator.ts`: thread the agent's adapter type into
both lease-acquiring drivers, including the heartbeat path (the two call
sites have historically drifted, hence the pinned test)
- `server/services/environments.ts`: `ensureKubernetesEnvironment` /
`findKubernetesEnvironment`, an idempotent managed Kubernetes
environment per company, identified by a metadata marker and refreshed
(not recreated) on config change; `timeoutMs` rides on the config for
slow cold-start leases
- `server/services/execution-allowlist.ts`: pure (driver, provider,
policy) -> allow/deny guard; `executionMode=kubernetes` only allows the
kubernetes sandbox provider
- `server/services/execution-policy-bootstrap.ts` + startup hook in
`server/index.ts`: parse `PAPERCLIP_EXECUTION_MODE` / `PAPERCLIP_K8S_*`,
persist `executionMode` into instance general settings, and provision
the managed environment for every company; fails loud on
misconfiguration
- `server/services/heartbeat.ts`: when the policy forces Kubernetes, pin
run selection to the managed environment (also overriding any persisted
workspace environment id), refuse to fall back to local, and re-check
the actually acquired environment against the allowlist as defense in
depth
- `server/services/adapter-registry-bootstrap.ts` + shared
`AdapterRegistryEntry` type/validator: declarative `PAPERCLIP_ADAPTERS`
registry (inline JSON or file) that reconciles adapter availability at
startup and rides on the Kubernetes environment config
- `server/services/adapter-models-env.ts` + `adapters/registry.ts`:
`PAPERCLIP_ADAPTER_MODELS` lets an operator declare picker model lists
the server cannot CLI-discover
- `server/services/plugin-loader.ts`: pass
`KUBERNETES_SERVICE_HOST/PORT(_HTTPS)` through to plugin workers that
register environment drivers, so in-cluster API clients can be
constructed; all other host env stays stripped
- `server/app.ts`: fail-safe auto-install of the bundled kubernetes
plugin at boot; no-ops when the bundle is absent and never blocks
startup on error
- `packages/shared` types/validators: `InstanceExecutionMode` on general
settings (optional, strict schema)
- `ui/lib/forced-kubernetes-environment.ts` + `AgentConfigForm`: when
the policy is active, show a read-only Kubernetes environment instead of
the environment picker and default new agents onto the managed
environment
- Tests for every new module plus the adapterType pin in
`heartbeat-plugin-environment` and the managed-environment lifecycle in
`environment-service`

Everything is gated: with `PAPERCLIP_EXECUTION_MODE`,
`PAPERCLIP_ADAPTERS`, and `PAPERCLIP_ADAPTER_MODELS` unset (and no
bundled plugin present), every code path reduces to current behavior.
The per-run `adapterType` is an optional SDK parameter that existing
providers ignore.

## Verification

- `cd server && npx tsc --noEmit`: clean (0 errors); `ui` typecheck also
clean
- Targeted suites all green (11 files, 90 tests): `npx vitest run
server/src/__tests__/heartbeat-plugin-environment.test.ts
server/src/__tests__/environment-service.test.ts
server/src/__tests__/environment-runtime.test.ts
server/src/__tests__/environment-run-orchestrator.test.ts
server/src/__tests__/plugin-database.test.ts
server/src/services/execution-policy-bootstrap.test.ts
server/src/services/execution-allowlist.test.ts
server/src/services/adapter-registry-bootstrap.test.ts
server/src/services/adapter-registry-bootstrap.reconcile.test.ts
server/src/services/adapter-models-env.test.ts
packages/shared/src/validators/adapter-registry.test.ts`
- `npx vitest run ui/src/components/AgentConfigForm.test.ts`: green (6
tests)
- Full `npx vitest run server/src/__tests__`: 2323 passed, 1 skipped;
the only failures (heartbeat-process-recovery pid-retry,
workspace-runtime symbolic-ref/git tests) reproduce identically on
pristine `master` in the same environment, so they are
machine-environment issues unrelated to this change;
`server-startup-feedback-export` needed its `services/index.js` mock
extended with the new export and is green
- This integration has been running in production on a hosted
multi-tenant deployment, where it executes agent runs across five
different harnesses through the stage 1 plugin

## Risks

- Low for existing deployments: every behavior is env-gated and the
defaults preserve current semantics; the auto-install block is wrapped
fail-safe and skips silently when the plugin bundle is absent
- `executionMode` is a new optional field on a strict zod schema; absent
input normalizes exactly as before
- The forced policy intentionally fails runs loudly (rather than falling
back to local) when no managed Kubernetes environment exists; this only
affects instances that explicitly set
`PAPERCLIP_EXECUTION_MODE=kubernetes`

## Model Used

Claude Opus 4.8 (claude-opus-4-8, 1M context), extended thinking,
agentic tool use via Claude Code.

## UI screenshots

The UI change is a new read-only "Execution" section in
`AgentConfigForm`, shown only when the instance execution policy forces
Kubernetes (`executionMode=kubernetes`); there is no "before" state for
it (the section did not exist, and instances without the forced policy
render the existing picker unchanged). Captured from the new Storybook
stories added in this PR (`Product/Agent Management`):

Managed Kubernetes environment present (read-only display, no local/SSH
picker):

![AgentConfigForm with forced Kubernetes
execution](https://raw.githubusercontent.com/paperclipinc/paperclip/296ad06e8/screenshots/PR-7938-agent-config-forced-kubernetes.png)

No managed environment available yet (warning notice, no silent local
fallback):

![AgentConfigForm forced Kubernetes, missing environment
warning](https://raw.githubusercontent.com/paperclipinc/paperclip/296ad06e8/screenshots/PR-7938-agent-config-forced-kubernetes-missing-env.png)

## 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
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 21:09:02 -07:00
Jannes Stubbemann 05ab45225a feat(plugin-kubernetes): self-hostable Kubernetes sandbox provider (stage 1/3: plugin package) (#5790)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandbox providers are the seam that lets agent runs execute in
isolated environments; today the only first-party remote provider is
Daytona, a hosted third-party service
> - Self-hosters running Paperclip on their own infrastructure (often
Kubernetes already) have no first-party way to run agent sandboxes on a
cluster they control
> - That gap matters for teams with data-residency, sovereignty, or cost
constraints who cannot or will not send workloads to a hosted sandbox
service
> - This pull request adds a Kubernetes sandbox-provider plugin as a
standalone, workspace-excluded package: it implements every
SandboxProvider hook the Daytona provider does, on infrastructure the
operator owns
> - The benefit is that any Paperclip deployment with a Kubernetes
cluster gets multi-tenant, network-isolated, quota-bounded agent
sandboxes with zero new external dependencies

## Linked Issues or Issue Description

No existing issue. Following the feature template:

- **Problem:** Paperclip's remote sandbox execution requires a hosted
third-party provider. Self-hosters cannot run agent sandboxes on their
own Kubernetes clusters with a first-party provider.
- **Proposed solution:** A `@paperclipai/plugin-kubernetes`
sandbox-provider plugin with two backends: long-lived sandboxes via the
[kubernetes-sigs/agent-sandbox](https://github.com/kubernetes-sigs/agent-sandbox)
CRD (multi-command exec, adapter-install pattern) and one-shot
`batch/v1` Jobs (stable APIs only, no extra controllers).
- **Alternatives considered:** Driving kubectl from a generic shell
provider (no lifecycle/lease semantics), or requiring a hosted provider
(exactly the constraint this removes).

## What Changed

This is **stage 1 of 3** of a staged contribution (direction agreed with
maintainers): the plugin package alone. Stage 2 (server integration:
lease params, provider registration) and stage 3 (agent runtime images +
CI) are companion PRs that will be cross-linked from a comment here.

- New package `packages/plugins/sandbox-providers/kubernetes`
(workspace-excluded, like the path already carved out in
`pnpm-workspace.yaml`): src, unit + kind integration tests, operator
prerequisite manifests, README, smoke-test guide
- Implements the full SandboxProvider hook surface the Daytona provider
implements: `validateConfig`, `probe`, `acquireLease`, `resumeLease`,
`releaseLease`, `destroyLease`, `realizeWorkspace`, `execute`
- Two backends: `sandbox-cr` (default; long-lived pod via the
agent-sandbox `Sandbox` CR, supports multi-command exec) and `job`
(one-shot `batch/v1` Job; nothing beyond k8s 1.27+ required)
- Per-run adapter resolution: one environment serves mixed harnesses;
the per-run `adapterType` hint is read through a local optional type
extension, so the plugin typechecks and builds against the current
plugin SDK and simply falls back to the environment's configured default
adapter until stage 2 lands
- Exec-env wrapping: the Kubernetes exec API carries no environment, so
commands are wrapped to receive the run's env
- Fast-upload interception for workspace realization, scoped per lease
- Per-tenant isolation: derived namespace per company, RBAC,
ResourceQuota, restricted-PSS pod security (runAsNonRoot, drop ALL,
seccomp RuntimeDefault, no SA token automount)
- Network egress policy in two flavors: native `NetworkPolicy` and
`CiliumNetworkPolicy` (FQDN allowlists)
- Image allowlist with glob matching, registry override, and per-run
image override validation
- Per-run Kubernetes Secrets carrying agent credentials, ownerRef'd to
the Job or Sandbox CR for cascade GC

## Verification

- Standalone build, exactly as the README documents:
  ```bash
  cd packages/plugins/sandbox-providers/kubernetes
  pnpm install --ignore-workspace
  pnpm test        # 147 unit tests, 17 files, all green
  pnpm typecheck   # clean against the in-repo plugin SDK on master
pnpm build # dist/ emitted, manifest + worker entrypoints present
  ```
- A kind-cluster end-to-end integration test is included
(`RUN_K8S_INTEGRATION_TESTS=1 pnpm test
test/integration/end-to-end-run.test.ts`)
- Beyond CI: this provider has been verified in a production
multi-tenant deployment against five harnesses (opencode, pi, codex,
gemini, claude code) with real billed runs

## Risks

- **Zero behavior change for any existing deployment.** The package is
workspace-excluded; nothing in the server imports or loads it until
stage 2's integration lands. No existing code paths are touched.
- The default `sandbox-cr` backend depends on an alpha CRD
(`agents.x-k8s.io/v1alpha1`); the README flags this and the `job`
backend uses only stable APIs as a fallback.
- Risk surface is confined to deployments that explicitly install and
configure the plugin.
- The default runtime images (`ghcr.io/paperclipai/agent-runtime-*`) are
published by the stage 3 companion PR (#7934); until that lands,
deployments must point `runtimeImage` at their own images.

## Model Used

Claude Opus 4.8 (1M context), extended thinking, with tool use (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 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 (no UI changes)
- [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 (pending this push)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 21:07:00 -07:00
Devin Foley c139d6c025 fix(codex-local): omit default model so codex CLI picks per auth mode (#7971)
## Thinking Path

> - Paperclip orchestrates AI agents through pluggable local adapters;
codex_local wraps OpenAI's `codex` CLI.
> - The codex_local adapter declares a hard-coded
`DEFAULT_CODEX_LOCAL_MODEL = "gpt-5.3-codex"` and multiple Paperclip
consumers (UI build-config, server route, OnboardingWizard, NewAgent
form, AgentConfigForm) fall back to it when the operator doesn't pick a
model.
> - That model — and every `*-codex` model plus the older
`gpt-5/5.1/5.2` lines — is API-key-only. Codex CLI rejects them on
ChatGPT subscription auth with "The 'gpt-5.3-codex' model is not
supported when using Codex with a ChatGPT account."
> - Every codex_local agent created through the default onboarding path
inherits this pin and breaks on its first heartbeat for any user authed
via `codex login` (ChatGPT).
> - claude_local already takes the right shape: its build-config only
sets `adapterConfig.model` when the operator actually picked one, and
falls through to whatever default `claude` CLI uses.
> - Codex CLI's own default is auth-mode-aware. ChatGPT-subscription
accounts get `gpt-5.5`; API-key accounts get the codex-tuned default. A
Paperclip-side pin masks this and downgrades whichever group it wasn't
built for.
> - This PR makes codex_local match claude_local's shape: omit
`adapterConfig.model` when the user picks "default," and let the CLI
choose. Subscription users stop breaking; API-key users stop getting
downgraded.
> - The benefit is auth-mode-correct defaults with no Paperclip-side
hard pin, plus future-proofing: when OpenAI bumps the CLI default we
inherit it for free.

## What Changed

- `packages/adapters/codex-local/src/ui/build-config.ts` — only set
`adapterConfig.model` when the operator picked one (parity with
`packages/adapters/claude-local/src/ui/build-config.ts`).
- `server/src/routes/agents.ts` — drop the codex_local-specific
`next.model = DEFAULT_CODEX_LOCAL_MODEL` fallback in
`applyCreateDefaultsByAdapterType`. Bypass-sandbox default is left in
place (security posture, not a model choice).
- `ui/src/pages/NewAgent.tsx`, `ui/src/components/AgentConfigForm.tsx`,
`ui/src/components/OnboardingWizard.tsx` — stop pre-populating the model
field with `DEFAULT_CODEX_LOCAL_MODEL` when the user selects the Codex
adapter. Other adapters' defaults (gemini_local, cursor, opencode_local)
are unchanged.
- `DEFAULT_CODEX_LOCAL_MODEL` is preserved as an exported constant for
downstream consumers / plugin authors who want to opt in to a pin; we
just stop forcing it on operators who didn't ask for one.
- Test: assert `buildCodexLocalConfig` omits `model` when input is
blank.

## Verification

- `pnpm exec vitest run
packages/adapters/codex-local/src/ui/build-config.test.ts
packages/adapters/codex-local/src/server/codex-args.test.ts
server/src/__tests__/adapter-registry.test.ts
server/src/__tests__/heartbeat-model-profile.test.ts
server/src/__tests__/agent-permissions-routes.test.ts` → 74/74 passing
- `pnpm exec vitest run ui/src/lib/duplicate-agent-payload.test.ts
ui/src/lib/acpx-model-filter.test.ts` → passing
- `pnpm tsc --noEmit -p .` → clean
- Live: I separately verified live during initial investigation that on
ChatGPT-subscription auth, `gpt-5.3-codex` is rejected and `gpt-5.5` is
what Codex CLI picks by default. Omitting model lets the CLI handle
that.

## Risks

- Telemetry: any sink that reads `adapterConfig.model` for cost
attribution will now see the empty/omitted case more often. The CLI
emits the actually-used model in its event stream; downstream telemetry
should already read from there for accuracy, but worth a check.
- Operator UX: "default" now means "whatever the CLI picks" instead of a
Paperclip-known model. The selectable catalog still includes `gpt-5.5`,
`gpt-5.4`, `gpt-5.3-codex`, etc. for operators who want to pin
explicitly.
- Existing agents are unaffected — their `adapterConfig.model` is
already set; this only changes the *new-agent* default flow.

## Related work

- Depends on: an open catalog-add PR adding `gpt-5.5` to the selectable
model list and to `CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS`. Operators
who want to switch to `gpt-5.5` explicitly need that PR merged first;
this PR is the structural change that makes "default" mean "let the CLI
choose."
- Closes #5371 — codex_local default model selection persists
`gpt-5.3-codex` instead of adapter default (this PR is the exact fix
#5371 proposes).
- Related: #5132 (opencode-local: hire-time default model fails on
ChatGPT-OAuth accounts) — same problem shape on a sibling adapter; not
fixed here but worth tracking for a parallel.
- Related: #5939 (codex_local adapter hardcodes `gpt-5.3-codex-spark`
validation, fails on ChatGPT OAuth accounts regardless of configured
model) — separate validation-path bug; not fixed here.

## Model Used

Claude (Sonnet-class), running inside Paperclip as a claude_local
executor.


## 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
- [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>
2026-06-10 20:53:33 -07:00
LIXin Ye 9a48d92104 Add GPT-5.5 to Codex local model options (#5575)
## Related work

This PR is the cleanest "add `gpt-5.5` to the codex-local catalog"
change open against master. Several other PRs propose the same
catalog/fast-mode update; they should close as duplicates once this
lands:

- #4646 — Add Codex gpt-5.5 model option
- #6044 — feat(codex-local): add gpt-5.5 to model catalog, default
reasoning to medium, cheap profile xhigh
- #6045 — feat(codex-local): add gpt-5.5 to model catalog, default
medium reasoning, xhigh cheap profile
- #6595 — feat(adapters): add new Codex models (gpt-5.5, gpt-5.4-mini,
gpt-5.3-codex, gpt-5.2)

Related issues this enables (catalog-level surface area):

- #5371 — codex_local default model selection persists `gpt-5.3-codex`
instead of adapter default. This PR makes `gpt-5.5` selectable in the
dropdown; a separate follow-up changes the *default* behavior so users
who don't pick a model are subscription-compatible.
- #5132 — opencode-local: hire-time default model fails on ChatGPT-OAuth
accounts. Sibling adapter, same problem shape; not fixed here but worth
tracking as a parallel for the opencode side.

---

## Thinking Path

> - Paperclip orchestrates AI agents through adapter-backed local and
remote runtimes.
> - The `codex_local` adapter declares built-in model options that feed
the server model list and, in turn, the agent configuration UI dropdown.
> - GPT-5.5 is available in newer Codex environments but was missing
from Paperclip's fallback `codex_local` model list.
> - Operators could still type a manual model ID, but the default
dropdown made the supported path look unavailable.
> - Codex fast mode support is declared separately, so adding GPT-5.5 to
the visible list should also include it in the supported fast-mode set.
> - This pull request adds GPT-5.5 to the built-in Codex local model
options and updates focused tests around argument generation and adapter
model listing.
> - The benefit is a clearer default setup path for agents using GPT-5.5
without changing existing defaults or migrations.

## What Changed

- Added `gpt-5.5` to the `codex_local` fallback model list.
- Added `gpt-5.5` to `CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS`.
- Updated Codex argument tests to cover GPT-5.5 fast mode and preserve
manual-model fast mode behavior.
- Updated adapter model listing tests to assert the Codex fallback list
includes GPT-5.5.

## Verification

- `pnpm exec vitest run
packages/adapters/codex-local/src/server/codex-args.test.ts
server/src/__tests__/adapter-models.test.ts`
- `git diff --check`
- UI note: this is a dropdown data-source change rather than a
layout/component change; the adapter model listing test covers the list
consumed by the UI.

## Risks

- Low risk. This only extends a static fallback model list and fast-mode
allowlist.
- Existing defaults remain unchanged (`gpt-5.3-codex`).
- If a local Codex CLI does not support `gpt-5.5`, selecting it will
still fail at execution time the same way any unavailable manual model
would.

> 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 desktop coding agent, GPT-5-family model. The exact
backing model ID was not exposed by the local runtime; the session used
shell, Git, test execution, and GitHub CLI tool access.


## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have 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
- [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: apple <apple@appledeMacBook-Pro.local>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Devin Foley <devin@paperclip.ing>
2026-06-10 20:02:39 -07:00
Jannes Stubbemann 93cdc5c1ce fix(adapter-utils): tar sandbox workspace by entry, not '.', to avoid EPERM on unowned target dir (#7836)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents can run in remote/sandboxed environments via the shared
sandbox managed-runtime in `@paperclipai/adapter-utils` (used by
SSH/E2B/Daytona and other sandbox providers), which syncs the workspace
into the sandbox by tarring it up and extracting it inside the pod/host
> - When the sandbox runs the harness as a non-root user whose
home/workspace dir it does not own (for example a hardened, non-root,
gVisor pod with an `emptyDir`-mounted workspace), the workspace upload
aborts before the agent can start
> - Root cause: `createTarballFromDirectory` archives `.`, embedding a
`./` self-entry whose mode/mtime tar then tries to restore onto the
**extraction target directory**; `chmod`/`utime` of `.` fails with
`Operation not permitted` for a non-owner
> - This is not specific to any one deployment: the `.` self-entry EPERM
can bite every sandbox provider built on the shared managed runtime as
soon as the extracting user does not own the target directory, which is
the norm for hardened non-root sandboxes
> - This pull request archives the directory's top-level entries by name
instead of `.`, so there is no `./` self-entry and extraction never
touches the target dir's metadata
> - The benefit is that workspace sync works in any sandbox where the
target dir is non-root or not owned by the extracting user, without
GNU-only tar flags

## Linked Issues or Issue Description

No existing issue; describing in-PR (bug).

- **What happens:** managed sandbox runs that sync the workspace fail at
upload with `tar: .: Cannot utime: Operation not permitted` / `tar: .:
Cannot change mode to ... : Operation not permitted`, aborting the run
before the harness starts.
- **Where:** `packages/adapter-utils/src/sandbox-managed-runtime.ts`, in
`createTarballFromDirectory` (archives `.`).
- **When:** the extraction target directory is not owned by the
(non-root) user extracting the tar inside the sandbox.
- Closely related (different root cause): #6560 (E2B workspace upload +
lease idle failures).

## What Changed

- `createTarballFromDirectory` enumerates the directory's top-level
entries with `fs.readdir` and passes them by name after `--` (guards
flag-like filenames) instead of archiving `.`, eliminating the `./`
self-entry that triggers the EPERM.
- Empty workspaces (legitimate for blank-workspace runs) write a valid
1024-byte all-zero EOF tar instead of invoking `tar` with no paths.
- `--exclude` patterns continue to apply (to nested matches and any
named entry).

## Verification

- `pnpm --filter @paperclipai/adapter-utils build` (tsc clean)
- `pnpm exec vitest run
packages/adapter-utils/src/sandbox-managed-runtime.test.ts` runs green
- New tests: uploaded workspace/asset tarballs contain no `.`/`./`
member yet still extract correctly; empty workspace produces a valid
(no-op) tarball. Existing managed-runtime sync test unchanged.
- Manually verified in a hardened (non-root, gVisor) sandbox pod: with
the fix, the workspace upload that previously aborted with the EPERM now
succeeds. That deployment is the reproduction and verification
environment; the fix itself is provider-agnostic.

## Risks

Low. Behavior is unchanged for owned/root targets; the archive contents
are the same minus the `./` self-entry (which tar recreates implicitly
on extract). Portable across GNU/BSD/busybox tar (no GNU-only
`--no-overwrite-dir`). No API/migration/UI impact.

## Model Used

Claude Opus 4.8 (`claude-opus-4-8`, 1M context), extended thinking +
tool use, 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 (bug fix in shared sandbox utils, not core feature
work)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (#6560)
- [x] I have either (a) linked existing issues 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
- [ ] If this change affects the UI, I have included before/after
screenshots (n/a, no UI)
- [ ] I have updated relevant documentation to reflect my changes (n/a,
internal behavior, no docs reference this)
- [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
(the only finding was the description-template P2, resolved by this
description; the latest review covers the current head with no code
findings and all 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: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 19:36:19 -07:00
Sherif Kozman b8fb81dee9 fix(gemini-local): treat token-overflow as a fresh-session signal (#4932)
## Thinking Path

The same 2026-04-30 audit that produced PR #4118 (`Invalid session`
regex extension) and the ENOTFOUND classifier (#4931) identified a third
stuck-session pattern: **13 failures in 7 days, all on a single agent
(Ernest)**, with stderr matching:

```
_ApiError: {"error":{"code":400,"message":"The input token count exceeds
the maximum number of tokens allowed 1048576","status":"INVALID_ARGUMENT"}}
  at ChatCompressionService.compress
```

The root cause is that gemini-cli's `ChatCompressionService` blew the 1M
token context limit **during its compression step itself**. Resuming the
same session ID will hit the same wall on the next attempt — the session
is effectively dead the same way it is when "Invalid session identifier"
fires (PR #4118).

## What Changed

Extends the `isGeminiUnknownSessionError` regex in `parse.ts` with two
phrases:
- `exceeds\s+the\s+maximum\s+number\s+of\s+tokens`
- `input\s+token\s+count\s+exceeds`

Both trigger the **existing** fresh-session retry path in
`execute.ts:596` — no new code path. Same extension pattern as PR #4118.

## Verification

- `npx vitest run --project @paperclipai/adapter-gemini-local` → 14/14
pass (11 in `parse.test.ts` + 3 existing in `execute.remote.test.ts`)
- 2 new tests cover the token-overflow patterns
- `pnpm --filter @paperclipai/adapter-gemini-local typecheck` → clean
- Audit query against `heartbeat_runs.stderr_excerpt` confirms regex
matches all 13 occurrences

## Stacking

This PR is stacked on top of #4931 (the ENOTFOUND classifier) which adds
the `parse.test.ts` file. If #4931 merges first, this PR's diff is just
the regex + 2 tests. If this PR is reviewed first, please merge #4931
first to avoid touching the same test scaffolding twice.

## Risks

- **Low.** Single-line regex extension. No new code paths.
- The session-reset path is well-trodden (PR #4118 in flight).
- If a non-Gemini caller produces a stderr containing "exceeds the
maximum number of tokens" by coincidence, they would trigger one
unnecessary fresh-session retry. Not plausible in the gemini-cli output
context where this stderr is sourced.

## Model Used

Claude Opus 4.7 (1M context), Anthropic SDK via Claude Code CLI.

## Checklist

- [x] Thinking path traces from audit data to single-line regex change
- [x] Model specified
- [x] No duplicate of planned core work
- [x] Tests pass locally
- [x] Tests added (2 new)
- [x] N/A — server-side regex
- [x] Internal pattern; no docs change
- [x] Risks documented
- [x] Will address Greptile + reviewer comments before merge
- [x] I searched the GitHub PR list for similar PRs and confirmed this
is not a duplicate (related: #4118 covers the "Invalid session
identifier" regex; this PR extends the same regex with token-overflow
phrases)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-10 19:10:29 -07:00
Abhishek gahlot c32193c85e test(codex-local): cover EEXIST race rejection with mismatched symlink (#5269)
## Thinking Path

> - The `codex-local` adapter sets up a per-company Codex home with an
auth symlink. Between `lstat` and `symlink` there is a race where two
concurrent setups can both try to create the same symlink, surfacing
`EEXIST`.
> - Master already handles this at runtime via `createExpectedSymlink`,
which accepts `EEXIST` only when the raced-in entry resolves to the
expected source, and ships a regression test for the tolerated-race path
(symlink already points at the right place).
> - The symmetric path — `EEXIST` raised by a symlink pointing somewhere
else — must stay strictly rejected so a future refactor cannot silently
weaken the guard.
> - This PR locks that in with a single additive test. No production
code change.

## What Changed

- Added one regression test in
`packages/adapters/codex-local/src/server/codex-home.test.ts` that
injects an `EEXIST` whose raced-in symlink target points at a different
file, and asserts:
  - `prepareManagedCodexHome` rejects with `code: "EEXIST"`.
- The mismatched symlink is left on disk (we do not blindly overwrite
the raced-in entry).

Complements the existing "treats a concurrently-created expected auth
symlink as success" test already on master.

Refs #5240 (Stack B — codex-home adapter session/auth handling).

## Verification

- `pnpm --filter @paperclipai/adapter-codex-local exec vitest run
src/server/codex-home.test.ts` — passes.
- `pnpm --filter @paperclipai/adapter-codex-local typecheck` — clean.

## Risks

- Test-only change. No production code is modified.

## Model Used

- Provider: Anthropic
- Model: Claude (Opus 4.7)
- Mode/capabilities: tool-using coding agent with shell execution and
test verification

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have 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
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
- [x] I searched the GitHub PR list for similar PRs and confirmed this
is not a duplicate

Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-10 15:15:39 -07:00
Harshit Khemani c297ba2a80 fix(codex-local): replace stale auth.json copy with symlink on prepare (#5028) (#5240)
## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies
> - codex_local runs Codex CLI under a per-company "managed home" so
multiple companies don't trample on each other's session state
> - For `auth.json` specifically, the managed home keeps a SYMLINK to
the user's real `~/.codex/auth.json` rather than a copy — Codex refresh
tokens rotate and are single-use, so any copy goes stale the moment the
source rotates and every subsequent run dies with `401
refresh_token_reused`
> - Older Paperclip versions copied `auth.json` instead. After
upgrading, `ensureSymlink()` saw a regular file at the target, hit `if
(!existing.isSymbolicLink()) return;`, and silently kept the stale copy
> - This pull request makes the upgrade path self-healing inside
`ensureSymlink()` itself: when the target is a regular file, unlink it
and create the symlink, since the target lives under the
Paperclip-managed home and is safe to delete. Directories are skipped to
avoid `EISDIR` on Unix (and inconsistent behavior on Windows)
> - The benefit is operators who upgraded from a copy-based version stop
getting refresh-token-reused failures without having to manually purge
`companies/<id>/codex-home/auth.json`, and the healing is
defense-in-depth even outside the `prepareManagedCodexHome` cleanup path

## What Changed

- `packages/adapters/codex-local/src/server/codex-home.ts` —
`ensureSymlink()` previously bailed out of the
`!existing.isSymbolicLink()` branch, leaving any pre-existing regular
file untouched. Now unlinks and recreates the symlink in that branch via
the existing `createExpectedSymlink()` helper (preserves the EEXIST
race-tolerance behavior added in #5119). A guard skips directories so
the call never throws `EISDIR` and aborts `prepareManagedCodexHome`.
Inline comment explains the safety: target is always under the
company-scoped managed home
(`<paperclipHome>/instances/<id>/companies/<companyId>/codex-home/`),
never the user's real `~/.codex`.
- `packages/adapters/codex-local/src/server/codex-home.test.ts` — adds a
regression test for #5028: pre-seed a stale copy at the target, run
`prepareManagedCodexHome`, assert the target is now a symlink and reads
through to the fresh source. The existing concurrent-symlink test is
preserved.

## Verification

```
pnpm --filter @paperclipai/adapter-codex-local exec vitest run
# Test Files  8 passed (8)
# Tests       26 passed (26)
pnpm --filter @paperclipai/adapter-codex-local exec tsc --noEmit
# clean
```

Manual repro flow that the regression test mirrors:
1. Create a stale copy: `echo '{"token":"old"}' >
<managedHome>/auth.json`.
2. Rotate source: `echo '{"token":"new"}' > ~/.codex/auth.json`.
3. Trigger any codex_local run — `prepareManagedCodexHome` is called
from the execute path, the managed file is now a symlink to the source,
and the CLI sees the fresh token.

## Risks

- **Low risk.** The new branch only fires when the target file is a
regular file (the upgrade path) — a pure copy that Codex couldn't have
written, since Codex never writes into the managed home. Operators in
steady-state on the symlink-based version are unaffected.
- The `fs.unlink` only runs against the per-company managed-home path,
never the user's real `~/.codex`. Inline comment makes this guarantee
explicit.
- A directory at the auth.json path is left in place (no silent `EISDIR`
crash) — this requires operator inspection rather than autonomous
deletion.
- The healing uses `createExpectedSymlink()` so it remains tolerant of
EEXIST races with concurrent prepare calls (the concurrent-symlink test
still passes).
- No DB / migration / schema impact.

## Model Used

- Anthropic Claude Opus 4.7 (claude-opus-4-7), via Claude Code CLI with
extended tool use (Read / Edit / Bash / Grep). No extended-thinking
budget consumed beyond default.

## 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 — N/A, adapter-only
- [x] I have updated relevant documentation to reflect my changes —
inline comment explains the why and the safety of the unlink
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
- [x] I searched the GitHub PR list for similar PRs and confirmed this
is not a duplicate

Fixes #5028.

---------

Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-10 06:50:20 -07:00
nullEFFORT dfd3ed44c5 fix: auto-retry on Claude "Could not process image" 400 during session resume (#3276)
## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies.
> - The Claude-local adapter resumes prior sessions via `claude --resume
<session-id>` so work continues across heartbeats.
> - When a resumed session contains an image whose content is no longer
accessible, Claude returns a 400 "Could not process image" — but the
session itself is poisoned and will keep returning the same error on
every resume.
> - The existing retry path only catches the "unknown session" 400 case;
image-processing 400s on resume fall through and the run fails for the
user.
> - This PR adds an `isClaudeImageProcessingError` detector mirroring
`isClaudeUnknownSessionError` and wires it into the same fresh-session
retry branch in `execute.ts`.
> - The benefit is that a poisoned-image resume self-recovers by
retrying once with a fresh session, exactly like the existing
unknown-session path.

## Linked Issues or Issue Description

Fixes #3275
Refs #3123

## What Changed

- Added `isClaudeImageProcessingError()` in
`packages/adapters/claude-local/src/server/parse.ts` that matches `Could
not process image` in 400 error messages.
- Wired the new detector into the existing session-resume retry branch
in `packages/adapters/claude-local/src/server/execute.ts` alongside
`isClaudeUnknownSessionError`.
- Retry only fires when `sessionId` is present (i.e. we were resuming),
so fresh-session runs that hit the same error are not retried (no
infinite loop).

## Verification

- `pnpm --filter @paperclipai/adapter-claude-local test` covers
`parse.ts` patterns and the resume-retry decision branch.
- `pnpm --filter @paperclipai/adapter-claude-local typecheck`

## Risks

Low. Behavior change is narrowly additive: a previously-fatal 400 on
resume now triggers a single fresh-session retry. No effect on
fresh-session runs, unknown-session retries, or non-image 400s.

## Model Used

Claude (Opus 4.6) — used to mirror the existing unknown-session pattern
and verify the guard against infinite loops.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have 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 — no UI changes)
- [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 (in progress)
- [ ] 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: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-10 06:00:05 -07:00
kengraversen 058381349e fix(heartbeat): don't reuse runtime.sessionId across an adapter swap (#4109)
## Thinking Path

> - Paperclip orchestrates AI agents on pluggable adapters
(`claude_local`, `opencode_local`, `codex_local`, …); each adapter wraps
an external CLI.
> - The heartbeat service stores a session ID per agent and replays it
back to the adapter via `--resume` so within-task continuity is
preserved.
> - Session IDs are adapter-specific in format: claude expects a UUID,
opencode emits `ses_…`, etc. They cannot be cross-replayed.
> - When the cross-adapter session ID does slip through (operator
changes `adapterType`, edge cases in the resume path, foreign-format ID
in stored task sessions), the claude CLI hard-fails with a validation
error and every subsequent heartbeat loops on the same error until the
stored ID is manually cleared.
> - Master now ships a canonical-session-ID guard at `heartbeat.ts:8450`
(via #5972) that prevents most of this at the source, and
`isClaudePoisonedPreviousMessageIdError` recovers from the 400-class API
error.
> - This PR adds defense-in-depth at the adapter layer: the `--resume
requires a valid session ID … not a UUID …` validation error from the
claude CLI is now classified as an unknown-session signal, so the
existing fresh-session retry recovers instead of hard-failing.

## Linked Issues or Issue Description

Refs #5972 — sibling fix on the same cluster (recovers from poisoned
`previous_message_id` 400). This PR complements it by handling the
CLI-layer `--resume` validation error class.

## What Changed

- `packages/adapters/claude-local/src/server/parse.ts` — broaden
`isClaudeUnknownSessionError` regex to also match `--resume requires a
valid session`, `is not a UUID`, and `does not match any session title`.
The existing fresh-session retry at `execute.ts:612-625` now fires for
this error class.
- `packages/adapters/claude-local/src/server/parse.test.ts` — adds 4 new
test cases for `isClaudeUnknownSessionError` covering the legacy and new
patterns plus a negative case.

**Dropped from the original PR on rebase** (already on master, would
conflict):
- `server/src/services/heartbeat.ts` runtimeSessionFallback gate —
superseded by the stricter `isCanonicalSessionIdForAdapter` check on
master (#5972 lineage).
- `packages/adapters/claude-local/vitest.config.ts` and
`vitest.config.ts` projects entry — both already in master.

## Verification

```sh
pnpm --filter @paperclipai/adapter-claude-local vitest run
# 19/19 passed (3 files, includes 4 new isClaudeUnknownSessionError cases)
```

Pre-existing failure on
`server/src/__tests__/heartbeat-process-recovery.test.ts > queues
exactly one retry when the recorded local pid is dead` reproduces on
`origin/master` — unrelated to this PR.

## Risks

- **Low-to-medium.** The added regex fragments are narrow. `--resume
requires a valid session` and `does not match any session title` are
unambiguously session-related. `is not a UUID` is more generic; worst
case is one extra retry on an unrelated CLI validation error that would
also fail on the same root issue. Happy to drop `is not a UUID` if
reviewers prefer.
- **No DB migration; no schema change; no behavior change when adapter
types match (the common path).**

## Model Used

- Provider: Anthropic (Claude)
- Model: `claude-opus-4-7` (Opus 4.7), 1M context window
- Tool: Claude Code CLI with extended thinking + tool use; human review
on the rebase and the regex narrowing tradeoffs

## Checklist

- [x] I searched the GitHub PR list for similar PRs and confirmed this
is not a duplicate (related: #5972 already merged, complementary scope)
- [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 (19/19 claude-local)
- [x] I have added or updated tests where applicable
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Devin Foley <devin@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-09 21:13:20 -07:00
NyDamon 0713dfa41f fix: validate session ID as UUID before --resume + error diagnostics (DLD-889) (#1742)
## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies.
> - The Claude-local adapter uses `claude --resume <session-id>` to
continue prior sessions; the `--resume` value MUST be a UUID per
Claude's CLI contract.
> - Paperclip internally uses session IDs prefixed with `ses_` (not
UUIDs); these get passed straight through to `--resume` and crash the
run.
> - On top of the crash, when the underlying error path triggers a
secret-decryption failure or heartbeat setup failure, the diagnostics
are too thin to tell key-mismatch from other failures, and the heartbeat
error code is mis-classified as `adapter_failed` instead of
`setup_failed`.
> - This PR validates `runtimeSessionId` against a UUID regex before
letting `canResumeSession` become true, adds `not a valid UUID` to
Claude's own retry-error regex, improves AES-256-GCM decryption
diagnostics in the local encrypted provider, and re-classifies
pre-adapter setup failures.
> - The benefit is that Paperclip session IDs are detected and skipped
gracefully (logged, no crash), legitimate Claude UUID-rejection errors
are treated as retriable, and operators can diagnose decryption/setup
failures from the run log.

## Linked Issues or Issue Description

**What happened?**

The `claude-local` adapter passes Paperclip's internal session
identifiers (e.g. `ses_…`) straight to `claude --resume <session-id>`.
Because Claude's CLI requires the `--resume` argument to be a UUID, the
run crashes with a `not a valid UUID` error. When the surrounding code
path also hits a secret-decryption failure, the heartbeat reports it as
`adapter_failed`, hiding the real `setup_failed` cause and making
diagnosis hard.

**Expected behavior**

Non-UUID session IDs should be detected before `--resume` is called, the
run should fall back to a fresh session with a clear log line, and any
decryption / setup failure should be reported with enough detail (and
the correct error code) for an operator to tell what failed.

**Steps to reproduce**

1. Have a persisted task session whose ID is not a UUID
(Paperclip-issued `ses_…` form).
2. Trigger a heartbeat that resumes that session via the `claude-local`
adapter.
3. Observe: the adapter crashes with a UUID-validation error; if the
path also involves a decryption failure, the heartbeat surfaces
`adapter_failed` instead of `setup_failed`.

## What Changed

- `packages/adapters/claude-local/src/server/execute.ts`: Validates
`runtimeSessionId` against a UUID regex before setting
`canResumeSession`; non-UUID IDs are logged and skipped gracefully.
Guards the cwd-mismatch log block on `isValidUuid` so it does not fire
for non-UUID session IDs.
- `packages/adapters/claude-local/src/server/parse.ts`: Adds `not a
valid UUID` to the session-error retry regex so Claude's own UUID
rejection is treated as a retriable error.
- `server/src/services/secrets/local-encrypted-provider.ts`: Wraps
AES-256-GCM decryption in try/catch and re-throws with a key fingerprint
hint to aid key-mismatch diagnosis.
- `server/src/services/heartbeat.ts`: Corrects the outer-catch
`errorCode` from `adapter_failed` to `setup_failed` for pre-adapter
setup failures.
- `AGENTS.md`: Adds task/PR/CI governance sections (10–13) and expands
the Definition of Done.

## Verification

- `pnpm --filter @paperclipai/adapter-claude-local test` covers UUID
validation and the parse retry regex.
- `pnpm --filter @paperclipai/server test src/services/secrets` covers
decryption diagnostics.
- `pnpm --filter @paperclipai/server typecheck`

## Risks

Low. UUID validation is strictly additive (non-UUIDs that previously
crashed now log and skip). Decryption diagnostics only fire on failure
paths. The `setup_failed` error code change is a clearer classification,
not a behavior change.

## Model Used

Claude (Opus 4.6) — used to identify the UUID-validation root cause,
mirror existing parse patterns, and re-classify the heartbeat setup
error 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 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 — no UI changes)
- [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 (in progress)
- [ ] 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: CTO Agent <cto@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Devin Foley <devin@paperclip.ing>
2026-06-09 20:21:10 -07:00
Danial Jawaid 8ee3987d12 adapter-claude-local: recover from poisoned previous_message_id 400 (detect + clearSession) (#5972)
## Thinking Path

> - Paperclip's `claude_local` adapter persists Claude Code session
jsonls under `~/.claude/projects/…/{sessionId}.jsonl` and resumes them
on the next heartbeat
> - When Claude Code injects `<synthetic>` placeholder assistant
messages (after rate-limit, max-turn exhaustion, or transient-upstream
failures) those placeholders get UUID-format `message.id`s rather than
`msg_…`-format ids
> - On the next `--resume`, Claude Code passes that UUID as
`previous_message_id` and Anthropic's API rejects it with a 400:
``diagnostics.previous_message_id: must be the `id` from a prior
/v1/messages response (starts with `msg_`)``
> - The adapter had a session-rotation fallback only for "unknown
session" errors, so the poisoned session was `--resume`-d indefinitely
and the agent flipped between `idle` and `error` every heartbeat
> - Even worse, the *result* event of the failing run still carried a
`session_id`, and the adapter was persisting that id into the
issue-scoped session store (`agentTaskSessions`). So even after we
detected the 400, every subsequent continuation re-loaded the same
poisoned id and hit the same 400 again — the issue was permanently
stranded
> - We observed this on multiple agents in our deployment; the only
manual fix was to rename the `.jsonl`, which is not a viable long-term
workaround
> - This PR detects the 400, runs the same session-rotation fallback the
unknown-session path uses **and** stops persisting the poisoned id, so
the next attempt starts genuinely fresh

## Linked Issues or Issue Description

No external GitHub issue is linked. Describing the problem inline
following the bug-report template:

**What happened:** `claude_local` agents flipped between `idle` and
`error` on every heartbeat because the persisted session jsonl carried a
synthetic UUID `previous_message_id` (from `<synthetic>` assistant
placeholders injected after rate-limit/max-turn/upstream errors).
Anthropic's API rejected every `--resume` with a 400:
``diagnostics.previous_message_id: must be the `id` from a prior
/v1/messages response (starts with `msg_`)``.

**Expected behavior:** When the persisted session is poisoned and
unrecoverable, the adapter should rotate to a fresh session — the same
fallback path already used for unknown-session errors — and stop
re-persisting the poisoned `session_id`.

**Actual behavior:** The session-rotation fallback only matched the
"unknown session" pattern, so the poisoned session was `--resume`-d
forever. The result event of the failing run still carried `session_id`,
which was being persisted into `agentTaskSessions`, so every subsequent
continuation reloaded the same poisoned id and hit the same 400.

**Reproduction:** Inject any flow that causes Claude Code to emit a
`<synthetic>` placeholder (rate-limit, max-turn exhaustion, transient
upstream failure). The next `--resume` will fail with the 400 and the
agent will not self-recover.

**Scope of fix:** Add a `previous_message_id` 400 detector; route it
through the existing unknown-session fallback; drop the poisoned
`sessionId` and emit `clearSession: true` so the heartbeat service wipes
the persisted row; best-effort delete the local poisoned `.jsonl`.

## What Changed

Two commits:

1. **`adapter-claude-local: auto-rotate session on previous_message_id
400 (synthetic-msg poisoning)`** — detector + execute-time rotation
2. **`adapter-claude-local: guard against persisting poisoned
sessionId`** — validate-before-persist + `clearSession`

Combined diff:

- `parse.ts`: new `isClaudePoisonedPreviousMessageIdError(parsed)`
matching ``/diagnostics\.previous_message_id.*starts with `msg_`/i``
against `parsed.result` and `extractClaudeErrorMessages(parsed)`
- `parse.ts`: `isClaudeTransientUpstreamError()` excludes the new error
from transient classification so it isn't masked as retryable upstream
noise
- `execute.ts`: expand the resume-fallback branch so it triggers on both
`isClaudeUnknownSessionError` and the new
`isClaudePoisonedPreviousMessageIdError`, with a distinct log line
(`"returned a poisoned message-id"` vs `"is unavailable"`)
- `execute.ts`: for local (non-remote) execution targets, best-effort
delete the poisoned `~/.claude/projects/.../{sessionId}.jsonl` before
retrying so the file can't be accidentally resumed by an out-of-band
caller. The `fs.unlink` and follow-up log call are in separate try/catch
blocks so a closed log stream cannot mask a successful unlink (and vice
versa)
- `execute.ts` / `toAdapterResult`: when a result carries the poisoned
400, **drop** `sessionId`/`sessionParams`/`sessionDisplayId` (return
`null`) and emit `clearSession: true` so the heartbeat service's
`resolveNextSessionState` wipes the persisted row. The result also
surfaces `errorCode: "claude_poisoned_previous_message_id"` for
observability
- `docs/adapters/claude-local.md`: runbook entry — symptom,
auto-recovery flow, on-call checklist
- Tests:
- 4 new `parse.test.ts` cases covering positive detection in `result`
and `errors[]`, negative cases, and non-transient classification
- 3 new `claude-local-execute.test.ts` cases: (a) fresh run reports the
poisoned error → sessionId dropped + `clearSession: true`; (b) recovery
retry also reports the poisoned error → same guards apply; (c)
session-rotation success on retry

## Verification

```bash
pnpm --filter @paperclipai/adapter-claude-local exec vitest run src/server/parse.test.ts
pnpm --filter @paperclipai/server exec vitest run src/__tests__/claude-local-execute.test.ts
```

Both suites green locally. This patch is also currently running as a
hot-patch over the published `2026.513.0` adapter on the reporting
deployment — sessions that previously looped indefinitely now
self-recover on the first heartbeat after the 400 surfaces.

## Risks

- Low risk. The detector is conservative (regex over `result` +
`errors[]` only) and the rotation reuses the existing unknown-session
fallback path
- The local-only `fs.unlink` of the poisoned `.jsonl` is wrapped in
`try/catch` and ignored on failure — strictly an optimization; the
server-side session clear is the authoritative reset
- Remote execution targets (`executionTargetIsRemote`) skip the disk
cleanup because the file lives on a remote host that we can't safely
reach from the adapter
- The `clearSession: true` + nulled session fields path is a no-op on
healthy runs; it only fires when the new detector matches, so existing
successful continuations are unaffected
- No DB schema changes, no public API changes, no new dependencies

## Model Used

- Provider: Anthropic Claude
- Model: `claude-opus-4-7` (Opus 4.7)
- Context window: 1M
- Capabilities: extended reasoning, tool use, code execution
- Role: implemented the detector, expanded the fallback branch, added
the persist-guard + `clearSession`, wrote the unit + integration tests,
validated locally, and applied the equivalent hot-patch to the deployed
`2026.513.0` install while this PR is in review

## 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 similar or duplicate PRs and linked
them — closed #2295, #2361, #3572, #5438 as duplicates of this canonical
fix; complementary fixes #4838 (heartbeat_timer reset) and #4932 (gemini
context-overflow rotation) target different code paths
- [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, adapter-only change
- [x] I have updated relevant documentation
(`docs/adapters/claude-local.md` runbook entry)
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Danial Jawaid <danial.jawaid@gmail.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Devin Foley <devin@paperclip.ing>
2026-06-09 15:45:47 -07:00
Dotta 468edd8b22 Add workspace file viewer and artifact links (#7681)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent work is issue-centered, and reviewers often need to inspect
files, artifacts, and path references produced during that work.
> - Before this branch, workspace-relative paths and artifact file
references were not first-class inspectable objects in the board UI.
> - Safe file viewing needs shared resource contracts, server-side
workspace boundary checks, and UI that opens files without exposing
arbitrary host paths.
> - The workspace file viewer branch needed to stay as one active PR and
be rebased onto current `paperclipai/paperclip:master` for review.
> - This pull request adds the workspace file resource API, issue-page
file viewer and browser, markdown file-reference links, and artifact
file chips.
> - The benefit is that board users can inspect relevant files from
issue context while preserving workspace boundaries and auditability.

## Linked Issues or Issue Description

No public GitHub issue exists for this branch. Internal Paperclip
issues: `PAP-1953`, `PAP-10539`, `PAP-10733`.

Problem / motivation:
- Board users need to open workspace-relative files mentioned by agents
or attached as work-product metadata without switching to a terminal.
- The UI needs to support both direct file-path opening and workspace
browsing/searching from an issue page.
- The server must enforce company access, workspace boundaries, size
limits, rate limits, and safe audit logging.

Related PR:
- Prior closed attempt: #4442
- Single active PR for this branch: #7681

## What Changed

- Added shared workspace file resource types, validators, and
workspace-file `resourceRef` metadata validation for work products.
- Added server routes/services for resolving, listing, and previewing
workspace-relative files with access checks, scan caps, list-specific
limits, and audit logging.
- Added the issue file viewer provider, sheet, workspace browser,
command-palette action, markdown workspace-file autolinks, and artifact
file chips.
- Updated issue workspace UI and stories/tests for file browsing and
workspace file opening.
- Rebased the branch onto current `paperclipai/paperclip:master` and
updated the existing single PR branch.
- Addressed current-head Greptile follow-ups by applying `offset`
consistently across search/recent/changed file listings, restoring
stopped-service port ownership checks before auto-port reuse, and
stabilizing the workspace browser pagination test.

## Verification

Current local verification after rebase to `public/master`:
- `pnpm exec vitest run packages/shared/src/work-product.test.ts
server/src/__tests__/file-resources.test.ts
server/src/__tests__/instance-settings-routes.test.ts
server/src/__tests__/instance-settings-service.test.ts
server/src/__tests__/workspace-runtime.test.ts
ui/src/components/FileViewerSheet.test.tsx
ui/src/components/FileViewerSheet.copy.test.tsx
ui/src/components/WorkspaceFileBrowser.test.tsx
ui/src/components/WorkspaceFileMarkdownBody.test.tsx
ui/src/context/FileViewerContext.test.ts
ui/src/lib/remark-workspace-file-refs.test.ts
ui/src/lib/workspace-file-parser.test.ts
ui/src/components/IssueWorkspaceCard.test.tsx` - 13 files passed, 197
tests passed.
- `pnpm -r --filter @paperclipai/shared --filter @paperclipai/server
--filter @paperclipai/ui typecheck` - passed.
- `pnpm exec vitest run ui/src/components/WorkspaceFileBrowser.test.tsx`
- 1 file passed, 25 tests passed.
- `pnpm exec vitest run server/src/__tests__/file-resources.test.ts
server/src/__tests__/workspace-runtime.test.ts` - 2 files passed, 90
tests passed.
- `pnpm -r --filter @paperclipai/server typecheck` - passed.
- Confirmed branch is `0` behind and `46` ahead of current
`public/master` after rebase and follow-up commits.
- Confirmed the PR diff does not include `pnpm-lock.yaml`.
- Confirmed the PR diff does not include `.github/workflows` changes.
- Searched GitHub for duplicate or related workspace file viewer
PRs/issues; #4442 is the prior closed attempt and this PR is the single
active PR for the branch.
- No screenshots were committed; the task explicitly asked not to add
design screenshots or images unless they were part of the work.

Current remote verification on head
`a698a7bc10137baf7d25bd5722e1d6e0343387c1`:
- Greptile Review - success, 64 files reviewed, 0 comments added, no
unresolved Greptile review threads.
- PR workflow `verify` - success.
- Typecheck + Release Registry, General tests, workspace test shards,
serialized server suites, Build, Canary Dry Run, e2e, Socket, and Snyk -
success.
- `security-review` - neutral, with output saying a draft advisory was
filed for maintainer review and is not a merge block.
- `commitperclip PR Review / review` - cancelled after the security gate
detected flags and timed out while creating/reviewing the advisory. I
reran it once and it cancelled the same way; no actionable code/test
failure was exposed in the job logs.

## Risks

- This is a broad UI/server feature PR, so review needs to pay attention
to route authorization, workspace boundary handling, and markdown
autolink false positives.
- Workspace browsing intentionally caps list results and scan depth;
very large workspaces may require users to refine search terms.
- Remote workspace preview remains unavailable until remote file-access
support is implemented.
- The neutral commitperclip security-review advisory needs maintainer
review, but the check output says it is not a merge block.

> 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 coding agent in a Paperclip/Codex local tool-use
environment, medium reasoning, with shell/GitHub CLI tool use for branch
inspection, verification, rebase, PR update, Greptile review, and CI
inspection.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have 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
- [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>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 17:17:43 -05:00
Dotta 393e6f5e68 Add Claude Fable 5 and Mythos 5 to the model selector (#7826)
## Summary

Adds the newly released Claude models from the [models
overview](https://platform.claude.com/docs/en/about-claude/models/overview)
to the `claude_local` adapter's model selector:

- **Claude Fable 5** (`claude-fable-5`) — generally available as of
2026-06-09, Anthropic's most capable widely-released model.
- **Claude Mythos 5** (`claude-mythos-5`) — limited availability
(Project Glasswing).

**Opus 4.8 stays first in the list so it remains the default selection**
— per the request, the new flagship models are *offered* but not
defaulted (not Fable, not Mythos).

## Changes

- `packages/adapters/claude-local/src/index.ts` — add `claude-fable-5`
and `claude-mythos-5` to the adapter model list, right after
`claude-opus-4-8`.
- `packages/adapters/claude-local/src/server/models.ts` — add the Fable
5 Bedrock identifier (`us.anthropic.claude-fable-5-v1`) to the Bedrock
fallback list. Mythos 5 is limited-availability on Bedrock, so it's
intentionally left out of that fallback.
- `server/src/__tests__/adapter-models.test.ts` — assert the new models
are present and that `claude-opus-4-8` remains first (the default).

These flow through the single `claudeModels` source, so they also appear
in the ACPX combined list (`registry.ts` prefixes them with `Claude:`)
and are recognized by the ACPX Claude model filter. The UI selector
reads models dynamically from the adapter, so no UI changes are needed.

## Testing

- `npx vitest run src/__tests__/adapter-models.test.ts` — 13 passed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 13:32:32 -05:00
Dotta 50bff3b274 feat(ui): add collapsible sidebar rail and takeover panes (#7824)
## Thinking Path

> - Paperclip is the open source control plane people use to manage AI
agents, work, and company context.
> - The board UI sidebar is the main way operators keep orientation
across companies, projects, agents, issues, and settings.
> - The existing fixed expanded sidebar competes with route-specific
navigation, especially company settings and plugin routes that bring
their own contextual sidebar.
> - A collapsible primary rail preserves global navigation while giving
contextual pages more horizontal room.
> - This pull request adds a persisted collapsed rail, hover/focus peek,
keyboard toggle, and a secondary sidebar takeover model for settings and
plugin `routeSidebar` surfaces.
> - The benefit is a denser board shell that keeps the app rail
available without replacing it when a route needs its own navigation.

## Linked Issues or Issue Description

Paperclip issue: PAP-10638 Create collapsible sidebar branch.

Related GitHub PR found during duplicate search: #3838
(`feat/collapsible-sidebar`) covers a similar sidebar area but is a
different head branch and implementation. This PR intentionally packages
the work from `PAP-10638-collapsable-sidebar` into one reviewable
branch.

Problem description:

The board shell needs a first-class collapsed sidebar mode. Contextual
surfaces such as company settings and plugin route sidebars should not
replace the global app sidebar; they should collapse the app sidebar to
a rail and render their contextual navigation beside it.

## What Changed

- Added desktop collapsed/sidebar-peek state to `SidebarContext`,
including persisted user pins, route collapse requests, and forced
collapse for secondary-sidebar routes.
- Replaced the old resizable sidebar pane with `SidebarShell`, which
supports a fixed 64px rail, persisted expanded width, keyboard/pointer
resizing, and hover/focus peek overlay behavior.
- Updated `Sidebar`, sidebar nav items, project/agent sections, badges,
and account/company menu presentation for expanded, collapsed, and
peeking states.
- Added `RequestCollapsedSidebar` and `SecondarySidebar` so routes and
plugin `routeSidebar` slots can request contextual sidebar layouts
without replacing the primary app sidebar.
- Wired company settings and plugin route sidebars into the
secondary-pane takeover model.
- Added focused Vitest coverage for sidebar state precedence, shell
sizing, nav item rail rendering, keyboard shortcuts, layout takeover
behavior, and route collapse requests.
- Updated plugin authoring docs/spec references for route sidebar
behavior.

## Verification

Targeted local verification passed:

```sh
NODE_ENV=test pnpm run preflight:workspace-links && NODE_ENV=test pnpm exec vitest run ui/src/context/SidebarContext.test.tsx ui/src/components/SidebarShell.test.tsx ui/src/components/Sidebar.test.tsx ui/src/components/Layout.test.tsx ui/src/components/RequestCollapsedSidebar.test.tsx ui/src/components/SidebarNavItem.test.tsx ui/src/components/SidebarAgents.test.tsx ui/src/components/SidebarProjects.test.tsx ui/src/components/KeyboardShortcutsCheatsheet.test.tsx ui/src/hooks/useKeyboardShortcuts.test.tsx
```

Result: 10 test files passed, 88 tests passed.

Additional follow-up verification passed after review fixes:

```sh
NODE_ENV=test pnpm run preflight:workspace-links && NODE_ENV=test pnpm exec vitest run ui/src/components/Layout.test.tsx ui/src/context/SidebarContext.test.tsx && pnpm --filter /ui typecheck
```

Result: 2 test files passed, 28 tests passed, and UI typecheck passed.

Latest PR-head remote checks: Paperclip PR workflow, Snyk, Socket, and
Greptile are green; commitperclip `review` is cancelled in its
security-gate step after filing a non-blocking neutral `security-review`
check.

Notes:

- A direct run without `NODE_ENV=test` loads React's production build in
this workspace, where `act` is unavailable; the command above matches
the repo stable runner's test environment.
- I did not run Playwright/browser e2e or full workspace build/typecheck
in this PR-creation heartbeat.
- QA screenshots are attached in
https://github.com/paperclipai/paperclip/pull/7824#issuecomment-4661968387
for expanded, collapsed rail, hover peek, and settings secondary-sidebar
states.

## Risks

- Medium UI layout risk: this changes the board shell and primary
sidebar composition across many routes.
- Local storage migration risk is low: new collapsed state uses a new
key and existing width storage remains scoped to the sidebar width.
- Plugin route risk: plugin `routeSidebar` slots now render as secondary
panes on desktop, so plugin authors should confirm their route sidebar
content fits a 240px contextual pane.
- Mobile risk appears low because mobile keeps the drawer model and
gates collapsed/peek behavior to desktop.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex coding agent based on GPT-5, with local shell/git/GitHub
CLI tool use. Exact service-side model identifier and context window
were not exposed in this runtime.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have 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
- [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: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-09 13:25:17 -05:00
Dotta 76c88e5855 [codex] Move instance settings under company settings (#7680)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators manage both company-scoped configuration and
instance-level runtime/admin settings from the board UI
> - Instance settings previously lived as their own top-level sidebar
area, separate from the company settings context operators already use
> - That split made settings navigation feel heavier and made instance
configuration less discoverable from the settings tab
> - This pull request moves instance settings under company settings
while preserving the existing instance settings routes and plugin/admin
surfaces
> - The benefit is a smaller primary sidebar and a more coherent
settings hierarchy for operators

## Linked Issues or Issue Description

- Refs #338
- Internal: PAP-10491, PAP-10538

## What Changed

- Moved instance settings navigation under the company settings area.
- Added route helpers and sidebar entries for nested instance settings
paths.
- Updated plugin/admin settings routes to use the company settings
instance scope.
- Preserved legacy instance-settings bookmarks through compatibility
redirects that keep the active company prefix.
- Updated focused UI and plugin tests for the new navigation shape.
- Stabilized the process-loss retry test that was failing the serialized
server shard in CI.
- Rebased the branch onto current `paperclipai/paperclip` `master` and
pushed the current head.

## Verification

- `pnpm exec vitest run
ui/src/components/CompanySettingsSidebar.test.tsx
ui/src/components/access/CompanySettingsNav.test.tsx
ui/src/lib/instance-settings.test.ts
ui/src/components/InstanceSidebar.test.tsx
ui/src/components/Layout.test.tsx
ui/src/components/SidebarAccountMenu.test.tsx
ui/src/pages/PluginPage.test.tsx ui/src/plugins/bridge.test.ts
packages/shared/src/validators/plugin.test.ts`
- `pnpm exec vitest run ui/src/lib/instance-settings.test.ts
ui/src/components/CompanySettingsSidebar.test.tsx
ui/src/components/access/CompanySettingsNav.test.tsx
ui/src/components/Layout.test.tsx ui/src/plugins/bridge.test.ts`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "queues
exactly one retry when the recorded local pid is dead"`
- `pnpm test:run:serialized -- --shard-index 0 --shard-count 4`
- GitHub PR checks are green on head
`fe7b0955169dcae55cbe10889c1876a70ab0b80c`, including `verify`, `General
tests (server)`, all serialized server shards, build, e2e, policy,
security checks, and Greptile.
- Confirmed the PR diff does not include `pnpm-lock.yaml` or
`.github/workflows` changes.

## Risks

- Medium UI/navigation risk: instance settings links are intentionally
moving under company settings, so stale external bookmarks to legacy
paths rely on the compatibility routing in this branch.
- Low test-only risk from the CI stabilization commit: it makes the
recovery assertion select the actual retry run by `retryOfRunId` instead
of whichever non-original run appears first.
- No database migrations.
- No dependency lockfile or workflow changes.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex coding agent based on GPT-5, with shell/tool execution in
a local repository worktree. Exact context window was not exposed by the
runtime.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have 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
- [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>
2026-06-07 17:23:53 -05:00
Dotta 2d1b531a49 [codex] Add clear-error agent action (#7695)
## Thinking Path

> - Paperclip is the open source control plane people use to manage AI
agents for work.
> - Agent runtime state is surfaced in both the server API and the board
UI so operators can tell whether an agent is idle, running, paused, or
in error.
> - When an agent is already in `error`, the existing pause/resume
action slot is not useful because there is no running work to pause.
> - Operators need a direct, audited recovery path that clears the stale
error state only for agents in the same company.
> - This pull request adds a company-scoped clear-error mutation,
exposes the shared API contract, and wires the board action cluster to
show Clear error in the pause/resume slot for errored agents.
> - The benefit is that operators can recover CEO/CTO-style errored
agents without resorting to database edits or unrelated session reset
actions.

## Linked Issues or Issue Description

Refs #4021

Paperclip issue: PAP-10515 — right now the CEO and CTO agents are in
error state, but there is no way to clear the error; they appear
otherwise fine.

## What Changed

- Added shared constants, API path, and agent status type support for a
company-scoped clear-error action.
- Added the server service and route to clear an agent from `error` back
to `idle`, with company access enforcement and activity logging.
- Added OpenAPI/docs coverage for the clear-error endpoint.
- Added backend coverage for service behavior and cross-tenant
authorization.
- Updated the board agent action cluster to show a red-tinted Clear
error button only when `agent.status === "error"`.
- Updated agent properties to show a red active last-error indicator
only while the agent is currently errored.
- Added UI component tests for the error-state action and the non-error
pause/resume behavior.

## Verification

Local:

- `pnpm exec vitest run
server/src/__tests__/agents-service-clear-error.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
ui/src/components/AgentActionButtons.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts`

PR checks:

- Main Paperclip workflow is green on
`a7378e584d50594e7bd507a1a02985bfaaa5abf8`.
- Greptile is 5/5 with no files requiring special attention and no new
comments on the latest review.
- `commitperclip PR Review` is still red because its security-gate step
canceled after filing a draft advisory; the linked `security-review`
check is neutral and says the draft advisory is not a merge block.

Visual artifact:

- ![Clear error visual
comparison](https://gist.githubusercontent.com/cryppadotta/59378c59869b971e92d3edf7b7073aa8/raw/clear-error-visual-comparison.svg)

## Risks

Low to medium risk. The mutation is intentionally narrow, but reviewers
should check that clearing `lastError`/`lastRunError` and returning to
`idle` is the desired recovery semantics for every adapter state. The
remaining red check is from the external commitperclip security-review
workflow, not from the code/test workflow for this PR.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex, GPT-5-family coding model, tool-assisted with local shell,
git, GitHub CLI, and targeted Vitest 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 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
- [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
2026-06-07 10:33:29 -05:00
dependabot[bot] e0bf19fe1c build(deps-dev): bump drizzle-kit from 0.31.9 to 0.31.10 (#7572)
Bumps [drizzle-kit](https://github.com/drizzle-team/drizzle-orm) from
0.31.9 to 0.31.10.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/drizzle-team/drizzle-orm/releases">drizzle-kit's
releases</a>.</em></p>
<blockquote>
<h2>drizzle-kit@0.31.10</h2>
<ul>
<li>Updated to <code>hanji@0.0.8</code> - native bun
<code>stringWidth</code>, <code>stripANSI</code> support, errors for
non-TTY environments</li>
<li>We've migrated away from <code>esbuild-register</code> to
<code>tsx</code> loader, it will now allow to use
<code>drizzle-kit</code> seamlessly with both <code>ESM</code> and
<code>CJS</code> modules</li>
<li>We've also added native <code>Bun</code> and <code>Deno</code>
launch support, which will not trigger <code>tsx</code> loader and
utilise native <code>bun</code> and <code>deno</code> imports
capabilities and faster startup times</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/drizzle-team/drizzle-orm/commit/4aa6ecfee4b4728dadf6f77f071a149878a3c6c0"><code>4aa6ecf</code></a>
Kit updates (<a
href="https://redirect.github.com/drizzle-team/drizzle-orm/issues/5490">#5490</a>)</li>
<li>See full diff in <a
href="https://github.com/drizzle-team/drizzle-orm/compare/drizzle-kit@0.31.9...drizzle-kit@0.31.10">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>
2026-06-06 23:23:47 -07:00
Dotta 71a8464fee [codex] prevent invalid agents from receiving assignments and runs (#7663)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The control plane owns agent lifecycle, issue assignment, routine
dispatch, heartbeat wakeups, and recovery paths
> - Terminated, paused, pending-approval, or otherwise invalid agents
should not receive new work or new execution attempts
> - The old behavior left eligibility checks spread across routes and
services, so assignment and run paths could drift apart
> - This pull request centralizes agent lifecycle eligibility and
applies it consistently to assignment, invocation, routines, recovery,
and UI affordances
> - The benefit is safer autonomy: terminated agents stay paused,
invalid org-chain agents are surfaced, and active agents keep receiving
valid work

## Linked Issues or Issue Description

Refs #5103
Related: #1864

Bug fix context:
- What happened: agent assignment and heartbeat/run paths did not share
one eligibility contract, so invalid lifecycle states could still be
considered in some paths.
- Expected behavior: terminated agents must never receive new
assignments or heartbeat runs, and paused or otherwise invalid agents
should be treated as non-invokable consistently.
- Steps to reproduce: create or select an agent in an invalid lifecycle
state, then attempt assignment, routine dispatch, or heartbeat/recovery
wake paths.
- Paperclip version/commit: fixed on top of `paperclipai/paperclip`
`master` at the PR base.
- Deployment mode: applies to the server control plane in local and
authenticated deployments.

## What Changed

- Added shared agent lifecycle eligibility helpers and exported the
related shared types.
- Centralized server-side assignability and invokability checks for
issue assignment, agent routes, heartbeat dispatch, routines, recovery,
and liveness logic.
- Hardened issue assignment so invalid assignees are rejected instead of
queued for work.
- Hardened heartbeat/routine/recovery paths so terminated and otherwise
invalid agents are not woken for new runs.
- Updated board UI affordances to disable invalid agent actions and
surface org-chain warnings where relevant.
- Added targeted shared, server, and UI tests for the new eligibility
behavior.

## Verification

- `pnpm exec vitest run packages/shared/src/agent-eligibility.test.ts
server/src/__tests__/agent-invokability.test.ts
server/src/__tests__/heartbeat-archived-company-guard.test.ts
server/src/__tests__/issue-liveness.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/routines-service.test.ts
ui/src/lib/company-members.test.ts ui/src/pages/Agents.test.tsx` — 8
files, 144 tests passed.
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck && pnpm --filter @paperclipai/ui
typecheck` — passed.
- Checked the PR diff does not include `pnpm-lock.yaml` or
`.github/workflows` changes.
- Checked `ROADMAP.md`; this is a targeted control-plane safety fix and
does not duplicate a planned core feature.
- Searched GitHub for duplicate or related PRs/issues; closest related
items are linked above.
- CI and Greptile verification are pending on the opened PR and will be
followed up before requesting merge.

## Risks

Low to moderate risk. The intended behavioral shift is that invalid
agents are refused earlier and more consistently, which could expose
existing data with paused, pending, terminated, or broken org-chain
assignees. The added tests cover the critical assignment, heartbeat,
routine, recovery, shared helper, and UI paths. No database migrations
are included.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI GPT-5 Codex via the Paperclip `codex_local` adapter, with
shell/git/GitHub CLI tool use. Reasoning mode and context window are
managed by the adapter runtime and 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 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 (not applicable: no design screenshots requested; UI
behavior is covered by tests)
- [x] I have updated relevant documentation to reflect my changes (not
applicable: no user-facing command or schema docs changed)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending CI)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending Greptile)
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-06 12:45:57 -05:00
Dotta d8e1004551 PAP-10440: group artifacts by task stacks (#7654)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The artifacts surface is where board users inspect files, media, and
documents produced by agents.
> - Grouped artifact stacks make that surface easier to scan by task,
but the first pass still made grouping feel secondary to media filters.
> - The follow-up request was to make grouping the default and give the
grouping control the same icon-only outline treatment used on the issues
page.
> - This pull request keeps the existing artifact grouping API/UI, then
polishes the artifacts toolbar state and Storybook review coverage.
> - The benefit is that `/artifacts` now opens in the task-stack view by
default while preserving explicit flat-mode filtering via
`groupBy=none`.

## Linked Issues or Issue Description

No public GitHub issue exists for this internal Paperclip task.

### Subsystem affected

ui/ — React + Vite board UI.

### Problem or motivation

The `/artifacts` grouping affordance was visually placed after the media
filters, rendered as a text button, and defaulted to a flat artifact
list. Internal follow-up `PAP-10465` requested the grouping icon move
left of the filters, become an icon-only outlined button like `/issues`,
and make Task grouping the default.

### Proposed solution

Default `/artifacts` to grouped Task stacks, keep explicit flat mode
available as `groupBy=none`, move the grouping control before the media
chips, and restyle it as the shared icon-only outline button pattern.

### Alternatives considered

Leaving flat mode as the implicit default was rejected because it does
not satisfy the follow-up. Keeping a text label on the grouping trigger
was rejected because `/issues` already established the icon-only outline
pattern for this class of toolbar control.

### Roadmap alignment

This aligns with the `Artifacts & Work Products` roadmap item by making
generated outputs easier to inspect and operate from the board UI.

## What Changed

- Defaulted the `/artifacts` page to `groupBy=task` when no grouping URL
param is present, while keeping explicit flat mode available with
`groupBy=none`.
- Moved the group control before the media filter chips and changed it
to an icon-only outlined button using the shared `Button` pattern.
- Updated artifact page tests to cover default Task grouping, explicit
flat mode, trigger ordering, and icon-only outline metadata.
- Updated the artifact Storybook story so its toolbar mock matches the
production ordering and grouped Task is documented as the default mode.

## Verification

- `pnpm exec vitest run ui/src/pages/Artifacts.test.tsx
ui/src/components/artifacts/ArtifactGroupCard.test.tsx` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `git diff --check` — passed.
- QA visual validation from internal follow-up PAP-10466 passed
desktop/mobile scenarios. Screenshot evidence attached there:
- Desktop default:
http://paperclip-dev:3100/api/attachments/bc81305d-f5de-485c-abeb-9e7c3d9d8539/content
- Desktop toolbar close-up:
http://paperclip-dev:3100/api/attachments/3375a62b-2110-48f3-bafa-ea98c00f99f7/content
- Mobile default:
http://paperclip-dev:3100/api/attachments/bfc5642e-9248-431e-9bac-36284dec1c89/content
- Mobile toolbar close-up:
http://paperclip-dev:3100/api/attachments/ca79401a-5ba8-464d-bc6e-aeffd47fe695/content
- GitHub PR checks on head `431964c8b` — passed, including Greptile 5/5.

## Risks

Low to medium risk. The main behavior shift is intentional: `/artifacts`
now queries grouped Task stacks by default. Existing flat mode remains
available through the grouping menu and explicit `groupBy=none` URLs.

## Model Used

OpenAI Codex, GPT-5.4 class coding model in this Paperclip heartbeat
environment, with shell, git, test, and GitHub CLI tool use. Context
window managed by the Codex runtime.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have 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
- [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>
2026-06-06 10:22:47 -05:00
Dotta 7428fb956f [codex] Guard git-sensitive adapter workspaces (#7644)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The affected subsystem is the heartbeat execution path that turns
issue assignment into adapter-backed work in a selected workspace.
> - PAP-10409 and sibling follow-ups failed before useful adapter output
because project/workspace identity became incoherent.
> - A project-workspace-linked child issue could keep
`projectWorkspaceId` / execution workspace state while losing
`projectId`, then a git-sensitive local adapter could fall through
toward an invalid fallback cwd.
> - Paperclip needs to treat coherent workspace identity as part of the
live-path contract, not only as post-failure cleanup.
> - This pull request documents that rule, repairs issue inheritance,
and blocks git-sensitive adapter launch before it can run from the wrong
cwd.
> - The benefit is a bounded recovery path: affected issues are repaired
explicitly, future malformed workspaces fail fast with a clear recovery
action, and the UI surfaces that reason.

## Linked Issues or Issue Description

Refs #7646

Bug report fields:

- Summary: adapter-backed follow-up issues can fail before doing work
when issue creation/inheritance preserves workspace ids but drops
project identity.
- Affected issues: internal Paperclip issues PAP-10408 through
PAP-10412, especially PAP-10409.
- Steps to reproduce: create a project-scoped parent/follow-up tree
where a child issue keeps `projectWorkspaceId` or an inherited execution
workspace but has `projectId: null`, then launch a git-sensitive local
adapter such as `codex_local`.
- Expected behavior: Paperclip derives or preserves coherent project
identity during issue creation, and heartbeat refuses malformed
git-sensitive workspace launches with one clear recovery action.
- Actual behavior before this PR: the run could reach adapter bootstrap
with an incoherent workspace context and fail with git errors such as
`fatal: not a git repository (or any parent up to mount point /srv)`.
- Root cause: child/follow-up issue inheritance preserved workspace
execution context without coherent project context. That let heartbeat
workspace resolution/adapter launch reach a fallback cwd instead of
refusing the malformed workspace state up front.

## What Changed

- Documented the adapter workspace-coherence live-path precondition in
`doc/execution-semantics.md`.
- Updated issue creation/inheritance so workspace-inheriting issues
preserve or derive project identity, while existing mismatch validation
still rejects incoherent project/workspace combinations.
- Added a heartbeat preflight guard for git-sensitive local adapters
that validates effective cwd, persisted workspace identity, project
workspace identity, and required git metadata before launch.
- Added `workspace_validation` recovery actions for this failure class
and ensured the source issue gets a visible, idempotent recovery
comment.
- Surfaced workspace-validation recovery state in issue rows, blocked
notices, and recovery action cards, including the manual-repair wake
policy label.
- Added focused regression coverage for issue inheritance, all heartbeat
workspace-validation guard branches, recovery display helpers, and UI
recovery components.

## Verification

- `pnpm exec vitest run
server/src/__tests__/heartbeat-workspace-session.test.ts`
  - Result: 1 test file passed, 68 tests passed.
- `pnpm exec vitest run
ui/src/components/IssueRecoveryActionCard.test.tsx`
  - Result: 1 test file passed, 12 tests passed.
- `pnpm exec vitest run ui/src/components/IssueBlockedNotice.test.tsx
ui/src/components/IssueRecoveryActionCard.test.tsx`
  - Result: 2 test files passed, 18 tests passed.
- `pnpm --filter @paperclipai/ui typecheck`
  - Result: passed.
- `pnpm exec vitest run
server/src/__tests__/heartbeat-plugin-environment.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/heartbeat-workspace-session.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
ui/src/components/IssueBlockedNotice.test.tsx
ui/src/components/IssueRecoveryActionCard.test.tsx
ui/src/lib/recovery-display.test.ts`
- Result: 7 test files passed, 200 tests passed before the final
guard-branch additions; the changed server file was re-run above.
- UI coverage: `ui/storybook/stories/source-issue-recovery.stories.tsx`
contains rendered scenarios for the generic recovery chip,
workspace-validation recovery chip, blocked notice indicator, recovery
action card, and issue-row chip.
- Screenshot capture attempt: Storybook started successfully on
`http://127.0.0.1:6016/`, but screenshots could not be captured in this
runner because `agent-browser` launched an unusable Chrome binary and
Playwright Chromium failed on missing system library `libatk-1.0.so.0`;
the runner is non-root and lacks passwordless sudo for installing
browser dependencies.
- Hosted CI on final commit `969594e7` is green, including `verify`,
`Build`, `Typecheck + Release Registry`, `General tests (server)`,
workspace suites, serialized server suites, `Canary Dry Run`, and `e2e`.
- Roadmap checked: no duplicate roadmap item; this is a tightly scoped
reliability fix for existing heartbeat/workspace behavior.
- Duplicate PR search checked: no open PR matched `workspace coherence
adapter cwd`.

## Risks

- Medium risk: heartbeat launch is stricter for git-sensitive local
adapters and can now block malformed workspace states before adapter
execution.
- Mitigation: the guard is limited to local git-sensitive adapters and
records a source-scoped recovery action with structured evidence instead
of retrying indefinitely.
- Compatibility: valid project/workspace execution paths continue
normally; explicit project/workspace mismatches remain rejected.

> 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-based `codex_local` coding agent with terminal/tool
use. Work was produced through Paperclip issue execution with focused
local test runs.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have 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
- [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>
2026-06-06 09:39:26 -05:00