fix: persist first Pi user message in JSONL
This commit is contained in:
@@ -39,6 +39,7 @@
|
|||||||
- Doctor: surface plugin diagnostics in the report.
|
- Doctor: surface plugin diagnostics in the report.
|
||||||
- Plugins: treat `plugins.load.paths` directory entries as package roots when they contain `package.json` + `clawdbot.extensions`.
|
- Plugins: treat `plugins.load.paths` directory entries as package roots when they contain `package.json` + `clawdbot.extensions`.
|
||||||
- Config: expand `~` in `CLAWDBOT_CONFIG_PATH` and common path-like config fields (including `plugins.load.paths`).
|
- Config: expand `~` in `CLAWDBOT_CONFIG_PATH` and common path-like config fields (including `plugins.load.paths`).
|
||||||
|
- Agents: stop pre-creating session transcripts so first user messages persist in JSONL history.
|
||||||
- Auto-reply: align `/think` default display with model reasoning defaults. (#751) — thanks @gabriel-trigo.
|
- Auto-reply: align `/think` default display with model reasoning defaults. (#751) — thanks @gabriel-trigo.
|
||||||
- Auto-reply: flush block reply buffers on tool boundaries. (#750) — thanks @sebslight.
|
- Auto-reply: flush block reply buffers on tool boundaries. (#750) — thanks @sebslight.
|
||||||
- Docker: tolerate unset optional env vars in docker-setup.sh under strict mode. (#725) — thanks @petradonka.
|
- Docker: tolerate unset optional env vars in docker-setup.sh under strict mode. (#725) — thanks @petradonka.
|
||||||
|
|||||||
@@ -18,6 +18,48 @@ import {
|
|||||||
} from "./pi-embedded-runner.js";
|
} from "./pi-embedded-runner.js";
|
||||||
import type { SandboxContext } from "./sandbox.js";
|
import type { SandboxContext } from "./sandbox.js";
|
||||||
|
|
||||||
|
vi.mock("@mariozechner/pi-ai", async () => {
|
||||||
|
const actual = await vi.importActual<typeof import("@mariozechner/pi-ai")>(
|
||||||
|
"@mariozechner/pi-ai",
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
streamSimple: (model: { api: string; provider: string; id: string }) => {
|
||||||
|
const stream = new actual.AssistantMessageEventStream();
|
||||||
|
queueMicrotask(() => {
|
||||||
|
stream.push({
|
||||||
|
type: "done",
|
||||||
|
reason: "stop",
|
||||||
|
message: {
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "text", text: "ok" }],
|
||||||
|
stopReason: "stop",
|
||||||
|
api: model.api,
|
||||||
|
provider: model.provider,
|
||||||
|
model: model.id,
|
||||||
|
usage: {
|
||||||
|
input: 1,
|
||||||
|
output: 1,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
totalTokens: 2,
|
||||||
|
cost: {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return stream;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe("buildEmbeddedSandboxInfo", () => {
|
describe("buildEmbeddedSandboxInfo", () => {
|
||||||
it("returns undefined when sandbox is missing", () => {
|
it("returns undefined when sandbox is missing", () => {
|
||||||
expect(buildEmbeddedSandboxInfo()).toBeUndefined();
|
expect(buildEmbeddedSandboxInfo()).toBeUndefined();
|
||||||
@@ -607,4 +649,78 @@ describe("runEmbeddedPiAgent", () => {
|
|||||||
fs.stat(path.join(agentDir, "models.json")),
|
fs.stat(path.join(agentDir, "models.json")),
|
||||||
).resolves.toBeTruthy();
|
).resolves.toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("persists the first user message before assistant output", async () => {
|
||||||
|
const agentDir = await fs.mkdtemp(
|
||||||
|
path.join(os.tmpdir(), "clawdbot-agent-"),
|
||||||
|
);
|
||||||
|
const workspaceDir = await fs.mkdtemp(
|
||||||
|
path.join(os.tmpdir(), "clawdbot-workspace-"),
|
||||||
|
);
|
||||||
|
const sessionFile = path.join(workspaceDir, "session.jsonl");
|
||||||
|
|
||||||
|
const cfg = {
|
||||||
|
models: {
|
||||||
|
providers: {
|
||||||
|
openai: {
|
||||||
|
api: "openai-responses",
|
||||||
|
apiKey: "sk-test",
|
||||||
|
baseUrl: "https://example.com",
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: "mock-1",
|
||||||
|
name: "Mock Model",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 16_000,
|
||||||
|
maxTokens: 2048,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies ClawdbotConfig;
|
||||||
|
|
||||||
|
await runEmbeddedPiAgent({
|
||||||
|
sessionId: "session:test",
|
||||||
|
sessionKey: "agent:main:main",
|
||||||
|
sessionFile,
|
||||||
|
workspaceDir,
|
||||||
|
config: cfg,
|
||||||
|
prompt: "hello",
|
||||||
|
provider: "openai",
|
||||||
|
model: "mock-1",
|
||||||
|
timeoutMs: 5_000,
|
||||||
|
agentDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
const raw = await fs.readFile(sessionFile, "utf-8");
|
||||||
|
const entries = raw
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((line) => JSON.parse(line) as { type?: string; message?: unknown });
|
||||||
|
const messages = entries.filter((entry) => entry.type === "message");
|
||||||
|
const textFromContent = (content: unknown) => {
|
||||||
|
if (typeof content === "string") return content;
|
||||||
|
if (Array.isArray(content) && content[0]?.type === "text") {
|
||||||
|
return (content[0] as { text?: string }).text;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
const firstUserIndex = messages.findIndex((entry) => {
|
||||||
|
const message = entry.message as { role?: string; content?: unknown };
|
||||||
|
return (
|
||||||
|
message?.role === "user" && textFromContent(message.content) === "hello"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const firstAssistantIndex = messages.findIndex((entry) => {
|
||||||
|
const message = entry.message as { role?: string };
|
||||||
|
return message?.role === "assistant";
|
||||||
|
});
|
||||||
|
expect(firstUserIndex).toBeGreaterThanOrEqual(0);
|
||||||
|
if (firstAssistantIndex !== -1) {
|
||||||
|
expect(firstUserIndex).toBeLessThan(firstAssistantIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1474,11 +1474,6 @@ export async function runEmbeddedPiAgent(params: {
|
|||||||
: sandbox.workspaceDir
|
: sandbox.workspaceDir
|
||||||
: resolvedWorkspace;
|
: resolvedWorkspace;
|
||||||
await fs.mkdir(effectiveWorkspace, { recursive: true });
|
await fs.mkdir(effectiveWorkspace, { recursive: true });
|
||||||
await ensureSessionHeader({
|
|
||||||
sessionFile: params.sessionFile,
|
|
||||||
sessionId: params.sessionId,
|
|
||||||
cwd: effectiveWorkspace,
|
|
||||||
});
|
|
||||||
|
|
||||||
let restoreSkillEnv: (() => void) | undefined;
|
let restoreSkillEnv: (() => void) | undefined;
|
||||||
process.chdir(effectiveWorkspace);
|
process.chdir(effectiveWorkspace);
|
||||||
|
|||||||
Reference in New Issue
Block a user