@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/src/index.ts CHANGED
@@ -48,6 +48,12 @@ import {
48
48
  resolveSupervisorDiagnosticsPaths,
49
49
  } from "./supervision/SupervisorDiagnostics.js";
50
50
  import { refreshedSystemEnvironment } from "./supervision/systemEnvironment.js";
51
+ import {
52
+ assertSelfUpdatePlanPath,
53
+ launchSelfUpdateRunner,
54
+ readPendingSelfUpdate,
55
+ releaseSelfUpdatePlan,
56
+ } from "./cli/SelfUpdater.js";
51
57
 
52
58
  const processStartedAt = new Date();
53
59
  const agentBotVersion = readPackageVersion();
@@ -119,16 +125,26 @@ if (routes.length === 0) throw new Error("Neither Feishu nor console input is en
119
125
  const outbound = new OutboundRouter(routes);
120
126
  let shuttingDown = false;
121
127
  let restartRequested = false;
128
+ let pendingSelfUpdatePlanPath: string | undefined;
122
129
  const safeRestart = new SafeRestartScheduler({
123
130
  readActivity: () => store.getServerActivityState(),
124
- onReady: (reason, notificationTargets) => initiateRestart(reason, notificationTargets),
131
+ onReady: (reason, notificationTargets) => pendingSelfUpdatePlanPath
132
+ ? initiateSelfUpdate(reason, notificationTargets)
133
+ : initiateRestart(reason, notificationTargets),
125
134
  onStatus: (status) => safeRestartNotifier?.update(status),
126
135
  onStatusError: (error) => logger.warn({ error }, "Failed to publish safe restart status."),
127
136
  });
128
137
  const controller = new ProxySessionController(config, store, runtimes, outbound, logger, {
129
138
  supervised,
130
139
  restart: requestRestart,
131
- cancelSafeRestart: (scheduleId) => safeRestart.cancelScheduled(scheduleId),
140
+ cancelSafeRestart: async (scheduleId) => {
141
+ const cancelled = await safeRestart.cancelScheduled(scheduleId);
142
+ if (cancelled && pendingSelfUpdatePlanPath) {
143
+ releaseSelfUpdatePlan(pendingSelfUpdatePlanPath);
144
+ pendingSelfUpdatePlanPath = undefined;
145
+ }
146
+ return cancelled;
147
+ },
132
148
  rememberFeishuUserOpenId: (userOpenId) => {
133
149
  if (config.feishu.userOpenId?.startsWith("ou_")) return;
134
150
  const persisted = persistFeishuUserOpenIdIfMissing(defaultDotEnvPath(), userOpenId);
@@ -185,6 +201,27 @@ await controller.recoverInterruptedTasks().catch((error: unknown) => {
185
201
  logger.error({ error }, "Startup task recovery failed; the server will remain available.");
186
202
  });
187
203
  consoleConnector?.start();
204
+ recoverPendingSelfUpdate();
205
+
206
+ function recoverPendingSelfUpdate(): void {
207
+ const pending = readPendingSelfUpdate(agentBotHome());
208
+ if (!pending || pendingSelfUpdatePlanPath || restartRequested || safeRestart.scheduled) return;
209
+ let notificationTarget: RestartNotificationTarget | undefined;
210
+ try {
211
+ notificationTarget = inferControlRestartTarget(pending.plan.notificationSessionId);
212
+ } catch (error) {
213
+ logger.warn({ error }, "Could not restore the self-update notification target; update recovery will continue.");
214
+ }
215
+ pendingSelfUpdatePlanPath = pending.planPath;
216
+ safeRestart.schedule(
217
+ pending.plan.reason ?? `Resume Agent Bot update to ${pending.plan.toVersion}`,
218
+ notificationTarget,
219
+ );
220
+ logger.warn(
221
+ { planPath: pending.planPath, toVersion: pending.plan.toVersion },
222
+ "Recovered a prepared Agent Bot self-update after Worker restart.",
223
+ );
224
+ }
188
225
 
189
226
  async function requestRestart(
190
227
  contextKey: string,
@@ -199,6 +236,10 @@ async function requestRestart(
199
236
  await outbound.sendText(contextKey, "Agent Bot 已在重启中,请稍候。").catch(() => undefined);
200
237
  return;
201
238
  }
239
+ if (pendingSelfUpdatePlanPath) {
240
+ await outbound.sendText(contextKey, "Agent Bot 更新已在等待中;更新完成后服务会自动恢复。").catch(() => undefined);
241
+ return;
242
+ }
202
243
  if (force) {
203
244
  await initiateRestart("用户执行 /restart --force 命令", [notificationTarget]);
204
245
  return;
@@ -223,6 +264,7 @@ async function initiateRestart(
223
264
  return;
224
265
  }
225
266
  restartRequested = true;
267
+ pendingSelfUpdatePlanPath = undefined;
226
268
  await safeRestart.cancelCurrent();
227
269
  await safeRestartNotifier?.flush();
228
270
  const allNotificationTargets = mergeRestartNotificationTargets(
@@ -240,6 +282,55 @@ async function initiateRestart(
240
282
  ), 100);
241
283
  }
242
284
 
285
+ async function initiateSelfUpdate(
286
+ reason: string,
287
+ notificationTargets: RestartNotificationTarget[] = [],
288
+ ): Promise<void> {
289
+ if (restartRequested) {
290
+ await sendRestartTexts(notificationTargets, "Agent Bot 已在重启或更新中,请稍候。");
291
+ return;
292
+ }
293
+ const planPath = pendingSelfUpdatePlanPath;
294
+ if (!planPath) return;
295
+ restartRequested = true;
296
+ await safeRestartNotifier?.flush();
297
+ const allNotificationTargets = mergeRestartNotificationTargets(
298
+ notificationTargets,
299
+ safeRestartNotifier?.getNotificationTargets() ?? [],
300
+ );
301
+ await sendRestartTexts(
302
+ allNotificationTargets,
303
+ "Agent Bot 正在更新,完成或回滚后会自动恢复服务并发送启动状态通知。",
304
+ );
305
+ const environmentRefresh = refreshedSystemEnvironment();
306
+ if (environmentRefresh.error) {
307
+ logger.warn({ error: environmentRefresh.error }, "Failed to refresh the environment for Agent Bot update.");
308
+ }
309
+ try {
310
+ assertSelfUpdatePlanPath(planPath, agentBotHome());
311
+ launchSelfUpdateRunner(planPath, {
312
+ waitPids: [process.pid, ...(supervised ? [process.ppid] : [])],
313
+ environment: replacementSupervisorEnvironment(
314
+ reason,
315
+ environmentRefresh.environment,
316
+ allNotificationTargets,
317
+ ),
318
+ });
319
+ } catch (error) {
320
+ restartRequested = false;
321
+ releaseSelfUpdatePlan(planPath);
322
+ pendingSelfUpdatePlanPath = undefined;
323
+ logger.error({ error, planPath }, "Failed to launch Agent Bot self-update; keeping the current service online.");
324
+ await sendRestartTexts(
325
+ allNotificationTargets,
326
+ `Agent Bot 更新未启动,当前服务保持在线:${error instanceof Error ? error.message : String(error)}`,
327
+ );
328
+ return;
329
+ }
330
+ pendingSelfUpdatePlanPath = undefined;
331
+ setTimeout(() => void shutdown(STOP_EXIT_CODE), 100);
332
+ }
333
+
243
334
  async function handleControlRequest(request: ControlRequest): Promise<ControlResponse> {
244
335
  switch (request.action) {
245
336
  case "health":
@@ -268,6 +359,9 @@ async function handleControlRequest(request: ControlRequest): Promise<ControlRes
268
359
  },
269
360
  };
270
361
  case "server_restart": {
362
+ if (pendingSelfUpdatePlanPath) {
363
+ return { ok: false, message: "An Agent Bot update is already pending." };
364
+ }
271
365
  const notificationTarget = inferControlRestartTarget(request.notificationSessionId);
272
366
  if (request.mode === "safe") {
273
367
  const newlyScheduled = safeRestart.schedule(request.reason, notificationTarget);
@@ -284,18 +378,90 @@ async function handleControlRequest(request: ControlRequest): Promise<ControlRes
284
378
  ), 25);
285
379
  return { ok: true, message: "Immediate restart requested." };
286
380
  }
381
+ case "server_update": {
382
+ if (restartRequested || pendingSelfUpdatePlanPath || safeRestart.scheduled) {
383
+ return { ok: false, message: "A restart or update is already pending." };
384
+ }
385
+ assertSelfUpdatePlanPath(request.planPath, agentBotHome());
386
+ const notificationTarget = inferControlRestartTarget(request.notificationSessionId);
387
+ pendingSelfUpdatePlanPath = request.planPath;
388
+ safeRestart.schedule(request.reason, notificationTarget);
389
+ return {
390
+ ok: true,
391
+ message: "Agent Bot update scheduled after active tasks finish and the server becomes idle.",
392
+ };
393
+ }
287
394
  case "server_stop":
288
395
  safeRestart.cancel();
396
+ if (pendingSelfUpdatePlanPath) releaseSelfUpdatePlan(pendingSelfUpdatePlanPath);
397
+ pendingSelfUpdatePlanPath = undefined;
289
398
  setTimeout(() => void shutdown(STOP_EXIT_CODE), 25);
290
399
  return { ok: true, message: "Agent Bot server stop requested." };
291
400
  case "task_status":
292
401
  return { ok: true, data: await controller.controlGetTaskStatus(request.localSessionId) };
293
402
  case "task_stop":
294
403
  return { ok: true, message: await controller.controlStopTask(request.localSessionId) };
404
+ case "task_archive":
405
+ return { ok: true, data: await controller.controlArchiveTask(request.localSessionId) };
295
406
  case "task_title":
296
407
  return { ok: true, message: await controller.controlSetTaskTitle(request.localSessionId, request.title) };
297
408
  case "task_prompt":
298
409
  return { ok: true, message: await controller.controlSendTaskPrompt(request.localSessionId, request.text) };
410
+ case "task_new":
411
+ return {
412
+ ok: true,
413
+ data: await controller.controlCreateTask(
414
+ request.localSessionId,
415
+ request.title,
416
+ request.cwd,
417
+ request.projectless === true,
418
+ request.agentName,
419
+ ),
420
+ };
421
+ case "task_fork":
422
+ return { ok: true, data: await controller.controlForkTask(request.localSessionId) };
423
+ case "task_switch":
424
+ return {
425
+ ok: true,
426
+ data: await controller.controlSwitchTask(
427
+ request.localSessionId,
428
+ request.targetLocalSessionId,
429
+ request.previous === true,
430
+ ),
431
+ };
432
+ case "task_queue":
433
+ return { ok: true, data: await controller.controlQueueTaskPrompt(request.localSessionId, request.text) };
434
+ case "task_agent":
435
+ return { ok: true, data: controller.controlTaskAgent(request.localSessionId, request.agentName) };
436
+ case "task_settings":
437
+ return {
438
+ ok: true,
439
+ data: await controller.controlTaskSettings(request.localSessionId, request.setting, request.value),
440
+ };
441
+ case "task_goal":
442
+ return {
443
+ ok: true,
444
+ data: await controller.controlTaskGoal(
445
+ request.localSessionId,
446
+ request.goalAction,
447
+ request.objective,
448
+ ),
449
+ };
450
+ case "task_turns":
451
+ return { ok: true, data: controller.controlListTaskTurns(request.localSessionId) };
452
+ case "task_reset":
453
+ return { ok: true, data: await controller.controlResetTaskToTurn(request.localSessionId, request.turnId) };
454
+ case "task_mute":
455
+ return { ok: true, data: controller.controlTaskMute(request.localSessionId, request.enabled) };
456
+ case "task_shell":
457
+ return { ok: true, data: await controller.controlRunTaskShell(request.localSessionId, request.command) };
458
+ case "task_directory":
459
+ return {
460
+ ok: true,
461
+ data: await controller.controlListTaskDirectory(request.localSessionId, request.directory, request.page),
462
+ };
463
+ case "task_send_file":
464
+ return { ok: true, data: { messageId: await controller.controlSendTaskFile(request.localSessionId, request.filePath) } };
299
465
  case "task_new_group":
300
466
  return {
301
467
  ok: true,
@@ -327,6 +493,10 @@ async function shutdown(
327
493
  ): Promise<void> {
328
494
  if (shuttingDown) return;
329
495
  shuttingDown = true;
496
+ if (pendingSelfUpdatePlanPath) {
497
+ releaseSelfUpdatePlan(pendingSelfUpdatePlanPath);
498
+ pendingSelfUpdatePlanPath = undefined;
499
+ }
330
500
  logger.info({ exitCode }, "Shutting down Agent Bot.");
331
501
  safeRestart.cancel();
332
502
  feishuConnector?.stop();