mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 02:01:36 +00:00
Auto-reply: bound abort memory map growth
This commit is contained in:
@@ -1,9 +1,16 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import type { OpenClawConfig } from "../../config/config.js";
|
import type { OpenClawConfig } from "../../config/config.js";
|
||||||
import { isAbortTrigger, tryFastAbortFromMessage } from "./abort.js";
|
import {
|
||||||
|
getAbortMemory,
|
||||||
|
getAbortMemorySizeForTest,
|
||||||
|
isAbortTrigger,
|
||||||
|
resetAbortMemoryForTest,
|
||||||
|
setAbortMemory,
|
||||||
|
tryFastAbortFromMessage,
|
||||||
|
} from "./abort.js";
|
||||||
import { enqueueFollowupRun, getFollowupQueueDepth, type FollowupRun } from "./queue.js";
|
import { enqueueFollowupRun, getFollowupQueueDepth, type FollowupRun } from "./queue.js";
|
||||||
import { initSessionState } from "./session.js";
|
import { initSessionState } from "./session.js";
|
||||||
import { buildTestCtx } from "./test-ctx.js";
|
import { buildTestCtx } from "./test-ctx.js";
|
||||||
@@ -28,6 +35,10 @@ vi.mock("../../agents/subagent-registry.js", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe("abort detection", () => {
|
describe("abort detection", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
resetAbortMemoryForTest();
|
||||||
|
});
|
||||||
|
|
||||||
it("triggerBodyNormalized extracts /stop from RawBody for abort detection", async () => {
|
it("triggerBodyNormalized extracts /stop from RawBody for abort detection", async () => {
|
||||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-abort-"));
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-abort-"));
|
||||||
const storePath = path.join(root, "sessions.json");
|
const storePath = path.join(root, "sessions.json");
|
||||||
@@ -62,6 +73,24 @@ describe("abort detection", () => {
|
|||||||
expect(isAbortTrigger("/stop")).toBe(false);
|
expect(isAbortTrigger("/stop")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("removes abort memory entry when flag is reset", () => {
|
||||||
|
setAbortMemory("session-1", true);
|
||||||
|
expect(getAbortMemory("session-1")).toBe(true);
|
||||||
|
|
||||||
|
setAbortMemory("session-1", false);
|
||||||
|
expect(getAbortMemory("session-1")).toBeUndefined();
|
||||||
|
expect(getAbortMemorySizeForTest()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("caps abort memory tracking to a bounded max size", () => {
|
||||||
|
for (let i = 0; i < 2105; i += 1) {
|
||||||
|
setAbortMemory(`session-${i}`, true);
|
||||||
|
}
|
||||||
|
expect(getAbortMemorySizeForTest()).toBe(2000);
|
||||||
|
expect(getAbortMemory("session-0")).toBeUndefined();
|
||||||
|
expect(getAbortMemory("session-2104")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("fast-aborts even when text commands are disabled", async () => {
|
it("fast-aborts even when text commands are disabled", async () => {
|
||||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-abort-"));
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-abort-"));
|
||||||
const storePath = path.join(root, "sessions.json");
|
const storePath = path.join(root, "sessions.json");
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { clearSessionQueues } from "./queue.js";
|
|||||||
|
|
||||||
const ABORT_TRIGGERS = new Set(["stop", "esc", "abort", "wait", "exit", "interrupt"]);
|
const ABORT_TRIGGERS = new Set(["stop", "esc", "abort", "wait", "exit", "interrupt"]);
|
||||||
const ABORT_MEMORY = new Map<string, boolean>();
|
const ABORT_MEMORY = new Map<string, boolean>();
|
||||||
|
const ABORT_MEMORY_MAX = 2000;
|
||||||
|
|
||||||
export function isAbortTrigger(text?: string): boolean {
|
export function isAbortTrigger(text?: string): boolean {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
@@ -32,11 +33,51 @@ export function isAbortTrigger(text?: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getAbortMemory(key: string): boolean | undefined {
|
export function getAbortMemory(key: string): boolean | undefined {
|
||||||
return ABORT_MEMORY.get(key);
|
const normalized = key.trim();
|
||||||
|
if (!normalized) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return ABORT_MEMORY.get(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pruneAbortMemory(): void {
|
||||||
|
if (ABORT_MEMORY.size <= ABORT_MEMORY_MAX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const excess = ABORT_MEMORY.size - ABORT_MEMORY_MAX;
|
||||||
|
let removed = 0;
|
||||||
|
for (const entryKey of ABORT_MEMORY.keys()) {
|
||||||
|
ABORT_MEMORY.delete(entryKey);
|
||||||
|
removed += 1;
|
||||||
|
if (removed >= excess) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setAbortMemory(key: string, value: boolean): void {
|
export function setAbortMemory(key: string, value: boolean): void {
|
||||||
ABORT_MEMORY.set(key, value);
|
const normalized = key.trim();
|
||||||
|
if (!normalized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!value) {
|
||||||
|
ABORT_MEMORY.delete(normalized);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Refresh insertion order so active keys are less likely to be evicted.
|
||||||
|
if (ABORT_MEMORY.has(normalized)) {
|
||||||
|
ABORT_MEMORY.delete(normalized);
|
||||||
|
}
|
||||||
|
ABORT_MEMORY.set(normalized, true);
|
||||||
|
pruneAbortMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAbortMemorySizeForTest(): number {
|
||||||
|
return ABORT_MEMORY.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetAbortMemoryForTest(): void {
|
||||||
|
ABORT_MEMORY.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatAbortReplyText(stoppedSubagents?: number): string {
|
export function formatAbortReplyText(stoppedSubagents?: number): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user