test(auto-reply): assert bare reset acknowledgement

This commit is contained in:
Peter Steinberger
2026-04-28 09:56:32 +01:00
parent 11f0244cf4
commit 2790825ae5
6 changed files with 20 additions and 30 deletions

View File

@@ -15,7 +15,7 @@ Docs: https://docs.openclaw.ai
- 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 <message>` and `/new <message>` still seed the next model turn. Fixes #73367. Thanks @hoyanhan and @wenxu007.
- 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 <message>` and `/new <message>` still seed the next model turn. Fixes #73367 and #73412. Thanks @hoyanhan, @wenxu007, and @amdhelper.
- Auto-reply: preserve voice-note media from silent turns while continuing to suppress text and non-voice media, so `NO_REPLY` TTS replies still deliver the requested audio bubble. (#73406) Thanks @zqchris.
- Channels/Mattermost: stop enqueueing regular inbound posts as system events, so Mattermost user messages reach the model only as user-role inbound-envelope content instead of also appearing as `System: Mattermost message...` directives. Fixes #71795. Thanks @juan-flores077.
- Agents/Anthropic: send implicit Anthropic beta headers only to direct public Anthropic endpoints, including OAuth, so custom Anthropic-compatible providers no longer mis-handle unsupported beta flags unless explicitly configured. Refs #73346. Thanks @byBrodowski.

View File

@@ -125,8 +125,9 @@ knob.
`agents.defaults.bootstrapTotalMaxChars`:
normal workspace bootstrap injection.
- `agents.defaults.startupContext.*`:
one-shot `/new` and `/reset` startup prelude, including recent daily
`memory/*.md` files.
one-shot reset/startup model-run prelude, including recent daily
`memory/*.md` files. Bare chat `/new` and `/reset` commands are
acknowledged without invoking the model.
- `skills.limits.*`:
the compact skills list injected into the system prompt.
- `agents.defaults.contextLimits.*`:
@@ -142,8 +143,9 @@ budget:
#### `agents.defaults.startupContext`
Controls the first-turn startup prelude injected on bare `/new` and `/reset`
runs.
Controls the first-turn startup prelude injected on reset/startup model runs.
Bare chat `/new` and `/reset` commands acknowledge the reset without invoking
the model, so they do not load this prelude.
```json5
{

View File

@@ -21,7 +21,7 @@ OpenClaw assembles its own system prompt on every run. It includes:
with optional per-agent override at
`agents.list[].skillsLimits.maxSkillsPromptChars`.
- Self-update instructions
- Workspace + bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md` when new, plus `MEMORY.md` when present). Lowercase root `memory.md` is not injected; it is legacy repair input for `openclaw doctor --fix` when paired with `MEMORY.md`. Large files are truncated by `agents.defaults.bootstrapMaxChars` (default: 12000), and total bootstrap injection is capped by `agents.defaults.bootstrapTotalMaxChars` (default: 60000). `memory/*.md` daily files are not part of the normal bootstrap prompt; they remain on-demand via memory tools on ordinary turns, but bare `/new` and `/reset` can prepend a one-shot startup-context block with recent daily memory for that first turn. That startup prelude is controlled by `agents.defaults.startupContext`.
- Workspace + bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md` when new, plus `MEMORY.md` when present). Lowercase root `memory.md` is not injected; it is legacy repair input for `openclaw doctor --fix` when paired with `MEMORY.md`. Large files are truncated by `agents.defaults.bootstrapMaxChars` (default: 12000), and total bootstrap injection is capped by `agents.defaults.bootstrapTotalMaxChars` (default: 60000). `memory/*.md` daily files are not part of the normal bootstrap prompt; they remain on-demand via memory tools on ordinary turns, but reset/startup model runs can prepend a one-shot startup-context block with recent daily memory for that first turn. Bare chat `/new` and `/reset` commands are acknowledged without invoking the model. The startup prelude is controlled by `agents.defaults.startupContext`.
- Time (UTC + user timezone)
- Reply tags + heartbeat behavior
- Runtime metadata (host/OS/model/thinking)

View File

@@ -159,7 +159,7 @@ Example:
- Session files: `~/.openclaw/agents/<agentId>/sessions/{{SessionId}}.jsonl`
- Session metadata (token usage, last route, etc): `~/.openclaw/agents/<agentId>/sessions/sessions.json` (legacy: `~/.openclaw/sessions/sessions.json`)
- `/new` or `/reset` starts a fresh session for that chat (configurable via `resetTriggers`). If sent alone, the agent replies with a short hello to confirm the reset.
- `/new` or `/reset` starts a fresh session for that chat (configurable via `resetTriggers`). If sent alone, OpenClaw acknowledges the reset without invoking the model.
- `/compact [instructions]` compacts the session context and reports the remaining context budget.
## Heartbeats (proactive mode)

View File

@@ -11,7 +11,7 @@ import {
makeCfg,
mockRunEmbeddedPiAgentOk,
requireSessionStorePath,
runGreetingPromptForBareNewOrReset,
expectBareNewOrResetAcknowledged,
withTempHome,
} from "../../test/helpers/auto-reply/trigger-handling-test-harness.js";
import { loadSessionStore, resolveSessionKey } from "../config/sessions.js";
@@ -375,7 +375,7 @@ describe("trigger handling", () => {
});
});
it("prepends runtime-loaded daily memory context on bare /new", async () => {
it("acknowledges bare /new without invoking the model or loading startup memory", async () => {
await withTempHome(async (home) => {
const workspaceDir = join(home, "openclaw");
const nowMs = Date.now();
@@ -392,18 +392,12 @@ describe("trigger handling", () => {
const res = await runAuthorizedSmsCommand("/new", cfg);
expect(maybeReplyText(res)).toBe("hello");
const prompt = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0]?.prompt ?? "";
expect(prompt).toContain("[Startup context loaded by runtime]");
expect(prompt).toContain(`[Untrusted daily memory: memory/${todayStamp}.md]`);
expect(prompt).toContain("BEGIN_QUOTED_NOTES");
expect(prompt).toContain("today startup note");
expect(prompt).toContain(`[Untrusted daily memory: memory/${yesterdayStamp}.md]`);
expect(prompt).toContain("yesterday startup note");
expect(maybeReplyText(res)).toBe("✅ New session started.");
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
});
});
it("treats normalized /RESET as reset for startupContext.applyOn", async () => {
it("acknowledges normalized bare /RESET without invoking the model", async () => {
await withTempHome(async (home) => {
const workspaceDir = join(home, "openclaw");
const nowMs = Date.now();
@@ -418,10 +412,8 @@ describe("trigger handling", () => {
const res = await runAuthorizedSmsCommand("/RESET", cfg);
expect(maybeReplyText(res)).toBe("hello");
const prompt = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0]?.prompt ?? "";
expect(prompt).toContain(`[Untrusted daily memory: memory/${todayStamp}.md]`);
expect(prompt).toContain("reset startup note");
expect(maybeReplyText(res)).toBe("✅ Session reset.");
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
});
});
@@ -824,7 +816,7 @@ describe("trigger handling", () => {
it("handles bare session reset, inline commands, and unauthorized inline status", async () => {
await withTempHome(async (home) => {
await runGreetingPromptForBareNewOrReset({ home, body: "/new", getReplyFromConfig });
await expectBareNewOrResetAcknowledged({ home, body: "/new", getReplyFromConfig });
await expectResetBlockedForNonOwner({ home });
await expectInlineCommandHandledAndStripped({
home,

View File

@@ -414,7 +414,7 @@ export async function expectInlineCommandHandledAndStripped(params: {
expect(text).toBe("ok");
}
export async function runGreetingPromptForBareNewOrReset(params: {
export async function expectBareNewOrResetAcknowledged(params: {
home: string;
body: "/new" | "/reset";
getReplyFromConfig: typeof import("../../../src/auto-reply/reply.js").getReplyFromConfig;
@@ -440,12 +440,8 @@ export async function runGreetingPromptForBareNewOrReset(params: {
makeCfg(params.home),
);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(text).toBe("hello");
expect(runEmbeddedPiAgentMock).toHaveBeenCalledOnce();
const prompt = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0]?.prompt ?? "";
expect(prompt).toContain("A new session was started via /new or /reset");
expect(prompt).toContain("Execute your Session Startup sequence now");
expect(prompt).toContain("read the required files before responding to the user");
expect(text).toBe(params.body === "/reset" ? "✅ Session reset." : "✅ New session started.");
expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled();
}
export function installTriggerHandlingE2eTestHooks() {