@keyou007/agent-bot 0.1.14-alpha.0 → 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 (113) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +82 -55
  3. package/README.zh.md +82 -55
  4. package/config.example.yaml +5 -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 +16 -5
  29. package/dist/codex/CodexRuntime.js.map +1 -1
  30. package/dist/commands/CommandRouter.js +20 -1
  31. package/dist/commands/CommandRouter.js.map +1 -1
  32. package/dist/config/loadConfig.js +2 -0
  33. package/dist/config/loadConfig.js.map +1 -1
  34. package/dist/config/schema.js +2 -0
  35. package/dist/config/schema.js.map +1 -1
  36. package/dist/console/ConsoleTurnPresenter.js +2 -0
  37. package/dist/console/ConsoleTurnPresenter.js.map +1 -1
  38. package/dist/feishu/CardRenderer.js +584 -16
  39. package/dist/feishu/CardRenderer.js.map +1 -1
  40. package/dist/feishu/FeishuConnector.js +29 -8
  41. package/dist/feishu/FeishuConnector.js.map +1 -1
  42. package/dist/feishu/FeishuMarkdown.js +25 -1
  43. package/dist/feishu/FeishuMarkdown.js.map +1 -1
  44. package/dist/feishu/FeishuMessageClient.js +43 -0
  45. package/dist/feishu/FeishuMessageClient.js.map +1 -1
  46. package/dist/feishu/FeishuTurnPresenter.js +29 -1
  47. package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
  48. package/dist/feishu/ownerAccess.js +7 -0
  49. package/dist/feishu/ownerAccess.js.map +1 -0
  50. package/dist/index.js +148 -19
  51. package/dist/index.js.map +1 -1
  52. package/dist/presentation/OutboundRouter.js +19 -1
  53. package/dist/presentation/OutboundRouter.js.map +1 -1
  54. package/dist/proxy/ProxySessionController.js +1444 -66
  55. package/dist/proxy/ProxySessionController.js.map +1 -1
  56. package/dist/startup/StartupNotifier.js +65 -16
  57. package/dist/startup/StartupNotifier.js.map +1 -1
  58. package/dist/startup/startFeishu.js +2 -2
  59. package/dist/startup/startFeishu.js.map +1 -1
  60. package/dist/state/StateStore.js +268 -67
  61. package/dist/state/StateStore.js.map +1 -1
  62. package/dist/state/migrations.js +26 -0
  63. package/dist/state/migrations.js.map +1 -1
  64. package/dist/supervision/SafeRestartNotifier.js +17 -16
  65. package/dist/supervision/SafeRestartNotifier.js.map +1 -1
  66. package/dist/supervision/SafeRestartScheduler.js +12 -7
  67. package/dist/supervision/SafeRestartScheduler.js.map +1 -1
  68. package/dist/supervision/replacementSupervisor.js +45 -4
  69. package/dist/supervision/replacementSupervisor.js.map +1 -1
  70. package/dist/supervisor.js +30 -18
  71. package/dist/supervisor.js.map +1 -1
  72. package/docs/technical-reference.md +33 -10
  73. package/docs/technical-reference.zh.md +33 -10
  74. package/npm-shrinkwrap.json +2 -2
  75. package/package.json +2 -2
  76. package/skills/agent-bot/SKILL.md +107 -182
  77. package/src/cli/AutostartManager.ts +579 -0
  78. package/src/cli/FeishuAppConfiguration.ts +22 -7
  79. package/src/cli/Initializer.ts +57 -0
  80. package/src/cli/SelfUpdateRunner.ts +817 -0
  81. package/src/cli/SelfUpdater.ts +688 -0
  82. package/src/cli/autostartOutput.ts +75 -0
  83. package/src/cli/controlProtocol.ts +111 -1
  84. package/src/cli/help.ts +92 -18
  85. package/src/cli/taskGroupOptions.ts +27 -17
  86. package/src/cli/taskListOutput.ts +10 -0
  87. package/src/cli/taskTarget.ts +168 -0
  88. package/src/cli.ts +781 -65
  89. package/src/codex/CodexRuntime.ts +17 -5
  90. package/src/commands/CommandRouter.ts +19 -1
  91. package/src/commands/commandTypes.ts +3 -0
  92. package/src/config/loadConfig.ts +2 -0
  93. package/src/config/schema.ts +2 -0
  94. package/src/console/ConsoleTurnPresenter.ts +4 -1
  95. package/src/feishu/CardRenderer.ts +716 -16
  96. package/src/feishu/FeishuConnector.ts +44 -10
  97. package/src/feishu/FeishuMarkdown.ts +27 -1
  98. package/src/feishu/FeishuMessageClient.ts +58 -0
  99. package/src/feishu/FeishuTurnPresenter.ts +35 -2
  100. package/src/feishu/ownerAccess.ts +7 -0
  101. package/src/feishu/types.ts +7 -0
  102. package/src/index.ts +192 -22
  103. package/src/presentation/OutboundRouter.ts +28 -3
  104. package/src/proxy/ProxySessionController.ts +1707 -70
  105. package/src/runtime/types.ts +1 -0
  106. package/src/startup/StartupNotifier.ts +67 -17
  107. package/src/startup/startFeishu.ts +5 -3
  108. package/src/state/StateStore.ts +413 -65
  109. package/src/state/migrations.ts +26 -0
  110. package/src/supervision/SafeRestartNotifier.ts +17 -17
  111. package/src/supervision/SafeRestartScheduler.ts +13 -6
  112. package/src/supervision/replacementSupervisor.ts +50 -4
  113. package/src/supervisor.ts +35 -16
package/src/cli.ts CHANGED
@@ -8,8 +8,16 @@ import { sendControlRequest, isServerReachable } from "./cli/LocalControlClient.
8
8
  import {
9
9
  controlEndpoint,
10
10
  type ControlResponse,
11
+ type TaskAgentControlData,
12
+ type TaskDirectoryControlData,
13
+ type TaskForkControlData,
14
+ type TaskGoalControlData,
11
15
  type TaskGroupControlData,
16
+ type TaskMuteControlData,
17
+ type TaskSettingsControlData,
18
+ type TaskShellControlData,
12
19
  type TaskStatusControlData,
20
+ type TaskTurnsControlData,
13
21
  } from "./cli/controlProtocol.js";
14
22
  import {
15
23
  acquireInitializationLock,
@@ -17,10 +25,14 @@ import {
17
25
  initializeAgentBot,
18
26
  readConfiguredAgentSelection,
19
27
  readFeishuCredentials,
28
+ readGroupMessageResponseMode,
20
29
  shouldCreateFeishuApp,
21
30
  shouldConfigureAgentsDuringInitialization,
31
+ shouldConfigureGroupMessagesDuringInitialization,
22
32
  writeConfiguredAgentSelection,
23
33
  writeFeishuCredentials,
34
+ writeGroupMessageResponseMode,
35
+ type GroupMessageResponseMode,
24
36
  type InitializationResult,
25
37
  type InitializationStatus,
26
38
  } from "./cli/Initializer.js";
@@ -77,13 +89,18 @@ import { taskChatRoute } from "./cli/taskChatRoute.js";
77
89
  import {
78
90
  currentAppServerThreadIds,
79
91
  formatTaskList,
80
- resolveCurrentTask,
81
92
  taskStateLabel,
82
93
  } from "./cli/taskListOutput.js";
94
+ import {
95
+ resolveCurrentTaskFromEnvironment,
96
+ resolveTask,
97
+ resolveTaskCommandTarget,
98
+ } from "./cli/taskTarget.js";
83
99
  import { isThreadContextKey } from "./feishu/contextKey.js";
84
100
  import { refreshedSystemEnvironment } from "./supervision/systemEnvironment.js";
85
101
  import {
86
102
  parseTaskForkGroupOptions,
103
+ parseTaskNewOptions,
87
104
  parseTaskNewGroupOptions,
88
105
  } from "./cli/taskGroupOptions.js";
89
106
  import { StateStore, type SessionRecord } from "./state/StateStore.js";
@@ -93,6 +110,20 @@ import {
93
110
  resolveSupervisorDiagnosticsPaths,
94
111
  } from "./supervision/SupervisorDiagnostics.js";
95
112
  import { agentBotHome, defaultSqlitePath } from "./config/paths.js";
113
+ import {
114
+ finalizeSelfUpdatePlan,
115
+ launchSelfUpdateRunner,
116
+ parseSelfUpdateOptions,
117
+ prepareSelfUpdate,
118
+ releaseSelfUpdatePlan,
119
+ requireNpmSelfUpdateInstallation,
120
+ } from "./cli/SelfUpdater.js";
121
+ import { createCurrentAutostartManager } from "./cli/AutostartManager.js";
122
+ import {
123
+ formatAutostartStatus,
124
+ type AutostartServerStatus,
125
+ type CombinedAutostartStatus,
126
+ } from "./cli/autostartOutput.js";
96
127
 
97
128
  const args = process.argv.slice(2);
98
129
 
@@ -119,6 +150,10 @@ async function main(input: string[]): Promise<void> {
119
150
  await initCommand(rest, parsed.configPath, Boolean(parsed.profilePath));
120
151
  return;
121
152
  }
153
+ if (command === "update") {
154
+ await updateCommand(rest);
155
+ return;
156
+ }
122
157
  if (command === "console") {
123
158
  await consoleCommand(rest);
124
159
  return;
@@ -141,6 +176,88 @@ async function main(input: string[]): Promise<void> {
141
176
  ));
142
177
  }
143
178
 
179
+ async function updateCommand(input: string[]): Promise<void> {
180
+ const options = parseSelfUpdateOptions(input);
181
+ requireNpmSelfUpdateInstallation();
182
+ const config = loadConfig();
183
+ const endpoint = controlEndpoint(config.storage.sqlitePath);
184
+ const serverRunning = await isServerReachable(endpoint);
185
+ if (!options.json) process.stdout.write(cliText(
186
+ "Checking the installed Agent Bot package and npm release channel...\n",
187
+ "正在检查已安装的 Agent Bot 包和 npm 发布通道……\n",
188
+ ));
189
+ const prepared = await prepareSelfUpdate({
190
+ channel: options.channel,
191
+ version: options.version,
192
+ allowDowngrade: options.allowDowngrade,
193
+ });
194
+ if (prepared.status === "current") {
195
+ if (options.json) printJson(prepared);
196
+ else process.stdout.write(cliText(
197
+ `Agent Bot ${prepared.currentVersion} is already current on the ${prepared.channel} channel.\n`,
198
+ `Agent Bot ${prepared.currentVersion} 已是 ${prepared.channel} 通道的当前版本。\n`,
199
+ ));
200
+ return;
201
+ }
202
+
203
+ const reason = cliText(
204
+ `Update Agent Bot from ${prepared.currentVersion} to ${prepared.targetVersion}`,
205
+ `将 Agent Bot 从 ${prepared.currentVersion} 更新到 ${prepared.targetVersion}`,
206
+ );
207
+ try {
208
+ const notificationSessionId = serverRunning
209
+ ? resolveRestartNotificationSessionId(
210
+ config.storage.sqlitePath,
211
+ options.taskReference ? ["--task", options.taskReference] : [],
212
+ )
213
+ : undefined;
214
+ finalizeSelfUpdatePlan(prepared.planPath, {
215
+ controlEndpoint: endpoint,
216
+ databasePath: config.storage.sqlitePath,
217
+ restartService: serverRunning,
218
+ workingDirectory: process.cwd(),
219
+ reason,
220
+ notificationSessionId,
221
+ });
222
+ if (serverRunning) {
223
+ ensureOk(await sendControlRequest(endpoint, {
224
+ action: "server_update",
225
+ planPath: prepared.planPath,
226
+ reason,
227
+ notificationSessionId,
228
+ }, 15_000));
229
+ } else {
230
+ launchSelfUpdateRunner(prepared.planPath, { waitPids: [process.pid] });
231
+ }
232
+ } catch (error) {
233
+ releaseSelfUpdatePlan(prepared.planPath);
234
+ throw error;
235
+ }
236
+
237
+ const result = {
238
+ status: serverRunning ? "scheduled" : "started",
239
+ fromVersion: prepared.currentVersion,
240
+ toVersion: prepared.targetVersion,
241
+ channel: prepared.channel,
242
+ serviceWasRunning: serverRunning,
243
+ logPath: prepared.logPath,
244
+ resultPath: prepared.resultPath,
245
+ };
246
+ if (options.json) printJson(result);
247
+ else {
248
+ process.stdout.write(serverRunning
249
+ ? cliText(
250
+ `Agent Bot ${prepared.targetVersion} is verified and ready. The update will run after active tasks finish and the server becomes idle.\n`,
251
+ `Agent Bot ${prepared.targetVersion} 已通过预检。更新将在活动任务完成且服务空闲后执行。\n`,
252
+ )
253
+ : cliText(
254
+ `Agent Bot ${prepared.targetVersion} is verified. Installation will continue after this command exits.\n`,
255
+ `Agent Bot ${prepared.targetVersion} 已通过预检。本命令退出后将继续安装。\n`,
256
+ ));
257
+ process.stdout.write(`${cliText("Update log: ", "更新日志:")}${prepared.logPath}\n`);
258
+ }
259
+ }
260
+
144
261
  type FeishuInitializationStatus = "created" | "existing" | "skipped";
145
262
 
146
263
  interface InitCommandResult extends InitializationResult {
@@ -150,12 +267,17 @@ interface InitCommandResult extends InitializationResult {
150
267
  name: string;
151
268
  status: "selected" | "existing";
152
269
  };
270
+ groupMessages: {
271
+ mode: GroupMessageResponseMode;
272
+ status: "selected" | "existing";
273
+ };
153
274
  feishu: {
154
275
  status: FeishuInitializationStatus;
155
276
  appId?: string;
156
277
  userOpenIdStatus?: "configured" | "pending";
157
278
  configuration?: EnsureFeishuAppConfigurationResult;
158
279
  };
280
+ skill: ReturnType<SkillRegistry["install"]>;
159
281
  server: InitializationServerResult;
160
282
  welcome: InitializationWelcomeResult;
161
283
  }
@@ -192,6 +314,11 @@ async function initCommand(
192
314
  options.json,
193
315
  shouldConfigureAgentsDuringInitialization(paths.config.status),
194
316
  );
317
+ const groupMessages = await configureGroupMessageResponse(
318
+ paths.config.path,
319
+ options.json,
320
+ shouldConfigureGroupMessagesDuringInitialization(paths.config.status),
321
+ );
195
322
  const configuredAgents = readConfiguredAgentSelection(paths.config.path).agents.map((agent) => agent.name);
196
323
  const configuredAgentNames = new Set(configuredAgents);
197
324
  const agents = inspectedAgents.map((agent) => ({
@@ -203,8 +330,10 @@ async function initCommand(
203
330
  const feishu = await initializeFeishu(
204
331
  paths,
205
332
  options,
333
+ groupMessages.mode === "all",
206
334
  );
207
- initialized = { ...paths, agents, configuredAgents, defaultAgent, feishu };
335
+ const skill = bundledSkillRegistry().install();
336
+ initialized = { ...paths, agents, configuredAgents, defaultAgent, groupMessages, feishu, skill };
208
337
  } finally {
209
338
  initializationLock?.release();
210
339
  }
@@ -415,6 +544,76 @@ async function configureAgentsAndDefault(
415
544
  return { name: selectedDefault.name, status: updated ? "selected" : "existing" };
416
545
  }
417
546
 
547
+ async function configureGroupMessageResponse(
548
+ configPath: string,
549
+ json: boolean,
550
+ configure: boolean,
551
+ ): Promise<InitCommandResult["groupMessages"]> {
552
+ const output = json ? process.stderr : process.stdout;
553
+ if (!configure) {
554
+ return { mode: readGroupMessageResponseMode(configPath), status: "existing" };
555
+ }
556
+
557
+ let mode: GroupMessageResponseMode = "mention-only";
558
+ if (process.stdin.isTTY) {
559
+ output.write(cliText(
560
+ "\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",
561
+ "\n请选择 Agent Bot 响应群消息的方式:\n 1. 接收所有群消息\n 无需 @ 机器人即可响应,需要额外开通飞书权限。\n 2. 明确 @ 机器人才能接收消息\n 不会申请“免 @ 机器人”所需的额外权限。\n",
562
+ ));
563
+ mode = await promptForGroupMessageResponseMode();
564
+ } else {
565
+ output.write(cliText(
566
+ "No interactive terminal is available. Group messages will require an explicit @ mention, and the all-group-message permission will not be requested.\n",
567
+ "当前没有交互式终端。群消息将要求明确 @ 机器人,并且不会申请接收所有群消息的额外权限。\n",
568
+ ));
569
+ }
570
+
571
+ writeGroupMessageResponseMode(configPath, mode);
572
+ output.write(cliText(
573
+ `Group message response: ${mode === "all" ? "receive all group messages" : "require an explicit @ mention"}\n`,
574
+ `群消息响应方式:${mode === "all" ? "接收所有群消息" : "必须明确 @ 机器人"}\n`,
575
+ ));
576
+ return { mode, status: "selected" };
577
+ }
578
+
579
+ function promptForGroupMessageResponseMode(): Promise<GroupMessageResponseMode> {
580
+ const readline = createInterface({ input: process.stdin, output: process.stderr });
581
+ return new Promise((resolve, reject) => {
582
+ let settled = false;
583
+ const finish = (mode: GroupMessageResponseMode): void => {
584
+ settled = true;
585
+ readline.close();
586
+ resolve(mode);
587
+ };
588
+ const ask = (): void => {
589
+ readline.question(cliText(
590
+ "Select 1 or 2 [2]: ",
591
+ "请选择 1 或 2 [2]:",
592
+ ), (answer) => {
593
+ const normalized = answer.trim().toLowerCase();
594
+ if (normalized === "1") {
595
+ finish("all");
596
+ return;
597
+ }
598
+ if (normalized === "" || normalized === "2") {
599
+ finish("mention-only");
600
+ return;
601
+ }
602
+ process.stderr.write(cliText(
603
+ "Enter 1 to receive all group messages, or 2 to require an @ mention.\n",
604
+ "请输入 1 接收所有群消息,或输入 2 要求明确 @ 机器人。\n",
605
+ ));
606
+ ask();
607
+ });
608
+ };
609
+ listenForPromptCancellation(readline, () => {
610
+ settled = true;
611
+ reject(new Error(cliText("Initialization was cancelled.", "初始化已取消。")));
612
+ }, () => settled);
613
+ ask();
614
+ });
615
+ }
616
+
418
617
  function promptForAgentMaintenance(choiceCount: number): Promise<number[]> {
419
618
  const readline = createInterface({ input: process.stdin, output: process.stderr });
420
619
  return new Promise((resolve, reject) => {
@@ -577,6 +776,7 @@ function refreshCurrentProcessPath(): void {
577
776
  async function initializeFeishu(
578
777
  paths: InitializationResult,
579
778
  options: InitCommandOptions,
779
+ respondToAllGroupMessages: boolean,
580
780
  ): Promise<InitCommandResult["feishu"]> {
581
781
  const existing = readFeishuCredentials(paths.env.path);
582
782
  if (options.skipFeishu) {
@@ -634,6 +834,7 @@ async function initializeFeishu(
634
834
  const optionalSkip = new AbortController();
635
835
  let skipListener: OptionalAuthorizationSkipListener | undefined;
636
836
  const configuration = await ensureFeishuAppConfiguration(credentials, {
837
+ respondToAllGroupMessages,
637
838
  signal: controller.signal,
638
839
  manualPermissionSkipSignal: manualPermissionSkip.signal,
639
840
  optionalSkipSignal: optionalSkip.signal,
@@ -686,9 +887,8 @@ async function assertResetProfileServerStopped(configPath?: string): Promise<voi
686
887
 
687
888
  function skillsCommand(input: string[]): void {
688
889
  const [action = "status", ...rest] = input;
689
- const sourcePath = fileURLToPath(new URL("../skills/agent-bot/", import.meta.url));
690
890
  const targetRoot = optionValue(rest, "--target") ?? resolveSystemSkillsRoot();
691
- const registry = new SkillRegistry(sourcePath, targetRoot);
891
+ const registry = bundledSkillRegistry(targetRoot);
692
892
  const json = rest.includes("--json");
693
893
 
694
894
  if (action === "install" || action === "register") {
@@ -738,6 +938,11 @@ function skillsCommand(input: string[]): void {
738
938
  throw new Error(cliText(`Unknown skills command: ${action}`, `未知的 skills 命令:${action}`));
739
939
  }
740
940
 
941
+ function bundledSkillRegistry(targetRoot = resolveSystemSkillsRoot()): SkillRegistry {
942
+ const sourcePath = fileURLToPath(new URL("../skills/agent-bot/", import.meta.url));
943
+ return new SkillRegistry(sourcePath, targetRoot);
944
+ }
945
+
741
946
  async function consoleCommand(input: string[]): Promise<void> {
742
947
  const config = loadConfig();
743
948
  const endpoint = controlEndpoint(config.storage.sqlitePath);
@@ -791,6 +996,10 @@ async function serverCommand(input: string[]): Promise<void> {
791
996
  printServerStartResult(await startServer(config));
792
997
  return;
793
998
  }
999
+ if (action === "autostart") {
1000
+ await serverAutostartCommand(config, endpoint, rest);
1001
+ return;
1002
+ }
794
1003
  if (action === "stop") {
795
1004
  ensureOk(await sendControlRequest(endpoint, { action: "server_stop" }));
796
1005
  process.stdout.write(cliText("Agent Bot server stop requested.\n", "已请求停止 Agent Bot 服务。\n"));
@@ -815,6 +1024,64 @@ async function serverCommand(input: string[]): Promise<void> {
815
1024
  throw new Error(cliText(`Unknown server command: ${action}`, `未知的 server 命令:${action}`));
816
1025
  }
817
1026
 
1027
+ async function serverAutostartCommand(
1028
+ config: ReturnType<typeof loadConfig>,
1029
+ endpoint: string,
1030
+ input: string[],
1031
+ ): Promise<void> {
1032
+ const actionIndex = input.findIndex((value) => !value.startsWith("--"));
1033
+ const action = actionIndex < 0 ? "status" : input[actionIndex]!;
1034
+ const rest = input.filter((_value, index) => index !== actionIndex);
1035
+ const json = rest.includes("--json");
1036
+ const linger = rest.includes("--linger");
1037
+ const unsupported = rest.filter((value) => value !== "--json" && value !== "--linger");
1038
+ if (unsupported.length > 0) throw new Error(cliText(
1039
+ `Unsupported server autostart options: ${unsupported.join(" ")}`,
1040
+ `不支持这些 server autostart 参数:${unsupported.join(" ")}`,
1041
+ ));
1042
+ if (action !== "enable" && linger) {
1043
+ throw new Error(cliText(
1044
+ "--linger can be used only with server autostart enable.",
1045
+ "--linger 只能与 server autostart enable 一起使用。",
1046
+ ));
1047
+ }
1048
+
1049
+ const manager = createCurrentAutostartManager(config);
1050
+ let registration;
1051
+ if (action === "enable") {
1052
+ registration = manager.enable({ linger });
1053
+ } else if (action === "disable") {
1054
+ registration = manager.disable();
1055
+ } else if (action === "status") {
1056
+ registration = manager.status();
1057
+ } else {
1058
+ throw new Error(cliText(
1059
+ `Unknown server autostart command: ${action}`,
1060
+ `未知的 server autostart 命令:${action}`,
1061
+ ));
1062
+ }
1063
+
1064
+ const result: CombinedAutostartStatus = {
1065
+ registration,
1066
+ server: await readAutostartServerStatus(endpoint),
1067
+ };
1068
+ if (json) printJson(result);
1069
+ else process.stdout.write(formatAutostartStatus(result));
1070
+ }
1071
+
1072
+ async function readAutostartServerStatus(endpoint: string): Promise<AutostartServerStatus> {
1073
+ try {
1074
+ const response = await sendControlRequest(endpoint, { action: "health" }, 1_500);
1075
+ if (!response.ok) return { running: true, ready: false };
1076
+ const data = response.data && typeof response.data === "object"
1077
+ ? response.data as Record<string, unknown>
1078
+ : {};
1079
+ return { running: true, ready: data.ready !== false };
1080
+ } catch {
1081
+ return { running: false, ready: false };
1082
+ }
1083
+ }
1084
+
818
1085
  function resolveRestartNotificationSessionId(sqlitePath: string, args: string[]): string | undefined {
819
1086
  const store = new StateStore(sqlitePath);
820
1087
  try {
@@ -857,6 +1124,10 @@ async function taskCommand(input: string[]): Promise<void> {
857
1124
  const store = new StateStore(config.storage.sqlitePath);
858
1125
  try {
859
1126
  const allSessions = store.listAllSessions();
1127
+ if (action === "help" || action === "--help" || action === "-h") {
1128
+ printHelp();
1129
+ return;
1130
+ }
860
1131
  if (action === "current") {
861
1132
  const unsupported = rest.filter((value) => value !== "--json");
862
1133
  if (unsupported.length > 0) throw new Error(cliText(
@@ -868,29 +1139,296 @@ async function taskCommand(input: string[]): Promise<void> {
868
1139
  return;
869
1140
  }
870
1141
  if (action === "prompt" || action === "send") {
871
- const [reference, ...promptParts] = rest;
872
- if (!reference) throw new Error(cliText(
873
- `task ${action} requires a task number or task ID.`,
874
- `task ${action} 需要任务序号或任务 ID。`,
875
- ));
876
- const text = promptParts.join(" ").trim();
1142
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1143
+ const text = target.args.join(" ").trim();
877
1144
  if (!text) throw new Error(cliText(
878
1145
  `task ${action} requires a Prompt.`,
879
1146
  `task ${action} 需要提示词。`,
880
1147
  ));
881
- const session = resolveTask(allSessions, reference);
882
1148
  const endpoint = controlEndpoint(config.storage.sqlitePath);
883
1149
  ensureOk(await sendControlRequest(endpoint, {
884
1150
  action: "task_prompt",
885
- localSessionId: session.localSessionId,
1151
+ localSessionId: target.session.localSessionId,
886
1152
  text,
887
1153
  }, 60_000));
888
1154
  process.stdout.write(cliText("Prompt submitted.\n", "提示词已提交。\n"));
889
1155
  return;
890
1156
  }
1157
+ if (action === "new") {
1158
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1159
+ const options = parseTaskNewOptions([target.session.localSessionId, ...target.args]);
1160
+ const source = target.session;
1161
+ const targetAgent = options.agentName ? config.agents[options.agentName] : undefined;
1162
+ if (options.agentName && !targetAgent) throw new Error(cliText(
1163
+ `Unknown Agent standard name: ${options.agentName}.`,
1164
+ `未知的 Agent 标准名:${options.agentName}。`,
1165
+ ));
1166
+ if (options.projectless && targetAgent && targetAgent.kind !== "app-server") throw new Error(cliText(
1167
+ "task new --nodir is only available for App Server agents.",
1168
+ "task new --nodir 仅适用于 App Server Agent。",
1169
+ ));
1170
+ const created = controlData<SessionRecord>(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1171
+ action: "task_new",
1172
+ localSessionId: source.localSessionId,
1173
+ ...(options.title ? { title: options.title } : {}),
1174
+ ...(options.cwd ? { cwd: options.cwd } : {}),
1175
+ ...(options.agentName ? { agentName: options.agentName } : {}),
1176
+ ...(options.projectless ? { projectless: true } : {}),
1177
+ }, 120_000));
1178
+ if (options.json) printJson(created);
1179
+ else printCreatedTask(created, "Task created");
1180
+ return;
1181
+ }
1182
+ if (action === "fork") {
1183
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1184
+ const options = target.args;
1185
+ rejectUnsupportedTaskOptions(action, options, ["--json"]);
1186
+ if (options.some((value) => !value.startsWith("--"))) throw new Error(cliText(
1187
+ "task fork accepts only one task reference.",
1188
+ "task fork 只接受一个任务引用。",
1189
+ ));
1190
+ const result = controlData<TaskForkControlData>(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1191
+ action: "task_fork",
1192
+ localSessionId: target.session.localSessionId,
1193
+ }, 120_000));
1194
+ if (options.includes("--json")) printJson(result);
1195
+ else {
1196
+ printCreatedTask(result.task, "Fork created");
1197
+ process.stdout.write(`${cliText("Source turn: ", "来源 Turn:")}${result.sourceTurnId}\n`);
1198
+ }
1199
+ return;
1200
+ }
1201
+ if (action === "archive") {
1202
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1203
+ const options = target.args;
1204
+ rejectUnsupportedTaskOptions(action, options, ["--json"]);
1205
+ if (options.some((value) => !value.startsWith("--"))) throw new Error(cliText(
1206
+ "task archive accepts only one task reference.",
1207
+ "task archive 只接受一个任务引用。",
1208
+ ));
1209
+ const archived = controlData<{
1210
+ localSessionId: string;
1211
+ remoteSessionId: string;
1212
+ title: string;
1213
+ }>(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1214
+ action: "task_archive",
1215
+ localSessionId: target.session.localSessionId,
1216
+ }, 60_000));
1217
+ if (options.includes("--json")) printJson(archived);
1218
+ else process.stdout.write(cliText(
1219
+ `Task archived: ${archived.title} (${archived.remoteSessionId})\n`,
1220
+ `任务已归档:${archived.title}(${archived.remoteSessionId})\n`,
1221
+ ));
1222
+ return;
1223
+ }
1224
+ if (action === "switch") {
1225
+ const targetSelection = resolveTaskCommandTarget(allSessions, rest, action, { preferCurrent: true });
1226
+ const switchArgs = targetSelection.args;
1227
+ rejectUnsupportedTaskOptions(action, switchArgs, ["--json", "--previous"]);
1228
+ const previous = switchArgs.includes("--previous");
1229
+ const targetReferences = switchArgs.filter((value) => !value.startsWith("--"));
1230
+ if (targetReferences.length > 1) throw new Error(cliText(
1231
+ "task switch accepts at most one target task.",
1232
+ "task switch 最多接受一个目标任务。",
1233
+ ));
1234
+ const targetReference = targetReferences[0];
1235
+ if (previous && targetReference) throw new Error(cliText(
1236
+ "task switch cannot combine a target task with --previous.",
1237
+ "task switch 不能同时指定目标任务和 --previous。",
1238
+ ));
1239
+ const target = targetReference ? resolveTask(allSessions, targetReference) : undefined;
1240
+ const switched = controlData<SessionRecord>(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1241
+ action: "task_switch",
1242
+ localSessionId: targetSelection.session.localSessionId,
1243
+ ...(target ? { targetLocalSessionId: target.localSessionId } : {}),
1244
+ ...(previous ? { previous: true } : {}),
1245
+ }));
1246
+ if (switchArgs.includes("--json")) printJson(switched);
1247
+ else printCreatedTask(switched, "Current task");
1248
+ return;
1249
+ }
1250
+ if (action === "queue" || action === "nosteer") {
1251
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1252
+ const text = target.args.join(" ").trim();
1253
+ if (!text) throw new Error(cliText(
1254
+ `task ${action} requires a Prompt.`,
1255
+ `task ${action} 需要提示词。`,
1256
+ ));
1257
+ const result = controlData<{ promptId: string; queued: number }>(await sendControlRequest(
1258
+ controlEndpoint(config.storage.sqlitePath),
1259
+ { action: "task_queue", localSessionId: target.session.localSessionId, text },
1260
+ ));
1261
+ process.stdout.write(cliText(
1262
+ `Prompt queued (${result.queued} waiting): ${result.promptId}\n`,
1263
+ `提示词已排队(当前 ${result.queued} 条):${result.promptId}\n`,
1264
+ ));
1265
+ return;
1266
+ }
1267
+ if (action === "agent") {
1268
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1269
+ const [agentName, ...options] = target.args;
1270
+ rejectUnsupportedTaskOptions(action, [agentName, ...options].filter((value): value is string => value !== undefined), ["--json"]);
1271
+ if (options.some((value) => !value.startsWith("--"))) throw new Error(cliText(
1272
+ "task agent accepts at most one Agent name.",
1273
+ "task agent 最多接受一个 Agent 名称。",
1274
+ ));
1275
+ const result = controlData<TaskAgentControlData>(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1276
+ action: "task_agent",
1277
+ localSessionId: target.session.localSessionId,
1278
+ ...(agentName && !agentName.startsWith("--") ? { agentName } : {}),
1279
+ }));
1280
+ const json = options.includes("--json") || agentName === "--json";
1281
+ if (json) printJson(result);
1282
+ else printTaskAgentSettings(result);
1283
+ return;
1284
+ }
1285
+ if (["provider", "model", "thinking", "permissions"].includes(action)) {
1286
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1287
+ const [rawValue, ...options] = target.args;
1288
+ rejectUnsupportedTaskOptions(action, [rawValue, ...options].filter((value): value is string => value !== undefined), ["--json"]);
1289
+ if (options.some((value) => !value.startsWith("--"))) throw new Error(cliText(
1290
+ `task ${action} accepts at most one value.`,
1291
+ `task ${action} 最多接受一个值。`,
1292
+ ));
1293
+ const value = rawValue && !rawValue.startsWith("--") ? rawValue : undefined;
1294
+ const json = options.includes("--json") || rawValue === "--json";
1295
+ const result = controlData<TaskSettingsControlData>(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1296
+ action: "task_settings",
1297
+ localSessionId: target.session.localSessionId,
1298
+ ...(value ? { setting: action as "provider" | "model" | "thinking" | "permissions", value } : {}),
1299
+ }, 60_000));
1300
+ if (json) printJson(result);
1301
+ else printTaskSettings(result, action as "provider" | "model" | "thinking" | "permissions");
1302
+ return;
1303
+ }
1304
+ if (action === "goal") {
1305
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1306
+ const goalArgs = target.args;
1307
+ const json = goalArgs.includes("--json");
1308
+ const args = goalArgs.filter((value) => value !== "--json");
1309
+ rejectUnsupportedTaskOptions(action, goalArgs, ["--json"]);
1310
+ const parsedGoal = parseTaskGoal(args);
1311
+ const result = controlData<TaskGoalControlData>(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1312
+ action: "task_goal",
1313
+ localSessionId: target.session.localSessionId,
1314
+ goalAction: parsedGoal.action,
1315
+ ...(parsedGoal.objective ? { objective: parsedGoal.objective } : {}),
1316
+ }));
1317
+ if (json) printJson(result);
1318
+ else printTaskGoal(result);
1319
+ return;
1320
+ }
1321
+ if (action === "turns") {
1322
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1323
+ const options = target.args;
1324
+ rejectUnsupportedTaskOptions(action, options, ["--json"]);
1325
+ if (options.some((value) => !value.startsWith("--"))) throw new Error(cliText(
1326
+ "task turns accepts only one task reference.",
1327
+ "task turns 只接受一个任务引用。",
1328
+ ));
1329
+ const result = controlData<TaskTurnsControlData>(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1330
+ action: "task_turns",
1331
+ localSessionId: target.session.localSessionId,
1332
+ }, 60_000));
1333
+ if (options.includes("--json")) printJson(result);
1334
+ else printTaskTurns(result);
1335
+ return;
1336
+ }
1337
+ if (action === "reset") {
1338
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1339
+ const [turnId, ...options] = target.args;
1340
+ if (!turnId || turnId.startsWith("--")) throw new Error(cliText(
1341
+ "task reset requires a Turn ID.",
1342
+ "task reset 需要 Turn ID。",
1343
+ ));
1344
+ rejectUnsupportedTaskOptions(action, options, ["--json"]);
1345
+ if (options.some((value) => !value.startsWith("--"))) throw new Error(cliText(
1346
+ "task reset accepts one task reference and one Turn ID.",
1347
+ "task reset 只接受一个任务引用和一个 Turn ID。",
1348
+ ));
1349
+ const result = controlData<SessionRecord>(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1350
+ action: "task_reset",
1351
+ localSessionId: target.session.localSessionId,
1352
+ turnId,
1353
+ }, 120_000));
1354
+ if (options.includes("--json")) printJson(result);
1355
+ else process.stdout.write(cliText(`Task reset to turn ${turnId}.\n`, `任务已 Reset 到 Turn ${turnId}。\n`));
1356
+ return;
1357
+ }
1358
+ if (action === "mute") {
1359
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1360
+ const [rawMode, ...options] = target.args;
1361
+ rejectUnsupportedTaskOptions(action, [rawMode, ...options].filter((value): value is string => value !== undefined), ["--json"]);
1362
+ if (options.some((value) => !value.startsWith("--"))) throw new Error(cliText(
1363
+ "task mute accepts at most one mode.",
1364
+ "task mute 最多接受一个模式。",
1365
+ ));
1366
+ const mode = rawMode && !rawMode.startsWith("--") ? rawMode.toLowerCase() : "on";
1367
+ if (mode && mode !== "on" && mode !== "off") throw new Error(cliText(
1368
+ "task mute accepts on or off.",
1369
+ "task mute 只接受 on 或 off。",
1370
+ ));
1371
+ const result = controlData<TaskMuteControlData>(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1372
+ action: "task_mute",
1373
+ localSessionId: target.session.localSessionId,
1374
+ enabled: mode === "on",
1375
+ }));
1376
+ const json = options.includes("--json") || rawMode === "--json";
1377
+ if (json) printJson(result);
1378
+ else process.stdout.write(cliText(
1379
+ `Mention-only mode: ${result.enabled ? "on" : "off"}\n`,
1380
+ `仅 @ 响应模式:${result.enabled ? "开启" : "关闭"}\n`,
1381
+ ));
1382
+ return;
1383
+ }
1384
+ if (action === "shell") {
1385
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1386
+ const command = target.args.join(" ").trim();
1387
+ if (!command) throw new Error(cliText("task shell requires a command.", "task shell 需要命令。"));
1388
+ const result = controlData<TaskShellControlData>(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1389
+ action: "task_shell",
1390
+ localSessionId: target.session.localSessionId,
1391
+ command,
1392
+ }, 130_000));
1393
+ printTaskShellResult(result);
1394
+ if (result.exitCode && result.exitCode !== 0) process.exitCode = result.exitCode;
1395
+ return;
1396
+ }
1397
+ if (action === "dir") {
1398
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1399
+ const parsedDirectory = parseTaskDirectoryArgs([target.session.localSessionId, ...target.args]);
1400
+ const result = controlData<TaskDirectoryControlData>(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1401
+ action: "task_directory",
1402
+ localSessionId: target.session.localSessionId,
1403
+ ...(parsedDirectory.directory ? { directory: parsedDirectory.directory } : {}),
1404
+ ...(parsedDirectory.page !== undefined ? { page: parsedDirectory.page } : {}),
1405
+ }, 60_000));
1406
+ if (parsedDirectory.json) printJson(result);
1407
+ else printTaskDirectory(result);
1408
+ return;
1409
+ }
1410
+ if (action === "file") {
1411
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1412
+ const filePath = target.args.join(" ").trim();
1413
+ if (!filePath) throw new Error(cliText("task file requires a path.", "task file 需要文件路径。"));
1414
+ ensureOk(await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
1415
+ action: "task_send_file",
1416
+ localSessionId: target.session.localSessionId,
1417
+ filePath,
1418
+ }, 120_000));
1419
+ process.stdout.write(cliText("File sent to the task conversation.\n", "文件已发送到任务会话。\n"));
1420
+ return;
1421
+ }
1422
+ if (action === "restart") {
1423
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1424
+ const translated = target.args.flatMap((value) => value === "--force" ? ["--immediate"] : [value]);
1425
+ await serverCommand(["restart", "--task", target.session.localSessionId, ...translated]);
1426
+ return;
1427
+ }
891
1428
  if (action === "newgroup") {
892
- const options = parseTaskNewGroupOptions(rest);
893
- const session = resolveTask(allSessions, options.reference);
1429
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1430
+ const options = parseTaskNewGroupOptions([target.session.localSessionId, ...target.args]);
1431
+ const session = target.session;
894
1432
  const targetAgentName = options.agentName ?? session.agentName;
895
1433
  const targetAgent = config.agents[targetAgentName];
896
1434
  if (!targetAgent) {
@@ -920,8 +1458,9 @@ async function taskCommand(input: string[]): Promise<void> {
920
1458
  return;
921
1459
  }
922
1460
  if (action === "forkgroup") {
923
- const options = parseTaskForkGroupOptions(rest);
924
- const session = resolveTask(allSessions, options.reference);
1461
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1462
+ const options = parseTaskForkGroupOptions([target.session.localSessionId, ...target.args]);
1463
+ const session = target.session;
925
1464
  requireCliGroupUser(config.feishu.userOpenId);
926
1465
  const response = await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
927
1466
  action: "task_fork_group",
@@ -933,37 +1472,50 @@ async function taskCommand(input: string[]): Promise<void> {
933
1472
  else printTaskGroupResult(result, "forkgroup");
934
1473
  return;
935
1474
  }
936
- const sessions = filterSessions(allSessions, rest);
937
- if (action === "list") {
1475
+ const sessions = filterSessions(
1476
+ allSessions,
1477
+ rest,
1478
+ action === "sessions" ? taskSessionsSearchTerm(rest) : undefined,
1479
+ );
1480
+ if (action === "list" || action === "sessions") {
938
1481
  if (rest.includes("--json")) printJson(sessions);
939
1482
  else printTaskList(sessions);
940
1483
  return;
941
1484
  }
942
- const reference = rest.find((value) => !value.startsWith("--"));
943
- if (!reference) throw new Error(cliText(
944
- `task ${action} requires a task number or task ID.`,
945
- `task ${action} 需要任务序号或任务 ID。`,
946
- ));
947
- const session = resolveTask(sessions, reference);
1485
+ const target = resolveTaskCommandTarget(allSessions, rest, action);
1486
+ const session = target.session;
948
1487
  if (action === "chat") {
1488
+ rejectUnsupportedTaskOptions(action, target.args, ["--json"]);
1489
+ if (target.args.some((value) => !value.startsWith("--"))) throw new Error(cliText(
1490
+ "task chat accepts only one task reference.",
1491
+ "task chat 只接受一个任务引用。",
1492
+ ));
949
1493
  const route = taskChatRoute(session);
950
- if (rest.includes("--json")) printJson(route);
1494
+ if (target.args.includes("--json")) printJson(route);
951
1495
  else process.stdout.write(`${route.chatId}\n`);
952
1496
  return;
953
1497
  }
954
1498
  if (action === "status") {
955
- await outputTaskStatus(config.storage.sqlitePath, store, session, rest.includes("--json"));
1499
+ rejectUnsupportedTaskOptions(action, target.args, ["--json"]);
1500
+ if (target.args.some((value) => !value.startsWith("--"))) throw new Error(cliText(
1501
+ "task status accepts only one task reference.",
1502
+ "task status 只接受一个任务引用。",
1503
+ ));
1504
+ await outputTaskStatus(config.storage.sqlitePath, store, session, target.args.includes("--json"));
956
1505
  return;
957
1506
  }
958
1507
  const endpoint = controlEndpoint(config.storage.sqlitePath);
959
1508
  if (action === "stop") {
1509
+ if (target.args.length > 0) throw new Error(cliText(
1510
+ "task stop accepts only one task reference.",
1511
+ "task stop 只接受一个任务引用。",
1512
+ ));
960
1513
  ensureOk(await sendControlRequest(endpoint, { action: "task_stop", localSessionId: session.localSessionId }));
961
1514
  process.stdout.write(cliText("Task stop requested.\n", "已请求停止任务。\n"));
962
1515
  return;
963
1516
  }
964
1517
  if (action === "title") {
965
- const referenceIndex = rest.indexOf(reference);
966
- const titleArgs = rest.slice(referenceIndex + 1);
1518
+ const titleArgs = target.args;
967
1519
  const optionIndex = titleArgs.findIndex((value) => value.startsWith("--"));
968
1520
  const title = titleArgs.slice(0, optionIndex < 0 ? undefined : optionIndex).join(" ").trim();
969
1521
  if (!title) throw new Error(cliText(
@@ -984,20 +1536,179 @@ async function taskCommand(input: string[]): Promise<void> {
984
1536
  }
985
1537
  }
986
1538
 
987
- function resolveCurrentTaskFromEnvironment(sessions: SessionRecord[]): SessionRecord {
988
- const resolution = resolveCurrentTask(sessions, currentAppServerThreadIds(process.env));
989
- if (resolution.status === "found") return resolution.session;
990
- if (resolution.status === "missing-thread-id") throw new Error(cliText(
991
- "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.",
992
- "无法检测当前任务:CODEX_THREAD_ID 和 TRAECLI_THREAD_ID 均未设置。请从 Codex 或 TraeX 任务中运行此命令。",
993
- ));
994
- if (resolution.status === "not-found") throw new Error(cliText(
995
- `No AgentBot task matches the current App Server Thread ID (${resolution.threadIds.join(", ")}). Check that the correct Profile is selected.`,
996
- `没有 AgentBot 任务匹配当前 App Server Thread ID(${resolution.threadIds.join(",")})。请检查是否选择了正确的 Profile。`,
1539
+ function requireTaskCommandReference(action: string, reference: string | undefined): asserts reference is string {
1540
+ if (reference && !reference.startsWith("--")) return;
1541
+ throw new Error(cliText(
1542
+ `task ${action} requires a task number or task ID.`,
1543
+ `task ${action} 需要任务序号或任务 ID。`,
997
1544
  ));
1545
+ }
1546
+
1547
+ function rejectUnsupportedTaskOptions(action: string, args: string[], supported: string[]): void {
1548
+ const unsupported = args.find((value) => value.startsWith("--") && !supported.includes(value));
1549
+ if (!unsupported) return;
998
1550
  throw new Error(cliText(
999
- "The current task is ambiguous because multiple injected Thread IDs match AgentBot tasks. Use task status <task> with an explicit AgentBot task ID.",
1000
- "当前任务存在歧义:多个注入的 Thread ID 匹配到了 AgentBot 任务。请使用明确的 AgentBot 任务 ID 执行 task status <任务>。",
1551
+ `task ${action} does not support option: ${unsupported}.`,
1552
+ `task ${action} 不支持参数:${unsupported}。`,
1553
+ ));
1554
+ }
1555
+
1556
+ function parseTaskGoal(args: string[]): {
1557
+ action: "show" | "set" | "edit" | "pause" | "resume" | "clear";
1558
+ objective?: string;
1559
+ } {
1560
+ if (args.length === 0 || args[0]?.toLowerCase() === "show") return { action: "show" };
1561
+ const first = args[0]!.toLowerCase();
1562
+ if (first === "pause" || first === "resume" || first === "clear") {
1563
+ if (args.length > 1) throw new Error(cliText(
1564
+ `task goal ${first} does not accept extra arguments.`,
1565
+ `task goal ${first} 不接受额外参数。`,
1566
+ ));
1567
+ return { action: first };
1568
+ }
1569
+ const action = first === "set" || first === "edit" ? first : "set";
1570
+ const objective = (action === "set" && first !== "set" ? args : args.slice(1)).join(" ").trim();
1571
+ if (!objective) throw new Error(cliText(
1572
+ `task goal ${action} requires an objective.`,
1573
+ `task goal ${action} 需要目标。`,
1574
+ ));
1575
+ return { action, objective };
1576
+ }
1577
+
1578
+ function parseTaskDirectoryArgs(args: string[]): {
1579
+ reference: string;
1580
+ directory?: string;
1581
+ page?: number;
1582
+ json: boolean;
1583
+ } {
1584
+ const positionals: string[] = [];
1585
+ let page: number | undefined;
1586
+ let json = false;
1587
+ for (let index = 0; index < args.length; index += 1) {
1588
+ const value = args[index]!;
1589
+ if (value === "--json") {
1590
+ json = true;
1591
+ continue;
1592
+ }
1593
+ if (value === "--page") {
1594
+ const rawPage = args[index + 1];
1595
+ const parsedPage = rawPage === undefined ? Number.NaN : Number(rawPage);
1596
+ if (!Number.isSafeInteger(parsedPage) || parsedPage < 1) throw new Error(cliText(
1597
+ "task dir --page requires a positive page number.",
1598
+ "task dir --page 需要正整数页码。",
1599
+ ));
1600
+ page = parsedPage - 1;
1601
+ index += 1;
1602
+ continue;
1603
+ }
1604
+ if (value.startsWith("--")) throw new Error(cliText(
1605
+ `task dir does not support option: ${value}.`,
1606
+ `task dir 不支持参数:${value}。`,
1607
+ ));
1608
+ positionals.push(value);
1609
+ }
1610
+ const [reference, ...directoryParts] = positionals;
1611
+ requireTaskCommandReference("dir", reference);
1612
+ return {
1613
+ reference,
1614
+ directory: directoryParts.join(" ").trim() || undefined,
1615
+ page,
1616
+ json,
1617
+ };
1618
+ }
1619
+
1620
+ function controlData<T>(response: ControlResponse): T {
1621
+ ensureOk(response);
1622
+ if (response.data === undefined) throw new Error(cliText(
1623
+ "Agent Bot returned no control result.",
1624
+ "Agent Bot 没有返回控制结果。",
1625
+ ));
1626
+ return response.data as T;
1627
+ }
1628
+
1629
+ function printCreatedTask(session: SessionRecord, label: string): void {
1630
+ process.stdout.write(`${cliText(`${label}: `, `${label === "Current task" ? "当前任务" : label === "Fork created" ? "分支任务已创建" : "任务已创建"}:`)}${session.title ?? "-"}\n`);
1631
+ process.stdout.write(`${cliText("Local ID: ", "本地 ID:")}${session.localSessionId}\n`);
1632
+ if (session.remoteSessionId) process.stdout.write(`${cliText("Agent task ID: ", "Agent 任务 ID:")}${session.remoteSessionId}\n`);
1633
+ process.stdout.write(`${cliText("Agent: ", "Agent:")}${session.agentName}\n`);
1634
+ process.stdout.write(`${cliText("Directory: ", "目录:")}${session.cwd}\n`);
1635
+ }
1636
+
1637
+ function printTaskAgentSettings(result: TaskAgentControlData): void {
1638
+ process.stdout.write(`${cliText("Default Agent: ", "默认 Agent:")}${result.current}\n`);
1639
+ process.stdout.write(`${cliText("Available Agents: ", "可用 Agent:")}${result.agents.map((agent) => `${agent.name} (${agent.title})`).join(", ")}\n`);
1640
+ }
1641
+
1642
+ function printTaskSettings(
1643
+ result: TaskSettingsControlData,
1644
+ setting: "provider" | "model" | "thinking" | "permissions",
1645
+ ): void {
1646
+ const session = result.session;
1647
+ process.stdout.write(`${cliText("Provider: ", "Provider:")}${session.modelProvider ?? cliText("Agent default", "Agent 默认")}\n`);
1648
+ process.stdout.write(`${cliText("Model: ", "模型:")}${session.model ?? cliText("default", "默认")}\n`);
1649
+ process.stdout.write(`${cliText("Thinking: ", "思考强度:")}${session.reasoningEffort ?? cliText("automatic", "自动")}\n`);
1650
+ process.stdout.write(`${cliText("Permissions: ", "权限:")}${session.permissionMode ?? "auto"}\n`);
1651
+ if (setting === "provider") {
1652
+ process.stdout.write(`${cliText("Available Providers: ", "可用 Provider:")}${result.providers.map((item) => item.id).join(", ") || "-"}\n`);
1653
+ } else if (setting === "model") {
1654
+ process.stdout.write(`${cliText("Available Models: ", "可用模型:")}${result.models.map((item) => item.id).join(", ") || "-"}\n`);
1655
+ } else if (setting === "thinking") {
1656
+ process.stdout.write(`${cliText("Available Thinking Levels: ", "可用思考强度:")}${result.reasoningOptions.map((item) => item.value).join(", ") || "-"}\n`);
1657
+ } else {
1658
+ process.stdout.write(`${cliText("Available Permission Modes: ", "可用权限模式:")}${result.permissionModes.join(", ")}\n`);
1659
+ }
1660
+ }
1661
+
1662
+ function printTaskGoal(result: TaskGoalControlData): void {
1663
+ if (!result.goal) {
1664
+ process.stdout.write(result.cleared
1665
+ ? cliText("Goal cleared.\n", "Goal 已清除。\n")
1666
+ : cliText("No Goal is configured.\n", "当前没有 Goal。\n"));
1667
+ return;
1668
+ }
1669
+ process.stdout.write(`${cliText("Goal: ", "Goal:")}${result.goal.objective}\n`);
1670
+ process.stdout.write(`${cliText("Status: ", "状态:")}${result.goal.status}\n`);
1671
+ process.stdout.write(`${cliText("Tokens used: ", "已用 Tokens:")}${result.goal.tokensUsed}\n`);
1672
+ process.stdout.write(`${cliText("Time used: ", "已用时间:")}${result.goal.timeUsedSeconds}s\n`);
1673
+ }
1674
+
1675
+ function printTaskTurns(result: TaskTurnsControlData): void {
1676
+ process.stdout.write(`${cliText("Task: ", "任务:")}${result.session.title ?? result.session.localSessionId}\n`);
1677
+ if (result.turns.length === 0) {
1678
+ process.stdout.write(cliText("No completed turns.\n", "没有已完成的 Turn。\n"));
1679
+ return;
1680
+ }
1681
+ for (const turn of result.turns) {
1682
+ const marker = turn.current ? "*" : " ";
1683
+ const parent = turn.parentTurnId ? ` <- ${turn.parentTurnId}` : "";
1684
+ process.stdout.write(`${marker} ${turn.sequence}. ${turn.prompt?.replace(/\s+/g, " ").trim() || "-"}\n`);
1685
+ process.stdout.write(` ${turn.turnId}${parent}\n`);
1686
+ }
1687
+ }
1688
+
1689
+ function printTaskShellResult(result: TaskShellControlData): void {
1690
+ if (result.stdout) process.stdout.write(result.stdout);
1691
+ if (result.stderr) process.stderr.write(result.stderr);
1692
+ if (!result.stdout && !result.stderr) process.stdout.write(cliText("(no output)\n", "(无输出)\n"));
1693
+ if (result.timedOut) process.stderr.write(cliText("Command timed out after 120 seconds.\n", "命令在 120 秒后超时。\n"));
1694
+ }
1695
+
1696
+ function printTaskDirectory(result: TaskDirectoryControlData): void {
1697
+ process.stdout.write(`${cliText("Directory: ", "目录:")}${result.directory}\n`);
1698
+ if (result.parentDirectory) process.stdout.write(` [D] .. -> ${result.parentDirectory}\n`);
1699
+ for (const entry of result.entries) {
1700
+ const marker = entry.kind === "directory" || entry.kind === "drive"
1701
+ ? "D"
1702
+ : entry.kind === "image"
1703
+ ? "I"
1704
+ : entry.kind === "binary"
1705
+ ? "B"
1706
+ : "F";
1707
+ process.stdout.write(` [${marker}] ${entry.name}\n`);
1708
+ }
1709
+ process.stdout.write(cliText(
1710
+ `Page ${result.page + 1}/${result.totalPages}; ${result.totalEntries} entries.\n`,
1711
+ `第 ${result.page + 1}/${result.totalPages} 页;共 ${result.totalEntries} 项。\n`,
1001
1712
  ));
1002
1713
  }
1003
1714
 
@@ -1031,37 +1742,35 @@ async function outputTaskStatus(
1031
1742
  else printTaskStatus(statusSession, snapshot, live?.remote);
1032
1743
  }
1033
1744
 
1034
- function filterSessions(sessions: SessionRecord[], args: string[]): SessionRecord[] {
1745
+ function filterSessions(sessions: SessionRecord[], args: string[], aliasSearchTerm?: string): SessionRecord[] {
1035
1746
  const context = optionValue(args, "--context");
1036
1747
  const status = optionValue(args, "--status");
1748
+ const search = (optionValue(args, "--search") ?? aliasSearchTerm)?.trim().toLowerCase();
1037
1749
  return sessions.filter((session) =>
1038
1750
  (!context || session.contextKey === context)
1039
- && (!status || session.status === status || session.lastTurnStatus === status));
1751
+ && (!status || session.status === status || session.lastTurnStatus === status)
1752
+ && (!search || [
1753
+ session.localSessionId,
1754
+ session.remoteSessionId,
1755
+ session.acpSessionId,
1756
+ session.title,
1757
+ session.agentName,
1758
+ session.cwd,
1759
+ ].some((value) => value?.toLowerCase().includes(search))));
1040
1760
  }
1041
1761
 
1042
- function resolveTask(sessions: SessionRecord[], reference: string): SessionRecord {
1043
- if (/^\d+$/.test(reference)) {
1044
- const index = Number(reference) - 1;
1045
- const session = sessions[index];
1046
- if (!session) throw new Error(cliText(
1047
- `Task number is out of range: ${reference}`,
1048
- `任务序号超出范围:${reference}`,
1049
- ));
1050
- return session;
1051
- }
1052
- const exact = sessions.find((session) =>
1053
- session.localSessionId === reference || session.remoteSessionId === reference || session.acpSessionId === reference);
1054
- if (exact) return exact;
1055
- const matches = sessions.filter((session) =>
1056
- session.localSessionId.startsWith(reference)
1057
- || session.remoteSessionId?.startsWith(reference)
1058
- || session.acpSessionId?.startsWith(reference));
1059
- if (matches.length === 1) return matches[0]!;
1060
- if (matches.length > 1) throw new Error(cliText(
1061
- `Task ID prefix is ambiguous: ${reference}`,
1062
- `任务 ID 前缀不唯一:${reference}`,
1063
- ));
1064
- throw new Error(cliText(`Task not found: ${reference}`, `未找到任务:${reference}`));
1762
+ function taskSessionsSearchTerm(args: string[]): string | undefined {
1763
+ const positionals: string[] = [];
1764
+ for (let index = 0; index < args.length; index += 1) {
1765
+ const value = args[index]!;
1766
+ if (value === "--json") continue;
1767
+ if (value === "--context" || value === "--status" || value === "--search") {
1768
+ index += 1;
1769
+ continue;
1770
+ }
1771
+ if (!value.startsWith("--")) positionals.push(value);
1772
+ }
1773
+ return positionals.join(" ").trim() || undefined;
1065
1774
  }
1066
1775
 
1067
1776
  function printTaskList(sessions: SessionRecord[]): void {
@@ -1136,6 +1845,13 @@ function printInitializationResult(result: Omit<InitCommandResult, "server" | "w
1136
1845
  ));
1137
1846
  process.stdout.write(`${cliText("Configured Agents: ", "已配置 Agent:")}${result.configuredAgents.join(", ")}\n`);
1138
1847
  process.stdout.write(`${cliText("Default Agent: ", "默认 Agent:")}${result.defaultAgent.name}\n`);
1848
+ process.stdout.write(cliText(
1849
+ `Group message response: ${result.groupMessages.mode === "all" ? "receive all group messages" : "require an explicit @ mention"}\n`,
1850
+ `群消息响应方式:${result.groupMessages.mode === "all" ? "接收所有群消息" : "必须明确 @ 机器人"}\n`,
1851
+ ));
1852
+ process.stdout.write(result.skill.updated
1853
+ ? `${cliText("Agent Bot Skill: installed or updated at ", "Agent Bot Skill:已安装或更新至 ")}${result.skill.status.targetPath}\n`
1854
+ : `${cliText("Agent Bot Skill: already up to date at ", "Agent Bot Skill:已是最新版本,位于 ")}${result.skill.status.targetPath}\n`);
1139
1855
  if (result.feishu.status === "created") {
1140
1856
  process.stdout.write(cliText(
1141
1857
  `Lark app: created and credentials saved (${result.feishu.appId})\n`,