feat(project): 发布 NovAI v1.0.0 - #18
Merged
Merged
Conversation
- core/llm: 流式请求接入 AbortSignal,停止时抛 AgentAbortedError 保留已生成内容,且不触发非流式 fallback - core/query: Agent Loop 每轮检查停止信号,工具批次执行前跳过剩余工具 - core/session: 停止作为正常结束,追加已停止摘要,状态回 waiting-user - app/chat-store: 持有 AbortController,新增 isRunning/isStopping 与 abortRun - app/chat-panel: 发送按钮运行中切换为停止,停止中显示禁用 spinner 与提示文本
- 新建 SettingsModal 组件,从 SettingsView 迁移为按需挂载弹窗 - ProjectView 通过 isSettingsOpen 状态用 v-if 挂载,每次打开重新读取磁盘配置 - 删除 /project/:id/settings 路由与 SettingsView 页面,设置全走模态框 - 新增 PasswordInput 通用组件,右侧眼睛切换明文/密文 - 设置弹窗三处 API Key 输入框统一替换为 PasswordInput
- ContentPanel 预览/原始两态升级为预览/编辑/原始三态分段控件 - 编辑模式:纯 textarea 编辑章节/提示词/要素三类文件,Ctrl/⌘+S 保存 - 草稿 dirty 标记:修改后头部显示未保存红点,保存后自动消失 - 可编辑范围校验:chapters/prompts/elements.md,不支持时编辑置灰 - core 新增 writeFile 通用写入封装,project store 新增 saveFile action - 新增 Tooltip 组件并接入 ActivityBar 与 ContentPanel,替换原生 title
- core: ChatMessage / ChatMessageView / RunAgentTurnInput 加 quote 字段 - core: runTurn 透传 quote 到 runChatTurn(根因修复点) - core: createUserMessage 与 buildAgentMessages 全链路透传 quote - core: buildAgentUserContext 注入「用户引用的内容」段到模型输入 - core: agent_run_start 日志补充 quote 字段便于排查 - app: chatStore sendMessage 接收并透传 quote - app: 新增 SelectionChip 引用 chip 组件 - app: ContentPanel 预览模式 mouseup 捕获选中文字(500 字截断) - app: ChatPanel 输入区上方显示 chip 并在发送时透传 quote - app: ProjectView 串联引用状态,切换文件自动清除 - app: MessageItem 用户气泡渲染独立引用块
项目卡片章节数恒为 0 的根因:core 已算出的 chapterCount 在 toProjectView 映射时被丢弃,存储链路从不持久化,app 首页加载时只能硬编码 0。 - 打通计数数据流:activateProject 写入时透传 metadata.chapterCount/ elementCount,IndexedDB 记录新增两个计数字段(兼容旧记录,读取兜底 0) - 新增要素数:summarizeProject 统计 elements 目录文件数,HomeView 卡片 改为「N 章 · M 个要素」横排展示 - 首屏静默刷新:首页加载后后台对已授权项目扫描目录更新计数(不弹权限框), 解决「必须打开一遍才显示数量」;updateRecentProjectCounts 仅改计数, 保留 lastOpenedAt 避免破坏列表排序
R7:新增 ResizeHandle 拖拽手柄组件,内容面板宽度可在 240~720px 区间拖拽调整,双击手柄重置默认 320px,宽度持久化到 localStorage。 拖拽基于 mousedown 起始宽度计算目标宽度,避免增量累加;拖拽中 暂停 aside 过渡动画保证跟手,松手后恢复。
R4:输入框输入 @ 触发场景选择弹层,支持筛选与键盘导航(↑↓/Enter/ Esc),选中后清除 @token 并激活场景。输入框上方常驻场景 chip (📍 绿色调),点 × 关闭场景,与提示词列表的激活标记双向联动。 core 层零改动,复用现有 activeScenePromptPath 配置。
同步 R7/Agent停止/要素LLM/章节数修复的落地状态到文档子模块, 并修正 INTERFACE.md 要素提取状态描述。
R6:输入 / 弹出斜杠命令菜单(SlashCommandMenu),列出可用命令, 支持筛选与键盘导航。选中「提取要素」后展开章节多选 (ChapterPicker),逐章提取要素并在对话流展示候选预览,确认后 写入 elements/*.md。多章节提取时同类型同名候选项智能合并。 新增命令注册表 slash-commands.ts,后续可扩展 /校对、/整理 等。 提取流程状态独立 useElementExtraction composable 管理,不污染 Agent 对话流。移除 ContentPanel 原要素提取按钮区。core 层零改动, 复用已解耦的 previewElementExtraction / writeExtractedElements。
Agent Step 1:写工具(CreateFile/EditFile/RenameFile/DeleteFile)通过 新增的 extractFileChange 在成功 output 上声明 FileChange, 经 AgentToolResultMessage.fileChange 与 tool-result 事件透传。 agent-service 的 collectChangedFiles 改读结构化字段,删除正则 isSuccessfulToolResult/extractTrashPath/collectToolResultTextById 等文本反推逻辑;session.lastWrittenPath 同步改读 fileChange。 补 file-tools extractFileChange 与 tool-execution 透传测试。
Agent Step 2/3:写工具(CreateFile/EditFile/RenameFile/DeleteFile) 执行前暂停等待用户确认,拒绝则返回拒绝结果回灌模型让其调整。 core 层: - ToolDefinition 新增 buildConfirmation;4 个写工具各自实现, 从 validatedInput 构造 WriteConfirmation(edit/create 带文本用于 diff,rename/delete 仅路径级)。 - tool-execution 在 run 前拦截写工具:调 confirm 回调等待决定, 拒绝不执行 run 并返回结构化拒绝结果;确认中断(停止)按拒绝处理。 - tool-orchestration/query/session 透传 confirm;query 每次写操作都确认。 - agent-service 新增确认注册表 confirmationMap + respondConfirmation, confirm 回调发 confirmation-required 事件并 await UI 响应; 出错时 rejectPendingConfirmations 清理未决确认。 app 层: - chat store 新增 pendingConfirmation + confirmWriteTool/rejectWriteTool。 - 新增 WriteConfirmationCard.vue(抄 ExtractionFlowPanel 预览样式), 按 kind 渲染 create 内容预览 / edit old-new diff / rename from-to / delete 路径说明,确认/拒绝按钮。 - ChatPanel 在消息列表与输入框之间挂载确认卡片。 测试:tool-execution 新增 4 个确认用例(接受执行 / 拒绝不执行 / 只读工具不触发 / 确认中断按拒绝)。
删除未被模型驱动 Agent Loop 调用的旧残留: - session.ts: confirmPendingFileChange / discardPendingFileChange / createPendingFileChange / callTool / loadRecentChapters / flattenChapterPaths 及 analyzeTurnMode / buildUserPrompt 等 helper 与对应 import(readProjectTextFile / createToolRuntimeContext 等), 共约 390 行。 - chat/tools.ts: 整个文件删除(旧 ToolDefinition.call 体系,零外部引用)。 - types/chat.ts: 删除 PendingFileChange / ToolRuntimeContext / 旧 ToolDefinition 类型与 ChatSessionState.pendingFileChange / lastTaskType 字段。 新确认流程(commit 0707809)已替代这些旧代码。
Agent Step 5:用户本轮说「不要读文件」「别改」等否定短语时, 自动解析并强制禁用对应工具,模型即使无视约束也拦得住。 约束机制(借鉴 Claude Code 双层防御): - 工具可见性过滤(filterAvailableTools):被禁工具的 schema 不发 给模型,从源头减少无效调用往返;input.tools 完整 map 保留做兜底。 - 执行层硬拦截(isToolDisabledByPolicy):在 tool-call 事件后、写 确认前判定,命中直接返回禁用结果回灌模型,100% 兜底。 - user context 动态注入约束声明(describeActivePolicy):只在有 约束时注入,说明具体禁了哪些工具,引导模型不绕过。 识别与分类: - 新增 tool-policy.ts:parseToolPolicy 关键词解析(否定词+读/写 动作词窗口匹配,「不要碰文件」通用全禁)+ 按 isReadOnly 二分 (不硬编码工具名,未来加新工具自动归类)+ describePolicyDenial。 - 透传链路照抄 confirm:agent-service 解析 instruction 生成 toolPolicy → ChatTurnInput → session → query → runAgentTools → executeAgentTool。 - 约束 per-turn 即时,不持久化到 session。 测试:tool-policy 19 个用例(解析 6 + 判定 3 + 描述 2 + 过滤 4 + 声明 4)+ tool-execution 4 个策略用例(禁读拒绝 / 禁写拒绝 / 允许 正常 / 策略优先于确认不弹窗)。
Agent Step 6:修复 buildAgentMessages 复用分支忽略本轮 systemPrompt/ scenePrompt 的 bug——同会话内改 prompts/system.md 或切换场景后, Agent 仍用首轮旧 system message。 机制:runChatTurn 每轮对 buildAgentSystemPrompt(systemPrompt+scenePrompt) 的输出算 FNV-1a hash,与 session.systemPromptHash 比对;不同则 in-place 替换 agentMessages[0](system message)的 content 并更新 hash。 system message 恒在 index 0,in-place 替换不破坏消息序列合法性。 首轮建立 hash 基线,后续轮据此检测变化。 附带:提取共享 hash 工具 util/hash.ts,统一 ReadFileState / RAG indexer / system prompt 三处原本各自重复的 FNV-1a 实现。 测试:hash 4 个用例(稳定/差异/格式兼容/空串)+ refreshSystemMessageContent 5 个用例(替换 index0/不可变/非 system 原样返回/空数组/保留 tool 消息)。
core: 会话状态新增 title/createdAt/updatedAt 元字段; 新增 session-store,落盘到 .novel/sessions/<id>.json。 core: agent-service 的 sessionMap 升级为多会话结构, 新增 listSessions/renameSession/deleteSession, runTurn 每轮结束落盘,首轮发送后用首句截断自动生成标题。 app: chat store 增加 sessions 列表与 initSessions/select/create/rename/delete 五个 action。 ui: 重写 ConversationList 占位为会话列表, 支持新建/切换/重命名/删除;CategoryPanel 与 ProjectView 接通 props 与事件链路。
- loadSessions 不再副作用式重置 activeSessionId,激活态清理由 明确语义的调用点自行处理,消除列表刷新竞态误清隐患。 - runServiceTurn 的 finally 改为仅标题变化时才全量刷新列表, 避免每轮对话都全量重扫文件系统。 - deleteSession 先本地移除项减少闪烁,空列表时复用 createNewSession(skipReload) 本地插入,省一次全量扫描。 - createNewSession 新增 skipReload 选项,initSessions 复用避免 重复全量扫描。
Enter 提交后 input 卸载触发 blur 会再次调用 commitRename, 导致 rename 事件被 emit 两次。加入 renamingId 防重入守卫。
Agent 控制能力 Step 5 第二轮,新增两类更细的工具约束策略: - 只读不写:识别「只读不改」「只看不写」「只分析不修改」等正面表达, 命中则 allowWrite:false,读取工具保留 - 只操作当前文件:识别「只改当前文件」「就在这个文件改」「别动其他文件」 等表达,结合 activeFilePath 设 allowedWritePath 白名单;activeFilePath 为空时降级为禁写 实现要点: - ToolPolicy 新增 allowedWritePath 字段承载路径约束 - parseToolPolicy 签名扩展为 (instruction, activeFilePath?),路径约束 是更具体的意图,覆盖「别动」被误判的全禁写 - 执行层新增 isWriteBlockedByPathPolicy,RenameFile 恒拦,EditFile/ CreateFile/DeleteFile 比对 path;插入在确认流程之前 - agent-service 解析点传入 activeFilePath - app 层 sendMessage 补传当前打开文件路径 测试新增 12 个用例,全量 91 个测试通过,类型检查通过。
- agent-service: sessionMap 改 LRU 上限16,命中回插刷新顺序,超限淘汰最旧 - agent-service: 新增 evictProjectSessions,项目关闭/删除时释放驻留会话 - project-service: closeProject 与 deleteRecentProject 接入会话缓存清理 - project-fs: 新增 listFilesInDirectory 原语,只遍历单个子目录 - session-store: listSessionMetas 改用 listFilesInDirectory 去掉全树扫描 - 新增 agent-service.session-cache.test.ts 覆盖 LRU 与项目级清理
- 新增 @orama/orama 依赖(core 包运行时依赖) - 新增 orama-index.ts:维护每个 projectId 一个内存 Orama 实例的注册表, buildOramaIndex/getOramaIndex(懒构建)/dropOramaIndex/searchOrama, 向量维度运行时确定,维度不匹配时降级为空召回 - search.ts 改写:searchRagCandidates 改走 searchOrama,删除本地 cosineSimilarity 全量遍历与 matchesFilters 过滤逻辑 - indexer.ts 接入:buildProjectIndex 在 upsert 后基于 IndexedDB 全量 当前状态重建内存 Orama(保证增量/全量一致);markProjectIndexStale 不 drop 内存索引(stale 仅改 meta 不改数据,drop 会触发无谓懒重建) - 过滤策略:type/lastUpdatedChapter 交 Orama where 精确过滤;tags 因中文 tokenizer 分词不稳定改为 Orama 多召回 + 本地精确兜底,避免 topK 截断 - 新增 orama-index.test.ts(10 例)与 search.test.ts(5 例)覆盖召回排序、 topK、三类过滤、rebuild 不泄漏、维度降级、drop 清空
将 RAG 索引状态/重建入口从设置页移到主工作区底部常驻状态栏, 要素写入后由事件总线自动反映 stale,用户点击状态栏才重建(控制权在用户); 重建时用 contentHash 短路,未变文件复用旧向量,只对变更文件调 Embedding。 core: - 新增 index-events.ts:EventTarget 事件总线,markProjectIndexStale / buildProjectIndex 写完 meta 后 dispatchIndexChange,跨 service 传播 - indexer.ts: buildProjectIndex 循环加 contentHash 短路(与 writer 的 normalizeForComparison 口径对齐,排除 updatedAt 行避免误触发), 旧文档 model/dim/version 全一致才复用向量跳过 Embedding; 增量场景清理已删除要素(removeDocuments);如实返回 skippedCount - rag-service.ts: rebuildIndex 支持可选 sourcePaths(有则增量,无则全量); 新增 subscribeIndexChange 透传事件总线供 app 层订阅 app: - 新建 useIndexStore (stores/index.ts):持有响应式 indexMeta, init 订阅事件 + 首次 inspect,dispose 退订;rebuild 切 busy 态 - 新建 IndexStatusBar.vue:底部常驻状态栏,健康度指示点 + 状态文案, stale/error 可点击触发重建,hover 显示构建时间/错误 - ProjectView: 根容器改纵向 flex,四面板包进内层,状态栏挂底部; onMounted init / onUnmounted dispose;去掉要素写入的 stale toast - SettingsModal: 移除向量索引卡片及相关逻辑(约 -90 行) - ExtractionFlowPanel: stale 提示文案改为指向底部状态栏 测试:新增 indexer.test.ts(6 例 contentHash 短路/模型变化/删除清理)与 index-events.test.ts(3 例);全量 121 测试通过,typecheck + build 干净
- chat store 新增 runStatusType 状态机(idle/running/error),收口所有 runStatus 赋值点,UI 不再靠字符串匹配上色 - IndexStatusBar 升级为项目状态栏:左侧新增紧凑场景指示(📍 名称 ×,无壳内联),右侧新增本轮执行状态并按 type 上色 - ChatPanel 移除输入框上方 SceneChip 与底部 runStatus 行,清理 activeSceneName prop - ProjectView 删除 watch runStatus 弹 toast(状态栏已常驻展示,重复)
场景提示词展示已移至 IndexStatusBar,该组件无引用方,清理掉。
项目级累积记忆,对齐 Claude Code /init 模式:斜杠命令是一段驱动 prompt, 完全复用现有 Agent Loop 扫描项目并生成/更新项目总览,不做后台自动更新。 注入侧(每轮自动读到 NovAI.md): - core/fs/project-fs.ts: 新增 readNovAiOverview(照 readScenePrompt 模式,缺失返回空串) - core/agent/prompt.ts: buildAgentSystemPrompt 加 novaiOverview 参,按 user system prompt → 项目总览 → 场景提示词 顺序拼可变头部 - types/chat.ts + services/agent-service.ts + chat/session.ts: ChatTurnInput 新增 novaiOverview 字段并一路透传到 buildAgentMessages 与 runChatTurn 两处 buildAgentSystemPrompt 调用;复用 Step 6 hash 刷新机制,NovAI.md 变化即刷新 初始化与修复: - core/project/defaults.ts: 新增 DEFAULT_NOVAI_OVERVIEW 空骨架(段落结构与 驱动 prompt 的生成结构对齐,便于手动与自动两种维护方式衔接) - core/fs/project-fs.ts: createProject 写骨架、repairProject 温和补齐 (不纳入 inspectProject 强制检测,避免给旧项目报缺失) 触发侧(/生成项目记忆 斜杠命令): - app/constants/slash-commands.ts: 注册命令 - core/agent/init-novel-prompt.ts: 驱动 prompt(Phase 化:扫描 → 生成 → 汇报, 照搬 Claude Code 删掉这行会犯错吗 质量判据,约束已存在文件先 ReadFile 再 EditFile 不静默覆盖) - services/agent-service.ts: re-export INIT_NOVEL_PROMPT - app/components/layout/ChatPanel.vue: 选中命令后 sendMessage(INIT_NOVEL_PROMPT) UI 入口: - app/components/category/PromptList.vue: 新增项目总览块,显式渲染 prompts/NovAI.md (原 PromptList 用硬编码白名单只认 system.md 和 scenes/,NovAI.md 在根目录被漏掉) 测试: 新增 prompt.test.ts 7 用例(注入/段落顺序/空白兜底/hash 变化/固定规则保留); 全量 104 测试通过,typecheck 零错误 对应决策 0004(推翻 0003),详见 docs submodule 更新
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
验证
release-v1.0.0tag 触发部署流程honlnk/dev已推送到远端