thincoder 0.12.61 → 0.12.63
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 +34 -1
- package/README.md +13 -12
- package/bin/thincoder.mjs +63 -28
- package/package.json +6 -5
- package/src/acp/bridge.mjs +35 -15
- package/src/acp/client-caps.mjs +86 -0
- package/src/acp/ext.mjs +86 -0
- package/src/acp/handlers-session.mjs +240 -0
- package/src/acp/handlers-slots.mjs +196 -0
- package/src/acp/login.mjs +48 -0
- package/src/acp/session.mjs +6 -4
- package/src/acp.mjs +67 -371
- package/src/cli/distill-command.mjs +3 -3
- package/src/cli/make-agent.mjs +59 -17
- package/src/cli/memory-command.mjs +3 -3
- package/src/cli/permission.mjs +4 -48
- package/src/cli/setup-wizard.mjs +1 -1
- package/src/completions.mjs +3 -1
- package/src/crash-reports.mjs +32 -10
- package/src/distill.mjs +4 -4
- package/src/heap-watch.mjs +88 -0
- package/src/prompt-injections.mjs +20 -0
- package/src/tui/agent-turn.mjs +40 -9
- package/src/tui/cmd-advisor.mjs +5 -5
- package/src/tui/cmd-clear.mjs +2 -0
- package/src/tui/cmd-config.mjs +8 -8
- package/src/tui/cmd-eng.mjs +25 -9
- package/src/tui/cmd-mcp.mjs +9 -8
- package/src/tui/cmd-model.mjs +1 -1
- package/src/tui/cmd-new.mjs +10 -5
- package/src/tui/cmd-reindex.mjs +1 -1
- package/src/tui/cmd-restore.mjs +2 -2
- package/src/tui/cmd-session.mjs +31 -4
- package/src/tui/cmd-skills.mjs +1 -1
- package/src/tui/cmd-think.mjs +22 -9
- package/src/tui/config-helpers.mjs +1 -1
- package/src/tui/display-budget.mjs +206 -0
- package/src/tui/index.mjs +40 -12
- package/src/tui/interaction.mjs +16 -7
- package/src/tui/key-handler-search.mjs +9 -1
- package/src/tui/key-modes.mjs +9 -4
- package/src/tui/ledger-surface.mjs +85 -0
- package/src/tui/model-catalog.mjs +4 -4
- package/src/tui/model-picker.mjs +8 -7
- package/src/tui/mouse.mjs +11 -6
- package/src/tui/pickers.mjs +15 -2
- package/src/tui/render-conversation.mjs +1 -1
- package/src/tui/render-frame.mjs +17 -6
- package/src/tui/render-loop.mjs +1 -1
- package/src/tui/render-segments.mjs +3 -1
- package/src/tui/slash-commands.mjs +1 -1
- package/src/tui/startup.mjs +49 -17
- package/src/tui/subagent-blocks.mjs +21 -3
- package/src/tui/subagent-children.mjs +86 -14
- package/src/tui/subagent-freeze.mjs +80 -3
- package/src/tui/suspension-drive.mjs +48 -22
- package/src/tui/tool-args.mjs +5 -2
- package/src/tui/tool-display.mjs +16 -2
- package/src/tui/tool-events.mjs +64 -22
- package/src/tui/tui-lifecycle.mjs +9 -2
- package/src/tui/wizard.mjs +3 -3
- package/src/tui/wrapped-spawn.mjs +21 -5
- package/src/abort-provenance.mjs +0 -116
- package/src/advisor/citations.mjs +0 -139
- package/src/advisor/compaction.mjs +0 -174
- package/src/advisor/convergence.mjs +0 -80
- package/src/advisor/history.mjs +0 -77
- package/src/advisor/loop.mjs +0 -293
- package/src/advisor/messages.mjs +0 -299
- package/src/advisor/project-context.mjs +0 -194
- package/src/advisor/repos.mjs +0 -150
- package/src/advisor/run.mjs +0 -293
- package/src/advisor/truncate.mjs +0 -57
- package/src/advisor.mjs +0 -290
- package/src/agent/completion.mjs +0 -146
- package/src/agent/dispatch.mjs +0 -489
- package/src/agent/helpers.mjs +0 -384
- package/src/agent/post-turn.mjs +0 -70
- package/src/agent/record-results.mjs +0 -174
- package/src/agent/relay-prefix.mjs +0 -39
- package/src/agent/run-stages.mjs +0 -242
- package/src/agent/setup-reminders.mjs +0 -69
- package/src/agent/setup.mjs +0 -354
- package/src/agent/spawn-child.mjs +0 -228
- package/src/agent-tools/advisor-async.mjs +0 -346
- package/src/agent-tools/advisor-settle.mjs +0 -231
- package/src/agent-tools/advisor.mjs +0 -260
- package/src/agent-tools/async-settle.mjs +0 -191
- package/src/agent-tools/batch-segment.mjs +0 -195
- package/src/agent-tools/consult.mjs +0 -468
- package/src/agent-tools/design-token.mjs +0 -117
- package/src/agent-tools/digest-budget.mjs +0 -76
- package/src/agent-tools/eng.mjs +0 -67
- package/src/agent-tools/escalate-async.mjs +0 -289
- package/src/agent-tools/goal.mjs +0 -119
- package/src/agent-tools/plan.mjs +0 -81
- package/src/agent-tools/read-history.mjs +0 -294
- package/src/agent-tools/recent-changes.mjs +0 -24
- package/src/agent-tools/review-streak.mjs +0 -93
- package/src/agent-tools/settings.mjs +0 -265
- package/src/agent-tools/skill.mjs +0 -47
- package/src/agent-tools/subagent-actions.mjs +0 -479
- package/src/agent-tools/subagent-async.mjs +0 -434
- package/src/agent-tools/subagent-panel.mjs +0 -160
- package/src/agent-tools/subagent-run.mjs +0 -205
- package/src/agent-tools/subagent-scheduler.mjs +0 -392
- package/src/agent-tools/subagent-spawn.mjs +0 -453
- package/src/agent-tools/subagent.mjs +0 -404
- package/src/agent-tools/task.mjs +0 -87
- package/src/agent-tools/timer.mjs +0 -46
- package/src/agent-tools/verify.mjs +0 -271
- package/src/agent-tools.mjs +0 -17
- package/src/agent.mjs +0 -413
- package/src/auto-think.mjs +0 -115
- package/src/config-migrate.mjs +0 -70
- package/src/config.mjs +0 -496
- package/src/context.mjs +0 -381
- package/src/conventions.mjs +0 -223
- package/src/embedding.mjs +0 -120
- package/src/escape.mjs +0 -152
- package/src/expand-home.mjs +0 -16
- package/src/explore-distill.mjs +0 -155
- package/src/generate-title.mjs +0 -83
- package/src/git/checkpoint.mjs +0 -448
- package/src/git/gitmem.mjs +0 -100
- package/src/hooks.mjs +0 -97
- package/src/log.mjs +0 -195
- package/src/markdown.mjs +0 -106
- package/src/mcp/helpers.mjs +0 -51
- package/src/mcp/transport-http.mjs +0 -248
- package/src/mcp/transport-stdio.mjs +0 -140
- package/src/mcp/transport-ws.mjs +0 -122
- package/src/mcp.mjs +0 -295
- package/src/memory/code-index.mjs +0 -219
- package/src/memory/code-sync.mjs +0 -413
- package/src/memory/core.mjs +0 -300
- package/src/memory/delete.mjs +0 -236
- package/src/memory/docs.mjs +0 -417
- package/src/memory/file-walk.mjs +0 -109
- package/src/memory/schema.mjs +0 -452
- package/src/memory.mjs +0 -21
- package/src/model-ref.mjs +0 -66
- package/src/model-specs.mjs +0 -179
- package/src/peer-domains.mjs +0 -265
- package/src/peer-instances.mjs +0 -231
- package/src/prompt-overlays.mjs +0 -82
- package/src/prompts/advisor-design.md +0 -41
- package/src/prompts/advisor-round1.md +0 -41
- package/src/prompts/advisor-round2.md +0 -46
- package/src/prompts/advisor-round3.md +0 -42
- package/src/prompts/common.md +0 -115
- package/src/prompts/consult-base.md +0 -19
- package/src/prompts/discipline-engineering.md +0 -217
- package/src/prompts/discipline-normal.md +0 -179
- package/src/prompts/persona-coder.md +0 -21
- package/src/prompts/persona-eng-coder.md +0 -37
- package/src/prompts/persona-eng-designer.md +0 -55
- package/src/prompts/persona-engineering.md +0 -54
- package/src/prompts/persona-explore.md +0 -15
- package/src/prompts/persona-normal.md +0 -27
- package/src/prompts/persona-plan.md +0 -26
- package/src/provider/anthropic.mjs +0 -225
- package/src/provider/core.mjs +0 -476
- package/src/provider/errors.mjs +0 -101
- package/src/provider/google.mjs +0 -257
- package/src/provider/index.mjs +0 -7
- package/src/provider/list-models.mjs +0 -93
- package/src/provider/normalize.mjs +0 -81
- package/src/provider/rate.mjs +0 -108
- package/src/provider/responses.mjs +0 -495
- package/src/provider/retry.mjs +0 -88
- package/src/provider/sse.mjs +0 -264
- package/src/proxy.mjs +0 -261
- package/src/rules.mjs +0 -53
- package/src/session-gc.mjs +0 -214
- package/src/session-guard.mjs +0 -47
- package/src/session-migrate.mjs +0 -48
- package/src/session-rename.mjs +0 -38
- package/src/session-slots.mjs +0 -489
- package/src/session.mjs +0 -475
- package/src/skills.mjs +0 -153
- package/src/token-ttl.mjs +0 -274
- package/src/tools/apply_patch.md +0 -15
- package/src/tools/bash.md +0 -37
- package/src/tools/bash.mjs +0 -268
- package/src/tools/checklist-sync.mjs +0 -181
- package/src/tools/checklist.md +0 -13
- package/src/tools/checklist.mjs +0 -299
- package/src/tools/delete.md +0 -13
- package/src/tools/edit-batch.mjs +0 -191
- package/src/tools/edit-diff.mjs +0 -348
- package/src/tools/edit.md +0 -30
- package/src/tools/execute.md +0 -21
- package/src/tools/execute.mjs +0 -228
- package/src/tools/fetch.md +0 -12
- package/src/tools/file.mjs +0 -469
- package/src/tools/file_ops.md +0 -17
- package/src/tools/get_current_time.md +0 -8
- package/src/tools/git-checkpoint.mjs +0 -143
- package/src/tools/git-ext.mjs +0 -173
- package/src/tools/git.md +0 -54
- package/src/tools/git.mjs +0 -356
- package/src/tools/glob-dialect.mjs +0 -130
- package/src/tools/glob.md +0 -11
- package/src/tools/grep.md +0 -19
- package/src/tools/hashline_edit.md +0 -14
- package/src/tools/index.mjs +0 -36
- package/src/tools/insert_after.md +0 -15
- package/src/tools/lint.md +0 -10
- package/src/tools/linter.mjs +0 -128
- package/src/tools/ls.md +0 -12
- package/src/tools/lsp.md +0 -10
- package/src/tools/lsp.mjs +0 -316
- package/src/tools/ops.mjs +0 -299
- package/src/tools/patch.mjs +0 -282
- package/src/tools/process.md +0 -10
- package/src/tools/question.md +0 -16
- package/src/tools/question.mjs +0 -26
- package/src/tools/read.md +0 -20
- package/src/tools/read_image.md +0 -8
- package/src/tools/repomap.mjs +0 -314
- package/src/tools/search.mjs +0 -236
- package/src/tools/shared.mjs +0 -446
- package/src/tools/tree.md +0 -14
- package/src/tools/tree.mjs +0 -66
- package/src/tools/wait_for.md +0 -22
- package/src/tools/web.mjs +0 -224
- package/src/tools/websearch.md +0 -16
- package/src/tools/write.md +0 -11
- package/src/traces/trace-store.mjs +0 -224
package/src/advisor/run.mjs
DELETED
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
/**
|
|
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 保持不变)。
|
|
6
|
-
*/
|
|
7
|
-
import { findProvider } from "../config.mjs"
|
|
8
|
-
import { prepareAdvisorMessages } from "../advisor.mjs"
|
|
9
|
-
import { buildObjectDeclarationBlock, buildDesignApprovalBlock } from "./messages.mjs"
|
|
10
|
-
import { appendCitationReport } from "./citations.mjs"
|
|
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"
|
|
24
|
-
|
|
25
|
-
// Mechanical convergence cap: up to 5 rounds suffice; a 6th call means the model
|
|
26
|
-
// is looping — refuse it instead of burning tokens. §11.2 D-24b (2026-09-06): PER
|
|
27
|
-
// REVIEW INSTANCE (agent._advisorRuns); CODE REVIEWS ONLY (2026-09-07 §8 ruling)
|
|
28
|
-
// — design reviews are EXEMPT: their rounds keep advancing, the cap never refuses.
|
|
29
|
-
export const MAX_ADVISOR_ROUNDS = 5
|
|
30
|
-
|
|
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",
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
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 — 被停的文档集(结论清单数据源)
|
|
58
|
-
*/
|
|
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")
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const MAX_UNFIXED_DISPLAY = 10 // unfixed issues shown in the cap message
|
|
78
|
-
|
|
79
|
-
/** Resolve the advisor's provider: cfg.provider/model when set, otherwise the main agent's provider */
|
|
80
|
-
export function resolveAdvisorProvider(agent) {
|
|
81
|
-
const cfg = agent.config?.advisor
|
|
82
|
-
if (cfg?.provider) {
|
|
83
|
-
try {
|
|
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 }
|
|
93
|
-
if (cfg.thinking === null || cfg.thinking === false) result.thinking = undefined // explicitly off
|
|
94
|
-
else if (cfg.thinking !== undefined) result.thinking = cfg.thinking
|
|
95
|
-
if (cfg.reasoningEffort !== undefined) result.reasoningEffort = cfg.reasoningEffort
|
|
96
|
-
return result
|
|
97
|
-
} catch (e) {
|
|
98
|
-
// Provider not found or lookup failed — fall back to main provider, but surface the reason
|
|
99
|
-
console.warn(`[advisor] resolveAdvisorProvider: ${e.message}`)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
const provider = { ...agent.provider }
|
|
103
|
-
if (cfg?.model) provider.model = cfg.model
|
|
104
|
-
// thinking off: null AND false both mean "explicitly off" — a raw `false`
|
|
105
|
-
// value is invalid for providers that expect undefined or an object.
|
|
106
|
-
if (cfg?.thinking === null || cfg?.thinking === false) provider.thinking = undefined
|
|
107
|
-
else if (cfg?.thinking !== undefined) provider.thinking = cfg.thinking
|
|
108
|
-
if (cfg?.reasoningEffort !== undefined) provider.reasoningEffort = cfg.reasoningEffort
|
|
109
|
-
return provider
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Extract unfixed issues from prior review text (for the cap message).
|
|
114
|
-
* Input: an advisor review markdown table (`| # | … |` rows). A row counts as
|
|
115
|
-
* unfixed unless its line carries a resolved-status word (fixed/resolved/done/
|
|
116
|
-
* addressed/corrected, ✓/✔). Returns at most MAX_UNFIXED_DISPLAY plain
|
|
117
|
-
* (pipe-stripped) row strings.
|
|
118
|
-
*/
|
|
119
|
-
function extractUnfixedIssues(priorText) {
|
|
120
|
-
if (!priorText) return []
|
|
121
|
-
const lines = priorText.split("\n")
|
|
122
|
-
// Resolved-status words: fixed/resolved/done/addressed/corrected (+ ✓/✔).
|
|
123
|
-
// \b prevents "unfixed"/"prefixed" from matching "fixed".
|
|
124
|
-
const resolvedRe = /\b(?:fixed|resolved|done|addressed|corrected)\b|✓|✔/i
|
|
125
|
-
return lines
|
|
126
|
-
.filter((line) => /\|\s*\d+\s*\|/.test(line)) // 匹配表格行
|
|
127
|
-
.filter((line) => !resolvedRe.test(line))
|
|
128
|
-
// Strip only the leading/trailing table pipes — inner pipes (escaped or
|
|
129
|
-
// in-cell content) stay intact instead of garbling the cap message.
|
|
130
|
-
.map((line) => line.trim().replace(/^\|/, "").replace(/\|$/, "").trim())
|
|
131
|
-
.filter(Boolean)
|
|
132
|
-
.slice(0, MAX_UNFIXED_DISPLAY)
|
|
133
|
-
}
|
|
134
|
-
/** Review-looking guard (async settle parity): a markdown table row or ≥200 chars of prose counts as a prior. */
|
|
135
|
-
export function looksLikeReviewOutput(text) {
|
|
136
|
-
const trimmed = String(text ?? "").trim()
|
|
137
|
-
return /\|.*\|.*\|/.test(trimmed) || trimmed.length >= 200
|
|
138
|
-
}
|
|
139
|
-
/** Cap message (shared by runAdvisorReview and the async pre-check — per-review refusal). */
|
|
140
|
-
export function buildCapMessage(agent) {
|
|
141
|
-
const prior = agent._lastAdvisorOutput
|
|
142
|
-
const unfixed = prior ? extractUnfixedIssues(prior) : []
|
|
143
|
-
let message = `Advisor: convergence cap reached after ${MAX_ADVISOR_ROUNDS} rounds.\n`
|
|
144
|
-
if (unfixed.length > 0) {
|
|
145
|
-
message += `\nUnresolved issues from prior rounds:\n${unfixed.map((i) => `- ${i}`).join("\n")}\n`
|
|
146
|
-
} else {
|
|
147
|
-
message += "\nAll prior issues appear resolved.\n"
|
|
148
|
-
}
|
|
149
|
-
message += "\nOptions:\n1. Accept current state and proceed\n2. Manually review specific concerns with read/grep\n3. Start a new session (/new) to reset the advisor"
|
|
150
|
-
return message
|
|
151
|
-
}
|
|
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
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Run an advisor review. reviewType: "code" (default) or "design". Returns review text or null when skipped.
|
|
173
|
-
* @param {string|null} [designToken] — injected into the design-review prompt; the advisor echoes it only on approval.
|
|
174
|
-
* @param {string[]|null} [documents] — design review only: explicit list of doc paths to review; passed through to the message builder.
|
|
175
|
-
* @param {string[]|null} [paths] — code review only: explicit list of file/dir paths to review.
|
|
176
|
-
* @param {Object|null} [object] — review-object declaration (§18.8 D-OA1/D-OA3): { type, target, status, reason, exclude }; absent → legacy behavior (no injection).
|
|
177
|
-
* @param {string|null} [designId] — §29.1 F2a: injected next to the design token in
|
|
178
|
-
* the Approval Signal (passed through to the message builders).
|
|
179
|
-
*/
|
|
180
|
-
export async function runAdvisorReview(agent, reviewType, callbacks, designToken = null, documents = null, paths = null, object = null, designId = null) {
|
|
181
|
-
const onOutput = callbacks?.onOutput
|
|
182
|
-
const signal = callbacks?.signal
|
|
183
|
-
const startTime = Date.now()
|
|
184
|
-
|
|
185
|
-
// Advisor reviews are ALWAYS available (2026-08-21 semantic refactor): the
|
|
186
|
-
// former advisor.enabled gate is removed — review capability has no off
|
|
187
|
-
// switch; only the guard (completion pushback) is opt-in via advisor.guard.
|
|
188
|
-
|
|
189
|
-
// Mechanical convergence cap — CODE REVIEWS ONLY (2026-09-07 §8 ruling: design
|
|
190
|
-
// reviews are exempt). _advisorRound is scoped to the current review instance
|
|
191
|
-
// (§11.2 D-24b ③), so >= MAX_ADVISOR_ROUNDS blocks the next call of THIS instance.
|
|
192
|
-
// 5 rounds max; after that the review is never pushed back.
|
|
193
|
-
if (reviewType !== "design" && (agent._advisorRound || 0) >= MAX_ADVISOR_ROUNDS) {
|
|
194
|
-
return buildCapMessage(agent)
|
|
195
|
-
}
|
|
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
|
-
|
|
206
|
-
const provider = resolveAdvisorProvider(agent)
|
|
207
|
-
// Advisor always works in the agent's cwd — scope is defined by paths/documents.
|
|
208
|
-
const advisorCwd = agent.cwd
|
|
209
|
-
|
|
210
|
-
const messages = prepareAdvisorMessages(agent, reviewType, designToken, documents, paths, null, object, designId)
|
|
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
|
-
|
|
235
|
-
try {
|
|
236
|
-
const result = await runAdvisorToolLoop(provider, messages, onOutput, signal, agent, advisorCwd, null, reviewType, boundBatchDoc, pinned)
|
|
237
|
-
|
|
238
|
-
// Host-verified citations (decision d698434): mechanically check every
|
|
239
|
-
// `file:line: content` reference in the review against the CURRENT file
|
|
240
|
-
// state. LLMs cannot self-enforce the evidence rule — the model may quote
|
|
241
|
-
// the prior table instead of re-reading (three consecutive false reports
|
|
242
|
-
// cited pre-fix line content). Unverified citations must not support a
|
|
243
|
-
// push-back; the parent agent sees the verification report.
|
|
244
|
-
// F14/§14.5:解析候选 = cwd + 评审对象声明范围派生根(scope = documents + paths)。
|
|
245
|
-
let final = result
|
|
246
|
-
if (!result.trimStart().startsWith("Advisor:")) {
|
|
247
|
-
final = appendCitationReport(result, advisorCwd, { scope: [...(documents ?? []), ...(paths ?? [])] })
|
|
248
|
-
// Success path: keep the FULL review output for convergence rounds —
|
|
249
|
-
// round 2+ injects this verbatim and the model understands it (decision
|
|
250
|
-
// 2026-08-08: prior-table hard parsing removed; no phrase/header matching).
|
|
251
|
-
// Guard: only store outputs that actually carry a review — a markdown
|
|
252
|
-
// table row (`| a | b | c |`) or substantial prose (>200 chars). An
|
|
253
|
-
// empty or tool-progress-only reply must not become the "prior review"
|
|
254
|
-
// of round 2+.
|
|
255
|
-
const looksLikeReview = looksLikeReviewOutput(final)
|
|
256
|
-
if (looksLikeReview) {
|
|
257
|
-
agent._lastAdvisorOutput = final
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Log review statistics for observability
|
|
262
|
-
const elapsed = Math.round((Date.now() - startTime) / 1000)
|
|
263
|
-
const toolCallCount = messages.filter((m) => m.role === "tool").length
|
|
264
|
-
const tokensUsed = estimateTokens(messages)
|
|
265
|
-
onOutput?.({
|
|
266
|
-
kind: "text",
|
|
267
|
-
text: `\n[advisor] Review completed: ${elapsed}s, ${toolCallCount} tool calls, ~${Math.round(tokensUsed / 1000)}k tokens\n`,
|
|
268
|
-
})
|
|
269
|
-
return final
|
|
270
|
-
} catch (e) {
|
|
271
|
-
if (e.name === "AbortError" && signal?.reason?.interrupt) throw e
|
|
272
|
-
|
|
273
|
-
// 细化错误类型
|
|
274
|
-
const errorType = e.message.includes("rate limit") || e.message.includes("429") ? "rate limit"
|
|
275
|
-
: e.message.includes("timeout") ? "timeout"
|
|
276
|
-
: e.message.includes("network") || e.message.includes("ECONNREFUSED") ? "network"
|
|
277
|
-
: e.message.includes("context length") ? "context_too_long"
|
|
278
|
-
: "unknown"
|
|
279
|
-
|
|
280
|
-
const retryAdvice = errorType === "rate limit"
|
|
281
|
-
? "Wait a moment and retry. Consider using a cheaper model for advisor."
|
|
282
|
-
: errorType === "timeout"
|
|
283
|
-
? "The model took too long. Try with a narrower scope."
|
|
284
|
-
: errorType === "context_too_long"
|
|
285
|
-
? "Reduce the scope (fewer files/paths) or use a model with larger context window."
|
|
286
|
-
: "You may retry or proceed to verify manually."
|
|
287
|
-
|
|
288
|
-
return `Advisor: review failed (${errorType}) — ${e.message || "unknown error"}. ${retryAdvice}`
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
// Host-verified citations — moved to citations.mjs (kept re-exported here for
|
|
292
|
-
// import compatibility: tests and callers import from run.mjs).
|
|
293
|
-
export { extractCitations, verifyCitations, appendCitationReport } from "./citations.mjs"
|
package/src/advisor/truncate.mjs
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* advisor.mjs — advisor system-prompt selection, follow-up building, session assembly.
|
|
3
|
-
* User-message building lives in advisor/messages.mjs; execution (tool loop, provider
|
|
4
|
-
* resolution, review entry) in advisor/run.mjs; history extraction in advisor/history.mjs.
|
|
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).
|
|
7
|
-
*
|
|
8
|
-
* The advisor runs as a read-only exploration sub-agent with tools
|
|
9
|
-
* (read, glob, grep, ls, lsp, code_search) — ZERO git, every round. The change
|
|
10
|
-
* surface comes from the review scope (paths / _touchedFiles), never from git;
|
|
11
|
-
* verification is `read`-only with quoted-line evidence (7d49a52, d3be613).
|
|
12
|
-
*
|
|
13
|
-
* Config:
|
|
14
|
-
* { advisor: { enabled: true, provider: "deepseek", model: "deepseek-chat" } }
|
|
15
|
-
* provider + model are optional — defaults to the main agent's provider/model.
|
|
16
|
-
*
|
|
17
|
-
* Convergence protocol:
|
|
18
|
-
* Round 1: full review → produces a numbered issue table.
|
|
19
|
-
* Agent responds with a response table per issue (fix claims).
|
|
20
|
-
* Round 2: semi-convergence — verifies the prior table + can flag obvious new issues.
|
|
21
|
-
* Round 3+: strict convergence — only checks the prior issue table.
|
|
22
|
-
* The prior issue table IS injected into rounds 2+ (decision 2026-08-05,
|
|
23
|
-
* reversed) — it is the ONLY complete verification list: the agent response
|
|
24
|
-
* table covers only issues the agent chose to answer, so skipped issues would
|
|
25
|
-
* silently escape convergence without it. The fix-claim table travels as a
|
|
26
|
-
* focus reference only. Restatement risk is handled mechanically:
|
|
27
|
-
* host-verified citations reject references that do not match the current
|
|
28
|
-
* disk state, and fresh sessions exclude old read data.
|
|
29
|
-
* Each round replaces the system prompt (ROUND1 → ROUND2 → ROUND3) so the
|
|
30
|
-
* round-1 full-scope mandate can't bleed into later rounds, plus a mechanical
|
|
31
|
-
* cap (MAX_ADVISOR_ROUNDS in run.mjs) refuses a 6th review call outright.
|
|
32
|
-
* Rounds 2+ also declare all earlier diffs STALE and require read-verified
|
|
33
|
-
* file:line evidence for any unfixed/new finding — see docs/design/ADVISOR-CONVERGENCE.md.
|
|
34
|
-
*
|
|
35
|
-
* Session memory (agent._advisorSession):
|
|
36
|
-
* RETAINED for initialization compatibility but NEVER read (decision d698434):
|
|
37
|
-
* every review round builds a fresh [system, user] session — round 2+ must not
|
|
38
|
-
* reuse round 1's messages, because the old read outputs are the anchoring
|
|
39
|
-
* source of re-review false reports and a token sink. Convergence data (prior
|
|
40
|
-
* issue table + agent response table) travels via buildAdvisorFollowUp.
|
|
41
|
-
* The field is reset by runAgent; the write sites are harmless leftovers.
|
|
42
|
-
*
|
|
43
|
-
* Project customisation: .thincoder/advisor.md in the project root.
|
|
44
|
-
*/
|
|
45
|
-
import { readFileSync } from "node:fs"
|
|
46
|
-
import { join, dirname } from "node:path"
|
|
47
|
-
import { fileURLToPath } from "node:url"
|
|
48
|
-
import { extractAgentResponseTable } from "./advisor/history.mjs"
|
|
49
|
-
import { buildAdvisorUserMessage, resolveScopeFiles, buildObjectDeclarationBlock, buildDesignApprovalBlock } from "./advisor/messages.mjs"
|
|
50
|
-
import { buildConvergenceBody } from "./advisor/convergence.mjs"
|
|
51
|
-
import { escapeLiteralEscapes } from "./escape.mjs"
|
|
52
|
-
// Re-export for run.mjs and tests (keeps their imports from "../advisor.mjs" stable)
|
|
53
|
-
export { ADVISOR_MD_PATH, extractAgentResponseTable, extractConversationBackground } from "./advisor/history.mjs"
|
|
54
|
-
export { buildAdvisorUserMessage } from "./advisor/messages.mjs"
|
|
55
|
-
export { buildObjectDeclarationBlock, buildDesignApprovalBlock } from "./advisor/messages.mjs"
|
|
56
|
-
|
|
57
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
58
|
-
|
|
59
|
-
// ────────────────────────────────────────
|
|
60
|
-
// Prompt files — loaded at module init
|
|
61
|
-
// ────────────────────────────────────────
|
|
62
|
-
|
|
63
|
-
function loadPrompt(file, name) {
|
|
64
|
-
try {
|
|
65
|
-
return readFileSync(join(__dirname, "prompts", file), "utf8")
|
|
66
|
-
} catch {
|
|
67
|
-
throw new Error(`${name} missing from the installation (prompts/${file}) — reinstall thincoder or restore the file`)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const ADVISOR_ROUND1 = loadPrompt("advisor-round1.md", "advisor-round1.md")
|
|
72
|
-
// ROUND2/3 are used whenever a convergence round (round 2+) is being built:
|
|
73
|
-
// in-run session continuation replaces the system prompt with them, and a
|
|
74
|
-
// rebuilt fresh session (e.g. after a failed review) also selects them via
|
|
75
|
-
// buildAdvisorSystemPrompt when _advisorRound > 0.
|
|
76
|
-
const ADVISOR_ROUND2 = loadPrompt("advisor-round2.md", "advisor-round2.md")
|
|
77
|
-
const ADVISOR_ROUND3 = loadPrompt("advisor-round3.md", "advisor-round3.md")
|
|
78
|
-
// Design-review prompt — hard-loaded like the round prompts (decision
|
|
79
|
-
// 2026-08-21): a missing file means a broken installation, and silently
|
|
80
|
-
// degrading to a lesser in-code prompt would quietly strip the approval-signal
|
|
81
|
-
// and citation rules, disabling design approval entirely. loadPrompt throws.
|
|
82
|
-
const ADVISOR_DESIGN = loadPrompt("advisor-design.md", "advisor-design.md")
|
|
83
|
-
|
|
84
|
-
// ────────────────────────────────────────
|
|
85
|
-
// System prompt building
|
|
86
|
-
// ────────────────────────────────────────
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Build the system prompt for an advisor review session.
|
|
90
|
-
* @param {Object} agent — the parent agent
|
|
91
|
-
* @param {Object|null} [prior] — prior review output (full text; decision 2026-08-08)
|
|
92
|
-
* @param {string} [reviewType] — "design" for design review, undefined/"code" for code review
|
|
93
|
-
* @returns {string} the system prompt
|
|
94
|
-
*/
|
|
95
|
-
/** Append local time so the reviewer knows "now" (same grounding as the agent loop). */
|
|
96
|
-
function withTime(prompt) {
|
|
97
|
-
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "local"
|
|
98
|
-
return prompt + `\n\nCurrent time: ${new Date().toLocaleString("sv-SE")} (${timeZone}).`
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function buildAdvisorSystemPrompt(agent, prior, reviewType) {
|
|
102
|
-
// Round decision is DETERMINISTIC (decision 2026-08-08): _advisorRound > 0
|
|
103
|
-
// with a stored review output means convergence (round 2+); 0 means round 1.
|
|
104
|
-
// No prior-table parsing, no all-clear phrase matching — the round counter
|
|
105
|
-
// and the stored output are the only inputs. A restarted process has
|
|
106
|
-
// _advisorRound 0 → conservative full re-review.
|
|
107
|
-
const hasPrior = (agent._advisorRound || 0) > 0 && (prior ?? agent._lastAdvisorOutput)
|
|
108
|
-
// Design review: round 1 uses the dedicated design-review prompt (full scope +
|
|
109
|
-
// approval token); rounds 2+ converge like code reviews (verify agent fix claims).
|
|
110
|
-
if (reviewType === "design") {
|
|
111
|
-
if (!hasPrior) {
|
|
112
|
-
return ADVISOR_DESIGN
|
|
113
|
-
}
|
|
114
|
-
const round = (agent._advisorRound || 0) + 1
|
|
115
|
-
if (round === 2) return ADVISOR_ROUND2
|
|
116
|
-
return ADVISOR_ROUND3
|
|
117
|
-
}
|
|
118
|
-
if (!hasPrior) return ADVISOR_ROUND1
|
|
119
|
-
const round = (agent._advisorRound || 0) + 1
|
|
120
|
-
if (round === 2) return ADVISOR_ROUND2
|
|
121
|
-
return ADVISOR_ROUND3
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ────────────────────────────────────────
|
|
125
|
-
// Follow-up building (round 2+)
|
|
126
|
-
// ────────────────────────────────────────
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Build a follow-up user message for round 2+ — the agent's response table +
|
|
130
|
-
* round-aware instructions, without re-sending the full round-1 context.
|
|
131
|
-
* Deliberately NO git information injected (no diff snapshot, no git context):
|
|
132
|
-
* git output misled re-reviews — committed fixes never show in `git diff HEAD`,
|
|
133
|
-
* so the model read "no changes" as "no fixes". Verification is `read`-only.
|
|
134
|
-
* NOTE: the caller (prepareAdvisorMessages) applies escapeLiteralEscapes to
|
|
135
|
-
* the return value — direct callers must do the same (the prior table and
|
|
136
|
-
* agent response can quote literal "\x"/"\u" sequences).
|
|
137
|
-
* @param {Object} agent — the parent agent (history used for the response table)
|
|
138
|
-
* @param {Object|null} prior — prior issue table (extracted from history when null)
|
|
139
|
-
* @param {string[]|null} [scopeFiles] — review surface for the no-response fallback (cwd-relative)
|
|
140
|
-
* @param {Object|null} [object] — review-object declaration (§18.8): mechanically
|
|
141
|
-
* prepended to the round-2+ follow-up so every round stays anchored (T-OA2).
|
|
142
|
-
* @returns {string} the follow-up user message — or a plain "System reminder: …"
|
|
143
|
-
* fresh-review fallback (NO brackets — some OpenAI-compatible servers parse
|
|
144
|
-
* '['-prefixed content as structured data / expand escapes) when no prior
|
|
145
|
-
* review exists at all (caller misuse; the response-table extraction would
|
|
146
|
-
* otherwise scan history from index 0 and could match an unrelated stale table)
|
|
147
|
-
*/
|
|
148
|
-
export function buildAdvisorFollowUp(agent, prior, scopeFiles = null, object = null) {
|
|
149
|
-
// Convergence follow-up REQUIRES a prior review record — the full output of
|
|
150
|
-
// the last review, injected VERBATIM (decision 2026-08-08: the model
|
|
151
|
-
// understands the review output; no table/header/phrase parsing). The caller
|
|
152
|
-
// usually passes it; fall back to the stored agent._lastAdvisorOutput.
|
|
153
|
-
const p = prior ?? agent._lastAdvisorOutput
|
|
154
|
-
if (!p) {
|
|
155
|
-
// Plain "System reminder:" prefix (no brackets) — same convention as the
|
|
156
|
-
// round-1 path (some OpenAI-compatible servers parse '['-prefixed content
|
|
157
|
-
// as structured data / expand escapes; see prepareAdvisorMessages).
|
|
158
|
-
return "System reminder: convergence follow-up requested without a prior review — perform a fresh full review."
|
|
159
|
-
}
|
|
160
|
-
// Convergence semantics require round >= 2 (round 1 is the full review, not
|
|
161
|
-
// verification). A direct caller with _advisorRound 0 would otherwise get a
|
|
162
|
-
// meaningless "Round 1 — Strict Verification".
|
|
163
|
-
if ((agent._advisorRound || 0) < 1) {
|
|
164
|
-
return "System reminder: convergence follow-up requested at round 1 — a full review is already in progress; no prior verification exists yet."
|
|
165
|
-
}
|
|
166
|
-
const noResponseFallback = scopeFiles?.length
|
|
167
|
-
? "(Agent did not provide a response table — perform a fresh review of: " + scopeFiles.slice(0, 10).join(", ") + ")"
|
|
168
|
-
: "(Agent did not provide a response table — perform a fresh full review; the review surface is unknown, ask the user for the file list)"
|
|
169
|
-
const response = extractAgentResponseTable(agent.history) || noResponseFallback
|
|
170
|
-
const round = (agent._advisorRound || 0) + 1
|
|
171
|
-
// Review-object declaration FIRST (T-OA2 — round 2+ stays anchored, no re-archaeology).
|
|
172
|
-
const declaration = buildObjectDeclarationBlock(object)
|
|
173
|
-
return (declaration ? declaration + "\n" : "") + buildConvergenceBody(p, response, round, scopeFiles)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Resolve the review surface for the convergence fallback — moved to
|
|
178
|
-
* messages.mjs so the legacy path shares it (see there).
|
|
179
|
-
*/
|
|
180
|
-
|
|
181
|
-
// escapeLiteralEscapes 已抽到 ./escape.mjs(advisor 与主 agent 发送路径共用),
|
|
182
|
-
// 这里 re-export 保持既有 import 稳定。
|
|
183
|
-
export { escapeLiteralEscapes }
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Build the advisor conversation for this run.
|
|
188
|
-
* EVERY call builds a fresh [system, user] session (decision d698434) — no
|
|
189
|
-
* session reuse across rounds: round 1 = full scope (ROUND1 prompt), rounds
|
|
190
|
-
* 2+ = convergence (ROUND2/ROUND3 prompt + fix-claims follow-up).
|
|
191
|
-
* @param {Object} agent — the parent agent
|
|
192
|
-
* @param {string} [reviewType] — "design" or "code" (default)
|
|
193
|
-
* @param {string|null} [designToken] — design-review approval token (design only)
|
|
194
|
-
* @param {string[]|null} [documents] — design review only: explicit list of doc paths to review (passed through to buildAdvisorUserMessage)
|
|
195
|
-
* @param {string[]|null} [paths] — code review only: explicit list of file/dir paths to review
|
|
196
|
-
* @param {Object|null} [object] — review-object declaration (§18.8 D-OA1): passed through
|
|
197
|
-
* to the user-message builders; mechanically injected at the start of every review
|
|
198
|
-
* round (round 1 design/code + round 2+ follow-up). Absent → legacy behavior.
|
|
199
|
-
* @param {string|null} [designId] — §29.1 F2a: injected next to the design token in
|
|
200
|
-
* the Approval Signal (round 1 + round 2+ — both values, verbatim anchor).
|
|
201
|
-
*/
|
|
202
|
-
export function prepareAdvisorMessages(agent, reviewType, designToken = null, documents = null, paths = null, priorParam = null, object = null, designId = null) {
|
|
203
|
-
// Deterministic convergence state (decision 2026-08-08): round 2+ requires
|
|
204
|
-
// _advisorRound > 0 AND a stored prior review output. No history parsing.
|
|
205
|
-
// priorParam (direct callers) wins over the stored output — same derivation
|
|
206
|
-
// as buildAdvisorSystemPrompt (single source of truth for round semantics).
|
|
207
|
-
const prior = (agent._advisorRound || 0) > 0 ? (priorParam ?? agent._lastAdvisorOutput) : null
|
|
208
|
-
|
|
209
|
-
// Design review round 1: the dedicated full-scope review with the approval
|
|
210
|
-
// token (an independent gate — it runs even when a prior review exists, e.g.
|
|
211
|
-
// after a failed design review). Fresh session.
|
|
212
|
-
if (reviewType === "design" && (agent._advisorRound || 0) === 0) {
|
|
213
|
-
return [
|
|
214
|
-
{ role: "system", content: withTime(buildAdvisorSystemPrompt(agent, prior, reviewType)) },
|
|
215
|
-
{ role: "user", content: escapeLiteralEscapes(buildAdvisorUserMessage(agent, prior, reviewType, designToken, documents, paths, object, designId)) },
|
|
216
|
-
]
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Every round is a FRESH session (decision d698434): round 2+ must NOT reuse
|
|
220
|
-
// round 1's messages — the old read outputs are the top anchoring source of
|
|
221
|
-
// re-review false reports (the model quoted pre-fix file content instead of
|
|
222
|
-
// re-reading) and a token sink. The agent response table (fix claims) is
|
|
223
|
-
// injected through buildAdvisorFollowUp instead; the system prompt carries
|
|
224
|
-
// the round (ROUND2/ROUND3) via buildAdvisorSystemPrompt.
|
|
225
|
-
// No prior review output: reset ONLY when this run made no code changes (user
|
|
226
|
-
// decision 2026-08-05: any loop that modified code must NOT reset — the
|
|
227
|
-
// advisor guard WILL push back, so the convergence round must keep advancing
|
|
228
|
-
// toward the cap; a run with no mutations has no push-back risk and a reset
|
|
229
|
-
// is safe). Deterministic runtime state (`_mutatedThisRun`) decides — never
|
|
230
|
-
// model output (phrases/table headers drift; three rounds of false reports
|
|
231
|
-
// proved it). Either way the message is a fresh full review (no prior output
|
|
232
|
-
// exists without a completed review) — only the round counter differs.
|
|
233
|
-
if (!prior || (agent._advisorRound || 0) === 0) {
|
|
234
|
-
if (!(agent._mutatedThisRun ?? false)) {
|
|
235
|
-
// New review cycle (first review, all-clear, or no code changes): reset
|
|
236
|
-
// the round so the cycle gets its own 5-round budget.
|
|
237
|
-
agent._advisorRound = 0
|
|
238
|
-
}
|
|
239
|
-
// Mutations exist → KEEP the round (cap keeps advancing through retries).
|
|
240
|
-
const user = buildAdvisorUserMessage(agent, prior, reviewType, designToken, documents, paths, object, designId)
|
|
241
|
-
return [
|
|
242
|
-
{ role: "system", content: withTime(buildAdvisorSystemPrompt(agent, prior, reviewType)) },
|
|
243
|
-
{
|
|
244
|
-
role: "user",
|
|
245
|
-
// NOTE (2026-08-06): the leading prefix is a PLAIN "System reminder:",
|
|
246
|
-
// NOT "[System reminder: ...]" — some OpenAI-compatible servers try to
|
|
247
|
-
// parse content that STARTS with '[' as structured content (or expand
|
|
248
|
-
// escape sequences in it). A literal "\x" inside the conversation
|
|
249
|
-
// background (e.g. the parent agent quoting escape sequences) then
|
|
250
|
-
// fails server-side as "unexpected end of hex escape" → 400. Plain
|
|
251
|
-
// prefix keeps the review message a plain string everywhere.
|
|
252
|
-
// The whole content also passes through escapeLiteralEscapes (below)
|
|
253
|
-
// so literal "\x"/"\u" quoted by the parent agent can never form an
|
|
254
|
-
// invalid escape when the server expands them.
|
|
255
|
-
content: escapeLiteralEscapes(`System reminder: no prior issue table is being carried into this review (first review, app restart, or session clear) — start with a fresh full review.\n\n${user}`),
|
|
256
|
-
},
|
|
257
|
-
]
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Convergence rounds (2+): fresh [system(ROUND2/3), user(prior table + fix
|
|
261
|
-
// claims)]. buildAdvisorFollowUp carries BOTH the prior issue table (the
|
|
262
|
-
// only complete verification list — decision 2026-08-05, reversed) and the
|
|
263
|
-
// agent's fix-claim table (focus reference). buildAdvisorSystemPrompt
|
|
264
|
-
// selects ROUND2 for round 2, ROUND3 for rounds 3+ — a failed review retry
|
|
265
|
-
// keeps _advisorRound so the convergence prompt matches the attempt count.
|
|
266
|
-
// scopeFiles gives the fallback (agent gave no response table) a concrete
|
|
267
|
-
// review surface.
|
|
268
|
-
const scopeFiles = resolveScopeFiles(agent, paths)
|
|
269
|
-
const followUp = buildAdvisorFollowUp(agent, prior, scopeFiles, object)
|
|
270
|
-
// §11.2 D-24b (design round 2+ — async fix-round continuations must be able to
|
|
271
|
-
// re-approve): re-anchor the review scope (the convergence follow-up carries no
|
|
272
|
-
// document list) and inject the round's design token with the approval signal.
|
|
273
|
-
if (reviewType === "design") {
|
|
274
|
-
const docList = Array.isArray(documents)
|
|
275
|
-
? documents.filter((d) => typeof d === "string" && d.trim())
|
|
276
|
-
: []
|
|
277
|
-
const scopeBlock = docList.length > 0
|
|
278
|
-
? `\n\n## Documents to Review\nThe documents below are the review scope. Review ONLY these files — do not scan git diff or read any other files.\n${docList.map((d) => `- ${d} — Read this file in full`).join("\n")}`
|
|
279
|
-
: ""
|
|
280
|
-
const tokenBlock = designToken ? `\n\n${buildDesignApprovalBlock(designToken, designId)}` : ""
|
|
281
|
-
return [
|
|
282
|
-
{ role: "system", content: withTime(buildAdvisorSystemPrompt(agent, prior, reviewType)) },
|
|
283
|
-
{ role: "user", content: escapeLiteralEscapes(followUp + scopeBlock + tokenBlock) },
|
|
284
|
-
]
|
|
285
|
-
}
|
|
286
|
-
return [
|
|
287
|
-
{ role: "system", content: withTime(buildAdvisorSystemPrompt(agent, prior, reviewType)) },
|
|
288
|
-
{ role: "user", content: escapeLiteralEscapes(followUp) },
|
|
289
|
-
]
|
|
290
|
-
}
|