(
ws: WorkspaceMeta,
adapter: CliAdapter,
prompt: string,
timeoutMs: number,
// Dispatch-path extras: a taskId keys the on-disk task log; onSessionId
// fires when the adapter's stdout scanner captures the agent's own session
// id (recorded WHILE running, so the panel can offer "open as session").
opts: { taskId?: string; onSessionId?: (id: string) => void } = {},
)
| 486 | }; |
| 487 | |
| 488 | const runHeadlessTaskMethod = async ( |
| 489 | ws: WorkspaceMeta, |
| 490 | adapter: CliAdapter, |
| 491 | prompt: string, |
| 492 | timeoutMs: number, |
| 493 | // Dispatch-path extras: a taskId keys the on-disk task log; onSessionId |
| 494 | // fires when the adapter's stdout scanner captures the agent's own session |
| 495 | // id (recorded WHILE running, so the panel can offer "open as session"). |
| 496 | opts: { taskId?: string; onSessionId?: (id: string) => void } = {}, |
| 497 | ): Promise<HeadlessTaskResult> => { |
| 498 | if (!adapter.capabilities.headless || !adapter.composeHeadlessCommand) { |
| 499 | throw new Error(`adapter "${adapter.id}" has no headless mode`); |
| 500 | } |
| 501 | await ensureAgentCredentialReady({ |
| 502 | meta: ws, |
| 503 | agentId: adapter.id, |
| 504 | adapter, |
| 505 | logger: launcherLogger, |
| 506 | }); |
| 507 | // Reuse a fresh interactive spawn's env/cwd (identical MCP injection), |
| 508 | // then swap the interactive command for the one-shot headless argv. Inject |
| 509 | // AQ_RUN_ID = this run's taskId so the agent's inbox pushes self-link to the |
| 510 | // run server-side (via the `alice` shim header / opencode's MCP header) — |
| 511 | // headless-only, agent never sees it. The taskId is the registry key the |
| 512 | // route resolves issueId/agent from. |
| 513 | const { cwd, env } = composeSpawnInputs( |
| 514 | ws, |
| 515 | adapter, |
| 516 | undefined, |
| 517 | undefined, |
| 518 | opts.taskId ? { AQ_RUN_ID: opts.taskId } : undefined, |
| 519 | ); |
| 520 | const command = adapter.composeHeadlessCommand(config.command, { cwd, env }, prompt); |
| 521 | const logPaths = opts.taskId ? headlessLogPaths(headlessLogsDir, opts.taskId) : null; |
| 522 | return runHeadlessTask({ |
| 523 | command, |
| 524 | cwd, |
| 525 | env, |
| 526 | timeoutMs, |
| 527 | logger: launcherLogger.child({ scope: 'headless', wsId: ws.id, agent: adapter.id }), |
| 528 | ...(logPaths ? { stdoutFile: logPaths.stdout, stderrFile: logPaths.stderr } : {}), |
| 529 | ...(adapter.extractHeadlessSessionId |
| 530 | ? { extractSessionId: adapter.extractHeadlessSessionId.bind(adapter) } |
| 531 | : {}), |
| 532 | ...(opts.onSessionId ? { onSessionId: opts.onSessionId } : {}), |
| 533 | }); |
| 534 | }; |
| 535 | |
| 536 | /** |
| 537 | * ASYNC dispatch: record the task, spawn it in the background, return the |
no test coverage detected