mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
fix(tui): fence stale session actions and preserve tool output (#129681)
This commit is contained in:
committed by
GitHub
parent
c28d11f780
commit
c07d0e495f
@@ -240,6 +240,21 @@ describe("helpText", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it("uses session-supported thinking levels in help before the provider fallback", () => {
|
||||
const model = { provider: "minimax", model: "MiniMax-M3" };
|
||||
|
||||
expect(
|
||||
helpText({
|
||||
...model,
|
||||
thinkingLevels: [
|
||||
{ id: "off", label: "off" },
|
||||
{ id: "max", label: "max" },
|
||||
],
|
||||
}),
|
||||
).toContain("/think <off|max|default>");
|
||||
expect(helpText({ ...model, thinkingLevels: [] })).toContain("/think <off|adaptive|default>");
|
||||
});
|
||||
|
||||
it("documents default reset values for model, thinking, and fast mode", () => {
|
||||
const output = helpText();
|
||||
|
||||
|
||||
+8
-11
@@ -8,7 +8,6 @@ import {
|
||||
resolveTextCommand,
|
||||
} from "../auto-reply/commands-registry.js";
|
||||
import {
|
||||
formatThinkingLevels,
|
||||
listThinkingLevelLabels,
|
||||
type ReasoningLevel,
|
||||
type VerboseLevel,
|
||||
@@ -47,6 +46,12 @@ type SlashCommandOptions = {
|
||||
dynamicCommands?: CommandEntry[];
|
||||
};
|
||||
|
||||
function resolveThinkingLevelLabels(options: SlashCommandOptions): string[] {
|
||||
return options.thinkingLevels?.length
|
||||
? options.thinkingLevels.map((level) => level.label)
|
||||
: listThinkingLevelLabels(options.provider, options.model, undefined, options.agentRuntime);
|
||||
}
|
||||
|
||||
function createLevelCompletion(
|
||||
levels: string[],
|
||||
): NonNullable<SlashCommand["getArgumentCompletions"]> {
|
||||
@@ -243,9 +248,7 @@ export function isSharedTextCommand(input: string): boolean {
|
||||
}
|
||||
|
||||
export function getSlashCommands(options: SlashCommandOptions = {}): SlashCommand[] {
|
||||
const thinkLevels = options.thinkingLevels?.length
|
||||
? options.thinkingLevels.map((level) => level.label)
|
||||
: listThinkingLevelLabels(options.provider, options.model, undefined, options.agentRuntime);
|
||||
const thinkLevels = resolveThinkingLevelLabels(options);
|
||||
const commands: SlashCommand[] = [];
|
||||
const seen = new Map<string, SlashCommand["getArgumentCompletions"]>();
|
||||
for (const command of TUI_COMMAND_DESCRIPTORS) {
|
||||
@@ -324,13 +327,7 @@ export function shouldSubmitExactArgumentCompletion(
|
||||
}
|
||||
|
||||
export function helpText(options: SlashCommandOptions = {}): string {
|
||||
const thinkLevels = formatThinkingLevels(
|
||||
options.provider,
|
||||
options.model,
|
||||
"|",
|
||||
undefined,
|
||||
options.agentRuntime,
|
||||
);
|
||||
const thinkLevels = resolveThinkingLevelLabels(options).join("|");
|
||||
const commandHelp = TUI_COMMAND_DESCRIPTORS.flatMap((command) => {
|
||||
if (!command.help || !commandIsVisible(command, options.local === true)) {
|
||||
return [];
|
||||
|
||||
@@ -13,6 +13,45 @@ function renderToolOutput(text: string, width: number) {
|
||||
}
|
||||
|
||||
describe("ToolExecutionComponent", () => {
|
||||
it.each(
|
||||
[
|
||||
{ source: " # heading\n command --flag", literal: "# heading" },
|
||||
{ source: " > quoted source\n next line", literal: "> quoted source" },
|
||||
{ source: " - source item\n nested", literal: "- source item" },
|
||||
].flatMap(({ source, literal }) => [
|
||||
{ source, literal, phase: "partial", complete: false },
|
||||
{ source, literal, phase: "final", complete: true },
|
||||
]),
|
||||
)("preserves indented $literal in $phase tool output", ({ source, literal, complete }) => {
|
||||
const component = new ToolExecutionComponent("read_file", { path: "example.txt" });
|
||||
const result = { content: [{ type: "text", text: source }] };
|
||||
if (complete) {
|
||||
component.setResult(result);
|
||||
} else {
|
||||
component.setPartialResult(result);
|
||||
}
|
||||
|
||||
const rendered = component.render(80).map(normalizeTestText).join("\n");
|
||||
expect(rendered).toContain("```");
|
||||
expect(rendered).toContain(literal);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ phase: "partial", complete: false },
|
||||
{ phase: "final", complete: true },
|
||||
])("keeps whitespace-only $phase tool output visually empty", ({ complete }) => {
|
||||
const component = new ToolExecutionComponent("read_file", { path: "example.txt" });
|
||||
const result = { content: [{ type: "text", text: " \n " }] };
|
||||
if (complete) {
|
||||
component.setResult(result);
|
||||
} else {
|
||||
component.setPartialResult(result);
|
||||
}
|
||||
|
||||
const rendered = component.render(80).map(normalizeTestText).join("\n");
|
||||
expect(rendered.includes("...")).toBe(!complete);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ width: 20, characters: 8_192 },
|
||||
{ width: 20, characters: 16_384 },
|
||||
|
||||
@@ -105,7 +105,7 @@ function extractText(result?: ToolResult): string {
|
||||
lines.push(`[${mime}${size}${omitted}]`);
|
||||
}
|
||||
}
|
||||
return lines.join("\n").trim();
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/** Displays a running or completed tool call with optional expandable output. */
|
||||
@@ -187,7 +187,7 @@ export class ToolExecutionComponent extends Container {
|
||||
this.argsLine.setText(argLine ? theme.dim(argLine) : theme.dim(" "));
|
||||
|
||||
const raw = extractText(this.result);
|
||||
const text = raw || (this.isPartial ? "…" : "");
|
||||
const text = raw.trim() ? raw : this.isPartial ? "…" : "";
|
||||
this.output.setExpanded(this.expanded);
|
||||
this.output.setText(text);
|
||||
}
|
||||
|
||||
@@ -2071,6 +2071,52 @@ describe("tui command handlers", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ command: "/think high", rejected: false },
|
||||
{ command: "/verbose full", rejected: false },
|
||||
{ command: "/usage reset", rejected: false },
|
||||
{ command: "/model openai/gpt-5.6-luna", rejected: true },
|
||||
])(
|
||||
"ignores a stale $command patch after the same session is reset",
|
||||
async ({ command, rejected }) => {
|
||||
const deferred = createDeferred<{
|
||||
ok: true;
|
||||
path: string;
|
||||
key: string;
|
||||
entry: Record<string, unknown>;
|
||||
}>();
|
||||
const harness = createHarness({
|
||||
currentSessionId: "session-before-reset",
|
||||
sessionGeneration: 4,
|
||||
sessionInfo: { responseUsage: "tokens", effectiveResponseUsage: "tokens" },
|
||||
patchSession: vi.fn(() => deferred.promise),
|
||||
});
|
||||
|
||||
const pending = harness.handleCommand(command);
|
||||
harness.state.currentSessionId = "session-after-reset";
|
||||
harness.state.sessionGeneration = 5;
|
||||
if (rejected) {
|
||||
deferred.reject(new Error("stale session setting"));
|
||||
} else {
|
||||
deferred.resolve({
|
||||
ok: true,
|
||||
path: "/sessions/patch",
|
||||
key: "agent:main:main",
|
||||
entry: { model: "stale-model" },
|
||||
});
|
||||
}
|
||||
await pending;
|
||||
|
||||
expect(harness.state.sessionInfo.responseUsage).toBe("tokens");
|
||||
expect(harness.state.sessionInfo.effectiveResponseUsage).toBe("tokens");
|
||||
expect(harness.applySessionInfoFromPatch).not.toHaveBeenCalled();
|
||||
expect(harness.refreshSessionInfo).not.toHaveBeenCalled();
|
||||
expect(harness.loadHistory).not.toHaveBeenCalled();
|
||||
expect(harness.clearTools).not.toHaveBeenCalled();
|
||||
expect(harness.addSystem).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it("applies a model patch after its selected session becomes canonical", async () => {
|
||||
const deferred = createDeferred<{
|
||||
ok: true;
|
||||
@@ -2119,6 +2165,11 @@ describe("tui command handlers", () => {
|
||||
it.each([
|
||||
{ name: "another session", initialKey: "agent:main:first", nextKey: "agent:main:second" },
|
||||
{ name: "another global agent", initialKey: "global", nextKey: "global" },
|
||||
{
|
||||
name: "a replacement incarnation",
|
||||
initialKey: "agent:main:main",
|
||||
nextKey: "agent:main:main",
|
||||
},
|
||||
])("ignores a stale reset after selecting $name", async ({ initialKey, nextKey }) => {
|
||||
const deferred = createDeferred<{
|
||||
ok: true;
|
||||
@@ -2128,6 +2179,8 @@ describe("tui command handlers", () => {
|
||||
const harness = createHarness({
|
||||
currentSessionKey: initialKey,
|
||||
currentAgentId: "main",
|
||||
currentSessionId: "first-session",
|
||||
sessionGeneration: 4,
|
||||
resetSession: vi.fn(() => deferred.promise),
|
||||
applySessionMutationResult: vi.fn().mockReturnValue(true),
|
||||
});
|
||||
@@ -2142,6 +2195,9 @@ describe("tui command handlers", () => {
|
||||
if (initialKey === "global") {
|
||||
harness.state.currentAgentId = "work";
|
||||
}
|
||||
if (initialKey === nextKey && initialKey !== "global") {
|
||||
harness.state.sessionGeneration += 1;
|
||||
}
|
||||
harness.state.currentSessionId = "second-session";
|
||||
deferred.resolve({
|
||||
ok: true,
|
||||
@@ -2249,6 +2305,28 @@ describe("tui command handlers", () => {
|
||||
expect(openclaw.addSystem).toHaveBeenCalledWith(expect.stringContaining("ultra"));
|
||||
});
|
||||
|
||||
it("uses the active session's supported thinking levels in help and command usage", async () => {
|
||||
const { handleCommand, addSystem } = createHarness({
|
||||
sessionInfo: {
|
||||
modelProvider: "minimax",
|
||||
model: "MiniMax-M3",
|
||||
thinkingLevels: [
|
||||
{ id: "off", label: "off" },
|
||||
{ id: "max", label: "max" },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await handleCommand("/help");
|
||||
await handleCommand("/think");
|
||||
|
||||
expect(addSystem).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.stringContaining("/think <off|max|default>"),
|
||||
);
|
||||
expect(addSystem).toHaveBeenNthCalledWith(2, "usage: /think <off|max|default>");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ command: "verbose", usage: "usage: /verbose <on|off|full>" },
|
||||
{ command: "reasoning", usage: "usage: /reasoning <on|off|stream>" },
|
||||
|
||||
@@ -264,19 +264,33 @@ export function createCommandHandlers(context: CommandHandlerContext) {
|
||||
state.currentAgentId === selection.agentId &&
|
||||
agentSessionKeysMatchByRequestKey(state.currentSessionKey, selection.sessionKey);
|
||||
|
||||
const captureSessionIncarnation = () => {
|
||||
const selection = captureSessionSelection();
|
||||
const sessionId = state.currentSessionId;
|
||||
const generation = state.sessionGeneration ?? 0;
|
||||
return {
|
||||
selection,
|
||||
sessionId,
|
||||
isCurrent: () =>
|
||||
isCurrentSessionSelection(selection) &&
|
||||
(state.sessionGeneration ?? 0) === generation &&
|
||||
(sessionId === null || state.currentSessionId === sessionId),
|
||||
};
|
||||
};
|
||||
|
||||
const patchCurrentSession = async (
|
||||
patch: Omit<Parameters<TuiBackend["patchSession"]>[0], "key" | "agentId">,
|
||||
): Promise<SessionsPatchResult | null> => {
|
||||
const selection = captureSessionSelection();
|
||||
const { selection, isCurrent } = captureSessionIncarnation();
|
||||
try {
|
||||
const result = await client.patchSession({
|
||||
key: selection.sessionKey,
|
||||
...(!parseAgentSessionKey(selection.sessionKey) ? { agentId: selection.agentId } : {}),
|
||||
...patch,
|
||||
});
|
||||
return isCurrentSessionSelection(selection) ? result : null;
|
||||
return isCurrent() ? result : null;
|
||||
} catch (err) {
|
||||
if (!isCurrentSessionSelection(selection)) {
|
||||
if (!isCurrent()) {
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
@@ -467,6 +481,7 @@ export function createCommandHandlers(context: CommandHandlerContext) {
|
||||
provider: state.sessionInfo.modelProvider,
|
||||
model: state.sessionInfo.model,
|
||||
agentRuntime: state.sessionInfo.agentRuntime?.id,
|
||||
thinkingLevels: state.sessionInfo.thinkingLevels,
|
||||
}),
|
||||
);
|
||||
},
|
||||
@@ -787,8 +802,8 @@ export function createCommandHandlers(context: CommandHandlerContext) {
|
||||
if (rejectUnsafeSessionRollover("reset")) {
|
||||
return;
|
||||
}
|
||||
const resetSelection = captureSessionSelection();
|
||||
let resetResultSelection = resetSelection;
|
||||
let resetIncarnation = captureSessionIncarnation();
|
||||
const resetSelection = resetIncarnation.selection;
|
||||
const finishSessionTransition = beginSessionTransition("reset");
|
||||
try {
|
||||
const result = await client.resetSession(
|
||||
@@ -798,7 +813,7 @@ export function createCommandHandlers(context: CommandHandlerContext) {
|
||||
? { agentId: resetSelection.agentId }
|
||||
: undefined,
|
||||
);
|
||||
if (!isCurrentSessionSelection(resetSelection)) {
|
||||
if (!resetIncarnation.isCurrent()) {
|
||||
return;
|
||||
}
|
||||
state.sessionInfo.inputTokens = null;
|
||||
@@ -806,17 +821,17 @@ export function createCommandHandlers(context: CommandHandlerContext) {
|
||||
state.sessionInfo.totalTokens = null;
|
||||
tui.requestRender();
|
||||
if (applySessionMutationResult(result, resetSelection)) {
|
||||
resetResultSelection = captureSessionSelection();
|
||||
resetIncarnation = captureSessionIncarnation();
|
||||
await refreshSessionInfo();
|
||||
} else {
|
||||
await loadHistory();
|
||||
}
|
||||
if (!isCurrentSessionSelection(resetResultSelection)) {
|
||||
if (!resetIncarnation.isCurrent()) {
|
||||
return;
|
||||
}
|
||||
chatLog.addSystem(`session ${state.currentSessionKey} reset`);
|
||||
} catch (err) {
|
||||
if (!isCurrentSessionSelection(resetResultSelection)) {
|
||||
if (!resetIncarnation.isCurrent()) {
|
||||
return;
|
||||
}
|
||||
chatLog.addSystem(`reset failed: ${formatTuiErrorMessage(err)}`);
|
||||
@@ -875,13 +890,11 @@ export function createCommandHandlers(context: CommandHandlerContext) {
|
||||
// The Gateway owns queue policy. TUI only serializes pending RPC admission;
|
||||
// an already-active run must not suppress steer/followup/collect/interrupt.
|
||||
const runId = randomUUID();
|
||||
const sendSelection = captureSessionSelection();
|
||||
const sendSessionId = state.currentSessionId;
|
||||
const sendSessionGeneration = state.sessionGeneration ?? 0;
|
||||
const isCurrentSendViewport = () =>
|
||||
isCurrentSessionSelection(sendSelection) &&
|
||||
(state.sessionGeneration ?? 0) === sendSessionGeneration &&
|
||||
(sendSessionId === null || state.currentSessionId === sendSessionId);
|
||||
const {
|
||||
selection: sendSelection,
|
||||
sessionId: sendSessionId,
|
||||
isCurrent: isCurrentSendViewport,
|
||||
} = captureSessionIncarnation();
|
||||
const sendScope = readTuiSessionProjectionScope(state);
|
||||
try {
|
||||
if (!isBtw) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import { objectFieldEquals, readFixtureLog } from "./tui-pty-harness-fixture-test-support.js";
|
||||
|
||||
const slashHelpMarkers =
|
||||
"Slash commands:,/help,/verbose <on|off|full>,/reasoning <on|off|stream>,/goal,/goal start <objective>,/btw <side question>,/queue,/stop,/exit".split(
|
||||
"Slash commands:,/help,/think <max|default>,/verbose <on|off|full>,/reasoning <on|off|stream>,/goal,/goal start <objective>,/btw <side question>,/queue,/stop,/exit".split(
|
||||
",",
|
||||
);
|
||||
const countHistoryLoads = async (logPath: string) =>
|
||||
@@ -23,6 +23,7 @@ export async function exerciseTuiCommandSurface(
|
||||
env: {
|
||||
OPENCLAW_TUI_PTY_COLS: "100",
|
||||
OPENCLAW_TUI_PTY_ROWS: "24",
|
||||
...(surface === "slash-commands" ? { OPENCLAW_TUI_PTY_SAFE_THINKING_LABEL: "max" } : {}),
|
||||
...(surface === "pickers" ? { OPENCLAW_TUI_PTY_PICKER_FIXTURE: "1" } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,7 +23,15 @@ export function toolFrame(rows: string[], complete: boolean) {
|
||||
const before = frame.indexOf("PTY_BEFORE_TOOL");
|
||||
const running = frame.indexOf("Read File (running)");
|
||||
const partial = frame.indexOf("PTY_TOOL_PARTIAL");
|
||||
return before >= 0 && running >= 0 && partial >= 0 && before < running && running < partial;
|
||||
return (
|
||||
before >= 0 &&
|
||||
running >= 0 &&
|
||||
partial >= 0 &&
|
||||
before < running &&
|
||||
running < partial &&
|
||||
frame.includes("# PTY_TOOL_PARTIAL") &&
|
||||
frame.includes("```")
|
||||
);
|
||||
}
|
||||
const markers = ["PTY_BEFORE_TOOL", "Read File", "PTY_TOOL_RESULT", "PTY_AFTER_TOOL"];
|
||||
return (
|
||||
@@ -34,6 +42,8 @@ export function toolFrame(rows: string[], complete: boolean) {
|
||||
) &&
|
||||
!frame.includes("(running)") &&
|
||||
!frame.includes("PTY_TOOL_PARTIAL") &&
|
||||
frame.includes("> PTY_TOOL_RESULT") &&
|
||||
frame.includes("```") &&
|
||||
frame.includes("idle")
|
||||
);
|
||||
}
|
||||
@@ -113,9 +123,9 @@ export const TUI_PTY_RENDERING_FIXTURE_SCRIPT = `
|
||||
const base = { toolCallId: "pty-rendering-tool", name: process.env.OPENCLAW_TUI_PTY_TOOL_NAME ?? "read_file" };
|
||||
backend.onEvent?.({ event: "agent", payload: { runId, sessionKey, stream: "tool", data: { ...base, phase: "start", args: { path: "chronology-proof.txt" } } } });
|
||||
if (process.env.OPENCLAW_TUI_PTY_VERBOSE_LEVEL === "full") {
|
||||
backend.onEvent?.({ event: "agent", payload: { runId, sessionKey, stream: "tool", data: { ...base, phase: "update", partialResult: { content: [{ type: "text", text: "PTY_TOOL_PARTIAL" }] } } } });
|
||||
backend.onEvent?.({ event: "agent", payload: { runId, sessionKey, stream: "tool", data: { ...base, phase: "update", partialResult: { content: [{ type: "text", text: " # PTY_TOOL_PARTIAL" }] } } } });
|
||||
record("toolPartialReady", { runId }); await waitForRenderingRelease("tool");
|
||||
backend.onEvent?.({ event: "agent", payload: { runId, sessionKey, stream: "tool", data: { ...base, phase: "result", result: { content: [{ type: "text", text: "PTY_TOOL_RESULT" }] } } } });
|
||||
backend.onEvent?.({ event: "agent", payload: { runId, sessionKey, stream: "tool", data: { ...base, phase: "result", result: { content: [{ type: "text", text: " > PTY_TOOL_RESULT" }] } } } });
|
||||
}
|
||||
const finalText = "PTY_BEFORE_TOOL\\n\\nPTY_AFTER_TOOL"; emitAssistant(backend, runId, sessionKey, "delta", finalText); emitAssistant(backend, runId, sessionKey, "final", finalText); record("toolComplete", { runId }); record("toolChronologyComplete", { runId });
|
||||
}
|
||||
|
||||
@@ -2459,6 +2459,27 @@ describe("tui session actions", () => {
|
||||
aborted: false,
|
||||
rejected: true,
|
||||
},
|
||||
{
|
||||
name: "successful abort after the same session is replaced",
|
||||
initialKey: "agent:main:main",
|
||||
nextKey: "agent:main:main",
|
||||
aborted: true,
|
||||
rejected: false,
|
||||
},
|
||||
{
|
||||
name: "no-active-run abort after the same session is replaced",
|
||||
initialKey: "agent:main:main",
|
||||
nextKey: "agent:main:main",
|
||||
aborted: false,
|
||||
rejected: false,
|
||||
},
|
||||
{
|
||||
name: "rejected abort after the same session is replaced",
|
||||
initialKey: "agent:main:main",
|
||||
nextKey: "agent:main:main",
|
||||
aborted: false,
|
||||
rejected: true,
|
||||
},
|
||||
])("ignores a $name", async ({ initialKey, nextKey, aborted, rejected }) => {
|
||||
const deferred = createDeferred<Awaited<ReturnType<TuiBackend["abortChat"]>>>();
|
||||
const abortChat = vi.fn(() => deferred.promise);
|
||||
@@ -2476,6 +2497,8 @@ describe("tui session actions", () => {
|
||||
const state = createBaseState({
|
||||
currentSessionKey: initialKey,
|
||||
currentAgentId: "main",
|
||||
currentSessionId: "first-session",
|
||||
sessionGeneration: 4,
|
||||
activeChatRunId: "first-active-run",
|
||||
pendingSubmit: acceptedSubmit("first-pending-run"),
|
||||
});
|
||||
@@ -2495,7 +2518,12 @@ describe("tui session actions", () => {
|
||||
sessionKey: initialKey,
|
||||
...(initialKey === "global" ? { agentId: "main" } : {}),
|
||||
});
|
||||
await setSession(nextKey, initialKey === "global" ? "work" : undefined);
|
||||
if (initialKey === nextKey && initialKey !== "global") {
|
||||
state.sessionGeneration = (state.sessionGeneration ?? 0) + 1;
|
||||
state.currentSessionId = "second-session";
|
||||
} else {
|
||||
await setSession(nextKey, initialKey === "global" ? "work" : undefined);
|
||||
}
|
||||
state.activeChatRunId = "second-active-run";
|
||||
state.pendingSubmit = acceptedSubmit("second-pending-run", "second draft");
|
||||
addSystem.mockClear();
|
||||
|
||||
@@ -659,8 +659,14 @@ export function createSessionActions(context: SessionActionContext) {
|
||||
return;
|
||||
}
|
||||
const selection = captureSessionSelection();
|
||||
const sessionId = state.currentSessionId;
|
||||
const sessionGeneration = state.sessionGeneration ?? 0;
|
||||
const pendingRunId = submit.getPendingSubmitAcceptedRunId(state);
|
||||
const activeRunId = state.activeChatRunId;
|
||||
const isCurrentAbort = () =>
|
||||
isCurrentSessionSelection(selection) &&
|
||||
(state.sessionGeneration ?? 0) === sessionGeneration &&
|
||||
(sessionId === null || state.currentSessionId === sessionId);
|
||||
const dropPendingRun = (runId: string) => {
|
||||
reduceTuiSessionProjection(state, {
|
||||
type: "sendFailed",
|
||||
@@ -677,7 +683,7 @@ export function createSessionActions(context: SessionActionContext) {
|
||||
sessionKey: selection.sessionKey,
|
||||
...(!parseAgentSessionKey(selection.sessionKey) ? { agentId: selection.agentId } : {}),
|
||||
});
|
||||
if (!isCurrentSessionSelection(selection)) {
|
||||
if (!isCurrentAbort()) {
|
||||
return;
|
||||
}
|
||||
if (!result.aborted) {
|
||||
@@ -704,7 +710,7 @@ export function createSessionActions(context: SessionActionContext) {
|
||||
}
|
||||
setActivityStatus("aborted");
|
||||
} catch (err) {
|
||||
if (!isCurrentSessionSelection(selection)) {
|
||||
if (!isCurrentAbort()) {
|
||||
return;
|
||||
}
|
||||
chatLog.addSystem(`abort failed: ${formatTuiErrorMessage(err)}`);
|
||||
|
||||
Reference in New Issue
Block a user