sapper-iq 1.4.6 → 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.
- package/package.json +1 -1
- package/sapper.mjs +684 -12
package/package.json
CHANGED
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 = '') {
|
|
@@ -1171,8 +1180,31 @@ const DEFAULT_CONFIG = Object.freeze({
|
|
|
1171
1180
|
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 by default — no write / patch / shell / open / mkdir / rmdir.
|
|
1172
1181
|
saveTranscripts: true, // Persist every consultation to disk for audit.
|
|
1173
1182
|
transcriptDir: '.sapper/consultations', // Where transcripts are written.
|
|
1183
|
+
verbose: true, // Print full request, each tool call/result, and final answer to the terminal during the consultation.
|
|
1174
1184
|
systemPrompt: '', // Override the built-in consultant system prompt (empty = use default).
|
|
1175
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
|
+
}),
|
|
1176
1208
|
prompt: Object.freeze({
|
|
1177
1209
|
prepend: '',
|
|
1178
1210
|
append: '',
|
|
@@ -1192,7 +1224,7 @@ RULES:
|
|
|
1192
1224
|
5. NO HALLUCINATIONS: If a file doesn't exist, don't guess its content. List the directory instead.`,
|
|
1193
1225
|
nativeTools: `TOOLS:
|
|
1194
1226
|
You have function-calling tools available. Call them directly — do NOT use [TOOL:...] text markers.
|
|
1195
|
-
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.
|
|
1196
1228
|
|
|
1197
1229
|
PATCH TIPS:
|
|
1198
1230
|
- For patch_file, set old_text to "LINE:<number>" to replace a specific line by number (most reliable).
|
|
@@ -1220,6 +1252,12 @@ CONSULTANT (consult_expert):
|
|
|
1220
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).
|
|
1221
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.
|
|
1222
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
|
+
|
|
1223
1261
|
SHELL TIPS:
|
|
1224
1262
|
- run_shell may keep long-running commands in a background session depending on config.
|
|
1225
1263
|
- If a shell result returns a session id, inspect more output with run_shell command "__shell_read__ <session_id>".
|
|
@@ -1253,6 +1291,9 @@ SHELL TIPS:
|
|
|
1253
1291
|
- [TOOL:OPEN]https://example.com[/TOOL] - Open a URL in the default browser (asks for approval)
|
|
1254
1292
|
- [TOOL:SHELL]command[/TOOL] - Run shell command
|
|
1255
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.
|
|
1256
1297
|
|
|
1257
1298
|
PATCH TIPS:
|
|
1258
1299
|
- PREFER the LINE:number mode when you know which line to change. It is much more reliable than text matching.
|
|
@@ -1591,10 +1632,59 @@ function normalizeConsultantConfig(consultantConfig = {}) {
|
|
|
1591
1632
|
transcriptDir: typeof consultantConfig.transcriptDir === 'string' && consultantConfig.transcriptDir.trim()
|
|
1592
1633
|
? consultantConfig.transcriptDir.trim()
|
|
1593
1634
|
: D.transcriptDir,
|
|
1635
|
+
verbose: normalizeBoolean(consultantConfig.verbose, D.verbose),
|
|
1594
1636
|
systemPrompt: typeof consultantConfig.systemPrompt === 'string' ? consultantConfig.systemPrompt : D.systemPrompt,
|
|
1595
1637
|
};
|
|
1596
1638
|
}
|
|
1597
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
|
+
|
|
1598
1688
|
function normalizePromptText(value) {
|
|
1599
1689
|
if (typeof value === 'string') return value;
|
|
1600
1690
|
if (value === null || value === undefined) return '';
|
|
@@ -1641,6 +1731,7 @@ function normalizeConfig(config = {}) {
|
|
|
1641
1731
|
chunking: normalizeChunkingConfig(config.chunking),
|
|
1642
1732
|
voice: normalizeVoiceConfig(config.voice),
|
|
1643
1733
|
consultant: normalizeConsultantConfig(config.consultant),
|
|
1734
|
+
deepthink: normalizeDeepthinkConfig(config.deepthink),
|
|
1644
1735
|
prompt: normalizePromptConfig(config.prompt),
|
|
1645
1736
|
};
|
|
1646
1737
|
}
|
|
@@ -1773,6 +1864,13 @@ function renderConfigFile(config) {
|
|
|
1773
1864
|
lines.push(' // Tip: pick a stronger reasoning model than your main model — that is the whole point.');
|
|
1774
1865
|
appendConfigProperty(lines, 'consultant', config.consultant);
|
|
1775
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
|
+
|
|
1776
1874
|
lines.push('');
|
|
1777
1875
|
lines.push(' // Prompt customization');
|
|
1778
1876
|
lines.push(' // prompt.system.* controls the assistant system prompt blocks');
|
|
@@ -2002,6 +2100,14 @@ function consultantEnabled() {
|
|
|
2002
2100
|
return getConsultantConfig().enabled;
|
|
2003
2101
|
}
|
|
2004
2102
|
|
|
2103
|
+
function getDeepthinkConfig() {
|
|
2104
|
+
return normalizeDeepthinkConfig(sapperConfig.deepthink);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
function deepthinkEnabled() {
|
|
2108
|
+
return getDeepthinkConfig().enabled;
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2005
2111
|
function streamPhaseStatusEnabled() {
|
|
2006
2112
|
return getStreamingConfig().showPhaseStatus;
|
|
2007
2113
|
}
|
|
@@ -3151,6 +3257,7 @@ const COMMAND_GROUPS = Object.freeze([
|
|
|
3151
3257
|
['/shell read <id>', 'Read output from a tracked shell session'],
|
|
3152
3258
|
['/shell stop <id>', 'Stop a tracked shell session'],
|
|
3153
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)'],
|
|
3154
3261
|
['/context', 'Inspect token usage, summary trigger, and model window'],
|
|
3155
3262
|
['/ctx <limit>', 'Set context window limit (e.g. /ctx 64k)'],
|
|
3156
3263
|
['/debug', 'Toggle regex and tool debug output'],
|
|
@@ -5901,6 +6008,34 @@ async function consultExpert({ summary, question, goal, attempts, hints, files }
|
|
|
5901
6008
|
'Consulting expert', 'magenta'
|
|
5902
6009
|
));
|
|
5903
6010
|
|
|
6011
|
+
const verbose = cfg.verbose !== false;
|
|
6012
|
+
const dim = chalk.gray;
|
|
6013
|
+
const accent = chalk.hex('#b7b9ff'); // magenta tone
|
|
6014
|
+
const log = (msg) => { if (verbose) console.log(msg); };
|
|
6015
|
+
const logBlock = (label, body, max = 600) => {
|
|
6016
|
+
if (!verbose) return;
|
|
6017
|
+
const text = String(body ?? '').trim();
|
|
6018
|
+
if (!text) { console.log(`${accent('[consult]')} ${dim(label + ': (empty)')}`); return; }
|
|
6019
|
+
const truncated = text.length > max ? text.slice(0, max) + dim(`\n... (${text.length - max} more chars)`) : text;
|
|
6020
|
+
console.log(`${accent('[consult]')} ${chalk.white(label)}:\n${dim(' ' + truncated.split('\\n').join('\n '))}`);
|
|
6021
|
+
};
|
|
6022
|
+
|
|
6023
|
+
// Show what we are about to send to the consultant
|
|
6024
|
+
log(`${accent('[consult]')} ${chalk.white('request')} ${dim('to')} ${chalk.white(cfg.model)}`);
|
|
6025
|
+
logBlock('goal', goal);
|
|
6026
|
+
logBlock('question', question);
|
|
6027
|
+
logBlock('summary', summaryText, 800);
|
|
6028
|
+
if (attempts) logBlock('attempts', attempts);
|
|
6029
|
+
if (hints) logBlock('hints', hints);
|
|
6030
|
+
if (attachments.length > 0) {
|
|
6031
|
+
log(`${accent('[consult]')} ${chalk.white('files')} ${dim(`(${attachments.length}, ${formatBytes(totalBytes)})`)}:`);
|
|
6032
|
+
for (const a of attachments) {
|
|
6033
|
+
log(` ${a.error ? chalk.red('!') : chalk.green('+')} ${chalk.white(a.path)}${a.error ? dim(` -- ${a.error}`) : ''}`);
|
|
6034
|
+
}
|
|
6035
|
+
} else {
|
|
6036
|
+
log(`${accent('[consult]')} ${dim('files: none attached \u2014 consultant must rely on its own tools')}`);
|
|
6037
|
+
}
|
|
6038
|
+
|
|
5904
6039
|
const consultantSpinner = ora(chalk.magenta(`Consultant (${cfg.model.split(':')[0]}) is thinking...`)).start();
|
|
5905
6040
|
let finalAnswer = '';
|
|
5906
6041
|
let rounds = 0;
|
|
@@ -5908,6 +6043,7 @@ async function consultExpert({ summary, question, goal, attempts, hints, files }
|
|
|
5908
6043
|
while (true) {
|
|
5909
6044
|
if (Date.now() > deadline) {
|
|
5910
6045
|
consultantSpinner.stop();
|
|
6046
|
+
log(`${chalk.red('[consult]')} timeout hit at ${Math.round((Date.now() - startedAt) / 1000)}s`);
|
|
5911
6047
|
return `Consultant aborted: hit timeout of ${Math.round(cfg.timeoutMs / 1000)}s before finishing.\n\nPartial answer:\n${finalAnswer || '(none produced yet)'}`;
|
|
5912
6048
|
}
|
|
5913
6049
|
|
|
@@ -5923,12 +6059,13 @@ async function consultExpert({ summary, question, goal, attempts, hints, files }
|
|
|
5923
6059
|
if (cfg.thinking === 'on') chatOpts.think = true;
|
|
5924
6060
|
if (useTools && consultantNativeTools.length) chatOpts.tools = consultantNativeTools;
|
|
5925
6061
|
|
|
6062
|
+
const roundStart = Date.now();
|
|
5926
6063
|
let resp;
|
|
5927
6064
|
try {
|
|
5928
6065
|
resp = await ollama.chat(chatOpts);
|
|
5929
6066
|
} catch (err) {
|
|
5930
|
-
const
|
|
5931
|
-
if (/does not support thinking/i.test(
|
|
6067
|
+
const errMsg = err?.message || String(err);
|
|
6068
|
+
if (/does not support thinking/i.test(errMsg) && chatOpts.think) {
|
|
5932
6069
|
delete chatOpts.think;
|
|
5933
6070
|
resp = await ollama.chat(chatOpts);
|
|
5934
6071
|
} else {
|
|
@@ -5938,7 +6075,16 @@ async function consultExpert({ summary, question, goal, attempts, hints, files }
|
|
|
5938
6075
|
|
|
5939
6076
|
const msg = resp?.message || {};
|
|
5940
6077
|
const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
|
|
5941
|
-
|
|
6078
|
+
const replyContent = msg.content || '';
|
|
6079
|
+
const replyThinking = msg.thinking || '';
|
|
6080
|
+
finalAnswer = replyContent || finalAnswer;
|
|
6081
|
+
|
|
6082
|
+
consultantSpinner.stop();
|
|
6083
|
+
if (replyThinking) logBlock(`round ${rounds} thinking (${Math.round((Date.now() - roundStart) / 1000)}s)`, replyThinking, 400);
|
|
6084
|
+
if (replyContent) logBlock(`round ${rounds} content`, replyContent, 1200);
|
|
6085
|
+
if (toolCalls.length > 0) {
|
|
6086
|
+
log(`${accent('[consult]')} ${chalk.white(`round ${rounds} \u2192 ${toolCalls.length} tool call${toolCalls.length === 1 ? '' : 's'}`)}`);
|
|
6087
|
+
}
|
|
5942
6088
|
|
|
5943
6089
|
if (toolCalls.length === 0 || rounds >= cfg.toolRoundLimit) {
|
|
5944
6090
|
break;
|
|
@@ -5947,31 +6093,49 @@ async function consultExpert({ summary, question, goal, attempts, hints, files }
|
|
|
5947
6093
|
// Push assistant message with tool_calls
|
|
5948
6094
|
consultMessages.push({ role: 'assistant', content: msg.content || '', tool_calls: toolCalls });
|
|
5949
6095
|
rounds++;
|
|
5950
|
-
consultantSpinner.
|
|
6096
|
+
consultantSpinner.start(chalk.magenta(`Consultant running tool round ${rounds}/${cfg.toolRoundLimit}...`));
|
|
5951
6097
|
|
|
5952
6098
|
for (const tc of toolCalls) {
|
|
5953
6099
|
const fn = tc.function || {};
|
|
5954
6100
|
const args = fn.arguments || {};
|
|
5955
6101
|
const mapped = normalizeToolName(fn.name || '');
|
|
6102
|
+
const argPreview = (() => {
|
|
6103
|
+
try {
|
|
6104
|
+
const keys = Object.keys(args || {});
|
|
6105
|
+
if (keys.length === 0) return '';
|
|
6106
|
+
return keys.map(k => `${k}=${ellipsis(String(args[k] ?? ''), 60)}`).join(', ');
|
|
6107
|
+
} catch { return ''; }
|
|
6108
|
+
})();
|
|
6109
|
+
consultantSpinner.stop();
|
|
6110
|
+
log(` ${accent('\u2192')} ${chalk.white(fn.name)}${argPreview ? dim('(' + argPreview + ')') : ''}`);
|
|
5956
6111
|
if (!allowedSet.has(mapped)) {
|
|
6112
|
+
log(` ${chalk.red('blocked')} ${dim('not in allowed tools')}`);
|
|
5957
6113
|
consultMessages.push({ role: 'tool', tool_name: fn.name, content: `Tool ${fn.name} is not allowed for the consultant (read-only restriction).` });
|
|
6114
|
+
consultantSpinner.start(chalk.magenta(`Consultant running tool round ${rounds}/${cfg.toolRoundLimit}...`));
|
|
5958
6115
|
continue;
|
|
5959
6116
|
}
|
|
5960
6117
|
let toolResult;
|
|
6118
|
+
const tStart = Date.now();
|
|
5961
6119
|
try {
|
|
5962
6120
|
toolResult = await runConsultantToolCall(fn.name, args);
|
|
5963
6121
|
} catch (err) {
|
|
5964
6122
|
toolResult = `Error: ${err.message}`;
|
|
5965
6123
|
}
|
|
6124
|
+
const tMs = Date.now() - tStart;
|
|
6125
|
+
const resultStr = String(toolResult ?? '');
|
|
6126
|
+
const isErr = /^error/i.test(resultStr.trim());
|
|
6127
|
+
log(` ${isErr ? chalk.red('err') : chalk.green('ok')} ${dim(`${tMs}ms, ${resultStr.length} chars`)}: ${dim(ellipsis(resultStr.replace(/\s+/g, ' '), 200))}`);
|
|
5966
6128
|
consultMessages.push({
|
|
5967
6129
|
role: 'tool',
|
|
5968
6130
|
tool_name: fn.name,
|
|
5969
|
-
content: truncateToolText(
|
|
6131
|
+
content: truncateToolText(resultStr, 16000),
|
|
5970
6132
|
});
|
|
6133
|
+
consultantSpinner.start(chalk.magenta(`Consultant running tool round ${rounds}/${cfg.toolRoundLimit}...`));
|
|
5971
6134
|
}
|
|
5972
6135
|
}
|
|
5973
6136
|
} catch (err) {
|
|
5974
6137
|
consultantSpinner.stop();
|
|
6138
|
+
log(`${chalk.red('[consult]')} error during consultation: ${err.message}`);
|
|
5975
6139
|
return `Error during consultation: ${err.message}`;
|
|
5976
6140
|
}
|
|
5977
6141
|
consultantSpinner.stop();
|
|
@@ -5979,6 +6143,11 @@ async function consultExpert({ summary, question, goal, attempts, hints, files }
|
|
|
5979
6143
|
const elapsed = Math.round((Date.now() - startedAt) / 1000);
|
|
5980
6144
|
const answer = (finalAnswer || '').trim() || '(consultant returned no content)';
|
|
5981
6145
|
|
|
6146
|
+
// Final summary box
|
|
6147
|
+
log('');
|
|
6148
|
+
log(`${accent('[consult]')} ${chalk.white('final answer')} ${dim(`(${elapsed}s, ${rounds} tool round${rounds === 1 ? '' : 's'})`)}:`);
|
|
6149
|
+
logBlock('answer', answer, 2000);
|
|
6150
|
+
|
|
5982
6151
|
// Save transcript for audit
|
|
5983
6152
|
const transcriptBody = [
|
|
5984
6153
|
`# Consultation Transcript`,
|
|
@@ -6001,6 +6170,292 @@ async function consultExpert({ summary, question, goal, attempts, hints, files }
|
|
|
6001
6170
|
return `${header}\n\n${answer}`;
|
|
6002
6171
|
}
|
|
6003
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
|
+
|
|
6004
6459
|
const tools = {
|
|
6005
6460
|
read: (path) => {
|
|
6006
6461
|
const trimmedPath = typeof path === 'string' ? path.trim() : '';
|
|
@@ -6552,7 +7007,8 @@ const tools = {
|
|
|
6552
7007
|
});
|
|
6553
7008
|
});
|
|
6554
7009
|
},
|
|
6555
|
-
consult: async (args) => consultExpert(args || {})
|
|
7010
|
+
consult: async (args) => consultExpert(args || {}),
|
|
7011
|
+
deepthink: async (args) => deepThink(args || {})
|
|
6556
7012
|
};
|
|
6557
7013
|
|
|
6558
7014
|
async function checkForUpdates() {
|
|
@@ -7164,6 +7620,22 @@ async function runSapper() {
|
|
|
7164
7620
|
required: ['goal', 'question', 'summary']
|
|
7165
7621
|
}
|
|
7166
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
|
+
}
|
|
7167
7639
|
}
|
|
7168
7640
|
];
|
|
7169
7641
|
|
|
@@ -8283,13 +8755,181 @@ async function runSapper() {
|
|
|
8283
8755
|
}
|
|
8284
8756
|
if (subcommand === 'transcripts' || subcommand === 'transcript') {
|
|
8285
8757
|
if (['on', 'true', 'yes'].includes(value.toLowerCase())) { updateConsultant({ saveTranscripts: true }); console.log(chalk.green('Consultant transcripts: on')); continue; }
|
|
8286
|
-
if (['off', 'false', 'no'].includes(value.toLowerCase())) { updateConsultant({ saveTranscripts: false }); console.log(chalk.yellow('Consultant transcripts: off')); continue; }
|
|
8287
|
-
|
|
8758
|
+
if (['off', 'false', 'no'].includes(value.toLowerCase())) { updateConsultant({ saveTranscripts: false }); console.log(chalk.yellow('Consultant transcripts: off')); continue; } console.log(chalk.yellow('Usage: /consult transcripts on|off'));
|
|
8759
|
+
continue;
|
|
8760
|
+
}
|
|
8761
|
+
if (subcommand === 'verbose' || subcommand === 'log' || subcommand === 'logging') {
|
|
8762
|
+
if (['on', 'true', 'yes', '1'].includes(value.toLowerCase())) { updateConsultant({ verbose: true }); console.log(chalk.green('Consultant verbose logging: on')); continue; }
|
|
8763
|
+
if (['off', 'false', 'no', '0'].includes(value.toLowerCase())) { updateConsultant({ verbose: false }); console.log(chalk.yellow('Consultant verbose logging: off')); continue; }
|
|
8764
|
+
console.log(chalk.yellow('Usage: /consult verbose on|off'));
|
|
8288
8765
|
continue;
|
|
8289
8766
|
}
|
|
8290
8767
|
|
|
8291
8768
|
console.log(chalk.yellow(`Unknown consult option: ${subcommand}`));
|
|
8292
|
-
console.log(chalk.gray(' Usage: /consult | /consult model <name> | /consult on|off | /consult tools <a,b,c> | /consult minwords <N> | /consult rounds <N> | /consult timeout <secs> | /consult thinking <auto|on|off> | /consult temp <0..2> | /consult transcripts on|off | /consult reset'));
|
|
8769
|
+
console.log(chalk.gray(' Usage: /consult | /consult model <name> | /consult on|off | /consult tools <a,b,c> | /consult minwords <N> | /consult rounds <N> | /consult timeout <secs> | /consult thinking <auto|on|off> | /consult temp <0..2> | /consult transcripts on|off | /consult verbose on|off | /consult reset'));
|
|
8770
|
+
continue;
|
|
8771
|
+
}
|
|
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'));
|
|
8293
8933
|
continue;
|
|
8294
8934
|
}
|
|
8295
8935
|
|
|
@@ -9629,7 +10269,7 @@ async function runSapper() {
|
|
|
9629
10269
|
ls: 'LS', cat: 'CAT', head: 'HEAD', tail: 'TAIL', grep: 'GREP', find: 'FIND',
|
|
9630
10270
|
pwd: 'PWD', cd: 'CD', rmdir: 'RMDIR', changes: 'CHANGES',
|
|
9631
10271
|
fetch_web: 'FETCH', recall_memory: 'MEMORY', open_url: 'OPEN', run_shell: 'SHELL',
|
|
9632
|
-
consult_expert: 'CONSULT'
|
|
10272
|
+
consult_expert: 'CONSULT', deep_think: 'DEEPTHINK'
|
|
9633
10273
|
};
|
|
9634
10274
|
|
|
9635
10275
|
showStreamPhase(`Running ${nativeToolCalls.length} native tool call${nativeToolCalls.length === 1 ? '' : 's'}...`);
|
|
@@ -9761,6 +10401,10 @@ async function runSapper() {
|
|
|
9761
10401
|
result = await tools.consult(args);
|
|
9762
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 });
|
|
9763
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;
|
|
9764
10408
|
default:
|
|
9765
10409
|
result = `Unknown tool: ${fn.name}`;
|
|
9766
10410
|
toolSuccess = false;
|
|
@@ -10050,9 +10694,37 @@ async function runSapper() {
|
|
|
10050
10694
|
result = await tools.consult(consultArgs);
|
|
10051
10695
|
logEntry('tool', { toolType: 'CONSULT', path: (consultArgs.question || 'consult').slice(0, 80), duration: Date.now() - toolStart, success: !String(result).startsWith('Error'), resultSize: result?.length });
|
|
10052
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
|
+
}
|
|
10053
10725
|
|
|
10054
10726
|
// Log tool execution (for non-shell, non-file specific ones)
|
|
10055
|
-
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())) {
|
|
10056
10728
|
logEntry('tool', { toolType: type.toUpperCase(), path, duration: Date.now() - toolStart, success: toolSuccess, resultSize: result?.length, error: toolSuccess ? undefined : result });
|
|
10057
10729
|
}
|
|
10058
10730
|
|