Skip to content

Commit aab7070

Browse files
feat: workspace review — withdrawn and removed requests, and a notice when someone removes yours (#1708)
* feat: workspace review — skill publish waits for the owner, review status, decision notices, your pending rules - `skill publish`: submits the skill to the linked workspace (`POST /datamates/{id}/reviews`) and reports the server's answer: published, sent for review, already waiting, or already approved; a member is told before upload when the owner has review off; older backends fall back to the previous attach; `--json`; every failure exits non-zero - `skill-sync`: reads skill detail and files with `datamate_id`, so a contributed skill arrives at its approved version (or, for its author, the waiting one); detects changes by `revision`, falling back to `updated_at` - `altimate-code workspace review status [--json]`: your submissions, their state and the reviewer's reason; reads only, starts nothing - Decision notices for your skill and knowledge submissions: recorded per account and workspace outside the project, kept (the 50 most recent) and replayed by the TUI like rule notices, so one is never lost when no client is listening - Your own pending rules (`my_pending` from the rules pull) reach your sessions under "Your rules pending review"; a pending edit shows its proposed wording to you in place of the approved rule; same scope, credential and kill-switch checks as team rules - Server text passes through `printable` before reaching the terminal - Docs: skills, CLI usage, learn Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: workspace review — skill sync downloads one version, publish finds rejected skills, CLI source - `skill-sync`: the detail and every file read carry the listed `revision`; a mismatch (including a 409 `revision_changed`) discards that skill's whole download and the next sync retries, so a project never holds files from two versions - A previous copy is kept over a failed download only while it is the version the workspace still serves (or was synced without a revision, from an older server); a copy of a version no longer served — a rejected or replaced one — is removed, even when every skill failed, and on an abandoned run - `skill publish --replace` finds your existing skill through your own skills (`GET /skills?mine=true`), so a skill whose first submission was rejected is found; it still refuses a same-name skill attached only to another workspace - Submissions from `skill publish` are recorded as from the CLI (`source: "cli"`) - Docs: CLI usage, learn, skills Note: workspace knowledge search is scoped by the engine that calls the knowledge query, outside this repository; it already receives the workspace id at start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: workspace review — skill sync withdraws what the server says is no longer served, serialised across processes From a second three-model review of the whole feature: - A copy is withdrawn when the server has said its version is not served: the successful list names another revision, a read is refused with 409 `revision_changed` naming another one, or the list no longer names a skill synced from a review-aware server (it recorded a revision). This holds even when every other download fails and on an abandoned run; a plain failure (network, 401/403, 5xx) still keeps a copy the server has not contradicted - The snapshot swap and the withdrawal on an abandoned run take the same cross-process `Flock` lock, and a withdrawal removes only a copy still identical to the one this run judged obsolete, so a newer copy another process published is kept - A detail that answers for another revision raises the same coded `revision_changed` as the server's 409 - Docs: CLI usage Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: workspace review — one credential per publish, sync never undoes a newer snapshot, notices scoped to their project Publish - The whole publish (identity, review summary, upload, submission, attach, ownership checks) uses one captured credential; review client calls require it. - A member whose server turns out to have no review keeps the upload and is told the submission failed, instead of falling back to an owner-only attach that takes the upload back. - A skill detail without a readable `attached_datamate_ids` fails rather than replacing the attachment set. - `skill publish --json` reports failures before the publish (opening the project, reading skills) as JSON, once. Skill sync - Every removal of the live snapshot takes the cross-process snapshot lock. - A swap or an empty-list removal goes ahead only while the live manifest is still the one the run read; a superseded run keeps the newer snapshot and still withdraws what its own read said is no longer served. - A copy recorded with a revision is kept on a failed download when the list names no revision. Review notices - A list with unreadable rows, a page short of its total, or paging without counts never lets a pending request go; a server that ignores `page` stops after the repeat. - Replay never waits on the network: an unknown account is looked up in the background; a link read under another credential returns nothing. - The TUI handles rules events of its own project only; an unknown decision status still shows a notice. - `workspace review status` resolves the link like `workspace status` (server revalidation, unlink, rebind). Rules - A pending edit keeps the pin of the rule it replaces; a pending proposal whose paths change rebuilds the session. - Pending proposals left out of a pull (unreadable or failing the lint) are reported in the sync status. - The learning-setting response and `learn status` read review notices for the session directory. Docs state which decisions are noticed, the first-replay exception, and what the sync and status command guarantee. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: workspace review — pending rules stay pending in plugin sessions, an abandoned sync withdraws replaced copies, notices reach every linked project Plugin host sessions (after merging main, which added them) - Session start gives what sync vouches for, as an altimate-code session does: the person's own pending proposals carry `review: "pending"` in `Start.rules` and are listed under their own heading, never under "approved by the workspace owner"; a pending edit no longer shows as approved wording. The no-session fallback uses the same set. Skill sync - A copy whose listed revision moved on is withdrawn when the run is abandoned or superseded before its swap, even when its replacement downloaded fine. - The liveness check also compares the "workspace has no skills" record, so a run that started with no snapshot cannot bring skills back after another run removed them as empty. - A previous workspace's or account's manifest is never used to withdraw; withdrawals report the sync as changed. Review notices - A server or tenant without review is asked again only after 6 hours, and its 404 is not reported as an API error. - A pending request past the page cap is looked up by id (up to 10 per poll), so its decision is still noticed. - Stored notices follow the IDE's pin for the tree, from what is on hand; every kept decision is replayed. Commands - `skill publish` captures the credential before resolving the link and stops if the sign-in changed; signed out is said as such. Error text is made printable; a failure after a written result still exits non-zero. - `workspace review status` lists with the credential the link was resolved for. - The TUI's fallback notice status is made printable. Docs: a notice is shown once in each project linked to the workspace; the poll's cost is stated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: workspace review — notice lookups let go of removed requests and rotate, an accepted submission never fails on an unknown row shape - Past the list cap, a pending request the server answers 404 for is let go, and one whose lookup fails moves behind the others, so a few failures never take every lookup. - `skill publish` keeps an accepted submission when the server's row has a status or change this version does not know. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: workspace roles — admins approve and publish like the owner, members send for review The server now sends the caller's `role` (`owner`, `admin` or `member`) next to `is_owner` on the review summary, the rules summary and rules `/sync`. `WorkspaceRole.roleOf` reads it, falling back to `is_owner` for an older server or a role this version does not know; `manages` is owner or admin. - Rule cards: an admin is offered "approve" like the owner; a member "send". The role from the last pull is kept in the rules state, and the remember tool says "you (a workspace admin)". - `skill publish`: review off refuses members only; an admin's skill is attached directly by the server; an admin whose server turns out to have no review falls back to attaching. A server without review still needs ownership. - Wording that named only the owner as reviewer now says "the owner or an admin" (plugin host sessions, the pending heading, the review-off error, `learn propose --send`), and the docs say admins review too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: workspace roles — review wording names the owner or an admin everywhere - `learn reply --approve` help: "(workspace owner or an admin)"; `--send-for-review` help and the bootstrap / import-reviews epilogs and prompts say the owner or an admin decides. - Rule replies, queued sends and history-staged sends say "Sent for review in <workspace>: <owner> or an admin decides" instead of naming the owner's queue. - TUI card hint: an admin's approve says "You are an admin of <workspace>", not "You own". - The training tool's description: a rule goes for review by the workspace owner or an admin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: workspace review — withdrawn and removed requests, and a notice when someone removes yours - `review-api.ts`: `withdrawn` and `retired` are read as statuses (never skipped, so a list stays complete and pending ids are let go), plus `decided_by_me` and `author_has_access`. - Notices: a withdrawn request is let go silently. Approved requests are tracked (bounded, `MAX_APPROVED`), and when an owner or admin removes one (`retired`, not by you) the TUI shows it once with who and why. Your own removal, and removals of requests this client never saw, show nothing. State files without the new field still load. - TUI toast and `workspace review status` wording for withdrawn and removed requests. - Docs: withdrawing and removing happen in the web app; the new notice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: workspace review — removal notices track approved requests stably and reach ones past the list cap - The approved requests a client tracks keep their order: a newer approval waits for room instead of evicting one, so past 200 the tracked set and the state file no longer change from poll to poll. - An approved request the capped `view=mine` list does not reach is looked up by id with the pending ones (same rotation, a 404 lets it go), so its removal is still noticed. - Docs say removal notices cover requests this client has seen approved or waiting, within the 200 it tracks, and not your own removal. - `decided`'s docstring covers removals; `author_has_access` is no longer parsed (nothing read it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ebaaefe commit aab7070

7 files changed

Lines changed: 154 additions & 21 deletions

File tree

‎docs/docs/configure/skills.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ altimate-code workspace review status # changes nothing on the server; --j
209209

210210
Requests sent from the CLI are recorded as such in the request's history. What the team gets is the exact version that was approved: editing the skill afterwards changes nothing for them until you run `skill publish` again, which submits the new version as an update. A member publishing to a workspace whose owner has review off is refused before anything is uploaded. If the skill uploads but cannot be sent (offline, an expired sign-in, too many open requests), the command says so and exits non-zero; running it again updates the uploaded skill and submits it. Against a workspace service without review, `skill publish` attaches the skill directly as before, which only the workspace owner can do. With `--json`, the output carries `ok`, the upload (`action`, `public_id`, `files`, `bytes`, `workspace_id`, `workspace_name`) and `review` with the service's `status`, `attached_directly`, `duplicate`, `owner_name` and `contribution` (`null` without review); every failure exits non-zero with `{"ok": false, "error": ...}`.
211211

212-
When the owner or an admin approves or rejects one of your requests that this client has seen pending (submitted from it, or listed by an earlier check), the TUI shows a notice once in each project linked to that workspace (with the reason for a rejection). Each altimate-code process with a linked project open checks at start and every 5 minutes (a server without review is asked again only after 6 hours) and keeps the 50 most recent decisions in your own state directory, so a decision that arrives while the TUI is closed or disconnected is shown when it next starts (as rule review notices are). The first time a TUI opens a project, decisions already kept are recorded without a notice; `workspace review status` lists them. `altimate-code workspace review status` lists your requests on the linked workspace (skills and knowledge bases): kind, name, status, whether it is new or an update, the reviewer and their reason, and `expired` for a request waiting longer than 14 days (it can still be approved).
212+
When the owner or an admin approves or rejects one of your requests that this client has seen pending (submitted from it, or listed by an earlier check), the TUI shows a notice once in each project linked to that workspace (with the reason for a rejection). Each altimate-code process with a linked project open checks at start and every 5 minutes (a server without review is asked again only after 6 hours) and keeps the 50 most recent decisions in your own state directory, so a decision that arrives while the TUI is closed or disconnected is shown when it next starts (as rule review notices are). The first time a TUI opens a project, decisions already kept are recorded without a notice; `workspace review status` lists them. `altimate-code workspace review status` lists your requests on the linked workspace (skills and knowledge bases): kind, name, status, whether it is new or an update, the reviewer and their reason, and `expired` for a request waiting longer than 14 days (it can still be approved). In the web app you can withdraw a request that is still waiting, and remove an approved skill or knowledge base of yours from the workspace; the owner or an admin can remove it too, with a reason. When the owner or an admin removes one of yours that this client has seen approved (or waiting; up to the 200 approved ones it tracks per workspace), the TUI shows that once too, with the reason; your own removal and withdrawal show nothing. `workspace review status` lists them as `withdrawn` and `removed from the workspace`.
213213

214214
`skill publish` sends the whole skill directory, not just `SKILL.md`, so keep secrets out of it. A built-in filter skips known file and directory names — `.env*`, `.git`, `id_rsa`, `*.pem`, `*.key`, `*.p12`, `.npmrc`/`.netrc`, `credentials.json`, `secrets.*`, `.ssh`/`.aws`, editor swap files — but it matches names only and never scans file contents, so a token inside `config.yaml` or a key named `server.crt` would still be uploaded. Built-in skills, global skills and skills the workspace itself sent you cannot be published — including the copies the VS Code / Cursor extension delivers into `.claude/skills/` and `.agents/skills/`, which carry an `.altimate-managed.json` marker.
215215

‎docs/docs/usage/cli.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ With workspaces on:
6767
- `/integrations` in the TUI opens the workspace's integration status directly (the same view as `/workspace` → Status). In a project that is not linked it offers the link, and when the workspace engine is missing or too old it offers the install and says which.
6868
- In a project linked to a workspace, integrations come only from the workspace's engine, so the `datamate_manager` tool is not offered there and refuses to run. `datamate-*` MCP servers already in the config still load, and a one-time notice names the file they are in; the config is not edited.
6969
- `altimate-code skill publish <name>` uploads a project skill to the linked workspace, attached directly for the workspace owner or an admin and sent for review for a member when review is on; see [Skills](../configure/skills.md#cli-commands).
70-
- When the owner or an admin approves or rejects one of your skill or knowledge base requests that altimate-code has seen pending (submitted from it, or listed by an earlier check), the TUI shows a notice once in each project linked to that workspace, with the reason for a rejection. Every altimate-code process that opens a linked project checks at start and every 5 minutes while workspaces are on and you are signed in: it lists your own requests on the workspace (up to 10 pages of 50); a server without review is asked again only after 6 hours. The decision is kept per account and workspace in your own state directory (`$XDG_STATE_HOME/altimate-code/workspace-reviews/`, the 50 most recent), so a TUI that was closed or disconnected when it arrived shows it when it next starts. The first time a TUI opens a project, decisions already kept are recorded without a notice; `altimate-code workspace review status` lists them.
70+
- When the owner or an admin approves or rejects one of your skill or knowledge base requests that altimate-code has seen pending (submitted from it, or listed by an earlier check), the TUI shows a notice once in each project linked to that workspace, with the reason for a rejection. Every altimate-code process that opens a linked project checks at start and every 5 minutes while workspaces are on and you are signed in: it lists your own requests on the workspace (up to 10 pages of 50); a server without review is asked again only after 6 hours. The decision is kept per account and workspace in your own state directory (`$XDG_STATE_HOME/altimate-code/workspace-reviews/`, the 50 most recent), so a TUI that was closed or disconnected when it arrived shows it when it next starts. The first time a TUI opens a project, decisions already kept are recorded without a notice; `altimate-code workspace review status` lists them. Withdrawing a waiting request and removing an approved one happen in the web app. When the owner or an admin removes one of yours that altimate-code has seen approved or waiting (up to the 200 approved ones it tracks per workspace), the TUI shows that once too, with the reason; your own removal and withdrawal show nothing.
7171
- Workspace skills are synced at the version the workspace serves you: the approved version, or your own pending version of a skill you submitted. The next sync follows what the workspace serves: a skill whose first submission is rejected, or that is detached from the workspace, is removed from the project; when an update to an approved skill is rejected or replaced, the skill stays at its approved version. Each skill is downloaded as one version: if it is approved or rejected while its files are being read, nothing of it is installed from that sync and it is fetched again on the next one. When a download fails, the copy already in the project is kept only if it is still the version the workspace serves, as far as the server has said (its list, or a read refused because the version just changed), or if neither the copy nor the list names a version (a copy synced before revisions, or an older server), in which case it is kept until the server says otherwise; a copy of a version it no longer serves (for example your rejected pending version), or of a skill it no longer lists, is removed until the download succeeds, even when no other skill could be downloaded. Two processes syncing the same project take turns: a sync never replaces or removes a snapshot another process installed after it started, and the next sync starts from the newer one.
7272
- When the linked workspace has knowledge, the agent is told to check it for best practices and team conventions before answering, without you having to mention the knowledge base.
7373
- In an ordinary session the agent is told every turn which workspace the project is linked to — or that none is, or that the link could not be verified just now. In an extension-pinned session it is told the pinned workspace instead, and that it differs from the project's own link. Either way "which workspace am I in?" has an answer, and the agent is told not to confuse it with a Databricks workspace or an IDE workspace folder.

‎packages/opencode/src/altimate/workspace/review-api.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ export const Contribution = z.object({
4040
name: z.string(),
4141
description: z.string().nullable().optional(),
4242
change: z.enum(["add", "edit"]),
43-
status: z.enum(["pending", "active", "rejected", "superseded"]),
43+
// `withdrawn`: the author withdrew it (or it was withdrawn when the author deleted the item or lost access);
44+
// `retired`: removed from the workspace after approval, by an owner, an admin or the author. A status this version
45+
// does not know makes the row unreadable (skipped and counted), never misread.
46+
status: z.enum(["pending", "active", "rejected", "superseded", "withdrawn", "retired"]),
4447
version: z.number().int(),
4548
is_mine: z.boolean(),
4649
proposed_by_name: z.string().nullable().optional(),
@@ -53,6 +56,9 @@ export const Contribution = z.object({
5356
expired: z.boolean().default(false),
5457
file_count: z.number().int().nullable().optional(),
5558
document_version: z.number().int().nullable().optional(),
59+
/** Whether the caller made the last decision (`reviewed_by`): a person is not told of their own retire. Absent from
60+
* older servers: read as not. */
61+
decided_by_me: z.boolean().default(false),
5662
})
5763
export type Contribution = z.infer<typeof Contribution>
5864

0 commit comments

Comments
 (0)