@keyou007/agent-bot 0.1.22 → 0.1.24-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 (77) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +26 -2
  3. package/README.zh.md +26 -2
  4. package/config.example.yaml +5 -0
  5. package/dist/cli/controlProtocol.js.map +1 -1
  6. package/dist/cli/help.js +4 -0
  7. package/dist/cli/help.js.map +1 -1
  8. package/dist/cli/taskGroupOptions.js +27 -0
  9. package/dist/cli/taskGroupOptions.js.map +1 -1
  10. package/dist/cli.js +21 -1
  11. package/dist/cli.js.map +1 -1
  12. package/dist/codex/CodexProcessManager.js +19 -3
  13. package/dist/codex/CodexProcessManager.js.map +1 -1
  14. package/dist/codex/CodexRuntime.js +92 -9
  15. package/dist/codex/CodexRuntime.js.map +1 -1
  16. package/dist/commands/CommandRouter.js +27 -0
  17. package/dist/commands/CommandRouter.js.map +1 -1
  18. package/dist/config/loadConfig.js +4 -0
  19. package/dist/config/loadConfig.js.map +1 -1
  20. package/dist/config/schema.js +1 -0
  21. package/dist/config/schema.js.map +1 -1
  22. package/dist/feishu/CardRenderer.js +31 -2
  23. package/dist/feishu/CardRenderer.js.map +1 -1
  24. package/dist/feishu/FeishuMessageClient.js +6 -3
  25. package/dist/feishu/FeishuMessageClient.js.map +1 -1
  26. package/dist/feishu/MergedForwardMessage.js +17 -7
  27. package/dist/feishu/MergedForwardMessage.js.map +1 -1
  28. package/dist/index.js +50 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/local-files/TurnPreviewPage.js +106 -87
  31. package/dist/local-files/TurnPreviewPage.js.map +1 -1
  32. package/dist/proxy/ConversationExport.js +41 -0
  33. package/dist/proxy/ConversationExport.js.map +1 -0
  34. package/dist/proxy/ProxySessionController.js +118 -7
  35. package/dist/proxy/ProxySessionController.js.map +1 -1
  36. package/dist/state/StateStore.js +65 -0
  37. package/dist/state/StateStore.js.map +1 -1
  38. package/dist/state/migrations.js +12 -0
  39. package/dist/state/migrations.js.map +1 -1
  40. package/dist/updates/AutomaticUpdatePreparer.js +66 -0
  41. package/dist/updates/AutomaticUpdatePreparer.js.map +1 -0
  42. package/dist/updates/DailyUpdateMonitor.js +261 -0
  43. package/dist/updates/DailyUpdateMonitor.js.map +1 -0
  44. package/dist/updates/PublishedRelease.js +43 -0
  45. package/dist/updates/PublishedRelease.js.map +1 -0
  46. package/dist/updates/types.js +2 -0
  47. package/dist/updates/types.js.map +1 -0
  48. package/docs/technical-reference.md +4 -0
  49. package/docs/technical-reference.zh.md +4 -0
  50. package/npm-shrinkwrap.json +2 -2
  51. package/package.json +1 -1
  52. package/skills/agent-bot/SKILL.md +11 -0
  53. package/src/cli/controlProtocol.ts +9 -0
  54. package/src/cli/help.ts +4 -0
  55. package/src/cli/taskGroupOptions.ts +33 -3
  56. package/src/cli.ts +21 -0
  57. package/src/codex/CodexProcessManager.ts +21 -4
  58. package/src/codex/CodexRuntime.ts +104 -12
  59. package/src/commands/CommandRouter.ts +24 -0
  60. package/src/commands/commandTypes.ts +1 -0
  61. package/src/config/loadConfig.ts +4 -0
  62. package/src/config/schema.ts +4 -1
  63. package/src/feishu/CardRenderer.ts +34 -2
  64. package/src/feishu/FeishuMessageClient.ts +6 -2
  65. package/src/feishu/MergedForwardMessage.ts +29 -7
  66. package/src/feishu/types.ts +1 -1
  67. package/src/index.ts +49 -0
  68. package/src/local-files/TurnPreviewPage.ts +105 -88
  69. package/src/proxy/ConversationExport.ts +43 -0
  70. package/src/proxy/ProxySessionController.ts +166 -8
  71. package/src/runtime/types.ts +6 -0
  72. package/src/state/StateStore.ts +69 -0
  73. package/src/state/migrations.ts +12 -0
  74. package/src/updates/AutomaticUpdatePreparer.ts +76 -0
  75. package/src/updates/DailyUpdateMonitor.ts +252 -0
  76. package/src/updates/PublishedRelease.ts +44 -0
  77. package/src/updates/types.ts +19 -0
@@ -24,6 +24,7 @@ import { createId } from "../utils/id.js";
24
24
  import { formatStorageSize } from "../utils/formatStorageSize.js";
25
25
  import { truncateMiddle, truncateText } from "../utils/markdown.js";
26
26
  import { normalizeTaskTitle } from "../utils/taskTitle.js";
27
+ import { conversationImportPrompt, exportConversation } from "./ConversationExport.js";
27
28
  import { executeShellCommand, } from "../utils/executeShellCommand.js";
28
29
  class PresentedRequestError extends Error {
29
30
  originalError;
@@ -109,6 +110,8 @@ const HELP_COMMAND_SECTIONS = [
109
110
  usage: "[序号或任务 ID]",
110
111
  description: "从当前或指定任务最近完成轮次创建分支",
111
112
  },
113
+ { command: "/clone", usage: "[title] [--agent <name>]", description: "导出用户 Prompt 和最终回答,交给新 Agent 任务读取" },
114
+ { command: "/clonegroup", usage: "[title] [--agent <name>]", description: "将对话上下文交给新群中的 Agent 任务" },
112
115
  { command: "/turns", usage: "[Turn ID 或序号]", description: "浏览历史轮次,并可 Reset 对话上下文;带参数查看运行详情" },
113
116
  {
114
117
  command: "/title",
@@ -601,7 +604,12 @@ export class ProxySessionController {
601
604
  await this.outbound.withReplyTarget(contextKey, replyTarget, async () => {
602
605
  try {
603
606
  const kind = String(scopedAction.value.action ?? "");
604
- if (kind === "help_command") {
607
+ if (kind === "agentbot_update_cancel") {
608
+ if (!this.lifecycle?.cancelAutomaticUpdate)
609
+ throw new Error("自动更新功能未启用。");
610
+ await this.lifecycle.cancelAutomaticUpdate(scopedAction);
611
+ }
612
+ else if (kind === "help_command") {
605
613
  await this.executeHelpCommandAction(scopedAction, contextKey, replyTarget);
606
614
  }
607
615
  else if (kind === "turn_details") {
@@ -1085,6 +1093,12 @@ export class ProxySessionController {
1085
1093
  task: created.task,
1086
1094
  };
1087
1095
  }
1096
+ async controlCloneTask(localSessionId, requestedTitle, requestedAgentName, newGroup = false, userOpenId) {
1097
+ const source = this.requireControlSession(localSessionId);
1098
+ const contextKey = this.controlSessionContextKey(source);
1099
+ const replyTarget = this.controlSessionReplyTarget(source);
1100
+ return this.outbound.withReplyTarget(contextKey, replyTarget, () => this.cloneSession(contextKey, { ...source, contextKey }, requestedTitle, requestedAgentName, newGroup, userOpenId, replyTarget));
1101
+ }
1088
1102
  async controlForkTaskGroup(localSessionId, requestedTitle, userOpenId) {
1089
1103
  const source = this.store.getSession(localSessionId);
1090
1104
  if (!source || source.status === "closed")
@@ -1228,6 +1242,7 @@ export class ProxySessionController {
1228
1242
  await loaded.runtime.setReasoningEffort(record.localSessionId, nextEffort);
1229
1243
  }
1230
1244
  this.store.updateRuntimeSession(record.localSessionId, { model: nextValue, reasoningEffort: nextEffort });
1245
+ this.outbound.updateSessionModel(record.localSessionId, nextValue);
1231
1246
  }
1232
1247
  else if (setting === "thinking" && nextValue) {
1233
1248
  const models = await loaded.runtime.listModels(loaded.session.modelProvider, loaded.session.model);
@@ -1478,6 +1493,19 @@ export class ProxySessionController {
1478
1493
  await this.outbound.sendText(contextKey, CREATE_GROUP_STARTED_MESSAGE);
1479
1494
  await this.createFeishuGroup(contextKey, context.defaultAgent, command.title, userId, command.cwd === undefined ? undefined : resolveUserPath(command.cwd), command.projectless === true);
1480
1495
  return;
1496
+ case "clone":
1497
+ case "clonegroup": {
1498
+ let source = this.currentSession(contextKey);
1499
+ let throughTurnId;
1500
+ if (incomingMessage?.threadContext && (!source
1501
+ || !this.store.findLatestCompletedTurnId(source.localSessionId, contextKey))) {
1502
+ const resolved = this.resolveThreadForkAnchor(incomingMessage, false);
1503
+ source = resolved.source;
1504
+ throughTurnId = resolved.anchor.turnId;
1505
+ }
1506
+ await this.cloneSession(contextKey, source ?? this.requireCurrentSession(contextKey), command.title, command.agentName, command.type === "clonegroup", userId, replyTarget, throughTurnId);
1507
+ return;
1508
+ }
1481
1509
  case "forkgroup":
1482
1510
  await this.forkCurrentSessionToFeishuGroup(contextKey, command.title, userId, incomingMessage);
1483
1511
  return;
@@ -2297,7 +2325,7 @@ export class ProxySessionController {
2297
2325
  throw error;
2298
2326
  }
2299
2327
  }
2300
- resolveThreadForkAnchor(message) {
2328
+ resolveThreadForkAnchor(message, includeSnapshot = true) {
2301
2329
  if (!message.threadContext || !message.threadId || !message.chatId) {
2302
2330
  throw new Error("当前消息不属于可识别的飞书话题。");
2303
2331
  }
@@ -2316,8 +2344,9 @@ export class ProxySessionController {
2316
2344
  if (baseChatContextKey(anchor.contextKey ?? source.contextKey) !== `chat_id:${message.chatId}`) {
2317
2345
  throw new Error("话题来源任务不属于当前会话,已拒绝创建分支。");
2318
2346
  }
2319
- const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(anchor.turnId));
2320
- if (isTurnStillRunning(snapshot?.status)
2347
+ const snapshot = includeSnapshot ? turnViewSnapshot(this.store.getTurnSnapshot(anchor.turnId)) : undefined;
2348
+ const status = includeSnapshot ? snapshot?.status : this.store.getTurnPromptSummary(anchor.turnId)?.status;
2349
+ if (isTurnStillRunning(status)
2321
2350
  || (source.lastTurnId === anchor.turnId && source.lastTurnStatus === "running")) {
2322
2351
  throw new Error("话题对应的轮次仍在执行,App Server 暂时不能从这一轮 fork。请等待该轮完成后再在话题中发送消息。");
2323
2352
  }
@@ -2914,6 +2943,7 @@ export class ProxySessionController {
2914
2943
  while (true) {
2915
2944
  let pendingTurnId;
2916
2945
  try {
2946
+ this.outbound.updateSessionModel(loaded.record.localSessionId, loaded.runtime.getSession(loaded.record.localSessionId)?.model ?? loaded.session.model);
2917
2947
  pendingTurnId = await this.outbound.startPendingTurn(loaded.record.localSessionId, loaded.record.contextKey, title, replyTarget, cardPrompt);
2918
2948
  }
2919
2949
  catch (error) {
@@ -2985,7 +3015,7 @@ export class ProxySessionController {
2985
3015
  }
2986
3016
  return turnId;
2987
3017
  }
2988
- async createSession(contextKey, agentName, cwd, announce, prepareTurn, prompt, replyTarget, requestedTitle, executionSettings = {}) {
3018
+ async createSession(contextKey, agentName, cwd, announce, prepareTurn, prompt, replyTarget, requestedTitle, executionSettings = {}, activate = true) {
2989
3019
  const agent = this.ensureAgent(agentName);
2990
3020
  const resolvedExecutionSettings = mergeExecutionSettings(agent.defaults ?? {}, executionSettings);
2991
3021
  const localSessionId = createId("sess");
@@ -3004,7 +3034,8 @@ export class ProxySessionController {
3004
3034
  permissionMode: resolvedExecutionSettings.permissionMode,
3005
3035
  });
3006
3036
  }
3007
- this.store.setCurrentSession(contextKey, localSessionId);
3037
+ if (activate)
3038
+ this.store.setCurrentSession(contextKey, localSessionId);
3008
3039
  this.outbound.registerSession(localSessionId, contextKey, initialTitle, sessionCwd, this.agentLabel(agentName), resolvedExecutionSettings.model);
3009
3040
  const runtime = this.runtimes.forAgent(agentName);
3010
3041
  try {
@@ -3085,6 +3116,83 @@ export class ProxySessionController {
3085
3116
  await this.outbound.sendText(sourceContextKey, `已创建飞书群:${group.name},并创建新任务 ${taskDescription}。`);
3086
3117
  return { group, task };
3087
3118
  }
3119
+ async cloneSession(sourceContextKey, source, requestedTitle, requestedAgentName, newGroup, userId, replyTarget, throughTurnId) {
3120
+ const context = this.store.getOrCreateUserContext(sourceContextKey, this.config.defaults.agent);
3121
+ const agentName = requestedAgentName?.trim() || context.defaultAgent;
3122
+ this.ensureAgent(agentName);
3123
+ if (newGroup && !userId?.startsWith("ou_")) {
3124
+ throw new Error("/clonegroup 只能由具有 open_id 的飞书用户触发。");
3125
+ }
3126
+ await this.outbound.sendText(sourceContextKey, `正在导出对话并创建 ${this.agentLabel(agentName)} 克隆任务,请稍后。`);
3127
+ const exported = await exportConversation(path.join(path.dirname(this.config.storage.sqlitePath), "context-transfers"), this.readCloneConversation(source, throughTurnId));
3128
+ const title = normalizeTaskTitle(requestedTitle) ?? `${source.title ?? "任务"}(克隆)`;
3129
+ const cwd = detectProjectlessWorkspace(source.cwd) ? undefined : source.cwd;
3130
+ const group = newGroup ? await this.createFeishuGroupContext(sourceContextKey, agentName, title, userId, cwd, "/clonegroup") : undefined;
3131
+ const contextKey = group?.contextKey ?? sourceContextKey;
3132
+ let task;
3133
+ try {
3134
+ task = await this.createSession(contextKey, agentName, cwd, false, false, undefined, undefined, title, { permissionMode: source.permissionMode ?? "auto" }, false);
3135
+ }
3136
+ catch (error) {
3137
+ await this.outbound.sendText(contextKey, `${group ? "群已创建,但" : ""}克隆任务创建失败;上下文文件已保留:${exported.filePath}`);
3138
+ throw error;
3139
+ }
3140
+ this.store.audit(contextKey, "session_cloned", {
3141
+ sourceLocalSessionId: source.localSessionId,
3142
+ clonedLocalSessionId: task.localSessionId,
3143
+ contextFile: exported.filePath,
3144
+ turnCount: exported.turnCount,
3145
+ });
3146
+ try {
3147
+ await this.startTurn(await this.loadSession(task), conversationImportPrompt(exported.filePath), group ? undefined : replyTarget, undefined, { displayPrompt: `读取历史上下文(${exported.turnCount} 轮对话)` });
3148
+ }
3149
+ catch (error) {
3150
+ await this.outbound.sendText(contextKey, `克隆任务已创建,但上下文读取启动失败。任务 ID:${task.localSessionId};上下文文件:${exported.filePath}`);
3151
+ throw error;
3152
+ }
3153
+ this.store.setCurrentSession(contextKey, task.localSessionId);
3154
+ await this.outbound.sendText(sourceContextKey, group
3155
+ ? `已创建克隆群:${group.name},正在读取 ${exported.turnCount} 轮历史对话。`
3156
+ : `已切换到 ${this.agentLabel(agentName)} 克隆任务:${title},正在读取 ${exported.turnCount} 轮历史对话。`);
3157
+ return {
3158
+ sourceLocalSessionId: source.localSessionId,
3159
+ contextFile: exported.filePath,
3160
+ turnCount: exported.turnCount,
3161
+ task: this.store.getSession(task.localSessionId) ?? task,
3162
+ ...(group ? { group } : {}),
3163
+ };
3164
+ }
3165
+ async *readCloneConversation(source, throughTurnId) {
3166
+ const runtime = this.runtimes.forAgent(source.agentName);
3167
+ if (!source.remoteSessionId || !runtime.readConversation) {
3168
+ yield* this.store.readConversation(source.localSessionId, throughTurnId);
3169
+ return;
3170
+ }
3171
+ let localSessionId = source.localSessionId;
3172
+ let remoteSessionId = source.remoteSessionId;
3173
+ const visited = new Set();
3174
+ while (!visited.has(remoteSessionId)) {
3175
+ visited.add(remoteSessionId);
3176
+ const fork = localSessionId
3177
+ ? this.store.getForkHistorySource(localSessionId) : undefined;
3178
+ if (fork && runtime.readRemoteForkSource) {
3179
+ const remote = await runtime.readRemoteForkSource(remoteSessionId);
3180
+ const completed = remote.lastCompletedTurnId
3181
+ ?? (remote.lastTurnStatus === "completed" ? remote.lastTurnId : undefined);
3182
+ if (!completed) {
3183
+ if (!fork.sourceRemoteSessionId)
3184
+ throw new Error("找不到分支的原始任务,无法导出继承的上下文。");
3185
+ throughTurnId ??= fork.sourceTurnId;
3186
+ localSessionId = fork.sourceLocalSessionId;
3187
+ remoteSessionId = fork.sourceRemoteSessionId;
3188
+ continue;
3189
+ }
3190
+ }
3191
+ yield* runtime.readConversation(remoteSessionId, throughTurnId);
3192
+ return;
3193
+ }
3194
+ throw new Error("分支来源包含循环,无法导出上下文。");
3195
+ }
3088
3196
  async forkCurrentSessionToFeishuGroup(sourceContextKey, requestedTitle, userId, incomingMessage) {
3089
3197
  if (!userId?.startsWith("ou_")) {
3090
3198
  throw new Error("/forkgroup 只能由具有 open_id 的飞书用户消息触发。");
@@ -4062,7 +4170,9 @@ export class ProxySessionController {
4062
4170
  throw error;
4063
4171
  }
4064
4172
  };
4065
- return loaded.runtime.setExecutionSettings(loaded.record.localSessionId, settings, persist);
4173
+ const session = await loaded.runtime.setExecutionSettings(loaded.record.localSessionId, settings, persist);
4174
+ this.outbound.updateSessionModel(loaded.record.localSessionId, session.model);
4175
+ return session;
4066
4176
  }
4067
4177
  async assertProviderModelSettings(loaded, modelProvider, model, effort) {
4068
4178
  await this.assertModelProvider(loaded, modelProvider);
@@ -4116,6 +4226,7 @@ export class ProxySessionController {
4116
4226
  await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, nextEffort);
4117
4227
  }
4118
4228
  this.store.updateRuntimeSession(loaded.record.localSessionId, { model, reasoningEffort: nextEffort });
4229
+ this.outbound.updateSessionModel(loaded.record.localSessionId, model);
4119
4230
  await this.persistAgentExecutionDefaults(loaded.record.localSessionId);
4120
4231
  const effortMessage = nextEffort && nextEffort !== currentEffort
4121
4232
  ? `,思考强度已自动调整为 ${nextEffort}`