diff --git a/CHANGELOG.md b/CHANGELOG.md index 3458254226e..6999498501d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Plugins/inspector: keep bundled plugin runtime capture quiet and config-tolerant for Codex, memory-lancedb, Feishu, Mattermost, QQBot, and Tlon so plugin-inspector JSON checks can validate the full bundled set. Thanks @vincentkoc. +- Slack/auto-reply: keep fully consumed text reset triggers such as `new session` out of `BodyForAgent` after directive cleanup, so configured Slack reset phrases do not leak into the fresh model turn. Fixes #73137. Thanks @neeravmakwana. - Gateway/startup: start chat channels without waiting for primary model prewarm, keeping model warmup bounded in the background so Slack and other channels come online promptly when provider discovery is slow. Supersedes #73420. Thanks @dorukardahan. - Gateway/install: carry env-backed config SecretRefs such as `channels.discord.token` into generated service environments when they are present only in the installing shell, while keeping gateway auth SecretRefs non-persisted. Fixes #67817; supersedes #73426. Thanks @wdimaculangan and @ztexydt-cqh. - Auto-reply/commands: stop bare `/reset` and `/new` after reset hooks acknowledge the command, so non-ACP channels no longer fall through into empty provider calls while `/reset ` and `/new ` still seed the next model turn. Fixes #73367 and #73412. Thanks @hoyanhan, @wenxu007, and @amdhelper. diff --git a/src/auto-reply/reply/get-reply-directives.target-session.test.ts b/src/auto-reply/reply/get-reply-directives.target-session.test.ts index 0579c433e80..506fbf40a9d 100644 --- a/src/auto-reply/reply/get-reply-directives.target-session.test.ts +++ b/src/auto-reply/reply/get-reply-directives.target-session.test.ts @@ -128,6 +128,7 @@ async function resolveHelloWithModelDefaults(params: { groupResolution: undefined, isGroup: false, triggerBodyNormalized: "hello", + resetTriggered: false, commandAuthorized: false, defaultProvider: "openai", defaultModel: "gpt-4o-mini", @@ -302,6 +303,7 @@ describe("resolveReplyDirectives", () => { groupResolution: undefined, isGroup: false, triggerBodyNormalized: "hello", + resetTriggered: false, commandAuthorized: false, defaultProvider: "openai", defaultModel: "gpt-4o-mini", @@ -393,6 +395,7 @@ describe("resolveReplyDirectives", () => { groupResolution: undefined, isGroup: false, triggerBodyNormalized: "/trace on", + resetTriggered: false, commandAuthorized: true, defaultProvider: "openai", defaultModel: "gpt-4o-mini", @@ -461,4 +464,69 @@ describe("resolveReplyDirectives", () => { }); expect(resolveDefaultReasoningLevel).not.toHaveBeenCalled(); }); + + it("keeps consumed text reset triggers empty after directive cleanup", async () => { + const sessionCtx = { + Body: "", + BodyStripped: "", + BodyForAgent: "", + BodyForCommands: "new session", + CommandBody: "new session", + Provider: "slack", + Surface: "slack", + } as TemplateContext; + + const result = await resolveReplyDirectives({ + ctx: buildTestCtx({ + Body: "new session", + BodyForAgent: "new session", + BodyForCommands: "new session", + CommandBody: "new session", + CommandAuthorized: true, + Provider: "slack", + Surface: "slack", + }), + cfg: { + session: { + resetTriggers: ["/new", "/reset", "new session"], + }, + }, + agentId: "main", + agentDir: "/tmp/main-agent", + workspaceDir: "/tmp", + agentCfg: {}, + sessionCtx, + sessionEntry: makeSessionEntry(), + sessionStore: { + "agent:main:slack:C123": makeSessionEntry(), + }, + sessionKey: "agent:main:slack:C123", + storePath: "/tmp/sessions.json", + sessionScope: "per-sender", + groupResolution: undefined, + isGroup: false, + triggerBodyNormalized: "new session", + resetTriggered: true, + commandAuthorized: true, + defaultProvider: "openai", + defaultModel: "gpt-4o-mini", + aliasIndex: { byAlias: new Map(), byKey: new Map() }, + provider: "openai", + model: "gpt-4o-mini", + hasResolvedHeartbeatModelOverride: false, + typing: makeTypingController(), + opts: undefined, + skillFilter: undefined, + }); + + expect(result).toEqual({ + kind: "continue", + result: expect.objectContaining({ + cleanedBody: "", + }), + }); + expect(sessionCtx.Body).toBe(""); + expect(sessionCtx.BodyForAgent).toBe(""); + expect(sessionCtx.BodyStripped).toBe(""); + }); }); diff --git a/src/auto-reply/reply/get-reply-directives.ts b/src/auto-reply/reply/get-reply-directives.ts index a02e1b6bc31..f8f3e547244 100644 --- a/src/auto-reply/reply/get-reply-directives.ts +++ b/src/auto-reply/reply/get-reply-directives.ts @@ -156,6 +156,7 @@ export async function resolveReplyDirectives(params: { groupResolution: Parameters[0]["groupResolution"]; isGroup: boolean; triggerBodyNormalized: string; + resetTriggered: boolean; commandAuthorized: boolean; defaultProvider: string; defaultModel: string; @@ -183,6 +184,7 @@ export async function resolveReplyDirectives(params: { groupResolution, isGroup, triggerBodyNormalized, + resetTriggered, commandAuthorized, defaultProvider, defaultModel, @@ -335,6 +337,9 @@ export async function resolveReplyDirectives(params: { const existingBody = sessionCtx.BodyStripped ?? sessionCtx.Body ?? ""; let cleanedBody = (() => { if (!existingBody) { + if (resetTriggered) { + return ""; + } return parsedDirectives.cleaned; } if (!sessionCtx.CommandBody && !sessionCtx.RawBody) { diff --git a/src/auto-reply/reply/get-reply.ts b/src/auto-reply/reply/get-reply.ts index 8921ce2a048..be2d0828f2d 100644 --- a/src/auto-reply/reply/get-reply.ts +++ b/src/auto-reply/reply/get-reply.ts @@ -469,6 +469,7 @@ export async function getReplyFromConfig( groupResolution, isGroup, triggerBodyNormalized, + resetTriggered, commandAuthorized, defaultProvider, defaultModel,