mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Fix slash command connection gating
This commit is contained in:
parent
2d490c6fca
commit
71feebcde0
7 changed files with 56 additions and 24 deletions
|
|
@ -172,11 +172,17 @@ export type SlashCommandName = SlashCommand extends `/${infer Name}` ? Name : ne
|
|||
|
||||
export type SlashCommandDefinition = (typeof SLASH_COMMANDS)[number];
|
||||
|
||||
const CONNECTION_INDEPENDENT_SLASH_COMMANDS = new Set<SlashCommandName>(["compact", "reconnect"]);
|
||||
|
||||
export interface SlashCommandHelpSection {
|
||||
readonly title: string;
|
||||
readonly auditFacts: readonly { key: string; value: string }[];
|
||||
}
|
||||
|
||||
export function slashCommandRequiresConnection(command: SlashCommandName): boolean {
|
||||
return !CONNECTION_INDEPENDENT_SLASH_COMMANDS.has(command);
|
||||
}
|
||||
|
||||
export function slashCommandDefinition(command: SlashCommandName): SlashCommandDefinition {
|
||||
const definition = SLASH_COMMANDS.find((item) => item.command === `/${command}`);
|
||||
if (!definition) throw new Error(`Unknown slash command: ${command}`);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { submissionStateSnapshot } from "../state/selectors";
|
|||
import type { ChatStateStore } from "../state/store";
|
||||
import { parseSlashCommand } from "../composer/suggestions";
|
||||
import type { SlashCommandExecutionResult } from "./slash-command-execution";
|
||||
import type { SlashCommandName } from "../composer/slash-commands";
|
||||
import { slashCommandRequiresConnection, type SlashCommandName } from "../composer/slash-commands";
|
||||
import type { ReferencedThreadMetadata } from "../../../../domain/threads/reference";
|
||||
|
||||
const STATUS_INTERRUPT_REQUESTED = "Interrupt requested.";
|
||||
|
|
@ -54,7 +54,7 @@ async function sendMessage(host: ComposerSubmitActionsHost): Promise<void> {
|
|||
|
||||
const slashCommand = parseSlashCommand(text);
|
||||
if (slashCommand) {
|
||||
if (!(await host.connection.connectedClient())) return;
|
||||
if (slashCommandRequiresConnection(slashCommand.command) && !(await host.connection.connectedClient())) return;
|
||||
host.composer.setDraft("", { clearSuggestions: true });
|
||||
const result = await host.slashCommandExecutor.execute(slashCommand.command, slashCommand.args);
|
||||
if (result?.composerDraft !== undefined) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
type SlashCommandSubcommandDefinition,
|
||||
} from "../composer/slash-commands";
|
||||
import type { MessageStreamAuditFact, MessageStreamNoticeSection } from "../../domain/message-stream/items";
|
||||
import { modelOverrideMessage, reasoningEffortOverrideMessage } from "../../presentation/runtime/messages";
|
||||
|
||||
const DEFAULT_RUNTIME_SETTING_ALIASES = new Set(["default", "reset", "clear", "off"]);
|
||||
|
||||
|
|
@ -92,16 +93,6 @@ function noActiveThreadToCompactMessage(): string {
|
|||
return "No active thread to compact.";
|
||||
}
|
||||
|
||||
function modelOverrideMessage(model: string | null): string {
|
||||
return model === null ? "Model reset to default for subsequent turns." : `Model set to ${model} for subsequent turns.`;
|
||||
}
|
||||
|
||||
function reasoningEffortOverrideMessage(effort: ReasoningEffort | null): string {
|
||||
return effort === null
|
||||
? "Reasoning effort reset to default for subsequent turns."
|
||||
: `Reasoning effort set to ${effort} for subsequent turns.`;
|
||||
}
|
||||
|
||||
export async function executeSlashCommand(
|
||||
command: SlashCommandName,
|
||||
args: string,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import type { RuntimeSnapshot } from "../../domain/runtime/snapshot";
|
|||
import { nextCollaborationMode, type CollaborationModeSelection, type RequestedFastMode } from "../../domain/runtime/intent";
|
||||
import type { ChatAction, ChatState } from "../state/root-reducer";
|
||||
import type { ChatStateStore } from "../state/store";
|
||||
import { modelOverrideMessage, reasoningEffortOverrideMessage } from "../../presentation/runtime/messages";
|
||||
|
||||
interface ApplyPendingThreadSettingsResult {
|
||||
ok: boolean;
|
||||
|
|
@ -183,16 +184,6 @@ async function setAutoReview(host: RuntimeSettingsActionsHost, mode: AutoReviewS
|
|||
host.addSystemMessage(autoReviewToggleMessage(mode));
|
||||
}
|
||||
|
||||
export function modelOverrideMessage(model: string | null): string {
|
||||
return model === null ? "Model reset to default for subsequent turns." : `Model set to ${model} for subsequent turns.`;
|
||||
}
|
||||
|
||||
export function reasoningEffortOverrideMessage(effort: ReasoningEffort | null): string {
|
||||
return effort === null
|
||||
? "Reasoning effort reset to default for subsequent turns."
|
||||
: `Reasoning effort set to ${effort} for subsequent turns.`;
|
||||
}
|
||||
|
||||
function fastModeToggleMessage(state: FastModeState): string {
|
||||
return state === "enabled" ? "Fast mode on for subsequent turns." : "Fast mode off for subsequent turns.";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,16 @@ export function collaborationModeLabel(mode: CollaborationModeSelection): string
|
|||
return mode === "plan" ? "Plan" : "Default";
|
||||
}
|
||||
|
||||
export function modelOverrideMessage(model: string | null): string {
|
||||
return model === null ? "Model reset to default for subsequent turns." : `Model set to ${model} for subsequent turns.`;
|
||||
}
|
||||
|
||||
export function reasoningEffortOverrideMessage(effort: ReasoningEffort | null): string {
|
||||
return effort === null
|
||||
? "Reasoning effort reset to default for subsequent turns."
|
||||
: `Reasoning effort set to ${effort} for subsequent turns.`;
|
||||
}
|
||||
|
||||
export function pendingRuntimeSettingLabel(
|
||||
setting: { kind: "unchanged" } | { kind: "set"; value: unknown } | { kind: "resetToConfig" },
|
||||
): string {
|
||||
|
|
|
|||
|
|
@ -79,6 +79,37 @@ describe("submitComposer", () => {
|
|||
expect(sendTurnText).toHaveBeenCalledWith("hello", undefined, undefined);
|
||||
});
|
||||
|
||||
it("does not execute connection-dependent slash commands when connection fails", async () => {
|
||||
const { host, connectedClient, execute, setDraft } = createHost("/clear");
|
||||
connectedClient.mockResolvedValue(null);
|
||||
|
||||
await submitComposer(host);
|
||||
|
||||
expect(connectedClient).toHaveBeenCalledOnce();
|
||||
expect(setDraft).not.toHaveBeenCalled();
|
||||
expect(execute).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("executes reconnect without a connected client preflight", async () => {
|
||||
const { host, connectedClient, execute, setDraft } = createHost("/reconnect");
|
||||
|
||||
await submitComposer(host);
|
||||
|
||||
expect(connectedClient).not.toHaveBeenCalled();
|
||||
expect(setDraft).toHaveBeenCalledWith("", { clearSuggestions: true });
|
||||
expect(execute).toHaveBeenCalledWith("reconnect", "");
|
||||
});
|
||||
|
||||
it("executes compact without a connected client preflight", async () => {
|
||||
const { host, connectedClient, execute, setDraft } = createHost("/compact");
|
||||
|
||||
await submitComposer(host);
|
||||
|
||||
expect(connectedClient).not.toHaveBeenCalled();
|
||||
expect(setDraft).toHaveBeenCalledWith("", { clearSuggestions: true });
|
||||
expect(execute).toHaveBeenCalledWith("compact", "");
|
||||
});
|
||||
|
||||
it("restores slash command composer drafts from command results", async () => {
|
||||
const { host, connectedClient, execute, sendTurnText, setDraft, showLatest } = createHost("/goal edit");
|
||||
execute.mockResolvedValue({ composerDraft: "/goal set Current objective" });
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ import { describe, expect, it } from "vitest";
|
|||
import { runtimeConfigSnapshotFromAppServerConfig, type ConfigReadResult } from "../../src/app-server/protocol/runtime-config";
|
||||
import type { RuntimeConfigSnapshot } from "../../src/domain/runtime/config";
|
||||
import type { ModelMetadata } from "../../src/domain/catalog/metadata";
|
||||
import { modelOverrideMessage, reasoningEffortOverrideMessage } from "../../src/features/chat/application/runtime/settings-actions";
|
||||
import { compactReasoningEffortLabel } from "../../src/features/chat/presentation/runtime/messages";
|
||||
import {
|
||||
compactReasoningEffortLabel,
|
||||
modelOverrideMessage,
|
||||
reasoningEffortOverrideMessage,
|
||||
} from "../../src/features/chat/presentation/runtime/messages";
|
||||
import {
|
||||
autoReviewActive,
|
||||
currentModel,
|
||||
|
|
|
|||
Loading…
Reference in a new issue