Skip to content

Add "Mark as ready to review" to workspace context menu#2166

Open
sebbo2002 wants to merge 1 commit into
getpaseo:mainfrom
sebbo2002:done-workspace-mark-ready-to-review
Open

Add "Mark as ready to review" to workspace context menu#2166
sebbo2002 wants to merge 1 commit into
getpaseo:mainfrom
sebbo2002:done-workspace-mark-ready-to-review

Conversation

@sebbo2002

Copy link
Copy Markdown

Summary

Adds a context menu item that lets users manually move a workspace from the Done bucket back to the Ready to review bucket. This is the inverse of the existing "Mark as read" action (which clears attention).

Motivation

When a workspace finishes and moves to "Done", there's no way to manually flag it for review later. Users sometimes need to re-surface a completed workspace for review without restarting an agent. This provides that capability through a simple context menu action.

Changes

New RPC: workspace.set_attention

  • Protocol: WorkspaceSetAttentionRequestSchema / WorkspaceSetAttentionResponseSchema — mirrors the existing workspace.clear_attention RPC
  • Server: AgentManager.markAgentAttention() method + handleWorkspaceSetAttentionRequest() handler in session.ts
  • Client: DaemonClient.markWorkspaceAttention() method

New attention reason: "manual"

The existing reasons ("finished", "error", "permission") are all set automatically by agent state transitions. Manual marking needs its own reason to distinguish it from an agent that genuinely just finished. Added "manual" to AgentAttentionReason across all locations:

  • Protocol schemas (messages.ts, agent-state-bucket.ts, agent-attention-notification.ts)
  • Server storage schema (agent-storage.ts)
  • App types and priority maps (session-store.ts, agent-attention.ts, use-agent-attention-clear.ts, session-context.tsx)

UI

  • New hook useMarkWorkspaceAttention (parallel to useClearWorkspaceAttention)
  • New menu item in sidebar-workspace-menu.tsx with Eye icon
  • Wired through sidebar-workspace-row.tsx — appears only when workspace.status === "done"

i18n

Added markAsReadyToReview and markAsRead translations across all 8 languages (en, ar, es, fr, ja, pt-BR, ru, zh-CN). Also fixed the previously hardcoded "Mark as read" label to use an i18n key.

How it works

  1. User right-clicks a workspace in the Done bucket
  2. Selects "Mark as ready to review"
  3. Client calls client.markWorkspaceAttention(workspaceId)
  4. Server sets requiresAttention: true, attentionReason: "manual" on all non-archived agents in the workspace
  5. Workspace status recomputes to Ready to review

Verification

  • npm run typecheck
  • npm run lint
  • npm run build:client
  • npm run build:server

Files changed

Layer File Change
Protocol packages/protocol/src/agent-state-bucket.ts Added "manual" to reason type
Protocol packages/protocol/src/agent-attention-notification.ts Added "manual" to reason type + resolvers
Protocol packages/protocol/src/messages.ts New request/response schemas + enum updates + union registration
Server packages/server/src/server/agent/agent-storage.ts Added "manual" to stored schema enum
Server packages/server/src/server/agent/agent-manager.ts New markAgentAttention() method
Server packages/server/src/server/session.ts New handler + routing case
Client packages/client/src/daemon-client.ts New markWorkspaceAttention() method
App packages/app/src/hooks/use-mark-workspace-attention.ts New file
App packages/app/src/utils/agent-attention.ts Added "manual" to priority map
App packages/app/src/hooks/use-agent-attention-clear.ts Updated AttentionReason type
App packages/app/src/contexts/session-context.tsx Updated notifyAgentAttention signature
App packages/app/src/stores/session-store.ts Updated Agent type
App packages/app/src/components/sidebar/sidebar-workspace-menu.tsx New menu item + prop
App packages/app/src/components/sidebar/sidebar-workspace-row.tsx Wired handler through component chain
App packages/app/src/i18n/resources/*.ts (8 files) Translations for markAsReadyToReview + markAsRead

Adds the inverse of "Mark as read" — users can now manually move done
workspaces back to the "Ready to review" bucket via the workspace
context menu.

Introduces a new `workspace.set_attention` RPC (parallel to
`workspace.clear_attention`) that sets attention on all non-archived
agents in a workspace with a new `"manual"` attention reason. The
`"manual"` reason was added to the `AgentAttentionReason` union across
the protocol, server storage schema, client, and app.

The menu item appears only when `workspace.status === "done"`.
@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a "Mark as ready to review" context menu item that lets users manually move a workspace from the Done bucket back to Ready to review without restarting an agent. It follows the existing workspace.clear_attention RPC pattern cleanly — new protocol schemas, a server-side handler, a DaemonClient method, a React hook, and i18n keys across all 8 supported languages.

  • Introduces a new "manual" AgentAttentionReason propagated consistently through the protocol, storage schema, and app types; adds it to the attention-priority map at priority 3 (lowest urgency).
  • New workspace.set_attention RPC mirrors the existing workspace.clear_attention design, handling both live and stored agents and supporting batch workspace IDs.
  • The existing hardcoded "Mark as read" label in the menu is fixed to use the i18n system as a drive-by improvement.

Confidence Score: 3/5

The core plumbing is correct, but the interaction between manual flagging and the auto-clear logic undermines the feature's usefulness in the common case where a user navigates into the workspace they just flagged.

The shouldClearAgentAttention function exempts "permission" from focus-triggered clearing but not "manual". A user who marks a workspace as ready to review and then opens it to inspect it will have the flag silently removed on navigation — the workspace returns to Done without any explicit "Mark as read" action. This defeats the main use case described in the PR. All other layers (protocol, RPC, hook, UI) look sound.

packages/app/src/utils/agent-attention.ts — the shouldClearAgentAttention function needs a "manual" exemption alongside the existing "permission" one.

Important Files Changed

Filename Overview
packages/app/src/utils/agent-attention.ts Added "manual" to ATTENTION_REASON_PRIORITY but missing exemption in shouldClearAgentAttention — manually-flagged workspaces will silently auto-clear when the user navigates into them.
packages/server/src/server/agent/agent-manager.ts New markAgentAttention method mirrors clearAgentAttention correctly; contains a dead null-guard that the TypeScript type already prevents.
packages/server/src/server/session.ts New handleWorkspaceSetAttentionRequest handler follows the existing clear_attention pattern; correctly scopes agent marking to workspaceId and handles batch workspaceId input.
packages/app/src/hooks/use-mark-workspace-attention.ts New hook mirrors useClearWorkspaceAttention; correctly gates on workspace.status === "done" and reuses the hostDisconnected i18n key for the client-not-found error.
packages/protocol/src/messages.ts Adds WorkspaceSetAttentionRequest/ResponseSchema and registers them in both inbound and outbound discriminated unions; enum updates are consistent across all four relevant schemas.
packages/app/src/components/sidebar/sidebar-workspace-menu.tsx New menu item wired correctly; also fixes the previously hardcoded "Mark as read" string to use the i18n key.
packages/client/src/daemon-client.ts New markWorkspaceAttention method is a clean mirror of the existing clearWorkspaceAttention pattern.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant SidebarRow as SidebarWorkspaceRow
    participant Hook as useMarkWorkspaceAttention
    participant Client as DaemonClient
    participant Session as Session (Server)
    participant AgentMgr as AgentManager
    participant Storage as AgentStorage

    User->>SidebarRow: Right-click "Mark as ready to review"
    SidebarRow->>Hook: markAttention()
    Hook->>Hook: "Guard: workspace.status === "done"?"
    Hook->>Client: markWorkspaceAttention(workspaceId)
    Client->>Session: workspace.set_attention.request
    Session->>Session: listAgentPayloads()
    loop For each agent in workspace
        alt Live agent
            Session->>AgentMgr: markAgentAttention(agentId, "manual")
            AgentMgr->>Storage: persistSnapshot(agent)
            AgentMgr->>Session: emitState(agent)
        else Stored agent
            Session->>Storage: agentStorage.upsert(nextRecord)
            Session->>Client: agent_update event
        end
    end
    Session->>Client: emitWorkspaceUpdateForWorkspaceId
    Session->>Client: workspace.set_attention.response
    Client->>SidebarRow: resolve / throw
    Note over SidebarRow: Workspace moves from Done to Ready to review
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant SidebarRow as SidebarWorkspaceRow
    participant Hook as useMarkWorkspaceAttention
    participant Client as DaemonClient
    participant Session as Session (Server)
    participant AgentMgr as AgentManager
    participant Storage as AgentStorage

    User->>SidebarRow: Right-click "Mark as ready to review"
    SidebarRow->>Hook: markAttention()
    Hook->>Hook: "Guard: workspace.status === "done"?"
    Hook->>Client: markWorkspaceAttention(workspaceId)
    Client->>Session: workspace.set_attention.request
    Session->>Session: listAgentPayloads()
    loop For each agent in workspace
        alt Live agent
            Session->>AgentMgr: markAgentAttention(agentId, "manual")
            AgentMgr->>Storage: persistSnapshot(agent)
            AgentMgr->>Session: emitState(agent)
        else Stored agent
            Session->>Storage: agentStorage.upsert(nextRecord)
            Session->>Client: agent_update event
        end
    end
    Session->>Client: emitWorkspaceUpdateForWorkspaceId
    Session->>Client: workspace.set_attention.response
    Client->>SidebarRow: resolve / throw
    Note over SidebarRow: Workspace moves from Done to Ready to review
Loading

Comments Outside Diff (1)

  1. packages/app/src/utils/agent-attention.ts, line 64-82 (link)

    P1 Manually-flagged workspaces auto-clear on navigation

    shouldClearAgentAttention exempts "permission" from auto-clearing but has no such exemption for "manual". Any trigger (focus-entry, input-focus, prompt-send, agent-blur) will clear a manually-flagged workspace as soon as the user opens it. The net effect: a user clicks "Mark as ready to review", then navigates into the workspace to inspect it, and the workspace immediately silently returns to "Done" — precisely the state the user was trying to escape. Adding "manual" to the exemption list (alongside "permission") would preserve the flag until the user explicitly selects "Mark as read".

Reviews (1): Last reviewed commit: "feat(app): add "Mark as ready to review"..." | Re-trigger Greptile

Comment on lines +1643 to +1650
async markAgentAttention(
agentId: string,
reason: Exclude<AgentAttentionReason, null | undefined>,
): Promise<void> {
if (!reason) {
throw new Error("Cannot mark agent attention without a reason");
}
const agent = this.requireAgent(agentId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The if (!reason) guard is unreachable — the TypeScript type Exclude<AgentAttentionReason, null | undefined> already excludes null and undefined from the call-site, so this branch can never fire. It's unnecessary defensive code that the type system renders dead.

Suggested change
async markAgentAttention(
agentId: string,
reason: Exclude<AgentAttentionReason, null | undefined>,
): Promise<void> {
if (!reason) {
throw new Error("Cannot mark agent attention without a reason");
}
const agent = this.requireAgent(agentId);
async markAgentAttention(
agentId: string,
reason: Exclude<AgentAttentionReason, null | undefined>,
): Promise<void> {
const agent = this.requireAgent(agentId);

Rule Used: # Code Review Pattern Reference: Slop, Tests, Feat... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant