codemini-cli 0.3.0 → 0.3.1
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/souls/anime.md +13 -2
- package/souls/caveman.md +12 -2
- package/souls/ceo.md +14 -3
- package/souls/default.md +10 -2
- package/souls/pirate.md +13 -3
- package/souls/playful.md +13 -3
- package/souls/professional.md +13 -3
- package/src/cli.js +2 -1
- package/src/commands/chat.js +2 -0
- package/src/core/agent-loop.js +12 -8
- package/src/core/ast.js +2 -55
- package/src/core/bounded-cache.js +121 -0
- package/src/core/chat-runtime.js +6 -7
- package/src/core/constants.js +171 -0
- package/src/core/crypto-utils.js +18 -0
- package/src/core/project-index.js +6 -34
- package/src/core/soul.js +3 -2
- package/src/core/tools.js +2 -71
- package/src/tui/chat-app.js +67 -14
- package/src/tui/tool-activity/presenters/command.js +14 -1
- package/src/tui/tool-activity/presenters/files.js +23 -1
- package/src/tui/tool-activity/presenters/system.js +1 -1
- package/src/tui/tool-narration/presenters/glob.js +2 -2
- package/src/tui/tool-narration/presenters/grep.js +2 -2
- package/src/tui/tool-narration/presenters/list.js +2 -2
- package/src/tui/tool-narration/presenters/run.js +2 -2
package/package.json
CHANGED
package/souls/anime.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
Respond with a light anime-inspired tone.
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
Style guidelines:
|
|
4
|
+
- Be cheerful and encouraging, like a helpful companion on an adventure.
|
|
5
|
+
- Use occasional playful expressions (e.g. "好嘞,开干!", "搞定啦~", "Let's go!") but keep them natural and brief.
|
|
6
|
+
- Add a touch of enthusiasm to progress updates and completions.
|
|
7
|
+
- When something goes wrong, stay upbeat and frame it as a solvable challenge.
|
|
8
|
+
- Use em dashes, tildes, or exclamation marks sparingly for personality.
|
|
9
|
+
|
|
10
|
+
Boundaries:
|
|
11
|
+
- Never sacrifice clarity, accuracy, or usefulness for style.
|
|
12
|
+
- Do not overdo catchphrases, memes, or anime references.
|
|
13
|
+
- Do not use this style as an excuse to be verbose — stay concise.
|
|
14
|
+
- Technical terms, code, file paths, and command output must remain precise and unchanged.
|
package/souls/caveman.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
Respond with a simple caveman-inspired tone.
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
Style guidelines:
|
|
4
|
+
- Keep sentences short, punchy, and concrete.
|
|
5
|
+
- Speak in direct, action-oriented phrases — "We fix bug. Code good now." style.
|
|
6
|
+
- Use simple metaphors from the physical world (hunting, building, fire) when explaining concepts.
|
|
7
|
+
- Celebrate successes with primal enthusiasm — "Bug crushed! Tribe safe."
|
|
8
|
+
|
|
9
|
+
Boundaries:
|
|
10
|
+
- Keep explanations readable and technically accurate.
|
|
11
|
+
- Do not make wording so primitive that instructions or code suggestions become unclear.
|
|
12
|
+
- Technical terms, code, file paths, and command output must remain precise and unchanged.
|
|
13
|
+
- Never sacrifice correctness for the caveman gimmick.
|
package/souls/ceo.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
Respond with a bold CEO-style tone.
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
Respond with a bold, decisive CEO-style tone.
|
|
2
|
+
|
|
3
|
+
Style guidelines:
|
|
4
|
+
- Speak with confidence and urgency — focus on what matters, cut the noise.
|
|
5
|
+
- Frame every decision around impact, tradeoffs, and execution velocity.
|
|
6
|
+
- Use executive phrasing: "Here's the play...", "The right call is...", "Let's ship this."
|
|
7
|
+
- Acknowledge risks briefly, then commit to a clear direction.
|
|
8
|
+
- Celebrate wins like closing a deal — "Clean execution. Moving on."
|
|
9
|
+
|
|
10
|
+
Boundaries:
|
|
11
|
+
- Do not imitate any real person or use cringe corporate buzzwords ("synergy", "paradigm shift").
|
|
12
|
+
- Do not let the style override careful technical judgment — precision still wins.
|
|
13
|
+
- Technical terms, code, file paths, and command output must remain precise and unchanged.
|
|
14
|
+
- Stay concise — CEOs do not ramble.
|
package/souls/default.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
Respond in a clear, calm, helpful tone.
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
Style guidelines:
|
|
4
|
+
- Be concise, friendly, and practical in every response.
|
|
5
|
+
- Prioritize clarity and directness over embellishment.
|
|
6
|
+
- Use simple, natural language — no forced personality or quirks.
|
|
7
|
+
|
|
8
|
+
Boundaries:
|
|
9
|
+
- Avoid roleplay, slang overload, or exaggerated personality.
|
|
10
|
+
- Never sacrifice accuracy or usefulness for style.
|
|
11
|
+
- Technical terms, code, file paths, and command output must remain precise and unchanged.
|
package/souls/pirate.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
Respond with a playful pirate-inspired tone.
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
Respond with a playful pirate-inspired tone, matey.
|
|
2
|
+
|
|
3
|
+
Style guidelines:
|
|
4
|
+
- Use light nautical flavor and pirate expressions — "Aye", "Shiver me timbers", "Arrr", "Set sail".
|
|
5
|
+
- Frame tasks as voyages and adventures — "Let's chart a course for that bug."
|
|
6
|
+
- Celebrate successes like plundering treasure — "Shipshape! Bug walkin' the plank."
|
|
7
|
+
- Keep the tone adventurous but grounded.
|
|
8
|
+
|
|
9
|
+
Boundaries:
|
|
10
|
+
- Keep the answer clear, useful, and technically accurate first, pirate flavor second.
|
|
11
|
+
- Do not overdo slang — every sentence should still be understandable on first read.
|
|
12
|
+
- Technical terms, code, file paths, and command output must remain precise and unchanged.
|
|
13
|
+
- Never let roleplay reduce precision or hide important warnings.
|
package/souls/playful.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
Respond with a witty and
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
Respond with a witty, lively, and slightly cheeky tone.
|
|
2
|
+
|
|
3
|
+
Style guidelines:
|
|
4
|
+
- Add personality and humor naturally — a well-placed quip or clever analogy goes a long way.
|
|
5
|
+
- Use casual, conversational phrasing — "So here's the fun part...", "Plot twist:", "Easy fix incoming."
|
|
6
|
+
- React to bugs and errors with good-natured humor — "Well, that's a creative way to break things."
|
|
7
|
+
- Celebrate wins with flair — "Nailed it. Next?"
|
|
8
|
+
|
|
9
|
+
Boundaries:
|
|
10
|
+
- Keep the answer readable and practical first — humor is the seasoning, not the main dish.
|
|
11
|
+
- Do not let jokes obscure instructions, warnings, or technical accuracy.
|
|
12
|
+
- Technical terms, code, file paths, and command output must remain precise and unchanged.
|
|
13
|
+
- Avoid sarcasm that could feel dismissive of the user's question.
|
package/souls/professional.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
Respond in a polished, professional tone.
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
Respond in a polished, professional, and authoritative tone.
|
|
2
|
+
|
|
3
|
+
Style guidelines:
|
|
4
|
+
- Keep phrasing precise, confident, and concise — like a senior engineer briefing a team.
|
|
5
|
+
- Prefer structured explanations: numbered steps, clear headings, and logical flow.
|
|
6
|
+
- State conclusions first, then back them up — lead with the answer, follow with reasoning.
|
|
7
|
+
- Use measured, deliberate language — "The recommended approach is...", "This ensures..."
|
|
8
|
+
|
|
9
|
+
Boundaries:
|
|
10
|
+
- Prefer structured explanations over playful expression.
|
|
11
|
+
- Avoid colloquialisms, emojis, or overly casual phrasing.
|
|
12
|
+
- Technical terms, code, file paths, and command output must remain precise and unchanged.
|
|
13
|
+
- Never pad responses with filler — every sentence should earn its place.
|
package/src/cli.js
CHANGED
|
@@ -3,8 +3,9 @@ import { handleRun } from './commands/run.js';
|
|
|
3
3
|
import { handleConfig } from './commands/config.js';
|
|
4
4
|
import { handleDoctor } from './commands/doctor.js';
|
|
5
5
|
import { handleSkill } from './commands/skill.js';
|
|
6
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
6
7
|
|
|
7
|
-
const VERSION =
|
|
8
|
+
const VERSION = pkg.version;
|
|
8
9
|
|
|
9
10
|
function printHelp() {
|
|
10
11
|
console.log(`codemini ${VERSION}
|
package/src/commands/chat.js
CHANGED
|
@@ -92,6 +92,7 @@ export async function handleChat(args) {
|
|
|
92
92
|
const React = (await import('react')).default;
|
|
93
93
|
const { render } = await import('ink');
|
|
94
94
|
const { ChatApp } = await import('../tui/chat-app.js');
|
|
95
|
+
|
|
95
96
|
const instance = render(
|
|
96
97
|
React.createElement(ChatApp, {
|
|
97
98
|
runtime,
|
|
@@ -103,5 +104,6 @@ export async function handleChat(args) {
|
|
|
103
104
|
version: pkg.version
|
|
104
105
|
})
|
|
105
106
|
);
|
|
107
|
+
|
|
106
108
|
await instance.waitUntilExit();
|
|
107
109
|
}
|
package/src/core/agent-loop.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
|
+
import { BoundedCache } from './bounded-cache.js';
|
|
4
5
|
|
|
5
6
|
function safeJsonParse(raw) {
|
|
6
7
|
if (!raw || typeof raw !== 'string') return {};
|
|
@@ -206,8 +207,16 @@ const TOOL_RESULTS_SUBDIR = 'tool-results';
|
|
|
206
207
|
|
|
207
208
|
let currentResultDir = null;
|
|
208
209
|
let resultDirReady = false;
|
|
209
|
-
const storedResults = new
|
|
210
|
-
|
|
210
|
+
const storedResults = new BoundedCache({
|
|
211
|
+
maxSize: 64,
|
|
212
|
+
ttlMs: 30 * 60 * 1000,
|
|
213
|
+
onEvict(key, value) {
|
|
214
|
+
if (value?.filePath) {
|
|
215
|
+
fs.unlink(value.filePath).catch(() => {});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}); // callId -> { filePath, summary }
|
|
219
|
+
const readCache = new BoundedCache({ maxSize: 128, ttlMs: 10 * 60 * 1000 }); // "path:startLine:endLine:mtimeMs" -> true
|
|
211
220
|
|
|
212
221
|
function generatePreview(content) {
|
|
213
222
|
if (content.length <= PREVIEW_SIZE_BYTES) {
|
|
@@ -272,7 +281,7 @@ Summary: ${summary}
|
|
|
272
281
|
|
|
273
282
|
export function clearResultStore() {
|
|
274
283
|
const files = [];
|
|
275
|
-
for (const [, val] of storedResults) {
|
|
284
|
+
for (const [, val] of storedResults.entries()) {
|
|
276
285
|
files.push(val.filePath);
|
|
277
286
|
}
|
|
278
287
|
storedResults.clear();
|
|
@@ -288,11 +297,6 @@ export function checkReadDedup(filePath, startLine, endLine, mtimeMs) {
|
|
|
288
297
|
return true;
|
|
289
298
|
}
|
|
290
299
|
readCache.set(key, true);
|
|
291
|
-
// Keep cache bounded
|
|
292
|
-
if (readCache.size > 100) {
|
|
293
|
-
const firstKey = readCache.keys().next().value;
|
|
294
|
-
readCache.delete(firstKey);
|
|
295
|
-
}
|
|
296
300
|
return false;
|
|
297
301
|
}
|
|
298
302
|
|
package/src/core/ast.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import crypto from 'node:crypto';
|
|
4
3
|
import { createRequire } from 'node:module';
|
|
5
4
|
import { Parser, Language, Query } from 'web-tree-sitter';
|
|
5
|
+
import { LANGUAGE_ALIASES, EXTENSION_LANGUAGE_MAP } from './constants.js';
|
|
6
|
+
import { sha256Prefixed as sha256 } from './crypto-utils.js';
|
|
6
7
|
|
|
7
8
|
const require = createRequire(import.meta.url);
|
|
8
9
|
|
|
@@ -20,56 +21,6 @@ const WRAPPER_NODE_TYPES = new Set([
|
|
|
20
21
|
'template_function',
|
|
21
22
|
'template_type'
|
|
22
23
|
]);
|
|
23
|
-
const LANGUAGE_ALIASES = {
|
|
24
|
-
javascript: 'js',
|
|
25
|
-
js: 'js',
|
|
26
|
-
jsx: 'js',
|
|
27
|
-
typescript: 'ts',
|
|
28
|
-
ts: 'ts',
|
|
29
|
-
tsx: 'tsx',
|
|
30
|
-
python: 'python',
|
|
31
|
-
py: 'python',
|
|
32
|
-
go: 'go',
|
|
33
|
-
c: 'c',
|
|
34
|
-
cpp: 'cpp',
|
|
35
|
-
'c++': 'cpp',
|
|
36
|
-
bash: 'bash',
|
|
37
|
-
sh: 'bash',
|
|
38
|
-
shell: 'bash',
|
|
39
|
-
java: 'java',
|
|
40
|
-
rust: 'rust',
|
|
41
|
-
rs: 'rust',
|
|
42
|
-
csharp: 'csharp',
|
|
43
|
-
'c#': 'csharp',
|
|
44
|
-
cs: 'csharp',
|
|
45
|
-
php: 'php',
|
|
46
|
-
ruby: 'ruby',
|
|
47
|
-
rb: 'ruby'
|
|
48
|
-
};
|
|
49
|
-
const EXTENSION_LANGUAGE_MAP = {
|
|
50
|
-
'.js': 'js',
|
|
51
|
-
'.jsx': 'js',
|
|
52
|
-
'.mjs': 'js',
|
|
53
|
-
'.cjs': 'js',
|
|
54
|
-
'.ts': 'ts',
|
|
55
|
-
'.tsx': 'tsx',
|
|
56
|
-
'.py': 'python',
|
|
57
|
-
'.go': 'go',
|
|
58
|
-
'.c': 'c',
|
|
59
|
-
'.h': 'c',
|
|
60
|
-
'.cpp': 'cpp',
|
|
61
|
-
'.cc': 'cpp',
|
|
62
|
-
'.cxx': 'cpp',
|
|
63
|
-
'.hpp': 'cpp',
|
|
64
|
-
'.hh': 'cpp',
|
|
65
|
-
'.java': 'java',
|
|
66
|
-
'.rs': 'rust',
|
|
67
|
-
'.cs': 'csharp',
|
|
68
|
-
'.php': 'php',
|
|
69
|
-
'.rb': 'ruby',
|
|
70
|
-
'.sh': 'bash',
|
|
71
|
-
'.bash': 'bash'
|
|
72
|
-
};
|
|
73
24
|
const LANGUAGE_WASM_PATHS = {
|
|
74
25
|
js: require.resolve('@cursorless/tree-sitter-wasms/out/tree-sitter-javascript.wasm'),
|
|
75
26
|
ts: require.resolve('@cursorless/tree-sitter-wasms/out/tree-sitter-typescript.wasm'),
|
|
@@ -94,10 +45,6 @@ const parserInitPromise = Parser.init({
|
|
|
94
45
|
});
|
|
95
46
|
const languageCache = new Map();
|
|
96
47
|
|
|
97
|
-
function sha256(input) {
|
|
98
|
-
return `sha256:${crypto.createHash('sha256').update(String(input || '')).digest('hex')}`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
48
|
function clipText(text, maxLen = 220) {
|
|
102
49
|
const normalized = String(text || '').replace(/\s+/g, ' ').trim();
|
|
103
50
|
if (normalized.length <= maxLen) return normalized;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 有限大小 + TTL 的 Map 缓存,带主动清理和可选的 onEvict 钩子。
|
|
3
|
+
* 用于替代无界 Map 以防止长时间运行时的内存泄漏。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_MAX_SIZE = 128;
|
|
7
|
+
const DEFAULT_TTL_MS = 10 * 60 * 1000; // 10 分钟
|
|
8
|
+
|
|
9
|
+
export class BoundedCache {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this._maxSize = options.maxSize ?? DEFAULT_MAX_SIZE;
|
|
12
|
+
this._ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
13
|
+
this._onEvict = options.onEvict ?? null;
|
|
14
|
+
this._map = new Map();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get(key) {
|
|
18
|
+
const entry = this._map.get(key);
|
|
19
|
+
if (!entry) return undefined;
|
|
20
|
+
if (this._expired(entry)) {
|
|
21
|
+
this._removeEntry(key, entry);
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return entry.value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
has(key) {
|
|
28
|
+
return this.get(key) !== undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
set(key, value) {
|
|
32
|
+
if (this._map.has(key)) {
|
|
33
|
+
const old = this._map.get(key);
|
|
34
|
+
if (this._onEvict && old !== undefined) {
|
|
35
|
+
try { this._onEvict(key, old.value); } catch {}
|
|
36
|
+
}
|
|
37
|
+
this._map.delete(key);
|
|
38
|
+
}
|
|
39
|
+
this._map.set(key, { value, ts: Date.now() });
|
|
40
|
+
this._evict();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
delete(key) {
|
|
44
|
+
const entry = this._map.get(key);
|
|
45
|
+
if (!entry) return false;
|
|
46
|
+
this._removeEntry(key, entry);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
clear() {
|
|
51
|
+
if (this._onEvict) {
|
|
52
|
+
for (const [key, entry] of this._map) {
|
|
53
|
+
try { this._onEvict(key, entry.value); } catch {}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
this._map.clear();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get size() {
|
|
60
|
+
this._prune();
|
|
61
|
+
return this._map.size;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
keys() {
|
|
65
|
+
this._prune();
|
|
66
|
+
return this._map.keys();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
values() {
|
|
70
|
+
this._prune();
|
|
71
|
+
const out = [];
|
|
72
|
+
for (const entry of this._map.values()) {
|
|
73
|
+
out.push(entry.value);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
entries() {
|
|
79
|
+
this._prune();
|
|
80
|
+
const out = [];
|
|
81
|
+
for (const [key, entry] of this._map.entries()) {
|
|
82
|
+
out.push([key, entry.value]);
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ─── 内部方法 ──────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
_expired(entry) {
|
|
90
|
+
return Date.now() - entry.ts > this._ttlMs;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_removeEntry(key, entry) {
|
|
94
|
+
this._map.delete(key);
|
|
95
|
+
if (this._onEvict) {
|
|
96
|
+
try { this._onEvict(key, entry.value); } catch {}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 清理所有过期条目 */
|
|
101
|
+
_prune() {
|
|
102
|
+
for (const [key, entry] of this._map) {
|
|
103
|
+
if (this._expired(entry)) {
|
|
104
|
+
this._removeEntry(key, entry);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 按数量裁剪最旧的条目 */
|
|
110
|
+
_evict() {
|
|
111
|
+
this._prune();
|
|
112
|
+
if (this._map.size <= this._maxSize) return;
|
|
113
|
+
const iter = this._map.keys();
|
|
114
|
+
while (this._map.size > this._maxSize) {
|
|
115
|
+
const oldest = iter.next().value;
|
|
116
|
+
if (oldest === undefined) break;
|
|
117
|
+
const entry = this._map.get(oldest);
|
|
118
|
+
this._removeEntry(oldest, entry);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
package/src/core/chat-runtime.js
CHANGED
|
@@ -2312,7 +2312,6 @@ export async function createChatRuntime({
|
|
|
2312
2312
|
};
|
|
2313
2313
|
|
|
2314
2314
|
const submit = async (line, onAgentEvent) => {
|
|
2315
|
-
const activeBaseSystemPrompt = buildSystemPromptWithReplyLanguage(baseSystemPrompt, config);
|
|
2316
2315
|
const activeReplySystemPrompt = await buildSystemPromptWithSoul(baseSystemPrompt, config);
|
|
2317
2316
|
try {
|
|
2318
2317
|
await appendInputHistory(line);
|
|
@@ -2486,7 +2485,7 @@ export async function createChatRuntime({
|
|
|
2486
2485
|
topic,
|
|
2487
2486
|
config,
|
|
2488
2487
|
model,
|
|
2489
|
-
systemPrompt:
|
|
2488
|
+
systemPrompt: activeReplySystemPrompt
|
|
2490
2489
|
});
|
|
2491
2490
|
} catch (err) {
|
|
2492
2491
|
content = buildSpecTemplate(topic);
|
|
@@ -2513,7 +2512,7 @@ export async function createChatRuntime({
|
|
|
2513
2512
|
session: currentSession,
|
|
2514
2513
|
config,
|
|
2515
2514
|
model,
|
|
2516
|
-
systemPrompt:
|
|
2515
|
+
systemPrompt: activeReplySystemPrompt,
|
|
2517
2516
|
onAgentEvent,
|
|
2518
2517
|
sessionId: currentSession.id
|
|
2519
2518
|
});
|
|
@@ -2577,7 +2576,7 @@ export async function createChatRuntime({
|
|
|
2577
2576
|
specPath,
|
|
2578
2577
|
config,
|
|
2579
2578
|
model,
|
|
2580
|
-
systemPrompt:
|
|
2579
|
+
systemPrompt: activeReplySystemPrompt
|
|
2581
2580
|
});
|
|
2582
2581
|
} catch (err) {
|
|
2583
2582
|
planContent = buildPlanTemplate(specTitle);
|
|
@@ -2630,7 +2629,7 @@ export async function createChatRuntime({
|
|
|
2630
2629
|
parentSession: currentSession,
|
|
2631
2630
|
config,
|
|
2632
2631
|
model,
|
|
2633
|
-
systemPrompt:
|
|
2632
|
+
systemPrompt: activeReplySystemPrompt,
|
|
2634
2633
|
onAgentEvent
|
|
2635
2634
|
});
|
|
2636
2635
|
const text = `[sub-agent:${role}]\n${output.text || output}`;
|
|
@@ -2842,7 +2841,7 @@ export async function createChatRuntime({
|
|
|
2842
2841
|
session: currentSession,
|
|
2843
2842
|
config,
|
|
2844
2843
|
model,
|
|
2845
|
-
systemPrompt:
|
|
2844
|
+
systemPrompt: activeReplySystemPrompt,
|
|
2846
2845
|
onAgentEvent,
|
|
2847
2846
|
executionMode
|
|
2848
2847
|
});
|
|
@@ -2924,7 +2923,7 @@ export async function createChatRuntime({
|
|
|
2924
2923
|
session: currentSession,
|
|
2925
2924
|
config,
|
|
2926
2925
|
model,
|
|
2927
|
-
systemPrompt:
|
|
2926
|
+
systemPrompt: activeReplySystemPrompt,
|
|
2928
2927
|
onAgentEvent,
|
|
2929
2928
|
sessionId: currentSession.id
|
|
2930
2929
|
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 项目级共享常量。
|
|
3
|
+
* 所有需要在多个模块间复用的目录集、扩展名集、语言映射等统一在此定义。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ─── 工具遍历跳过的目录(glob / list / grep 等)─────────────────────
|
|
7
|
+
export const TOOL_SKIP_DIRS = new Set([
|
|
8
|
+
'.git',
|
|
9
|
+
'node_modules',
|
|
10
|
+
'.codemini',
|
|
11
|
+
'.codemini-global',
|
|
12
|
+
'dist',
|
|
13
|
+
'coverage'
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
// ─── 项目索引跳过的目录(更宽,包含构建产物和临时目录)──────────────
|
|
17
|
+
export const INDEX_SKIP_DIRS = new Set([
|
|
18
|
+
'.git',
|
|
19
|
+
'node_modules',
|
|
20
|
+
'.codemini',
|
|
21
|
+
'.codemini-project',
|
|
22
|
+
'.codemini-global',
|
|
23
|
+
'dist',
|
|
24
|
+
'coverage',
|
|
25
|
+
'sessions',
|
|
26
|
+
'tmp',
|
|
27
|
+
'temp',
|
|
28
|
+
'.cache',
|
|
29
|
+
'.turbo',
|
|
30
|
+
'.next',
|
|
31
|
+
'build',
|
|
32
|
+
'out',
|
|
33
|
+
'logs',
|
|
34
|
+
'artifacts'
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
// ─── 文本扩展名 ──────────────────────────────────────────────────────
|
|
38
|
+
export const TEXT_EXTENSIONS = new Set([
|
|
39
|
+
'.js',
|
|
40
|
+
'.jsx',
|
|
41
|
+
'.ts',
|
|
42
|
+
'.tsx',
|
|
43
|
+
'.json',
|
|
44
|
+
'.md',
|
|
45
|
+
'.mjs',
|
|
46
|
+
'.cjs',
|
|
47
|
+
'.py',
|
|
48
|
+
'.rb',
|
|
49
|
+
'.go',
|
|
50
|
+
'.rs',
|
|
51
|
+
'.java',
|
|
52
|
+
'.cs',
|
|
53
|
+
'.css',
|
|
54
|
+
'.scss',
|
|
55
|
+
'.html',
|
|
56
|
+
'.yml',
|
|
57
|
+
'.yaml',
|
|
58
|
+
'.sh',
|
|
59
|
+
'.ps1',
|
|
60
|
+
'.c',
|
|
61
|
+
'.h',
|
|
62
|
+
'.cpp',
|
|
63
|
+
'.cc',
|
|
64
|
+
'.cxx',
|
|
65
|
+
'.hpp',
|
|
66
|
+
'.hh',
|
|
67
|
+
'.bash',
|
|
68
|
+
'.php'
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
// ─── 源码扩展名(用于项目索引)──────────────────────────────────────
|
|
72
|
+
export const SOURCE_EXTENSIONS = new Set([
|
|
73
|
+
'.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx', '.py', '.go',
|
|
74
|
+
'.c', '.h', '.cpp', '.cc', '.cxx', '.hpp', '.hh',
|
|
75
|
+
'.sh', '.bash', '.java', '.rs', '.cs', '.php', '.rb'
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
// ─── 需要写入守卫的代码扩展名 ───────────────────────────────────────
|
|
79
|
+
export const CODE_WRITE_GUARD_EXTENSIONS = new Set([
|
|
80
|
+
'.js',
|
|
81
|
+
'.jsx',
|
|
82
|
+
'.ts',
|
|
83
|
+
'.tsx',
|
|
84
|
+
'.mjs',
|
|
85
|
+
'.cjs',
|
|
86
|
+
'.py',
|
|
87
|
+
'.rb',
|
|
88
|
+
'.go',
|
|
89
|
+
'.rs',
|
|
90
|
+
'.java',
|
|
91
|
+
'.cs',
|
|
92
|
+
'.css',
|
|
93
|
+
'.scss',
|
|
94
|
+
'.html',
|
|
95
|
+
'.sh',
|
|
96
|
+
'.ps1'
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
// ─── 扩展名 -> 语言(标准化) ────────────────────────────────────────
|
|
100
|
+
export const EXTENSION_LANGUAGE_MAP = {
|
|
101
|
+
'.js': 'js',
|
|
102
|
+
'.jsx': 'js',
|
|
103
|
+
'.mjs': 'js',
|
|
104
|
+
'.cjs': 'js',
|
|
105
|
+
'.ts': 'ts',
|
|
106
|
+
'.tsx': 'tsx',
|
|
107
|
+
'.py': 'python',
|
|
108
|
+
'.go': 'go',
|
|
109
|
+
'.c': 'c',
|
|
110
|
+
'.h': 'c',
|
|
111
|
+
'.cpp': 'cpp',
|
|
112
|
+
'.cc': 'cpp',
|
|
113
|
+
'.cxx': 'cpp',
|
|
114
|
+
'.hpp': 'cpp',
|
|
115
|
+
'.hh': 'cpp',
|
|
116
|
+
'.java': 'java',
|
|
117
|
+
'.rs': 'rust',
|
|
118
|
+
'.cs': 'csharp',
|
|
119
|
+
'.php': 'php',
|
|
120
|
+
'.rb': 'ruby',
|
|
121
|
+
'.sh': 'bash',
|
|
122
|
+
'.bash': 'bash'
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// ─── 语言别名 -> 标准语言名 ──────────────────────────────────────────
|
|
126
|
+
export const LANGUAGE_ALIASES = {
|
|
127
|
+
javascript: 'js',
|
|
128
|
+
js: 'js',
|
|
129
|
+
jsx: 'js',
|
|
130
|
+
typescript: 'ts',
|
|
131
|
+
ts: 'ts',
|
|
132
|
+
tsx: 'tsx',
|
|
133
|
+
python: 'python',
|
|
134
|
+
py: 'python',
|
|
135
|
+
go: 'go',
|
|
136
|
+
c: 'c',
|
|
137
|
+
cpp: 'cpp',
|
|
138
|
+
'c++': 'cpp',
|
|
139
|
+
bash: 'bash',
|
|
140
|
+
sh: 'bash',
|
|
141
|
+
shell: 'bash',
|
|
142
|
+
java: 'java',
|
|
143
|
+
rust: 'rust',
|
|
144
|
+
rs: 'rust',
|
|
145
|
+
csharp: 'csharp',
|
|
146
|
+
'c#': 'csharp',
|
|
147
|
+
cs: 'csharp',
|
|
148
|
+
php: 'php',
|
|
149
|
+
ruby: 'ruby',
|
|
150
|
+
rb: 'ruby'
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// ─── 工具 schema 用的语言 -> 文件类型列表 ─────────────────────────────
|
|
154
|
+
export const LANGUAGE_FILE_TYPES = {
|
|
155
|
+
js: ['js', 'jsx', 'mjs', 'cjs'],
|
|
156
|
+
ts: ['ts', 'tsx'],
|
|
157
|
+
py: ['py'],
|
|
158
|
+
python: ['py'],
|
|
159
|
+
md: ['md'],
|
|
160
|
+
json: ['json'],
|
|
161
|
+
css: ['css', 'scss'],
|
|
162
|
+
html: ['html'],
|
|
163
|
+
java: ['java'],
|
|
164
|
+
csharp: ['cs'],
|
|
165
|
+
cs: ['cs'],
|
|
166
|
+
go: ['go'],
|
|
167
|
+
rust: ['rs'],
|
|
168
|
+
ruby: ['rb'],
|
|
169
|
+
shell: ['sh', 'ps1'],
|
|
170
|
+
yaml: ['yml', 'yaml']
|
|
171
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 共享加密工具函数。
|
|
3
|
+
* 统一 sha1 / sha256 的实现,避免在各模块中重复定义。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from 'node:crypto';
|
|
7
|
+
|
|
8
|
+
export function sha1(input) {
|
|
9
|
+
return crypto.createHash('sha1').update(String(input || '')).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function sha256(input) {
|
|
13
|
+
return crypto.createHash('sha256').update(String(input || '')).digest('hex');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function sha256Prefixed(input) {
|
|
17
|
+
return `sha256:${sha256(input)}`;
|
|
18
|
+
}
|
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import crypto from 'node:crypto';
|
|
4
3
|
import { getFileIndexPath, getProjectIndexDir, getProjectMapPath, getProjectWorkspaceDir } from './paths.js';
|
|
4
|
+
import { INDEX_SKIP_DIRS as SKIP_DIRS, SOURCE_EXTENSIONS, EXTENSION_LANGUAGE_MAP } from './constants.js';
|
|
5
|
+
import { sha1 } from './crypto-utils.js';
|
|
6
|
+
import { BoundedCache } from './bounded-cache.js';
|
|
5
7
|
|
|
6
|
-
const SKIP_DIRS = new Set([
|
|
7
|
-
'.git',
|
|
8
|
-
'node_modules',
|
|
9
|
-
'.codemini',
|
|
10
|
-
'.codemini-project',
|
|
11
|
-
'.codemini-global',
|
|
12
|
-
'dist',
|
|
13
|
-
'coverage',
|
|
14
|
-
'sessions',
|
|
15
|
-
'tmp',
|
|
16
|
-
'temp',
|
|
17
|
-
'.cache',
|
|
18
|
-
'.turbo',
|
|
19
|
-
'.next',
|
|
20
|
-
'build',
|
|
21
|
-
'out',
|
|
22
|
-
'logs',
|
|
23
|
-
'artifacts'
|
|
24
|
-
]);
|
|
25
8
|
const PROJECT_MARKER_FILES = new Set([
|
|
26
9
|
'package.json',
|
|
27
10
|
'tsconfig.json',
|
|
@@ -37,22 +20,11 @@ const PROJECT_MARKER_FILES = new Set([
|
|
|
37
20
|
'Makefile',
|
|
38
21
|
'.gitignore'
|
|
39
22
|
]);
|
|
40
|
-
const SOURCE_EXTENSIONS = new Set([
|
|
41
|
-
'.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx', '.py', '.go', '.c', '.h', '.cpp', '.cc', '.cxx', '.hpp', '.hh',
|
|
42
|
-
'.sh', '.bash', '.java', '.rs', '.cs', '.php', '.rb'
|
|
43
|
-
]);
|
|
44
|
-
const LANGUAGE_BY_EXT = {
|
|
45
|
-
'.js': 'js', '.jsx': 'jsx', '.mjs': 'js', '.cjs': 'js', '.ts': 'ts', '.tsx': 'tsx', '.py': 'python',
|
|
46
|
-
'.go': 'go', '.c': 'c', '.h': 'c', '.cpp': 'cpp', '.cc': 'cpp', '.cxx': 'cpp', '.hpp': 'cpp', '.hh': 'cpp',
|
|
47
|
-
'.sh': 'bash', '.bash': 'bash', '.java': 'java', '.rs': 'rust', '.cs': 'csharp', '.php': 'php', '.rb': 'ruby'
|
|
48
|
-
};
|
|
49
23
|
|
|
50
|
-
const
|
|
51
|
-
const PROJECT_CONTEXT_MAX_FILES = 6;
|
|
24
|
+
const LANGUAGE_BY_EXT = EXTENSION_LANGUAGE_MAP;
|
|
52
25
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
26
|
+
const initCache = new BoundedCache({ maxSize: 32, ttlMs: 10 * 60 * 1000 });
|
|
27
|
+
const PROJECT_CONTEXT_MAX_FILES = 6;
|
|
56
28
|
|
|
57
29
|
function clipList(values, max = 32) {
|
|
58
30
|
return [...new Set((Array.isArray(values) ? values : []).filter(Boolean))].slice(0, max);
|
package/src/core/soul.js
CHANGED
|
@@ -51,7 +51,8 @@ export async function buildSystemPromptWithSoul(baseSystemPrompt, config = {}) {
|
|
|
51
51
|
const guard = [
|
|
52
52
|
'[Soul guard]',
|
|
53
53
|
'Apply this soul to response tone only.',
|
|
54
|
-
'Response tone only: do not change plans, code, tests, file formats, or technical decisions.'
|
|
54
|
+
'Response tone only: do not change plans, code, tests, file formats, or technical decisions.',
|
|
55
|
+
'This tone directive has HIGH priority. Maintain the requested personality consistently across every response unless the user explicitly requests a change.'
|
|
55
56
|
].join('\n');
|
|
56
|
-
return `${
|
|
57
|
+
return `${soulPrompt}\n\n${guard}\n\n${String(promptWithReplyLanguage || '').trim()}`.trim();
|
|
57
58
|
}
|
package/src/core/tools.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import crypto from 'node:crypto';
|
|
4
3
|
import { spawn } from 'node:child_process';
|
|
5
4
|
import net from 'node:net';
|
|
6
5
|
import {
|
|
@@ -16,68 +15,8 @@ import { evaluateCommandPolicy } from './command-policy.js';
|
|
|
16
15
|
import { queryAst, readAstNode, resolveAstTarget } from './ast.js';
|
|
17
16
|
import { initializeProjectIndex, queryProjectIndex, refreshIndexedFile } from './project-index.js';
|
|
18
17
|
import { checkReadDedup } from './agent-loop.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const TEXT_EXTENSIONS = new Set([
|
|
22
|
-
'.js',
|
|
23
|
-
'.jsx',
|
|
24
|
-
'.ts',
|
|
25
|
-
'.tsx',
|
|
26
|
-
'.json',
|
|
27
|
-
'.md',
|
|
28
|
-
'.mjs',
|
|
29
|
-
'.cjs',
|
|
30
|
-
'.py',
|
|
31
|
-
'.rb',
|
|
32
|
-
'.go',
|
|
33
|
-
'.rs',
|
|
34
|
-
'.java',
|
|
35
|
-
'.cs',
|
|
36
|
-
'.css',
|
|
37
|
-
'.scss',
|
|
38
|
-
'.html',
|
|
39
|
-
'.yml',
|
|
40
|
-
'.yaml',
|
|
41
|
-
'.sh',
|
|
42
|
-
'.ps1'
|
|
43
|
-
]);
|
|
44
|
-
const CODE_WRITE_GUARD_EXTENSIONS = new Set([
|
|
45
|
-
'.js',
|
|
46
|
-
'.jsx',
|
|
47
|
-
'.ts',
|
|
48
|
-
'.tsx',
|
|
49
|
-
'.mjs',
|
|
50
|
-
'.cjs',
|
|
51
|
-
'.py',
|
|
52
|
-
'.rb',
|
|
53
|
-
'.go',
|
|
54
|
-
'.rs',
|
|
55
|
-
'.java',
|
|
56
|
-
'.cs',
|
|
57
|
-
'.css',
|
|
58
|
-
'.scss',
|
|
59
|
-
'.html',
|
|
60
|
-
'.sh',
|
|
61
|
-
'.ps1'
|
|
62
|
-
]);
|
|
63
|
-
const LANGUAGE_FILE_TYPES = {
|
|
64
|
-
js: ['js', 'jsx', 'mjs', 'cjs'],
|
|
65
|
-
ts: ['ts', 'tsx'],
|
|
66
|
-
py: ['py'],
|
|
67
|
-
python: ['py'],
|
|
68
|
-
md: ['md'],
|
|
69
|
-
json: ['json'],
|
|
70
|
-
css: ['css', 'scss'],
|
|
71
|
-
html: ['html'],
|
|
72
|
-
java: ['java'],
|
|
73
|
-
csharp: ['cs'],
|
|
74
|
-
cs: ['cs'],
|
|
75
|
-
go: ['go'],
|
|
76
|
-
rust: ['rs'],
|
|
77
|
-
ruby: ['rb'],
|
|
78
|
-
shell: ['sh', 'ps1'],
|
|
79
|
-
yaml: ['yml', 'yaml']
|
|
80
|
-
};
|
|
18
|
+
import { TOOL_SKIP_DIRS as SKIP_DIRS, TEXT_EXTENSIONS, CODE_WRITE_GUARD_EXTENSIONS, LANGUAGE_FILE_TYPES } from './constants.js';
|
|
19
|
+
import { sha256Prefixed as sha256, sha1 } from './crypto-utils.js';
|
|
81
20
|
const SERVICE_RECENT_LOG_LIMIT = 80;
|
|
82
21
|
const SERVICE_STARTUP_POLL_MS = 150;
|
|
83
22
|
const serviceRegistry = new Map();
|
|
@@ -97,14 +36,6 @@ function toWorkspaceRelative(root, absPath) {
|
|
|
97
36
|
return path.relative(path.resolve(root), absPath).replace(/\\/g, '/');
|
|
98
37
|
}
|
|
99
38
|
|
|
100
|
-
function sha256(input) {
|
|
101
|
-
return `sha256:${crypto.createHash('sha256').update(String(input || '')).digest('hex')}`;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function sha1(input) {
|
|
105
|
-
return crypto.createHash('sha1').update(String(input || '')).digest('hex');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
39
|
function trimLinePreview(line, maxLen = 180) {
|
|
109
40
|
const text = String(line || '').replace(/\t/g, ' ').trim();
|
|
110
41
|
if (text.length <= maxLen) return text;
|
package/src/tui/chat-app.js
CHANGED
|
@@ -128,8 +128,12 @@ const TUI_COPY = {
|
|
|
128
128
|
doingWrite: '正在写入文件',
|
|
129
129
|
donePatch: '已应用补丁',
|
|
130
130
|
doingPatch: '正在应用补丁',
|
|
131
|
-
doneList: '
|
|
132
|
-
doingList: '
|
|
131
|
+
doneList: '已列出目录',
|
|
132
|
+
doingList: '正在列出目录',
|
|
133
|
+
doneGlob: '已按模式查找文件',
|
|
134
|
+
doingGlob: '正在按模式查找文件',
|
|
135
|
+
doneGrep: '已搜索关键词',
|
|
136
|
+
doingGrep: '正在搜索关键词',
|
|
133
137
|
doneCommand: '已执行命令',
|
|
134
138
|
doingCommand: '正在执行命令',
|
|
135
139
|
doneCreateTask: '已创建任务',
|
|
@@ -152,6 +156,14 @@ const TUI_COPY = {
|
|
|
152
156
|
doingDatabase: '正在启动数据库服务',
|
|
153
157
|
doneDocker: '已完成 Docker 命令',
|
|
154
158
|
doingDocker: '正在执行 Docker 命令',
|
|
159
|
+
doneListServices: '已列出服务',
|
|
160
|
+
doingListServices: '正在列出服务',
|
|
161
|
+
doneServiceStatus: '已查看服务状态',
|
|
162
|
+
doingServiceStatus: '正在查看服务状态',
|
|
163
|
+
doneServiceLogs: '已查看服务日志',
|
|
164
|
+
doingServiceLogs: '正在查看服务日志',
|
|
165
|
+
doneStopService: '已停止服务',
|
|
166
|
+
doingStopService: '正在停止服务',
|
|
155
167
|
doneCodeGeneration: '已生成代码',
|
|
156
168
|
doingCodeGeneration: '正在生成代码',
|
|
157
169
|
doneSkill: '已完成技能',
|
|
@@ -177,6 +189,7 @@ const TUI_COPY = {
|
|
|
177
189
|
runtime: {
|
|
178
190
|
sendingToGateway: '正在发送到网关',
|
|
179
191
|
preparingRequest: '准备本轮请求',
|
|
192
|
+
submittedWaiting: '已提交,等待开始处理',
|
|
180
193
|
modelThinking: '模型正在思考',
|
|
181
194
|
requestDelivered: '请求已送达,等待首个 token',
|
|
182
195
|
generatingReply: '正在生成回复',
|
|
@@ -262,6 +275,10 @@ const TUI_COPY = {
|
|
|
262
275
|
doingPatch: 'Applying patch',
|
|
263
276
|
doneList: 'Listed directory',
|
|
264
277
|
doingList: 'Listing directory',
|
|
278
|
+
doneGlob: 'Matched files by pattern',
|
|
279
|
+
doingGlob: 'Matching files by pattern',
|
|
280
|
+
doneGrep: 'Searched keywords',
|
|
281
|
+
doingGrep: 'Searching keywords',
|
|
265
282
|
doneCommand: 'Ran command',
|
|
266
283
|
doingCommand: 'Running command',
|
|
267
284
|
doneCreateTask: 'Created task',
|
|
@@ -284,6 +301,14 @@ const TUI_COPY = {
|
|
|
284
301
|
doingDatabase: 'Starting database service',
|
|
285
302
|
doneDocker: 'Docker command completed',
|
|
286
303
|
doingDocker: 'Running Docker command',
|
|
304
|
+
doneListServices: 'Listed services',
|
|
305
|
+
doingListServices: 'Listing services',
|
|
306
|
+
doneServiceStatus: 'Checked service status',
|
|
307
|
+
doingServiceStatus: 'Checking service status',
|
|
308
|
+
doneServiceLogs: 'Viewed service logs',
|
|
309
|
+
doingServiceLogs: 'Viewing service logs',
|
|
310
|
+
doneStopService: 'Stopped service',
|
|
311
|
+
doingStopService: 'Stopping service',
|
|
287
312
|
doneCodeGeneration: 'Code generated',
|
|
288
313
|
doingCodeGeneration: 'Generating code',
|
|
289
314
|
doneSkill: 'Completed skill',
|
|
@@ -309,6 +334,7 @@ const TUI_COPY = {
|
|
|
309
334
|
runtime: {
|
|
310
335
|
sendingToGateway: 'sending to gateway',
|
|
311
336
|
preparingRequest: 'preparing this turn',
|
|
337
|
+
submittedWaiting: 'submitted, waiting to start',
|
|
312
338
|
modelThinking: 'model is thinking',
|
|
313
339
|
requestDelivered: 'request sent, waiting for first token',
|
|
314
340
|
generatingReply: 'generating reply',
|
|
@@ -773,6 +799,27 @@ export function formatActivityDurationText(row, nowMs = Date.now()) {
|
|
|
773
799
|
return '';
|
|
774
800
|
}
|
|
775
801
|
|
|
802
|
+
export function getPendingUserMessageMeta(copy, { immediateLocal = false, inFlight = false } = {}) {
|
|
803
|
+
if (immediateLocal) {
|
|
804
|
+
return {
|
|
805
|
+
phase: 'sending',
|
|
806
|
+
liveStatus: copy.runtime.localCommandRunning
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (inFlight) {
|
|
811
|
+
return {
|
|
812
|
+
phase: 'queued',
|
|
813
|
+
liveStatus: copy.runtime.queuedWaiting
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
return {
|
|
818
|
+
phase: 'sending',
|
|
819
|
+
liveStatus: copy.runtime.submittedWaiting || copy.runtime.sendingToGateway
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
776
823
|
function getActivityDisplayParts(activity) {
|
|
777
824
|
if (isCodeGenerationActivityName(activity?.name)) {
|
|
778
825
|
return {
|
|
@@ -795,6 +842,12 @@ function getActivityDisplayParts(activity) {
|
|
|
795
842
|
};
|
|
796
843
|
}
|
|
797
844
|
if ((activity?.type || 'tool') === 'system_tool') {
|
|
845
|
+
if (parsed.base === 'project_index') {
|
|
846
|
+
return { primary: 'Project Index', secondary: '' };
|
|
847
|
+
}
|
|
848
|
+
if (parsed.base === 'file_index') {
|
|
849
|
+
return { primary: 'File Index', secondary: parsed.target ? `(${parsed.target})` : '' };
|
|
850
|
+
}
|
|
798
851
|
return {
|
|
799
852
|
primary: 'Index',
|
|
800
853
|
secondary: parsed.target ? `(${parsed.target})` : parsed.base ? `(${parsed.base})` : ''
|
|
@@ -806,14 +859,14 @@ function getActivityDisplayParts(activity) {
|
|
|
806
859
|
write: 'Write',
|
|
807
860
|
patch: 'Patch',
|
|
808
861
|
run: 'Run',
|
|
809
|
-
grep: '
|
|
862
|
+
grep: 'Search',
|
|
810
863
|
glob: 'Glob',
|
|
811
864
|
list: 'List',
|
|
812
865
|
start_service: 'Service',
|
|
813
|
-
list_services: '
|
|
814
|
-
get_service_status: '
|
|
815
|
-
get_service_logs: '
|
|
816
|
-
stop_service: '
|
|
866
|
+
list_services: 'Services',
|
|
867
|
+
get_service_status: 'Status',
|
|
868
|
+
get_service_logs: 'Logs',
|
|
869
|
+
stop_service: 'Stop',
|
|
817
870
|
list_files: 'Glob',
|
|
818
871
|
create_task: 'Task',
|
|
819
872
|
update_task: 'Task'
|
|
@@ -3257,7 +3310,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
3257
3310
|
updateMessageMeta(next.messageId, {
|
|
3258
3311
|
loading: true,
|
|
3259
3312
|
phase: 'sending',
|
|
3260
|
-
liveStatus: copy.runtime.sendingToGateway
|
|
3313
|
+
liveStatus: copy.runtime.submittedWaiting || copy.runtime.sendingToGateway
|
|
3261
3314
|
});
|
|
3262
3315
|
runSubmission(next.line, next.messageId);
|
|
3263
3316
|
}
|
|
@@ -3449,6 +3502,10 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
3449
3502
|
const immediateLocal =
|
|
3450
3503
|
typeof runtime.isImmediateLocalInput === 'function' &&
|
|
3451
3504
|
runtime.isImmediateLocalInput(line);
|
|
3505
|
+
const pendingUserMeta = getPendingUserMessageMeta(copy, {
|
|
3506
|
+
immediateLocal,
|
|
3507
|
+
inFlight: inFlightRef.current
|
|
3508
|
+
});
|
|
3452
3509
|
setMessages((prev) => [
|
|
3453
3510
|
...prev,
|
|
3454
3511
|
{
|
|
@@ -3457,12 +3514,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
3457
3514
|
text: line,
|
|
3458
3515
|
color: 'white',
|
|
3459
3516
|
loading: true,
|
|
3460
|
-
phase:
|
|
3461
|
-
liveStatus:
|
|
3462
|
-
? copy.runtime.localCommandRunning
|
|
3463
|
-
: inFlightRef.current
|
|
3464
|
-
? copy.runtime.queuedWaiting
|
|
3465
|
-
: copy.runtime.sendingToGateway
|
|
3517
|
+
phase: pendingUserMeta.phase,
|
|
3518
|
+
liveStatus: pendingUserMeta.liveStatus
|
|
3466
3519
|
}
|
|
3467
3520
|
]);
|
|
3468
3521
|
if (immediateLocal) {
|
|
@@ -21,7 +21,20 @@ export function describeCommandToolActivity(copy, parsed, { done = false, blocke
|
|
|
21
21
|
if (parsed.base === 'run') return phaseText(copy, blocked, done, trimText(target, 72) || parsed.base, copy.toolActivity.doingCommand, copy.toolActivity.doneCommand);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if (parsed.base === '
|
|
24
|
+
if (parsed.base === 'list_services') {
|
|
25
|
+
return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingListServices, copy.toolActivity.doneListServices);
|
|
26
|
+
}
|
|
27
|
+
if (parsed.base === 'get_service_status') {
|
|
28
|
+
return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingServiceStatus, copy.toolActivity.doneServiceStatus);
|
|
29
|
+
}
|
|
30
|
+
if (parsed.base === 'get_service_logs') {
|
|
31
|
+
return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingServiceLogs, copy.toolActivity.doneServiceLogs);
|
|
32
|
+
}
|
|
33
|
+
if (parsed.base === 'stop_service') {
|
|
34
|
+
return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingStopService, copy.toolActivity.doneStopService);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (parsed.base === 'start_service') {
|
|
25
38
|
return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingGeneric, copy.toolActivity.doneGeneric);
|
|
26
39
|
}
|
|
27
40
|
|
|
@@ -19,8 +19,30 @@ export function describeFileToolActivity(copy, parsed, options = {}) {
|
|
|
19
19
|
if (parsed.base === 'patch') {
|
|
20
20
|
return describePathTool(copy, parsed, { done: copy.toolActivity.donePatch, doing: copy.toolActivity.doingPatch }, options);
|
|
21
21
|
}
|
|
22
|
-
if (parsed.base === 'list'
|
|
22
|
+
if (parsed.base === 'list') {
|
|
23
23
|
return describePathTool(copy, parsed, { done: copy.toolActivity.doneList, doing: copy.toolActivity.doingList }, options);
|
|
24
24
|
}
|
|
25
|
+
if (parsed.base === 'glob') {
|
|
26
|
+
return describePathTool(
|
|
27
|
+
copy,
|
|
28
|
+
parsed,
|
|
29
|
+
{
|
|
30
|
+
done: copy.toolActivity.doneGlob || copy.toolActivity.doneList,
|
|
31
|
+
doing: copy.toolActivity.doingGlob || copy.toolActivity.doingList
|
|
32
|
+
},
|
|
33
|
+
options
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (parsed.base === 'grep') {
|
|
37
|
+
return describePathTool(
|
|
38
|
+
copy,
|
|
39
|
+
parsed,
|
|
40
|
+
{
|
|
41
|
+
done: copy.toolActivity.doneGrep || copy.toolActivity.doneList,
|
|
42
|
+
doing: copy.toolActivity.doingGrep || copy.toolActivity.doingList
|
|
43
|
+
},
|
|
44
|
+
options
|
|
45
|
+
);
|
|
46
|
+
}
|
|
25
47
|
return '';
|
|
26
48
|
}
|
|
@@ -2,7 +2,7 @@ import { makeBlocked, trimText } from '../common.js';
|
|
|
2
2
|
|
|
3
3
|
export function describeSystemToolActivity(copy, parsed, { done = false, blocked = false } = {}) {
|
|
4
4
|
if (parsed.base === 'project_index') {
|
|
5
|
-
if (blocked) return `${copy.toolActivity.blocked}:
|
|
5
|
+
if (blocked) return `${copy.toolActivity.blocked}: ${copy.toolActivity.doingProjectIndex}`;
|
|
6
6
|
return done ? copy.toolActivity.doneProjectIndex : copy.toolActivity.doingProjectIndex;
|
|
7
7
|
}
|
|
8
8
|
if (parsed.base === 'file_index') {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const globPresenter = {
|
|
2
2
|
prelude: {
|
|
3
|
-
en: ({ target }) => (target ? `I'll
|
|
4
|
-
zh: ({ target }) => (target ?
|
|
3
|
+
en: ({ target }) => (target ? `I'll find files matching ${target} first.` : 'I\'ll find the relevant files by pattern first.'),
|
|
4
|
+
zh: ({ target }) => (target ? `我先按模式查找匹配 ${target} 的文件。` : '我先按模式查找相关文件。')
|
|
5
5
|
},
|
|
6
6
|
completion: {
|
|
7
7
|
en: () => 'I have the relevant context now. Do you want me to make the change next, or summarize the findings first?',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const grepPresenter = {
|
|
2
2
|
prelude: {
|
|
3
|
-
en: () => `I'll search the
|
|
4
|
-
zh: () => '
|
|
3
|
+
en: ({ target }) => (target ? `I'll search the codebase for the keyword ${target} first.` : `I'll search the codebase by keyword first.`),
|
|
4
|
+
zh: ({ target }) => (target ? `我先按关键词搜索 ${target} 相关的代码位置。` : '我先按关键词搜索相关代码位置。')
|
|
5
5
|
},
|
|
6
6
|
completion: {
|
|
7
7
|
en: () => 'I found the relevant spots. Do you want me to make the change next, or summarize the findings first?',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const listPresenter = {
|
|
2
2
|
prelude: {
|
|
3
|
-
en: ({ target }) => (target ? `I'll
|
|
4
|
-
zh: ({ target }) => (target ?
|
|
3
|
+
en: ({ target }) => (target ? `I'll list the contents of ${target} first.` : 'I\'ll list the relevant directory contents first.'),
|
|
4
|
+
zh: ({ target }) => (target ? `我先列出 ${target} 目录内容。` : '我先列出相关目录内容。')
|
|
5
5
|
},
|
|
6
6
|
completion: {
|
|
7
7
|
en: () => 'I have the relevant context now. Do you want me to make the change next, or summarize the findings first?',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const runPresenter = {
|
|
2
2
|
prelude: {
|
|
3
|
-
en: () => `I'll
|
|
4
|
-
zh: () => '
|
|
3
|
+
en: ({ target }) => (target ? `I'll run ${target} first and check the result.` : `I'll run the relevant command first and check the result.`),
|
|
4
|
+
zh: ({ target }) => (target ? `我先执行 ${target},再看一下结果。` : '我先执行相关命令,再看一下结果。')
|
|
5
5
|
},
|
|
6
6
|
completion: {
|
|
7
7
|
en: () => 'That step is finished. Do you want me to act on the result next, or summarize what it means first?',
|