@keyou007/agent-bot 0.1.14-alpha.1 → 0.1.14-alpha.2
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 +14 -1
- package/README.md +42 -12
- package/README.zh.md +42 -12
- 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 +17 -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 +92 -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 +643 -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 +7 -1
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +172 -23
- 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/index.js +134 -2
- package/dist/index.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +436 -12
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +18 -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 +107 -188
- package/src/cli/AutostartManager.ts +579 -0
- package/src/cli/FeishuAppConfiguration.ts +22 -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 +111 -1
- package/src/cli/help.ts +92 -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 +781 -65
- package/src/codex/CodexRuntime.ts +8 -2
- package/src/commands/CommandRouter.ts +6 -1
- package/src/commands/commandTypes.ts +1 -0
- package/src/feishu/CardRenderer.ts +199 -23
- package/src/feishu/FeishuMarkdown.ts +27 -1
- package/src/index.ts +172 -2
- package/src/proxy/ProxySessionController.ts +532 -15
- package/src/runtime/types.ts +1 -0
- package/src/state/StateStore.ts +19 -0
|
@@ -355,12 +355,18 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
355
355
|
|
|
356
356
|
async closeSession(sessionId: string): Promise<void> {
|
|
357
357
|
const session = this.requireSession(sessionId);
|
|
358
|
+
await this.archiveRemoteSession(session.remoteSessionId);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async archiveRemoteSession(remoteSessionId: string): Promise<void> {
|
|
358
362
|
await (await this.client()).request(
|
|
359
363
|
"thread/archive",
|
|
360
|
-
{ threadId:
|
|
364
|
+
{ threadId: remoteSessionId },
|
|
361
365
|
CONTROL_REQUEST_TIMEOUT_MS,
|
|
362
366
|
);
|
|
363
|
-
this.sessions
|
|
367
|
+
for (const [localSessionId, session] of this.sessions) {
|
|
368
|
+
if (session.remoteSessionId === remoteSessionId) this.sessions.delete(localSessionId);
|
|
369
|
+
}
|
|
364
370
|
}
|
|
365
371
|
|
|
366
372
|
async setTitle(sessionId: string, title: string): Promise<void> {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Command } from "./commandTypes.js";
|
|
2
2
|
|
|
3
|
-
const COMMAND_NAMES = [
|
|
3
|
+
export const COMMAND_NAMES = [
|
|
4
4
|
"agent",
|
|
5
|
+
"archive",
|
|
5
6
|
"dir",
|
|
6
7
|
"fork",
|
|
7
8
|
"forkgroup",
|
|
@@ -76,6 +77,10 @@ export class CommandRouter {
|
|
|
76
77
|
return args[0] ? { type: "switch", sessionId: args[0] } : { type: "switch" };
|
|
77
78
|
case "agent":
|
|
78
79
|
return args[0] ? { type: "agent", agent: args[0] } : { type: "agent" };
|
|
80
|
+
case "archive":
|
|
81
|
+
rejectOptions("/archive", args);
|
|
82
|
+
if (args.length > 1) throw new Error("/archive 只接受一个可选的任务序号或任务 ID。");
|
|
83
|
+
return args[0] ? { type: "archive", sessionId: args[0] } : { type: "archive" };
|
|
79
84
|
case "stop":
|
|
80
85
|
return { type: "stop" };
|
|
81
86
|
case "status":
|
|
@@ -10,6 +10,7 @@ export type Command =
|
|
|
10
10
|
| { type: "sessions"; searchTerm?: string }
|
|
11
11
|
| { type: "switch"; sessionId?: string }
|
|
12
12
|
| { type: "agent"; agent?: string }
|
|
13
|
+
| { type: "archive"; sessionId?: string }
|
|
13
14
|
| { type: "stop" }
|
|
14
15
|
| { type: "status"; sessionId?: string }
|
|
15
16
|
| { type: "goal"; action: "show" }
|
|
@@ -11,7 +11,7 @@ import type {
|
|
|
11
11
|
ToolState,
|
|
12
12
|
} from "../runtime/types.js";
|
|
13
13
|
import type { TurnActivity, TurnViewState, TurnViewStatus } from "../presentation/turnViewTypes.js";
|
|
14
|
-
import {
|
|
14
|
+
import { truncateText } from "../utils/markdown.js";
|
|
15
15
|
import { localCardImage } from "./LocalCardImage.js";
|
|
16
16
|
|
|
17
17
|
export interface StartupStatusView {
|
|
@@ -889,15 +889,26 @@ export class CardRenderer {
|
|
|
889
889
|
|
|
890
890
|
renderActivityHistory(state: TurnViewState, requestedPage: number): Record<string, unknown> {
|
|
891
891
|
const grouped = this.thinkingCardLayout === "grouped";
|
|
892
|
+
const activities = turnActivities(state);
|
|
893
|
+
const allGroups = grouped ? groupTurnActivities(activities).flatMap(splitGroupedExecutionActivity) : [];
|
|
894
|
+
const historyGroups = grouped
|
|
895
|
+
? groupedLiveActivityPage(groupedActivityPagesFromGroups(allGroups, state.projectCwd), state.projectCwd).historyGroups
|
|
896
|
+
: [];
|
|
897
|
+
const usesHistoricalPrefixPages = historyGroups.length > 0;
|
|
892
898
|
const pages = grouped
|
|
893
|
-
?
|
|
899
|
+
? groupedActivityPagesFromGroups(
|
|
900
|
+
usesHistoricalPrefixPages ? historyGroups : allGroups,
|
|
901
|
+
state.projectCwd,
|
|
902
|
+
{
|
|
894
903
|
fullActivityText: true,
|
|
895
904
|
includeReasoningHistory: true,
|
|
896
|
-
|
|
897
|
-
|
|
905
|
+
},
|
|
906
|
+
)
|
|
907
|
+
: activityPages(activities);
|
|
898
908
|
if (pages.length === 0) {
|
|
899
909
|
return this.renderSectionsCard("思考活动历史", [{ lines: ["没有保存到活动记录。"] }]);
|
|
900
910
|
}
|
|
911
|
+
const totalPages = usesHistoricalPrefixPages ? pages.length + 1 : pages.length;
|
|
901
912
|
const page = Math.max(0, Math.min(Math.trunc(requestedPage), pages.length - 1));
|
|
902
913
|
const actions: TaskListCardAction[] = [
|
|
903
914
|
...(pages.length > 1 ? [{
|
|
@@ -908,7 +919,7 @@ export class CardRenderer {
|
|
|
908
919
|
text: "上一页",
|
|
909
920
|
value: { action: "activity_history", turnId: state.turnId, page: String(page - 1) },
|
|
910
921
|
}] : []),
|
|
911
|
-
...(page < pages.length - 2 ? [{
|
|
922
|
+
...(page < pages.length - (usesHistoricalPrefixPages ? 1 : 2) ? [{
|
|
912
923
|
text: "下一页",
|
|
913
924
|
value: { action: "activity_history", turnId: state.turnId, page: String(page + 1) },
|
|
914
925
|
}] : []),
|
|
@@ -921,7 +932,7 @@ export class CardRenderer {
|
|
|
921
932
|
includeReasoningHistory: true,
|
|
922
933
|
})
|
|
923
934
|
: renderActivities(pages[page] as TurnActivity[] | undefined ?? [], state.projectCwd, true)));
|
|
924
|
-
return sectionCard(`思考活动历史 · ${page + 1}/${
|
|
935
|
+
return sectionCard(`思考活动历史 · ${page + 1}/${totalPages}`, elements.length > 0 ? elements : [markdown("无")]);
|
|
925
936
|
}
|
|
926
937
|
|
|
927
938
|
renderSessionStarted(session: RuntimeSession): Record<string, unknown> {
|
|
@@ -1249,24 +1260,24 @@ function renderGroupedTurnElements(
|
|
|
1249
1260
|
const elements: Record<string, unknown>[] = [];
|
|
1250
1261
|
const allActivities = turnActivities(state);
|
|
1251
1262
|
const livePages = groupedActivityPages(allActivities, state.projectCwd);
|
|
1252
|
-
const
|
|
1263
|
+
const visible = groupedLiveActivityPage(livePages, state.projectCwd);
|
|
1264
|
+
const historyPages = groupedActivityPagesFromGroups(visible.historyGroups, state.projectCwd, {
|
|
1253
1265
|
fullActivityText: true,
|
|
1254
1266
|
includeReasoningHistory: true,
|
|
1255
1267
|
});
|
|
1256
|
-
const visibleGroups = livePages.at(-1) ?? [];
|
|
1257
1268
|
if (state.plan.length > 0) elements.push(planPanel(state.plan));
|
|
1258
|
-
if (historyPages.length >
|
|
1269
|
+
if (historyPages.length > 0) {
|
|
1259
1270
|
elements.push(taskActionRow([{
|
|
1260
|
-
text: `查看历史思考(共 ${historyPages.length} 页)`,
|
|
1271
|
+
text: `查看历史思考(共 ${historyPages.length + 1} 页)`,
|
|
1261
1272
|
value: {
|
|
1262
1273
|
action: "activity_history",
|
|
1263
1274
|
turnId: state.turnId,
|
|
1264
|
-
page: String(historyPages.length -
|
|
1275
|
+
page: String(historyPages.length - 1),
|
|
1265
1276
|
},
|
|
1266
1277
|
}]));
|
|
1267
1278
|
}
|
|
1268
|
-
if (state.activitiesTruncated
|
|
1269
|
-
elements.push(...renderGroupedActivityGroups(
|
|
1279
|
+
if (state.activitiesTruncated && visible.groups[0]?.kind !== "gap") elements.push(markdown("…"));
|
|
1280
|
+
elements.push(...renderGroupedActivityGroups(visible.groups, state.projectCwd));
|
|
1270
1281
|
if (state.fileSummary.length > 0) elements.push(fileSummaryPanel(state));
|
|
1271
1282
|
|
|
1272
1283
|
if (state.approval) {
|
|
@@ -1407,6 +1418,7 @@ function renderActivities(
|
|
|
1407
1418
|
|
|
1408
1419
|
type GroupedTurnActivity =
|
|
1409
1420
|
| { kind: "activity"; activity: TurnActivity }
|
|
1421
|
+
| { kind: "gap"; id: string }
|
|
1410
1422
|
| {
|
|
1411
1423
|
kind: "execution";
|
|
1412
1424
|
id: string;
|
|
@@ -1424,6 +1436,7 @@ function renderGroupedActivityGroups(
|
|
|
1424
1436
|
} = {},
|
|
1425
1437
|
): Record<string, unknown>[] {
|
|
1426
1438
|
return groups.flatMap((group) => {
|
|
1439
|
+
if (group.kind === "gap") return [markdown("…")];
|
|
1427
1440
|
if (group.kind === "activity") {
|
|
1428
1441
|
return renderActivity(group.activity, projectCwd, options.fullActivityText === true);
|
|
1429
1442
|
}
|
|
@@ -1500,7 +1513,7 @@ function executionActivityPanel(
|
|
|
1500
1513
|
{
|
|
1501
1514
|
elementId: executionActivityPanelElementId(group.id),
|
|
1502
1515
|
expanded: false,
|
|
1503
|
-
borderColor: status === "
|
|
1516
|
+
borderColor: status === "running" ? "blue" : "grey",
|
|
1504
1517
|
compact: true,
|
|
1505
1518
|
},
|
|
1506
1519
|
);
|
|
@@ -1508,7 +1521,6 @@ function executionActivityPanel(
|
|
|
1508
1521
|
|
|
1509
1522
|
function executionActivityStatus(tools: ToolState[]): ToolState["status"] {
|
|
1510
1523
|
if (tools.some((tool) => tool.status === "running")) return "running";
|
|
1511
|
-
if (tools.some((tool) => tool.status === "failed")) return "failed";
|
|
1512
1524
|
return "completed";
|
|
1513
1525
|
}
|
|
1514
1526
|
|
|
@@ -1516,7 +1528,7 @@ function executionActivityTitle(
|
|
|
1516
1528
|
group: Extract<GroupedTurnActivity, { kind: "execution" }>,
|
|
1517
1529
|
status: ToolState["status"],
|
|
1518
1530
|
): string {
|
|
1519
|
-
const icon = status === "running" ? "⏳" :
|
|
1531
|
+
const icon = status === "running" ? "⏳" : "💭";
|
|
1520
1532
|
const latestReasoning = group.latestReasoning?.text
|
|
1521
1533
|
? removeMarkdownBold(group.latestReasoning.text).replace(/\s+/g, " ").trim()
|
|
1522
1534
|
: "";
|
|
@@ -1524,9 +1536,7 @@ function executionActivityTitle(
|
|
|
1524
1536
|
? truncateText(latestReasoning, 90)
|
|
1525
1537
|
: status === "running"
|
|
1526
1538
|
? "正在执行工具"
|
|
1527
|
-
:
|
|
1528
|
-
? "工具执行失败"
|
|
1529
|
-
: "已执行工具";
|
|
1539
|
+
: "已执行工具";
|
|
1530
1540
|
return `${icon} ${summary} · ${group.tools.length} 个工具`;
|
|
1531
1541
|
}
|
|
1532
1542
|
|
|
@@ -1559,6 +1569,7 @@ const ACTIVITIES_PER_PAGE = 40;
|
|
|
1559
1569
|
const GROUPED_TOOLS_PER_PANEL = 8;
|
|
1560
1570
|
const GROUPED_PAGE_ACTIVITY_BYTES = 24 * 1024;
|
|
1561
1571
|
const GROUPED_PAGE_ACTIVITY_COMPONENTS = 160;
|
|
1572
|
+
const PINNED_LIVE_COMMENTARIES = 3;
|
|
1562
1573
|
const MAX_LIVE_ASSISTANT_TEXT = 2_000;
|
|
1563
1574
|
const ACTIVITY_TEXT_CHUNK = 2_500;
|
|
1564
1575
|
|
|
@@ -1583,6 +1594,17 @@ function groupedActivityPages(
|
|
|
1583
1594
|
} = {},
|
|
1584
1595
|
): GroupedTurnActivity[][] {
|
|
1585
1596
|
const groups = groupTurnActivities(activities).flatMap(splitGroupedExecutionActivity);
|
|
1597
|
+
return groupedActivityPagesFromGroups(groups, projectCwd, renderOptions);
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
function groupedActivityPagesFromGroups(
|
|
1601
|
+
groups: GroupedTurnActivity[],
|
|
1602
|
+
projectCwd?: string,
|
|
1603
|
+
renderOptions: {
|
|
1604
|
+
fullActivityText?: boolean;
|
|
1605
|
+
includeReasoningHistory?: boolean;
|
|
1606
|
+
} = {},
|
|
1607
|
+
): GroupedTurnActivity[][] {
|
|
1586
1608
|
if (groups.length === 0) return [];
|
|
1587
1609
|
|
|
1588
1610
|
const newestFirst: GroupedTurnActivity[][] = [];
|
|
@@ -1613,6 +1635,82 @@ function groupedActivityPageFits(
|
|
|
1613
1635
|
&& countCardComponents(elements) <= GROUPED_PAGE_ACTIVITY_COMPONENTS;
|
|
1614
1636
|
}
|
|
1615
1637
|
|
|
1638
|
+
interface LiveGroupedActivityPage {
|
|
1639
|
+
groups: GroupedTurnActivity[];
|
|
1640
|
+
historyGroups: GroupedTurnActivity[];
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
function groupedLiveActivityPage(
|
|
1644
|
+
pages: GroupedTurnActivity[][],
|
|
1645
|
+
projectCwd?: string,
|
|
1646
|
+
): LiveGroupedActivityPage {
|
|
1647
|
+
const latest = pages.at(-1) ?? [];
|
|
1648
|
+
if (pages.length <= 1) return { groups: latest, historyGroups: [] };
|
|
1649
|
+
|
|
1650
|
+
const groups = pages.flat();
|
|
1651
|
+
const latestStart = groups.length - latest.length;
|
|
1652
|
+
const commentaryIndexes = groups
|
|
1653
|
+
.map((group, index) => isCommentaryGroup(group) ? index : -1)
|
|
1654
|
+
.filter((index) => index >= 0);
|
|
1655
|
+
if (commentaryIndexes.length === 0) {
|
|
1656
|
+
return { groups: latest, historyGroups: groups.slice(0, latestStart) };
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
const selected = new Set<number>();
|
|
1660
|
+
const pinnedCommentaries = commentaryIndexes.slice(0, PINNED_LIVE_COMMENTARIES);
|
|
1661
|
+
for (const index of pinnedCommentaries) selected.add(index);
|
|
1662
|
+
const latestCommentary = commentaryIndexes.at(-1)!;
|
|
1663
|
+
for (let index = latestCommentary; index < groups.length; index += 1) selected.add(index);
|
|
1664
|
+
|
|
1665
|
+
const renderSelection = (selection: ReadonlySet<number>): LiveGroupedActivityPage => ({
|
|
1666
|
+
groups: groupsWithGaps(groups, selection),
|
|
1667
|
+
historyGroups: groups.filter((_group, index) => index < latestCommentary || !selection.has(index)),
|
|
1668
|
+
});
|
|
1669
|
+
const fits = (selection: ReadonlySet<number>): boolean => {
|
|
1670
|
+
const view = renderSelection(selection);
|
|
1671
|
+
return groupedActivityPageFits(view.groups, projectCwd, {});
|
|
1672
|
+
};
|
|
1673
|
+
|
|
1674
|
+
if (!fits(selected)) {
|
|
1675
|
+
const latestExecutions = [...selected]
|
|
1676
|
+
.filter((index) => index > latestCommentary && groups[index]?.kind === "execution")
|
|
1677
|
+
.sort((left, right) => left - right);
|
|
1678
|
+
const newestExecution = latestExecutions.at(-1);
|
|
1679
|
+
for (const index of latestExecutions) {
|
|
1680
|
+
if (fits(selected) || index === newestExecution) break;
|
|
1681
|
+
selected.delete(index);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
return renderSelection(selected);
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
function isCommentaryGroup(group: GroupedTurnActivity): boolean {
|
|
1689
|
+
return group.kind === "activity" && isCommentaryActivity(group.activity);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
function groupsWithGaps(
|
|
1693
|
+
groups: GroupedTurnActivity[],
|
|
1694
|
+
selected: ReadonlySet<number>,
|
|
1695
|
+
): GroupedTurnActivity[] {
|
|
1696
|
+
const indexes = [...selected].sort((left, right) => left - right);
|
|
1697
|
+
const visible: GroupedTurnActivity[] = [];
|
|
1698
|
+
const append = (group: GroupedTurnActivity): void => {
|
|
1699
|
+
if (group.kind === "gap" && visible.at(-1)?.kind === "gap") return;
|
|
1700
|
+
visible.push(group);
|
|
1701
|
+
};
|
|
1702
|
+
let previous = -1;
|
|
1703
|
+
for (const index of indexes) {
|
|
1704
|
+
if (previous >= 0 && index > previous + 1) {
|
|
1705
|
+
append({ kind: "gap", id: `gap:${previous}:${index}` });
|
|
1706
|
+
}
|
|
1707
|
+
const group = groups[index];
|
|
1708
|
+
if (group) append(group);
|
|
1709
|
+
previous = index;
|
|
1710
|
+
}
|
|
1711
|
+
return visible;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1616
1714
|
function countCardComponents(value: unknown): number {
|
|
1617
1715
|
if (Array.isArray(value)) {
|
|
1618
1716
|
return value.reduce((total, entry) => total + countCardComponents(entry), 0);
|
|
@@ -1791,13 +1889,22 @@ function renderToolDetails(tool: ToolState, projectCwd?: string): string {
|
|
|
1791
1889
|
const result = tool.error ?? tool.output ?? fileSummary;
|
|
1792
1890
|
const normalizedCommand = stripAnsi(command).trim();
|
|
1793
1891
|
const displayCommand = tool.kind === "command"
|
|
1794
|
-
?
|
|
1892
|
+
? unwrapShellCommand(normalizedCommand) ?? normalizedCommand
|
|
1795
1893
|
: normalizedCommand;
|
|
1796
|
-
const commandText = truncateText(displayCommand,
|
|
1797
|
-
const resultText = result ?
|
|
1894
|
+
const commandText = truncateText(displayCommand, 600);
|
|
1895
|
+
const resultText = result ? truncateToolResult(stripAnsi(result).trim()) : undefined;
|
|
1798
1896
|
return codeBlock([`$ ${commandText}`, resultText].filter((part): part is string => part !== undefined).join("\n"), 2_003);
|
|
1799
1897
|
}
|
|
1800
1898
|
|
|
1899
|
+
function truncateToolResult(result: string): string {
|
|
1900
|
+
const maxLength = 900;
|
|
1901
|
+
const marker = "\n...\n";
|
|
1902
|
+
const tailLength = 600;
|
|
1903
|
+
if (result.length <= maxLength) return result;
|
|
1904
|
+
const headLength = maxLength - marker.length - tailLength;
|
|
1905
|
+
return `${result.slice(0, headLength)}${marker}${result.slice(-tailLength)}`;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1801
1908
|
function displayFilePath(filePath: string, projectCwd?: string): string {
|
|
1802
1909
|
if (!projectCwd) return filePath;
|
|
1803
1910
|
const pathApi = usesWindowsPaths(projectCwd, filePath) ? path.win32 : path;
|
|
@@ -1826,7 +1933,7 @@ function toolPanelTitle(tool: ToolState): string {
|
|
|
1826
1933
|
const command = stripAnsi(tool.command ?? tool.title).trim();
|
|
1827
1934
|
const meaningfulCommand = tool.kind === "web_search"
|
|
1828
1935
|
? tool.title
|
|
1829
|
-
:
|
|
1936
|
+
: unwrapShellCommand(command) ?? tool.title;
|
|
1830
1937
|
const duration = toolDuration(tool);
|
|
1831
1938
|
const prefix = `${icon} `;
|
|
1832
1939
|
const suffix = duration ? ` · ${duration}` : "";
|
|
@@ -1875,6 +1982,10 @@ function formatTurnDuration(durationMs: number): string {
|
|
|
1875
1982
|
return `${seconds}s`;
|
|
1876
1983
|
}
|
|
1877
1984
|
|
|
1985
|
+
function unwrapShellCommand(command: string): string | undefined {
|
|
1986
|
+
return unwrapPowerShellCommand(command) ?? unwrapPosixShellCommand(command);
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1878
1989
|
function unwrapPowerShellCommand(command: string): string | undefined {
|
|
1879
1990
|
const executable = command.match(
|
|
1880
1991
|
/^(?:"[^"]*(?:pwsh|powershell)\.exe"|(?:\S*[\\/])?(?:pwsh|powershell)(?:\.exe)?)(?=\s|$)/i,
|
|
@@ -1885,6 +1996,71 @@ function unwrapPowerShellCommand(command: string): string | undefined {
|
|
|
1885
1996
|
if (!commandFlag) return undefined;
|
|
1886
1997
|
const payload = args.slice(commandFlag.index + commandFlag[0].length).trim();
|
|
1887
1998
|
if (!payload) return undefined;
|
|
1999
|
+
return unwrapQuotedPayload(payload);
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
function unwrapPosixShellCommand(command: string): string | undefined {
|
|
2003
|
+
let executable = readCommandWord(command, 0);
|
|
2004
|
+
if (!executable) return undefined;
|
|
2005
|
+
|
|
2006
|
+
if (posixExecutableName(executable.value) === "env") {
|
|
2007
|
+
let cursor = executable.end;
|
|
2008
|
+
while (true) {
|
|
2009
|
+
const candidate = readCommandWord(command, cursor);
|
|
2010
|
+
if (!candidate) return undefined;
|
|
2011
|
+
const name = posixExecutableName(candidate.value);
|
|
2012
|
+
if (isPosixShell(name)) {
|
|
2013
|
+
executable = candidate;
|
|
2014
|
+
break;
|
|
2015
|
+
}
|
|
2016
|
+
if (!candidate.value.startsWith("-") && !/^[A-Za-z_][A-Za-z0-9_]*=/.test(candidate.value)) {
|
|
2017
|
+
return undefined;
|
|
2018
|
+
}
|
|
2019
|
+
cursor = candidate.end;
|
|
2020
|
+
}
|
|
2021
|
+
} else if (!isPosixShell(posixExecutableName(executable.value))) {
|
|
2022
|
+
return undefined;
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
const args = command.slice(executable.end);
|
|
2026
|
+
const commandFlag = /(?:^|\s)(?:-[A-Za-z]*c[A-Za-z]*|--command)(?:\s+|=)/.exec(args);
|
|
2027
|
+
if (!commandFlag) return undefined;
|
|
2028
|
+
const payload = args.slice(commandFlag.index + commandFlag[0].length).trim();
|
|
2029
|
+
if (!payload) return undefined;
|
|
2030
|
+
return unwrapQuotedPayload(payload);
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
function readCommandWord(command: string, start: number): { value: string; end: number } | undefined {
|
|
2034
|
+
let cursor = start;
|
|
2035
|
+
while (cursor < command.length && /\s/.test(command[cursor] ?? "")) cursor += 1;
|
|
2036
|
+
if (cursor >= command.length) return undefined;
|
|
2037
|
+
|
|
2038
|
+
const quote = command[cursor];
|
|
2039
|
+
if (quote === "\"" || quote === "'") {
|
|
2040
|
+
const endQuote = command.indexOf(quote, cursor + 1);
|
|
2041
|
+
if (endQuote < 0) return undefined;
|
|
2042
|
+
return { value: command.slice(cursor + 1, endQuote), end: endQuote + 1 };
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
const word = /^\S+/.exec(command.slice(cursor));
|
|
2046
|
+
if (!word) return undefined;
|
|
2047
|
+
return { value: word[0], end: cursor + word[0].length };
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
function posixExecutableName(executable: string): string {
|
|
2051
|
+
return executable.replaceAll("\\", "/").split("/").at(-1)?.toLowerCase() ?? "";
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
function isPosixShell(executable: string): boolean {
|
|
2055
|
+
return executable === "sh"
|
|
2056
|
+
|| executable === "bash"
|
|
2057
|
+
|| executable === "zsh"
|
|
2058
|
+
|| executable === "dash"
|
|
2059
|
+
|| executable === "ksh"
|
|
2060
|
+
|| executable === "fish";
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
function unwrapQuotedPayload(payload: string): string {
|
|
1888
2064
|
const quote = payload[0];
|
|
1889
2065
|
if ((quote === "\"" || quote === "'") && payload.at(-1) === quote) return payload.slice(1, -1).trim();
|
|
1890
2066
|
return payload;
|
|
@@ -6,6 +6,8 @@ interface FenceState {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
const FENCE_OPENER = /^([ \t]*)((?:`{3,})|(?:~{3,}))(.*)$/;
|
|
9
|
+
const MARKDOWN_LINK = /(!?)\[([^\]\r\n]*)\]\((<?[^)\r\n]+>?)\)/g;
|
|
10
|
+
const LOCAL_FILE_LINE_REFERENCE = /(:\d+(?::\d+)?)$/;
|
|
9
11
|
|
|
10
12
|
export function normalizeFeishuMarkdown(markdown: string): string {
|
|
11
13
|
const lines = markdown.match(/[^\r\n]*(?:\r\n|\n)|[^\r\n]+$/g) ?? [];
|
|
@@ -17,7 +19,7 @@ export function normalizeFeishuMarkdown(markdown: string): string {
|
|
|
17
19
|
|
|
18
20
|
if (!fence) {
|
|
19
21
|
const opener = FENCE_OPENER.exec(body);
|
|
20
|
-
if (!opener?.[2]) return
|
|
22
|
+
if (!opener?.[2]) return `${includeLocalFileLineReferences(body)}${ending}`;
|
|
21
23
|
|
|
22
24
|
const indent = opener[1] ?? "";
|
|
23
25
|
const delimiter = opener[2];
|
|
@@ -38,6 +40,30 @@ export function normalizeFeishuMarkdown(markdown: string): string {
|
|
|
38
40
|
}).join("");
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
function includeLocalFileLineReferences(markdown: string): string {
|
|
44
|
+
return markdown.replace(MARKDOWN_LINK, (link, imageMarker: string, label: string, rawTarget: string) => {
|
|
45
|
+
if (imageMarker) return link;
|
|
46
|
+
|
|
47
|
+
const target = unwrapMarkdownTarget(rawTarget.trim());
|
|
48
|
+
if (!isLocalFileTarget(target)) return link;
|
|
49
|
+
|
|
50
|
+
const reference = LOCAL_FILE_LINE_REFERENCE.exec(target)?.[1];
|
|
51
|
+
if (!reference || label.endsWith(reference)) return link;
|
|
52
|
+
return `[${label}${reference}](${rawTarget})`;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function unwrapMarkdownTarget(target: string): string {
|
|
57
|
+
return target.startsWith("<") && target.endsWith(">") ? target.slice(1, -1) : target;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isLocalFileTarget(target: string): boolean {
|
|
61
|
+
return /^file:\/\//i.test(target)
|
|
62
|
+
|| /^\/?[a-z]:[\\/]/i.test(target)
|
|
63
|
+
|| /^\\\\/.test(target)
|
|
64
|
+
|| /^\/(?!\/)/.test(target);
|
|
65
|
+
}
|
|
66
|
+
|
|
41
67
|
function isFenceCloser(line: string, fence: FenceState): boolean {
|
|
42
68
|
const match = /^[ \t]*(`+|~+)[ \t]*$/.exec(line);
|
|
43
69
|
const delimiter = match?.[1];
|