Skip to content

Commit 7e821d4

Browse files
committed
fix(btw): stop persisting side questions
1 parent 62afc4b commit 7e821d4

File tree

2 files changed

+10
-187
lines changed

2 files changed

+10
-187
lines changed

src/agents/btw.test.ts

Lines changed: 8 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
22
import type { SessionEntry } from "../config/sessions.js";
33

44
const streamSimpleMock = vi.fn();
5-
const appendCustomEntryMock = vi.fn();
65
const buildSessionContextMock = vi.fn();
76
const getLeafEntryMock = vi.fn();
87
const branchMock = vi.fn();
@@ -13,11 +12,8 @@ const discoverModelsMock = vi.fn();
1312
const resolveModelWithRegistryMock = vi.fn();
1413
const getApiKeyForModelMock = vi.fn();
1514
const requireApiKeyMock = vi.fn();
16-
const acquireSessionWriteLockMock = vi.fn();
1715
const resolveSessionAuthProfileOverrideMock = vi.fn();
1816
const getActiveEmbeddedRunSnapshotMock = vi.fn();
19-
const waitForEmbeddedPiRunEndMock = vi.fn();
20-
const diagWarnMock = vi.fn();
2117
const diagDebugMock = vi.fn();
2218

2319
vi.mock("@mariozechner/pi-ai", () => ({
@@ -31,7 +27,6 @@ vi.mock("@mariozechner/pi-coding-agent", () => ({
3127
branch: branchMock,
3228
resetLeaf: resetLeafMock,
3329
buildSessionContext: buildSessionContextMock,
34-
appendCustomEntry: appendCustomEntryMock,
3530
}),
3631
},
3732
}));
@@ -54,13 +49,8 @@ vi.mock("./model-auth.js", () => ({
5449
requireApiKey: (...args: unknown[]) => requireApiKeyMock(...args),
5550
}));
5651

57-
vi.mock("./session-write-lock.js", () => ({
58-
acquireSessionWriteLock: (...args: unknown[]) => acquireSessionWriteLockMock(...args),
59-
}));
60-
6152
vi.mock("./pi-embedded-runner/runs.js", () => ({
6253
getActiveEmbeddedRunSnapshot: (...args: unknown[]) => getActiveEmbeddedRunSnapshotMock(...args),
63-
waitForEmbeddedPiRunEnd: (...args: unknown[]) => waitForEmbeddedPiRunEndMock(...args),
6454
}));
6555

6656
vi.mock("./auth-profiles/session-override.js", () => ({
@@ -70,12 +60,11 @@ vi.mock("./auth-profiles/session-override.js", () => ({
7060

7161
vi.mock("../logging/diagnostic.js", () => ({
7262
diagnosticLogger: {
73-
warn: (...args: unknown[]) => diagWarnMock(...args),
7463
debug: (...args: unknown[]) => diagDebugMock(...args),
7564
},
7665
}));
7766

78-
const { BTW_CUSTOM_TYPE, runBtwSideQuestion } = await import("./btw.js");
67+
const { runBtwSideQuestion } = await import("./btw.js");
7968

8069
function makeAsyncEvents(events: unknown[]) {
8170
return {
@@ -99,7 +88,6 @@ function createSessionEntry(overrides: Partial<SessionEntry> = {}): SessionEntry
9988
describe("runBtwSideQuestion", () => {
10089
beforeEach(() => {
10190
streamSimpleMock.mockReset();
102-
appendCustomEntryMock.mockReset();
10391
buildSessionContextMock.mockReset();
10492
getLeafEntryMock.mockReset();
10593
branchMock.mockReset();
@@ -110,11 +98,8 @@ describe("runBtwSideQuestion", () => {
11098
resolveModelWithRegistryMock.mockReset();
11199
getApiKeyForModelMock.mockReset();
112100
requireApiKeyMock.mockReset();
113-
acquireSessionWriteLockMock.mockReset();
114101
resolveSessionAuthProfileOverrideMock.mockReset();
115102
getActiveEmbeddedRunSnapshotMock.mockReset();
116-
waitForEmbeddedPiRunEndMock.mockReset();
117-
diagWarnMock.mockReset();
118103
diagDebugMock.mockReset();
119104

120105
buildSessionContextMock.mockReturnValue({
@@ -128,15 +113,11 @@ describe("runBtwSideQuestion", () => {
128113
});
129114
getApiKeyForModelMock.mockResolvedValue({ apiKey: "secret", mode: "api-key", source: "test" });
130115
requireApiKeyMock.mockReturnValue("secret");
131-
acquireSessionWriteLockMock.mockResolvedValue({
132-
release: vi.fn().mockResolvedValue(undefined),
133-
});
134116
resolveSessionAuthProfileOverrideMock.mockResolvedValue("profile-1");
135117
getActiveEmbeddedRunSnapshotMock.mockReturnValue(undefined);
136-
waitForEmbeddedPiRunEndMock.mockResolvedValue(true);
137118
});
138119

139-
it("streams blocks and persists a non-context custom entry", async () => {
120+
it("streams blocks without persisting BTW data to disk", async () => {
140121
const onBlockReply = vi.fn().mockResolvedValue(undefined);
141122
streamSimpleMock.mockReturnValue(
142123
makeAsyncEvents([
@@ -212,17 +193,6 @@ describe("runBtwSideQuestion", () => {
212193
text: "Side answer.",
213194
btw: { question: "What changed?" },
214195
});
215-
await vi.waitFor(() => {
216-
expect(appendCustomEntryMock).toHaveBeenCalledWith(
217-
BTW_CUSTOM_TYPE,
218-
expect.objectContaining({
219-
question: "What changed?",
220-
answer: "Side answer.",
221-
provider: "anthropic",
222-
model: "claude-sonnet-4-5",
223-
}),
224-
);
225-
});
226196
});
227197

228198
it("returns a final payload when block streaming is unavailable", async () => {
@@ -641,14 +611,7 @@ describe("runBtwSideQuestion", () => {
641611
);
642612
});
643613

644-
it("returns the BTW answer and retries transcript persistence after a session lock", async () => {
645-
acquireSessionWriteLockMock
646-
.mockRejectedValueOnce(
647-
new Error("session file locked (timeout 250ms): pid=123 /tmp/session.lock"),
648-
)
649-
.mockResolvedValueOnce({
650-
release: vi.fn().mockResolvedValue(undefined),
651-
});
614+
it("returns the BTW answer without appending transcript custom entries", async () => {
652615
streamSimpleMock.mockReturnValue(
653616
makeAsyncEvents([
654617
{
@@ -688,26 +651,10 @@ describe("runBtwSideQuestion", () => {
688651
});
689652

690653
expect(result).toEqual({ text: "323" });
691-
await vi.waitFor(() => {
692-
expect(waitForEmbeddedPiRunEndMock).toHaveBeenCalledWith("session-1", 30000);
693-
expect(appendCustomEntryMock).toHaveBeenCalledWith(
694-
BTW_CUSTOM_TYPE,
695-
expect.objectContaining({
696-
question: "What is 17 * 19?",
697-
answer: "323",
698-
}),
699-
);
700-
});
654+
expect(buildSessionContextMock).toHaveBeenCalled();
701655
});
702656

703-
it("logs deferred persistence failures through the diagnostic logger", async () => {
704-
acquireSessionWriteLockMock
705-
.mockRejectedValueOnce(
706-
new Error("session file locked (timeout 250ms): pid=123 /tmp/session.lock"),
707-
)
708-
.mockRejectedValueOnce(
709-
new Error("session file locked (timeout 10000ms): pid=123 /tmp/session.lock"),
710-
);
657+
it("does not log transcript persistence warnings because BTW no longer writes to disk", async () => {
711658
streamSimpleMock.mockReturnValue(
712659
makeAsyncEvents([
713660
{
@@ -747,11 +694,9 @@ describe("runBtwSideQuestion", () => {
747694
});
748695

749696
expect(result).toEqual({ text: "323" });
750-
await vi.waitFor(() => {
751-
expect(diagWarnMock).toHaveBeenCalledWith(
752-
expect.stringContaining("btw transcript persistence skipped: sessionId=session-1"),
753-
);
754-
});
697+
expect(diagDebugMock).not.toHaveBeenCalledWith(
698+
expect.stringContaining("btw transcript persistence skipped"),
699+
);
755700
});
756701

757702
it("excludes tool results from BTW context to avoid replaying raw tool output", async () => {

src/agents/btw.ts

Lines changed: 2 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,10 @@ import { getApiKeyForModel, requireApiKey } from "./model-auth.js";
2121
import { ensureOpenClawModelsJson } from "./models-config.js";
2222
import { EmbeddedBlockChunker, type BlockReplyChunking } from "./pi-embedded-block-chunker.js";
2323
import { resolveModelWithRegistry } from "./pi-embedded-runner/model.js";
24-
import {
25-
getActiveEmbeddedRunSnapshot,
26-
waitForEmbeddedPiRunEnd,
27-
} from "./pi-embedded-runner/runs.js";
24+
import { getActiveEmbeddedRunSnapshot } from "./pi-embedded-runner/runs.js";
2825
import { mapThinkingLevel } from "./pi-embedded-runner/utils.js";
2926
import { discoverAuthStorage, discoverModels } from "./pi-model-discovery.js";
3027
import { stripToolResultDetails } from "./session-transcript-repair.js";
31-
import { acquireSessionWriteLock } from "./session-write-lock.js";
32-
33-
const BTW_CUSTOM_TYPE = "openclaw:btw";
34-
const BTW_PERSIST_TIMEOUT_MS = 250;
35-
const BTW_PERSIST_RETRY_WAIT_MS = 30_000;
36-
const BTW_PERSIST_RETRY_LOCK_MS = 10_000;
3728

3829
type SessionManagerLike = {
3930
getLeafEntry?: () => {
@@ -47,97 +38,6 @@ type SessionManagerLike = {
4738
buildSessionContext: () => { messages?: unknown[] };
4839
};
4940

50-
type BtwCustomEntryData = {
51-
timestamp: number;
52-
question: string;
53-
answer: string;
54-
provider: string;
55-
model: string;
56-
thinkingLevel: ThinkLevel | "off";
57-
reasoningLevel: ReasoningLevel;
58-
sessionKey?: string;
59-
authProfileId?: string;
60-
authProfileIdSource?: "auto" | "user";
61-
usage?: unknown;
62-
};
63-
64-
async function appendBtwCustomEntry(params: {
65-
sessionFile: string;
66-
timeoutMs: number;
67-
entry: BtwCustomEntryData;
68-
}) {
69-
const lock = await acquireSessionWriteLock({
70-
sessionFile: params.sessionFile,
71-
timeoutMs: params.timeoutMs,
72-
allowReentrant: false,
73-
});
74-
try {
75-
const persisted = SessionManager.open(params.sessionFile);
76-
persisted.appendCustomEntry(BTW_CUSTOM_TYPE, params.entry);
77-
} finally {
78-
await lock.release();
79-
}
80-
}
81-
82-
function isSessionLockError(error: unknown): boolean {
83-
const message = error instanceof Error ? error.message : String(error);
84-
return message.includes("session file locked");
85-
}
86-
87-
function deferBtwCustomEntryPersist(params: {
88-
sessionId: string;
89-
sessionFile: string;
90-
entry: BtwCustomEntryData;
91-
}) {
92-
void (async () => {
93-
try {
94-
await waitForEmbeddedPiRunEnd(params.sessionId, BTW_PERSIST_RETRY_WAIT_MS);
95-
await appendBtwCustomEntry({
96-
sessionFile: params.sessionFile,
97-
timeoutMs: BTW_PERSIST_RETRY_LOCK_MS,
98-
entry: params.entry,
99-
});
100-
} catch (error) {
101-
const message = error instanceof Error ? error.message : String(error);
102-
diag.warn(`btw transcript persistence skipped: sessionId=${params.sessionId} err=${message}`);
103-
}
104-
})();
105-
}
106-
107-
async function persistBtwCustomEntry(params: {
108-
sessionId: string;
109-
sessionFile: string;
110-
entry: BtwCustomEntryData;
111-
}) {
112-
try {
113-
await appendBtwCustomEntry({
114-
sessionFile: params.sessionFile,
115-
timeoutMs: BTW_PERSIST_TIMEOUT_MS,
116-
entry: params.entry,
117-
});
118-
} catch (error) {
119-
if (!isSessionLockError(error)) {
120-
throw error;
121-
}
122-
deferBtwCustomEntryPersist({
123-
sessionId: params.sessionId,
124-
sessionFile: params.sessionFile,
125-
entry: params.entry,
126-
});
127-
}
128-
}
129-
130-
function persistBtwCustomEntryInBackground(params: {
131-
sessionId: string;
132-
sessionFile: string;
133-
entry: BtwCustomEntryData;
134-
}) {
135-
void persistBtwCustomEntry(params).catch((error) => {
136-
const message = error instanceof Error ? error.message : String(error);
137-
diag.warn(`btw transcript persistence skipped: sessionId=${params.sessionId} err=${message}`);
138-
});
139-
}
140-
14141
function collectTextContent(content: Array<{ type?: string; text?: string }>): string {
14242
return content
14343
.filter((part): part is { type: "text"; text: string } => part.type === "text")
@@ -347,7 +247,7 @@ export async function runBtwSideQuestion(
347247
throw new Error("No active session context.");
348248
}
349249

350-
const { model, authProfileId, authProfileIdSource } = await resolveRuntimeModel({
250+
const { model, authProfileId } = await resolveRuntimeModel({
351251
cfg: params.cfg,
352252
provider: params.provider,
353253
model: params.model,
@@ -483,31 +383,9 @@ export async function runBtwSideQuestion(
483383
throw new Error("No BTW response generated.");
484384
}
485385

486-
const customEntry = {
487-
timestamp: Date.now(),
488-
question: params.question,
489-
answer,
490-
provider: model.provider,
491-
model: model.id,
492-
thinkingLevel: params.resolvedThinkLevel ?? "off",
493-
reasoningLevel: params.resolvedReasoningLevel,
494-
sessionKey: params.sessionKey,
495-
authProfileId,
496-
authProfileIdSource,
497-
usage: finalMessage?.usage,
498-
} satisfies BtwCustomEntryData;
499-
500-
persistBtwCustomEntryInBackground({
501-
sessionId,
502-
sessionFile,
503-
entry: customEntry,
504-
});
505-
506386
if (emittedBlocks > 0) {
507387
return undefined;
508388
}
509389

510390
return { text: answer };
511391
}
512-
513-
export { BTW_CUSTOM_TYPE };

0 commit comments

Comments
 (0)