64 lines
2.6 KiB
TypeScript
64 lines
2.6 KiB
TypeScript
/**
|
|
* Persona-contract injection at launch (North Star A3b).
|
|
*
|
|
* A spawned fleet agent should boot already knowing WHO it is: its class's role
|
|
* contract (mandate + boundaries). The companion goal A3a exports the agent's
|
|
* resolved class into the pane env as `MOSAIC_AGENT_CLASS`; here we read that
|
|
* class at launch (composeContract → system prompt) and inject the resolved
|
|
* persona contract so the identity is resident from the agent's first turn.
|
|
*
|
|
* OVERRIDE-AWARE: resolution goes through fleet-personas' resolver, so a
|
|
* user-customized persona in the PRESERVE-protected `fleet/roles.local/` layer
|
|
* WINS over the baseline `fleet/roles/` of the same class. That is the
|
|
* launch-time proof of AC-NS-7 — a customized persona actually reaches the model
|
|
* when the agent boots, not just in `mosaic fleet persona show`.
|
|
*
|
|
* Tolerant by contract (mirrors readFleetCommsBlock): an empty/missing class, an
|
|
* unknown class, or a missing role file all yield '' so the launcher no-ops
|
|
* silently. This MUST never throw during launch.
|
|
*
|
|
* Standalone module (no fleet.ts import) to keep launch.ts's prompt path free of
|
|
* the heavy fleet command module; it depends only on the lightweight persona
|
|
* resolver.
|
|
*/
|
|
|
|
import {
|
|
resolvePersonaSync,
|
|
defaultRolesDir,
|
|
defaultOverrideDir,
|
|
} from '../commands/fleet-personas.js';
|
|
|
|
/**
|
|
* Resolve `klass`'s persona contract (override-aware) and render it as a
|
|
* clearly-delimited launch block. Returns '' on any miss (falsy class, unknown
|
|
* class, missing/unreadable file) so composeContract can push it unconditionally
|
|
* and have it no-op silently. Never throws.
|
|
*/
|
|
export function readPersonaContractBlock(mosaicHome: string, klass: string | undefined): string {
|
|
if (!klass || !klass.trim()) return '';
|
|
let resolved: ReturnType<typeof resolvePersonaSync>;
|
|
try {
|
|
resolved = resolvePersonaSync(klass.trim(), {
|
|
rolesDir: defaultRolesDir(mosaicHome),
|
|
overrideDir: defaultOverrideDir(mosaicHome),
|
|
});
|
|
} catch {
|
|
// Best-effort onboarding: a resolver hiccup must not abort the launch.
|
|
return '';
|
|
}
|
|
if (!resolved) return '';
|
|
|
|
const layerNote =
|
|
resolved.layer === 'override'
|
|
? '_(resolved from the `fleet/roles.local/` override layer — wins over baseline)_'
|
|
: '_(resolved from the baseline `fleet/roles/` layer)_';
|
|
|
|
return `# Persona Contract (${resolved.klass})
|
|
|
|
${layerNote}
|
|
|
|
You are operating as the **${resolved.klass}** persona. The role contract below is your identity — its mandate and boundaries govern what you own and what you must not do for this assignment.
|
|
|
|
${resolved.content.trim()}`;
|
|
}
|