45bfae6e94
Initial import of all source code, config, and README assets: the packages workspace (cli, core, server, web, docs, landing, skills), build scripts, tooling config, and CI workflows. Includes the data-layout revision made on this branch: the local data root defaults to ~/.penguin/data (PENGUIN_HOME still overrides; the installer keeps its binaries in ~/.penguin), and every Agent lives under <project>/agents/<agent>/ — path helpers, the three agent-enumeration scans, the system prompt, built-in Skills, tests and docs all follow the new layout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ihk8iQuo3kv2aPjAYEPuR
103 lines
4.0 KiB
TypeScript
103 lines
4.0 KiB
TypeScript
/**
|
|
* Integration tests for createCoreSessionLoader (#3/#13): failures recovering a
|
|
* historical Session (Workspace deleted / Model removed from config / Trace
|
|
* missing session_meta) all collapse into HttpError(409, session_unrecoverable),
|
|
* preserving the original core message instead of bubbling up as a 500.
|
|
*/
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { createAgent, saveProjectConfig, sessionMeta, userText } from "@prismshadow/penguin-core";
|
|
import type { SessionMetaPayload } from "@prismshadow/penguin-core";
|
|
import { createCoreSessionLoader } from "../src/runtime/session-manager.js";
|
|
import type { SessionRow } from "../src/db/repos/sessions.js";
|
|
import { HttpError } from "../src/http/errors.js";
|
|
import { makeTempRoot, writeTraceFile } from "./helpers.js";
|
|
|
|
const PROJECT = "project-loader";
|
|
const AGENT = "default_agent";
|
|
const SID = "session-2026-07-06-11-00-00-abcd0001";
|
|
|
|
function meta(overrides: Partial<SessionMetaPayload> = {}): SessionMetaPayload {
|
|
return {
|
|
session_id: SID,
|
|
model_id: "custom/m1",
|
|
provider: "custom",
|
|
model_context_window: 1000,
|
|
system_prompt: "sp",
|
|
tools: [],
|
|
thinking_level: "default",
|
|
agent_state: "/tmp/a",
|
|
workspace: path.join("/tmp", "does-not-exist-xyz"),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function row(workspace: string): SessionRow {
|
|
return {
|
|
sessionId: SID,
|
|
projectId: PROJECT,
|
|
agentId: AGENT,
|
|
modelId: "custom/m1",
|
|
provider: "custom",
|
|
workspace,
|
|
approvalMode: "always-ask",
|
|
title: null,
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
}
|
|
|
|
describe("session-loader", () => {
|
|
let root: string;
|
|
|
|
beforeEach(async () => {
|
|
root = await makeTempRoot();
|
|
// Initialize Agent State (createAgent creates the directory and system_config.yaml).
|
|
await createAgent({ root, projectId: PROJECT, agentId: AGENT });
|
|
// Configure Model m1 so recovery doesn't fail on a missing Model (unless the test deletes it on purpose).
|
|
await saveProjectConfig(root, PROJECT, {
|
|
default_model: { provider: "custom", model_id: "m1" },
|
|
models: [{ provider: "custom", model_id: "m1", context_window: 1000 }],
|
|
});
|
|
});
|
|
afterEach(async () => {
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
});
|
|
|
|
it("有 Trace 但 Workspace 已删 → 409 session_unrecoverable,保留原文", async () => {
|
|
await writeTraceFile(root, PROJECT, AGENT, "2026-07-06", SID, 1, [
|
|
sessionMeta(meta()), // workspace points to a nonexistent directory
|
|
userText("hi"),
|
|
]);
|
|
const loader = createCoreSessionLoader(root);
|
|
const err = await loader.load(row("/tmp/does-not-exist-xyz")).catch((e) => e);
|
|
expect(err).toBeInstanceOf(HttpError);
|
|
expect((err as HttpError).status).toBe(409);
|
|
expect((err as HttpError).code).toBe("session_unrecoverable");
|
|
expect((err as HttpError).message).toContain("Workspace 已不存在");
|
|
});
|
|
|
|
it("有 Trace 但 Model 已从配置移除 → 409 session_unrecoverable", async () => {
|
|
const ws = path.join(root, "ws");
|
|
await fs.mkdir(ws, { recursive: true });
|
|
await writeTraceFile(root, PROJECT, AGENT, "2026-07-06", SID, 1, [
|
|
sessionMeta(meta({ model_id: "removed-model", workspace: ws })),
|
|
userText("hi"),
|
|
]);
|
|
const loader = createCoreSessionLoader(root);
|
|
const err = await loader.load(row(ws)).catch((e) => e);
|
|
expect(err).toBeInstanceOf(HttpError);
|
|
expect((err as HttpError).status).toBe(409);
|
|
expect((err as HttpError).code).toBe("session_unrecoverable");
|
|
expect((err as HttpError).message).toContain("Model 不在 Project 配置中");
|
|
});
|
|
|
|
it("无 Trace 且 Workspace 已删(自愈分支)→ 409 workspace_missing", async () => {
|
|
const loader = createCoreSessionLoader(root);
|
|
const err = await loader.load(row("/tmp/gone-workspace-abc")).catch((e) => e);
|
|
expect(err).toBeInstanceOf(HttpError);
|
|
expect((err as HttpError).status).toBe(409);
|
|
expect((err as HttpError).code).toBe("workspace_missing");
|
|
});
|
|
});
|