|
9 | 9 | workflowStatusNextActionsForPhase, |
10 | 10 | workflowStatusSummaryForPhase, |
11 | 11 | } from "./current-state-phase.js"; |
| 12 | +import { listTasks, readTaskTemplates } from "./task-store.js"; |
12 | 13 |
|
13 | 14 | export interface WorkflowStatusSummary { |
14 | 15 | version: "1"; |
@@ -73,41 +74,73 @@ function buildDerivedWorkflowStatus( |
73 | 74 | }; |
74 | 75 | } |
75 | 76 |
|
76 | | -export async function readWorkflowStatus( |
| 77 | +async function detectWorkflowStatusSourcePath( |
77 | 78 | contextDir: string, |
| 79 | + current: WorkflowStatusSummary | null, |
| 80 | +): Promise<string | null> { |
| 81 | + if (current?.source?.kind === "current_state_phase_v1" && current.source.path.trim()) { |
| 82 | + return current.source.path; |
| 83 | + } |
| 84 | + const templates = await readTaskTemplates(contextDir); |
| 85 | + const templateSource = templates.find((template) => template.phase_guard?.source_path)?.phase_guard?.source_path; |
| 86 | + if (templateSource) return templateSource; |
| 87 | + |
| 88 | + const tasks = await listTasks(contextDir); |
| 89 | + const taskSource = tasks.find((task) => task.phase_guard?.source_path)?.phase_guard?.source_path; |
| 90 | + return taskSource ?? null; |
| 91 | +} |
| 92 | + |
| 93 | +export async function syncWorkflowStatusToCurrentState( |
| 94 | + contextDir: string, |
| 95 | + ownerMemberId = "lead", |
| 96 | + sourcePath?: string | null, |
78 | 97 | ): Promise<WorkflowStatusSummary | null> { |
| 98 | + let current: WorkflowStatusSummary | null = null; |
79 | 99 | try { |
80 | 100 | const raw = await readFile(workflowStatusPath(contextDir), "utf-8"); |
81 | 101 | const data = JSON.parse(raw); |
82 | 102 | assertWorkflowStatusSummary(data); |
83 | | - if (data.source?.kind === "current_state_phase_v1") { |
84 | | - try { |
85 | | - const snapshot = await readCurrentStatePhaseSnapshot(contextDir, data.source.path); |
86 | | - const derived = buildDerivedWorkflowStatus(data.owner_member_id, snapshot); |
87 | | - const sourceMatches = data.source.path === derived.source?.path |
88 | | - && data.source.mtime_ms === derived.source?.mtime_ms; |
89 | | - if ( |
90 | | - !sourceMatches |
91 | | - || data.summary !== derived.summary |
92 | | - || !arraysEqual(data.blockers, derived.blockers) |
93 | | - || !arraysEqual(data.next_actions, derived.next_actions) |
94 | | - ) { |
95 | | - const root = agentsRoot(contextDir); |
96 | | - const tmpDir = resolve(root, "tmp"); |
97 | | - await mkdir(dirname(workflowStatusPath(contextDir)), { recursive: true }); |
98 | | - await atomicJsonWrite(tmpDir, workflowStatusPath(contextDir), derived); |
99 | | - return derived; |
100 | | - } |
101 | | - } catch { |
102 | | - // Fall back to the persisted summary if the source is temporarily unreadable. |
103 | | - } |
| 103 | + current = data; |
| 104 | + } catch { |
| 105 | + current = null; |
| 106 | + } |
| 107 | + |
| 108 | + const resolvedSourcePath = sourcePath ?? await detectWorkflowStatusSourcePath(contextDir, current); |
| 109 | + if (!resolvedSourcePath) { |
| 110 | + return current; |
| 111 | + } |
| 112 | + |
| 113 | + try { |
| 114 | + const snapshot = await readCurrentStatePhaseSnapshot(contextDir, resolvedSourcePath); |
| 115 | + const derived = buildDerivedWorkflowStatus(current?.owner_member_id ?? ownerMemberId, snapshot); |
| 116 | + if ( |
| 117 | + current |
| 118 | + && current.source?.kind === "current_state_phase_v1" |
| 119 | + && current.source.path === derived.source?.path |
| 120 | + && current.source.mtime_ms === derived.source?.mtime_ms |
| 121 | + && current.summary === derived.summary |
| 122 | + && arraysEqual(current.blockers, derived.blockers) |
| 123 | + && arraysEqual(current.next_actions, derived.next_actions) |
| 124 | + ) { |
| 125 | + return current; |
104 | 126 | } |
105 | | - return data; |
| 127 | + |
| 128 | + const root = agentsRoot(contextDir); |
| 129 | + const tmpDir = resolve(root, "tmp"); |
| 130 | + await mkdir(dirname(workflowStatusPath(contextDir)), { recursive: true }); |
| 131 | + await atomicJsonWrite(tmpDir, workflowStatusPath(contextDir), derived); |
| 132 | + return derived; |
106 | 133 | } catch { |
107 | | - return null; |
| 134 | + return current; |
108 | 135 | } |
109 | 136 | } |
110 | 137 |
|
| 138 | +export async function readWorkflowStatus( |
| 139 | + contextDir: string, |
| 140 | +): Promise<WorkflowStatusSummary | null> { |
| 141 | + return await syncWorkflowStatusToCurrentState(contextDir); |
| 142 | +} |
| 143 | + |
111 | 144 | export async function writeWorkflowStatus( |
112 | 145 | opts: WriteWorkflowStatusOptions, |
113 | 146 | ): Promise<WorkflowStatusSummary> { |
|
0 commit comments