Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
8.6 KiB
Approval checkpoints frontend handover
What is implemented in the backend
The backend now supports approval checkpoints in two places:
tool-callfinal-response
tool-call approvals are enforced inside the .NET sidecar through the Copilot permission callback.
final-response approvals are enforced in the Electron main process after the sidecar finishes generating assistant messages but before those messages are published into the session transcript.
The roadmap item "pause before handing off outside the original working set" is not implemented in this change. There is still no working-set model in the backend that can support that rule correctly.
Important behavioral semantics
- A session stays
status: 'running'while waiting for approval. - The backend exposes the paused state through
session.pendingApproval. - The frontend should treat
session.pendingApprovalas "awaiting approval" even thoughsession.statusis stillrunning. - Resolving an approval clears
session.pendingApproval. - Approving a checkpoint resumes the run.
- Rejecting a
final-responsecheckpoint causes the run to fail with an explicit error after the backend resumes the waiting flow. - Rejecting a
tool-callcheckpoint sends a denial back to the Copilot runtime. The eventual run outcome depends on the runtime response, but it should be treated as a rejected risky action. - If Eryx restarts while an approval is pending, the backend marks that session as errored and converts the pending approval into a rejected/interrupted run event.
New shared model shapes
Pattern approval policy
File: src/shared/domain/approval.ts
Patterns can now carry an optional approvalPolicy:
type ApprovalCheckpointKind = 'tool-call' | 'final-response';
interface ApprovalCheckpointRule {
kind: ApprovalCheckpointKind;
agentIds?: string[]; // omitted or empty = all agents in the pattern
}
interface ApprovalPolicy {
rules: ApprovalCheckpointRule[];
}
This is attached to PatternDefinition as:
approvalPolicy?: ApprovalPolicy;
Backend validation already rejects agent IDs that do not exist in the pattern.
Session pending approval
File: src/shared/domain/approval.ts
Sessions can now expose a single active pending approval:
interface PendingApprovalMessageRecord {
id: string;
authorName: string;
content: string;
}
interface PendingApprovalRecord {
id: string;
kind: 'tool-call' | 'final-response';
status: 'pending' | 'approved' | 'rejected';
requestedAt: string;
resolvedAt?: string;
agentId?: string;
agentName?: string;
toolName?: string;
permissionKind?: string;
title: string;
detail?: string;
messages?: PendingApprovalMessageRecord[];
}
This is attached to SessionRecord as:
pendingApproval?: PendingApprovalRecord;
For final-response, pendingApproval.messages contains the unpublished assistant output that the reviewer should inspect before publication.
For tool-call, pendingApproval.messages is typically absent.
Run timeline event
File: src/shared/domain/runTimeline.ts
There is a new run timeline event kind:
kind: 'approval'
The event is updated in place over time rather than creating separate requested/approved/rejected entries.
Relevant fields:
approvalId?: string;
approvalKind?: 'tool-call' | 'final-response';
approvalTitle?: string;
approvalDetail?: string;
permissionKind?: string;
decision?: 'approved' | 'rejected';
status: 'running' | 'completed' | 'error';
Interpretation:
status: 'running'=> approval requested and still pendingstatus: 'completed'+decision: 'approved'=> approval grantedstatus: 'error'+decision: 'rejected'=> approval rejected
New IPC surface
Files:
src/shared/contracts/ipc.tssrc/shared/contracts/channels.tssrc/preload/index.tssrc/main/ipc/registerIpcHandlers.ts
New preload/API call:
resolveSessionApproval({
sessionId: string;
approvalId: string;
decision: 'approved' | 'rejected';
}): Promise<WorkspaceState>
This is the frontend action to approve or reject the active checkpoint.
Internal sidecar protocol additions
These are backend-only, but useful context for debugging:
Files:
src/shared/contracts/sidecar.tssidecar/src/Eryx.AgentHost/Contracts/ProtocolModels.cssidecar/src/Eryx.AgentHost/Services/SidecarProtocolHost.cssidecar/src/Eryx.AgentHost/Services/CopilotWorkflowRunner.cs
New sidecar event:
type: 'approval-requested'
New sidecar command:
type: 'resolve-approval'
The Electron main process already handles this handshake. The frontend should not talk to the sidecar directly.
Files that already understand approval data
Backend and shared code:
src/shared/domain/approval.tssrc/shared/domain/pattern.tssrc/shared/domain/session.tssrc/shared/domain/runTimeline.tssrc/main/EryxAppService.tssrc/main/sidecar/sidecarProcess.ts
Minimal renderer compile-support only:
src/renderer/lib/runTimelineFormatting.tssrc/renderer/components/RunTimeline.tsx
Those renderer changes are intentionally minimal. They only keep the current app build/typecheck working with the new timeline event kind.
Frontend work still needed
1. Pattern editor support for approval policy
Files to inspect:
src/renderer/components/PatternEditor.tsxsrc/renderer/App.tsx
Needed UI:
- Allow enabling
tool-callapprovals. - Allow enabling
final-responseapprovals. - Allow scoping each checkpoint to:
- all agents
- selected agents
Recommended representation:
- one section named "Approval checkpoints"
- one row per checkpoint kind
- a toggle
- an "all agents / selected agents" selector
- multi-select agent chips when scoped
2. Pending approval UI in chat/session surfaces
Files to inspect:
src/renderer/components/ChatPane.tsxsrc/renderer/components/ActivityPanel.tsxsrc/renderer/components/Sidebar.tsxsrc/renderer/lib/sessionWorkspace.ts
Needed behavior:
- Show a visible checkpoint card/banner whenever
session.pendingApprovalexists. - Present:
- title
- detail
- agent name
- permission kind (for tool-call)
- preview messages (for final-response)
- Add Approve and Reject actions that call
api.resolveSessionApproval(...).
3. Final-response review UI
Use session.pendingApproval.messages when kind === 'final-response'.
Recommended UX:
- Render the candidate assistant messages exactly as they would appear in chat.
- Make it clear they are pending publication, not yet committed to transcript history.
- On approval, let the backend publish them.
- On rejection, expect the run to fail and show an error state after the backend resumes the blocked flow.
4. Run timeline UI
Files to inspect:
src/renderer/components/RunTimeline.tsxsrc/renderer/lib/runTimelineFormatting.ts
Needed improvements:
- Add a dedicated approval event treatment instead of the current minimal fallback.
- Show distinct visuals for:
- requested
- approved
- rejected
- Include checkpoint kind and detail text.
- Include message preview affordances for final-response if desired.
5. Session activity / status treatment
Files to inspect:
src/renderer/lib/sessionActivity.tssrc/renderer/components/ActivityPanel.tsxsrc/renderer/components/ChatPane.tsx
Important rule:
- Do not rely on
session.statusalone to decide whether the session is actively streaming versus blocked on approval. - Use
session.pendingApprovalas the signal for "paused awaiting human action."
6. Error / stale checkpoint handling
The frontend should handle two backend-produced edge cases cleanly:
resolveSessionApproval(...)can fail if the approval is no longer active.- A pending approval can disappear on reload because the backend converted it into an interrupted error after restart.
Recommended UX:
- show a toast or inline error if approval resolution fails
- refresh from workspace state afterward
- show the session error banner when the run has been interrupted
Suggested frontend implementation order
- Add read-only pending approval banner/card in
ChatPane. - Wire approve/reject buttons to
resolveSessionApproval. - Render final-response preview from
pendingApproval.messages. - Add timeline rendering for
approvalevents. - Add pattern editor controls for configuring
approvalPolicy. - Refine sidebar/activity badges for "awaiting approval" state.
Validation commands
Backend changes already pass:
bun run typecheckbun testbun run sidecar:testbun run build
After the frontend agent finishes, rerun the same four commands.