(input: LocalShellSpawnInput & {
shellCommand: ShellCommand;
}, context: TaskContext)
| 178 | } |
| 179 | }; |
| 180 | export async function spawnShellTask(input: LocalShellSpawnInput & { |
| 181 | shellCommand: ShellCommand; |
| 182 | }, context: TaskContext): Promise<TaskHandle> { |
| 183 | const { |
| 184 | command, |
| 185 | description, |
| 186 | shellCommand, |
| 187 | toolUseId, |
| 188 | agentId, |
| 189 | kind |
| 190 | } = input; |
| 191 | const { |
| 192 | setAppState |
| 193 | } = context; |
| 194 | |
| 195 | // TaskOutput owns the data — use its taskId so disk writes are consistent |
| 196 | const { |
| 197 | taskOutput |
| 198 | } = shellCommand; |
| 199 | const taskId = taskOutput.taskId; |
| 200 | const unregisterCleanup = registerCleanup(async () => { |
| 201 | killTask(taskId, setAppState); |
| 202 | }); |
| 203 | const taskState: LocalShellTaskState = { |
| 204 | ...createTaskStateBase(taskId, 'local_bash', description, toolUseId), |
| 205 | type: 'local_bash', |
| 206 | status: 'running', |
| 207 | command, |
| 208 | completionStatusSentInAttachment: false, |
| 209 | shellCommand, |
| 210 | unregisterCleanup, |
| 211 | lastReportedTotalLines: 0, |
| 212 | isBackgrounded: true, |
| 213 | agentId, |
| 214 | kind |
| 215 | }; |
| 216 | registerTask(taskState, setAppState); |
| 217 | |
| 218 | // Data flows through TaskOutput automatically — no stream listeners needed. |
| 219 | // Just transition to backgrounded state so the process keeps running. |
| 220 | shellCommand.background(taskId); |
| 221 | const cancelStallWatchdog = startStallWatchdog(taskId, description, kind, toolUseId, agentId); |
| 222 | void shellCommand.result.then(async result => { |
| 223 | cancelStallWatchdog(); |
| 224 | await flushAndCleanup(shellCommand); |
| 225 | let wasKilled = false; |
| 226 | updateTaskState<LocalShellTaskState>(taskId, setAppState, task => { |
| 227 | if (task.status === 'killed') { |
| 228 | wasKilled = true; |
| 229 | return task; |
| 230 | } |
| 231 | return { |
| 232 | ...task, |
| 233 | status: result.code === 0 ? 'completed' : 'failed', |
| 234 | result: { |
| 235 | code: result.code, |
| 236 | interrupted: result.interrupted |
| 237 | }, |
no test coverage detected