Răsfoiți Sursa

refactor: route tui through agent session

Ma 6 luni în urmă
părinte
comite
68bf504f8e

+ 1 - 1
packages/cli/src/__tests__/interaction-tools.test.ts

@@ -2,7 +2,7 @@ import { mkdtemp, mkdir, readFile, 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 { createInteractionToolsFromDeps } from "../tui/tools.js";
+import { createInteractionToolsFromDeps } from "../interaction/tools.js";
 
 const chapterResult = {
   chapterNumber: 1,

+ 0 - 163
packages/cli/src/__tests__/tui-control-flow.test.ts

@@ -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");
-  });
-});

+ 0 - 38
packages/cli/src/__tests__/tui-output.test.ts

@@ -1,38 +0,0 @@
-import { describe, expect, it } from "vitest";
-import { formatTuiResult } from "../tui/output.js";
-
-describe("tui output", () => {
-  it("summarizes a completed write flow", () => {
-    expect(formatTuiResult({
-      intent: "write_next",
-      status: "completed",
-      bookId: "harbor",
-    })).toContain("harbor");
-  });
-
-  it("summarizes a mode switch", () => {
-    expect(formatTuiResult({
-      intent: "switch_mode",
-      status: "completed",
-      mode: "auto",
-      locale: "en",
-    })).toContain("auto");
-  });
-
-  it("prefers explicit response text when provided", () => {
-    expect(formatTuiResult({
-      intent: "explain_status",
-      status: "completed",
-      responseText: "Current status: harbor is at repairing chapter 3.",
-    })).toBe("Current status: harbor is at repairing chapter 3.");
-  });
-
-  it("renders Chinese summaries when locale is zh-CN", () => {
-    expect(formatTuiResult({
-      intent: "select_book",
-      status: "completed",
-      bookId: "harbor",
-      locale: "zh-CN",
-    })).toBe("当前作品:harbor");
-  });
-});

+ 3 - 6
packages/cli/src/__tests__/tui-slash-autocomplete.test.ts

@@ -10,9 +10,9 @@ describe("tui slash autocomplete", () => {
   it("filters slash commands by prefix", () => {
     expect(getSlashSuggestions("/st", SLASH_COMMANDS)).toEqual(["/status"]);
     expect(getSlashSuggestions("/w", SLASH_COMMANDS)).toEqual(["/write"]);
-    expect(getSlashSuggestions("/o", SLASH_COMMANDS)).toEqual(["/open <book>"]);
-    expect(getSlashSuggestions("/d", SLASH_COMMANDS)).toEqual(["/draft", "/discard", "/depth <light|normal|deep>"]);
-    expect(getSlashSuggestions("/cr", SLASH_COMMANDS)).toEqual(["/create"]);
+    expect(getSlashSuggestions("/o", SLASH_COMMANDS)).toEqual([]);
+    expect(getSlashSuggestions("/d", SLASH_COMMANDS)).toEqual(["/depth <light|normal|deep>"]);
+    expect(getSlashSuggestions("/cr", SLASH_COMMANDS)).toEqual([]);
   });
 
   it("does not suggest anything for non-slash input", () => {
@@ -28,9 +28,6 @@ describe("tui slash autocomplete", () => {
 
   it("applies the selected suggestion to the composer input", () => {
     expect(applySlashSuggestion("/st", ["/status"], 0)).toBe("/status");
-    expect(applySlashSuggestion("/o", ["/open <book>"], 0)).toBe("/open ");
     expect(applySlashSuggestion("/d", ["/depth <light|normal|deep>"], 0)).toBe("/depth ");
-    expect(applySlashSuggestion("/dr", ["/draft"], 0)).toBe("/draft");
-    expect(applySlashSuggestion("/cr", ["/create"], 0)).toBe("/create");
   });
 });

+ 1 - 1
packages/cli/src/commands/interact.ts

@@ -3,7 +3,7 @@ import {
   processProjectInteractionInput,
   type InteractionRuntimeTools,
 } from "@actalk/inkos-core";
-import { createInteractionTools } from "../tui/tools.js";
+import { createInteractionTools } from "../interaction/tools.js";
 
 export interface InteractCommandHooks {
   readonly runInteraction?: (params: {

+ 1 - 1
packages/cli/src/tui/tools.ts → packages/cli/src/interaction/tools.ts

@@ -25,7 +25,7 @@ export function createCliInteractionToolsFromDeps(
   return createInteractionToolsFromDeps(pipeline, state, hooks);
 }
 
-// Backward-compatible export for the current CLI tests during the extraction phase.
+// Backward-compatible export for existing CLI interaction tests.
 export function createInteractionToolsFromDepsCompat(
   _projectRoot: string,
   pipeline: CliPipelineLike,

+ 1 - 56
packages/cli/src/tui/app.ts

@@ -1,18 +1,11 @@
 import { access } from "node:fs/promises";
 import { basename, dirname, join } from "node:path";
-import {
-  appendInteractionMessage,
-  processProjectInteractionInput,
-  type InteractionRuntimeTools,
-} from "@actalk/inkos-core";
 import { render } from "ink";
 import React from "react";
 import { InkTuiApp } from "./dashboard.js";
 import { formatModeLabel, getTuiCopy, normalizeStageLabel, resolveTuiLocale, type TuiLocale } from "./i18n.js";
-import { formatTuiResult } from "./output.js";
-import { loadProjectSession, persistProjectSession } from "./session-store.js";
+import { loadProjectSession } from "./session-store.js";
 import { detectModelInfo, detectProjectLanguage, ensureProject, interactiveLlmSetup } from "./setup.js";
-import { createInteractionTools } from "./tools.js";
 import { animateStartup } from "./effects.js";
 
 export interface TuiFrameState {
@@ -61,35 +54,6 @@ async function readVersion(): Promise<string> {
   }
 }
 
-export async function processTuiInput(
-  projectRoot: string,
-  input: string,
-  tools: InteractionRuntimeTools,
-) {
-  const projectLanguage = await detectProjectLanguage(projectRoot);
-  const locale = resolveTuiLocale(process.env, projectLanguage);
-  const result = await processProjectInteractionInput({
-    projectRoot,
-    input,
-    tools,
-  });
-  const summary = formatTuiResult({
-    intent: result.request.intent,
-    status: result.session.currentExecution?.status ?? "completed",
-    bookId: result.session.activeBookId,
-    mode: result.request.mode,
-    responseText: result.responseText,
-    locale,
-  });
-  const nextSession = appendInteractionMessage(result.session, {
-    role: "assistant",
-    content: summary,
-    timestamp: Date.now(),
-  });
-  await persistProjectSession(projectRoot, nextSession);
-  return { ...result, session: nextSession };
-}
-
 async function resolveProjectRoot(cwd: string): Promise<string> {
   // If CWD is a book directory (contains book.json), walk up to the actual project root.
   // Structure: <projectRoot>/books/<bookId>/book.json
@@ -107,7 +71,6 @@ async function resolveProjectRoot(cwd: string): Promise<string> {
 
 export async function launchTui(
   projectRoot: string,
-  toolsOverride?: InteractionRuntimeTools,
 ): Promise<void> {
   projectRoot = await resolveProjectRoot(projectRoot);
   const { hasLlmConfig } = await ensureProject(projectRoot);
@@ -134,23 +97,6 @@ export async function launchTui(
   const version = await readVersion();
   const chatStreamBridge: { onTextDelta?: (text: string) => void } = {};
 
-  let tools: InteractionRuntimeTools;
-  try {
-    tools = toolsOverride ?? (await createInteractionTools(projectRoot, {
-      onChatTextDelta: (text) => {
-        chatStreamBridge.onTextDelta?.(text);
-      },
-      onDraftTextDelta: (text) => {
-        chatStreamBridge.onTextDelta?.(text);
-      },
-    }));
-  } catch (error) {
-    const message = error instanceof Error ? error.message : String(error);
-    console.error(copy.notes.toolInitFailed(message));
-    console.error(copy.notes.toolInitHint);
-    return;
-  }
-
   const originalEmitWarning = process.emitWarning;
   process.emitWarning = (() => {}) as typeof process.emitWarning;
   const originalStderrWrite = process.stderr.write.bind(process.stderr);
@@ -172,7 +118,6 @@ export async function launchTui(
         projectName: basename(projectRoot),
         modelLabel,
         initialSession: session,
-        tools,
         chatStreamBridge,
       }),
       { exitOnCtrlC: true },

+ 11 - 54
packages/cli/src/tui/dashboard.tsx

@@ -1,10 +1,8 @@
 import React, { useEffect, useRef, useState } from "react";
 import {
   appendInteractionMessage,
-  processProjectInteractionInput,
   routeNaturalLanguageIntent,
   type InteractionIntentType,
-  type InteractionRuntimeTools,
   type InteractionSession,
 } from "@actalk/inkos-core";
 import { Box, Text, useApp, useInput } from "ink";
@@ -15,7 +13,6 @@ import { resolveChatDepthProfile, type ChatDepth } from "./chat-depth.js";
 import { appendStreamingAssistantChunk, createOptimisticUserMessageSession } from "./chat-draft.js";
 import { renderComposerDisplay } from "./composer-display.js";
 import { renderMarkdown } from "./markdown.js";
-import { formatTuiResult } from "./output.js";
 import { buildDashboardViewModel, type DashboardMessageRow } from "./dashboard-model.js";
 import { buildInputHistory, moveHistoryCursor } from "./input-history.js";
 import { formatModeLabel, getTuiCopy, normalizeStageLabel, type TuiLocale } from "./i18n.js";
@@ -34,20 +31,6 @@ import {
   isAppleTerminal,
 } from "./theme.js";
 
-function shouldUseLegacyTuiInteraction(intent: InteractionIntentType): boolean {
-  switch (intent) {
-    case "list_books":
-    case "show_book_draft":
-    case "discard_book_draft":
-    case "select_book":
-    case "pause_book":
-    case "switch_mode":
-      return true;
-    default:
-      return false;
-  }
-}
-
 export interface InkTuiDashboardProps {
   readonly locale: TuiLocale;
   readonly projectName: string;
@@ -73,7 +56,6 @@ export interface InkTuiAppProps {
   readonly projectName: string;
   readonly modelLabel: string;
   readonly initialSession: InteractionSession;
-  readonly tools: InteractionRuntimeTools;
   readonly chatStreamBridge?: {
     onTextDelta?: (text: string) => void;
     getChatRequestOptions?: () => {
@@ -373,8 +355,7 @@ export function InkTuiApp(props: InkTuiAppProps): React.JSX.Element {
         hasFailed: session.currentExecution?.status === "failed",
       });
       const userTimestamp = Date.now();
-      const assistantDraftTimestamp = (routed.intent === "chat" || routed.intent === "develop_book")
-        ? userTimestamp + 1 : null;
+      const assistantDraftTimestamp = userTimestamp + 1;
       assistantDraftTimestampRef.current = assistantDraftTimestamp;
       setActivityIntent(routed.intent);
       setIsSubmitting(true);
@@ -388,40 +369,16 @@ export function InkTuiApp(props: InkTuiAppProps): React.JSX.Element {
         appendSystemNote(copy.notes.newBookGuide);
       }
 
-      if (shouldUseLegacyTuiInteraction(routed.intent)) {
-        const result = await processProjectInteractionInput({
-          projectRoot: props.projectRoot,
-          input,
-          tools: props.tools,
-          activeBookId,
-        });
-        const summary = formatTuiResult({
-          intent: result.request.intent,
-          status: result.session.currentExecution?.status ?? "completed",
-          bookId: result.session.activeBookId,
-          mode: result.request.mode,
-          responseText: result.responseText,
-          locale: props.locale,
-        });
-        const nextSession = appendInteractionMessage(result.session, {
-          role: "assistant",
-          content: summary,
-          timestamp: Date.now(),
-        });
-        await persistProjectSession(props.projectRoot, nextSession);
-        setSession(nextSession);
-      } else {
-        const result = await processTuiAgentInput({
-          projectRoot: props.projectRoot,
-          input,
-          session,
-          activeBookId,
-          onTextDelta: (text) => {
-            props.chatStreamBridge?.onTextDelta?.(text);
-          },
-        });
-        setSession(result.session);
-      }
+      const result = await processTuiAgentInput({
+        projectRoot: props.projectRoot,
+        input,
+        session,
+        activeBookId,
+        onTextDelta: (text) => {
+          props.chatStreamBridge?.onTextDelta?.(text);
+        },
+      });
+      setSession(result.session);
     } catch (error) {
       const message = error instanceof Error ? error.message : String(error);
       const failedSession = await loadProjectSession(props.projectRoot);

+ 2 - 6
packages/cli/src/tui/effects.ts

@@ -457,15 +457,13 @@ export function buildStyledHelpSections(locale: TuiLocale = resolveTuiLocale()):
       {
         title: "Navigation",
         commands: [
-          ["/books", "List all books"],
-          ["/open <book>", "Select active book"],
+          ["/books", "Ask the agent to list books"],
           ["/status", "Show current status"],
         ],
       },
       {
         title: "Control",
         commands: [
-          ["/mode <auto|semi|manual>", "Switch automation mode"],
           ["/focus <text>", "Update current focus"],
         ],
       },
@@ -491,15 +489,13 @@ export function buildStyledHelpSections(locale: TuiLocale = resolveTuiLocale()):
     {
       title: "导航",
       commands: [
-        ["/books", "列出全部作品"],
-        ["/open <book>", "切换当前作品"],
+        ["/books", "让 agent 列出作品"],
         ["/status", "查看当前状态"],
       ],
     },
     {
       title: "控制",
       commands: [
-        ["/mode <auto|semi|manual>", "切换自动化模式"],
         ["/focus <text>", "更新当前焦点"],
       ],
     },

+ 6 - 55
packages/cli/src/tui/i18n.ts

@@ -39,8 +39,6 @@ export interface TuiCopy {
     readonly newBookGuide: string;
     readonly noLlmConfig: string;
     readonly setupProvider: string;
-    readonly toolInitFailed: (message: string) => string;
-    readonly toolInitHint: string;
   };
   readonly roles: {
     readonly user: string;
@@ -57,13 +55,6 @@ export interface TuiCopy {
     readonly readyToContinue: string;
   };
   readonly depthLabels: Record<ChatDepth, string>;
-  readonly results: {
-    readonly modeSwitched: (mode: string) => string;
-    readonly booksListed: string;
-    readonly activeBook: (bookId: string) => string;
-    readonly completed: (intent: string) => string;
-    readonly intentLabels: Partial<Record<string, string>>;
-  };
 }
 
 const ZH_CN: TuiCopy = {
@@ -94,21 +85,19 @@ const ZH_CN: TuiCopy = {
   composer: {
     placeholder: "告诉 InkOS 要写什么、修改什么,或解释什么…",
     emptyConversation: "先告诉 InkOS 你要做什么。",
-    helper: "回车发送 • /new 输入你的想法,自动构建新书 • /draft • /create • /write • /books • /open • /mode • /depth • /help",
+    helper: "回车发送 • /new 输入你的想法 • /write • /rewrite • /truth • /export • /depth • /help",
     submitting: "处理中…",
     failed: "上次请求失败",
     ready: "就绪",
   },
   notes: {
-    help: "可用命令:/new(输入想法,自动构建新书)、/draft、/create、/discard、/write、/books、/open、/mode、/rewrite、/focus、/truth、/rename、/replace、/export、/status、/clear、/depth、/quit。也支持直接输入自然语言。",
+    help: "可用命令:/new(输入想法)、/write、/books、/rewrite、/focus、/truth、/rename、/replace、/export、/status、/clear、/depth、/quit。其他写作和项目操作直接用自然语言交给 agent。",
     status: (stage, mode) => `当前状态:${stage}(${mode})。`,
     config: "当前 Ink 仪表盘里还不支持交互式 /config。请使用 inkos config set-global。",
     depthSet: (depthLabel) => `思考深度已切换为 ${depthLabel}。`,
-    newBookGuide: "开始构思新书。直接描述你的想法——题材、世界观、主角、核心冲突都可以。AI 会逐步引导你完善草案,随时用 /draft 查看进度,/create 建书。",
+    newBookGuide: "开始构思新书。直接描述你的想法——题材、世界观、主角、核心冲突都可以。AI 会逐步引导,信息足够时会直接调用建书能力。",
     noLlmConfig: "未发现 LLM 配置。",
     setupProvider: "先配置 API 提供方。",
-    toolInitFailed: (message) => `初始化 TUI 工具失败:${message}`,
-    toolInitHint: "请检查 .env,或运行:inkos config set-global",
   },
   roles: {
     user: "你",
@@ -135,24 +124,6 @@ const ZH_CN: TuiCopy = {
     normal: "标准",
     deep: "深入",
   },
-  results: {
-    modeSwitched: (mode) => `已切换到 ${mode} 模式。`,
-    booksListed: "已列出作品。",
-    activeBook: (bookId) => `当前作品:${bookId}`,
-    completed: (intent) => `已完成 ${intent}`,
-    intentLabels: {
-      write_next: "已写完下一章",
-      revise_chapter: "已修订章节",
-      rewrite_chapter: "已重写章节",
-      update_focus: "已更新焦点",
-      explain_status: "状态说明",
-      explain_failure: "失败说明",
-      pause_book: "已暂停作品",
-      rename_entity: "已重命名实体",
-      patch_chapter_text: "已修补正文",
-      edit_truth: "已更新真相文件",
-    },
-  },
 };
 
 const EN: TuiCopy = {
@@ -183,21 +154,19 @@ const EN: TuiCopy = {
   composer: {
     placeholder: "Ask InkOS to write, revise, or explain…",
     emptyConversation: "Start by asking InkOS what to do.",
-    helper: "Enter to send • /new describe your idea to start a book • /draft • /create • /write • /books • /open • /mode • /depth • /help",
+    helper: "Enter to send • /new describe your idea • /write • /rewrite • /truth • /export • /depth • /help",
     submitting: "Submitting…",
     failed: "Last request failed",
     ready: "Ready",
   },
   notes: {
-    help: "Commands: /new (describe your idea to start a book), /draft, /create, /discard, /write, /books, /open, /mode, /rewrite, /focus, /truth, /rename, /replace, /export, /status, /clear, /depth, /quit. Natural language still works.",
+    help: "Commands: /new (describe your idea), /write, /books, /rewrite, /focus, /truth, /rename, /replace, /export, /status, /clear, /depth, /quit. Other writing and project operations go through the agent as natural language.",
     status: (stage, mode) => `Status: ${stage} (${mode}).`,
     config: "Interactive /config is not available inside the Ink dashboard yet. Use inkos config set-global.",
     depthSet: (depthLabel) => `Thinking depth set to ${depthLabel}.`,
-    newBookGuide: "Starting a new book. Describe your idea — genre, world, protagonist, core conflict, anything. The AI will guide you step by step. Use /draft to check progress, /create to finalize.",
+    newBookGuide: "Starting a new book. Describe your idea — genre, world, protagonist, core conflict, anything. The AI will guide you and call the book-creation capability when enough information is available.",
     noLlmConfig: "No LLM configuration found.",
     setupProvider: "Let's set up your API provider first.",
-    toolInitFailed: (message) => `Failed to initialize TUI tools: ${message}`,
-    toolInitHint: "Check your .env or run: inkos config set-global",
   },
   roles: {
     user: "You",
@@ -224,24 +193,6 @@ const EN: TuiCopy = {
     normal: "normal",
     deep: "deep",
   },
-  results: {
-    modeSwitched: (mode) => `Mode switched to ${mode}.`,
-    booksListed: "Books listed.",
-    activeBook: (bookId) => `Active book: ${bookId}`,
-    completed: (intent) => `Completed ${intent}`,
-    intentLabels: {
-      write_next: "Chapter written",
-      revise_chapter: "Chapter revised",
-      rewrite_chapter: "Chapter rewritten",
-      update_focus: "Focus updated",
-      explain_status: "Status",
-      explain_failure: "Explanation",
-      pause_book: "Book paused",
-      rename_entity: "Entity renamed",
-      patch_chapter_text: "Text patched",
-      edit_truth: "Truth file updated",
-    },
-  },
 };
 
 export function resolveTuiLocale(

+ 0 - 39
packages/cli/src/tui/output.ts

@@ -1,39 +0,0 @@
-import type { AutomationMode, ExecutionStatus, InteractionIntentType } from "@actalk/inkos-core";
-import { formatModeLabel, getTuiCopy, resolveTuiLocale, type TuiLocale } from "./i18n.js";
-
-export function formatTuiResult(params: {
-  readonly intent: InteractionIntentType;
-  readonly status: ExecutionStatus;
-  readonly bookId?: string;
-  readonly mode?: AutomationMode;
-  readonly responseText?: string;
-  readonly locale?: TuiLocale;
-}): string {
-  const copy = getTuiCopy(params.locale ?? resolveTuiLocale());
-
-  if (params.responseText?.trim()) {
-    return params.responseText.trim();
-  }
-
-  if (params.intent === "switch_mode" && params.mode) {
-    return copy.results.modeSwitched(formatModeLabel(params.mode, copy));
-  }
-
-  if (params.intent === "list_books") {
-    return copy.results.booksListed;
-  }
-
-  if (params.intent === "select_book" && params.bookId) {
-    return copy.results.activeBook(params.bookId);
-  }
-
-  if (params.bookId) {
-    return `${intentLabel(params.intent, copy)} — ${params.bookId}`;
-  }
-
-  return intentLabel(params.intent, copy);
-}
-
-function intentLabel(intent: InteractionIntentType, copy: ReturnType<typeof getTuiCopy>): string {
-  return copy.results.intentLabels[intent] ?? copy.results.completed(intent);
-}

+ 0 - 5
packages/cli/src/tui/slash-autocomplete.ts

@@ -1,12 +1,7 @@
 export const SLASH_COMMANDS = [
   "/new 输入你的想法",
-  "/draft",
-  "/create",
-  "/discard",
   "/write",
   "/books",
-  "/open <book>",
-  "/mode <auto|semi|manual>",
   "/rewrite <n>",
   "/focus <text>",
   "/truth <file> <content>",