thincoder 0.7.5 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,133 +2,134 @@
2
2
 
3
3
  **Sharp Code, Zero Bloat.**
4
4
 
5
- **一个"" AI 编程 agent:纯** `.mjs`**、无构建、零 npm 依赖、Node.js 原生。**
5
+ **A "thin" AI coding agent: pure `.mjs`, no build step, zero npm dependencies, native Node.js.**
6
6
 
7
- ThinCoder "Thin" 不是"功能单薄",而是**思维锐利、直击要害**——像刀刃。
8
- AI agent 都在卷"全能"的今天,ThinCoder 打的是反面那张牌:**克制、精准、不废话**。
9
- 它的人设是一个"话不多、一刀见血"的极客工程师:你给它复杂需求,它还你干净实现。
7
+ The "Thin" in ThinCoder doesn't mean "feature-poor" — it means **sharp thinking, straight to the point** — like a blade.
8
+ While every AI agent races to be "all-powerful", ThinCoder plays the opposite card: **restraint, precision, no filler**.
9
+ Its persona is a geek engineer of few words who cuts to the bone: give it a complex requirement, get back a clean implementation.
10
10
 
11
- 设计哲学(也是这个名字的全部含义):只用 Node 标准库能实现的功能,就绝不引入依赖。整个项目的 `node_modules` 是空的。
11
+ Design philosophy (the entire meaning of the name): if the Node standard library can do it, no dependency is allowed. The project's `node_modules` is empty.
12
12
 
13
- ## 特性
13
+ ## Features
14
14
 
15
- - **Agent 主循环**:LLM ↔ 工具调用循环,上限 100 轮防失控,完成守卫拦截未验证的改动
16
- - **代码库理解** ⭐0.5.0:`repo_outline`(依赖大纲,启动自动注入)、`code_search`(源码 FTS5 + 向量 + JSDoc 提取)、`doc_search`(文档按 ## 标题分块检索)——后台索引、写文件自动增量更新、三工具按"结构→意图→细节"引导
17
- - **模型适配** ⭐:只跟顶流、只跟最新。内置 DeepSeek / Kimi / GLM / Qwen / MiniMax 五家国内顶流厂商的旗舰模型,不做老旧模型兼容、不做本地模型适配。自动匹配上下文窗口、截断续写协议(prefix/partial)、思考模式 API(thinking.type / reasoning_effort)、输出上限。
18
- - **工具集**:`read` / `write` / `edit` / `bash` / `glob`(支持 `**`) / `grep` / `websearch` / `ls` / `fetch` + `read_image`(图片/视频粘贴) + 三个检索工具 + MCP,全部零依赖,文件工具目录隔离
19
- - **记忆系统**:三层(personal/project/team),FTS5 + 向量 RRF 混合检索,markdown 格式 git 友好
20
- - **两段式工具调度**:权限确认串行,只读工具并行,副作用工具串行
21
- - **会话持久化** ⭐0.5.0:最多 5 个归档槽位,`/session` 随时切换,恢复时工具结果可见
22
- - **子 agent 并发**:`explore`/`plan`/`coder` 三种角色,并行派发,流式输出可见,报告进对话区
23
- - **Plan Mode**:只读探索 + 方案设计,用户确认后实现
24
- - **AUTO 模式**:`/auto` 完全授权,长任务免确认
25
- - **任务跟踪**:`task` 工具拆解多步任务,状态栏 ✓n/m 实时进度,自动过滤已完成项
26
- - **Goal/Verify/Skills**:长目标跟踪、完成验证、可复用技能
27
- - **流式 TUI**:裸 ANSI,权限预览紧挨输入框,write/edit 自动展示 diff,多模态模型时输入框右上角显示粘贴快捷键提示(Win: Alt+V / Mac/Linux: Ctrl+V
15
+ - **Agent loop**: LLM ↔ tool-call loop, 100-turn cap against runaway, completion guard blocks unverified changes
16
+ - **Codebase understanding** ⭐0.5.0: `repo_outline` (dependency outline, auto-injected at startup), `code_search` (source FTS5 + vectors + JSDoc extraction), `doc_search` (docs chunked by ## headings) — background indexing, auto-incremental updates on file writes, three tools guided by "structure → intent → details"
17
+ - **Model adaptation** ⭐: top-tier only, latest only. Built-in flagship models from five leading Chinese vendors — DeepSeek / Kimi / GLM / Qwen / MiniMax. No legacy model compatibility, no local model support. Auto-matched context windows, truncation-resume protocols (prefix/partial), thinking-mode APIs (thinking.type / reasoning_effort), reasoning_content echo strategies (reasoningEcho), output limits, temperature range clamping — all five deeply adapted.
18
+ - **Toolset**: `read` / `write` / `edit` / `bash` / `glob` (supports `**`) / `grep` / `websearch` / `ls` / `fetch` + `read_image` (image/video paste) + three retrieval tools + MCP — all zero-dependency, file tools confined to the working directory
19
+ - **Memory system**: three layers (personal/project/team), FTS5 + vector RRF hybrid retrieval, git-friendly markdown format
20
+ - **Two-phase tool scheduling**: permission prompts serialized, read-only tools parallelized, side-effect tools serialized
21
+ - **Session persistence** ⭐0.5.0: up to 5 archive slots, `/session` to switch anytime, tool results visible after restore
22
+ - **Concurrent subagents**: three roles — `explore`/`plan`/`coder` — dispatched in parallel, streaming output visible, reports land in the conversation
23
+ - **Plan Mode**: read-only exploration + design, implement after user approval
24
+ - **AUTO mode**: `/auto` full authorization, no confirmations on long tasks
25
+ - **Task tracking**: `task` tool breaks down multi-step work, status bar ✓n/m live progress, auto-filters completed items
26
+ - **Goal/Verify/Skills**: long-goal tracking, completion verification, reusable skills
27
+ - **Streaming TUI**: bare ANSI, permission preview right above the input box, write/edit auto-shows diffs, paste shortcut hint in the input box corner for multimodal models (Win: Alt+V / Mac/Linux: Ctrl+V)
28
28
 
29
- ## 记忆系统:一人学到,全队皆知
29
+ ## Memory: What One Learns, the Whole Team Knows
30
30
 
31
- 三层记忆,全部"有就查、没有就跳过",统一混合检索:
31
+ Three layers, all "query if present, skip if absent", unified hybrid retrieval:
32
32
 
33
- | | 位置 | 同步方式 |
34
- | ---------------- | --------------------------------------------------- | --------------------------------------------------------------------------------- |
35
- | **Personal** | `~/.thincoder/memory.db`(sqlite| 不同步,私有 |
36
- | **Project** | 项目仓库 `.thincoder/memory/*.md` | 随项目 gitThinCoder **只写文件,绝不替你 commit**) |
37
- | **Team**(可选) | 独立记忆仓库,clone `~/.thincoder/teams/<name>/` | `thincoder sync`(pull --rebase);写入时自动 commit + push(专用设施,可选启用) |
33
+ | Layer | Location | Sync method |
34
+ | ---------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
35
+ | **Personal** | `~/.thincoder/memory.db` (sqlite) | Not synced, private |
36
+ | **Project** | `.thincoder/memory/*.md` in the project repo | With the project git (ThinCoder **only writes files, never commits for you**) |
37
+ | **Team** (opt.) | Dedicated memory repo, cloned to `~/.thincoder/teams/<name>/` | `thincoder sync` (pull --rebase); auto commit + push on write (dedicated facility, opt-in) |
38
38
 
39
- - **混合检索**:FTS5BM25,中文逐字索引,双字词可命中)+ embedding 向量(暴力余弦)+ RRF(k=60) 融合排序
40
- - **embedding**:OpenAI 兼容 `/v1/embeddings`,默认 SiliconFlow `BAAI/bge-m3`(免费额度,中文好);Ollama 本地可作离线选项。向量惰性生成——写入不算,首次搜索时补算落库
41
- - **条目格式**:Markdown + frontmattertype/title/tags/author/created),GitHub 上直接可读可 review;每条目一个文件,天然规避合并冲突;真冲突时诚实报错,绝不自动合并
42
- - **双轨沉淀**:规范靠手动写(`memory_put`),经验靠 `/distill` 从会话提取——**LLM 出候选、逐条人工 y/n 确认**才入库,绝不做全自动沉淀
43
- - **检索隔离**:Project 层按项目路径隔离,A 项目的记忆不会漏进 B 项目
39
+ - **Hybrid retrieval**: FTS5 (BM25, per-character CJK indexing, bigrams matchable) + embedding vectors (brute-force cosine) + RRF(k=60) fusion ranking
40
+ - **Embeddings**: OpenAI-compatible `/v1/embeddings`, defaults to SiliconFlow `BAAI/bge-m3` (free tier, good CJK support); Ollama works as an offline option. Vectors generated lazily — not computed on write, backfilled and persisted on first search
41
+ - **Entry format**: Markdown + frontmatter (type/title/tags/author/created), readable and reviewable directly on GitHub; one file per entry, naturally avoiding merge conflicts; real conflicts produce honest errors, never auto-merged
42
+ - **Dual-track accumulation**: conventions written manually (`memory_put`), experience extracted from sessions via `/distill` — **the LLM proposes candidates, a human confirms each y/n** before anything is stored; never fully automatic
43
+ - **Retrieval isolation**: the Project layer is isolated by project path — project A's memories never leak into project B
44
44
 
45
- ## 要求
45
+ ## Requirements
46
46
 
47
47
  - Node.js >= 24
48
- - 一个 OpenAI 兼容端点的 API key
49
- - 可选:embedding 服务的 key(不配置则退化为纯 FTS 检索)
48
+ - An API key for any OpenAI-compatible endpoint
49
+ - Optional: an embedding service key (without it, retrieval degrades to pure FTS)
50
50
 
51
- ## 快速开始
51
+ ## Quick Start
52
52
 
53
53
  ```bash
54
- # 安装
54
+ # Install
55
55
  npm install -g thincoder
56
56
 
57
- # 启动 TUI(默认命令)
57
+ # Launch the TUI (default command)
58
58
  thincoder
59
59
  ```
60
60
 
61
- 首次启动会自动进入初始配置向导:方向键选提供商(内置预设或自定义端点)→ 输入 API key → 可选填 embedding keySiliconFlow,开启记忆向量检索,可跳过)→ 方向键选模型,全程不用手编配置文件。之后随时可用 `/provider`、`/model`、`/config embedkey` 调整。`chat`/`distill` 在终端下没配 key 时也会就地问答式配置(管道/CI 环境则报错退出并提示)。
61
+ First launch walks you through a setup wizard: arrow keys to pick a provider (built-in presets or a custom endpoint) → enter API key → optionally enter an embedding key (SiliconFlow, enables vector memory search, skippable) → arrow keys to pick a model — no hand-editing config files. Adjust anytime with `/provider`, `/model`, `/config embedkey`. `chat`/`distill` also offer in-place interactive setup when no key is configured in a terminal (in pipes/CI they exit with an error and instructions).
62
62
 
63
- 也可以直接手写配置 `~/.thincoder/config.json`(见下文"配置"),然后:
63
+ You can also hand-write `~/.thincoder/config.json` (see "Configuration" below), then:
64
64
 
65
65
  ```bash
66
- # 一次性问答(管道友好)
67
- thincoder chat "读一下 package.json 并总结"
66
+ # One-shot Q&A (pipe-friendly)
67
+ thincoder chat "read package.json and summarize it"
68
68
 
69
- # 记忆管理
70
- thincoder memory put --type=rule --title="代码规范" --content="不加分号"
71
- thincoder memory search "代码规范"
69
+ # Memory management
70
+ thincoder memory put --type=rule --title="code style" --content="no semicolons"
71
+ thincoder memory search "code style"
72
72
  thincoder memory list
73
73
  thincoder memory remove 1
74
74
 
75
- # 团队记忆(可选,配置 memory.team 后可用)
76
- thincoder sync # 拉取团队仓库并重建索引
75
+ # Team memory (optional, available after configuring memory.team)
76
+ thincoder sync # pull the team repo and rebuild the index
77
77
 
78
- # 从会话记录提取知识(逐条确认后入库)
78
+ # Extract knowledge from a session transcript (stored after per-item confirmation)
79
79
  thincoder distill session.txt
80
80
 
81
- # 升级
81
+ # Upgrade
82
82
  thincoder upgrade
83
83
  ```
84
84
 
85
- 从源码运行:把上面的 `thincoder` 换成 `node bin/thincoder.mjs`。
85
+ Running from source: replace `thincoder` above with `node bin/thincoder.mjs`.
86
86
 
87
- TUI 内斜杠命令:`/help`、`/model`(方向键选择全部 provider 的全部模型;`/model <名称>` 直接切换)、`/provider`(增/删 provider、配 key,支持自定义端点)、`/think`(思维模式开关与推理强度)、`/config`(查看配置、`/config embedkey` embedding key、`/config set` 改参数)、`/session`(列出/切换归档会话)、`/reindex`(重建索引)、`/distill`(从当前会话提取知识)、`/clear`、`/exit`。输入 `/` 时状态栏实时提示匹配命令。
87
+ Slash commands in the TUI: `/help`, `/model` (arrow-key picker across all models of all providers; `/model <name>` switches directly), `/provider` (add/remove providers, set keys, custom endpoints), `/think` (thinking mode toggle and reasoning effort), `/config` (view config, `/config embedkey` for the embedding key, `/config set` for parameters), `/session` (list/switch archived sessions), `/reindex` (rebuild the index), `/distill` (extract knowledge from the current session), `/clear`, `/exit`. Typing `/` shows live matching hints in the status bar.
88
88
 
89
- 环境变量:`THINCODER_API_KEY`(或 `DEEPSEEK_API_KEY` / `OPENAI_API_KEY`)、`THINCODER_BASE_URL`、`THINCODER_MODEL`、`SILICONFLOW_API_KEY`。
89
+ Environment variables: `THINCODER_API_KEY` (or `DEEPSEEK_API_KEY` / `OPENAI_API_KEY`), `THINCODER_BASE_URL`, `THINCODER_MODEL`, `SILICONFLOW_API_KEY`.
90
90
 
91
- ## 配置
91
+ ## Configuration
92
92
 
93
- `~/.thincoder/config.json`:
93
+ `~/.thincoder/config.json`:
94
94
 
95
95
  ```jsonc
96
96
  {
97
97
  "providers": [
98
- // 可配多个,/model <名称> 切换
98
+ // multiple allowed; switch with /model <name>
99
99
  {
100
100
  "name": "deepseek",
101
- "baseURL": "https://api.deepseek.com/v1", // 任意 OpenAI 兼容端点
102
- "apiKey": "sk-...", // 或留空走环境变量
101
+ "baseURL": "https://api.deepseek.com/v1", // any OpenAI-compatible endpoint
102
+ "apiKey": "sk-...", // or leave empty to use env vars
103
103
  "model": "deepseek-chat",
104
- // 可选:主动节流预算(按账户限速等级自配,不配则关闭闸门,429 退避仍生效)。
105
- // 限速是账户级独立计数器(RPM/TPM 60s 窗口),等级查各厂商控制台
106
- // "tpm": 200000, // tokens/分钟(输入+输出总量)
107
- // "rpm": 50, // 请求数/分钟
104
+ // optional: proactive throttling budget (match your account's rate-limit tier;
105
+ // without it the gate is off, 429 backoff still applies).
106
+ // Rate limits are per-account counters (RPM/TPM over a 60s window) — check each vendor's console
107
+ // "tpm": 200000, // tokens/minute (input + output total)
108
+ // "rpm": 50, // requests/minute
108
109
  },
109
110
  ],
110
- "activeProvider": "deepseek", // 当前激活的 provider
111
+ "activeProvider": "deepseek", // currently active provider name
111
112
  "embedding": {
112
- // 可选:不配则纯 FTS 检索
113
+ // optional: without it, retrieval is pure FTS
113
114
  "baseURL": "https://api.siliconflow.cn/v1",
114
- "apiKey": "sk-...", // SILICONFLOW_API_KEY
115
+ "apiKey": "sk-...", // or SILICONFLOW_API_KEY
115
116
  "model": "BAAI/bge-m3",
116
117
  },
117
118
  "agent": {
118
- "maxTurns": 100, // 工具循环上限
119
- "compactThreshold": 100000, // 上下文压缩阈值(约 token 数)
119
+ "maxTurns": 100, // tool-loop cap
120
+ "compactThreshold": 100000, // context compaction threshold (approx. tokens)
120
121
  },
121
122
  "memory": {
122
- "dbPath": "~/.thincoder/memory.db", // sqlite 索引库路径
123
- "projectDir": ".thincoder/memory", // Project 层目录(相对项目根)
123
+ "dbPath": "~/.thincoder/memory.db", // sqlite index path
124
+ "projectDir": ".thincoder/memory", // Project layer directory (relative to project root)
124
125
  "team": {
125
- // 可选:不配则 Team 层禁用
126
+ // optional: Team layer disabled without it
126
127
  "name": "myteam",
127
128
  "repo": "git@github.com:org/team-memory.git",
128
129
  },
129
130
  },
130
131
  "mcp": {
131
- // 可选:MCP server 列表
132
+ // optional: MCP server list
132
133
  "servers": [
133
134
  {
134
135
  "name": "filesystem",
@@ -140,128 +141,150 @@ TUI 内斜杠命令:`/help`、`/model`(方向键选择全部 provider 的全
140
141
  }
141
142
  ```
142
143
 
143
- ## 架构
144
+ ## Architecture
144
145
 
145
146
  ```
146
- bin/thincoder.mjs 命令入口(tui / chat / memory / sync / distill
147
+ bin/thincoder.mjs command entry (tui / chat / memory / sync / distill)
147
148
  src/
148
- provider.mjs LLM 调用(fetch, SSE 流式, 重试)
149
- embedding.mjs 向量嵌入(OpenAI 兼容 /v1/embeddings
150
- tools.mjs 16 个内置工具 + MCP 包装 + readonly 调度标记
151
- mcp.mjs MCP 客户端(JSON-RPC + stdio transport,零依赖)
152
- agent.mjs 主循环 + 两段式工具执行 + plan/task/goal/skill/subagent/verify 工具
153
- + 增量索引(write/edit/delete 后自动 reindexFile)
154
- repomap.mjs 仓库依赖大纲(import/export regex 解析,工具按需调用)
155
- context.mjs token 粗估 + 历史压缩 + task 回注
156
- memory.mjs 记忆核心:三层合并检索 + 代码/文档索引(code_chunks/doc_chunks
157
- + FTS5 + 向量 RRF + JSDoc 提取 + 单文件增量索引
158
- session.mjs 会话持久化(最多 5 个归档槽位,按项目 cwd 隔离)
159
- skills.mjs 技能发现/加载(.thincoder/skills/*.md
160
- markdown.mjs 条目格式(frontmatter 解析/序列化)
161
- gitmem.mjs Team git 同步(clone/pull --rebase/push,系统 git
162
- distill.mjs 会话知识提取(候选 + 人工确认)
163
- checkpoint.mjs git patch 快照 / 回滚
164
- config.mjs 配置加载
165
- tui.mjs ANSI 终端 UI(宽字符折行、滚动、权限确认、斜杠命令)
166
- test/ node:test 离线单测(npm test
167
- scripts/ 真实环境验证脚本(压缩、团队同步)
149
+ provider.mjs LLM calls (fetch, SSE streaming, retries)
150
+ embedding.mjs vector embeddings (OpenAI-compatible /v1/embeddings)
151
+ tools.mjs 16 builtin tools + MCP wrapping + readonly scheduling flags
152
+ mcp.mjs MCP client (JSON-RPC + stdio transport, zero-dependency)
153
+ agent.mjs main loop + two-phase tool execution + plan/task/goal/skill/subagent/verify tools
154
+ + incremental indexing (auto reindexFile after write/edit/delete)
155
+ repomap.mjs repo dependency outline (import/export regex parsing, on-demand via tool)
156
+ context.mjs rough token estimation + history compaction + task re-injection
157
+ memory.mjs memory core: three-layer merged retrieval + code/doc indexing (code_chunks/doc_chunks)
158
+ + FTS5 + vector RRF + JSDoc extraction + single-file incremental indexing
159
+ session.mjs session persistence (up to 5 archive slots, isolated by project cwd)
160
+ skills.mjs skill discovery/loading (.thincoder/skills/*.md)
161
+ markdown.mjs entry format (frontmatter parse/serialize)
162
+ gitmem.mjs Team layer git sync (clone/pull --rebase/push, system git)
163
+ distill.mjs session knowledge extraction (candidates + human confirmation)
164
+ checkpoint.mjs git patch snapshots / rewind
165
+ config.mjs config loading
166
+ tui.mjs bare-ANSI terminal UI (wide-char wrapping, scrolling, permission prompts, slash commands)
167
+ test/ node:test offline unit tests (npm test)
168
+ scripts/ real-environment verification scripts (compaction, team sync)
168
169
  ```
169
170
 
170
- 关键设计:
171
+ Key design decisions:
171
172
 
172
- - **工具执行两段式**:阶段一串行做权限确认(有副作用工具逐个问用户);阶段二只读工具 `Promise.all` 并行、有副作用工具串行。结果按 `toolCallId` 配对回喂
173
- - **权限在 UI 层**:工具只负责执行,"问不问用户" TUI/CLI 的事,headless 场景不用改工具
174
- - **索引是易失品**:sqlite 只是代码/文档/记忆的本地索引,`reindex` 随时可重建
175
- - **代码/文档分离索引**:源码和 markdown 文档分表索引,LLM 通过不同工具检索——避免模型把旧代码模式当做设计规范
176
- - **git 边界**:Project 层只写文件不碰用户的仓库;Team 层是 ThinCoder 自管仓库才可自动 commit+push
177
- - **中文检索**:FTS5 unicode61 + 写入/查询两侧 CJK 逐字加空格;语义匹配走向量通道
173
+ - **Two-phase tool execution**: phase one serializes permission prompts (each side-effect tool asks the user); phase two runs read-only tools in parallel (`Promise.all`) and side-effect tools serially. Results are fed back paired by `toolCallId`
174
+ - **Permissions live in the UI layer**: tools only execute; "ask the user or not" is the TUI/CLI's business, so headless scenarios need no tool changes
175
+ - **The index is disposable**: sqlite is just a local index of code/docs/memories — `reindex` rebuilds it anytime
176
+ - **Separate code/doc indexes**: source and markdown docs are indexed in separate tables and searched through different tools — keeps the model from mistaking old code patterns for design conventions
177
+ - **git boundaries**: the Project layer only writes files and never touches your repo's commits; the Team layer is a ThinCoder-managed repo where auto commit+push is allowed
178
+ - **CJK retrieval**: FTS5 unicode61 + per-character CJK spacing on both write and query sides; semantic matching goes through the vector channel
178
179
 
179
- ## 开发
180
+ ## Development
180
181
 
181
182
  ```bash
182
- npm test # 离线单测(node:test,含本地 mock 服务)
183
- node scripts/verify-compress.mjs # 上下文压缩的真实 API 验证(需要有效配置)
184
- node scripts/verify-team.mjs # 团队记忆 A->git->B 全链路验证(本地 git,离线)
183
+ npm test # offline unit tests (node:test, with local mock servers)
184
+ node scripts/verify-compress.mjs # real-API verification of context compaction (needs valid config)
185
+ node scripts/verify-team.mjs # team memory A->git->B full-chain verification (local git, offline)
185
186
  ```
186
187
 
187
- 代码约定:纯 `.mjs`,不加分号,禁止引入 npm 依赖(包括开发依赖)。
188
-
189
- ## 路线图
190
-
191
- - MCP HTTP transport(当前仅 stdio)
192
- - 更多内置 skills
193
-
194
- ## 更新日志
195
-
196
- ### 0.7.4(2026-07)
197
- - **verify 分层自检**:默认 quick 模式(语法检查变更文件 + git diff + 自检清单,毫秒级),`full=true` 才跑全量 npm test——不再每改一行都等十几秒全量;quick 即满足完成守卫,收尾/改核心设施时再用 full
198
- - **提示词纪律强化**:SYSTEM_PROMPT 新增测试纪律(何时跑哪层验证)与调试策略(先诊断再治、一次只改一处);coder/plan/main overlay 补自检清单(最简方案、匹配项目模式、不碰无关文件)
199
- - **修复**:quick 模式下语法检查失败被误标为验证通过,完成守卫被架空
200
-
201
- ### 0.7.3(2026-07)
202
- - **图片粘贴**:新增 `read_image` 工具,支持从剪贴板粘贴图片/视频,多模态模型可直接理解截图、UI 设计稿、架构图(Win: `Alt+V` / Mac/Linux: `Ctrl+V`)
203
- - **TUI 粘贴提示**:使用多模态模型时,输入框右上角自动显示操作系统对应的粘贴快捷键,纯文本模型不显示
204
-
205
- ### 0.7.2(2026-07)
206
- - **TPM/RPM 主动节流闸门**:provider 配置 `tpm`/`rpm` 预算后,发请求前本地滑动窗口记账(60s,输入+输出),超预算先睡到窗口腾出空间而不是打 429 碰运气;主循环/压缩摘要/子 agent/截断续写全覆盖。等待时状态栏显示 `TPM 节流等待 ~Ns`,不配的 provider 闸门关闭
207
- - **429 专项退避**:尊重 `Retry-After` 响应头,无则按 15s/30s/60s(60s 窗口,秒级退避无意义);配额/余额错误(`exceeded_current_quota_error`)与限速区分,不再无效重试
208
- - **依赖注入改为紧凑摘要**:`buildSummary`(目录级依赖 + 枢纽文件 + 入口,天然 ~1-2k 字符)替代全量大纲注入,详细 import/export `repo_outline` 按需查
209
- - **TUI 菜单化**:`/model` `/config` `/provider` `/think` `/mcp` `/goal` `/session` `/rewind` 统一改为选择器菜单
210
- - **会话健壮性**:归档/切换时文件损坏或磁盘异常不再崩,静默放弃
211
-
212
- ### 0.7.1(2026-07)
213
- - **修复上下文爆炸(紧急)**:依赖大纲开局注入不再无界——多仓库父目录(索引数千文件)的全量大纲实测达 140 万字符 ≈ 35 万 token,且每轮对话重复注入累积,几轮即打爆上下文并触发 TPM 限流。现截断到 6000 字符(超出指引用 `repo_outline` 聚焦查询)且每会话只注一次
214
- - **压缩逃逸口**:历史太短(≤13 条)切不出中间段时压缩永远不发生,一条巨型消息(大段粘贴/超大注入)即可卡死。现走确定性瘦身:超长 user/tool 正文截断换桩,不动 reasoning_content tool_calls 配对
215
- - **修复 docSync ReferenceError**:`failed`/`errors` 未声明导致文档索引同步每次调用必抛错(两个测试挂红)
216
- - **apply_patch 工具**:统一 diff 多文件原子打补丁(任一 hunk 不上整体不写盘),权限预览直接展示 diff
217
- - **checkpoint 工具**:`list`/`create`/`rewind` 快照能力暴露给模型(此前只接 TUI 自动快照 + /rewind,模型无法自救);bash 销毁性 git 护栏升级为分段检测(`&&`/`;`/`|`/命令替换链式写法不再绕过)
218
- - **bash 进程树杀**:超时/中断整树杀(POSIX 进程组 / Windows taskkill /T),不再残留孙进程
219
- - **子 agent 显示契约**:只 relay 正文/思考 token TUI 滚动区,内部工具调用不再刷屏
220
- - **路径安全**:`resolveInCwd` symlink 逃逸(realpath 二次校验);edit 拒绝空 old_string;单文件增量索引跳过隐藏目录与 node_modules
221
- - **其他**:SQLite WAL + busy_timeout、schema 迁移单事务、升级语义化版本比较、MCP cmd.exe 引号翻倍转义、gitmem 无变更不提交
222
-
223
- ### 0.7.0(2026-07
224
- - **模型协议深度适配**:reasoning_content 回传按模型区分(`reasoningEcho` 规格表字段)——DeepSeek/Kimi 必须回传,GLM 不回传;reasoning_effort 枚举校验(`reasoningEffortEnum`);temperature 范围裁剪(`tempRange`)
225
- - **Qwen/MiniMax 规格补齐**:reasoning_effort 枚举(Qwen 3.8-max-preview)、temperature 范围(Qwen [0,2)、MiniMax [0,2])、MiniMax M3 thinking 模式
226
- - **grep 上下文行**:`before`/`after` 参数(对应 grep -B/-A),匹配行用 `:`、上下文行用 `-`,同文件相邻区间去重合并
227
- - **系统提示词边界规则**:不修改工作目录外文件,不许用 bash 绕过 read/write/edit 的目录限制
228
- - **question 工具输入框标题**:固定为 ` Question `,提问文本走对话区(不再塞进输入框标题)
229
-
230
- ### 0.5.0(2026-07)
231
- - **代码库理解**:`repo_outline`(依赖大纲,启动自动注入)、`code_search`(FTS5 + 向量 + JSDoc)、`doc_search`(按 ## 标题分块),写文件自动增量索引
232
- - **模型适配**:5 家内置预设(DeepSeek/Kimi/GLM/Qwen/MiniMax),maxTokens 拉满、截断续写、思考模式 API 自动匹配
233
- - 会话 5 槽位归档、`/session` 切换、恢复时展示工具结果
234
- - agent 流式输出可见、最终报告进对话区
235
- - 文件工具目录隔离、权限预览紧挨输入框
236
- - write/edit 自动附 git diff、edit 错误信息增强提示
237
- - task 自动过滤已完成项、全部 done 时主动提醒
238
- - 提示词引导"查官方文档不一致就存项目记忆"
188
+ Code conventions: pure `.mjs`, no semicolons, no npm dependencies allowed (including devDependencies).
189
+
190
+ ## Roadmap
191
+
192
+ - More builtin skills
193
+
194
+ ## Changelog
195
+
196
+ ### 0.7.7 (2026-07)
197
+ - **Code review fixes (4 critical bugs)**:
198
+ - `gitSync` anchor never set after full `codeSync` fallback → fast path was dead in production; now `codeSync`/`docSync` write the anchor on success
199
+ - `gitSync` skipped deleted files → stale chunks remained in index forever; `--diff-filter` now includes `D`, and `ENOENT` is distinguished from other errors (failed files don't advance the anchor)
200
+ - Completion guard was a one-shot latch → after firing once, further mutations could finish unverified; now re-armed with a pushback counter (max 2 pushes, 3rd passes through)
201
+ - Verify-failure exhaustion returned raw model text without honesty framing → now injects a system reminder forcing the model to state what's still failing, what was tried, and that the work is unfinished
202
+ - **Input queue during processing**: messages typed while the agent is processing are queued and auto-executed when processing ends. Queue preview shown as a single line above the input box (no collision with subagent panel). `Ctrl+D` deletes the last queued item. `/cancel` and `/exit` bypass the queue
203
+
204
+ ### 0.7.6 (2026-07)
205
+ - **SYSTEM_PROMPT split into core + discipline**: core rules (shared by all agents) separated from coding/testing/debugging discipline (main agent + coder), so explore/plan subagents no longer burn attention on irrelevant coding clauses — single source of truth, one rule changed in one file
206
+ - **git-driven incremental indexing**: new `gitSync` uses `git diff` at startup to find files changed since the last index and rebuilds only their FTS5 chunks. Non-git repos / first run / large changesets (>200 files) automatically fall back to full scans. `codeSync` + `docSync` startup parallelized
207
+ - **Embeddings backfilled right after reindexFile**: incremental indexing after each agent write/edit no longer leaves vector NULLs — `ensureEmbeddings` runs immediately, so freshly changed files are semantically searchable at once
208
+ - **Project-instruction injection hardening**: AGENTS.md content wrapped with `escapeXml` + `<untrusted_project_instructions>`, closing the prompt-injection hole from malicious project instructions
209
+ - **Compaction threshold cap**: for 1M-window models the compaction threshold drops from 800K to 300K tokens (`COMPACT_CAP_TOKENS`), preventing history from blowing the TPM budget and the compaction request itself from hitting 429
210
+ - **readSSE tool_calls name dedup**: some APIs (GLM occasionally) resend the full name instead of deltas in the stream, and `+=` produced `readread`. Now only the first non-empty value is taken
211
+ - **Edge-case thinking across all prompt layers**: plan/explore/coder/main overlays each gained an edge-case recognition rule (open-ended, no scenario enumeration)
212
+ - **Testing discipline refined**: full-test trigger changed from "touched core infrastructure files" to "changed core infrastructure behavior" — adding a helper to memory.mjs no longer triggers the full suite
213
+
214
+ ### 0.7.5 (2026-07)
215
+ - **Compound prompt instructions split**: 8 compound sentences across SYSTEM_PROMPT / main-overlay / coder-overlay split into independent bullets (one attention node per instruction), improving instruction-following on DeepSeek/GLM/Qwen — fallback clauses like "add tests after changing code" no longer get skipped
216
+ - **Testing discipline strengthened**: SYSTEM_PROMPT Testing discipline gained an independent hard rule (changing behavior/adding code requires tests); main-overlay self-review checklist gained "do existing tests cover the change"; coder-overlay final checklist gained a test item
217
+ - **Plan mode workflow**: main-overlay's plan mode instruction split from one compound sentence into a 3-step numbered flow
218
+
219
+ ### 0.7.4 (2026-07)
220
+ - **verify tiered self-check**: default quick mode (syntax-check changed files + git diff + self-review checklist, milliseconds); `full=true` also runs the full npm test suite — no more waiting ten-plus seconds per line changed; quick satisfies the completion guard, use full when wrapping up or touching core infrastructure
221
+ - **Prompt discipline strengthened**: SYSTEM_PROMPT gained Testing discipline (when to run which tier) and Debugging strategy (diagnose before treating, one change at a time); coder/plan/main overlays gained self-review checklists (simplest solution, match project patterns, don't touch unrelated files)
222
+ - **Fix**: quick mode marked verification passed even when syntax checks failed, gutting the completion guard
223
+
224
+ ### 0.7.3 (2026-07)
225
+ - **Image paste**: new `read_image` tool — paste images/videos from the clipboard, multimodal models directly understand screenshots, UI mockups, architecture diagrams (Win: `Alt+V` / Mac/Linux: `Ctrl+V`)
226
+ - **TUI paste hint**: with a multimodal model, the input box corner shows the OS-appropriate paste shortcut; hidden for text-only models
227
+
228
+ ### 0.7.2 (2026-07)
229
+ - **TPM/RPM proactive rate gate**: with `tpm`/`rpm` budgets configured on a provider, requests are booked against a local sliding window (60s, input+output) before sending — over budget means sleeping until the window frees up instead of gambling on 429s; covers the main loop / compaction summaries / subagents / truncation resume. Status bar shows `TPM throttle wait ~Ns`; the gate is off for unconfigured providers
230
+ - **429-specific backoff**: respects the `Retry-After` header, otherwise backs off 15s/30s/60s (60s window — sub-second backoff is pointless); quota/balance errors (`exceeded_current_quota_error`) are distinguished from rate limits and no longer retried uselessly
231
+ - **Dependency injection as compact summary**: `buildSummary` (directory-level dependencies + hub files + entry points, naturally ~1-2k chars) replaces the full-outline injection; detailed import/export available on demand via `repo_outline`
232
+ - **TUI menus**: `/model` `/config` `/provider` `/think` `/mcp` `/goal` `/session` `/rewind` unified into picker menus
233
+ - **Session robustness**: corrupted files or disk errors during archive/switch no longer crash — silently abandoned
234
+
235
+ ### 0.7.1 (2026-07)
236
+ - **Context explosion fix (urgent)**: the startup dependency-outline injection is no longer unbounded — a multi-repo parent directory (thousands of indexed files) produced a 1.4M-char ≈ 350K-token outline, re-injected every turn, blowing up context within a few turns and tripping TPM limits. Now truncated to 6000 chars (with a pointer to `repo_outline` for focused queries) and injected only once per session
237
+ - **Compaction escape hatch**: when history was too short (≤13 messages) to slice a middle section, compaction never happened — one giant message (huge paste/oversized injection) could deadlock. Now a deterministic slimming path: oversized user/tool bodies are truncated to stubs, reasoning_content and tool_calls pairing untouched
238
+ - **docSync ReferenceError fix**: undeclared `failed`/`errors` made every doc index sync throw (two tests red)
239
+ - **apply_patch tool**: unified-diff multi-file atomic patching (any failed hunk nothing written), permission preview shows the diff directly
240
+ - **checkpoint tool**: `list`/`create`/`rewind` snapshot abilities exposed to the model (previously only wired to TUI auto-snapshots + /rewind, so the model couldn't save itself); the bash destructive-git guard upgraded to per-segment detection (chained forms like `&&`/`;`/`|`/command substitution no longer slip through)
241
+ - **bash process-tree kill**: timeout/interrupt kills the whole tree (POSIX process groups / Windows taskkill /T) — no orphaned grandchildren
242
+ - **Subagent display contract**: only content/thinking tokens relay to the TUI scrolling area; internal tool calls no longer flood the screen
243
+ - **Path safety**: `resolveInCwd` prevents symlink escapes (realpath double-check); edit rejects empty old_string; single-file incremental indexing skips hidden directories and node_modules
244
+ - **Misc**: SQLite WAL + busy_timeout, single-transaction schema migrations, semantic version comparison for upgrades, MCP cmd.exe quote-doubling escape, gitmem skips commits when nothing changed
245
+
246
+ ### 0.7.0 (2026-07)
247
+ - **Deep model protocol adaptation**: reasoning_content echo differentiated per model (`reasoningEcho` spec field) — DeepSeek/Kimi must echo, GLM must not; reasoning_effort enum validation (`reasoningEffortEnum`); temperature range clamping (`tempRange`)
248
+ - **Qwen/MiniMax spec completion**: reasoning_effort enums (Qwen 3.8-max-preview), temperature ranges (Qwen [0,2), MiniMax [0,2]), MiniMax M3 thinking mode
249
+ - **grep context lines**: `before`/`after` params (grep -B/-A equivalents), matches marked with `:`, context with `-`, adjacent ranges in the same file merged and deduped
250
+ - **System prompt boundary rule**: never modify files outside the working directory; never use bash to bypass the read/write/edit directory confinement
251
+ - **question tool input box title**: fixed to ` Question `, question text goes to the conversation area (no longer crammed into the box title)
252
+
253
+ ### 0.5.0 (2026-07)
254
+ - **Codebase understanding**: `repo_outline` (dependency outline, auto-injected at startup), `code_search` (FTS5 + vectors + JSDoc), `doc_search` (chunked by ## headings), auto-incremental indexing on file writes
255
+ - **Model adaptation**: 5 built-in presets (DeepSeek/Kimi/GLM/Qwen/MiniMax), maxTokens maxed out, truncation resume, thinking-mode APIs auto-matched
256
+ - Session 5-slot archiving, `/session` switching, tool results shown after restore
257
+ - Subagent streaming output visible, final reports in the conversation area
258
+ - File tools confined to the working directory, permission preview above the input box
259
+ - write/edit auto-attach git diffs, edit error messages with better hints
260
+ - task auto-filters completed items, proactive reminder when all done
261
+ - Prompt guidance: "check official docs → save discrepancies to project memory"
239
262
 
240
263
  ### 0.4.0
241
- - 权限审批展示文件内容预览(write 内容、edit diffbash 命令)
242
- - todo 面板进度可视、状态栏 token 用量与上下文利用率
243
- - 项目指令双层合并(全局 + 项目 AGENTS.md
264
+ - Permission approval shows content previews (write content, edit diff, bash command)
265
+ - Todo panel progress visualization, status bar token usage and context utilization
266
+ - Two-layer project instructions merge (global + project AGENTS.md)
244
267
 
245
268
  ### 0.3.0
246
- - MCP 客户端(JSON-RPC + stdio,零依赖)
247
- - Skills 系统(`.thincoder/skills/*.md`)
248
- - Plan/Goal/Question 工具
249
- - 提示词外部化到 `.md` 文件、子 agent 角色 overlay
250
- - task 严格纪律(keep ONE in_progress)、完成守卫(改文件未 verify 拦截)
251
- - DeepSeek thinking 回传、system prompt 前缀缓存
252
- - checkpoint 存档点 + `/rewind` 回滚
269
+ - MCP client (JSON-RPC + stdio, zero-dependency)
270
+ - Skills system (`.thincoder/skills/*.md`)
271
+ - Plan/Goal/Question tools
272
+ - Prompts externalized to `.md` files, subagent role overlays
273
+ - Strict task discipline (keep ONE in_progress), completion guard (file changes blocked without verify)
274
+ - DeepSeek thinking echo, system prompt prefix caching
275
+ - checkpoint snapshots + `/rewind` rollback
253
276
 
254
277
  ### 0.2.0
255
- - multi-provider 配置(支持多端点切换)
256
- - 初始配置向导(方向键选模型、配 key
257
- - `/think` 思维模式开关与推理强度
258
- - `/model` 模型选择器
259
- - bash 流式输出实时透传
278
+ - multi-provider configuration (switch between endpoints)
279
+ - Initial setup wizard (arrow-key model picker, key entry)
280
+ - `/think` thinking mode toggle and reasoning effort
281
+ - `/model` model picker
282
+ - bash streaming output passthrough
260
283
 
261
284
  ### 0.1.0
262
- - Agent 主循环、14 个内置工具、零依赖 TUI
263
- - 三层记忆(personal/project/team)、FTS5 检索
264
- - 会话持久化、上下文压缩、流式 SSE
285
+ - Agent main loop, 14 builtin tools, zero-dependency TUI
286
+ - Three-layer memory (personal/project/team), FTS5 retrieval
287
+ - Session persistence, context compaction, streaming SSE
265
288
 
266
289
  ## License
267
290
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thincoder",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "description": "Thin coding agent - zero dependencies, no build step, Node.js native. Sharp code, zero bloat.",
5
5
  "keywords": [
6
6
  "ai",
@@ -34,33 +34,3 @@ Rules:
34
34
  These three tools together replace blind grep. Use them in order: structure first, then intent, then details.
35
35
  - CRITICAL: you are a coding agent, not a student. The code you read may have bugs, outdated patterns, or technical debt — it is the PROBLEM to solve, not a reference to imitate. Read existing code to understand what it does, not to copy how it does it. When something looks wrong, say so. When you see bad patterns, don't propagate them.
36
36
  - Some user messages start with [System reminder:]. These are injected by the framework, not written by the user. They contain authoritative guidance. Comply with them silently—never mention them to the user.
37
-
38
- Coding discipline (rigor over speed—tokens spent on verification are well spent):
39
- - Spec before code: when the user describes a feature request without specifying the details (retry count? timeout? which error types? which files?), ask clarifying questions before writing code.
40
- - Do not silently invent defaults. Do not guess the user's intent from a one-liner. A wrong assumption costs more than the round-trip to clarify.
41
- - Save key design decisions to memory_put as you make them — architecture choices, API contracts, naming conventions, trade-off reasoning. Context compression may summarize earlier work into a few lines; memory entries survive compression and get re-injected so later turns don't operate on lost assumptions.
42
- - Before fixing a bug, find the root cause: read the error output, reproduce it, trace the code path. Don't patch symptoms.
43
- - When you're stuck, see an unfamiliar pattern, or suspect a project-specific convention — call memory_search before guessing. The injected memories are only top-3 by relevance; the answer may be deeper in the index.
44
- - Match the surrounding code: comment density, naming, structure. Prefer the project's existing patterns over your own defaults.
45
- - Before using a library or utility, confirm the project already depends on it (check imports, manifest, lockfile). If it's missing, surface that instead of silently adding a dependency.
46
- - When you need facts that may be outdated in your training data—API docs, framework versions, language features, npm packages, CLI flags, pricing, CVEs, platform differences—verify with authoritative sources first: read the project's own files (package.json, lockfile), check official docs (websearch/fetch), or test the actual environment. If findings contradict your training data, save the corrected fact to project memory so future sessions benefit.
47
- - Refactoring: update every caller when an interface changes; never change existing test logic just to make tests pass.
48
- - Deliver complete changes: no placeholder stubs, no "// rest unchanged", no TODO gaps left for the user to fill in.
49
- - After changing behavior, sweep comments and docstrings that now describe the old behavior and bring them in line with the code.
50
- - Before your final reply, re-read the user's latest request and confirm you are answering that one—not an earlier ask left over from a steer or compaction.
51
-
52
- Testing discipline (right check at the right time — don't run the full suite for every line change):
53
- - After every write/edit of .mjs/.js files: call syntax_check immediately — it catches parse errors in milliseconds
54
- - Before declaring a coding task complete: call verify — it checks syntax on all changed files, shows git diff, and displays a self-review checklist. This satisfies the framework's verification requirement so you can finish without a system reminder.
55
- - Run the full test suite (verify with full=true, or npm test directly) only when:
56
- a) You're about to mark the last task done and declare completion
57
- b) You changed core infrastructure files (agent loop, provider, config, tools, or memory system)
58
- c) The user explicitly asks you to run tests
59
- - If verify reports syntax errors or test failures, fix them before claiming completion — never mark work done with known failures
60
- - When you change behavior or add code, add at least one test that covers the change. If the project has no test suite yet, note that in your report. Never skip this step — untested code is incomplete code.
61
-
62
- Debugging strategy (when something goes wrong, diagnose before treating):
63
- - Read the FULL error output — the root cause is often at the end, not the first line
64
- - Don't change multiple things at once hoping one works — that destroys the signal
65
- - Narrow down systematically: reproduce the failure in isolation, read the file you just wrote to confirm it matches your intent, trace the control flow with grep or code_search, then fix ONE thing and re-run
66
- - If the error message is unclear, search the web for it before guessing at a fix
package/src/agent.mjs CHANGED
@@ -18,7 +18,8 @@ import { fileURLToPath } from "node:url"
18
18
  import { execSync } from "node:child_process"
19
19
 
20
20
  const __dirname = dirname(fileURLToPath(import.meta.url))
21
- const SYSTEM_PROMPT = readFileSync(join(__dirname, "SYSTEM_PROMPT.md"), "utf8") // 核心规则(主/子 agent 通用)
21
+ const SYSTEM_PROMPT = readFileSync(join(__dirname, "SYSTEM_PROMPT.md"), "utf8") // 核心规则(所有 agent 通用)
22
+ const DISCIPLINE_RULES = readFileSync(join(__dirname, "discipline-rules.md"), "utf8") // 编码/测试/调试纪律(主 agent + coder 子 agent)
22
23
  const MAIN_OVERLAY = readFileSync(join(__dirname, "main-overlay.md"), "utf8") // 主 agent 专属条款(子 agent 没有这些工具)
23
24
  const EXPLORE_OVERLAY = readFileSync(join(__dirname, "explore-overlay.md"), "utf8")
24
25
  const CODER_OVERLAY = readFileSync(join(__dirname, "coder-overlay.md"), "utf8")
@@ -309,6 +310,7 @@ export const subagentTool = {
309
310
  cwd: parent.cwd,
310
311
  memory: parent.memory,
311
312
  overlay,
313
+ role,
312
314
  })
313
315
 
314
316
  // explore/plan:注入 git 上下文(分支/最近提交/工作区状态)——探索与规划都和仓库现状有关(借鉴 kimi-code 的 promptPrefix)
@@ -761,7 +763,7 @@ export async function loadProjectInstructions(cwd) {
761
763
  * { provider, tools, config, cwd, memory?, overlay? }
762
764
  * overlay — 子 agent 角色覆盖文本,拼接在 system prompt 末尾
763
765
  */
764
- export function createAgent({ provider, tools, config, cwd, memory = null, overlay = "" }) {
766
+ export function createAgent({ provider, tools, config, cwd, memory = null, overlay = "", role = "" }) {
765
767
  return {
766
768
  provider,
767
769
  tools,
@@ -769,6 +771,7 @@ export function createAgent({ provider, tools, config, cwd, memory = null, overl
769
771
  cwd,
770
772
  memory,
771
773
  overlay,
774
+ _role: role,
772
775
  history: [], // OpenAI 格式的对话历史(不含 system)
773
776
  tasks: [], // task 工具维护的任务列表
774
777
  planMode: false, // plan 工具切换的规划模式
@@ -866,14 +869,16 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
866
869
  agent._onTaskUpdate = callbacks.onTaskUpdate
867
870
 
868
871
  // prompt 组织(借鉴 kimi-code 的自包含 profile,分文件方案):
869
- // agent = 角色 overlay(开头确立身份,对齐 kimi role prefix)+ 核心规则——
870
- // 不含它没有的工具条款(goal/verify/skill/subagent 只在主 overlay,避免教它调不存在的工具);
871
- // agent = 核心规则 + overlay
872
+ // 所有 agent 拿核心规则;主 agent + coder agent 额外拿编码/测试/调试纪律;
873
+ // explore/plan 只拿核心规则(它们是只读的,不需要写代码相关条款)
874
+ const needsDiscipline = depth === 0 || agent._role === "coder"
875
+ const base = needsDiscipline ? `${SYSTEM_PROMPT}\n\n${DISCIPLINE_RULES}` : SYSTEM_PROMPT
876
+
872
877
  let systemPrompt = agent.overlay
873
- ? `${agent.overlay}\n\n${SYSTEM_PROMPT}`
878
+ ? `${agent.overlay}\n\n${base}`
874
879
  : depth === 0
875
- ? `${SYSTEM_PROMPT}\n\n${MAIN_OVERLAY}`
876
- : SYSTEM_PROMPT
880
+ ? `${base}\n\n${MAIN_OVERLAY}`
881
+ : base
877
882
  // 注意:system prompt 里只能放跨 run 稳定的内容(前缀缓存要求逐字节一致)——
878
883
  // session start 时间戳每会话固定一次;每轮变化的记忆注入走上面的 user 上下文消息
879
884
  const platform = { win32: 'Windows', darwin: 'macOS', linux: 'Linux' }[process.platform] ?? process.platform
@@ -881,7 +886,7 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
881
886
  systemPrompt += `\n\nOS: ${platform}. Working directory: ${agent.cwd}. Session start: ${agent._sessionStart}.`
882
887
  const projectRules = await loadProjectInstructions(agent.cwd)
883
888
  if (projectRules) {
884
- systemPrompt += `\n\nProject instructions (follow these as project conventions):\n${projectRules}`
889
+ systemPrompt += `\n\nProject instructions (follow these as project conventions):\n<untrusted_project_instructions>\n${escapeXml(projectRules)}\n</untrusted_project_instructions>`
885
890
  }
886
891
  // 技能列表注入(仅顶层 agent,子 agent 不需要);按 cwd 稳定,变更才会破缓存(可接受)
887
892
  if (depth === 0) {
@@ -904,7 +909,8 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
904
909
  agent._touchedFiles = []
905
910
  agent._verifyRetries = 0 // 修复-验证循环计数,每个新 run 从头开始
906
911
  const MAX_VERIFY_RETRIES = 3
907
- let completionGuardFired = false
912
+ let guardPushbacks = 0 // 完成守卫推回次数(最多推 2 次:第三次直接放行,避免无限循环)
913
+ let honestReminderInjected = false // verify 耗尽后注入了诚实提醒,下一轮直接放行
908
914
  const recentCallSigs = [] // 停滞检测:最近的工具调用签名(同一调用连续 3 次即提醒)
909
915
 
910
916
  for (let turn = 0; turn < maxTurns; turn++) {
@@ -961,9 +967,10 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
961
967
  if (!response.content) {
962
968
  throw new Error("LLM 返回了空回复(可能是思考耗尽或被截断)。可 /think effort 降低推理强度后重试")
963
969
  }
964
- // 完成守卫:本轮改过文件却没跑过 verify,推回去验证一次
965
- if (depth === 0 && agent._mutatedThisRun && !agent._verifiedThisRun && !completionGuardFired) {
966
- completionGuardFired = true
970
+ // 完成守卫:本轮改过文件却没跑过 verify,推回去验证。
971
+ // 可重武装但最多推 2 次——第三次直接放行(避免 agent 死活不调 verify 时无限循环)
972
+ if (depth === 0 && agent._mutatedThisRun && !agent._verifiedThisRun && guardPushbacks < 2) {
973
+ guardPushbacks++
967
974
  agent.history.push({ role: "assistant", content: response.content })
968
975
  agent.history.push({
969
976
  role: "user",
@@ -982,10 +989,20 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
982
989
  })
983
990
  continue
984
991
  }
985
- // 重试用尽:测试仍然失败,诚实收尾
992
+ // 重试用尽:测试仍然失败,注入诚实提醒后给模型最后一轮总结
986
993
  if (depth === 0 && agent._verifiedThisRun && agent._verifyPassed === false && agent._verifyRetries >= MAX_VERIFY_RETRIES) {
994
+ if (honestReminderInjected) {
995
+ // 已经注入过诚实提醒且模型又给了最终回答 → 放行返回
996
+ agent.history.push({ role: "assistant", content: response.content })
997
+ return response.content
998
+ }
999
+ honestReminderInjected = true
987
1000
  agent.history.push({ role: "assistant", content: response.content })
988
- return response.content
1001
+ agent.history.push({
1002
+ role: "user",
1003
+ content: `[System reminder: ${MAX_VERIFY_RETRIES} verify attempts exhausted and tests are still failing. In your response to the user, you MUST state explicitly: (1) what tests are still failing, (2) what you tried, (3) what you believe the root cause is. Do not present this as complete — the user needs to know the work is unfinished.]`,
1004
+ })
1005
+ continue
989
1006
  }
990
1007
  agent.history.push({ role: "assistant", content: response.content })
991
1008
  return response.content
package/src/config.mjs CHANGED
@@ -93,7 +93,11 @@ const MODEL_SPECS = [
93
93
  const DEFAULT_SPEC = { context: 128_000, maxOutput: 32_000, cacheMode: "none" }
94
94
  // 窗口利用率上限:0.8(DeepSeek 内部即全窗口;压缩本身要花一次 LLM 调用,过早压缩是纯浪费。
95
95
  // 留 20% 余量给压缩后的尾部增长与输出 token)
96
+ // 但 1M 窗口模型按 0.8 算 = 80 万 token,历史涨到那么大才压缩会打爆 TPM 预算、
97
+ // 压缩请求本身也可能 429。加 cap:不超过 maxOutput 的 8 倍(128K×8≈100万→实际仍偏大但合理),
98
+ // 不超过 30 万(大窗口模型的合理工作上限,再大缓存命中率下降)
96
99
  const COMPACT_RATIO = 0.8
100
+ const COMPACT_CAP_TOKENS = 300_000
97
101
 
98
102
  /** 按模型名前缀查规格(大小写不敏感),未知模型给保守默认 */
99
103
  export function specForModel(model) {
@@ -111,7 +115,11 @@ export function contextWindowForModel(model) {
111
115
  /** 推导压缩阈值;explicit 为配置文件中显式设置的值(优先),否则按模型自动算 */
112
116
  export function resolveCompactThreshold(explicit, model) {
113
117
  if (explicit != null) return { value: explicit, auto: false }
114
- return { value: Math.floor(contextWindowForModel(model) * COMPACT_RATIO), auto: true }
118
+ const spec = specForModel(model)
119
+ const ratioBased = Math.floor(spec.context * COMPACT_RATIO)
120
+ // 大窗口模型(1M)按比例算出来太大,用 cap 限制——宁可早压缩也别让历史涨到打爆 TPM
121
+ const value = Math.min(ratioBased, COMPACT_CAP_TOKENS)
122
+ return { value, auto: true }
115
123
  }
116
124
 
117
125
  /**
@@ -0,0 +1,30 @@
1
+ Coding discipline (rigor over speed—tokens spent on verification are well spent):
2
+ - Spec before code: when the user describes a feature request without specifying the details (retry count? timeout? which error types? which files?), ask clarifying questions before writing code.
3
+ - Do not silently invent defaults. Do not guess the user's intent from a one-liner. A wrong assumption costs more than the round-trip to clarify.
4
+ - Save key design decisions to memory_put as you make them — architecture choices, API contracts, naming conventions, trade-off reasoning. Context compression may summarize earlier work into a few lines; memory entries survive compression and get re-injected so later turns don't operate on lost assumptions.
5
+ - Before fixing a bug, find the root cause: read the error output, reproduce it, trace the code path. Don't patch symptoms.
6
+ - When you're stuck, see an unfamiliar pattern, or suspect a project-specific convention — call memory_search before guessing. The injected memories are only top-3 by relevance; the answer may be deeper in the index.
7
+ - Match the surrounding code: comment density, naming, structure. Prefer the project's existing patterns over your own defaults.
8
+ - Before using a library or utility, confirm the project already depends on it (check imports, manifest, lockfile). If it's missing, surface that instead of silently adding a dependency.
9
+ - When you need facts that may be outdated in your training data—API docs, framework versions, language features, npm packages, CLI flags, pricing, CVEs, platform differences—verify with authoritative sources first: read the project's own files (package.json, lockfile), check official docs (websearch/fetch), or test the actual environment. If findings contradict your training data, save the corrected fact to project memory so future sessions benefit.
10
+ - Refactoring: update every caller when an interface changes; never change existing test logic just to make tests pass.
11
+ - Deliver complete changes: no placeholder stubs, no "// rest unchanged", no TODO gaps left for the user to fill in.
12
+ - Before finalizing any implementation, pause and think through edge cases: what could go wrong? what happens on failure? what boundary conditions exist? Reason about the failure modes — then handle or document the fallback. "It works on my machine" is not completion.
13
+ - After changing behavior, sweep comments and docstrings that now describe the old behavior and bring them in line with the code.
14
+ - Before your final reply, re-read the user's latest request and confirm you are answering that one—not an earlier ask left over from a steer or compaction.
15
+
16
+ Testing discipline (right check at the right time — don't run the full suite for every line change):
17
+ - After every write/edit of .mjs/.js files: call syntax_check immediately — it catches parse errors in milliseconds
18
+ - Before declaring a coding task complete: call verify — it checks syntax on all changed files, shows git diff, and displays a self-review checklist. This satisfies the framework's verification requirement so you can finish without a system reminder.
19
+ - Run the full test suite (verify with full=true, or npm test directly) only when:
20
+ a) You're about to commit or publish — final gate before code ships
21
+ b) You changed core infrastructure behavior (agent loop, provider protocol, config schema, tool execution, memory schema) — not just touched the file
22
+ c) The user explicitly asks you to run tests
23
+ - If verify reports syntax errors or test failures, fix them before claiming completion — never mark work done with known failures
24
+ - When you change behavior or add code, add at least one test that covers the change. If the project has no test suite yet, note that in your report. Never skip this step — untested code is incomplete code.
25
+
26
+ Debugging strategy (when something goes wrong, diagnose before treating):
27
+ - Read the FULL error output — the root cause is often at the end, not the first line
28
+ - Don't change multiple things at once hoping one works — that destroys the signal
29
+ - Narrow down systematically: reproduce the failure in isolation, read the file you just wrote to confirm it matches your intent, trace the control flow with grep or code_search, then fix ONE thing and re-run
30
+ - If the error message is unclear, search the web for it before guessing at a fix
@@ -11,4 +11,5 @@ Guidelines:
11
11
  - Use WebSearch or Fetch when external context is needed (docs, error messages)
12
12
  - Issue parallel tool calls whenever possible — read multiple files at once
13
13
  - Complete the search efficiently and report findings in a structured format
14
+ - If the expected pattern doesn't exist, report that explicitly: what you searched for, which tools you used, and that nothing matched. "Probably there" is not a finding — only report what you actually saw.
14
15
  - If something is ambiguous, note it in your report; do not ask the user
package/src/memory.mjs CHANGED
@@ -819,6 +819,94 @@ function _upsertDocFile(memory, origin, rel, lines, mtimeMs) {
819
819
  }
820
820
  }
821
821
 
822
+ /**
823
+ * git 驱动增量索引:用 git diff 找出上次索引以来的变更文件,
824
+ * 只重建这些文件的 FTS5 块(不碰向量)。比全量 mtime 扫描快一个数量级。
825
+ * 返回 { updated, removed, skipped } 或 null(git 不可用时,调用方退到 codeSync)。
826
+ */
827
+ export async function gitSync(memory, dir, { onProgress } = {}) {
828
+ const { execSync } = await import("node:child_process")
829
+ const opts = { cwd: dir, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 10000 }
830
+
831
+ let head
832
+ try { head = execSync("git rev-parse HEAD", opts).trim() } catch { return null }
833
+
834
+ const stored = memory.db.prepare(`SELECT value FROM meta WHERE key = 'last_indexed_commit'`).get()?.value
835
+ if (!stored) return null // 首次运行,走全量 codeSync
836
+
837
+ // 取两个 diff 的并集:已提交的变更(pull/merge)+ 工作区脏文件(用户在外部编辑器改的)
838
+ let diffOut
839
+ try {
840
+ // 包含 D(删除):外部删文件也要清理索引块
841
+ const committed = execSync(`git diff --name-only --diff-filter=ACMRTD ${stored} HEAD`, opts).trim()
842
+ const dirty = execSync(`git diff --name-only --diff-filter=ACMRTD`, opts).trim()
843
+ const lines = [...new Set([...committed.split("\n").filter(Boolean), ...dirty.split("\n").filter(Boolean)])]
844
+ diffOut = lines
845
+ } catch {
846
+ // rebase / shallow clone 导致旧 commit 不可达 → 退到全量
847
+ return null
848
+ }
849
+
850
+ if (diffOut.length > 200) {
851
+ // 大范围变更(分支切换等)→ 退到 codeSync,它有更好的进度反馈
852
+ return null
853
+ }
854
+
855
+ let updated = 0, removed = 0, skipped = 0, failed = 0
856
+ const errors = []
857
+ for (let i = 0; i < diffOut.length; i++) {
858
+ const rel = diffOut[i].replaceAll("\\", "/")
859
+ const abs = join(dir, rel)
860
+ const ext = rel.slice(rel.lastIndexOf(".")).toLowerCase()
861
+
862
+ // 跳过隐藏目录和 SKIP_DIRS 里的文件
863
+ const pathDirs = rel.split("/")
864
+ if (pathDirs.some((d) => SKIP_DIRS.has(d) || d.startsWith("."))) continue
865
+
866
+ if (!CODE_EXTS.has(ext) && !DOC_EXTS.has(ext)) { skipped++; continue }
867
+
868
+ try {
869
+ const text = await readFile(abs, "utf8")
870
+ const lines = text.split("\n")
871
+ if (CODE_EXTS.has(ext)) {
872
+ const lang = detectLanguage(abs)
873
+ let mtimeMs = 0
874
+ try { mtimeMs = Math.floor((await stat(abs)).mtimeMs) } catch { /* 新文件 */ }
875
+ _upsertCodeFile(memory, dir, rel, lines, lang, mtimeMs)
876
+ } else {
877
+ let mtimeMs = 0
878
+ try { mtimeMs = Math.floor((await stat(abs)).mtimeMs) } catch { /* 新文件 */ }
879
+ _upsertDocFile(memory, dir, rel, lines, mtimeMs)
880
+ }
881
+ updated++
882
+ } catch (e) {
883
+ // readFile 失败 = 文件已删 → 清理索引块;
884
+ // 其他错误(权限/SQLite)不推进锚点——下次 gitSync 会重试
885
+ const isDeleted = e.code === "ENOENT"
886
+ if (isDeleted) {
887
+ if (CODE_EXTS.has(ext)) memory.db.prepare(`DELETE FROM code_chunks WHERE origin = ? AND path = ?`).run(dir, rel)
888
+ else memory.db.prepare(`DELETE FROM doc_chunks WHERE origin = ? AND path = ?`).run(dir, rel)
889
+ removed++
890
+ } else {
891
+ failed++
892
+ if (errors.length < 5) errors.push(`${rel}: ${e.message}`)
893
+ }
894
+ }
895
+ if (onProgress && i % 5 === 0) {
896
+ onProgress({ phase: "index", current: i + 1, total: diffOut.length, updated, removed, skipped })
897
+ }
898
+ }
899
+
900
+ // 更新锚点(有 failed 时不推进——下次 gitSync 会重试这些文件)
901
+ if (failed === 0) {
902
+ memory.db.prepare(`INSERT INTO meta (key, value) VALUES ('last_indexed_commit', ?)
903
+ ON CONFLICT (key) DO UPDATE SET value = excluded.value`).run(head)
904
+ }
905
+
906
+ onProgress?.({ phase: "done", total: diffOut.length, updated, removed, skipped, failed })
907
+ return { updated, removed, skipped, failed, errors }
908
+ }
909
+
822
910
  /**
823
911
  * 同步代码索引:扫描 dir 下所有源文件 → 分块 → upsert 到 code_chunks。
824
912
  * 按 mtime 增量——只重建变更过的文件块。
@@ -891,9 +979,21 @@ export async function codeSync(memory, dir, { onProgress } = {}) {
891
979
  }
892
980
 
893
981
  onProgress?.({ phase: "done", total: files.length, updated, removed, skipped, failed })
982
+ // 全量同步成功后记录 git 锚点(下次启动 gitSync 用它做增量 diff 基准)
983
+ markIndexedCommit(memory, dir)
894
984
  return { updated, removed, skipped, failed, errors, total: files.length }
895
985
  }
896
986
 
987
+ /** 记录当前 HEAD 作为索引锚点(gitSync 增量 diff 基准);非 git 仓库静默跳过 */
988
+ async function markIndexedCommit(memory, dir) {
989
+ try {
990
+ const { execSync } = await import("node:child_process")
991
+ const head = execSync("git rev-parse HEAD", { cwd: dir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000 }).trim()
992
+ memory.db.prepare(`INSERT INTO meta (key, value) VALUES ('last_indexed_commit', ?)
993
+ ON CONFLICT (key) DO UPDATE SET value = excluded.value`).run(head)
994
+ } catch { /* 非 git 仓库或 git 不可用,跳过 */ }
995
+ }
996
+
897
997
  /**
898
998
  * 代码检索:FTS5(BM25) + 可选向量余弦,RRF 合并。
899
999
  * 无 embedder 时退化为纯 FTS;ftsQuery 为空(纯标点查询)且有 embedder 时退化为纯向量。
@@ -1023,6 +1123,10 @@ export async function reindexFile(memory, cwd, absPath) {
1023
1123
  try { mtimeMs = Math.floor((await stat(absPath)).mtimeMs) } catch { /* 新文件 */ }
1024
1124
  _upsertDocFile(memory, cwd, rel, lines, mtimeMs)
1025
1125
  }
1126
+ // 立即补算向量,不等惰性检索(刚改的文件应该有语义搜索能力)
1127
+ if (memory.embedder) {
1128
+ try { await ensureEmbeddings(memory) } catch { /* embedding 失败不阻塞 */ }
1129
+ }
1026
1130
  }
1027
1131
 
1028
1132
  // ========== 文档索引 ==========
@@ -1121,6 +1225,7 @@ export async function docSync(memory, dir, { onProgress } = {}) {
1121
1225
  }
1122
1226
 
1123
1227
  onProgress?.({ phase: "done", total: files.length, updated, removed, skipped, failed })
1228
+ markIndexedCommit(memory, dir)
1124
1229
  return { updated, removed, skipped, failed, errors, total: files.length }
1125
1230
  }
1126
1231
 
@@ -8,6 +8,7 @@ Guidelines:
8
8
  3. Your plan—preliminary if questions remain, final if context is sufficient
9
9
  - Ground the plan in reality: cite real file paths and line numbers, name actual functions and modules. No invented architecture.
10
10
  - Make steps concrete and verifiable: each step small enough to check, ordered so dependencies come first.
11
+ - Identify edge cases and failure modes in the plan. What boundary conditions does the implementation need to handle? Each step that encounters a risk must specify its fallback — not "handle error", but the concrete recovery path.
11
12
  - Where a real design choice exists, call out the trade-offs and recommend ONE option with reasoning—don't list possibilities without taking a stance.
12
13
  - Keep scope minimal: the plan should solve the task, not redesign the codebase. Prefer modifying existing files over creating new ones—new files should only appear when the task genuinely demands a new module. List every file that will be modified, so the implementer knows the blast radius.
13
14
  - If something is ambiguous, note it in the plan; do not ask the user.
package/src/provider.mjs CHANGED
@@ -353,11 +353,12 @@ async function readSSE(response, { onToken, onReasoning }) {
353
353
  result.content += delta.content
354
354
  onToken?.(delta.content)
355
355
  }
356
- // tool_calls 按 index 分槽累积,name/arguments 都是分片到达的
356
+ // tool_calls 按 index 分槽累积,arguments 是分片到达的需拼接;
357
+ // name 个别 API(GLM 偶尔)会重发完整 name 而非增量,用 += 会拼成 "readread"——只取第一次非空值
357
358
  for (const tc of delta.tool_calls ?? []) {
358
359
  const slot = (result.toolCalls[tc.index] ??= { id: "", name: "", arguments: "" })
359
360
  if (tc.id) slot.id = tc.id
360
- if (tc.function?.name) slot.name += tc.function.name
361
+ if (tc.function?.name && !slot.name) slot.name = tc.function.name
361
362
  if (tc.function?.arguments) slot.arguments += tc.function.arguments
362
363
  }
363
364
  }
package/src/tui.mjs CHANGED
@@ -279,6 +279,7 @@ export async function startTUI(agent, opts = {}) {
279
279
  currentTool: null, // 正在执行的工具名 (状态栏显示)
280
280
  processingStarted: 0, // 本轮处理开始时间 (状态栏计时)
281
281
  status: "Ready",
282
+ queue: [], // 处理中排队的待执行消息:[{ text }],处理完自动取下一条
282
283
  }
283
284
 
284
285
  // 恢复的会话如果所有任务completed,自动收起 todo 面板 (对齐运行时行为)
@@ -553,6 +554,13 @@ export async function startTUI(agent, opts = {}) {
553
554
  }
554
555
  }
555
556
 
557
+ // 队列预览 (暗色,紧挨输入框上方):与子 agent 面板/权限预览共享输入框上方空间
558
+ // 只在 processing 时显示(非 processing 时队列应为空),且最多 1 行预览避免挤压对话区
559
+ if (state.queue.length > 0 && state.processing) {
560
+ const preview = sliceByWidth(state.queue[0].text, W - 20)
561
+ out.push(`${C.dim}❯ Queue: ${state.queue.length} pending${state.queue.length > 1 ? ` (next: ${preview}…)` : ` (next: ${preview})`} — Ctrl+D del${ansi.reset}${ansi.clearLine}`)
562
+ }
563
+
556
564
  // 输入框 (全边框,宽 W)
557
565
  let borderColor = C.tool
558
566
  let title
@@ -663,7 +671,8 @@ export async function startTUI(agent, opts = {}) {
663
671
  ? ` │ ${ansi.reset}${C.warn}ctx ${ctxPct}%${ansi.reset}${ansi.dim}`
664
672
  : ` │ ctx ${ctxPct}%`
665
673
  : ""
666
- statusLine = ` ${statusText}${taskHint}${tokenHint}${ctxHint}${scrollHint} Enter: send/: commands │ wheel/PgUp/PgDn: scroll │ Ctrl+C: exit`
674
+ const queueHint = state.queue.length > 0 ? `queue: ${state.queue.length}` : ""
675
+ statusLine = ` ${statusText}${taskHint}${tokenHint}${ctxHint}${queueHint}${scrollHint} │ Enter: send${state.processing ? " (queue)" : ""} │ /: commands │ wheel/PgUp/PgDn: scroll │ Ctrl+C: exit`
667
676
  }
668
677
  const autoBanner = agent.autoApprove ? `${C.warn} AUTO${ansi.reset}${ansi.dim}│` : ""
669
678
  const planBanner = agent.planMode ? `${C.tool} PLAN${ansi.reset}${ansi.dim}│` : ""
@@ -679,8 +688,8 @@ export async function startTUI(agent, opts = {}) {
679
688
  process.stdout.write(frame)
680
689
  }
681
690
 
682
- // 光标:输入态定位到输入框内 (IME 候选框跟随真实光标);处理中/权限确认/菜单态时隐藏
683
- if (state.processing || state.permission || state.question || state.picker || state.wizard?.step === "provider") {
691
+ // 光标:输入态定位到输入框内 (IME 候选框跟随真实光标);权限确认/菜单态时隐藏
692
+ if (state.permission || state.question || state.picker || state.wizard?.step === "provider") {
684
693
  process.stdout.write(ansi.hideCursor)
685
694
  } else {
686
695
  const cursorRow = 1 + convH + pickerH + taskPanelH + 2 + (layout.cursorLine - inputOffset) // header + 对话区 + todo 面板 + 上边框 + 行偏移
@@ -695,20 +704,43 @@ export async function startTUI(agent, opts = {}) {
695
704
 
696
705
  async function submit() {
697
706
  const text = state.input.join("").trim()
698
- if (!text || state.processing) return
707
+ if (!text) return
699
708
  state.input = []
700
709
  state.cursor = 0
701
710
  state.history.push(text)
702
711
  state.historyIndex = -1
703
712
  state.scroll = 0
704
713
 
705
- // 斜杠Commands:本地处理,不进入 agent
714
+ // 斜杠Commands:本地处理,不进入 agent(处理中也允许执行部分命令如 /cancel)
706
715
  if (text.startsWith("/")) {
716
+ if (state.processing) {
717
+ // 处理中只允许取消当前任务,其他命令排队
718
+ if (text === "/cancel" || text === "/exit") {
719
+ await handleSlash(text)
720
+ } else {
721
+ state.queue.push({ text })
722
+ render()
723
+ }
724
+ return
725
+ }
707
726
  await handleSlash(text)
708
727
  return
709
728
  }
710
729
 
711
- pushLabel(`❯ You:`, ansi.bold + C.user)
730
+ // 处理中:入队等待,不立即执行
731
+ if (state.processing) {
732
+ state.queue.push({ text })
733
+ pushLabel(`❯ You: (queued #${state.queue.length})`, ansi.bold + C.user)
734
+ pushLine(text, C.dim)
735
+ render()
736
+ return
737
+ }
738
+
739
+ await runAgentTurn(text)
740
+ }
741
+
742
+ /** 执行一轮 agent 对话(从 submit 或队列取出调用) */
743
+ async function runAgentTurn(text) {
712
744
  pushLine(text, C.text)
713
745
 
714
746
  // 任务开始前自动打存档点 (git 仓库内;失败静默,不挡任务)
@@ -894,6 +926,24 @@ export async function startTUI(agent, opts = {}) {
894
926
  // 存失败不打断使用
895
927
  }
896
928
  render()
929
+
930
+ // 队列里有待执行消息:自动取下一条执行
931
+ if (state.queue.length > 0) {
932
+ const next = state.queue.shift()
933
+ // 队列里的斜杠命令直接执行
934
+ if (next.text.startsWith("/")) {
935
+ await handleSlash(next.text)
936
+ render()
937
+ // 斜杠命令执行完也继续检查队列
938
+ if (state.queue.length > 0 && !state.processing) {
939
+ const next2 = state.queue.shift()
940
+ await runAgentTurn(next2.text)
941
+ }
942
+ } else {
943
+ pushLabel(`❯ You: (from queue)`, ansi.bold + C.user)
944
+ await runAgentTurn(next.text)
945
+ }
946
+ }
897
947
  }
898
948
 
899
949
  function flushStream() {
@@ -1117,25 +1167,25 @@ export async function startTUI(agent, opts = {}) {
1117
1167
  total += s.added
1118
1168
  pushLine(` team: +${s.added} ~${s.updated} -${s.removed}`, C.dim)
1119
1169
  }
1120
- // 重建代码索引
1121
- pushLine(` [code] Rebuilding code index...`, C.tool)
1122
- const cr = await codeSync(agent.memory, agent.cwd, {
1123
- onProgress: (p) => {
1124
- if (p.phase === "index" && p.current % 20 === 0) {
1125
- pushLine(` Indexing... ${p.current}/${p.total}`, C.dim)
1170
+ // 重建代码索引和文档索引并行(读写不同表,WAL 支持)
1171
+ pushLine(` [code+doc] Rebuilding indexes...`, C.tool)
1172
+ const [cr, dr] = await Promise.all([
1173
+ codeSync(agent.memory, agent.cwd, {
1174
+ onProgress: (p) => {
1175
+ if (p.phase === "index" && p.current % 20 === 0) {
1176
+ pushLine(` code: ${p.current}/${p.total}`, C.dim)
1177
+ }
1126
1178
  }
1127
- }
1128
- })
1129
- pushLine(` code: ${cr.total} files, +${cr.updated} ~${cr.skipped} -${cr.removed}`, C.dim)
1130
- // 重建文档索引
1131
- pushLine(` [doc] Rebuilding doc index...`, C.tool)
1132
- const dr = await docSync(agent.memory, agent.cwd, {
1133
- onProgress: (p) => {
1134
- if (p.phase === "index" && p.current % 5 === 0) {
1135
- pushLine(` Indexing... ${p.current}/${p.total}`, C.dim)
1179
+ }),
1180
+ docSync(agent.memory, agent.cwd, {
1181
+ onProgress: (p) => {
1182
+ if (p.phase === "index" && p.current % 5 === 0) {
1183
+ pushLine(` doc: ${p.current}/${p.total}`, C.dim)
1184
+ }
1136
1185
  }
1137
- }
1138
- })
1186
+ }),
1187
+ ])
1188
+ pushLine(` code: ${cr.total} files, +${cr.updated} ~${cr.skipped} -${cr.removed}`, C.dim)
1139
1189
  pushLine(` doc: ${dr.total} files, +${dr.updated} ~${dr.skipped} -${dr.removed}`, C.dim)
1140
1190
  pushLine(`[reindex] Done, ${total} entries total. Vectors will be lazily generated on next search.`, C.tool)
1141
1191
  return
@@ -2286,7 +2336,19 @@ export async function startTUI(agent, opts = {}) {
2286
2336
  return
2287
2337
  }
2288
2338
 
2289
- if (state.processing) return // 处理中锁定输入
2339
+ if (state.processing) {
2340
+ // 处理中允许输入(排队),但屏蔽方向键历史和 Tab 补全
2341
+ if (key.name === "tab" || key.name === "up" || key.name === "down") return
2342
+ // Ctrl+D:删除队列中最后一条
2343
+ if (key.ctrl && key.name === "d") {
2344
+ if (state.queue.length > 0) {
2345
+ state.queue.pop()
2346
+ render()
2347
+ }
2348
+ return
2349
+ }
2350
+ // 其余可打印字符正常进入输入框
2351
+ }
2290
2352
 
2291
2353
  // Tab:斜杠Commands补全 (循环候选);其余输入忽略 (\t 会顶破输入框,永不直接插入)
2292
2354
  if (key.name === "tab") {
@@ -2423,36 +2485,56 @@ export async function startTUI(agent, opts = {}) {
2423
2485
  render()
2424
2486
 
2425
2487
  // 后台索引 (进界面后再跑,不阻塞启动);进度走底部状态栏,不往对话区塞行
2488
+ // 优先用 git diff 增量(快),git 不可用或首次运行时退到全量扫描
2426
2489
  ;(async () => {
2427
- const { codeSync, docSync } = await import("./memory.mjs")
2490
+ const { codeSync, docSync, gitSync } = await import("./memory.mjs")
2428
2491
  const cwd = agent.cwd
2429
2492
  let codeFiles = 0, docFiles = 0
2430
- try {
2431
- state.status = "Indexing code..."
2432
- render()
2433
- await codeSync(agent.memory, cwd, {
2434
- onProgress: (p) => {
2435
- if (p.phase === "index" && p.current % 30 === 0) {
2436
- state.status = `Indexing code... ${p.current}/${p.total}`
2437
- render()
2438
- }
2493
+
2494
+ state.status = "Indexing..."
2495
+ render()
2496
+
2497
+ const gitRes = await gitSync(agent.memory, cwd, {
2498
+ onProgress: (p) => {
2499
+ if (p.phase === "index" && p.current % 5 === 0) {
2500
+ state.status = `Indexing... ${p.current}/${p.total}`
2501
+ render()
2439
2502
  }
2440
- })
2503
+ }
2504
+ })
2505
+
2506
+ if (gitRes !== null) {
2507
+ // git 增量成功,直接统计
2441
2508
  codeFiles = agent.memory.db.prepare(`SELECT COUNT(DISTINCT path) AS n FROM code_chunks`).get()?.n ?? 0
2442
- } catch { /* 不阻塞 */ }
2443
- try {
2444
- state.status = "Indexing docs..."
2445
- render()
2446
- await docSync(agent.memory, cwd, {
2447
- onProgress: (p) => {
2448
- if (p.phase === "index" && p.current % 10 === 0) {
2449
- state.status = `Indexing docs... ${p.current}/${p.total}`
2450
- render()
2451
- }
2452
- }
2453
- })
2454
2509
  docFiles = agent.memory.db.prepare(`SELECT COUNT(DISTINCT path) AS n FROM doc_chunks`).get()?.n ?? 0
2455
- } catch { /* 不阻塞 */ }
2510
+ } else {
2511
+ // 退到全量扫描(codeSync 和 docSync 并行——读写不同表,SQLite WAL 天然支持)
2512
+ const [codeRes, docRes] = await Promise.allSettled([
2513
+ codeSync(agent.memory, cwd, {
2514
+ onProgress: (p) => {
2515
+ if (p.phase === "index" && p.current % 30 === 0) {
2516
+ state.status = `Indexing code... ${p.current}/${p.total}`
2517
+ render()
2518
+ }
2519
+ }
2520
+ }),
2521
+ docSync(agent.memory, cwd, {
2522
+ onProgress: (p) => {
2523
+ if (p.phase === "index" && p.current % 10 === 0) {
2524
+ state.status = `Indexing docs... ${p.current}/${p.total}`
2525
+ render()
2526
+ }
2527
+ }
2528
+ }),
2529
+ ])
2530
+ if (codeRes.status === "fulfilled") {
2531
+ codeFiles = agent.memory.db.prepare(`SELECT COUNT(DISTINCT path) AS n FROM code_chunks`).get()?.n ?? 0
2532
+ }
2533
+ if (docRes.status === "fulfilled") {
2534
+ docFiles = agent.memory.db.prepare(`SELECT COUNT(DISTINCT path) AS n FROM doc_chunks`).get()?.n ?? 0
2535
+ }
2536
+ }
2537
+
2456
2538
  state.status = codeFiles || docFiles
2457
2539
  ? `Ready — idx code ${codeFiles} doc ${docFiles}`
2458
2540
  : "Ready"