@keyou007/agent-bot 0.1.15-alpha.2 → 0.1.15-alpha.4
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.
- package/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/README.zh.md +2 -2
- package/dist/cli/VerificationOutput.js +1 -6
- package/dist/cli/VerificationOutput.js.map +1 -1
- package/dist/cli.js +7 -13
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexRuntime.js +19 -0
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/feishu/CardRenderer.js +7 -6
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +129 -52
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +46 -0
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/migrations.js +11 -0
- package/dist/state/migrations.js.map +1 -1
- package/docs/technical-reference.md +5 -5
- package/docs/technical-reference.zh.md +5 -5
- package/npm-shrinkwrap.json +2 -19
- package/package.json +1 -3
- package/src/cli/VerificationOutput.ts +1 -12
- package/src/cli.ts +6 -22
- package/src/codex/CodexRuntime.ts +22 -0
- package/src/feishu/CardRenderer.ts +4 -3
- package/src/proxy/ProxySessionController.ts +143 -51
- package/src/runtime/types.ts +1 -0
- package/src/state/StateStore.ts +51 -0
- package/src/state/migrations.ts +11 -0
|
@@ -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 =
|
|
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
|
-
|
|
462
|
-
|
|
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
|
|
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}
|
|
4117
|
+
summary: `${offset + index + 1}. ${marker}${title} · ${entry.agentName}`,
|
|
4072
4118
|
detailLines: [
|
|
4073
|
-
|
|
4074
|
-
|
|
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
|
-
">
|
|
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
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
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
|
}
|