// P3: run real harness exports through the plug-in loader, with made-up session keys only. import test from 'node:test'; import assert from 'node:assert/strict'; import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { createLoader } from '../src/loader.mjs'; import { sessionEnv } from '../../seat/src/session.mjs'; async function harness(name) { const loader = createLoader({ log() {} }); const result = await loader.load({ harness: [name] }); assert.deepEqual(result.refused, []); return loader.modules('harness')[0]; } function scratch(t) { const root = mkdtempSync(join(tmpdir(), 'p332-')); t.after(() => rmSync(root, { recursive: true, force: true })); return root; } const policy = { tools: [], typed: [] }; const session = { model: 'claude-fixture', thinking: 'low' }; test('P3 Claude env receives the session proxy URL and key, without another agent or host credential', async () => { const claude = await harness('claude-code'); const host = { CLAUDE_CODE_OAUTH_TOKEN: 'fx332-host', OPENAI_API_KEY: 'fx332-upstream' }; for (const [run, key] of [['r332a', 'fx332-agent-a'], ['r332b', 'fx332-agent-b']]) { const proxy = { baseUrl: 'http://127.0.0.1:12345', key }; const env = { ...sessionEnv(host, run, proxy), ...claude.env({ files: {}, policy, session, proxy }) }; assert.equal(env.ANTHROPIC_BASE_URL, 'http://127.0.0.1:12345'); assert.equal(env.ANTHROPIC_AUTH_TOKEN, key); assert.equal(Object.hasOwn(env, 'CLAUDE_CODE_OAUTH_TOKEN'), false); assert.equal(Object.hasOwn(env, 'OPENAI_API_KEY'), false); const other = run === 'r332a' ? 'fx332-agent-b' : 'fx332-agent-a'; assert.ok(!Object.values(env).includes(other)); } }); test('P3 Claude preflight accepts a proxy key without a host provider credential', async t => { const claude = await harness('claude-code'); const root = scratch(t), binary = join(root, 'claude'); writeFileSync(binary, '#!/bin/sh\nexit 0\n'); chmodSync(binary, 0o700); assert.equal(claude.preflight({ env: { MOSAIC_CLAUDE_BIN: binary }, proxy: { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a', } }), null); }); test('P3 pi refuses no-session-credential with no provider key', async () => { const pi = await harness('pi'); assert.equal(pi.preflight({ env: {} })?.code, 'no-session-credential'); }); test('P3 pi refuses no-session-credential with only a Claude OAuth token', async () => { const pi = await harness('pi'); assert.equal(pi.preflight({ env: { CLAUDE_CODE_OAUTH_TOKEN: 'fx332-oauth' } })?.code, 'no-session-credential'); }); test('P3 pi accepts its own provider key or a session proxy key', async () => { const pi = await harness('pi'); for (const name of ['ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'OPENROUTER_API_KEY', 'ZAI_API_KEY']) { assert.equal(pi.preflight({ env: { [name]: 'fx332-own' } }), null, name); } assert.equal(pi.preflight({ env: {}, proxy: { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' } }), null); }); // Row 332 R2 (#1830): callers that only want pi's bundle sources call bundle() with no argument, // as they could before P3 (packages/cli/tests/stack-copy.test.mjs). It returns the non-proxy bundle. test('row 332 R2 pi bundle() with no argument returns the non-proxy bundle it returned before P3', async () => { const pi = await harness('pi'); const base = pi.bundle(); assert.deepEqual(base.files, {}); assert.deepEqual(base.digests, []); assert.deepEqual(Object.keys(base.sources).sort(), ['piExtension', 'unslopCheck', 'unslopExtension', 'unslopLists']); assert.deepEqual(pi.bundle({ dir: '/nonexistent/bundle', models: {} }), base, 'same as a call with no proxy context'); });