fix: only deactivate on a standalone "stop ponytail" / "normal mode" (#162)
The deactivation check matched the phrase anywhere in the prompt, so an ordinary request like "add a normal mode toggle" silently turned ponytail off for the rest of the session. Match the whole message instead (trimmed, case-insensitive, trailing punctuation ignored) through a shared helper used by both the Claude/Codex hook and the pi extension. Fixes #161
This commit is contained in:
@@ -33,6 +33,15 @@ function normalizePersistedMode(mode) {
|
|||||||
return normalizeMode(mode) || normalizeConfigMode(mode);
|
return normalizeMode(mode) || normalizeConfigMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "stop ponytail" / "normal mode" turn ponytail off, but only as a standalone
|
||||||
|
// command. Matching the phrase anywhere in the message turned it off mid-task
|
||||||
|
// for ordinary requests like "add a normal mode toggle" — so require the whole
|
||||||
|
// message to be the command, ignoring case and trailing punctuation.
|
||||||
|
function isDeactivationCommand(text) {
|
||||||
|
const t = String(text || '').trim().toLowerCase().replace(/[.!?\s]+$/, '');
|
||||||
|
return t === 'stop ponytail' || t === 'normal mode';
|
||||||
|
}
|
||||||
|
|
||||||
function getConfigDir() {
|
function getConfigDir() {
|
||||||
if (process.env.XDG_CONFIG_HOME) {
|
if (process.env.XDG_CONFIG_HOME) {
|
||||||
return path.join(process.env.XDG_CONFIG_HOME, 'ponytail');
|
return path.join(process.env.XDG_CONFIG_HOME, 'ponytail');
|
||||||
@@ -98,5 +107,6 @@ module.exports = {
|
|||||||
normalizeMode,
|
normalizeMode,
|
||||||
normalizeConfigMode,
|
normalizeConfigMode,
|
||||||
normalizePersistedMode,
|
normalizePersistedMode,
|
||||||
|
isDeactivationCommand,
|
||||||
writeDefaultMode,
|
writeDefaultMode,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// ponytail — UserPromptSubmit hook to track which ponytail mode is active
|
// ponytail — UserPromptSubmit hook to track which ponytail mode is active
|
||||||
// Inspects user input for /ponytail commands and writes mode to flag file
|
// Inspects user input for /ponytail commands and writes mode to flag file
|
||||||
|
|
||||||
const { getDefaultMode } = require('./ponytail-config');
|
const { getDefaultMode, isDeactivationCommand } = require('./ponytail-config');
|
||||||
const { clearMode, setMode, writeHookOutput } = require('./ponytail-runtime');
|
const { clearMode, setMode, writeHookOutput } = require('./ponytail-runtime');
|
||||||
|
|
||||||
let input = '';
|
let input = '';
|
||||||
@@ -45,7 +45,7 @@ process.stdin.on('end', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Detect deactivation
|
// Detect deactivation
|
||||||
if (/\b(stop ponytail|normal mode)\b/i.test(prompt)) {
|
if (isDeactivationCommand(prompt)) {
|
||||||
clearMode();
|
clearMode();
|
||||||
writeHookOutput('UserPromptSubmit', 'off', 'PONYTAIL MODE OFF');
|
writeHookOutput('UserPromptSubmit', 'off', 'PONYTAIL MODE OFF');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const {
|
|||||||
normalizeMode,
|
normalizeMode,
|
||||||
normalizeConfigMode,
|
normalizeConfigMode,
|
||||||
normalizePersistedMode,
|
normalizePersistedMode,
|
||||||
|
isDeactivationCommand,
|
||||||
writeDefaultMode,
|
writeDefaultMode,
|
||||||
} = require("../hooks/ponytail-config.js");
|
} = require("../hooks/ponytail-config.js");
|
||||||
const { getPonytailInstructions, filterSkillBodyForMode } = require("../hooks/ponytail-instructions.js");
|
const { getPonytailInstructions, filterSkillBodyForMode } = require("../hooks/ponytail-instructions.js");
|
||||||
@@ -133,7 +134,7 @@ export default function ponytailExtension(pi) {
|
|||||||
if (event?.source === "extension") return;
|
if (event?.source === "extension") return;
|
||||||
|
|
||||||
const text = String(event?.text || "");
|
const text = String(event?.text || "");
|
||||||
if (currentMode !== "off" && /\b(stop ponytail|normal mode)\b/i.test(text)) {
|
if (currentMode !== "off" && isDeactivationCommand(text)) {
|
||||||
setMode("off");
|
setMode("off");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -121,3 +121,15 @@ test("normal mode disables persistent instructions", async () => withTempConfig(
|
|||||||
const disabled = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
const disabled = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
||||||
assert.equal(disabled, undefined);
|
assert.equal(disabled, undefined);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
test("a request mentioning normal mode stays active", async () => withTempConfig(async () => {
|
||||||
|
const { commands, events } = createPiHarness();
|
||||||
|
const ctx = createCommandContext();
|
||||||
|
|
||||||
|
await events.get("session_start")({ reason: "startup" }, ctx);
|
||||||
|
await commands.get("ponytail").handler("ultra", ctx);
|
||||||
|
await events.get("input")({ text: "add a normal mode toggle next to dark mode", source: "interactive" }, ctx);
|
||||||
|
|
||||||
|
const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
|
||||||
|
assert.match(result.systemPrompt, /PONYTAIL MODE ACTIVE/);
|
||||||
|
}));
|
||||||
|
|||||||
@@ -64,6 +64,23 @@ assert.equal(fs.existsSync(codexState), false);
|
|||||||
output = JSON.parse(result.stdout);
|
output = JSON.parse(result.stdout);
|
||||||
assert.equal(output.systemMessage, 'PONYTAIL:OFF');
|
assert.equal(output.systemMessage, 'PONYTAIL:OFF');
|
||||||
|
|
||||||
|
// A request that merely mentions "normal mode" must not deactivate ponytail.
|
||||||
|
result = run('ponytail-mode-tracker.js', codexEnv, JSON.stringify({ prompt: '@ponytail lite' }));
|
||||||
|
assert.equal(result.status, 0, result.stderr);
|
||||||
|
assert.equal(fs.readFileSync(codexState, 'utf8'), 'lite');
|
||||||
|
|
||||||
|
result = run(
|
||||||
|
'ponytail-mode-tracker.js',
|
||||||
|
codexEnv,
|
||||||
|
JSON.stringify({ prompt: 'add a normal mode toggle next to dark mode' }),
|
||||||
|
);
|
||||||
|
assert.equal(result.status, 0, result.stderr);
|
||||||
|
assert.equal(
|
||||||
|
fs.readFileSync(codexState, 'utf8'),
|
||||||
|
'lite',
|
||||||
|
'incidental "normal mode" in a request must not turn ponytail off',
|
||||||
|
);
|
||||||
|
|
||||||
const claudeEnv = {
|
const claudeEnv = {
|
||||||
HOME: home,
|
HOME: home,
|
||||||
USERPROFILE: home,
|
USERPROFILE: home,
|
||||||
|
|||||||
Reference in New Issue
Block a user