fix: gate typed chat stops

This commit is contained in:
Shakker
2026-08-02 19:40:49 +01:00
parent f699aebb50
commit 1317838ac4
3 changed files with 31 additions and 2 deletions
+1 -1
View File
@@ -95,7 +95,7 @@ function currentSessionAccessSnapshot(
};
}
function requireChatSessionAction(
export function requireChatSessionAction(
host: ChatCommandHost,
action: "abort" | "compact" | "reset",
): boolean {
+8 -1
View File
@@ -9,7 +9,11 @@ import {
getChatAttachmentDataUrl,
releaseChatAttachmentPayloads,
} from "./attachment-payload-store.ts";
import { dispatchChatSlashCommand, shouldQueueLocalSlashCommand } from "./chat-commands.ts";
import {
dispatchChatSlashCommand,
requireChatSessionAction,
shouldQueueLocalSlashCommand,
} from "./chat-commands.ts";
import type { ChatState } from "./chat-history.ts";
import {
admitQueuedMessageForSession,
@@ -213,6 +217,9 @@ export async function handleSendChat(
isChatStopCommand(message) &&
(message.trim().startsWith("/") || hasAbortableSessionRun(host))
) {
if (!requireChatSessionAction(host, "abort")) {
return;
}
if (messageOverride == null) {
recordNonTranscriptInputHistory(host, message);
}
+22
View File
@@ -8688,6 +8688,28 @@ describe("handleAbortChat", () => {
},
);
it("blocks a typed stop before aborting when the operator lacks write scope", async () => {
const host = makeHost({
requestHandlers: {},
chatRunId: "run-main",
chatMessage: "/stop",
hello: {
type: "hello-ok",
protocol: 4,
auth: { role: "operator", scopes: ["operator.read"] },
features: { methods: ["chat.abort"] },
},
sessionKey: "agent:main",
});
await handleSendChat(host);
expect(host.request).not.toHaveBeenCalled();
expect(host.lastError).toBeTruthy();
expect(host.chatError).toBe(host.lastError);
expect(host.chatMessage).toBe("/stop");
});
it("queues the active run abort while disconnected", async () => {
const client = { request: vi.fn() } as unknown as NonNullable<ChatHost["client"]>;
const host = makeHost({