@keyou007/agent-bot 0.1.14-alpha.1 → 0.1.14-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 +19 -1
- package/README.md +48 -15
- package/README.zh.md +48 -15
- package/config.example.yaml +1 -1
- package/dist/cli/AutostartManager.js +431 -0
- package/dist/cli/AutostartManager.js.map +1 -0
- package/dist/cli/FeishuAppConfiguration.js +21 -13
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/Initializer.js +35 -0
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/SelfUpdateRunner.js +552 -0
- package/dist/cli/SelfUpdateRunner.js.map +1 -0
- package/dist/cli/SelfUpdater.js +431 -0
- package/dist/cli/SelfUpdater.js.map +1 -0
- package/dist/cli/autostartOutput.js +63 -0
- package/dist/cli/autostartOutput.js.map +1 -0
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/help.js +94 -18
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/taskGroupOptions.js +14 -11
- package/dist/cli/taskGroupOptions.js.map +1 -1
- package/dist/cli/taskListOutput.js +3 -0
- package/dist/cli/taskListOutput.js.map +1 -1
- package/dist/cli/taskTarget.js +99 -0
- package/dist/cli/taskTarget.js.map +1 -0
- package/dist/cli.js +661 -58
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexRuntime.js +8 -2
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +16 -1
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +266 -38
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuMarkdown.js +25 -1
- package/dist/feishu/FeishuMarkdown.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +16 -0
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +42 -6
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/index.js +136 -2
- package/dist/index.js.map +1 -1
- package/dist/presentation/OutboundRouter.js +7 -0
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +21 -7
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +537 -13
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +53 -0
- package/dist/state/StateStore.js.map +1 -1
- package/docs/technical-reference.md +17 -7
- package/docs/technical-reference.zh.md +17 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
- package/skills/agent-bot/SKILL.md +109 -188
- package/src/cli/AutostartManager.ts +579 -0
- package/src/cli/FeishuAppConfiguration.ts +26 -7
- package/src/cli/Initializer.ts +57 -0
- package/src/cli/SelfUpdateRunner.ts +817 -0
- package/src/cli/SelfUpdater.ts +688 -0
- package/src/cli/autostartOutput.ts +75 -0
- package/src/cli/controlProtocol.ts +119 -1
- package/src/cli/help.ts +94 -18
- package/src/cli/taskGroupOptions.ts +27 -17
- package/src/cli/taskListOutput.ts +10 -0
- package/src/cli/taskTarget.ts +168 -0
- package/src/cli.ts +809 -65
- package/src/codex/CodexRuntime.ts +8 -2
- package/src/commands/CommandRouter.ts +14 -1
- package/src/commands/commandTypes.ts +2 -0
- package/src/feishu/CardRenderer.ts +313 -35
- package/src/feishu/FeishuMarkdown.ts +27 -1
- package/src/feishu/FeishuMessageClient.ts +33 -0
- package/src/feishu/FeishuTurnPresenter.ts +43 -6
- package/src/feishu/types.ts +1 -0
- package/src/index.ts +174 -2
- package/src/presentation/OutboundRouter.ts +7 -0
- package/src/presentation/TurnStateReducer.ts +21 -7
- package/src/proxy/ProxySessionController.ts +661 -16
- package/src/runtime/types.ts +1 -0
- package/src/state/StateStore.ts +56 -0
|
@@ -39,6 +39,13 @@ interface CreateGroupResponse {
|
|
|
39
39
|
error?: unknown;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
interface DeleteGroupResponse {
|
|
43
|
+
code: number;
|
|
44
|
+
msg: string;
|
|
45
|
+
data?: Record<string, never>;
|
|
46
|
+
error?: unknown;
|
|
47
|
+
}
|
|
48
|
+
|
|
42
49
|
interface UploadImageResponse {
|
|
43
50
|
code: number;
|
|
44
51
|
msg: string;
|
|
@@ -128,6 +135,31 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
128
135
|
}
|
|
129
136
|
}
|
|
130
137
|
|
|
138
|
+
async deleteGroup(chatId: string): Promise<void> {
|
|
139
|
+
try {
|
|
140
|
+
const token = await this.getTenantAccessToken();
|
|
141
|
+
const response = await fetch(
|
|
142
|
+
`https://open.feishu.cn/open-apis/im/v1/chats/${encodeURIComponent(chatId)}`,
|
|
143
|
+
{
|
|
144
|
+
method: "DELETE",
|
|
145
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
const payload = (await response.json()) as DeleteGroupResponse;
|
|
149
|
+
if (!response.ok || payload.code !== 0) {
|
|
150
|
+
throw new FeishuApiError(
|
|
151
|
+
payload.msg || response.statusText,
|
|
152
|
+
payload.code,
|
|
153
|
+
payload,
|
|
154
|
+
"delete group",
|
|
155
|
+
response.status,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
throw normalizeTransportError(error, "delete group");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
131
163
|
async addReaction(messageId: string, emojiType: string): Promise<string | undefined> {
|
|
132
164
|
try {
|
|
133
165
|
const token = await this.getTenantAccessToken();
|
|
@@ -731,6 +763,7 @@ export class FeishuApiError extends Error {
|
|
|
731
763
|
readonly payload:
|
|
732
764
|
| SendMessageResponse
|
|
733
765
|
| CreateGroupResponse
|
|
766
|
+
| DeleteGroupResponse
|
|
734
767
|
| UploadImageResponse
|
|
735
768
|
| UploadFileResponse
|
|
736
769
|
| ReactionResponse
|
|
@@ -42,12 +42,14 @@ export interface TurnPresentationStore {
|
|
|
42
42
|
export interface FeishuTurnPresenterOptions {
|
|
43
43
|
normalIntervalMs?: number;
|
|
44
44
|
criticalGapMs?: number;
|
|
45
|
+
elapsedUpdateIntervalMs?: number;
|
|
45
46
|
finalChunkLength?: number;
|
|
46
47
|
onError?: (error: unknown) => void;
|
|
47
48
|
finalRetryBackoffMs?: number[];
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
const MAX_FINAL_TABLES_PER_CARD = 5;
|
|
52
|
+
const DEFAULT_ELAPSED_UPDATE_INTERVAL_MS = 3_000;
|
|
51
53
|
|
|
52
54
|
interface TurnEntry {
|
|
53
55
|
contextKey: string;
|
|
@@ -58,6 +60,7 @@ interface TurnEntry {
|
|
|
58
60
|
reactionId?: string;
|
|
59
61
|
reactionEmoji?: string;
|
|
60
62
|
scheduler?: CardUpdateScheduler<TurnViewState>;
|
|
63
|
+
elapsedTimer?: ReturnType<typeof setInterval>;
|
|
61
64
|
finalizing?: Promise<void>;
|
|
62
65
|
lastFinalizedStatus?: TurnViewState["status"];
|
|
63
66
|
}
|
|
@@ -148,6 +151,7 @@ export class FeishuTurnPresenter {
|
|
|
148
151
|
await entry.initializing;
|
|
149
152
|
return state.turnId;
|
|
150
153
|
} catch (error) {
|
|
154
|
+
this.stopElapsedUpdates(entry);
|
|
151
155
|
if (this.pendingEntries.get(sessionId) === entry) this.pendingEntries.delete(sessionId);
|
|
152
156
|
this.entries.delete(state.turnId);
|
|
153
157
|
throw error;
|
|
@@ -158,7 +162,15 @@ export class FeishuTurnPresenter {
|
|
|
158
162
|
const entry = this.pendingEntries.get(sessionId);
|
|
159
163
|
if (!entry) return;
|
|
160
164
|
this.pendingEntries.delete(sessionId);
|
|
161
|
-
|
|
165
|
+
this.stopElapsedUpdates(entry);
|
|
166
|
+
const completedAt = Date.now();
|
|
167
|
+
entry.state = {
|
|
168
|
+
...entry.state,
|
|
169
|
+
status: "failed",
|
|
170
|
+
error: message,
|
|
171
|
+
completedAt,
|
|
172
|
+
durationMs: Math.max(0, completedAt - entry.state.startedAt),
|
|
173
|
+
};
|
|
162
174
|
this.store.saveTurnSnapshot(entry.state.turnId, sessionId, entry.state, entry.contextKey);
|
|
163
175
|
await entry.initializing;
|
|
164
176
|
try {
|
|
@@ -178,24 +190,28 @@ export class FeishuTurnPresenter {
|
|
|
178
190
|
): Promise<void> {
|
|
179
191
|
const snapshot = this.store.getTurnSnapshot(turnId);
|
|
180
192
|
if (!isTurnViewState(snapshot) || snapshot.sessionId !== sessionId || isTerminalViewStatus(snapshot.status)) return;
|
|
193
|
+
const completedAt = Date.now();
|
|
181
194
|
const state: TurnViewState = {
|
|
182
195
|
...snapshot,
|
|
183
196
|
status: "cancelled",
|
|
184
197
|
progressText: message,
|
|
185
198
|
activeTool: undefined,
|
|
186
199
|
approval: undefined,
|
|
187
|
-
completedAt
|
|
200
|
+
completedAt,
|
|
201
|
+
durationMs: Math.max(0, completedAt - snapshot.startedAt),
|
|
188
202
|
};
|
|
189
203
|
this.store.saveTurnSnapshot(turnId, sessionId, state, contextKey);
|
|
190
|
-
const delivery = this.store.getTurnDelivery(turnId);
|
|
191
|
-
if (delivery?.progressMessageId) {
|
|
192
|
-
await this.outbound.updateInteractiveCard(delivery.progressMessageId, this.renderer.renderTurn(state));
|
|
193
|
-
}
|
|
194
204
|
const entry = this.entries.get(turnId);
|
|
195
205
|
if (entry) {
|
|
206
|
+
entry.state = state;
|
|
207
|
+
this.stopElapsedUpdates(entry);
|
|
196
208
|
this.entries.delete(turnId);
|
|
197
209
|
if (this.pendingEntries.get(sessionId) === entry) this.pendingEntries.delete(sessionId);
|
|
198
210
|
}
|
|
211
|
+
const delivery = this.store.getTurnDelivery(turnId);
|
|
212
|
+
if (delivery?.progressMessageId) {
|
|
213
|
+
await this.outbound.updateInteractiveCard(delivery.progressMessageId, this.renderer.renderTurn(state));
|
|
214
|
+
}
|
|
199
215
|
}
|
|
200
216
|
|
|
201
217
|
async appendSteerMessage(
|
|
@@ -287,6 +303,7 @@ export class FeishuTurnPresenter {
|
|
|
287
303
|
if (!entry.scheduler) return;
|
|
288
304
|
|
|
289
305
|
if (isTerminalEvent(event)) {
|
|
306
|
+
this.stopElapsedUpdates(entry);
|
|
290
307
|
if (entry.finalizing) await entry.finalizing;
|
|
291
308
|
const terminalState = entry.state;
|
|
292
309
|
if (entry.lastFinalizedStatus === terminalState.status) return;
|
|
@@ -434,10 +451,30 @@ export class FeishuTurnPresenter {
|
|
|
434
451
|
onError: this.options.onError,
|
|
435
452
|
});
|
|
436
453
|
entry.scheduler.seed(sentState);
|
|
454
|
+
this.startElapsedUpdates(entry);
|
|
437
455
|
if (entry.state !== sentState) entry.scheduler.update(entry.state, "critical");
|
|
438
456
|
await this.syncThinkingCardReaction(entry);
|
|
439
457
|
}
|
|
440
458
|
|
|
459
|
+
private startElapsedUpdates(entry: TurnEntry): void {
|
|
460
|
+
if (entry.elapsedTimer || isTerminalViewStatus(entry.state.status)) return;
|
|
461
|
+
const intervalMs = Math.max(1, this.options.elapsedUpdateIntervalMs ?? DEFAULT_ELAPSED_UPDATE_INTERVAL_MS);
|
|
462
|
+
entry.elapsedTimer = setInterval(() => {
|
|
463
|
+
if (isTerminalViewStatus(entry.state.status)) {
|
|
464
|
+
this.stopElapsedUpdates(entry);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (!entry.historySnapshot) entry.scheduler?.update(entry.state);
|
|
468
|
+
}, intervalMs);
|
|
469
|
+
entry.elapsedTimer.unref?.();
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
private stopElapsedUpdates(entry: TurnEntry): void {
|
|
473
|
+
if (!entry.elapsedTimer) return;
|
|
474
|
+
clearInterval(entry.elapsedTimer);
|
|
475
|
+
entry.elapsedTimer = undefined;
|
|
476
|
+
}
|
|
477
|
+
|
|
441
478
|
private async finalize(entry: TurnEntry, state: TurnViewState): Promise<void> {
|
|
442
479
|
let terminalCardError: unknown;
|
|
443
480
|
if (!entry.historySnapshot) {
|
package/src/feishu/types.ts
CHANGED
|
@@ -50,6 +50,7 @@ export interface ChatUpdatedEvent {
|
|
|
50
50
|
|
|
51
51
|
export interface FeishuOutbound {
|
|
52
52
|
createGroup?(input: CreateGroupInput): Promise<CreatedGroup>;
|
|
53
|
+
deleteGroup?(chatId: string): Promise<void>;
|
|
53
54
|
addReaction?(messageId: string, emojiType: string): Promise<string | undefined>;
|
|
54
55
|
deleteReaction?(messageId: string, reactionId: string): Promise<void>;
|
|
55
56
|
downloadImage?(messageId: string, imageKey: string): Promise<string>;
|
package/src/index.ts
CHANGED
|
@@ -48,6 +48,12 @@ import {
|
|
|
48
48
|
resolveSupervisorDiagnosticsPaths,
|
|
49
49
|
} from "./supervision/SupervisorDiagnostics.js";
|
|
50
50
|
import { refreshedSystemEnvironment } from "./supervision/systemEnvironment.js";
|
|
51
|
+
import {
|
|
52
|
+
assertSelfUpdatePlanPath,
|
|
53
|
+
launchSelfUpdateRunner,
|
|
54
|
+
readPendingSelfUpdate,
|
|
55
|
+
releaseSelfUpdatePlan,
|
|
56
|
+
} from "./cli/SelfUpdater.js";
|
|
51
57
|
|
|
52
58
|
const processStartedAt = new Date();
|
|
53
59
|
const agentBotVersion = readPackageVersion();
|
|
@@ -119,16 +125,26 @@ if (routes.length === 0) throw new Error("Neither Feishu nor console input is en
|
|
|
119
125
|
const outbound = new OutboundRouter(routes);
|
|
120
126
|
let shuttingDown = false;
|
|
121
127
|
let restartRequested = false;
|
|
128
|
+
let pendingSelfUpdatePlanPath: string | undefined;
|
|
122
129
|
const safeRestart = new SafeRestartScheduler({
|
|
123
130
|
readActivity: () => store.getServerActivityState(),
|
|
124
|
-
onReady: (reason, notificationTargets) =>
|
|
131
|
+
onReady: (reason, notificationTargets) => pendingSelfUpdatePlanPath
|
|
132
|
+
? initiateSelfUpdate(reason, notificationTargets)
|
|
133
|
+
: initiateRestart(reason, notificationTargets),
|
|
125
134
|
onStatus: (status) => safeRestartNotifier?.update(status),
|
|
126
135
|
onStatusError: (error) => logger.warn({ error }, "Failed to publish safe restart status."),
|
|
127
136
|
});
|
|
128
137
|
const controller = new ProxySessionController(config, store, runtimes, outbound, logger, {
|
|
129
138
|
supervised,
|
|
130
139
|
restart: requestRestart,
|
|
131
|
-
cancelSafeRestart: (scheduleId) =>
|
|
140
|
+
cancelSafeRestart: async (scheduleId) => {
|
|
141
|
+
const cancelled = await safeRestart.cancelScheduled(scheduleId);
|
|
142
|
+
if (cancelled && pendingSelfUpdatePlanPath) {
|
|
143
|
+
releaseSelfUpdatePlan(pendingSelfUpdatePlanPath);
|
|
144
|
+
pendingSelfUpdatePlanPath = undefined;
|
|
145
|
+
}
|
|
146
|
+
return cancelled;
|
|
147
|
+
},
|
|
132
148
|
rememberFeishuUserOpenId: (userOpenId) => {
|
|
133
149
|
if (config.feishu.userOpenId?.startsWith("ou_")) return;
|
|
134
150
|
const persisted = persistFeishuUserOpenIdIfMissing(defaultDotEnvPath(), userOpenId);
|
|
@@ -185,6 +201,27 @@ await controller.recoverInterruptedTasks().catch((error: unknown) => {
|
|
|
185
201
|
logger.error({ error }, "Startup task recovery failed; the server will remain available.");
|
|
186
202
|
});
|
|
187
203
|
consoleConnector?.start();
|
|
204
|
+
recoverPendingSelfUpdate();
|
|
205
|
+
|
|
206
|
+
function recoverPendingSelfUpdate(): void {
|
|
207
|
+
const pending = readPendingSelfUpdate(agentBotHome());
|
|
208
|
+
if (!pending || pendingSelfUpdatePlanPath || restartRequested || safeRestart.scheduled) return;
|
|
209
|
+
let notificationTarget: RestartNotificationTarget | undefined;
|
|
210
|
+
try {
|
|
211
|
+
notificationTarget = inferControlRestartTarget(pending.plan.notificationSessionId);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
logger.warn({ error }, "Could not restore the self-update notification target; update recovery will continue.");
|
|
214
|
+
}
|
|
215
|
+
pendingSelfUpdatePlanPath = pending.planPath;
|
|
216
|
+
safeRestart.schedule(
|
|
217
|
+
pending.plan.reason ?? `Resume Agent Bot update to ${pending.plan.toVersion}`,
|
|
218
|
+
notificationTarget,
|
|
219
|
+
);
|
|
220
|
+
logger.warn(
|
|
221
|
+
{ planPath: pending.planPath, toVersion: pending.plan.toVersion },
|
|
222
|
+
"Recovered a prepared Agent Bot self-update after Worker restart.",
|
|
223
|
+
);
|
|
224
|
+
}
|
|
188
225
|
|
|
189
226
|
async function requestRestart(
|
|
190
227
|
contextKey: string,
|
|
@@ -199,6 +236,10 @@ async function requestRestart(
|
|
|
199
236
|
await outbound.sendText(contextKey, "Agent Bot 已在重启中,请稍候。").catch(() => undefined);
|
|
200
237
|
return;
|
|
201
238
|
}
|
|
239
|
+
if (pendingSelfUpdatePlanPath) {
|
|
240
|
+
await outbound.sendText(contextKey, "Agent Bot 更新已在等待中;更新完成后服务会自动恢复。").catch(() => undefined);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
202
243
|
if (force) {
|
|
203
244
|
await initiateRestart("用户执行 /restart --force 命令", [notificationTarget]);
|
|
204
245
|
return;
|
|
@@ -223,6 +264,7 @@ async function initiateRestart(
|
|
|
223
264
|
return;
|
|
224
265
|
}
|
|
225
266
|
restartRequested = true;
|
|
267
|
+
pendingSelfUpdatePlanPath = undefined;
|
|
226
268
|
await safeRestart.cancelCurrent();
|
|
227
269
|
await safeRestartNotifier?.flush();
|
|
228
270
|
const allNotificationTargets = mergeRestartNotificationTargets(
|
|
@@ -240,6 +282,55 @@ async function initiateRestart(
|
|
|
240
282
|
), 100);
|
|
241
283
|
}
|
|
242
284
|
|
|
285
|
+
async function initiateSelfUpdate(
|
|
286
|
+
reason: string,
|
|
287
|
+
notificationTargets: RestartNotificationTarget[] = [],
|
|
288
|
+
): Promise<void> {
|
|
289
|
+
if (restartRequested) {
|
|
290
|
+
await sendRestartTexts(notificationTargets, "Agent Bot 已在重启或更新中,请稍候。");
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const planPath = pendingSelfUpdatePlanPath;
|
|
294
|
+
if (!planPath) return;
|
|
295
|
+
restartRequested = true;
|
|
296
|
+
await safeRestartNotifier?.flush();
|
|
297
|
+
const allNotificationTargets = mergeRestartNotificationTargets(
|
|
298
|
+
notificationTargets,
|
|
299
|
+
safeRestartNotifier?.getNotificationTargets() ?? [],
|
|
300
|
+
);
|
|
301
|
+
await sendRestartTexts(
|
|
302
|
+
allNotificationTargets,
|
|
303
|
+
"Agent Bot 正在更新,完成或回滚后会自动恢复服务并发送启动状态通知。",
|
|
304
|
+
);
|
|
305
|
+
const environmentRefresh = refreshedSystemEnvironment();
|
|
306
|
+
if (environmentRefresh.error) {
|
|
307
|
+
logger.warn({ error: environmentRefresh.error }, "Failed to refresh the environment for Agent Bot update.");
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
assertSelfUpdatePlanPath(planPath, agentBotHome());
|
|
311
|
+
launchSelfUpdateRunner(planPath, {
|
|
312
|
+
waitPids: [process.pid, ...(supervised ? [process.ppid] : [])],
|
|
313
|
+
environment: replacementSupervisorEnvironment(
|
|
314
|
+
reason,
|
|
315
|
+
environmentRefresh.environment,
|
|
316
|
+
allNotificationTargets,
|
|
317
|
+
),
|
|
318
|
+
});
|
|
319
|
+
} catch (error) {
|
|
320
|
+
restartRequested = false;
|
|
321
|
+
releaseSelfUpdatePlan(planPath);
|
|
322
|
+
pendingSelfUpdatePlanPath = undefined;
|
|
323
|
+
logger.error({ error, planPath }, "Failed to launch Agent Bot self-update; keeping the current service online.");
|
|
324
|
+
await sendRestartTexts(
|
|
325
|
+
allNotificationTargets,
|
|
326
|
+
`Agent Bot 更新未启动,当前服务保持在线:${error instanceof Error ? error.message : String(error)}`,
|
|
327
|
+
);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
pendingSelfUpdatePlanPath = undefined;
|
|
331
|
+
setTimeout(() => void shutdown(STOP_EXIT_CODE), 100);
|
|
332
|
+
}
|
|
333
|
+
|
|
243
334
|
async function handleControlRequest(request: ControlRequest): Promise<ControlResponse> {
|
|
244
335
|
switch (request.action) {
|
|
245
336
|
case "health":
|
|
@@ -268,6 +359,9 @@ async function handleControlRequest(request: ControlRequest): Promise<ControlRes
|
|
|
268
359
|
},
|
|
269
360
|
};
|
|
270
361
|
case "server_restart": {
|
|
362
|
+
if (pendingSelfUpdatePlanPath) {
|
|
363
|
+
return { ok: false, message: "An Agent Bot update is already pending." };
|
|
364
|
+
}
|
|
271
365
|
const notificationTarget = inferControlRestartTarget(request.notificationSessionId);
|
|
272
366
|
if (request.mode === "safe") {
|
|
273
367
|
const newlyScheduled = safeRestart.schedule(request.reason, notificationTarget);
|
|
@@ -284,18 +378,92 @@ async function handleControlRequest(request: ControlRequest): Promise<ControlRes
|
|
|
284
378
|
), 25);
|
|
285
379
|
return { ok: true, message: "Immediate restart requested." };
|
|
286
380
|
}
|
|
381
|
+
case "server_update": {
|
|
382
|
+
if (restartRequested || pendingSelfUpdatePlanPath || safeRestart.scheduled) {
|
|
383
|
+
return { ok: false, message: "A restart or update is already pending." };
|
|
384
|
+
}
|
|
385
|
+
assertSelfUpdatePlanPath(request.planPath, agentBotHome());
|
|
386
|
+
const notificationTarget = inferControlRestartTarget(request.notificationSessionId);
|
|
387
|
+
pendingSelfUpdatePlanPath = request.planPath;
|
|
388
|
+
safeRestart.schedule(request.reason, notificationTarget);
|
|
389
|
+
return {
|
|
390
|
+
ok: true,
|
|
391
|
+
message: "Agent Bot update scheduled after active tasks finish and the server becomes idle.",
|
|
392
|
+
};
|
|
393
|
+
}
|
|
287
394
|
case "server_stop":
|
|
288
395
|
safeRestart.cancel();
|
|
396
|
+
if (pendingSelfUpdatePlanPath) releaseSelfUpdatePlan(pendingSelfUpdatePlanPath);
|
|
397
|
+
pendingSelfUpdatePlanPath = undefined;
|
|
289
398
|
setTimeout(() => void shutdown(STOP_EXIT_CODE), 25);
|
|
290
399
|
return { ok: true, message: "Agent Bot server stop requested." };
|
|
291
400
|
case "task_status":
|
|
292
401
|
return { ok: true, data: await controller.controlGetTaskStatus(request.localSessionId) };
|
|
293
402
|
case "task_stop":
|
|
294
403
|
return { ok: true, message: await controller.controlStopTask(request.localSessionId) };
|
|
404
|
+
case "task_archive":
|
|
405
|
+
return { ok: true, data: await controller.controlArchiveTask(request.localSessionId) };
|
|
406
|
+
case "task_dismiss":
|
|
407
|
+
return { ok: true, data: await controller.controlDismissTask(request.localSessionId) };
|
|
295
408
|
case "task_title":
|
|
296
409
|
return { ok: true, message: await controller.controlSetTaskTitle(request.localSessionId, request.title) };
|
|
297
410
|
case "task_prompt":
|
|
298
411
|
return { ok: true, message: await controller.controlSendTaskPrompt(request.localSessionId, request.text) };
|
|
412
|
+
case "task_new":
|
|
413
|
+
return {
|
|
414
|
+
ok: true,
|
|
415
|
+
data: await controller.controlCreateTask(
|
|
416
|
+
request.localSessionId,
|
|
417
|
+
request.title,
|
|
418
|
+
request.cwd,
|
|
419
|
+
request.projectless === true,
|
|
420
|
+
request.agentName,
|
|
421
|
+
),
|
|
422
|
+
};
|
|
423
|
+
case "task_fork":
|
|
424
|
+
return { ok: true, data: await controller.controlForkTask(request.localSessionId) };
|
|
425
|
+
case "task_switch":
|
|
426
|
+
return {
|
|
427
|
+
ok: true,
|
|
428
|
+
data: await controller.controlSwitchTask(
|
|
429
|
+
request.localSessionId,
|
|
430
|
+
request.targetLocalSessionId,
|
|
431
|
+
request.previous === true,
|
|
432
|
+
),
|
|
433
|
+
};
|
|
434
|
+
case "task_queue":
|
|
435
|
+
return { ok: true, data: await controller.controlQueueTaskPrompt(request.localSessionId, request.text) };
|
|
436
|
+
case "task_agent":
|
|
437
|
+
return { ok: true, data: controller.controlTaskAgent(request.localSessionId, request.agentName) };
|
|
438
|
+
case "task_settings":
|
|
439
|
+
return {
|
|
440
|
+
ok: true,
|
|
441
|
+
data: await controller.controlTaskSettings(request.localSessionId, request.setting, request.value),
|
|
442
|
+
};
|
|
443
|
+
case "task_goal":
|
|
444
|
+
return {
|
|
445
|
+
ok: true,
|
|
446
|
+
data: await controller.controlTaskGoal(
|
|
447
|
+
request.localSessionId,
|
|
448
|
+
request.goalAction,
|
|
449
|
+
request.objective,
|
|
450
|
+
),
|
|
451
|
+
};
|
|
452
|
+
case "task_turns":
|
|
453
|
+
return { ok: true, data: controller.controlListTaskTurns(request.localSessionId) };
|
|
454
|
+
case "task_reset":
|
|
455
|
+
return { ok: true, data: await controller.controlResetTaskToTurn(request.localSessionId, request.turnId) };
|
|
456
|
+
case "task_mute":
|
|
457
|
+
return { ok: true, data: controller.controlTaskMute(request.localSessionId, request.enabled) };
|
|
458
|
+
case "task_shell":
|
|
459
|
+
return { ok: true, data: await controller.controlRunTaskShell(request.localSessionId, request.command) };
|
|
460
|
+
case "task_directory":
|
|
461
|
+
return {
|
|
462
|
+
ok: true,
|
|
463
|
+
data: await controller.controlListTaskDirectory(request.localSessionId, request.directory, request.page),
|
|
464
|
+
};
|
|
465
|
+
case "task_send_file":
|
|
466
|
+
return { ok: true, data: { messageId: await controller.controlSendTaskFile(request.localSessionId, request.filePath) } };
|
|
299
467
|
case "task_new_group":
|
|
300
468
|
return {
|
|
301
469
|
ok: true,
|
|
@@ -327,6 +495,10 @@ async function shutdown(
|
|
|
327
495
|
): Promise<void> {
|
|
328
496
|
if (shuttingDown) return;
|
|
329
497
|
shuttingDown = true;
|
|
498
|
+
if (pendingSelfUpdatePlanPath) {
|
|
499
|
+
releaseSelfUpdatePlan(pendingSelfUpdatePlanPath);
|
|
500
|
+
pendingSelfUpdatePlanPath = undefined;
|
|
501
|
+
}
|
|
330
502
|
logger.info({ exitCode }, "Shutting down Agent Bot.");
|
|
331
503
|
safeRestart.cancel();
|
|
332
504
|
feishuConnector?.stop();
|
|
@@ -172,6 +172,13 @@ export class OutboundRouter {
|
|
|
172
172
|
return createGroup.call(outbound, input);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
async deleteGroup(contextKey: string, chatId: string): Promise<void> {
|
|
176
|
+
const outbound = this.route(contextKey).outbound;
|
|
177
|
+
const deleteGroup = outbound.deleteGroup;
|
|
178
|
+
if (!deleteGroup) throw new Error("当前消息通道不支持解散飞书群。");
|
|
179
|
+
await deleteGroup.call(outbound, chatId);
|
|
180
|
+
}
|
|
181
|
+
|
|
175
182
|
withReplyTarget<T>(
|
|
176
183
|
contextKey: string,
|
|
177
184
|
target: MessageReplyTarget | undefined,
|
|
@@ -102,31 +102,45 @@ export function reduceTurnEvent(state: TurnViewState, event: AgentEvent): TurnVi
|
|
|
102
102
|
return { ...state, status: "waiting_for_approval", approval: event.request };
|
|
103
103
|
case "approval_resolved":
|
|
104
104
|
return { ...state, status: state.activeTool ? "tool_running" : "running", approval: undefined };
|
|
105
|
-
case "turn_completed":
|
|
105
|
+
case "turn_completed": {
|
|
106
|
+
const durationMs = Math.max(0, event.durationMs ?? Date.now() - state.startedAt);
|
|
106
107
|
return {
|
|
107
108
|
...state,
|
|
108
109
|
status: "completed",
|
|
109
110
|
activeTool: undefined,
|
|
110
111
|
approval: undefined,
|
|
111
|
-
completedAt: state.startedAt +
|
|
112
|
-
durationMs
|
|
112
|
+
completedAt: state.startedAt + durationMs,
|
|
113
|
+
durationMs,
|
|
113
114
|
finalResponse: appendGeneratedImageMarkdown(
|
|
114
115
|
event.finalResponse,
|
|
115
116
|
state.completedTools.flatMap((tool) =>
|
|
116
117
|
tool.kind === "image_generation" && tool.imagePath ? [tool.imagePath] : []),
|
|
117
118
|
),
|
|
118
119
|
};
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
}
|
|
121
|
+
case "turn_cancelled": {
|
|
122
|
+
const completedAt = Date.now();
|
|
123
|
+
return {
|
|
124
|
+
...state,
|
|
125
|
+
status: "cancelled",
|
|
126
|
+
activeTool: undefined,
|
|
127
|
+
approval: undefined,
|
|
128
|
+
completedAt,
|
|
129
|
+
durationMs: Math.max(0, completedAt - state.startedAt),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
case "turn_failed": {
|
|
133
|
+
const completedAt = Date.now();
|
|
122
134
|
return {
|
|
123
135
|
...state,
|
|
124
136
|
status: "failed",
|
|
125
137
|
activeTool: undefined,
|
|
126
138
|
approval: undefined,
|
|
127
|
-
completedAt
|
|
139
|
+
completedAt,
|
|
140
|
+
durationMs: Math.max(0, completedAt - state.startedAt),
|
|
128
141
|
error: bound(event.message),
|
|
129
142
|
};
|
|
143
|
+
}
|
|
130
144
|
}
|
|
131
145
|
}
|
|
132
146
|
|