Ma
|
ac7764283b
|
docs: retain Kimi open source partner banners
|
2026-07-16 02:23:02 +08:00 |
|
Ma
|
6c6574ff52
|
docs: add Kimi sponsorship banners
|
2026-07-16 02:21:30 +08:00 |
|
Ma
|
d3f6ae7e84
|
test(core): use shared timeout for root import
|
2026-07-16 01:27:27 +08:00 |
|
Ma
|
eb6c8f88bd
|
fix(core): restore direct terminal tool cards
|
2026-07-16 01:07:07 +08:00 |
|
Ma
|
6ac708a9c8
|
docs: document narrative forecast workflow
|
2026-07-16 00:27:18 +08:00 |
|
Ma
|
b98252bb49
|
feat(studio): compare narrative forecasts in book chat
|
2026-07-16 00:26:04 +08:00 |
|
Ma
|
b38f6039c2
|
docs: design Studio narrative forecast comparison
|
2026-07-15 23:20:07 +08:00 |
|
Ma
|
7dce957485
|
fix(core): fingerprint every context input the narrative forecast reads
根因:buildForecastContext 把 story_frame、volume_map、角色卡、支线看板、
章节摘要等内容读进推演提示词,但 contextFingerprint 只哈希了基准章节号、
story/state/*.json 和四个控制文档。于是修改 story_frame.md 等正史输入后,
get_narrative_forecast 仍然返回 stale:false,旧推演被误判为新鲜。
修复:新增 collectFingerprintFiles,枚举 context builder(含其 helper 的
legacy 回退路径 story_bible.md / volume_outline.md / character_matrix.md)
实际读取的全部输入文件,把「相对路径 + 内容」一起纳入哈希:
- 固定路径:book.json、四个控制文档、chapter_summaries.md、
subplot_board.md、story_frame.md、volume_map.md 及三个 legacy 回退文件
- 枚举目录:story/state/*.json、story/roles/{主要角色,次要角色,major,minor}/*.md
- 缺失文件不产生条目、空文件产生 ["path",""],因此新增/删除输入文件
也会改变指纹,且空文件与不存在可区分
- story/runtime/(含推演自身目录)不在枚举范围内,防止自引用
- 保持原有性质:内容哈希不用 mtime,条目按路径排序与读取顺序无关
兼容性影响:旧 forecast.json 里存的是旧算法指纹,在新算法下必然 mismatch,
已有推演会全部判为 stale。这是正确且期望的行为——旧指纹本来就无法证明
这些推演仍然新鲜,无需迁移逻辑。
|
2026-07-15 19:10:13 +08:00 |
|
Ma
|
7dd07f49e8
|
test(core): keep narrative forecast tools available while a production task runs
rebase 到 master 后与工具门控(a975fb5)的集成裁决:叙事推演三工具只读写
story/runtime/narrative-forecasts/ 下的非正史产物,不修改正史章节与状态,
不属于门控要拦的会改书工具——suppressProductionTools 期间保留,flag 关闭
后的完整工具表断言同步补上这三个工具。
|
2026-07-15 16:27:17 +08:00 |
|
Ma
|
148b158348
|
feat(cli): add inkos forecast create/show/select commands
- commands/forecast.ts:
- inkos forecast create [bookId] --divergence <text> --branches N
--horizon N [--model/--llm-base-url/--json]:从当前正史生成非正史
多分支推演,进度信息在非 JSON 模式下逐条输出
- inkos forecast show [bookId] <forecastId> --json:读取推演并对照当前
正史重新校验指纹,正史变化时提示已过期(同时持久化 stale 标记)
- inkos forecast select [bookId] <forecastId> <branchId> --json:只写
selected-branch-plan.md,输出明确提示不修改正史
- bookId 位置参数可省略(复用 resolveBookId 的单书自动识别);show 与
select 用变长参数手动解析以兼容 RFC 里"可选 bookId 在前"的写法,
解析函数导出供测试
- 出错时按 --json 与否输出结构化错误或 logError,并设置 exitCode=1
- program.ts:注册 forecast 命令
- 测试:命令与子命令注册、选项齐全(--divergence 必填)、位置参数解析
的四种形态与报错
|
2026-07-15 16:23:10 +08:00 |
|
Ma
|
9e493973e3
|
feat(core): add narrative forecast agent tools
- agent/forecast-tools.ts:三个 pi-agent 工具
create_narrative_forecast / get_narrative_forecast / select_narrative_branch,
TypeBox schema 即文档(每个字段的 description 标注适用工具与默认值),
参数与 forecast runner 函数签名一一对应,不在 execute 里写死 pipeline
参数;返回文本包含 forecastId、每个分支的 id/标题/意图匹配分/风险数/
前提以及下一步操作提示(可操作);错误直接抛出交给 agent loop
- create 通过 pipeline.createAgentContext("forecast", bookId) 取 LLM 上下文,
外部 AbortSignal 透传给 LLM 调用;在聊天轮内同步执行(Studio 确认式
后台任务是 v2)
- agent-session.ts:三个工具注册进有书会话的 bookTools;edit 模式保持
确定性工具集,显式排除三个 forecast 工具(create 会调 LLM)
- 更新 agent-session 工具清单快照测试,新增 forecast-tools 行为测试
(bookId 与活动书一致性校验、stale 提示、选不存在分支时报错列出可选项)
|
2026-07-15 16:23:10 +08:00 |
|
Ma
|
7b009ea39d
|
feat(core): add narrative forecast agent, prompts, renderers and runner
RFC #342 v1 的三个操作 create / get / select:
- forecast/prompts.ts:zh/en 双语提示词(系统/用户/修复三个 builder),
向模型明确要求互斥分支、只输出 JSON、字段结构与合法枚举值
- forecast/agent.ts:NarrativeForecastAgent 继承 BaseAgent,单次调用 +
一次带校验错误反馈的重试;分支数量与请求不符也算非法输出;两次都
失败直接抛错,绝不把非法输出往下传
- forecast/render.ts:从 forecast.json 确定性渲染 comparison.md(对比表 +
分支详情)与 selected-branch-plan.md(含过期警告与"不修改正史"脚注),
不需要第二次 LLM 调用
- forecast/runner.ts:
- create:读上下文 → 生成分支 → 赋 branch-N id → schema 校验通过后才写
story/runtime/narrative-forecasts/<id>/,模型输出非法时不留任何文件
- get:重算 contextFingerprint,正史变化后把 status 持久化标记为 stale
- select:只写 selected-branch-plan.md(不改 forecast.json、不碰正史),
选不存在的分支报错并列出可选分支
- index.ts:导出 forecast 模块公共 API
- 测试:agent 校验重试路径、create/get/select 全流程、兄弟分支隔离、
正史文件前后快照一致、stale 标记、非法输出零残留、越界参数先于模型
调用被拒绝
|
2026-07-15 16:23:10 +08:00 |
|
Ma
|
28a04c0a07
|
feat(core): add narrative forecast context builder with content fingerprint
- forecast/context-builder.ts:只读地收集正史输入(作者意图/当前聚焦/
当前状态/伏笔账本/故事框架/卷映射/近期章节摘要/人物关系/支线看板 +
story/state/*.json),复用 planner-context 与 outline-paths 的既有读取
函数;空书不会补建缺失的控制文件(区别于 StateManager 的 ensure 流程)
- contextFingerprint 是对正史输入的内容哈希(sha256):基准章节号 +
story/state/*.json + 四个控制/状态 markdown 的内容,与文件顺序无关,
不依赖 mtime;正史章节落地、结构化状态或控制文档变化都会改变指纹,
runtime 下的非正史文件不影响指纹
- renderForecastContextMarkdown 按书籍语言输出 zh/en 上下文 markdown,
空 section 自动跳过
- 测试:指纹确定性、状态/章节/控制文档变化触发指纹变化、无关文件不触发、
空书零副作用
|
2026-07-15 16:23:10 +08:00 |
|
Ma
|
4daa7b8dc8
|
feat(core): add narrative forecast schema and store
叙事多线推演(issue #342)v1 的数据层:
- forecast/schema.ts:Zod schema 定义 NarrativeForecast / ForecastBranch
(前提假设、未来章节节拍、人物决策、预计变化、一致性风险、不确定性、
意图匹配度),分支数限定 2-5,superRefine 拒绝重复 branchId;
parseForecastModelOutput 解析模型输出(容忍代码围栏/前后缀散文/尾逗号,
非法 JSON 或 schema 不符直接抛错,不落任何文件)
- forecast/store.ts:确定性本地存储,产物固定在
story/runtime/narrative-forecasts/<forecastId>/ 下(forecast.json、
comparison.md、selected-branch-plan.md);时钟与 id 工厂可注入,
id 冲突自动加后缀;写入前先过 schema 校验,非法数据不产生半成品目录
- 测试:schema 校验边界(分支数/重复 id/越界分数/非法风险类型)、
store 读写往返、损坏文件报错、markStale 不可变更新、路径安全
|
2026-07-15 16:23:10 +08:00 |
|
Ma
|
1420040f16
|
feat(cli): whole-book backup and restore
新增整本书的备份与恢复,解决 issue #339 第三项诉求(agent 改坏后
无处回档):
- 新增 book-backup.ts:备份是把 books/<book-id>/ 整目录复制到
.inkos/backups/<book-id>/<UTC时间戳>/。备份存放在书目录之外,
所以备份内容天然不含备份目录自身。时间戳时钟可注入(测试不依赖
真实时间),同一秒内的多个备份自动加 -2/-3 后缀。恢复前先把当前
书目录自动备份为 <时间戳>-pre-restore(防手滑),再整目录还原;
backup-id 是用户输入并拼进路径,校验为单个目录名,拒绝路径分隔符。
- CLI 新增 inkos book backup <book-id>(--list 列出既有备份)和
inkos book restore <book-id> <backup-id>,均支持 --json,
人类可读输出按环境语言双语显示(备份要能用于 book.json 已损坏
的书,所以不读书籍配置来决定语言)。
|
2026-07-15 16:19:44 +08:00 |
|
Ma
|
e25848fd71
|
feat(core): delete the latest chapter with state rollback
复用审阅"拒绝章节"的回滚机制(StateManager.rollbackToChapter),提供
正式的删除最新章能力,解决用户手动删章要同时清理索引、状态、快照、
runtime 文件的痛点:
- core 新增 deleteLatestChapter():只允许删最新章(删中间章需要重编号
后续章节并重放状态,v1 明确不支持并在报错里说明);删除前先校验回滚
目标章的状态快照存在,避免出现改到一半的状态;章节 markdown 移入
chapters/.trash/(重名自动加 -2/-3 后缀),不做物理删除;然后调用
rollbackToChapter 一次性回滚 index.json、story 状态、快照、runtime
产物和 sqlite 记忆索引。
- entity rename 的文件收集跳过点开头目录(如 chapters/.trash),
回收站内容不再被全书改名波及。
- CLI 新增 inkos chapter delete <book-id> [--chapter N],默认删最新章,
确认交互沿用 book delete 的 (y/N) + --force 惯例,支持 --json,
提示语按书籍语言双语显示。
不加聊天 agent 工具:破坏性操作先只开 CLI 入口,聊天入口等有确认卡
语义后再说。对应 GitHub issue #339 的第二项诉求(一键删除章节)。
|
2026-07-15 16:13:39 +08:00 |
|
Ma
|
9bc68fb489
|
fix(core): update index word count on chapter patch edits
patch_chapter_text(chapter-local-edit)应用补丁后原本不更新 index.json 里
该章的字数,导致局部编辑后索引字数与章节文件不一致;现在补丁写入后按
replace 路径同样的口径(去掉标题和空白后的字符数)重算并写回。
同时新增显式的字数同步入口,处理用户直接手改章节文件后的对齐:
- core 新增 syncChapterWordCounts():按书籍语言选择计数口径
(zh_chars / en_words),从章节文件重算全部字数,只在有偏差时写回
index.json;索引里有记录但文件缺失的章节保持原样并在结果里报告。
- CLI 新增 inkos chapter sync [book-id],支持 --json,人类可读输出
按书籍语言双语显示。
对应 GitHub issue #339 的第一项诉求(章节字数对齐不自动)。
|
2026-07-15 16:05:38 +08:00 |
|
Ma
|
e1eb12f636
|
feat(studio): retry the last failed chat message
GitHub issue #335 的"重试按钮"部分(推理强度参数 UI 不在本次范围)。
聊天轮请求失败后(/agent fetch 拒绝、/agent 返回 error 字段、模型返回空
内容、模型未选择、草稿会话落库失败),把这次 sendMessage 的原样参数
(text + options)记录到 SessionRuntime.lastFailedSend;ChatPage 在存在
记录且非聊天流式中时,在输入区上方显示"重试"按钮,点击调用新增的
retryLastSend action 用原参数重发,并在重发前清除记录避免重复点击双发。
不记录的情况:用户主动停止(abortSession 已先把 isChatStreaming 置回
false,各失败出口据此跳过)、确认式生产任务轮(任务失败由任务卡自己
展示)。新一轮发送开始时清除旧记录。
测试:store 层覆盖 fetch 拒绝记录、error 字段记录、重试请求体与首发
一致且记录清除、成功轮无记录、用户停止不记录、无记录时重试为空操作。
|
2026-07-15 15:48:13 +08:00 |
|
Ma
|
a975fb5423
|
feat(core): gate book-mutating tools while a production task runs
根因:后台生产任务运行期间,同会话并行聊天的 agent 仍持有全部生产工具
(agent-session.ts 的书籍会话工具表),只靠注入 backgroundTaskContext
提示词软约束,没有 host 层的硬门控——模型仍可能在任务运行中并发修改
同一本书。
修法(窄门控):
- core:AgentSessionConfig 加可选布尔 suppressProductionTools。为 true 时
工具表剔除会创建/修改书籍与产物的生产工具(sub_agent、generate_cover、
write_truth_file、rename_entity、patch_chapter_text、replace_chapter_text、
import_chapters);read/grep/ls、research/material 与 propose_action
保留——propose_action 引发的确认任务在 host 侧已有单任务 409 闸门。
该布尔纳入 Agent 缓存失效判断(与 backgroundTaskContext 同样处理),
任务开始/结束时缓存的 Agent 重建、工具表恢复。
- studio server:已判定"本会话任务运行中"并注入 backgroundTaskContext 的
同一处传 suppressProductionTools: true;任务状态提示词补一句
"生产类工具已临时不可用,任务结束后恢复"(zh/en 双语)。
|
2026-07-15 15:36:09 +08:00 |
|
Ma
|
bfae2d35cf
|
fix(studio): route id-less and compression events away from background task cards
三个事件路由缺陷(并行聊天与后台任务时互相串排):
- context:compression 处理器忽略服务端已带的 executionId,一律写进当前
聊天流消息:并行时任务 pipeline 的压缩事件会在聊天消息里长出
context-* 伪工具卡。
- 无 id 的 log / llm:progress 回退到"最近一张运行中工具卡":聊天轮还没有
自己的工具卡时会落到后台任务卡上(聊天日志串排进任务)。
- 服务端 broadcast("tool:update") 全仓无前端消费者,是死事件。
修法:
- context:compression 带 executionId 时按 id 定位任务卡,把压缩事件作为
阶段挂上去(不可变更新);卡不存在时丢弃,绝不写进聊天流消息;无 id
维持现状(聊天轮自己的压缩展示)。
- 无 id 回退改为"最近一张运行中的聊天轮工具卡":跳过带 background 标记的
任务卡;跳过后没有可挂的卡时丢弃(任务快照重放会带回任务自己的累积
日志,不丢信息)。
- 删除两处 tool:update 死广播(确认任务 onUpdate 内与聊天轮
tool_execution_update 转发),已 grep 确认零消费者、无测试引用。
|
2026-07-15 15:26:39 +08:00 |
|
Ma
|
29b6af4349
|
fix(studio): stop appending transcript messages to deleted sessions
根因:deletedSessionIds 只拦了任务快照持久化。任务成功/失败路径的
appendManualSessionMessages 仍会给已删除会话追加助手消息,底层
appendTranscriptEvents 是 mkdir + appendFile,会把已删除会话的 sessions
目录条目和 transcript 文件重新建出来(任务收尾追加正好落在删除之后)。
修法:server.ts 增加 appendSessionMessagesUnlessDeleted 守卫(检查
deletedSessionIds 后再调 appendManualSessionMessages),server 内全部
四处手动追加(外部编辑回复、确认分支的指令预写、成功收尾、失败收尾)
统一走守卫;refreshBookSessionFromTranscript 同样在会话已删除时直接
返回,不再刷新和广播标题。不改 core。
测试:把"删除运行中任务的会话"用例接回真实 transcript 实现与真实
deleteBookSession,任务错误路径走完后断言 transcript 文件不存在、
loadBookSession 返回 null(原用例只查了任务快照文件)。
|
2026-07-15 15:22:37 +08:00 |
|
Ma
|
0091c3c665
|
fix(studio): find running tasks in memory before the disk snapshot when deleting or aborting
根因:DELETE /sessions/:id 和 POST /sessions/:id/abort 都靠
loadReconciledTaskSnapshot 从磁盘快照拿 execution.id 再查 controller Map。
但任务的 AbortController 注册(同步)与首次快照持久化(在
executeConfirmedProductionAction 内部)之间有多个 await 间隙,窗口内的
删除/中止从磁盘读不到快照,刚启动的任务被漏掉、停不下来。
修法:reservedProductionSessions 从 Set<string> 升级为 Map<string, string>
(sessionId → taskId;预留时 confirmedTaskId 已生成,两处都在手)。新增
findRunningTaskController:优先查内存预留表拿 taskId → controller,磁盘
快照只作回退(覆盖进程重启后的对账场景)。DELETE 与 /abort scope=all
统一改走该入口;finally 的释放逻辑不变(Map.delete 同 key)。
测试:让任务开始时预写指令的首次 appendManualSessionMessages 挂起,制造
"controller 已注册、磁盘无快照"的窗口,断言窗口内 /abort 返回
aborted: true、DELETE 能让任务的中止信号生效且请求以错误结束。
|
2026-07-15 15:20:15 +08:00 |
|
Ma
|
28f2923fc0
|
fix(studio): reclassify a chat turn as a production task when the server starts one
根因:free-text"写下一章"(无 requestedIntent)在服务端经
isWriteNextProductionRequest 命中写章启发式后按后台生产任务执行,但前端
isConfirmedProductionSend 对 requestedIntent === undefined 直接返回 false,
该轮被当成聊天轮(isChatStreaming=true)。用户点"停止"时 ChatPage 按
聊天轮走 scope=chat,abortAgentSession 在 agent 会话注册表里找不到会话
(任务不在其中),第一次点停止什么都停不掉。
修法(前端无法预知服务端启发式,用事件驱动重分类):
- 服务端:确认式生产任务分支的 tool:start 广播加 background: true 标记,
聊天轮工具的 tool:start 不带。
- 前端 stream-events:收到带标记的 tool:start 且当前 isChatStreaming=true
时,把本轮重分类为任务轮——isChatStreaming 归 false(停止按钮据此走
scope=all 拿到任务控制器,用户可继续聊天),isStreaming 维持 true;
挂起的 fetch 返回后仍由 sendMessage 的 finally 按"是否还有任务在跑"收尾,
不会重复收尾或把任务流关掉。
- ToolExecution 加可选 background 字段并记到工具卡上;mergeTaskExecution
从任务快照恢复的卡同样补该标记(快照来源必然是后台任务,终态快照替换
整个 execution 时标记不能丢)。该标记供后续无 id 事件的回退路由使用。
|
2026-07-15 15:15:12 +08:00 |
|
Ma
|
8073aaa9f8
|
feat(studio): tag pipeline progress events with the task execution id
后台生产任务与聊天并行时,SSE 的 log / llm:progress / context:compression
事件只带 sessionId,前端只能把它们附加到"最近一张运行中的工具卡",导致
任务日志和进度串排进聊天轮的工具卡(反之亦然)。
服务端:/api/v1/agent 在构建 pipeline 之前生成确认式任务的 execution id,
经 buildPipelineConfig 新增的 executionIdForSSE 传给任务的 pipeline,该
pipeline 广播的三类进度事件都带上 executionId。按构建时打标而不是按
sessionId 查表打标:同会话并行聊天轮的 pipeline 是另一次请求单独构建的、
不带任务 id,聊天轮工具经 pipeline 广播的日志不会被误标成任务日志。
前端:log 与 llm:progress 处理器改为——事件带 executionId 时用
updateToolPartById 按 id 跨消息精确定位工具卡;不带时维持"最近一张运行中
的卡"回退(兼容聊天轮工具与旧版事件)。llm:progress 改为每个 executionId
一个节流器,任务与聊天并行时双方进度不会在"只保留最新事件"的节流器里
互相覆盖;带 id 但找不到卡的日志直接丢弃(任务快照重放会带回累积 logs),
不回退到最新卡。
测试:server 端验证任务运行期间广播的 log/llm:progress 带任务 execution
id、并行聊天轮与任务结束后的日志不带;store 端验证带 executionId 的
log/llm:progress 附加到更早的任务卡、不带的维持附加到最新运行中卡。
|
2026-07-15 12:23:20 +08:00 |
|
Ma
|
550bdf2a8a
|
fix(core): align short-fiction length validation between confirmation and runner
根因:确认卡 payload 的 zod 校验(ShortRunActionPayloadSchema)对
charsPerChapter 只做 600-1200 的并集校验,而执行层(short-fiction-runner
的 boundedInteger)按语言分段校验:en 600-800 词/章、zh 900-1200 字/章。
两层不一致导致非法组合(如 en+1100)能通过确认卡校验并把任务跑起来,
用户要等 pipeline 中途才看到报错。
修法(两层收紧,错误尽早浮出):
1. action-envelope.ts:ShortRunActionPayloadSchema 加 superRefine——
language 与 charsPerChapter 同时存在时按语言分段校验;language 缺省时
维持 600-1200 并集(此时最终语言由会话默认决定,envelope 层无法预知)。
分段区间直接引用 short-fiction 的常量(600/800、900/1200),与执行层
共用同一组数字;新增 shortRunCharsPerChapterRange / Error 两个导出,
错误文案 zh/en 双语并写明该语言的合法范围。
2. agent-tools.ts:createShortFictionRunTool 在启动 pipeline 之前,按最终
语言(payload.language ?? 会话语言 ?? zh,与 runner 默认一致)校验
charsPerChapter,越界立即抛出带合法范围的双语错误。
3. schema 即文档:propose_action 的 shortRun.charsPerChapter 与
short_fiction_run 的 charsPerChapter description 补上"按语言分段、
越界会在任务开始前被拒绝"的说明。
生效链路:propose_action 生成确认卡时(validateProposedActionPayload)、
Studio 确认请求进入时(normalizeActionPayload → 400)、工具启动前
(assertShortRunCharsPerChapter)三处都会在任务开跑前拦下非法组合。
测试:short-run-length-validation.test.ts——envelope 拒绝 en+1100 与
zh+650、接受 en+700 与 zh+1000、language 缺省时 900 合法;propose_action
对 en+1100 直接抛错;工具层会话 zh 默认 + payload en+1100 在启动前抛错且
不调用 runner;params 路径 en+1100 同样拦截;"en 未填字数 → runner 收到
undefined 并用自身 650 默认值"行为不回归。
|
2026-07-15 11:53:42 +08:00 |
|
Ma
|
fc6babc335
|
fix(studio): persist the production instruction to the transcript at task start
根因:确认式生产任务(create_book/short_run/write_next 等)的用户指令,
要等任务完成/失败后才随助手工具消息一起传给 appendManualSessionMessages,
且只作为 request_started.input 落在 transcript 里——transcript 恢复逻辑
(deriveBookSessionFromTranscript)只把 role=user 的 message 事件还原成
用户气泡,从不读取 request_started.input。后果:
1. 任务运行期间刷新页面,GET session 的消息里没有这轮用户指令,用户气泡消失;
2. 任务完成后指令依然不会作为用户气泡恢复;
3. 并行聊天的消息先写入 transcript,重新加载后聊天排在生产指令前,顺序倒置。
修法:任务开始执行前(预留名额与快照检查之后、executeConfirmedProductionAction
之前)就把用户指令作为 role=user 的消息事件写进 transcript(时间戳为开始时刻);
成功与失败(ConfirmedActionExecutionError)两条完成路径改为只追加助手工具消息,
instruction 参数传空字符串,指令不会写第二遍。刷新场景下用户气泡来自 transcript、
运行中任务卡来自快照 mergeTaskExecution 按 execution id 合并,两者共存且不重复。
测试:server.test.ts 新增三条真实 transcript 往返用例(运行中刷新含指令气泡、
并行聊天轮后顺序按真实时间、失败路径指令只写一次),并把三条既有断言更新为
"开始预写指令 + 完成只补助手消息"的新契约;action.test.ts 新增前端恢复用例
(transcript 用户气泡 + 运行中任务卡合并不重复)。
|
2026-07-15 11:46:31 +08:00 |
|
Ma
|
790aace6b9
|
fix(studio): reserve the single-task slot atomically
根因:确认式生产任务的单任务检查是"await findActiveRunningTask 读快照 →
之后才 set AbortController"的 check-then-act 结构:两个并发确认请求都能在
对方注册 controller 之前通过检查,双任务同时启动、每会话唯一的任务快照
互相覆盖,前端丢失其中一个任务卡。
修法:
- server 闭包新增 reservedProductionSessions(sessionId 集合)。进入确认
分支后、任何 await 之前同步预留名额:占位失败的请求直接按
PRODUCTION_TASK_ALREADY_RUNNING 返回 409。
- 预留成功后再走既有的快照检查(防旧进程遗留的运行中快照等边界情况),
检查移入 try 块,finally 里与 activeConfirmedTasks 一起释放,任何出错
路径都不会遗留占位。
- 预留键固定为进入分支时的 sessionId:bookSession 之后可能因建书迁移被
重新赋值,finally 释放的必须是当初预留的那个键。
测试:用 loadBookSession 做屏障让两个并发确认请求同时到达检查窗口——
恰好一个 409、一个正常执行,败者的任务没有启动,胜者的快照收敛为
completed。修复前该场景两个请求都返回 200、任务双启动。
|
2026-07-15 11:21:08 +08:00 |
|
Ma
|
ea9bce50dd
|
fix(studio): don't close a streaming chat when a terminal task snapshot lands
根因:stream-events.ts 的 task:snapshot 处理里,终态快照只要通过了
hasInFlightExecution 门槛就无条件置 stream:null、isStreaming:false 并关闭
连接。竞态场景:任务刚结束、消息里还留着 in-flight 任务卡,此刻用户发消息
建立了新连接且聊天轮正在流式——服务端在连接建立时重放终态快照,门槛通过,
正在跑的聊天流被关掉,本轮所有增量事件全部丢失。
修法:接受终态快照时照常 merge 收尾任务卡,但只有聊天轮不在流式
(!isChatStreaming,从会话 runtime 读取)时才关闭连接、置 stream:null 和
isStreaming:false;聊天轮流式中则保持连接和流式状态,等聊天轮请求结束时
由 sendMessage 的收尾逻辑处理。
测试:聊天轮流式中 + 消息有 in-flight 任务卡 + 收到该任务的终态快照 →
任务卡转 completed、连接未关、isStreaming/isChatStreaming 仍为 true;
聊天轮结束后连接正常关闭。
|
2026-07-15 11:13:44 +08:00 |
|
Ma
|
a995e6a440
|
fix(studio): abort the production task when its session is deleted
根因:DELETE /api/v1/sessions/:sessionId 只删除会话文件和任务快照,不中止
任务的 AbortController。任务在后台继续跑、继续产出内容;而且任务的
onTaskChange 持久化会把快照文件重新写回磁盘,给已删除的会话"还魂"——
前端刷新后又能看到一个属于已删除会话的任务卡。
修法:
- DELETE 时通过 loadReconciledTaskSnapshot 找到该会话运行中的任务,
abort 它的 controller。
- server 闭包里新增 deletedSessionIds 集合:删除会话时先标记,
persistConfirmedTask 写盘前检查标记,已删除会话的任务持久化(包括中止
后的错误持久化)直接跳过,不再重建快照文件。
- POST /sessions 用同一个 sessionId 重新创建会话时移除标记,新会话的
生产任务照常持久化。
测试:删除有运行中任务的会话后,任务控制器的 signal 已中止;等任务的错误
路径走完后,快照文件不存在。
|
2026-07-15 11:11:36 +08:00 |
|
Ma
|
766c28e3e1
|
feat(studio): run write_next through the background task system
根因:Quick Action"写下一章"(actionSource=quick-action、requestedIntent=write_next)
不走确认式生产任务分支——isConfirmedProductionAction 只接受 button/slash 来源、
生产意图列表里也没有 write_next,写章走的是 shouldRunDirectWriteNext 的直跑路径:
没有 taskId、没有 AbortController、不写 task-store 快照。后果是写章期间发消息时
scope=chat 的中止杀不掉它、UI 端丢失后无法取消、刷新后无法恢复进度;前端
CONFIRMED_PRODUCTION_INTENTS 也不含 write_next,这一轮被当成聊天轮,阻塞写章
期间的后续发送。
修法:
- 服务端把 write_next 纳入确认式生产任务体系:shouldRunDirectWriteNext 更名为
isWriteNextProductionRequest,命中的三种写章触发方式(显式 write_next intent、
free-text 明确写章命令、写作指令启发式)统一归一为 confirmedIntent=write_next,
与其它确认式生产 intent 走同一条任务分支,获得 taskId、AbortController、磁盘
快照与单任务 409 闸门;原直跑代码块整体删除,不留第二条不可中止的执行通道。
- executeConfirmedProductionAction 新增 write_next 工具映射:
createWriteNextChapterTool 通过 pipeline.runWithAbortSignal 调用
writeNextChapter,让任务控制器的中止信号传进写作流程;结果文本沿用原直跑
路径的中英双语文案;审稿未通过时结果带 isError=true,任务卡按错误态展示、
请求仍按 200 返回结果文本,保持原直跑路径的对外行为。
- 前端 CONFIRMED_PRODUCTION_INTENTS 增加 write_next,且 quick-action 来源的
write_next 也算生产任务发送:写章轮不置 isChatStreaming,写章期间可以继续聊天。
测试:server.test.ts 新增三条(写章期间快照 running→completed、/abort 默认
scope 能把中止信号传进写作流程并把快照收敛为 error、写章期间第二个生产任务
409);message-policy.test.ts 补 isConfirmedProductionSend 的 write_next 用例。
|
2026-07-15 11:08:27 +08:00 |
|
Ma
|
39d2edfcc9
|
feat(studio): send messages alongside a running production task
此前任务运行期间用户发消息,前端会先中止任务再发送;聊天轮结束时
agent:complete 一到就关掉 SSE 连接,把还在跑的任务流也一起关了。
本次改动(issue #341 前端部分):
1. SessionRuntime 新增 isChatStreaming(聊天轮流式中);isStreaming 保持
"聊天轮或任务运行中"语义,Sidebar/PlayHud 等现有读取方不受影响。
确认式生产任务的发送轮不置 isChatStreaming(该请求挂起到任务结束,
期间用户仍可聊天),判定逻辑与服务端 isConfirmedProductionAction 对齐。
2. sendMessage 只挡 isChatStreaming:任务在跑时允许发送。发送时关掉旧的
任务恢复连接、换成新连接(单连接原则);任务卡不受影响——服务端在新
连接建立时重放 running 快照,任务日志与收尾均按 execution id 匹配。
3. 聊天轮收尾不再一律关连接:agent:complete/agent:error/agent:aborted
到达时,聊天轮还在进行或消息里还有 in-flight 任务卡就保持连接,等任务
自己的终态事件(tool:end → agent:complete)再关闭。sendMessage 的
finally 统一按"是否还有任务在跑"决定连接与 isStreaming 的最终状态。
4. tool:end / log / llm:progress 改为跨消息定位工具卡(按 execution id /
倒序找运行中的卡),并行聊天时任务卡挂在更早的任务轮消息上,原先只在
当前 streamTs 消息里找会漏更新。
5. 停止按钮分对象:聊天轮流式中 abort 用 scope=chat(只停聊天轮,任务
继续跑,也不把任务卡标记为失败);只有任务在跑时才 scope=all 停任务。
聊天轮出错时只把本轮消息里的运行中工具标记为失败,不连带任务卡。
|
2026-07-15 01:52:13 +08:00 |
|
Ma
|
c81272febb
|
feat(studio): keep production tasks running while the user chats
任务运行期间用户发消息,服务端此前无并发防护、agent 也感知不到任务存在。
本次改动(issue #341 服务端部分):
1. 拒绝并发生产任务:确认式生产分支开头用对账后的任务快照 +
activeConfirmedTasks 判断是否已有任务在跑,有则返回 409
(PRODUCTION_TASK_ALREADY_RUNNING,双语文案),不覆盖运行中的任务快照。
2. agent 感知后台任务:聊天路径构建 agent 会话前读取同一判断,任务在跑时
把任务状态块(工具名/label、状态、已运行时长、最近 3 条日志)作为
backgroundTaskContext 注入。core 侧 AgentSessionConfig 新增该可选字段,
附加到系统提示词末尾,并纳入 Agent 缓存失效判断,保证状态块跟着任务
的启停实时更新。
3. 停止接口分作用域:POST /sessions/:id/abort 接受可选 body
{ scope: "chat" | "all" }。scope=chat 只中止聊天轮(agent loop),
不触发任务 AbortController;默认 all 维持旧行为。
|
2026-07-15 01:39:10 +08:00 |
|
Ma
|
a662224308
|
fix(core): let short-fiction output language follow the user's request instead of the session language
修复 issue #345 残留缺口:用户用中文对话要求"写一篇英文短篇"时,产出仍然是中文。
根因:
- Studio 用 inferLanguage 按 CJK/拉丁字符比例推断会话语言,中文指令推断为 zh;
- zh 分支短篇系统提示词硬编码"shortRun 填 language=zh"(en 分支同样硬编码 language=en),
即使用户明确要求另一种产出语言,模型也照抄硬编码值;
- propose_action 的 shortRun.language schema description 只写了
"Confirmed short-fiction language.",没有告诉模型产出语言可以和对话语言不同。
修法:
- agent-system-prompt.ts 短篇 zh/en 两个分支对称改为:language 填用户要求的产出语言,
用户没提时跟对话语言一致;同时把 charsPerChapter 指引改为按 language 分语言说明
(zh 每章 900-1200 字默认 1000,en 每章 600-800 个英文单词默认 650);
- agent-tools.ts 的 shortRun.language description 改为说明按用户要求的产出语言填,
可以和对话语言不同,未指定时默认对话语言。
传参链路本身无需改动:proposedActionPayload 里 { language, ...shortRun } 的展开顺序
保证模型显式填的 language 覆盖会话默认值,且会话默认只注入 language 不注入字数,
runner 在未填 charsPerChapter 时按 language 取对应默认(en 650 词),不会把 zh 字数
带给 en 任务。新增测试固定以上行为和提示词/schema 文案。
|
2026-07-15 01:01:03 +08:00 |
|
Ma
|
26e659d0ab
|
fix(studio): rewrite zombie running task snapshots after server restart
任务状态快照持久化在磁盘(.inkos/tasks/),但任务本体跑在 HTTP handler
里、AbortController 只存在内存 Map 中。server 进程在任务运行期间重启后,
磁盘上会留下永远 running 的快照:前端每次刷新都恢复出一个运行中的任务
卡,停止按钮只中止内存 controller、不更新磁盘,刷新后又变回 running。
现在三处读取快照的地方(SSE 重放、GET session、abort)统一走对账加载:
running/processing 快照若在 activeConfirmedTasks 中没有对应 controller,
只可能是进程重启过(正常流程里 controller 先于首次持久化进入 Map、晚于
终态持久化删除,无竞态窗口),直接把快照改写为 error 终态并保存。
|
2026-07-15 00:43:37 +08:00 |
|
Ma
|
80140e345f
|
fix(studio): ignore stale terminal task snapshots replayed onto new streams
服务端在每次 SSE 连接建立时都会重放该会话磁盘上的任务快照。此前前端
task:snapshot 监听器对终态快照无条件收尾并关闭 EventSource,导致会话
跑完一次确认式任务后,后续每一轮新消息的流都会在连接瞬间被旧的
completed 快照关掉:普通聊天丢失流式渲染,第二次生产任务的实时进度
整轮不可见(2e7985c 引入的回归)。
现在终态快照只在当前消息里确实存在同 id 且仍为 running/processing 的
任务卡时才生效(刷新恢复场景);否则视为上一轮任务的残留快照直接忽略。
|
2026-07-15 00:43:26 +08:00 |
|
Ma
|
fa20ad7cc8
|
fix(short): honor English language in Studio and CLI
|
2026-07-14 23:32:39 +08:00 |
|
Ma
|
d7c3f03fe2
|
fix(core): update Xiaomi MiMo API endpoint
|
2026-07-14 23:02:36 +08:00 |
|
Ma
|
d61993459a
|
fix(studio): stop reviving stale Play choices
|
2026-07-14 22:19:48 +08:00 |
|
Ma
|
2e7985c2f3
|
fix(studio): persist long-task progress across refresh
|
2026-07-14 20:14:20 +08:00 |
|
Ma
|
7ac8d53055
|
docs: replace keaiapi references with kkaiapi
|
2026-07-12 18:41:30 +08:00 |
|
Ma
|
0bca0e6e50
|
docs: simplify project title
|
2026-07-11 20:55:07 +08:00 |
|
Ma
|
1ecce46821
|
docs: present v1.7 translation workflows
|
2026-07-11 20:46:57 +08:00 |
|
Ma
|
fd87b04c3f
|
chore(release): prepare v1.7.0
v1.7.0
|
2026-07-11 18:03:27 +08:00 |
|
Ma
|
c202ce4a2d
|
fix(studio): normalize uploaded project paths
|
2026-07-11 14:15:13 +08:00 |
|
Ma
|
3fcc6ef90b
|
fix(core): recover stale book write locks
|
2026-07-11 01:47:37 +08:00 |
|
Ma
|
d316c8e4fe
|
feat(studio): complete translation review workflow
|
2026-07-10 02:39:16 +08:00 |
|
Ma
|
070e707766
|
test: cover txt translation ingestion
|
2026-07-08 14:07:04 +08:00 |
|
Ma
|
8ac666fbed
|
feat: add translation workflow
|
2026-07-08 14:03:19 +08:00 |
|
Ma
|
f71987cd0a
|
docs: promote keaiapi in English and Japanese READMEs
|
2026-07-07 20:16:17 +08:00 |
|
Ma
|
a4a9a26409
|
docs: full English CHANGELOG + README.en missing sections
新建 CHANGELOG.en.md(全 33 个版本逐条翻译,与中文版互链);
README.en 补配置排查(doctor + effective config mode 表)、15 家
服务商能力表、通知渠道纠正(补飞书/企业微信)、CLI 表 3 行与
一次性 LLM 覆盖参数、路线图平台格式导出项
|
2026-07-07 17:23:17 +08:00 |
|