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
94 lines
2.9 KiB
TypeScript
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' };
|
|
}
|