thincoder 0.7.6 → 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 +195 -187
- package/package.json +1 -1
- package/src/agent.mjs +18 -6
- package/src/memory.mjs +36 -13
- package/src/tui.mjs +69 -7
package/README.md
CHANGED
|
@@ -2,133 +2,134 @@
|
|
|
2
2
|
|
|
3
3
|
**Sharp Code, Zero Bloat.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**A "thin" AI coding agent: pure `.mjs`, no build step, zero npm dependencies, native Node.js.**
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
- **Plan Mode
|
|
24
|
-
- **AUTO
|
|
25
|
-
-
|
|
26
|
-
- **Goal/Verify/Skills
|
|
27
|
-
-
|
|
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
|
|
36
|
-
| **Project** |
|
|
37
|
-
| **Team
|
|
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
|
-
-
|
|
40
|
-
- **
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
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
|
-
-
|
|
49
|
-
-
|
|
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
|
-
#
|
|
57
|
+
# Launch the TUI (default command)
|
|
58
58
|
thincoder
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
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
|
-
|
|
63
|
+
You can also hand-write `~/.thincoder/config.json` (see "Configuration" below), then:
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
|
-
#
|
|
67
|
-
thincoder chat "
|
|
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="
|
|
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
|
-
#
|
|
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
|
-
|
|
85
|
+
Running from source: replace `thincoder` above with `node bin/thincoder.mjs`.
|
|
86
86
|
|
|
87
|
-
TUI
|
|
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
|
-
|
|
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
|
-
//
|
|
98
|
+
// multiple allowed; switch with /model <name>
|
|
99
99
|
{
|
|
100
100
|
"name": "deepseek",
|
|
101
|
-
"baseURL": "https://api.deepseek.com/v1", //
|
|
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
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
// "
|
|
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", //
|
|
111
|
+
"activeProvider": "deepseek", // currently active provider name
|
|
111
112
|
"embedding": {
|
|
112
|
-
//
|
|
113
|
+
// optional: without it, retrieval is pure FTS
|
|
113
114
|
"baseURL": "https://api.siliconflow.cn/v1",
|
|
114
|
-
"apiKey": "sk-...", //
|
|
115
|
+
"apiKey": "sk-...", // or SILICONFLOW_API_KEY
|
|
115
116
|
"model": "BAAI/bge-m3",
|
|
116
117
|
},
|
|
117
118
|
"agent": {
|
|
118
|
-
"maxTurns": 100, //
|
|
119
|
-
"compactThreshold": 100000, //
|
|
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
|
-
//
|
|
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
|
-
//
|
|
132
|
+
// optional: MCP server list
|
|
132
133
|
"servers": [
|
|
133
134
|
{
|
|
134
135
|
"name": "filesystem",
|
|
@@ -140,143 +141,150 @@ TUI 内斜杠命令:`/help`、`/model`(方向键选择全部 provider 的全
|
|
|
140
141
|
}
|
|
141
142
|
```
|
|
142
143
|
|
|
143
|
-
##
|
|
144
|
+
## Architecture
|
|
144
145
|
|
|
145
146
|
```
|
|
146
|
-
bin/thincoder.mjs
|
|
147
|
+
bin/thincoder.mjs command entry (tui / chat / memory / sync / distill)
|
|
147
148
|
src/
|
|
148
|
-
provider.mjs LLM
|
|
149
|
-
embedding.mjs
|
|
150
|
-
tools.mjs 16
|
|
151
|
-
mcp.mjs MCP
|
|
152
|
-
agent.mjs
|
|
153
|
-
+
|
|
154
|
-
repomap.mjs
|
|
155
|
-
context.mjs token
|
|
156
|
-
memory.mjs
|
|
157
|
-
+ FTS5 +
|
|
158
|
-
session.mjs
|
|
159
|
-
skills.mjs
|
|
160
|
-
markdown.mjs
|
|
161
|
-
gitmem.mjs Team
|
|
162
|
-
distill.mjs
|
|
163
|
-
checkpoint.mjs git patch
|
|
164
|
-
config.mjs
|
|
165
|
-
tui.mjs
|
|
166
|
-
test/ node: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
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
- **git
|
|
177
|
-
-
|
|
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 #
|
|
183
|
-
node scripts/verify-compress.mjs #
|
|
184
|
-
node scripts/verify-team.mjs #
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
##
|
|
190
|
-
|
|
191
|
-
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
-
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
-
|
|
201
|
-
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
- **
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
- **
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
- **
|
|
225
|
-
-
|
|
226
|
-
|
|
227
|
-
### 0.7.
|
|
228
|
-
-
|
|
229
|
-
-
|
|
230
|
-
-
|
|
231
|
-
- **
|
|
232
|
-
- **
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
-
|
|
236
|
-
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
-
|
|
240
|
-
- **
|
|
241
|
-
- **
|
|
242
|
-
-
|
|
243
|
-
- **
|
|
244
|
-
|
|
245
|
-
### 0.
|
|
246
|
-
-
|
|
247
|
-
-
|
|
248
|
-
-
|
|
249
|
-
-
|
|
250
|
-
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
-
|
|
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"
|
|
254
262
|
|
|
255
263
|
### 0.4.0
|
|
256
|
-
-
|
|
257
|
-
-
|
|
258
|
-
-
|
|
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)
|
|
259
267
|
|
|
260
268
|
### 0.3.0
|
|
261
|
-
- MCP
|
|
262
|
-
- Skills
|
|
263
|
-
- Plan/Goal/Question
|
|
264
|
-
-
|
|
265
|
-
- task
|
|
266
|
-
- DeepSeek thinking
|
|
267
|
-
- checkpoint
|
|
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
|
|
268
276
|
|
|
269
277
|
### 0.2.0
|
|
270
|
-
- multi-provider
|
|
271
|
-
-
|
|
272
|
-
- `/think`
|
|
273
|
-
- `/model`
|
|
274
|
-
- 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
|
|
275
283
|
|
|
276
284
|
### 0.1.0
|
|
277
|
-
- Agent
|
|
278
|
-
-
|
|
279
|
-
-
|
|
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
|
|
280
288
|
|
|
281
289
|
## License
|
|
282
290
|
|
package/package.json
CHANGED
package/src/agent.mjs
CHANGED
|
@@ -909,7 +909,8 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
909
909
|
agent._touchedFiles = []
|
|
910
910
|
agent._verifyRetries = 0 // 修复-验证循环计数,每个新 run 从头开始
|
|
911
911
|
const MAX_VERIFY_RETRIES = 3
|
|
912
|
-
let
|
|
912
|
+
let guardPushbacks = 0 // 完成守卫推回次数(最多推 2 次:第三次直接放行,避免无限循环)
|
|
913
|
+
let honestReminderInjected = false // verify 耗尽后注入了诚实提醒,下一轮直接放行
|
|
913
914
|
const recentCallSigs = [] // 停滞检测:最近的工具调用签名(同一调用连续 3 次即提醒)
|
|
914
915
|
|
|
915
916
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
@@ -966,9 +967,10 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
966
967
|
if (!response.content) {
|
|
967
968
|
throw new Error("LLM 返回了空回复(可能是思考耗尽或被截断)。可 /think effort 降低推理强度后重试")
|
|
968
969
|
}
|
|
969
|
-
// 完成守卫:本轮改过文件却没跑过 verify
|
|
970
|
-
|
|
971
|
-
|
|
970
|
+
// 完成守卫:本轮改过文件却没跑过 verify,推回去验证。
|
|
971
|
+
// 可重武装但最多推 2 次——第三次直接放行(避免 agent 死活不调 verify 时无限循环)
|
|
972
|
+
if (depth === 0 && agent._mutatedThisRun && !agent._verifiedThisRun && guardPushbacks < 2) {
|
|
973
|
+
guardPushbacks++
|
|
972
974
|
agent.history.push({ role: "assistant", content: response.content })
|
|
973
975
|
agent.history.push({
|
|
974
976
|
role: "user",
|
|
@@ -987,10 +989,20 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
987
989
|
})
|
|
988
990
|
continue
|
|
989
991
|
}
|
|
990
|
-
//
|
|
992
|
+
// 重试用尽:测试仍然失败,注入诚实提醒后给模型最后一轮总结
|
|
991
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
|
|
992
1000
|
agent.history.push({ role: "assistant", content: response.content })
|
|
993
|
-
|
|
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
|
|
994
1006
|
}
|
|
995
1007
|
agent.history.push({ role: "assistant", content: response.content })
|
|
996
1008
|
return response.content
|
package/src/memory.mjs
CHANGED
|
@@ -837,9 +837,9 @@ export async function gitSync(memory, dir, { onProgress } = {}) {
|
|
|
837
837
|
// 取两个 diff 的并集:已提交的变更(pull/merge)+ 工作区脏文件(用户在外部编辑器改的)
|
|
838
838
|
let diffOut
|
|
839
839
|
try {
|
|
840
|
-
//
|
|
841
|
-
const committed = execSync(`git diff --name-only --diff-filter=
|
|
842
|
-
const dirty = execSync(`git diff --name-only --diff-filter=
|
|
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
843
|
const lines = [...new Set([...committed.split("\n").filter(Boolean), ...dirty.split("\n").filter(Boolean)])]
|
|
844
844
|
diffOut = lines
|
|
845
845
|
} catch {
|
|
@@ -852,7 +852,8 @@ export async function gitSync(memory, dir, { onProgress } = {}) {
|
|
|
852
852
|
return null
|
|
853
853
|
}
|
|
854
854
|
|
|
855
|
-
let updated = 0, removed = 0, skipped = 0
|
|
855
|
+
let updated = 0, removed = 0, skipped = 0, failed = 0
|
|
856
|
+
const errors = []
|
|
856
857
|
for (let i = 0; i < diffOut.length; i++) {
|
|
857
858
|
const rel = diffOut[i].replaceAll("\\", "/")
|
|
858
859
|
const abs = join(dir, rel)
|
|
@@ -879,22 +880,31 @@ export async function gitSync(memory, dir, { onProgress } = {}) {
|
|
|
879
880
|
}
|
|
880
881
|
updated++
|
|
881
882
|
} catch (e) {
|
|
882
|
-
//
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
+
}
|
|
886
894
|
}
|
|
887
895
|
if (onProgress && i % 5 === 0) {
|
|
888
896
|
onProgress({ phase: "index", current: i + 1, total: diffOut.length, updated, removed, skipped })
|
|
889
897
|
}
|
|
890
898
|
}
|
|
891
899
|
|
|
892
|
-
//
|
|
893
|
-
|
|
894
|
-
|
|
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
|
+
}
|
|
895
905
|
|
|
896
|
-
onProgress?.({ phase: "done", total: diffOut.length, updated, removed, skipped })
|
|
897
|
-
return { updated, removed, skipped }
|
|
906
|
+
onProgress?.({ phase: "done", total: diffOut.length, updated, removed, skipped, failed })
|
|
907
|
+
return { updated, removed, skipped, failed, errors }
|
|
898
908
|
}
|
|
899
909
|
|
|
900
910
|
/**
|
|
@@ -969,9 +979,21 @@ export async function codeSync(memory, dir, { onProgress } = {}) {
|
|
|
969
979
|
}
|
|
970
980
|
|
|
971
981
|
onProgress?.({ phase: "done", total: files.length, updated, removed, skipped, failed })
|
|
982
|
+
// 全量同步成功后记录 git 锚点(下次启动 gitSync 用它做增量 diff 基准)
|
|
983
|
+
markIndexedCommit(memory, dir)
|
|
972
984
|
return { updated, removed, skipped, failed, errors, total: files.length }
|
|
973
985
|
}
|
|
974
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
|
+
|
|
975
997
|
/**
|
|
976
998
|
* 代码检索:FTS5(BM25) + 可选向量余弦,RRF 合并。
|
|
977
999
|
* 无 embedder 时退化为纯 FTS;ftsQuery 为空(纯标点查询)且有 embedder 时退化为纯向量。
|
|
@@ -1203,6 +1225,7 @@ export async function docSync(memory, dir, { onProgress } = {}) {
|
|
|
1203
1225
|
}
|
|
1204
1226
|
|
|
1205
1227
|
onProgress?.({ phase: "done", total: files.length, updated, removed, skipped, failed })
|
|
1228
|
+
markIndexedCommit(memory, dir)
|
|
1206
1229
|
return { updated, removed, skipped, failed, errors, total: files.length }
|
|
1207
1230
|
}
|
|
1208
1231
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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() {
|
|
@@ -2286,7 +2336,19 @@ export async function startTUI(agent, opts = {}) {
|
|
|
2286
2336
|
return
|
|
2287
2337
|
}
|
|
2288
2338
|
|
|
2289
|
-
if (state.processing)
|
|
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") {
|