thincoder 0.10.0 → 0.11.1

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.
Files changed (64) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/advisor.mjs +360 -72
  4. package/src/agent/helpers.mjs +7 -3
  5. package/src/agent/setup.mjs +2 -2
  6. package/src/agent-tools/advisor.mjs +36 -0
  7. package/src/agent-tools/plan.mjs +53 -2
  8. package/src/agent-tools/subagent.mjs +7 -1
  9. package/src/agent-tools/timer.mjs +1 -1
  10. package/src/agent-tools/verify.mjs +1 -0
  11. package/src/agent-tools.mjs +1 -0
  12. package/src/agent.mjs +73 -21
  13. package/src/auto-think.mjs +23 -5
  14. package/src/cli/make-agent.mjs +7 -0
  15. package/src/config.mjs +47 -20
  16. package/src/prompts/advisor-round1.md +23 -0
  17. package/src/prompts/advisor-round2.md +26 -0
  18. package/src/prompts/advisor-round3.md +24 -0
  19. package/src/prompts/coder.md +6 -2
  20. package/src/prompts/discipline.md +23 -6
  21. package/src/prompts/explore.md +2 -0
  22. package/src/prompts/plan.md +2 -0
  23. package/src/prompts/system.md +13 -6
  24. package/src/provider/anthropic.mjs +190 -0
  25. package/src/provider/core.mjs +42 -130
  26. package/src/provider/google.mjs +199 -0
  27. package/src/provider/sse.mjs +112 -0
  28. package/src/proxy.mjs +236 -0
  29. package/src/tools/bash.md +8 -0
  30. package/src/tools/codemode.mjs +5 -16
  31. package/src/tools/edit.md +8 -0
  32. package/src/tools/fetch.md +2 -1
  33. package/src/tools/git.mjs +125 -156
  34. package/src/tools/index.mjs +9 -9
  35. package/src/tools/linter.mjs +46 -32
  36. package/src/tools/read.md +7 -0
  37. package/src/tools/shared.mjs +16 -0
  38. package/src/tools/system.mjs +14 -10
  39. package/src/tools/web.mjs +115 -89
  40. package/src/tools/websearch.md +5 -3
  41. package/src/tui/agent-turn.mjs +86 -75
  42. package/src/tui/cmd-advisor.mjs +138 -49
  43. package/src/tui/cmd-clear.mjs +11 -17
  44. package/src/tui/cmd-config.mjs +226 -142
  45. package/src/tui/cmd-extract.mjs +1 -1
  46. package/src/tui/cmd-fold.mjs +2 -3
  47. package/src/tui/cmd-goal.mjs +58 -27
  48. package/src/tui/cmd-help.mjs +3 -1
  49. package/src/tui/cmd-mcp.mjs +178 -142
  50. package/src/tui/cmd-model.mjs +15 -4
  51. package/src/tui/cmd-new.mjs +7 -13
  52. package/src/tui/cmd-restore.mjs +12 -16
  53. package/src/tui/cmd-session.mjs +28 -32
  54. package/src/tui/cmd-think.mjs +75 -50
  55. package/src/tui/cmd-undo.mjs +19 -23
  56. package/src/tui/cmd-upgrade.mjs +22 -26
  57. package/src/tui/index.mjs +61 -215
  58. package/src/tui/key-handler.mjs +56 -20
  59. package/src/tui/layout.mjs +13 -3
  60. package/src/tui/pickers.mjs +151 -182
  61. package/src/tui/render-conversation.mjs +92 -0
  62. package/src/tui/render-frame.mjs +56 -114
  63. package/src/tui/render-loop.mjs +181 -0
  64. package/src/tui/slash-commands.mjs +26 -16
@@ -29,7 +29,7 @@ export const timerTool = {
29
29
  readonly: true,
30
30
  sideEffectExempt: true,
31
31
  execute(args, ctx) {
32
- const seconds = args.seconds ?? 30
32
+ const seconds = args.seconds ?? 180
33
33
  const expiresAt = Date.now() + seconds * 1000
34
34
  const message = args.message || `⏰ Time's up (${seconds}s). Have you tried running the code, adding a console.log, or checking the output? Thinking more without data is guessing.`
35
35
 
@@ -19,6 +19,7 @@ const MODULE_TO_TEST = {
19
19
  skills: "test/tools.test.mjs",
20
20
  distill: "test/tools.test.mjs",
21
21
  markdown: "test/agent.test.mjs",
22
+ advisor: "test/advisor.test.mjs",
22
23
  mcp: null,
23
24
  prompts: null,
24
25
  context: null,
@@ -11,3 +11,4 @@ export { goalTool } from "./agent-tools/goal.mjs"
11
11
  export { verifyTool } from "./agent-tools/verify.mjs"
12
12
  export { recentChangesTool } from "./agent-tools/recent-changes.mjs"
13
13
  export { timerTool } from "./agent-tools/timer.mjs"
14
+ export { advisorTool } from "./agent-tools/advisor.mjs"
package/src/agent.mjs CHANGED
@@ -31,7 +31,7 @@ export const EXPLORE_OVERLAY = _EXPLORE
31
31
  export const CODER_OVERLAY = _CODER
32
32
  export const PLAN_OVERLAY = _PLAN
33
33
 
34
- // Re-exported for consumption by agent-tools.mjs
34
+ // exported for consumption by agent-tools.mjs
35
35
  export {
36
36
  ContinueError,
37
37
  repairHistory, listWorkDir, loadProjectInstructions,
@@ -61,8 +61,8 @@ export function createAgent({
61
61
  provider, tools, config, cwd, memory, _role: role,
62
62
  overlay, tasks, history,
63
63
  planMode, autoApprove, goal,
64
- _mutatedThisRun: false, _verifiedThisRun: false, _verifyPassed: undefined,
65
- _touchedFiles: [], _verifyRetries: 0,
64
+ _mutatedThisRun: false, _verifiedThisRun: false, _verifyPassed: undefined, _calledAdvisorThisRun: false,
65
+ _touchedFiles: [], _verifyRetries: 0, _advisorRound: 0,
66
66
  _pendingReminders: [],
67
67
  _pendingTimers: [],
68
68
  _sessionStart: sessionStart,
@@ -81,11 +81,16 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
81
81
  agent._mutatedThisRun = false
82
82
  agent._verifiedThisRun = false
83
83
  agent._verifyPassed = undefined
84
+ agent._calledAdvisorThisRun = false
84
85
  agent._touchedFiles = []
85
86
  agent._verifyRetries = 0
87
+ agent._advisorRound = 0
86
88
  let guardPushbacks = 0
87
89
  let honestReminderInjected = false
88
90
  const recentCallSigs = []
91
+ // repeat: "once" stream rules fire at most once per runAgent call (user turn):
92
+ // this set survives across chat() calls (rule abort-retry, tool loop) within the turn.
93
+ const streamRuleFired = new Set()
89
94
 
90
95
  for (let turn = 0; turn < maxTurns; turn++) {
91
96
 
@@ -94,6 +99,7 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
94
99
  try {
95
100
  if (await compressIfNeeded(agent, threshold, callbacks)) {
96
101
  agent._compressFailures = 0
102
+ agent._planReminderAtLen = 0 // After compression history shrinks, reset cadence so reminders resume
97
103
  recentCallSigs.length = 0 // After compression history is rebuilt, reset stall detection counter
98
104
  callbacks.onCompress?.()
99
105
  if (agent.autoApprove && !agent.history.some((m) => m.content === AUTO_REMINDER)) {
@@ -111,6 +117,24 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
111
117
  }
112
118
  }
113
119
 
120
+ // Plan-mode reminder cadence: re-inject constraint reminders while plan mode is active
121
+ // (sparse every 2 turns, full every 5 turns or when the user sends a new message),
122
+ // so the read-only restriction never fades from context.
123
+ if (agent.planMode) {
124
+ const lastMsg = agent.history.at(-1)
125
+ const realUserMsg = lastMsg?.role === "user"
126
+ && typeof lastMsg.content === "string"
127
+ && !lastMsg.content.startsWith("[System reminder:")
128
+ && !lastMsg.content.startsWith("[User interrupt:")
129
+ const newUserSince = realUserMsg && agent.history.length > (agent._planReminderAtLen ?? 0)
130
+ const { planReminderForTurn } = await import("./agent-tools/plan.mjs")
131
+ const reminder = planReminderForTurn(agent, newUserSince)
132
+ if (reminder) {
133
+ agent._planReminderAtLen = agent.history.length + 1
134
+ agent.history.push({ role: "user", content: reminder, transient: true })
135
+ }
136
+ }
137
+
114
138
  const messages = [{ role: "system", content: systemPrompt }, ...agent.history]
115
139
  let response
116
140
 
@@ -129,15 +153,18 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
129
153
  onWait: callbacks.onWait,
130
154
  signal,
131
155
  streamRules: agent.config.agent?.streamRules ?? [],
156
+ firedPatterns: streamRuleFired,
132
157
  })
133
158
  } catch (e) {
134
159
  // User interrupt (Ctrl+I): controller.abort({ interrupt: true, message: "…" }).
135
160
  // Inject the message into history and let the outer loop recreate the controller.
136
161
  if (e.name === "AbortError" && signal?.reason?.interrupt) {
137
- agent.history.push({
138
- role: "user",
139
- content: `[User interrupt: ${signal.reason.message}]`,
140
- })
162
+ const msg = `[User interrupt: ${signal.reason.message}]`
163
+ // Dedup: if the interrupt was already handled during tool execution (L302-310),
164
+ // don't push a duplicate — the outer loop will still recreate the controller.
165
+ if (agent.history.at(-1)?.content !== msg) {
166
+ agent.history.push({ role: "user", content: msg })
167
+ }
141
168
  }
142
169
  throw e
143
170
  }
@@ -219,6 +246,7 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
219
246
  role: "user",
220
247
  content: `[System reminder: you still have pending tasks: ${pending}. Update their status with the task tool before finishing — if they're done, mark them done; if they're not applicable, remove them.]`,
221
248
  })
249
+ callbacks.onTurnEnd?.(agent, turn)
222
250
  continue
223
251
  }
224
252
  // --- verify guard: push model to verify mutated files before completion ---
@@ -230,15 +258,15 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
230
258
  role: "user",
231
259
  content: "[System reminder: you modified files in this run but have not verified the changes. Before finishing: call the verify tool to run syntax checks and tests. If verify reports failures, fix them and run verify again. If verification is genuinely impossible here, say so explicitly in your reply.]",
232
260
  })
261
+ callbacks.onTurnEnd?.(agent, turn)
233
262
  continue
234
- }
235
- if (agent._verifiedThisRun && agent._verifyPassed === false && agent._verifyRetries < MAX_VERIFY_RETRIES) {
236
263
  agent._verifyRetries++
237
264
  agent.history.push({ role: "assistant", content: response.content })
238
265
  agent.history.push({
239
266
  role: "user",
240
267
  content: `[System reminder: verify reported test failures (retry ${agent._verifyRetries}/${MAX_VERIFY_RETRIES}). Review the failures, fix the issues, then run verify again. If you cannot fix after ${MAX_VERIFY_RETRIES} attempts, explain honestly what's blocking you.]`,
241
268
  })
269
+ callbacks.onTurnEnd?.(agent, turn)
242
270
  continue
243
271
  }
244
272
  if (agent._verifyPassed === false && agent._verifyRetries >= MAX_VERIFY_RETRIES) {
@@ -252,6 +280,22 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
252
280
  role: "user",
253
281
  content: `[System reminder: ${MAX_VERIFY_RETRIES} verify attempts exhausted and tests are still failing. In your response to the user, you MUST state explicitly: (1) what tests are still failing, (2) what you tried, (3) what you believe the root cause is. Do not present this as complete — the user needs to know the work is unfinished.]`,
254
282
  })
283
+ callbacks.onTurnEnd?.(agent, turn)
284
+ continue
285
+ }
286
+ }
287
+ // --- advisor guard: push model to review changes before completion ---
288
+ // When advisor is enabled and guard is not explicitly disabled, mutated files
289
+ // must be reviewed before the turn ends. No hard round cap — convergence protocol
290
+ // (round 3+ strict verification) naturally limits divergence.
291
+ if (depth === 0 && agent.config?.advisor?.enabled && agent.config?.advisor?.guard !== false) {
292
+ if (agent._mutatedThisRun && !agent._calledAdvisorThisRun && (agent._touchedFiles ?? []).length > 0) {
293
+ agent.history.push({ role: "assistant", content: response.content })
294
+ agent.history.push({
295
+ role: "user",
296
+ content: `[System reminder: you changed code in this run but haven't reviewed with advisor (round ${agent._advisorRound + 1}). Call the \`advisor\` tool to get an independent code review. After the review, produce a response table for every issue found (see discipline rules for format). If the changes are trivial (typo, one-liner, formatting only), you may skip and explain why in your reply.]`,
297
+ })
298
+ callbacks.onTurnEnd?.(agent, turn)
255
299
  continue
256
300
  }
257
301
  }
@@ -276,6 +320,17 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
276
320
 
277
321
  const results = await executeToolCalls(agent, toolByName, response.toolCalls, callbacks, depth, signal)
278
322
 
323
+ // Ctrl+I interrupt during tool execution: skip committing partial results —
324
+ // the tool failure messages would mislead the model. Inject the interrupt and retry.
325
+ if (signal?.reason?.interrupt) {
326
+ agent.history.push({
327
+ role: "user",
328
+ content: `[User interrupt: ${signal.reason.message}]`,
329
+ })
330
+ callbacks.onTurnEnd?.(agent, turn)
331
+ continue
332
+ }
333
+
279
334
  // Model is executing tools → doing real work, reset guard pushback counter
280
335
  guardPushbacks = 0
281
336
 
@@ -308,8 +363,16 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
308
363
  }
309
364
  agent.history.push({ role: "tool", tool_call_id: toolCall.id, content: result })
310
365
  if (tool && ok) {
311
- if (!tool.readonly && !tool.sideEffectExempt) agent._mutatedThisRun = true
366
+ if (!tool.readonly && !tool.sideEffectExempt) {
367
+ agent._mutatedThisRun = true
368
+ // Any mutation after advisor invalidates the review — need re-review
369
+ if (agent._calledAdvisorThisRun) agent._calledAdvisorThisRun = false
370
+ }
312
371
  if (toolCall.name === "verify") agent._verifiedThisRun = true
372
+ if (toolCall.name === "advisor") {
373
+ agent._calledAdvisorThisRun = true
374
+ agent._advisorRound++ // advance convergence round
375
+ }
313
376
  if (FILE_MUTATORS.has(toolCall.name)) {
314
377
  const args = JSON.parse(toolCall.arguments)
315
378
  const paths = tool.touchedPaths ? tool.touchedPaths(args) : [args.path]
@@ -386,17 +449,6 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
386
449
  }
387
450
 
388
451
  callbacks.onTurnEnd?.(agent, turn)
389
-
390
- // Advisor: automated code review after each tool-execution turn.
391
- // Runs asynchronously — failure is silent, main loop continues regardless.
392
- if (agent.config?.advisor?.enabled) {
393
- const { runAdvisor } = await import("./advisor.mjs")
394
- const note = await runAdvisor(agent)
395
- if (note) {
396
- agent.history.push({ role: "user", content: note })
397
- callbacks.onAdvisor?.(note)
398
- }
399
- }
400
452
  }
401
453
 
402
454
  throw new ContinueError(maxTurns)
@@ -31,6 +31,26 @@ const EFFORT_MAP = {
31
31
  high: ["max", "max", "xhigh", "max"],
32
32
  }
33
33
 
34
+ /**
35
+ * Build classifier input from history: the latest real user message (reminders and
36
+ * interrupt injections excluded), plus the previous user message as context when the
37
+ * latest is too short to classify on its own (e.g. "继续" / "还有几个问题").
38
+ * Exported for tests.
39
+ */
40
+ export function buildClassifierInput(history) {
41
+ const isRealUser = (m) =>
42
+ m.role === "user" && typeof m.content === "string"
43
+ && !m.content.startsWith("[System reminder:") && !m.content.startsWith("[User interrupt:")
44
+ const users = history.filter(isRealUser)
45
+ const last = users.at(-1)
46
+ if (!last) return null
47
+ let prompt = last.content
48
+ if (prompt.length < 200 && users.length > 1) {
49
+ prompt = `Previous request (context):\n${users.at(-2).content.slice(0, 1200)}\n\nLatest message:\n${prompt}`
50
+ }
51
+ return prompt.slice(0, 2000)
52
+ }
53
+
34
54
  /**
35
55
  * Classify the difficulty of the user's prompt and adjust reasoning effort.
36
56
  * Only runs on the first turn (turn === 0) of a user message.
@@ -47,10 +67,8 @@ export async function classifyAndApply(agent, turn) {
47
67
  const validEfforts = spec.reasoningEffortEnum
48
68
  if (!validEfforts) return null // Model doesn't support reasoning effort
49
69
 
50
- // Get the last user message (should be the most recent history entry or the input)
51
- const lastUser = [...agent.history].reverse().find(m => m.role === "user")
52
- if (!lastUser) return null
53
- const prompt = typeof lastUser.content === "string" ? lastUser.content : ""
70
+ const prompt = buildClassifierInput(agent.history)
71
+ if (prompt == null) return null
54
72
 
55
73
  // Classification call: use same provider, minimal tokens, no tools, no streaming
56
74
  let level
@@ -59,7 +77,7 @@ export async function classifyAndApply(agent, turn) {
59
77
  const response = await chat(classifierProvider, {
60
78
  messages: [
61
79
  { role: "system", content: CLASSIFY_PROMPT },
62
- { role: "user", content: prompt.slice(0, 2000) },
80
+ { role: "user", content: prompt },
63
81
  ],
64
82
  tools: [],
65
83
  signal: AbortSignal.timeout(5_000),
@@ -12,6 +12,13 @@ export async function assembleAgent() {
12
12
  const config = loadConfig()
13
13
  const provider = config.provider
14
14
  const providers = config.providersList
15
+
16
+ // Inject proxy URI into providers (double opt-in: provider.proxy + config.proxy.model)
17
+ const { injectProxy } = await import("../proxy.mjs")
18
+ injectProxy(providers, config)
19
+ // config.provider 是 loadConfig 里的独立拷贝,同步注入结果
20
+ provider.proxyUri = providers.find((p) => p.name === config.activeProvider)?.proxyUri
21
+
15
22
  const memory = createMemory({ dbPath: config.memory.dbPath })
16
23
  // Vector retrieval: enabled if embedding is configured (lazy vector generation, computed on first search)
17
24
  if (config.embedding?.apiKey) {
package/src/config.mjs CHANGED
@@ -14,11 +14,16 @@ export const configPath = join(configDir, "config.json")
14
14
 
15
15
  /** Built-in provider presets: shared by /provider add <preset> and first-run wizard */
16
16
  export const PROVIDER_PRESETS = {
17
- deepseek: { baseURL: "https://api.deepseek.com/v1", model: "deepseek-v4-pro", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 393216, desc: "DeepSeek" },
17
+ deepseek: { baseURL: "https://api.deepseek.com", model: "deepseek-v4-pro", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 393216, desc: "DeepSeek" },
18
18
  kimi: { baseURL: "https://api.moonshot.cn/v1", model: "kimi-k3", thinking: null, reasoningEffort: "max", maxTokens: 131072, desc: "Kimi / Moonshot" },
19
- glm: { baseURL: "https://open.bigmodel.cn/api/paas/v4", model: "glm-5.2", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 131072, desc: "Zhipu GLM" },
19
+ glm: { baseURL: "https://open.bigmodel.cn/api/paas/v4", model: "glm-5.2", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 128000, desc: "Zhipu GLM" },
20
20
  qwen: { baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", model: "qwen3.7-max", maxTokens: 131072, desc: "Qwen / Alibaba" },
21
- minimax: { baseURL: "https://api.minimaxi.com/v1", model: "MiniMax-M3", thinking: { type: "adaptive" }, maxTokens: 131072, desc: "MiniMax" },
21
+ minimax: { baseURL: "https://api.minimaxi.com/v1", model: "MiniMax-M3", thinking: { type: "adaptive" }, maxTokens: 128000, chatPath: "/text/chatcompletion_v2", desc: "MiniMax" },
22
+ openai: { baseURL: "https://api.openai.com/v1", model: "gpt-4o", desc: "OpenAI" },
23
+ claude: { baseURL: "https://api.anthropic.com/v1", model: "claude-sonnet-4", format: "anthropic", maxTokens: 8192, desc: "Claude (Anthropic)" },
24
+ gemini: { baseURL: "https://generativelanguage.googleapis.com/v1beta", model: "gemini-2.5-flash", format: "google", maxTokens: 8192, desc: "Gemini (Google)" },
25
+ grok: { baseURL: "https://api.x.ai/v1", model: "grok-4.5", maxTokens: 65536, desc: "Grok (xAI)" },
26
+ mistral: { baseURL: "https://api.mistral.ai/v1", model: "mistral-large", maxTokens: 32768, desc: "Mistral" },
22
27
  }
23
28
 
24
29
  // Default provider matches deepseek preset (strip the desc display field)
@@ -34,7 +39,7 @@ const DEFAULTS = {
34
39
  compactThreshold: 100000,
35
40
  verifyGuard: false, // push model back to verify when files were mutated but verify not run (opt-in)
36
41
  streamRules: [], // time-traveling stream rules: [{ pattern: "regex", message: "reminder", action: "abort"|"warn", repeat: "always"|"once" }]
37
- advisor: { enabled: false }, // automated code review after each tool-execution turn; optionally: { enabled: true, provider: "deepseek", model: "deepseek-chat" }
42
+ advisor: { enabled: false }, // code review; { enabled: true, provider: "deepseek", model: "deepseek-chat", thinking: { type: "enabled" }, reasoningEffort: "max", guard: true }
38
43
  autoThink: false, // auto-classify task difficulty and set reasoning effort per-turn
39
44
  },
40
45
  memory: {
@@ -75,13 +80,13 @@ const MODEL_SPECS = [
75
80
  ["deepseek-reasoner", { context: 256_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "prompt", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["high", "max"], tempRange: [0, 2] }],
76
81
  ["deepseek-chat", { context: 256_000, maxOutput: 384_000, thinking: false, prefixMode: true, cacheMode: "prompt", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["high", "max"], tempRange: [0, 2] }],
77
82
  // Kimi series
78
- ["kimi-k3", { context: 1_000_000, maxOutput: 128_000, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
83
+ ["kimi-k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
79
84
  ["kimi-k2", { context: 256_000, maxOutput: 128_000, thinking: false, partialMode: true, multimodal: true, cacheMode: "none" }],
80
85
  ["moonshot", { context: 128_000, maxOutput: 32_000, thinking: false, cacheMode: "none" }],
81
86
  // GLM series
82
- ["glm-5.2", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["max", "xhigh", "high", "medium", "low", "minimal", "none"], tempRange: [0, 1] }],
83
- ["glm-5", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["max", "xhigh", "high", "medium", "low", "minimal", "none"], tempRange: [0, 1] }],
84
- ["glm-4", { context: 128_000, maxOutput: 32_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", tempRange: [0, 1] }],
87
+ ["glm-5.2", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["max", "xhigh", "high", "medium", "low", "minimal", "none"], tempRange: [0, 1], noUsageStream: true }],
88
+ ["glm-5", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["max", "xhigh", "high", "medium", "low", "minimal", "none"], tempRange: [0, 1], noUsageStream: true }],
89
+ ["glm-4", { context: 128_000, maxOutput: 32_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", tempRange: [0, 1], noUsageStream: true }],
85
90
  // GPT series
86
91
  ["gpt-4.1", { context: 1_000_000, maxOutput: 128_000, thinking: false, cacheMode: "prompt" }],
87
92
  ["gpt-4o", { context: 128_000, maxOutput: 16_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
@@ -93,18 +98,29 @@ const MODEL_SPECS = [
93
98
  ["qwen-plus", { context: 1_000_000, maxOutput: 32_000, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
94
99
  ["qwen", { context: 1_000_000, maxOutput: 128_000, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
95
100
  // MiniMax series
96
- ["MiniMax-M3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkOnValue: "adaptive", tempRange: [0, 2] }],
97
- ["minimax-m3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkOnValue: "adaptive", tempRange: [0, 2] }],
98
- ["minimax-m1", { context: 256_000, maxOutput: 128_000, thinking: false, cacheMode: "auto" }],
101
+ ["MiniMax-M3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkEnabledValue: "adaptive", tempRange: [0, 2], noUsageStream: true }],
102
+ ["minimax-m3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkEnabledValue: "adaptive", tempRange: [0, 2], noUsageStream: true }],
103
+ ["minimax-m1", { context: 256_000, maxOutput: 128_000, thinking: false, cacheMode: "auto", noUsageStream: true }],
104
+ // Grok series (xAI — OpenAI-compatible)
105
+ ["grok-4.5", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
106
+ ["grok-4", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
107
+ ["grok-4-mini", { context: 128_000, maxOutput: 16_000, thinking: false, tempRange: [0, 2] }],
108
+ // Mistral series (OpenAI-compatible)
109
+ ["mistral-large", { context: 128_000, maxOutput: 32_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
110
+ ["codestral", { context: 256_000, maxOutput: 32_000, thinking: false, tempRange: [0, 2] }],
111
+ // Claude series (Anthropic)
112
+ ["claude-opus-4", { context: 200_000, maxOutput: 32_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
113
+ ["claude-sonnet-4", { context: 200_000, maxOutput: 32_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
114
+ ["claude-3.5-haiku", { context: 200_000, maxOutput: 8_192, thinking: false, cacheMode: "none", format: "anthropic" }],
115
+ // Gemini series (Google)
116
+ ["gemini-2.5-pro", { context: 2_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
117
+ ["gemini-2.5-flash", { context: 1_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
99
118
  ]
100
119
  const DEFAULT_SPEC = { context: 128_000, maxOutput: 32_000, cacheMode: "none" }
101
- // Window utilization cap: 0.6 triggers earlier (reserving 40% headroom) because
102
- // injected context (directory tree, git context, outline, project instructions, memory/doc
103
- // search results) can consume 30-50K tokens each turn; waiting until 80% leaves no room.
104
- // For 1M-window models: 600K is still too high → cap at 300K.
120
+ // Window utilization threshold: compacts at 60% context, reserving 40% headroom
121
+ // for injected context (directory tree, git context, outline, project instructions,
122
+ // memory/doc search results) which can consume 30-50K tokens each turn.
105
123
  const COMPACT_RATIO = 0.6
106
- const COMPACT_CAP_TOKENS = 300_000
107
- const COMPACT_FLOOR = 40_000
108
124
 
109
125
  /** Look up spec by model name prefix (case-insensitive), conservative default for unknown models */
110
126
  export function specForModel(model) {
@@ -119,9 +135,7 @@ export function specForModel(model) {
119
135
  export function resolveCompactThreshold(explicit, model) {
120
136
  if (explicit != null) return { value: explicit, auto: false }
121
137
  const spec = specForModel(model)
122
- const ratioBased = Math.floor(spec.context * COMPACT_RATIO)
123
- // Cap for large-window models (1M) and floor for small-window models (<64K, should not compact too aggressively)
124
- const value = Math.max(Math.min(ratioBased, COMPACT_CAP_TOKENS), COMPACT_FLOOR)
138
+ const value = Math.floor(spec.context * COMPACT_RATIO)
125
139
  return { value, auto: true }
126
140
  }
127
141
 
@@ -140,6 +154,15 @@ export function findProvider(providers, name) {
140
154
  return providers[0] ?? { name: "default", baseURL: "", model: "" }
141
155
  }
142
156
 
157
+ /** Normalize proxy config to { uri, web, model } or undefined (uri/url both accepted; invalid types dropped) */
158
+ export function normalizeProxy(proxy) {
159
+ if (typeof proxy === "string") return proxy ? { uri: proxy, web: true, model: false } : undefined
160
+ if (!proxy || typeof proxy !== "object" || Array.isArray(proxy)) return undefined
161
+ const uri = proxy.uri || proxy.url || ""
162
+ if (typeof uri !== "string" || !uri) return undefined
163
+ return { uri, web: proxy.web !== false, model: proxy.model === true }
164
+ }
165
+
143
166
  /**
144
167
  * Load configuration.
145
168
  * Env var priority: THINCODER_ACTIVE_PROVIDER > config file activeProvider
@@ -170,6 +193,10 @@ export function loadConfig() {
170
193
  if (p.baseURL) p.baseURL = p.baseURL.replace(/\/+$/, "")
171
194
  }
172
195
 
196
+ // Normalize proxy: string → { uri, web:true, model:false }; object 补默认值;非法类型丢弃。
197
+ // 保证 agent.config.proxy 永远是规范形态或 undefined
198
+ merged.proxy = normalizeProxy(merged.proxy)
199
+
173
200
  // Env var overrides activeProvider
174
201
  if (process.env.THINCODER_ACTIVE_PROVIDER) {
175
202
  merged.activeProvider = process.env.THINCODER_ACTIVE_PROVIDER
@@ -0,0 +1,23 @@
1
+ You are a code review advisor.
2
+ Perform a full-scope review of the code changes.
3
+ You have read-only tools to explore the codebase.
4
+
5
+ Review workflow:
6
+ 1. Read AGENTS.md and any design documents to understand project conventions, version requirements, and architecture decisions.
7
+ 2. Run git diff HEAD to discover uncommitted changes.
8
+ 3. Read changed files for full context.
9
+ 4. Use grep or lsp to trace callers, imports, and dependencies.
10
+ 5. Produce your review table.
11
+
12
+ Rules:
13
+ - Reply in the same language as the task summary.
14
+ - Respect the project's stated platform requirements — do not flag features as errors if they are valid under the project's target environment.
15
+ - Output a Markdown table. This table becomes the sole basis for convergence in later rounds — be thorough.
16
+ | # | File | Severity | Issue | Suggestion |
17
+ |---|------|----------|-------|------------|
18
+ | 1 | src/x.mjs | 🔴 | ... | ... |
19
+ - Order by severity: 🔴 Critical · 🟡 Advisory · 🔵 Style.
20
+ - For each issue state: which file, what the problem is, why it is a problem, how to fix it.
21
+ - If the code is clean, say exactly: "No issues found — code quality looks good."
22
+ - Cover everything now. Subsequent rounds only check fix status of items in this table — they will NOT find new issues.
23
+ - Stop calling tools once you are ready to produce the review table.
@@ -0,0 +1,26 @@
1
+ You are a code review advisor.
2
+ Verify the prior issue table (provided in the review context).
3
+ You may note obvious new issues introduced by the fixes.
4
+ You have read-only tools to explore the codebase.
5
+
6
+ Review workflow:
7
+ 1. Read AGENTS.md and any design documents to understand project conventions, version requirements, and architecture decisions.
8
+ 2. Run git diff HEAD to see what changed since the last review.
9
+ 3. Read changed files for full context.
10
+ 4. Use grep or lsp to trace callers, imports, and dependencies.
11
+ 5. Produce your review table.
12
+
13
+ Rules:
14
+ - Respect the project's stated platform requirements — do not flag features as errors if they are valid under the project's target environment.
15
+ - Primarily check fix status of items in the prior issue table.
16
+ - For items marked "fixed": verify they were actually fixed.
17
+ - For items marked "not an issue": evaluate whether the reasoning is sound.
18
+ - You may flag obvious new problems — but only if clearly visible in the diff and would cause crashes, data loss, or logic errors.
19
+ - Do NOT nitpick style or naming.
20
+ - Output a Markdown table listing all remaining problems (old or new):
21
+ | # | Orig# | File | Severity | Status | Notes |
22
+ |---|-------|------|----------|--------|-------|
23
+ | 1 | 3 | src/x.mjs | 🔴 | Unfixed | ... |
24
+ | N | (new) | src/y.mjs | 🔴 | New: null check missing after fix | ... |
25
+ - If all issues are resolved, say exactly: "All issues resolved — review passed."
26
+ - Stop calling tools once you are ready to produce the review table.
@@ -0,0 +1,24 @@
1
+ You are a code review advisor.
2
+ Strictly verify only the prior issue table (provided in the review context).
3
+ Do NOT look for new issues.
4
+ You have read-only tools to explore the codebase.
5
+
6
+ Review workflow:
7
+ 1. Read AGENTS.md and any design documents to understand project conventions, version requirements, and architecture decisions.
8
+ 2. Run git diff HEAD to see what changed since the last review.
9
+ 3. Read changed files for full context.
10
+ 4. Verify fix status of each item in the prior issue table.
11
+ 5. Produce your review table.
12
+
13
+ Rules:
14
+ - Respect the project's stated platform requirements — do not flag features as errors if they are valid under the project's target environment.
15
+ - Only check fix status of items in the prior issue table.
16
+ - For items marked "fixed": verify they were actually fixed.
17
+ - For items marked "not an issue": evaluate whether the reasoning is sound.
18
+ - Output a Markdown table. Only list items that still have problems:
19
+ | # | Orig# | File | Severity | Status | Notes |
20
+ |---|-------|------|----------|--------|-------|
21
+ | 1 | 3 | src/x.mjs | 🔴 | Unfixed | ... |
22
+ | 2 | 5 | src/y.mjs | 🟡 | Reasoning invalid | ... |
23
+ - If all issues are resolved, say exactly: "All issues resolved — review passed."
24
+ - Stop calling tools once you are ready to produce the review table.
@@ -3,6 +3,7 @@ You are a coding subagent. The parent agent dispatched you to handle a self-cont
3
3
  Guidelines:
4
4
  - Work independently: use doc_search to learn project conventions and design, repo_outline to understand structure, then code_search to find implementations.
5
5
  Don't write code until you know what the project intends.
6
+ - MINIMAL changes: solve the task, nothing more. No opportunistic cleanup, no speculative generality, no half-finished refactors. Keep the diff small enough to review at a glance — the parent agent evaluates your work by reading the diff, and every unrelated change dilutes it.
6
7
  - Write code in small, verified steps — don't write multiple files at once without checking each along the way:
7
8
  1. After every write/edit of a file: run a syntax/lint check to catch parse errors immediately
8
9
  2. After a logical group of changes: run the relevant tests to confirm behavior
@@ -16,8 +17,11 @@ Guidelines:
16
17
  3. Read every file you changed — catch leftover debug code, stale comments, or incomplete edits
17
18
  4. Check that comments and docstrings match what the code actually does
18
19
  5. Verify imports/dependencies are correct — no stale or missing references
19
- - Your last message IS the report the parent sees — make it complete and self-contained
20
- - List every file you changed (with paths), why you changed it, and whether tests passed
20
+ - Your last message IS the report the parent sees — it is the ONLY thing the parent receives. Make it complete and self-contained. A report that fails this checklist is sent back for expansion, costing an extra turn:
21
+ 1. What you changed and why
22
+ 2. The path of every file you touched
23
+ 3. How you verified the change (tests run, commands executed, with results)
24
+ 4. Anything left undone or worth follow-up
21
25
 
22
26
  IMPORTANT — Tool permissions: when you see "permission denied by user" for a tool, it means the parent has not granted that tool.
23
27
  This is expected: your job is to write a detailed report of what SHOULD be done, not to force tool execution.
@@ -3,9 +3,12 @@ Coding discipline (rigor over speed—tokens spent on verification are well spen
3
3
  **Workflow — match the process to the task:**
4
4
  - Complex tasks (3+ distinct steps, architectural changes, new features): follow the full process — 1) Requirements, 2) Design, 3) Development, 4) Testing.
5
5
  In the Requirements step, identify affected users and scenarios: who calls this code? what workflows touch it? how does the change alter their experience?
6
- Write a design doc for step 2. Use the task tool to track progress.
7
- Use a checklist (`.thincoder/checklist.md`) to map requirements to verifiable items one entry per requirement point. Mark in_progress/done as you go; context compresses, the checklist persists.
8
- - Medium tasks (2-3 steps, localized refactoring, non-trivial bug fixes): plan briefly before coding — a few lines of approach is enough, no full design doc needed. Consider who is affected and whether the change alters user-facing behavior. Use a checklist for tracking.
6
+ Write a design doc for step 2.
7
+ Two tracking tools, two different purposesuse BOTH for complex work:
8
+ * `checklist` project-level deliverable tracking (persists to `.thincoder/checklist.md` across sessions). One entry per requirement point. This is what the user sees as "done."
9
+ * `task` — session-level step breakdown (in-memory, replaced each call). Exactly one item in_progress at a time. This is your working plan for THIS conversation.
10
+ Mark checklist items done when the deliverable is complete; mark task items done when the step is finished.
11
+ - Medium tasks (2-3 steps, localized refactoring, non-trivial bug fixes): plan briefly before coding — a few lines of approach is enough, no full design doc needed. Consider who is affected and whether the change alters user-facing behavior. Use the `task` tool to track steps; checklist is optional for medium tasks.
9
12
  - Small tasks (typo, one-line fix, trivial refactor): skip the ritual. Read the affected code, think about whether the change affects the user experience, make the change, syntax-check, verify. Done.
10
13
  - Never guess which tier a task belongs to — if unsure, treat it as complex. Under-planning costs far more than over-planning.
11
14
 
@@ -41,7 +44,7 @@ Coding discipline (rigor over speed—tokens spent on verification are well spen
41
44
  After making the change, update every dependent — no exceptions, no "I'll fix it later."
42
45
  A change that compiles but breaks callers is not a working change — it's a regression.
43
46
  This is not a suggestion. Modifying exports without tracing dependents is the single most common cause of incomplete work.
44
- - Before destructive operations (git reset, git clean, large-scale edits, applying a big patch): create a checkpoint (action=create) first. Uncommitted work is the most valuable thing in the repo — protect it before risking it.
47
+ - Before destructive operations (git reset, git clean, large-scale edits, applying a big patch): use `git action="checkpoint" checkpointAction="create"` first. Uncommitted work is the most valuable thing in the repo — protect it before risking it.
45
48
  - Deliver complete changes: no placeholder stubs, no "// rest unchanged", no TODO gaps left for the user to fill in.
46
49
  - Before finalizing any implementation, pause and think through edge cases: what could go wrong? what happens on failure? what boundary conditions exist?
47
50
  Reason about the failure modes — then handle or document the fallback.
@@ -56,7 +59,7 @@ Coding discipline (rigor over speed—tokens spent on verification are well spen
56
59
  Would someone USING this code find it intuitive, predictable, and consistent with the rest of the project?
57
60
 
58
61
  Testing discipline (right check at the right time):
59
- - After every write/edit of .mjs/.js files: call syntax_check immediately — it catches parse errors in milliseconds
62
+ - After every write/edit of code files: call `lint` immediately — it catches parse errors in milliseconds (node --check). Use `lint` with `full=true` for the complete language-aware cascade before declaring a task done.
60
63
  - Before declaring a coding task complete: call verify — it checks syntax on all changed files, automatically runs test files related to the changed modules, shows git diff, and displays a self-review checklist. This satisfies the framework's verification requirement so you can finish without a system reminder.
61
64
  - Run the full test suite (verify with full=true, or npm test directly) only when:
62
65
  a) You're about to commit or publish — final gate before code ships
@@ -65,9 +68,23 @@ Testing discipline (right check at the right time):
65
68
  - When verify reports "ACTION REQUIRED: write a test", stop. Do NOT proceed to "done." Write a test that validates the change, then re-run verify.
66
69
  - If verify reports syntax errors, test failures, or a missing-test warning, fix them before claiming completion — never mark work done with known failures.
67
70
  - When you change behavior or add code, add at least one test that covers the change. If no related test file exists for the module, create one. Untested code is incomplete code — the verify tool will enforce this.
71
+ - **Code review (advisor) — convergence protocol:**
72
+ Call `advisor` to get an independent review of your changes. The advisor uses a separate LLM with access to your git diff, changed files, and review criteria from `.thincoder/advisor.md`.
73
+ - **Round 1**: full-scope review. Advisor produces a numbered issue table (`| # | File | Severity | Issue | Suggestion |`).
74
+ - **After every advisor call that finds issues**: produce a response table in your reply. Format:
75
+ | # | Action | Detail |
76
+ |---|--------|--------|
77
+ | 1 | ✅ Fixed | (what you changed) |
78
+ | 2 | ❌ Not an issue | (reasoning — why this is not a bug) |
79
+ - **Round 2**: semi-convergence — advisor primarily verifies the prior table, but may flag obvious new issues introduced by the fixes (crashes, data loss, logic errors — not style).
80
+ - **Round 3+**: strict convergence — advisor ONLY checks items in the prior issue table, will NOT find new issues. The response table you wrote guides its verification.
81
+ - If advisor says "all clear": proceed to verify.
82
+ - If issues persist: fix them, update your response table, re-run advisor.
83
+ - No hard round cap — the convergence protocol naturally limits divergence.
84
+ - The advisor is optional if you only made trivial changes (typo, one-liner).
68
85
 
69
86
  Debugging strategy (when something goes wrong, three steps before anything else):
70
- - **Step 0 — Set a timer before you start reasoning**: immediately call `timer(30, "试试加个日志?")` to give yourself a bounded thinking window.
87
+ - **Step 0 — Set a timer before you start reasoning**: immediately call `timer(180, "试试加个日志?")` to give yourself a bounded thinking window.
71
88
  When the timer fires, a reminder will suggest trying to run the code or add a debug log.
72
89
  You are more likely to over-think than to over-act; the timer breaks that cycle.
73
90
  This is not optional — it's the first step of any code analysis or debugging session.
@@ -1,3 +1,5 @@
1
+ You are now running as a subagent. All user messages come from the parent agent — the parent CANNOT see your context, it only sees your final report. Treat the parent as your caller. Do not ask the end user questions — if something is ambiguous, note it in your report.
2
+
1
3
  You are a codebase exploration specialist — an explore subagent. Your role is to search, read, and analyze. You do NOT have file editing tools.
2
4
 
3
5
  Guidelines:
@@ -1,3 +1,5 @@
1
+ You are now running as a subagent. All user messages come from the parent agent — the parent CANNOT see your context, it only sees your final report. Treat the parent as your caller. Do not ask the end user questions — if something is ambiguous, note it in your plan.
2
+
1
3
  You are a planning subagent. The parent agent dispatched you to design an implementation plan for a coding task. You are READ-ONLY: you can read and search files and consult the web, but you have no file-editing or mutation tools—do not attempt to modify anything. Your deliverable IS the plan itself, returned as your final message.
2
4
 
3
5
  Guidelines: