miii-cli 1.2.4 → 1.3.0
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/dist/init.js +47 -2
- package/dist/mcp/client.js +5 -0
- package/dist/skills/loader.js +6 -2
- package/dist/tools/index.js +73 -20
- package/dist/tui/InputBar.js +2 -2
- package/dist/tui/components/InputArea.js +10 -2
- package/dist/tui/deepThink.js +0 -1
- package/dist/tui/hooks/useRefactor.js +4 -3
- package/dist/tui/hooks/useRunLoop.js +7 -3
- package/dist/tui/hooks/useSession.js +2 -2
- package/dist/tui/printer.js +1 -2
- package/package.json +1 -1
package/dist/init.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createRequire } from 'module';
|
|
|
5
5
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
import { homedir } from 'os';
|
|
8
|
-
import { execSync } from 'child_process';
|
|
8
|
+
import { execSync, spawnSync } from 'child_process';
|
|
9
9
|
import { loadConfig } from './config.js';
|
|
10
10
|
import { SkillLoader } from './skills/loader.js';
|
|
11
11
|
import { InputBar } from './tui/InputBar.js';
|
|
@@ -67,6 +67,45 @@ async function checkLatestVersion(current, force = false) {
|
|
|
67
67
|
catch { }
|
|
68
68
|
return undefined;
|
|
69
69
|
}
|
|
70
|
+
function promptYN(question) {
|
|
71
|
+
return new Promise(resolve => {
|
|
72
|
+
process.stdout.write(` ${question} (y/N) `);
|
|
73
|
+
const onData = (key) => {
|
|
74
|
+
const k = key.toString();
|
|
75
|
+
process.stdin.setRawMode(false);
|
|
76
|
+
process.stdin.pause();
|
|
77
|
+
process.stdin.removeListener('data', onData);
|
|
78
|
+
process.stdout.write('\n');
|
|
79
|
+
if (k === '') {
|
|
80
|
+
process.exit(130);
|
|
81
|
+
} // ctrl+c in raw mode — exit cleanly
|
|
82
|
+
resolve(k.toLowerCase() === 'y');
|
|
83
|
+
};
|
|
84
|
+
try {
|
|
85
|
+
process.stdin.setRawMode(true);
|
|
86
|
+
process.stdin.resume();
|
|
87
|
+
process.stdin.setEncoding('utf-8');
|
|
88
|
+
process.stdin.on('data', onData);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// stdin not a TTY (piped input) — skip prompt
|
|
92
|
+
process.stdin.removeListener('data', onData);
|
|
93
|
+
process.stdout.write('\n');
|
|
94
|
+
resolve(false);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function runAutoUpdate(latestVersion) {
|
|
99
|
+
process.stdout.write(`\n Updating miii-cli to v${latestVersion}…\n\n`);
|
|
100
|
+
const result = spawnSync('npm', ['install', '-g', 'miii-cli'], { stdio: 'inherit', shell: true });
|
|
101
|
+
if (result.status === 0) {
|
|
102
|
+
process.stdout.write(`\n Updated to v${latestVersion}. Restart miii.\n`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
process.stdout.write(`\n Update failed (exit ${result.status}). Run manually: npm install -g miii-cli\n`);
|
|
106
|
+
}
|
|
107
|
+
process.exit(result.status ?? 1);
|
|
108
|
+
}
|
|
70
109
|
export async function lazyInit() {
|
|
71
110
|
const argv = minimist(process.argv.slice(2), {
|
|
72
111
|
string: ['model', 'url', 'provider', 'session'],
|
|
@@ -105,7 +144,13 @@ export async function lazyInit() {
|
|
|
105
144
|
process.stderr.write(`MCP: loaded ${mcpTools.length} tool(s) from ${mcpClients.length} server(s)\n`);
|
|
106
145
|
}
|
|
107
146
|
// Print welcome banner to scrollback BEFORE Ink starts
|
|
108
|
-
welcome(
|
|
147
|
+
welcome(process.cwd(), currentVersion, updateAvailable, linked);
|
|
148
|
+
// If update available and not a linked dev install, offer auto-update
|
|
149
|
+
if (updateAvailable && !linked && process.stdin.isTTY) {
|
|
150
|
+
const doUpdate = await promptYN(`Update available: v${updateAvailable}. Auto-update now?`);
|
|
151
|
+
if (doUpdate)
|
|
152
|
+
await runAutoUpdate(updateAvailable);
|
|
153
|
+
}
|
|
109
154
|
const sessionName = argv.session || `s-${Date.now()}`;
|
|
110
155
|
const { waitUntilExit } = render(React.createElement(InputBar, { config, skills, cwd: process.cwd(), session: sessionName, version: currentVersion, mcpTools }), { exitOnCtrlC: false });
|
|
111
156
|
await waitUntilExit();
|
package/dist/mcp/client.js
CHANGED
|
@@ -73,6 +73,11 @@ export class MCPClient {
|
|
|
73
73
|
resolve: (v) => { clearTimeout(timer); resolve(v); },
|
|
74
74
|
reject: (e) => { clearTimeout(timer); reject(e); },
|
|
75
75
|
});
|
|
76
|
+
if (!this.proc?.stdin?.writable) {
|
|
77
|
+
this.pending.delete(id);
|
|
78
|
+
reject(new Error('MCP process stdin not writable'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
76
81
|
this.proc.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
|
|
77
82
|
timer = setTimeout(() => {
|
|
78
83
|
if (this.pending.has(id)) {
|
package/dist/skills/loader.js
CHANGED
|
@@ -167,8 +167,10 @@ export class SkillLoader {
|
|
|
167
167
|
const pkg = nameOrPkg.includes('/') || nameOrPkg.startsWith('miii-skill-')
|
|
168
168
|
? nameOrPkg
|
|
169
169
|
: `miii-skill-${nameOrPkg}`;
|
|
170
|
+
if (!/^[a-zA-Z0-9@/._-]+$/.test(pkg))
|
|
171
|
+
throw new Error(`invalid package name: ${pkg}`);
|
|
170
172
|
createDir(NPM_SKILLS_DIR);
|
|
171
|
-
const { stdout, stderr } = await run(`npm install --prefix ${JSON.stringify(NPM_SKILLS_DIR)} ${pkg}`);
|
|
173
|
+
const { stdout, stderr } = await run(`npm install --prefix ${JSON.stringify(NPM_SKILLS_DIR)} ${JSON.stringify(pkg)}`);
|
|
172
174
|
const out = (stdout + stderr).trim();
|
|
173
175
|
// Reload newly installed skill
|
|
174
176
|
await this.loadAll();
|
|
@@ -178,7 +180,9 @@ export class SkillLoader {
|
|
|
178
180
|
const pkg = nameOrPkg.includes('/') || nameOrPkg.startsWith('miii-skill-')
|
|
179
181
|
? nameOrPkg
|
|
180
182
|
: `miii-skill-${nameOrPkg}`;
|
|
181
|
-
|
|
183
|
+
if (!/^[a-zA-Z0-9@/._-]+$/.test(pkg))
|
|
184
|
+
throw new Error(`invalid package name: ${pkg}`);
|
|
185
|
+
const { stdout, stderr } = await run(`npm uninstall --prefix ${JSON.stringify(NPM_SKILLS_DIR)} ${JSON.stringify(pkg)}`);
|
|
182
186
|
const out = (stdout + stderr).trim();
|
|
183
187
|
// Remove from map
|
|
184
188
|
const shortName = pkg.replace(/^miii-skill-/, '');
|
package/dist/tools/index.js
CHANGED
|
@@ -20,7 +20,10 @@ export const tools = [
|
|
|
20
20
|
params: '{"path": "string"}',
|
|
21
21
|
execute: async ({ path }) => {
|
|
22
22
|
try {
|
|
23
|
-
|
|
23
|
+
const safe = guardPath(requireArg(path, 'path', 'read_file'));
|
|
24
|
+
if (!existsSync(safe))
|
|
25
|
+
throw new Error(`file not found: ${path}`);
|
|
26
|
+
return readFile(safe);
|
|
24
27
|
}
|
|
25
28
|
catch (e) {
|
|
26
29
|
throw new Error(`read_file: ${e}`);
|
|
@@ -73,15 +76,18 @@ export const tools = [
|
|
|
73
76
|
params: '{"path": "string", "old": "string", "new": "string"}',
|
|
74
77
|
execute: async ({ path, old: oldStr, new: newStr }) => {
|
|
75
78
|
const safe = guardPath(requireArg(path, 'path', 'update_file'));
|
|
76
|
-
|
|
77
|
-
if (current === null)
|
|
79
|
+
if (!existsSync(safe))
|
|
78
80
|
throw new Error(`file not found: ${path}`);
|
|
81
|
+
const current = readFile(safe);
|
|
79
82
|
if (current === '')
|
|
80
83
|
throw new Error(`file empty: ${path}`);
|
|
81
84
|
const old = requireArg(oldStr, 'old', 'update_file');
|
|
82
85
|
if (newStr === undefined || newStr === null)
|
|
83
86
|
throw new Error('update_file: "new" argument is required');
|
|
84
|
-
const
|
|
87
|
+
const norm = (s) => s.replace(/\r\n/g, '\n');
|
|
88
|
+
const currentNorm = norm(current);
|
|
89
|
+
const oldNorm = norm(old);
|
|
90
|
+
const count = currentNorm.split(oldNorm).length - 1;
|
|
85
91
|
if (count === 0) {
|
|
86
92
|
throw new Error(`old text not found in ${path} — file may have changed since last read.\n` +
|
|
87
93
|
`Call read_file again to get current content, then retry with exact matching text.`);
|
|
@@ -89,11 +95,11 @@ export const tools = [
|
|
|
89
95
|
if (count > 1) {
|
|
90
96
|
throw new Error(`ambiguous: ${count} matches found in ${path} — extend <old> block with more surrounding lines to make it unique`);
|
|
91
97
|
}
|
|
92
|
-
const updated =
|
|
98
|
+
const updated = currentNorm.replace(oldNorm, norm(String(newStr)));
|
|
93
99
|
writeFile(safe, updated);
|
|
94
100
|
// Compute affected line range for the snippet
|
|
95
|
-
const startLine =
|
|
96
|
-
const oldLines =
|
|
101
|
+
const startLine = currentNorm.slice(0, currentNorm.indexOf(oldNorm)).split('\n').length;
|
|
102
|
+
const oldLines = oldNorm.split('\n').length;
|
|
97
103
|
const newLines = newStr.split('\n').length;
|
|
98
104
|
const updatedArr = updated.split('\n');
|
|
99
105
|
const snippetStart = Math.max(0, startLine - 3);
|
|
@@ -287,14 +293,16 @@ export function getSystemPrompt(extra = '', extraTools = []) {
|
|
|
287
293
|
const toolDocs = allTools.map(t => `- ${t.name}(${t.params}): ${t.description}`).join('\n');
|
|
288
294
|
const deepThinkDoc = `- deep_think({"query": "string", "needs_web": "boolean (optional)"}): Research tool — gathers information from files, git, and optionally the web before answering. Returns a compiled research summary. Guardrails: read-only tools only, max 6 tool calls, max 4 web calls inside. Use when a question requires reading multiple files or searching the web first.
|
|
289
295
|
- search_codebase({"query": "string", "k": "number (optional)"}): Semantic vector search over the indexed codebase. Returns top-k relevant code snippets by meaning. Requires the user to have run /index build. Use this when you need to find code by concept rather than exact string — e.g. "authentication logic", "error handling patterns", "database queries".`;
|
|
290
|
-
return `You are Miii — AI coding assistant.
|
|
296
|
+
return `You are Miii — a precise, disciplined AI coding assistant. You implement exactly what is asked. Nothing more.
|
|
297
|
+
|
|
298
|
+
## Tool format
|
|
291
299
|
|
|
292
|
-
Tools via:
|
|
293
300
|
<tool_call>
|
|
294
301
|
{"name": "tool_name", "args": {...}}
|
|
295
302
|
</tool_call>
|
|
296
303
|
|
|
297
|
-
File content in named blocks
|
|
304
|
+
File content goes in named blocks outside the JSON — never inside it:
|
|
305
|
+
|
|
298
306
|
<tool_call>
|
|
299
307
|
{"name": "edit_file", "args": {"path": "src/foo.ts"}}
|
|
300
308
|
<content>
|
|
@@ -305,23 +313,68 @@ full file content here
|
|
|
305
313
|
<tool_call>
|
|
306
314
|
{"name": "update_file", "args": {"path": "src/foo.ts"}}
|
|
307
315
|
<old>
|
|
308
|
-
exact text to replace
|
|
316
|
+
exact text to replace (copy verbatim from read_file output)
|
|
309
317
|
</old>
|
|
310
318
|
<new>
|
|
311
319
|
replacement text
|
|
312
320
|
</new>
|
|
313
321
|
</tool_call>
|
|
314
322
|
|
|
315
|
-
Tools
|
|
323
|
+
## Tools
|
|
316
324
|
${toolDocs}
|
|
317
325
|
${deepThinkDoc}
|
|
318
326
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
+
## Execution protocol
|
|
328
|
+
|
|
329
|
+
For every task, follow this sequence:
|
|
330
|
+
1. Read relevant files first — never assume file contents. When reading multiple independent files, emit all read_file calls in a single batch — do not wait for one before requesting the next.
|
|
331
|
+
2. Make the minimal targeted change that satisfies the request
|
|
332
|
+
3. Run run_tests after any edit. If tests fail, fix and retry up to 3 times before reporting
|
|
333
|
+
4. For refactors or commits: git_status → git_diff first, always
|
|
334
|
+
|
|
335
|
+
Parallel tool calls: when multiple tool calls have no dependency between them, issue them together in one batch. Sequential only when a later call depends on an earlier result.
|
|
336
|
+
|
|
337
|
+
For exploratory questions ("how should we approach X?", "what could we do about Y?"):
|
|
338
|
+
- Respond in 2-3 sentences: recommendation + main tradeoff
|
|
339
|
+
- Do not implement until the user agrees
|
|
340
|
+
|
|
341
|
+
For UI or frontend changes: verify the change works in a browser before reporting done. If browser testing is not possible, say so explicitly rather than claiming success.
|
|
342
|
+
|
|
343
|
+
## Code discipline
|
|
344
|
+
|
|
345
|
+
- Implement exactly what is asked. A bug fix is not a refactor opportunity. A one-shot task does not need a helper abstraction.
|
|
346
|
+
- Three similar lines of code is better than a premature abstraction.
|
|
347
|
+
- Write no comments by default. Add one only when the WHY is non-obvious: a hidden constraint, a subtle invariant, a specific bug workaround. Never explain what the code does — names do that.
|
|
348
|
+
- Add no error handling for scenarios that cannot occur. Trust framework and internal code guarantees. Validate only at system boundaries: user input, external APIs, file I/O.
|
|
349
|
+
- Add no backwards-compatibility shims, feature flags, or dead code for hypothetical future requirements.
|
|
350
|
+
|
|
351
|
+
## File editing rules
|
|
352
|
+
|
|
353
|
+
- edit_file: new files only — throws if file exists. For existing files: read_file → update_file.
|
|
354
|
+
- update_file: copy the <old> text verbatim from read_file output. Never guess or paraphrase it.
|
|
355
|
+
- If "old text not found": read_file again immediately and retry with exact current text.
|
|
356
|
+
- Prefer update_file (surgical patch) over edit_file (full rewrite) for existing files.
|
|
357
|
+
- Read a file immediately before patching it — not from earlier in the conversation.
|
|
358
|
+
|
|
359
|
+
## Safety and reversibility
|
|
360
|
+
|
|
361
|
+
- Before any destructive action (delete_file, overwriting content, git_commit with -A), verify the blast radius.
|
|
362
|
+
- Never introduce security vulnerabilities: no command injection, no path traversal, no hardcoded secrets, no XSS, no SQL injection. If you wrote insecure code, fix it immediately.
|
|
363
|
+
- run_command executes in a shell — validate any user-supplied values before interpolating into commands.
|
|
364
|
+
|
|
365
|
+
## Git discipline
|
|
366
|
+
|
|
367
|
+
- git_status before every commit. Never commit if working tree is unexpected.
|
|
368
|
+
- Stage specific files. Use -A only when all changes are intentional and reviewed.
|
|
369
|
+
- Never amend a commit unless explicitly asked.
|
|
370
|
+
- Never force-push unless explicitly asked and confirmed.
|
|
371
|
+
- Never skip hooks (--no-verify) unless explicitly asked. If a hook fails, diagnose and fix the root cause.
|
|
372
|
+
- Never use interactive git flags (-i) — they require terminal input that is not available.
|
|
373
|
+
|
|
374
|
+
## Communication
|
|
375
|
+
|
|
376
|
+
- Plain text only. No markdown (no #, *, \`, ---). No code blocks in responses — write code with tools.
|
|
377
|
+
- No filler: no "sure", "certainly", "happy to", "great question". State results and next steps directly.
|
|
378
|
+
- web_search requires "query" key exactly. Never say you can't search — always call web_search.
|
|
379
|
+
- deep_think: read-only research only. Cannot edit files.${extra}`;
|
|
327
380
|
}
|
package/dist/tui/InputBar.js
CHANGED
|
@@ -101,7 +101,8 @@ export function InputBar({ config: initialConfig, skills, cwd, session, version,
|
|
|
101
101
|
const abortRef = useRef(null);
|
|
102
102
|
const [designTeachState, setDesignTeachState] = useState(null);
|
|
103
103
|
const [designReadyPrompt, setDesignReadyPrompt] = useState(null);
|
|
104
|
-
const {
|
|
104
|
+
const { currentModel, setCurrentModel, currentModelRef, pickerOpen, setPickerOpen, pickerModels, pickerLoading, pickerError, pullState, handleModelSelect, handleModelPull, } = useModelPicker(config);
|
|
105
|
+
const { projectDir, setSessionName, sessionNameRef, historyRef, saveTimerRef, systemPromptRef, pushHistory, setHistory, buildContext, renameFromMessage, updateMemory, } = useSession(session, cwd, config, mcpTools, currentModelRef);
|
|
105
106
|
const startDesignTeach = useCallback(() => {
|
|
106
107
|
setDesignTeachState({ answers: [], idx: 0 });
|
|
107
108
|
}, []);
|
|
@@ -119,7 +120,6 @@ export function InputBar({ config: initialConfig, skills, cwd, session, version,
|
|
|
119
120
|
return { answers, idx: nextIdx };
|
|
120
121
|
});
|
|
121
122
|
}, []);
|
|
122
|
-
const { currentModel, setCurrentModel, currentModelRef, pickerOpen, setPickerOpen, pickerModels, pickerLoading, pickerError, pullState, handleModelSelect, handleModelPull, } = useModelPicker(config);
|
|
123
123
|
const deepThinkTool = useMemo(() => ({
|
|
124
124
|
name: 'deep_think',
|
|
125
125
|
description: 'Research tool: gather info from files and web before answering.',
|
|
@@ -428,10 +428,14 @@ export function InputArea({ status, skills, cwd, planningMode, permissionRequest
|
|
|
428
428
|
appendChar(input);
|
|
429
429
|
if (prospective.startsWith('/')) {
|
|
430
430
|
if (prospective.slice(1).includes(' ')) {
|
|
431
|
-
if (input === '@'
|
|
431
|
+
if (input === '@') {
|
|
432
|
+
filesLoadedRef.current = false;
|
|
432
433
|
setOverlay('at');
|
|
433
434
|
setOverlayIdx(0);
|
|
434
435
|
}
|
|
436
|
+
else if (overlay === 'at') {
|
|
437
|
+
setOverlay('at');
|
|
438
|
+
}
|
|
435
439
|
else {
|
|
436
440
|
setOverlay('none');
|
|
437
441
|
}
|
|
@@ -441,10 +445,14 @@ export function InputArea({ status, skills, cwd, planningMode, permissionRequest
|
|
|
441
445
|
setOverlayIdx(0);
|
|
442
446
|
}
|
|
443
447
|
}
|
|
444
|
-
else if (input === '@'
|
|
448
|
+
else if (input === '@') {
|
|
449
|
+
filesLoadedRef.current = false;
|
|
445
450
|
setOverlay('at');
|
|
446
451
|
setOverlayIdx(0);
|
|
447
452
|
}
|
|
453
|
+
else if (overlay === 'at' && atQuery !== '') {
|
|
454
|
+
setOverlay('at');
|
|
455
|
+
}
|
|
448
456
|
else if (overlay === 'command') {
|
|
449
457
|
setOverlay('none');
|
|
450
458
|
}
|
package/dist/tui/deepThink.js
CHANGED
|
@@ -20,14 +20,15 @@ export function useRefactor(deps) {
|
|
|
20
20
|
content: `Refactor goal: ${goal}\n\nList every file that needs to change. For each file output:\nFILE: <path>\nCHANGE: <one sentence describing the edit>\n\nUse list_files and read_file to discover relevant files first. Only list files that genuinely need changes.`,
|
|
21
21
|
},
|
|
22
22
|
];
|
|
23
|
-
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
abortRef.current = controller;
|
|
24
25
|
let planText = '';
|
|
25
26
|
await chat({
|
|
26
27
|
provider: config.provider,
|
|
27
28
|
model: currentModelRef.current,
|
|
28
29
|
baseUrl: config.baseUrl,
|
|
29
30
|
messages: planCtx,
|
|
30
|
-
signal:
|
|
31
|
+
signal: controller.signal,
|
|
31
32
|
async onDone(text) { planText = text; },
|
|
32
33
|
onError(err) { printer.errorMsg(err.message); },
|
|
33
34
|
});
|
|
@@ -95,7 +96,7 @@ export function useRefactor(deps) {
|
|
|
95
96
|
model: currentModelRef.current,
|
|
96
97
|
baseUrl: config.baseUrl,
|
|
97
98
|
messages: editCtx,
|
|
98
|
-
signal:
|
|
99
|
+
signal: controller.signal,
|
|
99
100
|
async onDone(text) { editText = text; },
|
|
100
101
|
onError(err) { printer.errorMsg(`edit LLM error: ${err.message}`); },
|
|
101
102
|
});
|
|
@@ -134,7 +134,9 @@ export function useRunLoop(config, currentModelRef, pushHistory, extraTools = []
|
|
|
134
134
|
sessionApprovedRef.current.add(sessionKey);
|
|
135
135
|
if (decision === 'no') {
|
|
136
136
|
printer.systemMsg(`denied: ${tc.name}`);
|
|
137
|
-
|
|
137
|
+
const remaining = pendingTools.slice(pendingTools.indexOf(tc) + 1).map(t => t.name);
|
|
138
|
+
const skippedNote = remaining.length ? ` The following tools were also skipped: ${remaining.join(', ')}.` : '';
|
|
139
|
+
next.push({ role: 'user', content: `Tool ${tc.name} was denied by the user.${skippedNote} Do not retry these tools unless the user explicitly asks.` });
|
|
138
140
|
break;
|
|
139
141
|
}
|
|
140
142
|
// Checkpoint: store pre-execution file state
|
|
@@ -158,8 +160,9 @@ export function useRunLoop(config, currentModelRef, pushHistory, extraTools = []
|
|
|
158
160
|
const filePath = tc.args.path;
|
|
159
161
|
const oldText = tc.args.old;
|
|
160
162
|
if (filePath && oldText && existsSync(filePath)) {
|
|
163
|
+
const norm = (s) => s.replace(/\r\n/g, '\n');
|
|
161
164
|
const current = readFileSync(filePath, 'utf-8');
|
|
162
|
-
const occurrences = current.split(oldText).length - 1;
|
|
165
|
+
const occurrences = norm(current).split(norm(oldText)).length - 1;
|
|
163
166
|
if (occurrences === 0) {
|
|
164
167
|
printer.errorMsg(`patch stale: old text not found in ${filePath} — injecting fresh content`);
|
|
165
168
|
next.push({ role: 'user', content: `Tool read_file result:\n${current}` });
|
|
@@ -208,7 +211,8 @@ export function useRunLoop(config, currentModelRef, pushHistory, extraTools = []
|
|
|
208
211
|
const didEditFiles = pendingTools.some(tc => FILE_EDIT_TOOLS.has(tc.name));
|
|
209
212
|
if (didEditFiles) {
|
|
210
213
|
const systemMsg = msgs.find(m => m.role === 'system');
|
|
211
|
-
const goalMsg = msgs.find(m => m.role === 'user' && !m.content.startsWith('[') && !m.content.startsWith('Tool '))
|
|
214
|
+
const goalMsg = msgs.find(m => m.role === 'user' && !m.content.startsWith('[') && !m.content.startsWith('Tool '))
|
|
215
|
+
?? (goal ? { role: 'user', content: goal } : undefined);
|
|
212
216
|
const batchStart = msgs.length; // include assistant message so model sees its own tool call on retry
|
|
213
217
|
const batchMsgs = next.slice(batchStart);
|
|
214
218
|
const slimCtx = [
|
|
@@ -9,7 +9,7 @@ const SHORT_MEMORY_SIZE = 50;
|
|
|
9
9
|
function buildSystemPrompt(cwd, facts, extraTools = []) {
|
|
10
10
|
return getSystemPrompt(`\n- CWD: ${cwd}`, extraTools) + formatMemoryBlock(facts);
|
|
11
11
|
}
|
|
12
|
-
export function useSession(initialSession, cwd, config, extraTools = []) {
|
|
12
|
+
export function useSession(initialSession, cwd, config, extraTools = [], currentModelRef) {
|
|
13
13
|
const projectDir = getProjectDir(cwd);
|
|
14
14
|
const [sessionName, setSessionName] = useState(initialSession);
|
|
15
15
|
const sessionNameRef = useRef(initialSession);
|
|
@@ -49,7 +49,7 @@ export function useSession(initialSession, cwd, config, extraTools = []) {
|
|
|
49
49
|
if (historyRef.current.length > SHORT_MEMORY_SIZE && !extractingRef.current) {
|
|
50
50
|
const dropped = historyRef.current.splice(0, historyRef.current.length - SHORT_MEMORY_SIZE);
|
|
51
51
|
extractingRef.current = true;
|
|
52
|
-
extractFacts(dropped, config, config.model).then(newFacts => {
|
|
52
|
+
extractFacts(dropped, config, currentModelRef?.current ?? config.model).then(newFacts => {
|
|
53
53
|
if (newFacts.length) {
|
|
54
54
|
const updated = mergeFacts(longMemoryRef.current, newFacts);
|
|
55
55
|
longMemoryRef.current = updated;
|
package/dist/tui/printer.js
CHANGED
|
@@ -81,7 +81,7 @@ export function toolArgSummary(args) {
|
|
|
81
81
|
const first = Object.values(args)[0];
|
|
82
82
|
return first ? truncate(String(first), 60) : '';
|
|
83
83
|
}
|
|
84
|
-
export function welcome(
|
|
84
|
+
export function welcome(cwd, version, updateAvailable, linked) {
|
|
85
85
|
const cols = Math.min(process.stdout.columns ?? 80, 100);
|
|
86
86
|
const innerW = cols - 2;
|
|
87
87
|
const leftW = Math.floor(innerW * 0.44);
|
|
@@ -114,7 +114,6 @@ export function welcome(provider, model, cwd, version, updateAvailable, linked)
|
|
|
114
114
|
'',
|
|
115
115
|
...miniArt,
|
|
116
116
|
'',
|
|
117
|
-
` ${gray(model + ' · ' + provider)}`,
|
|
118
117
|
` ${gray(shortCwd)}`,
|
|
119
118
|
'',
|
|
120
119
|
];
|