Files
Codewalkers/apps/server/trpc/routers/initiative-activity.ts
Lukas May 28521e1c20 chore: merge main into cw/small-change-flow
Integrates main branch changes (headquarters dashboard, task retry count,
agent prompt persistence, remote sync improvements) with the initiative's
errand agent feature. Both features coexist in the merged result.

Key resolutions:
- Schema: take main's errands table (nullable projectId, no conflictFiles,
  with errandsRelations); migrate to 0035_faulty_human_fly
- Router: keep both errandProcedures and headquartersProcedures
- Errand prompt: take main's simpler version (no question-asking flow)
- Manager: take main's status check (running|idle only, no waiting_for_input)
- Tests: update to match removed conflictFiles field and undefined vs null
2026-03-06 16:48:12 +01:00

94 lines
2.9 KiB
TypeScript

/**
* Initiative Activity — derives current activity state from initiative + phases.
*/
import type { Initiative, Phase } from '../../db/schema.js';
import type { InitiativeActivity, InitiativeActivityState } from '@codewalk-district/shared';
export interface ActiveArchitectAgent {
initiativeId: string;
mode: string;
status: string;
name?: string;
}
const MODE_TO_STATE: Record<string, InitiativeActivityState> = {
discuss: 'discussing',
plan: 'detailing',
detail: 'detailing',
refine: 'refining',
};
export function deriveInitiativeActivity(
initiative: Initiative,
phases: Phase[],
activeArchitectAgents?: ActiveArchitectAgent[],
): InitiativeActivity {
const phasesTotal = phases.length;
const phasesCompleted = phases.filter(p => p.status === 'completed').length;
const base = { phasesTotal, phasesCompleted };
if (initiative.status === 'archived') {
return { ...base, state: 'archived' };
}
// Check for active conflict resolution agent — takes priority over pending_review
// because the agent is actively working to resolve merge conflicts
const conflictAgent = activeArchitectAgents?.find(
a => a.initiativeId === initiative.id
&& a.name?.startsWith('conflict-')
&& (a.status === 'running' || a.status === 'waiting_for_input'),
);
if (conflictAgent) {
return { ...base, state: 'resolving_conflict' };
}
if (initiative.status === 'pending_review') {
return { ...base, state: 'pending_review' };
}
if (initiative.status === 'completed') {
return { ...base, state: 'complete' };
}
// Check for active architect agents BEFORE zero-phases check
// so architect agents (discuss/plan/detail/refine) surface activity
const activeAgent = activeArchitectAgents?.find(
a => a.initiativeId === initiative.id
&& !a.name?.startsWith('conflict-')
&& (a.status === 'running' || a.status === 'waiting_for_input'),
);
if (activeAgent) {
const state = MODE_TO_STATE[activeAgent.mode] ?? 'detailing';
return { ...base, state };
}
if (phasesTotal === 0) {
return { ...base, state: 'idle' };
}
// Priority-ordered state detection (first match wins)
const priorities: Array<{ status: Phase['status']; state: InitiativeActivityState }> = [
{ status: 'pending_review', state: 'pending_review' },
{ status: 'in_progress', state: 'executing' },
{ status: 'blocked', state: 'blocked' },
];
for (const { status, state } of priorities) {
const match = phases.find(p => p.status === status);
if (match) {
return { ...base, state, activePhase: { id: match.id, name: match.name } };
}
}
if (phasesCompleted === phasesTotal) {
return { ...base, state: 'complete' };
}
const approved = phases.find(p => p.status === 'approved');
if (approved) {
return { ...base, state: 'ready', activePhase: { id: approved.id, name: approved.name } };
}
return { ...base, state: 'planning' };
}