merge(cli): intègre feature/163-slash-command-autocomplete — menu slash-commands #163 (QA verte)
UX d'autocomplétion slash dans le composer custom, consommant le contrat unifié de #162 (aucune liste codée en dur). Débloque #164 (/profile). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -203,6 +203,56 @@ describe("TauriAgentGateway invoke payloads", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("listSlashCommands forwards the unified slash-command filter request and unwraps commands", async () => {
|
||||
invoke.mockResolvedValueOnce({
|
||||
commands: [
|
||||
{
|
||||
name: "/clean",
|
||||
shortDescription: "Clean current conversation",
|
||||
requiresConfirmation: false,
|
||||
availability: { status: "available" },
|
||||
source: "native",
|
||||
native: "clean",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const out = await new TauriAgentGateway().listSlashCommands("/c");
|
||||
|
||||
expect(invoke).toHaveBeenCalledWith("list_slash_commands", {
|
||||
request: { prefix: "/c" },
|
||||
});
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0].name).toBe("/clean");
|
||||
});
|
||||
|
||||
it("executeSlashCommand wraps the selected command and current session id", async () => {
|
||||
invoke.mockResolvedValueOnce({
|
||||
command: {
|
||||
name: "/clean",
|
||||
shortDescription: "Clean current conversation",
|
||||
requiresConfirmation: false,
|
||||
availability: { status: "available" },
|
||||
source: "native",
|
||||
native: "clean",
|
||||
},
|
||||
effect: {
|
||||
kind: "cleanConversation",
|
||||
sessionId: "chat-session-1",
|
||||
clearedChunks: 2,
|
||||
},
|
||||
});
|
||||
|
||||
const out = await new TauriAgentGateway().executeSlashCommand("/clean", {
|
||||
sessionId: "chat-session-1",
|
||||
});
|
||||
|
||||
expect(invoke).toHaveBeenCalledWith("execute_slash_command", {
|
||||
request: { name: "/clean", sessionId: "chat-session-1" },
|
||||
});
|
||||
expect(out.effect.kind).toBe("cleanConversation");
|
||||
});
|
||||
|
||||
it("launchAgentChat returns a handle only when launch_agent confirms cellKind chat", async () => {
|
||||
invoke.mockResolvedValueOnce({
|
||||
sessionId: "chat-session-1",
|
||||
|
||||
@ -19,8 +19,10 @@ import type {
|
||||
AgentContextDocument,
|
||||
EffortSelection,
|
||||
GatewayError,
|
||||
ExecuteSlashCommandResult,
|
||||
ReplyChunk,
|
||||
ResumableAgent,
|
||||
SlashCommand,
|
||||
TerminalSession,
|
||||
} from "@/domain";
|
||||
import type {
|
||||
@ -274,6 +276,26 @@ export class TauriAgentGateway implements AgentGateway {
|
||||
});
|
||||
}
|
||||
|
||||
async listSlashCommands(prefix?: string): Promise<SlashCommand[]> {
|
||||
const request = prefix === undefined ? {} : { prefix };
|
||||
const res = await invoke<{ commands: SlashCommand[] }>("list_slash_commands", {
|
||||
request,
|
||||
});
|
||||
return res.commands;
|
||||
}
|
||||
|
||||
async executeSlashCommand(
|
||||
name: string,
|
||||
options: { sessionId?: string | null } = {},
|
||||
): Promise<ExecuteSlashCommandResult> {
|
||||
return invoke<ExecuteSlashCommandResult>("execute_slash_command", {
|
||||
request: {
|
||||
name,
|
||||
sessionId: options.sessionId ?? null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async closeAgentChat(sessionId: string): Promise<void> {
|
||||
await invoke("close_agent_session", { sessionId });
|
||||
}
|
||||
|
||||
@ -79,6 +79,8 @@ import type {
|
||||
ServerExposureSettings,
|
||||
Skill,
|
||||
ReplyChunk,
|
||||
ExecuteSlashCommandResult,
|
||||
SlashCommand,
|
||||
SkillScope,
|
||||
Sprint,
|
||||
Template,
|
||||
@ -399,6 +401,35 @@ export class MockAgentGateway implements AgentGateway {
|
||||
private liveKindByAgent = new Map<string, "pty" | "structured">();
|
||||
/** Retained structured reply chunks per live chat session. */
|
||||
private chatScrollback = new Map<string, ReplyChunk[]>();
|
||||
private slashCommands: SlashCommand[] = [
|
||||
{
|
||||
name: "/help",
|
||||
shortDescription: "Afficher les commandes disponibles",
|
||||
requiresConfirmation: false,
|
||||
availability: { status: "available" },
|
||||
source: "native",
|
||||
native: "help",
|
||||
},
|
||||
{
|
||||
name: "/clean",
|
||||
shortDescription: "Nettoyer la conversation courante",
|
||||
requiresConfirmation: false,
|
||||
availability: { status: "available" },
|
||||
source: "native",
|
||||
native: "clean",
|
||||
},
|
||||
{
|
||||
name: "/profile",
|
||||
shortDescription: "Changer le profil de l'agent",
|
||||
requiresConfirmation: true,
|
||||
availability: {
|
||||
status: "unavailable",
|
||||
reason: "La selection de profil est livree par le ticket #164",
|
||||
},
|
||||
source: "native",
|
||||
native: "profile",
|
||||
},
|
||||
];
|
||||
|
||||
private getAgents(projectId: string): Agent[] {
|
||||
if (!this.agents.has(projectId)) this.agents.set(projectId, []);
|
||||
@ -862,6 +893,61 @@ export class MockAgentGateway implements AgentGateway {
|
||||
}
|
||||
}
|
||||
|
||||
async listSlashCommands(prefix?: string): Promise<SlashCommand[]> {
|
||||
const normalized = prefix?.trim();
|
||||
const query = normalized
|
||||
? normalized.startsWith("/")
|
||||
? normalized
|
||||
: `/${normalized}`
|
||||
: "";
|
||||
return structuredClone(
|
||||
this.slashCommands.filter((command) =>
|
||||
query ? command.name.startsWith(query) : true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async executeSlashCommand(
|
||||
name: string,
|
||||
options: { sessionId?: string | null } = {},
|
||||
): Promise<ExecuteSlashCommandResult> {
|
||||
const normalized = name.trim().startsWith("/") ? name.trim() : `/${name.trim()}`;
|
||||
const command = this.slashCommands.find((item) => item.name === normalized);
|
||||
if (!command) {
|
||||
throw { code: "NOT_FOUND", message: `slash command ${normalized}` } as GatewayError;
|
||||
}
|
||||
if (command.availability.status === "unavailable") {
|
||||
throw {
|
||||
code: "INVALID",
|
||||
message: `slash command ${normalized} unavailable: ${command.availability.reason}`,
|
||||
} as GatewayError;
|
||||
}
|
||||
if (command.native === "clean") {
|
||||
if (!options.sessionId) {
|
||||
throw {
|
||||
code: "INVALID",
|
||||
message: "/clean requires a current session id",
|
||||
} as GatewayError;
|
||||
}
|
||||
this.chatScrollback.set(options.sessionId, []);
|
||||
return {
|
||||
command: structuredClone(command),
|
||||
effect: {
|
||||
kind: "cleanConversation",
|
||||
sessionId: options.sessionId,
|
||||
clearedChunks: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
command: structuredClone(command),
|
||||
effect: {
|
||||
kind: "help",
|
||||
commands: structuredClone(this.slashCommands),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async closeAgentChat(sessionId: string): Promise<void> {
|
||||
this.chatScrollback.delete(sessionId);
|
||||
for (const [agentId, liveSessionId] of this.liveSessionByAgent) {
|
||||
|
||||
@ -1681,6 +1681,35 @@ export type ReplyChunk =
|
||||
| { kind: "final"; content: string }
|
||||
| { kind: "error"; message: string };
|
||||
|
||||
export type SlashCommandAvailability =
|
||||
| { status: "available" }
|
||||
| { status: "unavailable"; reason: string };
|
||||
|
||||
export type SlashCommandSource =
|
||||
| "native"
|
||||
| { plugin: { pluginId: string } };
|
||||
|
||||
export type NativeSlashCommand = "help" | "clean" | "profile";
|
||||
|
||||
export interface SlashCommand {
|
||||
name: string;
|
||||
shortDescription: string;
|
||||
requiresConfirmation: boolean;
|
||||
availability: SlashCommandAvailability;
|
||||
source: SlashCommandSource;
|
||||
native?: NativeSlashCommand;
|
||||
}
|
||||
|
||||
export type SlashCommandEffect =
|
||||
| { kind: "help"; commands: SlashCommand[] }
|
||||
| { kind: "cleanConversation"; sessionId: string; clearedChunks: number }
|
||||
| { kind: "profileSwitch"; sessionId: string };
|
||||
|
||||
export interface ExecuteSlashCommandResult {
|
||||
command: SlashCommand;
|
||||
effect: SlashCommandEffect;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Paired devices + pairing code (ticket #77)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -380,6 +380,148 @@ describe("CustomAgentChatView", () => {
|
||||
expect(screen.getByText("Progress").parentElement?.textContent).toContain("done");
|
||||
});
|
||||
|
||||
it("shows slash-command suggestions from the gateway, filters by prefix, and inserts the selected command", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
reattachAgentChat: vi.fn(async (sessionId: string) => ({
|
||||
sessionId,
|
||||
scrollback: [],
|
||||
})),
|
||||
sendAgentChat: vi.fn(async () => {}),
|
||||
listSlashCommands: vi.fn(async (prefix?: string) =>
|
||||
[
|
||||
{
|
||||
name: "/clean",
|
||||
shortDescription: "Nettoyer la conversation courante",
|
||||
requiresConfirmation: false,
|
||||
availability: { status: "available" as const },
|
||||
source: "native" as const,
|
||||
native: "clean" as const,
|
||||
},
|
||||
{
|
||||
name: "/commit",
|
||||
shortDescription: "Préparer un commit",
|
||||
requiresConfirmation: true,
|
||||
availability: { status: "available" as const },
|
||||
source: { plugin: { pluginId: "git" } },
|
||||
},
|
||||
].filter((command) => !prefix || command.name.startsWith(prefix)),
|
||||
),
|
||||
cancelAgentChat: vi.fn(async () => {}),
|
||||
closeAgentChat: vi.fn(async () => {}),
|
||||
};
|
||||
|
||||
render(
|
||||
<DIProvider
|
||||
gateways={{
|
||||
agent,
|
||||
system: { pickFile: vi.fn(async () => null) },
|
||||
} as unknown as Gateways}
|
||||
>
|
||||
<CustomAgentChatView
|
||||
projectId="project-1"
|
||||
agentId="agent-1"
|
||||
agentName="Worker"
|
||||
profile={profile}
|
||||
cwd="/repo"
|
||||
nodeId="node-1"
|
||||
sessionId="chat-session-1"
|
||||
conversationId="conversation-1"
|
||||
onSessionId={vi.fn()}
|
||||
onConversationId={vi.fn()}
|
||||
/>
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(agent.reattachAgentChat).toHaveBeenCalledWith(
|
||||
"chat-session-1",
|
||||
expect.any(Function),
|
||||
),
|
||||
);
|
||||
|
||||
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
|
||||
fireEvent.change(composer, { target: { value: "/c" } });
|
||||
|
||||
const menu = await screen.findByRole("listbox", {
|
||||
name: "suggestions commandes slash",
|
||||
});
|
||||
expect(agent.listSlashCommands).toHaveBeenLastCalledWith("/c");
|
||||
expect(screen.getByRole("option", { name: /\/clean/ })).toBeTruthy();
|
||||
expect(screen.getByRole("option", { name: /\/commit/ })).toBeTruthy();
|
||||
|
||||
fireEvent.keyDown(composer, { key: "ArrowDown" });
|
||||
fireEvent.keyDown(composer, { key: "Enter" });
|
||||
|
||||
await waitFor(() => expect(composer.value).toBe("/commit "));
|
||||
expect(menu).toBeTruthy();
|
||||
expect(screen.queryByRole("listbox", { name: "suggestions commandes slash" })).toBeNull();
|
||||
expect(agent.sendAgentChat).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("only opens slash-command suggestions at the start of an unfinished slash token", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
reattachAgentChat: vi.fn(async (sessionId: string) => ({
|
||||
sessionId,
|
||||
scrollback: [],
|
||||
})),
|
||||
sendAgentChat: vi.fn(async () => {}),
|
||||
listSlashCommands: vi.fn(async () => [
|
||||
{
|
||||
name: "/clean",
|
||||
shortDescription: "Nettoyer la conversation courante",
|
||||
requiresConfirmation: false,
|
||||
availability: { status: "available" as const },
|
||||
source: "native" as const,
|
||||
native: "clean" as const,
|
||||
},
|
||||
]),
|
||||
cancelAgentChat: vi.fn(async () => {}),
|
||||
closeAgentChat: vi.fn(async () => {}),
|
||||
};
|
||||
|
||||
render(
|
||||
<DIProvider
|
||||
gateways={{
|
||||
agent,
|
||||
system: { pickFile: vi.fn(async () => null) },
|
||||
} as unknown as Gateways}
|
||||
>
|
||||
<CustomAgentChatView
|
||||
projectId="project-1"
|
||||
agentId="agent-1"
|
||||
agentName="Worker"
|
||||
profile={profile}
|
||||
cwd="/repo"
|
||||
nodeId="node-1"
|
||||
sessionId="chat-session-1"
|
||||
conversationId="conversation-1"
|
||||
onSessionId={vi.fn()}
|
||||
onConversationId={vi.fn()}
|
||||
/>
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(agent.reattachAgentChat).toHaveBeenCalledWith(
|
||||
"chat-session-1",
|
||||
expect.any(Function),
|
||||
),
|
||||
);
|
||||
|
||||
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
|
||||
fireEvent.change(composer, { target: { value: "hello /c" } });
|
||||
expect(screen.queryByRole("listbox", { name: "suggestions commandes slash" })).toBeNull();
|
||||
expect(agent.listSlashCommands).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.change(composer, { target: { value: "/" } });
|
||||
await screen.findByRole("listbox", { name: "suggestions commandes slash" });
|
||||
|
||||
fireEvent.change(composer, { target: { value: "/clean now" } });
|
||||
expect(screen.queryByRole("listbox", { name: "suggestions commandes slash" })).toBeNull();
|
||||
});
|
||||
|
||||
it("pastes a clipboard image as a removable preview chip", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
|
||||
@ -23,6 +23,7 @@ import type {
|
||||
ReplyProgressKind,
|
||||
ReplyProgressSource,
|
||||
ReplyProgressStage,
|
||||
SlashCommand,
|
||||
} from "@/domain";
|
||||
import { useGateways } from "@/app/di";
|
||||
import { Button, Spinner, cn } from "@/shared";
|
||||
@ -259,6 +260,16 @@ function clipboardImageFiles(event: ClipboardEvent<HTMLTextAreaElement>): File[]
|
||||
.filter((file): file is File => Boolean(file));
|
||||
}
|
||||
|
||||
function slashCommandPrefix(draft: string): string | null {
|
||||
if (!draft.startsWith("/")) return null;
|
||||
if (/\s/.test(draft)) return null;
|
||||
return draft;
|
||||
}
|
||||
|
||||
function isSlashCommandAvailable(command: SlashCommand): boolean {
|
||||
return command.availability.status === "available";
|
||||
}
|
||||
|
||||
function foldChunk(turns: ChatTurn[], raw: unknown): ChatTurn[] {
|
||||
if (!isReplyRecord(raw)) {
|
||||
return [...turns, { role: "unknown", text: unknownChunkLabel(raw) }];
|
||||
@ -314,12 +325,17 @@ export function CustomAgentChatView({
|
||||
const [externalSessionId, setExternalSessionId] = useState(sessionId);
|
||||
const [draft, setDraft] = useState("");
|
||||
const [attachments, setAttachments] = useState<AttachmentDraft[]>([]);
|
||||
const [slashCommands, setSlashCommands] = useState<SlashCommand[]>([]);
|
||||
const [slashMenuOpen, setSlashMenuOpen] = useState(false);
|
||||
const [slashActiveIndex, setSlashActiveIndex] = useState(0);
|
||||
const [opening, setOpening] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
const composerRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const sessionRef = useRef<string | null>(sessionId);
|
||||
sessionRef.current = currentSession;
|
||||
const slashQuerySeqRef = useRef(0);
|
||||
const mountedRef = useRef(false);
|
||||
const openOrAttachCountRef = useRef(0);
|
||||
const selfEmittedSessionIdRef = useRef<string | null | undefined>(undefined);
|
||||
@ -336,6 +352,7 @@ export function CustomAgentChatView({
|
||||
agent.cancelAgentChat &&
|
||||
agent.closeAgentChat,
|
||||
);
|
||||
const slashPrefix = slashCommandPrefix(draft);
|
||||
|
||||
useEffect(() => {
|
||||
mountedRef.current = true;
|
||||
@ -349,6 +366,47 @@ export function CustomAgentChatView({
|
||||
if (chunk.kind === "final" || chunk.kind === "error") setBusy(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!supported ||
|
||||
!agent.listSlashCommands ||
|
||||
slashPrefix === null ||
|
||||
busy ||
|
||||
opening
|
||||
) {
|
||||
setSlashMenuOpen(false);
|
||||
setSlashCommands([]);
|
||||
setSlashActiveIndex(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const seq = ++slashQuerySeqRef.current;
|
||||
agent
|
||||
.listSlashCommands(slashPrefix)
|
||||
.then((commands) => {
|
||||
if (slashQuerySeqRef.current !== seq) return;
|
||||
setSlashCommands(commands);
|
||||
setSlashMenuOpen(commands.length > 0);
|
||||
const firstAvailable = commands.findIndex(isSlashCommandAvailable);
|
||||
setSlashActiveIndex(firstAvailable >= 0 ? firstAvailable : 0);
|
||||
})
|
||||
.catch(() => {
|
||||
if (slashQuerySeqRef.current !== seq) return;
|
||||
setSlashCommands([]);
|
||||
setSlashMenuOpen(false);
|
||||
setSlashActiveIndex(0);
|
||||
});
|
||||
}, [agent, busy, opening, slashPrefix, supported]);
|
||||
|
||||
const selectSlashCommand = useCallback((command: SlashCommand) => {
|
||||
if (!isSlashCommandAvailable(command)) return;
|
||||
setDraft(`${command.name} `);
|
||||
setSlashMenuOpen(false);
|
||||
setSlashCommands([]);
|
||||
setSlashActiveIndex(0);
|
||||
requestAnimationFrame(() => composerRef.current?.focus());
|
||||
}, []);
|
||||
|
||||
const publishSessionId = useCallback((nextSessionId: string | null) => {
|
||||
selfEmittedSessionIdRef.current = nextSessionId;
|
||||
onSessionIdRef.current(nextSessionId);
|
||||
@ -795,9 +853,52 @@ export function CustomAgentChatView({
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{slashMenuOpen && slashCommands.length > 0 && (
|
||||
<div
|
||||
id={`slash-command-menu-${nodeId}`}
|
||||
role="listbox"
|
||||
aria-label="suggestions commandes slash"
|
||||
className="max-h-48 overflow-y-auto rounded-md border border-border bg-surface py-1 shadow-lg"
|
||||
>
|
||||
{slashCommands.map((command, index) => {
|
||||
const available = isSlashCommandAvailable(command);
|
||||
const selected = index === slashActiveIndex;
|
||||
return (
|
||||
<button
|
||||
key={command.name}
|
||||
type="button"
|
||||
id={`slash-command-${nodeId}-${index}`}
|
||||
role="option"
|
||||
aria-selected={selected}
|
||||
aria-disabled={!available}
|
||||
className={cn(
|
||||
"flex w-full min-w-0 items-start gap-3 px-2.5 py-1.5 text-left text-xs",
|
||||
selected ? "bg-primary/15 text-content" : "text-content",
|
||||
!available && "cursor-not-allowed opacity-55",
|
||||
)}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => selectSlashCommand(command)}
|
||||
>
|
||||
<span className="shrink-0 font-semibold">{command.name}</span>
|
||||
<span className="min-w-0 flex-1 truncate text-muted">
|
||||
{command.shortDescription}
|
||||
</span>
|
||||
{command.requiresConfirmation && (
|
||||
<span className="shrink-0 text-muted">confirm</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex min-w-0 items-end gap-2">
|
||||
<textarea
|
||||
ref={composerRef}
|
||||
aria-label={`message CLI custom ${nodeId}`}
|
||||
aria-controls={slashMenuOpen ? `slash-command-menu-${nodeId}` : undefined}
|
||||
aria-activedescendant={
|
||||
slashMenuOpen ? `slash-command-${nodeId}-${slashActiveIndex}` : undefined
|
||||
}
|
||||
className={cn(
|
||||
"min-h-10 flex-1 resize-none rounded-md border border-border bg-surface p-2 text-sm text-content outline-none",
|
||||
"focus:border-primary disabled:cursor-not-allowed disabled:opacity-50",
|
||||
@ -809,6 +910,31 @@ export function CustomAgentChatView({
|
||||
onChange={(e) => setDraft(e.target.value)}
|
||||
onPaste={(e) => void pasteClipboardImages(e)}
|
||||
onKeyDown={(e) => {
|
||||
if (slashMenuOpen && slashCommands.length > 0) {
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
setSlashActiveIndex((prev) => (prev + 1) % slashCommands.length);
|
||||
return;
|
||||
}
|
||||
if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setSlashActiveIndex(
|
||||
(prev) => (prev - 1 + slashCommands.length) % slashCommands.length,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
setSlashMenuOpen(false);
|
||||
return;
|
||||
}
|
||||
if (e.key === "Enter" || e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
const command = slashCommands[slashActiveIndex];
|
||||
if (command) selectSlashCommand(command);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
void send();
|
||||
|
||||
@ -76,6 +76,8 @@ import type {
|
||||
ResolvedAgentPermissions,
|
||||
ResolvedAgentSystemPermissions,
|
||||
ReplyChunk,
|
||||
SlashCommand,
|
||||
ExecuteSlashCommandResult,
|
||||
ServerExposurePreview,
|
||||
ServerExposureSettings,
|
||||
Skill,
|
||||
@ -305,6 +307,16 @@ export interface AgentGateway {
|
||||
onChunk: (chunk: ReplyChunk) => void,
|
||||
options?: SendAgentChatOptions,
|
||||
): Promise<void>;
|
||||
/**
|
||||
* Lists/filter the unified slash-command registry for the custom structured
|
||||
* CLI. The backend owns command metadata and future plugin contributions.
|
||||
*/
|
||||
listSlashCommands?(prefix?: string): Promise<SlashCommand[]>;
|
||||
/** Executes a selected slash command and returns the planned UI effect. */
|
||||
executeSlashCommand?(
|
||||
name: string,
|
||||
options?: { sessionId?: string | null },
|
||||
): Promise<ExecuteSlashCommandResult>;
|
||||
/** Interrupts only the current turn of a live structured session. */
|
||||
cancelAgentChat?(sessionId: string): Promise<void>;
|
||||
/** Shuts a live structured session down. */
|
||||
|
||||
Reference in New Issue
Block a user