mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-14 00:26:28 +01:00
## Thinking Path > - Paperclip helps people manage AI agents and their work. > - The inbox collects requests that need a human decision. > - Teams need to know who owns each request and who may act on it. > - A shared close operation must differ from hiding a card for one person. > - This pull request adds personal routing and durable outcomes to the existing attention service. > - People can claim or transfer work and inspect what another person did for them. > - Domain permissions still decide who may resolve each request. ## Linked Issues or Issue Description **Subsystem affected** Server, shared contracts, and database. This is the routing foundation for a stacked inbox UI change. **Problem or motivation** A company-wide attention feed does not give teams a clear personal queue. Archive, withdrawal, transfer, and decisions by another person need distinct scopes and records. **Proposed solution** Resolve a request audience from its addressee, task participants, and configured owners. Provide personal, unassigned, company, all-assignee, and archived views. Add claim, transfer, personal archive and snooze, shared withdrawal, receipts, and personal history. **Alternatives considered** A single shared inbox leaves ownership and personal hiding ambiguous. The service keeps one canonical request and projects its audience instead of copying requests for each person. **Roadmap alignment** This extends the existing Multiple Human Users and Agent Reviews and Approvals capabilities in ROADMAP.md. **Additional context** Refs #19363 and #19520 for the current inbox navigation and confirmation replacement rules. Refs #20268 for related actionable-request work. That PR filters actionability; this stack adds audience routing and personal outcomes. This is part 1 of a two-PR stack. The inbox UI PR #20961 targets this branch. ## What Changed - Add nullable company and project decision owners and a company budget owner. - Add audience views and personal badge counts to the canonical attention projections. - Add claim, transfer, withdrawal, personal archive, snooze, and restore operations with permission checks and activity records. Lock addressed transfers to the current addressee or routing administrators, and validate the recipient against canonical resolution policies. - Add receipts and scoped history for work handled by another person. - Keep closed-task requests and replaced confirmations out of current cards. - Add an idempotent migration and a concurrent receipt-history index. - Update typed fixtures so the existing UI remains compatible with the new contracts. ## Verification - Server layered typechecking passes, including all three test-tree partitions. - Seven routing and interaction-resolution suites pass all 249 tests against a dedicated external PostgreSQL test database on `4caecc21d8`. Six new regressions failed before the repair; they cover takeover followed by approval, legitimate delegation, excluded creators/review requesters, and unauthorized claims. - Prior focused verification covers private approval history and receipts, transfer visibility, audience escalation, withdrawal governance, hidden-card counts, per-recipient confirmation replacement, and SDK source contracts. - A synthetic three-person company passed 37 routing and lifecycle acceptance checks. The UI stack adds browser acceptance for the shared controls. - The complete [GitHub Actions CI matrix](https://github.com/paperclipai/paperclip/actions/runs/43134997331) passes on `4caecc21d891677323a7335f23b1a015cce24c13` (Linux/Node 24: typecheck, build, general and serialized tests, Runner support, browser shards, and aggregate gates). Greptile is 5/5 on that exact head with no open findings. The fresh security scan passes after explicitly rechecking the addressee-transfer repair. - `pnpm verify` reports exit 2 because a CI-global script changed; full CI supplies that broader coverage. Embedded PostgreSQL could not start on this Mac because shared-memory slots were exhausted, so local database checks use a separate external test database. Linux CI covers the embedded setup. ## Risks - Personal badge counts change to the viewer's assigned requests. Accessible feed totals and task membership keep their existing scope. - The migration adds nullable owner fields and a dismissal fingerprint. Its index runs outside a transaction. The migration is classified and rerunnable. - Routing changes the addressee used by resolution authorization. The current addressee or a routing administrator must authorize an addressed transfer; the recipient must satisfy writer access, canonical resolver exclusions, and task review policy under row locks. - The shared validator export barrel crosses 1,000 lines because it exports the new attention schemas. Schema implementation stays in the dedicated attention validator module. ## Model Used OpenAI GPT-6 through Codex. Used repository tools, code execution, and browser checks. This session does not expose a more specific runtime model ID or its context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links - [x] My branch name describes the change and contains no internal Paperclip ticket id - [x] I have run focused tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes (OpenAPI contracts and docs/api/attention.md) - [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: Scott Tong <scott@scottsmbpm5max.lan> Co-authored-by: Paperclip <noreply@paperclip.ing>
129 lines
5.9 KiB
Markdown
129 lines
5.9 KiB
Markdown
---
|
|
title: Attention inbox
|
|
summary: Personal request routing, close actions, and decision history
|
|
---
|
|
|
|
The attention API projects pending requests and operational alerts into an inbox.
|
|
All routes require a board user with access to the company. Task and approval
|
|
privacy checks apply to cards, counts, history, and receipts. Agents cannot use
|
|
these board routes.
|
|
|
|
## Read the inbox
|
|
|
|
```text
|
|
GET /api/companies/{companyId}/attention
|
|
GET /api/companies/{companyId}/attention/summary
|
|
GET /api/companies/{companyId}/attention/tasks
|
|
```
|
|
|
|
The feed accepts these query parameters:
|
|
|
|
| Parameter | Meaning |
|
|
| --- | --- |
|
|
| `view` | `mine`, `unassigned`, `company`, `archived`, or `all`. The API default is `all`. |
|
|
| `includeDismissed` | Include personally hidden cards. Default: `false`. |
|
|
| `archived` | Read retained source archives instead of current sources. This differs from personal `view=archived`. |
|
|
| `all` | Return a complete snapshot instead of a page. |
|
|
| `limit`, `cursor` | Page size and opaque continuation cursor. Default size: 50; maximum: 100. |
|
|
| `sort` | `activity` or `decide`. |
|
|
| `activitySince`, `activityUntil` | Inclusive activity bounds in ISO date-time format. |
|
|
| `queue` | Filter by a decision queue key. |
|
|
|
|
The response includes `items`, `totalCount`, `countsBySourceKind`, `viewCounts`,
|
|
`deskBadgeCount`, and `nextCursor`. Related evidence shares one obligation lead
|
|
and counts once. `deskBadgeCount` counts requests assigned to the viewer.
|
|
Summary returns the same current counts without card bodies. Tasks also returns
|
|
unique `taskIds` for visible requests. Neither endpoint returns card bodies.
|
|
|
|
Routing uses explicit recipients, chat ownership, review participants, task
|
|
assignees, human instigators, and configured decision owners. A card with no
|
|
owner enters Unassigned. After seven days it moves to Company. A required
|
|
request that waits 30 days escalates to the configured decision owner or Company.
|
|
Task privacy still applies. Escalation keeps the request pending and its gate closed.
|
|
|
|
`view=mine` also includes receipts when someone else handles the viewer's request.
|
|
Receipts expire after seven days. They do not count as decisions or in the badge.
|
|
Every approval-linked task must remain readable to return an approval receipt.
|
|
|
|
## Claim or transfer
|
|
|
|
```text
|
|
POST /api/companies/{companyId}/attention/{sourceKind}/{sourceId}/claim
|
|
POST /api/companies/{companyId}/attention/{sourceKind}/{sourceId}/transfer
|
|
```
|
|
|
|
Claim has no body. Transfer takes `{ "userId": "<user ID>", "reason": "optional" }`.
|
|
These operations support `issue_thread_interaction` sources. Claim assigns an
|
|
ownerless request to the caller. Changing an addressed request requires its current
|
|
addressee or an active company owner/administrator (or an instance administrator).
|
|
Transfer assigns it to a writer who can read the task and satisfy the interaction
|
|
creator exclusions and the task review policy. Viewers cannot claim or transfer
|
|
requests. A chat question stays with its chat owner. A request to an agent that
|
|
can still answer stays with that agent.
|
|
|
|
The addressee is part of resolution authorization, so routing validates authority
|
|
and the recipient under task and interaction row locks before updating it. Inbox
|
|
view placement alone grants no authority. Concurrent claims and transfers retain
|
|
their compare-and-set checks. An identical repeated assignment returns
|
|
`alreadyClaimed` or `alreadyTransferred`. Changes record `attention.claimed` or
|
|
`attention.transferred` activity.
|
|
|
|
## Close a card
|
|
|
|
```text
|
|
POST /api/companies/{companyId}/attention/{sourceKind}/{sourceId}/close
|
|
```
|
|
|
|
Use one of these bodies:
|
|
|
|
```json
|
|
{ "mode": "archive", "fingerprint": "<changeFingerprint from the card>" }
|
|
```
|
|
|
|
```json
|
|
{ "mode": "snooze", "until": "2099-01-01T12:00:00.000Z", "fingerprint": "<changeFingerprint>" }
|
|
```
|
|
|
|
```json
|
|
{ "mode": "withdraw", "reason": "The proposal is no longer needed" }
|
|
```
|
|
|
|
Archive and snooze affect only the caller. The source stays pending. Hiding
|
|
obligation evidence hides its lead and all related evidence. The returned
|
|
`itemKey` identifies the personal dismissal. Restore it through the existing
|
|
inbox-dismissal restore endpoint. A content change or a new assignment to the
|
|
viewer can make a hidden card return. A snoozed card also returns when time expires.
|
|
|
|
Withdrawal affects everyone. It cancels an interaction, approval, or decision
|
|
through that source's existing lifecycle. Review policy and execution checks
|
|
still apply. A hire request must be rejected through approvals instead.
|
|
The card's `closeModes` lists its supported actions. A successful close records
|
|
`attention.closed` activity and returns the source, mode, dismissal key, and time.
|
|
|
|
## Decision history
|
|
|
|
```text
|
|
GET /api/companies/{companyId}/attention/history
|
|
```
|
|
|
|
History includes resolved interactions, decisions, and approvals. `scope=mine` shows work handled by or for the caller. `scope=all`
|
|
shows accessible company history. `handledForMe=true` narrows it to work handled
|
|
for the caller. Filters include `search`, `outcome`, `agentId`, `projectId`,
|
|
`since`, `sort=newest|oldest`, `limit` (maximum 100), and `cursor`.
|
|
Withdrawn requests, expired requests, and requests closed with their task have
|
|
separate outcomes. Personal archive and snooze do not record consent.
|
|
|
|
## Errors
|
|
|
|
| Status | Meaning |
|
|
| --- | --- |
|
|
| `400` | Invalid source kind, UUID, filters, cursor, or request body; a snooze needs a future time. |
|
|
| `403` | No company access, no board user context, read-only membership, or a denied review policy. |
|
|
| `404` | Missing source or a source the caller cannot read. |
|
|
| `409` | Another person claimed, routed, or answered first; the request is already closed; or execution prevents withdrawal. |
|
|
| `422` | Unsupported operation, unavailable recipient, recipient without task access, or a chat ownership constraint. |
|
|
|
|
A closed-source conflict includes `code: attention_already_closed` and the
|
|
recorded resolver when known. Clients should refresh the feed and show that
|
|
outcome instead of retrying a decision automatically.
|