@keyou007/agent-bot 0.1.11 → 0.1.13-alpha.0

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 (97) hide show
  1. package/.env.example +1 -1
  2. package/CHANGELOG.md +19 -0
  3. package/README.md +63 -44
  4. package/README.zh.md +63 -44
  5. package/config.example.yaml +8 -22
  6. package/dist/cli/AgentPrerequisites.js +262 -0
  7. package/dist/cli/AgentPrerequisites.js.map +1 -0
  8. package/dist/cli/DefaultAgentSelection.js +47 -0
  9. package/dist/cli/DefaultAgentSelection.js.map +1 -0
  10. package/dist/cli/FeishuAppConfiguration.js +1 -1
  11. package/dist/cli/FeishuAppConfiguration.js.map +1 -1
  12. package/dist/cli/FeishuAppRegistration.js +2 -2
  13. package/dist/cli/FeishuAppRegistration.js.map +1 -1
  14. package/dist/cli/Initializer.js +166 -3
  15. package/dist/cli/Initializer.js.map +1 -1
  16. package/dist/cli/ServerStarter.js +7 -2
  17. package/dist/cli/ServerStarter.js.map +1 -1
  18. package/dist/cli/controlProtocol.js.map +1 -1
  19. package/dist/cli/help.js +24 -20
  20. package/dist/cli/help.js.map +1 -1
  21. package/dist/cli/taskGroupOptions.js +95 -0
  22. package/dist/cli/taskGroupOptions.js.map +1 -0
  23. package/dist/cli.js +268 -6
  24. package/dist/cli.js.map +1 -1
  25. package/dist/codex/AppServerConnection.js +3 -3
  26. package/dist/codex/AppServerConnection.js.map +1 -1
  27. package/dist/codex/CodexProcessManager.js +4 -4
  28. package/dist/codex/CodexProcessManager.js.map +1 -1
  29. package/dist/codex/CodexRuntime.js +63 -25
  30. package/dist/codex/CodexRuntime.js.map +1 -1
  31. package/dist/config/loadConfig.js +8 -22
  32. package/dist/config/loadConfig.js.map +1 -1
  33. package/dist/config/schema.js +6 -1
  34. package/dist/config/schema.js.map +1 -1
  35. package/dist/console/ConsoleConnector.js +3 -3
  36. package/dist/console/ConsoleConnector.js.map +1 -1
  37. package/dist/console/ConsoleTurnPresenter.js +14 -7
  38. package/dist/console/ConsoleTurnPresenter.js.map +1 -1
  39. package/dist/deprecated-cli.js +5 -0
  40. package/dist/deprecated-cli.js.map +1 -0
  41. package/dist/feishu/CardRenderer.js +13 -12
  42. package/dist/feishu/CardRenderer.js.map +1 -1
  43. package/dist/feishu/FeishuTurnPresenter.js +9 -5
  44. package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
  45. package/dist/feishu/transport.js +1 -1
  46. package/dist/feishu/transport.js.map +1 -1
  47. package/dist/index.js +21 -3
  48. package/dist/index.js.map +1 -1
  49. package/dist/presentation/OutboundRouter.js +2 -2
  50. package/dist/presentation/OutboundRouter.js.map +1 -1
  51. package/dist/presentation/TurnStateReducer.js +2 -1
  52. package/dist/presentation/TurnStateReducer.js.map +1 -1
  53. package/dist/proxy/ProxySessionController.js +128 -72
  54. package/dist/proxy/ProxySessionController.js.map +1 -1
  55. package/dist/runtime/AcpRuntimeAdapter.js +3 -2
  56. package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
  57. package/dist/runtime/createAgentRuntimeRegistry.js +1 -1
  58. package/dist/runtime/createAgentRuntimeRegistry.js.map +1 -1
  59. package/dist/supervision/systemEnvironment.js +129 -0
  60. package/dist/supervision/systemEnvironment.js.map +1 -0
  61. package/dist/supervisor.js +13 -1
  62. package/dist/supervisor.js.map +1 -1
  63. package/docs/technical-reference.md +53 -34
  64. package/docs/technical-reference.zh.md +53 -34
  65. package/npm-shrinkwrap.json +4 -3
  66. package/package.json +9 -4
  67. package/skills/agent-bot/SKILL.md +72 -33
  68. package/src/cli/AgentPrerequisites.ts +377 -0
  69. package/src/cli/DefaultAgentSelection.ts +56 -0
  70. package/src/cli/FeishuAppConfiguration.ts +2 -2
  71. package/src/cli/FeishuAppRegistration.ts +4 -4
  72. package/src/cli/Initializer.ts +206 -5
  73. package/src/cli/ServerStarter.ts +8 -3
  74. package/src/cli/controlProtocol.ts +21 -1
  75. package/src/cli/help.ts +24 -20
  76. package/src/cli/taskGroupOptions.ts +149 -0
  77. package/src/cli.ts +391 -9
  78. package/src/codex/AppServerConnection.ts +3 -3
  79. package/src/codex/CodexProcessManager.ts +4 -4
  80. package/src/codex/CodexRuntime.ts +84 -31
  81. package/src/config/loadConfig.ts +8 -22
  82. package/src/config/schema.ts +7 -1
  83. package/src/console/ConsoleConnector.ts +3 -3
  84. package/src/console/ConsoleTurnPresenter.ts +20 -7
  85. package/src/deprecated-cli.ts +9 -0
  86. package/src/feishu/CardRenderer.ts +13 -12
  87. package/src/feishu/FeishuTurnPresenter.ts +15 -1
  88. package/src/feishu/transport.ts +1 -1
  89. package/src/index.ts +34 -3
  90. package/src/presentation/OutboundRouter.ts +15 -3
  91. package/src/presentation/TurnStateReducer.ts +2 -0
  92. package/src/presentation/turnViewTypes.ts +1 -0
  93. package/src/proxy/ProxySessionController.ts +217 -73
  94. package/src/runtime/AcpRuntimeAdapter.ts +3 -1
  95. package/src/runtime/createAgentRuntimeRegistry.ts +1 -1
  96. package/src/supervision/systemEnvironment.ts +174 -0
  97. package/src/supervisor.ts +12 -1
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { cliText } from "./i18n.js";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { parse as parseDotEnv } from "dotenv";
8
+ import { isMap, isScalar, parseDocument, type YAMLMap } from "yaml";
8
9
  import {
9
10
  agentBotHome,
10
11
  defaultConfigPath,
@@ -12,7 +13,7 @@ import {
12
13
  resolveUserPath,
13
14
  } from "../config/paths.js";
14
15
 
15
- export type InitializationStatus = "created" | "existing" | "reset";
16
+ export type InitializationStatus = "created" | "existing" | "updated" | "reset";
16
17
 
17
18
  export interface InitializedPath {
18
19
  path: string;
@@ -38,6 +39,16 @@ export interface InitializationOptions {
38
39
  reset?: boolean;
39
40
  }
40
41
 
42
+ export interface ConfiguredAgentOption {
43
+ name: string;
44
+ title: string;
45
+ }
46
+
47
+ export interface ConfiguredAgentSelection {
48
+ agents: ConfiguredAgentOption[];
49
+ defaultAgent?: string;
50
+ }
51
+
41
52
  export type FeishuCredentialStatus = "configured" | "missing" | "incomplete";
42
53
 
43
54
  export interface FeishuCredentialState {
@@ -82,8 +93,8 @@ export function initializeAgentBot(options: InitializationOptions = {}): Initial
82
93
  ? resetProfileContents(homePath, configPath, envPath, configDirectory)
83
94
  : undefined;
84
95
 
85
- const config = copyIfMissing(configTemplatePath, configPath);
86
- const envFile = copyIfMissing(envTemplatePath, envPath);
96
+ const config = initializeConfig(configTemplatePath, configPath);
97
+ const envFile = initializeEnv(envTemplatePath, envPath);
87
98
  restrictFilePermissions(envFile.path);
88
99
  const data = ensureDirectory(path.join(configDirectory, "data"));
89
100
  const logs = ensureDirectory(path.join(configDirectory, "logs"));
@@ -98,6 +109,68 @@ export function initializeAgentBot(options: InitializationOptions = {}): Initial
98
109
  };
99
110
  }
100
111
 
112
+ export function readConfiguredAgentSelection(configPath: string): ConfiguredAgentSelection {
113
+ const document = parseConfigurationDocument(fs.readFileSync(configPath, "utf8"), configPath);
114
+ if (!isMap(document.contents)) {
115
+ throw new Error(cliText(
116
+ `Configuration file must contain a YAML mapping: ${configPath}`,
117
+ `配置文件必须包含 YAML 映射:${configPath}`,
118
+ ));
119
+ }
120
+ const configuredAgents = document.contents.get("agents", true);
121
+ if (!isMap(configuredAgents)) {
122
+ throw new Error(cliText(
123
+ `Configuration file must contain an agents mapping: ${configPath}`,
124
+ `配置文件必须包含 agents 映射:${configPath}`,
125
+ ));
126
+ }
127
+ const agents = configuredAgents.items.flatMap((pair) => {
128
+ const name = yamlMappingKey(pair.key);
129
+ if (!name) return [];
130
+ const titleNode = isMap(pair.value) ? pair.value.get("title", true) : undefined;
131
+ const title = isScalar(titleNode) && typeof titleNode.value === "string"
132
+ ? titleNode.value
133
+ : name;
134
+ return [{ name, title }];
135
+ });
136
+ const defaultAgent = document.getIn(["defaults", "agent"]);
137
+ return {
138
+ agents,
139
+ ...(typeof defaultAgent === "string" && defaultAgent.trim() ? { defaultAgent } : {}),
140
+ };
141
+ }
142
+
143
+ export function writeDefaultAgent(configPath: string, agentName: string): boolean {
144
+ const original = fs.readFileSync(configPath, "utf8");
145
+ const document = parseConfigurationDocument(original, configPath);
146
+ if (!isMap(document.contents)) {
147
+ throw new Error(cliText(
148
+ `Configuration file must contain a YAML mapping: ${configPath}`,
149
+ `配置文件必须包含 YAML 映射:${configPath}`,
150
+ ));
151
+ }
152
+ const configuredAgents = document.contents.get("agents", true);
153
+ if (!isMap(configuredAgents) || !configuredAgents.has(agentName)) {
154
+ throw new Error(cliText(
155
+ `Cannot select an Agent that is not configured: ${agentName}`,
156
+ `不能选择尚未配置的 Agent:${agentName}`,
157
+ ));
158
+ }
159
+ const defaults = document.contents.get("defaults", true);
160
+ if (defaults !== undefined && !isMap(defaults)) {
161
+ throw new Error(cliText(
162
+ `Configuration defaults must contain a YAML mapping: ${configPath}`,
163
+ `配置文件的 defaults 必须是 YAML 映射:${configPath}`,
164
+ ));
165
+ }
166
+ const currentAgent = document.getIn(["defaults", "agent"]);
167
+ if (currentAgent === agentName) return false;
168
+ if (isMap(defaults)) defaults.set("agent", agentName);
169
+ else document.setIn(["defaults"], { agent: agentName });
170
+ writeFileAtomically(configPath, document.toString());
171
+ return true;
172
+ }
173
+
101
174
  export function readFeishuCredentials(envPath: string, env: NodeJS.ProcessEnv = process.env): FeishuCredentialState {
102
175
  const fileValues = fs.existsSync(envPath) ? parseDotEnv(fs.readFileSync(envPath, "utf8")) : {};
103
176
  const appId = firstNonBlank(env.FEISHU_APP_ID, fileValues.FEISHU_APP_ID);
@@ -188,8 +261,8 @@ export function acquireInitializationLock(lockDirectory: string): Initialization
188
261
  if (!isFileExistsError(error)) throw error;
189
262
  if (!isStaleInitializationLock(lockPath)) {
190
263
  throw new Error(cliText(
191
- `Another agent-bot init process is running (lock file: ${lockPath}).`,
192
- `另一个 agent-bot init 进程正在运行(锁文件:${lockPath})。`,
264
+ `Another agentbot init process is running (lock file: ${lockPath}).`,
265
+ `另一个 agentbot init 进程正在运行(锁文件:${lockPath})。`,
193
266
  ));
194
267
  }
195
268
  fs.rmSync(lockPath, { force: true });
@@ -244,6 +317,134 @@ function copyIfMissing(sourcePath: string, targetPath: string): InitializedPath
244
317
  }
245
318
  }
246
319
 
320
+ function initializeConfig(templatePath: string, configPath: string): InitializedPath {
321
+ if (!fs.existsSync(configPath)) {
322
+ const copied = copyIfMissing(templatePath, configPath);
323
+ if (copied.status === "created") return copied;
324
+ }
325
+
326
+ const original = fs.readFileSync(configPath, "utf8");
327
+ const template = fs.readFileSync(templatePath, "utf8");
328
+ const configDocument = parseConfigurationDocument(original, configPath);
329
+ const templateDocument = parseConfigurationDocument(template, templatePath);
330
+ if (!isMap(configDocument.contents) || !isMap(templateDocument.contents)) {
331
+ throw new Error(cliText(
332
+ `Configuration files must contain a YAML mapping: ${configPath}`,
333
+ `配置文件必须包含 YAML 映射:${configPath}`,
334
+ ));
335
+ }
336
+
337
+ const updated = mergeMissingConfiguration(
338
+ configDocument.contents,
339
+ templateDocument.contents,
340
+ [],
341
+ );
342
+ if (!updated) return { path: configPath, status: "existing" };
343
+ writeFileAtomically(configPath, configDocument.toString());
344
+ return { path: configPath, status: "updated" };
345
+ }
346
+
347
+ function initializeEnv(templatePath: string, envPath: string): InitializedPath {
348
+ if (!fs.existsSync(envPath)) {
349
+ const copied = copyIfMissing(templatePath, envPath);
350
+ if (copied.status === "created") return copied;
351
+ }
352
+
353
+ const original = fs.readFileSync(envPath, "utf8");
354
+ const template = fs.readFileSync(templatePath, "utf8");
355
+ const existingKeys = dotEnvKeys(original);
356
+ const missingLines = template
357
+ .split(/\r?\n/)
358
+ .flatMap((line) => {
359
+ const key = dotEnvAssignmentKey(line);
360
+ if (!key || existingKeys.has(key)) return [];
361
+ existingKeys.add(key);
362
+ return [line];
363
+ });
364
+ if (missingLines.length === 0) return { path: envPath, status: "existing" };
365
+
366
+ const newline = original.includes("\r\n") ? "\r\n" : "\n";
367
+ const separator = original.length === 0 || /\r?\n$/u.test(original) ? "" : newline;
368
+ writeFileAtomically(envPath, `${original}${separator}${missingLines.join(newline)}${newline}`);
369
+ return { path: envPath, status: "updated" };
370
+ }
371
+
372
+ function parseConfigurationDocument(contents: string, filePath: string) {
373
+ const document = parseDocument(contents);
374
+ if (document.errors.length === 0) return document;
375
+ throw new Error(cliText(
376
+ `Could not upgrade configuration file ${filePath}: ${document.errors[0]?.message ?? "invalid YAML"}`,
377
+ `无法升级配置文件 ${filePath}:${document.errors[0]?.message ?? "YAML 无效"}`,
378
+ ));
379
+ }
380
+
381
+ function mergeMissingConfiguration(
382
+ config: YAMLMap,
383
+ template: YAMLMap,
384
+ parentPath: string[],
385
+ ): boolean {
386
+ let updated = false;
387
+ for (const templatePair of template.items) {
388
+ const key = yamlMappingKey(templatePair.key);
389
+ if (!key) continue;
390
+ if (!config.has(key)) {
391
+ if (shouldPreserveMissingConfiguration(parentPath, key)) continue;
392
+ const clonedPair = templatePair.clone();
393
+ if (parentPath.length === 0 && key === "defaults" && isMap(clonedPair.value)) {
394
+ clonedPair.value.delete("agent");
395
+ if (clonedPair.value.items.length === 0) continue;
396
+ }
397
+ config.add(clonedPair);
398
+ updated = true;
399
+ continue;
400
+ }
401
+
402
+ const configValue = config.get(key, true);
403
+ if (isMap(configValue) && isMap(templatePair.value)) {
404
+ updated = mergeMissingConfiguration(configValue, templatePair.value, [...parentPath, key]) || updated;
405
+ }
406
+ }
407
+ return updated;
408
+ }
409
+
410
+ function shouldPreserveMissingConfiguration(parentPath: string[], key: string): boolean {
411
+ if (parentPath.length === 1 && parentPath[0] === "agents") return true;
412
+ return parentPath.length === 1 && parentPath[0] === "defaults" && key === "agent";
413
+ }
414
+
415
+ function yamlMappingKey(value: unknown): string | undefined {
416
+ if (typeof value === "string") return value;
417
+ return isScalar(value) && typeof value.value === "string" ? value.value : undefined;
418
+ }
419
+
420
+ function dotEnvKeys(contents: string): Set<string> {
421
+ return new Set(contents.split(/\r?\n/).flatMap((line) => {
422
+ const key = dotEnvAssignmentKey(line);
423
+ return key ? [key] : [];
424
+ }));
425
+ }
426
+
427
+ function dotEnvAssignmentKey(line: string): string | undefined {
428
+ return /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/u.exec(line)?.[1];
429
+ }
430
+
431
+ function writeFileAtomically(filePath: string, contents: string): void {
432
+ const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
433
+ let temporaryFile: number | undefined;
434
+ try {
435
+ temporaryFile = fs.openSync(temporaryPath, "wx", fs.statSync(filePath).mode);
436
+ fs.writeFileSync(temporaryFile, contents, "utf8");
437
+ fs.fsyncSync(temporaryFile);
438
+ fs.closeSync(temporaryFile);
439
+ temporaryFile = undefined;
440
+ fs.renameSync(temporaryPath, filePath);
441
+ syncDirectory(path.dirname(filePath));
442
+ } finally {
443
+ if (temporaryFile !== undefined) fs.closeSync(temporaryFile);
444
+ fs.rmSync(temporaryPath, { force: true });
445
+ }
446
+ }
447
+
247
448
  function resetProfileContents(
248
449
  homePath: string,
249
450
  configPath: string,
@@ -8,6 +8,7 @@ import {
8
8
  prepareCrashReportDirectory,
9
9
  resolveSupervisorDiagnosticsPaths,
10
10
  } from "../supervision/SupervisorDiagnostics.js";
11
+ import { refreshedSystemEnvironment } from "../supervision/systemEnvironment.js";
11
12
  import { controlEndpoint } from "./controlProtocol.js";
12
13
  import { cliText } from "./i18n.js";
13
14
  import { isServerReachable, isServerRunning } from "./LocalControlClient.js";
@@ -53,8 +54,8 @@ export async function startServer(
53
54
  } catch (error) {
54
55
  if (error instanceof Error && /Lark bot is not configured/.test(error.message)) {
55
56
  throw new Error(cliText(
56
- "The Lark bot is not configured. Run agent-bot init first, or use agent-bot console for local-only testing.",
57
- "尚未配置飞书机器人。请先运行 agent-bot init,或使用 agent-bot console 进行纯本地测试。",
57
+ "The Lark bot is not configured. Run agentbot init first, or use agentbot console for local-only testing.",
58
+ "尚未配置飞书机器人。请先运行 agentbot init,或使用 agentbot console 进行纯本地测试。",
58
59
  ));
59
60
  }
60
61
  throw error;
@@ -94,6 +95,7 @@ const defaultDependencies: ServerStarterDependencies = {
94
95
  const entry = fileURLToPath(new URL("../supervisor.js", import.meta.url));
95
96
  const reportDirectory = resolveSupervisorDiagnosticsPaths(config).crashReportDirectory;
96
97
  prepareCrashReportDirectory(reportDirectory);
98
+ const environmentRefresh = refreshedSystemEnvironment();
97
99
  const child = spawn(process.execPath, [
98
100
  ...nodeDiagnosticReportArguments(reportDirectory),
99
101
  entry,
@@ -102,7 +104,10 @@ const defaultDependencies: ServerStarterDependencies = {
102
104
  detached: true,
103
105
  windowsHide: true,
104
106
  stdio: "ignore",
105
- env: { ...process.env, AGENT_BOT_RESTART_REASON: "通过 Agent Bot CLI 启动" },
107
+ env: {
108
+ ...environmentRefresh.environment,
109
+ AGENT_BOT_RESTART_REASON: "通过 Agent Bot CLI 启动",
110
+ },
106
111
  });
107
112
  child.unref();
108
113
  },
@@ -11,7 +11,16 @@ export type ControlRequest =
11
11
  | { action: "task_status"; localSessionId: string }
12
12
  | { action: "task_stop"; localSessionId: string }
13
13
  | { action: "task_title"; localSessionId: string; title: string }
14
- | { action: "task_prompt"; localSessionId: string; text: string };
14
+ | { action: "task_prompt"; localSessionId: string; text: string }
15
+ | {
16
+ action: "task_new_group";
17
+ localSessionId: string;
18
+ title?: string;
19
+ cwd?: string;
20
+ agentName?: string;
21
+ projectless?: boolean;
22
+ }
23
+ | { action: "task_fork_group"; localSessionId: string; title?: string };
15
24
 
16
25
  export interface ControlResponse {
17
26
  ok: boolean;
@@ -25,6 +34,17 @@ export interface TaskStatusControlData {
25
34
  remote?: RemoteSessionSummary;
26
35
  }
27
36
 
37
+ export interface TaskGroupControlData {
38
+ sourceLocalSessionId: string;
39
+ sourceTurnId?: string;
40
+ group: {
41
+ chatId: string;
42
+ contextKey: string;
43
+ name: string;
44
+ };
45
+ task: SessionRecord;
46
+ }
47
+
28
48
  export function controlEndpoint(sqlitePath: string): string {
29
49
  const resolvedPath = path.resolve(sqlitePath);
30
50
  const key = createHash("sha256").update(resolvedPath.toLowerCase()).digest("hex").slice(0, 16);
package/src/cli/help.ts CHANGED
@@ -3,13 +3,13 @@ import { cliLanguage, type CliLanguage } from "./i18n.js";
3
3
  export function renderCliHelp(version: string, language: CliLanguage = cliLanguage): string {
4
4
  if (language === "zh") {
5
5
  return `Agent Bot ${version}
6
- 通过飞书使用本地 Codex 和 ACP Agent。
6
+ 通过飞书使用本地 Codex、TraeX 和兼容 ACP 的 Agent。
7
7
 
8
8
  用法:
9
- agent-bot [选项] <命令>
9
+ agentbot [选项] <命令>
10
10
 
11
11
  常用命令:
12
- init 初始化飞书应用并启动服务
12
+ init 检查并选择 Agent,初始化飞书应用和服务
13
13
  init --reset 重置并重新配置显式指定的 Profile
14
14
  console 打开本地控制台
15
15
  server start 在后台启动 Agent Bot
@@ -20,6 +20,8 @@ export function renderCliHelp(version: string, language: CliLanguage = cliLangua
20
20
  task status <任务> 显示任务状态
21
21
  task chat <任务> 显示任务绑定的飞书会话
22
22
  task prompt <任务> <提示词> 向任务发送提示词
23
+ task newgroup <任务> [标题] 创建新群;支持 --agent、--dir、--nodir
24
+ task forkgroup <任务> [标题] 从任务 Fork 新群
23
25
  task stop <任务> 停止任务
24
26
  task title <任务> <标题> 重命名任务
25
27
  skills status 显示内置 Skill 状态
@@ -38,24 +40,24 @@ export function renderCliHelp(version: string, language: CliLanguage = cliLangua
38
40
  --reconfigure-feishu 仅替换现有飞书凭据
39
41
 
40
42
  示例:
41
- agent-bot init
42
- agent-bot --profile ~/.agent-bot init --reset
43
- agent-bot --profile ~/.agent-bot-rescue init
44
- agent-bot server start
45
- agent-bot server status
46
- agent-bot task list
47
- agent-bot server restart --reason "更新 Agent Bot"
43
+ agentbot init
44
+ agentbot --profile ~/.agent-bot init --reset
45
+ agentbot --profile ~/.agent-bot-rescue init
46
+ agentbot server start
47
+ agentbot server status
48
+ agentbot task list
49
+ agentbot server restart --reason "更新 Agent Bot"
48
50
  `;
49
51
  }
50
52
 
51
53
  return `Agent Bot ${version}
52
- Use local Codex and ACP agents from Lark/Feishu.
54
+ Use Codex, TraeX, and compatible ACP agents from Lark/Feishu.
53
55
 
54
56
  Usage:
55
- agent-bot [options] <command>
57
+ agentbot [options] <command>
56
58
 
57
59
  Common commands:
58
- init Initialize the Lark app and start the service
60
+ init Check and select an Agent; initialize the Lark app and service
59
61
  init --reset Reset and reconfigure an explicit profile
60
62
  console Open the local console
61
63
  server start Start Agent Bot in the background
@@ -66,6 +68,8 @@ Common commands:
66
68
  task status <task> Show task status
67
69
  task chat <task> Show the task's Lark chat binding
68
70
  task prompt <task> <prompt> Send a Prompt to a task
71
+ task newgroup <task> [title] Create a group; supports --agent, --dir, --nodir
72
+ task forkgroup <task> [title] Fork a task into a new group
69
73
  task stop <task> Stop a task
70
74
  task title <task> <title> Rename a task
71
75
  skills status Show the built-in Skill status
@@ -84,12 +88,12 @@ Init options:
84
88
  --reconfigure-feishu Replace only the existing Lark credentials
85
89
 
86
90
  Examples:
87
- agent-bot init
88
- agent-bot --profile ~/.agent-bot init --reset
89
- agent-bot --profile ~/.agent-bot-rescue init
90
- agent-bot server start
91
- agent-bot server status
92
- agent-bot task list
93
- agent-bot server restart --reason "Update Agent Bot"
91
+ agentbot init
92
+ agentbot --profile ~/.agent-bot init --reset
93
+ agentbot --profile ~/.agent-bot-rescue init
94
+ agentbot server start
95
+ agentbot server status
96
+ agentbot task list
97
+ agentbot server restart --reason "Update Agent Bot"
94
98
  `;
95
99
  }
@@ -0,0 +1,149 @@
1
+ import { cliLanguage, cliText, type CliLanguage } from "./i18n.js";
2
+
3
+ export interface TaskNewGroupOptions {
4
+ reference: string;
5
+ title?: string;
6
+ cwd?: string;
7
+ agentName?: string;
8
+ projectless: boolean;
9
+ json: boolean;
10
+ }
11
+
12
+ export interface TaskForkGroupOptions {
13
+ reference: string;
14
+ title?: string;
15
+ json: boolean;
16
+ }
17
+
18
+ export function parseTaskNewGroupOptions(
19
+ input: string[],
20
+ language: CliLanguage = cliLanguage,
21
+ ): TaskNewGroupOptions {
22
+ const positionals: string[] = [];
23
+ let cwd: string | undefined;
24
+ let agentName: string | undefined;
25
+ let projectless = false;
26
+ let json = false;
27
+ for (let index = 0; index < input.length; index += 1) {
28
+ const argument = input[index]!;
29
+ if (argument === "--json") {
30
+ json = true;
31
+ continue;
32
+ }
33
+ if (argument === "--nodir") {
34
+ if (projectless) throw optionError("newgroup", "--nodir", language);
35
+ if (cwd !== undefined) throw conflictingProjectOptions(language);
36
+ projectless = true;
37
+ continue;
38
+ }
39
+ if (argument === "--dir") {
40
+ if (projectless) throw conflictingProjectOptions(language);
41
+ if (cwd !== undefined) throw optionError("newgroup", "--dir", language);
42
+ const directory = input[index + 1];
43
+ if (!directory || directory.startsWith("--")) {
44
+ throw new Error(cliText(
45
+ "task newgroup requires a project directory after --dir.",
46
+ "task newgroup 需要在 --dir 后指定项目目录。",
47
+ language,
48
+ ));
49
+ }
50
+ cwd = directory;
51
+ index += 1;
52
+ continue;
53
+ }
54
+ if (argument === "--agent") {
55
+ if (agentName !== undefined) throw optionError("newgroup", "--agent", language);
56
+ const name = input[index + 1];
57
+ if (!name?.trim() || name.startsWith("--")) {
58
+ throw new Error(cliText(
59
+ "task newgroup requires an Agent standard name after --agent.",
60
+ "task newgroup 需要在 --agent 后指定 Agent 标准名。",
61
+ language,
62
+ ));
63
+ }
64
+ agentName = name.trim();
65
+ index += 1;
66
+ continue;
67
+ }
68
+ if (argument.startsWith("--")) throw unsupportedOption("newgroup", argument, language);
69
+ positionals.push(argument);
70
+ }
71
+ const [reference, ...titleParts] = positionals;
72
+ requireTaskReference("newgroup", reference, language);
73
+ return {
74
+ reference,
75
+ title: titleParts.join(" ").trim() || undefined,
76
+ cwd,
77
+ ...(agentName ? { agentName } : {}),
78
+ projectless,
79
+ json,
80
+ };
81
+ }
82
+
83
+ export function parseTaskForkGroupOptions(
84
+ input: string[],
85
+ language: CliLanguage = cliLanguage,
86
+ ): TaskForkGroupOptions {
87
+ const positionals: string[] = [];
88
+ let json = false;
89
+ for (const argument of input) {
90
+ if (argument === "--json") {
91
+ json = true;
92
+ continue;
93
+ }
94
+ if (argument.startsWith("--")) throw unsupportedOption("forkgroup", argument, language);
95
+ positionals.push(argument);
96
+ }
97
+ const [reference, ...titleParts] = positionals;
98
+ requireTaskReference("forkgroup", reference, language);
99
+ return {
100
+ reference,
101
+ title: titleParts.join(" ").trim() || undefined,
102
+ json,
103
+ };
104
+ }
105
+
106
+ function requireTaskReference(
107
+ action: "newgroup" | "forkgroup",
108
+ reference: string | undefined,
109
+ language: CliLanguage,
110
+ ): asserts reference is string {
111
+ if (reference && !reference.startsWith("--")) return;
112
+ throw new Error(cliText(
113
+ `task ${action} requires a source task number or task ID.`,
114
+ `task ${action} 需要源任务序号或任务 ID。`,
115
+ language,
116
+ ));
117
+ }
118
+
119
+ function optionError(
120
+ action: "newgroup",
121
+ option: "--agent" | "--dir" | "--nodir",
122
+ language: CliLanguage,
123
+ ): Error {
124
+ return new Error(cliText(
125
+ `task ${action} accepts ${option} only once.`,
126
+ `task ${action} 只能指定一次 ${option}。`,
127
+ language,
128
+ ));
129
+ }
130
+
131
+ function conflictingProjectOptions(language: CliLanguage): Error {
132
+ return new Error(cliText(
133
+ "task newgroup cannot combine --dir and --nodir.",
134
+ "task newgroup 的 --dir 和 --nodir 不能同时使用。",
135
+ language,
136
+ ));
137
+ }
138
+
139
+ function unsupportedOption(
140
+ action: "newgroup" | "forkgroup",
141
+ option: string,
142
+ language: CliLanguage,
143
+ ): Error {
144
+ return new Error(cliText(
145
+ `task ${action} does not support option: ${option}.`,
146
+ `task ${action} 不支持参数:${option}。`,
147
+ language,
148
+ ));
149
+ }