(state: AppState)
| 156 | * Called by the framework to create push notifications. |
| 157 | */ |
| 158 | export async function generateTaskAttachments(state: AppState): Promise<{ |
| 159 | attachments: TaskAttachment[] |
| 160 | // Only the offset patch — NOT the full task. The task may transition to |
| 161 | // completed during getTaskOutputDelta's async disk read, and spreading the |
| 162 | // full stale snapshot would clobber that transition (zombifying the task). |
| 163 | updatedTaskOffsets: Record<string, number> |
| 164 | evictedTaskIds: string[] |
| 165 | }> { |
| 166 | const attachments: TaskAttachment[] = [] |
| 167 | const updatedTaskOffsets: Record<string, number> = {} |
| 168 | const evictedTaskIds: string[] = [] |
| 169 | const tasks = state.tasks ?? {} |
| 170 | |
| 171 | for (const taskState of Object.values(tasks)) { |
| 172 | if (taskState.notified) { |
| 173 | switch (taskState.status) { |
| 174 | case 'completed': |
| 175 | case 'failed': |
| 176 | case 'killed': |
| 177 | // Evict terminal tasks — they've been consumed and can be GC'd |
| 178 | evictedTaskIds.push(taskState.id) |
| 179 | continue |
| 180 | case 'pending': |
| 181 | // Keep in map — hasn't run yet, but parent already knows about it |
| 182 | continue |
| 183 | case 'running': |
| 184 | // Fall through to running logic below |
| 185 | break |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | if (taskState.status === 'running') { |
| 190 | const delta = await getTaskOutputDelta( |
| 191 | taskState.id, |
| 192 | taskState.outputOffset, |
| 193 | ) |
| 194 | if (delta.content) { |
| 195 | updatedTaskOffsets[taskState.id] = delta.newOffset |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | // Completed tasks are NOT notified here — each task type handles its own |
| 200 | // completion notification via enqueuePendingNotification(). Generating |
| 201 | // attachments here would race with those per-type callbacks, causing |
| 202 | // dual delivery (one inline attachment + one separate API turn). |
| 203 | } |
| 204 | |
| 205 | return { attachments, updatedTaskOffsets, evictedTaskIds } |
| 206 | } |
| 207 | |
| 208 | /** |
| 209 | * Apply the outputOffset patches and evictions from generateTaskAttachments. |
no test coverage detected