(
ws: WorkspaceMeta,
adapter: CliAdapter,
resume: SessionFactoryContext['resume'],
initialPrompt?: string,
// Per-spawn env on TOP of the shared base — the identity-injection seam.
// Two MUTUALLY-EXCLUSIVE uses (a spawn carries AQ_RUN_ID XOR AQ_SESSION_ID):
// - the headless path injects AQ_RUN_ID (the run's taskId);
// - the interactive POOL factory injects AQ_SESSION_ID (the pre-allocated
// SessionRegistry record id).
// Deliberately a param, NOT folded into baseEnv: the probe spawn must carry
// NEITHER, and the two live spawns each carry exactly one. Merging it before
// adapter.composeEnv() lets an MCP-config adapter (opencode) read it and emit
// the matching out-of-band header.
extraEnv?: Record<string, string>,
)
| 349 | * Keeps the three call sites byte-identical on every env / command field. |
| 350 | */ |
| 351 | const composeSpawnInputs = ( |
| 352 | ws: WorkspaceMeta, |
| 353 | adapter: CliAdapter, |
| 354 | resume: SessionFactoryContext['resume'], |
| 355 | initialPrompt?: string, |
| 356 | // Per-spawn env on TOP of the shared base — the identity-injection seam. |
| 357 | // Two MUTUALLY-EXCLUSIVE uses (a spawn carries AQ_RUN_ID XOR AQ_SESSION_ID): |
| 358 | // - the headless path injects AQ_RUN_ID (the run's taskId); |
| 359 | // - the interactive POOL factory injects AQ_SESSION_ID (the pre-allocated |
| 360 | // SessionRegistry record id). |
| 361 | // Deliberately a param, NOT folded into baseEnv: the probe spawn must carry |
| 362 | // NEITHER, and the two live spawns each carry exactly one. Merging it before |
| 363 | // adapter.composeEnv() lets an MCP-config adapter (opencode) read it and emit |
| 364 | // the matching out-of-band header. |
| 365 | extraEnv?: Record<string, string>, |
| 366 | ): { |
| 367 | command: readonly string[]; |
| 368 | cwd: string; |
| 369 | env: Record<string, string>; |
| 370 | transcriptDir: string | null; |
| 371 | } => { |
| 372 | const baseEnv = buildSpawnEnv(process.env, { |
| 373 | AQ_WS_ID: ws.id, |
| 374 | AQ_LAUNCHER_REPO_ROOT: config.launcherRepoRoot, |
| 375 | // Local tool gateway for the injected `alice*` CLI shims. Electron/dev |
| 376 | // can point this at the web listener's `/cli`; Docker/public-web can keep |
| 377 | // it on a separate loopback-only port. This is the default agent tool |
| 378 | // path and does not require MCP to be enabled. |
| 379 | OPENALICE_TOOL_URL: opts.toolBaseUrl, |
| 380 | ...(opts.toolSocketPath ? { OPENALICE_TOOL_SOCKET: opts.toolSocketPath } : {}), |
| 381 | ...(opts.mcpBaseUrl ? { OPENALICE_MCP_URL: opts.mcpBaseUrl } : {}), |
| 382 | // Prepend the `alice` CLI shim dir so the workspace agent can invoke it |
| 383 | // from its shell (it reads OPENALICE_TOOL_URL + AQ_WS_ID above). Shared |
| 384 | // script — not written into the workspace, so it never pollutes the |
| 385 | // workspace's git repo. |
| 386 | PATH: `${cliBinPath()}${pathDelimiter}${process.env.PATH ?? ''}`, |
| 387 | // Per-workspace git identity — so any commit the agent makes (in its own |
| 388 | // repo OR a peer's, during cross-workspace collaboration) self-attributes |
| 389 | // to this workspace, and never fails for a missing identity on a clean |
| 390 | // box. This rides the PTY session env only; the launcher's own |
| 391 | // `commitInitial` (-c user.name=launcher) runs in the launcher's |
| 392 | // process.env, which we don't touch, so the initial commit stays |
| 393 | // `launcher`. Set explicitly here so a host ~/.gitconfig identity leaking |
| 394 | // through `process.env` can't shadow the workspace one (extras win). |
| 395 | GIT_AUTHOR_NAME: ws.tag, |
| 396 | GIT_AUTHOR_EMAIL: `${ws.id}@workspace.local`, |
| 397 | GIT_COMMITTER_NAME: ws.tag, |
| 398 | GIT_COMMITTER_EMAIL: `${ws.id}@workspace.local`, |
| 399 | // Headless-only run identity (see extraEnv above). Merged here so it's |
| 400 | // visible to adapter.composeEnv() below (opencode's inline MCP config) and |
| 401 | // to the spawned process's env (the `alice` shim reads it). |
| 402 | ...(extraEnv ?? {}), |
| 403 | }, ws.dir); |
| 404 | const baseCtx = { |
| 405 | ...(resume !== undefined ? { resume } : {}), |
| 406 | cwd: ws.dir, |
| 407 | env: baseEnv, |
| 408 | }; |
no test coverage detected