@@ -91,75 +91,67 @@ function wrapPersonality(content: string): string {
}
export default function (pi: ExtensionAPI) {
+ // "active" tracks what the user last picked (persisted to disk).
+ // "session" is locked at session_start and never changes — it
+ // controls what goes into the system prompt for this session.
let activePersonality: string | null = null;
- let personalityContent: string | null = null;
- let isFirstTurn = true;
-
- /** Load personality by name. Returns true if found and loaded. */
- function loadPersonality(name: string): boolean {
- const content = readPersonalityContent(name);
- if (!content) return false;
+ let sessionPersonalityContent: string | null = null;
+ let pendingReminder: string | null = null;
+ /** Set the active personality name. Does NOT touch session prompt. */
+ function setActive(name: string | null): void {
activePersonality = name;
- personalityContent = content;
- return true;
- }
-
- /** Clear active personality (both in-memory and on disk). */
- function clearPersonality(): void {
- activePersonality = null;
- personalityContent = null;
- writeActivePersonality(null);
- }
-
- /** Switch to a personality: load into memory and persist to disk. */
- function switchPersonality(name: string): boolean {
- if (!loadPersonality(name)) return false;
writeActivePersonality(name);
- return true;
}
- // Restore persisted personality on session start
+ // Lock personality into the system prompt at session start.
+ // This content never changes for the lifetime of the session.
pi.on("session_start", async (_event, ctx) => {
const persisted = readActivePersonality();
-
- // Determine whether this is a fresh session or a resumed one
- const entries = ctx.sessionManager.getEntries();
- const hasMessages = entries.some((e) => e.type === "message" && e.message.role === "assistant");
- isFirstTurn = !hasMessages;
+ pendingReminder = null;
+ sessionPersonalityContent = null;
if (persisted) {
- if (loadPersonality(persisted)) {
+ const content = readPersonalityContent(persisted);
+ if (content) {
+ activePersonality = persisted;
+ sessionPersonalityContent = content;
ctx.ui.notify(`Set personality to "${persisted}"`, "info");
} else {
// Personality file was removed since last session
- clearPersonality();
+ setActive(null);
}
}
});
- // Inject personality into the system prompt or as a reminder message.
- // On the first turn (no prior messages), we modify the system prompt
- // directly so the personality is baked into the initial cache.
- // On subsequent turns, we inject a reminder message instead to avoid
- // invalidating the inference cache.
+ // Append the session personality to the system prompt (ephemeral,
+ // never persisted). The content is identical every turn so the
+ // provider cache stays valid.
+ //
+ // Mid-session switches only fire a one-shot message — they never
+ // alter the system prompt.
pi.on("before_agent_start", async (event) => {
- if (!personalityContent) return undefined;
+ if (!sessionPersonalityContent && !pendingReminder) return undefined;
- if (isFirstTurn) {
- isFirstTurn = false;
- return {
- systemPrompt: event.systemPrompt + wrapPersonality(personalityContent),
- };
+ const result: {
+ systemPrompt?: string;
+ message?: { customType: string; content: string; display: boolean };
+ } = {};
+
+ if (sessionPersonalityContent) {
+ result.systemPrompt = event.systemPrompt + wrapPersonality(sessionPersonalityContent);
}
- return {
- message: {
- customType: "system-reminder",
- content: wrapPersonality(personalityContent),
+ if (pendingReminder) {
+ result.message = {
+ customType: "personality-switch",
+ content: pendingReminder,
display: false,
- },
- };
+ };
+ pendingReminder = null;
+ }
+
+ return result;
});
// Register /personality command
@@ -180,7 +172,8 @@ export default function (pi: ExtensionAPI) {
return;
}
const was = activePersonality;
- clearPersonality();
+ pendingReminder = `Personality "${was}" has been cleared. Revert to your default behaviour.`;
+ setActive(null);
ctx.ui.notify(`Personality cleared (was: ${was})`, "info");
return;
}
@@ -192,7 +185,13 @@ export default function (pi: ExtensionAPI) {
ctx.ui.notify(`Personality "${arg}" not found`, "error");
return;
}
- switchPersonality(arg);
+ const content = readPersonalityContent(arg);
+ if (!content) {
+ ctx.ui.notify(`Personality "${arg}" could not be read`, "error");
+ return;
+ }
+ pendingReminder = wrapPersonality(content);
+ setActive(arg);
ctx.ui.notify(`Set personality to "${arg}"`, "info");
return;
}
@@ -220,13 +219,20 @@ export default function (pi: ExtensionAPI) {
ctx.ui.notify("No personality is active", "info");
} else {
const was = activePersonality;
- clearPersonality();
+ pendingReminder = `Personality "${was}" has been cleared. Revert to your default behaviour.`;
+ setActive(null);
ctx.ui.notify(`Personality cleared (was: ${was})`, "info");
}
return;
}
- switchPersonality(picked);
+ const content = readPersonalityContent(picked);
+ if (!content) {
+ ctx.ui.notify(`Personality "${picked}" could not be read`, "error");
+ return;
+ }
+ pendingReminder = wrapPersonality(content);
+ setActive(picked);
ctx.ui.notify(`Set personality to "${picked}"`, "info");
},
});