fix: Surface active architect agents in initiative activity state
Auto-spawned discuss/plan/refine agents were invisible because: 1. listInitiatives only filtered for mode='detail' agents 2. deriveInitiativeActivity returned 'idle' for zero phases before checking for active agents Broadened agent filter to all architect modes (discuss, plan, detail, refine), moved active agent check before zero-phases early return, and added 'discussing'/'refining' activity states with pulsing indicators.
This commit is contained in:
@@ -5,16 +5,23 @@
|
|||||||
import type { Initiative, Phase } from '../../db/schema.js';
|
import type { Initiative, Phase } from '../../db/schema.js';
|
||||||
import type { InitiativeActivity, InitiativeActivityState } from '@codewalk-district/shared';
|
import type { InitiativeActivity, InitiativeActivityState } from '@codewalk-district/shared';
|
||||||
|
|
||||||
export interface ActiveDetailAgent {
|
export interface ActiveArchitectAgent {
|
||||||
initiativeId: string;
|
initiativeId: string;
|
||||||
mode: string;
|
mode: string;
|
||||||
status: string;
|
status: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MODE_TO_STATE: Record<string, InitiativeActivityState> = {
|
||||||
|
discuss: 'discussing',
|
||||||
|
plan: 'detailing',
|
||||||
|
detail: 'detailing',
|
||||||
|
refine: 'refining',
|
||||||
|
};
|
||||||
|
|
||||||
export function deriveInitiativeActivity(
|
export function deriveInitiativeActivity(
|
||||||
initiative: Initiative,
|
initiative: Initiative,
|
||||||
phases: Phase[],
|
phases: Phase[],
|
||||||
activeDetailAgents?: ActiveDetailAgent[],
|
activeArchitectAgents?: ActiveArchitectAgent[],
|
||||||
): InitiativeActivity {
|
): InitiativeActivity {
|
||||||
const phasesTotal = phases.length;
|
const phasesTotal = phases.length;
|
||||||
const phasesCompleted = phases.filter(p => p.status === 'completed').length;
|
const phasesCompleted = phases.filter(p => p.status === 'completed').length;
|
||||||
@@ -26,6 +33,18 @@ export function deriveInitiativeActivity(
|
|||||||
if (initiative.status === 'completed') {
|
if (initiative.status === 'completed') {
|
||||||
return { ...base, state: 'complete' };
|
return { ...base, state: 'complete' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for active architect agents BEFORE zero-phases check
|
||||||
|
// so auto-spawned discuss/plan/refine agents surface activity
|
||||||
|
const activeAgent = activeArchitectAgents?.find(
|
||||||
|
a => a.initiativeId === initiative.id
|
||||||
|
&& (a.status === 'running' || a.status === 'waiting_for_input'),
|
||||||
|
);
|
||||||
|
if (activeAgent) {
|
||||||
|
const state = MODE_TO_STATE[activeAgent.mode] ?? 'detailing';
|
||||||
|
return { ...base, state };
|
||||||
|
}
|
||||||
|
|
||||||
if (phasesTotal === 0) {
|
if (phasesTotal === 0) {
|
||||||
return { ...base, state: 'idle' };
|
return { ...base, state: 'idle' };
|
||||||
}
|
}
|
||||||
@@ -53,15 +72,5 @@ export function deriveInitiativeActivity(
|
|||||||
return { ...base, state: 'ready', activePhase: { id: approved.id, name: approved.name } };
|
return { ...base, state: 'ready', activePhase: { id: approved.id, name: approved.name } };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for active detail agents (detailing trumps planning)
|
|
||||||
const detailing = activeDetailAgents?.some(
|
|
||||||
a => a.initiativeId === initiative.id
|
|
||||||
&& a.mode === 'detail'
|
|
||||||
&& (a.status === 'running' || a.status === 'waiting_for_input'),
|
|
||||||
);
|
|
||||||
if (detailing) {
|
|
||||||
return { ...base, state: 'detailing' };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...base, state: 'planning' };
|
return { ...base, state: 'planning' };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,11 +98,12 @@ export function initiativeProcedures(publicProcedure: ProcedureBuilder) {
|
|||||||
? await repo.findByStatus(input.status)
|
? await repo.findByStatus(input.status)
|
||||||
: await repo.findAll();
|
: await repo.findAll();
|
||||||
|
|
||||||
// Fetch active detail agents once for all initiatives
|
// Fetch active architect agents once for all initiatives
|
||||||
|
const ARCHITECT_MODES = ['discuss', 'plan', 'detail', 'refine'];
|
||||||
const allAgents = ctx.agentManager ? await ctx.agentManager.list() : [];
|
const allAgents = ctx.agentManager ? await ctx.agentManager.list() : [];
|
||||||
const activeDetailAgents = allAgents
|
const activeArchitectAgents = allAgents
|
||||||
.filter(a =>
|
.filter(a =>
|
||||||
a.mode === 'detail'
|
ARCHITECT_MODES.includes(a.mode ?? '')
|
||||||
&& (a.status === 'running' || a.status === 'waiting_for_input')
|
&& (a.status === 'running' || a.status === 'waiting_for_input')
|
||||||
&& !a.userDismissedAt,
|
&& !a.userDismissedAt,
|
||||||
)
|
)
|
||||||
@@ -112,13 +113,13 @@ export function initiativeProcedures(publicProcedure: ProcedureBuilder) {
|
|||||||
const phaseRepo = ctx.phaseRepository;
|
const phaseRepo = ctx.phaseRepository;
|
||||||
return Promise.all(initiatives.map(async (init) => {
|
return Promise.all(initiatives.map(async (init) => {
|
||||||
const phases = await phaseRepo.findByInitiativeId(init.id);
|
const phases = await phaseRepo.findByInitiativeId(init.id);
|
||||||
return { ...init, activity: deriveInitiativeActivity(init, phases, activeDetailAgents) };
|
return { ...init, activity: deriveInitiativeActivity(init, phases, activeArchitectAgents) };
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return initiatives.map(init => ({
|
return initiatives.map(init => ({
|
||||||
...init,
|
...init,
|
||||||
activity: deriveInitiativeActivity(init, [], activeDetailAgents),
|
activity: deriveInitiativeActivity(init, [], activeArchitectAgents),
|
||||||
}));
|
}));
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ function activityVisual(state: string): { label: string; variant: StatusVariant;
|
|||||||
switch (state) {
|
switch (state) {
|
||||||
case "executing": return { label: "Executing", variant: "active", pulse: true };
|
case "executing": return { label: "Executing", variant: "active", pulse: true };
|
||||||
case "pending_review": return { label: "Pending Review", variant: "warning", pulse: true };
|
case "pending_review": return { label: "Pending Review", variant: "warning", pulse: true };
|
||||||
|
case "discussing": return { label: "Discussing", variant: "active", pulse: true };
|
||||||
case "detailing": return { label: "Detailing", variant: "active", pulse: true };
|
case "detailing": return { label: "Detailing", variant: "active", pulse: true };
|
||||||
|
case "refining": return { label: "Refining", variant: "active", pulse: true };
|
||||||
case "ready": return { label: "Ready", variant: "active", pulse: false };
|
case "ready": return { label: "Ready", variant: "active", pulse: false };
|
||||||
case "blocked": return { label: "Blocked", variant: "error", pulse: false };
|
case "blocked": return { label: "Blocked", variant: "error", pulse: false };
|
||||||
case "complete": return { label: "Complete", variant: "success", pulse: false };
|
case "complete": return { label: "Complete", variant: "success", pulse: false };
|
||||||
|
|||||||
@@ -180,4 +180,4 @@ Configured in `src/lib/trpc.ts`. Uses `@trpc/react-query` with TanStack Query fo
|
|||||||
|
|
||||||
`listInitiatives` returns an `activity` field on each initiative, computed server-side from phase statuses via `deriveInitiativeActivity()` in `apps/server/trpc/routers/initiative-activity.ts`. This eliminates per-card N+1 `listPhases` queries.
|
`listInitiatives` returns an `activity` field on each initiative, computed server-side from phase statuses via `deriveInitiativeActivity()` in `apps/server/trpc/routers/initiative-activity.ts`. This eliminates per-card N+1 `listPhases` queries.
|
||||||
|
|
||||||
Activity states (priority order): `pending_review` > `executing` > `blocked` > `complete` > `ready` > `detailing` > `planning` > `idle` > `archived`. Each state maps to a `StatusVariant` + pulse animation in `InitiativeCard`'s `activityVisual()` function. The `detailing` state is derived from active detail agents (mode='detail', status running/waiting_for_input) and shows a pulsing indigo dot. `PhaseSidebarItem` also shows a spinner when a detail agent is active for its phase.
|
Activity states (priority order): active architect agents > `pending_review` > `executing` > `blocked` > `complete` > `ready` > `planning` > `idle` > `archived`. Each state maps to a `StatusVariant` + pulse animation in `InitiativeCard`'s `activityVisual()` function. Active architect agents (modes: discuss, plan, detail, refine) are checked first — mapping to `discussing`, `detailing`, `detailing`, `refining` states respectively — so auto-spawned agents surface activity even when no phases exist yet. `PhaseSidebarItem` also shows a spinner when a detail agent is active for its phase.
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ export type { PendingQuestions, QuestionItem } from '../../../apps/server/agent/
|
|||||||
export type ExecutionMode = 'yolo' | 'review_per_phase';
|
export type ExecutionMode = 'yolo' | 'review_per_phase';
|
||||||
|
|
||||||
export type InitiativeActivityState =
|
export type InitiativeActivityState =
|
||||||
| 'idle' // Active but no phases
|
| 'idle' // Active but no phases and no agents
|
||||||
|
| 'discussing' // Discuss agent actively scoping the initiative
|
||||||
| 'planning' // All phases pending (no work started)
|
| 'planning' // All phases pending (no work started)
|
||||||
| 'detailing' // Detail agent actively decomposing phases into tasks
|
| 'detailing' // Detail/plan agent actively decomposing phases into tasks
|
||||||
|
| 'refining' // Refine agent actively working on content
|
||||||
| 'ready' // Phases approved, waiting to execute
|
| 'ready' // Phases approved, waiting to execute
|
||||||
| 'executing' // At least one phase in_progress
|
| 'executing' // At least one phase in_progress
|
||||||
| 'pending_review' // At least one phase pending_review
|
| 'pending_review' // At least one phase pending_review
|
||||||
|
|||||||
Reference in New Issue
Block a user