@keyou007/agent-bot 0.1.15-alpha.3 → 0.1.15-alpha.5

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 (35) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +4 -1
  3. package/README.zh.md +5 -2
  4. package/dist/cli/VerificationOutput.js +1 -6
  5. package/dist/cli/VerificationOutput.js.map +1 -1
  6. package/dist/cli.js +7 -13
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/CommandRouter.js +7 -0
  9. package/dist/commands/CommandRouter.js.map +1 -1
  10. package/dist/feishu/CardRenderer.js +76 -9
  11. package/dist/feishu/CardRenderer.js.map +1 -1
  12. package/dist/feishu/FinalResponseBranding.js +1 -1
  13. package/dist/feishu/FinalResponseBranding.js.map +1 -1
  14. package/dist/proxy/ProxySessionController.js +161 -83
  15. package/dist/proxy/ProxySessionController.js.map +1 -1
  16. package/dist/shell/ShellCommandJobManager.js +264 -0
  17. package/dist/shell/ShellCommandJobManager.js.map +1 -0
  18. package/dist/shell/shellCommandRunner.js +157 -0
  19. package/dist/shell/shellCommandRunner.js.map +1 -0
  20. package/dist/utils/executeShellCommand.js +1 -1
  21. package/dist/utils/executeShellCommand.js.map +1 -1
  22. package/docs/technical-reference.md +5 -3
  23. package/docs/technical-reference.zh.md +5 -3
  24. package/npm-shrinkwrap.json +2 -19
  25. package/package.json +1 -3
  26. package/src/cli/VerificationOutput.ts +1 -12
  27. package/src/cli.ts +6 -22
  28. package/src/commands/CommandRouter.ts +6 -0
  29. package/src/commands/commandTypes.ts +1 -0
  30. package/src/feishu/CardRenderer.ts +91 -17
  31. package/src/feishu/FinalResponseBranding.ts +2 -1
  32. package/src/proxy/ProxySessionController.ts +196 -89
  33. package/src/shell/ShellCommandJobManager.ts +341 -0
  34. package/src/shell/shellCommandRunner.ts +163 -0
  35. package/src/utils/executeShellCommand.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyou007/agent-bot",
3
- "version": "0.1.15-alpha.3",
3
+ "version": "0.1.15-alpha.5",
4
4
  "type": "module",
5
5
  "description": "Use Codex, TraeX, and compatible coding agents from Lark/Feishu.",
6
6
  "license": "GPL-3.0-only",
@@ -70,14 +70,12 @@
70
70
  "better-sqlite3": "12.11.1",
71
71
  "dotenv": "17.4.2",
72
72
  "pino": "10.3.1",
73
- "qrcode-terminal": "0.12.0",
74
73
  "yaml": "2.9.0",
75
74
  "zod": "4.4.3"
76
75
  },
77
76
  "devDependencies": {
78
77
  "@types/better-sqlite3": "7.6.13",
79
78
  "@types/node": "22.18.11",
80
- "@types/qrcode-terminal": "0.12.2",
81
79
  "eslint": "10.6.0",
82
80
  "prettier": "3.9.5",
83
81
  "tsx": "4.23.0",
@@ -1,25 +1,14 @@
1
- import qrcode from "qrcode-terminal";
2
1
  import { cliText, type CliLanguage } from "./i18n.js";
3
2
 
4
3
  export interface VerificationOutputOptions {
5
4
  verificationUrl: string;
6
- json: boolean;
7
- qrInstruction: string;
8
5
  linkLabel?: string;
9
6
  language?: CliLanguage;
10
7
  }
11
8
 
12
- export function printVerificationQrAndLink(
9
+ export function printVerificationLink(
13
10
  options: VerificationOutputOptions,
14
11
  write: (value: string) => void = (value) => process.stderr.write(value),
15
- renderQr: (
16
- value: string,
17
- callback: (output: string) => void,
18
- ) => void = (value, callback) => qrcode.generate(value, { small: true }, callback),
19
12
  ): void {
20
- if (!options.json) {
21
- write(`\n${options.qrInstruction}\n\n`);
22
- renderQr(options.verificationUrl, (output) => write(`${output}\n`));
23
- }
24
13
  write(`${options.linkLabel ?? cliText("Authorization link", "授权链接", options.language)}${cliText(":", ":", options.language)}\n${options.verificationUrl}\n\n`);
25
14
  }
package/src/cli.ts CHANGED
@@ -79,7 +79,7 @@ import {
79
79
  type InitializationWelcomeResult,
80
80
  } from "./cli/InitializationWelcome.js";
81
81
  import { applyExplicitProfile, parseGlobalOptions } from "./cli/profile.js";
82
- import { printVerificationQrAndLink } from "./cli/VerificationOutput.js";
82
+ import { printVerificationLink } from "./cli/VerificationOutput.js";
83
83
  import {
84
84
  startInitializedServer,
85
85
  startServer,
@@ -818,7 +818,7 @@ async function initializeFeishu(
818
818
  }
819
819
  credentials = await registerFeishuApp({
820
820
  signal: controller.signal,
821
- onVerification: (challenge) => printFeishuVerification(challenge, options.json),
821
+ onVerification: (challenge) => printFeishuVerification(challenge),
822
822
  });
823
823
  writeFeishuCredentials(paths.env.path, credentials);
824
824
  status = "created";
@@ -839,7 +839,7 @@ async function initializeFeishu(
839
839
  manualPermissionSkipSignal: manualPermissionSkip.signal,
840
840
  optionalSkipSignal: optionalSkip.signal,
841
841
  onVerification: (challenge) => {
842
- printFeishuConfigurationVerification(challenge, options.json);
842
+ printFeishuConfigurationVerification(challenge);
843
843
  if (challenge.kind === "manual_scope" && challenge.blocking) {
844
844
  skipListener?.close();
845
845
  skipListener = listenForManualPermissionSkip(() => manualPermissionSkip.abort());
@@ -1981,15 +1981,10 @@ function printServerStartResult(result: ServerStartResult): void {
1981
1981
  );
1982
1982
  }
1983
1983
 
1984
- function printFeishuVerification(challenge: FeishuAppRegistrationChallenge, json: boolean): void {
1985
- printVerificationQrAndLink(
1984
+ function printFeishuVerification(challenge: FeishuAppRegistrationChallenge): void {
1985
+ printVerificationLink(
1986
1986
  {
1987
1987
  verificationUrl: challenge.verificationUrl,
1988
- json,
1989
- qrInstruction: cliText(
1990
- "Scan this QR code with Lark to create the bot app:",
1991
- "使用飞书扫描此二维码以创建机器人应用:",
1992
- ),
1993
1988
  },
1994
1989
  );
1995
1990
  process.stderr.write(cliText(
@@ -2000,7 +1995,6 @@ function printFeishuVerification(challenge: FeishuAppRegistrationChallenge, json
2000
1995
 
2001
1996
  function printFeishuConfigurationVerification(
2002
1997
  challenge: FeishuConfigurationChallenge,
2003
- json: boolean,
2004
1998
  ): void {
2005
1999
  process.stderr.write(challenge.kind === "manual_scope"
2006
2000
  ? cliText(
@@ -2020,19 +2014,9 @@ function printFeishuConfigurationVerification(
2020
2014
  if (challenge.missing.callbacks.length > 0) {
2021
2015
  process.stderr.write(`${cliText("Callbacks: ", "回调:")}${challenge.missing.callbacks.join(", ")}\n`);
2022
2016
  }
2023
- printVerificationQrAndLink(
2017
+ printVerificationLink(
2024
2018
  {
2025
2019
  verificationUrl: challenge.verificationUrl,
2026
- json,
2027
- qrInstruction: challenge.kind === "manual_scope"
2028
- ? cliText(
2029
- "Scan this QR code with Lark to open the filtered permission page:",
2030
- "使用飞书扫描此二维码以打开已筛选的权限页面:",
2031
- )
2032
- : cliText(
2033
- "Scan this QR code with Lark to complete the configuration:",
2034
- "使用飞书扫描此二维码以完成配置:",
2035
- ),
2036
2020
  linkLabel: challenge.kind === "manual_scope"
2037
2021
  ? cliText("Permission page", "权限页面")
2038
2022
  : undefined,
@@ -5,6 +5,7 @@ export const COMMAND_NAMES = [
5
5
  "archive",
6
6
  "dir",
7
7
  "dismiss",
8
+ "file",
8
9
  "fork",
9
10
  "forkgroup",
10
11
  "goal",
@@ -56,6 +57,11 @@ export class CommandRouter {
56
57
  switch (command) {
57
58
  case "dir":
58
59
  return { type: "dir", directory: args.join(" ").trim() || undefined };
60
+ case "file": {
61
+ const filePath = args.join(" ").trim();
62
+ if (!filePath) throw new Error("请输入文件路径,例如:/file ./report.pdf。");
63
+ return { type: "file", filePath };
64
+ }
59
65
  case "new":
60
66
  return parseNewCommand(args);
61
67
  case "newgroup":
@@ -3,6 +3,7 @@ export type Command =
3
3
  | { type: "new"; title?: string; cwd?: string; projectless?: boolean }
4
4
  | { type: "newgroup"; title?: string; cwd?: string; projectless?: boolean }
5
5
  | { type: "dir"; directory?: string }
6
+ | { type: "file"; filePath: string }
6
7
  | { type: "forkgroup"; title?: string }
7
8
  | { type: "fork"; sessionId?: string }
8
9
  | { type: "title"; title: string }
@@ -240,11 +240,12 @@ export interface DismissGroupCardView {
240
240
  }
241
241
 
242
242
  export interface ShellCommandCardView {
243
+ jobId?: string;
244
+ contextKey?: string;
243
245
  command: string;
244
246
  cwd: string;
245
- stdout: string;
246
- stderr: string;
247
- status: "running" | "completed" | "failed" | "timed_out";
247
+ output: string;
248
+ status: "running" | "cancelling" | "completed" | "failed" | "cancelled" | "timed_out";
248
249
  exitCode?: number | null;
249
250
  elapsedMs: number;
250
251
  outputTruncated: boolean;
@@ -258,12 +259,7 @@ export class CardRenderer {
258
259
  }
259
260
 
260
261
  renderShellCommandCard(view: ShellCommandCardView): Record<string, unknown> {
261
- const stdout = normalizeTerminalOutput(view.stdout).trimEnd();
262
- const stderr = normalizeTerminalOutput(view.stderr).trimEnd();
263
- const rawOutput = [
264
- stdout,
265
- stderr ? `[stderr]\n${stderr}` : "",
266
- ].filter(Boolean).join("\n");
262
+ const rawOutput = normalizeTerminalOutput(view.output).trimEnd();
267
263
  const displayOutput = truncateMiddle(
268
264
  rawOutput,
269
265
  6_000,
@@ -273,15 +269,32 @@ export class CardRenderer {
273
269
  const output = displayOutput || (view.status === "running" ? "等待输出..." : "(无输出)");
274
270
  const status = shellCommandStatus(view);
275
271
  const outputWasTruncated = view.outputTruncated || rawOutput.length > 6_000;
272
+ const elements = [
273
+ markdown(codeBlock(`$ ${command}\n${output}`, 6_700)),
274
+ markdown([
275
+ `${inlineCode(view.cwd)} · ${status.label} · ${formatTurnDuration(view.elapsedMs)}`,
276
+ ...(outputWasTruncated ? ["输出过长,已保留开头和结尾并截断中间内容。"] : []),
277
+ ].join("\n")),
278
+ ];
279
+ if (view.status === "running" && view.jobId && view.contextKey) {
280
+ elements.push({
281
+ tag: "button",
282
+ text: { tag: "plain_text", content: "Cancel" },
283
+ type: "danger",
284
+ size: "small",
285
+ behaviors: [{
286
+ type: "callback",
287
+ value: {
288
+ action: "shell_command_cancel",
289
+ jobId: view.jobId,
290
+ contextKey: view.contextKey,
291
+ },
292
+ }],
293
+ });
294
+ }
276
295
  return sectionCard(
277
296
  status.title,
278
- [
279
- markdown(codeBlock(`$ ${command}\n${output}`, 6_700)),
280
- markdown([
281
- `${inlineCode(view.cwd)} · ${status.label} · ${formatTurnDuration(view.elapsedMs)}`,
282
- ...(outputWasTruncated ? ["输出过长,已保留开头和结尾并截断中间内容。"] : []),
283
- ].join("\n")),
284
- ],
297
+ elements,
285
298
  status.template,
286
299
  );
287
300
  }
@@ -2052,11 +2065,66 @@ function renderToolDetails(tool: ToolState, projectCwd?: string): string {
2052
2065
  const displayCommand = tool.kind === "command"
2053
2066
  ? unwrapShellCommand(normalizedCommand) ?? normalizedCommand
2054
2067
  : normalizedCommand;
2055
- const commandText = truncateText(displayCommand, 600);
2068
+ const commandText = truncateText(
2069
+ tool.kind === "command" ? formatShellCommandForDisplay(displayCommand) : displayCommand,
2070
+ 600,
2071
+ );
2056
2072
  const resultText = result ? truncateToolResult(normalizeTerminalOutput(result).trim()) : undefined;
2057
2073
  return codeBlock([`$ ${commandText}`, resultText].filter((part): part is string => part !== undefined).join("\n"), 2_003);
2058
2074
  }
2059
2075
 
2076
+ function formatShellCommandForDisplay(command: string): string {
2077
+ const normalized = command.replace(/\r\n/g, "\n");
2078
+ let result = "";
2079
+ let quote: "'" | "\"" | undefined;
2080
+
2081
+ for (let index = 0; index < normalized.length; index += 1) {
2082
+ const character = normalized[index] ?? "";
2083
+
2084
+ if (quote) {
2085
+ result += character;
2086
+ if ((character === "\\" || character === "`") && quote === "\"") {
2087
+ if (index + 1 < normalized.length) result += normalized[++index];
2088
+ } else if (character === quote) {
2089
+ quote = undefined;
2090
+ }
2091
+ continue;
2092
+ }
2093
+
2094
+ if (character === "'" || character === "\"") {
2095
+ quote = character;
2096
+ result += character;
2097
+ continue;
2098
+ }
2099
+
2100
+ if (character === "\\" || character === "`" || character === "^") {
2101
+ result += character;
2102
+ if (index + 1 < normalized.length) result += normalized[++index];
2103
+ continue;
2104
+ }
2105
+
2106
+ const operator = readShellDisplayOperator(normalized, index);
2107
+ if (!operator) {
2108
+ result += character;
2109
+ continue;
2110
+ }
2111
+
2112
+ result += operator;
2113
+ index += operator.length - 1;
2114
+ while (index + 1 < normalized.length && /\s/.test(normalized[index + 1] ?? "")) index += 1;
2115
+ if (index + 1 < normalized.length) result += " \\\n ";
2116
+ }
2117
+
2118
+ return result;
2119
+ }
2120
+
2121
+ function readShellDisplayOperator(command: string, index: number): string | undefined {
2122
+ for (const operator of ["&&", "||", "|&", ";", "|"]) {
2123
+ if (command.startsWith(operator, index)) return operator;
2124
+ }
2125
+ return undefined;
2126
+ }
2127
+
2060
2128
  function normalizeTerminalOutput(value: string): string {
2061
2129
  const clean = stripAnsi(value).replace(/\r\n/g, "\n");
2062
2130
  return clean
@@ -2167,6 +2235,12 @@ function shellCommandStatus(view: ShellCommandCardView): {
2167
2235
  if (view.status === "running") {
2168
2236
  return { title: "正在执行命令", label: "运行中", template: "blue" };
2169
2237
  }
2238
+ if (view.status === "cancelling") {
2239
+ return { title: "正在取消命令", label: "正在取消", template: "orange" };
2240
+ }
2241
+ if (view.status === "cancelled") {
2242
+ return { title: "命令已取消", label: "已取消", template: "grey" };
2243
+ }
2170
2244
  if (view.status === "timed_out") {
2171
2245
  return { title: "命令执行超时", label: "已超时(120s)", template: "orange" };
2172
2246
  }
@@ -1,4 +1,5 @@
1
- export const FINAL_RESPONSE_BRANDING = "> Powered by [AgentBot](https://github.com/keyou/agent-bot)";
1
+ export const FINAL_RESPONSE_BRANDING =
2
+ "> Powered by [AgentBot](https://bytedance.larkoffice.com/docx/UTukdYBopojSivxRVeUcCXTtnid)";
2
3
  export const FINAL_RESPONSE_BRANDING_BLOCK = `----\n\n${FINAL_RESPONSE_BRANDING}`;
3
4
 
4
5
  export function splitFinalResponseBranding(markdown: string): {