import { execFile } from 'node:child_process'; import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; import { describe, expect, it } from 'vitest'; import { generateAgentEnv, loadFleetRoster } from '../commands/fleet.js'; import { provisionInteractionService, readInteractionServiceProfile, } from './interaction-service-profile.js'; import type { InteractionServiceProfileError } from './interaction-service-profile.js'; const frameworkFleet = resolve( dirname(fileURLToPath(import.meta.url)), '..', '..', 'framework', 'fleet', ); const profilePath = join(frameworkFleet, 'services', 'operator-interaction.yaml'); const examplePath = join(frameworkFleet, 'examples', 'operator-interaction.yaml'); const policyScript = join( frameworkFleet, '..', 'tools', 'fleet', 'print-interaction-effective-policy.sh', ); const interactionStartScript = join( frameworkFleet, '..', 'tools', 'fleet', 'start-interaction-service.sh', ); const agentStartScript = join(frameworkFleet, '..', 'tools', 'fleet', 'start-agent-session.sh'); const execFileAsync = promisify(execFile); describe('operator interaction service profile', (): void => { it('provisions a user-supplied Nova identity as data with the required effective policy', async (): Promise => { const profile = await readInteractionServiceProfile(profilePath); const provisioned = provisionInteractionService(profile, { agentName: 'Nova' }); expect(provisioned.rosterAgent).toEqual({ name: 'Nova', runtime: 'pi', className: 'operator-interaction', modelHint: 'openai/gpt-5.6-sol', reasoningLevel: 'high', toolPolicy: 'operator-interaction', persistentPersona: true, }); expect(provisioned.effectivePolicy).toEqual({ agentName: 'Nova', runtime: 'pi', model: 'openai/gpt-5.6-sol', reasoning: 'high', toolPolicy: 'operator-interaction', }); expect(JSON.stringify(provisioned.effectivePolicy)).not.toMatch( /token|secret|credential|api.?key/i, ); }); it('accepts a renamed example roster and surfaces only its effective policy', async (): Promise => { const directory = await mkdtemp(join(tmpdir(), 'mosaic-interaction-profile-')); const rosterPath = join(directory, 'roster.yaml'); try { const example = await readFile(examplePath, 'utf8'); await writeFile(rosterPath, example.replace('name: Tess', 'name: Nova'), 'utf8'); const roster = await loadFleetRoster(rosterPath); const agent = roster.agents[0]!; expect(agent.name).toBe('Nova'); expect(agent.reasoningLevel).toBe('high'); expect(agent.toolPolicy).toBe('operator-interaction'); expect(generateAgentEnv(roster, agent)).toContain('MOSAIC_AGENT_NAME=Nova'); expect(generateAgentEnv(roster, agent)).toContain('MOSAIC_AGENT_REASONING=high'); const { stdout } = await execFileAsync(policyScript, [], { env: { ...process.env, MOSAIC_AGENT_NAME: agent.name, MOSAIC_AGENT_RUNTIME: agent.runtime, MOSAIC_AGENT_MODEL: agent.modelHint, MOSAIC_AGENT_REASONING: agent.reasoningLevel, MOSAIC_AGENT_TOOL_POLICY: agent.toolPolicy, }, }); expect(JSON.parse(stdout)).toEqual({ agentName: 'Nova', runtime: 'pi', model: 'openai/gpt-5.6-sol', reasoning: 'high', toolPolicy: 'operator-interaction', }); } finally { await rm(directory, { recursive: true, force: true }); } }); it('fails before launch when service environment drifts from the pinned policy', async (): Promise => { await expect( execFileAsync(interactionStartScript, ['Nova'], { env: { ...process.env, MOSAIC_AGENT_NAME: 'Nova', MOSAIC_AGENT_RUNTIME: 'pi', MOSAIC_AGENT_MODEL: 'other/model', MOSAIC_AGENT_REASONING: 'high', MOSAIC_AGENT_TOOL_POLICY: 'operator-interaction', }, }), ).rejects.toMatchObject({ code: 64 }); }); it('rejects unsafe names from the policy printer and reasoning before tmux launch', async (): Promise => { await expect( execFileAsync(policyScript, [], { env: { ...process.env, MOSAIC_AGENT_NAME: 'Nova"bad', MOSAIC_AGENT_RUNTIME: 'pi', MOSAIC_AGENT_MODEL: 'openai/gpt-5.6-sol', MOSAIC_AGENT_REASONING: 'high', MOSAIC_AGENT_TOOL_POLICY: 'operator-interaction', }, }), ).rejects.toMatchObject({ code: 64 }); await expect( execFileAsync(agentStartScript, ['Nova'], { env: { ...process.env, MOSAIC_AGENT_REASONING: 'high; id' }, }), ).rejects.toMatchObject({ code: 64 }); }); it('keeps the product name confined to an example instance, not service source or defaults', async (): Promise => { const [profile, source, example] = await Promise.all([ readFile(profilePath, 'utf8'), readFile(new URL('./interaction-service-profile.ts', import.meta.url), 'utf8'), readFile(examplePath, 'utf8'), ]); expect(profile).not.toMatch(/tess/i); expect(source).not.toMatch(/tess/i); expect(example).toMatch(/name: Tess/); }); it('fails fast when a required policy field is absent or changed from the pinned policy', async (): Promise => { await expect(readInteractionServiceProfile(profilePath, { model: '' })).rejects.toMatchObject({ code: 'invalid_profile', } satisfies Partial); await expect( readInteractionServiceProfile(profilePath, { reasoning: 'medium' }), ).rejects.toMatchObject({ code: 'invalid_profile', } satisfies Partial); }); });