(
taskId: string,
ownerWorkspaceId: string | undefined
)
| 4145 | } |
| 4146 | |
| 4147 | private async persistNotifyOnTerminalPolicy( |
| 4148 | taskId: string, |
| 4149 | ownerWorkspaceId: string | undefined |
| 4150 | ): Promise<void> { |
| 4151 | if (isWorkspaceTurnTaskId(taskId)) { |
| 4152 | if (ownerWorkspaceId == null) return; |
| 4153 | const pendingNotify = await this.workspaceTurnSettlementLocks.withLock( |
| 4154 | taskId, |
| 4155 | async (): Promise<{ |
| 4156 | handleId: string; |
| 4157 | outcome: TerminalAttentionOutcome; |
| 4158 | title?: string; |
| 4159 | } | null> => { |
| 4160 | const current = await this.taskHandleStore.getWorkspaceTurn(ownerWorkspaceId, taskId); |
| 4161 | if (current == null) return null; |
| 4162 | |
| 4163 | const updatedRecord: WorkspaceTurnTaskHandleRecord = |
| 4164 | current.attentionPolicy === "notify_on_terminal" |
| 4165 | ? current |
| 4166 | : { ...current, attentionPolicy: "notify_on_terminal", updatedAt: getIsoNow() }; |
| 4167 | if (updatedRecord !== current) { |
| 4168 | await this.taskHandleStore.upsertWorkspaceTurn(updatedRecord); |
| 4169 | } |
| 4170 | |
| 4171 | // A queued-message/timeout detach can race with child stream-end settlement: the waiter is |
| 4172 | // gone before notify_on_terminal is durably persisted, so settleWorkspaceTurn may have seen a |
| 4173 | // blocking policy and skipped the terminal wake-up. If the handle is already terminal here, |
| 4174 | // enqueue the missing wake-up after releasing the settlement lock. |
| 4175 | if ( |
| 4176 | this.isTerminalWorkspaceTurnStatus(updatedRecord.status) && |
| 4177 | updatedRecord.terminalAttentionNotifiedAt == null |
| 4178 | ) { |
| 4179 | return { |
| 4180 | handleId: updatedRecord.handleId, |
| 4181 | outcome: workspaceTurnTerminalOutcome(updatedRecord.status), |
| 4182 | ...(updatedRecord.title != null ? { title: updatedRecord.title } : {}), |
| 4183 | }; |
| 4184 | } |
| 4185 | return null; |
| 4186 | } |
| 4187 | ); |
| 4188 | if (pendingNotify != null) { |
| 4189 | await this.enqueueTerminalAttention({ |
| 4190 | ownerWorkspaceId, |
| 4191 | sourceKind: "workspace_turn", |
| 4192 | sourceId: pendingNotify.handleId, |
| 4193 | outputDelivery: "requires_task_await", |
| 4194 | terminalOutcome: pendingNotify.outcome, |
| 4195 | ...(pendingNotify.title != null ? { title: pendingNotify.title } : {}), |
| 4196 | }); |
| 4197 | await this.workspaceTurnSettlementLocks.withLock(taskId, async () => { |
| 4198 | const terminal = await this.taskHandleStore.getWorkspaceTurn(ownerWorkspaceId, taskId); |
| 4199 | if (terminal != null && terminal.terminalAttentionNotifiedAt == null) { |
| 4200 | await this.taskHandleStore.upsertWorkspaceTurn({ |
| 4201 | ...terminal, |
| 4202 | terminalAttentionNotifiedAt: getIsoNow(), |
| 4203 | }); |
| 4204 | } |
no test coverage detected