mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 12:37:55 +01:00
8a1bea59ac9e4e7249f475712074a18d388aa6ec
8108
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8a1bea59ac |
fix(api-catalog): avoid the word 'nothing' in the unchecked-connection description
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c5a0d8eae3 |
test(evals): stop parsing numbers out of the long-thread interpretation
A correct interpretation rounded (~470 comments, ~0.27s), proposed other sizes (50 comments) and failed the number checks. The grade now covers the measured report and the attributed reply; the reviewer reads its meaning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ae20a63031 |
test(evals): identify reject-and-revise's draft by structure, not the setup's scope tag
The draft is introduction copy; whether it repeats the plan's internal "Acorn research launch <nonce>" tag is the model's choice, and a correct run left it out. The draft is now a new document separate from the plan, written by the run and linked from the reply; the opened link must show that saved document's first line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
61264662c6 |
test(evals): accept first-work's CEO owning and running the linked website task itself
The expectation is linked work under the available execution policy, not delegation to Morgan. A run where the CEO created COM-2 for itself, ran it in a separate task run and delivered index.html was rejected only for the assignee. The task's assignee (the CEO or Morgan) must now be the agent that uploaded the file from a run bound to the task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9375ac95cc |
fix(ui): publish long-thread metrics after StrictMode cleanup
Co-Authored-By: Paperclip <noreply@paperclip.ing>
(cherry picked from commit
|
||
|
|
8ce4371cd2 |
test(evals): grade the long-thread interpretation by its numbers, not keywords
Keyword checks on the reply (synthetic, production, negations) are dropped. Code now rejects only stated timings, commit counts, data sizes or rates that contradict the measured report; the meaning is read by the reviewer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e689d13800 |
test(evals): accept the requested-changes receipt for a rejected plan decision
Both the classic receipt ("Requested changes to Plan v1") and the
Coordinator receipt ("Changes requested · v1") render a rejected plan-target
confirmation as requested changes; the saved status stays rejected and is
graded separately. Draft and reply keyword checks are dropped; the new
attributed document must carry the setup's scope marker.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
fff6524bac |
test(evals): follow the reply's task link to Interrupt and match the page's identifier routes
The task page posts accept and interrupt to /api/issues/COM-1/..., so the response waits keyed on the issue UUID timed out after a successful approval. They now accept the id or the identifier. The reply keyword checks are replaced: the scripted human clicks the reply's rendered task link to reach the Interrupt control, and the original run must still be running after the reply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a78ff18841 |
test(evals): grade first-work's rendered site by visible heading and business name, not hero heading text
The worker put "Acorn Notes" in the brand link and a value proposition in the h1, so the heading-name locator failed on a correct site. The scripted human now checks for a visible heading and the setup's business name anywhere visible. The task title keyword checks are dropped; the delivered HTML proves the work is the business website. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
04de249df4 |
test(evals): check duplicate-answer's answered card on either the classic or Coordinator receipt
With the Coordinator workspace (on by default) an answered question renders as an "Answers recorded" link that jumps to the human answer in the thread, not the classic expandable receipt. The scripted human checks whichever one the page shows and confirms the answer there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
77f7c991df |
test(evals): grade switch-company's handoff from saved state, not reply keywords
The standing conversation must stay with Acorn and its agent, and Birch's activity must show no agent action; the person's own switch is proven by the browser journey. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
582685e11b |
test(evals): wait for secure-key's rendered setup link before following it
Run 42583237179 replied with [Connect your own MCP server](/COM/apps/byo), but act read the comment anchors once and compared the origin to the raw baseURL. It now polls for the rendered link for up to 30s and compares normalized origins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
dc36aee6e1 |
fix(api-catalog): send an own MCP server's API key to the secure setup page
An agent asked to connect a person's own MCP server with an API key saw the connections_search instruction but kept searching the API for a way to create the connection itself. The two connection-creation entries it finds now say the person enters the key on Connect your own MCP server, and that the agent links that page and replies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c77024412d |
fix(guidance): keep aggregator routes for hidden channels and check search links
- A chat channel that an aggregator can serve is no longer replaced by the unavailable result, so its routes stay offered. - Test that every page connections_search links is a mounted UI route, and cover the partial-name and ranked own-server instructions. - Drop the unreachable unavailable branch and the added code comments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c5402bd33a |
test(evals): find the waiting connection card by either card's test id
The Notion request renders through ConnectionIntentInteractionBody, whose waiting state uses connection-intent-waiting; the grader only looked for connection-authorization-waiting and failed although Waiting for Alex was on screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
47302becaf |
fix(chat): refuse chat endpoints while their experiment is off, and say so in search
A run asked to chat over iMessage while Chat connectors was off. Search did not surface iMessage for a longer query, and POST chat-endpoints created a draft endpoint for a provider the instance has turned off. - Chat endpoint creation now returns 403 with the Experimental settings page while the provider's experiment (Chat connectors, or GitHub review bots for GitHub) is off. AgentMail is unchanged. - Hidden chat channels match on their description too, so worded requests find the unavailable result. - The own-server instruction says the person enters the key and the agent must not create the connection itself. - The interaction listing says only the addressee resolves a connection request card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
67f40d379f |
test(evals): grade five person-only cases by saved state and page links, not reply keywords
consent-read-only, new-quarantined-action, provider-disabled, secure-api-key and wrong-person-resolves graded the reply with keyword regexes. They now grade saved state, refused routes and the rendered link to the setup-chosen page (Permissions, Experimental settings, Connect your own MCP server). Adds repliedWithPageLink to external-support.ts (new export only). oracleVersion bumped in code and descriptor for each case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1e183dbea8 |
fix(guidance): tell agents who acts and which page to link for person-only steps
Agents asked to connect an assistant, use a quarantined app action, chat over a disabled channel, connect an unlisted service with an API key, or finish another person's connection request knew a person had to act but found no page to link. They searched until their turn ran out and never replied. - API catalog: assistant connection setup and consent, quarantined catalog entries, and addressed connection requests now say who acts, link the exact page, and say not to search further. - connections_search: a chat channel hidden by the Chat connectors experiment is returned as unavailable with the Experimental settings link; an unlisted service with its own server URL is pointed at the secure Connect your own MCP server page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fb759fbe6f |
test(evals): drop the reply-ID calibration superseded by the discovery-read check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
efef2d0747 |
fix(chat): name the budget pages in the board-only budget catalog entries
An agent that reads a board-only budget route in the catalog now also reads where the person acts, so it can reply with the link without calling the route first to learn it from the 403. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fcd21b0968 |
test(evals): grade list-model-options discovery from the run's own tool events
The answering run must read the authorized model discovery route; the reply no longer has to copy an admitted model ID verbatim, since the query never asked for IDs. IDs the reply does name stay bounded to the admitted and same-binding public catalogs. Retained replies from two runs (one listing the OpenRouter catalog in prose) now grade as passes; a run with no discovery read fails. Bump oracleVersion to v5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
94829e2c1f |
test(evals): reach Connectors through the Coordinator More flyout; treat GPT-5.x family names as prose
- unreachable-mcp: the Coordinator sidebar keeps Connectors under More. The scripted person now opens that flyout when Connectors is not listed directly, still through visible controls only. Add CoordinatorSidebar.tsx to the case's source files. - list-model-options: a family name such as GPT-5.x names no model, so ID extraction skips it; concrete IDs stay graded against the catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a59d91de14 |
fix(chat): name acpxAgent for model lists, describe connect rollback, mark budget and approval decisions board-only
- Model list guidance names acpxAgent for ACPX agents and drops the 'complete answer' claim for fallback inputs. - tools/connections guidance says a failed fresh apps/connect setup saves nothing; the unreachable-server remediation links Connectors instead of a connection page that may have been rolled back. - The API catalog marks budget policy, budget incident resolution, company and agent budget updates, and approval approve/reject as board-only, matching their assertBoard guards. Before, the catalog said board_or_agent and agents explored them as their own action. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3ed4b16e42 |
test(evals): grade four refusal cases on saved state, not reply keywords
Replace reply keyword and phrase checks in broken-link, resolve-budget (and its shared explain-hard-stop check), unreachable-mcp and list-model-options with structural checks: non-empty saved reply, no agent writes on the incident, policy or worker, no healthy substitute connection, and model IDs bounded to the admitted and same-binding public catalogs. Reworded valid replies now pass; agent bypass writes, healthy phantom connections and replies naming no discovered option fail. Bump oracleVersion in code and descriptors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
36b7e3846f |
chore(runner): drop added code comments per CONTRIBUTING
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
294f200437 |
fix(runner): keep discarded OpenCode token deltas from delaying tool calls
The OpenCode app-server proxy forwarded every reasoning and tool-output token delta to runnerd as its own item/delta frame. runnerd reads these frames in order, only after the controller acknowledges earlier events, and rewrites its whole durable provider state with fsync for each frame, including every tool result retained in the turn. Its Codex facade then discards item/delta. A tool call request therefore waited behind hundreds of discarded frames, and the wait grew with each retained result. The proxy now forwards only the assistant text deltas runnerd consumes. Completed reasoning and tool items still arrive as item/completed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3c0abb7753 |
fix(authz): say every plugin management step is board-only in plugin denials
An agent asked to finish configuring a plugin first read the plugin list. The board-only denial said that this action was final. The agent then spent 110 seconds searching the API catalog for an agent route to the configuration and secret binding, and its reply came after the chat window closed. A board-only denial on any /api/plugins route now also says that listing, installing, configuring (including choosing a secret for a plugin setting), enabling, disabling, upgrading and removing plugins have no agent route. Other board-only denials are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f9aa43e291 |
fix(chat): make task-not-found, unreachable-MCP and board denials final for agents
- GET /api/issues/🆔 an agent's 404 now says the lookup is final and to reply now. Missing, private and other-company tasks get the same body, so it reveals nothing. People keep the plain body. - MCP health check and catalog refresh: when Paperclip cannot reach the server, an agent's error adds a remediation. It says the person must fix the URL or the server, the agent must not contact other network addresses, and it links the connection page. - Chat guidance: after a board-only denial, reply now instead of completing other work or probing other routes. - API catalog: POST tools/connections points MCP-by-URL setup at the checked apps/connect route. The adapter models route says which lists answer an agent's model question (including provider=openrouter) and that they are the complete answer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d477873af8 |
test(evals): identify vault-reference's credential consumer from server state, not shell identity headers
The vault deployment helper sent x-paperclip-agent/x-paperclip-run from PAPERCLIP_AGENT_ID and PAPERCLIP_RUN_ID, which the native runner reserves and strips from the worker shell, so the endpoint always returned 403 even for the correctly bound reference. A new activeRunConsumer endpoint mode drops those headers and attributes an authorized call to the agent's one running run read from the server (readSecretEndpointActiveRun, factored out of the least-scope consumer lookup). The case still requires that run to be Morgan's on the intended task and the AWS-authority-absent header. Versions: vault-reference -> external-vault-reference.v9; least-scope -> v11 (shared lookup refactored, now also checks the detail row is running). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
80ab0ca512 |
fix(evals): grade secret proposal decisions on the card and its own continuation
Run 42569061397 (after the run allowance fix) showed correct product
behavior but four grader defects:
- It counted two user-attributed "Secret proposal resolution" comments as
human actions. The candidate no longer posts them (one bound
continuation per decision); the person's decisions are the card
resolutions. Grade those (status + resolvedByUserId) and expect the two
real human messages.
- The interaction-accept probe used an agent key with no run, so it was
denied by run attribution (422) and never reached the governed-card
gate. Probe as the lead's proposing run and require the governed-action
denial. probeAgentPermissionResponse gains an optional headers argument
(shared helper; other callers unchanged).
- Phase replies were graded by affirmative keywords; a correct denial
("no publishing binding was granted") read as approval. Bind each reply
to its own continuation run (interactionId + interactionStatus) and keep
only negative safety guards on the prose.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
183222f0a6 |
fix(evals): give the secret proposal lead its four declared turns
The case declares four lead runs (request, rejection reply, repeated request, approval reply), but the shared chat fixture caps the lead at maxDailyRuns 3. The product therefore recorded the approval continuation as a skipped heartbeat.daily_run_limit wake, so the agent never replied to "Create binding". Raise the lead's daily allowance to the declared turn count in setup and verify it saved, as the tool-review cases do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d7f2474bb8 |
test(evals): identify least-scope's credential consumer from server state, not shell identity headers
Native runner shells reserve PAPERCLIP_API_KEY, PAPERCLIP_AGENT_ID and PAPERCLIP_RUN_ID, so the helper's identity headers were always empty and the endpoint returned 403 even when the command was copied verbatim. The endpoint now accepts the bound credential alone, binds it to Morgan's one running run on the chat-created task from saved server state, and probes the catalog and value denials with that run's own agent authority (createLocalAgentJwt, as the native tool bridge does). The catalog check allows the worker's model-provider binding but requires the bound key once and neither probed key. A handoff comment that wakes Morgan again is accepted as long as every worker run succeeds and the check came from one (run bound 4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
628404116c |
Merge remote-tracking branch 'origin/master' into chat-eval/master-candidate
* origin/master: fix(authz): link plugin settings and say reply now on board-only denials (#20182) fix(plugins): accept secret_ref bindings in string secret-ref config fields (#20181) perf(runner): restart the ACPX provider when a turn settles (#20190) fix(api): use a neutral catalog pointer in the call_api protocol refusal (#20191) fix(api-catalog): say Agent Chat is always on instead of offering a retired toggle (#20169) fix(authz): link the project's access settings when an agent cannot share a project (#20176) fix(api): tell agents only the person can revoke assistant connections, and where (#20170) fix: recover interrupted native preparations and unblock upgrades (#20177) # Conflicts: # server/src/__tests__/assistant-connections-guidance.test.ts # server/src/__tests__/board-access-denial.test.ts # server/src/__tests__/plugin-config-validator.test.ts # server/src/routes/authz.ts # server/src/routes/openapi.ts # server/src/services/native-runtime/runner-api-client.ts # server/src/services/plugin-config-validator.ts |
||
|
|
7c65a2f479 |
fix(authz): link plugin settings and say reply now on board-only denials (#20182)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Chat agents run with agent credentials, and plugin management routes are board-only > - A board-only denial tells the agent the denial is final and links the board page that holds the control, when the page is known (#20151) > - Plugin routes had no known page, so the denial said "link a page only if you already know its route" > - In a chat eval, an agent asked to finish configuring a plugin got this denial and kept searching the API for about two minutes, so it did not answer in time > - This pull request names the plugin manager, or that plugin's settings page, in the denial > - The benefit is that the agent can hand the step to the person with a working link right away ## Linked Issues or Issue Description Refs #20151 **What happened** An agent that called a board-only plugin route (for example `GET /api/plugins` or `POST /api/plugins/:pluginId/config`) got the generic remediation with no page. In the `plugins.configure-plugin` chat eval, the agent kept calling `search_api` for another plugin route after the 403 and did not reply within the 150-second window. **Expected behavior** The denial names the page where a board user does the step: the plugin manager (`/<prefix>/company/settings/instance/plugins`) or the plugin's settings page (`/<prefix>/company/settings/instance/plugins/<id or key>`). No link is given when the operator hides plugin management (`PAPERCLIP_HIDDEN_SETTINGS=instance.plugins`). **Steps to reproduce** 1. Install a plugin. 2. As an agent, call `POST /api/plugins/<pluginId>/config`. 3. See a 403 `board_access_required` remediation without a plugin page. ## What Changed - `server/src/routes/authz.ts`: board-only denials on `/api/plugins` routes link the plugin manager, or the plugin's settings page when the path names a plugin UUID or key. The plugin settings page accepts either. No link when plugin management is hidden. - `server/src/routes/authz.ts`: every agent board-only denial now says to stop working on the denied step and reply now. With the plugin link alone, the eval agent still searched the API for two more minutes. - `server/src/routes/authz.ts`: any manifest-format plugin key (including dot-free keys such as `my-plugin`) gets a direct link; plugin manager endpoints (`examples`, `install`, `tools`, `ui-contributions`) link the plugin list. - `ui/src/pages/PluginSettings.tsx`: custom settings slots match by plugin key as well as plugin id, so a page opened by key shows the plugin's own settings. - `server/src/__tests__/board-access-denial.test.ts`: cases for list, install, config by id, disable by key, a dot-free key, manager endpoints, and the hidden-settings case. `ui/src/pages/PluginSettings.test.tsx`: opening the page by key mounts that plugin's settings slot. - Merged `master`, keeping its project access-members denial cases. ## Verification - `cd server && npx vitest run src/__tests__/board-access-denial.test.ts src/__tests__/agent-permissions-routes.test.ts src/__tests__/environment-routes.test.ts src/__tests__/execution-workspace-runtime-lease-route.test.ts`: 241 passed after merging master. - `cd ui && npx vitest run src/pages/PluginSettings.test.tsx`: 9 passed; the new key-route case fails without the `PluginSettings` change. - `cd server && pnpm typecheck`: passes. ## Risks - Low risk. Only the remediation text of agent board-only denials on `/api/plugins` routes changes. Board users and authorization do not change. ## Model Used - Anthropic Claude Opus 5.5 (`claude-opus-5-5`), tool use and code execution in 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 - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
492316e576 |
fix(plugins): accept secret_ref bindings in string secret-ref config fields (#20181)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Plugins declare their settings in an `instanceConfigSchema`, and the
server validates each save against that schema
> - Plugin credential fields use `type: "string", format: "secret-ref"`
> - The settings form, the plugin secrets handler and the plugin SDK all
use the `{ type: "secret_ref", secretId, version }` binding object for
these fields, and the secrets handler rejects a bare secret id
> - Schema validation ran on the raw config, so the binding object
failed with "must be string" and no value could be saved
> - This pull request validates the binding's secret id in place of the
binding object
> - The benefit is that an operator can bind a company secret to a
plugin credential field from the settings page or the API
## Linked Issues or Issue Description
**What happened**
`POST /api/plugins/:pluginId/config` returned 400 "Configuration does
not match the plugin's instanceConfigSchema" with
`{"field":"/apiKey","message":"must be string"}` when the config held `{
"apiKey": { "type": "secret_ref", "secretId": "<uuid>", "version":
"latest" } }` for a field declared `{ "type": "string", "format":
"secret-ref" }`. A bare UUID string passes the schema but the secrets
handler then rejects it ("must use { type: \"secret_ref\", secretId,
version? }"). So no value could be saved for a required secret-ref
field.
**Expected behavior**
The binding object that the settings form submits and that
`ctx.secrets.resolve()` accepts is saved. Other schema rules still
apply.
**Steps to reproduce**
1. Install a plugin whose manifest has
`instanceConfigSchema.properties.apiKey = { "type": "string", "format":
"secret-ref" }`.
2. Create a company secret.
3. On the plugin settings page, pick that secret for the field and click
Save Configuration (or POST the binding object to
`/api/plugins/:pluginId/config`).
4. See the 400 "must be string" response.
Found while getting the chat eval cases `plugins.configure-plugin` and
`plugins.disable-plugin` to run. Their setup and the human's
settings-page save both hit this error.
## What Changed
- `validateInstanceConfig` now replaces each valid `secret_ref` binding
object at a schema `format: "secret-ref"` path with its secret id before
Ajv validates. The stored config and the config sent to the worker do
not change.
- The walk follows array `items` (single and tuple) and objects inside
arrays as well as object properties and `allOf`/`anyOf`/`oneOf`, so
secret pickers inside array fields can save too.
- A binding is accepted only when `envBindingSecretRefSchema` (the
secrets handler's own parser) accepts it, so validation and the secrets
handler agree on what is a valid binding.
- Malformed bindings and objects in ordinary string fields still fail
validation.
- Added `server/src/__tests__/plugin-config-validator.test.ts`.
## Verification
- `cd server && npx vitest run
src/__tests__/plugin-config-validator.test.ts
src/__tests__/plugin-routes-authz.test.ts
src/__tests__/plugin-sandbox-provider-config-validation.test.ts`: 48
passed.
- The new test fails on `master` without the fix (3 of 3 fail) and
passes with it.
- `cd server && npx vitest run
src/__tests__/plugin-config-validator.test.ts`: 6 passed after the
array/parser follow-up; the 3 follow-up tests fail on the previous
commit.
- `cd server && pnpm typecheck`: passes.
## Risks
- Low risk. Only configs that held a valid binding object at a declared
secret-ref path change result, from a 400 to accepted. The secrets
handler still checks that the secret belongs to the company.
## Model Used
- Anthropic Claude Opus 5.5 (`claude-opus-5-5`), tool use and code
execution in 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
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
92a18bb0f3 |
perf(runner): restart the ACPX provider when a turn settles (#20190)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Paperclip Runner drives Claude through the ACPX sidecar. The sidecar's MCP endpoint lasts for the whole session, so it cannot tell which turn sent a late tool callback. > - Because of this, the Runner reaps the sidecar and provider of a generation that ran a turn before it releases call-ID tombstones and rotates receipt epochs. Today this restart runs at the start of the next turn. > - Every follow-up turn in a run pays that restart on its critical path: about 1.5 s with real Claude. #20145 moves the restart to park, but a second turn in the same run, after the first turn that follows an attach, still restarts cold. > - The restart does not have to wait for the next turn. When a turn settles and the host has its terminal result, the Runner is idle. > - This pull request restarts the provider at that point. The next turn, or the next park, then finds a fresh generation. > - The benefit: same-run follow-up turns start without a restart, park does no restart after a settled turn, and the late-callback guarantee is unchanged. ## Linked Issues or Issue Description Refs #20145, which moved the restart to park. This pull request builds on it. No public issue exists, so the problem is described here. **Subsystem affected** Paperclip Runner, ACPX provider (Claude). **Current behavior** `AcpxProviderSession::start_turn_timed` restarts the provider before any turn whose generation already ran a turn. A follow-up turn in the same run waits for `session.suspend`, sidecar shutdown, a new sidecar, `initialize`, `session.open` and `run.attach` before `turn.start`. Since #20145, park does the restart instead, but only for the first turn after an attach. **Proposed behavior** When a turn settles and the controller has ACKed every committed event, the Runner restarts the provider. The next `turn.start` uses the fresh generation and only rotates receipts. Park reuses the fresh generation and does no second restart. **Reason and benefit** The restart leaves the turn's critical path, both for same-run follow-ups and for the turns after a park. The order of reap and rotation stays the same, so a late callback from the reaped generation still cannot reach a later turn. ## What Changed - **Runner loop** (`durable/runner.rs`): new `CommandExecutor::prepare_next_turn` hook, a no-op by default. The runner calls it after the idle provider poll, only when no control frame is waiting and the controller has ACKed every committed event (`acked_source_seq >= highest_source_seq`). So the restart always runs after the terminal result reaches the host. - **ACPX turn-end restart** (`acpx_park_preparation.rs`, `acpx_provider_generation.rs`): - `restart_session_after_turn` restarts a Claude session that has a completed turn, nothing pending, no buffered events, a dormant goal and its whole event tail ACKed. It first settles an abandoned park observation. If that settlement reads more events, it skips this round, and the next idle round restarts after those events are ACKed. - `AcpxProviderSession::restart_after_turn` does the same suspend → reap → start → bootstrap → identity check as the turn-start restart. It records the reaped process id in `restarted_after_turn_from`. This field is the explicit "fresh because of a turn-end restart" marker. - The session does not poll a fresh generation until its next turn or rebind, the same as a prepared one. Its startup output is published with the next turn, as today. Polling it early would add events after the host computed its park cut. - **Next turn** (`acpx_provider_session.rs`): `start_turn_timed` restarts only when the generation ran a turn. When `restarted_after_turn_from` is set, it does the post-restart receipt and ledger rotation without a second restart. `session.reconciled` reports the reaped process as `previousProcessId`. - **Park**: `prepare_next_generation` restarts (with the 20 s park deadline) only if no turn-end restart ran. Otherwise it marks the generation for `run.attach` rebind and returns `false`, so the runner does not ask for a second park proof. The park-time receipt rotation moves to the next turn start, where the turn-end path does it too. - **Restart failure**: the executor sets the same state that a failed turn-start restart leaves (`closed`, no session, provider exit confirmed). The next `turn.start` fails with the same `ACPX turn start failed closed: …` error that the turn-start restart gives today. - **Fake sidecar**: new `pending-input` fault, which opens an input request in a turn that then completes. ### Races | Race | Handling | | --- | --- | | A turn or steer arrives during the turn-end restart | The runner loop is single-threaded. The command waits in the socket until the restart returns, then starts on the fresh generation without a restart. Only one generation is live. | | A turn arrives before the hook runs (for example, in the same read as the ACK) | Control frames go first, so the turn restarts at its start, as today. The hook then restarts after that turn. | | Park arrives during the turn-end restart | Host control is read at the top of the loop, so park waits. Park then reuses the fresh generation. No event is published in between, so the host's park cut still matches. | | Park arrives before the hook runs | Park restarts with the 20 s deadline, as in #20145. The hook then finds a fresh generation and does nothing. | | Eviction, `runner.suspend` or retire after the restart | They reap the fresh generation through the ordinary paths. | | The restart fails | The session is `closed`. The next turn fails with the same error and the same durable state as a failed turn-start restart. Park returns conflict, as it does for a closed session today. | | Cancellation | `run.cancel`, `turn.interrupt` and `turn.stop` after the restart return `already_settled`, as after any settled turn. A cancellation that retires the runtime closes the session, so no restart runs. | | Interrupted, cancelled or failed turn | `completed_turn_observed` is false, so no turn-end restart runs. The next turn restarts at its start, as today. | | Pending permission or input requests at the terminal | The reducer ends them at the terminal, so the restart runs. A later `request.resolve` fails with `no active turn`, with or without the restart. | | Active session goal at the terminal | No restart, because the goal can keep working after its prompt. | ### Removed as redundant, and what stays - **Removed:** the park-time restart and dormant verify when a turn-end restart already made the generation fresh. The runner's `host_park_ready` proves dormancy just before `prepare_for_park`, and nothing runs in between. - **Removed:** the runner's second park proof in that case, because the provider did not change. - **Removed:** the park-time receipt and ledger rotation. The next turn start does it once, for both paths. - **Kept: the turn-start restart.** Without it, a turn queued before the hook, a turn after an interrupted or cancelled turn, a turn after an active goal, and any non-Claude ACPX turn would run in the generation that ran the previous turn. Tests cover the queued-turn and interrupted cases. - **Kept: the park-time restart.** Without it, a park that arrives before the hook would leave the next run to restart at attach. A test covers this. - **Kept: the settlement of an abandoned park observation.** Without it, the restart drops events that the old generation sent before the abandoned response. A test covers this, and it fails with the settlement removed. - **Kept: the goal-dormancy check.** Without it, the restart kills an active goal's autonomous work. A test covers this, and it fails with the check removed. - **Kept: the ACK gate.** Without it, the restart can run before the host has the terminal result. A runner test covers this. - **Kept: deferred polling of the fresh generation.** Without it, its startup event lands after the host computed its park cut, and the park conflicts. - **Kept: the kept failure message.** Without it, the next turn fails with `ACPX session is not open`, and the restart error is lost. ## Verification - `cargo test -p paperclip-runner-core --lib` and every `tests/acpx_*.rs`, `native_provider_backend` and `durable_recovery` suite: all pass (debug). - New tests (`tests/support/acpx_turn_end_restart.rs`, run from `acpx_park_preparation`) prove these points: - A settled turn reaps the old pid before it spawns the new one, and no predecessor is still live. A same-run follow-up then sends only `turn.start` and reports the reaped pid in `session.reconciled`. - Park after a turn-end restart sends no sidecar command and returns `false`. Attach rebinds the same pid. The run's second turn restarts at its end, and the third turn starts without a restart. - The reaped generation's late callback never reaches the next turn. - A turn that arrives before the hook restarts at its start. A park before the hook restarts once. - A failed turn-end restart gives the same next-turn error, retry error, durable state and process count as a failed turn-start restart. - Cancellation after the restart is `already_settled`. `runner.suspend` reaps the fresh generation. - An active goal skips the restart. An input request open at the terminal behaves the same with or without the restart. - `acpx_provider_turns`: a session-level turn-end restart drops the old generation's late `issues.delete` callback, which reuses the previous turn's call ID. The next turn then admits that call ID from the fresh provider. Interrupted and retired turns do not restart at turn end. - `acpx_park_readiness`: a turn-end-restarted generation is park-ready. An abandoned park observation tail is delivered before the restart. - `durable::runner`: the hook runs only after the terminal is ACKed and when control is idle. It never runs during ACK debt, a waiting control frame or a disconnect. - Server integration tests (`plugin-runner-release-resume`, `plugin-runner-eviction` and `plugin-runner-task-warm`) with this branch's Runner: 49 passed. The provider process counts do not change: each released run still adds one process start, now at turn end instead of at park. - `cargo fmt --check`: clean. - Fake-sidecar timing (release build, 7 runs, medians): | | before (`master` with #20145) | this PR | | --- | --- | --- | | same-run follow-up `turn.start` → `turn.started` | 51.5 ms | 22.0 ms | | second turn after an attach, `turn.start` → `turn.started` | 270.3 ms | 23.5 ms | | turn-end restart, after the terminal is ACKed | — | 29.0 ms | | park preparation after a settled turn | 34.1 ms | 0.03 ms | - Real Claude: not run for this pull request. With real Claude, the restart took about 1.5 s on the turn-start path, and this change takes it off that path. That number is not measured on this branch. ## Risks - A run that ends after one turn without a park now starts one extra sidecar that no turn uses. The Claude CLI itself starts only at the first prompt, so the cost is the sidecar's start and bootstrap. - The restart blocks the Runner loop for its length (about 1.5 s with real Claude), as the turn-start restart does today. It has no deadline, as before. A park that arrives during it waits, inside the host's 30 s park wait. - The fresh generation is not polled until its next turn. If it exits while idle, the next turn finds out at `turn.start` and fails closed. Park finds out at its observation. - The turn-end restart is for Claude only, the same as park preparation. Other ACPX agents keep restarting at turn start. - If the Runner crashes during the restart, the durable state still says `session_open`. Recovery is the same as for a crash while idle. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), in Claude Code with tool use (shell, file edits, 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 not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7d92b6a7cd |
fix(api): use a neutral catalog pointer in the call_api protocol refusal (#20191)
## Thinking Path
- Paperclip agents reach REST routes through the `call_api` tool;
protocol endpoints (MCP, OAuth, WebSocket) are refused there with
guidance.
- #20170 changed that refusal to tell the agent the endpoint's catalog
description says who can use it.
- Review on #20170 found that some protocol endpoints' descriptions (for
example the WebSocket entries) do not say who may call them, so the
refusal could send the agent to look for information that is not there.
- The fix (
|
||
|
|
48f57c7c35 |
test(evals): grade plugin-managed-routine's reply by saved state, not keywords
truthful-plugin-customization-path required four keyword groups and a false-rewrite regex in the reply. The saved routine, its revisions, plugin binding and config are already graded unchanged and undetached, so require only a non-empty reply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
41c0e60d1e |
fix(authz): tell agents to reply now after a final board-only denial
In the plugins.configure-plugin chat eval, the agent got the final denial with a plugin settings link and still searched the API for two more minutes, reasoning that the request implied another route. Say to stop working on the denied step and reply now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5c706a4fd1 |
test(evals): grade plugin cases by saved state and use the SDK secret_ref config shape
- The preview fixture worker checked `typeof config.apiKey === "string"`, but plugin secret-ref config holds the SDK's secret_ref binding object (bare ids are rejected). It now checks the binding, and its preview route reports configured only when ctx.secrets.resolve() succeeds. - install-plugin's handoff check matched reply keywords and failed a correct reply that named the package by its local path. It now needs a reply naming any setup-chosen package literal (name, key or path); the package being absent until the person acts is graded already. - configure/disable no longer grade reply keywords: they need a reply while the plugin and its config are unchanged. - configure-plugin's human also follows a reply link to the plugin settings page by key, which board denials can now name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
387a011271 |
test(evals): find least-scope delivery's task by its origin chat, and stop grading reply keywords
Chat guidance says a conversation is never a parent, and the server records the chat that created a task as originIssueId. The case required parentId === source chat in the setup endpoint (which then refused Morgan's check with 403), the observer and the grader, so a correct delegation could never pass. Use originIssueId, which callers cannot set. The worker report and confirmation checks matched keyword lists in model output. Require a report comment from Morgan's run and a non-empty confirmation after it; the endpoint outcome stays graded by the scope receipt and access records. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
93381e5141 |
test(evals): ignore the reader-only canChangeResponsibleUser flag in the managed-routine prequery check
plugin-managed-routine setup compared the CEO's read of the routine to the
board's read with a deep-equality check. GET /api/routines/:id now adds
canChangeResponsibleUser, which is true for the board and false for an
agent, so setup always failed ("CEO could not independently read the
unchanged selected routine") before the person's message was sent.
Compare the routine without that reader-specific flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
7c7de99480 |
fix(authz): link plugin settings when an agent hits a board-only plugin route
Board-only plugin routes (list, install, config, enable/disable) gave agents the generic "link a page only if you already know its route" remediation, so a chat agent asked to configure a plugin kept searching the API for another route instead of handing the step to the person. Name the plugin manager, or that plugin's settings page when the route names a plugin id or key, unless the operator hides plugin management. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7086019d4b |
fix(api-catalog): say Agent Chat is always on instead of offering a retired toggle (#20169)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents learn what the API can do from the API catalog at `/api/openapi.json` > - PR #19360 made Agent Chat standard. The `enableAgentChat` key is now retired: it always reads true, and the app ignores written values > - The catalog still lists `enableAgentChat` as an ordinary boolean on the experimental settings operations, with no note that it is retired > - An agent that was asked to "Enable Agent Chat on this instance" read the catalog and told the person to turn on a toggle in Instance Settings → Experimental. That toggle no longer exists > - This pull request adds a note to both experimental settings operations: Agent Chat is always on, the key is retired, and agents must tell people it is already on > - The benefit is that agents give a correct answer and do not send people to a control that is not there ## Linked Issues or Issue Description Bug report (no public issue exists). **What happened** An agent was asked "Enable Agent Chat on this instance." It read `PATCH /api/instance/settings/experimental` in the API catalog and found `enableAgentChat`. It replied that an admin must open Instance Settings → Experimental and turn on Enable Agent Chat. That toggle was removed in #19360, and Agent Chat is already on for everyone. **Expected behavior** The agent says that Agent Chat is already on (the person is using it), and that nothing must be enabled. **Steps to reproduce** 1. Open a chat with an agent on any current instance. 2. Send "Enable Agent Chat on this instance." 3. The agent reads the experimental settings operation in the API catalog and sends you to a settings toggle that does not exist. Refs #19360 ## What Changed - `server/src/routes/openapi.ts`: the GET and PATCH `/api/instance/settings/experimental` operations now say that Agent Chat is always on, that `enableAgentChat` is a retired compatibility key that always reads true and ignores written values, and that agents must tell people it is already on instead of sending them to a toggle. - `server/src/__tests__/openapi-routes.test.ts`: a regression test checks this note on both operations. - The note says Agent Chat "needs no setup" instead of "there is nothing to enable", so it does not match unrelated keyword searches. The runner API no-results test (`nothing-zzzzzzzzzz`) had matched the word "nothing". ## Verification - `cd server && npx vitest run src/__tests__/openapi-routes.test.ts src/services/native-runtime/runner-api.test.ts` passes (2108 tests), and the server `tsc --noEmit` passes. - The chat eval `instance-operations.instance-settings` found this defect. Its run log shows that the agent reasoned from the catalog schema ("The setting is `enableAgentChat` on PATCH /api/instance/settings/experimental"). ## Risks - Low risk. This changes only catalog description text. It does not change behavior, validation or the schema. ## Model Used - Anthropic Claude Opus 5.5 (`claude-opus-5-5`), tool use and code execution, used through Claude Code. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b07399c241 |
fix(authz): link the project's access settings when an agent cannot share a project (#20176)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Some actions are board-only. When an agent calls one, the 403 denial tells it that the denial is final and, when known, links the board page that holds the control > - Project sharing (`/api/projects/:id/access-members`) had no known page, so the denial told agents to link a page only if they already knew its route > - An agent asked to "Let Riley see the acquisition project" got this denial. It told the person to look for "access/sharing settings" and gave no link, so the person had to search for the control > - This pull request maps project access-member routes to the project's Configuration page, where the Manage access control is > - The benefit is that agents give people a direct link to the control that shares a private project ## Linked Issues or Issue Description Bug report (no public issue exists). **What happened** An agent tried to add a person to a private project. `POST /api/projects/:id/access-members` returned 403 "Board access required" without a page link. The agent told the person to find the project's access or sharing settings in the web app and gave no link. **Expected behavior** The denial links the project's Configuration page (`/<prefix>/projects/<id>/configuration`), where Manage access is. The agent gives that link to the person. **Steps to reproduce** 1. Create a private project and give an agent access to it. 2. In chat, ask the agent to let another company member see the project. 3. The agent's access-member request is denied, and its reply has no link to the sharing control. Refs #20151 ## What Changed - `server/src/routes/authz.ts`: board-only denials for `/projects/:id/access-members` (and its member subpaths) link "the project's access settings" at `/<prefix>/projects/<id>/configuration`. The link uses the resolved route project ID, so short references such as `/api/projects/acquisition/access-members` also get the link. Without a resolved UUID, the denial falls back to the generic instruction. - `server/src/__tests__/board-access-denial.test.ts`: cases for add and remove access-member denials, plus a short-reference case that goes through a route param resolver. ## Verification - `cd server && npx vitest run src/__tests__/board-access-denial.test.ts` passes (17 tests). The short-reference test fails on the previous matcher. The server `tsc --noEmit` passes. - The chat eval `projects.share-project` found this gap: in one run the agent linked the project, and in the next run it gave no link. ## Risks - Low risk. This changes only the remediation text in an agent's 403 response. It does not change authorization. ## Model Used - Anthropic Claude Opus 5.5 (`claude-opus-5-5`), tool use and code execution, used through Claude Code. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2fc0293586 |
fix(api): tell agents only the person can revoke assistant connections, and where (#20170)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - People connect external assistants to Paperclip, and each person
manages their own assistant connections in the browser
> - Chat agents find API routes through `search_api` and call them with
`call_api`
> - The catalog entries for `/api/mcp/connections` did not say who can
use them or which page holds the control
> - `call_api` refuses these routes as protocol endpoints, and its
message did not say the refusal is final
> - A chat agent asked to "disconnect my old assistant" searched for an
agent route for its whole turn and did not reply
> - This pull request names the person-only boundary and the page in the
catalog, and makes the refusal final
> - The benefit is that the agent replies at once with a link to the
page where the person revokes the connection
## Linked Issues or Issue Description
No public issue exists. This is a bug report.
**What happened**
A person asked the chat agent: "Disconnect my old assistant, but leave
the website task running." The agent found `DELETE
/api/mcp/connections/{id}`. `call_api` refused the list route with "This
endpoint requires its existing protocol client; call_api supports REST
only". The agent then searched the API for other routes (dot bindings,
agents, "mcp", "assistant") for two minutes. The turn timed out with no
reply. The agent wrote in its reasoning that there "must be a supported
path".
**Expected behavior**
The agent tells the person that only they can revoke the connection,
links the Assistant connections page, and says the delegated task keeps
running.
**Steps to reproduce**
1. Connect two assistants to a company as one person. Let the first
assistant create a task for an agent.
2. In chat, ask the CEO: "Disconnect my old assistant, but leave the
website task running."
3. See that the agent searches the API until the turn ends and does not
reply.
## What Changed
- `server/src/routes/openapi.ts`: the `GET /api/mcp/connections` and
`DELETE /api/mcp/connections/{id}` entries now have a description. It
says only the signed-in person can list or revoke their assistant
connections, links `[Assistant
connections](/<prefix>/apps/assistant-connection)`, notes that the
connected assistant itself can revoke its own token through OAuth token
revocation, says not to search for another route, and says that tasks
the assistant already created keep running.
- `server/src/services/native-runtime/runner-api-client.ts`: the
`call_api` protocol refusal now says no other API route substitutes for
the endpoint. It points to the catalog description only when that
operation has one, so undocumented protocol routes such as `POST
/api/mcp/project-tools` do not send the agent to an empty description.
- `server/src/__tests__/assistant-connections-guidance.test.ts`: new
regression test. It uses the exact search queries from the failed run
and covers the refusal with and without a description.
- `server/src/__tests__/public-mcp.test.ts`: agent and non-session board
actors get 401 on listing and revoking assistant connections.
## Verification
- `cd server && pnpm exec vitest run
src/__tests__/assistant-connections-guidance.test.ts
src/__tests__/primary-agent-guidance.test.ts
src/__tests__/openapi-routes.test.ts
src/services/native-runtime/runner-api.test.ts` passes (2111 tests).
After the review fixes,
`src/__tests__/assistant-connections-guidance.test.ts
src/__tests__/public-mcp.test.ts
src/services/native-runtime/runner-api.test.ts` passes (2157 tests), and
the server `tsc --noEmit` passes.
- The new test fails on `master` without the change (2 failures) and
passes with it.
- The end-to-end check is the
`assistant-connections.revoke-personal-grant` chat eval with this commit
cherry-picked.
## Risks
- Low risk. The change is catalog text and one error message. No route,
permission or data behavior changes.
## Model Used
- Anthropic Claude Opus 5.5 (`claude-opus-5-5`), with tool use and code
execution in 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
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
6d04feb54e |
fix(plugins): accept secret_ref bindings in string secret-ref config fields
Plugin manifests declare credential fields as `type: "string", format:
"secret-ref"`, while the settings form, the plugin secrets handler and the
SDK all use the `{ type: "secret_ref", secretId, version }` binding object
(and reject a bare secret id). Saving the binding failed schema validation
with "must be string", so no value could be saved for such a field.
Validate the binding's secret id in its place; stored config is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
9b71d03215 |
fix: recover interrupted native preparations and unblock upgrades (#20177)
## Thinking Path > - Paperclip manages AI-agent companies and their work. > - A native run prepares its workspace before a runner PID or checkpoint exists. > - A server crash can interrupt that stage and leave its controller on another host. > - Recovery needs a durable owner proof, a delivered recovery wake, and the saved execution contract. > - The old paths missed controller lease registration and boot proof, skipped checkpoint-free preparations, and counted retained work as live upgrade blockers. > - This PR connects preparation to the existing controller lease and recovery paths, and separates retained work from the upgrade census. > - The result is bounded recovery without a second executor or loss of saved work. ## Linked Issues or Issue Description **What happened?** A native preparation interrupted by a server crash could remain running indefinitely with no runner PID or checkpoint. Native controller stamps did not wake the shared instance lease. That lease was disabled on volume-backed Railway services, and native takeover omitted the boot binding needed for its cross-host proof. Scoped recovery selected checkpoint-backed runs but skipped these preparations. The upgrade probe then counted the retained row as current execution and could block every later upgrade. **Expected behavior** Track the preparation's controller before a runner starts. Recover it through the normal classifier after proving its owner stopped or fencing that boot. Keep live or unprovable owners held. Deliver recovery wakes to preparations without checkpoints. Preserve unresolved historical work and report it separately from current execution during upgrade observations. **Steps to reproduce** 1. Save an observed native preparation with no runner process, result, provider-start evidence or checkpoint. 2. Replace its controller with a process in another PID space. Let its owner lease expire and send a scoped recovery wake. 3. Before this fix, recovery skips the checkpoint-free preparation or cannot prove its foreign controller stopped. Upgrade readiness continues to count that old row as a live blocker. Related work: #18037, #19651, #19868, and #20168. The foreign-PID guard from #19868 remains. The queued first-turn input fix from #20168 is included through the current base. ## What Changed - Register the existing instance lease when native execution and restart claims are written. Keep passive identity reads free of ownership effects. Keep that lease enabled on volume-backed Railway services unless explicitly disabled. - Preserve the existing PID/start-time proof within the same readable PID space. Bind foreign or unknown-space takeover to its saved boot and last write. A live lease or grace period keeps the run held with a scheduled check. Repeated evidence waits preserve their timestamp. - Include explicitly fenced, checkpoint-free preparations in the existing finite recovery candidate inventory. Normal launch, checkpoint, provider evidence and admission checks still decide whether work may resume. - Expose the existing board stop-confirmation operation for historical foreign-host owners. Bind confirmation to the exact run, boot, PID, process start and host. Reject changed ownership. - Report narrowly defined old, unowned preparations as `retainedBlockedRuns` outside the live upgrade census. Local executions, pending admission, drain state, truncation and positive checkpoint proof retain their barriers. - Update the execution contract and add PostgreSQL, real-route, race and preservation regressions. ## Verification - Genuine red/green regressions cover the old upgrade census, missing native lease registration, the volume-backed default, foreign-host confirmation, and scoped recovery skipping checkpoint-free preparation. Test bodies ran against real PostgreSQL before their corresponding production changes. - Combined verification before the final idle correction (head `055be6c84d9`): **459 passed, 22 skipped, 11 files passed**. Covers controller lease/identity, native restart classification and integration, host confirmation, heartbeat recovery, instance route authorization, upgrade readiness, and queued first-turn input. The focused command used `pnpm exec vitest run` with those 11 suites and `--maxWorkers=1 --no-file-parallelism`. The 22 skipped cases require the Rust debug fake-Codex fixture, which was not built locally; the real-process CI coverage must pass. - `pnpm --filter @paperclipai/server typecheck`: passed on head `055be6c84d9b058064daad0af71bb5d893ab9529`. - Actual route proof uses the real heartbeat service and PostgreSQL. Unauthorized access returns 403. The retained run and owner records remain unchanged. Recovery integration admits one bootstrap claim across two replacement controllers, keeps a live owner held, and preserves local execution. - `pnpm verify`: exit 2, requests broad CI because the execution contract changed. Full local suite not run. Current-head CI must supply broad coverage. - Local source checkout on macOS ARM64, Node v25.6.1, pnpm 9.15.4. Tests use isolated PostgreSQL. No live provider restart is claimed by those fixtures. - CI found an idle-query regression in the first version of the lease wiring. Two focused PostgreSQL regressions reproduced the misplaced identity-read notification and the missing claim notification. The fix moves registration to actual ownership writes. `env -u npm_execpath pnpm exec vitest run server/src/__tests__/maintenance-server-routes.test.ts --maxWorkers=1 --no-file-parallelism`: **17 passed**, including 90 real seconds and two qualified hourly windows with no extra idle queries. An initial local setup attempt inherited npm metadata and did not run tests; clearing that metadata selected the correct pnpm helper. Three focused controller/native suites also passed **168 tests, 22 fixture-gated skips**. Server typecheck passed after the correction on head `4a527326dcd5b3a9e286f0e4c3e761d3056af4d9`. - The older CI server shard also exposed a same-space graceful-restart regression. The new boot fallback had overridden local process-exit proof. Seven suspension cases and a focused unit regression reproduced it; the fix limits boot fallback to foreign or unknown PID spaces. Fresh verification on `acf1065d98150210c1e227c7f3b05a1eb489730b`: **251 passed, 22 fixture-gated skips, 4 files passed**, including all **40** graceful-restart suspension tests. Server typecheck passed. The local regression failed before this correction, and the same-space and foreign-space paths pass together after it. - Current-head CI completed successfully on `acf1065d98150210c1e227c7f3b05a1eb489730b` (run 42568109784). Greptile reviewed that exact head at 5/5 with no unresolved review threads. - Secret and private-data scans passed. No workflow, lockfile or migration changes. ## Risks - The instance lease now applies to volume-backed Railway services. Its existing 30-second renewal, quiescence, grace and self-fence behavior applies while work is held. Idle servers retain the existing no-query behavior. An explicit `PAPERCLIP_CONTROLLER_INSTANCE_LEASE=off` remains available. - A controller lease proves only the server owner's state. It does not prove an agent process stopped or authorize replay, resource release or volume removal. The existing classifier and provider/runner checks remain responsible for those decisions. - Previous-release records keep their shape and saved state. A missing boot lease stays unprovable and requires the scoped stop-confirmation path. A changed confirmation target is rejected. - The upgrade observation does not recover a held task by itself. Source tests do not qualify a hosted replacement. Deployment and the affected task's useful result must be verified separately. - This repairs recovery after a crash; it does not claim to identify or fix every source of heap exhaustion. ## Model Used OpenAI `gpt-6.1-sol`, xhigh reasoning effort, through Codex in T3 Code, with repository, shell, and GitHub tools. Context window size is not exposed by the runtime. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>canary/v2026.1109.0-canary.6 |
||
|
|
faaab3ccca |
test(evals): refresh source SDK public-input pins after merging master
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6fcb3ddab6 |
test(evals): grade the hire revision by saved state, not reply wording
The formal hire revision oracle required the chat reply to name the selected model, and its side-effect check matched approval words in the reply. The query asks for no literal. The saved proposal, the resubmission audit and the request opened from the reply's own link already prove the revision. Accept a linked reply that does not repeat the model, and still reject one that names another catalog model. Bump oracleVersion to native1017.v6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |