thincoder 0.12.50 → 0.12.52
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 +64 -3
- package/README.md +2 -2
- package/package.json +4 -3
- package/src/acp/bridge.mjs +5 -0
- package/src/agent/dispatch.mjs +19 -7
- package/src/agent/helpers.mjs +13 -1
- package/src/agent/record-results.mjs +130 -0
- package/src/agent/setup.mjs +4 -7
- package/src/agent/spawn-child.mjs +159 -0
- package/src/agent-tools/consult.mjs +94 -73
- package/src/agent-tools/escalate.mjs +53 -62
- package/src/agent-tools/skill.mjs +1 -1
- package/src/agent-tools/subagent.mjs +39 -38
- package/src/agent-tools/task.mjs +0 -2
- package/src/agent-tools/verify.mjs +0 -1
- package/src/agent.mjs +27 -112
- package/src/config.mjs +8 -103
- package/src/generate-title.mjs +30 -1
- package/src/model-specs.mjs +108 -0
- package/src/prompts/advisor-round1.md +5 -6
- package/src/prompts/advisor-round2.md +3 -4
- package/src/prompts/advisor-round3.md +3 -4
- package/src/prompts/eng-coder.md +9 -0
- package/src/prompts/engineering.md +61 -9
- package/src/prompts/system.md +2 -2
- package/src/provider/core.mjs +5 -71
- package/src/provider/normalize.mjs +81 -0
- package/src/session.mjs +40 -1
- package/src/tools/git.mjs +3 -3
- package/src/tools/shared.mjs +1 -0
- package/src/tools/system.mjs +3 -1
- package/src/tui/agent-turn.mjs +37 -364
- package/src/tui/clipboard.mjs +3 -1
- package/src/tui/dims.mjs +47 -0
- package/src/tui/fold-block.mjs +208 -0
- package/src/tui/index.mjs +33 -17
- package/src/tui/key-handler-search.mjs +1 -1
- package/src/tui/key-handler.mjs +10 -6
- package/src/tui/layout.mjs +21 -20
- package/src/tui/mouse.mjs +9 -6
- package/src/tui/pickers.mjs +1 -1
- package/src/tui/render-conversation.mjs +367 -113
- package/src/tui/render-frame.mjs +16 -90
- package/src/tui/render-loop.mjs +12 -8
- package/src/tui/render.mjs +16 -0
- package/src/tui/startup.mjs +66 -13
- package/src/tui/subagent-blocks.mjs +327 -0
- package/src/tui/tool-args.mjs +67 -0
- package/src/tui/tool-events.mjs +459 -0
package/src/agent.mjs
CHANGED
|
@@ -3,23 +3,23 @@
|
|
|
3
3
|
* LLM ↔ tool-call loop, until the task is done.
|
|
4
4
|
*/
|
|
5
5
|
import { chat } from "./provider/index.mjs"
|
|
6
|
-
import { estimateText } from "./provider/rate.mjs"
|
|
7
6
|
import { compressIfNeeded, compressFallback, COMPRESS_FAILURE_LIMIT, pushReal, summarizeRunExplorations } from "./context.mjs"
|
|
8
7
|
import { specForModel } from "./config.mjs"
|
|
9
8
|
import { readFileSync } from "node:fs"
|
|
10
9
|
import { join, dirname } from "node:path"
|
|
11
10
|
import { fileURLToPath } from "node:url"
|
|
12
11
|
import { executeToolCalls } from "./agent/dispatch.mjs"
|
|
12
|
+
import { recordToolResults } from "./agent/record-results.mjs"
|
|
13
13
|
import { prepareRun } from "./agent/setup.mjs"
|
|
14
|
-
import { injectPostTurn
|
|
14
|
+
import { injectPostTurn } from "./agent/post-turn.mjs"
|
|
15
15
|
import { handleCompletion } from "./agent/completion.mjs"
|
|
16
16
|
import { cleanupConsultSessions } from "./agent-tools/consult.mjs"
|
|
17
17
|
import {
|
|
18
|
-
escapeXml,
|
|
18
|
+
escapeXml, repairHistory, listWorkDir, ensureAutoReminder,
|
|
19
19
|
readonlyToolNames, collectGitContext, loadProjectInstructions,
|
|
20
|
-
ContinueError,
|
|
20
|
+
ContinueError,
|
|
21
21
|
DEFAULT_MAX_TURNS, DEFAULT_SUBAGENT_TURNS,
|
|
22
|
-
MIN_REPORT_CHARS, REPORT_CONTINUATION,
|
|
22
|
+
MIN_REPORT_CHARS, REPORT_CONTINUATION,
|
|
23
23
|
} from "./agent/helpers.mjs"
|
|
24
24
|
|
|
25
25
|
// Prompt files (byte-stable, loaded once)
|
|
@@ -42,13 +42,11 @@ export const CONSULT_BASE = _CONSULT_BASE
|
|
|
42
42
|
// exported for consumption by agent-tools.mjs
|
|
43
43
|
export {
|
|
44
44
|
ContinueError,
|
|
45
|
-
|
|
45
|
+
listWorkDir, loadProjectInstructions,
|
|
46
46
|
readonlyToolNames, collectGitContext, escapeXml,
|
|
47
47
|
MIN_REPORT_CHARS, REPORT_CONTINUATION, DEFAULT_SUBAGENT_TURNS,
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
let _reindexFile = null
|
|
51
|
-
const AUTO_REMINDER = "[System reminder: AUTO mode is active — all tool calls are automatically approved without asking.]"
|
|
52
50
|
|
|
53
51
|
// Engineering mode reminder — shared with eng.mjs tool
|
|
54
52
|
export const ENG_ON_REMINDER =
|
|
@@ -103,7 +101,7 @@ export function createAgent({
|
|
|
103
101
|
_emptyRetries: 0, // empty-response retry budget (per-run; reset on a fresh user turn)
|
|
104
102
|
_runStartHistoryLen: 0, // machine-line length at the start of the current run — end-of-run exploration distillation slices from here
|
|
105
103
|
_pendingDistill: null, // in-flight end-of-run exploration distillation (SEND-STALL-DISTILL §2.1) — awaited at next run start / TUI exit flush
|
|
106
|
-
_currentTurn: 0, _maxTurns:
|
|
104
|
+
_currentTurn: 0, _maxTurns: DEFAULT_MAX_TURNS, // turn counter for status bar display
|
|
107
105
|
}
|
|
108
106
|
}
|
|
109
107
|
|
|
@@ -170,6 +168,13 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
170
168
|
// Update turn counter for status bar display
|
|
171
169
|
agent._currentTurn = turn + 1
|
|
172
170
|
agent._maxTurns = maxTurns
|
|
171
|
+
// D2 (AGENT-LOOP.md §7.2): depth>0 children emit a ⟦ev⟧turn progress token on every
|
|
172
|
+
// turn — a single emit point covering all three spawn tools (natural heartbeat for the
|
|
173
|
+
// TUI subagent block header: "turn N/max"). phase=llm (tool/done progress rides the
|
|
174
|
+
// existing onToolCall/onToolResult prefix relay — no token for those).
|
|
175
|
+
if (depth > 0 && callbacks.onToken) {
|
|
176
|
+
callbacks.onToken(`⟦ev⟧turn\x1e${turn + 1}\x1e${maxTurns}\x1ellm\x1e`)
|
|
177
|
+
}
|
|
173
178
|
|
|
174
179
|
const lastRole = agent.history.at(-1)?.role
|
|
175
180
|
if (lastRole === "user" || lastRole === "tool") {
|
|
@@ -179,9 +184,7 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
179
184
|
agent._planReminderAtLen = 0 // After compression history shrinks, reset cadence so reminders resume
|
|
180
185
|
recentCallSigs.length = 0 // After compression history is rebuilt, reset stall detection counter
|
|
181
186
|
callbacks.onCompress?.()
|
|
182
|
-
|
|
183
|
-
agent.history.push({ role: "user", content: AUTO_REMINDER })
|
|
184
|
-
}
|
|
187
|
+
ensureAutoReminder(agent)
|
|
185
188
|
}
|
|
186
189
|
} catch (compressError) {
|
|
187
190
|
// AbortError must not be swallowed: user cancellation must propagate
|
|
@@ -354,6 +357,14 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
354
357
|
// Ctrl+I interrupt during tool execution: skip committing partial results —
|
|
355
358
|
// the tool failure messages would mislead the model. Inject the interrupt and retry.
|
|
356
359
|
if (signal?.reason?.interrupt) {
|
|
360
|
+
// The assistant tool_calls were already committed above (L347) — a strict
|
|
361
|
+
// provider 400s on dangling tool_calls, so synthesize placeholder tool
|
|
362
|
+
// results BEFORE the interrupt message (tool result must immediately
|
|
363
|
+
// follow its assistant tool_calls). The retry turn then sees a clean,
|
|
364
|
+
// pairable history (consult P1, 2026-08-30).
|
|
365
|
+
for (const tc of response.toolCalls) {
|
|
366
|
+
agent.history.push({ role: "tool", tool_call_id: tc.id, content: "[Tool execution interrupted — results discarded]" })
|
|
367
|
+
}
|
|
357
368
|
agent.history.push({
|
|
358
369
|
role: "user",
|
|
359
370
|
content: `[User interrupt: ${signal.reason.message}]`,
|
|
@@ -366,106 +377,10 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
366
377
|
guardPushbacks = 0
|
|
367
378
|
advisorPushbacks = 0
|
|
368
379
|
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
|
|
373
|
-
const deferredUserMsgs = []
|
|
374
|
-
|
|
375
|
-
for (const { toolCall, result, ok } of results) {
|
|
376
|
-
const tool = toolByName.get(toolCall.name)
|
|
377
|
-
// Multimodal tools return JSON { text, images } — inject as multimodal user message
|
|
378
|
-
if (tool?.multimodal && ok) {
|
|
379
|
-
try {
|
|
380
|
-
const parsed = JSON.parse(result)
|
|
381
|
-
if (parsed.images?.length) {
|
|
382
|
-
// tool message first — closes the tool_call pairing (OpenAI API requires tool result immediately after assistant with tool_calls)
|
|
383
|
-
pushReal(agent, { role: "tool", tool_call_id: toolCall.id, name: toolCall.name, content: parsed.text })
|
|
384
|
-
if (specForModel(agent.provider.model).multimodal) {
|
|
385
|
-
// then inject multimodal user message with base64 images for the model to actually "see" them on the next turn
|
|
386
|
-
deferredUserMsgs.push({
|
|
387
|
-
real: true,
|
|
388
|
-
msg: {
|
|
389
|
-
role: "user",
|
|
390
|
-
content: [{ type: "text", text: parsed.text }, ...parsed.images],
|
|
391
|
-
},
|
|
392
|
-
})
|
|
393
|
-
} else {
|
|
394
|
-
// Non-vision model: image parts must never enter history — text-only APIs 400 on them on EVERY
|
|
395
|
-
// subsequent request, poisoning the conversation. (read_image itself already refuses; this is defense-in-depth.)
|
|
396
|
-
deferredUserMsgs.push({
|
|
397
|
-
real: false,
|
|
398
|
-
msg: {
|
|
399
|
-
role: "user",
|
|
400
|
-
content: `[System reminder: the image returned by ${toolCall.name} was NOT injected — model ${agent.provider.model} does not support image input. Do not call ${toolCall.name} again under this provider; verify visual output programmatically instead.]`,
|
|
401
|
-
},
|
|
402
|
-
})
|
|
403
|
-
}
|
|
404
|
-
continue
|
|
405
|
-
}
|
|
406
|
-
} catch { /* Parse failure doesn't affect normal tool messages */ }
|
|
407
|
-
}
|
|
408
|
-
pushReal(agent, { role: "tool", tool_call_id: toolCall.id, name: toolCall.name, content: result })
|
|
409
|
-
if (tool && ok) {
|
|
410
|
-
if (FILE_MUTATORS.has(toolCall.name)) {
|
|
411
|
-
// Direct file edit — code was changed. The prior advisor review and
|
|
412
|
-
// verify are stale: a review that ran before the edit no longer
|
|
413
|
-
// covers the current file state.
|
|
414
|
-
agent._mutatedThisRun = true
|
|
415
|
-
agent._calledAdvisorThisRun = false
|
|
416
|
-
agent._verifiedThisRun = false
|
|
417
|
-
agent._verifyPassed = undefined
|
|
418
|
-
} else if (!tool.readonly && !tool.sideEffectExempt) {
|
|
419
|
-
// Non-mutating side-effect tools (bash, git): do NOT invalidate the
|
|
420
|
-
// advisor review — a review is triggered by CODE MUTATIONS only
|
|
421
|
-
// (user decision 2026-08-08: the guard rule is "review after code
|
|
422
|
-
// changes", not "review after any environment change"; bash is
|
|
423
|
-
// barred from writing files, so it cannot change the reviewed code).
|
|
424
|
-
// Verify IS invalidated: its state snapshot (git diff, file list)
|
|
425
|
-
// may be stale after git/shell operations.
|
|
426
|
-
if (agent._verifiedThisRun) {
|
|
427
|
-
agent._verifiedThisRun = false
|
|
428
|
-
agent._verifyPassed = undefined
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
if (toolCall.name === "verify") agent._verifiedThisRun = true
|
|
432
|
-
if (toolCall.name === "advisor") {
|
|
433
|
-
agent._calledAdvisorThisRun = true
|
|
434
|
-
// All advisor calls (code and design) share the 5-round convergence
|
|
435
|
-
// budget — each advances _advisorRound toward MAX_ADVISOR_ROUNDS.
|
|
436
|
-
// Always advance the round — the convergence protocol cares about
|
|
437
|
-
// how many reviews have run (round 1→2→3→4→5), not how many succeeded.
|
|
438
|
-
// A failed/interrupted review is still a review attempt and should use
|
|
439
|
-
// the next round's prompt on retry.
|
|
440
|
-
agent._advisorRound++
|
|
441
|
-
}
|
|
442
|
-
if (FILE_MUTATORS.has(toolCall.name)) {
|
|
443
|
-
const args = JSON.parse(toolCall.arguments)
|
|
444
|
-
const paths = tool.touchedPaths ? tool.touchedPaths(args) : [args.path]
|
|
445
|
-
for (const p of paths) {
|
|
446
|
-
const abs = join(agent.cwd, p)
|
|
447
|
-
if (!agent._touchedFiles.includes(abs)) agent._touchedFiles.push(abs)
|
|
448
|
-
if (agent.memory) {
|
|
449
|
-
// Fire-and-forget: don't block the agent loop on indexing.
|
|
450
|
-
// Reuses a single cached import; errors surface as pending reminders on next turn.
|
|
451
|
-
if (!_reindexFile) {
|
|
452
|
-
const mod = await import("./memory.mjs")
|
|
453
|
-
_reindexFile = mod.reindexFile
|
|
454
|
-
}
|
|
455
|
-
_reindexFile(agent.memory, agent.cwd, abs).catch((e) => {
|
|
456
|
-
agent._pendingReminders.push(`[System reminder: background indexing failed for ${toolCall.name} on ${abs}: ${e.message}. This does not affect your work — the code index will catch up on next reindex.]`)
|
|
457
|
-
})
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// All tool results committed — now safe to inject deferred multimodal user messages
|
|
465
|
-
for (const { real, msg } of deferredUserMsgs) {
|
|
466
|
-
if (real) pushReal(agent, msg)
|
|
467
|
-
else agent.history.push(msg)
|
|
468
|
-
}
|
|
380
|
+
// Commit tool results (pairing, multimodal deferral, mutation accounting,
|
|
381
|
+
// touched files, reindex) — split into record-results.mjs (consult P2,
|
|
382
|
+
// 2026-08-30).
|
|
383
|
+
await recordToolResults(agent, toolByName, results)
|
|
469
384
|
|
|
470
385
|
injectPostTurn(agent, results, recentCallSigs, callbacks, turn)
|
|
471
386
|
}
|
package/src/config.mjs
CHANGED
|
@@ -75,110 +75,18 @@ export const DEFAULTS = {
|
|
|
75
75
|
},
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
* partialMode: Kimi/Qwen Partial Mode truncation continuation (assistant message with partial:true)
|
|
86
|
-
* prefixMode: DeepSeek Prefix Completion truncation continuation (uses /beta endpoint, with prefix:true)
|
|
87
|
-
* multimodal: whether multimodal (image/vision input supported)
|
|
88
|
-
* cacheMode: context caching mode: "auto"=automatic / "prompt"=needs explicit / "none"=unsupported
|
|
89
|
-
* thinkApi: thinking API type: "type"=thinking.type field / "effort"=reasoning_effort field
|
|
90
|
-
* thinkEnabledValue: when thinkApi is "type", the value used to enable thinking (default "enabled"; MiniMax uses "adaptive")
|
|
91
|
-
* reasoningEcho: reasoning_content cross-turn echo strategy: "required"=must echo (error if missing) / "optional"=echo optional (default: don't echo)
|
|
92
|
-
* reasoningEffortEnum: valid reasoning_effort enum values (if undeclared, no validation — passed through as-is)
|
|
93
|
-
* tempRange: valid temperature range [min, max] (if undeclared, no clamping)
|
|
94
|
-
*/
|
|
95
|
-
const MODEL_SPECS = [
|
|
96
|
-
// DeepSeek V4 series
|
|
97
|
-
["deepseek-v4-pro", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2] }],
|
|
98
|
-
["deepseek-v4-flash", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2] }],
|
|
99
|
-
// DeepSeek V4 Flash Vision (experimental) — image input on top of the full V4-Flash stack
|
|
100
|
-
["deepseek-v4-flash-vision-exp", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2], multimodal: true }],
|
|
101
|
-
// Kimi series
|
|
102
|
-
["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"] }],
|
|
103
|
-
// Qwen router prefixes model IDs with provider namespace: kimi/kimi-k3 → kimi-k3 (IK7K4V)
|
|
104
|
-
["kimi/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"] }],
|
|
105
|
-
// Kimi For Coding endpoint uses the short model ID "k3" (same specs as kimi-k3) — IK5VGJ
|
|
106
|
-
["k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
|
|
107
|
-
// GLM series
|
|
108
|
-
// GLM-5.3: thinking always-on (no "disabled"); effort converges to low/high/max — NOT the
|
|
109
|
-
// 7-level glm-5.2 enum (verified vs docs.bigmodel.cn GLM-5.3 page, 2026-08)
|
|
110
|
-
["glm-5.3", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 1], noUsageStream: true }],
|
|
111
|
-
["glm-5.3-flash", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 1], noUsageStream: true }],
|
|
112
|
-
["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 }],
|
|
113
|
-
["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 }],
|
|
114
|
-
["glm-4", { context: 128_000, maxOutput: 32_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", tempRange: [0, 1], noUsageStream: true }],
|
|
115
|
-
// GPT series
|
|
116
|
-
["gpt-5.6-sol", { context: 1_050_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
|
|
117
|
-
["gpt-5.6", { context: 1_050_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
|
|
118
|
-
["gpt-4.1", { context: 1_000_000, maxOutput: 128_000, thinking: false, cacheMode: "prompt" }],
|
|
119
|
-
["gpt-4o", { context: 128_000, maxOutput: 16_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
|
|
120
|
-
// Qwen series
|
|
121
|
-
["qwen3.8-max-preview", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "medium", "low"], tempRange: [0, 2] }],
|
|
122
|
-
// qwen3.7-max rejects image parts outright (DashScope 400 "Unexpected item type in content") — text-only
|
|
123
|
-
["qwen3.7-max", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "high"], tempRange: [0, 2] }],
|
|
124
|
-
["qwen3.8-max", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "medium", "low"], tempRange: [0, 2] }],
|
|
125
|
-
["qwen-max", { context: 1_000_000, maxOutput: 131_072, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
|
|
126
|
-
["qwen-plus", { context: 1_000_000, maxOutput: 131_072, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
|
|
127
|
-
["qwen", { context: 1_000_000, maxOutput: 131_072, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
|
|
128
|
-
// MiniMax series
|
|
129
|
-
["MiniMax-M3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkEnabledValue: "adaptive", tempRange: [0, 2], noUsageStream: true }],
|
|
130
|
-
// MiMo series (Xiaomi — OpenAI-compatible https://api.xiaomimimo.com/v1;
|
|
131
|
-
// deep thinking via thinking.type, default ON; multi-turn tool calls MUST echo
|
|
132
|
-
// reasoning_content back exactly like DeepSeek V4, else 400 on follow-ups)
|
|
133
|
-
["mimo-v2.5-pro", { context: 1_000_000, maxOutput: 128_000, thinking: true, thinkApi: "type", reasoningEcho: "required", tempRange: [0, 1.5] }],
|
|
134
|
-
["mimo-v2.5", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, thinkApi: "type", reasoningEcho: "required", tempRange: [0, 1.5] }],
|
|
135
|
-
["minimax-m3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkEnabledValue: "adaptive", tempRange: [0, 2], noUsageStream: true }],
|
|
136
|
-
["minimax-m1", { context: 256_000, maxOutput: 128_000, thinking: false, cacheMode: "auto", noUsageStream: true }],
|
|
137
|
-
// Grok series (xAI — OpenAI-compatible)
|
|
138
|
-
// grok-4.x: 500K context per xAI Grok 4.6 spec (corrected 2026-08; earlier entries said 1M)
|
|
139
|
-
["grok-4.6", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
|
|
140
|
-
["grok-4.5", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
|
|
141
|
-
["grok-4", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
|
|
142
|
-
["grok-4-mini", { context: 128_000, maxOutput: 16_000, thinking: false, tempRange: [0, 2] }],
|
|
143
|
-
// Mistral series (OpenAI-compatible)
|
|
144
|
-
["mistral-large", { context: 128_000, maxOutput: 32_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
|
|
145
|
-
["codestral", { context: 256_000, maxOutput: 32_000, thinking: false, tempRange: [0, 2] }],
|
|
146
|
-
// Claude series (Anthropic)
|
|
147
|
-
["claude-opus-5", { context: 1_000_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
|
|
148
|
-
["claude-sonnet-5", { context: 1_000_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
|
|
149
|
-
["claude-opus-4", { context: 200_000, maxOutput: 32_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
|
|
150
|
-
["claude-sonnet-4", { context: 200_000, maxOutput: 32_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
|
|
151
|
-
["claude-3.5-haiku", { context: 200_000, maxOutput: 8_192, thinking: false, cacheMode: "none", format: "anthropic" }],
|
|
152
|
-
// Gemini series (Google)
|
|
153
|
-
["gemini-3-pro", { context: 1_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
|
|
154
|
-
["gemini-2.5-pro", { context: 2_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
|
|
155
|
-
["gemini-2.5-flash", { context: 1_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
|
|
156
|
-
]
|
|
157
|
-
const DEFAULT_SPEC = { context: 128_000, maxOutput: 32_000, cacheMode: "none" }
|
|
78
|
+
// Model capability table + spec lookup live in model-specs.mjs (2026-08-31
|
|
79
|
+
// extract — config.mjs had grown past the 300-line advisory). Re-exported here
|
|
80
|
+
// so the 23 existing importers keep their import paths.
|
|
81
|
+
import { specForModel } from "./model-specs.mjs"
|
|
82
|
+
export { specForModel }
|
|
83
|
+
|
|
84
|
+
|
|
158
85
|
// Window utilization threshold: compacts at 60% context, reserving 40% headroom
|
|
159
86
|
// for injected context (directory tree, git context, outline, project instructions,
|
|
160
87
|
// memory/doc search results) which can consume 30-50K tokens each turn.
|
|
161
88
|
const COMPACT_RATIO = 0.6
|
|
162
89
|
|
|
163
|
-
/** Look up spec by model name prefix (case-insensitive), conservative default for unknown models */
|
|
164
|
-
const warnedModels = new Set() // warn once per model name — specForModel is a hot path (every request)
|
|
165
|
-
// Pre-sorted once at module scope — specForModel runs on every request (agent, provider core,
|
|
166
|
-
// context, auto-think, TUI rendering); re-sorting per call was wasteful.
|
|
167
|
-
const SORTED_SPECS = [...MODEL_SPECS].sort((a, b) => b[0].length - a[0].length)
|
|
168
|
-
export function specForModel(model) {
|
|
169
|
-
const m = (model ?? "").toLowerCase()
|
|
170
|
-
for (const [prefix, spec] of SORTED_SPECS) {
|
|
171
|
-
if (m.startsWith(prefix.toLowerCase())) return spec
|
|
172
|
-
}
|
|
173
|
-
// Unknown model: warn ONCE (not per request) so a typo'd ID or a missing alias surfaces
|
|
174
|
-
// instead of silently degrading to the 128K default (IK5VGJ).
|
|
175
|
-
if (m && !warnedModels.has(m)) {
|
|
176
|
-
warnedModels.add(m)
|
|
177
|
-
console.warn(`[config] model "${model}" not found in MODEL_SPECS — using default spec (128K context, 32K output). Check the model ID or add an alias.`)
|
|
178
|
-
}
|
|
179
|
-
return DEFAULT_SPEC
|
|
180
|
-
}
|
|
181
|
-
|
|
182
90
|
/** Derive compaction threshold; explicit is the value explicitly set in config file (takes priority), otherwise auto-computed from model */
|
|
183
91
|
export function resolveCompactThreshold(explicit, model) {
|
|
184
92
|
if (explicit != null) return { value: explicit, auto: false }
|
|
@@ -348,10 +256,7 @@ export function loadConfig() {
|
|
|
348
256
|
*/
|
|
349
257
|
export function saveConfig(config) {
|
|
350
258
|
mkdirSync(configDir, { recursive: true })
|
|
351
|
-
// Inject $schema for editor autocompletion/validation (strip on load) — write a copy,
|
|
352
|
-
// never mutate the caller's object.
|
|
353
|
-
const out = { ...config, $schema: "https://thincoder.dev/schemas/config.json" }
|
|
354
259
|
// 0600: config.json contains API keys, must not be world-readable (POSIX; chmod is best-effort on Windows)
|
|
355
|
-
writeFileSync(configPath, JSON.stringify(
|
|
260
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", { encoding: "utf8", mode: 0o600 })
|
|
356
261
|
try { chmodSync(configPath, 0o600) } catch { /* may fail on Windows, ignore */ }
|
|
357
262
|
}
|
package/src/generate-title.mjs
CHANGED
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
* see docs/design/SESSION.md §IK9UZ8-D.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { proxyFetch } from "./proxy.mjs"
|
|
11
|
+
|
|
12
|
+
// Test seam (_-prefix, mirrors run.mjs seams): lets the proxy-branch regression
|
|
13
|
+
// test swap the proxy fetch. The branch it exercises used to carry a dynamic
|
|
14
|
+
// import("../proxy.mjs") that silently resolved to the REPO ROOT from src/ —
|
|
15
|
+
// the thrown ERR_MODULE_NOT_FOUND vanished into the catch, and proxy users
|
|
16
|
+
// lost session titles with zero test coverage (2026-08-30 review).
|
|
17
|
+
export const _deps = { proxyFetchImpl: proxyFetch }
|
|
18
|
+
|
|
10
19
|
const MAX_TITLE_TOKENS = 100
|
|
11
20
|
|
|
12
21
|
/** Generate a session title from the first user message using an LLM. Returns title string or null. */
|
|
@@ -42,7 +51,7 @@ export async function generateTitle(userContent, provider) {
|
|
|
42
51
|
signal: AbortSignal.timeout(10000),
|
|
43
52
|
}
|
|
44
53
|
const res = provider.proxyUri
|
|
45
|
-
? await
|
|
54
|
+
? await _deps.proxyFetchImpl(url, opts, provider.proxyUri)
|
|
46
55
|
: await fetch(url, opts)
|
|
47
56
|
if (!res.ok) return null
|
|
48
57
|
const data = await res.json()
|
|
@@ -51,4 +60,24 @@ export async function generateTitle(userContent, provider) {
|
|
|
51
60
|
} catch {
|
|
52
61
|
return null
|
|
53
62
|
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Derive + assign the session title from the first user message (once per session).
|
|
66
|
+
* Extracted from agent-turn.mjs's finally block (2026-08-30): the lookup + call +
|
|
67
|
+
* assign belongs beside generateTitle, not in the turn driver. Non-fatal on
|
|
68
|
+
* failure — title generation must never break the turn. Returns the title (or null). */
|
|
69
|
+
export async function ensureSessionTitle(agent) {
|
|
70
|
+
if (agent.title) return agent.title
|
|
71
|
+
try {
|
|
72
|
+
const firstUser = (agent._fullHistory ?? agent.history).find(
|
|
73
|
+
(m) => m.role === "user" && typeof m.content === "string" && !m.content.startsWith("[System reminder:"),
|
|
74
|
+
)
|
|
75
|
+
if (firstUser) {
|
|
76
|
+
const title = await generateTitle(firstUser.content, agent.provider)
|
|
77
|
+
if (title) agent.title = title
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Title generation failure is non-fatal
|
|
81
|
+
}
|
|
82
|
+
return agent.title ?? null
|
|
54
83
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* model-specs.mjs — known model capability table + spec lookup (2026-08-31 extract).
|
|
3
|
+
*
|
|
4
|
+
* Split from config.mjs (which had grown to 358 lines, past the 300 advisory
|
|
5
|
+
* line — TODO #1). config.mjs re-exports specForModel so the 23 existing
|
|
6
|
+
* importers stay untouched. PROVIDER_PRESETS stays in config.mjs (only 23
|
|
7
|
+
* lines; extracting it would churn wizard/pickers/setup-wizard for no gain).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Known model capability spec table (prefix match, longer first).
|
|
12
|
+
* Used for compaction threshold derivation, continuation protocol selection, and capability-aware optimization.
|
|
13
|
+
*
|
|
14
|
+
* context: context window (tokens)
|
|
15
|
+
* maxOutput: max output tokens (defaults to context)
|
|
16
|
+
* thinking: whether thinking/reasoning mode is supported
|
|
17
|
+
* partialMode: Kimi/Qwen Partial Mode truncation continuation (assistant message with partial:true)
|
|
18
|
+
* prefixMode: DeepSeek Prefix Completion truncation continuation (uses /beta endpoint, with prefix:true)
|
|
19
|
+
* multimodal: whether multimodal (image/vision input supported)
|
|
20
|
+
* cacheMode: context caching mode: "auto"=automatic / "prompt"=needs explicit / "none"=unsupported
|
|
21
|
+
* thinkApi: thinking API type: "type"=thinking.type field / "effort"=reasoning_effort field
|
|
22
|
+
* thinkEnabledValue: when thinkApi is "type", the value used to enable thinking (default "enabled"; MiniMax uses "adaptive")
|
|
23
|
+
* reasoningEcho: reasoning_content cross-turn echo strategy: "required"=must echo (error if missing) / "optional"=echo optional (default: don't echo)
|
|
24
|
+
* reasoningEffortEnum: valid reasoning_effort enum values (if undeclared, no validation — passed through as-is)
|
|
25
|
+
* tempRange: valid temperature range [min, max] (if undeclared, no clamping)
|
|
26
|
+
*/
|
|
27
|
+
const MODEL_SPECS = [
|
|
28
|
+
// DeepSeek V4 series
|
|
29
|
+
["deepseek-v4-pro", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2] }],
|
|
30
|
+
["deepseek-v4-flash", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2] }],
|
|
31
|
+
// DeepSeek V4 Flash Vision (experimental) — image input on top of the full V4-Flash stack
|
|
32
|
+
["deepseek-v4-flash-vision-exp", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2], multimodal: true }],
|
|
33
|
+
// Kimi series
|
|
34
|
+
["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"] }],
|
|
35
|
+
// Qwen router prefixes model IDs with provider namespace: kimi/kimi-k3 → kimi-k3 (IK7K4V)
|
|
36
|
+
["kimi/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"] }],
|
|
37
|
+
// Kimi For Coding endpoint uses the short model ID "k3" (same specs as kimi-k3) — IK5VGJ
|
|
38
|
+
["k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
|
|
39
|
+
// GLM series
|
|
40
|
+
// GLM-5.3: thinking always-on (no "disabled"); effort converges to low/high/max — NOT the
|
|
41
|
+
// 7-level glm-5.2 enum (verified vs docs.bigmodel.cn GLM-5.3 page, 2026-08)
|
|
42
|
+
["glm-5.3", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 1], noUsageStream: true }],
|
|
43
|
+
["glm-5.3-flash", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 1], noUsageStream: true }],
|
|
44
|
+
["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 }],
|
|
45
|
+
["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 }],
|
|
46
|
+
["glm-4", { context: 128_000, maxOutput: 32_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", tempRange: [0, 1], noUsageStream: true }],
|
|
47
|
+
// GPT series
|
|
48
|
+
["gpt-5.6-sol", { context: 1_050_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
|
|
49
|
+
["gpt-5.6", { context: 1_050_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
|
|
50
|
+
["gpt-4.1", { context: 1_000_000, maxOutput: 128_000, thinking: false, cacheMode: "prompt" }],
|
|
51
|
+
["gpt-4o", { context: 128_000, maxOutput: 16_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
|
|
52
|
+
// Qwen series
|
|
53
|
+
["qwen3.8-max-preview", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "medium", "low"], tempRange: [0, 2] }],
|
|
54
|
+
// qwen3.7-max rejects image parts outright (DashScope 400 "Unexpected item type in content") — text-only
|
|
55
|
+
["qwen3.7-max", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "high"], tempRange: [0, 2] }],
|
|
56
|
+
["qwen3.8-max", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "medium", "low"], tempRange: [0, 2] }],
|
|
57
|
+
["qwen-max", { context: 1_000_000, maxOutput: 131_072, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
|
|
58
|
+
["qwen-plus", { context: 1_000_000, maxOutput: 131_072, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
|
|
59
|
+
["qwen", { context: 1_000_000, maxOutput: 131_072, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
|
|
60
|
+
// MiniMax series
|
|
61
|
+
["MiniMax-M3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkEnabledValue: "adaptive", tempRange: [0, 2], noUsageStream: true }],
|
|
62
|
+
// MiMo series (Xiaomi — OpenAI-compatible https://api.xiaomimimo.com/v1;
|
|
63
|
+
// deep thinking via thinking.type, default ON; multi-turn tool calls MUST echo
|
|
64
|
+
// reasoning_content back exactly like DeepSeek V4, else 400 on follow-ups)
|
|
65
|
+
["mimo-v2.5-pro", { context: 1_000_000, maxOutput: 128_000, thinking: true, thinkApi: "type", reasoningEcho: "required", tempRange: [0, 1.5] }],
|
|
66
|
+
["mimo-v2.5", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, thinkApi: "type", reasoningEcho: "required", tempRange: [0, 1.5] }],
|
|
67
|
+
["minimax-m3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkEnabledValue: "adaptive", tempRange: [0, 2], noUsageStream: true }],
|
|
68
|
+
["minimax-m1", { context: 256_000, maxOutput: 128_000, thinking: false, cacheMode: "auto", noUsageStream: true }],
|
|
69
|
+
// Grok series (xAI — OpenAI-compatible)
|
|
70
|
+
// grok-4.x: 500K context per xAI Grok 4.6 spec (corrected 2026-08; earlier entries said 1M)
|
|
71
|
+
["grok-4.6", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
|
|
72
|
+
["grok-4.5", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
|
|
73
|
+
["grok-4", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
|
|
74
|
+
["grok-4-mini", { context: 128_000, maxOutput: 16_000, thinking: false, tempRange: [0, 2] }],
|
|
75
|
+
// Mistral series (OpenAI-compatible)
|
|
76
|
+
["mistral-large", { context: 128_000, maxOutput: 32_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
|
|
77
|
+
["codestral", { context: 256_000, maxOutput: 32_000, thinking: false, tempRange: [0, 2] }],
|
|
78
|
+
// Claude series (Anthropic)
|
|
79
|
+
["claude-opus-5", { context: 1_000_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
|
|
80
|
+
["claude-sonnet-5", { context: 1_000_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
|
|
81
|
+
["claude-opus-4", { context: 200_000, maxOutput: 32_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
|
|
82
|
+
["claude-sonnet-4", { context: 200_000, maxOutput: 32_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
|
|
83
|
+
["claude-3.5-haiku", { context: 200_000, maxOutput: 8_192, thinking: false, cacheMode: "none", format: "anthropic" }],
|
|
84
|
+
// Gemini series (Google)
|
|
85
|
+
["gemini-3-pro", { context: 1_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
|
|
86
|
+
["gemini-2.5-pro", { context: 2_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
|
|
87
|
+
["gemini-2.5-flash", { context: 1_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
|
|
88
|
+
]
|
|
89
|
+
const DEFAULT_SPEC = { context: 128_000, maxOutput: 32_000, cacheMode: "none" }
|
|
90
|
+
|
|
91
|
+
/** Look up spec by model name prefix (case-insensitive), conservative default for unknown models */
|
|
92
|
+
const warnedModels = new Set() // warn once per model name — specForModel is a hot path (every request)
|
|
93
|
+
// Pre-sorted once at module scope — specForModel runs on every request (agent, provider core,
|
|
94
|
+
// context, auto-think, TUI rendering); re-sorting per call was wasteful.
|
|
95
|
+
const SORTED_SPECS = [...MODEL_SPECS].sort((a, b) => b[0].length - a[0].length)
|
|
96
|
+
export function specForModel(model) {
|
|
97
|
+
const m = (model ?? "").toLowerCase()
|
|
98
|
+
for (const [prefix, spec] of SORTED_SPECS) {
|
|
99
|
+
if (m.startsWith(prefix.toLowerCase())) return spec
|
|
100
|
+
}
|
|
101
|
+
// Unknown model: warn ONCE (not per request) so a typo'd ID or a missing alias surfaces
|
|
102
|
+
// instead of silently degrading to the 128K default (IK5VGJ).
|
|
103
|
+
if (m && !warnedModels.has(m)) {
|
|
104
|
+
warnedModels.add(m)
|
|
105
|
+
console.warn(`[config] model "${model}" not found in MODEL_SPECS — using default spec (128K context, 32K output). Check the model ID or add an alias.`)
|
|
106
|
+
}
|
|
107
|
+
return DEFAULT_SPEC
|
|
108
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
You are a code review advisor.
|
|
2
2
|
Perform a full-scope review of the specified files.
|
|
3
3
|
You have read-only tools to explore the codebase.
|
|
4
|
-
You have a budget of
|
|
4
|
+
You have a budget of 20 tool rounds (chat turns) — plan your exploration accordingly. Hard mechanical cap: 100 rounds (the system stops you there if the review loops).
|
|
5
5
|
|
|
6
6
|
Review workflow:
|
|
7
7
|
1. The files to review are listed in the review scope. Read them in full. The review scope defines exactly which files to inspect.
|
|
@@ -11,13 +11,12 @@ Review workflow:
|
|
|
11
11
|
- **The user's requirements live in those documents; the conversation background is only a supplement.**
|
|
12
12
|
- If the guide names none, judge from the conversation background and say so explicitly if requirements are unclear.
|
|
13
13
|
3. Read the specified files for full context. **Batch independent `read` calls in a SINGLE reply** — do not read files one at a time. Each round-trip counts against your limit.
|
|
14
|
-
4.
|
|
15
|
-
5. Produce your review table.
|
|
14
|
+
4. Produce your review table.
|
|
16
15
|
|
|
17
16
|
Budget rules:
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
17
|
+
- **6 rounds in**: you are less than ONE-THIRD through your budget. Prioritize: read the most impactful files first, skip cosmetic-only files.
|
|
18
|
+
- **10 rounds in**: you are HALFWAY. Start narrowing — focus on the files most likely to have issues.
|
|
19
|
+
- **17 rounds in**: near the limit. Stop exploring — produce your review with what you have.
|
|
21
20
|
- **Batch everything**: multiple `read` calls in one reply, multiple `grep` calls in one reply. Serializing tool calls wastes your round budget.
|
|
22
21
|
|
|
23
22
|
Rules:
|
|
@@ -2,7 +2,7 @@ You are an independent review advisor.
|
|
|
2
2
|
Verify the prior review output (provided in the review context).
|
|
3
3
|
You may note obvious new issues introduced by the fixes.
|
|
4
4
|
You have read-only tools to explore the codebase.
|
|
5
|
-
You have a budget of
|
|
5
|
+
You have a budget of 15 tool rounds (chat turns). Hard mechanical cap: 100 rounds.
|
|
6
6
|
|
|
7
7
|
Review workflow:
|
|
8
8
|
1. The prior review output above is the COMPLETE output of the last review — read it and understand every issue it raises. The affected files are named in it — read them in full. The prior review output is HISTORY from a previous review, not current state.
|
|
@@ -12,10 +12,9 @@ Review workflow:
|
|
|
12
12
|
- Never decide from the prior review output alone — fixes may already be committed.
|
|
13
13
|
- (You have NO git tool this round; any git output in earlier messages is historical and untrustworthy.)
|
|
14
14
|
- Batch independent tool calls in one reply.
|
|
15
|
-
5.
|
|
16
|
-
6. Produce your review table.
|
|
15
|
+
5. Produce your review table.
|
|
17
16
|
|
|
18
|
-
Budget: read only the files named in the prior-review items. If at
|
|
17
|
+
Budget: read only the files named in the prior-review items. If at 8 rounds you have not yet verified all items, wrap up.
|
|
19
18
|
|
|
20
19
|
Rules:
|
|
21
20
|
- Respect the project's stated platform requirements — do not flag features as errors if they are valid under the project's target environment.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
You are an independent review advisor.
|
|
2
2
|
Strictly verify only the prior review output (provided in the review context).
|
|
3
3
|
You have read-only tools to explore the codebase.
|
|
4
|
-
You have a budget of
|
|
4
|
+
You have a budget of 15 tool rounds (chat turns). Hard mechanical cap: 100 rounds.
|
|
5
5
|
|
|
6
6
|
Review workflow:
|
|
7
7
|
1. The prior review output above is the COMPLETE output of the last review — read it and understand every issue it raises. The affected files are named in it — read them in full. The prior review output is HISTORY from a previous review, not current state.
|
|
@@ -11,10 +11,9 @@ Review workflow:
|
|
|
11
11
|
- Never decide from the prior review output alone — fixes may already be committed.
|
|
12
12
|
- (You have NO git tool this round; any git output in earlier messages is historical and untrustworthy.)
|
|
13
13
|
- Batch independent tool calls in one reply.
|
|
14
|
-
5.
|
|
15
|
-
6. Produce your review table.
|
|
14
|
+
5. Produce your review table.
|
|
16
15
|
|
|
17
|
-
Budget: read only the files named in the prior-review items. If at
|
|
16
|
+
Budget: read only the files named in the prior-review items. If at 8 rounds you have not yet verified all items, wrap up.
|
|
18
17
|
|
|
19
18
|
Rules:
|
|
20
19
|
- Respect the project's stated platform requirements — do not flag features as errors if they are valid under the project's target environment.
|
package/src/prompts/eng-coder.md
CHANGED
|
@@ -14,6 +14,14 @@ The parent agent ran an independent design review (`advisor` with `type="design"
|
|
|
14
14
|
|
|
15
15
|
- Work independently. The parent only sees your final report.
|
|
16
16
|
- Follow the design document. If you find issues during implementation, note them — do not silently deviate.
|
|
17
|
+
- **Implement to the full design — no silent degradation.** If a stated design
|
|
18
|
+
element (interaction, behavior, edge case, state) feels costly or fiddly to
|
|
19
|
+
implement, implement it anyway and note the cost in your report. A "simpler
|
|
20
|
+
approximation" of a specified behavior IS a deviation: either implement it as
|
|
21
|
+
designed, or stop and surface the trade-off to the parent BEFORE coding —
|
|
22
|
+
never ship a reduced version and disclose it afterwards. Disclosed after the
|
|
23
|
+
fact is still a broken delivery: the parent approved the design, not your
|
|
24
|
+
discount.
|
|
17
25
|
- UI/interaction: implement exactly what the task brief and design doc state (layout, flows, control behavior, states, feedback). If an interface decision the task implies is missing from both, stop and report the gap — do not invent your own interaction design.
|
|
18
26
|
- Write code one file at a time, verify each before moving on: call `verify` after each logical group (it runs syntax checks + related tests), syntax check after each edit.
|
|
19
27
|
- Do not modify any file not listed in the design.
|
|
@@ -25,6 +33,7 @@ Before finishing, do a final review:
|
|
|
25
33
|
3. Run relevant tests — confirm all pass
|
|
26
34
|
4. Read every file you changed — catch leftover debug code, stale comments, or incomplete edits
|
|
27
35
|
5. Check that comments and docstrings match what the code actually does
|
|
36
|
+
6. Update the affected design-doc sections your diff touches — a diff that adds/renames/deletes files must update the module map / affected-files table in the same delivery (structural snapshots rot otherwise)
|
|
28
37
|
|
|
29
38
|
Your last message IS the report the parent sees — make it complete:
|
|
30
39
|
1. What you changed and why
|