diff --git a/AGENTS.md b/AGENTS.md index 8a01b989..769ded1a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,8 @@ Live team smoke runtime: - Use the orchestrator source launcher by default for live/dev smoke loops: `/Users/belief/dev/projects/claude/agent_teams_orchestrator/cli-source` - The source launcher runs `src/entrypoints/cli.tsx` through Bun, so it reflects local orchestrator source edits immediately and cannot accidentally test stale `dist` output. +- The source launcher normalizes inherited `NODE_ENV=production` to `NODE_ENV=development`. Release or production-like smoke must use the built wrapper instead of preserving production mode on source. +- Local live/prove scripts should use `scripts/lib/live-smoke-runtime.mjs`, which defaults to `cli-source` unless `CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH` is explicitly set. - Source-mode teammate startup can be slower than bundled startup. Live smoke harnesses may raise `CLAUDE_TEAM_PROCESS_RUNTIME_READY_TIMEOUT_MS` and `CLAUDE_TEAM_PROCESS_INBOX_POLLER_READY_TIMEOUT_MS` when the test is validating source behavior instead of watchdog latency. - Use the built wrapper only for release or production-like smoke checks. Build first in `/Users/belief/dev/projects/claude/agent_teams_orchestrator` with `bun run build`, then set `CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH=/Users/belief/dev/projects/claude/agent_teams_orchestrator/cli`. - Do not use `cli-dev` or `bun run build:dev` as proof for the production wrapper. `cli` reads `dist/local-cli/cli.js`; `cli-dev` reads `dist/local-cli-dev/cli.js`. diff --git a/docs/team-management/debugging-agent-teams.md b/docs/team-management/debugging-agent-teams.md index a080aa33..6741f8c6 100644 --- a/docs/team-management/debugging-agent-teams.md +++ b/docs/team-management/debugging-agent-teams.md @@ -95,6 +95,10 @@ export CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH=/Users/belief/dev/projects/claud The source launcher executes `src/entrypoints/cli.tsx` through Bun. It is the right default for local debug loops, live model/provider checks, and cross-repo runtime fixes. +It normalizes inherited `NODE_ENV=production` to `NODE_ENV=development`, because source smoke is a +dev/runtime validation path. If you need production semantics, run the release smoke path below. +Local live/prove scripts should resolve their default CLI through `scripts/lib/live-smoke-runtime.mjs`, +which points at `cli-source` unless `CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH` is explicitly set. Source-mode teammate startup can be slower than bundled startup, so live smoke harnesses may set `CLAUDE_TEAM_PROCESS_RUNTIME_READY_TIMEOUT_MS` and `CLAUDE_TEAM_PROCESS_INBOX_POLLER_READY_TIMEOUT_MS` to larger values when they are validating source diff --git a/scripts/lib/live-smoke-runtime.mjs b/scripts/lib/live-smoke-runtime.mjs new file mode 100644 index 00000000..9cf959e6 --- /dev/null +++ b/scripts/lib/live-smoke-runtime.mjs @@ -0,0 +1,37 @@ +import path from 'node:path'; + +export function resolveLiveSmokeOrchestratorCliPath({ + env = process.env, + repoRoot, +} = {}) { + const explicitCliPath = env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH?.trim(); + if (explicitCliPath) { + return explicitCliPath; + } + + const configuredRuntimeRoot = env.CLAUDE_DEV_RUNTIME_ROOT?.trim(); + const baseRepoRoot = repoRoot ? path.resolve(repoRoot) : process.cwd(); + const runtimeRoot = configuredRuntimeRoot + ? path.resolve(configuredRuntimeRoot) + : path.resolve(baseRepoRoot, '..', 'agent_teams_orchestrator'); + + return path.join(runtimeRoot, 'cli-source'); +} + +export function resolveReleaseSmokeOrchestratorCliPath({ + env = process.env, + repoRoot, +} = {}) { + const explicitCliPath = env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH?.trim(); + if (explicitCliPath) { + return explicitCliPath; + } + + const configuredRuntimeRoot = env.CLAUDE_DEV_RUNTIME_ROOT?.trim(); + const baseRepoRoot = repoRoot ? path.resolve(repoRoot) : process.cwd(); + const runtimeRoot = configuredRuntimeRoot + ? path.resolve(configuredRuntimeRoot) + : path.resolve(baseRepoRoot, '..', 'agent_teams_orchestrator'); + + return path.join(runtimeRoot, 'cli'); +} diff --git a/scripts/prove-agent-cli-launch.mjs b/scripts/prove-agent-cli-launch.mjs index 68d5e7c5..eaf63af5 100644 --- a/scripts/prove-agent-cli-launch.mjs +++ b/scripts/prove-agent-cli-launch.mjs @@ -5,18 +5,24 @@ import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; +import { resolveLiveSmokeOrchestratorCliPath } from './lib/live-smoke-runtime.mjs'; + const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(scriptDir, '..'); -const siblingOrchestrator = path.resolve(repoRoot, '..', 'agent_teams_orchestrator'); const env = { ...process.env, AGENT_CLI_LAUNCH_LIVE_E2E: '1', CLAUDE_TEAM_CLI_FLAVOR: process.env.CLAUDE_TEAM_CLI_FLAVOR || 'agent_teams_orchestrator', - CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH: - process.env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH || path.join(siblingOrchestrator, 'cli'), }; +if (!env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH?.trim()) { + env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = resolveLiveSmokeOrchestratorCliPath({ + env, + repoRoot, + }); +} + console.log('Running agent CLI launch live smoke'); console.log(`Claude runtime: ${env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH}`); diff --git a/scripts/prove-opencode-mixed-recovery.mjs b/scripts/prove-opencode-mixed-recovery.mjs index 9fa176da..3a896a7f 100644 --- a/scripts/prove-opencode-mixed-recovery.mjs +++ b/scripts/prove-opencode-mixed-recovery.mjs @@ -5,6 +5,7 @@ import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; +import { resolveLiveSmokeOrchestratorCliPath } from './lib/live-smoke-runtime.mjs'; import { exitForSkippedPreflight, preflightOpenCodeLiveEnvironment, @@ -12,8 +13,6 @@ import { const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(scriptDir, '..'); -const orchestratorRoot = process.env.CLAUDE_DEV_RUNTIME_ROOT?.trim(); -const siblingOrchestrator = path.resolve(repoRoot, '..', 'agent_teams_orchestrator'); const env = { ...process.env, @@ -26,8 +25,10 @@ const env = { }; if (!env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH?.trim()) { - const runtimeRoot = orchestratorRoot ? path.resolve(orchestratorRoot) : siblingOrchestrator; - env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = path.join(runtimeRoot, 'cli'); + env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = resolveLiveSmokeOrchestratorCliPath({ + env, + repoRoot, + }); } console.log('Running OpenCode mixed recovery live smoke'); diff --git a/scripts/prove-opencode-semantic-gauntlet.mjs b/scripts/prove-opencode-semantic-gauntlet.mjs index abb9cc1a..031b18a1 100644 --- a/scripts/prove-opencode-semantic-gauntlet.mjs +++ b/scripts/prove-opencode-semantic-gauntlet.mjs @@ -5,6 +5,7 @@ import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; +import { resolveLiveSmokeOrchestratorCliPath } from './lib/live-smoke-runtime.mjs'; import { exitForSkippedPreflight, preflightOpenCodeLiveEnvironment, @@ -12,8 +13,6 @@ import { const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(scriptDir, '..'); -const orchestratorRoot = process.env.CLAUDE_DEV_RUNTIME_ROOT?.trim(); -const siblingOrchestrator = path.resolve(repoRoot, '..', 'agent_teams_orchestrator'); const env = { ...process.env, @@ -33,8 +32,10 @@ const env = { }; if (!env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH?.trim()) { - const runtimeRoot = orchestratorRoot ? path.resolve(orchestratorRoot) : siblingOrchestrator; - env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = path.join(runtimeRoot, 'cli'); + env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = resolveLiveSmokeOrchestratorCliPath({ + env, + repoRoot, + }); } console.log('Running OpenCode semantic gauntlet live smoke'); diff --git a/scripts/prove-opencode-semantic-messaging.mjs b/scripts/prove-opencode-semantic-messaging.mjs index 19d4e0bd..1eee1cb0 100644 --- a/scripts/prove-opencode-semantic-messaging.mjs +++ b/scripts/prove-opencode-semantic-messaging.mjs @@ -5,6 +5,7 @@ import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; +import { resolveLiveSmokeOrchestratorCliPath } from './lib/live-smoke-runtime.mjs'; import { exitForSkippedPreflight, preflightOpenCodeLiveEnvironment, @@ -12,8 +13,6 @@ import { const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(scriptDir, '..'); -const orchestratorRoot = process.env.CLAUDE_DEV_RUNTIME_ROOT?.trim(); -const siblingOrchestrator = path.resolve(repoRoot, '..', 'agent_teams_orchestrator'); const env = { ...process.env, @@ -25,8 +24,10 @@ const env = { }; if (!env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH?.trim()) { - const runtimeRoot = orchestratorRoot ? path.resolve(orchestratorRoot) : siblingOrchestrator; - env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = path.join(runtimeRoot, 'cli'); + env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = resolveLiveSmokeOrchestratorCliPath({ + env, + repoRoot, + }); } console.log('Running OpenCode semantic messaging live smoke'); diff --git a/scripts/prove-opencode-semantic-model-matrix.mjs b/scripts/prove-opencode-semantic-model-matrix.mjs index 717dc804..9f91862f 100644 --- a/scripts/prove-opencode-semantic-model-matrix.mjs +++ b/scripts/prove-opencode-semantic-model-matrix.mjs @@ -5,6 +5,7 @@ import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; +import { resolveLiveSmokeOrchestratorCliPath } from './lib/live-smoke-runtime.mjs'; import { exitForSkippedPreflight, preflightOpenCodeLiveEnvironment, @@ -12,8 +13,6 @@ import { const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(scriptDir, '..'); -const orchestratorRoot = process.env.CLAUDE_DEV_RUNTIME_ROOT?.trim(); -const siblingOrchestrator = path.resolve(repoRoot, '..', 'agent_teams_orchestrator'); const env = { ...process.env, @@ -24,8 +23,10 @@ const env = { }; if (!env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH?.trim()) { - const runtimeRoot = orchestratorRoot ? path.resolve(orchestratorRoot) : siblingOrchestrator; - env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = path.join(runtimeRoot, 'cli'); + env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = resolveLiveSmokeOrchestratorCliPath({ + env, + repoRoot, + }); } console.log('Running OpenCode semantic model matrix live smoke'); diff --git a/scripts/prove-opencode-team-provisioning.mjs b/scripts/prove-opencode-team-provisioning.mjs index 246e891a..12673fd3 100644 --- a/scripts/prove-opencode-team-provisioning.mjs +++ b/scripts/prove-opencode-team-provisioning.mjs @@ -5,6 +5,7 @@ import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; +import { resolveLiveSmokeOrchestratorCliPath } from './lib/live-smoke-runtime.mjs'; import { exitForSkippedPreflight, preflightOpenCodeLiveEnvironment, @@ -12,8 +13,6 @@ import { const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(scriptDir, '..'); -const orchestratorRoot = process.env.CLAUDE_DEV_RUNTIME_ROOT?.trim(); -const siblingOrchestrator = path.resolve(repoRoot, '..', 'agent_teams_orchestrator'); const env = { ...process.env, @@ -25,8 +24,10 @@ const env = { }; if (!env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH?.trim()) { - const runtimeRoot = orchestratorRoot ? path.resolve(orchestratorRoot) : siblingOrchestrator; - env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = path.join(runtimeRoot, 'cli'); + env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = resolveLiveSmokeOrchestratorCliPath({ + env, + repoRoot, + }); } console.log('Running OpenCode team provisioning live smoke'); diff --git a/scripts/prove-provider-launch-stress.mjs b/scripts/prove-provider-launch-stress.mjs index f8abe106..397e87d7 100644 --- a/scripts/prove-provider-launch-stress.mjs +++ b/scripts/prove-provider-launch-stress.mjs @@ -7,12 +7,11 @@ import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; +import { resolveLiveSmokeOrchestratorCliPath } from './lib/live-smoke-runtime.mjs'; import { preflightOpenCodeLiveEnvironment } from './lib/opencode-live-preflight.mjs'; const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(scriptDir, '..'); -const orchestratorRoot = process.env.CLAUDE_DEV_RUNTIME_ROOT?.trim(); -const siblingOrchestrator = path.resolve(repoRoot, '..', 'agent_teams_orchestrator'); const requestedOrder = process.env.PROVIDER_LAUNCH_STRESS_ORDER?.trim() || 'anthropic,codex,opencode,mixed'; @@ -31,8 +30,10 @@ const env = { }; if (!env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH?.trim()) { - const runtimeRoot = orchestratorRoot ? path.resolve(orchestratorRoot) : siblingOrchestrator; - env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = path.join(runtimeRoot, 'cli'); + env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH = resolveLiveSmokeOrchestratorCliPath({ + env, + repoRoot, + }); } console.log('Running provider launch stress live smoke'); diff --git a/test/scripts/liveSmokeRuntime.test.ts b/test/scripts/liveSmokeRuntime.test.ts new file mode 100644 index 00000000..3b8bc74d --- /dev/null +++ b/test/scripts/liveSmokeRuntime.test.ts @@ -0,0 +1,66 @@ +// @vitest-environment node + +import * as path from 'path'; +import { pathToFileURL } from 'url'; +import { describe, expect, it } from 'vitest'; + +interface LiveSmokeRuntimeModule { + resolveLiveSmokeOrchestratorCliPath(input?: { + env?: NodeJS.ProcessEnv; + repoRoot?: string; + }): string; + resolveReleaseSmokeOrchestratorCliPath(input?: { + env?: NodeJS.ProcessEnv; + repoRoot?: string; + }): string; +} + +describe('live smoke runtime launcher paths', () => { + const repoRoot = '/Users/belief/dev/projects/claude/claude_team'; + const siblingRuntimeRoot = '/Users/belief/dev/projects/claude/agent_teams_orchestrator'; + + it('defaults live smoke to the source launcher', async () => { + const { resolveLiveSmokeOrchestratorCliPath } = await loadModule(); + + expect(resolveLiveSmokeOrchestratorCliPath({ env: {}, repoRoot })).toBe( + path.join(siblingRuntimeRoot, 'cli-source') + ); + }); + + it('uses CLAUDE_DEV_RUNTIME_ROOT with cli-source for live smoke', async () => { + const { resolveLiveSmokeOrchestratorCliPath } = await loadModule(); + + expect( + resolveLiveSmokeOrchestratorCliPath({ + env: { CLAUDE_DEV_RUNTIME_ROOT: '/tmp/runtime-source' }, + repoRoot, + }) + ).toBe(path.join('/tmp/runtime-source', 'cli-source')); + }); + + it('keeps explicit CLI path overrides authoritative', async () => { + const { resolveLiveSmokeOrchestratorCliPath } = await loadModule(); + + expect( + resolveLiveSmokeOrchestratorCliPath({ + env: { CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH: ' /custom/runtime/cli ' }, + repoRoot, + }) + ).toBe('/custom/runtime/cli'); + }); + + it('keeps release smoke pointed at the built wrapper', async () => { + const { resolveReleaseSmokeOrchestratorCliPath } = await loadModule(); + + expect(resolveReleaseSmokeOrchestratorCliPath({ env: {}, repoRoot })).toBe( + path.join(siblingRuntimeRoot, 'cli') + ); + }); +}); + +async function loadModule(): Promise { + const moduleUrl = pathToFileURL( + path.join(process.cwd(), 'scripts/lib/live-smoke-runtime.mjs') + ).href; + return (await import(`${moduleUrl}?t=${Date.now()}`)) as LiveSmokeRuntimeModule; +}