mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Gateway: keep session-only Control UI tool-start mirrors flowing during diagnostic queue pressure instead of silently dropping non-terminal tool updates.
|
||||
- Agents/memory: return optional not-found context for missing date-only daily memory reads instead of logging benign first-run `ENOENT` failures. Fixes #82928. Thanks @galiniliev.
|
||||
- Gateway: avoid sending duplicate tool-event frames to Control UI connections that are subscribed by both run and session.
|
||||
- Discord/OpenAI voice: accept longer leading wake-name mistranscripts such as "Open Club" for OpenClaw.
|
||||
- Agents/OpenAI-compatible: stop ModelStudio-compatible chat requests before sending system/tool-only payloads that have no usable user or assistant turn. (#86177) Thanks @TurboTheTurtle.
|
||||
|
||||
@@ -60,6 +60,7 @@ type ReadTruncationDetails = {
|
||||
const OFFSET_BEYOND_EOF_RE = /^Offset \d+ is beyond end of file \(\d+ lines total\)$/;
|
||||
const READ_CONTINUATION_NOTICE_RE =
|
||||
/\n\n\[(?:Showing lines [^\]]*?Use offset=\d+ to continue\.|\d+ more lines in file\. Use offset=\d+ to continue\.)\]\s*$/;
|
||||
const DAILY_MEMORY_PATH_RE = /^memory\/\d{4}-\d{2}-\d{2}\.md$/;
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
@@ -218,6 +219,40 @@ function emptyReadResult(): AgentToolResult<unknown> {
|
||||
return { content: [textBlock], details: undefined };
|
||||
}
|
||||
|
||||
function missingDailyMemoryReadResult(relativePath: string): AgentToolResult<unknown> {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `No daily memory file exists yet at ${relativePath}.`,
|
||||
},
|
||||
],
|
||||
details: {
|
||||
status: "not_found",
|
||||
path: relativePath,
|
||||
optional: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeDailyMemoryReadPath(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = value.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
|
||||
return DAILY_MEMORY_PATH_RE.test(normalized) ? normalized : undefined;
|
||||
}
|
||||
|
||||
function isNotFoundError(error: unknown): boolean {
|
||||
if (typeof (error as NodeJS.ErrnoException | undefined)?.code === "string") {
|
||||
return (error as NodeJS.ErrnoException).code === "ENOENT";
|
||||
}
|
||||
if (!(error instanceof Error)) {
|
||||
return false;
|
||||
}
|
||||
return /\bENOENT\b|no such file or directory|file not found/i.test(error.message);
|
||||
}
|
||||
|
||||
async function executeReadPage(params: {
|
||||
base: AnyAgentTool;
|
||||
toolCallId: string;
|
||||
@@ -230,6 +265,10 @@ async function executeReadPage(params: {
|
||||
if (isOffsetBeyondEof(error, params.args)) {
|
||||
return emptyReadResult();
|
||||
}
|
||||
const missingDailyMemoryPath = normalizeDailyMemoryReadPath(params.args.path);
|
||||
if (missingDailyMemoryPath && isNotFoundError(error)) {
|
||||
return missingDailyMemoryReadResult(missingDailyMemoryPath);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +165,40 @@ describe("FS tools with workspaceOnly=false", () => {
|
||||
expect(JSON.stringify(result.content)).toContain("test read content");
|
||||
});
|
||||
|
||||
it("returns optional not-found context for missing date-only daily memory reads", async () => {
|
||||
const result = await runFsTool(
|
||||
"read",
|
||||
"test-call-missing-daily-memory",
|
||||
{
|
||||
path: "memory/2026-05-15.md",
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
expect(result).toStrictEqual({
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "No daily memory file exists yet at memory/2026-05-15.md.",
|
||||
},
|
||||
],
|
||||
details: {
|
||||
status: "not_found",
|
||||
path: "memory/2026-05-15.md",
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("still throws for ordinary missing read paths", async () => {
|
||||
const readTool = requireTool(toolsFor(undefined), "read");
|
||||
|
||||
await expect(
|
||||
readTool.execute("test-call-missing-ordinary-file", {
|
||||
path: "notes/missing.md",
|
||||
}),
|
||||
).rejects.toThrow(/ENOENT|no such file|not found/i);
|
||||
});
|
||||
|
||||
it("should allow write outside workspace when workspaceOnly is unset", async () => {
|
||||
const outsideUnsetFile = path.join(tmpDir, "outside-unset-write.txt");
|
||||
await runFsTool(
|
||||
|
||||
Reference in New Issue
Block a user