thincoder 0.12.60 → 0.12.61
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 +24 -1
- package/README.md +8 -6
- package/bin/thincoder.mjs +27 -122
- package/package.json +3 -2
- package/src/abort-provenance.mjs +116 -0
- package/src/acp/bridge.mjs +38 -17
- package/src/acp.mjs +6 -1
- package/src/advisor/citations.mjs +83 -21
- package/src/advisor/compaction.mjs +174 -0
- package/src/advisor/loop.mjs +293 -0
- package/src/advisor/messages.mjs +36 -134
- package/src/advisor/project-context.mjs +194 -0
- package/src/advisor/repos.mjs +17 -40
- package/src/advisor/run.mjs +124 -329
- package/src/advisor/truncate.mjs +57 -0
- package/src/advisor.mjs +3 -2
- package/src/agent/completion.mjs +1 -1
- package/src/agent/dispatch.mjs +47 -12
- package/src/agent/helpers.mjs +71 -13
- package/src/agent/record-results.mjs +13 -5
- package/src/agent/relay-prefix.mjs +39 -0
- package/src/agent/run-stages.mjs +21 -6
- package/src/agent/setup-reminders.mjs +16 -9
- package/src/agent/setup.mjs +92 -128
- package/src/agent/spawn-child.mjs +26 -9
- package/src/agent-tools/advisor-async.mjs +70 -180
- package/src/agent-tools/advisor-settle.mjs +231 -0
- package/src/agent-tools/advisor.mjs +69 -20
- package/src/agent-tools/batch-segment.mjs +195 -0
- package/src/agent-tools/consult.mjs +23 -10
- package/src/agent-tools/design-token.mjs +14 -1
- package/src/agent-tools/digest-budget.mjs +76 -0
- package/src/agent-tools/eng.mjs +3 -3
- package/src/agent-tools/escalate-async.mjs +16 -13
- package/src/agent-tools/read-history.mjs +13 -3
- package/src/agent-tools/review-streak.mjs +93 -0
- package/src/agent-tools/settings.mjs +130 -17
- package/src/agent-tools/subagent-actions.mjs +15 -6
- package/src/agent-tools/subagent-async.mjs +66 -14
- package/src/agent-tools/subagent-panel.mjs +22 -15
- package/src/agent-tools/subagent-run.mjs +9 -6
- package/src/agent-tools/subagent-scheduler.mjs +57 -8
- package/src/agent-tools/subagent-spawn.mjs +63 -16
- package/src/agent-tools/subagent.mjs +175 -49
- package/src/agent-tools/verify.mjs +13 -34
- package/src/agent-tools.mjs +1 -0
- package/src/agent.mjs +38 -21
- package/src/cli/distill-command.mjs +2 -2
- package/src/cli/make-agent.mjs +23 -7
- package/src/cli/memory-command.mjs +2 -2
- package/src/cli/setup-wizard.mjs +29 -9
- package/src/completions.mjs +114 -0
- package/src/config-migrate.mjs +70 -0
- package/src/config.mjs +132 -63
- package/src/conventions.mjs +223 -0
- package/src/crash-reports.mjs +7 -2
- package/src/expand-home.mjs +16 -0
- package/src/generate-title.mjs +1 -1
- package/src/hooks.mjs +7 -3
- package/src/memory/code-index.mjs +9 -3
- package/src/memory/code-sync.mjs +70 -31
- package/src/memory/delete.mjs +2 -0
- package/src/memory/docs.mjs +10 -6
- package/src/memory/file-walk.mjs +109 -0
- package/src/memory/schema.mjs +15 -3
- package/src/model-ref.mjs +66 -0
- package/src/model-specs.mjs +42 -8
- package/src/prompt-overlays.mjs +73 -16
- package/src/prompts/advisor-design.md +18 -8
- package/src/prompts/advisor-round1.md +7 -1
- package/src/prompts/advisor-round2.md +13 -2
- package/src/prompts/advisor-round3.md +13 -2
- package/src/prompts/common.md +115 -0
- package/src/prompts/consult-base.md +2 -0
- package/src/prompts/discipline-engineering.md +217 -0
- package/src/prompts/discipline-normal.md +179 -0
- package/src/prompts/persona-coder.md +21 -0
- package/src/prompts/persona-eng-coder.md +37 -0
- package/src/prompts/persona-eng-designer.md +55 -0
- package/src/prompts/persona-engineering.md +54 -0
- package/src/prompts/persona-explore.md +15 -0
- package/src/prompts/persona-normal.md +27 -0
- package/src/prompts/persona-plan.md +26 -0
- package/src/provider/anthropic.mjs +4 -4
- package/src/provider/core.mjs +13 -32
- package/src/provider/errors.mjs +26 -1
- package/src/provider/google.mjs +5 -6
- package/src/provider/index.mjs +2 -1
- package/src/provider/list-models.mjs +93 -0
- package/src/provider/rate.mjs +2 -1
- package/src/provider/responses.mjs +5 -3
- package/src/provider/sse.mjs +3 -4
- package/src/proxy.mjs +9 -14
- package/src/session-slots.mjs +7 -2
- package/src/session.mjs +59 -38
- package/src/token-ttl.mjs +2 -1
- package/src/tools/{system.mjs → bash.mjs} +6 -243
- package/src/tools/file.mjs +30 -10
- package/src/tools/git.md +1 -1
- package/src/tools/git.mjs +15 -34
- package/src/tools/index.mjs +4 -2
- package/src/tools/ops.mjs +20 -7
- package/src/tools/question.md +1 -0
- package/src/tools/question.mjs +26 -0
- package/src/tools/read.md +1 -1
- package/src/tools/read_image.md +1 -1
- package/src/tools/search.mjs +236 -0
- package/src/tui/agent-turn.mjs +32 -13
- package/src/tui/ansi.mjs +2 -0
- package/src/tui/clipboard.mjs +7 -1
- package/src/tui/cmd-advisor.mjs +3 -2
- package/src/tui/cmd-config.mjs +108 -37
- package/src/tui/cmd-eng.mjs +11 -27
- package/src/tui/cmd-exit.mjs +6 -8
- package/src/tui/cmd-model.mjs +14 -12
- package/src/tui/cmd-reindex.mjs +7 -0
- package/src/tui/cmd-submodel.mjs +8 -5
- package/src/tui/cmd-undo.mjs +4 -3
- package/src/tui/index.mjs +38 -37
- package/src/tui/key-handler.mjs +61 -17
- package/src/tui/key-modes.mjs +86 -8
- package/src/tui/layout.mjs +18 -10
- package/src/tui/model-catalog.mjs +89 -0
- package/src/tui/model-picker.mjs +498 -0
- package/src/tui/mouse.mjs +47 -10
- package/src/tui/pickers.mjs +28 -410
- package/src/tui/render-frame.mjs +32 -16
- package/src/tui/render-loop.mjs +2 -0
- package/src/tui/render-segments.mjs +5 -19
- package/src/tui/render.mjs +37 -5
- package/src/tui/slash-commands.mjs +2 -2
- package/src/tui/subagent-blocks.mjs +69 -90
- package/src/tui/subagent-children.mjs +50 -64
- package/src/tui/subagent-freeze.mjs +40 -43
- package/src/tui/subagent-panel.mjs +21 -66
- package/src/tui/suspension-drive.mjs +28 -82
- package/src/tui/tool-args.mjs +4 -2
- package/src/tui/tool-display.mjs +4 -3
- package/src/tui/tool-events.mjs +23 -16
- package/src/tui/tui-lifecycle.mjs +18 -5
- package/src/tui/wizard.mjs +52 -18
- package/src/tui/wrapped-spawn.mjs +38 -0
- package/src/prompts/coder.md +0 -13
- package/src/prompts/discipline.md +0 -84
- package/src/prompts/eng-coder.md +0 -19
- package/src/prompts/engineering-sub.md +0 -14
- package/src/prompts/engineering.md +0 -87
- package/src/prompts/explore.md +0 -12
- package/src/prompts/main.md +0 -34
- package/src/prompts/methodology-template.md +0 -38
- package/src/prompts/plan.md +0 -9
- package/src/prompts/system.md +0 -44
package/src/advisor/run.mjs
CHANGED
|
@@ -1,351 +1,95 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* advisor/run.mjs — advisor execution:
|
|
3
|
-
* Message building lives in advisor.mjs.
|
|
2
|
+
* advisor/run.mjs — advisor execution: provider resolution and the review entry
|
|
3
|
+
* point. Message building lives in advisor.mjs; the tool loop lives in loop.mjs
|
|
4
|
+
* and the context/limit/tail guards in compaction.mjs(第 11 批硬帽拆分——
|
|
5
|
+
* run.mjs 原 498/500;既有 import 面经 re-export 保持不变)。
|
|
4
6
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import { findProvider, providerSpec } from "../config.mjs"
|
|
7
|
-
import { toOpenAISchema } from "../tools/index.mjs"
|
|
7
|
+
import { findProvider } from "../config.mjs"
|
|
8
8
|
import { prepareAdvisorMessages } from "../advisor.mjs"
|
|
9
|
+
import { buildObjectDeclarationBlock, buildDesignApprovalBlock } from "./messages.mjs"
|
|
9
10
|
import { appendCitationReport } from "./citations.mjs"
|
|
10
|
-
import {
|
|
11
|
+
import { runAdvisorToolLoop } from "./loop.mjs"
|
|
12
|
+
import { advisorIncompleteMarker, estimateTokens } from "./compaction.mjs"
|
|
13
|
+
import { batchDocForReview } from "../agent-tools/batch-segment.mjs"
|
|
14
|
+
// 第 33 批(§17.5 模块图):护栏常量 / doc-set 键 / 记录读取——单向导入(review-streak 无回指)。
|
|
15
|
+
import {
|
|
16
|
+
MAX_DESIGN_REVIEW_STREAK, docSetKey, designReviewStreakRecord, designReviewStreakStopped,
|
|
17
|
+
} from "../agent-tools/review-streak.mjs"
|
|
18
|
+
|
|
19
|
+
// 拆分后 import 面(既有导出名逐一保面——re-export;谓词为本批新增)。
|
|
20
|
+
export { ADVISOR_THINKING_PLACEHOLDER, MAX_RESULT_CHARS, renderTimeline as _renderTimeline } from "./compaction.mjs"
|
|
21
|
+
export { advisorToolsFor, advisorToolsFor as _advisorToolsFor } from "./loop.mjs"
|
|
22
|
+
export { runAdvisorToolLoop as _runAdvisorToolLoop } from "./loop.mjs"
|
|
23
|
+
export { advisorIncompleteMarker } from "./compaction.mjs"
|
|
11
24
|
|
|
12
|
-
const MAX_ADVISOR_TURNS = 100
|
|
13
25
|
// Mechanical convergence cap: up to 5 rounds suffice; a 6th call means the model
|
|
14
|
-
// is looping — refuse it instead of burning tokens. §
|
|
26
|
+
// is looping — refuse it instead of burning tokens. §11.2 D-24b (2026-09-06): PER
|
|
15
27
|
// REVIEW INSTANCE (agent._advisorRuns); CODE REVIEWS ONLY (2026-09-07 §8 ruling)
|
|
16
28
|
// — design reviews are EXEMPT: their rounds keep advancing, the cap never refuses.
|
|
17
29
|
export const MAX_ADVISOR_ROUNDS = 5
|
|
18
30
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
function estimateTokens(messages) {
|
|
39
|
-
return messages.reduce((sum, msg) => {
|
|
40
|
-
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content || "")
|
|
41
|
-
const toolCalls = msg.tool_calls ? JSON.stringify(msg.tool_calls) : ""
|
|
42
|
-
return sum + Math.ceil((content.length + toolCalls.length) / 4)
|
|
43
|
-
}, 0)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Compact early messages when context grows too large — LOCAL trimming only
|
|
47
|
-
* (no LLM summarization). MUTATES in place (splice) so the caller's array
|
|
48
|
-
* reference stays valid — a reassignment would leave the caller's logging
|
|
49
|
-
* (tool-call count, token estimate) reading a stale array. */
|
|
50
|
-
function compactMessages(messages) {
|
|
51
|
-
// Keep: system prompt, last 20 messages (≈ 10 assistant+tool exchanges),
|
|
52
|
-
// user message — the rest is summarized.
|
|
53
|
-
if (messages.length <= 20) return
|
|
54
|
-
|
|
55
|
-
const system = messages[0]
|
|
56
|
-
const recent = messages.slice(-20)
|
|
57
|
-
const old = messages.slice(1, -20)
|
|
58
|
-
|
|
59
|
-
// Count actual tool messages (old.length counts user/assistant rows too)
|
|
60
|
-
const toolCount = old.filter((m) => m.role === "tool").length
|
|
61
|
-
const keyFiles = old
|
|
62
|
-
.filter((m) => m.role === "tool")
|
|
63
|
-
.map((m) => m.content?.split("\n")[0]?.slice(0, 50)) // first line of tool results typically names the file that was read/grepped
|
|
64
|
-
.filter(Boolean)
|
|
65
|
-
.slice(0, MAX_KEY_FILES_IN_COMPACTION)
|
|
66
|
-
const filesPart = keyFiles.length > 0 ? ` Key files examined: ${keyFiles.join(", ")}` : ""
|
|
67
|
-
const summary = `Earlier exploration: ${toolCount} tool calls completed.${filesPart}`
|
|
68
|
-
|
|
69
|
-
messages.splice(0, messages.length,
|
|
70
|
-
system,
|
|
71
|
-
{ role: "user", content: `[Context compacted] ${summary}` },
|
|
72
|
-
...recent)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const { readTool, globTool, grepTool, lsTool } = await import("../tools/index.mjs")
|
|
76
|
-
const { lspTool } = await import("../tools/lsp.mjs")
|
|
77
|
-
const { codeSearchTool } = await import("../memory/code-sync.mjs")
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Advisor tool set — ZERO git, read-only ONLY, every round. The change surface
|
|
81
|
-
* comes from the review scope (paths / _touchedFiles injected by the caller),
|
|
82
|
-
* never from git: git output misled reviews (committed fixes never show in
|
|
83
|
-
* `git diff HEAD`, so "no changes" was read as "not fixed") and the user
|
|
84
|
-
* mandate is full decoupling (7d49a52 + d3be613). The reviewer reads files
|
|
85
|
-
* and searches code; it never touches git and never writes.
|
|
86
|
-
* No round parameter — the set is constant across all rounds.
|
|
87
|
-
* @param {Object} agent — only used for the code index (agent.memory); the
|
|
88
|
-
* semantic code_search tool needs it. Without a memory, the set is 5 tools.
|
|
89
|
-
*/
|
|
90
|
-
function advisorToolsFor(agent) {
|
|
91
|
-
const search = agent?.memory ? codeSearchTool(agent.memory) : null
|
|
92
|
-
const tools = search
|
|
93
|
-
? [readTool, globTool, grepTool, lsTool, lspTool, search]
|
|
94
|
-
: [readTool, globTool, grepTool, lsTool, lspTool]
|
|
95
|
-
return { schemas: tools.map(toOpenAISchema), byName: new Map(tools.map((t) => [t.name, t])) }
|
|
31
|
+
/** B 启动拒绝前缀(§14.4 #2)——稳定契约单源(三消费点同串):run.mjs 生成;同步工具面
|
|
32
|
+
* 据此登记 `_advisorRefusals`;异步结算面据此不置 `_calledAdvisorThisRun`。 */
|
|
33
|
+
export const ADVISOR_LAUNCH_REFUSAL_PREFIX = "Advisor: design review launch refused"
|
|
34
|
+
|
|
35
|
+
/** 评审失败护栏稳定前缀(第 33 批 §17.4——与 `ADVISOR_LAUNCH_REFUSAL_PREFIX` 同族;
|
|
36
|
+
* 实现 grep / 用例断言锚)。凭证卫生:串内零 token / designId 值。 */
|
|
37
|
+
export const ADVISOR_DESIGN_STREAK_STOP_PREFIX = "Advisor: design review stopped"
|
|
38
|
+
|
|
39
|
+
/** kind → 人读说明(第 33 批 §17.4 逐字——结论表第三列;八类 = 五 kind + stale +
|
|
40
|
+
* no_credential + no_report,与 review-streak.mjs 分类输出同集)。 */
|
|
41
|
+
const DESIGN_REVIEW_OUTCOME_MEANINGS = {
|
|
42
|
+
timeout: "review exceeded the wall-clock budget (agent.advisor.timeoutMs)",
|
|
43
|
+
context_limit: "review exceeded the model context budget",
|
|
44
|
+
turn_cap: "review exceeded the tool-round limit",
|
|
45
|
+
empty: "the provider returned an empty response",
|
|
46
|
+
review_failed: "provider / transport error",
|
|
47
|
+
stale: "the reviewed documents changed while the review was in flight",
|
|
48
|
+
no_credential: "the token could not be written to the session ledger",
|
|
49
|
+
no_report: "the review settled without a report",
|
|
96
50
|
}
|
|
97
|
-
// Test seam: the tool set is pure (agent.memory → code_search inclusion).
|
|
98
|
-
export { advisorToolsFor as _advisorToolsFor }
|
|
99
51
|
|
|
100
52
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Render the ordered review timeline — thinking / tool progress / final text
|
|
108
|
-
* interleaved EXACTLY as emitted, so the persisted record shows the review
|
|
109
|
-
* process at its real positions. A summary appended at the end would lose the
|
|
110
|
-
* order (the user-visible "no tool calls in the advisor record" gap). The
|
|
111
|
-
* live "[thinking…]" placeholder is stripped (wait indicator, not content).
|
|
53
|
+
* 停止结论串(第 33 批 §17.4 逐字;F29):稳定前缀 + 停止的 doc-set 清单 + 失败尝试表
|
|
54
|
+
* (记录逐条——kind + 人读说明)+ 三选项(接受现状 / 改变或缩小范围后重跑 / /new 重置)。
|
|
55
|
+
* 全文零凭证值;不自动执行任何恢复动作(发起权在父代理 / 用户)。
|
|
56
|
+
* @param {{count: number, log: string[]}|null} record — 护栏记录(`designReviewStreakRecord`)
|
|
57
|
+
* @param {string[]|null} documents — 被停的文档集(结论清单数据源)
|
|
112
58
|
*/
|
|
113
|
-
function
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
59
|
+
export function buildDesignReviewGuardMessage(record, documents) {
|
|
60
|
+
const log = Array.isArray(record?.log) ? record.log : []
|
|
61
|
+
const docList = Array.isArray(documents) ? documents.filter((d) => typeof d === "string" && d.trim()) : []
|
|
62
|
+
return [
|
|
63
|
+
`${ADVISOR_DESIGN_STREAK_STOP_PREFIX} — ${MAX_DESIGN_REVIEW_STREAK} consecutive attempts on this document set produced no design token (repeated failed settlements; no further reviews will start for this set in this session).`,
|
|
64
|
+
"Document set (1 design instance — no token issued):",
|
|
65
|
+
...docList.map((d) => `- ${d}`),
|
|
66
|
+
"Attempts (most recent last):",
|
|
67
|
+
"| # | outcome | meaning |",
|
|
68
|
+
"|---|---|---|",
|
|
69
|
+
...log.map((kind, i) => `| ${i + 1} | ${kind} | ${DESIGN_REVIEW_OUTCOME_MEANINGS[kind] ?? kind} |`),
|
|
70
|
+
"Options:",
|
|
71
|
+
"1. Accept the current state and proceed — implementation for this document set stays gated (no design token).",
|
|
72
|
+
"2. Narrow or change the scope: a different document set starts a fresh budget — fix the cause first (agent.advisor.timeoutMs / advisor model / provider).",
|
|
73
|
+
"3. Start a new session (/new) to reset the guard.",
|
|
74
|
+
].join("\n")
|
|
119
75
|
}
|
|
120
|
-
// Test seam (mirrors _advisorToolsFor).
|
|
121
|
-
export { renderTimeline as _renderTimeline }
|
|
122
|
-
// Test seam (T-TS8/9): the tool loop itself — toolsOverride injects a mock tool
|
|
123
|
-
// set with controllable timing/errors (the real set comes from advisorToolsFor).
|
|
124
|
-
export { runAdvisorToolLoop as _runAdvisorToolLoop }
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Run the advisor's tool loop: chat → execute tools → repeat.
|
|
128
|
-
* Stops when the model produces text without tool calls.
|
|
129
|
-
*
|
|
130
|
-
* Progress lines (→ tool args) are emitted via onOutput between model bursts so
|
|
131
|
-
* the panel keeps moving while the advisor explores — otherwise the panel sits
|
|
132
|
-
* frozen through every tool-call phase and the review appears to have stalled.
|
|
133
|
-
*/
|
|
134
|
-
async function runAdvisorToolLoop(provider, messages, onOutput, signal, agent, cwd, toolsOverride = null) {
|
|
135
|
-
// Kind-tagged wrappers: the TUI panel colors reasoning / answer / tool progress differently.
|
|
136
|
-
// Every chunk is ALSO recorded into an ordered timeline — the persisted record
|
|
137
|
-
// must show the review process (thinking ↔ tool progress ↔ final text) at its
|
|
138
|
-
// real positions, not a summary appended at the end. Same-kind consecutive
|
|
139
|
-
// chunks merge (token streams); kind flips start a new entry.
|
|
140
|
-
const timeline = []
|
|
141
|
-
const record = (kind, text) => {
|
|
142
|
-
const last = timeline.at(-1)
|
|
143
|
-
if (last && last.kind === kind) last.text += text
|
|
144
|
-
else timeline.push({ kind, text })
|
|
145
|
-
}
|
|
146
|
-
const emit = (kind) => (text) => { record(kind, text); onOutput?.({ kind, text }) }
|
|
147
|
-
const onThink = emit("think")
|
|
148
|
-
const onText = emit("text")
|
|
149
|
-
const onTool = emit("tool")
|
|
150
|
-
// toolsOverride = test seam (T-TS8/9): the real advisor tool set, or a mock
|
|
151
|
-
// set with controllable timing/errors.
|
|
152
|
-
const { schemas: toolSchemas, byName: toolByName } = toolsOverride ?? advisorToolsFor(agent)
|
|
153
|
-
let turns = 0
|
|
154
|
-
const startTime = Date.now()
|
|
155
|
-
|
|
156
|
-
while (true) {
|
|
157
|
-
// Interrupted (Ctrl+I) — stop immediately instead of spinning a fresh uncancellable signal
|
|
158
|
-
if (signal?.aborted) return renderTimeline(timeline, "Advisor: interrupted.")
|
|
159
|
-
|
|
160
|
-
// Check review timeout (10 minutes by default; agent.advisor.timeoutMs overrides)
|
|
161
|
-
// 运行时校验(设计评审 #1,2026-08-24):手写 config.json 的非法值(0/负数/字符串)
|
|
162
|
-
// 不得静默禁用或立即触发超时——非法一律回退默认。
|
|
163
|
-
const cfg = agent.config?.advisor?.timeoutMs
|
|
164
|
-
const timeoutMs = (Number.isFinite(cfg) && cfg > 0) ? cfg : REVIEW_TIMEOUT_MS
|
|
165
|
-
if (Date.now() - startTime > timeoutMs) {
|
|
166
|
-
return renderTimeline(timeline, `Advisor: review timeout after ${Math.round(timeoutMs / 1000)}s. Partial results may be available. Try again with a narrower scope.`)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (++turns > MAX_ADVISOR_TURNS) {
|
|
170
|
-
return renderTimeline(timeline, "Advisor: stopped after " + MAX_ADVISOR_TURNS + " tool rounds — the review appears to be looping. You may retry with a narrower scope.")
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Check context window and compact if needed
|
|
174
|
-
const currentTokens = estimateTokens(messages)
|
|
175
|
-
if (currentTokens > MAX_CONTEXT_TOKENS * 0.8) {
|
|
176
|
-
onText(`\n[Context compacted: ${currentTokens} tokens → reducing to fit window]\n`)
|
|
177
|
-
compactMessages(messages)
|
|
178
|
-
if (estimateTokens(messages) > MAX_CONTEXT_TOKENS) {
|
|
179
|
-
// Report the POST-compaction count — the pre-compaction currentTokens
|
|
180
|
-
// is stale by the time compaction has run.
|
|
181
|
-
return renderTimeline(timeline, `Advisor: context window limit reached (${estimateTokens(messages)} tokens). Review incomplete — too many tool calls. Try a narrower scope.`)
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// LLM generation silence: the reasoning phase produces no SSE bytes for
|
|
186
|
-
// seconds to tens of seconds (server-side prefill on large contexts, per
|
|
187
|
-
// tool-round LLM return). A placeholder keeps the panel visibly working.
|
|
188
|
-
// kind "think" (NOT "text"): the placeholder must land in the SAME buffer
|
|
189
|
-
// and position as the upcoming reasoning — a "text"-kind placeholder
|
|
190
|
-
// rendered BELOW the think block, and the reasoning stream appeared ABOVE
|
|
191
|
-
// it ("the stream runs back to the front"). Same buffer = same spot; the
|
|
192
|
-
// reasoning continues right where the placeholder sits.
|
|
193
|
-
onOutput?.({ kind: "think", text: ADVISOR_THINKING_PLACEHOLDER })
|
|
194
76
|
|
|
195
|
-
|
|
196
|
-
messages,
|
|
197
|
-
tools: toolSchemas,
|
|
198
|
-
// Pass the signal UNCONDITIONALLY: a signal aborted between the check
|
|
199
|
-
// above and here must still cancel the fetch. core.mjs composes
|
|
200
|
-
// AbortSignal.any([signal, timeout]) — an already-aborted signal makes
|
|
201
|
-
// the request fail immediately instead of ignoring the interrupt.
|
|
202
|
-
signal: signal ?? null,
|
|
203
|
-
onToken: onText,
|
|
204
|
-
onReasoning: onThink,
|
|
205
|
-
// LOGGING(vscode advisor/run.mjs parity——按 stage 可 grep)
|
|
206
|
-
// §18.6 D-TR4:轨迹元数据增补——kind=advisor(评审独立于子代理——T-TR2);role
|
|
207
|
-
// 透出调用方角色(eng-coder 内嵌评审时为 "eng-coder");session/cwd 供轨迹对回;
|
|
208
|
-
// traces 开关沿 agent.config(D-TR6)。
|
|
209
|
-
logCtx: {
|
|
210
|
-
stage: "advisor",
|
|
211
|
-
role: agent?._role ?? null,
|
|
212
|
-
kind: "advisor",
|
|
213
|
-
session: agent?._sessionStart ?? null,
|
|
214
|
-
cwd,
|
|
215
|
-
traces: agent?.config?.traces?.enabled !== false,
|
|
216
|
-
},
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
// No tool calls — this is the final review text. The final answer was
|
|
220
|
-
// already streamed into the timeline via onText; fall back to
|
|
221
|
-
// response.content only if nothing was recorded.
|
|
222
|
-
if (!response.toolCalls?.length) {
|
|
223
|
-
if (!response.content?.trim()) return renderTimeline(timeline) || "Advisor: empty response — review was inconclusive"
|
|
224
|
-
return renderTimeline(timeline) || response.content.trim()
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Push assistant message with tool calls. reasoning_content ECHO is
|
|
228
|
-
// mandatory for reasoningEcho:"required" providers (deepseek/kimi): the
|
|
229
|
-
// server stops returning reasoning_content on later rounds when the
|
|
230
|
-
// tool-call assistant history lacks it — the observed "reasoning stops
|
|
231
|
-
// after the first tool call, returns only at the final answer" symptom.
|
|
232
|
-
// Mirrors the main agent's push (agent.mjs).
|
|
233
|
-
messages.push({
|
|
234
|
-
role: "assistant",
|
|
235
|
-
content: response.content || null,
|
|
236
|
-
tool_calls: response.toolCalls.map((tc) => ({
|
|
237
|
-
id: tc.id, type: "function",
|
|
238
|
-
function: { name: tc.name, arguments: tc.arguments },
|
|
239
|
-
})),
|
|
240
|
-
...(response.reasoning && providerSpec(provider).reasoningEcho === "required"
|
|
241
|
-
? { reasoning_content: response.reasoning }
|
|
242
|
-
: {}),
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
// B1 (AGENT-LOOP.md §18.7 D-TS7): the SAME LLM reply's multiple read-only
|
|
246
|
-
// tool calls run in PARALLEL (Promise.all) — results are backfilled in
|
|
247
|
-
// toolCalls order (Promise.all preserves the input order → tool_call_id
|
|
248
|
-
// never mismatches); each tool's timeout/error is captured independently
|
|
249
|
-
// (the existing TOOL_TIMEOUT stays — one failing tool does not block the
|
|
250
|
-
// others); progress lines are emitted in toolCalls order. The read-only
|
|
251
|
-
// tool set has no side effects — no sequencing/serialization needed.
|
|
252
|
-
// Scope note (round1 review #10): B1 is ONLY in-loop tool parallelism — it
|
|
253
|
-
// does NOT solve the TODO "platform execution: advisor parallel calls are
|
|
254
|
-
// actually serial" mystery (docs/TODO.md — LOGGING evidence item), which
|
|
255
|
-
// concerns multiple advisor CALLS observed as serial, not one reply's
|
|
256
|
-
// tool calls.
|
|
257
|
-
const parsed = response.toolCalls.map((tc) => {
|
|
258
|
-
const tool = toolByName.get(tc.name)
|
|
259
|
-
let args = {}
|
|
260
|
-
let parseError = null
|
|
261
|
-
try {
|
|
262
|
-
args = JSON.parse(tc.arguments || "{}")
|
|
263
|
-
} catch (e) {
|
|
264
|
-
parseError = `Error: invalid JSON in tool arguments: ${e.message}\nRaw arguments: ${(tc.arguments || "").slice(0, 200)}`
|
|
265
|
-
}
|
|
266
|
-
return { tc, tool, args, parseError }
|
|
267
|
-
})
|
|
268
|
-
// Progress lines first, in toolCalls order (emitted before the parallel
|
|
269
|
-
// run — display order is independent of completion order).
|
|
270
|
-
for (const p of parsed) {
|
|
271
|
-
if (p.parseError) continue // parse-error tools get no progress line (legacy behavior)
|
|
272
|
-
const argsLine = describeToolArgs(p.tc.name, p.args)
|
|
273
|
-
onTool(`\n→ ${p.tc.name}${argsLine ? " " + argsLine : ""}\n`)
|
|
274
|
-
}
|
|
275
|
-
// Every tool runs CONCURRENTLY; each result/error lands in its own slot —
|
|
276
|
-
// Promise.all preserves input order, so index i always matches parsed[i].
|
|
277
|
-
const executed = await Promise.all(parsed.map(async (p) => {
|
|
278
|
-
// Parse failure → error to model immediately (no execution)
|
|
279
|
-
if (p.parseError) return p.parseError
|
|
280
|
-
if (!p.tool) return `Error: unknown tool "${p.tc.name}". Available: ${[...toolByName.keys()].join(", ")}`
|
|
281
|
-
// Execute with timeout (clear the timer when the tool wins the race —
|
|
282
|
-
// otherwise up to MAX_ADVISOR_TURNS dangling timers accumulate)
|
|
283
|
-
try {
|
|
284
|
-
let timeoutId
|
|
285
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
286
|
-
timeoutId = setTimeout(() => reject(new Error(`tool timeout after ${TOOL_TIMEOUT_MS}ms`)), TOOL_TIMEOUT_MS)
|
|
287
|
-
})
|
|
288
|
-
let toolPromise
|
|
289
|
-
try {
|
|
290
|
-
toolPromise = p.tool.execute(p.args, { cwd, agent, onOutput, signal })
|
|
291
|
-
return await Promise.race([toolPromise, timeoutPromise])
|
|
292
|
-
} finally {
|
|
293
|
-
clearTimeout(timeoutId)
|
|
294
|
-
// Timeout won → toolPromise is still pending; a later rejection
|
|
295
|
-
// would surface as an unhandled rejection. The race already
|
|
296
|
-
// consumed the result/error in the normal path, so this no-op
|
|
297
|
-
// catch only fires for the abandoned-tool case.
|
|
298
|
-
toolPromise?.catch(() => {})
|
|
299
|
-
}
|
|
300
|
-
} catch (e) {
|
|
301
|
-
const errorType = e.message.includes("timeout") ? "timeout"
|
|
302
|
-
: e.message.includes("ENOENT") ? "file_not_found"
|
|
303
|
-
: e.message.includes("permission") ? "permission_denied"
|
|
304
|
-
: "execution_error"
|
|
305
|
-
return `Error (${errorType}): ${e.message}`
|
|
306
|
-
}
|
|
307
|
-
}))
|
|
308
|
-
|
|
309
|
-
// Backfill in toolCalls order (executed[i] ↔ parsed[i]); per-result
|
|
310
|
-
// non-string serialization + line-aware truncation stay per-tool.
|
|
311
|
-
for (let i = 0; i < parsed.length; i++) {
|
|
312
|
-
let result = executed[i]
|
|
313
|
-
if (typeof result !== "string") result = JSON.stringify(result)
|
|
314
|
-
|
|
315
|
-
if (result.length > MAX_RESULT_CHARS) {
|
|
316
|
-
const lines = result.split("\n")
|
|
317
|
-
let truncated = ""
|
|
318
|
-
let charCount = 0
|
|
319
|
-
let keptLines = 0
|
|
320
|
-
|
|
321
|
-
for (let j = 0; j < lines.length; j++) {
|
|
322
|
-
const line = lines[j]
|
|
323
|
-
if (charCount + line.length + 1 > MAX_RESULT_CHARS) break
|
|
324
|
-
truncated += line + "\n"
|
|
325
|
-
charCount += line.length + 1
|
|
326
|
-
keptLines++
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const remainingLines = lines.length - keptLines
|
|
330
|
-
result = (
|
|
331
|
-
truncated +
|
|
332
|
-
`\n… (truncated: ${remainingLines} more lines, ${result.length} chars total)\n` +
|
|
333
|
-
`To see more content, use: read(path, offset=${keptLines + 1}, limit=200)`
|
|
334
|
-
)
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
messages.push({ role: "tool", tool_call_id: parsed[i].tc.id, content: result })
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
77
|
+
const MAX_UNFIXED_DISPLAY = 10 // unfixed issues shown in the cap message
|
|
341
78
|
|
|
342
79
|
/** Resolve the advisor's provider: cfg.provider/model when set, otherwise the main agent's provider */
|
|
343
80
|
export function resolveAdvisorProvider(agent) {
|
|
344
81
|
const cfg = agent.config?.advisor
|
|
345
82
|
if (cfg?.provider) {
|
|
346
83
|
try {
|
|
347
|
-
|
|
348
|
-
|
|
84
|
+
// F-1 (ISSUE-FIX-BATCH): children carry no agent.providers (spawn childConfig copies the
|
|
85
|
+
// parent config) — fall back to config.providersList (.length: [] must not skip the list).
|
|
86
|
+
const provider = findProvider(agent.providers?.length ? agent.providers : agent.config?.providersList ?? [agent.provider], cfg.provider)
|
|
87
|
+
// F-2a (MODEL-400-FIX):无 cfg.model 时渠道克隆须重派生 model——渠道裸克隆会丢 model 键
|
|
88
|
+
// → 无 model 请求 → serde 400。
|
|
89
|
+
// MODEL-SELECTION v2(M3④):无 cfg.model → 命中渠道自己的默认模型(`provider.model`
|
|
90
|
+
// 单值);渠道无默认模型 → 父 provider 兜底(与 subagent F-2c 同构)——绝不产出静默
|
|
91
|
+
// undefined-model 请求(最极端两者皆无 → chat 前 assertProviderModel fail-fast)。
|
|
92
|
+
const result = cfg.model ? { ...provider, model: cfg.model } : { ...provider, model: provider.model ?? agent.provider?.model }
|
|
349
93
|
if (cfg.thinking === null || cfg.thinking === false) result.thinking = undefined // explicitly off
|
|
350
94
|
else if (cfg.thinking !== undefined) result.thinking = cfg.thinking
|
|
351
95
|
if (cfg.reasoningEffort !== undefined) result.reasoningEffort = cfg.reasoningEffort
|
|
@@ -406,6 +150,24 @@ export function buildCapMessage(agent) {
|
|
|
406
150
|
return message
|
|
407
151
|
}
|
|
408
152
|
|
|
153
|
+
/**
|
|
154
|
+
* 压缩定锚简报(F13/§14.4 #3)——**由评审参数构建**(非模型输出):对象声明 / 文档清单 /
|
|
155
|
+
* Approval Signal 三锚;重内容(项目指南 / 方法论 / 文档地图)不入 pin(压缩的意义所在)。
|
|
156
|
+
* 形态逐字见设计 §14.4 #3(首行为机械化重挂说明)。
|
|
157
|
+
*/
|
|
158
|
+
function buildPinnedBrief(reviewType, documents, object, designToken, designId) {
|
|
159
|
+
const docList = Array.isArray(documents) ? documents.filter((d) => typeof d === "string" && d.trim()) : []
|
|
160
|
+
const parts = ["[review brief — re-attached after context compaction; the original review request is no longer in the context]"]
|
|
161
|
+
const declaration = buildObjectDeclarationBlock(object)
|
|
162
|
+
if (declaration) parts.push(declaration.trimEnd())
|
|
163
|
+
if (docList.length > 0) {
|
|
164
|
+
parts.push("## Documents to Review")
|
|
165
|
+
parts.push(docList.map((d) => `- ${d} — Read this file in full`).join("\n"))
|
|
166
|
+
}
|
|
167
|
+
if (reviewType === "design" && designToken) parts.push(buildDesignApprovalBlock(designToken, designId))
|
|
168
|
+
return parts.join("\n\n")
|
|
169
|
+
}
|
|
170
|
+
|
|
409
171
|
/**
|
|
410
172
|
* Run an advisor review. reviewType: "code" (default) or "design". Returns review text or null when skipped.
|
|
411
173
|
* @param {string|null} [designToken] — injected into the design-review prompt; the advisor echoes it only on approval.
|
|
@@ -426,20 +188,52 @@ export async function runAdvisorReview(agent, reviewType, callbacks, designToken
|
|
|
426
188
|
|
|
427
189
|
// Mechanical convergence cap — CODE REVIEWS ONLY (2026-09-07 §8 ruling: design
|
|
428
190
|
// reviews are exempt). _advisorRound is scoped to the current review instance
|
|
429
|
-
// (§
|
|
191
|
+
// (§11.2 D-24b ③), so >= MAX_ADVISOR_ROUNDS blocks the next call of THIS instance.
|
|
430
192
|
// 5 rounds max; after that the review is never pushed back.
|
|
431
193
|
if (reviewType !== "design" && (agent._advisorRound || 0) >= MAX_ADVISOR_ROUNDS) {
|
|
432
194
|
return buildCapMessage(agent)
|
|
433
195
|
}
|
|
434
196
|
|
|
197
|
+
// 第 33 批(§17.5 检查点 2——内防线):同一 doc-set 连续未产出可用结算达阈值 ⇒ 直接拒绝
|
|
198
|
+
// (不建消息、不发起、零 LLM)——防直接调用方绕过工具层预检(正常工具链在预检已拒)。
|
|
199
|
+
if (reviewType === "design" && Array.isArray(documents) && documents.length > 0) {
|
|
200
|
+
const streakKey = docSetKey(documents, agent.cwd)
|
|
201
|
+
if (designReviewStreakStopped(agent, streakKey)) {
|
|
202
|
+
return buildDesignReviewGuardMessage(designReviewStreakRecord(agent, streakKey), documents)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
435
206
|
const provider = resolveAdvisorProvider(agent)
|
|
436
207
|
// Advisor always works in the agent's cwd — scope is defined by paths/documents.
|
|
437
208
|
const advisorCwd = agent.cwd
|
|
438
209
|
|
|
439
210
|
const messages = prepareAdvisorMessages(agent, reviewType, designToken, documents, paths, null, object, designId)
|
|
440
211
|
|
|
212
|
+
// B 启动断言(fail-closed——§14.4 #2):设计评审请求内**必须**携带与本次签发 token 精确
|
|
213
|
+
// 对应的 Approval Signal——构建面补不上就拒绝启动(不发"请回显一个不存在的 token"的请求)。
|
|
214
|
+
// 拒绝报告前缀 `Advisor: design review launch refused` = 稳定契约(同步工具面据此登记
|
|
215
|
+
// _advisorRefusals;异步结算面据此不置 _calledAdvisorThisRun)。工具路径恒签发 token ⇒
|
|
216
|
+
// 该拒绝为直接调用方兜底(防御纵深——正常链不可达,如实注)。
|
|
217
|
+
if (reviewType === "design") {
|
|
218
|
+
const missing = !designToken
|
|
219
|
+
? "no design token was minted"
|
|
220
|
+
: (messages.some((m) => m.role === "user" && String(m.content ?? "").includes(`[DESIGN-TOKEN:${designToken}`))
|
|
221
|
+
? null
|
|
222
|
+
: "the request does not carry the approval signal")
|
|
223
|
+
if (missing) {
|
|
224
|
+
return `${ADVISOR_LAUNCH_REFUSAL_PREFIX} — ${missing}. Nothing was sent: a request that asks the reviewer to echo a token it cannot see would break the credential chain. Re-run advisor(type='design') to mint a fresh token.`
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// §2.20.3 批次档写通道的绑定(仅设计评审):同步路径 = 调用方(advisor 工具)传入的
|
|
229
|
+
// callbacks.batchDoc(即 resolved.run 的实例绑定);异步路径 = 本实例在跑池条目的
|
|
230
|
+
// run.batchDoc(同文档集实例键——各评审各取各档,不用单值会话态)。
|
|
231
|
+
const boundBatchDoc = reviewType === "design" ? batchDocForReview(agent, documents, callbacks) : null
|
|
232
|
+
// F13/§14.4 #3:压缩定锚简报(评审参数构建——压缩触发时由 compaction 重挂)。
|
|
233
|
+
const pinned = buildPinnedBrief(reviewType, documents, object, designToken, designId)
|
|
234
|
+
|
|
441
235
|
try {
|
|
442
|
-
const result = await runAdvisorToolLoop(provider, messages, onOutput, signal, agent, advisorCwd)
|
|
236
|
+
const result = await runAdvisorToolLoop(provider, messages, onOutput, signal, agent, advisorCwd, null, reviewType, boundBatchDoc, pinned)
|
|
443
237
|
|
|
444
238
|
// Host-verified citations (decision d698434): mechanically check every
|
|
445
239
|
// `file:line: content` reference in the review against the CURRENT file
|
|
@@ -447,9 +241,10 @@ export async function runAdvisorReview(agent, reviewType, callbacks, designToken
|
|
|
447
241
|
// the prior table instead of re-reading (three consecutive false reports
|
|
448
242
|
// cited pre-fix line content). Unverified citations must not support a
|
|
449
243
|
// push-back; the parent agent sees the verification report.
|
|
244
|
+
// F14/§14.5:解析候选 = cwd + 评审对象声明范围派生根(scope = documents + paths)。
|
|
450
245
|
let final = result
|
|
451
246
|
if (!result.trimStart().startsWith("Advisor:")) {
|
|
452
|
-
final = appendCitationReport(result, advisorCwd)
|
|
247
|
+
final = appendCitationReport(result, advisorCwd, { scope: [...(documents ?? []), ...(paths ?? [])] })
|
|
453
248
|
// Success path: keep the FULL review output for convergence rounds —
|
|
454
249
|
// round 2+ injects this verbatim and the model understands it (decision
|
|
455
250
|
// 2026-08-08: prior-table hard parsing removed; no phrase/header matching).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* truncate.mjs — advisor 工具结果的行感知双端截断(DUAL-END-TRUNCATION F-2,
|
|
3
|
+
* DUAL-END-TRUNCATION.md,2026-09-09——CLI/VSC 双端逐字同构镜像)。
|
|
4
|
+
*
|
|
5
|
+
* 原 run.mjs 内联截断为纯头向(line-aware 从首行累加至 64K break)——评审尾部
|
|
6
|
+
* 结论/裁决被切。现改头尾双保:头行累加至预算 ~60% → 中段省略注 → 尾行累加至
|
|
7
|
+
* 剩余 ~40%(保尾结论——与 read 工具 C 方案 / offload 双端预览同构的
|
|
8
|
+
* "头+尾保留、中段截断"策略——共享设计语言、分别实现)。
|
|
9
|
+
*
|
|
10
|
+
* 纯函数无导入:maxChars 由调用方传入(run.mjs 传 MAX_RESULT_CHARS)。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** advisor 截断头占比——头行累加至此份额后切中段,尾自动取剩余预算(设计 §4 ≈0.6)。 */
|
|
14
|
+
export const ADVISOR_HEAD_RATIO = 0.6
|
|
15
|
+
|
|
16
|
+
/** 尾部预算预留(字符):中段省略注 + offset 续读提示的保守预留——注的位数只减不增
|
|
17
|
+
* (middle ≤ totalChars——超大结果位数可达 ~10 位——200 字符仍宽松覆盖注+提示 ≤ ~140;
|
|
18
|
+
* 差额留给尾行)。 */
|
|
19
|
+
const NOTE_RESERVE_CHARS = 200
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 行感知双端截断:result > maxChars → 头 + `… (truncated: K more lines, TOTAL chars
|
|
23
|
+
* total)` + 尾 + offset 续读提示;≤ maxChars 原样返回(无假截断注)。K = 头尾之间
|
|
24
|
+
* 的中段行数——头(≤60%) + 尾(≤余预算) 均行级累加、绝不半行切开;中段存在时 K ≥ 1
|
|
25
|
+
* (防御:K ≤ 0 时静默返回头尾拼合——不谎报截断)。
|
|
26
|
+
*/
|
|
27
|
+
export function truncateAdvisorResult(result, maxChars) {
|
|
28
|
+
if (result.length <= maxChars) return result
|
|
29
|
+
const totalChars = result.length
|
|
30
|
+
const lines = result.split("\n")
|
|
31
|
+
const headBudget = Math.floor(maxChars * ADVISOR_HEAD_RATIO)
|
|
32
|
+
let head = ""
|
|
33
|
+
let headLines = 0
|
|
34
|
+
let chars = 0
|
|
35
|
+
while (headLines < lines.length && chars + lines[headLines].length + 1 <= headBudget) {
|
|
36
|
+
chars += lines[headLines].length + 1
|
|
37
|
+
head += lines[headLines] + "\n"
|
|
38
|
+
headLines++
|
|
39
|
+
}
|
|
40
|
+
const tailBudget = Math.max(0, maxChars - chars - NOTE_RESERVE_CHARS)
|
|
41
|
+
let tail = ""
|
|
42
|
+
let tailChars = 0
|
|
43
|
+
let tailStart = lines.length
|
|
44
|
+
while (tailStart > headLines && tailChars + lines[tailStart - 1].length + 1 <= tailBudget) {
|
|
45
|
+
tailChars += lines[tailStart - 1].length + 1
|
|
46
|
+
tailStart--
|
|
47
|
+
tail = lines[tailStart] + "\n" + tail
|
|
48
|
+
}
|
|
49
|
+
const middle = tailStart - headLines
|
|
50
|
+
if (middle <= 0) return head + tail // 头尾已覆盖全部行——绝不产生假截断注
|
|
51
|
+
return (
|
|
52
|
+
head +
|
|
53
|
+
`\n… (truncated: ${middle} more lines, ${totalChars} chars total)\n` +
|
|
54
|
+
tail +
|
|
55
|
+
`To see more content, use: read(path, offset=${headLines + 1}, limit=200)`
|
|
56
|
+
)
|
|
57
|
+
}
|
package/src/advisor.mjs
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* advisor.mjs — advisor system-prompt selection, follow-up building, session assembly.
|
|
3
3
|
* User-message building lives in advisor/messages.mjs; execution (tool loop, provider
|
|
4
4
|
* resolution, review entry) in advisor/run.mjs; history extraction in advisor/history.mjs.
|
|
5
|
-
*
|
|
5
|
+
* Path classification (code / doc / temp) and the project declaration surface live in
|
|
6
|
+
* ../conventions.mjs — the single authority every gate consumes (repos.mjs included).
|
|
6
7
|
*
|
|
7
8
|
* The advisor runs as a read-only exploration sub-agent with tools
|
|
8
9
|
* (read, glob, grep, ls, lsp, code_search) — ZERO git, every round. The change
|
|
@@ -266,7 +267,7 @@ export function prepareAdvisorMessages(agent, reviewType, designToken = null, do
|
|
|
266
267
|
// review surface.
|
|
267
268
|
const scopeFiles = resolveScopeFiles(agent, paths)
|
|
268
269
|
const followUp = buildAdvisorFollowUp(agent, prior, scopeFiles, object)
|
|
269
|
-
// §
|
|
270
|
+
// §11.2 D-24b (design round 2+ — async fix-round continuations must be able to
|
|
270
271
|
// re-approve): re-anchor the review scope (the convergence follow-up carries no
|
|
271
272
|
// document list) and inject the round's design token with the approval signal.
|
|
272
273
|
if (reviewType === "design") {
|
package/src/agent/completion.mjs
CHANGED
|
@@ -120,7 +120,7 @@ export function handleCompletion(agent, response, depth, turn, guardPushbacks, h
|
|
|
120
120
|
const cfg = agent.config?.advisor
|
|
121
121
|
const advisorReview = cfg?.guard === true
|
|
122
122
|
if (depth === 0 && advisorReview && !agent.config?.agent?.engineering) {
|
|
123
|
-
// §
|
|
123
|
+
// §11.2 D-24b (T-24b4 — guard timing): an async review that is still in flight
|
|
124
124
|
// (or queued in the advisor pool) means the review was launched — the guard
|
|
125
125
|
// does NOT push back while it is pending (未决不算未评审); once it settles
|
|
126
126
|
// non-stale it marks _calledAdvisorThisRun, and a STALE settle leaves the
|