docs(19): create agent inbox phase plan
Phase 19: Agent Inbox - 4 plans in 2 waves - 3 parallel (Wave 1), 1 sequential (Wave 2) - Ready for execution
This commit is contained in:
@@ -294,10 +294,13 @@ Plans:
|
||||
**Goal**: Message list, multi-question forms (radio/checkbox/free-text), answer submission, notifications
|
||||
**Depends on**: Phase 18
|
||||
**Research**: Unlikely (internal patterns, form handling from wireframes)
|
||||
**Plans**: TBD
|
||||
**Plans**: 4 plans
|
||||
|
||||
Plans:
|
||||
- [ ] 19-01: TBD (run /gsd:plan-phase 19 to break down)
|
||||
- [ ] 19-01: Backend API for Agent Questions
|
||||
- [ ] 19-02: InboxList & MessageCard Components
|
||||
- [ ] 19-03: QuestionForm & Input Components
|
||||
- [ ] 19-04: Inbox Page Assembly
|
||||
|
||||
#### Phase 20: Real-time Subscriptions
|
||||
|
||||
|
||||
99
.planning/phases/19-agent-inbox/19-01-PLAN.md
Normal file
99
.planning/phases/19-agent-inbox/19-01-PLAN.md
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
phase: 19-agent-inbox
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified: [src/trpc/router.ts]
|
||||
autonomous: true
|
||||
---
|
||||
|
||||
<objective>
|
||||
Add tRPC procedures to expose structured agent question data to the frontend.
|
||||
|
||||
Purpose: The agent inbox UI needs structured question data (options, multiSelect) to render form controls. Questions are stored in-memory on AgentManager via `getPendingQuestions(agentId)`, but no tRPC procedure exposes this. The message table `content` is a plain string and does NOT contain the structured question data. This plan bridges that gap.
|
||||
|
||||
Output: Two new tRPC procedures: `getAgentQuestions` and `listWaitingAgents`.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@~/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
|
||||
@src/trpc/router.ts
|
||||
@src/agent/types.ts
|
||||
@src/agent/schema.ts
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Add getAgentQuestions tRPC procedure</name>
|
||||
<files>src/trpc/router.ts</files>
|
||||
<action>
|
||||
Add a `getAgentQuestions` query procedure to the appRouter. It should:
|
||||
1. Accept `agentIdentifierSchema` input (same as getAgent — name or id)
|
||||
2. Resolve the agent via `resolveAgent(ctx, input)`
|
||||
3. Call `agentManager.getPendingQuestions(agent.id)`
|
||||
4. Return the `PendingQuestions` object (or null if no pending questions)
|
||||
|
||||
Place it near the existing `getAgentResult` procedure since it follows the same pattern.
|
||||
|
||||
Also add a `listWaitingAgents` query that filters `agentManager.list()` to only agents with `status === 'waiting_for_input'`. This lets the inbox efficiently fetch only agents that have questions without filtering client-side.
|
||||
|
||||
Update the JSDoc procedure list comment at the top of appRouter to include both new procedures.
|
||||
</action>
|
||||
<verify>npx tsc --noEmit (TypeScript compiles without errors)</verify>
|
||||
<done>
|
||||
- `getAgentQuestions` procedure exists and returns PendingQuestions | null
|
||||
- `listWaitingAgents` procedure exists and returns AgentInfo[] filtered to waiting_for_input
|
||||
- TypeScript compiles clean
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Export PendingQuestions and QuestionItem types from shared package</name>
|
||||
<files>packages/shared/src/types.ts</files>
|
||||
<action>
|
||||
The frontend needs `PendingQuestions` and `QuestionItem` types to properly type the question form props. Export these types from the shared package:
|
||||
|
||||
1. In `packages/shared/src/types.ts`, add re-exports for `PendingQuestions` and `QuestionItem` from `../../src/agent/types.js`
|
||||
2. If the import path doesn't work with the shared package's rootDir config, define matching interfaces directly in the shared types file (same shapes as in src/agent/types.ts)
|
||||
|
||||
The key types needed by the frontend are:
|
||||
- `QuestionItem`: `{ id: string; question: string; options?: { label: string; description?: string }[]; multiSelect?: boolean }`
|
||||
- `PendingQuestions`: `{ questions: QuestionItem[] }`
|
||||
</action>
|
||||
<verify>npx tsc --noEmit -p packages/shared/tsconfig.json && npx tsc --noEmit -p packages/web/tsconfig.app.json</verify>
|
||||
<done>
|
||||
- PendingQuestions and QuestionItem available to import from @codewalk-district/shared
|
||||
- Both frontend and backend packages compile
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] `npx tsc --noEmit` passes in root
|
||||
- [ ] `npm run build` succeeds
|
||||
- [ ] New procedures visible in router type
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
- getAgentQuestions procedure returns structured question data from AgentManager
|
||||
- listWaitingAgents returns only agents in waiting_for_input status
|
||||
- PendingQuestions/QuestionItem types available in shared package
|
||||
- All builds pass
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/19-agent-inbox/19-01-SUMMARY.md`
|
||||
</output>
|
||||
120
.planning/phases/19-agent-inbox/19-02-PLAN.md
Normal file
120
.planning/phases/19-agent-inbox/19-02-PLAN.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
phase: 19-agent-inbox
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified: [packages/web/src/components/InboxList.tsx, packages/web/src/components/MessageCard.tsx]
|
||||
autonomous: true
|
||||
---
|
||||
|
||||
<objective>
|
||||
Build the InboxList and MessageCard components for the agent inbox message list view.
|
||||
|
||||
Purpose: These are the entry-point components for the inbox — showing a filterable, sortable list of agent messages. Following the wireframe spec from docs/wireframes/agent-inbox.md.
|
||||
|
||||
Output: InboxList and MessageCard components with filter/sort controls and empty state.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@~/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
|
||||
@docs/wireframes/agent-inbox.md
|
||||
@packages/web/src/components/InitiativeList.tsx
|
||||
@packages/web/src/components/InitiativeCard.tsx
|
||||
@packages/web/src/components/StatusBadge.tsx
|
||||
@packages/web/src/components/ui/card.tsx
|
||||
@packages/web/src/components/ui/badge.tsx
|
||||
@packages/web/src/components/ui/button.tsx
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create MessageCard component</name>
|
||||
<files>packages/web/src/components/MessageCard.tsx</files>
|
||||
<action>
|
||||
Create MessageCard following the wireframe spec. Props:
|
||||
- `agentName: string` — agent human-readable name
|
||||
- `agentStatus: string` — 'waiting_for_input' | 'running' | 'stopped' | etc.
|
||||
- `preview: string` — message preview text (truncated)
|
||||
- `timestamp: string` — ISO date string for relative time display
|
||||
- `requiresResponse: boolean` — determines filled (●) vs empty (○) indicator
|
||||
- `isSelected: boolean` — highlighted state
|
||||
- `onClick: () => void`
|
||||
|
||||
Display:
|
||||
- Status indicator: ● (filled circle) when requiresResponse=true, ○ (empty) otherwise
|
||||
- Agent name with status text in parentheses (e.g., "gastown (waiting)")
|
||||
- Message preview truncated to ~80 chars with ellipsis
|
||||
- Relative timestamp using simple helper (e.g., "2 min ago", "1h ago"). Write an inline `formatRelativeTime(isoDate: string): string` helper at the top of the file — do NOT create a separate utils file.
|
||||
- Selected state: slightly different background via className toggle
|
||||
|
||||
Use shadcn Card as the base, Tailwind for styling. Match the visual density of InitiativeCard.
|
||||
</action>
|
||||
<verify>npx tsc --noEmit -p packages/web/tsconfig.app.json</verify>
|
||||
<done>MessageCard renders with indicator, name, preview, timestamp, and selected state</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Create InboxList component</name>
|
||||
<files>packages/web/src/components/InboxList.tsx</files>
|
||||
<action>
|
||||
Create InboxList following the wireframe spec. Props:
|
||||
- `agents: Array<{ id: string; name: string; status: string; taskId: string; updatedAt: string }>` — agent data
|
||||
- `messages: Array<{ id: string; senderId: string | null; content: string; requiresResponse: boolean; status: string; createdAt: string }>` — message data
|
||||
- `selectedAgentId: string | null` — currently selected agent
|
||||
- `onSelectAgent: (agentId: string) => void` — selection handler
|
||||
- `onRefresh: () => void` — refresh handler
|
||||
|
||||
Internal state:
|
||||
- `filter: 'all' | 'waiting' | 'completed'` — default 'all'
|
||||
- `sort: 'newest' | 'oldest'` — default 'newest'
|
||||
|
||||
Behavior:
|
||||
1. Join agents with their latest message (match message.senderId to agent.id)
|
||||
2. Filter: 'waiting' = requiresResponse messages, 'completed' = responded/non-requiring messages, 'all' = everything
|
||||
3. Sort by message timestamp (newest or oldest)
|
||||
4. Render MessageCard for each result
|
||||
5. Show header with count badge: "Agent Inbox (3)" and Refresh button
|
||||
6. Filter and sort as simple button groups or select elements (not dropdowns — keep it simple)
|
||||
7. Empty state when no messages match filter: "No pending messages" with subtitle text
|
||||
|
||||
Follow the pattern from InitiativeList for the list layout structure.
|
||||
</action>
|
||||
<verify>npx tsc --noEmit -p packages/web/tsconfig.app.json</verify>
|
||||
<done>
|
||||
- InboxList renders with filter/sort controls
|
||||
- MessageCard instances rendered for each agent+message pair
|
||||
- Empty state shown when no messages
|
||||
- Filter and sort work correctly
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] `npx tsc --noEmit -p packages/web/tsconfig.app.json` passes
|
||||
- [ ] `npx vite build` in packages/web succeeds
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
- MessageCard shows indicator, agent name, preview, relative time
|
||||
- InboxList filters by waiting/completed/all
|
||||
- InboxList sorts by newest/oldest
|
||||
- Empty state renders when no messages match
|
||||
- All builds pass
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/19-agent-inbox/19-02-SUMMARY.md`
|
||||
</output>
|
||||
128
.planning/phases/19-agent-inbox/19-03-PLAN.md
Normal file
128
.planning/phases/19-agent-inbox/19-03-PLAN.md
Normal file
@@ -0,0 +1,128 @@
|
||||
---
|
||||
phase: 19-agent-inbox
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified: [packages/web/src/components/QuestionForm.tsx, packages/web/src/components/OptionGroup.tsx, packages/web/src/components/FreeTextInput.tsx]
|
||||
autonomous: true
|
||||
---
|
||||
|
||||
<objective>
|
||||
Build the question form components that render multi-question forms with mixed input types.
|
||||
|
||||
Purpose: Agents ask structured questions with options (radio/checkbox), multi-select, free-text, and "Other" fields. These components render the question forms inside the message detail view. Following the wireframe spec from docs/wireframes/agent-inbox.md.
|
||||
|
||||
Output: QuestionForm, OptionGroup, and FreeTextInput components.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@~/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
|
||||
@docs/wireframes/agent-inbox.md
|
||||
@src/agent/types.ts
|
||||
@packages/web/src/components/ui/input.tsx
|
||||
@packages/web/src/components/ui/label.tsx
|
||||
@packages/web/src/components/ui/textarea.tsx
|
||||
@packages/web/src/components/ui/button.tsx
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create OptionGroup and FreeTextInput components</name>
|
||||
<files>packages/web/src/components/OptionGroup.tsx, packages/web/src/components/FreeTextInput.tsx</files>
|
||||
<action>
|
||||
**OptionGroup** — renders radio buttons OR checkboxes for a question's options. Props:
|
||||
- `questionId: string` — for form identification
|
||||
- `options: Array<{ label: string; description?: string }>` — available choices
|
||||
- `multiSelect: boolean` — checkboxes (true) or radio buttons (false)
|
||||
- `value: string` — current selection (for radio: single label, for checkbox: comma-separated labels)
|
||||
- `onChange: (value: string) => void` — change handler
|
||||
- `allowOther?: boolean` — show "Other" free-text option (default true)
|
||||
|
||||
Behavior:
|
||||
- Radio buttons for single-select (default). Use native HTML radio inputs with Tailwind styling.
|
||||
- Checkboxes for multi-select. Use native HTML checkbox inputs with Tailwind styling.
|
||||
- Each option shows label and optional description in lighter text
|
||||
- "Other" option: text input that auto-selects its radio/checkbox when user types. The "Other" value is whatever the user typed.
|
||||
- For multi-select, value is comma-joined selected labels. When "Other" is checked, append the typed text.
|
||||
|
||||
**FreeTextInput** — renders when question has NO options (pure free-text). Props:
|
||||
- `questionId: string`
|
||||
- `value: string`
|
||||
- `onChange: (value: string) => void`
|
||||
- `multiline?: boolean` — textarea vs single input (default false)
|
||||
- `placeholder?: string`
|
||||
|
||||
Use shadcn Input for single-line, shadcn Textarea for multiline. Keep it simple.
|
||||
</action>
|
||||
<verify>npx tsc --noEmit -p packages/web/tsconfig.app.json</verify>
|
||||
<done>
|
||||
- OptionGroup renders radio or checkbox based on multiSelect prop
|
||||
- "Other" field auto-selects when typed into
|
||||
- FreeTextInput renders Input or Textarea based on multiline prop
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Create QuestionForm component</name>
|
||||
<files>packages/web/src/components/QuestionForm.tsx</files>
|
||||
<action>
|
||||
QuestionForm orchestrates rendering multiple questions with mixed input types. Props:
|
||||
- `questions: Array<{ id: string; question: string; options?: Array<{ label: string; description?: string }>; multiSelect?: boolean }>` — structured question data from agent
|
||||
- `onSubmit: (answers: Record<string, string>) => void` — submit handler with questionId→answer map
|
||||
- `onCancel: () => void` — cancel handler
|
||||
- `isSubmitting?: boolean` — disable form during submission
|
||||
|
||||
Internal state:
|
||||
- `answers: Record<string, string>` — maps question ID to answer string
|
||||
|
||||
Behavior:
|
||||
1. Render each question sequentially with "Q1:", "Q2:", etc. prefix and question text
|
||||
2. For each question, determine input type:
|
||||
- If `options` array exists → render OptionGroup (with multiSelect from question)
|
||||
- If no options → render FreeTextInput
|
||||
3. Track answers in local state via `onAnswerChange` callbacks
|
||||
4. "Send Answers" button: enabled only when ALL questions have non-empty answers
|
||||
5. "Cancel" button: always enabled
|
||||
6. On submit: call `onSubmit(answers)` with the complete answer map
|
||||
7. Button row at bottom: [Cancel] [Send Answers] — right-aligned, matching wireframe
|
||||
|
||||
Use shadcn Button for actions.
|
||||
</action>
|
||||
<verify>npx tsc --noEmit -p packages/web/tsconfig.app.json</verify>
|
||||
<done>
|
||||
- QuestionForm renders mixed question types from questions array
|
||||
- Submit disabled until all questions answered
|
||||
- onSubmit called with Record<string, string> mapping questionId to answer
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] `npx tsc --noEmit -p packages/web/tsconfig.app.json` passes
|
||||
- [ ] `npx vite build` in packages/web succeeds
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
- OptionGroup handles radio (single-select) and checkbox (multi-select) with "Other" field
|
||||
- FreeTextInput handles single-line and multiline inputs
|
||||
- QuestionForm renders sequential questions with correct input type per question
|
||||
- Submit validates all questions answered
|
||||
- All builds pass
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/19-agent-inbox/19-03-SUMMARY.md`
|
||||
</output>
|
||||
139
.planning/phases/19-agent-inbox/19-04-PLAN.md
Normal file
139
.planning/phases/19-agent-inbox/19-04-PLAN.md
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
phase: 19-agent-inbox
|
||||
plan: 04
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["19-01", "19-02", "19-03"]
|
||||
files_modified: [packages/web/src/routes/inbox.tsx]
|
||||
autonomous: true
|
||||
---
|
||||
|
||||
<objective>
|
||||
Wire the inbox page with tRPC data fetching, message detail panel, and answer submission.
|
||||
|
||||
Purpose: Assemble all Phase 19 components into the working inbox route. Connects InboxList + QuestionForm to backend via tRPC, handles the full answer submission flow (submit answers → resume agent), and shows notification messages.
|
||||
|
||||
Output: Fully functional Agent Inbox page at /inbox route.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@~/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
|
||||
@.planning/phases/19-agent-inbox/19-01-SUMMARY.md
|
||||
@.planning/phases/19-agent-inbox/19-02-SUMMARY.md
|
||||
@.planning/phases/19-agent-inbox/19-03-SUMMARY.md
|
||||
|
||||
@docs/wireframes/agent-inbox.md
|
||||
@packages/web/src/routes/inbox.tsx
|
||||
@packages/web/src/routes/initiatives/$id.tsx
|
||||
@packages/web/src/lib/trpc.ts
|
||||
@packages/web/src/components/InboxList.tsx
|
||||
@packages/web/src/components/QuestionForm.tsx
|
||||
@packages/web/src/components/MessageCard.tsx
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Wire inbox page with data fetching, detail panel, and answer submission</name>
|
||||
<files>packages/web/src/routes/inbox.tsx</files>
|
||||
<action>
|
||||
Replace the stub inbox page with the full implementation. The page has two sections:
|
||||
1. **Left/main area**: InboxList showing agents with messages
|
||||
2. **Detail panel**: appears when an agent is selected, showing message detail + question form
|
||||
|
||||
**Data fetching:**
|
||||
- `trpc.listWaitingAgents.useQuery({})` — get agents in waiting_for_input status
|
||||
- `trpc.listMessages.useQuery({})` — get all user-addressed messages
|
||||
- When agent selected: `trpc.getAgentQuestions.useQuery({ id: selectedAgentId }, { enabled: !!selectedAgentId })` — get structured question data
|
||||
|
||||
**Page state:**
|
||||
- `selectedAgentId: string | null` — which agent's detail is shown
|
||||
- Track loading/error states for each query
|
||||
|
||||
**Message Detail section** (inline in this file, not a separate component — it's page-specific):
|
||||
- Header: agent name, relative timestamp, task info (use agent.taskId to show task reference)
|
||||
- If agent has pending questions: render QuestionForm with the structured questions
|
||||
- If message is notification (requiresResponse=false): show content with "Dismiss" button
|
||||
|
||||
**Answer submission flow:**
|
||||
1. User fills QuestionForm and clicks "Send Answers"
|
||||
2. Call `trpc.resumeAgent.useMutation()` with `{ id: selectedAgentId, answers }`
|
||||
3. On success: invalidate listWaitingAgents and listMessages queries, clear selectedAgentId
|
||||
4. On error: show error message
|
||||
|
||||
**Notification handling:**
|
||||
- For messages with `requiresResponse: false` (type='info'), show content text
|
||||
- "Dismiss" button: call `trpc.respondToMessage.useMutation()` to mark as responded, then invalidate queries
|
||||
|
||||
**Layout:**
|
||||
- Use responsive grid similar to initiative detail: `lg:grid-cols-[1fr_400px]`
|
||||
- InboxList on left, detail panel on right (or stacked on mobile)
|
||||
- Loading states: skeleton or spinner matching existing patterns from initiatives pages
|
||||
- Error states: simple error text matching existing patterns
|
||||
|
||||
Follow patterns established in `initiatives/$id.tsx` for query management, loading states, and mutation/invalidation flow.
|
||||
</action>
|
||||
<verify>npx tsc --noEmit -p packages/web/tsconfig.app.json && cd packages/web && npx vite build</verify>
|
||||
<done>
|
||||
- Inbox page fetches agents and messages via tRPC
|
||||
- Selecting an agent shows detail panel with questions
|
||||
- Answer submission calls resumeAgent and refreshes data
|
||||
- Notification messages can be dismissed
|
||||
- Loading and error states handled
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Full build verification and integration check</name>
|
||||
<files>packages/web/src/routes/inbox.tsx</files>
|
||||
<action>
|
||||
Run full build verification:
|
||||
1. `npx tsc --noEmit` in root — all TypeScript checks pass
|
||||
2. `npm run build` in root — full monorepo build succeeds
|
||||
3. `cd packages/web && npx vite build` — frontend bundle builds
|
||||
4. Verify route is registered in routeTree.gen.ts for /inbox
|
||||
5. Verify no unused imports or type errors
|
||||
|
||||
Fix any issues found during verification.
|
||||
</action>
|
||||
<verify>npm run build (root) succeeds with zero errors</verify>
|
||||
<done>
|
||||
- All TypeScript checks pass
|
||||
- Monorepo build succeeds
|
||||
- Frontend bundles without errors
|
||||
- /inbox route properly registered
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] `npx tsc --noEmit` passes in root
|
||||
- [ ] `npm run build` succeeds in root
|
||||
- [ ] `npx vite build` succeeds in packages/web
|
||||
- [ ] /inbox route registered in routeTree.gen.ts
|
||||
- [ ] No TypeScript errors or warnings
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
- Inbox page shows list of waiting agents with their messages
|
||||
- Selecting an agent shows structured question form
|
||||
- Answer submission calls resumeAgent and refreshes the list
|
||||
- Notification messages display content and can be dismissed
|
||||
- Loading and error states work correctly
|
||||
- All builds pass
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/19-agent-inbox/19-04-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user