thincoder 0.12.1 → 0.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -5
- package/package.json +1 -1
- package/src/advisor/history.mjs +112 -0
- package/src/advisor/messages.mjs +182 -0
- package/src/advisor/repos.mjs +133 -0
- package/src/advisor/run.mjs +346 -0
- package/src/advisor.mjs +109 -509
- package/src/agent/completion.mjs +119 -0
- package/src/agent/dispatch.mjs +54 -7
- package/src/agent/post-turn.mjs +70 -0
- package/src/agent/setup.mjs +93 -5
- package/src/agent-tools/advisor.mjs +159 -12
- package/src/agent-tools/eng.mjs +64 -0
- package/src/agent-tools/subagent.mjs +73 -3
- package/src/agent-tools/task.mjs +45 -6
- package/src/agent-tools/verify.mjs +18 -0
- package/src/agent-tools.mjs +1 -0
- package/src/agent.mjs +110 -150
- package/src/cli/make-agent.mjs +1 -0
- package/src/cli/setup-wizard.mjs +1 -0
- package/src/config.mjs +22 -4
- package/src/prompts/advisor-design.md +43 -0
- package/src/prompts/advisor-round1.md +11 -4
- package/src/prompts/advisor-round2.md +12 -7
- package/src/prompts/advisor-round3.md +11 -6
- package/src/prompts/coder.md +9 -3
- package/src/prompts/discipline.md +12 -96
- package/src/prompts/eng-coder.md +34 -0
- package/src/prompts/engineering-sub.md +12 -0
- package/src/prompts/engineering.md +96 -0
- package/src/prompts/main.md +1 -1
- package/src/prompts/methodology-template.md +39 -0
- package/src/prompts/plan.md +2 -2
- package/src/prompts/system.md +43 -61
- package/src/session.mjs +270 -89
- package/src/skills.mjs +48 -15
- package/src/tools/apply_patch.md +1 -1
- package/src/tools/codemode.mjs +10 -4
- package/src/tools/delete.md +1 -0
- package/src/tools/edit.md +1 -1
- package/src/tools/execute.md +5 -0
- package/src/tools/file.mjs +4 -0
- package/src/tools/git.md +15 -0
- package/src/tools/git.mjs +1 -6
- package/src/tools/lint.md +8 -0
- package/src/tools/linter.mjs +1 -5
- package/src/tools/lsp.md +7 -0
- package/src/tools/lsp.mjs +8 -9
- package/src/tools/patch.mjs +1 -29
- package/src/tools/read_image.md +5 -1
- package/src/tools/system.mjs +1 -1
- package/src/tools/web.mjs +3 -3
- package/src/tui/agent-turn.mjs +169 -66
- package/src/tui/cmd-config.mjs +12 -0
- package/src/tui/cmd-eng.mjs +44 -0
- package/src/tui/cmd-exit.mjs +6 -16
- package/src/tui/cmd-fold.mjs +3 -4
- package/src/tui/cmd-model.mjs +11 -6
- package/src/tui/cmd-new.mjs +5 -5
- package/src/tui/cmd-session.mjs +21 -11
- package/src/tui/cmd-think.mjs +1 -0
- package/src/tui/index.mjs +7 -6
- package/src/tui/key-handler.mjs +132 -4
- package/src/tui/layout.mjs +5 -5
- package/src/tui/pickers.mjs +184 -44
- package/src/tui/render-conversation.mjs +49 -11
- package/src/tui/render-frame.mjs +38 -12
- package/src/tui/render-loop.mjs +2 -1
- package/src/tui/slash-commands.mjs +11 -7
- package/src/tui/startup.mjs +4 -3
- package/src/tui/wizard.mjs +3 -0
- package/src/tools/checkpoint.md +0 -15
- package/src/tools/git_diff.md +0 -11
- package/src/tools/git_log.md +0 -10
- package/src/tools/git_status.md +0 -8
- package/src/tools/linter.md +0 -13
- package/src/tools/syntax_check.md +0 -10
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* advisor/run.mjs — advisor execution: tool loop, provider resolution, and the review entry point.
|
|
3
|
+
* Message building lives in advisor.mjs; git collection in advisor/repos.mjs.
|
|
4
|
+
*/
|
|
5
|
+
import { chat } from "../provider/core.mjs"
|
|
6
|
+
import { findProvider } from "../config.mjs"
|
|
7
|
+
import { toOpenAISchema } from "../tools/index.mjs"
|
|
8
|
+
import { prepareAdvisorMessages } from "../advisor.mjs"
|
|
9
|
+
import { extractPriorIssueTable } from "../advisor/history.mjs"
|
|
10
|
+
|
|
11
|
+
export const MAX_ADVISOR_TURNS = 100
|
|
12
|
+
// Mechanical convergence cap: the protocol assumes up to 5 rounds suffice
|
|
13
|
+
// (full review, verify+fix cycles, strict verification). A 6th call means the
|
|
14
|
+
// model is looping — refuse it instead of burning tokens on a review that cannot
|
|
15
|
+
// converge. Design reviews are exempt (each call resets the round).
|
|
16
|
+
export const MAX_ADVISOR_ROUNDS = 5
|
|
17
|
+
|
|
18
|
+
// Context window limits
|
|
19
|
+
const MAX_CONTEXT_TOKENS = 120_000 // 预留 headroom,避免 OOM
|
|
20
|
+
const TOOL_TIMEOUT_MS = 30_000 // 单个工具 30 秒
|
|
21
|
+
const REVIEW_TIMEOUT_MS = 300_000 // 整个审查 5 分钟
|
|
22
|
+
|
|
23
|
+
/** Estimate token count from messages (rough: 1 token ≈ 4 chars) */
|
|
24
|
+
function estimateTokens(messages) {
|
|
25
|
+
return messages.reduce((sum, msg) => {
|
|
26
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content || "")
|
|
27
|
+
const toolCalls = msg.tool_calls ? JSON.stringify(msg.tool_calls) : ""
|
|
28
|
+
return sum + Math.ceil((content.length + toolCalls.length) / 4)
|
|
29
|
+
}, 0)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Compact early messages when context grows too large */
|
|
33
|
+
async function compactMessages(messages, provider) {
|
|
34
|
+
// Keep: system prompt, last 10 assistant+tool pairs, user message
|
|
35
|
+
if (messages.length <= 20) return messages
|
|
36
|
+
|
|
37
|
+
const system = messages[0]
|
|
38
|
+
const recent = messages.slice(-20)
|
|
39
|
+
const old = messages.slice(1, -20)
|
|
40
|
+
|
|
41
|
+
// Summarize old messages
|
|
42
|
+
const summary = `Earlier exploration: ${old.length} tool calls completed. Key files examined: ${
|
|
43
|
+
old
|
|
44
|
+
.filter((m) => m.role === "tool")
|
|
45
|
+
.map((m) => m.content?.split("\n")[0]?.slice(0, 50))
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.slice(0, 5)
|
|
48
|
+
.join(", ")
|
|
49
|
+
}`
|
|
50
|
+
|
|
51
|
+
return [system, { role: "user", content: `[Context compacted] ${summary}` }, ...recent]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { gitTool, readTool, globTool, grepTool, lsTool } = await import("../tools/index.mjs")
|
|
55
|
+
const { lspTool } = await import("../tools/lsp.mjs")
|
|
56
|
+
const { codeModeTool: codeSearchTool } = await import("../tools/codemode.mjs")
|
|
57
|
+
|
|
58
|
+
/** Restricted git tool: diff / status / log only.
|
|
59
|
+
* Checkpoint create/rewind are blocked — the advisor must not mutate state. */
|
|
60
|
+
const advisorGitTool = {
|
|
61
|
+
...gitTool,
|
|
62
|
+
readonly: true,
|
|
63
|
+
async execute(args, ctx) {
|
|
64
|
+
if (args.action === "checkpoint") {
|
|
65
|
+
if (args.checkpointAction === "create" || args.checkpointAction === "rewind") {
|
|
66
|
+
return "Error: checkpoint create/rewind is disabled in advisor mode. Use diff/status/log only."
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return gitTool.execute(args, ctx)
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const ADVISOR_TOOLS = [readTool, globTool, grepTool, lsTool, advisorGitTool, lspTool, codeSearchTool]
|
|
74
|
+
const ADVISOR_TOOL_SCHEMAS = ADVISOR_TOOLS.map(toOpenAISchema)
|
|
75
|
+
const ADVISOR_TOOL_BY_NAME = new Map(ADVISOR_TOOLS.map((t) => [t.name, t]))
|
|
76
|
+
|
|
77
|
+
/** Compact one-line summary of tool args for panel progress lines.
|
|
78
|
+
* Picks the most identifying field; falls back to truncated JSON. */
|
|
79
|
+
function summarizeToolArgs(args) {
|
|
80
|
+
// e.g. "git diff HEAD", "read src/x.mjs" — action first when present
|
|
81
|
+
const parts = [args.action, args.path ?? args.pattern ?? args.command].filter((v) => v != null)
|
|
82
|
+
let s = parts.length > 0 ? parts.map(String).join(" ") : JSON.stringify(args)
|
|
83
|
+
s = s.replace(/\s+/g, " ").trim()
|
|
84
|
+
return s.length > 80 ? s.slice(0, 79) + "…" : s
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Run the advisor's tool loop: chat → execute tools → repeat.
|
|
89
|
+
* Stops when the model produces text without tool calls.
|
|
90
|
+
*
|
|
91
|
+
* Progress lines (→ tool args) are emitted via onOutput between model bursts so
|
|
92
|
+
* the panel keeps moving while the advisor explores — otherwise the panel sits
|
|
93
|
+
* frozen through every tool-call phase and the review appears to have stalled.
|
|
94
|
+
*/
|
|
95
|
+
async function runAdvisorToolLoop(provider, messages, onOutput, signal, agent, cwd) {
|
|
96
|
+
// Kind-tagged wrappers: the TUI panel colors reasoning / answer / tool progress differently.
|
|
97
|
+
const emit = (kind) => (onOutput ? (text) => onOutput({ kind, text }) : undefined)
|
|
98
|
+
const onThink = emit("think")
|
|
99
|
+
const onText = emit("text")
|
|
100
|
+
let turns = 0
|
|
101
|
+
const startTime = Date.now()
|
|
102
|
+
|
|
103
|
+
while (true) {
|
|
104
|
+
// Interrupted (Ctrl+I) — stop immediately instead of spinning a fresh uncancellable signal
|
|
105
|
+
if (signal?.aborted) return "Advisor: interrupted."
|
|
106
|
+
|
|
107
|
+
// Check review timeout (5 minutes)
|
|
108
|
+
if (Date.now() - startTime > REVIEW_TIMEOUT_MS) {
|
|
109
|
+
return `Advisor: review timeout after ${Math.round(REVIEW_TIMEOUT_MS / 1000)}s. Partial results may be available. Try again with a narrower scope.`
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (++turns > MAX_ADVISOR_TURNS) {
|
|
113
|
+
return "Advisor: stopped after " + MAX_ADVISOR_TURNS + " tool rounds — the review appears to be looping. You may retry with a narrower scope."
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check context window and compact if needed
|
|
117
|
+
const currentTokens = estimateTokens(messages)
|
|
118
|
+
if (currentTokens > MAX_CONTEXT_TOKENS * 0.8) {
|
|
119
|
+
onOutput?.({ kind: "text", text: `\n[Context compacted: ${currentTokens} tokens → reducing to fit window]\n` })
|
|
120
|
+
messages = await compactMessages(messages, provider)
|
|
121
|
+
if (estimateTokens(messages) > MAX_CONTEXT_TOKENS) {
|
|
122
|
+
return `Advisor: context window limit reached (${currentTokens} tokens). Review incomplete — too many tool calls. Try a narrower scope.`
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const response = await chat(provider, {
|
|
127
|
+
messages,
|
|
128
|
+
tools: ADVISOR_TOOL_SCHEMAS,
|
|
129
|
+
signal: (signal && !signal.aborted) ? signal : null,
|
|
130
|
+
onToken: onText,
|
|
131
|
+
onReasoning: onThink,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
// No tool calls — this is the final review text
|
|
135
|
+
if (!response.toolCalls?.length) {
|
|
136
|
+
if (!response.content?.trim()) return "Advisor: (empty response — review was inconclusive)"
|
|
137
|
+
return response.content.trim()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Push assistant message with tool calls
|
|
141
|
+
messages.push({
|
|
142
|
+
role: "assistant",
|
|
143
|
+
content: response.content || null,
|
|
144
|
+
tool_calls: response.toolCalls.map((tc) => ({
|
|
145
|
+
id: tc.id, type: "function",
|
|
146
|
+
function: { name: tc.name, arguments: tc.arguments },
|
|
147
|
+
})),
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// Execute each tool call
|
|
151
|
+
for (const tc of response.toolCalls) {
|
|
152
|
+
const tool = ADVISOR_TOOL_BY_NAME.get(tc.name)
|
|
153
|
+
let args = {}
|
|
154
|
+
let parseError = null
|
|
155
|
+
try {
|
|
156
|
+
args = JSON.parse(tc.arguments || "{}")
|
|
157
|
+
} catch (e) {
|
|
158
|
+
parseError = `Error: invalid JSON in tool arguments: ${e.message}\nRaw arguments: ${(tc.arguments || "").slice(0, 200)}`
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If parse failed, return error to model immediately
|
|
162
|
+
if (parseError) {
|
|
163
|
+
messages.push({ role: "tool", tool_call_id: tc.id, content: parseError })
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
onOutput?.({ kind: "tool", text: `\n→ ${tc.name} ${summarizeToolArgs(args)}\n` })
|
|
168
|
+
let result
|
|
169
|
+
if (!tool) {
|
|
170
|
+
result = `Error: unknown tool "${tc.name}". Available: ${[...ADVISOR_TOOL_BY_NAME.keys()].join(", ")}`
|
|
171
|
+
} else {
|
|
172
|
+
// Execute with timeout
|
|
173
|
+
try {
|
|
174
|
+
const toolPromise = tool.execute(args, {
|
|
175
|
+
cwd,
|
|
176
|
+
agent,
|
|
177
|
+
onOutput,
|
|
178
|
+
signal,
|
|
179
|
+
})
|
|
180
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
181
|
+
setTimeout(() => reject(new Error(`tool timeout after ${TOOL_TIMEOUT_MS}ms`)), TOOL_TIMEOUT_MS)
|
|
182
|
+
)
|
|
183
|
+
result = await Promise.race([toolPromise, timeoutPromise])
|
|
184
|
+
} catch (e) {
|
|
185
|
+
const errorType = e.message.includes("timeout") ? "timeout"
|
|
186
|
+
: e.message.includes("ENOENT") ? "file_not_found"
|
|
187
|
+
: e.message.includes("permission") ? "permission_denied"
|
|
188
|
+
: "execution_error"
|
|
189
|
+
result = `Error (${errorType}): ${e.message}`
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (typeof result !== "string") result = JSON.stringify(result)
|
|
193
|
+
|
|
194
|
+
// Line-aware truncation: preserve line integrity
|
|
195
|
+
const MAX_RESULT_CHARS = 12_000
|
|
196
|
+
if (result.length > MAX_RESULT_CHARS) {
|
|
197
|
+
const lines = result.split("\n")
|
|
198
|
+
let truncated = ""
|
|
199
|
+
let charCount = 0
|
|
200
|
+
let keptLines = 0
|
|
201
|
+
|
|
202
|
+
for (let i = 0; i < lines.length; i++) {
|
|
203
|
+
const line = lines[i]
|
|
204
|
+
if (charCount + line.length + 1 > MAX_RESULT_CHARS) break
|
|
205
|
+
truncated += line + "\n"
|
|
206
|
+
charCount += line.length + 1
|
|
207
|
+
keptLines++
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const remainingLines = lines.length - keptLines
|
|
211
|
+
result = (
|
|
212
|
+
truncated +
|
|
213
|
+
`\n… (truncated: ${remainingLines} more lines, ${result.length} chars total)\n` +
|
|
214
|
+
`To see more content, use: read(path, offset=${keptLines}, limit=200)`
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
messages.push({ role: "tool", tool_call_id: tc.id, content: result })
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Resolve the advisor's provider: cfg.provider/model when set, otherwise the main agent's provider */
|
|
224
|
+
export function resolveAdvisorProvider(agent) {
|
|
225
|
+
const cfg = agent.config?.advisor
|
|
226
|
+
if (cfg?.provider) {
|
|
227
|
+
try {
|
|
228
|
+
const provider = findProvider(agent.providers ?? [agent.provider], cfg.provider)
|
|
229
|
+
const result = cfg.model ? { ...provider, model: cfg.model } : { ...provider }
|
|
230
|
+
if (cfg.thinking === null) result.thinking = undefined // explicitly off
|
|
231
|
+
else if (cfg.thinking !== undefined) result.thinking = cfg.thinking
|
|
232
|
+
if (cfg.reasoningEffort !== undefined) result.reasoningEffort = cfg.reasoningEffort
|
|
233
|
+
return result
|
|
234
|
+
} catch (e) {
|
|
235
|
+
// Provider not found or lookup failed — fall back to main provider, but surface the reason
|
|
236
|
+
console.warn(`[advisor] resolveAdvisorProvider: ${e.message}`)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const provider = { ...agent.provider }
|
|
240
|
+
if (cfg?.model) provider.model = cfg.model
|
|
241
|
+
if (cfg?.thinking === null) provider.thinking = undefined // explicitly off
|
|
242
|
+
else if (cfg?.thinking !== undefined) provider.thinking = cfg.thinking
|
|
243
|
+
if (cfg?.reasoningEffort !== undefined) provider.reasoningEffort = cfg.reasoningEffort
|
|
244
|
+
return provider
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Extract unfixed issues from prior review text
|
|
249
|
+
*/
|
|
250
|
+
function extractUnfixedIssues(priorText) {
|
|
251
|
+
if (!priorText) return []
|
|
252
|
+
const lines = priorText.split("\n")
|
|
253
|
+
return lines
|
|
254
|
+
.filter((line) => /\|\s*\d+\s*\|/.test(line)) // 匹配表格行
|
|
255
|
+
.filter((line) => !/fixed|resolved|done|✓|✔/i.test(line))
|
|
256
|
+
.map((line) => line.replace(/\|/g, "").trim())
|
|
257
|
+
.filter(Boolean)
|
|
258
|
+
.slice(0, 10) // 最多显示 10 个
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Run an advisor review. reviewType: "code" (default) or "design". Returns review text or null when skipped.
|
|
263
|
+
* @param {string|null} [designToken] — injected into the design-review prompt; the advisor echoes it only on approval.
|
|
264
|
+
* @param {string[]|null} [documents] — design review only: explicit list of doc paths to review; passed through to the message builder.
|
|
265
|
+
*/
|
|
266
|
+
export async function runAdvisorReview(agent, reviewType, callbacks, designToken = null, documents = null, paths = null) {
|
|
267
|
+
const onOutput = callbacks?.onOutput
|
|
268
|
+
const signal = callbacks?.signal
|
|
269
|
+
const cfg = agent.config?.advisor
|
|
270
|
+
const startTime = Date.now()
|
|
271
|
+
|
|
272
|
+
// Engineering mode overrides advisor toggle — reviews are mandatory regardless
|
|
273
|
+
if (!cfg?.enabled && !agent.config?.agent?.engineering) {
|
|
274
|
+
return "Advisor: not enabled (set advisor.enabled in config.json)."
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Mechanical convergence cap — refuse further reviews once the protocol has run
|
|
278
|
+
// its rounds. _advisorRound counts completed advisor calls (incremented by the
|
|
279
|
+
// agent after each one), so >= MAX_ADVISOR_ROUNDS blocks the next call.
|
|
280
|
+
if (reviewType !== "design" && (agent._advisorRound || 0) >= MAX_ADVISOR_ROUNDS) {
|
|
281
|
+
// 提取未解决的问题,给出更具体的指导
|
|
282
|
+
const prior = extractPriorIssueTable(agent.history)
|
|
283
|
+
const unfixed = prior ? extractUnfixedIssues(prior.text) : []
|
|
284
|
+
|
|
285
|
+
let message = `Advisor: convergence cap reached after ${MAX_ADVISOR_ROUNDS} rounds.\n`
|
|
286
|
+
if (unfixed.length > 0) {
|
|
287
|
+
message += `\nUnresolved issues from prior rounds:\n${unfixed.map((i) => `- ${i}`).join("\n")}\n`
|
|
288
|
+
} else {
|
|
289
|
+
message += "\nAll prior issues appear resolved.\n"
|
|
290
|
+
}
|
|
291
|
+
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"
|
|
292
|
+
|
|
293
|
+
return message
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const provider = resolveAdvisorProvider(agent)
|
|
297
|
+
// Advisor always works in the agent's cwd — scope is defined by paths/documents.
|
|
298
|
+
const advisorCwd = agent.cwd
|
|
299
|
+
|
|
300
|
+
const messages = prepareAdvisorMessages(agent, reviewType, designToken, documents, paths)
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
const result = await runAdvisorToolLoop(provider, messages, onOutput, signal, agent, advisorCwd)
|
|
304
|
+
|
|
305
|
+
// Log review statistics for observability
|
|
306
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000)
|
|
307
|
+
const toolCallCount = messages.filter((m) => m.role === "tool").length
|
|
308
|
+
const tokensUsed = estimateTokens(messages)
|
|
309
|
+
onOutput?.({
|
|
310
|
+
kind: "text",
|
|
311
|
+
text: `\n[advisor] Review completed: ${elapsed}s, ${toolCallCount} tool calls, ~${Math.round(tokensUsed / 1000)}k tokens\n`,
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
// Only persist the session on success — timeout/interrupt/empty results
|
|
315
|
+
// would poison the next review call (the conversation is truncated mid-review,
|
|
316
|
+
// and the model picks up from a broken state, burning more rounds).
|
|
317
|
+
if (!result.trimStart().startsWith("Advisor:")) {
|
|
318
|
+
// Assign only for fresh sessions; re-assignment of the same reference on
|
|
319
|
+
// continued sessions is a no-op but communicating intent matters.
|
|
320
|
+
agent._advisorSession = reviewType === "design" ? null : messages
|
|
321
|
+
} else {
|
|
322
|
+
agent._advisorSession = null
|
|
323
|
+
}
|
|
324
|
+
return result
|
|
325
|
+
} catch (e) {
|
|
326
|
+
if (e.name === "AbortError" && signal?.reason?.interrupt) throw e
|
|
327
|
+
|
|
328
|
+
// 细化错误类型
|
|
329
|
+
const errorType = e.message.includes("rate limit") || e.message.includes("429") ? "rate limit"
|
|
330
|
+
: e.message.includes("timeout") ? "timeout"
|
|
331
|
+
: e.message.includes("network") || e.message.includes("ECONNREFUSED") ? "network"
|
|
332
|
+
: e.message.includes("context length") ? "context_too_long"
|
|
333
|
+
: "unknown"
|
|
334
|
+
|
|
335
|
+
const retryAdvice = errorType === "rate limit"
|
|
336
|
+
? "Wait a moment and retry. Consider using a cheaper model for advisor."
|
|
337
|
+
: errorType === "timeout"
|
|
338
|
+
? "The model took too long. Try with a narrower scope."
|
|
339
|
+
: errorType === "context_too_long"
|
|
340
|
+
? "Reduce the scope (fewer files/paths) or use a model with larger context window."
|
|
341
|
+
: "You may retry or proceed to verify manually."
|
|
342
|
+
|
|
343
|
+
agent._advisorSession = null // failed review: don't keep a half-built conversation
|
|
344
|
+
return `Advisor: review failed (${errorType}) — ${e.message || "unknown error"}. ${retryAdvice}`
|
|
345
|
+
}
|
|
346
|
+
}
|