Compare commits
2 Commits
docs/north
...
feat/a3a-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7170aa1114 | ||
|
|
65ab835150 |
@@ -246,6 +246,8 @@ describe('fleet roster parsing', () => {
|
|||||||
expect(generateAgentEnv(roster, getRosterAgent(roster, 'coder0'))).toBe(
|
expect(generateAgentEnv(roster, getRosterAgent(roster, 'coder0'))).toBe(
|
||||||
[
|
[
|
||||||
'MOSAIC_AGENT_NAME=coder0',
|
'MOSAIC_AGENT_NAME=coder0',
|
||||||
|
// Reflects the roster's non-default `class: implementer` (A3a).
|
||||||
|
'MOSAIC_AGENT_CLASS=implementer',
|
||||||
'MOSAIC_AGENT_RUNTIME=codex',
|
'MOSAIC_AGENT_RUNTIME=codex',
|
||||||
'MOSAIC_AGENT_MODEL=',
|
'MOSAIC_AGENT_MODEL=',
|
||||||
'MOSAIC_AGENT_WORKDIR=/srv/mosaic',
|
'MOSAIC_AGENT_WORKDIR=/srv/mosaic',
|
||||||
@@ -255,6 +257,40 @@ describe('fleet roster parsing', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('emits MOSAIC_AGENT_CLASS=worker for an agent that declares no class', async () => {
|
||||||
|
cleanup = await tempDir();
|
||||||
|
const rosterPath = join(cleanup, 'roster.json');
|
||||||
|
await writeFile(
|
||||||
|
rosterPath,
|
||||||
|
JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
transport: 'tmux',
|
||||||
|
agents: [{ name: 'coder0', runtime: 'codex' }],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const roster = await loadFleetRoster(rosterPath);
|
||||||
|
expect(generateAgentEnv(roster, getRosterAgent(roster, 'coder0'))).toContain(
|
||||||
|
'MOSAIC_AGENT_CLASS=worker\n',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shell-escapes MOSAIC_AGENT_CLASS so a launcher reads it verbatim', async () => {
|
||||||
|
cleanup = await tempDir();
|
||||||
|
const rosterPath = join(cleanup, 'roster.json');
|
||||||
|
await writeFile(
|
||||||
|
rosterPath,
|
||||||
|
JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
transport: 'tmux',
|
||||||
|
agents: [{ name: 'coder0', runtime: 'codex', class: 'orchestrator' }],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const roster = await loadFleetRoster(rosterPath);
|
||||||
|
expect(generateAgentEnv(roster, getRosterAgent(roster, 'coder0'))).toContain(
|
||||||
|
'MOSAIC_AGENT_CLASS=orchestrator\n',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('preserves site-owned agent EnvironmentFile overrides while refreshing roster keys', () => {
|
it('preserves site-owned agent EnvironmentFile overrides while refreshing roster keys', () => {
|
||||||
const generated = [
|
const generated = [
|
||||||
'MOSAIC_AGENT_NAME=coder0',
|
'MOSAIC_AGENT_NAME=coder0',
|
||||||
@@ -286,6 +322,28 @@ describe('fleet roster parsing', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('updates (does not duplicate) MOSAIC_AGENT_CLASS on re-launch', () => {
|
||||||
|
const generated = [
|
||||||
|
'MOSAIC_AGENT_NAME=coder0',
|
||||||
|
'MOSAIC_AGENT_CLASS=orchestrator',
|
||||||
|
'MOSAIC_AGENT_RUNTIME=codex',
|
||||||
|
'',
|
||||||
|
].join('\n');
|
||||||
|
const existing = [
|
||||||
|
'MOSAIC_AGENT_NAME=coder0',
|
||||||
|
'MOSAIC_AGENT_CLASS=worker',
|
||||||
|
'MOSAIC_AGENT_RUNTIME=codex',
|
||||||
|
'',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const merged = mergeAgentEnv(generated, existing);
|
||||||
|
// mergeAgentEnv keys by VAR name, so the regenerated CLASS wins and there is
|
||||||
|
// exactly one MOSAIC_AGENT_CLASS line (no stale worker value left behind).
|
||||||
|
expect(merged).toContain('MOSAIC_AGENT_CLASS=orchestrator');
|
||||||
|
expect(merged).not.toContain('MOSAIC_AGENT_CLASS=worker');
|
||||||
|
expect(merged.match(/^MOSAIC_AGENT_CLASS=/gm)).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('rejects unknown roster fields instead of silently defaulting', async () => {
|
it('rejects unknown roster fields instead of silently defaulting', async () => {
|
||||||
cleanup = await tempDir();
|
cleanup = await tempDir();
|
||||||
const rosterPath = join(cleanup, 'roster.yaml');
|
const rosterPath = join(cleanup, 'roster.yaml');
|
||||||
|
|||||||
@@ -490,6 +490,9 @@ export function generateAgentEnv(roster: FleetRoster, agent: FleetAgent): string
|
|||||||
const workingDirectory = agent.workingDirectory ?? roster.defaults.workingDirectory;
|
const workingDirectory = agent.workingDirectory ?? roster.defaults.workingDirectory;
|
||||||
return [
|
return [
|
||||||
`MOSAIC_AGENT_NAME=${shellEnvValue(agent.name)}`,
|
`MOSAIC_AGENT_NAME=${shellEnvValue(agent.name)}`,
|
||||||
|
// Per-agent class → start-agent-session.sh / launcher reads this to inject the
|
||||||
|
// matching persona contract for the agent's class (default `worker`).
|
||||||
|
`MOSAIC_AGENT_CLASS=${shellEnvValue(agent.className)}`,
|
||||||
`MOSAIC_AGENT_RUNTIME=${shellEnvValue(agent.runtime)}`,
|
`MOSAIC_AGENT_RUNTIME=${shellEnvValue(agent.runtime)}`,
|
||||||
// Per-agent model hint → start-agent-session.sh appends `--model <hint>` to
|
// Per-agent model hint → start-agent-session.sh appends `--model <hint>` to
|
||||||
// the `mosaic yolo` launch so workers run on the roster's model (e.g. pi on
|
// the `mosaic yolo` launch so workers run on the roster's model (e.g. pi on
|
||||||
|
|||||||
Reference in New Issue
Block a user