|
@@ -1,163 +0,0 @@
|
|
|
-import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
|
|
|
|
|
-import { tmpdir } from "node:os";
|
|
|
|
|
-import { join } from "node:path";
|
|
|
|
|
-import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
|
|
|
-import { createProjectSession, persistProjectSession } from "../tui/session-store.js";
|
|
|
|
|
-import { processTuiInput } from "../tui/app.js";
|
|
|
|
|
-
|
|
|
|
|
-let projectRoot: string;
|
|
|
|
|
-
|
|
|
|
|
-describe("tui control flow", () => {
|
|
|
|
|
- beforeAll(async () => {
|
|
|
|
|
- projectRoot = await mkdtemp(join(tmpdir(), "inkos-tui-control-"));
|
|
|
|
|
- await mkdir(join(projectRoot, "books", "harbor"), { recursive: true });
|
|
|
|
|
- await writeFile(join(projectRoot, "books", "harbor", "book.json"), "{}", "utf-8");
|
|
|
|
|
- await writeFile(join(projectRoot, "inkos.json"), JSON.stringify({ language: "zh" }), "utf-8");
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- afterAll(async () => {
|
|
|
|
|
- // tmpdir cleanup omitted
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- it("routes continue through the active project book", async () => {
|
|
|
|
|
- await persistProjectSession(projectRoot, {
|
|
|
|
|
- ...createProjectSession(projectRoot),
|
|
|
|
|
- activeBookId: "harbor",
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const tools = {
|
|
|
|
|
- listBooks: vi.fn(async () => ["harbor"]),
|
|
|
|
|
- writeNextChapter: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- reviseDraft: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- patchChapterText: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- renameEntity: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- updateCurrentFocus: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- updateAuthorIntent: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- writeTruthFile: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const result = await processTuiInput(projectRoot, "continue", tools);
|
|
|
|
|
-
|
|
|
|
|
- expect(tools.writeNextChapter).toHaveBeenCalledWith("harbor");
|
|
|
|
|
- expect(result.session.activeBookId).toBe("harbor");
|
|
|
|
|
- expect(result.session.currentExecution?.status).toBe("waiting_human");
|
|
|
|
|
- expect(result.session.pendingDecision?.kind).toBe("review-next-step");
|
|
|
|
|
- expect(result.session.pendingDecision?.summary).toContain("等待");
|
|
|
|
|
- expect(result.session.messages.at(0)?.role).toBe("user");
|
|
|
|
|
- expect(result.session.messages.at(-1)?.role).toBe("assistant");
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- it("routes focus updates through the interaction runtime", async () => {
|
|
|
|
|
- await persistProjectSession(projectRoot, {
|
|
|
|
|
- ...createProjectSession(projectRoot),
|
|
|
|
|
- activeBookId: "harbor",
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const tools = {
|
|
|
|
|
- listBooks: vi.fn(async () => ["harbor"]),
|
|
|
|
|
- writeNextChapter: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- reviseDraft: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- patchChapterText: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- renameEntity: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- updateCurrentFocus: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- updateAuthorIntent: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- writeTruthFile: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- await processTuiInput(projectRoot, "把 focus 拉回旧案线", tools);
|
|
|
|
|
-
|
|
|
|
|
- expect(tools.updateCurrentFocus).toHaveBeenCalledWith("harbor", "把 focus 拉回旧案线");
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- it("persists automation mode changes through the session", async () => {
|
|
|
|
|
- await persistProjectSession(projectRoot, {
|
|
|
|
|
- ...createProjectSession(projectRoot),
|
|
|
|
|
- activeBookId: "harbor",
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const tools = {
|
|
|
|
|
- listBooks: vi.fn(async () => ["harbor"]),
|
|
|
|
|
- writeNextChapter: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- reviseDraft: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- patchChapterText: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- renameEntity: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- updateCurrentFocus: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- updateAuthorIntent: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- writeTruthFile: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const result = await processTuiInput(projectRoot, "切换到全自动", tools);
|
|
|
|
|
-
|
|
|
|
|
- expect(result.session.automationMode).toBe("auto");
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- it("routes truth-file edits through the shared control flow", async () => {
|
|
|
|
|
- await persistProjectSession(projectRoot, {
|
|
|
|
|
- ...createProjectSession(projectRoot),
|
|
|
|
|
- activeBookId: "harbor",
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const tools = {
|
|
|
|
|
- listBooks: vi.fn(async () => ["harbor"]),
|
|
|
|
|
- writeNextChapter: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- reviseDraft: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- patchChapterText: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- renameEntity: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- updateCurrentFocus: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- updateAuthorIntent: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- writeTruthFile: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- await processTuiInput(projectRoot, "/truth current_focus.md Bring it back", tools);
|
|
|
|
|
-
|
|
|
|
|
- expect(tools.writeTruthFile).toHaveBeenCalledWith(
|
|
|
|
|
- "harbor",
|
|
|
|
|
- "current_focus.md",
|
|
|
|
|
- "Bring it back",
|
|
|
|
|
- );
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- it("binds the active book through /open in multi-book projects", async () => {
|
|
|
|
|
- await mkdir(join(projectRoot, "books", "beta"), { recursive: true });
|
|
|
|
|
- await writeFile(join(projectRoot, "books", "beta", "book.json"), "{}", "utf-8");
|
|
|
|
|
- await persistProjectSession(projectRoot, createProjectSession(projectRoot));
|
|
|
|
|
-
|
|
|
|
|
- const tools = {
|
|
|
|
|
- listBooks: vi.fn(async () => ["harbor", "beta"]),
|
|
|
|
|
- writeNextChapter: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- reviseDraft: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- patchChapterText: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- renameEntity: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- updateCurrentFocus: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- updateAuthorIntent: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- writeTruthFile: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const result = await processTuiInput(projectRoot, "/open beta", tools);
|
|
|
|
|
-
|
|
|
|
|
- expect(result.session.activeBookId).toBe("beta");
|
|
|
|
|
- expect(result.session.messages.at(-1)?.content).toContain("beta");
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- it("formats interactive summaries in English when the project language is en", async () => {
|
|
|
|
|
- await writeFile(join(projectRoot, "inkos.json"), JSON.stringify({ language: "en" }), "utf-8");
|
|
|
|
|
- await persistProjectSession(projectRoot, createProjectSession(projectRoot));
|
|
|
|
|
-
|
|
|
|
|
- const tools = {
|
|
|
|
|
- listBooks: vi.fn(async () => ["harbor", "beta"]),
|
|
|
|
|
- writeNextChapter: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- reviseDraft: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- patchChapterText: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- renameEntity: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- updateCurrentFocus: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- updateAuthorIntent: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- writeTruthFile: vi.fn(async () => ({ ok: true })),
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const result = await processTuiInput(projectRoot, "/open beta", tools);
|
|
|
|
|
-
|
|
|
|
|
- expect(result.session.messages.at(-1)?.content).toBe("Active book: beta");
|
|
|
|
|
-
|
|
|
|
|
- await writeFile(join(projectRoot, "inkos.json"), JSON.stringify({ language: "zh" }), "utf-8");
|
|
|
|
|
- });
|
|
|
|
|
-});
|
|
|