@keyou007/agent-bot 0.1.15-alpha.2 → 0.1.15-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.
@@ -1,6 +1,7 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { promises as fs } from "node:fs";
2
- import path from "node:path";
3
3
  import os from "node:os";
4
+ import path from "node:path";
4
5
  import { createProjectlessWorkspace, detectProjectlessWorkspace } from "../codex/ProjectlessWorkspace.js";
5
6
  import { resolveUserPath } from "../config/paths.js";
6
7
  import { CommandRouter } from "../commands/CommandRouter.js";
@@ -19,7 +20,8 @@ const MESSAGE_RECEIVED_REACTION = "OnIt";
19
20
  const MESSAGE_COMPLETED_REACTION = "DONE";
20
21
  const MESSAGE_FAILED_REACTION = "ERROR";
21
22
  const MESSAGE_CANCELLED_REACTION = "CrossMark";
22
- const SESSION_PAGE_SIZE = 5;
23
+ const SESSION_PAGE_SIZE = 10;
24
+ const SESSION_PROMPT_PREVIEW_LENGTH = 50;
23
25
  const DIRECTORY_PAGE_SIZE = 15;
24
26
  const RESET_HISTORY_PAGE_SIZE = 10;
25
27
  const MAX_LLM_TURN_RETRIES = 3;
@@ -458,8 +460,18 @@ export class ProxySessionController {
458
460
  }
459
461
  if (!this.store.claimInboundEvent(action.actionId, "card_action"))
460
462
  return;
461
- const contextKey = this.cardActionContextKey(action);
462
- const scopedAction = contextKey === action.contextKey ? action : { ...action, contextKey };
463
+ let resolvedAction;
464
+ try {
465
+ resolvedAction = this.resolveCardActionBinding(action);
466
+ }
467
+ catch (error) {
468
+ await this.sendError(action.contextKey, error);
469
+ return;
470
+ }
471
+ const contextKey = this.cardActionContextKey(resolvedAction);
472
+ const scopedAction = contextKey === resolvedAction.contextKey
473
+ ? resolvedAction
474
+ : { ...resolvedAction, contextKey };
463
475
  const replyTarget = isThreadContextKey(contextKey) && action.messageId
464
476
  ? { messageId: action.messageId, replyInThread: true }
465
477
  : undefined;
@@ -1237,6 +1249,19 @@ export class ProxySessionController {
1237
1249
  ? turnSession.contextKey
1238
1250
  : action.contextKey;
1239
1251
  }
1252
+ resolveCardActionBinding(action) {
1253
+ if (typeof action.value.action === "string")
1254
+ return action;
1255
+ const token = typeof action.value.t === "string" ? action.value.t : undefined;
1256
+ if (!token)
1257
+ return action;
1258
+ if (!action.messageId)
1259
+ throw new Error("任务列表卡片缺少消息 ID,请重新发送 /sessions。");
1260
+ const value = this.store.getCardActionBinding(action.messageId, token);
1261
+ if (!value)
1262
+ throw new Error("任务列表卡片已失效,请重新发送 /sessions。");
1263
+ return { ...action, value };
1264
+ }
1240
1265
  async execute(contextKey, command, messageId, replyTarget, localImagePaths, userId, incomingMessage) {
1241
1266
  const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
1242
1267
  if (!this.currentSession(contextKey) && isThreadContextKey(contextKey) && commandRequiresCurrentSession(command)) {
@@ -3969,10 +3994,28 @@ export class ProxySessionController {
3969
3994
  const remoteHint = remoteErrors.length > 0
3970
3995
  ? `部分 Agent 的任务读取失败:${remoteErrors.join(";")}`
3971
3996
  : undefined;
3972
- const entries = orderTaskListByProject(mergeTaskList(localSessions, remoteSessions, context.currentSessionId)
3997
+ const latestLocalUserPrompts = new Map();
3998
+ for (const session of localSessions) {
3999
+ const persisted = this.store.findLatestTurnSnapshotForSession(session.localSessionId);
4000
+ const prompt = latestUserPromptFromTurnView(turnViewSnapshot(persisted?.snapshot));
4001
+ if (prompt)
4002
+ latestLocalUserPrompts.set(session.localSessionId, prompt);
4003
+ }
4004
+ const latestRemoteUserPrompts = new Map();
4005
+ for (const { agentName, session: remote } of remoteSessions) {
4006
+ const local = this.store.findSessionByRemoteSessionId(remote.id, undefined, agentName);
4007
+ if (!local)
4008
+ continue;
4009
+ const persisted = this.store.findLatestTurnSnapshotForSession(local.localSessionId);
4010
+ const prompt = latestUserPromptFromTurnView(turnViewSnapshot(persisted?.snapshot));
4011
+ if (prompt)
4012
+ latestRemoteUserPrompts.set(agentRemoteKey(agentName, remote.id), prompt);
4013
+ }
4014
+ const globallyOrderedEntries = orderTaskListByRecency(mergeTaskList(localSessions, remoteSessions, context.currentSessionId, latestLocalUserPrompts, latestRemoteUserPrompts)
3973
4015
  .filter((entry) => !normalizedSearch
3974
4016
  || [entry.id, entry.title, entry.cwd, entry.agentName]
3975
4017
  .some((value) => value.toLowerCase().includes(normalizedSearch))));
4018
+ const entries = groupTaskListPagesByProject(globallyOrderedEntries, SESSION_PAGE_SIZE);
3976
4019
  const activeCount = entries.filter((entry) => entry.active).length;
3977
4020
  const lastKnownPage = Math.max(0, Math.ceil(entries.length / SESSION_PAGE_SIZE) - 1);
3978
4021
  const page = remoteHasMore ? requestedPage : Math.min(requestedPage, lastKnownPage);
@@ -3987,14 +4030,16 @@ export class ProxySessionController {
3987
4030
  const hasPrevious = page > 0;
3988
4031
  const hasNext = remoteHasMore || entries.length > offset + SESSION_PAGE_SIZE;
3989
4032
  this.lastSessionListings.set(contextKey, entries.map((entry) => entry.reference));
4033
+ const cardActionBindings = new Map();
4034
+ const bindCardAction = (action) => bindSessionCardAction(action, cardActionBindings);
3990
4035
  const cardEntries = visibleEntries.map((entry, index) => {
3991
4036
  const project = taskProjectInfo(entry);
3992
4037
  const projectActionSource = projectActionSources.get(project.key) ?? entry;
3993
- const marker = entry.current ? "✅" : entry.active ? "🟢" : "•";
4038
+ const marker = entry.current ? "✅ " : entry.active ? "🟢 " : "";
3994
4039
  const showStop = entry.status === "外部执行中"
3995
4040
  && entry.reference !== options.forceSwitchTaskId
3996
4041
  && entry.id !== options.forceSwitchTaskId;
3997
- const actions = entry.current ? [] : [{
4042
+ const actions = entry.current ? [] : [bindCardAction({
3998
4043
  text: showStop ? "Stop" : "Switch",
3999
4044
  type: showStop ? "danger" : "default",
4000
4045
  value: {
@@ -4004,8 +4049,8 @@ export class ProxySessionController {
4004
4049
  page: String(page),
4005
4050
  contextKey,
4006
4051
  },
4007
- }];
4008
- const projectActions = [{
4052
+ })];
4053
+ const projectActions = [bindCardAction({
4009
4054
  text: "New",
4010
4055
  value: {
4011
4056
  action: "session_new",
@@ -4014,7 +4059,7 @@ export class ProxySessionController {
4014
4059
  page: String(page),
4015
4060
  contextKey,
4016
4061
  },
4017
- }, {
4062
+ }), bindCardAction({
4018
4063
  text: "NewGroup",
4019
4064
  value: {
4020
4065
  action: "session_new_group",
@@ -4023,8 +4068,8 @@ export class ProxySessionController {
4023
4068
  page: String(page),
4024
4069
  contextKey,
4025
4070
  },
4026
- }];
4027
- actions.push({
4071
+ })];
4072
+ actions.push(bindCardAction({
4028
4073
  text: "Fork",
4029
4074
  value: {
4030
4075
  action: "session_fork",
@@ -4033,8 +4078,8 @@ export class ProxySessionController {
4033
4078
  page: String(page),
4034
4079
  contextKey,
4035
4080
  },
4036
- });
4037
- actions.push({
4081
+ }));
4082
+ actions.push(bindCardAction({
4038
4083
  text: "ForkGroup",
4039
4084
  value: {
4040
4085
  action: "session_fork_group",
@@ -4043,16 +4088,16 @@ export class ProxySessionController {
4043
4088
  page: String(page),
4044
4089
  contextKey,
4045
4090
  },
4046
- });
4047
- actions.push({
4091
+ }));
4092
+ actions.push(bindCardAction({
4048
4093
  text: "Status",
4049
4094
  value: {
4050
4095
  action: "session_status",
4051
4096
  sessionId: entry.reference,
4052
4097
  contextKey,
4053
4098
  },
4054
- });
4055
- actions.push({
4099
+ }));
4100
+ actions.push(bindCardAction({
4056
4101
  text: "Archive",
4057
4102
  value: {
4058
4103
  action: "session_archive",
@@ -4061,18 +4106,18 @@ export class ProxySessionController {
4061
4106
  page: String(page),
4062
4107
  contextKey,
4063
4108
  },
4064
- });
4109
+ }));
4065
4110
  const title = truncateText(entry.title.replace(/\s+/gu, " ").trim() || "未命名任务", 56);
4111
+ const lastUserPrompt = sessionPromptPreview(entry.lastUserPrompt);
4066
4112
  return {
4067
4113
  project,
4068
4114
  projectActions,
4069
4115
  cardEntry: {
4070
4116
  reference: entry.reference,
4071
- summary: `${offset + index + 1}. ${marker} ${title} · ${entry.agentName}`,
4117
+ summary: `${offset + index + 1}. ${marker}${title} · ${entry.agentName}`,
4072
4118
  detailLines: [
4073
- `**状态 / 更新时间**:${cardText(entry.status)} / ${cardText(entry.updatedLabel)}`,
4074
- `**Agent / 任务 ID**:${cardCode(entry.agentName)} / ${cardCode(entry.id)}`,
4075
- `**目录**:${cardCode(entry.cwd || "目录未知")}`,
4119
+ cardText(lastUserPrompt),
4120
+ `**更新时间**:${cardText(entry.updatedLabel)}`,
4076
4121
  ],
4077
4122
  actions,
4078
4123
  current: entry.current,
@@ -4099,10 +4144,10 @@ export class ProxySessionController {
4099
4144
  ...(remoteHint ? [remoteHint] : []),
4100
4145
  `第 ${page + 1} 页 · 每页 ${SESSION_PAGE_SIZE} 个任务${hasNext ? "" : ` · 当前共 ${entries.length} 个任务`}`,
4101
4146
  "",
4102
- "> 项目行:**New** 新建任务,**NewGroup** 新建群。",
4147
+ "> 项目菜单:**New** 新建任务,**NewGroup** 新建群。",
4103
4148
  "> 任务详情:**Switch** 切换,**Stop** 停止,**Fork** / **ForkGroup** 创建分支,**Status** 查看状态,**Archive** 归档。",
4104
4149
  ], [
4105
- ...(hasPrevious ? [{
4150
+ ...(hasPrevious ? [bindCardAction({
4106
4151
  text: "Previous",
4107
4152
  value: {
4108
4153
  action: "session_page",
@@ -4110,8 +4155,8 @@ export class ProxySessionController {
4110
4155
  page: String(page - 1),
4111
4156
  contextKey,
4112
4157
  },
4113
- }] : []),
4114
- ...(hasNext ? [{
4158
+ })] : []),
4159
+ ...(hasNext ? [bindCardAction({
4115
4160
  text: "Next",
4116
4161
  value: {
4117
4162
  action: "session_page",
@@ -4119,13 +4164,20 @@ export class ProxySessionController {
4119
4164
  page: String(page + 1),
4120
4165
  contextKey,
4121
4166
  },
4122
- }] : []),
4167
+ })] : []),
4123
4168
  ]);
4169
+ const bindings = [...cardActionBindings.values()];
4124
4170
  if (options.updateMessageId) {
4171
+ this.store.upsertCardActionBindings(options.updateMessageId, bindings);
4125
4172
  await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
4173
+ this.store.retainCardActionBindings(options.updateMessageId, bindings.map((binding) => binding.token));
4126
4174
  }
4127
4175
  else {
4128
- await this.outbound.sendInteractiveCard(contextKey, card);
4176
+ const messageId = await this.outbound.sendInteractiveCard(contextKey, card);
4177
+ if (messageId) {
4178
+ this.store.upsertCardActionBindings(messageId, bindings);
4179
+ this.store.retainCardActionBindings(messageId, bindings.map((binding) => binding.token));
4180
+ }
4129
4181
  }
4130
4182
  }
4131
4183
  async refreshSessionsCardFromAction(action, options = {}) {
@@ -4815,28 +4867,26 @@ function statusRefreshAction(sessionId, contextKey) {
4815
4867
  },
4816
4868
  };
4817
4869
  }
4818
- function orderTaskListByProject(entries) {
4819
- const groups = new Map();
4820
- for (const entry of entries) {
4821
- const project = taskProjectInfo(entry);
4822
- const group = groups.get(project.key);
4823
- if (group)
4824
- group.entries.push(entry);
4825
- else
4826
- groups.set(project.key, { project, entries: [entry] });
4827
- }
4828
- const orderedGroups = [...groups.values()]
4829
- .map((group) => ({
4830
- ...group,
4831
- current: group.entries.some((entry) => entry.current),
4832
- active: group.entries.some((entry) => entry.active),
4833
- updatedAt: Math.max(...group.entries.map((entry) => entry.updatedAt)),
4834
- }))
4835
- .sort((left, right) => Number(right.current) - Number(left.current)
4836
- || Number(right.active) - Number(left.active)
4837
- || right.updatedAt - left.updatedAt
4838
- || left.project.title.localeCompare(right.project.title, undefined, { sensitivity: "base" }));
4839
- return orderedGroups.flatMap((group) => group.entries.sort(compareTaskListEntries));
4870
+ function orderTaskListByRecency(entries) {
4871
+ return [...entries].sort(compareTaskListEntries);
4872
+ }
4873
+ function groupTaskListPagesByProject(entries, pageSize) {
4874
+ const result = [];
4875
+ for (let offset = 0; offset < entries.length; offset += pageSize) {
4876
+ const groups = new Map();
4877
+ for (const entry of entries.slice(offset, offset + pageSize)) {
4878
+ const key = taskProjectInfo(entry).key;
4879
+ const group = groups.get(key);
4880
+ if (group)
4881
+ group.push(entry);
4882
+ else
4883
+ groups.set(key, [entry]);
4884
+ }
4885
+ for (const group of groups.values()) {
4886
+ result.push(...group.sort(compareTaskListEntries));
4887
+ }
4888
+ }
4889
+ return result;
4840
4890
  }
4841
4891
  function compareTaskListEntries(left, right) {
4842
4892
  return Number(right.current) - Number(left.current)
@@ -4859,7 +4909,7 @@ function taskProjectInfo(entry) {
4859
4909
  title: `📁 ${abbreviateHomeDirectory(entry.cwd)}`,
4860
4910
  };
4861
4911
  }
4862
- function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
4912
+ function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId, latestLocalUserPrompts = new Map(), latestRemoteUserPrompts = new Map()) {
4863
4913
  const localByRemoteId = new Map(localSessions
4864
4914
  .filter((session) => session.runtimeKind === "codex" && session.remoteSessionId)
4865
4915
  .map((session) => [agentRemoteKey(session.agentName, session.remoteSessionId), session]));
@@ -4882,6 +4932,9 @@ function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
4882
4932
  status,
4883
4933
  active,
4884
4934
  current: Boolean(local && local.localSessionId === currentLocalSessionId),
4935
+ lastUserPrompt: remote.lastUserPrompt
4936
+ ?? (local ? latestLocalUserPrompts.get(local.localSessionId) : undefined)
4937
+ ?? latestRemoteUserPrompts.get(agentRemoteKey(agentName, remote.id)),
4885
4938
  updatedAt: (recencyAt ?? 0) * 1_000,
4886
4939
  updatedLabel: formatRemoteTime(recencyAt),
4887
4940
  };
@@ -4901,6 +4954,7 @@ function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
4901
4954
  status: sessionStatusLabel(local.status),
4902
4955
  active: local.status === "running",
4903
4956
  current: local.localSessionId === currentLocalSessionId,
4957
+ lastUserPrompt: latestLocalUserPrompts.get(local.localSessionId),
4904
4958
  updatedAt: Number.isNaN(updatedAt) ? 0 : updatedAt,
4905
4959
  updatedLabel: formatStatusTime(local.updatedAt),
4906
4960
  });
@@ -4929,6 +4983,16 @@ function parseRemoteSessionReference(reference) {
4929
4983
  return undefined;
4930
4984
  }
4931
4985
  }
4986
+ function bindSessionCardAction(action, bindings) {
4987
+ const serialized = JSON.stringify(action.value);
4988
+ const token = createHash("sha256").update(serialized).digest("hex").slice(0, 16);
4989
+ const existing = bindings.get(token);
4990
+ if (existing && JSON.stringify(existing.value) !== serialized) {
4991
+ throw new Error("任务列表卡片操作令牌发生冲突,请重试。");
4992
+ }
4993
+ bindings.set(token, { token, value: action.value });
4994
+ return { ...action, value: { t: token } };
4995
+ }
4932
4996
  function runtimeErrorMessage(error) {
4933
4997
  return error instanceof Error ? error.message : String(error);
4934
4998
  }
@@ -4948,6 +5012,19 @@ function turnViewSnapshot(value) {
4948
5012
  ? candidate
4949
5013
  : undefined;
4950
5014
  }
5015
+ function latestUserPromptFromTurnView(snapshot) {
5016
+ const latestUserActivity = [...(snapshot?.activities ?? [])]
5017
+ .reverse()
5018
+ .find((activity) => activity.kind === "user" && Boolean(activity.text.trim()));
5019
+ return latestUserActivity?.text.trim() || snapshot?.prompt?.trim() || undefined;
5020
+ }
5021
+ function sessionPromptPreview(prompt) {
5022
+ const normalized = prompt?.replace(/\s+/gu, " ").trim() || "暂无用户 Prompt";
5023
+ const characters = Array.from(normalized);
5024
+ return characters.length > SESSION_PROMPT_PREVIEW_LENGTH
5025
+ ? `${characters.slice(0, SESSION_PROMPT_PREVIEW_LENGTH).join("")}...`
5026
+ : normalized;
5027
+ }
4951
5028
  function isTerminalTurnViewStatus(status) {
4952
5029
  return status === "completed" || status === "cancelled" || status === "failed";
4953
5030
  }