@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
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { createInterface } from "node:readline";
6
6
  import { loadConfig, loadConfigWithoutEnvironmentMutation } from "./config/loadConfig.js";
7
7
  import { sendControlRequest, isServerReachable } from "./cli/LocalControlClient.js";
8
8
  import { controlEndpoint, } from "./cli/controlProtocol.js";
9
- import { acquireInitializationLock, cleanupFeishuCredentialTemporaryFiles, initializeAgentBot, readConfiguredAgentSelection, readFeishuCredentials, shouldCreateFeishuApp, shouldConfigureAgentsDuringInitialization, writeConfiguredAgentSelection, writeFeishuCredentials, } from "./cli/Initializer.js";
9
+ import { acquireInitializationLock, cleanupFeishuCredentialTemporaryFiles, initializeAgentBot, readConfiguredAgentSelection, readFeishuCredentials, readGroupMessageResponseMode, shouldCreateFeishuApp, shouldConfigureAgentsDuringInitialization, shouldConfigureGroupMessagesDuringInitialization, writeConfiguredAgentSelection, writeFeishuCredentials, writeGroupMessageResponseMode, } from "./cli/Initializer.js";
10
10
  import { parseInitCommandOptions } from "./cli/initOptions.js";
11
11
  import { listenForManualPermissionSkip, listenForOptionalAuthorizationSkip, } from "./cli/OptionalAuthorizationSkip.js";
12
12
  import { registerFeishuApp, } from "./cli/FeishuAppRegistration.js";
@@ -23,13 +23,17 @@ import { applyExplicitProfile, parseGlobalOptions } from "./cli/profile.js";
23
23
  import { printVerificationQrAndLink } from "./cli/VerificationOutput.js";
24
24
  import { startInitializedServer, startServer, } from "./cli/ServerStarter.js";
25
25
  import { taskChatRoute } from "./cli/taskChatRoute.js";
26
- import { currentAppServerThreadIds, formatTaskList, resolveCurrentTask, taskStateLabel, } from "./cli/taskListOutput.js";
26
+ import { currentAppServerThreadIds, formatTaskList, taskStateLabel, } from "./cli/taskListOutput.js";
27
+ import { resolveCurrentTaskFromEnvironment, resolveTask, resolveTaskCommandTarget, } from "./cli/taskTarget.js";
27
28
  import { isThreadContextKey } from "./feishu/contextKey.js";
28
29
  import { refreshedSystemEnvironment } from "./supervision/systemEnvironment.js";
29
- import { parseTaskForkGroupOptions, parseTaskNewGroupOptions, } from "./cli/taskGroupOptions.js";
30
+ import { parseTaskForkGroupOptions, parseTaskNewOptions, parseTaskNewGroupOptions, } from "./cli/taskGroupOptions.js";
30
31
  import { StateStore } from "./state/StateStore.js";
31
32
  import { nodeDiagnosticReportArguments, prepareCrashReportDirectory, resolveSupervisorDiagnosticsPaths, } from "./supervision/SupervisorDiagnostics.js";
32
33
  import { agentBotHome, defaultSqlitePath } from "./config/paths.js";
34
+ import { finalizeSelfUpdatePlan, launchSelfUpdateRunner, parseSelfUpdateOptions, prepareSelfUpdate, releaseSelfUpdatePlan, requireNpmSelfUpdateInstallation, } from "./cli/SelfUpdater.js";
35
+ import { createCurrentAutostartManager } from "./cli/AutostartManager.js";
36
+ import { formatAutostartStatus, } from "./cli/autostartOutput.js";
33
37
  const args = process.argv.slice(2);
34
38
  void main(args).catch((error) => {
35
39
  const message = error instanceof Error ? error.message : String(error);
@@ -55,6 +59,10 @@ async function main(input) {
55
59
  await initCommand(rest, parsed.configPath, Boolean(parsed.profilePath));
56
60
  return;
57
61
  }
62
+ if (command === "update") {
63
+ await updateCommand(rest);
64
+ return;
65
+ }
58
66
  if (command === "console") {
59
67
  await consoleCommand(rest);
60
68
  return;
@@ -73,6 +81,73 @@ async function main(input) {
73
81
  }
74
82
  throw new Error(cliText(`Unknown command: ${command}. Run agentbot --help for usage.`, `未知命令:${command}。运行 agentbot --help 查看用法。`));
75
83
  }
84
+ async function updateCommand(input) {
85
+ const options = parseSelfUpdateOptions(input);
86
+ requireNpmSelfUpdateInstallation();
87
+ const config = loadConfig();
88
+ const endpoint = controlEndpoint(config.storage.sqlitePath);
89
+ const serverRunning = await isServerReachable(endpoint);
90
+ if (!options.json)
91
+ process.stdout.write(cliText("Checking the installed Agent Bot package and npm release channel...\n", "正在检查已安装的 Agent Bot 包和 npm 发布通道……\n"));
92
+ const prepared = await prepareSelfUpdate({
93
+ channel: options.channel,
94
+ version: options.version,
95
+ allowDowngrade: options.allowDowngrade,
96
+ });
97
+ if (prepared.status === "current") {
98
+ if (options.json)
99
+ printJson(prepared);
100
+ else
101
+ process.stdout.write(cliText(`Agent Bot ${prepared.currentVersion} is already current on the ${prepared.channel} channel.\n`, `Agent Bot ${prepared.currentVersion} 已是 ${prepared.channel} 通道的当前版本。\n`));
102
+ return;
103
+ }
104
+ const reason = cliText(`Update Agent Bot from ${prepared.currentVersion} to ${prepared.targetVersion}`, `将 Agent Bot 从 ${prepared.currentVersion} 更新到 ${prepared.targetVersion}`);
105
+ try {
106
+ const notificationSessionId = serverRunning
107
+ ? resolveRestartNotificationSessionId(config.storage.sqlitePath, options.taskReference ? ["--task", options.taskReference] : [])
108
+ : undefined;
109
+ finalizeSelfUpdatePlan(prepared.planPath, {
110
+ controlEndpoint: endpoint,
111
+ databasePath: config.storage.sqlitePath,
112
+ restartService: serverRunning,
113
+ workingDirectory: process.cwd(),
114
+ reason,
115
+ notificationSessionId,
116
+ });
117
+ if (serverRunning) {
118
+ ensureOk(await sendControlRequest(endpoint, {
119
+ action: "server_update",
120
+ planPath: prepared.planPath,
121
+ reason,
122
+ notificationSessionId,
123
+ }, 15_000));
124
+ }
125
+ else {
126
+ launchSelfUpdateRunner(prepared.planPath, { waitPids: [process.pid] });
127
+ }
128
+ }
129
+ catch (error) {
130
+ releaseSelfUpdatePlan(prepared.planPath);
131
+ throw error;
132
+ }
133
+ const result = {
134
+ status: serverRunning ? "scheduled" : "started",
135
+ fromVersion: prepared.currentVersion,
136
+ toVersion: prepared.targetVersion,
137
+ channel: prepared.channel,
138
+ serviceWasRunning: serverRunning,
139
+ logPath: prepared.logPath,
140
+ resultPath: prepared.resultPath,
141
+ };
142
+ if (options.json)
143
+ printJson(result);
144
+ else {
145
+ process.stdout.write(serverRunning
146
+ ? cliText(`Agent Bot ${prepared.targetVersion} is verified and ready. The update will run after active tasks finish and the server becomes idle.\n`, `Agent Bot ${prepared.targetVersion} 已通过预检。更新将在活动任务完成且服务空闲后执行。\n`)
147
+ : cliText(`Agent Bot ${prepared.targetVersion} is verified. Installation will continue after this command exits.\n`, `Agent Bot ${prepared.targetVersion} 已通过预检。本命令退出后将继续安装。\n`));
148
+ process.stdout.write(`${cliText("Update log: ", "更新日志:")}${prepared.logPath}\n`);
149
+ }
150
+ }
76
151
  async function initCommand(input, configPath, explicitProfile) {
77
152
  const options = parseInitCommandOptions(input, explicitProfile);
78
153
  const version = readPackageVersion();
@@ -90,6 +165,7 @@ async function initCommand(input, configPath, explicitProfile) {
90
165
  printInitializationPaths(paths);
91
166
  const inspectedAgents = await initializeSupportedAgents(options.json);
92
167
  const defaultAgent = await configureAgentsAndDefault(paths.config.path, inspectedAgents, options.json, shouldConfigureAgentsDuringInitialization(paths.config.status));
168
+ const groupMessages = await configureGroupMessageResponse(paths.config.path, options.json, shouldConfigureGroupMessagesDuringInitialization(paths.config.status));
93
169
  const configuredAgents = readConfiguredAgentSelection(paths.config.path).agents.map((agent) => agent.name);
94
170
  const configuredAgentNames = new Set(configuredAgents);
95
171
  const agents = inspectedAgents.map((agent) => ({
@@ -98,8 +174,9 @@ async function initCommand(input, configPath, explicitProfile) {
98
174
  }));
99
175
  initializationLock ??= acquireInitializationLock(paths.home.path);
100
176
  cleanupFeishuCredentialTemporaryFiles(paths.env.path);
101
- const feishu = await initializeFeishu(paths, options);
102
- initialized = { ...paths, agents, configuredAgents, defaultAgent, feishu };
177
+ const feishu = await initializeFeishu(paths, options, groupMessages.mode === "all");
178
+ const skill = bundledSkillRegistry().install();
179
+ initialized = { ...paths, agents, configuredAgents, defaultAgent, groupMessages, feishu, skill };
103
180
  }
104
181
  finally {
105
182
  initializationLock?.release();
@@ -256,6 +333,54 @@ async function configureAgentsAndDefault(configPath, inspections, json, configur
256
333
  output.write(cliText(`Default Agent: ${selectedDefault.name}${process.stdin.isTTY ? "" : " (selected automatically)"}\n`, `默认 Agent:${selectedDefault.name}${process.stdin.isTTY ? "" : "(已自动选择)"}\n`));
257
334
  return { name: selectedDefault.name, status: updated ? "selected" : "existing" };
258
335
  }
336
+ async function configureGroupMessageResponse(configPath, json, configure) {
337
+ const output = json ? process.stderr : process.stdout;
338
+ if (!configure) {
339
+ return { mode: readGroupMessageResponseMode(configPath), status: "existing" };
340
+ }
341
+ let mode = "mention-only";
342
+ if (process.stdin.isTTY) {
343
+ output.write(cliText("\nChoose how Agent Bot responds to group messages:\n 1. Receive all group messages\n The bot can respond without an @ mention. This requires an additional Lark permission.\n 2. Require an explicit @ mention\n The additional all-group-message permission will not be requested.\n", "\n请选择 Agent Bot 响应群消息的方式:\n 1. 接收所有群消息\n 无需 @ 机器人即可响应,需要额外开通飞书权限。\n 2. 明确 @ 机器人才能接收消息\n 不会申请“免 @ 机器人”所需的额外权限。\n"));
344
+ mode = await promptForGroupMessageResponseMode();
345
+ }
346
+ else {
347
+ output.write(cliText("No interactive terminal is available. Group messages will require an explicit @ mention, and the all-group-message permission will not be requested.\n", "当前没有交互式终端。群消息将要求明确 @ 机器人,并且不会申请接收所有群消息的额外权限。\n"));
348
+ }
349
+ writeGroupMessageResponseMode(configPath, mode);
350
+ output.write(cliText(`Group message response: ${mode === "all" ? "receive all group messages" : "require an explicit @ mention"}\n`, `群消息响应方式:${mode === "all" ? "接收所有群消息" : "必须明确 @ 机器人"}\n`));
351
+ return { mode, status: "selected" };
352
+ }
353
+ function promptForGroupMessageResponseMode() {
354
+ const readline = createInterface({ input: process.stdin, output: process.stderr });
355
+ return new Promise((resolve, reject) => {
356
+ let settled = false;
357
+ const finish = (mode) => {
358
+ settled = true;
359
+ readline.close();
360
+ resolve(mode);
361
+ };
362
+ const ask = () => {
363
+ readline.question(cliText("Select 1 or 2 [2]: ", "请选择 1 或 2 [2]:"), (answer) => {
364
+ const normalized = answer.trim().toLowerCase();
365
+ if (normalized === "1") {
366
+ finish("all");
367
+ return;
368
+ }
369
+ if (normalized === "" || normalized === "2") {
370
+ finish("mention-only");
371
+ return;
372
+ }
373
+ process.stderr.write(cliText("Enter 1 to receive all group messages, or 2 to require an @ mention.\n", "请输入 1 接收所有群消息,或输入 2 要求明确 @ 机器人。\n"));
374
+ ask();
375
+ });
376
+ };
377
+ listenForPromptCancellation(readline, () => {
378
+ settled = true;
379
+ reject(new Error(cliText("Initialization was cancelled.", "初始化已取消。")));
380
+ }, () => settled);
381
+ ask();
382
+ });
383
+ }
259
384
  function promptForAgentMaintenance(choiceCount) {
260
385
  const readline = createInterface({ input: process.stdin, output: process.stderr });
261
386
  return new Promise((resolve, reject) => {
@@ -373,7 +498,7 @@ function refreshCurrentProcessPath() {
373
498
  if (pathEntry?.[1])
374
499
  process.env.PATH = pathEntry[1];
375
500
  }
376
- async function initializeFeishu(paths, options) {
501
+ async function initializeFeishu(paths, options, respondToAllGroupMessages) {
377
502
  const existing = readFeishuCredentials(paths.env.path);
378
503
  if (options.skipFeishu) {
379
504
  return {
@@ -420,6 +545,7 @@ async function initializeFeishu(paths, options) {
420
545
  const optionalSkip = new AbortController();
421
546
  let skipListener;
422
547
  const configuration = await ensureFeishuAppConfiguration(credentials, {
548
+ respondToAllGroupMessages,
423
549
  signal: controller.signal,
424
550
  manualPermissionSkipSignal: manualPermissionSkip.signal,
425
551
  optionalSkipSignal: optionalSkip.signal,
@@ -466,9 +592,8 @@ async function assertResetProfileServerStopped(configPath) {
466
592
  }
467
593
  function skillsCommand(input) {
468
594
  const [action = "status", ...rest] = input;
469
- const sourcePath = fileURLToPath(new URL("../skills/agent-bot/", import.meta.url));
470
595
  const targetRoot = optionValue(rest, "--target") ?? resolveSystemSkillsRoot();
471
- const registry = new SkillRegistry(sourcePath, targetRoot);
596
+ const registry = bundledSkillRegistry(targetRoot);
472
597
  const json = rest.includes("--json");
473
598
  if (action === "install" || action === "register") {
474
599
  const result = registry.install();
@@ -511,6 +636,10 @@ function skillsCommand(input) {
511
636
  }
512
637
  throw new Error(cliText(`Unknown skills command: ${action}`, `未知的 skills 命令:${action}`));
513
638
  }
639
+ function bundledSkillRegistry(targetRoot = resolveSystemSkillsRoot()) {
640
+ const sourcePath = fileURLToPath(new URL("../skills/agent-bot/", import.meta.url));
641
+ return new SkillRegistry(sourcePath, targetRoot);
642
+ }
514
643
  async function consoleCommand(input) {
515
644
  const config = loadConfig();
516
645
  const endpoint = controlEndpoint(config.storage.sqlitePath);
@@ -565,6 +694,10 @@ async function serverCommand(input) {
565
694
  printServerStartResult(await startServer(config));
566
695
  return;
567
696
  }
697
+ if (action === "autostart") {
698
+ await serverAutostartCommand(config, endpoint, rest);
699
+ return;
700
+ }
568
701
  if (action === "stop") {
569
702
  ensureOk(await sendControlRequest(endpoint, { action: "server_stop" }));
570
703
  process.stdout.write(cliText("Agent Bot server stop requested.\n", "已请求停止 Agent Bot 服务。\n"));
@@ -588,6 +721,55 @@ async function serverCommand(input) {
588
721
  }
589
722
  throw new Error(cliText(`Unknown server command: ${action}`, `未知的 server 命令:${action}`));
590
723
  }
724
+ async function serverAutostartCommand(config, endpoint, input) {
725
+ const actionIndex = input.findIndex((value) => !value.startsWith("--"));
726
+ const action = actionIndex < 0 ? "status" : input[actionIndex];
727
+ const rest = input.filter((_value, index) => index !== actionIndex);
728
+ const json = rest.includes("--json");
729
+ const linger = rest.includes("--linger");
730
+ const unsupported = rest.filter((value) => value !== "--json" && value !== "--linger");
731
+ if (unsupported.length > 0)
732
+ throw new Error(cliText(`Unsupported server autostart options: ${unsupported.join(" ")}`, `不支持这些 server autostart 参数:${unsupported.join(" ")}`));
733
+ if (action !== "enable" && linger) {
734
+ throw new Error(cliText("--linger can be used only with server autostart enable.", "--linger 只能与 server autostart enable 一起使用。"));
735
+ }
736
+ const manager = createCurrentAutostartManager(config);
737
+ let registration;
738
+ if (action === "enable") {
739
+ registration = manager.enable({ linger });
740
+ }
741
+ else if (action === "disable") {
742
+ registration = manager.disable();
743
+ }
744
+ else if (action === "status") {
745
+ registration = manager.status();
746
+ }
747
+ else {
748
+ throw new Error(cliText(`Unknown server autostart command: ${action}`, `未知的 server autostart 命令:${action}`));
749
+ }
750
+ const result = {
751
+ registration,
752
+ server: await readAutostartServerStatus(endpoint),
753
+ };
754
+ if (json)
755
+ printJson(result);
756
+ else
757
+ process.stdout.write(formatAutostartStatus(result));
758
+ }
759
+ async function readAutostartServerStatus(endpoint) {
760
+ try {
761
+ const response = await sendControlRequest(endpoint, { action: "health" }, 1_500);
762
+ if (!response.ok)
763
+ return { running: true, ready: false };
764
+ const data = response.data && typeof response.data === "object"
765
+ ? response.data
766
+ : {};
767
+ return { running: true, ready: data.ready !== false };
768
+ }
769
+ catch {
770
+ return { running: false, ready: false };
771
+ }
772
+ }
591
773
  function resolveRestartNotificationSessionId(sqlitePath, args) {
592
774
  const store = new StateStore(sqlitePath);
593
775
  try {
@@ -619,6 +801,10 @@ async function taskCommand(input) {
619
801
  const store = new StateStore(config.storage.sqlitePath);
620
802
  try {
621
803
  const allSessions = store.listAllSessions();
804
+ if (action === "help" || action === "--help" || action === "-h") {
805
+ printHelp();
806
+ return;
807
+ }
622
808
  if (action === "current") {
623
809
  const unsupported = rest.filter((value) => value !== "--json");
624
810
  if (unsupported.length > 0)
@@ -628,25 +814,274 @@ async function taskCommand(input) {
628
814
  return;
629
815
  }
630
816
  if (action === "prompt" || action === "send") {
631
- const [reference, ...promptParts] = rest;
632
- if (!reference)
633
- throw new Error(cliText(`task ${action} requires a task number or task ID.`, `task ${action} 需要任务序号或任务 ID。`));
634
- const text = promptParts.join(" ").trim();
817
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
818
+ const text = target.args.join(" ").trim();
635
819
  if (!text)
636
820
  throw new Error(cliText(`task ${action} requires a Prompt.`, `task ${action} 需要提示词。`));
637
- const session = resolveTask(allSessions, reference);
638
821
  const endpoint = controlEndpoint(config.storage.sqlitePath);
639
822
  ensureOk(await sendControlRequest(endpoint, {
640
823
  action: "task_prompt",
641
- localSessionId: session.localSessionId,
824
+ localSessionId: target.session.localSessionId,
642
825
  text,
643
826
  }, 60_000));
644
827
  process.stdout.write(cliText("Prompt submitted.\n", "提示词已提交。\n"));
645
828
  return;
646
829
  }
830
+ if (action === "new") {
831
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
832
+ const options = parseTaskNewOptions([target.session.localSessionId, ...target.args]);
833
+ const source = target.session;
834
+ const targetAgent = options.agentName ? config.agents[options.agentName] : undefined;
835
+ if (options.agentName && !targetAgent)
836
+ throw new Error(cliText(`Unknown Agent standard name: ${options.agentName}.`, `未知的 Agent 标准名:${options.agentName}。`));
837
+ if (options.projectless && targetAgent && targetAgent.kind !== "app-server")
838
+ throw new Error(cliText("task new --nodir is only available for App Server agents.", "task new --nodir 仅适用于 App Server Agent。"));
839
+ const created = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
840
+ action: "task_new",
841
+ localSessionId: source.localSessionId,
842
+ ...(options.title ? { title: options.title } : {}),
843
+ ...(options.cwd ? { cwd: options.cwd } : {}),
844
+ ...(options.agentName ? { agentName: options.agentName } : {}),
845
+ ...(options.projectless ? { projectless: true } : {}),
846
+ }, 120_000));
847
+ if (options.json)
848
+ printJson(created);
849
+ else
850
+ printCreatedTask(created, "Task created");
851
+ return;
852
+ }
853
+ if (action === "fork") {
854
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
855
+ const options = target.args;
856
+ rejectUnsupportedTaskOptions(action, options, ["--json"]);
857
+ if (options.some((value) => !value.startsWith("--")))
858
+ throw new Error(cliText("task fork accepts only one task reference.", "task fork 只接受一个任务引用。"));
859
+ const result = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
860
+ action: "task_fork",
861
+ localSessionId: target.session.localSessionId,
862
+ }, 120_000));
863
+ if (options.includes("--json"))
864
+ printJson(result);
865
+ else {
866
+ printCreatedTask(result.task, "Fork created");
867
+ process.stdout.write(`${cliText("Source turn: ", "来源 Turn:")}${result.sourceTurnId}\n`);
868
+ }
869
+ return;
870
+ }
871
+ if (action === "archive") {
872
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
873
+ const options = target.args;
874
+ rejectUnsupportedTaskOptions(action, options, ["--json"]);
875
+ if (options.some((value) => !value.startsWith("--")))
876
+ throw new Error(cliText("task archive accepts only one task reference.", "task archive 只接受一个任务引用。"));
877
+ const archived = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
878
+ action: "task_archive",
879
+ localSessionId: target.session.localSessionId,
880
+ }, 60_000));
881
+ if (options.includes("--json"))
882
+ printJson(archived);
883
+ else
884
+ process.stdout.write(cliText(`Task archived: ${archived.title} (${archived.remoteSessionId})\n`, `任务已归档:${archived.title}(${archived.remoteSessionId})\n`));
885
+ return;
886
+ }
887
+ if (action === "switch") {
888
+ const targetSelection = resolveTaskCommandTarget(allSessions, rest, action, { preferCurrent: true });
889
+ const switchArgs = targetSelection.args;
890
+ rejectUnsupportedTaskOptions(action, switchArgs, ["--json", "--previous"]);
891
+ const previous = switchArgs.includes("--previous");
892
+ const targetReferences = switchArgs.filter((value) => !value.startsWith("--"));
893
+ if (targetReferences.length > 1)
894
+ throw new Error(cliText("task switch accepts at most one target task.", "task switch 最多接受一个目标任务。"));
895
+ const targetReference = targetReferences[0];
896
+ if (previous && targetReference)
897
+ throw new Error(cliText("task switch cannot combine a target task with --previous.", "task switch 不能同时指定目标任务和 --previous。"));
898
+ const target = targetReference ? resolveTask(allSessions, targetReference) : undefined;
899
+ const switched = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
900
+ action: "task_switch",
901
+ localSessionId: targetSelection.session.localSessionId,
902
+ ...(target ? { targetLocalSessionId: target.localSessionId } : {}),
903
+ ...(previous ? { previous: true } : {}),
904
+ }));
905
+ if (switchArgs.includes("--json"))
906
+ printJson(switched);
907
+ else
908
+ printCreatedTask(switched, "Current task");
909
+ return;
910
+ }
911
+ if (action === "queue" || action === "nosteer") {
912
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
913
+ const text = target.args.join(" ").trim();
914
+ if (!text)
915
+ throw new Error(cliText(`task ${action} requires a Prompt.`, `task ${action} 需要提示词。`));
916
+ const result = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), { action: "task_queue", localSessionId: target.session.localSessionId, text }));
917
+ process.stdout.write(cliText(`Prompt queued (${result.queued} waiting): ${result.promptId}\n`, `提示词已排队(当前 ${result.queued} 条):${result.promptId}\n`));
918
+ return;
919
+ }
920
+ if (action === "agent") {
921
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
922
+ const [agentName, ...options] = target.args;
923
+ rejectUnsupportedTaskOptions(action, [agentName, ...options].filter((value) => value !== undefined), ["--json"]);
924
+ if (options.some((value) => !value.startsWith("--")))
925
+ throw new Error(cliText("task agent accepts at most one Agent name.", "task agent 最多接受一个 Agent 名称。"));
926
+ const result = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
927
+ action: "task_agent",
928
+ localSessionId: target.session.localSessionId,
929
+ ...(agentName && !agentName.startsWith("--") ? { agentName } : {}),
930
+ }));
931
+ const json = options.includes("--json") || agentName === "--json";
932
+ if (json)
933
+ printJson(result);
934
+ else
935
+ printTaskAgentSettings(result);
936
+ return;
937
+ }
938
+ if (["provider", "model", "thinking", "permissions"].includes(action)) {
939
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
940
+ const [rawValue, ...options] = target.args;
941
+ rejectUnsupportedTaskOptions(action, [rawValue, ...options].filter((value) => value !== undefined), ["--json"]);
942
+ if (options.some((value) => !value.startsWith("--")))
943
+ throw new Error(cliText(`task ${action} accepts at most one value.`, `task ${action} 最多接受一个值。`));
944
+ const value = rawValue && !rawValue.startsWith("--") ? rawValue : undefined;
945
+ const json = options.includes("--json") || rawValue === "--json";
946
+ const result = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
947
+ action: "task_settings",
948
+ localSessionId: target.session.localSessionId,
949
+ ...(value ? { setting: action, value } : {}),
950
+ }, 60_000));
951
+ if (json)
952
+ printJson(result);
953
+ else
954
+ printTaskSettings(result, action);
955
+ return;
956
+ }
957
+ if (action === "goal") {
958
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
959
+ const goalArgs = target.args;
960
+ const json = goalArgs.includes("--json");
961
+ const args = goalArgs.filter((value) => value !== "--json");
962
+ rejectUnsupportedTaskOptions(action, goalArgs, ["--json"]);
963
+ const parsedGoal = parseTaskGoal(args);
964
+ const result = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
965
+ action: "task_goal",
966
+ localSessionId: target.session.localSessionId,
967
+ goalAction: parsedGoal.action,
968
+ ...(parsedGoal.objective ? { objective: parsedGoal.objective } : {}),
969
+ }));
970
+ if (json)
971
+ printJson(result);
972
+ else
973
+ printTaskGoal(result);
974
+ return;
975
+ }
976
+ if (action === "turns") {
977
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
978
+ const options = target.args;
979
+ rejectUnsupportedTaskOptions(action, options, ["--json"]);
980
+ if (options.some((value) => !value.startsWith("--")))
981
+ throw new Error(cliText("task turns accepts only one task reference.", "task turns 只接受一个任务引用。"));
982
+ const result = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
983
+ action: "task_turns",
984
+ localSessionId: target.session.localSessionId,
985
+ }, 60_000));
986
+ if (options.includes("--json"))
987
+ printJson(result);
988
+ else
989
+ printTaskTurns(result);
990
+ return;
991
+ }
992
+ if (action === "reset") {
993
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
994
+ const [turnId, ...options] = target.args;
995
+ if (!turnId || turnId.startsWith("--"))
996
+ throw new Error(cliText("task reset requires a Turn ID.", "task reset 需要 Turn ID。"));
997
+ rejectUnsupportedTaskOptions(action, options, ["--json"]);
998
+ if (options.some((value) => !value.startsWith("--")))
999
+ throw new Error(cliText("task reset accepts one task reference and one Turn ID.", "task reset 只接受一个任务引用和一个 Turn ID。"));
1000
+ const result = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1001
+ action: "task_reset",
1002
+ localSessionId: target.session.localSessionId,
1003
+ turnId,
1004
+ }, 120_000));
1005
+ if (options.includes("--json"))
1006
+ printJson(result);
1007
+ else
1008
+ process.stdout.write(cliText(`Task reset to turn ${turnId}.\n`, `任务已 Reset 到 Turn ${turnId}。\n`));
1009
+ return;
1010
+ }
1011
+ if (action === "mute") {
1012
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1013
+ const [rawMode, ...options] = target.args;
1014
+ rejectUnsupportedTaskOptions(action, [rawMode, ...options].filter((value) => value !== undefined), ["--json"]);
1015
+ if (options.some((value) => !value.startsWith("--")))
1016
+ throw new Error(cliText("task mute accepts at most one mode.", "task mute 最多接受一个模式。"));
1017
+ const mode = rawMode && !rawMode.startsWith("--") ? rawMode.toLowerCase() : "on";
1018
+ if (mode && mode !== "on" && mode !== "off")
1019
+ throw new Error(cliText("task mute accepts on or off.", "task mute 只接受 on 或 off。"));
1020
+ const result = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1021
+ action: "task_mute",
1022
+ localSessionId: target.session.localSessionId,
1023
+ enabled: mode === "on",
1024
+ }));
1025
+ const json = options.includes("--json") || rawMode === "--json";
1026
+ if (json)
1027
+ printJson(result);
1028
+ else
1029
+ process.stdout.write(cliText(`Mention-only mode: ${result.enabled ? "on" : "off"}\n`, `仅 @ 响应模式:${result.enabled ? "开启" : "关闭"}\n`));
1030
+ return;
1031
+ }
1032
+ if (action === "shell") {
1033
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1034
+ const command = target.args.join(" ").trim();
1035
+ if (!command)
1036
+ throw new Error(cliText("task shell requires a command.", "task shell 需要命令。"));
1037
+ const result = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1038
+ action: "task_shell",
1039
+ localSessionId: target.session.localSessionId,
1040
+ command,
1041
+ }, 130_000));
1042
+ printTaskShellResult(result);
1043
+ if (result.exitCode && result.exitCode !== 0)
1044
+ process.exitCode = result.exitCode;
1045
+ return;
1046
+ }
1047
+ if (action === "dir") {
1048
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1049
+ const parsedDirectory = parseTaskDirectoryArgs([target.session.localSessionId, ...target.args]);
1050
+ const result = controlData(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1051
+ action: "task_directory",
1052
+ localSessionId: target.session.localSessionId,
1053
+ ...(parsedDirectory.directory ? { directory: parsedDirectory.directory } : {}),
1054
+ ...(parsedDirectory.page !== undefined ? { page: parsedDirectory.page } : {}),
1055
+ }, 60_000));
1056
+ if (parsedDirectory.json)
1057
+ printJson(result);
1058
+ else
1059
+ printTaskDirectory(result);
1060
+ return;
1061
+ }
1062
+ if (action === "file") {
1063
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1064
+ const filePath = target.args.join(" ").trim();
1065
+ if (!filePath)
1066
+ throw new Error(cliText("task file requires a path.", "task file 需要文件路径。"));
1067
+ ensureOk(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1068
+ action: "task_send_file",
1069
+ localSessionId: target.session.localSessionId,
1070
+ filePath,
1071
+ }, 120_000));
1072
+ process.stdout.write(cliText("File sent to the task conversation.\n", "文件已发送到任务会话。\n"));
1073
+ return;
1074
+ }
1075
+ if (action === "restart") {
1076
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1077
+ const translated = target.args.flatMap((value) => value === "--force" ? ["--immediate"] : [value]);
1078
+ await serverCommand(["restart", "--task", target.session.localSessionId, ...translated]);
1079
+ return;
1080
+ }
647
1081
  if (action === "newgroup") {
648
- const options = parseTaskNewGroupOptions(rest);
649
- const session = resolveTask(allSessions, options.reference);
1082
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1083
+ const options = parseTaskNewGroupOptions([target.session.localSessionId, ...target.args]);
1084
+ const session = target.session;
650
1085
  const targetAgentName = options.agentName ?? session.agentName;
651
1086
  const targetAgent = config.agents[targetAgentName];
652
1087
  if (!targetAgent) {
@@ -672,8 +1107,9 @@ async function taskCommand(input) {
672
1107
  return;
673
1108
  }
674
1109
  if (action === "forkgroup") {
675
- const options = parseTaskForkGroupOptions(rest);
676
- const session = resolveTask(allSessions, options.reference);
1110
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1111
+ const options = parseTaskForkGroupOptions([target.session.localSessionId, ...target.args]);
1112
+ const session = target.session;
677
1113
  requireCliGroupUser(config.feishu.userOpenId);
678
1114
  const response = await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
679
1115
  action: "task_fork_group",
@@ -687,39 +1123,44 @@ async function taskCommand(input) {
687
1123
  printTaskGroupResult(result, "forkgroup");
688
1124
  return;
689
1125
  }
690
- const sessions = filterSessions(allSessions, rest);
691
- if (action === "list") {
1126
+ const sessions = filterSessions(allSessions, rest, action === "sessions" ? taskSessionsSearchTerm(rest) : undefined);
1127
+ if (action === "list" || action === "sessions") {
692
1128
  if (rest.includes("--json"))
693
1129
  printJson(sessions);
694
1130
  else
695
1131
  printTaskList(sessions);
696
1132
  return;
697
1133
  }
698
- const reference = rest.find((value) => !value.startsWith("--"));
699
- if (!reference)
700
- throw new Error(cliText(`task ${action} requires a task number or task ID.`, `task ${action} 需要任务序号或任务 ID。`));
701
- const session = resolveTask(sessions, reference);
1134
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1135
+ const session = target.session;
702
1136
  if (action === "chat") {
1137
+ rejectUnsupportedTaskOptions(action, target.args, ["--json"]);
1138
+ if (target.args.some((value) => !value.startsWith("--")))
1139
+ throw new Error(cliText("task chat accepts only one task reference.", "task chat 只接受一个任务引用。"));
703
1140
  const route = taskChatRoute(session);
704
- if (rest.includes("--json"))
1141
+ if (target.args.includes("--json"))
705
1142
  printJson(route);
706
1143
  else
707
1144
  process.stdout.write(`${route.chatId}\n`);
708
1145
  return;
709
1146
  }
710
1147
  if (action === "status") {
711
- await outputTaskStatus(config.storage.sqlitePath, store, session, rest.includes("--json"));
1148
+ rejectUnsupportedTaskOptions(action, target.args, ["--json"]);
1149
+ if (target.args.some((value) => !value.startsWith("--")))
1150
+ throw new Error(cliText("task status accepts only one task reference.", "task status 只接受一个任务引用。"));
1151
+ await outputTaskStatus(config.storage.sqlitePath, store, session, target.args.includes("--json"));
712
1152
  return;
713
1153
  }
714
1154
  const endpoint = controlEndpoint(config.storage.sqlitePath);
715
1155
  if (action === "stop") {
1156
+ if (target.args.length > 0)
1157
+ throw new Error(cliText("task stop accepts only one task reference.", "task stop 只接受一个任务引用。"));
716
1158
  ensureOk(await sendControlRequest(endpoint, { action: "task_stop", localSessionId: session.localSessionId }));
717
1159
  process.stdout.write(cliText("Task stop requested.\n", "已请求停止任务。\n"));
718
1160
  return;
719
1161
  }
720
1162
  if (action === "title") {
721
- const referenceIndex = rest.indexOf(reference);
722
- const titleArgs = rest.slice(referenceIndex + 1);
1163
+ const titleArgs = target.args;
723
1164
  const optionIndex = titleArgs.findIndex((value) => value.startsWith("--"));
724
1165
  const title = titleArgs.slice(0, optionIndex < 0 ? undefined : optionIndex).join(" ").trim();
725
1166
  if (!title)
@@ -738,15 +1179,151 @@ async function taskCommand(input) {
738
1179
  store.close();
739
1180
  }
740
1181
  }
741
- function resolveCurrentTaskFromEnvironment(sessions) {
742
- const resolution = resolveCurrentTask(sessions, currentAppServerThreadIds(process.env));
743
- if (resolution.status === "found")
744
- return resolution.session;
745
- if (resolution.status === "missing-thread-id")
746
- throw new Error(cliText("The current task cannot be detected because neither CODEX_THREAD_ID nor TRAECLI_THREAD_ID is set. Run this command from a Codex or TraeX task.", "无法检测当前任务:CODEX_THREAD_ID 和 TRAECLI_THREAD_ID 均未设置。请从 Codex 或 TraeX 任务中运行此命令。"));
747
- if (resolution.status === "not-found")
748
- throw new Error(cliText(`No AgentBot task matches the current App Server Thread ID (${resolution.threadIds.join(", ")}). Check that the correct Profile is selected.`, `没有 AgentBot 任务匹配当前 App Server Thread ID(${resolution.threadIds.join(",")})。请检查是否选择了正确的 Profile。`));
749
- throw new Error(cliText("The current task is ambiguous because multiple injected Thread IDs match AgentBot tasks. Use task status <task> with an explicit AgentBot task ID.", "当前任务存在歧义:多个注入的 Thread ID 匹配到了 AgentBot 任务。请使用明确的 AgentBot 任务 ID 执行 task status <任务>。"));
1182
+ function requireTaskCommandReference(action, reference) {
1183
+ if (reference && !reference.startsWith("--"))
1184
+ return;
1185
+ throw new Error(cliText(`task ${action} requires a task number or task ID.`, `task ${action} 需要任务序号或任务 ID。`));
1186
+ }
1187
+ function rejectUnsupportedTaskOptions(action, args, supported) {
1188
+ const unsupported = args.find((value) => value.startsWith("--") && !supported.includes(value));
1189
+ if (!unsupported)
1190
+ return;
1191
+ throw new Error(cliText(`task ${action} does not support option: ${unsupported}.`, `task ${action} 不支持参数:${unsupported}。`));
1192
+ }
1193
+ function parseTaskGoal(args) {
1194
+ if (args.length === 0 || args[0]?.toLowerCase() === "show")
1195
+ return { action: "show" };
1196
+ const first = args[0].toLowerCase();
1197
+ if (first === "pause" || first === "resume" || first === "clear") {
1198
+ if (args.length > 1)
1199
+ throw new Error(cliText(`task goal ${first} does not accept extra arguments.`, `task goal ${first} 不接受额外参数。`));
1200
+ return { action: first };
1201
+ }
1202
+ const action = first === "set" || first === "edit" ? first : "set";
1203
+ const objective = (action === "set" && first !== "set" ? args : args.slice(1)).join(" ").trim();
1204
+ if (!objective)
1205
+ throw new Error(cliText(`task goal ${action} requires an objective.`, `task goal ${action} 需要目标。`));
1206
+ return { action, objective };
1207
+ }
1208
+ function parseTaskDirectoryArgs(args) {
1209
+ const positionals = [];
1210
+ let page;
1211
+ let json = false;
1212
+ for (let index = 0; index < args.length; index += 1) {
1213
+ const value = args[index];
1214
+ if (value === "--json") {
1215
+ json = true;
1216
+ continue;
1217
+ }
1218
+ if (value === "--page") {
1219
+ const rawPage = args[index + 1];
1220
+ const parsedPage = rawPage === undefined ? Number.NaN : Number(rawPage);
1221
+ if (!Number.isSafeInteger(parsedPage) || parsedPage < 1)
1222
+ throw new Error(cliText("task dir --page requires a positive page number.", "task dir --page 需要正整数页码。"));
1223
+ page = parsedPage - 1;
1224
+ index += 1;
1225
+ continue;
1226
+ }
1227
+ if (value.startsWith("--"))
1228
+ throw new Error(cliText(`task dir does not support option: ${value}.`, `task dir 不支持参数:${value}。`));
1229
+ positionals.push(value);
1230
+ }
1231
+ const [reference, ...directoryParts] = positionals;
1232
+ requireTaskCommandReference("dir", reference);
1233
+ return {
1234
+ reference,
1235
+ directory: directoryParts.join(" ").trim() || undefined,
1236
+ page,
1237
+ json,
1238
+ };
1239
+ }
1240
+ function controlData(response) {
1241
+ ensureOk(response);
1242
+ if (response.data === undefined)
1243
+ throw new Error(cliText("Agent Bot returned no control result.", "Agent Bot 没有返回控制结果。"));
1244
+ return response.data;
1245
+ }
1246
+ function printCreatedTask(session, label) {
1247
+ process.stdout.write(`${cliText(`${label}: `, `${label === "Current task" ? "当前任务" : label === "Fork created" ? "分支任务已创建" : "任务已创建"}:`)}${session.title ?? "-"}\n`);
1248
+ process.stdout.write(`${cliText("Local ID: ", "本地 ID:")}${session.localSessionId}\n`);
1249
+ if (session.remoteSessionId)
1250
+ process.stdout.write(`${cliText("Agent task ID: ", "Agent 任务 ID:")}${session.remoteSessionId}\n`);
1251
+ process.stdout.write(`${cliText("Agent: ", "Agent:")}${session.agentName}\n`);
1252
+ process.stdout.write(`${cliText("Directory: ", "目录:")}${session.cwd}\n`);
1253
+ }
1254
+ function printTaskAgentSettings(result) {
1255
+ process.stdout.write(`${cliText("Default Agent: ", "默认 Agent:")}${result.current}\n`);
1256
+ process.stdout.write(`${cliText("Available Agents: ", "可用 Agent:")}${result.agents.map((agent) => `${agent.name} (${agent.title})`).join(", ")}\n`);
1257
+ }
1258
+ function printTaskSettings(result, setting) {
1259
+ const session = result.session;
1260
+ process.stdout.write(`${cliText("Provider: ", "Provider:")}${session.modelProvider ?? cliText("Agent default", "Agent 默认")}\n`);
1261
+ process.stdout.write(`${cliText("Model: ", "模型:")}${session.model ?? cliText("default", "默认")}\n`);
1262
+ process.stdout.write(`${cliText("Thinking: ", "思考强度:")}${session.reasoningEffort ?? cliText("automatic", "自动")}\n`);
1263
+ process.stdout.write(`${cliText("Permissions: ", "权限:")}${session.permissionMode ?? "auto"}\n`);
1264
+ if (setting === "provider") {
1265
+ process.stdout.write(`${cliText("Available Providers: ", "可用 Provider:")}${result.providers.map((item) => item.id).join(", ") || "-"}\n`);
1266
+ }
1267
+ else if (setting === "model") {
1268
+ process.stdout.write(`${cliText("Available Models: ", "可用模型:")}${result.models.map((item) => item.id).join(", ") || "-"}\n`);
1269
+ }
1270
+ else if (setting === "thinking") {
1271
+ process.stdout.write(`${cliText("Available Thinking Levels: ", "可用思考强度:")}${result.reasoningOptions.map((item) => item.value).join(", ") || "-"}\n`);
1272
+ }
1273
+ else {
1274
+ process.stdout.write(`${cliText("Available Permission Modes: ", "可用权限模式:")}${result.permissionModes.join(", ")}\n`);
1275
+ }
1276
+ }
1277
+ function printTaskGoal(result) {
1278
+ if (!result.goal) {
1279
+ process.stdout.write(result.cleared
1280
+ ? cliText("Goal cleared.\n", "Goal 已清除。\n")
1281
+ : cliText("No Goal is configured.\n", "当前没有 Goal。\n"));
1282
+ return;
1283
+ }
1284
+ process.stdout.write(`${cliText("Goal: ", "Goal:")}${result.goal.objective}\n`);
1285
+ process.stdout.write(`${cliText("Status: ", "状态:")}${result.goal.status}\n`);
1286
+ process.stdout.write(`${cliText("Tokens used: ", "已用 Tokens:")}${result.goal.tokensUsed}\n`);
1287
+ process.stdout.write(`${cliText("Time used: ", "已用时间:")}${result.goal.timeUsedSeconds}s\n`);
1288
+ }
1289
+ function printTaskTurns(result) {
1290
+ process.stdout.write(`${cliText("Task: ", "任务:")}${result.session.title ?? result.session.localSessionId}\n`);
1291
+ if (result.turns.length === 0) {
1292
+ process.stdout.write(cliText("No completed turns.\n", "没有已完成的 Turn。\n"));
1293
+ return;
1294
+ }
1295
+ for (const turn of result.turns) {
1296
+ const marker = turn.current ? "*" : " ";
1297
+ const parent = turn.parentTurnId ? ` <- ${turn.parentTurnId}` : "";
1298
+ process.stdout.write(`${marker} ${turn.sequence}. ${turn.prompt?.replace(/\s+/g, " ").trim() || "-"}\n`);
1299
+ process.stdout.write(` ${turn.turnId}${parent}\n`);
1300
+ }
1301
+ }
1302
+ function printTaskShellResult(result) {
1303
+ if (result.stdout)
1304
+ process.stdout.write(result.stdout);
1305
+ if (result.stderr)
1306
+ process.stderr.write(result.stderr);
1307
+ if (!result.stdout && !result.stderr)
1308
+ process.stdout.write(cliText("(no output)\n", "(无输出)\n"));
1309
+ if (result.timedOut)
1310
+ process.stderr.write(cliText("Command timed out after 120 seconds.\n", "命令在 120 秒后超时。\n"));
1311
+ }
1312
+ function printTaskDirectory(result) {
1313
+ process.stdout.write(`${cliText("Directory: ", "目录:")}${result.directory}\n`);
1314
+ if (result.parentDirectory)
1315
+ process.stdout.write(` [D] .. -> ${result.parentDirectory}\n`);
1316
+ for (const entry of result.entries) {
1317
+ const marker = entry.kind === "directory" || entry.kind === "drive"
1318
+ ? "D"
1319
+ : entry.kind === "image"
1320
+ ? "I"
1321
+ : entry.kind === "binary"
1322
+ ? "B"
1323
+ : "F";
1324
+ process.stdout.write(` [${marker}] ${entry.name}\n`);
1325
+ }
1326
+ process.stdout.write(cliText(`Page ${result.page + 1}/${result.totalPages}; ${result.totalEntries} entries.\n`, `第 ${result.page + 1}/${result.totalPages} 页;共 ${result.totalEntries} 项。\n`));
750
1327
  }
751
1328
  async function outputTaskStatus(sqlitePath, store, session, json) {
752
1329
  const endpoint = controlEndpoint(sqlitePath);
@@ -775,31 +1352,35 @@ async function outputTaskStatus(sqlitePath, store, session, json) {
775
1352
  else
776
1353
  printTaskStatus(statusSession, snapshot, live?.remote);
777
1354
  }
778
- function filterSessions(sessions, args) {
1355
+ function filterSessions(sessions, args, aliasSearchTerm) {
779
1356
  const context = optionValue(args, "--context");
780
1357
  const status = optionValue(args, "--status");
1358
+ const search = (optionValue(args, "--search") ?? aliasSearchTerm)?.trim().toLowerCase();
781
1359
  return sessions.filter((session) => (!context || session.contextKey === context)
782
- && (!status || session.status === status || session.lastTurnStatus === status));
1360
+ && (!status || session.status === status || session.lastTurnStatus === status)
1361
+ && (!search || [
1362
+ session.localSessionId,
1363
+ session.remoteSessionId,
1364
+ session.acpSessionId,
1365
+ session.title,
1366
+ session.agentName,
1367
+ session.cwd,
1368
+ ].some((value) => value?.toLowerCase().includes(search))));
783
1369
  }
784
- function resolveTask(sessions, reference) {
785
- if (/^\d+$/.test(reference)) {
786
- const index = Number(reference) - 1;
787
- const session = sessions[index];
788
- if (!session)
789
- throw new Error(cliText(`Task number is out of range: ${reference}`, `任务序号超出范围:${reference}`));
790
- return session;
791
- }
792
- const exact = sessions.find((session) => session.localSessionId === reference || session.remoteSessionId === reference || session.acpSessionId === reference);
793
- if (exact)
794
- return exact;
795
- const matches = sessions.filter((session) => session.localSessionId.startsWith(reference)
796
- || session.remoteSessionId?.startsWith(reference)
797
- || session.acpSessionId?.startsWith(reference));
798
- if (matches.length === 1)
799
- return matches[0];
800
- if (matches.length > 1)
801
- throw new Error(cliText(`Task ID prefix is ambiguous: ${reference}`, `任务 ID 前缀不唯一:${reference}`));
802
- throw new Error(cliText(`Task not found: ${reference}`, `未找到任务:${reference}`));
1370
+ function taskSessionsSearchTerm(args) {
1371
+ const positionals = [];
1372
+ for (let index = 0; index < args.length; index += 1) {
1373
+ const value = args[index];
1374
+ if (value === "--json")
1375
+ continue;
1376
+ if (value === "--context" || value === "--status" || value === "--search") {
1377
+ index += 1;
1378
+ continue;
1379
+ }
1380
+ if (!value.startsWith("--"))
1381
+ positionals.push(value);
1382
+ }
1383
+ return positionals.join(" ").trim() || undefined;
803
1384
  }
804
1385
  function printTaskList(sessions) {
805
1386
  process.stdout.write(formatTaskList(sessions, cliLanguage, currentAppServerThreadIds(process.env)));
@@ -864,6 +1445,10 @@ function printInitializationResult(result) {
864
1445
  process.stdout.write(cliText("\nAgent Bot initialization completed.\n", "\nAgent Bot 初始化完成。\n"));
865
1446
  process.stdout.write(`${cliText("Configured Agents: ", "已配置 Agent:")}${result.configuredAgents.join(", ")}\n`);
866
1447
  process.stdout.write(`${cliText("Default Agent: ", "默认 Agent:")}${result.defaultAgent.name}\n`);
1448
+ process.stdout.write(cliText(`Group message response: ${result.groupMessages.mode === "all" ? "receive all group messages" : "require an explicit @ mention"}\n`, `群消息响应方式:${result.groupMessages.mode === "all" ? "接收所有群消息" : "必须明确 @ 机器人"}\n`));
1449
+ process.stdout.write(result.skill.updated
1450
+ ? `${cliText("Agent Bot Skill: installed or updated at ", "Agent Bot Skill:已安装或更新至 ")}${result.skill.status.targetPath}\n`
1451
+ : `${cliText("Agent Bot Skill: already up to date at ", "Agent Bot Skill:已是最新版本,位于 ")}${result.skill.status.targetPath}\n`);
867
1452
  if (result.feishu.status === "created") {
868
1453
  process.stdout.write(cliText(`Lark app: created and credentials saved (${result.feishu.appId})\n`, `飞书应用:已创建并保存凭据 (${result.feishu.appId})\n`));
869
1454
  }