@keyou007/agent-bot 0.1.14-alpha.1 → 0.1.14-alpha.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/CHANGELOG.md +19 -1
- package/README.md +48 -15
- package/README.zh.md +48 -15
- package/config.example.yaml +1 -1
- package/dist/cli/AutostartManager.js +431 -0
- package/dist/cli/AutostartManager.js.map +1 -0
- package/dist/cli/FeishuAppConfiguration.js +21 -13
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/Initializer.js +35 -0
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/SelfUpdateRunner.js +552 -0
- package/dist/cli/SelfUpdateRunner.js.map +1 -0
- package/dist/cli/SelfUpdater.js +431 -0
- package/dist/cli/SelfUpdater.js.map +1 -0
- package/dist/cli/autostartOutput.js +63 -0
- package/dist/cli/autostartOutput.js.map +1 -0
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/help.js +94 -18
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/taskGroupOptions.js +14 -11
- package/dist/cli/taskGroupOptions.js.map +1 -1
- package/dist/cli/taskListOutput.js +3 -0
- package/dist/cli/taskListOutput.js.map +1 -1
- package/dist/cli/taskTarget.js +99 -0
- package/dist/cli/taskTarget.js.map +1 -0
- package/dist/cli.js +661 -58
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexRuntime.js +8 -2
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +16 -1
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +266 -38
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuMarkdown.js +25 -1
- package/dist/feishu/FeishuMarkdown.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +16 -0
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +42 -6
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/index.js +136 -2
- package/dist/index.js.map +1 -1
- package/dist/presentation/OutboundRouter.js +7 -0
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +21 -7
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +537 -13
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +53 -0
- package/dist/state/StateStore.js.map +1 -1
- package/docs/technical-reference.md +17 -7
- package/docs/technical-reference.zh.md +17 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
- package/skills/agent-bot/SKILL.md +109 -188
- package/src/cli/AutostartManager.ts +579 -0
- package/src/cli/FeishuAppConfiguration.ts +26 -7
- package/src/cli/Initializer.ts +57 -0
- package/src/cli/SelfUpdateRunner.ts +817 -0
- package/src/cli/SelfUpdater.ts +688 -0
- package/src/cli/autostartOutput.ts +75 -0
- package/src/cli/controlProtocol.ts +119 -1
- package/src/cli/help.ts +94 -18
- package/src/cli/taskGroupOptions.ts +27 -17
- package/src/cli/taskListOutput.ts +10 -0
- package/src/cli/taskTarget.ts +168 -0
- package/src/cli.ts +809 -65
- package/src/codex/CodexRuntime.ts +8 -2
- package/src/commands/CommandRouter.ts +14 -1
- package/src/commands/commandTypes.ts +2 -0
- package/src/feishu/CardRenderer.ts +313 -35
- package/src/feishu/FeishuMarkdown.ts +27 -1
- package/src/feishu/FeishuMessageClient.ts +33 -0
- package/src/feishu/FeishuTurnPresenter.ts +43 -6
- package/src/feishu/types.ts +1 -0
- package/src/index.ts +174 -2
- package/src/presentation/OutboundRouter.ts +7 -0
- package/src/presentation/TurnStateReducer.ts +21 -7
- package/src/proxy/ProxySessionController.ts +661 -16
- package/src/runtime/types.ts +1 -0
- package/src/state/StateStore.ts +56 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { truncateText } from "../utils/markdown.js";
|
|
4
4
|
import { localCardImage } from "./LocalCardImage.js";
|
|
5
5
|
const DIRECTORY_BROWSER_ROW_COUNT = 16;
|
|
6
6
|
export class CardRenderer {
|
|
@@ -604,18 +604,24 @@ export class CardRenderer {
|
|
|
604
604
|
const elements = this.thinkingCardLayout === "timeline"
|
|
605
605
|
? renderTurnElements(state, "hidden")
|
|
606
606
|
: renderGroupedTurnElements(state, "hidden");
|
|
607
|
+
const footerActions = [];
|
|
607
608
|
if (isTurnStoppable(state.status)) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
609
|
+
footerActions.push({
|
|
610
|
+
text: "Stop",
|
|
611
|
+
type: "danger",
|
|
612
|
+
value: { action: "turn_cancel", sessionId: state.sessionId, turnId: state.turnId },
|
|
613
|
+
});
|
|
614
|
+
elements.push({ tag: "hr" }, taskActionRow(footerActions));
|
|
613
615
|
}
|
|
614
616
|
else if (state.status === "completed") {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
617
|
+
footerActions.push({
|
|
618
|
+
text: "Reset",
|
|
619
|
+
value: { action: "turn_reset", sessionId: state.sessionId, turnId: state.turnId },
|
|
620
|
+
});
|
|
621
|
+
elements.push({ tag: "hr" }, taskActionRow(footerActions));
|
|
622
|
+
}
|
|
623
|
+
if (state.status === "completed") {
|
|
624
|
+
elements.push({
|
|
619
625
|
...markdown("<font color='grey'>Reset 会将当前任务的对话上下文恢复到本轮完成时;不会回退本地文件。</font>"),
|
|
620
626
|
text_size: "notation",
|
|
621
627
|
});
|
|
@@ -630,15 +636,22 @@ export class CardRenderer {
|
|
|
630
636
|
}
|
|
631
637
|
renderActivityHistory(state, requestedPage) {
|
|
632
638
|
const grouped = this.thinkingCardLayout === "grouped";
|
|
639
|
+
const activities = turnActivities(state);
|
|
640
|
+
const allGroups = grouped ? groupTurnActivities(activities).flatMap(splitGroupedExecutionActivity) : [];
|
|
641
|
+
const historyGroups = grouped
|
|
642
|
+
? groupedLiveActivityPage(groupedActivityPagesFromGroups(allGroups, state.projectCwd), state.projectCwd).historyGroups
|
|
643
|
+
: [];
|
|
644
|
+
const usesHistoricalPrefixPages = historyGroups.length > 0;
|
|
633
645
|
const pages = grouped
|
|
634
|
-
?
|
|
646
|
+
? groupedActivityPagesFromGroups(usesHistoricalPrefixPages ? historyGroups : allGroups, state.projectCwd, {
|
|
635
647
|
fullActivityText: true,
|
|
636
648
|
includeReasoningHistory: true,
|
|
637
649
|
})
|
|
638
|
-
: activityPages(
|
|
650
|
+
: activityPages(activities);
|
|
639
651
|
if (pages.length === 0) {
|
|
640
652
|
return this.renderSectionsCard("思考活动历史", [{ lines: ["没有保存到活动记录。"] }]);
|
|
641
653
|
}
|
|
654
|
+
const totalPages = usesHistoricalPrefixPages ? pages.length + 1 : pages.length;
|
|
642
655
|
const page = Math.max(0, Math.min(Math.trunc(requestedPage), pages.length - 1));
|
|
643
656
|
const actions = [
|
|
644
657
|
...(pages.length > 1 ? [{
|
|
@@ -649,7 +662,7 @@ export class CardRenderer {
|
|
|
649
662
|
text: "上一页",
|
|
650
663
|
value: { action: "activity_history", turnId: state.turnId, page: String(page - 1) },
|
|
651
664
|
}] : []),
|
|
652
|
-
...(page < pages.length - 2 ? [{
|
|
665
|
+
...(page < pages.length - (usesHistoricalPrefixPages ? 1 : 2) ? [{
|
|
653
666
|
text: "下一页",
|
|
654
667
|
value: { action: "activity_history", turnId: state.turnId, page: String(page + 1) },
|
|
655
668
|
}] : []),
|
|
@@ -663,7 +676,7 @@ export class CardRenderer {
|
|
|
663
676
|
includeReasoningHistory: true,
|
|
664
677
|
})
|
|
665
678
|
: renderActivities(pages[page] ?? [], state.projectCwd, true)));
|
|
666
|
-
return sectionCard(`思考活动历史 · ${page + 1}/${
|
|
679
|
+
return sectionCard(`思考活动历史 · ${page + 1}/${totalPages}`, elements.length > 0 ? elements : [markdown("无")]);
|
|
667
680
|
}
|
|
668
681
|
renderSessionStarted(session) {
|
|
669
682
|
return this.baseCard("ACP 会话已创建", "green", [
|
|
@@ -701,6 +714,51 @@ export class CardRenderer {
|
|
|
701
714
|
renderStatus(status) {
|
|
702
715
|
return this.baseCard("Agent Bot 状态", "blue", [markdown(status)]);
|
|
703
716
|
}
|
|
717
|
+
renderDismissGroupConfirmation(view) {
|
|
718
|
+
const actionValue = {
|
|
719
|
+
contextKey: view.contextKey,
|
|
720
|
+
sessionId: view.sessionId,
|
|
721
|
+
requestedBy: view.requestedBy,
|
|
722
|
+
};
|
|
723
|
+
const button = (content, type, action) => ({
|
|
724
|
+
tag: "button",
|
|
725
|
+
width: "fill",
|
|
726
|
+
text: { tag: "plain_text", content },
|
|
727
|
+
type,
|
|
728
|
+
behaviors: [{
|
|
729
|
+
type: "callback",
|
|
730
|
+
value: { action, ...actionValue },
|
|
731
|
+
}],
|
|
732
|
+
});
|
|
733
|
+
return compactCard("解散当前群聊", "blue", [
|
|
734
|
+
markdown([
|
|
735
|
+
"确定要解散当前群聊吗?",
|
|
736
|
+
`当前任务「${escapeCardHtml(view.taskTitle)}」将同时归档。此操作无法撤销。`,
|
|
737
|
+
].join("\n")),
|
|
738
|
+
{
|
|
739
|
+
tag: "column_set",
|
|
740
|
+
flex_mode: "none",
|
|
741
|
+
horizontal_spacing: "8px",
|
|
742
|
+
columns: [
|
|
743
|
+
{
|
|
744
|
+
tag: "column",
|
|
745
|
+
width: "weighted",
|
|
746
|
+
weight: 1,
|
|
747
|
+
elements: [button("Dismiss", "danger", "group_dismiss_confirm")],
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
tag: "column",
|
|
751
|
+
width: "weighted",
|
|
752
|
+
weight: 1,
|
|
753
|
+
elements: [button("Keep", "default", "group_dismiss_keep")],
|
|
754
|
+
},
|
|
755
|
+
],
|
|
756
|
+
},
|
|
757
|
+
]);
|
|
758
|
+
}
|
|
759
|
+
renderDismissGroupKept() {
|
|
760
|
+
return compactCard("已保留当前群聊", "grey", [markdown("已取消解散群聊。")]);
|
|
761
|
+
}
|
|
704
762
|
renderSectionsCard(title, sections, actions = []) {
|
|
705
763
|
const elements = [];
|
|
706
764
|
sections.forEach((section, index) => {
|
|
@@ -868,16 +926,19 @@ export class CardRenderer {
|
|
|
868
926
|
}
|
|
869
927
|
}
|
|
870
928
|
function renderTurnSubtitle(state) {
|
|
871
|
-
const elapsed = state.durationMs ?? Math.max(0, Date.now() - state.startedAt);
|
|
872
929
|
const activityTools = turnActivities(state).filter((activity) => activity.kind === "tool").length;
|
|
873
930
|
const totalTools = state.totalToolCount ?? activityTools;
|
|
874
931
|
return [
|
|
875
|
-
`耗时 ${
|
|
932
|
+
`耗时 ${renderTurnDuration(state)}`,
|
|
876
933
|
state.totalTokens !== undefined ? `${formatTokenCount(state.totalTokens)} tokens` : undefined,
|
|
877
934
|
totalTools > 0 ? `${totalTools} 个工具` : undefined,
|
|
878
935
|
state.fileSummary.length > 0 ? `${state.fileSummary.length} 个文件` : undefined,
|
|
879
936
|
].filter(Boolean).join(" · ");
|
|
880
937
|
}
|
|
938
|
+
function renderTurnDuration(state) {
|
|
939
|
+
const endedAt = state.completedAt ?? Date.now();
|
|
940
|
+
return formatTurnDuration(state.durationMs ?? Math.max(0, endedAt - state.startedAt));
|
|
941
|
+
}
|
|
881
942
|
function isTurnStoppable(status) {
|
|
882
943
|
return status === "running" || status === "tool_running" || status === "waiting_for_approval";
|
|
883
944
|
}
|
|
@@ -953,26 +1014,26 @@ function renderGroupedTurnElements(state, assistantTextMode) {
|
|
|
953
1014
|
const elements = [];
|
|
954
1015
|
const allActivities = turnActivities(state);
|
|
955
1016
|
const livePages = groupedActivityPages(allActivities, state.projectCwd);
|
|
956
|
-
const
|
|
1017
|
+
const visible = groupedLiveActivityPage(livePages, state.projectCwd);
|
|
1018
|
+
const historyPages = groupedActivityPagesFromGroups(visible.historyGroups, state.projectCwd, {
|
|
957
1019
|
fullActivityText: true,
|
|
958
1020
|
includeReasoningHistory: true,
|
|
959
1021
|
});
|
|
960
|
-
const visibleGroups = livePages.at(-1) ?? [];
|
|
961
1022
|
if (state.plan.length > 0)
|
|
962
1023
|
elements.push(planPanel(state.plan));
|
|
963
|
-
if (historyPages.length >
|
|
1024
|
+
if (historyPages.length > 0) {
|
|
964
1025
|
elements.push(taskActionRow([{
|
|
965
|
-
text: `查看历史思考(共 ${historyPages.length} 页)`,
|
|
1026
|
+
text: `查看历史思考(共 ${historyPages.length + 1} 页)`,
|
|
966
1027
|
value: {
|
|
967
1028
|
action: "activity_history",
|
|
968
1029
|
turnId: state.turnId,
|
|
969
|
-
page: String(historyPages.length -
|
|
1030
|
+
page: String(historyPages.length - 1),
|
|
970
1031
|
},
|
|
971
1032
|
}]));
|
|
972
1033
|
}
|
|
973
|
-
if (state.activitiesTruncated
|
|
1034
|
+
if (state.activitiesTruncated && visible.groups[0]?.kind !== "gap")
|
|
974
1035
|
elements.push(markdown("…"));
|
|
975
|
-
elements.push(...renderGroupedActivityGroups(
|
|
1036
|
+
elements.push(...renderGroupedActivityGroups(visible.groups, state.projectCwd));
|
|
976
1037
|
if (state.fileSummary.length > 0)
|
|
977
1038
|
elements.push(fileSummaryPanel(state));
|
|
978
1039
|
if (state.approval) {
|
|
@@ -1101,6 +1162,8 @@ function renderActivities(activities, projectCwd, fullAssistantText = false) {
|
|
|
1101
1162
|
}
|
|
1102
1163
|
function renderGroupedActivityGroups(groups, projectCwd, options = {}) {
|
|
1103
1164
|
return groups.flatMap((group) => {
|
|
1165
|
+
if (group.kind === "gap")
|
|
1166
|
+
return [markdown("…")];
|
|
1104
1167
|
if (group.kind === "activity") {
|
|
1105
1168
|
return renderActivity(group.activity, projectCwd, options.fullActivityText === true);
|
|
1106
1169
|
}
|
|
@@ -1169,19 +1232,17 @@ function executionActivityPanel(group, projectCwd, includeReasoningHistory = fal
|
|
|
1169
1232
|
return collapsiblePanel(executionActivityTitle(group, status), elements, {
|
|
1170
1233
|
elementId: executionActivityPanelElementId(group.id),
|
|
1171
1234
|
expanded: false,
|
|
1172
|
-
borderColor: status === "
|
|
1235
|
+
borderColor: status === "running" ? "blue" : "grey",
|
|
1173
1236
|
compact: true,
|
|
1174
1237
|
});
|
|
1175
1238
|
}
|
|
1176
1239
|
function executionActivityStatus(tools) {
|
|
1177
1240
|
if (tools.some((tool) => tool.status === "running"))
|
|
1178
1241
|
return "running";
|
|
1179
|
-
if (tools.some((tool) => tool.status === "failed"))
|
|
1180
|
-
return "failed";
|
|
1181
1242
|
return "completed";
|
|
1182
1243
|
}
|
|
1183
1244
|
function executionActivityTitle(group, status) {
|
|
1184
|
-
const icon = status === "running" ? "⏳" :
|
|
1245
|
+
const icon = status === "running" ? "⏳" : "💭";
|
|
1185
1246
|
const latestReasoning = group.latestReasoning?.text
|
|
1186
1247
|
? removeMarkdownBold(group.latestReasoning.text).replace(/\s+/g, " ").trim()
|
|
1187
1248
|
: "";
|
|
@@ -1189,9 +1250,7 @@ function executionActivityTitle(group, status) {
|
|
|
1189
1250
|
? truncateText(latestReasoning, 90)
|
|
1190
1251
|
: status === "running"
|
|
1191
1252
|
? "正在执行工具"
|
|
1192
|
-
:
|
|
1193
|
-
? "工具执行失败"
|
|
1194
|
-
: "已执行工具";
|
|
1253
|
+
: "已执行工具";
|
|
1195
1254
|
return `${icon} ${summary} · ${group.tools.length} 个工具`;
|
|
1196
1255
|
}
|
|
1197
1256
|
function executionActivityPanelElementId(activityId) {
|
|
@@ -1217,6 +1276,7 @@ const ACTIVITIES_PER_PAGE = 40;
|
|
|
1217
1276
|
const GROUPED_TOOLS_PER_PANEL = 8;
|
|
1218
1277
|
const GROUPED_PAGE_ACTIVITY_BYTES = 24 * 1024;
|
|
1219
1278
|
const GROUPED_PAGE_ACTIVITY_COMPONENTS = 160;
|
|
1279
|
+
const PINNED_LIVE_COMMENTARIES = 3;
|
|
1220
1280
|
const MAX_LIVE_ASSISTANT_TEXT = 2_000;
|
|
1221
1281
|
const ACTIVITY_TEXT_CHUNK = 2_500;
|
|
1222
1282
|
function activityPages(activities) {
|
|
@@ -1234,6 +1294,9 @@ function activityPages(activities) {
|
|
|
1234
1294
|
}
|
|
1235
1295
|
function groupedActivityPages(activities, projectCwd, renderOptions = {}) {
|
|
1236
1296
|
const groups = groupTurnActivities(activities).flatMap(splitGroupedExecutionActivity);
|
|
1297
|
+
return groupedActivityPagesFromGroups(groups, projectCwd, renderOptions);
|
|
1298
|
+
}
|
|
1299
|
+
function groupedActivityPagesFromGroups(groups, projectCwd, renderOptions = {}) {
|
|
1237
1300
|
if (groups.length === 0)
|
|
1238
1301
|
return [];
|
|
1239
1302
|
const newestFirst = [];
|
|
@@ -1256,6 +1319,69 @@ function groupedActivityPageFits(groups, projectCwd, renderOptions) {
|
|
|
1256
1319
|
return Buffer.byteLength(JSON.stringify(elements), "utf8") <= GROUPED_PAGE_ACTIVITY_BYTES
|
|
1257
1320
|
&& countCardComponents(elements) <= GROUPED_PAGE_ACTIVITY_COMPONENTS;
|
|
1258
1321
|
}
|
|
1322
|
+
function groupedLiveActivityPage(pages, projectCwd) {
|
|
1323
|
+
const latest = pages.at(-1) ?? [];
|
|
1324
|
+
if (pages.length <= 1)
|
|
1325
|
+
return { groups: latest, historyGroups: [] };
|
|
1326
|
+
const groups = pages.flat();
|
|
1327
|
+
const latestStart = groups.length - latest.length;
|
|
1328
|
+
const commentaryIndexes = groups
|
|
1329
|
+
.map((group, index) => isCommentaryGroup(group) ? index : -1)
|
|
1330
|
+
.filter((index) => index >= 0);
|
|
1331
|
+
if (commentaryIndexes.length === 0) {
|
|
1332
|
+
return { groups: latest, historyGroups: groups.slice(0, latestStart) };
|
|
1333
|
+
}
|
|
1334
|
+
const selected = new Set();
|
|
1335
|
+
const pinnedCommentaries = commentaryIndexes.slice(0, PINNED_LIVE_COMMENTARIES);
|
|
1336
|
+
for (const index of pinnedCommentaries)
|
|
1337
|
+
selected.add(index);
|
|
1338
|
+
const latestCommentary = commentaryIndexes.at(-1);
|
|
1339
|
+
for (let index = latestCommentary; index < groups.length; index += 1)
|
|
1340
|
+
selected.add(index);
|
|
1341
|
+
const renderSelection = (selection) => ({
|
|
1342
|
+
groups: groupsWithGaps(groups, selection),
|
|
1343
|
+
historyGroups: groups.filter((_group, index) => index < latestCommentary || !selection.has(index)),
|
|
1344
|
+
});
|
|
1345
|
+
const fits = (selection) => {
|
|
1346
|
+
const view = renderSelection(selection);
|
|
1347
|
+
return groupedActivityPageFits(view.groups, projectCwd, {});
|
|
1348
|
+
};
|
|
1349
|
+
if (!fits(selected)) {
|
|
1350
|
+
const latestExecutions = [...selected]
|
|
1351
|
+
.filter((index) => index > latestCommentary && groups[index]?.kind === "execution")
|
|
1352
|
+
.sort((left, right) => left - right);
|
|
1353
|
+
const newestExecution = latestExecutions.at(-1);
|
|
1354
|
+
for (const index of latestExecutions) {
|
|
1355
|
+
if (fits(selected) || index === newestExecution)
|
|
1356
|
+
break;
|
|
1357
|
+
selected.delete(index);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
return renderSelection(selected);
|
|
1361
|
+
}
|
|
1362
|
+
function isCommentaryGroup(group) {
|
|
1363
|
+
return group.kind === "activity" && isCommentaryActivity(group.activity);
|
|
1364
|
+
}
|
|
1365
|
+
function groupsWithGaps(groups, selected) {
|
|
1366
|
+
const indexes = [...selected].sort((left, right) => left - right);
|
|
1367
|
+
const visible = [];
|
|
1368
|
+
const append = (group) => {
|
|
1369
|
+
if (group.kind === "gap" && visible.at(-1)?.kind === "gap")
|
|
1370
|
+
return;
|
|
1371
|
+
visible.push(group);
|
|
1372
|
+
};
|
|
1373
|
+
let previous = -1;
|
|
1374
|
+
for (const index of indexes) {
|
|
1375
|
+
if (previous >= 0 && index > previous + 1) {
|
|
1376
|
+
append({ kind: "gap", id: `gap:${previous}:${index}` });
|
|
1377
|
+
}
|
|
1378
|
+
const group = groups[index];
|
|
1379
|
+
if (group)
|
|
1380
|
+
append(group);
|
|
1381
|
+
previous = index;
|
|
1382
|
+
}
|
|
1383
|
+
return visible;
|
|
1384
|
+
}
|
|
1259
1385
|
function countCardComponents(value) {
|
|
1260
1386
|
if (Array.isArray(value)) {
|
|
1261
1387
|
return value.reduce((total, entry) => total + countCardComponents(entry), 0);
|
|
@@ -1415,12 +1541,21 @@ function renderToolDetails(tool, projectCwd) {
|
|
|
1415
1541
|
const result = tool.error ?? tool.output ?? fileSummary;
|
|
1416
1542
|
const normalizedCommand = stripAnsi(command).trim();
|
|
1417
1543
|
const displayCommand = tool.kind === "command"
|
|
1418
|
-
?
|
|
1544
|
+
? unwrapShellCommand(normalizedCommand) ?? normalizedCommand
|
|
1419
1545
|
: normalizedCommand;
|
|
1420
|
-
const commandText = truncateText(displayCommand,
|
|
1421
|
-
const resultText = result ?
|
|
1546
|
+
const commandText = truncateText(displayCommand, 600);
|
|
1547
|
+
const resultText = result ? truncateToolResult(stripAnsi(result).trim()) : undefined;
|
|
1422
1548
|
return codeBlock([`$ ${commandText}`, resultText].filter((part) => part !== undefined).join("\n"), 2_003);
|
|
1423
1549
|
}
|
|
1550
|
+
function truncateToolResult(result) {
|
|
1551
|
+
const maxLength = 900;
|
|
1552
|
+
const marker = "\n...\n";
|
|
1553
|
+
const tailLength = 600;
|
|
1554
|
+
if (result.length <= maxLength)
|
|
1555
|
+
return result;
|
|
1556
|
+
const headLength = maxLength - marker.length - tailLength;
|
|
1557
|
+
return `${result.slice(0, headLength)}${marker}${result.slice(-tailLength)}`;
|
|
1558
|
+
}
|
|
1424
1559
|
function displayFilePath(filePath, projectCwd) {
|
|
1425
1560
|
if (!projectCwd)
|
|
1426
1561
|
return filePath;
|
|
@@ -1447,7 +1582,7 @@ function toolPanelTitle(tool) {
|
|
|
1447
1582
|
const command = stripAnsi(tool.command ?? tool.title).trim();
|
|
1448
1583
|
const meaningfulCommand = tool.kind === "web_search"
|
|
1449
1584
|
? tool.title
|
|
1450
|
-
:
|
|
1585
|
+
: unwrapShellCommand(command) ?? tool.title;
|
|
1451
1586
|
const duration = toolDuration(tool);
|
|
1452
1587
|
const prefix = `${icon} `;
|
|
1453
1588
|
const suffix = duration ? ` · ${duration}` : "";
|
|
@@ -1494,6 +1629,9 @@ function formatTurnDuration(durationMs) {
|
|
|
1494
1629
|
return `${String(totalMinutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
1495
1630
|
return `${seconds}s`;
|
|
1496
1631
|
}
|
|
1632
|
+
function unwrapShellCommand(command) {
|
|
1633
|
+
return unwrapPowerShellCommand(command) ?? unwrapPosixShellCommand(command);
|
|
1634
|
+
}
|
|
1497
1635
|
function unwrapPowerShellCommand(command) {
|
|
1498
1636
|
const executable = command.match(/^(?:"[^"]*(?:pwsh|powershell)\.exe"|(?:\S*[\\/])?(?:pwsh|powershell)(?:\.exe)?)(?=\s|$)/i);
|
|
1499
1637
|
if (!executable)
|
|
@@ -1505,6 +1643,71 @@ function unwrapPowerShellCommand(command) {
|
|
|
1505
1643
|
const payload = args.slice(commandFlag.index + commandFlag[0].length).trim();
|
|
1506
1644
|
if (!payload)
|
|
1507
1645
|
return undefined;
|
|
1646
|
+
return unwrapQuotedPayload(payload);
|
|
1647
|
+
}
|
|
1648
|
+
function unwrapPosixShellCommand(command) {
|
|
1649
|
+
let executable = readCommandWord(command, 0);
|
|
1650
|
+
if (!executable)
|
|
1651
|
+
return undefined;
|
|
1652
|
+
if (posixExecutableName(executable.value) === "env") {
|
|
1653
|
+
let cursor = executable.end;
|
|
1654
|
+
while (true) {
|
|
1655
|
+
const candidate = readCommandWord(command, cursor);
|
|
1656
|
+
if (!candidate)
|
|
1657
|
+
return undefined;
|
|
1658
|
+
const name = posixExecutableName(candidate.value);
|
|
1659
|
+
if (isPosixShell(name)) {
|
|
1660
|
+
executable = candidate;
|
|
1661
|
+
break;
|
|
1662
|
+
}
|
|
1663
|
+
if (!candidate.value.startsWith("-") && !/^[A-Za-z_][A-Za-z0-9_]*=/.test(candidate.value)) {
|
|
1664
|
+
return undefined;
|
|
1665
|
+
}
|
|
1666
|
+
cursor = candidate.end;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
else if (!isPosixShell(posixExecutableName(executable.value))) {
|
|
1670
|
+
return undefined;
|
|
1671
|
+
}
|
|
1672
|
+
const args = command.slice(executable.end);
|
|
1673
|
+
const commandFlag = /(?:^|\s)(?:-[A-Za-z]*c[A-Za-z]*|--command)(?:\s+|=)/.exec(args);
|
|
1674
|
+
if (!commandFlag)
|
|
1675
|
+
return undefined;
|
|
1676
|
+
const payload = args.slice(commandFlag.index + commandFlag[0].length).trim();
|
|
1677
|
+
if (!payload)
|
|
1678
|
+
return undefined;
|
|
1679
|
+
return unwrapQuotedPayload(payload);
|
|
1680
|
+
}
|
|
1681
|
+
function readCommandWord(command, start) {
|
|
1682
|
+
let cursor = start;
|
|
1683
|
+
while (cursor < command.length && /\s/.test(command[cursor] ?? ""))
|
|
1684
|
+
cursor += 1;
|
|
1685
|
+
if (cursor >= command.length)
|
|
1686
|
+
return undefined;
|
|
1687
|
+
const quote = command[cursor];
|
|
1688
|
+
if (quote === "\"" || quote === "'") {
|
|
1689
|
+
const endQuote = command.indexOf(quote, cursor + 1);
|
|
1690
|
+
if (endQuote < 0)
|
|
1691
|
+
return undefined;
|
|
1692
|
+
return { value: command.slice(cursor + 1, endQuote), end: endQuote + 1 };
|
|
1693
|
+
}
|
|
1694
|
+
const word = /^\S+/.exec(command.slice(cursor));
|
|
1695
|
+
if (!word)
|
|
1696
|
+
return undefined;
|
|
1697
|
+
return { value: word[0], end: cursor + word[0].length };
|
|
1698
|
+
}
|
|
1699
|
+
function posixExecutableName(executable) {
|
|
1700
|
+
return executable.replaceAll("\\", "/").split("/").at(-1)?.toLowerCase() ?? "";
|
|
1701
|
+
}
|
|
1702
|
+
function isPosixShell(executable) {
|
|
1703
|
+
return executable === "sh"
|
|
1704
|
+
|| executable === "bash"
|
|
1705
|
+
|| executable === "zsh"
|
|
1706
|
+
|| executable === "dash"
|
|
1707
|
+
|| executable === "ksh"
|
|
1708
|
+
|| executable === "fish";
|
|
1709
|
+
}
|
|
1710
|
+
function unwrapQuotedPayload(payload) {
|
|
1508
1711
|
const quote = payload[0];
|
|
1509
1712
|
if ((quote === "\"" || quote === "'") && payload.at(-1) === quote)
|
|
1510
1713
|
return payload.slice(1, -1).trim();
|
|
@@ -1889,10 +2092,12 @@ function turnCard(title, template, elements, subtitle) {
|
|
|
1889
2092
|
tag: "plain_text",
|
|
1890
2093
|
content: title,
|
|
1891
2094
|
},
|
|
1892
|
-
subtitle
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
2095
|
+
...(subtitle ? {
|
|
2096
|
+
subtitle: {
|
|
2097
|
+
tag: "plain_text",
|
|
2098
|
+
content: subtitle,
|
|
2099
|
+
},
|
|
2100
|
+
} : {}),
|
|
1896
2101
|
padding: "12px 12px 12px 12px",
|
|
1897
2102
|
},
|
|
1898
2103
|
body: {
|
|
@@ -1926,6 +2131,29 @@ function sectionCard(title, elements, template = "blue", verticalSpacing = "8px"
|
|
|
1926
2131
|
},
|
|
1927
2132
|
};
|
|
1928
2133
|
}
|
|
2134
|
+
function compactCard(title, template, elements) {
|
|
2135
|
+
return {
|
|
2136
|
+
schema: "2.0",
|
|
2137
|
+
config: {
|
|
2138
|
+
update_multi: true,
|
|
2139
|
+
width_mode: "compact",
|
|
2140
|
+
},
|
|
2141
|
+
header: {
|
|
2142
|
+
template,
|
|
2143
|
+
title: {
|
|
2144
|
+
tag: "plain_text",
|
|
2145
|
+
content: title,
|
|
2146
|
+
},
|
|
2147
|
+
padding: "12px 12px 12px 12px",
|
|
2148
|
+
},
|
|
2149
|
+
body: {
|
|
2150
|
+
direction: "vertical",
|
|
2151
|
+
vertical_spacing: "12px",
|
|
2152
|
+
padding: "12px 12px 12px 12px",
|
|
2153
|
+
elements,
|
|
2154
|
+
},
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
1929
2157
|
function formatUpdate(update) {
|
|
1930
2158
|
const updateType = update.sessionUpdate;
|
|
1931
2159
|
if (updateType === "agent_message_chunk" && isObject(update.content)) {
|