diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index eece46b..ea97161 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -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 diff --git a/.planning/phases/19-agent-inbox/19-01-PLAN.md b/.planning/phases/19-agent-inbox/19-01-PLAN.md new file mode 100644 index 0000000..88c1719 --- /dev/null +++ b/.planning/phases/19-agent-inbox/19-01-PLAN.md @@ -0,0 +1,99 @@ +--- +phase: 19-agent-inbox +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: [src/trpc/router.ts] +autonomous: true +--- + + +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`. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + +@src/trpc/router.ts +@src/agent/types.ts +@src/agent/schema.ts + + + + + + Task 1: Add getAgentQuestions tRPC procedure + src/trpc/router.ts + +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. + + npx tsc --noEmit (TypeScript compiles without errors) + + - `getAgentQuestions` procedure exists and returns PendingQuestions | null + - `listWaitingAgents` procedure exists and returns AgentInfo[] filtered to waiting_for_input + - TypeScript compiles clean + + + + + Task 2: Export PendingQuestions and QuestionItem types from shared package + packages/shared/src/types.ts + +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[] }` + + npx tsc --noEmit -p packages/shared/tsconfig.json && npx tsc --noEmit -p packages/web/tsconfig.app.json + + - PendingQuestions and QuestionItem available to import from @codewalk-district/shared + - Both frontend and backend packages compile + + + + + + +Before declaring plan complete: +- [ ] `npx tsc --noEmit` passes in root +- [ ] `npm run build` succeeds +- [ ] New procedures visible in router type + + + + +- 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 + + + +After completion, create `.planning/phases/19-agent-inbox/19-01-SUMMARY.md` + diff --git a/.planning/phases/19-agent-inbox/19-02-PLAN.md b/.planning/phases/19-agent-inbox/19-02-PLAN.md new file mode 100644 index 0000000..8898385 --- /dev/null +++ b/.planning/phases/19-agent-inbox/19-02-PLAN.md @@ -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 +--- + + +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. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.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 + + + + + + Task 1: Create MessageCard component + packages/web/src/components/MessageCard.tsx + +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. + + npx tsc --noEmit -p packages/web/tsconfig.app.json + MessageCard renders with indicator, name, preview, timestamp, and selected state + + + + Task 2: Create InboxList component + packages/web/src/components/InboxList.tsx + +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. + + npx tsc --noEmit -p packages/web/tsconfig.app.json + + - 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 + + + + + + +Before declaring plan complete: +- [ ] `npx tsc --noEmit -p packages/web/tsconfig.app.json` passes +- [ ] `npx vite build` in packages/web succeeds + + + + +- 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 + + + +After completion, create `.planning/phases/19-agent-inbox/19-02-SUMMARY.md` + diff --git a/.planning/phases/19-agent-inbox/19-03-PLAN.md b/.planning/phases/19-agent-inbox/19-03-PLAN.md new file mode 100644 index 0000000..92c0bd6 --- /dev/null +++ b/.planning/phases/19-agent-inbox/19-03-PLAN.md @@ -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 +--- + + +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. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.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 + + + + + + Task 1: Create OptionGroup and FreeTextInput components + packages/web/src/components/OptionGroup.tsx, packages/web/src/components/FreeTextInput.tsx + +**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. + + npx tsc --noEmit -p packages/web/tsconfig.app.json + + - 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 + + + + + Task 2: Create QuestionForm component + packages/web/src/components/QuestionForm.tsx + +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) => void` — submit handler with questionId→answer map +- `onCancel: () => void` — cancel handler +- `isSubmitting?: boolean` — disable form during submission + +Internal state: +- `answers: Record` — 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. + + npx tsc --noEmit -p packages/web/tsconfig.app.json + + - QuestionForm renders mixed question types from questions array + - Submit disabled until all questions answered + - onSubmit called with Record mapping questionId to answer + + + + + + +Before declaring plan complete: +- [ ] `npx tsc --noEmit -p packages/web/tsconfig.app.json` passes +- [ ] `npx vite build` in packages/web succeeds + + + + +- 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 + + + +After completion, create `.planning/phases/19-agent-inbox/19-03-SUMMARY.md` + diff --git a/.planning/phases/19-agent-inbox/19-04-PLAN.md b/.planning/phases/19-agent-inbox/19-04-PLAN.md new file mode 100644 index 0000000..1535fe3 --- /dev/null +++ b/.planning/phases/19-agent-inbox/19-04-PLAN.md @@ -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 +--- + + +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. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.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 + + + + + + Task 1: Wire inbox page with data fetching, detail panel, and answer submission + packages/web/src/routes/inbox.tsx + +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. + + npx tsc --noEmit -p packages/web/tsconfig.app.json && cd packages/web && npx vite build + + - 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 + + + + + Task 2: Full build verification and integration check + packages/web/src/routes/inbox.tsx + +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. + + npm run build (root) succeeds with zero errors + + - All TypeScript checks pass + - Monorepo build succeeds + - Frontend bundles without errors + - /inbox route properly registered + + + + + + +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 + + + + +- 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 + + + +After completion, create `.planning/phases/19-agent-inbox/19-04-SUMMARY.md` +