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.
- package/package.json +1 -1
- package/sapper.mjs +607 -4
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 = '') {
|
|
@@ -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
|
|