diff --git a/packages/cli/src/commands/e2e.ts b/packages/cli/src/commands/e2e.ts index ecfd6ce..81eb56e 100755 --- a/packages/cli/src/commands/e2e.ts +++ b/packages/cli/src/commands/e2e.ts @@ -50,15 +50,30 @@ function runCmd(label: string, cmd: string, args: string[], cwd?: string, timeou /** * Run a bun test suite and return pass/fail. */ -function runTest(label: string, testPath: string): { pass: boolean; detail: string } { +function runTest(label: string, testPath: string, repoRoot: string): { pass: boolean; detail: string } { const bun = findBun() - const paths = testPath.split(' ').filter(p => p.length > 0) + const paths = testPath.split(' ').filter(p => p.length > 0).map(p => join(repoRoot, p.replace(/^\.\//, ''))) return runCmd(label, bun, ['test', ...paths]) } export function e2eCommand(): void { - const projectRoot = process.cwd() - console.log('Running E2E validation suite...\n') + // Find AirCoding repo root (where package.json + turbo.json exist) + let repoRoot = process.env.AIRCODING_REPO_ROOT || '' + if (!repoRoot) { + // Walk up from script location to find package.json + turbo.json + let dir = __dirname + for (let i = 0; i < 10; i++) { + if (existsSync(join(dir, 'package.json')) && existsSync(join(dir, 'turbo.json'))) { + repoRoot = dir + break + } + dir = join(dir, '..') + } + } + if (!repoRoot) repoRoot = process.cwd() + + console.log('Running E2E validation suite...') + console.log(` Repo: ${repoRoot}\n`) let passed = 0 let failed = 0 @@ -66,15 +81,15 @@ export function e2eCommand(): void { const gates: Array<{ label: string; fn: () => { pass: boolean; detail: string } }> = [ // P0: monorepo structure + depcruise (zero violations) + tsc (zero errors) { label: 'P0: Monorepo structure', fn: () => { - const pkg = existsSync(join(projectRoot, 'package.json')) && - existsSync(join(projectRoot, 'turbo.json')) && - existsSync(join(projectRoot, 'tsconfig.base.json')) + const pkg = existsSync(join(repoRoot, 'package.json')) && + existsSync(join(repoRoot, 'turbo.json')) && + existsSync(join(repoRoot, 'tsconfig.base.json')) return { pass: pkg, detail: pkg ? '✅' : '❌ (package.json/turbo.json/tsconfig.base.json missing)' } }}, { label: 'P0: depcruise dependency boundary (INV-4)', fn: () => { try { - execFileSync('node_modules/.bin/depcruise', ['--config', '.dependency-cruiser.js', 'packages/cli/src/', 'packages/contracts/src/', 'packages/llm/src/', 'packages/runtime/src/', 'packages/toolchain-cpp/src/', 'packages/tui/src/', 'packages/workers/src/'], { - encoding: 'utf-8', stdio: 'pipe', timeout: 60000 + execFileSync(join(repoRoot, 'node_modules/.bin/depcruise'), ['--config', join(repoRoot, '.dependency-cruiser.js'), join(repoRoot, 'packages/cli/src/'), join(repoRoot, 'packages/contracts/src/'), join(repoRoot, 'packages/llm/src/'), join(repoRoot, 'packages/runtime/src/'), join(repoRoot, 'packages/toolchain-cpp/src/'), join(repoRoot, 'packages/tui/src/'), join(repoRoot, 'packages/workers/src/')], { + cwd: repoRoot, encoding: 'utf-8', stdio: 'pipe', timeout: 60000 }) return { pass: true, detail: '✅' } } catch (err: any) { @@ -82,9 +97,9 @@ export function e2eCommand(): void { } }}, { label: 'P0: tsc strict typecheck (0 errors)', fn: () => { - const tsc = findTsc() + const tsc = join(repoRoot, 'node_modules/.bin/tsc') try { - execFileSync(tsc, ['--noEmit', '-p', 'tsconfig.check.json'], { encoding: 'utf-8', stdio: 'pipe', timeout: 90000 }) + execFileSync(tsc, ['--noEmit', '-p', join(repoRoot, 'tsconfig.check.json')], { cwd: repoRoot, encoding: 'utf-8', stdio: 'pipe', timeout: 90000 }) return { pass: true, detail: '✅' } } catch (err: any) { const stdout = err.stdout || '' @@ -94,35 +109,35 @@ export function e2eCommand(): void { } }}, - // P1: Storage/Events — run all 16 repository tests + migration tests - { label: 'P1: Storage/Events (test)', fn: () => runTest('P1', './packages/runtime/test/storage/ ./packages/runtime/test/regression/transaction-boundary.test.ts ./packages/runtime/test/regression/event-repository-route.test.ts') }, + // P1: Storage/Events + { label: 'P1: Storage/Events (test)', fn: () => runTest('P1', './packages/runtime/test/storage/ ./packages/runtime/test/regression/transaction-boundary.test.ts ./packages/runtime/test/regression/event-repository-route.test.ts', repoRoot) }, - // P2: Tools/Permission — 28 MVP tool registration tests - { label: 'P2: Tools/Permission (test)', fn: () => runTest('P2', './packages/runtime/test/regression/tool-stubs.test.ts ./packages/runtime/test/regression/permission-engine-actions.test.ts ./packages/runtime/test/regression/path-classifier-categories.test.ts ./packages/runtime/test/regression/command-risk-analyzer.test.ts') }, + // P2: Tools/Permission + { label: 'P2: Tools/Permission (test)', fn: () => runTest('P2', './packages/runtime/test/regression/tool-stubs.test.ts ./packages/runtime/test/regression/permission-engine-actions.test.ts ./packages/runtime/test/regression/path-classifier-categories.test.ts ./packages/runtime/test/regression/command-risk-analyzer.test.ts', repoRoot) }, // P3: Provider/Context - { label: 'P3: Provider/Context (test)', fn: () => runTest('P3', './packages/llm/test/ ./packages/runtime/test/regression/context-assembler-layers.test.ts') }, + { label: 'P3: Provider/Context (test)', fn: () => runTest('P3', './packages/llm/test/ ./packages/runtime/test/regression/context-assembler-layers.test.ts', repoRoot) }, // P4: Worker IPC - { label: 'P4: Worker IPC (test)', fn: () => runTest('P4', './packages/runtime/test/e2e/worker-fixture.test.ts ./packages/runtime/test/regression/worker-exit-code.test.ts ./packages/runtime/test/regression/worker-result-envelope.test.ts') }, + { label: 'P4: Worker IPC (test)', fn: () => runTest('P4', './packages/runtime/test/e2e/worker-fixture.test.ts ./packages/runtime/test/regression/worker-exit-code.test.ts ./packages/runtime/test/regression/worker-result-envelope.test.ts', repoRoot) }, // P5: C++ Toolchain - { label: 'P5: C++ Toolchain (test)', fn: () => runTest('P5', './packages/toolchain-cpp/test/') }, + { label: 'P5: C++ Toolchain (test)', fn: () => runTest('P5', './packages/toolchain-cpp/test/', repoRoot) }, // P6: Projection/TUI - { label: 'P6: Projection/TUI', fn: () => runTest('P6', './packages/runtime/test/regression/projection-store-apply.test.ts ./packages/runtime/test/regression/workspace-enum.test.ts') }, + { label: 'P6: Projection/TUI', fn: () => runTest('P6', './packages/runtime/test/regression/projection-store-apply.test.ts ./packages/runtime/test/regression/workspace-enum.test.ts', repoRoot) }, // P7: Agents - { label: 'P7: Agents (test)', fn: () => runTest('P7', './packages/runtime/test/e2e/direct-mode-fixture.test.ts ./packages/runtime/test/e2e/architecture-review-fixture.test.ts ./packages/runtime/test/regression/main-agent-states.test.ts') }, + { label: 'P7: Agents (test)', fn: () => runTest('P7', './packages/runtime/test/e2e/direct-mode-fixture.test.ts ./packages/runtime/test/e2e/architecture-review-fixture.test.ts ./packages/runtime/test/regression/main-agent-states.test.ts', repoRoot) }, // P8: Full regression suite - { label: 'P8: Full regression suite', fn: () => runTest('P8', './packages/runtime/test/regression/') }, + { label: 'P8: Full regression suite', fn: () => runTest('P8', './packages/runtime/test/regression/', repoRoot) }, // Security - { label: 'SEC: Command injection regression', fn: () => runTest('SEC', './packages/toolchain-cpp/test/command-injection.test.ts') }, + { label: 'SEC: Command injection regression', fn: () => runTest('SEC', './packages/toolchain-cpp/test/command-injection.test.ts', repoRoot) }, // Capability trust levels - { label: 'CAP: Capability trust regression', fn: () => runTest('CAP', './packages/runtime/test/regression/capability-trust-level.test.ts') }, + { label: 'CAP: Capability trust regression', fn: () => runTest('CAP', './packages/runtime/test/regression/capability-trust-level.test.ts', repoRoot) }, ] for (const gate of gates) { diff --git a/packages/llm/src/adapters/OpenAICompatibleAdapter.ts b/packages/llm/src/adapters/OpenAICompatibleAdapter.ts index f7bc878..c0987fa 100755 --- a/packages/llm/src/adapters/OpenAICompatibleAdapter.ts +++ b/packages/llm/src/adapters/OpenAICompatibleAdapter.ts @@ -104,7 +104,7 @@ export class OpenAICompatibleAdapter implements ProviderAdapter { yield { type: 'message_start', payload: { id: response.id, role: 'assistant' } } for (const choice of response.choices) { - const content = choice.message?.content + const content = choice.message?.content || (choice.message as any)?.reasoning || '' if (content) { yield { type: 'content_delta', payload: { type: 'text_delta', text: content, index: choice.index } } } @@ -128,8 +128,13 @@ export class OpenAICompatibleAdapter implements ProviderAdapter { max_tokens: options.max_tokens || 1024, stream: false, }) + const choice = response.choices[0] + // Some models (GLM) return content in reasoning field, not content + const content = choice?.message?.content + || (choice?.message as any)?.reasoning + || '' return { - content: response.choices[0]?.message?.content || '', + content, usage: response.usage ? { input_tokens: response.usage.prompt_tokens, output_tokens: response.usage.completion_tokens } : undefined, } }