diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f3f68af312..779d56805ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Agents/Compaction: pass `agentDir` into manual `/compact` command runs so compaction auth/profile resolution stays scoped to the active agent. (#24133) thanks @Glucksberg. - Security/Skills: escape user-controlled prompt, filename, and output-path values in `openai-image-gen` HTML gallery generation to prevent stored XSS in generated `index.html` output. (#12538) Thanks @CornBrother0x. - Security/Skills: harden `skill-creator` packaging by skipping symlink entries and rejecting files whose resolved paths escape the selected skill root. (#24260, #16959) Thanks @CornBrother0x and @vincentkoc. - Security/OTEL: redact sensitive values (API keys, tokens, credential fields) from diagnostics-otel log bodies, log attributes, and error/reason span fields before OTLP export. (#12542) Thanks @brandonwise. diff --git a/src/auto-reply/reply/commands-compact.ts b/src/auto-reply/reply/commands-compact.ts index f6242232a16..1533bb24393 100644 --- a/src/auto-reply/reply/commands-compact.ts +++ b/src/auto-reply/reply/commands-compact.ts @@ -92,6 +92,7 @@ export const handleCompactCommand: CommandHandler = async (params) => { }), ), workspaceDir: params.workspaceDir, + agentDir: params.agentDir, config: params.cfg, skillsSnapshot: params.sessionEntry.skillsSnapshot, provider: params.provider, diff --git a/src/auto-reply/reply/commands-types.ts b/src/auto-reply/reply/commands-types.ts index 96b615b21f0..6ff476b8c20 100644 --- a/src/auto-reply/reply/commands-types.ts +++ b/src/auto-reply/reply/commands-types.ts @@ -27,6 +27,7 @@ export type HandleCommandsParams = { cfg: OpenClawConfig; command: CommandContext; agentId?: string; + agentDir?: string; directives: InlineDirectives; elevated: { enabled: boolean; diff --git a/src/auto-reply/reply/commands.test.ts b/src/auto-reply/reply/commands.test.ts index db4ba74db40..a402f8dd42b 100644 --- a/src/auto-reply/reply/commands.test.ts +++ b/src/auto-reply/reply/commands.test.ts @@ -389,6 +389,7 @@ describe("/compact command", () => { From: "+15550001", To: "+15550002", }); + const agentDir = "/tmp/openclaw-agent-compact"; vi.mocked(compactEmbeddedPiSession).mockResolvedValueOnce({ ok: true, compacted: false, @@ -397,6 +398,7 @@ describe("/compact command", () => { const result = await handleCompactCommand( { ...params, + agentDir, sessionEntry: { sessionId: "session-1", updatedAt: Date.now(), @@ -423,6 +425,7 @@ describe("/compact command", () => { groupChannel: "#general", groupSpace: "workspace-1", spawnedBy: "agent:main:parent", + agentDir, }), ); }); diff --git a/src/auto-reply/reply/get-reply-inline-actions.skip-when-config-empty.test.ts b/src/auto-reply/reply/get-reply-inline-actions.skip-when-config-empty.test.ts index 7ecead2d596..5c7caab7781 100644 --- a/src/auto-reply/reply/get-reply-inline-actions.skip-when-config-empty.test.ts +++ b/src/auto-reply/reply/get-reply-inline-actions.skip-when-config-empty.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import type { TemplateContext } from "../templating.js"; import { clearInlineDirectives } from "./get-reply-directives-utils.js"; import { buildTestCtx } from "./test-ctx.js"; @@ -16,6 +16,10 @@ vi.mock("./commands.js", () => ({ const { handleInlineActions } = await import("./get-reply-inline-actions.js"); describe("handleInlineActions", () => { + beforeEach(() => { + handleCommandsMock.mockReset(); + }); + it("skips whatsapp replies when config is empty and From !== To", async () => { const typing: TypingController = { onReplyStart: async () => {}, @@ -81,4 +85,77 @@ describe("handleInlineActions", () => { expect(typing.cleanup).toHaveBeenCalled(); expect(handleCommandsMock).not.toHaveBeenCalled(); }); + + it("forwards agentDir into handleCommands", async () => { + const typing: TypingController = { + onReplyStart: async () => {}, + startTypingLoop: async () => {}, + startTypingOnText: async () => {}, + refreshTypingTtl: () => {}, + isActive: () => false, + markRunComplete: () => {}, + markDispatchIdle: () => {}, + cleanup: vi.fn(), + }; + + handleCommandsMock.mockResolvedValue({ shouldContinue: false, reply: { text: "done" } }); + + const ctx = buildTestCtx({ + Body: "/status", + CommandBody: "/status", + }); + const agentDir = "/tmp/inline-agent"; + + const result = await handleInlineActions({ + ctx, + sessionCtx: ctx as unknown as TemplateContext, + cfg: { commands: { text: true } }, + agentId: "main", + agentDir, + sessionKey: "s:main", + workspaceDir: "/tmp", + isGroup: false, + typing, + allowTextCommands: false, + inlineStatusRequested: false, + command: { + surface: "whatsapp", + channel: "whatsapp", + channelId: "whatsapp", + ownerList: [], + senderIsOwner: false, + isAuthorizedSender: true, + senderId: "sender-1", + abortKey: "sender-1", + rawBodyNormalized: "/status", + commandBodyNormalized: "/status", + from: "whatsapp:+999", + to: "whatsapp:+999", + }, + directives: clearInlineDirectives("/status"), + cleanedBody: "/status", + elevatedEnabled: false, + elevatedAllowed: false, + elevatedFailures: [], + defaultActivation: () => "always", + resolvedThinkLevel: undefined, + resolvedVerboseLevel: undefined, + resolvedReasoningLevel: "off", + resolvedElevatedLevel: "off", + resolveDefaultThinkingLevel: async () => "off", + provider: "openai", + model: "gpt-4o-mini", + contextTokens: 0, + abortedLastRun: false, + sessionScope: "per-sender", + }); + + expect(result).toEqual({ kind: "reply", reply: { text: "done" } }); + expect(handleCommandsMock).toHaveBeenCalledTimes(1); + expect(handleCommandsMock).toHaveBeenCalledWith( + expect.objectContaining({ + agentDir, + }), + ); + }); }); diff --git a/src/auto-reply/reply/get-reply-inline-actions.ts b/src/auto-reply/reply/get-reply-inline-actions.ts index 9044abf515b..89066a47d57 100644 --- a/src/auto-reply/reply/get-reply-inline-actions.ts +++ b/src/auto-reply/reply/get-reply-inline-actions.ts @@ -302,6 +302,7 @@ export async function handleInlineActions(params: { cfg, command: commandInput, agentId, + agentDir, directives, elevated: { enabled: elevatedEnabled,