@keyou007/agent-bot 0.1.15-alpha.1 → 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.
- package/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/dist/codex/CodexRuntime.js +19 -0
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/feishu/CardRenderer.js +70 -14
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +4 -1
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +208 -57
- 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 +2 -2
- package/docs/technical-reference.zh.md +2 -2
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/codex/CodexRuntime.ts +22 -0
- package/src/feishu/CardRenderer.ts +79 -11
- package/src/feishu/FeishuConnector.ts +4 -1
- package/src/proxy/ProxySessionController.ts +228 -56
- 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;
|
|
@@ -526,6 +538,25 @@ export class ProxySessionController {
|
|
|
526
538
|
await this.assertSendableFile(filePath);
|
|
527
539
|
await this.outbound.sendFile(contextKey, filePath);
|
|
528
540
|
}
|
|
541
|
+
else if (kind === "directory_new_folder_prompt") {
|
|
542
|
+
const directory = directoryActionPath(scopedAction.value.directory);
|
|
543
|
+
await this.assertBrowsableDirectory(directory);
|
|
544
|
+
await this.outbound.updateInteractiveCard(contextKey, requiredCardMessageId(scopedAction.messageId), this.cardRenderer.renderDirectoryNewFolderCard({
|
|
545
|
+
directory,
|
|
546
|
+
displayDirectory: abbreviateHomeDirectory(directory),
|
|
547
|
+
contextKey,
|
|
548
|
+
page: directoryPageValue(scopedAction.value.page),
|
|
549
|
+
}));
|
|
550
|
+
}
|
|
551
|
+
else if (kind === "directory_new_folder_submit") {
|
|
552
|
+
await this.createDirectoryFromCard(contextKey, scopedAction);
|
|
553
|
+
}
|
|
554
|
+
else if (kind === "directory_new_folder_cancel") {
|
|
555
|
+
await this.openDirectoryBrowser(contextKey, directoryActionPath(scopedAction.value.directory), {
|
|
556
|
+
updateMessageId: requiredCardMessageId(scopedAction.messageId),
|
|
557
|
+
page: directoryPageValue(scopedAction.value.page),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
529
560
|
else if (kind === "directory_new" || kind === "directory_new_group") {
|
|
530
561
|
const directory = directoryActionPath(scopedAction.value.directory);
|
|
531
562
|
await this.assertBrowsableDirectory(directory);
|
|
@@ -1218,6 +1249,19 @@ export class ProxySessionController {
|
|
|
1218
1249
|
? turnSession.contextKey
|
|
1219
1250
|
: action.contextKey;
|
|
1220
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
|
+
}
|
|
1221
1265
|
async execute(contextKey, command, messageId, replyTarget, localImagePaths, userId, incomingMessage) {
|
|
1222
1266
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
1223
1267
|
if (!this.currentSession(contextKey) && isThreadContextKey(contextKey) && commandRequiresCurrentSession(command)) {
|
|
@@ -3828,12 +3872,12 @@ export class ProxySessionController {
|
|
|
3828
3872
|
const card = this.cardRenderer.renderDirectoryBrowserCard({
|
|
3829
3873
|
directory: abbreviateHomeDirectory(directory),
|
|
3830
3874
|
entries: visibleEntries,
|
|
3831
|
-
currentActions: directoryCreationActions(directory, contextKey),
|
|
3875
|
+
currentActions: directoryCreationActions(directory, contextKey, page),
|
|
3832
3876
|
navigationActions,
|
|
3833
3877
|
footerLines: [
|
|
3834
3878
|
`第 ${page + 1}/${totalPages} 页 · ${directoryCount} 个目录 · ${entries.length - directoryCount} 个文件`,
|
|
3835
3879
|
"",
|
|
3836
|
-
">
|
|
3880
|
+
"> 点击目录名称进入,点击文件名称发送到当前会话;卡片顶部可新建目录、任务或群聊。",
|
|
3837
3881
|
],
|
|
3838
3882
|
});
|
|
3839
3883
|
if (options.updateMessageId) {
|
|
@@ -3892,6 +3936,28 @@ export class ProxySessionController {
|
|
|
3892
3936
|
if (!stats.isDirectory())
|
|
3893
3937
|
throw new Error(`这不是目录:${directory}`);
|
|
3894
3938
|
}
|
|
3939
|
+
async createDirectoryFromCard(contextKey, action) {
|
|
3940
|
+
const directory = directoryActionPath(action.value.directory);
|
|
3941
|
+
await this.assertBrowsableDirectory(directory);
|
|
3942
|
+
const folderName = directoryFolderName(cardFormValue(action.value, "folderName"));
|
|
3943
|
+
const newDirectory = path.join(directory, folderName);
|
|
3944
|
+
try {
|
|
3945
|
+
await fs.mkdir(newDirectory);
|
|
3946
|
+
}
|
|
3947
|
+
catch (error) {
|
|
3948
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
3949
|
+
? String(error.code)
|
|
3950
|
+
: undefined;
|
|
3951
|
+
if (code === "EEXIST")
|
|
3952
|
+
throw new Error(`目录已存在:${newDirectory}`);
|
|
3953
|
+
throw new Error(`无法创建目录 ${newDirectory}:${runtimeErrorMessage(error)}`);
|
|
3954
|
+
}
|
|
3955
|
+
await this.openDirectoryBrowser(contextKey, directory, {
|
|
3956
|
+
updateMessageId: requiredCardMessageId(action.messageId),
|
|
3957
|
+
page: directoryPageValue(action.value.page),
|
|
3958
|
+
});
|
|
3959
|
+
await this.outbound.sendText(contextKey, `已创建目录:${newDirectory}`);
|
|
3960
|
+
}
|
|
3895
3961
|
async assertSendableFile(filePath) {
|
|
3896
3962
|
let stats;
|
|
3897
3963
|
try {
|
|
@@ -3928,10 +3994,28 @@ export class ProxySessionController {
|
|
|
3928
3994
|
const remoteHint = remoteErrors.length > 0
|
|
3929
3995
|
? `部分 Agent 的任务读取失败:${remoteErrors.join(";")}`
|
|
3930
3996
|
: undefined;
|
|
3931
|
-
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)
|
|
3932
4015
|
.filter((entry) => !normalizedSearch
|
|
3933
4016
|
|| [entry.id, entry.title, entry.cwd, entry.agentName]
|
|
3934
4017
|
.some((value) => value.toLowerCase().includes(normalizedSearch))));
|
|
4018
|
+
const entries = groupTaskListPagesByProject(globallyOrderedEntries, SESSION_PAGE_SIZE);
|
|
3935
4019
|
const activeCount = entries.filter((entry) => entry.active).length;
|
|
3936
4020
|
const lastKnownPage = Math.max(0, Math.ceil(entries.length / SESSION_PAGE_SIZE) - 1);
|
|
3937
4021
|
const page = remoteHasMore ? requestedPage : Math.min(requestedPage, lastKnownPage);
|
|
@@ -3946,14 +4030,16 @@ export class ProxySessionController {
|
|
|
3946
4030
|
const hasPrevious = page > 0;
|
|
3947
4031
|
const hasNext = remoteHasMore || entries.length > offset + SESSION_PAGE_SIZE;
|
|
3948
4032
|
this.lastSessionListings.set(contextKey, entries.map((entry) => entry.reference));
|
|
4033
|
+
const cardActionBindings = new Map();
|
|
4034
|
+
const bindCardAction = (action) => bindSessionCardAction(action, cardActionBindings);
|
|
3949
4035
|
const cardEntries = visibleEntries.map((entry, index) => {
|
|
3950
4036
|
const project = taskProjectInfo(entry);
|
|
3951
4037
|
const projectActionSource = projectActionSources.get(project.key) ?? entry;
|
|
3952
|
-
const marker = entry.current ? "✅" : entry.active ? "🟢" : "
|
|
4038
|
+
const marker = entry.current ? "✅ " : entry.active ? "🟢 " : "";
|
|
3953
4039
|
const showStop = entry.status === "外部执行中"
|
|
3954
4040
|
&& entry.reference !== options.forceSwitchTaskId
|
|
3955
4041
|
&& entry.id !== options.forceSwitchTaskId;
|
|
3956
|
-
const actions = entry.current ? [] : [{
|
|
4042
|
+
const actions = entry.current ? [] : [bindCardAction({
|
|
3957
4043
|
text: showStop ? "Stop" : "Switch",
|
|
3958
4044
|
type: showStop ? "danger" : "default",
|
|
3959
4045
|
value: {
|
|
@@ -3963,8 +4049,8 @@ export class ProxySessionController {
|
|
|
3963
4049
|
page: String(page),
|
|
3964
4050
|
contextKey,
|
|
3965
4051
|
},
|
|
3966
|
-
}];
|
|
3967
|
-
const projectActions = [{
|
|
4052
|
+
})];
|
|
4053
|
+
const projectActions = [bindCardAction({
|
|
3968
4054
|
text: "New",
|
|
3969
4055
|
value: {
|
|
3970
4056
|
action: "session_new",
|
|
@@ -3973,7 +4059,7 @@ export class ProxySessionController {
|
|
|
3973
4059
|
page: String(page),
|
|
3974
4060
|
contextKey,
|
|
3975
4061
|
},
|
|
3976
|
-
}, {
|
|
4062
|
+
}), bindCardAction({
|
|
3977
4063
|
text: "NewGroup",
|
|
3978
4064
|
value: {
|
|
3979
4065
|
action: "session_new_group",
|
|
@@ -3982,8 +4068,8 @@ export class ProxySessionController {
|
|
|
3982
4068
|
page: String(page),
|
|
3983
4069
|
contextKey,
|
|
3984
4070
|
},
|
|
3985
|
-
}];
|
|
3986
|
-
actions.push({
|
|
4071
|
+
})];
|
|
4072
|
+
actions.push(bindCardAction({
|
|
3987
4073
|
text: "Fork",
|
|
3988
4074
|
value: {
|
|
3989
4075
|
action: "session_fork",
|
|
@@ -3992,8 +4078,8 @@ export class ProxySessionController {
|
|
|
3992
4078
|
page: String(page),
|
|
3993
4079
|
contextKey,
|
|
3994
4080
|
},
|
|
3995
|
-
});
|
|
3996
|
-
actions.push({
|
|
4081
|
+
}));
|
|
4082
|
+
actions.push(bindCardAction({
|
|
3997
4083
|
text: "ForkGroup",
|
|
3998
4084
|
value: {
|
|
3999
4085
|
action: "session_fork_group",
|
|
@@ -4002,16 +4088,16 @@ export class ProxySessionController {
|
|
|
4002
4088
|
page: String(page),
|
|
4003
4089
|
contextKey,
|
|
4004
4090
|
},
|
|
4005
|
-
});
|
|
4006
|
-
actions.push({
|
|
4091
|
+
}));
|
|
4092
|
+
actions.push(bindCardAction({
|
|
4007
4093
|
text: "Status",
|
|
4008
4094
|
value: {
|
|
4009
4095
|
action: "session_status",
|
|
4010
4096
|
sessionId: entry.reference,
|
|
4011
4097
|
contextKey,
|
|
4012
4098
|
},
|
|
4013
|
-
});
|
|
4014
|
-
actions.push({
|
|
4099
|
+
}));
|
|
4100
|
+
actions.push(bindCardAction({
|
|
4015
4101
|
text: "Archive",
|
|
4016
4102
|
value: {
|
|
4017
4103
|
action: "session_archive",
|
|
@@ -4020,18 +4106,18 @@ export class ProxySessionController {
|
|
|
4020
4106
|
page: String(page),
|
|
4021
4107
|
contextKey,
|
|
4022
4108
|
},
|
|
4023
|
-
});
|
|
4109
|
+
}));
|
|
4024
4110
|
const title = truncateText(entry.title.replace(/\s+/gu, " ").trim() || "未命名任务", 56);
|
|
4111
|
+
const lastUserPrompt = sessionPromptPreview(entry.lastUserPrompt);
|
|
4025
4112
|
return {
|
|
4026
4113
|
project,
|
|
4027
4114
|
projectActions,
|
|
4028
4115
|
cardEntry: {
|
|
4029
4116
|
reference: entry.reference,
|
|
4030
|
-
summary: `${offset + index + 1}. ${marker}
|
|
4117
|
+
summary: `${offset + index + 1}. ${marker}${title} · ${entry.agentName}`,
|
|
4031
4118
|
detailLines: [
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
`**目录**:${cardCode(entry.cwd || "目录未知")}`,
|
|
4119
|
+
cardText(lastUserPrompt),
|
|
4120
|
+
`**更新时间**:${cardText(entry.updatedLabel)}`,
|
|
4035
4121
|
],
|
|
4036
4122
|
actions,
|
|
4037
4123
|
current: entry.current,
|
|
@@ -4058,10 +4144,10 @@ export class ProxySessionController {
|
|
|
4058
4144
|
...(remoteHint ? [remoteHint] : []),
|
|
4059
4145
|
`第 ${page + 1} 页 · 每页 ${SESSION_PAGE_SIZE} 个任务${hasNext ? "" : ` · 当前共 ${entries.length} 个任务`}`,
|
|
4060
4146
|
"",
|
|
4061
|
-
">
|
|
4147
|
+
"> 项目菜单:**New** 新建任务,**NewGroup** 新建群。",
|
|
4062
4148
|
"> 任务详情:**Switch** 切换,**Stop** 停止,**Fork** / **ForkGroup** 创建分支,**Status** 查看状态,**Archive** 归档。",
|
|
4063
4149
|
], [
|
|
4064
|
-
...(hasPrevious ? [{
|
|
4150
|
+
...(hasPrevious ? [bindCardAction({
|
|
4065
4151
|
text: "Previous",
|
|
4066
4152
|
value: {
|
|
4067
4153
|
action: "session_page",
|
|
@@ -4069,8 +4155,8 @@ export class ProxySessionController {
|
|
|
4069
4155
|
page: String(page - 1),
|
|
4070
4156
|
contextKey,
|
|
4071
4157
|
},
|
|
4072
|
-
}] : []),
|
|
4073
|
-
...(hasNext ? [{
|
|
4158
|
+
})] : []),
|
|
4159
|
+
...(hasNext ? [bindCardAction({
|
|
4074
4160
|
text: "Next",
|
|
4075
4161
|
value: {
|
|
4076
4162
|
action: "session_page",
|
|
@@ -4078,13 +4164,20 @@ export class ProxySessionController {
|
|
|
4078
4164
|
page: String(page + 1),
|
|
4079
4165
|
contextKey,
|
|
4080
4166
|
},
|
|
4081
|
-
}] : []),
|
|
4167
|
+
})] : []),
|
|
4082
4168
|
]);
|
|
4169
|
+
const bindings = [...cardActionBindings.values()];
|
|
4083
4170
|
if (options.updateMessageId) {
|
|
4171
|
+
this.store.upsertCardActionBindings(options.updateMessageId, bindings);
|
|
4084
4172
|
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
4173
|
+
this.store.retainCardActionBindings(options.updateMessageId, bindings.map((binding) => binding.token));
|
|
4085
4174
|
}
|
|
4086
4175
|
else {
|
|
4087
|
-
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
|
+
}
|
|
4088
4181
|
}
|
|
4089
4182
|
}
|
|
4090
4183
|
async refreshSessionsCardFromAction(action, options = {}) {
|
|
@@ -4774,28 +4867,26 @@ function statusRefreshAction(sessionId, contextKey) {
|
|
|
4774
4867
|
},
|
|
4775
4868
|
};
|
|
4776
4869
|
}
|
|
4777
|
-
function
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|| left.project.title.localeCompare(right.project.title, undefined, { sensitivity: "base" }));
|
|
4798
|
-
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;
|
|
4799
4890
|
}
|
|
4800
4891
|
function compareTaskListEntries(left, right) {
|
|
4801
4892
|
return Number(right.current) - Number(left.current)
|
|
@@ -4818,7 +4909,7 @@ function taskProjectInfo(entry) {
|
|
|
4818
4909
|
title: `📁 ${abbreviateHomeDirectory(entry.cwd)}`,
|
|
4819
4910
|
};
|
|
4820
4911
|
}
|
|
4821
|
-
function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
|
|
4912
|
+
function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId, latestLocalUserPrompts = new Map(), latestRemoteUserPrompts = new Map()) {
|
|
4822
4913
|
const localByRemoteId = new Map(localSessions
|
|
4823
4914
|
.filter((session) => session.runtimeKind === "codex" && session.remoteSessionId)
|
|
4824
4915
|
.map((session) => [agentRemoteKey(session.agentName, session.remoteSessionId), session]));
|
|
@@ -4841,6 +4932,9 @@ function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
|
|
|
4841
4932
|
status,
|
|
4842
4933
|
active,
|
|
4843
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)),
|
|
4844
4938
|
updatedAt: (recencyAt ?? 0) * 1_000,
|
|
4845
4939
|
updatedLabel: formatRemoteTime(recencyAt),
|
|
4846
4940
|
};
|
|
@@ -4860,6 +4954,7 @@ function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
|
|
|
4860
4954
|
status: sessionStatusLabel(local.status),
|
|
4861
4955
|
active: local.status === "running",
|
|
4862
4956
|
current: local.localSessionId === currentLocalSessionId,
|
|
4957
|
+
lastUserPrompt: latestLocalUserPrompts.get(local.localSessionId),
|
|
4863
4958
|
updatedAt: Number.isNaN(updatedAt) ? 0 : updatedAt,
|
|
4864
4959
|
updatedLabel: formatStatusTime(local.updatedAt),
|
|
4865
4960
|
});
|
|
@@ -4888,6 +4983,16 @@ function parseRemoteSessionReference(reference) {
|
|
|
4888
4983
|
return undefined;
|
|
4889
4984
|
}
|
|
4890
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
|
+
}
|
|
4891
4996
|
function runtimeErrorMessage(error) {
|
|
4892
4997
|
return error instanceof Error ? error.message : String(error);
|
|
4893
4998
|
}
|
|
@@ -4907,6 +5012,19 @@ function turnViewSnapshot(value) {
|
|
|
4907
5012
|
? candidate
|
|
4908
5013
|
: undefined;
|
|
4909
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
|
+
}
|
|
4910
5028
|
function isTerminalTurnViewStatus(status) {
|
|
4911
5029
|
return status === "completed" || status === "cancelled" || status === "failed";
|
|
4912
5030
|
}
|
|
@@ -5242,10 +5360,43 @@ function directoryFileActionPath(value) {
|
|
|
5242
5360
|
}
|
|
5243
5361
|
return path.resolve(value);
|
|
5244
5362
|
}
|
|
5245
|
-
function
|
|
5363
|
+
function cardFormValue(value, name) {
|
|
5364
|
+
const formValue = value.formValue;
|
|
5365
|
+
return typeof formValue === "object" && formValue !== null
|
|
5366
|
+
? formValue[name]
|
|
5367
|
+
: undefined;
|
|
5368
|
+
}
|
|
5369
|
+
function directoryFolderName(value) {
|
|
5370
|
+
if (typeof value !== "string" || !value.trim())
|
|
5371
|
+
throw new Error("请输入目录名。");
|
|
5372
|
+
const name = value.trim();
|
|
5373
|
+
if (name === "." || name === "..")
|
|
5374
|
+
throw new Error("目录名不能是 . 或 ..。");
|
|
5375
|
+
if (name.length > 255)
|
|
5376
|
+
throw new Error("目录名不能超过 255 个字符。");
|
|
5377
|
+
if (/[<>:"/\\|?*\u0000-\u001F]/u.test(name)) {
|
|
5378
|
+
throw new Error("目录名不能包含路径分隔符或以下字符:< > : \" / \\ | ? *");
|
|
5379
|
+
}
|
|
5380
|
+
if (/[. ]$/u.test(name))
|
|
5381
|
+
throw new Error("目录名不能以空格或句点结尾。");
|
|
5382
|
+
if (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/iu.test(name)) {
|
|
5383
|
+
throw new Error("该名称是系统保留名称,请使用其他目录名。");
|
|
5384
|
+
}
|
|
5385
|
+
return name;
|
|
5386
|
+
}
|
|
5387
|
+
function directoryCreationActions(directory, contextKey, page) {
|
|
5246
5388
|
return [
|
|
5247
5389
|
{
|
|
5248
|
-
text: "
|
|
5390
|
+
text: "NewFolder",
|
|
5391
|
+
value: {
|
|
5392
|
+
action: "directory_new_folder_prompt",
|
|
5393
|
+
directory,
|
|
5394
|
+
contextKey,
|
|
5395
|
+
page: String(page),
|
|
5396
|
+
},
|
|
5397
|
+
},
|
|
5398
|
+
{
|
|
5399
|
+
text: "NewTask",
|
|
5249
5400
|
value: {
|
|
5250
5401
|
action: "directory_new",
|
|
5251
5402
|
directory,
|
|
@@ -5253,7 +5404,7 @@ function directoryCreationActions(directory, contextKey) {
|
|
|
5253
5404
|
},
|
|
5254
5405
|
},
|
|
5255
5406
|
{
|
|
5256
|
-
text: "
|
|
5407
|
+
text: "NewGroupTask",
|
|
5257
5408
|
value: {
|
|
5258
5409
|
action: "directory_new_group",
|
|
5259
5410
|
directory,
|