thincoder 0.12.41 → 0.12.43
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/CHANGELOG.md +28 -0
- package/package.json +1 -1
- package/src/advisor/run.mjs +9 -5
- package/src/agent/helpers.mjs +4 -4
- package/src/agent.mjs +16 -3
- package/src/config.mjs +2 -2
- package/src/context.mjs +6 -2
- package/src/prompts/discipline.md +1 -1
- package/src/prompts/engineering.md +56 -33
- package/src/tools/index.mjs +3 -3
- package/src/tools/ops.mjs +1 -29
- package/src/tui/agent-turn.mjs +26 -0
- package/src/tui/cmd-config.mjs +4 -4
- package/src/tools/sleep.md +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录 ThinCoder CLI 的发布历史。格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),版本遵循[语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
+
## [0.12.43] — 2026-08-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **评审超时可配置**:`agent.advisor.timeoutMs`(默认 600s,原固定 300s)——运行期读取,非法值(0/负数/字符串)回退默认;长评审不再被固定墙钟截断
|
|
10
|
+
- **主 agent 轮次上限默认 100 → 200**:大任务(多文件重构、修复-验证循环)不轻易撞墙
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **工具输出限制全链路 16K → 64K(65536)**:落盘阈值 `TOOL_RESULT_OFFLOAD_LIMIT`/`TOOL_RESULT_PREVIEW`、advisor 内部截断 `MAX_RESULT_CHARS` 全部放宽——大输出(advisor 评审、大文件读取)不再被过早落盘/截断
|
|
15
|
+
- **轮末探索蒸馏异步化**:回合结束信号先行(TUI 状态栏立即恢复,不再等第二次静默 LLM 调用);蒸馏 promise 挂 `agent._pendingDistill`,下一轮开头 await(摘要必在下一轮 LLM 调用前落位),退出前 bounded flush(≤5s);`onDistilled` 回调触发压缩版落盘
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
|
|
19
|
+
- **`sleep` 工具删除**:编程场景零真实使用(会话历史 0 次调用),且工具说明误导模型在同步工具(advisor/subagent)后 sleep 空等——白耗 10-300 秒;等待需求改走 bash 内联命令;内部速率限制/重试退避(`_rateHooks.sleep`)不受影响
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- 工具输出落盘失败回退截断对齐 64K;旧阈值残留自动化断言(`MAX_RESULT_CHARS` 导出 + import 断言、helpers/run.mjs 边界匹配无残留)
|
|
24
|
+
|
|
25
|
+
## [0.12.42] — 2026-08-24
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **工程模式发起权归用户**:设计评审只能由用户发起——agent 准备设计后只呈递+提醒「设计就绪」,不再自行调 advisor(此前 agent 可自行判断"讨论完了"直接提交评审并开发,属越权);评审打回后每轮呈递发现+修复建议、用户逐条拍板再改,不再自行修完重送
|
|
30
|
+
- **交付 code review 改为自动流程节点**:eng-coder 返回后自动评审(不问用户);工程模式下 guard 推回维持关闭
|
|
31
|
+
- 提示词注意力优化:核心规则开头立纲 + 结尾钉死 + 状态表补「Review fix loop」态;设计文档 ENGINEERING-MODE/WORKLOOP/PROMPT-DECOUPLING 同步
|
|
32
|
+
|
|
5
33
|
## [0.12.41] — 2026-08-23
|
|
6
34
|
|
|
7
35
|
### Added
|
package/package.json
CHANGED
package/src/advisor/run.mjs
CHANGED
|
@@ -29,8 +29,8 @@ export const ADVISOR_THINKING_PLACEHOLDER = "\n[thinking…]\n"
|
|
|
29
29
|
// Context window limits
|
|
30
30
|
const MAX_CONTEXT_TOKENS = 120_000 // Reserve headroom to avoid OOM
|
|
31
31
|
const TOOL_TIMEOUT_MS = 30_000 // single tool timeout
|
|
32
|
-
const REVIEW_TIMEOUT_MS =
|
|
33
|
-
const MAX_RESULT_CHARS =
|
|
32
|
+
const REVIEW_TIMEOUT_MS = 600_000 // whole review timeout (10 minutes)
|
|
33
|
+
export const MAX_RESULT_CHARS = 64 * 1024 // tool result truncation (line-aware; 64K, aligned with main offload limit)
|
|
34
34
|
const MAX_UNFIXED_DISPLAY = 10 // unfixed issues shown in the cap message
|
|
35
35
|
const MAX_KEY_FILES_IN_COMPACTION = 5 // files named in the compaction summary
|
|
36
36
|
|
|
@@ -156,9 +156,13 @@ async function runAdvisorToolLoop(provider, messages, onOutput, signal, agent, c
|
|
|
156
156
|
// Interrupted (Ctrl+I) — stop immediately instead of spinning a fresh uncancellable signal
|
|
157
157
|
if (signal?.aborted) return renderTimeline(timeline, "Advisor: interrupted.")
|
|
158
158
|
|
|
159
|
-
// Check review timeout (
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
// Check review timeout (10 minutes by default; agent.advisor.timeoutMs overrides)
|
|
160
|
+
// 运行时校验(设计评审 #1,2026-08-24):手写 config.json 的非法值(0/负数/字符串)
|
|
161
|
+
// 不得静默禁用或立即触发超时——非法一律回退默认。
|
|
162
|
+
const cfg = agent.config?.advisor?.timeoutMs
|
|
163
|
+
const timeoutMs = (Number.isFinite(cfg) && cfg > 0) ? cfg : REVIEW_TIMEOUT_MS
|
|
164
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
165
|
+
return renderTimeline(timeline, `Advisor: review timeout after ${Math.round(timeoutMs / 1000)}s. Partial results may be available. Try again with a narrower scope.`)
|
|
162
166
|
}
|
|
163
167
|
|
|
164
168
|
if (++turns > MAX_ADVISOR_TURNS) {
|
package/src/agent/helpers.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { writeFile, mkdir, readdir, stat, unlink } from "node:fs/promises"
|
|
|
8
8
|
import { join } from "node:path"
|
|
9
9
|
import { execSync } from "node:child_process"
|
|
10
10
|
|
|
11
|
-
export const DEFAULT_MAX_TURNS =
|
|
11
|
+
export const DEFAULT_MAX_TURNS = 200
|
|
12
12
|
export const DEFAULT_SUBAGENT_TURNS = 100
|
|
13
13
|
export const DEFAULT_GOAL_TURNS = 200
|
|
14
14
|
export const MIN_REPORT_CHARS = 200
|
|
@@ -20,8 +20,8 @@ export const REPORT_CONTINUATION =
|
|
|
20
20
|
"3. How you verified (tests run, commands executed, with results)\n" +
|
|
21
21
|
"4. Anything left undone or worth follow-up"
|
|
22
22
|
|
|
23
|
-
const TOOL_RESULT_OFFLOAD_LIMIT =
|
|
24
|
-
const TOOL_RESULT_PREVIEW =
|
|
23
|
+
const TOOL_RESULT_OFFLOAD_LIMIT = 64 * 1024 // 65536 chars — offload only above 64K (2026-08-24)
|
|
24
|
+
const TOOL_RESULT_PREVIEW = 64 * 1024 // chars shown inline when offloaded (aligns with CLI/VS Code webview)
|
|
25
25
|
|
|
26
26
|
/** Offload-dir write-time self-cleanup retention window (2026-08-21): files older than 3 days are deleted on the next offload. */
|
|
27
27
|
export const TMP_RETENTION_MS = 3 * 24 * 3600 * 1000
|
|
@@ -65,7 +65,7 @@ export async function cleanupOldToolResults(dir) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
/** Offload oversized tool results (>
|
|
68
|
+
/** Offload oversized tool results (>64K chars) to disk, returning a preview + file path.
|
|
69
69
|
* Writes trigger write-time self-cleanup of the offload dir first (dir param overridable for tests). */
|
|
70
70
|
export async function offloadToolResult(text, callId, dir = join(configDir, "tool-results")) {
|
|
71
71
|
if (text.length <= TOOL_RESULT_OFFLOAD_LIMIT) return text
|
package/src/agent.mjs
CHANGED
|
@@ -97,12 +97,21 @@ export function createAgent({
|
|
|
97
97
|
_compressFailures: 0,
|
|
98
98
|
_emptyRetries: 0, // empty-response retry budget (per-run; reset on a fresh user turn)
|
|
99
99
|
_runStartHistoryLen: 0, // machine-line length at the start of the current run — end-of-run exploration distillation slices from here
|
|
100
|
+
_pendingDistill: null, // in-flight end-of-run exploration distillation (SEND-STALL-DISTILL §2.1) — awaited at next run start / TUI exit flush
|
|
100
101
|
_currentTurn: 0, _maxTurns: 100, // turn counter for status bar display
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
/** Run the agent loop: LLM ↔ tool-call cycle until task completion or turn limit. Returns final text content. */
|
|
105
106
|
export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal, maxTurns: overrideTurns, resume = false } = {}) {
|
|
107
|
+
// Previous run's async exploration distillation must settle before this run pushes new
|
|
108
|
+
// input (SEND-STALL-DISTILL §2.2, N1): the compressed machine line is this run's starting
|
|
109
|
+
// point — await BEFORE prepareRun, or the history replacement would wipe the new input.
|
|
110
|
+
if (agent._pendingDistill) {
|
|
111
|
+
const p = agent._pendingDistill
|
|
112
|
+
agent._pendingDistill = null
|
|
113
|
+
await p
|
|
114
|
+
}
|
|
106
115
|
const { maxTurns, threshold, tools, toolSchemas, toolByName, systemPrompt } = await prepareRun(
|
|
107
116
|
agent, input, callbacks,
|
|
108
117
|
{ depth, signal, overrideTurns, resume, systemPrompt: SYSTEM_PROMPT, disciplineRules: DISCIPLINE_RULES, mainOverlay: MAIN_OVERLAY },
|
|
@@ -309,9 +318,13 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
309
318
|
if (cr.action === "continue") continue
|
|
310
319
|
if (depth === 0) {
|
|
311
320
|
// End-of-run exploration distillation (CONTEXT-COMPACTION §5): this run's inline
|
|
312
|
-
// exploration results become one semantic note before the final return.
|
|
313
|
-
//
|
|
314
|
-
|
|
321
|
+
// exploration results become one semantic note before the final return. Async
|
|
322
|
+
// (SEND-STALL-DISTILL §2.1): the turn-end signal goes out first — the promise hangs
|
|
323
|
+
// on agent._pendingDistill and settles at the next runAgent's start or the TUI's
|
|
324
|
+
// exit flush. Silent (N3): distillation failure must never block the return or lose
|
|
325
|
+
// history.
|
|
326
|
+
const distill = summarizeRunExplorations(agent, callbacks, signal).catch(() => {})
|
|
327
|
+
agent._pendingDistill = distill
|
|
315
328
|
}
|
|
316
329
|
return cr.content
|
|
317
330
|
}
|
package/src/config.mjs
CHANGED
|
@@ -39,7 +39,7 @@ export const PROVIDER_PRESETS = {
|
|
|
39
39
|
export const DEFAULTS = {
|
|
40
40
|
activeModel: null, // optional: override provider.model (set via /model picker or /model provider:model)
|
|
41
41
|
agent: {
|
|
42
|
-
maxTurns:
|
|
42
|
+
maxTurns: 200,
|
|
43
43
|
subagentTurns: 100,
|
|
44
44
|
subagentModel: null, // default subagent model: "provider:model" | provider name | model name (parent provider); null = inherit parent provider
|
|
45
45
|
subagentModels: {}, // per-type override: { explore, plan, coder, "eng-coder" } — priority: subagent tool model arg > this[role] > subagentModel > parent provider
|
|
@@ -52,7 +52,7 @@ export const DEFAULTS = {
|
|
|
52
52
|
consultTurns: 40, // per-consultant tool-turn budget (diagnosis tasks)
|
|
53
53
|
consultTimeoutMs: 600000, // wall-clock ceiling per consultant (10min)
|
|
54
54
|
streamRules: [], // time-traveling stream rules: [{ pattern: "regex", message: "reminder", action: "abort"|"warn", repeat: "always"|"once" }]
|
|
55
|
-
advisor: { guard: false }, // code review is always available; guard: true pushes completion back until reviewed (opt-in). Also accepts provider/model/thinking/reasoningEffort overrides. Deprecated: enabled (2026-08-21)
|
|
55
|
+
advisor: { guard: false }, // code review is always available; guard: true pushes completion back until reviewed (opt-in). Also accepts provider/model/thinking/reasoningEffort/timeoutMs overrides. Deprecated: enabled (2026-08-21)
|
|
56
56
|
autoThink: false, // auto-classify task difficulty and set reasoning effort per-turn
|
|
57
57
|
engineering: false, // strict methodology enforcement — read METHODOLOGY.md, design-before-code
|
|
58
58
|
},
|
package/src/context.mjs
CHANGED
|
@@ -424,8 +424,9 @@ async function distillExplorations(history, start, provider, signal) {
|
|
|
424
424
|
* End-of-run exploration distillation (runAgent's final return). Shrinks the MACHINE line
|
|
425
425
|
* (agent.history) only; agent._fullHistory is never touched. Triggers when this run added ≥3
|
|
426
426
|
* exploration tool results; on LLM failure it silently keeps the original history (N3).
|
|
427
|
-
*
|
|
428
|
-
*
|
|
427
|
+
* The distillation itself is silent and never streams (D11); `callbacks.onDistilled` fires
|
|
428
|
+
* ONLY after the replacement actually lands (never on no-op/failure) — callers persist the
|
|
429
|
+
* compressed session (SEND-STALL-DISTILL §2.3).
|
|
429
430
|
*/
|
|
430
431
|
export async function summarizeRunExplorations(agent, callbacks, signal) {
|
|
431
432
|
const next = await distillExplorations(agent.history, agent._runStartHistoryLen ?? 0, agent.provider, signal)
|
|
@@ -435,4 +436,7 @@ export async function summarizeRunExplorations(agent, callbacks, signal) {
|
|
|
435
436
|
// Invalidate so the next compaction check re-estimates instead of over-counting stale history.
|
|
436
437
|
agent._lastPromptTokens = null
|
|
437
438
|
agent._usageAtLen = null
|
|
439
|
+
// The compressed machine line must reach the disk: the run's own save already happened,
|
|
440
|
+
// so without this hook the async distill would leave the session un-compressed on exit.
|
|
441
|
+
callbacks.onDistilled?.()
|
|
438
442
|
}
|
|
@@ -25,7 +25,7 @@ Tool routing — use the dedicated tool, not bash:
|
|
|
25
25
|
- **JavaScript** → `execute` (inline code; or `scriptFile`+`nodeArgs` for `node <file>` / `node --test` / `node --check`). Never `bash node -e`.
|
|
26
26
|
- **File reads/searches** → `read` / `grep` / `ls` / `glob` — never `cat` / `type` / `findstr` / `dir` / shell-grep.
|
|
27
27
|
- **File mutations** → `write` / `edit` / `apply_patch` / `hashline_edit` / `insert_after` / `file_ops` (move/copy/rename) / `delete`.
|
|
28
|
-
- **Process / time /
|
|
28
|
+
- **Process / time / tree** → the dedicated tools (never `tasklist`/`ps`/`date`/`tree` via bash); waiting (e.g. `sleep`/`timeout`) is fine via bash when truly needed.
|
|
29
29
|
- Each tool's description carries a "Route to X instead of bash" mapping.
|
|
30
30
|
- **bash IS correct for**: package-manager/CLI subprocesses (`npm`/`vsce`/`ovsx`, git-CLI-only flags the tool lacks), servers, interactive/TTY programs, and one-off shell pipelines no dedicated tool expresses.
|
|
31
31
|
|
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
You are the ARCHITECT. In this mode your deliverables are:
|
|
6
6
|
1. the requirements + design documents (docs/),
|
|
7
|
-
2. the
|
|
8
|
-
|
|
7
|
+
2. the approved implementation plan handed to an eng-coder.
|
|
8
|
+
|
|
9
|
+
You PREPARE and REMIND — you never FIRE. The design review and the start of
|
|
10
|
+
implementation are both initiated by the user, not by you (2026-08-24
|
|
11
|
+
decision: an agent that judges "discussion is done" by itself and fires
|
|
12
|
+
review + development is not engineering mode).
|
|
9
13
|
|
|
10
14
|
You do NOT write implementation code yourself. Writing or editing code files
|
|
11
15
|
directly violates this workflow — implementation is done by `eng-coder`
|
|
@@ -32,30 +36,39 @@ subagents only.
|
|
|
32
36
|
2. **Design.** Write the design document in `docs/` (problem statement,
|
|
33
37
|
solution approach, full affected-file list, verifiable acceptance criteria).
|
|
34
38
|
Do NOT open any code file for editing before this document exists.
|
|
35
|
-
3. **
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
3. **Remind readiness — never self-initiate review.** Present the design
|
|
40
|
+
summary and say it is ready for review, then WAIT. You do NOT call the
|
|
41
|
+
advisor yourself — the initiation right belongs to the user: you prepare
|
|
42
|
+
and remind, the user fires.
|
|
43
|
+
4. **User-initiated design review.** Only when the user asks for it, call
|
|
44
|
+
`advisor` with `type="design"`, passing `documents=[...]` — the explicit
|
|
45
|
+
list of doc paths to review (requirements + design + referenced docs;
|
|
46
|
+
METHODOLOGY.md is read by the advisor itself). This runs a dedicated
|
|
47
|
+
design review in an isolated context.
|
|
48
|
+
- If advisor finds issues: present the findings AND your proposed fix for
|
|
49
|
+
each item, and let the user decide item by item — design questions are
|
|
50
|
+
decided WITH the user, not guessed by you (a fix without user input is
|
|
51
|
+
at best a formal patch). Amend per their call, then remind them it is
|
|
52
|
+
ready for re-review. Never fix-and-resubmit on your own.
|
|
40
53
|
- If advisor approves: it returns a design token in plain text in its response.
|
|
41
54
|
- If the advisor keeps rejecting after 3 rounds, STOP and report the open
|
|
42
55
|
issues to the user — do not loop silently.
|
|
43
|
-
|
|
56
|
+
5. **User sign-off.** Present the design summary AND the advisor's findings
|
|
44
57
|
(any remaining 🟡 advisories the user should know about) and WAIT for
|
|
45
58
|
explicit approval before any implementation step.
|
|
46
|
-
|
|
59
|
+
6. **Implement via eng-coder.** Spawn a subagent with `role="eng-coder"`,
|
|
47
60
|
providing the METHODOLOGY task structure: the **Docs involved** list (design
|
|
48
61
|
doc + requirements + referenced docs), the file list, the acceptance
|
|
49
62
|
criteria. Pass the designToken via the `designToken` PARAMETER — never in
|
|
50
63
|
the task text. The token is required — eng-coder cannot modify files
|
|
51
64
|
without it.
|
|
52
|
-
|
|
53
|
-
the acceptance criteria from the design (run the
|
|
54
|
-
the changed files)
|
|
55
|
-
advisor(code
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
7. **Delivery review — automatic flow node.** After eng-coder returns, verify
|
|
66
|
+
the delivery against the acceptance criteria from the design (run the
|
|
67
|
+
tests it claims pass, read the changed files) AND run the code review with
|
|
68
|
+
the `advisor` tool (`type="code"`, `documents=[...]` = the task's Docs
|
|
69
|
+
involved list). This review happens automatically — no user initiation
|
|
70
|
+
needed (2026-08-24 decision).
|
|
71
|
+
8. **Verify.** Run `verify` — it must pass before you claim the task complete.
|
|
59
72
|
|
|
60
73
|
## Work Loop (every user message)
|
|
61
74
|
|
|
@@ -66,10 +79,12 @@ passed?
|
|
|
66
79
|
| State | Default action |
|
|
67
80
|
|---|---|
|
|
68
81
|
| Requirements exploration | Clarify (who/what/why — never how), explore the current state, then write the REQUIREMENTS doc — three layers per METHODOLOGY: overall goal / functional user stories / non-functional standards (flow step 1) |
|
|
69
|
-
| Design | Write or refine the DESIGN doc (approach + rationale, architecture/interface, affected files, key decisions), organized by business domain per METHODOLOGY, ask for confirmation (flow steps 2
|
|
70
|
-
|
|
|
82
|
+
| Design | Write or refine the DESIGN doc (approach + rationale, architecture/interface, affected files, key decisions), organized by business domain per METHODOLOGY, ask for confirmation (flow steps 1-2) |
|
|
83
|
+
| Design ready | Present the design summary, say it is ready for review, WAIT — do NOT call advisor yourself; the user initiates the design review (flow steps 3-4) |
|
|
84
|
+
| Review fix loop | Present findings + proposed fixes, the user decides item by item, amend per their call, remind for re-review (flow step 4) |
|
|
85
|
+
| Awaiting approval | Present design summary + advisor findings, WAIT for explicit approval (flow step 5) |
|
|
71
86
|
| Implementation | eng-coder is working — do not redesign in parallel |
|
|
72
|
-
| Delivery review | Verify the delivery against the acceptance criteria
|
|
87
|
+
| Delivery review | Verify the delivery against the acceptance criteria AND run advisor (type="code", documents = Docs involved) — automatic flow node, no user initiation (flow step 7); report |
|
|
73
88
|
| Wrapped up | Report, wait for next instruction |
|
|
74
89
|
|
|
75
90
|
Then handle the message:
|
|
@@ -83,13 +98,16 @@ Then handle the message:
|
|
|
83
98
|
design doc path, file list, acceptance criteria; token via the `designToken`
|
|
84
99
|
parameter, never in the task text.
|
|
85
100
|
- **Question / discussion** → answer; write any decision to the relevant doc.
|
|
86
|
-
- **eng-coder delivery** → verify the acceptance criteria
|
|
87
|
-
|
|
101
|
+
- **eng-coder delivery** → verify the acceptance criteria AND run the advisor
|
|
102
|
+
code review (automatic flow node — never wait for the user to ask); report.
|
|
88
103
|
|
|
89
104
|
End every turn with three checks: ① decisions written to docs? ② current state
|
|
90
|
-
named and next step stated? ③ what the user must do (
|
|
91
|
-
|
|
92
|
-
|
|
105
|
+
named and next step stated? ③ what the user must do (initiate review / approve /
|
|
106
|
+
clarify / continue)?
|
|
107
|
+
No code edits outside approved minor fixes (post-delivery-review minor fixes
|
|
108
|
+
once the design is approved, typos in docs you own, etc. — anything larger
|
|
109
|
+
goes back to eng-coder). Design review ONLY when the user initiates it;
|
|
110
|
+
delivery code review is an automatic flow node.
|
|
93
111
|
|
|
94
112
|
## Questioning Style (requirement clarification)
|
|
95
113
|
|
|
@@ -118,10 +136,15 @@ cannot enumerate. When using the `question` tool:
|
|
|
118
136
|
constraint, or preference during design discussion or review, update the
|
|
119
137
|
relevant docs (design doc, METHODOLOGY.md, ENGINEERING-MODE.md) right away —
|
|
120
138
|
do not wait to be asked. A decision that isn't in a doc didn't land.
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
139
|
+
- Review initiation split: the DESIGN review is called ONLY when the user
|
|
140
|
+
explicitly asks (e.g. "评审吧") — remind them when the design is ready,
|
|
141
|
+
never fire it yourself; each round of findings goes back to the user for
|
|
142
|
+
item-by-item decisions, no self-fix-resubmit loops. The CODE review at
|
|
143
|
+
eng-coder delivery is an automatic flow node — run it without asking.
|
|
144
|
+
Both hold regardless of `/advisor` toggle state. Use `advisor`'s configured
|
|
145
|
+
model if set; otherwise the main model is used automatically. The key
|
|
146
|
+
property is independent context — every review runs in a fresh isolated
|
|
147
|
+
session.
|
|
125
148
|
- **Advisor response table.** After each advisor review you run, reply with a
|
|
126
149
|
response table — exact header `| # | Action | Detail |`, one row per issue;
|
|
127
150
|
`#` = the advisor's issue number (`Orig#` on rounds 2+).
|
|
@@ -135,9 +158,9 @@ cannot enumerate. When using the `question` tool:
|
|
|
135
158
|
- A 🔴 you neither fix nor surface blocks convergence. `Deferred` fits 🟡/🔵
|
|
136
159
|
improvements or a 🔴 needing a user decision first — never a way to silently
|
|
137
160
|
drop a real defect; surface any unresolved 🔴 to the user.
|
|
138
|
-
- **Review timing**:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
161
|
+
- **Review timing**: design review — ONLY user-initiated (you prepare and
|
|
162
|
+
remind, the user fires); each round of findings goes back to the user for
|
|
163
|
+
decisions. Delivery code review — automatic flow node after eng-coder
|
|
164
|
+
returns, run it without asking. Beyond these, do NOT call advisor
|
|
165
|
+
unprompted or repeatedly.
|
|
143
166
|
If advisor fails or is interrupted, stop retrying — report to the user.
|
package/src/tools/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { checklistTool } from "./checklist.mjs";
|
|
|
10
10
|
import { lintTool } from "./linter.mjs";
|
|
11
11
|
import { lspTool } from "./lsp.mjs";
|
|
12
12
|
import { executeTool } from "./execute.mjs";
|
|
13
|
-
import { fileOpsTool, processTool, getCurrentTimeTool
|
|
13
|
+
import { fileOpsTool, processTool, getCurrentTimeTool } from "./ops.mjs";
|
|
14
14
|
import { treeTool } from "./tree.mjs";
|
|
15
15
|
|
|
16
16
|
export const builtinTools = [
|
|
@@ -19,7 +19,7 @@ export const builtinTools = [
|
|
|
19
19
|
websearchTool, lsTool, fetchTool, deleteTool,
|
|
20
20
|
gitTool, questionTool,
|
|
21
21
|
checklistTool, lintTool, lspTool, executeTool,
|
|
22
|
-
fileOpsTool, processTool, getCurrentTimeTool,
|
|
22
|
+
fileOpsTool, processTool, getCurrentTimeTool,
|
|
23
23
|
treeTool,
|
|
24
24
|
];
|
|
25
25
|
|
|
@@ -29,6 +29,6 @@ export {
|
|
|
29
29
|
websearchTool, lsTool, fetchTool, deleteTool,
|
|
30
30
|
gitTool, questionTool,
|
|
31
31
|
checklistTool, lintTool, lspTool, executeTool,
|
|
32
|
-
fileOpsTool, processTool, getCurrentTimeTool,
|
|
32
|
+
fileOpsTool, processTool, getCurrentTimeTool,
|
|
33
33
|
treeTool,
|
|
34
34
|
};
|
package/src/tools/ops.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ops.mjs — operational tools: file_ops (move/copy/rename), process (list),
|
|
3
|
-
* get_current_time
|
|
3
|
+
* get_current_time. Each exists so the model reaches for a dedicated tool
|
|
4
4
|
* instead of shelling out to `bash` for the same operation (parity with thinworker).
|
|
5
5
|
*/
|
|
6
6
|
import { DESC, resolveInCwd, truncate } from "./shared.mjs"
|
|
@@ -111,32 +111,4 @@ export const getCurrentTimeTool = {
|
|
|
111
111
|
const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
|
|
112
112
|
return `Date: ${now.toISOString()} (UTC)\nTimezone: ${tz}\nWeekday: ${days[now.getDay()]}\nLocal: ${now.toLocaleString()}`
|
|
113
113
|
},
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ─── sleep ─────────────────────────────────────────────────────
|
|
117
|
-
|
|
118
|
-
export const sleepTool = {
|
|
119
|
-
name: "sleep",
|
|
120
|
-
description: DESC("sleep"),
|
|
121
|
-
parameters: {
|
|
122
|
-
type: "object",
|
|
123
|
-
properties: {
|
|
124
|
-
seconds: { type: "number", description: "Seconds to wait (1-300)" },
|
|
125
|
-
reason: { type: "string", description: "Why wait (shown to the user)" },
|
|
126
|
-
},
|
|
127
|
-
required: ["seconds"],
|
|
128
|
-
},
|
|
129
|
-
readonly: true,
|
|
130
|
-
async execute({ seconds, reason }, ctx) {
|
|
131
|
-
const raw = Number(seconds)
|
|
132
|
-
const n = Number.isFinite(raw) ? Math.min(Math.max(Math.round(raw), 1), 300) : 1
|
|
133
|
-
await new Promise((resolve, reject) => {
|
|
134
|
-
const t = setTimeout(resolve, n * 1000)
|
|
135
|
-
if (ctx?.signal) {
|
|
136
|
-
if (ctx.signal.aborted) { clearTimeout(t); reject(new Error("aborted")) }
|
|
137
|
-
else ctx.signal.addEventListener("abort", () => { clearTimeout(t); reject(new Error("aborted")) }, { once: true })
|
|
138
|
-
}
|
|
139
|
-
})
|
|
140
|
-
return `Waited ${n}s${reason ? ` (${reason})` : ""}`
|
|
141
|
-
},
|
|
142
114
|
}
|
package/src/tui/agent-turn.mjs
CHANGED
|
@@ -5,6 +5,11 @@ import { ansi, C } from "./ansi.mjs"
|
|
|
5
5
|
import { formatToolSummary } from "./tool-summaries.mjs"
|
|
6
6
|
import { ADVISOR_THINKING_PLACEHOLDER, resolveAdvisorProvider } from "../advisor/run.mjs"
|
|
7
7
|
|
|
8
|
+
/** Exit-flush bound for the async end-of-run distillation (SEND-STALL-DISTILL §2.5):
|
|
9
|
+
* wait at most this long for the in-flight distill before the final session save —
|
|
10
|
+
* never let shutdown hang on the background summary call. */
|
|
11
|
+
const DISTILL_FLUSH_TIMEOUT_MS = 5000
|
|
12
|
+
|
|
8
13
|
/** Tool execution start timestamps (performance.now ms), keyed by tool name. */
|
|
9
14
|
const _toolTicks = Object.create(null)
|
|
10
15
|
|
|
@@ -325,6 +330,12 @@ export async function runAgentTurn(ctx, text) {
|
|
|
325
330
|
onCompress: () => {
|
|
326
331
|
pushLine(" [context] Context too long, auto-compacted (early conversation summarized by LLM, task state preserved)", C.warn)
|
|
327
332
|
},
|
|
333
|
+
// Async distillation landed (SEND-STALL-DISTILL §2.3): the machine line was replaced by
|
|
334
|
+
// the compressed version — persist it so the session file ends up compressed. Silent:
|
|
335
|
+
// a save failure must never surface after the turn already returned.
|
|
336
|
+
onDistilled: () => {
|
|
337
|
+
try { saveSessionImpl(agent, state.lines) } catch { /* 静默 */ }
|
|
338
|
+
},
|
|
328
339
|
onUsage: (usage) => {
|
|
329
340
|
state.tokens.prompt += usage.prompt_tokens ?? 0
|
|
330
341
|
state.tokens.completion += usage.completion_tokens ?? 0
|
|
@@ -429,6 +440,10 @@ export async function runAgentTurn(ctx, text) {
|
|
|
429
440
|
state._advisorBlocks = []
|
|
430
441
|
state.controller = null
|
|
431
442
|
state.status = "Ready"
|
|
443
|
+
// FR1: status bar must recover immediately — the awaits below (title-gen, distill flush,
|
|
444
|
+
// save) may take seconds and the 1s ticker is already stopped, so render NOW or the bar
|
|
445
|
+
// keeps showing the stale "Processing..." until the turn function fully unwinds.
|
|
446
|
+
render()
|
|
432
447
|
// Auto-collapse todo panel when all tasks done (matching kimi-code TUI; agent.tasks are preserved)
|
|
433
448
|
if (state.tasks.length > 0 && state.tasks.every((t) => t.status === "done")) {
|
|
434
449
|
state.tasks = []
|
|
@@ -448,6 +463,17 @@ export async function runAgentTurn(ctx, text) {
|
|
|
448
463
|
// Title generation failure is non-fatal
|
|
449
464
|
}
|
|
450
465
|
}
|
|
466
|
+
// Exit flush (SEND-STALL-DISTILL §2.5): the round-end distillation runs async — before
|
|
467
|
+
// the final save, give it a bounded window to land the compressed history on disk.
|
|
468
|
+
// The next turn's runAgent would await it anyway; this covers the real exit path
|
|
469
|
+
// (no next turn). Bounded: never let shutdown wait longer than the timeout.
|
|
470
|
+
// NOTE: the promise is NOT detached (no `agent._pendingDistill = null` here) — a submit
|
|
471
|
+
// during this window starts the next runAgentTurn concurrently, and its runAgent start
|
|
472
|
+
// MUST still see the in-flight distill to await it BEFORE pushing input (N1). If the
|
|
473
|
+
// flush times out, the next runAgent's start-await takes over — safe by construction.
|
|
474
|
+
if (agent._pendingDistill) {
|
|
475
|
+
await Promise.race([agent._pendingDistill, new Promise((r) => setTimeout(r, ctx.distillFlushTimeoutMs ?? DISTILL_FLUSH_TIMEOUT_MS))])
|
|
476
|
+
}
|
|
451
477
|
// Save session after every turn (survives crashes)
|
|
452
478
|
try {
|
|
453
479
|
saveSessionImpl(agent, state.lines)
|
package/src/tui/cmd-config.mjs
CHANGED
|
@@ -234,8 +234,8 @@ export async function handleConfigCommand(ctx, args = []) {
|
|
|
234
234
|
while (running) {
|
|
235
235
|
const consultCount = (ac.consultModels ?? []).length
|
|
236
236
|
const mainEntries = [
|
|
237
|
-
{ type: "header", text: `proxy=${proxySummary()} | maxTurns=${ac.maxTurns ??
|
|
238
|
-
{ type: "item", text: `agent.maxTurns = ${ac.maxTurns ??
|
|
237
|
+
{ type: "header", text: `proxy=${proxySummary()} | maxTurns=${ac.maxTurns ?? 200} | compactThreshold=${ac.compactThreshold ?? 100000} | verifyGuard=${ac.verifyGuard === true ? "on" : "off"} | consult=${consultCount} model(s) | embedding=${agent.memory?.embedder ? "on" : "off"}` },
|
|
238
|
+
{ type: "item", text: `agent.maxTurns = ${ac.maxTurns ?? 200}`, action: "agent.maxTurns" },
|
|
239
239
|
{ type: "item", text: `agent.subagentTurns = ${ac.subagentTurns ?? 100}`, action: "agent.subagentTurns" },
|
|
240
240
|
{ type: "item", text: `agent.compactThreshold = ${ac.compactThreshold ?? 100000}${agent.config?.agent?.compactThresholdAuto ? " (auto)" : ""}`, action: "agent.compactThreshold" },
|
|
241
241
|
{ type: "item", text: `agent.verifyGuard = ${ac.verifyGuard === true ? "on" : "off"}`, action: "agent.verifyGuard" },
|
|
@@ -255,7 +255,7 @@ export async function handleConfigCommand(ctx, args = []) {
|
|
|
255
255
|
pushLabel("❯ Config", ansi.bold + C.tool)
|
|
256
256
|
pushLine(`Active: ${agent.activeProvider} / ${agent.provider.model}`, C.dim)
|
|
257
257
|
pushLine(`Key: ${maskKey(agent.provider.apiKey)}`, C.dim)
|
|
258
|
-
pushLine(`agent.maxTurns: ${ac.maxTurns ??
|
|
258
|
+
pushLine(`agent.maxTurns: ${ac.maxTurns ?? 200}`, C.dim)
|
|
259
259
|
pushLine(`agent.subagentTurns: ${ac.subagentTurns ?? 100}`, C.dim)
|
|
260
260
|
pushLine(`agent.compactThreshold: ${ac.compactThreshold ?? 100000}${agent.config?.agent?.compactThresholdAuto ? " (auto)" : ""}`, C.dim)
|
|
261
261
|
pushLine(`agent.verifyGuard: ${ac.verifyGuard === true ? "on" : "off"}`, C.dim)
|
|
@@ -304,7 +304,7 @@ export async function handleConfigCommand(ctx, args = []) {
|
|
|
304
304
|
// Numeric config items
|
|
305
305
|
const label = choice.action
|
|
306
306
|
const isTimeout = label === "agent.consultTimeoutMs"
|
|
307
|
-
const current = label === "agent.maxTurns" ? (ac.maxTurns ??
|
|
307
|
+
const current = label === "agent.maxTurns" ? (ac.maxTurns ?? 200)
|
|
308
308
|
: label === "agent.subagentTurns" ? (ac.subagentTurns ?? 100)
|
|
309
309
|
: label === "agent.compactThreshold" ? (ac.compactThreshold ?? 100000)
|
|
310
310
|
: label === "agent.consultTurns" ? (ac.consultTurns ?? 40)
|
package/src/tools/sleep.md
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
Wait a number of seconds before continuing. Use to wait for a web page to load, an async task to finish, or to respect a rate limit — cheaper than repeatedly polling.
|
|
2
|
-
|
|
3
|
-
Parameters:
|
|
4
|
-
- seconds (required): how many seconds to wait (1-300)
|
|
5
|
-
- reason (optional): why you are waiting (shown to the user)
|