@keyou007/agent-bot 0.1.14-alpha.2 → 0.1.14-alpha.3

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 (46) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +7 -4
  3. package/README.zh.md +7 -4
  4. package/dist/cli/FeishuAppConfiguration.js +4 -0
  5. package/dist/cli/FeishuAppConfiguration.js.map +1 -1
  6. package/dist/cli/controlProtocol.js.map +1 -1
  7. package/dist/cli/help.js +2 -0
  8. package/dist/cli/help.js.map +1 -1
  9. package/dist/cli.js +18 -0
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commands/CommandRouter.js +9 -0
  12. package/dist/commands/CommandRouter.js.map +1 -1
  13. package/dist/feishu/CardRenderer.js +94 -15
  14. package/dist/feishu/CardRenderer.js.map +1 -1
  15. package/dist/feishu/FeishuMessageClient.js +16 -0
  16. package/dist/feishu/FeishuMessageClient.js.map +1 -1
  17. package/dist/feishu/FeishuTurnPresenter.js +42 -6
  18. package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/presentation/OutboundRouter.js +7 -0
  22. package/dist/presentation/OutboundRouter.js.map +1 -1
  23. package/dist/presentation/TurnStateReducer.js +21 -7
  24. package/dist/presentation/TurnStateReducer.js.map +1 -1
  25. package/dist/proxy/ProxySessionController.js +102 -2
  26. package/dist/proxy/ProxySessionController.js.map +1 -1
  27. package/dist/state/StateStore.js +35 -0
  28. package/dist/state/StateStore.js.map +1 -1
  29. package/npm-shrinkwrap.json +2 -2
  30. package/package.json +1 -1
  31. package/skills/agent-bot/SKILL.md +2 -0
  32. package/src/cli/FeishuAppConfiguration.ts +4 -0
  33. package/src/cli/controlProtocol.ts +8 -0
  34. package/src/cli/help.ts +2 -0
  35. package/src/cli.ts +28 -0
  36. package/src/commands/CommandRouter.ts +8 -0
  37. package/src/commands/commandTypes.ts +1 -0
  38. package/src/feishu/CardRenderer.ts +114 -12
  39. package/src/feishu/FeishuMessageClient.ts +33 -0
  40. package/src/feishu/FeishuTurnPresenter.ts +43 -6
  41. package/src/feishu/types.ts +1 -0
  42. package/src/index.ts +2 -0
  43. package/src/presentation/OutboundRouter.ts +7 -0
  44. package/src/presentation/TurnStateReducer.ts +21 -7
  45. package/src/proxy/ProxySessionController.ts +131 -3
  46. package/src/state/StateStore.ts +37 -0
@@ -76,6 +76,7 @@ const HELP_COMMAND_SECTIONS = [
76
76
  usage: "[序号或任务 ID]",
77
77
  description: "归档当前或指定任务",
78
78
  },
79
+ { command: "/dismiss", description: "归档当前任务并解散当前群" },
79
80
  {
80
81
  command: "/switch",
81
82
  usage: "[序号或任务 ID]",
@@ -273,7 +274,7 @@ export class ProxySessionController {
273
274
  if (isQueueIndependentCommand(command)) {
274
275
  await this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
275
276
  try {
276
- if (command.type !== "forkgroup")
277
+ if (command.type !== "forkgroup" && command.type !== "dismiss")
277
278
  await this.ensureThreadFork(message);
278
279
  await this.execute(message.contextKey, command, message.messageId, replyTarget, localImagePaths, message.userId, message);
279
280
  if (!isPromptCommand(command))
@@ -426,6 +427,14 @@ export class ProxySessionController {
426
427
  });
427
428
  await this.refreshSessionsCardFromAction(scopedAction);
428
429
  }
430
+ else if (kind === "group_dismiss_keep") {
431
+ this.assertDismissGroupRequester(scopedAction);
432
+ await this.outbound.updateInteractiveCard(contextKey, requiredCardMessageId(scopedAction.messageId), this.cardRenderer.renderDismissGroupKept());
433
+ }
434
+ else if (kind === "group_dismiss_confirm") {
435
+ this.assertDismissGroupRequester(scopedAction);
436
+ await this.dismissGroupAndArchiveTask(contextKey, String(scopedAction.value.sessionId ?? ""), "command");
437
+ }
429
438
  else if (kind === "session_stop") {
430
439
  const sessionId = String(scopedAction.value.sessionId ?? "");
431
440
  await this.stopSessionReference(contextKey, sessionId);
@@ -577,6 +586,12 @@ export class ProxySessionController {
577
586
  source: "cli",
578
587
  });
579
588
  }
589
+ async controlDismissTask(localSessionId) {
590
+ const record = this.store.getSession(localSessionId);
591
+ if (!record)
592
+ throw new Error(`Task not found: ${localSessionId}`);
593
+ return this.dismissGroupAndArchiveTask(this.controlSessionContextKey(record), localSessionId, "cli");
594
+ }
580
595
  async controlGetTaskStatus(localSessionId) {
581
596
  const record = this.store.getSession(localSessionId);
582
597
  if (!record)
@@ -1114,6 +1129,9 @@ export class ProxySessionController {
1114
1129
  });
1115
1130
  return;
1116
1131
  }
1132
+ case "dismiss":
1133
+ await this.openDismissGroupCard(contextKey, userId);
1134
+ return;
1117
1135
  case "status":
1118
1136
  await this.status(contextKey, command.sessionId);
1119
1137
  return;
@@ -3875,6 +3893,88 @@ export class ProxySessionController {
3875
3893
  title,
3876
3894
  };
3877
3895
  }
3896
+ async openDismissGroupCard(contextKey, userId) {
3897
+ const groupContextKey = this.dismissibleGroupContextKey(contextKey);
3898
+ if (!userId)
3899
+ throw new Error("无法识别当前用户,不能发起解散群操作。");
3900
+ const session = this.requireCurrentSession(groupContextKey);
3901
+ await this.outbound.sendInteractiveCard(groupContextKey, this.cardRenderer.renderDismissGroupConfirmation({
3902
+ contextKey: groupContextKey,
3903
+ sessionId: session.localSessionId,
3904
+ taskTitle: session.title ?? session.remoteSessionId ?? session.localSessionId,
3905
+ requestedBy: userId,
3906
+ }));
3907
+ }
3908
+ assertDismissGroupRequester(action) {
3909
+ const requestedBy = String(action.value.requestedBy ?? "");
3910
+ if (!requestedBy || !action.userId || action.userId !== requestedBy) {
3911
+ throw new Error("只有发起解散操作的用户可以点击这张卡片。");
3912
+ }
3913
+ }
3914
+ async dismissGroupAndArchiveTask(contextKey, localSessionId, source) {
3915
+ const groupContextKey = this.dismissibleGroupContextKey(contextKey);
3916
+ const session = this.store.getSessionForContext(localSessionId, groupContextKey);
3917
+ if (!session)
3918
+ throw new Error("解散群卡片已失效,请重新发送 /dismiss。");
3919
+ const currentSessionId = this.store.getUserContext(groupContextKey)?.currentSessionId;
3920
+ if (session.status !== "closed" && currentSessionId !== localSessionId) {
3921
+ throw new Error("当前任务已经切换,解散群卡片已失效,请重新发送 /dismiss。");
3922
+ }
3923
+ this.assertNoOtherActiveGroupTasks(groupContextKey, localSessionId);
3924
+ const archived = session.status === "closed"
3925
+ ? {
3926
+ localSessionId: session.localSessionId,
3927
+ remoteSessionId: session.remoteSessionId ?? session.acpSessionId ?? "",
3928
+ title: session.title ?? session.remoteSessionId ?? session.localSessionId,
3929
+ }
3930
+ : await this.archiveSessionReference(groupContextKey, localSessionId, {
3931
+ announce: false,
3932
+ source: "dismiss",
3933
+ });
3934
+ const chatId = groupContextKey.slice("chat_id:".length);
3935
+ try {
3936
+ await this.outbound.deleteGroup(groupContextKey, chatId);
3937
+ }
3938
+ catch (error) {
3939
+ const message = error instanceof Error ? error.message : String(error);
3940
+ throw new Error(`当前任务已归档,但解散群失败:${message}`);
3941
+ }
3942
+ const detachedSessionIds = this.store.removeChatContext(groupContextKey);
3943
+ for (const detachedSessionId of detachedSessionIds) {
3944
+ const linked = this.store.getSession(detachedSessionId);
3945
+ const routedContextKey = this.outbound.getSessionContextKey(detachedSessionId) ?? linked?.contextKey;
3946
+ if (routedContextKey && baseChatContextKey(routedContextKey) === groupContextKey) {
3947
+ this.outbound.unregisterSession(detachedSessionId);
3948
+ }
3949
+ }
3950
+ this.store.audit(groupContextKey, "group_dismissed", {
3951
+ chatId,
3952
+ localSessionId,
3953
+ remoteSessionId: archived.remoteSessionId,
3954
+ source,
3955
+ });
3956
+ return { ...archived, chatId };
3957
+ }
3958
+ assertNoOtherActiveGroupTasks(contextKey, dismissedSessionId) {
3959
+ const active = this.store.listSessionsForChat(contextKey).find((session) => (session.localSessionId !== dismissedSessionId
3960
+ && session.status !== "closed"
3961
+ && (session.status === "running"
3962
+ || Boolean(this.runtimes.forAgent(session.agentName).getSession(session.localSessionId)?.activeTurnId)
3963
+ || this.store.countQueuedPrompts(session.localSessionId) > 0)));
3964
+ if (active) {
3965
+ throw new Error(`群内任务仍在执行或有排队消息,暂时不能解散:${active.title ?? active.localSessionId}`);
3966
+ }
3967
+ }
3968
+ dismissibleGroupContextKey(contextKey) {
3969
+ if (isThreadContextKey(contextKey)) {
3970
+ throw new Error("/dismiss 不能在话题中使用,请回到群聊正文后重试。");
3971
+ }
3972
+ const groupContextKey = baseChatContextKey(contextKey);
3973
+ if (!groupContextKey.startsWith("chat_id:") || this.store.getChatContext(groupContextKey)?.chatType !== "group") {
3974
+ throw new Error("/dismiss 仅适用于普通群聊。");
3975
+ }
3976
+ return groupContextKey;
3977
+ }
3878
3978
  resolveSessionReference(contextKey, reference) {
3879
3979
  if (reference === undefined) {
3880
3980
  const previous = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent).previousSessionId;
@@ -4700,7 +4800,7 @@ function isBotOwnedActiveTurn(record, remote) {
4700
4800
  && record.lastTurnId === remote.lastTurnId;
4701
4801
  }
4702
4802
  function isQueueIndependentCommand(command) {
4703
- if (["archive", "stop", "status", "restart", "mute", "help", "sessions", "dir", "goal", "nosteer"].includes(command.type))
4803
+ if (["archive", "dismiss", "stop", "status", "restart", "mute", "help", "sessions", "dir", "goal", "nosteer"].includes(command.type))
4704
4804
  return true;
4705
4805
  if (command.type === "agent")
4706
4806
  return command.agent === undefined;