# TASK-006: Agent Plans Admin UI **Status:** all_phases_verified (Ready for human approval) **Created:** 2026-01-02 **Last Updated:** 2026-01-02 23:45 by Claude Opus 4.5 (Verification Agent) **Assignee:** Claude Opus 4.5 (Implementation Agent) **Verifier:** Claude Opus 4.5 (Verification Agent) --- ## Critical Context (READ FIRST) **The agent infrastructure exists. Now we need visibility.** ### What Already Exists The codebase has sophisticated agent workflow infrastructure: | Component | Location | Purpose | |-----------|----------|---------| | AgentPlan | `app/Models/Agent/AgentPlan.php` | Multi-phase work plans | | AgentPhase | `app/Models/Agent/AgentPhase.php` | Individual phases with tasks | | AgentSession | `app/Models/Agent/AgentSession.php` | Work sessions with handoff | | AgentWorkspaceState | `app/Models/Agent/AgentWorkspaceState.php` | Shared state storage | | McpToolCall | `app/Models/Mcp/McpToolCall.php` | Tool invocation logs | | McpToolCallStat | `app/Models/Mcp/McpToolCallStat.php` | Daily aggregates | | PlanTemplateService | `app/Services/Agent/PlanTemplateService.php` | Template-based plan creation | | MCP Registry | `routes/mcp.php` | Agent discovery endpoints | **What's Missing:** Admin UI to view, manage, and monitor all of this. ### The Vision A Hades admin section that provides: 1. **Plan Dashboard** — See all active plans across workspaces 2. **Phase Tracking** — Visual progress through plan phases 3. **Session Monitor** — Watch agent sessions in real-time 4. **Tool Analytics** — MCP tool usage and performance 5. **API Key Management** — Enable external agent access 6. **Plan Templates** — Browse and create from templates This becomes the control tower for AI-stabilised hosting — where humans supervise agent work. --- ## Objective Build a comprehensive Agent Plans admin UI in the Hades section that surfaces all existing agent infrastructure. Enable operators to monitor, manage, and guide agent work across the platform. **"Done" looks like:** - Hades users can see all agent plans and their progress - Real-time session monitoring shows active agent work - MCP tool analytics reveal usage patterns - API keys allow external agents to create/update plans - Plan templates can be browsed and instantiated --- ## Acceptance Criteria ### Phase 1: Plan Dashboard ✅ - [x] AC1: Route `hub.agents.plans` shows all AgentPlans (filterable by workspace, status) - [x] AC2: Plan list shows: title, workspace, status, phase progress, last activity - [x] AC3: Plan detail view shows all phases with task checklists - [x] AC4: Phase progress displayed as visual percentage bar - [x] AC5: Can manually update plan status (activate, complete, archive) - [x] AC6: Can manually update phase status (start, complete, block, skip) ### Phase 2: Session Monitor ✅ - [x] AC7: Route `hub.agents.sessions` shows all AgentSessions - [x] AC8: Active sessions show real-time activity (polling or WebSocket) - [x] AC9: Session detail shows: work log, artifacts, handoff notes - [x] AC10: Can view session context summary and final summary - [x] AC11: Timeline view shows session sequence within a plan - [x] AC12: Can pause/resume/fail active sessions manually ### Phase 3: Tool Analytics ✅ - [x] AC13: Route `hub.agents.tools` shows MCP tool usage dashboard - [x] AC14: Top 10 tools by usage displayed - [x] AC15: Daily trend chart (calls per day, 30-day window) - [x] AC16: Server breakdown shows usage by MCP server - [x] AC17: Error rate and average duration displayed per tool - [x] AC18: Can drill down to individual tool calls with full params ### Phase 4: API Key Management ✅ - [x] AC19: Route `hub.agents.api-keys` manages agent API access - [x] AC20: Can create API keys scoped to workspace - [x] AC21: Keys have configurable permissions (read plans, write plans, execute tools) - [x] AC22: Key usage tracking (last used, call count) - [x] AC23: Can revoke keys immediately - [x] AC24: Rate limiting configuration per key ### Phase 5: Plan Templates ✅ - [x] AC25: Route `hub.agents.templates` lists available plan templates - [x] AC26: Template preview shows phases and variables - [x] AC27: Can create new plan from template with variable input - [x] AC28: Template categories displayed for organisation - [x] AC29: Can import custom templates (YAML upload) --- ## Implementation Checklist ### Routes (`routes/web.php`) Add after line 164 (after Commerce routes), inside the hub group: ```php // Agent Operations (Hades only) Route::prefix('agents')->name('agents.')->group(function () { Route::get('/', \App\Livewire\Admin\Agent\Dashboard::class)->name('index'); Route::get('/plans', \App\Livewire\Admin\Agent\Plans::class)->name('plans'); Route::get('/plans/{slug}', \App\Livewire\Admin\Agent\PlanDetail::class)->name('plans.show'); Route::get('/sessions', \App\Livewire\Admin\Agent\Sessions::class)->name('sessions'); Route::get('/sessions/{id}', \App\Livewire\Admin\Agent\SessionDetail::class)->name('sessions.show'); Route::get('/tools', \App\Livewire\Admin\Agent\ToolAnalytics::class)->name('tools'); Route::get('/tools/calls', \App\Livewire\Admin\Agent\ToolCalls::class)->name('tools.calls'); Route::get('/api-keys', \App\Livewire\Admin\Agent\ApiKeys::class)->name('api-keys'); Route::get('/templates', \App\Livewire\Admin\Agent\Templates::class)->name('templates'); }); ``` - [x] Add route group `hub/agents` - [x] Route `hub.agents.index` → Dashboard (overview) - [x] Route `hub.agents.plans` → Plans (list) - [x] Route `hub.agents.plans.show` → PlanDetail (single plan by slug) - [x] Route `hub.agents.sessions` → Sessions (list) *(Phase 2)* ✅ - [x] Route `hub.agents.sessions.show` → SessionDetail (single session) *(Phase 2)* ✅ - [x] Route `hub.agents.tools` → ToolAnalytics (MCP stats) *(Phase 3)* ✅ - [x] Route `hub.agents.tools.calls` → ToolCalls (detailed logs) *(Phase 3)* ✅ - [x] Route `hub.agents.api-keys` → ApiKeys (key management) *(Phase 4)* ✅ - [x] Route `hub.agents.templates` → Templates (template browser) *(Phase 5)* ✅ ### Livewire Components (`app/Livewire/Admin/Agent/`) **Create directory:** `mkdir -p app/Livewire/Admin/Agent` Each component should check `$this->authorize()` or `auth()->user()->isHades()` in mount(). - [x] Create `Dashboard.php` — Overview with key metrics ✅ - Query: `AgentPlan::where('status', 'active')->count()` - Query: `AgentSession::where('status', 'active')->count()` - Query: `McpToolCallStat::getTopTools()` - Query: `McpToolCallStat::getDailyTrend()` - [x] Create `Plans.php` — Plan list with filters ✅ - Properties: `$status`, `$workspace`, `$search`, `$perPage` - Query: `AgentPlan::with(['workspace', 'agentPhases'])->filter()->paginate()` - Methods: `activate()`, `archive()`, `delete()` - [x] Create `PlanDetail.php` — Single plan with phases ✅ - Property: `AgentPlan $plan` (route model binding via slug) - Relationships: `$plan->agentPhases`, `$plan->sessions`, `$plan->states` - Methods: `updatePhaseStatus()`, `completeTask()`, `addTask()` - [x] Create `Sessions.php` — Session list ✅ - Properties: `$status`, `$agentType`, `$planSlug`, `$workspace`, `$search` - Query: `AgentSession::with(['plan', 'workspace'])->filter()->paginate()` - Methods: `pause()`, `resume()`, `complete()`, `fail()`, `clearFilters()` - [x] Create `SessionDetail.php` — Single session with logs ✅ - Property: `AgentSession $session` - Display: `$session->work_log`, `$session->artifacts`, `$session->handoff_notes` - Methods: `pauseSession()`, `resumeSession()`, `completeSession()`, `failSession()`, `poll()` - Polling: 5-second interval for active sessions (disabled when ended) - [x] Create `ToolAnalytics.php` — MCP usage charts ✅ - Query: `McpToolCallStat::getTopTools()`, `getDailyTrend()`, `getServerStats()` - Chart data for Chart.js with daily trend visualization - Filters: days (7/14/30), workspace, server - [x] Create `ToolCalls.php` — Tool call log browser ✅ - Properties: `$search`, `$server`, `$tool`, `$status`, `$workspace`, `$agentType` - Query: `McpToolCall::with(['workspace'])->filter()->paginate()` - Modal detail view for individual tool calls (AC18) - [x] Create `ApiKeys.php` — Key CRUD *(Phase 4)* ✅ - Methods: `createKey()`, `updateKey()`, `revokeKey()` - Display plaintext key ONCE on creation - Filters: workspace, status (active/revoked/expired) - Edit modal for permissions and rate limit updates - [x] Create `Templates.php` — Template browser and instantiation *(Phase 5)* ✅ - Uses: `PlanTemplateService::list()`, `get()`, `createPlan()`, `previewTemplate()` - Modal for variable input before instantiation - YAML import with validation and preview - Category filtering and search - Template preview with phases and variables ### Views (`resources/views/admin/livewire/agent/`) - [x] Create `dashboard.blade.php` ✅ - [x] Create `plans.blade.php` ✅ - [x] Create `plan-detail.blade.php` ✅ - [x] Create `sessions.blade.php` *(Phase 2)* ✅ - [x] Create `session-detail.blade.php` *(Phase 2)* ✅ - [x] Create `tool-analytics.blade.php` *(Phase 3)* ✅ - [x] Create `tool-calls.blade.php` *(Phase 3)* ✅ - [x] Create `api-keys.blade.php` *(Phase 4)* ✅ - [x] Create `templates.blade.php` *(Phase 5)* ✅ ### API Endpoints (`routes/api.php`) - [ ] Add API route group `api/v1/agents` with API key auth - [ ] `GET /api/v1/agents/plans` — List plans for workspace - [ ] `POST /api/v1/agents/plans` — Create plan (from template or raw) - [ ] `GET /api/v1/agents/plans/{slug}` — Get plan detail - [ ] `PATCH /api/v1/agents/plans/{slug}` — Update plan status - [ ] `POST /api/v1/agents/plans/{slug}/phases/{id}/tasks` — Add task to phase - [ ] `PATCH /api/v1/agents/plans/{slug}/phases/{id}/tasks/{name}` — Complete task - [ ] `POST /api/v1/agents/sessions` — Start new session - [ ] `PATCH /api/v1/agents/sessions/{id}` — Update session (log, artifacts, handoff) - [ ] `POST /api/v1/agents/sessions/{id}/complete` — End session with summary - [ ] `GET /api/v1/agents/templates` — List available templates - [ ] `POST /api/v1/agents/templates/{slug}/instantiate` — Create plan from template ### Models & Migrations - [x] Create `AgentApiKey` model with: *(Phase 4)* ✅ - workspace_id, name, key (hashed), permissions (JSON) - last_used_at, call_count, rate_limit - expires_at, revoked_at - Key methods: `generate()`, `findByKey()`, `hasPermission()`, `revoke()`, `recordUsage()` - [x] Migration for `agent_api_keys` table *(Phase 4)* ✅ - [x] Add `api_key_id` to `agent_sessions` table (track which key started session) *(Phase 4)* ✅ - [x] Add `api_key_id` to `mcp_tool_calls` table (track which key made call) *(Phase 4)* ✅ ### Services - [x] Create `AgentApiKeyService.php`: *(Phase 4)* ✅ - `create()`, `revoke()`, `validate()` - `checkPermission()`, `checkPermissions()`, `recordUsage()` - `isRateLimited()`, `getRateLimitStatus()` - `authenticate()` — Full auth flow with rate limiting - `updatePermissions()`, `updateRateLimit()` - [x] `PlanTemplateService.php` already has all needed methods *(Phase 5)* ✅ - `list()`, `get()`, `createPlan()`, `previewTemplate()` - `getCategories()`, `getByCategory()`, `validateVariables()` - YAML import handled directly in Livewire component ### Sidebar Integration - [x] Add "Agents" group to Hades sidebar: ✅ ``` Agents ├── Dashboard → hub.agents.index ✅ ├── Plans → hub.agents.plans ✅ ├── Sessions → hub.agents.sessions ✅ (Phase 2) ├── Tool Analytics → hub.agents.tools ✅ (Phase 3) ├── API Keys → hub.agents.api-keys (Phase 4) └── Templates → hub.agents.templates (Phase 5) ``` - [x] Sessions link added to sidebar *(Phase 2)* ✅ - [x] Sessions quick link activated in dashboard *(Phase 2)* ✅ - [x] Add badge for plans needing attention (blocked phases) ✅ - [x] Tool Analytics link added to sidebar *(Phase 3)* ✅ - [x] Tool Analytics quick link activated in dashboard *(Phase 3)* ✅ - [x] API Keys link added to sidebar *(Phase 4)* ✅ - [x] Templates link added to sidebar *(Phase 5)* ✅ - [x] Templates quick link activated in dashboard *(Phase 5)* ✅ ### Testing - [ ] Feature test: Plan list filters work - [ ] Feature test: Plan status transitions work - [ ] Feature test: Session creation and update work - [ ] Feature test: API key authentication works - [ ] Feature test: API key permissions enforced - [ ] Feature test: Rate limiting works - [ ] Feature test: Template instantiation works - [ ] Browser test: Dashboard loads with charts - [ ] Browser test: Real-time session updates work --- ## UI Specifications ### Agent Dashboard (Overview) ``` ┌─────────────────────────────────────────────────────────────────┐ │ Agent Operations [Refresh] │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 12 │ │ 3 │ │ 847 │ │ 98.2% │ │ │ │ Active │ │ Sessions │ │ Tool │ │ Success │ │ │ │ Plans │ │ Today │ │ Calls │ │ Rate │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ Recent Activity Tool Usage (7 days) │ │ ┌─────────────────────────┐ ┌─────────────────────┐ │ │ │ • Plan "content-gen" │ │ ▓▓▓▓▓▓▓▓░░ 80% │ │ │ │ Phase 2 completed │ │ content_create │ │ │ │ 2 minutes ago │ │ │ │ │ │ │ │ ▓▓▓▓▓░░░░░ 50% │ │ │ │ • Session opus-abc123 │ │ content_read │ │ │ │ Started work on plan │ │ │ │ │ │ 5 minutes ago │ │ ▓▓▓░░░░░░░ 30% │ │ │ └─────────────────────────┘ │ search_docs │ │ │ └─────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ### Plan Detail View ``` ┌─────────────────────────────────────────────────────────────────┐ │ Plan: Content Migration Status: Active│ │ Workspace: main │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Progress: ████████████░░░░░░░░ 60% │ │ │ │ Phases: │ │ ┌─────────────────────────────────────────────────────────────┐│ │ │ ✓ Phase 1: Audit Existing Content [Completed] ││ │ │ └─ 5/5 tasks complete ││ │ ├─────────────────────────────────────────────────────────────┤│ │ │ ● Phase 2: Migrate Blog Posts [In Progress] ││ │ │ ├─ ✓ Export WordPress posts ││ │ │ ├─ ✓ Transform to native format ││ │ │ ├─ ○ Import to ContentItem ││ │ │ └─ ○ Verify all posts render ││ │ │ Progress: ██████░░░░ 50% ││ │ ├─────────────────────────────────────────────────────────────┤│ │ │ ○ Phase 3: Migrate Help Articles [Pending] ││ │ │ └─ Waiting for Phase 2 ││ │ └─────────────────────────────────────────────────────────────┘│ │ │ │ Sessions: │ │ ┌─────────────────────────────────────────────────────────────┐│ │ │ sess_abc123 │ opus │ Active │ Started 10m ago │ [View] ││ │ │ sess_xyz789 │ sonnet │ Completed │ 2h duration │ [View] ││ │ └─────────────────────────────────────────────────────────────┘│ └─────────────────────────────────────────────────────────────────┘ ``` ### Session Detail View ``` ┌─────────────────────────────────────────────────────────────────┐ │ Session: sess_abc123 Agent: opus │ │ Plan: Content Migration Status: Active │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Duration: 10m 23s Last Active: 30s ago │ │ │ │ Work Log: │ │ ┌─────────────────────────────────────────────────────────────┐│ │ │ 15:30:00 [info] Starting phase 2 execution ││ │ │ 15:30:15 [success] Exported 47 WordPress posts ││ │ │ 15:32:00 [info] Transforming post format... ││ │ │ 15:35:22 [checkpoint] 25/47 posts transformed ││ │ │ 15:38:45 [success] All posts transformed ││ │ │ 15:39:00 [info] Beginning import to ContentItem... ││ │ └─────────────────────────────────────────────────────────────┘│ │ │ │ Artifacts: │ │ ┌─────────────────────────────────────────────────────────────┐│ │ │ storage/exports/wp-posts.json │ created │ 15:30:15 ││ │ │ app/Services/ContentImporter.php │ modified │ 15:35:22 ││ │ └─────────────────────────────────────────────────────────────┘│ │ │ │ Handoff Notes: │ │ ┌─────────────────────────────────────────────────────────────┐│ │ │ Summary: Completed export and transformation phases. ││ │ │ Next: Import remaining 22 posts, verify rendering. ││ │ │ Blockers: None currently. ││ │ └─────────────────────────────────────────────────────────────┘│ │ │ │ [Pause Session] [Complete Session] [Fail Session] │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## API Authentication ### Key Generation ```php // Create key $key = AgentApiKey::create([ 'workspace_id' => $workspace->id, 'name' => 'Claude Code Integration', 'permissions' => ['plans.read', 'plans.write', 'sessions.write'], 'rate_limit' => 100, // per minute 'expires_at' => now()->addYear(), ]); // Returns unhashed key ONCE for user to copy return $key->plainTextKey; // ak_live_xxxxxxxxxxxx ``` ### Key Usage ```bash # External agent creates a plan curl -X POST https://host.uk.com/api/v1/agents/plans \ -H "Authorization: Bearer ak_live_xxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "template": "content-migration", "variables": { "source": "wordpress", "workspace": "main" } }' # Agent logs work to session curl -X PATCH https://host.uk.com/api/v1/agents/sessions/sess_abc123 \ -H "Authorization: Bearer ak_live_xxxxxxxxxxxx" \ -d '{ "work_log": { "type": "checkpoint", "message": "Completed 25/47 posts" } }' ``` ### Permissions | Permission | Allows | |------------|--------| | `plans.read` | List and view plans | | `plans.write` | Create, update, archive plans | | `phases.write` | Update phase status, add/complete tasks | | `sessions.read` | List and view sessions | | `sessions.write` | Start, update, complete sessions | | `tools.read` | View tool analytics | | `templates.read` | List and view templates | | `templates.instantiate` | Create plans from templates | --- ## Dependencies - Flux UI components (cards, tables, charts) - Chart library for analytics (ApexCharts or Chart.js via Livewire) - Real-time updates (Livewire polling or Laravel Echo) - Existing agent models (AgentPlan, AgentPhase, AgentSession) - Existing MCP models (McpToolCall, McpToolCallStat) --- ## Integration with Existing Infrastructure ### MCP Registry The existing MCP registry at `mcp.host.uk.com` provides agent discovery. The new API endpoints extend this with: - Plan management (create, track, complete) - Session logging (work log, artifacts, handoff) - State persistence (shared workspace state) ### Entitlement Gating Future: Add entitlement features for agent access: - `agents.plans.create` — Can create agent plans - `agents.sessions.concurrent` — Max concurrent sessions - `agents.tools.calls` — Monthly tool call limit - `agents.api_keys` — Max API keys per workspace ### External Agent Flow ``` External Agent (Claude Code, Cursor, etc.) │ ├─ Discovers Host Hub via mcp.host.uk.com │ ├─ Authenticates with API key │ ├─ Creates plan from template │ POST /api/v1/agents/plans │ ├─ Starts session │ POST /api/v1/agents/sessions │ ├─ Logs work progress │ PATCH /api/v1/agents/sessions/{id} │ ├─ Completes tasks │ PATCH /api/v1/agents/plans/{slug}/phases/{id}/tasks/{name} │ └─ Completes session with handoff notes POST /api/v1/agents/sessions/{id}/complete ``` --- ## Notes ### Why API Keys (Not OAuth) For agent-to-agent communication: - OAuth is designed for human authorization flows - API keys are simpler for programmatic access - Keys can be scoped per workspace and permission - Rate limiting is straightforward - Revocation is immediate ### Real-Time Considerations For session monitoring: - Start with Livewire polling (every 5 seconds) - Upgrade to WebSocket (Laravel Echo) if needed - Consider SSE for one-way updates (server → browser) ### Multi-System Cooperation The API enables: 1. **Claude Code** creates plan for Host Hub work 2. **Cursor** picks up session for code changes 3. **Host Hub AI** generates content as directed 4. **Human** reviews in admin UI, approves or redirects Each system maintains its own context but shares state via the plan. --- ## Verification Results ### Phase 1 Verification (2026-01-02 19:30) **Verification Agent:** Claude Opus 4.5 **Verdict:** ✅ PASS - All Phase 1 acceptance criteria met #### AC1: Route `hub.agents.plans` shows all AgentPlans (filterable by workspace, status) ✅ **Method:** Code inspection of `routes/web.php` and `app/Livewire/Admin/Agent/Plans.php` **Evidence - Routes (lines 180-185):** ```php Route::prefix('agents')->name('agents.')->group(function () { Route::get('/', \App\Livewire\Admin\Agent\Dashboard::class)->name('index'); Route::get('/plans', \App\Livewire\Admin\Agent\Plans::class)->name('plans'); Route::get('/plans/{slug}', \App\Livewire\Admin\Agent\PlanDetail::class)->name('plans.show'); }); ``` **Evidence - Plans.php (lines 39-61):** ```php public function plans(): LengthAwarePaginator { $query = AgentPlan::with(['workspace', 'agentPhases']) ->withCount('sessions'); if ($this->search) { ... } if ($this->status) { $query->where('status', $this->status); } if ($this->workspace) { $query->where('workspace_id', $this->workspace); } return $query->latest('updated_at')->paginate($this->perPage); } ``` **Filters confirmed:** Search (title/slug/description), Status (draft/active/completed/archived), Workspace ✅ #### AC2: Plan list shows: title, workspace, status, phase progress, last activity ✅ **Method:** Code inspection of `resources/views/admin/livewire/agent/plans.blade.php` **Evidence - Table headers (lines 44-53):** ```blade Plan Workspace Status Progress Sessions Last Activity ``` **All required columns present:** ✅ - Title (line 64): `{{ $plan->title }}` - Workspace (line 69): `{{ $plan->workspace?->name ?? 'N/A' }}` - Status (lines 71-86): Coloured badge with ucfirst status - Progress (lines 88-96): Visual bar + percentage + phases count - Last Activity (lines 100-102): `{{ $plan->updated_at->diffForHumans() }}` #### AC3: Plan detail view shows all phases with task checklists ✅ **Method:** Code inspection of `resources/views/admin/livewire/agent/plan-detail.blade.php` **Evidence - Phases section (lines 66-183):** ```blade Phases @foreach($this->phases as $phase) @if($phase->tasks && count($phase->tasks) > 0)
@foreach($phase->tasks as $index => $task) @endforeach
@endif @endforeach
``` **Task checklist features:** - Checkbox toggle for completing tasks (line 143-156) - Completed tasks show strikethrough (line 158) - Optional task notes displayed (lines 159-161) - "Add Task" modal (lines 248-274) ✅ #### AC4: Phase progress displayed as visual percentage bar ✅ **Method:** Code inspection of views **Evidence - Plan-level progress (lines 29-56):** ```blade
``` **Evidence - Phase-level progress (lines 96-102):** ```blade @if($taskProgress['total'] > 0)
{{ $taskProgress['completed'] }}/{{ $taskProgress['total'] }} @endif ``` **Progress bars present at:** - Plan list (each row has progress bar + percentage) - Plan detail (overview card with large progress bar) - Each phase (task completion progress) ✅ #### AC5: Can manually update plan status (activate, complete, archive) ✅ **Method:** Code inspection of `Plans.php` and `PlanDetail.php` **Evidence - Plans.php (lines 103-122):** ```php public function activate(int $planId): void { $plan = AgentPlan::findOrFail($planId); $plan->activate(); } public function complete(int $planId): void { $plan = AgentPlan::findOrFail($planId); $plan->complete(); } public function archive(int $planId): void { $plan = AgentPlan::findOrFail($planId); $plan->archive('Archived via admin UI'); } ``` **Evidence - PlanDetail.php (lines 55-72):** ```php public function activatePlan(): void { $this->plan->activate(); } public function completePlan(): void { $this->plan->complete(); } public function archivePlan(): void { $this->plan->archive('Archived via admin UI'); } ``` **UI buttons confirmed:** - Plans list: Dropdown menu with Activate/Complete/Archive/Delete (lines 108-122) - Plan detail: Header buttons for Activate/Complete/Archive (lines 17-26) ✅ #### AC6: Can manually update phase status (start, complete, block, skip) ✅ **Method:** Code inspection of `PlanDetail.php` **Evidence - Phase actions (lines 75-120):** ```php public function startPhase(int $phaseId): void { $phase = AgentPhase::findOrFail($phaseId); if (! $phase->canStart()) { return; } $phase->start(); } public function completePhase(int $phaseId): void { $phase = AgentPhase::findOrFail($phaseId); $phase->complete(); } public function blockPhase(int $phaseId): void { $phase = AgentPhase::findOrFail($phaseId); $phase->block('Blocked via admin UI'); } public function skipPhase(int $phaseId): void { $phase = AgentPhase::findOrFail($phaseId); $phase->skip('Skipped via admin UI'); } public function resetPhase(int $phaseId): void { $phase = AgentPhase::findOrFail($phaseId); $phase->reset(); } ``` **Evidence - View dropdown (lines 106-128):** ```blade @if($phase->isPending()) Start Phase @endif @if($phase->isInProgress()) Complete Phase Block Phase @endif @if($phase->isBlocked()) Unblock (Reset) @endif @if(!$phase->isCompleted() && !$phase->isSkipped()) Skip Phase @endif @if($phase->isCompleted() || $phase->isSkipped()) Reset to Pending @endif ``` **All phase status actions available:** Start, Complete, Block, Skip, Reset ✅ ### Phase 1 Summary | AC | Description | Evidence | Status | |----|-------------|----------|--------| | AC1 | Route exists with filters | `routes/web.php:184`, `Plans.php` query builder | ✅ PASS | | AC2 | Plan list columns | `plans.blade.php` table with all 6 columns | ✅ PASS | | AC3 | Phases with task checklists | `plan-detail.blade.php:132-165` task list with checkboxes | ✅ PASS | | AC4 | Visual progress bars | Plan list + detail + per-phase progress | ✅ PASS | | AC5 | Plan status updates | `activate()`, `complete()`, `archive()` methods | ✅ PASS | | AC6 | Phase status updates | `startPhase()`, `completePhase()`, `blockPhase()`, `skipPhase()`, `resetPhase()` | ✅ PASS | **Additional Implementation Verified:** - Hades access control in all components (`checkHadesAccess()` in mount) - Sidebar integration (lines 520-576 in sidebar.blade.php) - Dashboard with stats and recent activity (`Dashboard.php`) - Task management (add task modal, complete task) - Session list in plan detail view **Phase 1 Status:** ✅ VERIFIED — Ready for human approval --- ### Phase 2 Verification (2026-01-02 20:00) **Verification Agent:** Claude Opus 4.5 **Verdict:** ✅ PASS - All Phase 2 acceptance criteria met #### AC7: Route `hub.agents.sessions` shows all AgentSessions ✅ **Method:** Code inspection of `routes/web.php` and `app/Livewire/Admin/Agent/Sessions.php` **Evidence - Routes (lines 187-188):** ```php Route::get('/sessions', \App\Livewire\Admin\Agent\Sessions::class)->name('sessions'); Route::get('/sessions/{id}', \App\Livewire\Admin\Agent\SessionDetail::class)->name('sessions.show'); ``` **Evidence - Sessions.php (lines 46-76):** ```php public function sessions(): LengthAwarePaginator { $query = AgentSession::with(['workspace', 'plan']); if ($this->search) { ... } if ($this->status) { $query->where('status', $this->status); } if ($this->agentType) { $query->where('agent_type', $this->agentType); } if ($this->workspace) { $query->where('workspace_id', $this->workspace); } if ($this->planSlug) { $query->whereHas('plan', ...); } return $query->latest('last_active_at')->paginate($this->perPage); } ``` **Filters confirmed:** Search, Status, Agent Type, Workspace, Plan ✅ #### AC8: Active sessions show real-time activity (polling or WebSocket) ✅ **Method:** Code inspection of `SessionDetail.php` and `session-detail.blade.php` **Evidence - Polling implementation (SessionDetail.php lines 27-41, 104-114):** ```php public int $pollingInterval = 5000; public function mount(int $id): void { // Disable polling for completed/failed sessions if ($this->session->isEnded()) { $this->pollingInterval = 0; } } public function poll(): void { $this->session->refresh(); if ($this->session->isEnded()) { $this->pollingInterval = 0; } } ``` **Evidence - View polling directive (session-detail.blade.php line 1):** ```blade
``` **Polling behaviour:** - 5-second interval for active sessions - Automatically disabled when session ends - Refreshes session data on each poll ✅ #### AC9: Session detail shows: work log, artifacts, handoff notes ✅ **Method:** Code inspection of `session-detail.blade.php` **Evidence - Work Log (lines 143-188):** ```blade
Work Log
@foreach($this->recentWorkLog as $entry) @endforeach
``` **Evidence - Artifacts (lines 228-257):** ```blade Artifacts @foreach($this->artifacts as $artifact) @endforeach ``` **Evidence - Handoff Notes (lines 259-296):** ```blade Handoff Notes ``` **All three sections present with proper data display** ✅ #### AC10: Can view session context summary and final summary ✅ **Method:** Code inspection of `SessionDetail.php` and `session-detail.blade.php` **Evidence - Context Summary computed property (lines 69-73):** ```php #[Computed] public function contextSummary(): ?array { return $this->session->context_summary; } ``` **Evidence - Context Summary view (lines 110-141):** ```blade @if($this->contextSummary) Context Summary @endif ``` **Evidence - Final Summary view (lines 190-200):** ```blade @if($session->final_summary) Final Summary {{ $session->final_summary }} @endif ``` **Both summary types displayed when present** ✅ #### AC11: Timeline view shows session sequence within a plan ✅ **Method:** Code inspection of `SessionDetail.php` and `session-detail.blade.php` **Evidence - Plan Sessions computed property (lines 75-85):** ```php #[Computed] public function planSessions(): Collection { if (! $this->session->agent_plan_id) { return collect(); } return AgentSession::where('agent_plan_id', $this->session->agent_plan_id) ->orderBy('started_at') ->get(); } ``` **Evidence - Session Index (lines 87-102):** ```php #[Computed] public function sessionIndex(): int { // Returns 1-based index of current session in plan } ``` **Evidence - Timeline view (lines 71-105):** ```blade @if($session->agent_plan_id && $this->planSessions->count() > 1) Plan Timeline (Session {{ $this->sessionIndex }} of {{ $this->planSessions->count() }})
@foreach($this->planSessions as $index => $planSession) @endforeach
@endif ``` **Timeline shows:** - Session number in sequence (1 of N) - All sessions ordered by start time - Current session highlighted - Click to navigate to other sessions - Status indicators (active pulse, completed/failed colours) ✅ #### AC12: Can pause/resume/fail active sessions manually ✅ **Method:** Code inspection of `Sessions.php`, `SessionDetail.php`, and views **Evidence - Sessions.php list actions (lines 127-153):** ```php public function pause(int $sessionId): void { $session->pause(); } public function resume(int $sessionId): void { $session->resume(); } public function complete(int $sessionId): void { $session->complete('...'); } public function fail(int $sessionId): void { $session->fail('...'); } ``` **Evidence - SessionDetail.php actions (lines 117-156):** ```php public function pauseSession(): void { $this->session->pause(); } public function resumeSession(): void { $this->session->resume(); } public function completeSession(): void { $this->session->complete($this->completeSummary); } public function failSession(): void { $this->session->fail($this->failReason); } ``` **Evidence - Sessions list dropdown (sessions.blade.php lines 143-157):** ```blade @if($session->isActive()) Pause @endif @if($session->isPaused()) Resume @endif Complete Fail ``` **Evidence - Session detail buttons (session-detail.blade.php lines 28-39):** ```blade @if($session->isActive()) Pause @elseif($session->isPaused()) Resume @endif @if(!$session->isEnded()) Complete Fail @endif ``` **Complete/Fail modals (lines 300-324):** - Complete modal with optional summary - Fail modal with reason input ✅ ### Phase 2 Summary | AC | Description | Evidence | Status | |----|-------------|----------|--------| | AC7 | Sessions route with filters | `routes/web.php:187-188`, `Sessions.php` query builder | ✅ PASS | | AC8 | Real-time polling | `wire:poll.5000ms`, disabled when session ends | ✅ PASS | | AC9 | Work log, artifacts, handoff notes | Three dedicated sections in `session-detail.blade.php` | ✅ PASS | | AC10 | Context and final summary | Computed properties + conditional display | ✅ PASS | | AC11 | Timeline view | Horizontal timeline with session sequence navigation | ✅ PASS | | AC12 | Manual session controls | Pause/Resume/Complete/Fail with modals | ✅ PASS | **Additional Implementation Verified:** - Hades access control in both components - Sidebar link at line 558 (`hub.agents.sessions`) - Dashboard quick link at line 160 - Active session count indicator with animated pulse - Agent type badges (Opus/Sonnet/Haiku) - Status colour coding **Phase 2 Status:** ✅ VERIFIED — Ready for human approval --- ### Phase 3 Verification (2026-01-02 21:45) **Verification Agent:** Claude Opus 4.5 **Verdict:** ✅ PASS - All Phase 3 acceptance criteria met #### AC13: Route `hub.agents.tools` shows MCP tool usage dashboard ✅ **Method:** Code inspection of `routes/web.php` and `app/Livewire/Admin/Agent/ToolAnalytics.php` **Evidence - Routes (lines 189-191):** ```php / Phase 3: Tool Analytics Route::get('/tools', \App\Livewire\Admin\Agent\ToolAnalytics::class)->name('tools'); Route::get('/tools/calls', \App\Livewire\Admin\Agent\ToolCalls::class)->name('tools.calls'); ``` **Evidence - ToolAnalytics.php (lines 17-18, 29-32):** ```php #[Title('Tool Analytics')] class ToolAnalytics extends Component { public function mount(): void { $this->checkHadesAccess(); } } ``` **Dashboard features:** - Stats cards: Total calls, Successful, Errors, Success rate, Unique tools (lines 51-111 in view) - Filters: Days (7/14/30/90), Workspace, Server (lines 15-48 in view) - Hades access control enforced ✅ #### AC14: Top 10 tools by usage displayed ✅ **Method:** Code inspection of `ToolAnalytics.php` and `tool-analytics.blade.php` **Evidence - ToolAnalytics.php (lines 77-88):** ```php #[Computed] public function topTools(): Collection { $workspaceId = $this->workspace ? (int) $this->workspace : null; $tools = McpToolCallStat::getTopTools($this->days, 10, $workspaceId); if ($this->server) { $tools = $tools->filter(fn ($t) => $t->server_id === $this->server)->values(); } return $tools->take(10); } ``` **Evidence - View (lines 168-236):** ```blade Top 10 Tools @foreach($this->topTools as $tool) @endforeach
ToolServerCallsSuccess RateErrorsAvg Duration
``` **Top 10 tools displayed with:** - Tool name, Server ID - Total calls count - Success rate (colour-coded) - Error count - Average duration - Drill-down button ✅ #### AC15: Daily trend chart (calls per day, 30-day window) ✅ **Method:** Code inspection of `ToolAnalytics.php` and `tool-analytics.blade.php` **Evidence - ToolAnalytics.php (lines 90-96, 131-142):** ```php #[Computed] public function dailyTrend(): Collection { $workspaceId = $this->workspace ? (int) $this->workspace : null; return McpToolCallStat::getDailyTrend($this->days, $workspaceId); } #[Computed] public function chartData(): array { $trend = $this->dailyTrend; return [ 'labels' => $trend->pluck('date')->map(fn ($d) => $d->format('M j'))->toArray(), 'calls' => $trend->pluck('total_calls')->toArray(), 'errors' => $trend->pluck('total_errors')->toArray(), 'success_rates' => $trend->pluck('success_rate')->toArray(), ]; } ``` **Evidence - Chart.js integration (lines 272-346):** ```blade ``` **Chart features:** - Line chart with Chart.js - Configurable window: 7/14/30/90 days (view line 19-24) - Shows Total Calls (violet) and Errors (red) - Dark mode support - Responsive design ✅ #### AC16: Server breakdown shows usage by MCP server ✅ **Method:** Code inspection of `ToolAnalytics.php` and `tool-analytics.blade.php` **Evidence - ToolAnalytics.php (lines 98-109):** ```php #[Computed] public function serverStats(): Collection { $workspaceId = $this->workspace ? (int) $this->workspace : null; $stats = McpToolCallStat::getServerStats($this->days, $workspaceId); if ($this->server) { $stats = $stats->filter(fn ($s) => $s->server_id === $this->server)->values(); } return $stats; } ``` **Evidence - View (lines 132-165):** ```blade Server Breakdown @foreach($this->serverStats as $serverStat)
{{ $serverStat->server_id }} {{ number_format($serverStat->total_calls) }} calls
{{ $serverStat->unique_tools }} tools {{ $serverStat->success_rate }}% success
@endforeach ``` **Server breakdown shows:** - Server ID - Total call count - Visual progress bar (relative to max) - Unique tools count - Success rate (colour-coded) ✅ #### AC17: Error rate and average duration displayed per tool ✅ **Method:** Code inspection of `tool-analytics.blade.php` **Evidence - Top Tools table columns (lines 183-218):** ```blade Success Rate Errors Avg Duration ... {{ $tool->success_rate }}% @if($tool->total_errors > 0) {{ number_format($tool->total_errors) }} @else 0 @endif @if($tool->avg_duration) {{ round($tool->avg_duration) < 1000 ? round($tool->avg_duration) . 'ms' : round($tool->avg_duration / 1000, 2) . 's' }} @endif ``` **Error/duration display features:** - Success rate with colour coding (green ≥95%, amber ≥80%, red <80%) - Error count highlighted in red when >0 - Duration formatted as ms or seconds - Recent Errors section (lines 238-269) shows last 10 failures ✅ #### AC18: Can drill down to individual tool calls with full params ✅ **Method:** Code inspection of `ToolCalls.php` and `tool-calls.blade.php` **Evidence - ToolCalls.php (lines 133-151):** ```php #[Computed] public function selectedCall(): ?McpToolCall { if (! $this->selectedCallId) { return null; } return McpToolCall::with('workspace')->find($this->selectedCallId); } public function viewCall(int $id): void { $this->selectedCallId = $id; } public function closeCallDetail(): void { $this->selectedCallId = null; } ``` **Evidence - View modal (lines 155-244):** ```blade @if($selectedCall) {{ $selectedCall->tool_name }} {{-- Input Parameters --}} @if($selectedCall->input_params && count($selectedCall->input_params) > 0)
{{ json_encode($selectedCall->input_params, JSON_PRETTY_PRINT) }}
@endif {{-- Error Details --}} @if(!$selectedCall->success)
Code: {{ $selectedCall->error_code }} {{ $selectedCall->error_message }}
@endif {{-- Result Summary --}} @if($selectedCall->result_summary && count($selectedCall->result_summary) > 0)
{{ json_encode($selectedCall->result_summary, JSON_PRETTY_PRINT) }}
@endif
@endif ``` **Drill-down features:** - Modal with full tool call details - Input parameters (JSON pretty-printed) - Result summary (JSON pretty-printed) - Error details (code + message) for failed calls - Metadata: duration, agent type, workspace, timestamp - Session and Plan links for traceability - Filters: server, tool, status, workspace, agent type - Pagination with 25 per page ✅ ### Phase 3 Summary | AC | Description | Evidence | Status | |----|-------------|----------|--------| | AC13 | Tool usage dashboard route | `routes/web.php:190`, `ToolAnalytics.php` with Hades auth | ✅ PASS | | AC14 | Top 10 tools by usage | `topTools()` computed, table with 6 columns | ✅ PASS | | AC15 | Daily trend chart | Chart.js line chart, configurable 7-90 days | ✅ PASS | | AC16 | Server breakdown | `serverStats()` computed, progress bars, success rates | ✅ PASS | | AC17 | Error rate and duration | Table columns + colour-coded success rate | ✅ PASS | | AC18 | Drill-down to tool calls | Modal with input_params, result_summary, error_details | ✅ PASS | **Additional Implementation Verified:** - Hades access control in both components - Sidebar link at `hub.agents.tools` - Dashboard quick link to Tool Analytics - Drill-down links from Top Tools table - Recent Errors section in dashboard - Comprehensive filtering in ToolCalls - Pagination with proper links **Phase 3 Status:** ✅ VERIFIED — Ready for human approval --- ### Phase 3 Implementation Notes (2026-01-02 21:30) **Implementation Agent:** Claude Opus 4.5 **Routes Added (`routes/web.php`):** - `hub.agents.tools` → ToolAnalytics dashboard - `hub.agents.tools.calls` → ToolCalls drill-down list **Livewire Components Created:** - `app/Livewire/Admin/Agent/ToolAnalytics.php` - Dashboard with stats, charts, filters - `app/Livewire/Admin/Agent/ToolCalls.php` - Paginated call list with modal detail **Views Created:** - `resources/views/admin/livewire/agent/tool-analytics.blade.php` - `resources/views/admin/livewire/agent/tool-calls.blade.php` **Phase 5:** Not yet implemented (commented out in routes) --- ### Phase 4 Implementation Notes (2026-01-02 22:30) **Implementation Agent:** Claude Opus 4.5 **Route Added (`routes/web.php`):** - `hub.agents.api-keys` → ApiKeys management **Model Created:** - `app/Models/Agent/AgentApiKey.php` - Key generation with `ak_` prefix + 32 random chars - SHA-256 hashing for storage - Permission constants: `PERM_PLANS_READ`, `PERM_PLANS_WRITE`, `PERM_PHASES_WRITE`, `PERM_SESSIONS_READ`, `PERM_SESSIONS_WRITE`, `PERM_TOOLS_READ`, `PERM_TEMPLATES_READ`, `PERM_TEMPLATES_INSTANTIATE` - Status helpers: `isActive()`, `isRevoked()`, `isExpired()` - Scopes: `active()`, `revoked()`, `expired()`, `forWorkspace()` **Migration Created:** - `database/migrations/2026_01_02_220000_create_agent_api_keys_table.php` - Creates `agent_api_keys` table with key, permissions (JSON), rate_limit, call_count, timestamps - Adds `agent_api_key_id` foreign key to `agent_sessions` and `mcp_tool_calls` **Service Created:** - `app/Services/Agent/AgentApiKeyService.php` - `create()` — Generate new key with permissions and rate limit - `validate()` — Validate plaintext key and return model - `checkPermission()`, `checkPermissions()` — Verify access - `recordUsage()` — Increment call count and cache for rate limiting - `isRateLimited()`, `getRateLimitStatus()` — Rate limiting helpers - `authenticate()` — Full auth flow returning structured result - `revoke()`, `updatePermissions()`, `updateRateLimit()` **Livewire Component Created:** - `app/Livewire/Admin/Agent/ApiKeys.php` - Stats: total, active, revoked, total calls - Filters: workspace, status - Create modal: name, workspace, permissions, rate limit, expiry - Created key modal: shows plaintext key ONCE - Edit modal: update permissions and rate limit - Revoke action with immediate effect **View Created:** - `resources/views/admin/livewire/agent/api-keys.blade.php` **Sidebar Updated:** - Added API Keys link to Agents submenu **Phase 4 Status:** ✅ COMPLETE — Ready for verification --- ### Phase 4 Verification (2026-01-02 23:15) **Verification Agent:** Claude Opus 4.5 **Verdict:** ✅ PASS - All Phase 4 acceptance criteria met #### AC19: Route `hub.agents.api-keys` manages agent API access ✅ **Method:** Code inspection of `routes/web.php` and `app/Livewire/Admin/Agent/ApiKeys.php` **Evidence - Route (routes/web.php line 193):** ```php Route::get('/api-keys', \App\Livewire\Admin\Agent\ApiKeys::class)->name('api-keys'); ``` **Evidence - Sidebar link (sidebar.blade.php line 568):** ```blade API Keys ``` **Evidence - Hades access control (ApiKeys.php lines 49-55):** ```php public function mount(): void { $this->checkHadesAccess(); } private function checkHadesAccess(): void { if (! auth()->user()?->isHades()) { abort(403, 'Hades access required'); } } ``` **Route exists and is accessible only to Hades users** ✅ #### AC20: Can create API keys scoped to workspace ✅ **Method:** Code inspection of `ApiKeys.php` and `api-keys.blade.php` **Evidence - ApiKeys.php createKey() (lines 89-116):** ```php public function createKey(): void { $this->validate([ 'newKeyName' => 'required|string|max:255', 'newKeyWorkspace' => 'required|exists:workspaces,id', 'newKeyPermissions' => 'array', 'newKeyRateLimit' => 'required|integer|min:1|max:10000', ]); $key = $this->apiKeyService->create( workspace: (int) $this->newKeyWorkspace, name: $this->newKeyName, permissions: $this->newKeyPermissions, rateLimit: $this->newKeyRateLimit, expiresAt: $this->newKeyExpiry ? Carbon::parse($this->newKeyExpiry) : null ); $this->createdKey = $key->plainTextKey; // ... } ``` **Evidence - View workspace dropdown (api-keys.blade.php lines 211-221):** ```blade Select workspace... @foreach($this->workspaces as $ws) {{ $ws->name }} @endforeach ``` **Evidence - AgentApiKey model (lines 113-118):** ```php public function scopeForWorkspace($query, Workspace|int $workspace) { $workspaceId = $workspace instanceof Workspace ? $workspace->id : $workspace; return $query->where('workspace_id', $workspaceId); } ``` **Keys are scoped to workspace via foreign key and workspace selector** ✅ #### AC21: Keys have configurable permissions (read plans, write plans, execute tools) ✅ **Method:** Code inspection of `AgentApiKey.php` and view **Evidence - Permission constants (AgentApiKey.php lines 63-78):** ```php public const PERM_PLANS_READ = 'plans.read'; public const PERM_PLANS_WRITE = 'plans.write'; public const PERM_PHASES_WRITE = 'phases.write'; public const PERM_SESSIONS_READ = 'sessions.read'; public const PERM_SESSIONS_WRITE = 'sessions.write'; public const PERM_TOOLS_READ = 'tools.read'; public const PERM_TEMPLATES_READ = 'templates.read'; public const PERM_TEMPLATES_INSTANTIATE = 'templates.instantiate'; ``` **Evidence - availablePermissions() (lines 83-95):** ```php public static function availablePermissions(): array { return [ self::PERM_PLANS_READ => 'List and view plans', self::PERM_PLANS_WRITE => 'Create, update, archive plans', self::PERM_PHASES_WRITE => 'Update phase status, add/complete tasks', self::PERM_SESSIONS_READ => 'List and view sessions', self::PERM_SESSIONS_WRITE => 'Start, update, complete sessions', self::PERM_TOOLS_READ => 'View tool analytics', self::PERM_TEMPLATES_READ => 'List and view templates', self::PERM_TEMPLATES_INSTANTIATE => 'Create plans from templates', ]; } ``` **Evidence - View permission checkboxes (api-keys.blade.php lines 227-237):** ```blade
@foreach(\Mod\Agentic\Models\AgentApiKey::availablePermissions() as $perm => $description) @endforeach
``` **Evidence - Permission helpers (AgentApiKey.php lines 195-220):** ```php public function hasPermission(string $permission): bool public function hasAnyPermission(array $permissions): bool public function hasAllPermissions(array $permissions): bool ``` **8 configurable permissions with checkboxes in create/edit modals** ✅ #### AC22: Key usage tracking (last used, call count) ✅ **Method:** Code inspection of model, service, and view **Evidence - Migration fields (migration lines 24-25):** ```php $table->unsignedBigInteger('call_count')->default(0); $table->timestamp('last_used_at')->nullable(); ``` **Evidence - AgentApiKey recordUsage() (lines 230-236):** ```php public function recordUsage(): self { $this->increment('call_count'); $this->update(['last_used_at' => now()]); return $this; } ``` **Evidence - AgentApiKeyService recordUsage() (lines 79-91):** ```php public function recordUsage(AgentApiKey $key): void { $key->recordUsage(); // Increment rate limit counter in cache $cacheKey = $this->getRateLimitCacheKey($key); Cache::increment($cacheKey); // ... } ``` **Evidence - View table columns (api-keys.blade.php lines 106-118):** ```blade {{ number_format($key->call_count) }} {{ $key->getLastUsedForHumans() }} ``` **Evidence - Stats card (api-keys.blade.php lines 54-59):** ```blade {{ number_format($this->stats['total_calls']) }} Total Calls ``` **Usage tracked via call_count + last_used_at, displayed in table and stats** ✅ #### AC23: Can revoke keys immediately ✅ **Method:** Code inspection of service, model, and Livewire component **Evidence - AgentApiKey revoke() (lines 223-228):** ```php public function revoke(): self { $this->update(['revoked_at' => now()]); return $this; } ``` **Evidence - AgentApiKeyService revoke() (lines 129-135):** ```php public function revoke(AgentApiKey $key): void { $key->revoke(); // Clear rate limit cache Cache::forget($this->getRateLimitCacheKey($key)); } ``` **Evidence - ApiKeys.php revokeKey() (lines 118-128):** ```php public function revokeKey(int $keyId): void { $key = AgentApiKey::findOrFail($keyId); $this->apiKeyService->revoke($key); Flux::toast( heading: 'API Key Revoked', text: "Key '{$key->name}' has been revoked and can no longer be used.", variant: 'warning', ); } ``` **Evidence - View revoke button (api-keys.blade.php line 133):** ```blade Revoke Key ``` **Evidence - isRevoked helper (AgentApiKey.php lines 184-187):** ```php public function isRevoked(): bool { return $this->revoked_at !== null; } ``` **Revocation sets revoked_at timestamp, clears cache, shows confirmation** ✅ #### AC24: Rate limiting configuration per key ✅ **Method:** Code inspection of model, service, and view **Evidence - Migration field (line 23):** ```php $table->integer('rate_limit')->default(100); // Calls per minute ``` **Evidence - AgentApiKeyService rate limiting (lines 96-124):** ```php public function isRateLimited(AgentApiKey $key): bool { $cacheKey = $this->getRateLimitCacheKey($key); $currentCalls = (int) Cache::get($cacheKey, 0); return $currentCalls >= $key->rate_limit; } public function getRateLimitStatus(AgentApiKey $key): array { $cacheKey = $this->getRateLimitCacheKey($key); $currentCalls = (int) Cache::get($cacheKey, 0); $remaining = max(0, $key->rate_limit - $currentCalls); // ... return [ 'limit' => $key->rate_limit, 'remaining' => $remaining, 'reset_in_seconds' => max(0, $ttl), 'used' => $currentCalls, ]; } ``` **Evidence - authenticate() rate limiting check (lines 253-262):** ```php if ($this->isRateLimited($key)) { $status = $this->getRateLimitStatus($key); return [ 'success' => false, 'error' => 'rate_limited', 'message' => 'Rate limit exceeded', 'rate_limit' => $status, ]; } ``` **Evidence - View rate limit input (api-keys.blade.php lines 239-241):** ```blade ``` **Evidence - Table column (api-keys.blade.php line 102):** ```blade {{ $key->rate_limit }}/min ``` **Evidence - Edit modal rate limit (api-keys.blade.php lines 334-336):** ```blade ``` **Rate limit configurable 1-10000 calls/min, enforced via Cache with 60s window** ✅ ### Phase 4 Summary | AC | Description | Evidence | Status | |----|-------------|----------|--------| | AC19 | Route manages API access | `routes/web.php:193`, Hades auth, sidebar link | ✅ PASS | | AC20 | Keys scoped to workspace | `workspace_id` FK, workspace dropdown in create modal | ✅ PASS | | AC21 | Configurable permissions | 8 permissions with checkboxes, `hasPermission()` helpers | ✅ PASS | | AC22 | Usage tracking | `call_count` + `last_used_at` fields, displayed in UI | ✅ PASS | | AC23 | Immediate revocation | `revoke()` sets timestamp, clears cache, confirmation dialog | ✅ PASS | | AC24 | Rate limiting per key | Configurable 1-10000/min, Cache-based enforcement | ✅ PASS | **Additional Implementation Verified:** - Hades access control in component - Sidebar link at line 568 - Stats cards: total, active, revoked, total calls - Create modal shows plaintext key ONCE - Edit modal for permissions and rate limit updates - Key expiry support (optional) - Filter by workspace and status (active/revoked/expired) - Foreign keys added to `agent_sessions` and `mcp_tool_calls` for tracking **Phase 4 Status:** ✅ VERIFIED — Ready for human approval --- ### Phase 5 Implementation Notes (2026-01-02 23:45) **Implementation Agent:** Claude Opus 4.5 **Route Added (`routes/web.php`):** - `hub.agents.templates` → Templates browser **Livewire Component Created:** - `app/Livewire/Admin/Agent/Templates.php` - Uses existing `PlanTemplateService` for all template operations - Template listing with category filter and search - Preview modal showing phases, tasks, variables, guidelines - Create plan modal with variable inputs and workspace selection - YAML import modal with file upload, validation, and preview - Delete template functionality **View Created:** - `resources/views/admin/livewire/agent/templates.blade.php` - Stats cards: total templates, categories, total phases, with variables - Grid layout for template cards - Category colour coding - Preview modal with full template details - Create modal with variable input fields - Import modal with drag-drop file upload **Sidebar Updated:** - Added Templates link to Agents submenu **Dashboard Updated:** - Templates quick link now active (was "Coming soon") **Existing Templates (5):** - `bug-fix.yaml` - Bug fix workflow - `code-review.yaml` - Code review process - `feature-port.yaml` - Feature porting - `new-feature.yaml` - New feature development - `refactor.yaml` - Code refactoring **Phase 5 Status:** ✅ COMPLETE — Ready for verification --- ### Phase 5 Verification (2026-01-02 23:45) **Verification Agent:** Claude Opus 4.5 **Verdict:** ✅ PASS - All Phase 5 acceptance criteria met #### AC25: Route `hub.agents.templates` lists available plan templates ✅ **Method:** Code inspection of `routes/web.php` and `app/Livewire/Admin/Agent/Templates.php` **Evidence - Route (routes/web.php line 195):** ```php Route::get('/templates', \App\Livewire\Admin\Agent\Templates::class)->name('templates'); ``` **Evidence - Sidebar link (sidebar.blade.php lines 573-574):** ```blade Templates ``` **Evidence - Templates.php templates() computed property (lines 76-93):** ```php #[Computed] public function templates(): Collection { $templates = $this->templateService->list(); if ($this->category) { $templates = $templates->filter(fn ($t) => $t['category'] === $this->category); } if ($this->search) { $search = strtolower($this->search); $templates = $templates->filter(fn ($t) => ...); } return $templates->values(); } ``` **Evidence - View grid (templates.blade.php lines 60-149):** ```blade
@foreach($this->templates as $template) @endforeach
``` **5 templates available:** bug-fix, code-review, feature-port, new-feature, refactor ✅ #### AC26: Template preview shows phases and variables ✅ **Method:** Code inspection of `Templates.php` and `templates.blade.php` **Evidence - previewTemplate() computed property (Templates.php lines 107-115):** ```php #[Computed] public function previewTemplate(): ?array { if (! $this->previewSlug) { return null; } return $this->templateService->previewTemplate($this->previewSlug, []); } ``` **Evidence - Preview modal phases (templates.blade.php lines 207-235):** ```blade Phases ({{ count($this->previewTemplate['phases']) }})
@foreach($this->previewTemplate['phases'] as $index => $phase)
{{ $phase['order'] }} {{ $phase['name'] }} @if(!empty($phase['tasks']))
    @foreach($phase['tasks'] as $task)
  • {{ is_array($task) ? $task['name'] : $task }}
  • @endforeach
@endif
@endforeach
``` **Evidence - Preview modal variables table (templates.blade.php lines 237-274):** ```blade @if(!empty($variables)) Variables @foreach($variables as $name => $config) @endforeach
VariableDescriptionDefaultRequired
{{ $name }} {{ $config['description'] ?? '-' }} {{ $config['default'] ?? '-' }} {{ $config['required'] ? 'Yes' : 'No' }}
@endif ``` **Preview shows:** Phases with order/name/tasks, Variables with name/description/default/required, Guidelines ✅ #### AC27: Can create new plan from template with variable input ✅ **Method:** Code inspection of `Templates.php` and `templates.blade.php` **Evidence - openCreateModal() (Templates.php lines 162-188):** ```php public function openCreateModal(string $slug): void { $template = $this->templateService->get($slug); $this->createTemplateSlug = $slug; $this->createTitle = $template['name']; $this->createWorkspaceId = $this->workspaces->first()?->id ?? 0; // Initialise variables with defaults $this->createVariables = []; foreach ($template['variables'] ?? [] as $name => $config) { $this->createVariables[$name] = $config['default'] ?? ''; } $this->showCreateModal = true; } ``` **Evidence - createPlan() (Templates.php lines 198-272):** ```php public function createPlan(): void { // Validate required variables $this->validate([ 'createWorkspaceId' => 'required|exists:workspaces,id', 'createTitle' => 'required|string|max:255', ]); // Add variable validation foreach ($template['variables'] ?? [] as $name => $config) { if ($config['required'] ?? false) { $rules["createVariables.{$name}"] = 'required|string'; } } // Create the plan via service $plan = $this->templateService->createPlan( $this->createTemplateSlug, $this->createVariables, ['title' => $this->createTitle, 'activate' => $this->createActivate], $workspace ); // Redirect to new plan $this->redirect(route('hub.agents.plans.show', $plan->slug), navigate: true); } ``` **Evidence - Create modal with variable inputs (templates.blade.php lines 319-341):** ```blade @if(!empty($this->createTemplate['variables'])) Template Variables
@foreach($this->createTemplate['variables'] as $name => $config) @endforeach
@endif ``` **Create plan features:** Plan title, Workspace selection, Variable inputs, Activate immediately option, Live preview ✅ #### AC28: Template categories displayed for organisation ✅ **Method:** Code inspection of `Templates.php`, `PlanTemplateService.php`, and view **Evidence - categories() computed property (Templates.php lines 95-99):** ```php #[Computed] public function categories(): Collection { return $this->templateService->getCategories(); } ``` **Evidence - PlanTemplateService getCategories() (lines 319-326):** ```php public function getCategories(): Collection { return $this->list() ->pluck('category') ->unique() ->sort() ->values(); } ``` **Evidence - Category filter dropdown (templates.blade.php lines 45-50):** ```blade All Categories @foreach($this->categories as $cat) {{ ucfirst($cat) }} @endforeach ``` **Evidence - Category badge in template cards (templates.blade.php lines 69-71):** ```blade {{ ucfirst($template['category']) }} ``` **Evidence - Category colour coding (Templates.php lines 436-446):** ```php public function getCategoryColor(string $category): string { return match ($category) { 'development' => 'bg-blue-100 text-blue-700 ...', 'maintenance' => 'bg-green-100 text-green-700 ...', 'review' => 'bg-amber-100 text-amber-700 ...', 'migration' => 'bg-purple-100 text-purple-700 ...', 'custom' => 'bg-zinc-100 text-zinc-700 ...', default => 'bg-violet-100 text-violet-700 ...', }; } ``` **Category features:** Filter dropdown, Colour-coded badges, Stats showing category count ✅ #### AC29: Can import custom templates (YAML upload) ✅ **Method:** Code inspection of `Templates.php` and `templates.blade.php` **Evidence - Import modal state (Templates.php lines 53-62):** ```php public bool $showImportModal = false; public ?UploadedFile $importFile = null; public string $importFileName = ''; public ?array $importPreview = null; public ?string $importError = null; ``` **Evidence - updatedImportFile() validation (Templates.php lines 293-351):** ```php public function updatedImportFile(): void { $content = file_get_contents($this->importFile->getRealPath()); $parsed = Yaml::parse($content); // Validate basic structure if (! is_array($parsed)) { $this->importError = 'Invalid YAML format: expected an object.'; return; } if (! isset($parsed['name'])) { $this->importError = 'Template must have a "name" field.'; return; } if (! isset($parsed['phases']) || ! is_array($parsed['phases'])) { $this->importError = 'Template must have a "phases" array.'; return; } // Build preview $this->importPreview = [ 'name' => $parsed['name'], 'description' => $parsed['description'] ?? null, 'category' => $parsed['category'] ?? 'custom', 'phases_count' => count($parsed['phases']), 'variables_count' => count($parsed['variables'] ?? []), ]; } ``` **Evidence - importTemplate() save (Templates.php lines 353-397):** ```php public function importTemplate(): void { $this->validate([ 'importFileName' => 'required|string|regex:/^[a-z0-9-]+$/|max:64', ]); $content = file_get_contents($this->importFile->getRealPath()); $targetPath = resource_path("plan-templates/{$this->importFileName}.yaml"); // Check for existing file if (File::exists($targetPath)) { $this->importError = 'A template with this filename already exists.'; return; } // Save the file File::put($targetPath, $content); Flux::toast(heading: 'Template Imported', ...); } ``` **Evidence - Import modal with file upload (templates.blade.php lines 381-476):** ```blade Import Template {{-- File Upload --}}
{{-- Preview --}} @if($importPreview)
Name:
{{ $importPreview['name'] }}
Category:
{{ $importPreview['category'] }}
Phases:
{{ $importPreview['phases_count'] }}
Variables:
{{ $importPreview['variables_count'] }}
@endif Import Template
``` **Import features:** YAML file upload, Validation (structure, name, phases), Preview before import, Custom filename, Duplicate detection ✅ ### Phase 5 Summary | AC | Description | Evidence | Status | |----|-------------|----------|--------| | AC25 | Route lists templates | `routes/web.php:195`, `templates()` computed, grid layout | ✅ PASS | | AC26 | Preview shows phases/variables | `previewTemplate()`, modal with phases list + variables table | ✅ PASS | | AC27 | Create plan with variables | `createPlan()`, variable inputs in modal, validation | ✅ PASS | | AC28 | Categories displayed | `getCategories()`, filter dropdown, colour-coded badges | ✅ PASS | | AC29 | YAML import | `importTemplate()`, file upload, validation, preview | ✅ PASS | **Additional Implementation Verified:** - Hades access control in component (`checkHadesAccess()`) - Sidebar link at line 573 - Dashboard quick link activated - Template deletion with confirmation - 5 templates available: bug-fix, code-review, feature-port, new-feature, refactor - Stats cards: total templates, categories, phases, with variables - Search functionality with debounce - Category colour coding for visual organisation **Phase 5 Status:** ✅ VERIFIED — Ready for human approval --- ## Implementation Hints (Added by Planning Agent) ### Existing Code Reference **Agent models already exist** — don't recreate them: | Model | Location | Key Methods | |-------|----------|-------------| | `AgentPlan` | `app/Models/Agent/AgentPlan.php` | `getProgress()`, `getCurrentPhase()`, `activate()`, `complete()`, `archive()`, `toMcpContext()` | | `AgentPhase` | `app/Models/Agent/AgentPhase.php` | Status constants: `STATUS_PENDING`, `STATUS_IN_PROGRESS`, `STATUS_COMPLETED`, `STATUS_BLOCKED`, `STATUS_SKIPPED` | | `AgentSession` | `app/Models/Agent/AgentSession.php` | `start()`, `pause()`, `resume()`, `complete()`, `fail()`, `logAction()`, `prepareHandoff()`, `getDurationFormatted()` | | `McpToolCallStat` | `app/Models/Mcp/McpToolCallStat.php` | `getTopTools()`, `getDailyTrend()`, `getServerStats()` — these are ready-made for dashboards | ### Hades Auth Pattern Follow existing pattern from other Hades-only components: ```php // In mount() method public function mount(): void { if (!auth()->user()?->isHades()) { abort(403); } // ... rest of mount } ``` See examples: `PackageManager.php:21`, `FeatureManager.php:20`, `Platform.php:36` ### Sidebar Integration Add to `resources/views/admin/components/sidebar.blade.php` **after line 376** (before the closing `` of the Hades section), using the existing pattern: ```blade {{-- Agent Operations --}}
  • Agents
  • ``` ### Admin Layout Pattern Use existing admin layout: ```php public function render() { return view('admin.admin.agent.dashboard') ->layout('admin.layouts.app', ['title' => 'Agent Dashboard']); } ``` ### View Location Views go in `resources/views/admin/livewire/agent/` (matches other admin components) ### Dashboard Queries (Ready to Use) ```php // These methods already exist on McpToolCallStat $topTools = McpToolCallStat::getTopTools(days: 7, limit: 10); $dailyTrend = McpToolCallStat::getDailyTrend(days: 7); $serverStats = McpToolCallStat::getServerStats(days: 7); // AgentPlan queries $activePlans = AgentPlan::active()->with(['workspace', 'agentPhases'])->count(); $activeSessions = AgentSession::active()->count(); ``` ### Routes Location Add after line 164 in `routes/web.php` (inside the hub group, after Commerce routes): ```php // Check existing structure at lines 131-169 for context ``` --- *This task creates the human interface for supervising AI agent operations.*