codemini-cli 0.3.1 → 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 +2 -1
- package/src/commands/chat.js +1 -0
- package/src/commands/run.js +6 -2
- package/src/core/agent-loop.js +20 -19
- package/src/core/chat-runtime.js +567 -233
- package/src/core/checkpoint-store.js +2 -3
- package/src/core/command-policy.js +144 -10
- package/src/core/config-store.js +36 -10
- package/src/core/context-compact.js +7 -1
- package/src/core/default-system-prompt.js +12 -1
- package/src/core/memory-policy.js +33 -0
- package/src/core/memory-prompt.js +45 -0
- package/src/core/memory-store.js +181 -0
- package/src/core/paths.js +8 -0
- package/src/core/provider/anthropic.js +388 -0
- package/src/core/provider/index.js +37 -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 +486 -235
- package/src/tui/chat-app.js +278 -57
- 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
|
@@ -102,7 +102,7 @@ const TUI_COPY = {
|
|
|
102
102
|
startupHint: '使用 /help、/commands、/compact、/exit、!<shell>。Tab 可自动补全 slash 命令。',
|
|
103
103
|
toolSummaryExpanded: '工具摘要:已展开',
|
|
104
104
|
toolSummaryCollapsed: '工具摘要:已收起',
|
|
105
|
-
toolChainCollapsed: (count) => `已折叠更早的 ${count}
|
|
105
|
+
toolChainCollapsed: (count) => `已折叠更早的 ${count} 个工具调用`,
|
|
106
106
|
toggleToolSummary: 'Ctrl+T 切换',
|
|
107
107
|
scrollHint: '使用终端自己的滚动条或 scrollback',
|
|
108
108
|
keyboardDebugEnabled: '键盘调试已开启',
|
|
@@ -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: '已完成技能',
|
|
@@ -247,7 +243,7 @@ const TUI_COPY = {
|
|
|
247
243
|
startupHint: 'Use /help, /commands, /compact, /exit, !<shell>. Tab for slash autocomplete.',
|
|
248
244
|
toolSummaryExpanded: 'Tool summary: expanded',
|
|
249
245
|
toolSummaryCollapsed: 'Tool summary: collapsed',
|
|
250
|
-
toolChainCollapsed: (count) => `${count} earlier tool calls hidden
|
|
246
|
+
toolChainCollapsed: (count) => `${count} earlier tool calls hidden`,
|
|
251
247
|
toggleToolSummary: 'Ctrl+T to toggle',
|
|
252
248
|
scrollHint: 'Scroll with your terminal scrollbar or scrollback',
|
|
253
249
|
keyboardDebugEnabled: 'Keyboard debug enabled',
|
|
@@ -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',
|
|
@@ -697,6 +689,61 @@ function parseRichTextSegments(line, baseColor) {
|
|
|
697
689
|
});
|
|
698
690
|
}
|
|
699
691
|
|
|
692
|
+
export function sanitizeRenderableText(value) {
|
|
693
|
+
const input = String(value ?? '');
|
|
694
|
+
if (!input) return '';
|
|
695
|
+
|
|
696
|
+
return input
|
|
697
|
+
.replace(/\r\n/g, '\n')
|
|
698
|
+
.replace(/\r/g, '\n')
|
|
699
|
+
.replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g, '')
|
|
700
|
+
.replace(/[\u001b\u009b][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g, '')
|
|
701
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, '');
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function textFromSessionContent(content) {
|
|
705
|
+
if (typeof content === 'string') return sanitizeRenderableText(content);
|
|
706
|
+
if (Array.isArray(content)) {
|
|
707
|
+
return sanitizeRenderableText(
|
|
708
|
+
content
|
|
709
|
+
.map((part) => {
|
|
710
|
+
if (typeof part === 'string') return part;
|
|
711
|
+
if (part?.type === 'text') return part.text || '';
|
|
712
|
+
return '';
|
|
713
|
+
})
|
|
714
|
+
.join('')
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
return sanitizeRenderableText(String(content || ''));
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function buildUiMessagesFromSessionHistory(sessionMessages, nextId) {
|
|
721
|
+
const source = Array.isArray(sessionMessages) ? sessionMessages : [];
|
|
722
|
+
const out = [];
|
|
723
|
+
|
|
724
|
+
for (const message of source) {
|
|
725
|
+
if (!message || typeof message !== 'object') continue;
|
|
726
|
+
if (message.role === 'tool') continue;
|
|
727
|
+
|
|
728
|
+
const text = textFromSessionContent(message.content);
|
|
729
|
+
if (!text.trim() && message.role !== 'assistant') continue;
|
|
730
|
+
|
|
731
|
+
if (message.role === 'user') {
|
|
732
|
+
out.push({ id: nextId(), label: 'you', text, color: 'blueBright' });
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
if (message.role === 'assistant') {
|
|
736
|
+
out.push({ id: nextId(), label: 'coder', text, color: 'greenBright' });
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
if (message.role === 'system') {
|
|
740
|
+
out.push({ id: nextId(), label: 'system', text, color: 'yellowBright' });
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return out;
|
|
745
|
+
}
|
|
746
|
+
|
|
700
747
|
function safeJsonParse(raw) {
|
|
701
748
|
try {
|
|
702
749
|
return JSON.parse(String(raw || '{}'));
|
|
@@ -828,7 +875,7 @@ function getActivityDisplayParts(activity) {
|
|
|
828
875
|
};
|
|
829
876
|
}
|
|
830
877
|
const parsed = parseToolDisplayName(activity?.name);
|
|
831
|
-
if (parsed.base === 'run'
|
|
878
|
+
if (parsed.base === 'run') {
|
|
832
879
|
const intent = classifyCommandIntent(parsed.target);
|
|
833
880
|
return {
|
|
834
881
|
primary: getIntentLabel(intent.kind),
|
|
@@ -862,14 +909,11 @@ function getActivityDisplayParts(activity) {
|
|
|
862
909
|
grep: 'Search',
|
|
863
910
|
glob: 'Glob',
|
|
864
911
|
list: 'List',
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
get_service_logs: 'Logs',
|
|
869
|
-
stop_service: 'Stop',
|
|
912
|
+
list_background_tasks: 'Tasks',
|
|
913
|
+
get_background_task: 'Task',
|
|
914
|
+
stop_background_task: 'Stop',
|
|
870
915
|
list_files: 'Glob',
|
|
871
|
-
|
|
872
|
-
update_task: 'Task'
|
|
916
|
+
update_todos: 'Update Todos'
|
|
873
917
|
};
|
|
874
918
|
return {
|
|
875
919
|
primary: labels[parsed.base] || parsed.base || 'Tool',
|
|
@@ -1018,6 +1062,21 @@ function ContextProgressMeter({ runtimeState, runtimeStatus, compact = false })
|
|
|
1018
1062
|
function PlanStrip({ planState, copy }) {
|
|
1019
1063
|
if (!planState || !planState.total) return null;
|
|
1020
1064
|
const progress = `${planState.current}/${planState.total}`;
|
|
1065
|
+
const stripComplete = Boolean(planState.completed) && !planState.failed;
|
|
1066
|
+
const statusLabel = planState.failed ? copy.generic.attention : stripComplete ? copy.generic.taskCompleted : copy.generic.active;
|
|
1067
|
+
const statusColor = planState.failed ? 'redBright' : stripComplete ? 'cyanBright' : 'greenBright';
|
|
1068
|
+
const roleLabel =
|
|
1069
|
+
planState.resultStatus || stripComplete || planState.failed
|
|
1070
|
+
? copy?.roleLabels?.system === 'SYSTEM'
|
|
1071
|
+
? 'RESULT'
|
|
1072
|
+
: '结果'
|
|
1073
|
+
: String(planState.role || 'agent').toUpperCase();
|
|
1074
|
+
const titleLabel =
|
|
1075
|
+
planState.resultStatus || stripComplete || planState.failed
|
|
1076
|
+
? copy?.roleLabels?.system === 'SYSTEM'
|
|
1077
|
+
? 'Plan execution result'
|
|
1078
|
+
: '计划执行结果'
|
|
1079
|
+
: planState.title || 'running plan step';
|
|
1021
1080
|
return h(
|
|
1022
1081
|
Box,
|
|
1023
1082
|
{
|
|
@@ -1036,11 +1095,18 @@ function PlanStrip({ planState, copy }) {
|
|
|
1036
1095
|
null,
|
|
1037
1096
|
h(Text, { color: 'black', backgroundColor: planState.failed ? 'red' : 'cyanBright' }, ` ${copy.generic.plan} ${progress} `),
|
|
1038
1097
|
h(Text, { color: 'gray' }, ' '),
|
|
1039
|
-
h(Text, { color: 'magentaBright' },
|
|
1098
|
+
h(Text, { color: 'magentaBright' }, roleLabel)
|
|
1040
1099
|
),
|
|
1041
|
-
h(Text, { color:
|
|
1100
|
+
h(Text, { color: statusColor }, statusLabel)
|
|
1042
1101
|
),
|
|
1043
|
-
h(Text, { color: 'white' },
|
|
1102
|
+
h(Text, { color: 'white' }, titleLabel),
|
|
1103
|
+
planState.resultVerified
|
|
1104
|
+
? h(
|
|
1105
|
+
Box,
|
|
1106
|
+
{ marginTop: 1 },
|
|
1107
|
+
h(Text, { color: 'gray' }, trimText(planState.resultVerified, 120))
|
|
1108
|
+
)
|
|
1109
|
+
: null,
|
|
1044
1110
|
planState.steps.length > 0
|
|
1045
1111
|
? h(
|
|
1046
1112
|
Box,
|
|
@@ -1058,15 +1124,24 @@ function PlanStrip({ planState, copy }) {
|
|
|
1058
1124
|
)
|
|
1059
1125
|
)
|
|
1060
1126
|
)
|
|
1127
|
+
: null,
|
|
1128
|
+
planState.resultNext
|
|
1129
|
+
? h(
|
|
1130
|
+
Box,
|
|
1131
|
+
{ marginTop: 1 },
|
|
1132
|
+
h(Text, { color: 'black', backgroundColor: 'yellowBright' }, ' NEXT '),
|
|
1133
|
+
h(Text, { color: 'gray' }, ` ${trimText(planState.resultNext, 108)}`)
|
|
1134
|
+
)
|
|
1061
1135
|
: null
|
|
1062
1136
|
);
|
|
1063
1137
|
}
|
|
1064
1138
|
|
|
1065
|
-
function Header({ sessionId, model, shellName, safeMode = true }) {
|
|
1139
|
+
function Header({ sessionId, model, sdkProvider, shellName, safeMode = true }) {
|
|
1066
1140
|
const shortSession = String(sessionId || '').slice(-12) || '-';
|
|
1067
1141
|
const modeValue = safeMode ? 'SAFE' : 'OPEN';
|
|
1068
1142
|
const modeColor = safeMode ? 'greenBright' : 'redBright';
|
|
1069
1143
|
const modeTextColor = safeMode ? 'black' : 'white';
|
|
1144
|
+
const sdkValue = String(sdkProvider || 'openai-compatible');
|
|
1070
1145
|
return h(
|
|
1071
1146
|
Box,
|
|
1072
1147
|
{ width: '100%', justifyContent: 'center', marginTop: 1, marginBottom: 2 },
|
|
@@ -1094,6 +1169,7 @@ function Header({ sessionId, model, shellName, safeMode = true }) {
|
|
|
1094
1169
|
h(
|
|
1095
1170
|
Box,
|
|
1096
1171
|
{ flexDirection: 'row', justifyContent: 'center' },
|
|
1172
|
+
h(StatusPill, { label: 'SDK', value: sdkValue, color: 'blueBright', textColor: 'white' }),
|
|
1097
1173
|
h(StatusPill, { label: 'MODEL', value: model, color: 'cyanBright', textColor: 'black' }),
|
|
1098
1174
|
h(StatusPill, { label: 'SHELL', value: shellName || 'powershell', color: 'yellowBright', textColor: 'black' }),
|
|
1099
1175
|
h(StatusPill, { label: 'SESSION', value: shortSession, color: 'magentaBright', textColor: 'black' }),
|
|
@@ -1149,6 +1225,7 @@ export function parseAutoPlanSummaryMessage(text) {
|
|
|
1149
1225
|
filePath: '',
|
|
1150
1226
|
planSummary: '',
|
|
1151
1227
|
finalSummary: '',
|
|
1228
|
+
approval: '',
|
|
1152
1229
|
stepsTotal: '',
|
|
1153
1230
|
completed: '',
|
|
1154
1231
|
warnings: '',
|
|
@@ -1159,8 +1236,10 @@ export function parseAutoPlanSummaryMessage(text) {
|
|
|
1159
1236
|
|
|
1160
1237
|
for (const line of lines.slice(1)) {
|
|
1161
1238
|
if (line.startsWith('File: ')) parsed.filePath = line.slice('File: '.length).trim();
|
|
1239
|
+
else if (line.startsWith('Plan File: ')) parsed.filePath = line.slice('Plan File: '.length).trim();
|
|
1162
1240
|
else if (line.startsWith('Plan Summary: ')) parsed.planSummary = line.slice('Plan Summary: '.length).trim();
|
|
1163
1241
|
else if (line.startsWith('Final Summary: ')) parsed.finalSummary = line.slice('Final Summary: '.length).trim();
|
|
1242
|
+
else if (line.startsWith('Approval: ')) parsed.approval = line.slice('Approval: '.length).trim();
|
|
1164
1243
|
else if (line.startsWith('Steps: ')) parsed.stepsTotal = line.slice('Steps: '.length).trim();
|
|
1165
1244
|
else if (line.startsWith('Completed: ')) parsed.completed = line.slice('Completed: '.length).trim();
|
|
1166
1245
|
else if (line.startsWith('Warnings: ')) parsed.warnings = line.slice('Warnings: '.length).trim();
|
|
@@ -1184,6 +1263,31 @@ export function parsePlanProgressLine(text) {
|
|
|
1184
1263
|
};
|
|
1185
1264
|
}
|
|
1186
1265
|
|
|
1266
|
+
export function parsePlanExecutionResult(text) {
|
|
1267
|
+
const raw = String(text || '').trim();
|
|
1268
|
+
if (!raw) return null;
|
|
1269
|
+
const statusMatch = raw.match(/(?:^|\n)\s*Status:\s*(done|partial|blocked)\s*$/im);
|
|
1270
|
+
const verifiedMatch = raw.match(/(?:^|\n)\s*Verified:\s*(.+)\s*$/im);
|
|
1271
|
+
const nextMatch = raw.match(/(?:^|\n)\s*Next:\s*(.+)\s*$/im);
|
|
1272
|
+
if (!statusMatch && !verifiedMatch && !nextMatch) return null;
|
|
1273
|
+
return {
|
|
1274
|
+
status: String(statusMatch?.[1] || '').trim().toLowerCase(),
|
|
1275
|
+
verified: String(verifiedMatch?.[1] || '').trim(),
|
|
1276
|
+
next: String(nextMatch?.[1] || '').trim()
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
export function stripPlanExecutionResult(text) {
|
|
1281
|
+
const raw = String(text || '');
|
|
1282
|
+
if (!parsePlanExecutionResult(raw)) return raw;
|
|
1283
|
+
return raw
|
|
1284
|
+
.replace(/(?:^|\n)\s*Status:\s*(done|partial|blocked)\s*$/im, '')
|
|
1285
|
+
.replace(/(?:^|\n)\s*Verified:\s*.+\s*$/im, '')
|
|
1286
|
+
.replace(/(?:^|\n)\s*Next:\s*.+\s*$/im, '')
|
|
1287
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
1288
|
+
.trim();
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1187
1291
|
function getTailPreviewWindow(text, maxLines = 3) {
|
|
1188
1292
|
const source = String(text || '');
|
|
1189
1293
|
if (!source.trim()) return { lines: [], startLine: 1 };
|
|
@@ -1410,7 +1514,7 @@ function finishCodeGeneration(msg, now = Date.now()) {
|
|
|
1410
1514
|
|
|
1411
1515
|
export function injectPlanStateMessage(messages, planState, activeUserMessageId, activeAssistantId) {
|
|
1412
1516
|
const source = Array.isArray(messages) ? messages : [];
|
|
1413
|
-
if (!planState || !planState.total) return source;
|
|
1517
|
+
if (!planState || !planState.total || planState.pendingApproval) return source;
|
|
1414
1518
|
const synthetic = {
|
|
1415
1519
|
id: `plan-state-${planState.current}-${planState.total}-${planState.role || 'agent'}`,
|
|
1416
1520
|
label: 'system',
|
|
@@ -1456,6 +1560,7 @@ function PlanSummaryBubble({ msg, copy }) {
|
|
|
1456
1560
|
? {
|
|
1457
1561
|
conclusion: 'Conclusion',
|
|
1458
1562
|
plan: 'Plan',
|
|
1563
|
+
approval: 'Approval',
|
|
1459
1564
|
warnings: 'Warnings',
|
|
1460
1565
|
failed: 'Failed',
|
|
1461
1566
|
file: 'File',
|
|
@@ -1467,6 +1572,7 @@ function PlanSummaryBubble({ msg, copy }) {
|
|
|
1467
1572
|
: {
|
|
1468
1573
|
conclusion: '结论',
|
|
1469
1574
|
plan: '计划',
|
|
1575
|
+
approval: '审批',
|
|
1470
1576
|
warnings: '警告',
|
|
1471
1577
|
failed: '失败',
|
|
1472
1578
|
file: '文件',
|
|
@@ -1520,11 +1626,19 @@ function PlanSummaryBubble({ msg, copy }) {
|
|
|
1520
1626
|
summary.planSummary
|
|
1521
1627
|
? h(
|
|
1522
1628
|
Box,
|
|
1523
|
-
{ marginBottom: metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
|
|
1629
|
+
{ marginBottom: summary.approval || metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
|
|
1524
1630
|
h(Text, { color: 'cyanBright' }, labels.plan),
|
|
1525
1631
|
h(Text, { color: 'gray' }, summary.planSummary)
|
|
1526
1632
|
)
|
|
1527
1633
|
: null,
|
|
1634
|
+
summary.approval
|
|
1635
|
+
? h(
|
|
1636
|
+
Box,
|
|
1637
|
+
{ marginBottom: metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
|
|
1638
|
+
h(Text, { color: 'yellowBright' }, labels.approval),
|
|
1639
|
+
h(Text, { color: 'gray' }, summary.approval)
|
|
1640
|
+
)
|
|
1641
|
+
: null,
|
|
1528
1642
|
metaItems.length > 0
|
|
1529
1643
|
? h(
|
|
1530
1644
|
Box,
|
|
@@ -1706,7 +1820,7 @@ export function normalizeActivitySpacingRows(inputRows) {
|
|
|
1706
1820
|
|
|
1707
1821
|
if (isActivityRow(row) && !isActivityRow(next) && next) {
|
|
1708
1822
|
const last = normalized.at(-1);
|
|
1709
|
-
if (!isBlankTextRow(last) && !(next.kind === 'status')) {
|
|
1823
|
+
if (!isBlankTextRow(last) && !(next.kind === 'status') && !isTodoRow(next)) {
|
|
1710
1824
|
normalized.push({
|
|
1711
1825
|
kind: 'text',
|
|
1712
1826
|
text: ' ',
|
|
@@ -1723,6 +1837,10 @@ export function normalizeActivitySpacingRows(inputRows) {
|
|
|
1723
1837
|
return normalized;
|
|
1724
1838
|
}
|
|
1725
1839
|
|
|
1840
|
+
function isTodoRow(row) {
|
|
1841
|
+
return row?.kind === 'todo-item';
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1726
1844
|
function isReadActivityName(name) {
|
|
1727
1845
|
const parsed = parseToolDisplayName(name);
|
|
1728
1846
|
return parsed.base === 'read' || parsed.base === 'Read';
|
|
@@ -1906,6 +2024,19 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
|
|
|
1906
2024
|
durationText,
|
|
1907
2025
|
isLatestTool: idx === total - 1
|
|
1908
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
|
+
}
|
|
1909
2040
|
if ((showToolDetails || idx === total - 1) && tool.summary && tool.status !== 'running') {
|
|
1910
2041
|
for (const line of String(tool.summary).split('\n')) {
|
|
1911
2042
|
pushWrappedRow(rows, { kind: 'activity-summary', text: line || ' ', color: 'gray' }, Math.max(8, contentWidth - 4));
|
|
@@ -2000,6 +2131,21 @@ function renderMessageRow(msg, row, idx, loaderTick) {
|
|
|
2000
2131
|
h(Text, { color: 'gray' }, `└ ${row.text}`)
|
|
2001
2132
|
);
|
|
2002
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
|
+
}
|
|
2003
2149
|
if (row.kind === 'table') {
|
|
2004
2150
|
return h(
|
|
2005
2151
|
Box,
|
|
@@ -2497,7 +2643,7 @@ function makeIdleStatus(copy, snapshot, variant = 'ready') {
|
|
|
2497
2643
|
);
|
|
2498
2644
|
}
|
|
2499
2645
|
|
|
2500
|
-
export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName = 'powershell', version = '', safeMode = true }) {
|
|
2646
|
+
export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compatible', language = 'zh', shellName = 'powershell', version = '', safeMode = true }) {
|
|
2501
2647
|
const copy = getCopy(language);
|
|
2502
2648
|
const stdoutCols = Number(process.stdout?.columns || 120);
|
|
2503
2649
|
const [inputValue, setInputValue] = useState('');
|
|
@@ -2513,6 +2659,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2513
2659
|
const [cursorVisible, setCursorVisible] = useState(true);
|
|
2514
2660
|
const [displaySessionId, setDisplaySessionId] = useState(sessionId);
|
|
2515
2661
|
const [displayModel, setDisplayModel] = useState(model);
|
|
2662
|
+
const [displaySdkProvider, setDisplaySdkProvider] = useState(sdkProvider);
|
|
2516
2663
|
const [pendingQueue, setPendingQueue] = useState([]);
|
|
2517
2664
|
const [loaderTick, setLoaderTick] = useState(0);
|
|
2518
2665
|
const [runtimeStatus, setRuntimeStatus] = useState(
|
|
@@ -2526,7 +2673,12 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2526
2673
|
role: '',
|
|
2527
2674
|
title: '',
|
|
2528
2675
|
failed: false,
|
|
2529
|
-
steps: []
|
|
2676
|
+
steps: [],
|
|
2677
|
+
pendingApproval: false,
|
|
2678
|
+
completed: false,
|
|
2679
|
+
resultStatus: '',
|
|
2680
|
+
resultVerified: '',
|
|
2681
|
+
resultNext: ''
|
|
2530
2682
|
});
|
|
2531
2683
|
const [debugKeys, setDebugKeys] = useState(false);
|
|
2532
2684
|
const [lastKeyDebug, setLastKeyDebug] = useState('');
|
|
@@ -2605,6 +2757,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2605
2757
|
if (!snapshot) return;
|
|
2606
2758
|
setDisplaySessionId(snapshot.sessionId || sessionId);
|
|
2607
2759
|
setDisplayModel(snapshot.model || model);
|
|
2760
|
+
setDisplaySdkProvider(snapshot.sdkProvider || sdkProvider);
|
|
2608
2761
|
setRuntimeState(snapshot);
|
|
2609
2762
|
setRuntimeStatus(makeIdleStatus(copy, snapshot, variant));
|
|
2610
2763
|
};
|
|
@@ -2641,6 +2794,11 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2641
2794
|
role,
|
|
2642
2795
|
title,
|
|
2643
2796
|
failed: false,
|
|
2797
|
+
pendingApproval: false,
|
|
2798
|
+
completed: false,
|
|
2799
|
+
resultStatus: '',
|
|
2800
|
+
resultVerified: '',
|
|
2801
|
+
resultNext: '',
|
|
2644
2802
|
steps: [...withoutCurrent, { index: current, total, role, title, status: 'active' }].sort((a, b) => a.index - b.index)
|
|
2645
2803
|
};
|
|
2646
2804
|
});
|
|
@@ -2763,6 +2921,10 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2763
2921
|
|
|
2764
2922
|
const appendResultMessage = (result) => {
|
|
2765
2923
|
if (result.type === 'noop') return;
|
|
2924
|
+
if (Array.isArray(result.restoredMessages)) {
|
|
2925
|
+
setMessages(buildUiMessagesFromSessionHistory(result.restoredMessages, nextId));
|
|
2926
|
+
syncRuntimeVisualState('after');
|
|
2927
|
+
}
|
|
2766
2928
|
if (
|
|
2767
2929
|
result.type === 'system' &&
|
|
2768
2930
|
typeof result.text === 'string' &&
|
|
@@ -2790,7 +2952,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2790
2952
|
{
|
|
2791
2953
|
id: nextId(),
|
|
2792
2954
|
label: 'system',
|
|
2793
|
-
text: copy.generic.keyboardDebugStatus(debugKeys),
|
|
2955
|
+
text: sanitizeRenderableText(copy.generic.keyboardDebugStatus(debugKeys)),
|
|
2794
2956
|
color: 'yellowBright'
|
|
2795
2957
|
}
|
|
2796
2958
|
]);
|
|
@@ -2798,13 +2960,14 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2798
2960
|
}
|
|
2799
2961
|
}
|
|
2800
2962
|
if (result.type === 'assistant') {
|
|
2801
|
-
|
|
2963
|
+
const { displayText } = applyPlanExecutionResult(result.text);
|
|
2964
|
+
if (!activeAssistantIdRef.current && displayText) {
|
|
2802
2965
|
setMessages((prev) => [
|
|
2803
2966
|
...prev,
|
|
2804
2967
|
{
|
|
2805
2968
|
id: nextId(),
|
|
2806
2969
|
label: 'coder',
|
|
2807
|
-
text:
|
|
2970
|
+
text: sanitizeRenderableText(displayText),
|
|
2808
2971
|
color: 'greenBright',
|
|
2809
2972
|
autoSkillNames: activeAssistantAutoSkillNamesRef.current
|
|
2810
2973
|
}
|
|
@@ -2813,12 +2976,27 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2813
2976
|
return;
|
|
2814
2977
|
}
|
|
2815
2978
|
const parsedPlanSummary = result.type === 'system' ? parseAutoPlanSummaryMessage(result.text || '') : null;
|
|
2979
|
+
if (parsedPlanSummary?.approval === 'pending') {
|
|
2980
|
+
setPlanState({
|
|
2981
|
+
current: 0,
|
|
2982
|
+
total: 0,
|
|
2983
|
+
role: '',
|
|
2984
|
+
title: '',
|
|
2985
|
+
failed: false,
|
|
2986
|
+
steps: [],
|
|
2987
|
+
pendingApproval: true,
|
|
2988
|
+
completed: false,
|
|
2989
|
+
resultStatus: '',
|
|
2990
|
+
resultVerified: '',
|
|
2991
|
+
resultNext: ''
|
|
2992
|
+
});
|
|
2993
|
+
}
|
|
2816
2994
|
setMessages((prev) => [
|
|
2817
2995
|
...prev,
|
|
2818
2996
|
{
|
|
2819
2997
|
id: nextId(),
|
|
2820
2998
|
label: 'system',
|
|
2821
|
-
text: result.text || '',
|
|
2999
|
+
text: sanitizeRenderableText(result.text || ''),
|
|
2822
3000
|
color: 'yellowBright',
|
|
2823
3001
|
...(parsedPlanSummary ? { planSummary: parsedPlanSummary } : {})
|
|
2824
3002
|
}
|
|
@@ -2865,6 +3043,31 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2865
3043
|
});
|
|
2866
3044
|
};
|
|
2867
3045
|
|
|
3046
|
+
const applyPlanExecutionResult = (rawText) => {
|
|
3047
|
+
const parsedExecution = parsePlanExecutionResult(rawText);
|
|
3048
|
+
if (!parsedExecution || !planTextBufferRef.current) return { parsedExecution: null, displayText: rawText };
|
|
3049
|
+
setPlanState((prev) => {
|
|
3050
|
+
if (!prev.total) return prev;
|
|
3051
|
+
return {
|
|
3052
|
+
...prev,
|
|
3053
|
+
completed: parsedExecution.status === 'done',
|
|
3054
|
+
failed: parsedExecution.status === 'blocked',
|
|
3055
|
+
resultStatus: parsedExecution.status || prev.resultStatus,
|
|
3056
|
+
resultVerified: parsedExecution.verified || prev.resultVerified,
|
|
3057
|
+
resultNext: parsedExecution.next || prev.resultNext,
|
|
3058
|
+
steps: (prev.steps || []).map((step) =>
|
|
3059
|
+
step.index === prev.current && step.status === 'active'
|
|
3060
|
+
? { ...step, status: parsedExecution.status === 'blocked' ? 'failed' : 'done' }
|
|
3061
|
+
: step
|
|
3062
|
+
)
|
|
3063
|
+
};
|
|
3064
|
+
});
|
|
3065
|
+
return {
|
|
3066
|
+
parsedExecution,
|
|
3067
|
+
displayText: stripPlanExecutionResult(rawText)
|
|
3068
|
+
};
|
|
3069
|
+
};
|
|
3070
|
+
|
|
2868
3071
|
const ensureActiveAssistant = () => {
|
|
2869
3072
|
if (activeAssistantIdRef.current) return activeAssistantIdRef.current;
|
|
2870
3073
|
const aid = nextId();
|
|
@@ -2913,7 +3116,19 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2913
3116
|
setBusy(true);
|
|
2914
3117
|
setInputStage('sending');
|
|
2915
3118
|
setRuntimeStatus(makeStatus(copy.runtime.sendingToGateway, copy.runtime.preparingRequest, 'yellowBright'));
|
|
2916
|
-
setPlanState({
|
|
3119
|
+
setPlanState({
|
|
3120
|
+
current: 0,
|
|
3121
|
+
total: 0,
|
|
3122
|
+
role: '',
|
|
3123
|
+
title: '',
|
|
3124
|
+
failed: false,
|
|
3125
|
+
steps: [],
|
|
3126
|
+
pendingApproval: false,
|
|
3127
|
+
completed: false,
|
|
3128
|
+
resultStatus: '',
|
|
3129
|
+
resultVerified: '',
|
|
3130
|
+
resultNext: ''
|
|
3131
|
+
});
|
|
2917
3132
|
planTextBufferRef.current = '';
|
|
2918
3133
|
activeAssistantIdRef.current = null;
|
|
2919
3134
|
activeAssistantAutoSkillNamesRef.current = [];
|
|
@@ -2996,16 +3211,19 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2996
3211
|
if (hadActiveAssistant) {
|
|
2997
3212
|
streamedAssistantHandledRef.current = true;
|
|
2998
3213
|
}
|
|
3214
|
+
const { displayText } = applyPlanExecutionResult(event.text);
|
|
2999
3215
|
if (targetId && !hasPlannedTools) {
|
|
3000
3216
|
setMessages((prev) =>
|
|
3001
3217
|
prev.map((m) => {
|
|
3002
3218
|
if (m.id !== targetId) return m;
|
|
3003
|
-
const responseText = typeof
|
|
3004
|
-
const
|
|
3219
|
+
const responseText = typeof displayText === 'string' ? displayText.trim() : '';
|
|
3220
|
+
const cleanedExistingText = stripPlanExecutionResult(String(m.text || '')).trim();
|
|
3221
|
+
const finalText = responseText || cleanedExistingText;
|
|
3222
|
+
const shouldSynthesizeCompletion = !finalText && m.syntheticPrelude;
|
|
3005
3223
|
return {
|
|
3006
3224
|
...m,
|
|
3007
|
-
...(
|
|
3008
|
-
? { text:
|
|
3225
|
+
...(finalText
|
|
3226
|
+
? { text: finalText, syntheticPrelude: false }
|
|
3009
3227
|
: shouldSynthesizeCompletion
|
|
3010
3228
|
? { text: buildSyntheticCompletionText(m, copy), syntheticPrelude: false }
|
|
3011
3229
|
: {}),
|
|
@@ -3024,9 +3242,10 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
3024
3242
|
activeAssistantIdRef.current = null;
|
|
3025
3243
|
}
|
|
3026
3244
|
if (!hadActiveAssistant && !hasPlannedTools && event.text) {
|
|
3245
|
+
const cleanedStandaloneText = stripPlanExecutionResult(String(displayText || event.text)).trim();
|
|
3027
3246
|
setMessages((prev) => [
|
|
3028
3247
|
...prev,
|
|
3029
|
-
{ id: nextId(), label: 'coder', text:
|
|
3248
|
+
{ id: nextId(), label: 'coder', text: cleanedStandaloneText, color: 'greenBright' }
|
|
3030
3249
|
]);
|
|
3031
3250
|
}
|
|
3032
3251
|
}
|
|
@@ -3265,7 +3484,8 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
3265
3484
|
appendResultMessage(result);
|
|
3266
3485
|
})
|
|
3267
3486
|
.catch((err) => {
|
|
3268
|
-
|
|
3487
|
+
const message = sanitizeRenderableText(err?.message || String(err));
|
|
3488
|
+
setRuntimeStatus(makeStatus(copy.runtime.requestFailed, message, 'redBright'));
|
|
3269
3489
|
setInputStage('idle');
|
|
3270
3490
|
updateMessageMeta(activeUserMessageIdRef.current, {
|
|
3271
3491
|
loading: false,
|
|
@@ -3281,7 +3501,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
3281
3501
|
}));
|
|
3282
3502
|
setMessages((prev) => [
|
|
3283
3503
|
...prev,
|
|
3284
|
-
{ id: nextId(), label: 'error', text:
|
|
3504
|
+
{ id: nextId(), label: 'error', text: message, color: 'redBright' }
|
|
3285
3505
|
]);
|
|
3286
3506
|
})
|
|
3287
3507
|
.finally(() => {
|
|
@@ -3343,6 +3563,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
3343
3563
|
appendResultMessage(result);
|
|
3344
3564
|
})
|
|
3345
3565
|
.catch((err) => {
|
|
3566
|
+
const message = sanitizeRenderableText(err?.message || String(err));
|
|
3346
3567
|
updateMessageMeta(userMessageId, {
|
|
3347
3568
|
loading: false,
|
|
3348
3569
|
phase: undefined,
|
|
@@ -3350,7 +3571,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
3350
3571
|
});
|
|
3351
3572
|
setMessages((prev) => [
|
|
3352
3573
|
...prev,
|
|
3353
|
-
{ id: nextId(), label: 'error', text:
|
|
3574
|
+
{ id: nextId(), label: 'error', text: message, color: 'redBright' }
|
|
3354
3575
|
]);
|
|
3355
3576
|
});
|
|
3356
3577
|
};
|
|
@@ -3677,7 +3898,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
3677
3898
|
return h(
|
|
3678
3899
|
Box,
|
|
3679
3900
|
{ flexDirection: 'column' },
|
|
3680
|
-
h(Header, { sessionId: displaySessionId, model: displayModel, shellName, safeMode }),
|
|
3901
|
+
h(Header, { sessionId: displaySessionId, model: displayModel, sdkProvider: displaySdkProvider, shellName, safeMode }),
|
|
3681
3902
|
h(MessageList, {
|
|
3682
3903
|
messages: visibleMessages,
|
|
3683
3904
|
loaderTick,
|