@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.
- package/CHANGELOG.md +13 -0
- package/README.md +26 -2
- package/README.zh.md +26 -2
- package/config.example.yaml +5 -0
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/help.js +4 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/taskGroupOptions.js +27 -0
- package/dist/cli/taskGroupOptions.js.map +1 -1
- package/dist/cli.js +21 -1
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +19 -3
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +92 -9
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +27 -0
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/config/loadConfig.js +4 -0
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +1 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/feishu/CardRenderer.js +31 -2
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +6 -3
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/MergedForwardMessage.js +17 -7
- package/dist/feishu/MergedForwardMessage.js.map +1 -1
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -1
- package/dist/local-files/TurnPreviewPage.js +106 -87
- package/dist/local-files/TurnPreviewPage.js.map +1 -1
- package/dist/proxy/ConversationExport.js +41 -0
- package/dist/proxy/ConversationExport.js.map +1 -0
- package/dist/proxy/ProxySessionController.js +118 -7
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +65 -0
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/migrations.js +12 -0
- package/dist/state/migrations.js.map +1 -1
- package/dist/updates/AutomaticUpdatePreparer.js +66 -0
- package/dist/updates/AutomaticUpdatePreparer.js.map +1 -0
- package/dist/updates/DailyUpdateMonitor.js +261 -0
- package/dist/updates/DailyUpdateMonitor.js.map +1 -0
- package/dist/updates/PublishedRelease.js +43 -0
- package/dist/updates/PublishedRelease.js.map +1 -0
- package/dist/updates/types.js +2 -0
- package/dist/updates/types.js.map +1 -0
- package/docs/technical-reference.md +4 -0
- package/docs/technical-reference.zh.md +4 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +11 -0
- package/src/cli/controlProtocol.ts +9 -0
- package/src/cli/help.ts +4 -0
- package/src/cli/taskGroupOptions.ts +33 -3
- package/src/cli.ts +21 -0
- package/src/codex/CodexProcessManager.ts +21 -4
- package/src/codex/CodexRuntime.ts +104 -12
- package/src/commands/CommandRouter.ts +24 -0
- package/src/commands/commandTypes.ts +1 -0
- package/src/config/loadConfig.ts +4 -0
- package/src/config/schema.ts +4 -1
- package/src/feishu/CardRenderer.ts +34 -2
- package/src/feishu/FeishuMessageClient.ts +6 -2
- package/src/feishu/MergedForwardMessage.ts +29 -7
- package/src/feishu/types.ts +1 -1
- package/src/index.ts +49 -0
- package/src/local-files/TurnPreviewPage.ts +105 -88
- package/src/proxy/ConversationExport.ts +43 -0
- package/src/proxy/ProxySessionController.ts +166 -8
- package/src/runtime/types.ts +6 -0
- package/src/state/StateStore.ts +69 -0
- package/src/state/migrations.ts +12 -0
- package/src/updates/AutomaticUpdatePreparer.ts +76 -0
- package/src/updates/DailyUpdateMonitor.ts +252 -0
- package/src/updates/PublishedRelease.ts +44 -0
- 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 === "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}`
|