Merge 2bfee9429fe93b1984a6ccb647ed9ca0b4468953 into 8a05c05596ca9ba0735dafd8e359885de4c2c969

This commit is contained in:
eggyrooch-blip 2026-03-21 13:53:06 +08:00 committed by GitHub
commit eed1615394
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 128 additions and 2 deletions

View File

@ -0,0 +1,112 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { SessionEntry } from "./types.js";
const state = vi.hoisted(() => ({
sessionFile: "",
store: {} as Record<string, SessionEntry>,
appendMessage: vi.fn(() => "message-1"),
}));
vi.mock("../io.js", () => ({
loadConfig: () => ({
agents: {
list: [{ id: "sunke", default: true }],
},
}),
}));
vi.mock("../../agents/agent-scope.js", () => ({
resolveDefaultAgentId: () => "sunke",
resolveAgentWorkspaceDir: (_cfg: unknown, agentId: string) =>
`/Users/admin/.openclaw/workspace-${agentId}`,
}));
vi.mock("../../sessions/transcript-events.js", () => ({
emitSessionTranscriptUpdate: vi.fn(),
}));
vi.mock("./delivery-info.js", () => ({
parseSessionThreadInfo: () => ({ baseSessionKey: undefined, threadId: undefined }),
}));
vi.mock("./paths.js", () => ({
resolveDefaultSessionStorePath: () => "/tmp/session-store.json",
resolveSessionFilePath: () => state.sessionFile,
resolveSessionFilePathOptions: () => ({
agentId: "sunke",
sessionsDir: path.dirname(state.sessionFile),
}),
resolveSessionTranscriptPath: () => state.sessionFile,
}));
vi.mock("./session-file.js", () => ({
resolveAndPersistSessionFile: async () => ({
sessionFile: state.sessionFile,
sessionEntry: state.store["agent:main:test:user-1"],
}),
}));
vi.mock("./store.js", () => ({
loadSessionStore: () => state.store,
normalizeStoreSessionKey: (value: string) => value,
}));
vi.mock("@mariozechner/pi-coding-agent", () => ({
CURRENT_SESSION_VERSION: 2,
SessionManager: {
open: () => ({
appendMessage: state.appendMessage,
}),
},
}));
let appendAssistantMessageToSessionTranscript: typeof import("./transcript.js").appendAssistantMessageToSessionTranscript;
const originalCwd = process.cwd();
beforeEach(async () => {
vi.resetModules();
state.appendMessage = vi.fn(() => "message-1");
state.store = {
"agent:main:test:user-1": {
sessionId: "session-1",
updatedAt: Date.now(),
} as SessionEntry,
};
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-transcript-"));
const wrongWorkspace = path.join(tmpRoot, "wrong-workspace");
fs.mkdirSync(wrongWorkspace, { recursive: true });
process.chdir(wrongWorkspace);
state.sessionFile = path.join(tmpRoot, "sessions", "session-1.jsonl");
({ appendAssistantMessageToSessionTranscript } = await import("./transcript.js"));
});
afterEach(() => {
process.chdir(originalCwd);
});
describe("appendAssistantMessageToSessionTranscript", () => {
it("writes the session header cwd from the target agent workspace instead of process.cwd()", async () => {
const result = await appendAssistantMessageToSessionTranscript({
agentId: "sunke",
sessionKey: "agent:main:test:user-1",
text: "hello",
});
expect(result).toEqual({
ok: true,
sessionFile: state.sessionFile,
messageId: "message-1",
});
const [headerLine] = fs.readFileSync(state.sessionFile, "utf-8").split(/\r?\n/);
const header = JSON.parse(headerLine) as { cwd?: string };
expect(header.cwd).toBe("/Users/admin/.openclaw/workspace-sunke");
expect(header.cwd).not.toBe(process.cwd());
});
});

View File

@ -1,7 +1,9 @@
import fs from "node:fs";
import path from "node:path";
import { CURRENT_SESSION_VERSION, SessionManager } from "@mariozechner/pi-coding-agent";
import { resolveDefaultAgentId, resolveAgentWorkspaceDir } from "../../agents/agent-scope.js";
import { emitSessionTranscriptUpdate } from "../../sessions/transcript-events.js";
import { loadConfig } from "../io.js";
import { parseSessionThreadInfo } from "./delivery-info.js";
import {
resolveDefaultSessionStorePath,
@ -67,6 +69,7 @@ export function resolveMirroredTranscriptText(params: {
async function ensureSessionHeader(params: {
sessionFile: string;
sessionId: string;
cwd: string;
}): Promise<void> {
if (fs.existsSync(params.sessionFile)) {
return;
@ -77,7 +80,7 @@ async function ensureSessionHeader(params: {
version: CURRENT_SESSION_VERSION,
id: params.sessionId,
timestamp: new Date().toISOString(),
cwd: process.cwd(),
cwd: params.cwd,
};
await fs.promises.writeFile(params.sessionFile, `${JSON.stringify(header)}\n`, {
encoding: "utf-8",
@ -160,6 +163,13 @@ export async function appendAssistantMessageToSessionTranscript(params: {
return { ok: false, reason: `unknown sessionKey: ${sessionKey}` };
}
const cfg = loadConfig();
const agentId =
typeof params.agentId === "string" && params.agentId.trim()
? params.agentId.trim()
: resolveDefaultAgentId(cfg);
const sessionCwd = resolveAgentWorkspaceDir(cfg, agentId);
let sessionFile: string;
try {
const resolvedSessionFile = await resolveAndPersistSessionFile({
@ -179,7 +189,11 @@ export async function appendAssistantMessageToSessionTranscript(params: {
};
}
await ensureSessionHeader({ sessionFile, sessionId: entry.sessionId });
await ensureSessionHeader({
sessionFile,
sessionId: entry.sessionId,
cwd: sessionCwd,
});
const existingMessageId = params.idempotencyKey
? await transcriptHasIdempotencyKey(sessionFile, params.idempotencyKey)