feat: inject toolset swap and slash-command context reminders (#1022)

This commit is contained in:
Charles Packer
2026-02-18 18:15:38 -08:00
committed by GitHub
parent f90244de82
commit a0a5bbfc72
10 changed files with 458 additions and 16 deletions

View File

@@ -85,6 +85,8 @@ import {
import { buildSharedReminderParts } from "../reminders/engine";
import {
createSharedReminderState,
enqueueCommandIoReminder,
enqueueToolsetChangeReminder,
resetSharedReminderState,
syncReminderStateFromContextTracker,
} from "../reminders/state";
@@ -95,6 +97,7 @@ import {
analyzeToolApproval,
checkToolPermission,
executeTool,
getToolNames,
releaseToolExecutionContext,
savePermissionRule,
type ToolExecutionResult,
@@ -117,7 +120,11 @@ import {
setActiveCommandId as setActiveProfileCommandId,
validateProfileLoad,
} from "./commands/profile";
import { type CommandHandle, createCommandRunner } from "./commands/runner";
import {
type CommandFinishedEvent,
type CommandHandle,
createCommandRunner,
} from "./commands/runner";
import { AgentSelector } from "./components/AgentSelector";
// ApprovalDialog removed - all approvals now render inline
import { ApprovalPreview } from "./components/ApprovalPreview";
@@ -2281,14 +2288,47 @@ export default function App({
commitEligibleLines(b);
}, [commitEligibleLines]);
const recordCommandReminder = useCallback((event: CommandFinishedEvent) => {
const input = event.input.trim();
if (!input.startsWith("/")) {
return;
}
enqueueCommandIoReminder(sharedReminderStateRef.current, {
input,
output: event.output,
success: event.success,
});
}, []);
const maybeRecordToolsetChangeReminder = useCallback(
(params: {
source: string;
previousToolset: string | null;
newToolset: string | null;
previousTools: string[];
newTools: string[];
}) => {
const toolsetChanged = params.previousToolset !== params.newToolset;
const previousSnapshot = params.previousTools.join("\n");
const nextSnapshot = params.newTools.join("\n");
const toolsChanged = previousSnapshot !== nextSnapshot;
if (!toolsetChanged && !toolsChanged) {
return;
}
enqueueToolsetChangeReminder(sharedReminderStateRef.current, params);
},
[],
);
const commandRunner = useMemo(
() =>
createCommandRunner({
buffersRef,
refreshDerived,
createId: uid,
onCommandFinished: recordCommandReminder,
}),
[refreshDerived],
[recordCommandReminder, refreshDerived],
);
const startOverlayCommand = useCallback(
@@ -10034,6 +10074,8 @@ ${SYSTEM_REMINDER_CLOSE}
const persistedToolsetPreference =
settingsManager.getToolsetPreference(agentId);
const previousToolsetSnapshot = currentToolset;
const previousToolNamesSnapshot = getToolNames();
let toolsetNoticeLine: string | null = null;
if (persistedToolsetPreference === "auto") {
@@ -10048,11 +10090,25 @@ ${SYSTEM_REMINDER_CLOSE}
"Auto toolset selected: switched to " +
toolsetName +
". Use /toolset to set a manual override.";
maybeRecordToolsetChangeReminder({
source: "/model (auto toolset)",
previousToolset: previousToolsetSnapshot,
newToolset: toolsetName,
previousTools: previousToolNamesSnapshot,
newTools: getToolNames(),
});
} else {
const { forceToolsetSwitch } = await import("../tools/toolset");
if (currentToolset !== persistedToolsetPreference) {
await forceToolsetSwitch(persistedToolsetPreference, agentId);
setCurrentToolset(persistedToolsetPreference);
maybeRecordToolsetChangeReminder({
source: "/model (manual toolset override)",
previousToolset: previousToolsetSnapshot,
newToolset: persistedToolsetPreference,
previousTools: previousToolNamesSnapshot,
newTools: getToolNames(),
});
}
setCurrentToolsetPreference(persistedToolsetPreference);
toolsetNoticeLine =
@@ -10093,6 +10149,7 @@ ${SYSTEM_REMINDER_CLOSE}
consumeOverlayCommand,
currentToolset,
isAgentBusy,
maybeRecordToolsetChangeReminder,
resetPendingReasoningCycle,
withCommandLock,
],
@@ -10297,6 +10354,8 @@ ${SYSTEM_REMINDER_CLOSE}
const { forceToolsetSwitch, switchToolsetForModel } = await import(
"../tools/toolset"
);
const previousToolsetSnapshot = currentToolset;
const previousToolNamesSnapshot = getToolNames();
if (toolsetId === "auto") {
const modelHandle =
@@ -10317,6 +10376,13 @@ ${SYSTEM_REMINDER_CLOSE}
settingsManager.setToolsetPreference(agentId, "auto");
setCurrentToolsetPreference("auto");
setCurrentToolset(derivedToolset);
maybeRecordToolsetChangeReminder({
source: "/toolset",
previousToolset: previousToolsetSnapshot,
newToolset: derivedToolset,
previousTools: previousToolNamesSnapshot,
newTools: getToolNames(),
});
cmd.finish(
`Toolset mode set to auto (currently ${derivedToolset}).`,
true,
@@ -10328,6 +10394,13 @@ ${SYSTEM_REMINDER_CLOSE}
settingsManager.setToolsetPreference(agentId, toolsetId);
setCurrentToolsetPreference(toolsetId);
setCurrentToolset(toolsetId);
maybeRecordToolsetChangeReminder({
source: "/toolset",
previousToolset: previousToolsetSnapshot,
newToolset: toolsetId,
previousTools: previousToolNamesSnapshot,
newTools: getToolNames(),
});
cmd.finish(
`Switched toolset to ${toolsetId} (manual override)`,
true,
@@ -10342,9 +10415,11 @@ ${SYSTEM_REMINDER_CLOSE}
agentId,
commandRunner,
consumeOverlayCommand,
currentToolset,
currentModelHandle,
isAgentBusy,
llmConfig,
maybeRecordToolsetChangeReminder,
withCommandLock,
],
);

View File

@@ -24,12 +24,22 @@ export type CommandHandle = {
fail: (output: string) => void;
};
export type CommandFinishedEvent = {
id: string;
input: string;
output: string;
success: boolean;
dimOutput?: boolean;
preformatted?: boolean;
};
type CreateId = (prefix: string) => string;
type RunnerDeps = {
buffersRef: MutableRefObject<Buffers>;
refreshDerived: () => void;
createId: CreateId;
onCommandFinished?: (event: CommandFinishedEvent) => void;
};
function upsertCommandLine(
@@ -56,13 +66,33 @@ export function createCommandRunner({
buffersRef,
refreshDerived,
createId,
onCommandFinished,
}: RunnerDeps) {
function getHandle(id: string, input: string): CommandHandle {
const update = (updateData: CommandUpdate) => {
const previous = buffersRef.current.byId.get(id);
const wasFinished =
previous?.kind === "command" && previous.phase === "finished";
upsertCommandLine(buffersRef.current, id, input, updateData);
if (!buffersRef.current.order.includes(id)) {
buffersRef.current.order.push(id);
}
const next = buffersRef.current.byId.get(id);
const becameFinished =
!wasFinished && next?.kind === "command" && next.phase === "finished";
if (becameFinished) {
onCommandFinished?.({
id,
input: next.input,
output: next.output,
success: next.success !== false,
dimOutput: next.dimOutput,
preformatted: next.preformatted,
});
}
refreshDerived();
};