Skip to content

Commit 17fc1c4

Browse files
committed
docs: document command session tests
1 parent a767c6d commit 17fc1c4

4 files changed

Lines changed: 29 additions & 0 deletions

File tree

src/agents/command/cli-compaction.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Covers CLI turn compaction lifecycle and external CLI resume-state cleanup.
12
import fs from "node:fs/promises";
23
import os from "node:os";
34
import path from "node:path";
@@ -42,6 +43,8 @@ function buildContextEngine(params: {
4243
}
4344

4445
async function writeSessionFile(params: { sessionFile: string; sessionId: string }) {
46+
// The lifecycle compacts canonical OpenClaw session JSONL, so tests write the
47+
// same session/message envelope the real store appends.
4548
await fs.mkdir(path.dirname(params.sessionFile), { recursive: true });
4649
await fs.writeFile(
4750
params.sessionFile,
@@ -116,6 +119,8 @@ describe("runCliTurnCompactionLifecycle", () => {
116119
const compactCalls: Array<Parameters<ContextEngine["compact"]>[0]> = [];
117120
const maintenance = vi.fn(async () => ({ changed: false, bytesFreed: 0, rewrittenEntries: 0 }));
118121
const settingsCwds: string[] = [];
122+
// Compaction settings should be resolved against the task cwd, not the
123+
// bootstrap workspace, because CLI prompts may run from nested repos.
119124
setCliCompactionTestDeps({
120125
resolveContextEngine: async () => buildContextEngine({ compactCalls }),
121126
createPreparedEmbeddedAgentSettingsManager: async (params) => {
@@ -183,6 +188,8 @@ describe("runCliTurnCompactionLifecycle", () => {
183188
expect(maintenanceCall?.sessionKey).toBe(sessionKey);
184189
expect(maintenanceCall?.sessionFile).toBe(sessionFile);
185190
expect(updatedEntry?.compactionCount).toBe(1);
191+
// Once OpenClaw rewrites the transcript, external CLI resume ids are stale
192+
// and must be cleared so the next turn starts from the compacted prompt.
186193
expect(updatedEntry?.cliSessionBindings?.["claude-cli"]).toBeUndefined();
187194
expect(updatedEntry?.cliSessionIds?.["claude-cli"]).toBeUndefined();
188195
expect(updatedEntry?.claudeCliSessionId).toBeUndefined();

src/agents/command/delivery.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Covers agent-command reply normalization and outbound delivery status.
12
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
23
import type { ReplyPayload } from "../../auto-reply/reply-payload.js";
34
import type { ChannelOutboundAdapter } from "../../channels/plugins/types.js";
@@ -46,6 +47,8 @@ const slackOutboundForTest: ChannelOutboundAdapter = {
4647
}),
4748
};
4849

50+
// Two registries let tests switch between no-channel and Slack-capable delivery
51+
// without loading the full plugin runtime.
4952
const emptyRegistry = createTestRegistry([]);
5053
const slackRegistry = createTestRegistry([
5154
{
@@ -172,6 +175,8 @@ async function deliverMediaReplyForTest(
172175
outboundSession: DeliverParams["outboundSession"],
173176
optsOverrides: Partial<AgentCommandOpts> = {},
174177
) {
178+
// Media replies go through the same normalizer seam as production so relative
179+
// paths are interpreted with agent/session context before delivery.
175180
const runtime = { log: vi.fn(), error: vi.fn() };
176181
return await deliverAgentCommandResult({
177182
cfg: {
@@ -213,6 +218,8 @@ describe("normalizeAgentCommandReplyPayloads", () => {
213218
});
214219

215220
it("keeps Slack directives in text for direct agent deliveries", () => {
221+
// Direct CLI deliveries preserve Slack directive markup because no channel
222+
// adapter has consumed it yet.
216223
const normalized = normalizeAgentCommandReplyPayloads({
217224
cfg: {
218225
channels: {

src/agents/command/session-store.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// Covers command-session store updates after agent runs, CLI compaction, and
2+
// runtime metadata persistence.
13
import fs from "node:fs/promises";
24
import os from "node:os";
35
import path from "node:path";
@@ -99,6 +101,8 @@ vi.mock("../../config/sessions.js", async () => {
99101
.map(([key, entry]) => [key, entry.acp]),
100102
);
101103
const result = await mutator(store);
104+
// The mocked store keeps ACP metadata sticky to preserve the production
105+
// merge behavior that protects persistent ACP session handles.
102106
for (const [key, acp] of previousAcpByKey) {
103107
const next = store[key];
104108
if (next && !next.acp) {
@@ -155,6 +159,8 @@ function acpMeta() {
155159
async function withTempSessionStore<T>(
156160
run: (params: { dir: string; storePath: string }) => Promise<T>,
157161
): Promise<T> {
162+
// Session-store tests exercise real JSON persistence, but each case gets an
163+
// isolated file so mutation order remains deterministic.
158164
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-session-store-"));
159165
try {
160166
return await run({ dir, storePath: path.join(dir, "sessions.json") });
@@ -206,6 +212,8 @@ describe("updateSessionStoreAfterAgentRun", () => {
206212
result,
207213
});
208214

215+
// The gateway write takes cache ownership and supplies single-entry
216+
// persistence so large stores are not rewritten unnecessarily.
209217
const updateOptions = sessionStoreMocks.updateSessionStore.mock.calls.at(-1)?.[2];
210218
expect(updateOptions).toMatchObject({
211219
takeCacheOwnership: true,

src/agents/command/session.resolve-session-key.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Covers cross-store session-key resolution for multi-agent session stores.
12
import { beforeEach, describe, expect, it, vi } from "vitest";
23
import type { OpenClawConfig } from "../../config/config.js";
34
import type { SessionEntry } from "../../config/sessions/types.js";
@@ -32,6 +33,8 @@ const { resolveSessionKeyForRequest, resolveStoredSessionKeyForSessionId } =
3233
await import("./session.js");
3334

3435
function mockSessionStores(storesByPath: Record<string, Record<string, SessionEntry>>): void {
36+
// Store paths are the routing boundary here; returning the exact object lets
37+
// tests assert whether callers borrowed or cloned the selected store.
3538
hoisted.loadSessionStoreMock.mockImplementation((storePath) => storesByPath[storePath] ?? {});
3639
}
3740

@@ -83,6 +86,8 @@ describe("resolveSessionKeyForRequest", () => {
8386
});
8487

8588
it("keeps a cross-store structural winner over a newer local fuzzy duplicate", () => {
89+
// Structural keys beat fuzzy timestamp matches so ACP/subagent resumes do
90+
// not accidentally attach to a newer generic main-session duplicate.
8691
const mainStore = {
8792
"agent:main:main": { sessionId: "sid", updatedAt: 20 },
8893
} satisfies Record<string, SessionEntry>;
@@ -131,6 +136,8 @@ describe("resolveSessionKeyForRequest", () => {
131136
});
132137

133138
it("borrows session stores when requested", () => {
139+
// clone=false is used by callers that intend to mutate the selected store,
140+
// so the resolver must pass that option through every candidate load.
134141
const mainStore = {
135142
"agent:main:main": { sessionId: "sid", updatedAt: 10 },
136143
} satisfies Record<string, SessionEntry>;

0 commit comments

Comments
 (0)