sapper-iq 1.4.7 → 1.4.8

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 (2) hide show
  1. package/package.json +1 -1
  2. package/sapper.mjs +607 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapper-iq",
3
- "version": "1.4.7",
3
+ "version": "1.4.8",
4
4
  "description": "AI-powered development assistant that executes commands and builds projects",
5
5
  "main": "sapper.mjs",
6
6
  "bin": {
package/sapper.mjs CHANGED
@@ -656,6 +656,14 @@ const TOOL_NAME_MAP = {
656
656
  'ask_expert': 'CONSULT',
657
657
  'expert': 'CONSULT',
658
658
  'advisor': 'CONSULT',
659
+ 'think': 'DEEPTHINK',
660
+ 'deepthink': 'DEEPTHINK',
661
+ 'deep_think': 'DEEPTHINK',
662
+ 'talk': 'DEEPTHINK',
663
+ 'talk_about': 'DEEPTHINK',
664
+ 'discuss': 'DEEPTHINK',
665
+ 'moe': 'DEEPTHINK',
666
+ 'reason': 'DEEPTHINK',
659
667
  'todo': 'LIST', // alias — list tasks
660
668
  };
661
669
 
@@ -683,6 +691,7 @@ const TOOL_ALLOWED_BY = {
683
691
  OPEN: ['OPEN', 'SHELL'],
684
692
  SHELL: ['SHELL'],
685
693
  CONSULT: ['CONSULT'],
694
+ DEEPTHINK: ['DEEPTHINK'],
686
695
  };
687
696
 
688
697
  function normalizeToolName(toolName = '') {
@@ -1174,6 +1183,28 @@ const DEFAULT_CONFIG = Object.freeze({
1174
1183
  verbose: true, // Print full request, each tool call/result, and final answer to the terminal during the consultation.
1175
1184
  systemPrompt: '', // Override the built-in consultant system prompt (empty = use default).
1176
1185
  }),
1186
+ deepthink: Object.freeze({
1187
+ // Multi-turn back-and-forth with a reasoning model — like Claude Opus thinking.
1188
+ // Unlike the consultant (one-shot), this preserves a thought-session across calls so the
1189
+ // main agent can have a real back-and-forth: ask, get a thoughtful answer, ask follow-up,
1190
+ // refine, etc. Use when the main agent hits something unexpected or needs deep reasoning.
1191
+ enabled: true,
1192
+ model: 'juilpark/Qwen3.5-27B-Claude-4.6-Opus-Reasoning-Distilled-heretic:q4_k_m', // Pick a strong reasoning model.
1193
+ contextLimit: null, // Tokens to give the thinker (null = use model default).
1194
+ temperature: 0.7, // Higher than consultant — reasoning benefits from a little exploration.
1195
+ thinking: 'on', // 'on' | 'off' | 'auto' — should usually stay on for reasoning models.
1196
+ maxTurnsPerSession: 30, // Max back-and-forth exchanges in one thought session.
1197
+ maxSessions: 5, // Max concurrent thought sessions kept in memory.
1198
+ sessionIdleTimeoutMs: 1800000, // 30 min — idle sessions are evicted.
1199
+ perTurnTimeoutMs: 180000, // 3 min hard cap on a single thought turn.
1200
+ maxMessageChars: 16000, // Reject incoming messages larger than this.
1201
+ tools: ['read', 'read_chunk', 'list', 'search', 'grep', 'find', 'regex', 'head', 'tail', 'cat', 'pwd', 'changes', 'fetch_web', 'recall_memory', 'search_memory_notes', 'read_memory_notes'], // Read-only subset — thinker can verify code itself instead of needing everything inline. Set to [] to make the thinker tool-less (pure reasoning from the message).
1202
+ toolRoundLimit: 6, // Max tool-call rounds the thinker may run within a single turn before being forced to answer.
1203
+ saveTranscripts: true, // Persist every thought session to disk for audit.
1204
+ transcriptDir: '.sapper/thinking', // Where thought transcripts are written.
1205
+ verbose: true, // Print each thought exchange to the terminal.
1206
+ systemPrompt: '', // Override the built-in deep-think system prompt (empty = use default).
1207
+ }),
1177
1208
  prompt: Object.freeze({
1178
1209
  prepend: '',
1179
1210
  append: '',
@@ -1193,7 +1224,7 @@ RULES:
1193
1224
  5. NO HALLUCINATIONS: If a file doesn't exist, don't guess its content. List the directory instead.`,
1194
1225
  nativeTools: `TOOLS:
1195
1226
  You have function-calling tools available. Call them directly — do NOT use [TOOL:...] text markers.
1196
- Available tools: list_directory, read_file, search_files, write_file, patch_file, create_directory, ls, cat, head, tail, grep, find, regex_search, read_chunk, pwd, cd, rmdir, changes, fetch_web, recall_memory, save_memory_note, search_memory_notes, read_memory_notes, open_url, run_shell, consult_expert.
1227
+ Available tools: list_directory, read_file, search_files, write_file, patch_file, create_directory, ls, cat, head, tail, grep, find, regex_search, read_chunk, pwd, cd, rmdir, changes, fetch_web, recall_memory, save_memory_note, search_memory_notes, read_memory_notes, open_url, run_shell, consult_expert, deep_think.
1197
1228
 
1198
1229
  PATCH TIPS:
1199
1230
  - For patch_file, set old_text to "LINE:<number>" to replace a specific line by number (most reliable).
@@ -1221,6 +1252,12 @@ CONSULTANT (consult_expert):
1221
1252
  - DO NOT call it casually. Every call requires: goal (what important thing you're doing), question (specific decision you need help with), summary (≥25 words of what you've explored and why you're stuck), and files (paths the consultant should read — use "path:start-end" for line ranges on large files).
1222
1253
  - The consultant is read-only. It will use its own tools to verify facts and will return a RECOMMENDATION / REASONING / RISKS / NEXT STEPS answer that you then act on.
1223
1254
 
1255
+ DEEP THINK (deep_think):
1256
+ - deep_think opens a multi-turn back-and-forth with a separate reasoning model (think Claude Opus thinking). Use when something is unexpected, the logic is twisted, or you want to think out loud with a smarter model across several turns.
1257
+ - The thinker has a small set of READ-ONLY tools (read_file, read_chunk, list_directory, search_files, grep, find, regex_search, head, tail, cat, pwd, changes, fetch_web, recall_memory, search_memory_notes, read_memory_notes — actual set is configurable). It can verify code itself, so you do NOT need to inline everything; mentioning paths and what to look at is enough. Still include relevant errors and constraints in the message.
1258
+ - Each call returns a session_id. To continue the SAME thought across multiple exchanges, pass that session_id back. To start fresh, omit session_id or set new_session: true.
1259
+ - Sessions are capped at a configured number of turns. Use them as real conversations, not one-shot queries: ask, get reply, then ask follow-up that builds on it.
1260
+
1224
1261
  SHELL TIPS:
1225
1262
  - run_shell may keep long-running commands in a background session depending on config.
1226
1263
  - If a shell result returns a session id, inspect more output with run_shell command "__shell_read__ <session_id>".
@@ -1254,6 +1291,9 @@ SHELL TIPS:
1254
1291
  - [TOOL:OPEN]https://example.com[/TOOL] - Open a URL in the default browser (asks for approval)
1255
1292
  - [TOOL:SHELL]command[/TOOL] - Run shell command
1256
1293
  - [TOOL:CONSULT]goal:::question:::summary (>=25 words):::attempts:::hints:::file1,file2:start-end[/TOOL] - Call the heavyweight consultant model for advice. ONLY when stuck or before an important change. Or pass a JSON blob: [TOOL:CONSULT]{"goal":"...","question":"...","summary":"...","files":["src/x.ts:40-120"]}[/TOOL]
1294
+ - [TOOL:THINK]<message>[/TOOL] - Open a multi-turn back-and-forth with a separate reasoning model. New session.
1295
+ - [TOOL:THINK]<session_id>:::<follow-up message>[/TOOL] - Continue a previous thought session (use the session_id from the prior reply).
1296
+ - [TOOL:THINK]{"message":"...","session_id":"think-abc-1"}[/TOOL] - JSON form for clarity.
1257
1297
 
1258
1298
  PATCH TIPS:
1259
1299
  - PREFER the LINE:number mode when you know which line to change. It is much more reliable than text matching.
@@ -1597,6 +1637,54 @@ function normalizeConsultantConfig(consultantConfig = {}) {
1597
1637
  };
1598
1638
  }
1599
1639
 
1640
+ function normalizeDeepthinkConfig(deepthinkConfig = {}) {
1641
+ if (typeof deepthinkConfig === 'boolean') {
1642
+ return { ...DEFAULT_CONFIG.deepthink, enabled: deepthinkConfig };
1643
+ }
1644
+ if (typeof deepthinkConfig === 'string') {
1645
+ return { ...DEFAULT_CONFIG.deepthink, enabled: normalizeBoolean(deepthinkConfig, DEFAULT_CONFIG.deepthink.enabled) };
1646
+ }
1647
+ if (!deepthinkConfig || typeof deepthinkConfig !== 'object' || Array.isArray(deepthinkConfig)) {
1648
+ return { ...DEFAULT_CONFIG.deepthink };
1649
+ }
1650
+ const D = DEFAULT_CONFIG.deepthink;
1651
+ return {
1652
+ enabled: normalizeBoolean(deepthinkConfig.enabled, D.enabled),
1653
+ model: typeof deepthinkConfig.model === 'string' && deepthinkConfig.model.trim()
1654
+ ? deepthinkConfig.model.trim()
1655
+ : D.model,
1656
+ contextLimit: normalizeContextLimit(deepthinkConfig.contextLimit),
1657
+ temperature: (() => {
1658
+ const v = Number(deepthinkConfig.temperature);
1659
+ return Number.isFinite(v) && v >= 0 && v <= 2 ? v : D.temperature;
1660
+ })(),
1661
+ thinking: normalizeThinkingMode(deepthinkConfig.thinking),
1662
+ maxTurnsPerSession: normalizeIntegerInRange(deepthinkConfig.maxTurnsPerSession, D.maxTurnsPerSession, 1, 500),
1663
+ maxSessions: normalizeIntegerInRange(deepthinkConfig.maxSessions, D.maxSessions, 1, 50),
1664
+ sessionIdleTimeoutMs: normalizeIntegerInRange(deepthinkConfig.sessionIdleTimeoutMs, D.sessionIdleTimeoutMs, 60000, 7200000),
1665
+ perTurnTimeoutMs: normalizeIntegerInRange(deepthinkConfig.perTurnTimeoutMs, D.perTurnTimeoutMs, 10000, 1800000),
1666
+ maxMessageChars: normalizeIntegerInRange(deepthinkConfig.maxMessageChars, D.maxMessageChars, 100, 200000),
1667
+ tools: (() => {
1668
+ const raw = deepthinkConfig.tools;
1669
+ if (raw === undefined || raw === null) return [...D.tools];
1670
+ if (Array.isArray(raw)) return raw.map(t => String(t || '').trim()).filter(Boolean);
1671
+ if (typeof raw === 'string') {
1672
+ const trimmed = raw.trim().toLowerCase();
1673
+ if (trimmed === '' || trimmed === 'none' || trimmed === 'off') return [];
1674
+ return raw.split(',').map(t => t.trim()).filter(Boolean);
1675
+ }
1676
+ return [...D.tools];
1677
+ })(),
1678
+ toolRoundLimit: normalizeIntegerInRange(deepthinkConfig.toolRoundLimit, D.toolRoundLimit, 0, 50),
1679
+ saveTranscripts: normalizeBoolean(deepthinkConfig.saveTranscripts, D.saveTranscripts),
1680
+ transcriptDir: typeof deepthinkConfig.transcriptDir === 'string' && deepthinkConfig.transcriptDir.trim()
1681
+ ? deepthinkConfig.transcriptDir.trim()
1682
+ : D.transcriptDir,
1683
+ verbose: normalizeBoolean(deepthinkConfig.verbose, D.verbose),
1684
+ systemPrompt: typeof deepthinkConfig.systemPrompt === 'string' ? deepthinkConfig.systemPrompt : D.systemPrompt,
1685
+ };
1686
+ }
1687
+
1600
1688
  function normalizePromptText(value) {
1601
1689
  if (typeof value === 'string') return value;
1602
1690
  if (value === null || value === undefined) return '';
@@ -1643,6 +1731,7 @@ function normalizeConfig(config = {}) {
1643
1731
  chunking: normalizeChunkingConfig(config.chunking),
1644
1732
  voice: normalizeVoiceConfig(config.voice),
1645
1733
  consultant: normalizeConsultantConfig(config.consultant),
1734
+ deepthink: normalizeDeepthinkConfig(config.deepthink),
1646
1735
  prompt: normalizePromptConfig(config.prompt),
1647
1736
  };
1648
1737
  }
@@ -1775,6 +1864,13 @@ function renderConfigFile(config) {
1775
1864
  lines.push(' // Tip: pick a stronger reasoning model than your main model — that is the whole point.');
1776
1865
  appendConfigProperty(lines, 'consultant', config.consultant);
1777
1866
 
1867
+ lines.push('');
1868
+ lines.push(' // Deep-think tool — multi-turn back-and-forth with a reasoning model.');
1869
+ lines.push(' // Use when the agent hits something unexpected or needs deep reasoning.');
1870
+ lines.push(' // Each call extends an ongoing thought session (pass session_id) or starts a new one.');
1871
+ lines.push(' // Sessions cap at maxTurnsPerSession exchanges; idle sessions are evicted automatically.');
1872
+ appendConfigProperty(lines, 'deepthink', config.deepthink);
1873
+
1778
1874
  lines.push('');
1779
1875
  lines.push(' // Prompt customization');
1780
1876
  lines.push(' // prompt.system.* controls the assistant system prompt blocks');
@@ -2004,6 +2100,14 @@ function consultantEnabled() {
2004
2100
  return getConsultantConfig().enabled;
2005
2101
  }
2006
2102
 
2103
+ function getDeepthinkConfig() {
2104
+ return normalizeDeepthinkConfig(sapperConfig.deepthink);
2105
+ }
2106
+
2107
+ function deepthinkEnabled() {
2108
+ return getDeepthinkConfig().enabled;
2109
+ }
2110
+
2007
2111
  function streamPhaseStatusEnabled() {
2008
2112
  return getStreamingConfig().showPhaseStatus;
2009
2113
  }
@@ -3153,6 +3257,7 @@ const COMMAND_GROUPS = Object.freeze([
3153
3257
  ['/shell read <id>', 'Read output from a tracked shell session'],
3154
3258
  ['/shell stop <id>', 'Stop a tracked shell session'],
3155
3259
  ['/consult', 'Show or configure the heavyweight consultant tool (separate model)'],
3260
+ ['/think', 'Show or configure the deep-think tool (multi-turn reasoning with a separate model)'],
3156
3261
  ['/context', 'Inspect token usage, summary trigger, and model window'],
3157
3262
  ['/ctx <limit>', 'Set context window limit (e.g. /ctx 64k)'],
3158
3263
  ['/debug', 'Toggle regex and tool debug output'],
@@ -6065,6 +6170,292 @@ async function consultExpert({ summary, question, goal, attempts, hints, files }
6065
6170
  return `${header}\n\n${answer}`;
6066
6171
  }
6067
6172
 
6173
+ // ─────────────────────────────────────────────────────────────────
6174
+ // DEEP-THINK TOOL — multi-turn back-and-forth with a reasoning model
6175
+ // Each call extends an ongoing thought session so the main agent can
6176
+ // have a real conversation with the thinker (ask, refine, follow up).
6177
+ // Session state lives in memory and is evicted on idle timeout or LRU.
6178
+ // ─────────────────────────────────────────────────────────────────
6179
+
6180
+ const DEFAULT_DEEPTHINK_SYSTEM_PROMPT = `You are a deep-reasoning partner for a junior coding agent. The agent is stuck or facing something unexpected and wants to think out loud with you across multiple turns.
6181
+
6182
+ Your job each turn:
6183
+ - Respond with substantive reasoning, not pleasantries.
6184
+ - Identify what the agent is actually trying to figure out, separate from what they think the problem is.
6185
+ - Walk through the relevant logic, edge cases, alternatives, and tradeoffs.
6186
+ - End with a concrete next thought, suggestion, or clarifying question — something the agent can act on or push back against.
6187
+ - Stay focused. Each turn should build on the previous one. Do not restart the analysis from scratch every turn.
6188
+
6189
+ You have a small set of READ-ONLY tools (read_file, read_chunk, list_directory, search_files, grep, find, regex_search, head, tail, cat, pwd, changes, fetch_web, recall_memory, search_memory_notes, read_memory_notes — exact set depends on configuration). Use them sparingly when the agent's message references code you need to verify. Do not over-explore: only read what is necessary to answer the current turn. You cannot write, patch, run shell commands, or modify state.
6190
+
6191
+ Be direct. Skip filler like "great question!" or "let me think about this...". Just think.`;
6192
+
6193
+ const deepThinkSessions = new Map(); // session_id -> { id, model, messages, turn, startedAt, lastUsedAt }
6194
+ let _deepThinkCounter = 0;
6195
+
6196
+ function pruneIdleDeepThinkSessions(cfg) {
6197
+ const now = Date.now();
6198
+ for (const [sid, s] of deepThinkSessions) {
6199
+ if (now - s.lastUsedAt > cfg.sessionIdleTimeoutMs) {
6200
+ deepThinkSessions.delete(sid);
6201
+ }
6202
+ }
6203
+ }
6204
+
6205
+ function evictOldestDeepThinkSession() {
6206
+ let oldestSid = null;
6207
+ let oldestTs = Infinity;
6208
+ for (const [sid, s] of deepThinkSessions) {
6209
+ if (s.lastUsedAt < oldestTs) { oldestTs = s.lastUsedAt; oldestSid = sid; }
6210
+ }
6211
+ if (oldestSid) deepThinkSessions.delete(oldestSid);
6212
+ }
6213
+
6214
+ function saveDeepThinkTranscript(cfg, session) {
6215
+ if (!cfg.saveTranscripts) return null;
6216
+ try {
6217
+ const dir = isAbsolute(cfg.transcriptDir)
6218
+ ? cfg.transcriptDir
6219
+ : pathResolve(getToolWorkingDirectory(), cfg.transcriptDir);
6220
+ fs.mkdirSync(dir, { recursive: true });
6221
+ const file = join(dir, `${session.id}.md`);
6222
+ const lines = [
6223
+ `# Deep-Think Session ${session.id}`,
6224
+ `- model: ${session.model}`,
6225
+ `- started: ${new Date(session.startedAt).toISOString()}`,
6226
+ `- last used: ${new Date(session.lastUsedAt).toISOString()}`,
6227
+ `- turns so far: ${session.turn}`,
6228
+ ``,
6229
+ ];
6230
+ for (const m of session.messages) {
6231
+ if (m.role === 'system') continue;
6232
+ lines.push(`## ${m.role}`);
6233
+ if (m.thinking) lines.push(`> _thinking_\n>\n> ${m.thinking.split('\n').join('\n> ')}\n`);
6234
+ lines.push(m.content || '(empty)');
6235
+ lines.push('');
6236
+ }
6237
+ fs.writeFileSync(file, lines.join('\n'));
6238
+ return file;
6239
+ } catch { return null; }
6240
+ }
6241
+
6242
+ async function deepThink({ message, session_id, new_session, system_override } = {}) {
6243
+ const cfg = getDeepthinkConfig();
6244
+ if (!cfg.enabled) {
6245
+ return 'Deep-think tool is disabled (deepthink.enabled = false in .sapper/config.json). Enable it before calling deep_think.';
6246
+ }
6247
+
6248
+ const trimmedMessage = String(message ?? '').trim();
6249
+ if (!trimmedMessage) {
6250
+ return 'Error invoking deep_think: \'message\' is required. Pass the question, thought, or context you want to reason about.';
6251
+ }
6252
+ if (trimmedMessage.length > cfg.maxMessageChars) {
6253
+ return `Error invoking deep_think: message is ${trimmedMessage.length} chars (max ${cfg.maxMessageChars}). Trim it or split into multiple turns.`;
6254
+ }
6255
+
6256
+ pruneIdleDeepThinkSessions(cfg);
6257
+
6258
+ let session;
6259
+ const wantsNew = !!new_session;
6260
+ let sid = String(session_id || '').trim();
6261
+ if (sid && !wantsNew && deepThinkSessions.has(sid)) {
6262
+ session = deepThinkSessions.get(sid);
6263
+ if (session.turn >= cfg.maxTurnsPerSession) {
6264
+ return `Deep-think session ${sid} reached max ${cfg.maxTurnsPerSession} turns. Start a new session by calling deep_think with new_session: true and no session_id.`;
6265
+ }
6266
+ } else {
6267
+ if (deepThinkSessions.size >= cfg.maxSessions) {
6268
+ evictOldestDeepThinkSession();
6269
+ }
6270
+ _deepThinkCounter++;
6271
+ sid = `think-${Date.now().toString(36)}-${_deepThinkCounter}`;
6272
+ const systemPrompt = (typeof system_override === 'string' && system_override.trim())
6273
+ ? system_override.trim()
6274
+ : ((cfg.systemPrompt || '').trim() || DEFAULT_DEEPTHINK_SYSTEM_PROMPT);
6275
+ session = {
6276
+ id: sid,
6277
+ model: cfg.model,
6278
+ messages: [{ role: 'system', content: systemPrompt }],
6279
+ turn: 0,
6280
+ startedAt: Date.now(),
6281
+ lastUsedAt: Date.now(),
6282
+ };
6283
+ deepThinkSessions.set(sid, session);
6284
+ }
6285
+
6286
+ session.messages.push({ role: 'user', content: trimmedMessage });
6287
+ session.turn++;
6288
+ session.lastUsedAt = Date.now();
6289
+
6290
+ // Verbose logging
6291
+ const verbose = cfg.verbose !== false;
6292
+ const dim = chalk.gray;
6293
+ const accent = chalk.hex('#b7b9ff');
6294
+ const log = (m) => { if (verbose) console.log(m); };
6295
+ const logBlock = (label, body, max = 600) => {
6296
+ if (!verbose) return;
6297
+ const text = String(body ?? '').trim();
6298
+ if (!text) { console.log(`${accent('[think]')} ${dim(label + ': (empty)')}`); return; }
6299
+ const truncated = text.length > max ? text.slice(0, max) + dim(`\n... (${text.length - max} more chars)`) : text;
6300
+ console.log(`${accent('[think]')} ${chalk.white(label)}:\n${dim(' ' + truncated.split('\n').join('\n '))}`);
6301
+ };
6302
+
6303
+ console.log();
6304
+ console.log(box(
6305
+ `${keyValue('session', chalk.white(sid), 11)}\n` +
6306
+ `${keyValue('model', chalk.white(session.model), 11)}\n` +
6307
+ `${keyValue('turn', chalk.white(`${session.turn} / ${cfg.maxTurnsPerSession}`), 11)} ${UI.slate('·')} ${UI.slate(`per-turn timeout ${Math.round(cfg.perTurnTimeoutMs / 1000)}s`)}\n` +
6308
+ `${keyValue('tools', chalk.white(cfg.tools && cfg.tools.length ? `${cfg.tools.length} read-only · max ${cfg.toolRoundLimit} rounds` : 'disabled (pure reasoning)'), 11)}`,
6309
+ 'Deep Think', 'magenta'
6310
+ ));
6311
+ logBlock(`turn ${session.turn} message`, trimmedMessage, 1200);
6312
+
6313
+ // ── Detect tool capability and build the allowed read-only tool defs ──
6314
+ const allowedSet = new Set((cfg.tools || []).map(normalizeToolName));
6315
+ let useTools = false;
6316
+ let thinkerNativeTools = [];
6317
+ if (allowedSet.size > 0 && cfg.toolRoundLimit > 0) {
6318
+ try {
6319
+ const info = await ollama.show({ model: session.model });
6320
+ useTools = !!(info?.capabilities && info.capabilities.includes('tools'));
6321
+ } catch { useTools = false; }
6322
+ if (useTools) thinkerNativeTools = buildConsultantNativeTools(allowedSet);
6323
+ }
6324
+
6325
+ const start = Date.now();
6326
+ const spinner = ora(chalk.magenta(`Deep-think turn ${session.turn}...`)).start();
6327
+
6328
+ const callOllama = async (messagesForCall, withThink) => {
6329
+ const chatOpts = {
6330
+ model: session.model,
6331
+ messages: messagesForCall,
6332
+ stream: false,
6333
+ options: {
6334
+ temperature: cfg.temperature,
6335
+ ...(cfg.contextLimit ? { num_ctx: cfg.contextLimit } : {}),
6336
+ },
6337
+ };
6338
+ if (withThink && cfg.thinking === 'on') chatOpts.think = true;
6339
+ if (useTools && thinkerNativeTools.length) chatOpts.tools = thinkerNativeTools;
6340
+ return Promise.race([
6341
+ ollama.chat(chatOpts),
6342
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`per-turn timeout ${cfg.perTurnTimeoutMs}ms exceeded`)), cfg.perTurnTimeoutMs)),
6343
+ ]);
6344
+ };
6345
+
6346
+ let resp;
6347
+ let toolRounds = 0;
6348
+ let aggregatedThinking = '';
6349
+ try {
6350
+ while (true) {
6351
+ try {
6352
+ resp = await callOllama(session.messages, true);
6353
+ } catch (err) {
6354
+ const msg = err?.message || String(err);
6355
+ if (/does not support thinking/i.test(msg)) {
6356
+ resp = await callOllama(session.messages, false);
6357
+ } else {
6358
+ throw err;
6359
+ }
6360
+ }
6361
+
6362
+ const reply = resp?.message || {};
6363
+ const toolCalls = Array.isArray(reply.tool_calls) ? reply.tool_calls : [];
6364
+ if (reply.thinking) aggregatedThinking += (aggregatedThinking ? '\n\n' : '') + reply.thinking;
6365
+
6366
+ if (toolCalls.length === 0 || toolRounds >= cfg.toolRoundLimit || !useTools) {
6367
+ break;
6368
+ }
6369
+
6370
+ // Push the assistant turn that requested tools, then run each tool and feed results back
6371
+ session.messages.push({ role: 'assistant', content: reply.content || '', tool_calls: toolCalls });
6372
+ toolRounds++;
6373
+ spinner.text = chalk.magenta(`Deep-think turn ${session.turn} · tool round ${toolRounds}/${cfg.toolRoundLimit}...`);
6374
+
6375
+ for (const tc of toolCalls) {
6376
+ const fn = tc.function || {};
6377
+ const args = fn.arguments || {};
6378
+ const mapped = normalizeToolName(fn.name || '');
6379
+ const argPreview = (() => {
6380
+ try {
6381
+ const keys = Object.keys(args || {});
6382
+ if (keys.length === 0) return '';
6383
+ return keys.map(k => `${k}=${ellipsis(String(args[k] ?? ''), 50)}`).join(', ');
6384
+ } catch { return ''; }
6385
+ })();
6386
+ spinner.stop();
6387
+ log(` ${accent('→')} ${chalk.white(fn.name)}${argPreview ? dim('(' + argPreview + ')') : ''}`);
6388
+ if (!allowedSet.has(mapped)) {
6389
+ log(` ${chalk.red('blocked')} ${dim('not in allowed tools')}`);
6390
+ session.messages.push({ role: 'tool', tool_name: fn.name, content: `Tool ${fn.name} is not allowed for the deep-think reasoner.` });
6391
+ spinner.start(chalk.magenta(`Deep-think turn ${session.turn} · tool round ${toolRounds}/${cfg.toolRoundLimit}...`));
6392
+ continue;
6393
+ }
6394
+ let toolResult;
6395
+ const tStart = Date.now();
6396
+ try {
6397
+ toolResult = await runConsultantToolCall(fn.name, args);
6398
+ } catch (err) {
6399
+ toolResult = `Error: ${err.message}`;
6400
+ }
6401
+ const tMs = Date.now() - tStart;
6402
+ const resultStr = String(toolResult ?? '');
6403
+ const isErr = /^error/i.test(resultStr.trim());
6404
+ log(` ${isErr ? chalk.red('err') : chalk.green('ok')} ${dim(`${tMs}ms, ${resultStr.length} chars`)}: ${dim(ellipsis(resultStr.replace(/\s+/g, ' '), 200))}`);
6405
+ session.messages.push({
6406
+ role: 'tool',
6407
+ tool_name: fn.name,
6408
+ content: truncateToolText(resultStr, 12000),
6409
+ });
6410
+ spinner.start(chalk.magenta(`Deep-think turn ${session.turn} · tool round ${toolRounds}/${cfg.toolRoundLimit}...`));
6411
+ }
6412
+ }
6413
+ } catch (err) {
6414
+ spinner.stop();
6415
+ const msg = err?.message || String(err);
6416
+ // Roll back the user message and any half-pushed assistant/tool entries so the session stays usable
6417
+ while (session.messages.length > 0 && session.messages[session.messages.length - 1].role !== 'user') {
6418
+ session.messages.pop();
6419
+ }
6420
+ if (session.messages.length > 0 && session.messages[session.messages.length - 1].role === 'user') {
6421
+ session.messages.pop();
6422
+ }
6423
+ session.turn--;
6424
+ log(`${chalk.red('[think]')} error: ${msg}`);
6425
+ return `Deep-think error on session ${sid}: ${msg}`;
6426
+ }
6427
+ spinner.stop();
6428
+
6429
+ const replyMessage = resp?.message || {};
6430
+ const content = replyMessage.content || '';
6431
+ const thinking = aggregatedThinking || replyMessage.thinking || '';
6432
+ const elapsed = Math.round((Date.now() - start) / 1000);
6433
+
6434
+ session.messages.push({
6435
+ role: 'assistant',
6436
+ content,
6437
+ ...(thinking ? { thinking } : {}),
6438
+ });
6439
+ session.lastUsedAt = Date.now();
6440
+
6441
+ if (thinking) logBlock(`turn ${session.turn} reasoning (${elapsed}s)`, thinking, 800);
6442
+ if (toolRounds > 0) log(`${accent('[think]')} ${dim(`turn ${session.turn} ran ${toolRounds} tool round${toolRounds === 1 ? '' : 's'}`)}`);
6443
+ logBlock(`turn ${session.turn} reply`, content, 1500);
6444
+
6445
+ const transcriptPath = saveDeepThinkTranscript(cfg, session);
6446
+ const remaining = cfg.maxTurnsPerSession - session.turn;
6447
+ const transcriptNote = transcriptPath
6448
+ ? ` · transcript: ${relative(getToolWorkingDirectory(), transcriptPath) || transcriptPath}`
6449
+ : '';
6450
+
6451
+ const header = `Deep-think session ${sid} · turn ${session.turn}/${cfg.maxTurnsPerSession} · ${elapsed}s${transcriptNote}`;
6452
+ const continuation = remaining > 0
6453
+ ? `\n\nTo continue this thought, call deep_think again with session_id="${sid}" and a follow-up message. ${remaining} turn${remaining === 1 ? '' : 's'} remaining.`
6454
+ : `\n\nThis was the final turn for this session. Start a fresh thought with new_session: true if you need to keep reasoning.`;
6455
+
6456
+ return `${header}\n\n${content || '(no content returned)'}${continuation}`;
6457
+ }
6458
+
6068
6459
  const tools = {
6069
6460
  read: (path) => {
6070
6461
  const trimmedPath = typeof path === 'string' ? path.trim() : '';
@@ -6616,7 +7007,8 @@ const tools = {
6616
7007
  });
6617
7008
  });
6618
7009
  },
6619
- consult: async (args) => consultExpert(args || {})
7010
+ consult: async (args) => consultExpert(args || {}),
7011
+ deepthink: async (args) => deepThink(args || {})
6620
7012
  };
6621
7013
 
6622
7014
  async function checkForUpdates() {
@@ -7228,6 +7620,22 @@ async function runSapper() {
7228
7620
  required: ['goal', 'question', 'summary']
7229
7621
  }
7230
7622
  }
7623
+ },
7624
+ {
7625
+ type: 'function',
7626
+ function: {
7627
+ name: 'deep_think',
7628
+ description: 'Have a multi-turn back-and-forth with a separate reasoning model . Use when you hit something unexpected, need deep reasoning, or want to think out loud with a smarter model. Each call extends an ongoing thought session if you pass session_id; otherwise a new session starts. Sessions cap at a configured max turns. The thinker has NO tools \u2014 it reasons from the message you give it. Include relevant code/context inline. Returns the thinker\\u2019s reply plus the session_id to use for the next turn.',
7629
+ parameters: {
7630
+ type: 'object',
7631
+ properties: {
7632
+ message: { type: 'string', description: 'The question, thought, or context to reason about. The thinker has read-only tools, so referencing paths and what to inspect is enough — include errors, constraints, or short code snippets when they matter.' },
7633
+ session_id: { type: 'string', description: 'Optional. Pass the session_id from a previous deep_think reply to continue the same conversation. Omit to start a new session.' },
7634
+ new_session: { type: 'boolean', description: 'Optional. Set true to force a new thought session even if session_id is provided.' }
7635
+ },
7636
+ required: ['message']
7637
+ }
7638
+ }
7231
7639
  }
7232
7640
  ];
7233
7641
 
@@ -8362,6 +8770,169 @@ async function runSapper() {
8362
8770
  continue;
8363
8771
  }
8364
8772
 
8773
+ if (input.toLowerCase() === '/think' || input.toLowerCase().startsWith('/think ')) {
8774
+ const arg = input.substring(6).trim();
8775
+ const cfg = getDeepthinkConfig();
8776
+ const updateThink = (patch) => {
8777
+ sapperConfig.deepthink = { ...getDeepthinkConfig(), ...patch };
8778
+ saveConfig(sapperConfig);
8779
+ };
8780
+
8781
+ if (!arg || ['status', 'show'].includes(arg.toLowerCase())) {
8782
+ const liveSessions = Array.from(deepThinkSessions.values()).sort((a, b) => b.lastUsedAt - a.lastUsedAt);
8783
+ const lines = [
8784
+ `enabled ${chalk.white(cfg.enabled ? 'on' : 'off')}`,
8785
+ `model ${chalk.white(cfg.model)}`,
8786
+ `loop ${UI.slate(`max ${cfg.maxTurnsPerSession} turns/session \u00b7 max ${cfg.maxSessions} sessions \u00b7 idle ${Math.round(cfg.sessionIdleTimeoutMs/60000)}m \u00b7 per-turn ${Math.round(cfg.perTurnTimeoutMs/1000)}s`)}`,
8787
+ `reasoning ${UI.slate(`thinking ${cfg.thinking} \u00b7 temp ${cfg.temperature} \u00b7 max msg ${cfg.maxMessageChars} chars`)}`,
8788
+ `tools ${chalk.white(cfg.tools && cfg.tools.length ? `${cfg.tools.length} read-only` : 'disabled')} ${UI.slate('\u00b7')} ${UI.slate(`max ${cfg.toolRoundLimit} rounds/turn`)}`,
8789
+ ` ${UI.slate((cfg.tools || []).join(', ') || '(no tools \u2014 pure reasoning from the message)')}`,
8790
+ `transcripts ${chalk.white(cfg.saveTranscripts ? 'on' : 'off')} ${UI.slate('\u2192')} ${UI.slate(cfg.transcriptDir)}`,
8791
+ `verbose ${chalk.white(cfg.verbose ? 'on' : 'off')}`,
8792
+ `live sessions ${chalk.white(`${liveSessions.length}`)}`,
8793
+ ];
8794
+ for (const s of liveSessions.slice(0, 5)) {
8795
+ const ageMin = Math.round((Date.now() - s.lastUsedAt) / 60000);
8796
+ lines.push(` ${chalk.white(s.id)} ${UI.slate('\u00b7')} ${UI.slate(`turn ${s.turn}/${cfg.maxTurnsPerSession}`)} ${UI.slate('\u00b7')} ${UI.slate(`idle ${ageMin}m`)}`);
8797
+ }
8798
+ lines.push('');
8799
+ lines.push(UI.slate('Usage: /think model <name> | /think on|off | /think turns <N> | /think sessions <N>'));
8800
+ lines.push(UI.slate(' /think idle <minutes> | /think timeout <secs> | /think thinking <auto|on|off>'));
8801
+ lines.push(UI.slate(' /think temp <0..2> | /think maxmsg <chars> | /think tools <a,b,c | none>'));
8802
+ lines.push(UI.slate(' /think toolrounds <N> | /think transcripts <on|off> | /think verbose <on|off>'));
8803
+ lines.push(UI.slate(' /think list | /think clear [<id>] | /think reset'));
8804
+ console.log();
8805
+ console.log(box(lines.join('\n'), 'Deep Think', 'magenta'));
8806
+ continue;
8807
+ }
8808
+
8809
+ const [subcommandRaw, ...rest] = arg.split(/\s+/);
8810
+ const subcommand = subcommandRaw.toLowerCase();
8811
+ const value = rest.join(' ').trim();
8812
+
8813
+ if (subcommand === 'reset' || subcommand === 'default') {
8814
+ sapperConfig.deepthink = { ...DEFAULT_CONFIG.deepthink };
8815
+ saveConfig(sapperConfig);
8816
+ console.log(chalk.green('Deep-think settings reset to defaults.'));
8817
+ continue;
8818
+ }
8819
+ if (['on', 'true', 'yes', 'enable', 'enabled'].includes(subcommand)) { updateThink({ enabled: true }); console.log(chalk.green('Deep-think enabled.')); continue; }
8820
+ if (['off', 'false', 'no', 'disable', 'disabled'].includes(subcommand)) { updateThink({ enabled: false }); console.log(chalk.yellow('Deep-think disabled.')); continue; }
8821
+ if (subcommand === 'model') {
8822
+ if (!value) { console.log(chalk.yellow('Usage: /think model <name>')); continue; }
8823
+ updateThink({ model: value });
8824
+ console.log(chalk.green(`Deep-think model set to ${value}.`));
8825
+ continue;
8826
+ }
8827
+ if (subcommand === 'turns' || subcommand === 'maxturns') {
8828
+ const n = parseInt(value, 10);
8829
+ if (!Number.isFinite(n)) { console.log(chalk.yellow('Usage: /think turns <N>')); continue; }
8830
+ updateThink({ maxTurnsPerSession: n });
8831
+ console.log(chalk.green(`Deep-think max turns per session: ${getDeepthinkConfig().maxTurnsPerSession}`));
8832
+ continue;
8833
+ }
8834
+ if (subcommand === 'sessions' || subcommand === 'maxsessions') {
8835
+ const n = parseInt(value, 10);
8836
+ if (!Number.isFinite(n)) { console.log(chalk.yellow('Usage: /think sessions <N>')); continue; }
8837
+ updateThink({ maxSessions: n });
8838
+ console.log(chalk.green(`Deep-think max concurrent sessions: ${getDeepthinkConfig().maxSessions}`));
8839
+ continue;
8840
+ }
8841
+ if (subcommand === 'idle') {
8842
+ const n = parseInt(value, 10);
8843
+ if (!Number.isFinite(n)) { console.log(chalk.yellow('Usage: /think idle <minutes>')); continue; }
8844
+ updateThink({ sessionIdleTimeoutMs: n * 60000 });
8845
+ console.log(chalk.green(`Deep-think idle timeout: ${n}m`));
8846
+ continue;
8847
+ }
8848
+ if (subcommand === 'timeout') {
8849
+ const n = parseInt(value, 10);
8850
+ if (!Number.isFinite(n)) { console.log(chalk.yellow('Usage: /think timeout <seconds>')); continue; }
8851
+ updateThink({ perTurnTimeoutMs: n * 1000 });
8852
+ console.log(chalk.green(`Deep-think per-turn timeout: ${n}s`));
8853
+ continue;
8854
+ }
8855
+ if (subcommand === 'thinking') {
8856
+ updateThink({ thinking: value });
8857
+ console.log(chalk.green(`Deep-think reasoning mode: ${getDeepthinkConfig().thinking}`));
8858
+ continue;
8859
+ }
8860
+ if (subcommand === 'temp' || subcommand === 'temperature') {
8861
+ const n = Number(value);
8862
+ if (!Number.isFinite(n)) { console.log(chalk.yellow('Usage: /think temp <0..2>')); continue; }
8863
+ updateThink({ temperature: n });
8864
+ console.log(chalk.green(`Deep-think temperature: ${getDeepthinkConfig().temperature}`));
8865
+ continue;
8866
+ }
8867
+ if (subcommand === 'maxmsg' || subcommand === 'msgmax') {
8868
+ const n = parseInt(value, 10);
8869
+ if (!Number.isFinite(n)) { console.log(chalk.yellow('Usage: /think maxmsg <chars>')); continue; }
8870
+ updateThink({ maxMessageChars: n });
8871
+ console.log(chalk.green(`Deep-think max message chars: ${getDeepthinkConfig().maxMessageChars}`));
8872
+ continue;
8873
+ }
8874
+ if (subcommand === 'tools') {
8875
+ if (!value || ['none', 'off', 'disable', 'disabled', '[]'].includes(value.toLowerCase())) {
8876
+ updateThink({ tools: [] });
8877
+ console.log(chalk.yellow('Deep-think tools: disabled (thinker now reasons only from the message)'));
8878
+ continue;
8879
+ }
8880
+ if (['default', 'reset'].includes(value.toLowerCase())) {
8881
+ updateThink({ tools: [...DEFAULT_CONFIG.deepthink.tools] });
8882
+ console.log(chalk.green(`Deep-think tools reset to defaults: ${getDeepthinkConfig().tools.join(', ')}`));
8883
+ continue;
8884
+ }
8885
+ const list = value.split(/[,\s]+/).map(t => t.trim()).filter(Boolean);
8886
+ if (!list.length) { console.log(chalk.yellow('Usage: /think tools read,list,grep,... | /think tools none | /think tools default')); continue; }
8887
+ updateThink({ tools: list });
8888
+ console.log(chalk.green(`Deep-think tools: ${getDeepthinkConfig().tools.join(', ')}`));
8889
+ continue;
8890
+ }
8891
+ if (subcommand === 'toolrounds' || subcommand === 'rounds') {
8892
+ const n = parseInt(value, 10);
8893
+ if (!Number.isFinite(n)) { console.log(chalk.yellow('Usage: /think toolrounds <N> (0 disables tool calls for the thinker)')); continue; }
8894
+ updateThink({ toolRoundLimit: n });
8895
+ console.log(chalk.green(`Deep-think tool-round limit: ${getDeepthinkConfig().toolRoundLimit}`));
8896
+ continue;
8897
+ }
8898
+ if (subcommand === 'transcripts' || subcommand === 'transcript') {
8899
+ if (['on', 'true', 'yes'].includes(value.toLowerCase())) { updateThink({ saveTranscripts: true }); console.log(chalk.green('Deep-think transcripts: on')); continue; }
8900
+ if (['off', 'false', 'no'].includes(value.toLowerCase())) { updateThink({ saveTranscripts: false }); console.log(chalk.yellow('Deep-think transcripts: off')); continue; }
8901
+ console.log(chalk.yellow('Usage: /think transcripts on|off'));
8902
+ continue;
8903
+ }
8904
+ if (subcommand === 'verbose' || subcommand === 'log' || subcommand === 'logging') {
8905
+ if (['on', 'true', 'yes', '1'].includes(value.toLowerCase())) { updateThink({ verbose: true }); console.log(chalk.green('Deep-think verbose logging: on')); continue; }
8906
+ if (['off', 'false', 'no', '0'].includes(value.toLowerCase())) { updateThink({ verbose: false }); console.log(chalk.yellow('Deep-think verbose logging: off')); continue; }
8907
+ console.log(chalk.yellow('Usage: /think verbose on|off'));
8908
+ continue;
8909
+ }
8910
+ if (subcommand === 'list' || subcommand === 'sess' || subcommand === 'ls') {
8911
+ if (deepThinkSessions.size === 0) { console.log(UI.slate('No active deep-think sessions.')); continue; }
8912
+ for (const s of deepThinkSessions.values()) {
8913
+ const ageMin = Math.round((Date.now() - s.lastUsedAt) / 60000);
8914
+ console.log(` ${chalk.white(s.id)} ${UI.slate('\u00b7')} ${UI.slate(`turn ${s.turn}/${cfg.maxTurnsPerSession}`)} ${UI.slate('\u00b7')} ${UI.slate(`idle ${ageMin}m`)}`);
8915
+ }
8916
+ continue;
8917
+ }
8918
+ if (subcommand === 'clear' || subcommand === 'drop' || subcommand === 'end') {
8919
+ if (!value) {
8920
+ const n = deepThinkSessions.size;
8921
+ deepThinkSessions.clear();
8922
+ console.log(chalk.green(`Cleared ${n} deep-think session${n === 1 ? '' : 's'}.`));
8923
+ } else if (deepThinkSessions.delete(value)) {
8924
+ console.log(chalk.green(`Cleared session ${value}.`));
8925
+ } else {
8926
+ console.log(chalk.yellow(`No session named ${value}.`));
8927
+ }
8928
+ continue;
8929
+ }
8930
+
8931
+ console.log(chalk.yellow(`Unknown think option: ${subcommand}`));
8932
+ console.log(chalk.gray(' Usage: /think | /think model <name> | /think on|off | /think turns <N> | /think sessions <N> | /think idle <m> | /think timeout <s> | /think thinking <auto|on|off> | /think temp <0..2> | /think maxmsg <chars> | /think tools <a,b,c | none | default> | /think toolrounds <N> | /think transcripts on|off | /think verbose on|off | /think list | /think clear [<id>] | /think reset'));
8933
+ continue;
8934
+ }
8935
+
8365
8936
  if (input.toLowerCase().startsWith('/ui')) {
8366
8937
  const arg = input.substring(3).trim();
8367
8938
  const currentUI = getUIConfig();
@@ -9698,7 +10269,7 @@ async function runSapper() {
9698
10269
  ls: 'LS', cat: 'CAT', head: 'HEAD', tail: 'TAIL', grep: 'GREP', find: 'FIND',
9699
10270
  pwd: 'PWD', cd: 'CD', rmdir: 'RMDIR', changes: 'CHANGES',
9700
10271
  fetch_web: 'FETCH', recall_memory: 'MEMORY', open_url: 'OPEN', run_shell: 'SHELL',
9701
- consult_expert: 'CONSULT'
10272
+ consult_expert: 'CONSULT', deep_think: 'DEEPTHINK'
9702
10273
  };
9703
10274
 
9704
10275
  showStreamPhase(`Running ${nativeToolCalls.length} native tool call${nativeToolCalls.length === 1 ? '' : 's'}...`);
@@ -9830,6 +10401,10 @@ async function runSapper() {
9830
10401
  result = await tools.consult(args);
9831
10402
  logEntry('tool', { toolType: 'CONSULT', path: (args && args.question ? String(args.question).slice(0, 80) : 'consult'), duration: Date.now() - toolStart, success: !String(result).startsWith('Error'), resultSize: result?.length });
9832
10403
  break;
10404
+ case 'deep_think':
10405
+ result = await tools.deepthink(args);
10406
+ logEntry('tool', { toolType: 'DEEPTHINK', path: (args && args.message ? String(args.message).slice(0, 80) : 'deepthink'), duration: Date.now() - toolStart, success: !String(result).startsWith('Error'), resultSize: result?.length });
10407
+ break;
9833
10408
  default:
9834
10409
  result = `Unknown tool: ${fn.name}`;
9835
10410
  toolSuccess = false;
@@ -10119,9 +10694,37 @@ async function runSapper() {
10119
10694
  result = await tools.consult(consultArgs);
10120
10695
  logEntry('tool', { toolType: 'CONSULT', path: (consultArgs.question || 'consult').slice(0, 80), duration: Date.now() - toolStart, success: !String(result).startsWith('Error'), resultSize: result?.length });
10121
10696
  }
10697
+ else if (type.toLowerCase() === 'deepthink' || type.toLowerCase() === 'think' || type.toLowerCase() === 'talk' || type.toLowerCase() === 'moe') {
10698
+ // Text-marker forms:
10699
+ // [TOOL:THINK]<message>[/TOOL] -> new session
10700
+ // [TOOL:THINK]session_id:::<message>[/TOOL] -> continue session
10701
+ // [TOOL:THINK]{"message":"...","session_id":"..."}[/TOOL]
10702
+ let thinkArgs = null;
10703
+ const raw = (content && content.trim()) ? `${path}:::${content}` : String(path || '');
10704
+ const trimmedRaw = raw.trim();
10705
+ if (trimmedRaw.startsWith('{')) {
10706
+ try { thinkArgs = JSON.parse(trimmedRaw); } catch { thinkArgs = null; }
10707
+ }
10708
+ if (!thinkArgs) {
10709
+ const sepIdx = trimmedRaw.indexOf(':::');
10710
+ if (sepIdx > -1) {
10711
+ const maybeSid = trimmedRaw.slice(0, sepIdx).trim();
10712
+ // Treat the first segment as a session_id only if it looks like one
10713
+ if (/^think-[a-z0-9-]+$/i.test(maybeSid)) {
10714
+ thinkArgs = { session_id: maybeSid, message: trimmedRaw.slice(sepIdx + 3).trim() };
10715
+ } else {
10716
+ thinkArgs = { message: trimmedRaw };
10717
+ }
10718
+ } else {
10719
+ thinkArgs = { message: trimmedRaw };
10720
+ }
10721
+ }
10722
+ result = await tools.deepthink(thinkArgs);
10723
+ logEntry('tool', { toolType: 'DEEPTHINK', path: ((thinkArgs && thinkArgs.message) || 'deepthink').slice(0, 80), duration: Date.now() - toolStart, success: !String(result).startsWith('Error'), resultSize: result?.length });
10724
+ }
10122
10725
 
10123
10726
  // Log tool execution (for non-shell, non-file specific ones)
10124
- if (!['list', 'ls', 'read', 'cat', 'head', 'tail', 'mkdir', 'rmdir', 'pwd', 'cd', 'write', 'patch', 'search', 'grep', 'find', 'regex', 'chunk', 'read_chunk', 'changes', 'fetch', 'memory', 'memory_note_save', 'memory_note_search', 'memory_note_read', 'open', 'shell', 'consult'].includes(type.toLowerCase())) {
10727
+ if (!['list', 'ls', 'read', 'cat', 'head', 'tail', 'mkdir', 'rmdir', 'pwd', 'cd', 'write', 'patch', 'search', 'grep', 'find', 'regex', 'chunk', 'read_chunk', 'changes', 'fetch', 'memory', 'memory_note_save', 'memory_note_search', 'memory_note_read', 'open', 'shell', 'consult', 'deepthink', 'think', 'talk', 'moe'].includes(type.toLowerCase())) {
10125
10728
  logEntry('tool', { toolType: type.toUpperCase(), path, duration: Date.now() - toolStart, success: toolSuccess, resultSize: result?.length, error: toolSuccess ? undefined : result });
10126
10729
  }
10127
10730