(
input: LocalShellSpawnInput & { shellCommand: ShellCommand },
context: TaskContext,
)
| 213 | }; |
| 214 | |
| 215 | export async function spawnShellTask( |
| 216 | input: LocalShellSpawnInput & { shellCommand: ShellCommand }, |
| 217 | context: TaskContext, |
| 218 | ): Promise<TaskHandle> { |
| 219 | const { command, description, shellCommand, toolUseId, agentId, kind } = input; |
| 220 | const { setAppState } = context; |
| 221 | |
| 222 | // TaskOutput owns the data — use its taskId so disk writes are consistent |
| 223 | const { taskOutput } = shellCommand; |
| 224 | const taskId = taskOutput.taskId; |
| 225 | |
| 226 | const unregisterCleanup = registerCleanup(async () => { |
| 227 | killTask(taskId, setAppState); |
| 228 | }); |
| 229 | |
| 230 | const taskState: LocalShellTaskState = { |
| 231 | ...createTaskStateBase(taskId, 'local_bash', description, toolUseId), |
| 232 | type: 'local_bash', |
| 233 | status: 'running', |
| 234 | command, |
| 235 | completionStatusSentInAttachment: false, |
| 236 | shellCommand, |
| 237 | unregisterCleanup, |
| 238 | lastReportedTotalLines: 0, |
| 239 | isBackgrounded: true, |
| 240 | agentId, |
| 241 | kind, |
| 242 | }; |
| 243 | |
| 244 | registerTask(taskState, setAppState); |
| 245 | |
| 246 | // Data flows through TaskOutput automatically — no stream listeners needed. |
| 247 | // Just transition to backgrounded state so the process keeps running. |
| 248 | shellCommand.background(taskId); |
| 249 | |
| 250 | const cancelStallWatchdog = startStallWatchdog(taskId, description, kind, toolUseId, agentId); |
| 251 | |
| 252 | void shellCommand.result.then(async result => { |
| 253 | cancelStallWatchdog(); |
| 254 | await flushAndCleanup(shellCommand); |
| 255 | let wasKilled = false; |
| 256 | |
| 257 | updateTaskState<LocalShellTaskState>(taskId, setAppState, task => { |
| 258 | if (task.status === 'killed') { |
| 259 | wasKilled = true; |
| 260 | return task; |
| 261 | } |
| 262 | |
| 263 | return { |
| 264 | ...task, |
| 265 | status: result.code === 0 ? 'completed' : 'failed', |
| 266 | result: { code: result.code, interrupted: result.interrupted }, |
| 267 | shellCommand: null, |
| 268 | unregisterCleanup: undefined, |
| 269 | endTime: Date.now(), |
| 270 | }; |
| 271 | }); |
| 272 |
no test coverage detected