codemini-cli 0.3.2 → 0.3.3
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/OPERATIONS.md +4 -0
- package/README.md +199 -133
- package/package.json +1 -1
- package/src/core/agent-loop.js +19 -18
- package/src/core/chat-runtime.js +30 -106
- package/src/core/checkpoint-store.js +2 -3
- package/src/core/command-policy.js +144 -10
- package/src/core/config-store.js +6 -10
- package/src/core/context-compact.js +7 -1
- package/src/core/default-system-prompt.js +12 -1
- package/src/core/memory-policy.js +6 -0
- package/src/core/session-store.js +4 -0
- package/src/core/shell-profile.js +29 -17
- package/src/core/todo-state.js +19 -0
- package/src/core/tools.js +396 -318
- package/src/tui/chat-app.js +54 -33
- package/src/tui/tool-activity/presenters/command.js +8 -15
- package/src/tui/tool-activity/presenters/misc.js +2 -5
- package/src/core/task-store.js +0 -117
package/src/tui/chat-app.js
CHANGED
|
@@ -136,10 +136,8 @@ const TUI_COPY = {
|
|
|
136
136
|
doingGrep: '正在搜索关键词',
|
|
137
137
|
doneCommand: '已执行命令',
|
|
138
138
|
doingCommand: '正在执行命令',
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
doneUpdateTask: '已更新任务',
|
|
142
|
-
doingUpdateTask: '正在更新任务',
|
|
139
|
+
doneUpdateTodos: '已更新待办',
|
|
140
|
+
doingUpdateTodos: '正在更新待办',
|
|
143
141
|
doneGeneric: '已完成工具',
|
|
144
142
|
doingGeneric: '正在执行工具',
|
|
145
143
|
doneInstall: '已安装依赖',
|
|
@@ -156,14 +154,12 @@ const TUI_COPY = {
|
|
|
156
154
|
doingDatabase: '正在启动数据库服务',
|
|
157
155
|
doneDocker: '已完成 Docker 命令',
|
|
158
156
|
doingDocker: '正在执行 Docker 命令',
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
doneStopService: '已停止服务',
|
|
166
|
-
doingStopService: '正在停止服务',
|
|
157
|
+
doneListBackgroundTasks: '已列出后台任务',
|
|
158
|
+
doingListBackgroundTasks: '正在列出后台任务',
|
|
159
|
+
doneBackgroundTaskStatus: '已查看后台任务',
|
|
160
|
+
doingBackgroundTaskStatus: '正在查看后台任务',
|
|
161
|
+
doneStopBackgroundTask: '已停止后台任务',
|
|
162
|
+
doingStopBackgroundTask: '正在停止后台任务',
|
|
167
163
|
doneCodeGeneration: '已生成代码',
|
|
168
164
|
doingCodeGeneration: '正在生成代码',
|
|
169
165
|
doneSkill: '已完成技能',
|
|
@@ -281,10 +277,8 @@ const TUI_COPY = {
|
|
|
281
277
|
doingGrep: 'Searching keywords',
|
|
282
278
|
doneCommand: 'Ran command',
|
|
283
279
|
doingCommand: 'Running command',
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
doneUpdateTask: 'Updated task',
|
|
287
|
-
doingUpdateTask: 'Updating task',
|
|
280
|
+
doneUpdateTodos: 'Updated todos',
|
|
281
|
+
doingUpdateTodos: 'Updating todos',
|
|
288
282
|
doneGeneric: 'Completed tool',
|
|
289
283
|
doingGeneric: 'Running tool',
|
|
290
284
|
doneInstall: 'Dependencies installed',
|
|
@@ -301,14 +295,12 @@ const TUI_COPY = {
|
|
|
301
295
|
doingDatabase: 'Starting database service',
|
|
302
296
|
doneDocker: 'Docker command completed',
|
|
303
297
|
doingDocker: 'Running Docker command',
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
doneStopService: 'Stopped service',
|
|
311
|
-
doingStopService: 'Stopping service',
|
|
298
|
+
doneListBackgroundTasks: 'Listed background tasks',
|
|
299
|
+
doingListBackgroundTasks: 'Listing background tasks',
|
|
300
|
+
doneBackgroundTaskStatus: 'Checked background task',
|
|
301
|
+
doingBackgroundTaskStatus: 'Checking background task',
|
|
302
|
+
doneStopBackgroundTask: 'Stopped background task',
|
|
303
|
+
doingStopBackgroundTask: 'Stopping background task',
|
|
312
304
|
doneCodeGeneration: 'Code generated',
|
|
313
305
|
doingCodeGeneration: 'Generating code',
|
|
314
306
|
doneSkill: 'Completed skill',
|
|
@@ -883,7 +875,7 @@ function getActivityDisplayParts(activity) {
|
|
|
883
875
|
};
|
|
884
876
|
}
|
|
885
877
|
const parsed = parseToolDisplayName(activity?.name);
|
|
886
|
-
if (parsed.base === 'run'
|
|
878
|
+
if (parsed.base === 'run') {
|
|
887
879
|
const intent = classifyCommandIntent(parsed.target);
|
|
888
880
|
return {
|
|
889
881
|
primary: getIntentLabel(intent.kind),
|
|
@@ -917,14 +909,11 @@ function getActivityDisplayParts(activity) {
|
|
|
917
909
|
grep: 'Search',
|
|
918
910
|
glob: 'Glob',
|
|
919
911
|
list: 'List',
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
get_service_logs: 'Logs',
|
|
924
|
-
stop_service: 'Stop',
|
|
912
|
+
list_background_tasks: 'Tasks',
|
|
913
|
+
get_background_task: 'Task',
|
|
914
|
+
stop_background_task: 'Stop',
|
|
925
915
|
list_files: 'Glob',
|
|
926
|
-
|
|
927
|
-
update_task: 'Task'
|
|
916
|
+
update_todos: 'Update Todos'
|
|
928
917
|
};
|
|
929
918
|
return {
|
|
930
919
|
primary: labels[parsed.base] || parsed.base || 'Tool',
|
|
@@ -1831,7 +1820,7 @@ export function normalizeActivitySpacingRows(inputRows) {
|
|
|
1831
1820
|
|
|
1832
1821
|
if (isActivityRow(row) && !isActivityRow(next) && next) {
|
|
1833
1822
|
const last = normalized.at(-1);
|
|
1834
|
-
if (!isBlankTextRow(last) && !(next.kind === 'status')) {
|
|
1823
|
+
if (!isBlankTextRow(last) && !(next.kind === 'status') && !isTodoRow(next)) {
|
|
1835
1824
|
normalized.push({
|
|
1836
1825
|
kind: 'text',
|
|
1837
1826
|
text: ' ',
|
|
@@ -1848,6 +1837,10 @@ export function normalizeActivitySpacingRows(inputRows) {
|
|
|
1848
1837
|
return normalized;
|
|
1849
1838
|
}
|
|
1850
1839
|
|
|
1840
|
+
function isTodoRow(row) {
|
|
1841
|
+
return row?.kind === 'todo-item';
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1851
1844
|
function isReadActivityName(name) {
|
|
1852
1845
|
const parsed = parseToolDisplayName(name);
|
|
1853
1846
|
return parsed.base === 'read' || parsed.base === 'Read';
|
|
@@ -2031,6 +2024,19 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
|
|
|
2031
2024
|
durationText,
|
|
2032
2025
|
isLatestTool: idx === total - 1
|
|
2033
2026
|
});
|
|
2027
|
+
const todoItems = parseToolDisplayName(tool.name).base === 'update_todos' ? tool?.arguments?.todos : null;
|
|
2028
|
+
if (Array.isArray(todoItems) && todoItems.length > 0 && tool.status !== 'running') {
|
|
2029
|
+
for (const item of todoItems) {
|
|
2030
|
+
const status = String(item?.status || 'pending').trim();
|
|
2031
|
+
rows.push({
|
|
2032
|
+
kind: 'todo-item',
|
|
2033
|
+
status,
|
|
2034
|
+
text: String(item?.content || '').trim(),
|
|
2035
|
+
activeForm: String(item?.activeForm || '').trim()
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2034
2040
|
if ((showToolDetails || idx === total - 1) && tool.summary && tool.status !== 'running') {
|
|
2035
2041
|
for (const line of String(tool.summary).split('\n')) {
|
|
2036
2042
|
pushWrappedRow(rows, { kind: 'activity-summary', text: line || ' ', color: 'gray' }, Math.max(8, contentWidth - 4));
|
|
@@ -2125,6 +2131,21 @@ function renderMessageRow(msg, row, idx, loaderTick) {
|
|
|
2125
2131
|
h(Text, { color: 'gray' }, `└ ${row.text}`)
|
|
2126
2132
|
);
|
|
2127
2133
|
}
|
|
2134
|
+
if (row.kind === 'todo-item') {
|
|
2135
|
+
const marker =
|
|
2136
|
+
row.status === 'completed' ? '[✓]' : row.status === 'in_progress' ? '[*]' : '[ ]';
|
|
2137
|
+
const color =
|
|
2138
|
+
row.status === 'completed' ? 'gray' : row.status === 'in_progress' ? 'white' : 'gray';
|
|
2139
|
+
const dimColor = row.status === 'completed';
|
|
2140
|
+
return h(
|
|
2141
|
+
Box,
|
|
2142
|
+
{ key: `row-todo-${msg.id}-${idx}`, marginLeft: 2, marginBottom: 1 },
|
|
2143
|
+
h(Text, { color: 'gray' }, ' '),
|
|
2144
|
+
h(Text, { color }, marker),
|
|
2145
|
+
h(Text, { color: 'gray' }, ' '),
|
|
2146
|
+
h(Text, { color, dimColor }, row.text || row.activeForm || ' ')
|
|
2147
|
+
);
|
|
2148
|
+
}
|
|
2128
2149
|
if (row.kind === 'table') {
|
|
2129
2150
|
return h(
|
|
2130
2151
|
Box,
|
|
@@ -9,7 +9,7 @@ export function describeCommandToolActivity(copy, parsed, { done = false, blocke
|
|
|
9
9
|
const target = parsed.target || 'command';
|
|
10
10
|
const intent = classifyRunIntent(parsed.target);
|
|
11
11
|
|
|
12
|
-
if (parsed.base === 'run'
|
|
12
|
+
if (parsed.base === 'run') {
|
|
13
13
|
if (intent.kind === 'install') return phaseText(copy, blocked, done, target, copy.toolActivity.doingInstall, copy.toolActivity.doneInstall);
|
|
14
14
|
if (intent.kind === 'build') return phaseText(copy, blocked, done, target, copy.toolActivity.doingBuild, copy.toolActivity.doneBuild);
|
|
15
15
|
if (intent.kind === 'test') return phaseText(copy, blocked, done, target, copy.toolActivity.doingTest, copy.toolActivity.doneTest);
|
|
@@ -18,24 +18,17 @@ export function describeCommandToolActivity(copy, parsed, { done = false, blocke
|
|
|
18
18
|
if (intent.kind === 'database-service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingDatabase, copy.toolActivity.doneDatabase);
|
|
19
19
|
if (intent.kind === 'docker-service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingDocker, copy.toolActivity.doneDocker);
|
|
20
20
|
if (intent.kind === 'service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingGeneric, copy.toolActivity.doneGeneric);
|
|
21
|
-
|
|
21
|
+
return phaseText(copy, blocked, done, trimText(target, 72) || parsed.base, copy.toolActivity.doingCommand, copy.toolActivity.doneCommand);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if (parsed.base === '
|
|
25
|
-
return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.
|
|
24
|
+
if (parsed.base === 'list_background_tasks') {
|
|
25
|
+
return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingListBackgroundTasks, copy.toolActivity.doneListBackgroundTasks);
|
|
26
26
|
}
|
|
27
|
-
if (parsed.base === '
|
|
28
|
-
return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.
|
|
27
|
+
if (parsed.base === 'get_background_task') {
|
|
28
|
+
return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingBackgroundTaskStatus, copy.toolActivity.doneBackgroundTaskStatus);
|
|
29
29
|
}
|
|
30
|
-
if (parsed.base === '
|
|
31
|
-
return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.
|
|
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') {
|
|
38
|
-
return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingGeneric, copy.toolActivity.doneGeneric);
|
|
30
|
+
if (parsed.base === 'stop_background_task') {
|
|
31
|
+
return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingStopBackgroundTask, copy.toolActivity.doneStopBackgroundTask);
|
|
39
32
|
}
|
|
40
33
|
|
|
41
34
|
return '';
|
|
@@ -9,11 +9,8 @@ export function describeMiscToolActivity(copy, parsed, rawName, { done = false,
|
|
|
9
9
|
if (blocked) return `${copy.toolActivity.blocked}: code generation`;
|
|
10
10
|
return done ? copy.toolActivity.doneCodeGeneration : copy.toolActivity.doingCodeGeneration;
|
|
11
11
|
}
|
|
12
|
-
if (parsed.base === '
|
|
13
|
-
return blocked ? makeBlocked(copy, '
|
|
14
|
-
}
|
|
15
|
-
if (parsed.base === 'update_task') {
|
|
16
|
-
return blocked ? makeBlocked(copy, 'update_task') : done ? copy.toolActivity.doneUpdateTask : copy.toolActivity.doingUpdateTask;
|
|
12
|
+
if (parsed.base === 'update_todos') {
|
|
13
|
+
return blocked ? makeBlocked(copy, 'update_todos') : done ? copy.toolActivity.doneUpdateTodos : copy.toolActivity.doingUpdateTodos;
|
|
17
14
|
}
|
|
18
15
|
return blocked ? `${copy.toolActivity.blocked}: ${parsed.raw}` : done ? `${copy.toolActivity.doneGeneric}: ${parsed.raw}` : `${copy.toolActivity.doingGeneric}: ${parsed.raw}`;
|
|
19
16
|
}
|
package/src/core/task-store.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { getProjectLegacyTasksFilePath, getProjectTasksDir } from './paths.js';
|
|
4
|
-
|
|
5
|
-
function legacyTasksFilePath(cwd = process.cwd()) {
|
|
6
|
-
return getProjectLegacyTasksFilePath(cwd);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function tasksFilePath(cwd = process.cwd(), sessionId = '') {
|
|
10
|
-
const sid = String(sessionId || '').trim();
|
|
11
|
-
if (!sid) return legacyTasksFilePath(cwd);
|
|
12
|
-
return path.join(getProjectTasksDir(cwd), `${sid}.json`);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async function ensureDir(filePath) {
|
|
16
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function normalizeTasks(value) {
|
|
20
|
-
if (!Array.isArray(value)) return [];
|
|
21
|
-
return value
|
|
22
|
-
.map((t) => ({
|
|
23
|
-
id: String(t?.id || '').trim(),
|
|
24
|
-
title: String(t?.title || '').trim(),
|
|
25
|
-
status: String(t?.status || 'pending').trim() || 'pending',
|
|
26
|
-
description: String(t?.description || '').trim(),
|
|
27
|
-
createdAt: String(t?.createdAt || ''),
|
|
28
|
-
updatedAt: String(t?.updatedAt || '')
|
|
29
|
-
}))
|
|
30
|
-
.filter((t) => t.id && t.title);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function createTaskId() {
|
|
34
|
-
const ts = Date.now().toString(36);
|
|
35
|
-
const rnd = Math.random().toString(36).slice(2, 7);
|
|
36
|
-
return `task-${ts}-${rnd}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export async function loadTasks(cwd = process.cwd(), sessionId = '') {
|
|
40
|
-
const filePath = tasksFilePath(cwd, sessionId);
|
|
41
|
-
try {
|
|
42
|
-
const raw = await fs.readFile(filePath, 'utf8');
|
|
43
|
-
const parsed = JSON.parse(raw);
|
|
44
|
-
return normalizeTasks(parsed?.tasks);
|
|
45
|
-
} catch {
|
|
46
|
-
if (sessionId) {
|
|
47
|
-
try {
|
|
48
|
-
const raw = await fs.readFile(legacyTasksFilePath(cwd), 'utf8');
|
|
49
|
-
const parsed = JSON.parse(raw);
|
|
50
|
-
return normalizeTasks(parsed?.tasks);
|
|
51
|
-
} catch {
|
|
52
|
-
return [];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return [];
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function saveTasks(tasks, cwd = process.cwd(), sessionId = '') {
|
|
60
|
-
const filePath = tasksFilePath(cwd, sessionId);
|
|
61
|
-
await ensureDir(filePath);
|
|
62
|
-
const normalized = normalizeTasks(tasks);
|
|
63
|
-
await fs.writeFile(
|
|
64
|
-
filePath,
|
|
65
|
-
`${JSON.stringify({ updatedAt: new Date().toISOString(), tasks: normalized }, null, 2)}\n`,
|
|
66
|
-
'utf8'
|
|
67
|
-
);
|
|
68
|
-
return normalized;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function createTasks(items, cwd = process.cwd(), sessionId = '') {
|
|
72
|
-
const existing = await loadTasks(cwd, sessionId);
|
|
73
|
-
const now = new Date().toISOString();
|
|
74
|
-
const input = Array.isArray(items) ? items : [];
|
|
75
|
-
const add = input
|
|
76
|
-
.map((t) => ({
|
|
77
|
-
id: createTaskId(),
|
|
78
|
-
title: String(t?.title || '').trim(),
|
|
79
|
-
description: String(t?.description || '').trim(),
|
|
80
|
-
status: 'pending',
|
|
81
|
-
createdAt: now,
|
|
82
|
-
updatedAt: now
|
|
83
|
-
}))
|
|
84
|
-
.filter((t) => t.title);
|
|
85
|
-
const next = [...existing, ...add];
|
|
86
|
-
await saveTasks(next, cwd, sessionId);
|
|
87
|
-
return add;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export async function updateTask(taskId, patch, cwd = process.cwd(), sessionId = '') {
|
|
91
|
-
const tasks = await loadTasks(cwd, sessionId);
|
|
92
|
-
const idx = tasks.findIndex((t) => t.id === taskId);
|
|
93
|
-
if (idx === -1) return null;
|
|
94
|
-
const next = [...tasks];
|
|
95
|
-
const status = String(patch?.status || next[idx].status).trim();
|
|
96
|
-
next[idx] = {
|
|
97
|
-
...next[idx],
|
|
98
|
-
...(patch?.title ? { title: String(patch.title) } : {}),
|
|
99
|
-
...(patch?.description !== undefined ? { description: String(patch.description || '') } : {}),
|
|
100
|
-
status: ['pending', 'in_progress', 'completed'].includes(status) ? status : next[idx].status,
|
|
101
|
-
updatedAt: new Date().toISOString()
|
|
102
|
-
};
|
|
103
|
-
await saveTasks(next, cwd, sessionId);
|
|
104
|
-
return next[idx];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export async function deleteTasks(ids, cwd = process.cwd(), sessionId = '') {
|
|
108
|
-
const remove = new Set((Array.isArray(ids) ? ids : []).map((v) => String(v)));
|
|
109
|
-
const before = await loadTasks(cwd, sessionId);
|
|
110
|
-
const kept = before.filter((t) => !remove.has(t.id));
|
|
111
|
-
await saveTasks(kept, cwd, sessionId);
|
|
112
|
-
return { removed: before.length - kept.length, remaining: kept.length };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export async function clearTasks(cwd = process.cwd(), sessionId = '') {
|
|
116
|
-
await saveTasks([], cwd, sessionId);
|
|
117
|
-
}
|