@keyou007/agent-bot 0.1.13 → 0.1.14-alpha.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/CHANGELOG.md +17 -0
- package/README.md +41 -44
- package/README.zh.md +41 -44
- package/config.example.yaml +4 -0
- package/dist/codex/CodexRuntime.js +8 -3
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +13 -0
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/config/loadConfig.js +2 -0
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +2 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +2 -0
- package/dist/console/ConsoleTurnPresenter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +428 -9
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +29 -8
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +78 -8
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +31 -2
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/ownerAccess.js +7 -0
- package/dist/feishu/ownerAccess.js.map +1 -0
- package/dist/index.js +14 -17
- package/dist/index.js.map +1 -1
- package/dist/presentation/OutboundRouter.js +19 -1
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/presentation/splitMarkdown.js +16 -4
- package/dist/presentation/splitMarkdown.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +1012 -58
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/startup/StartupNotifier.js +65 -16
- package/dist/startup/StartupNotifier.js.map +1 -1
- package/dist/startup/startFeishu.js +2 -2
- package/dist/startup/startFeishu.js.map +1 -1
- package/dist/state/StateStore.js +250 -67
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/migrations.js +26 -0
- package/dist/state/migrations.js.map +1 -1
- package/dist/supervision/SafeRestartNotifier.js +17 -16
- package/dist/supervision/SafeRestartNotifier.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +12 -7
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/dist/supervision/replacementSupervisor.js +45 -4
- package/dist/supervision/replacementSupervisor.js.map +1 -1
- package/dist/supervisor.js +30 -18
- package/dist/supervisor.js.map +1 -1
- package/docs/technical-reference.md +18 -5
- package/docs/technical-reference.zh.md +18 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +8 -2
- package/src/codex/CodexRuntime.ts +9 -3
- package/src/commands/CommandRouter.ts +13 -0
- package/src/commands/commandTypes.ts +2 -0
- package/src/config/loadConfig.ts +2 -0
- package/src/config/schema.ts +2 -0
- package/src/console/ConsoleTurnPresenter.ts +4 -1
- package/src/feishu/CardRenderer.ts +533 -9
- package/src/feishu/FeishuConnector.ts +44 -10
- package/src/feishu/FeishuMessageClient.ts +118 -7
- package/src/feishu/FeishuTurnPresenter.ts +42 -3
- package/src/feishu/ownerAccess.ts +7 -0
- package/src/feishu/types.ts +7 -0
- package/src/index.ts +20 -20
- package/src/presentation/OutboundRouter.ts +28 -3
- package/src/presentation/splitMarkdown.ts +26 -3
- package/src/proxy/ProxySessionController.ts +1177 -57
- package/src/startup/StartupNotifier.ts +67 -17
- package/src/startup/startFeishu.ts +5 -3
- package/src/state/StateStore.ts +394 -65
- package/src/state/migrations.ts +26 -0
- package/src/supervision/SafeRestartNotifier.ts +17 -17
- package/src/supervision/SafeRestartScheduler.ts +13 -6
- package/src/supervision/replacementSupervisor.ts +50 -4
- package/src/supervisor.ts +35 -16
|
@@ -177,6 +177,43 @@ export interface TaskListCardEntry {
|
|
|
177
177
|
current?: boolean;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
export interface SessionTaskCardEntry {
|
|
181
|
+
reference: string;
|
|
182
|
+
summary: string;
|
|
183
|
+
detailLines: string[];
|
|
184
|
+
actions?: TaskListCardAction[];
|
|
185
|
+
current?: boolean;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface SessionTaskCardGroup {
|
|
189
|
+
title: string;
|
|
190
|
+
entries: SessionTaskCardEntry[];
|
|
191
|
+
actions?: TaskListCardAction[];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface DirectoryBrowserCardEntry {
|
|
195
|
+
name: string;
|
|
196
|
+
kind: "directory" | "drive" | "file" | "image" | "binary";
|
|
197
|
+
openAction?: TaskListCardAction;
|
|
198
|
+
actions?: TaskListCardAction[];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface DirectoryBrowserCardView {
|
|
202
|
+
directory: string;
|
|
203
|
+
entries: DirectoryBrowserCardEntry[];
|
|
204
|
+
currentActions: TaskListCardAction[];
|
|
205
|
+
navigationActions: TaskListCardAction[];
|
|
206
|
+
footerLines: string[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const DIRECTORY_BROWSER_ROW_COUNT = 16;
|
|
210
|
+
|
|
211
|
+
export type ThinkingCardLayout = "grouped" | "timeline";
|
|
212
|
+
|
|
213
|
+
export interface CardRendererOptions {
|
|
214
|
+
thinkingCardLayout?: ThinkingCardLayout;
|
|
215
|
+
}
|
|
216
|
+
|
|
180
217
|
export interface ResetHistoryCardEntry extends TaskListCardEntry {
|
|
181
218
|
sequence: number;
|
|
182
219
|
graphNodeLine: string;
|
|
@@ -190,6 +227,12 @@ export interface ResetHistoryCardView {
|
|
|
190
227
|
}
|
|
191
228
|
|
|
192
229
|
export class CardRenderer {
|
|
230
|
+
private readonly thinkingCardLayout: ThinkingCardLayout;
|
|
231
|
+
|
|
232
|
+
constructor(options: CardRendererOptions = {}) {
|
|
233
|
+
this.thinkingCardLayout = options.thinkingCardLayout ?? "grouped";
|
|
234
|
+
}
|
|
235
|
+
|
|
193
236
|
renderExecutionSettings(view: ExecutionSettingsCardView): Record<string, unknown> {
|
|
194
237
|
const baseAction: Record<string, string> = {
|
|
195
238
|
contextKey: view.contextKey,
|
|
@@ -807,7 +850,9 @@ export class CardRenderer {
|
|
|
807
850
|
}
|
|
808
851
|
|
|
809
852
|
renderTurn(state: TurnViewState): Record<string, unknown> {
|
|
810
|
-
const elements =
|
|
853
|
+
const elements = this.thinkingCardLayout === "timeline"
|
|
854
|
+
? renderTurnElements(state, "hidden")
|
|
855
|
+
: renderGroupedTurnElements(state, "hidden");
|
|
811
856
|
if (isTurnStoppable(state.status)) {
|
|
812
857
|
elements.push({ tag: "hr" }, taskActionRow([{
|
|
813
858
|
text: "Stop",
|
|
@@ -843,7 +888,13 @@ export class CardRenderer {
|
|
|
843
888
|
}
|
|
844
889
|
|
|
845
890
|
renderActivityHistory(state: TurnViewState, requestedPage: number): Record<string, unknown> {
|
|
846
|
-
const
|
|
891
|
+
const grouped = this.thinkingCardLayout === "grouped";
|
|
892
|
+
const pages = grouped
|
|
893
|
+
? groupedActivityPages(turnActivities(state), state.projectCwd, {
|
|
894
|
+
fullActivityText: true,
|
|
895
|
+
includeReasoningHistory: true,
|
|
896
|
+
})
|
|
897
|
+
: activityPages(turnActivities(state));
|
|
847
898
|
if (pages.length === 0) {
|
|
848
899
|
return this.renderSectionsCard("思考活动历史", [{ lines: ["没有保存到活动记录。"] }]);
|
|
849
900
|
}
|
|
@@ -864,7 +915,12 @@ export class CardRenderer {
|
|
|
864
915
|
];
|
|
865
916
|
const elements: Record<string, unknown>[] = [];
|
|
866
917
|
if (actions.length > 0) elements.push(taskActionRow(actions), { tag: "hr" });
|
|
867
|
-
elements.push(...
|
|
918
|
+
elements.push(...(grouped
|
|
919
|
+
? renderGroupedActivityGroups(pages[page] as GroupedTurnActivity[] | undefined ?? [], state.projectCwd, {
|
|
920
|
+
fullActivityText: true,
|
|
921
|
+
includeReasoningHistory: true,
|
|
922
|
+
})
|
|
923
|
+
: renderActivities(pages[page] as TurnActivity[] | undefined ?? [], state.projectCwd, true)));
|
|
868
924
|
return sectionCard(`思考活动历史 · ${page + 1}/${pages.length}`, elements.length > 0 ? elements : [markdown("无")]);
|
|
869
925
|
}
|
|
870
926
|
|
|
@@ -990,6 +1046,68 @@ export class CardRenderer {
|
|
|
990
1046
|
};
|
|
991
1047
|
}
|
|
992
1048
|
|
|
1049
|
+
renderSessionTaskListCard(
|
|
1050
|
+
title: string,
|
|
1051
|
+
sectionTitle: string,
|
|
1052
|
+
groups: SessionTaskCardGroup[],
|
|
1053
|
+
footerLines: string[],
|
|
1054
|
+
footerActions: TaskListCardAction[] = [],
|
|
1055
|
+
): Record<string, unknown> {
|
|
1056
|
+
const elements: Record<string, unknown>[] = [markdown(`**${sectionTitle}**`)];
|
|
1057
|
+
if (groups.length === 0) {
|
|
1058
|
+
elements.push(markdown("无"));
|
|
1059
|
+
} else {
|
|
1060
|
+
groups.forEach((group) => {
|
|
1061
|
+
elements.push(sessionProjectRow(group));
|
|
1062
|
+
elements.push(...group.entries.map(sessionTaskPanel));
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
if (footerActions.length > 0) {
|
|
1066
|
+
elements.push(taskActionRow(footerActions));
|
|
1067
|
+
}
|
|
1068
|
+
if (footerLines.length > 0) {
|
|
1069
|
+
elements.push(markdown(footerLines.join("\n")));
|
|
1070
|
+
}
|
|
1071
|
+
return {
|
|
1072
|
+
schema: "2.0",
|
|
1073
|
+
config: {
|
|
1074
|
+
update_multi: true,
|
|
1075
|
+
width_mode: "fill",
|
|
1076
|
+
},
|
|
1077
|
+
header: {
|
|
1078
|
+
template: "blue",
|
|
1079
|
+
title: {
|
|
1080
|
+
tag: "plain_text",
|
|
1081
|
+
content: title,
|
|
1082
|
+
},
|
|
1083
|
+
},
|
|
1084
|
+
body: {
|
|
1085
|
+
vertical_spacing: "4px",
|
|
1086
|
+
elements,
|
|
1087
|
+
},
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
renderDirectoryBrowserCard(view: DirectoryBrowserCardView): Record<string, unknown> {
|
|
1092
|
+
const elements: Record<string, unknown>[] = [
|
|
1093
|
+
markdown(`**当前目录**:${inlineCode(view.directory)}`),
|
|
1094
|
+
taskActionRow(view.currentActions),
|
|
1095
|
+
{ tag: "hr" },
|
|
1096
|
+
];
|
|
1097
|
+
const entryRows = view.entries.length === 0
|
|
1098
|
+
? [directoryBrowserEmptyRow()]
|
|
1099
|
+
: view.entries.slice(0, DIRECTORY_BROWSER_ROW_COUNT).map(directoryBrowserEntryRow);
|
|
1100
|
+
while (entryRows.length < DIRECTORY_BROWSER_ROW_COUNT) entryRows.push(directoryBrowserPlaceholderRow());
|
|
1101
|
+
elements.push(...entryRows);
|
|
1102
|
+
if (view.navigationActions.length > 0) {
|
|
1103
|
+
elements.push({ tag: "hr" }, taskActionRow(view.navigationActions));
|
|
1104
|
+
}
|
|
1105
|
+
if (view.footerLines.length > 0) {
|
|
1106
|
+
elements.push({ tag: "hr" }, markdown(view.footerLines.join("\n")));
|
|
1107
|
+
}
|
|
1108
|
+
return sectionCard("文件浏览", elements, "blue", "2px");
|
|
1109
|
+
}
|
|
1110
|
+
|
|
993
1111
|
renderResetHistoryCard(view: ResetHistoryCardView): Record<string, unknown> {
|
|
994
1112
|
const elements: Record<string, unknown>[] = [
|
|
995
1113
|
{
|
|
@@ -1124,6 +1242,77 @@ function renderTurnElements(
|
|
|
1124
1242
|
return elements;
|
|
1125
1243
|
}
|
|
1126
1244
|
|
|
1245
|
+
function renderGroupedTurnElements(
|
|
1246
|
+
state: TurnViewState,
|
|
1247
|
+
assistantTextMode: "hidden" | "always",
|
|
1248
|
+
): Record<string, unknown>[] {
|
|
1249
|
+
const elements: Record<string, unknown>[] = [];
|
|
1250
|
+
const allActivities = turnActivities(state);
|
|
1251
|
+
const livePages = groupedActivityPages(allActivities, state.projectCwd);
|
|
1252
|
+
const historyPages = groupedActivityPages(allActivities, state.projectCwd, {
|
|
1253
|
+
fullActivityText: true,
|
|
1254
|
+
includeReasoningHistory: true,
|
|
1255
|
+
});
|
|
1256
|
+
const visibleGroups = livePages.at(-1) ?? [];
|
|
1257
|
+
if (state.plan.length > 0) elements.push(planPanel(state.plan));
|
|
1258
|
+
if (historyPages.length > 1) {
|
|
1259
|
+
elements.push(taskActionRow([{
|
|
1260
|
+
text: `查看历史思考(共 ${historyPages.length} 页)`,
|
|
1261
|
+
value: {
|
|
1262
|
+
action: "activity_history",
|
|
1263
|
+
turnId: state.turnId,
|
|
1264
|
+
page: String(historyPages.length - 2),
|
|
1265
|
+
},
|
|
1266
|
+
}]));
|
|
1267
|
+
}
|
|
1268
|
+
if (state.activitiesTruncated || livePages.length > 1 || historyPages.length > 1) elements.push(markdown("…"));
|
|
1269
|
+
elements.push(...renderGroupedActivityGroups(visibleGroups, state.projectCwd));
|
|
1270
|
+
if (state.fileSummary.length > 0) elements.push(fileSummaryPanel(state));
|
|
1271
|
+
|
|
1272
|
+
if (state.approval) {
|
|
1273
|
+
const request = state.approval;
|
|
1274
|
+
elements.push(markdown([
|
|
1275
|
+
`**${request.title}**`,
|
|
1276
|
+
request.command ? codeBlock(request.command, 800) : undefined,
|
|
1277
|
+
request.reason,
|
|
1278
|
+
].filter(Boolean).join("\n")));
|
|
1279
|
+
elements.push({
|
|
1280
|
+
tag: "column_set",
|
|
1281
|
+
flex_mode: "flow",
|
|
1282
|
+
horizontal_spacing: "8px",
|
|
1283
|
+
vertical_spacing: "8px",
|
|
1284
|
+
columns: request.options.map((option) => ({
|
|
1285
|
+
tag: "column",
|
|
1286
|
+
width: "auto",
|
|
1287
|
+
elements: [{
|
|
1288
|
+
tag: "button",
|
|
1289
|
+
text: { tag: "plain_text", content: approvalDecisionLabel(option.id) },
|
|
1290
|
+
type: option.id === "accept" || option.id === "acceptForSession" ? "primary" : option.id === "cancel" ? "danger" : "default",
|
|
1291
|
+
behaviors: [{
|
|
1292
|
+
type: "callback",
|
|
1293
|
+
value: {
|
|
1294
|
+
action: "approval",
|
|
1295
|
+
sessionId: state.sessionId,
|
|
1296
|
+
turnId: state.turnId,
|
|
1297
|
+
requestId: request.id,
|
|
1298
|
+
decision: option.id,
|
|
1299
|
+
},
|
|
1300
|
+
}],
|
|
1301
|
+
}],
|
|
1302
|
+
})),
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
if (state.error) elements.push(markdown(codeBlock(state.error, 2_000)));
|
|
1306
|
+
const showAssistantText = state.assistantText && assistantTextMode === "always";
|
|
1307
|
+
if (showAssistantText) {
|
|
1308
|
+
if (elements.length > 0) elements.push({ tag: "hr" });
|
|
1309
|
+
const heading = state.status === "completed" ? "回答" : "回答生成中";
|
|
1310
|
+
elements.push(markdown(`**${heading}**\n${truncateText(state.assistantText, 3_000)}`));
|
|
1311
|
+
}
|
|
1312
|
+
if (elements.length === 0) elements.push(markdown(emptyTurnText(state.status, state.agentLabel)));
|
|
1313
|
+
return elements;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1127
1316
|
function emptyTurnText(status: TurnViewStatus, agentLabel?: string): string {
|
|
1128
1317
|
const label = agentLabel?.trim().replace(/[\r\n]+/g, " ") || "Agent";
|
|
1129
1318
|
if (status === "starting") return `正在连接 ${label}…`;
|
|
@@ -1216,6 +1405,136 @@ function renderActivities(
|
|
|
1216
1405
|
return elements;
|
|
1217
1406
|
}
|
|
1218
1407
|
|
|
1408
|
+
type GroupedTurnActivity =
|
|
1409
|
+
| { kind: "activity"; activity: TurnActivity }
|
|
1410
|
+
| {
|
|
1411
|
+
kind: "execution";
|
|
1412
|
+
id: string;
|
|
1413
|
+
latestReasoning?: Extract<TurnActivity, { kind: "reasoning" }>;
|
|
1414
|
+
reasonings: Array<Extract<TurnActivity, { kind: "reasoning" }>>;
|
|
1415
|
+
tools: ToolState[];
|
|
1416
|
+
};
|
|
1417
|
+
|
|
1418
|
+
function renderGroupedActivityGroups(
|
|
1419
|
+
groups: GroupedTurnActivity[],
|
|
1420
|
+
projectCwd?: string,
|
|
1421
|
+
options: {
|
|
1422
|
+
fullActivityText?: boolean;
|
|
1423
|
+
includeReasoningHistory?: boolean;
|
|
1424
|
+
} = {},
|
|
1425
|
+
): Record<string, unknown>[] {
|
|
1426
|
+
return groups.flatMap((group) => {
|
|
1427
|
+
if (group.kind === "activity") {
|
|
1428
|
+
return renderActivity(group.activity, projectCwd, options.fullActivityText === true);
|
|
1429
|
+
}
|
|
1430
|
+
if (group.tools.length === 0) {
|
|
1431
|
+
const reasonings = options.includeReasoningHistory
|
|
1432
|
+
? group.reasonings
|
|
1433
|
+
: group.latestReasoning ? [group.latestReasoning] : [];
|
|
1434
|
+
return renderReasoningGroup(reasonings);
|
|
1435
|
+
}
|
|
1436
|
+
return [executionActivityPanel(group, projectCwd, options.includeReasoningHistory === true)];
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
function groupTurnActivities(activities: TurnActivity[]): GroupedTurnActivity[] {
|
|
1441
|
+
const groups: GroupedTurnActivity[] = [];
|
|
1442
|
+
let segment: TurnActivity[] = [];
|
|
1443
|
+
const flushSegment = (): void => {
|
|
1444
|
+
if (segment.length === 0) return;
|
|
1445
|
+
let execution: Extract<GroupedTurnActivity, { kind: "execution" }> | undefined;
|
|
1446
|
+
let executionPosition = -1;
|
|
1447
|
+
for (let index = 0; index < segment.length; index += 1) {
|
|
1448
|
+
const activity = segment[index]!;
|
|
1449
|
+
if (isRawReasoning(activity)) {
|
|
1450
|
+
execution ??= { kind: "execution", id: activity.id, reasonings: [], tools: [] };
|
|
1451
|
+
execution.reasonings.push(activity);
|
|
1452
|
+
execution.latestReasoning = activity;
|
|
1453
|
+
executionPosition = index;
|
|
1454
|
+
} else if (activity.kind === "tool") {
|
|
1455
|
+
execution ??= { kind: "execution", id: activity.id, reasonings: [], tools: [] };
|
|
1456
|
+
execution.tools.push(activity.tool);
|
|
1457
|
+
executionPosition = index;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
for (let index = 0; index < segment.length; index += 1) {
|
|
1461
|
+
const activity = segment[index]!;
|
|
1462
|
+
if (index === executionPosition && execution) groups.push(execution);
|
|
1463
|
+
if (!isRawReasoning(activity) && activity.kind !== "tool") {
|
|
1464
|
+
groups.push({ kind: "activity", activity });
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
segment = [];
|
|
1468
|
+
};
|
|
1469
|
+
|
|
1470
|
+
for (const activity of activities) {
|
|
1471
|
+
if (!isCommentaryActivity(activity)) {
|
|
1472
|
+
segment.push(activity);
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
flushSegment();
|
|
1476
|
+
groups.push({ kind: "activity", activity });
|
|
1477
|
+
}
|
|
1478
|
+
flushSegment();
|
|
1479
|
+
return groups;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
function isCommentaryActivity(activity: TurnActivity): boolean {
|
|
1483
|
+
return activity.kind === "assistant"
|
|
1484
|
+
|| (activity.kind === "reasoning" && activity.id.startsWith("commentary:"));
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function executionActivityPanel(
|
|
1488
|
+
group: Extract<GroupedTurnActivity, { kind: "execution" }>,
|
|
1489
|
+
projectCwd: string | undefined,
|
|
1490
|
+
includeReasoningHistory = false,
|
|
1491
|
+
): Record<string, unknown> {
|
|
1492
|
+
const status = executionActivityStatus(group.tools);
|
|
1493
|
+
const elements = [
|
|
1494
|
+
...(includeReasoningHistory ? renderReasoningGroup(group.reasonings) : []),
|
|
1495
|
+
...group.tools.map((tool) => toolPanel(tool, projectCwd)),
|
|
1496
|
+
];
|
|
1497
|
+
return collapsiblePanel(
|
|
1498
|
+
executionActivityTitle(group, status),
|
|
1499
|
+
elements,
|
|
1500
|
+
{
|
|
1501
|
+
elementId: executionActivityPanelElementId(group.id),
|
|
1502
|
+
expanded: false,
|
|
1503
|
+
borderColor: status === "failed" ? "red" : status === "running" ? "blue" : "grey",
|
|
1504
|
+
compact: true,
|
|
1505
|
+
},
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
function executionActivityStatus(tools: ToolState[]): ToolState["status"] {
|
|
1510
|
+
if (tools.some((tool) => tool.status === "running")) return "running";
|
|
1511
|
+
if (tools.some((tool) => tool.status === "failed")) return "failed";
|
|
1512
|
+
return "completed";
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
function executionActivityTitle(
|
|
1516
|
+
group: Extract<GroupedTurnActivity, { kind: "execution" }>,
|
|
1517
|
+
status: ToolState["status"],
|
|
1518
|
+
): string {
|
|
1519
|
+
const icon = status === "running" ? "⏳" : status === "failed" ? "❌" : "💭";
|
|
1520
|
+
const latestReasoning = group.latestReasoning?.text
|
|
1521
|
+
? removeMarkdownBold(group.latestReasoning.text).replace(/\s+/g, " ").trim()
|
|
1522
|
+
: "";
|
|
1523
|
+
const summary = latestReasoning
|
|
1524
|
+
? truncateText(latestReasoning, 90)
|
|
1525
|
+
: status === "running"
|
|
1526
|
+
? "正在执行工具"
|
|
1527
|
+
: status === "failed"
|
|
1528
|
+
? "工具执行失败"
|
|
1529
|
+
: "已执行工具";
|
|
1530
|
+
return `${icon} ${summary} · ${group.tools.length} 个工具`;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
function executionActivityPanelElementId(activityId: string): string {
|
|
1534
|
+
const digest = createHash("sha256").update(activityId).digest("hex").slice(0, 10);
|
|
1535
|
+
return `turn_exec_${digest}`;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1219
1538
|
function renderReasoningGroup(
|
|
1220
1539
|
activities: Array<Extract<TurnActivity, { kind: "reasoning" }>>,
|
|
1221
1540
|
): Record<string, unknown>[] {
|
|
@@ -1237,6 +1556,9 @@ function isRawReasoning(
|
|
|
1237
1556
|
}
|
|
1238
1557
|
|
|
1239
1558
|
const ACTIVITIES_PER_PAGE = 40;
|
|
1559
|
+
const GROUPED_TOOLS_PER_PANEL = 8;
|
|
1560
|
+
const GROUPED_PAGE_ACTIVITY_BYTES = 24 * 1024;
|
|
1561
|
+
const GROUPED_PAGE_ACTIVITY_COMPONENTS = 160;
|
|
1240
1562
|
const MAX_LIVE_ASSISTANT_TEXT = 2_000;
|
|
1241
1563
|
const ACTIVITY_TEXT_CHUNK = 2_500;
|
|
1242
1564
|
|
|
@@ -1252,6 +1574,72 @@ function activityPages(activities: TurnActivity[]): TurnActivity[][] {
|
|
|
1252
1574
|
return pages;
|
|
1253
1575
|
}
|
|
1254
1576
|
|
|
1577
|
+
function groupedActivityPages(
|
|
1578
|
+
activities: TurnActivity[],
|
|
1579
|
+
projectCwd?: string,
|
|
1580
|
+
renderOptions: {
|
|
1581
|
+
fullActivityText?: boolean;
|
|
1582
|
+
includeReasoningHistory?: boolean;
|
|
1583
|
+
} = {},
|
|
1584
|
+
): GroupedTurnActivity[][] {
|
|
1585
|
+
const groups = groupTurnActivities(activities).flatMap(splitGroupedExecutionActivity);
|
|
1586
|
+
if (groups.length === 0) return [];
|
|
1587
|
+
|
|
1588
|
+
const newestFirst: GroupedTurnActivity[][] = [];
|
|
1589
|
+
let page: GroupedTurnActivity[] = [];
|
|
1590
|
+
for (let index = groups.length - 1; index >= 0; index -= 1) {
|
|
1591
|
+
const group = groups[index]!;
|
|
1592
|
+
const candidate = [group, ...page];
|
|
1593
|
+
if (page.length > 0 && !groupedActivityPageFits(candidate, projectCwd, renderOptions)) {
|
|
1594
|
+
newestFirst.push(page);
|
|
1595
|
+
page = [];
|
|
1596
|
+
}
|
|
1597
|
+
page.unshift(group);
|
|
1598
|
+
}
|
|
1599
|
+
if (page.length > 0) newestFirst.push(page);
|
|
1600
|
+
return newestFirst.reverse();
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function groupedActivityPageFits(
|
|
1604
|
+
groups: GroupedTurnActivity[],
|
|
1605
|
+
projectCwd: string | undefined,
|
|
1606
|
+
renderOptions: {
|
|
1607
|
+
fullActivityText?: boolean;
|
|
1608
|
+
includeReasoningHistory?: boolean;
|
|
1609
|
+
},
|
|
1610
|
+
): boolean {
|
|
1611
|
+
const elements = renderGroupedActivityGroups(groups, projectCwd, renderOptions);
|
|
1612
|
+
return Buffer.byteLength(JSON.stringify(elements), "utf8") <= GROUPED_PAGE_ACTIVITY_BYTES
|
|
1613
|
+
&& countCardComponents(elements) <= GROUPED_PAGE_ACTIVITY_COMPONENTS;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
function countCardComponents(value: unknown): number {
|
|
1617
|
+
if (Array.isArray(value)) {
|
|
1618
|
+
return value.reduce((total, entry) => total + countCardComponents(entry), 0);
|
|
1619
|
+
}
|
|
1620
|
+
if (!value || typeof value !== "object") return 0;
|
|
1621
|
+
const record = value as Record<string, unknown>;
|
|
1622
|
+
return (typeof record.tag === "string" ? 1 : 0)
|
|
1623
|
+
+ Object.values(record).reduce<number>((total, entry) => total + countCardComponents(entry), 0);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function splitGroupedExecutionActivity(group: GroupedTurnActivity): GroupedTurnActivity[] {
|
|
1627
|
+
if (group.kind !== "execution" || group.tools.length <= GROUPED_TOOLS_PER_PANEL) return [group];
|
|
1628
|
+
const chunks: Array<Extract<GroupedTurnActivity, { kind: "execution" }>> = [];
|
|
1629
|
+
for (let index = 0; index < group.tools.length; index += GROUPED_TOOLS_PER_PANEL) {
|
|
1630
|
+
const tools = group.tools.slice(index, index + GROUPED_TOOLS_PER_PANEL);
|
|
1631
|
+
const isLast = index + GROUPED_TOOLS_PER_PANEL >= group.tools.length;
|
|
1632
|
+
chunks.push({
|
|
1633
|
+
kind: "execution",
|
|
1634
|
+
id: index === 0 ? group.id : tools[0]?.id ?? `${group.id}:${index}`,
|
|
1635
|
+
...(isLast && group.latestReasoning ? { latestReasoning: group.latestReasoning } : {}),
|
|
1636
|
+
reasonings: isLast ? group.reasonings : [],
|
|
1637
|
+
tools,
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
return chunks;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1255
1643
|
function splitText(value: string, maxLength: number): string[] {
|
|
1256
1644
|
if (!value) return [];
|
|
1257
1645
|
const chunks: string[] = [];
|
|
@@ -1302,20 +1690,26 @@ function toolPanelElementId(toolId: string): string {
|
|
|
1302
1690
|
function collapsiblePanel(
|
|
1303
1691
|
title: string,
|
|
1304
1692
|
content: string | Record<string, unknown>[],
|
|
1305
|
-
options: {
|
|
1693
|
+
options: {
|
|
1694
|
+
expanded?: boolean;
|
|
1695
|
+
borderColor?: string;
|
|
1696
|
+
elementId?: string;
|
|
1697
|
+
compact?: boolean;
|
|
1698
|
+
} = {},
|
|
1306
1699
|
): Record<string, unknown> {
|
|
1700
|
+
const compact = options.compact === true;
|
|
1307
1701
|
return {
|
|
1308
1702
|
tag: "collapsible_panel",
|
|
1309
1703
|
...(options.elementId ? { element_id: options.elementId } : {}),
|
|
1310
1704
|
direction: "vertical",
|
|
1311
|
-
vertical_spacing: "4px",
|
|
1312
|
-
padding: "8px",
|
|
1705
|
+
vertical_spacing: compact ? "2px" : "4px",
|
|
1706
|
+
padding: compact ? "4px 6px" : "8px",
|
|
1313
1707
|
margin: "0px",
|
|
1314
1708
|
expanded: options.expanded ?? false,
|
|
1315
1709
|
header: {
|
|
1316
1710
|
title: { tag: "plain_text", content: title },
|
|
1317
1711
|
vertical_align: "center",
|
|
1318
|
-
padding: "4px 8px 4px 8px",
|
|
1712
|
+
padding: compact ? "2px 4px 2px 4px" : "4px 8px 4px 8px",
|
|
1319
1713
|
},
|
|
1320
1714
|
border: {
|
|
1321
1715
|
color: options.borderColor ?? "grey",
|
|
@@ -1325,6 +1719,68 @@ function collapsiblePanel(
|
|
|
1325
1719
|
};
|
|
1326
1720
|
}
|
|
1327
1721
|
|
|
1722
|
+
function sessionTaskPanel(entry: SessionTaskCardEntry): Record<string, unknown> {
|
|
1723
|
+
const elements: Record<string, unknown>[] = [markdown(entry.detailLines.join("\n"))];
|
|
1724
|
+
if (entry.actions?.length) elements.push(sessionActionOverflow(entry.actions));
|
|
1725
|
+
return collapsiblePanel(entry.summary, elements, {
|
|
1726
|
+
elementId: sessionTaskPanelElementId(entry.reference),
|
|
1727
|
+
borderColor: entry.current ? "green" : "grey",
|
|
1728
|
+
compact: true,
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
function sessionProjectRow(group: SessionTaskCardGroup): Record<string, unknown> {
|
|
1733
|
+
return {
|
|
1734
|
+
tag: "column_set",
|
|
1735
|
+
flex_mode: "none",
|
|
1736
|
+
horizontal_spacing: "8px",
|
|
1737
|
+
vertical_align: "center",
|
|
1738
|
+
margin: "6px 0px 0px 0px",
|
|
1739
|
+
columns: [
|
|
1740
|
+
{
|
|
1741
|
+
tag: "column",
|
|
1742
|
+
width: "weighted",
|
|
1743
|
+
weight: 1,
|
|
1744
|
+
vertical_align: "center",
|
|
1745
|
+
elements: [markdown(`**${escapeCardActionText(group.title)}**`)],
|
|
1746
|
+
},
|
|
1747
|
+
...(group.actions ?? []).map((action) => ({
|
|
1748
|
+
tag: "column",
|
|
1749
|
+
width: "auto",
|
|
1750
|
+
vertical_align: "center",
|
|
1751
|
+
elements: [sessionActionButton(action)],
|
|
1752
|
+
})),
|
|
1753
|
+
],
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
function sessionTaskPanelElementId(reference: string): string {
|
|
1758
|
+
return `session_task_${createHash("sha256").update(reference).digest("hex").slice(0, 16)}`;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
function sessionActionOverflow(actions: TaskListCardAction[]): Record<string, unknown> {
|
|
1762
|
+
return {
|
|
1763
|
+
tag: "overflow",
|
|
1764
|
+
options: actions.map((action) => ({
|
|
1765
|
+
text: { tag: "plain_text", content: action.text },
|
|
1766
|
+
value: JSON.stringify(action.value),
|
|
1767
|
+
})),
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
function sessionActionButton(action: TaskListCardAction): Record<string, unknown> {
|
|
1772
|
+
return {
|
|
1773
|
+
tag: "button",
|
|
1774
|
+
text: { tag: "plain_text", content: action.text },
|
|
1775
|
+
type: action.type === "danger" ? "danger" : "default",
|
|
1776
|
+
size: "tiny",
|
|
1777
|
+
behaviors: [{
|
|
1778
|
+
type: "callback",
|
|
1779
|
+
value: action.value,
|
|
1780
|
+
}],
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1328
1784
|
function renderToolDetails(tool: ToolState, projectCwd?: string): string {
|
|
1329
1785
|
const command = tool.command ?? tool.title;
|
|
1330
1786
|
const fileSummary = tool.files?.length
|
|
@@ -1726,6 +2182,69 @@ function taskActionRow(actions: TaskListCardAction[]): Record<string, unknown> {
|
|
|
1726
2182
|
};
|
|
1727
2183
|
}
|
|
1728
2184
|
|
|
2185
|
+
function directoryBrowserEntryRow(entry: DirectoryBrowserCardEntry): Record<string, unknown> {
|
|
2186
|
+
const label = `${directoryBrowserEntryIcon(entry.kind)} ${entry.name}`;
|
|
2187
|
+
return {
|
|
2188
|
+
tag: "column_set",
|
|
2189
|
+
flex_mode: "none",
|
|
2190
|
+
horizontal_spacing: "12px",
|
|
2191
|
+
vertical_align: "center",
|
|
2192
|
+
margin: "0px",
|
|
2193
|
+
columns: [
|
|
2194
|
+
{
|
|
2195
|
+
tag: "column",
|
|
2196
|
+
width: "weighted",
|
|
2197
|
+
weight: 1,
|
|
2198
|
+
vertical_align: "center",
|
|
2199
|
+
elements: entry.openAction
|
|
2200
|
+
? [taskActionElement({ ...entry.openAction, text: label })]
|
|
2201
|
+
: [markdown(escapeCardHtml(label))],
|
|
2202
|
+
},
|
|
2203
|
+
...(entry.actions ?? []).map((action) => ({
|
|
2204
|
+
tag: "column",
|
|
2205
|
+
width: "auto",
|
|
2206
|
+
vertical_align: "center",
|
|
2207
|
+
elements: [taskActionElement(action)],
|
|
2208
|
+
})),
|
|
2209
|
+
],
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
function directoryBrowserEmptyRow(): Record<string, unknown> {
|
|
2214
|
+
return directoryBrowserStaticRow("这个目录为空。");
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
function directoryBrowserPlaceholderRow(): Record<string, unknown> {
|
|
2218
|
+
return directoryBrowserStaticRow("\u00a0");
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
function directoryBrowserStaticRow(content: string): Record<string, unknown> {
|
|
2222
|
+
return {
|
|
2223
|
+
tag: "column_set",
|
|
2224
|
+
flex_mode: "none",
|
|
2225
|
+
horizontal_spacing: "12px",
|
|
2226
|
+
vertical_align: "center",
|
|
2227
|
+
margin: "0px",
|
|
2228
|
+
columns: [{
|
|
2229
|
+
tag: "column",
|
|
2230
|
+
width: "weighted",
|
|
2231
|
+
weight: 1,
|
|
2232
|
+
vertical_align: "center",
|
|
2233
|
+
elements: [markdown(content)],
|
|
2234
|
+
}],
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
function directoryBrowserEntryIcon(kind: DirectoryBrowserCardEntry["kind"]): string {
|
|
2239
|
+
switch (kind) {
|
|
2240
|
+
case "directory": return "📁";
|
|
2241
|
+
case "drive": return "💽";
|
|
2242
|
+
case "image": return "🖼️";
|
|
2243
|
+
case "binary": return "📦";
|
|
2244
|
+
case "file": return "📄";
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
1729
2248
|
function taskActionElement(action: TaskListCardAction): Record<string, unknown> {
|
|
1730
2249
|
return {
|
|
1731
2250
|
tag: "interactive_container",
|
|
@@ -1733,7 +2252,7 @@ function taskActionElement(action: TaskListCardAction): Record<string, unknown>
|
|
|
1733
2252
|
padding: "0px",
|
|
1734
2253
|
has_border: false,
|
|
1735
2254
|
elements: [markdown(
|
|
1736
|
-
`<font color='${action.type === "danger" ? "red" : "blue"}'>${
|
|
2255
|
+
`<font color='${action.type === "danger" ? "red" : "blue"}'>${escapeCardActionText(action.text)}</font>`,
|
|
1737
2256
|
)],
|
|
1738
2257
|
behaviors: [{
|
|
1739
2258
|
type: "callback",
|
|
@@ -1742,6 +2261,10 @@ function taskActionElement(action: TaskListCardAction): Record<string, unknown>
|
|
|
1742
2261
|
};
|
|
1743
2262
|
}
|
|
1744
2263
|
|
|
2264
|
+
function escapeCardActionText(value: string): string {
|
|
2265
|
+
return escapeCardHtml(value).replaceAll("\\", "\");
|
|
2266
|
+
}
|
|
2267
|
+
|
|
1745
2268
|
function helpCommandRow(command: HelpCardCommand): Record<string, unknown> {
|
|
1746
2269
|
const details = [
|
|
1747
2270
|
command.usage ? `**${command.usage}**` : undefined,
|
|
@@ -1812,6 +2335,7 @@ function sectionCard(
|
|
|
1812
2335
|
title: string,
|
|
1813
2336
|
elements: Record<string, unknown>[],
|
|
1814
2337
|
template = "blue",
|
|
2338
|
+
verticalSpacing = "8px",
|
|
1815
2339
|
): Record<string, unknown> {
|
|
1816
2340
|
return {
|
|
1817
2341
|
schema: "2.0",
|
|
@@ -1829,7 +2353,7 @@ function sectionCard(
|
|
|
1829
2353
|
},
|
|
1830
2354
|
body: {
|
|
1831
2355
|
direction: "vertical",
|
|
1832
|
-
vertical_spacing:
|
|
2356
|
+
vertical_spacing: verticalSpacing,
|
|
1833
2357
|
padding: "12px 12px 12px 12px",
|
|
1834
2358
|
elements,
|
|
1835
2359
|
},
|