@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.
Files changed (67) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/README.md +42 -12
  3. package/README.zh.md +42 -12
  4. package/config.example.yaml +1 -1
  5. package/dist/cli/AutostartManager.js +431 -0
  6. package/dist/cli/AutostartManager.js.map +1 -0
  7. package/dist/cli/FeishuAppConfiguration.js +17 -13
  8. package/dist/cli/FeishuAppConfiguration.js.map +1 -1
  9. package/dist/cli/Initializer.js +35 -0
  10. package/dist/cli/Initializer.js.map +1 -1
  11. package/dist/cli/SelfUpdateRunner.js +552 -0
  12. package/dist/cli/SelfUpdateRunner.js.map +1 -0
  13. package/dist/cli/SelfUpdater.js +431 -0
  14. package/dist/cli/SelfUpdater.js.map +1 -0
  15. package/dist/cli/autostartOutput.js +63 -0
  16. package/dist/cli/autostartOutput.js.map +1 -0
  17. package/dist/cli/controlProtocol.js.map +1 -1
  18. package/dist/cli/help.js +92 -18
  19. package/dist/cli/help.js.map +1 -1
  20. package/dist/cli/taskGroupOptions.js +14 -11
  21. package/dist/cli/taskGroupOptions.js.map +1 -1
  22. package/dist/cli/taskListOutput.js +3 -0
  23. package/dist/cli/taskListOutput.js.map +1 -1
  24. package/dist/cli/taskTarget.js +99 -0
  25. package/dist/cli/taskTarget.js.map +1 -0
  26. package/dist/cli.js +643 -58
  27. package/dist/cli.js.map +1 -1
  28. package/dist/codex/CodexRuntime.js +8 -2
  29. package/dist/codex/CodexRuntime.js.map +1 -1
  30. package/dist/commands/CommandRouter.js +7 -1
  31. package/dist/commands/CommandRouter.js.map +1 -1
  32. package/dist/feishu/CardRenderer.js +172 -23
  33. package/dist/feishu/CardRenderer.js.map +1 -1
  34. package/dist/feishu/FeishuMarkdown.js +25 -1
  35. package/dist/feishu/FeishuMarkdown.js.map +1 -1
  36. package/dist/index.js +134 -2
  37. package/dist/index.js.map +1 -1
  38. package/dist/proxy/ProxySessionController.js +436 -12
  39. package/dist/proxy/ProxySessionController.js.map +1 -1
  40. package/dist/state/StateStore.js +18 -0
  41. package/dist/state/StateStore.js.map +1 -1
  42. package/docs/technical-reference.md +17 -7
  43. package/docs/technical-reference.zh.md +17 -7
  44. package/npm-shrinkwrap.json +2 -2
  45. package/package.json +2 -2
  46. package/skills/agent-bot/SKILL.md +107 -188
  47. package/src/cli/AutostartManager.ts +579 -0
  48. package/src/cli/FeishuAppConfiguration.ts +22 -7
  49. package/src/cli/Initializer.ts +57 -0
  50. package/src/cli/SelfUpdateRunner.ts +817 -0
  51. package/src/cli/SelfUpdater.ts +688 -0
  52. package/src/cli/autostartOutput.ts +75 -0
  53. package/src/cli/controlProtocol.ts +111 -1
  54. package/src/cli/help.ts +92 -18
  55. package/src/cli/taskGroupOptions.ts +27 -17
  56. package/src/cli/taskListOutput.ts +10 -0
  57. package/src/cli/taskTarget.ts +168 -0
  58. package/src/cli.ts +781 -65
  59. package/src/codex/CodexRuntime.ts +8 -2
  60. package/src/commands/CommandRouter.ts +6 -1
  61. package/src/commands/commandTypes.ts +1 -0
  62. package/src/feishu/CardRenderer.ts +199 -23
  63. package/src/feishu/FeishuMarkdown.ts +27 -1
  64. package/src/index.ts +172 -2
  65. package/src/proxy/ProxySessionController.ts +532 -15
  66. package/src/runtime/types.ts +1 -0
  67. package/src/state/StateStore.ts +19 -0
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import path from "node:path";
3
- import { truncateMiddle, truncateText } from "../utils/markdown.js";
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 {
@@ -630,15 +630,22 @@ export class CardRenderer {
630
630
  }
631
631
  renderActivityHistory(state, requestedPage) {
632
632
  const grouped = this.thinkingCardLayout === "grouped";
633
+ const activities = turnActivities(state);
634
+ const allGroups = grouped ? groupTurnActivities(activities).flatMap(splitGroupedExecutionActivity) : [];
635
+ const historyGroups = grouped
636
+ ? groupedLiveActivityPage(groupedActivityPagesFromGroups(allGroups, state.projectCwd), state.projectCwd).historyGroups
637
+ : [];
638
+ const usesHistoricalPrefixPages = historyGroups.length > 0;
633
639
  const pages = grouped
634
- ? groupedActivityPages(turnActivities(state), state.projectCwd, {
640
+ ? groupedActivityPagesFromGroups(usesHistoricalPrefixPages ? historyGroups : allGroups, state.projectCwd, {
635
641
  fullActivityText: true,
636
642
  includeReasoningHistory: true,
637
643
  })
638
- : activityPages(turnActivities(state));
644
+ : activityPages(activities);
639
645
  if (pages.length === 0) {
640
646
  return this.renderSectionsCard("思考活动历史", [{ lines: ["没有保存到活动记录。"] }]);
641
647
  }
648
+ const totalPages = usesHistoricalPrefixPages ? pages.length + 1 : pages.length;
642
649
  const page = Math.max(0, Math.min(Math.trunc(requestedPage), pages.length - 1));
643
650
  const actions = [
644
651
  ...(pages.length > 1 ? [{
@@ -649,7 +656,7 @@ export class CardRenderer {
649
656
  text: "上一页",
650
657
  value: { action: "activity_history", turnId: state.turnId, page: String(page - 1) },
651
658
  }] : []),
652
- ...(page < pages.length - 2 ? [{
659
+ ...(page < pages.length - (usesHistoricalPrefixPages ? 1 : 2) ? [{
653
660
  text: "下一页",
654
661
  value: { action: "activity_history", turnId: state.turnId, page: String(page + 1) },
655
662
  }] : []),
@@ -663,7 +670,7 @@ export class CardRenderer {
663
670
  includeReasoningHistory: true,
664
671
  })
665
672
  : renderActivities(pages[page] ?? [], state.projectCwd, true)));
666
- return sectionCard(`思考活动历史 · ${page + 1}/${pages.length}`, elements.length > 0 ? elements : [markdown("无")]);
673
+ return sectionCard(`思考活动历史 · ${page + 1}/${totalPages}`, elements.length > 0 ? elements : [markdown("无")]);
667
674
  }
668
675
  renderSessionStarted(session) {
669
676
  return this.baseCard("ACP 会话已创建", "green", [
@@ -953,26 +960,26 @@ function renderGroupedTurnElements(state, assistantTextMode) {
953
960
  const elements = [];
954
961
  const allActivities = turnActivities(state);
955
962
  const livePages = groupedActivityPages(allActivities, state.projectCwd);
956
- const historyPages = groupedActivityPages(allActivities, state.projectCwd, {
963
+ const visible = groupedLiveActivityPage(livePages, state.projectCwd);
964
+ const historyPages = groupedActivityPagesFromGroups(visible.historyGroups, state.projectCwd, {
957
965
  fullActivityText: true,
958
966
  includeReasoningHistory: true,
959
967
  });
960
- const visibleGroups = livePages.at(-1) ?? [];
961
968
  if (state.plan.length > 0)
962
969
  elements.push(planPanel(state.plan));
963
- if (historyPages.length > 1) {
970
+ if (historyPages.length > 0) {
964
971
  elements.push(taskActionRow([{
965
- text: `查看历史思考(共 ${historyPages.length} 页)`,
972
+ text: `查看历史思考(共 ${historyPages.length + 1} 页)`,
966
973
  value: {
967
974
  action: "activity_history",
968
975
  turnId: state.turnId,
969
- page: String(historyPages.length - 2),
976
+ page: String(historyPages.length - 1),
970
977
  },
971
978
  }]));
972
979
  }
973
- if (state.activitiesTruncated || livePages.length > 1 || historyPages.length > 1)
980
+ if (state.activitiesTruncated && visible.groups[0]?.kind !== "gap")
974
981
  elements.push(markdown("…"));
975
- elements.push(...renderGroupedActivityGroups(visibleGroups, state.projectCwd));
982
+ elements.push(...renderGroupedActivityGroups(visible.groups, state.projectCwd));
976
983
  if (state.fileSummary.length > 0)
977
984
  elements.push(fileSummaryPanel(state));
978
985
  if (state.approval) {
@@ -1101,6 +1108,8 @@ function renderActivities(activities, projectCwd, fullAssistantText = false) {
1101
1108
  }
1102
1109
  function renderGroupedActivityGroups(groups, projectCwd, options = {}) {
1103
1110
  return groups.flatMap((group) => {
1111
+ if (group.kind === "gap")
1112
+ return [markdown("…")];
1104
1113
  if (group.kind === "activity") {
1105
1114
  return renderActivity(group.activity, projectCwd, options.fullActivityText === true);
1106
1115
  }
@@ -1169,19 +1178,17 @@ function executionActivityPanel(group, projectCwd, includeReasoningHistory = fal
1169
1178
  return collapsiblePanel(executionActivityTitle(group, status), elements, {
1170
1179
  elementId: executionActivityPanelElementId(group.id),
1171
1180
  expanded: false,
1172
- borderColor: status === "failed" ? "red" : status === "running" ? "blue" : "grey",
1181
+ borderColor: status === "running" ? "blue" : "grey",
1173
1182
  compact: true,
1174
1183
  });
1175
1184
  }
1176
1185
  function executionActivityStatus(tools) {
1177
1186
  if (tools.some((tool) => tool.status === "running"))
1178
1187
  return "running";
1179
- if (tools.some((tool) => tool.status === "failed"))
1180
- return "failed";
1181
1188
  return "completed";
1182
1189
  }
1183
1190
  function executionActivityTitle(group, status) {
1184
- const icon = status === "running" ? "⏳" : status === "failed" ? "❌" : "💭";
1191
+ const icon = status === "running" ? "⏳" : "💭";
1185
1192
  const latestReasoning = group.latestReasoning?.text
1186
1193
  ? removeMarkdownBold(group.latestReasoning.text).replace(/\s+/g, " ").trim()
1187
1194
  : "";
@@ -1189,9 +1196,7 @@ function executionActivityTitle(group, status) {
1189
1196
  ? truncateText(latestReasoning, 90)
1190
1197
  : status === "running"
1191
1198
  ? "正在执行工具"
1192
- : status === "failed"
1193
- ? "工具执行失败"
1194
- : "已执行工具";
1199
+ : "已执行工具";
1195
1200
  return `${icon} ${summary} · ${group.tools.length} 个工具`;
1196
1201
  }
1197
1202
  function executionActivityPanelElementId(activityId) {
@@ -1217,6 +1222,7 @@ const ACTIVITIES_PER_PAGE = 40;
1217
1222
  const GROUPED_TOOLS_PER_PANEL = 8;
1218
1223
  const GROUPED_PAGE_ACTIVITY_BYTES = 24 * 1024;
1219
1224
  const GROUPED_PAGE_ACTIVITY_COMPONENTS = 160;
1225
+ const PINNED_LIVE_COMMENTARIES = 3;
1220
1226
  const MAX_LIVE_ASSISTANT_TEXT = 2_000;
1221
1227
  const ACTIVITY_TEXT_CHUNK = 2_500;
1222
1228
  function activityPages(activities) {
@@ -1234,6 +1240,9 @@ function activityPages(activities) {
1234
1240
  }
1235
1241
  function groupedActivityPages(activities, projectCwd, renderOptions = {}) {
1236
1242
  const groups = groupTurnActivities(activities).flatMap(splitGroupedExecutionActivity);
1243
+ return groupedActivityPagesFromGroups(groups, projectCwd, renderOptions);
1244
+ }
1245
+ function groupedActivityPagesFromGroups(groups, projectCwd, renderOptions = {}) {
1237
1246
  if (groups.length === 0)
1238
1247
  return [];
1239
1248
  const newestFirst = [];
@@ -1256,6 +1265,69 @@ function groupedActivityPageFits(groups, projectCwd, renderOptions) {
1256
1265
  return Buffer.byteLength(JSON.stringify(elements), "utf8") <= GROUPED_PAGE_ACTIVITY_BYTES
1257
1266
  && countCardComponents(elements) <= GROUPED_PAGE_ACTIVITY_COMPONENTS;
1258
1267
  }
1268
+ function groupedLiveActivityPage(pages, projectCwd) {
1269
+ const latest = pages.at(-1) ?? [];
1270
+ if (pages.length <= 1)
1271
+ return { groups: latest, historyGroups: [] };
1272
+ const groups = pages.flat();
1273
+ const latestStart = groups.length - latest.length;
1274
+ const commentaryIndexes = groups
1275
+ .map((group, index) => isCommentaryGroup(group) ? index : -1)
1276
+ .filter((index) => index >= 0);
1277
+ if (commentaryIndexes.length === 0) {
1278
+ return { groups: latest, historyGroups: groups.slice(0, latestStart) };
1279
+ }
1280
+ const selected = new Set();
1281
+ const pinnedCommentaries = commentaryIndexes.slice(0, PINNED_LIVE_COMMENTARIES);
1282
+ for (const index of pinnedCommentaries)
1283
+ selected.add(index);
1284
+ const latestCommentary = commentaryIndexes.at(-1);
1285
+ for (let index = latestCommentary; index < groups.length; index += 1)
1286
+ selected.add(index);
1287
+ const renderSelection = (selection) => ({
1288
+ groups: groupsWithGaps(groups, selection),
1289
+ historyGroups: groups.filter((_group, index) => index < latestCommentary || !selection.has(index)),
1290
+ });
1291
+ const fits = (selection) => {
1292
+ const view = renderSelection(selection);
1293
+ return groupedActivityPageFits(view.groups, projectCwd, {});
1294
+ };
1295
+ if (!fits(selected)) {
1296
+ const latestExecutions = [...selected]
1297
+ .filter((index) => index > latestCommentary && groups[index]?.kind === "execution")
1298
+ .sort((left, right) => left - right);
1299
+ const newestExecution = latestExecutions.at(-1);
1300
+ for (const index of latestExecutions) {
1301
+ if (fits(selected) || index === newestExecution)
1302
+ break;
1303
+ selected.delete(index);
1304
+ }
1305
+ }
1306
+ return renderSelection(selected);
1307
+ }
1308
+ function isCommentaryGroup(group) {
1309
+ return group.kind === "activity" && isCommentaryActivity(group.activity);
1310
+ }
1311
+ function groupsWithGaps(groups, selected) {
1312
+ const indexes = [...selected].sort((left, right) => left - right);
1313
+ const visible = [];
1314
+ const append = (group) => {
1315
+ if (group.kind === "gap" && visible.at(-1)?.kind === "gap")
1316
+ return;
1317
+ visible.push(group);
1318
+ };
1319
+ let previous = -1;
1320
+ for (const index of indexes) {
1321
+ if (previous >= 0 && index > previous + 1) {
1322
+ append({ kind: "gap", id: `gap:${previous}:${index}` });
1323
+ }
1324
+ const group = groups[index];
1325
+ if (group)
1326
+ append(group);
1327
+ previous = index;
1328
+ }
1329
+ return visible;
1330
+ }
1259
1331
  function countCardComponents(value) {
1260
1332
  if (Array.isArray(value)) {
1261
1333
  return value.reduce((total, entry) => total + countCardComponents(entry), 0);
@@ -1415,12 +1487,21 @@ function renderToolDetails(tool, projectCwd) {
1415
1487
  const result = tool.error ?? tool.output ?? fileSummary;
1416
1488
  const normalizedCommand = stripAnsi(command).trim();
1417
1489
  const displayCommand = tool.kind === "command"
1418
- ? unwrapPowerShellCommand(normalizedCommand) ?? normalizedCommand
1490
+ ? unwrapShellCommand(normalizedCommand) ?? normalizedCommand
1419
1491
  : normalizedCommand;
1420
- const commandText = truncateText(displayCommand, 800);
1421
- const resultText = result ? truncateMiddle(stripAnsi(result).trim(), 1_200) : undefined;
1492
+ const commandText = truncateText(displayCommand, 600);
1493
+ const resultText = result ? truncateToolResult(stripAnsi(result).trim()) : undefined;
1422
1494
  return codeBlock([`$ ${commandText}`, resultText].filter((part) => part !== undefined).join("\n"), 2_003);
1423
1495
  }
1496
+ function truncateToolResult(result) {
1497
+ const maxLength = 900;
1498
+ const marker = "\n...\n";
1499
+ const tailLength = 600;
1500
+ if (result.length <= maxLength)
1501
+ return result;
1502
+ const headLength = maxLength - marker.length - tailLength;
1503
+ return `${result.slice(0, headLength)}${marker}${result.slice(-tailLength)}`;
1504
+ }
1424
1505
  function displayFilePath(filePath, projectCwd) {
1425
1506
  if (!projectCwd)
1426
1507
  return filePath;
@@ -1447,7 +1528,7 @@ function toolPanelTitle(tool) {
1447
1528
  const command = stripAnsi(tool.command ?? tool.title).trim();
1448
1529
  const meaningfulCommand = tool.kind === "web_search"
1449
1530
  ? tool.title
1450
- : unwrapPowerShellCommand(command) ?? tool.title;
1531
+ : unwrapShellCommand(command) ?? tool.title;
1451
1532
  const duration = toolDuration(tool);
1452
1533
  const prefix = `${icon} `;
1453
1534
  const suffix = duration ? ` · ${duration}` : "";
@@ -1494,6 +1575,9 @@ function formatTurnDuration(durationMs) {
1494
1575
  return `${String(totalMinutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
1495
1576
  return `${seconds}s`;
1496
1577
  }
1578
+ function unwrapShellCommand(command) {
1579
+ return unwrapPowerShellCommand(command) ?? unwrapPosixShellCommand(command);
1580
+ }
1497
1581
  function unwrapPowerShellCommand(command) {
1498
1582
  const executable = command.match(/^(?:"[^"]*(?:pwsh|powershell)\.exe"|(?:\S*[\\/])?(?:pwsh|powershell)(?:\.exe)?)(?=\s|$)/i);
1499
1583
  if (!executable)
@@ -1505,6 +1589,71 @@ function unwrapPowerShellCommand(command) {
1505
1589
  const payload = args.slice(commandFlag.index + commandFlag[0].length).trim();
1506
1590
  if (!payload)
1507
1591
  return undefined;
1592
+ return unwrapQuotedPayload(payload);
1593
+ }
1594
+ function unwrapPosixShellCommand(command) {
1595
+ let executable = readCommandWord(command, 0);
1596
+ if (!executable)
1597
+ return undefined;
1598
+ if (posixExecutableName(executable.value) === "env") {
1599
+ let cursor = executable.end;
1600
+ while (true) {
1601
+ const candidate = readCommandWord(command, cursor);
1602
+ if (!candidate)
1603
+ return undefined;
1604
+ const name = posixExecutableName(candidate.value);
1605
+ if (isPosixShell(name)) {
1606
+ executable = candidate;
1607
+ break;
1608
+ }
1609
+ if (!candidate.value.startsWith("-") && !/^[A-Za-z_][A-Za-z0-9_]*=/.test(candidate.value)) {
1610
+ return undefined;
1611
+ }
1612
+ cursor = candidate.end;
1613
+ }
1614
+ }
1615
+ else if (!isPosixShell(posixExecutableName(executable.value))) {
1616
+ return undefined;
1617
+ }
1618
+ const args = command.slice(executable.end);
1619
+ const commandFlag = /(?:^|\s)(?:-[A-Za-z]*c[A-Za-z]*|--command)(?:\s+|=)/.exec(args);
1620
+ if (!commandFlag)
1621
+ return undefined;
1622
+ const payload = args.slice(commandFlag.index + commandFlag[0].length).trim();
1623
+ if (!payload)
1624
+ return undefined;
1625
+ return unwrapQuotedPayload(payload);
1626
+ }
1627
+ function readCommandWord(command, start) {
1628
+ let cursor = start;
1629
+ while (cursor < command.length && /\s/.test(command[cursor] ?? ""))
1630
+ cursor += 1;
1631
+ if (cursor >= command.length)
1632
+ return undefined;
1633
+ const quote = command[cursor];
1634
+ if (quote === "\"" || quote === "'") {
1635
+ const endQuote = command.indexOf(quote, cursor + 1);
1636
+ if (endQuote < 0)
1637
+ return undefined;
1638
+ return { value: command.slice(cursor + 1, endQuote), end: endQuote + 1 };
1639
+ }
1640
+ const word = /^\S+/.exec(command.slice(cursor));
1641
+ if (!word)
1642
+ return undefined;
1643
+ return { value: word[0], end: cursor + word[0].length };
1644
+ }
1645
+ function posixExecutableName(executable) {
1646
+ return executable.replaceAll("\\", "/").split("/").at(-1)?.toLowerCase() ?? "";
1647
+ }
1648
+ function isPosixShell(executable) {
1649
+ return executable === "sh"
1650
+ || executable === "bash"
1651
+ || executable === "zsh"
1652
+ || executable === "dash"
1653
+ || executable === "ksh"
1654
+ || executable === "fish";
1655
+ }
1656
+ function unwrapQuotedPayload(payload) {
1508
1657
  const quote = payload[0];
1509
1658
  if ((quote === "\"" || quote === "'") && payload.at(-1) === quote)
1510
1659
  return payload.slice(1, -1).trim();