@keyou007/agent-bot 0.1.16-alpha.2 → 0.1.16-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 +12 -0
- package/dist/cli/LocalControlServer.js +4 -1
- package/dist/cli/LocalControlServer.js.map +1 -1
- package/dist/feishu/CardRenderer.js +75 -16
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +155 -21
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/startup/SessionMetadataHydrator.js +0 -25
- package/dist/startup/SessionMetadataHydrator.js.map +1 -1
- package/dist/state/StateStore.js +40 -0
- package/dist/state/StateStore.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/cli/LocalControlServer.ts +3 -1
- package/src/feishu/CardRenderer.ts +74 -10
- package/src/proxy/ProxySessionController.ts +173 -33
- package/src/startup/SessionMetadataHydrator.ts +0 -27
- package/src/state/StateStore.ts +43 -0
|
@@ -28,7 +28,7 @@ const SESSION_PROMPT_PREVIEW_LENGTH = 50;
|
|
|
28
28
|
const DIRECTORY_PAGE_SIZE = 15;
|
|
29
29
|
const RESET_HISTORY_PAGE_SIZE = 10;
|
|
30
30
|
const MAX_LLM_TURN_RETRIES = 3;
|
|
31
|
-
const RECOVERY_ACTIVITY_WINDOW_MS =
|
|
31
|
+
const RECOVERY_ACTIVITY_WINDOW_MS = 10 * 60 * 1_000;
|
|
32
32
|
const RECOVERY_HEARTBEAT_INTERVAL_MS = 60 * 1_000;
|
|
33
33
|
const SHELL_COMMAND_JOB_POLL_INTERVAL_MS = 1_000;
|
|
34
34
|
const SHELL_COMMAND_JOB_RELAUNCH_DELAY_MS = 5_000;
|
|
@@ -1439,6 +1439,7 @@ export class ProxySessionController {
|
|
|
1439
1439
|
return recovery;
|
|
1440
1440
|
}
|
|
1441
1441
|
async runStartupRecovery() {
|
|
1442
|
+
await this.finishPersistedCancellationRequests();
|
|
1442
1443
|
await this.recoverShellCommandJobs();
|
|
1443
1444
|
this.backfillTurnAttemptsForUpgrade();
|
|
1444
1445
|
const attempts = this.store.listIncompleteTurnAttempts();
|
|
@@ -1464,6 +1465,31 @@ export class ProxySessionController {
|
|
|
1464
1465
|
}
|
|
1465
1466
|
this.restorePersistedQueuedPrompts();
|
|
1466
1467
|
}
|
|
1468
|
+
async finishPersistedCancellationRequests() {
|
|
1469
|
+
for (const attempt of this.store.listCancellationRequestedTurnAttempts()) {
|
|
1470
|
+
const session = this.store.getSession(attempt.localSessionId);
|
|
1471
|
+
if (!attempt.turnId) {
|
|
1472
|
+
this.store.updateTurnAttempt(attempt.attemptId, { status: "cancelled" });
|
|
1473
|
+
if (session?.status === "starting" || session?.status === "running") {
|
|
1474
|
+
this.store.updateSession(session.localSessionId, { status: "ready" });
|
|
1475
|
+
}
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
if (session) {
|
|
1479
|
+
this.outbound.registerSession(session.localSessionId, attempt.contextKey, session.title, session.cwd, this.agentLabel(session.agentName));
|
|
1480
|
+
}
|
|
1481
|
+
try {
|
|
1482
|
+
await this.handleRuntimeEvent({
|
|
1483
|
+
type: "turn_cancelled",
|
|
1484
|
+
sessionId: attempt.localSessionId,
|
|
1485
|
+
turnId: attempt.turnId,
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
catch (error) {
|
|
1489
|
+
this.logger.warn({ error, attemptId: attempt.attemptId, turnId: attempt.turnId }, "Failed to finish a persisted user-requested turn cancellation.");
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1467
1493
|
backfillTurnAttemptsForUpgrade() {
|
|
1468
1494
|
for (const session of this.store.listAllSessions()) {
|
|
1469
1495
|
if (session.status !== "starting" && session.status !== "running" && session.status !== "ready")
|
|
@@ -1521,7 +1547,7 @@ export class ProxySessionController {
|
|
|
1521
1547
|
const session = this.store.getSession(attempt.localSessionId);
|
|
1522
1548
|
const oldCardTurnId = attempt.pendingTurnId ?? attempt.turnId;
|
|
1523
1549
|
if (session && oldCardTurnId && this.outbound.canRoute(attempt.contextKey)) {
|
|
1524
|
-
await this.outbound.interruptTurnForRecovery(session.localSessionId, attempt.contextKey, oldCardTurnId, "执行中断已超过
|
|
1550
|
+
await this.outbound.interruptTurnForRecovery(session.localSessionId, attempt.contextKey, oldCardTurnId, "执行中断已超过 10 分钟,未自动恢复。").catch((error) => {
|
|
1525
1551
|
this.logger.warn({ error, attemptId: attempt.attemptId, turnId: oldCardTurnId }, "Failed to close an expired interrupted thinking card.");
|
|
1526
1552
|
});
|
|
1527
1553
|
}
|
|
@@ -2382,17 +2408,68 @@ export class ProxySessionController {
|
|
|
2382
2408
|
});
|
|
2383
2409
|
}
|
|
2384
2410
|
let turnId;
|
|
2411
|
+
let runtimeText = text;
|
|
2412
|
+
let cardPrompt = options.displayPrompt ?? text;
|
|
2413
|
+
let retriedStart = false;
|
|
2385
2414
|
try {
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2415
|
+
while (true) {
|
|
2416
|
+
let pendingTurnId;
|
|
2417
|
+
try {
|
|
2418
|
+
pendingTurnId = await this.outbound.startPendingTurn(loaded.record.localSessionId, loaded.record.contextKey, title, replyTarget, cardPrompt);
|
|
2419
|
+
}
|
|
2420
|
+
catch (error) {
|
|
2421
|
+
this.store.updateTurnAttempt(attemptId, {
|
|
2422
|
+
status: options.preserveAttemptOnFailure ? "recovering" : "failed",
|
|
2423
|
+
});
|
|
2424
|
+
throw error;
|
|
2425
|
+
}
|
|
2426
|
+
this.store.updateTurnAttempt(attemptId, { pendingTurnId: pendingTurnId ?? null });
|
|
2427
|
+
try {
|
|
2428
|
+
turnId = await loaded.runtime.startTurn(loaded.record.localSessionId, runtimePrompt(runtimeText, localImagePaths));
|
|
2429
|
+
break;
|
|
2430
|
+
}
|
|
2431
|
+
catch (error) {
|
|
2432
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2433
|
+
const attempt = this.store.getTurnAttempt(attemptId);
|
|
2434
|
+
const retryAttempt = attempt
|
|
2435
|
+
&& isRetryableLlmTurnFailure(message)
|
|
2436
|
+
&& attempt.retryCount < MAX_LLM_TURN_RETRIES
|
|
2437
|
+
? this.store.prepareUnstartedTurnAttemptRetry(attemptId)
|
|
2438
|
+
: undefined;
|
|
2439
|
+
if (!retryAttempt) {
|
|
2440
|
+
const exhausted = attempt
|
|
2441
|
+
&& isRetryableLlmTurnFailure(message)
|
|
2442
|
+
&& attempt.retryCount >= MAX_LLM_TURN_RETRIES;
|
|
2443
|
+
const finalMessage = exhausted
|
|
2444
|
+
? `${message}\n\n已自动重试 ${MAX_LLM_TURN_RETRIES} 次,仍未成功。`
|
|
2445
|
+
: message;
|
|
2446
|
+
this.store.updateTurnAttempt(attemptId, {
|
|
2447
|
+
status: options.preserveAttemptOnFailure ? "recovering" : "failed",
|
|
2448
|
+
});
|
|
2449
|
+
await this.outbound.failPendingTurn(loaded.record.localSessionId, finalMessage);
|
|
2450
|
+
if (finalMessage === message)
|
|
2451
|
+
throw error;
|
|
2452
|
+
throw new Error(finalMessage, { cause: error });
|
|
2453
|
+
}
|
|
2454
|
+
retriedStart = true;
|
|
2455
|
+
this.llmRetryingSessions.add(loaded.record.localSessionId);
|
|
2456
|
+
this.store.audit(loaded.record.contextKey, "llm_turn_start_retry_started", {
|
|
2457
|
+
attemptId,
|
|
2458
|
+
retryNumber: retryAttempt.retryCount,
|
|
2459
|
+
maxRetries: MAX_LLM_TURN_RETRIES,
|
|
2460
|
+
error: message,
|
|
2461
|
+
});
|
|
2462
|
+
await this.outbound.failPendingTurn(loaded.record.localSessionId, `${message}\n\n检测到临时模型服务错误,正在自动重试(${retryAttempt.retryCount}/${MAX_LLM_TURN_RETRIES})。`).catch((presentationError) => {
|
|
2463
|
+
this.logger.warn({ error: presentationError, sessionId: loaded.record.localSessionId }, "Failed to finalize the pending thinking card before an LLM start retry.");
|
|
2464
|
+
});
|
|
2465
|
+
runtimeText = llmRetryRuntimePrompt(retryAttempt.promptText, retryAttempt.retryCount);
|
|
2466
|
+
cardPrompt = llmRetryCardPrompt(options.displayPrompt ?? retryAttempt.promptText, retryAttempt.retryCount);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2389
2469
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
});
|
|
2394
|
-
await this.outbound.failPendingTurn(loaded.record.localSessionId, error instanceof Error ? error.message : String(error));
|
|
2395
|
-
throw error;
|
|
2470
|
+
finally {
|
|
2471
|
+
if (retriedStart)
|
|
2472
|
+
this.llmRetryingSessions.delete(loaded.record.localSessionId);
|
|
2396
2473
|
}
|
|
2397
2474
|
const attempt = this.store.getTurnAttempt(attemptId);
|
|
2398
2475
|
this.store.updateTurnAttempt(attemptId, {
|
|
@@ -3043,7 +3120,15 @@ export class ProxySessionController {
|
|
|
3043
3120
|
await this.outbound.sendText(record.contextKey, "当前没有正在执行的任务。");
|
|
3044
3121
|
return;
|
|
3045
3122
|
}
|
|
3046
|
-
|
|
3123
|
+
const cancellationAttempt = this.store.requestTurnAttemptCancellation(turnId);
|
|
3124
|
+
try {
|
|
3125
|
+
await runtime.interruptRemoteTurn(record.remoteSessionId, turnId);
|
|
3126
|
+
}
|
|
3127
|
+
catch (error) {
|
|
3128
|
+
if (cancellationAttempt)
|
|
3129
|
+
this.store.restoreTurnAttemptAfterCancellationFailure(cancellationAttempt);
|
|
3130
|
+
throw error;
|
|
3131
|
+
}
|
|
3047
3132
|
this.store.audit(record.contextKey, "turn_interrupt_sent", {
|
|
3048
3133
|
localSessionId: record.localSessionId,
|
|
3049
3134
|
remoteSessionId: record.remoteSessionId,
|
|
@@ -3058,7 +3143,15 @@ export class ProxySessionController {
|
|
|
3058
3143
|
await this.outbound.sendText(record.contextKey, "当前没有正在执行的任务。");
|
|
3059
3144
|
return;
|
|
3060
3145
|
}
|
|
3061
|
-
|
|
3146
|
+
const cancellationAttempt = this.store.requestTurnAttemptCancellation(turnId);
|
|
3147
|
+
try {
|
|
3148
|
+
await loaded.runtime.cancelTurn(record.localSessionId, turnId);
|
|
3149
|
+
}
|
|
3150
|
+
catch (error) {
|
|
3151
|
+
if (cancellationAttempt)
|
|
3152
|
+
this.store.restoreTurnAttemptAfterCancellationFailure(cancellationAttempt);
|
|
3153
|
+
throw error;
|
|
3154
|
+
}
|
|
3062
3155
|
}
|
|
3063
3156
|
async resetCurrentSessionToTurn(contextKey, sessionId, turnId) {
|
|
3064
3157
|
if (!sessionId || !turnId)
|
|
@@ -3797,7 +3890,43 @@ export class ProxySessionController {
|
|
|
3797
3890
|
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
3798
3891
|
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
3799
3892
|
}
|
|
3800
|
-
const
|
|
3893
|
+
const completedTurns = this.store.listTaskTurnGraph(current.localSessionId);
|
|
3894
|
+
const completedTurnIds = new Set(completedTurns.map((turn) => turn.turnId));
|
|
3895
|
+
const runningTurnId = current.lastTurnStatus === "running" && current.lastTurnId
|
|
3896
|
+
&& !completedTurnIds.has(current.lastTurnId)
|
|
3897
|
+
? current.lastTurnId
|
|
3898
|
+
: undefined;
|
|
3899
|
+
const persistedRunningSnapshot = runningTurnId
|
|
3900
|
+
? turnViewSnapshot(this.store.getTurnSnapshot(runningTurnId))
|
|
3901
|
+
: undefined;
|
|
3902
|
+
const runningAttempt = runningTurnId
|
|
3903
|
+
? this.store.findIncompleteTurnAttemptByTurnId(runningTurnId)
|
|
3904
|
+
: undefined;
|
|
3905
|
+
const attemptStartedAt = Date.parse(runningAttempt?.createdAt ?? "");
|
|
3906
|
+
const runningSnapshot = runningTurnId
|
|
3907
|
+
? persistedRunningSnapshot ?? {
|
|
3908
|
+
sessionId: current.localSessionId,
|
|
3909
|
+
turnId: runningTurnId,
|
|
3910
|
+
prompt: runningAttempt?.promptText ?? current.title,
|
|
3911
|
+
status: "running",
|
|
3912
|
+
startedAt: Number.isFinite(attemptStartedAt) ? attemptStartedAt : Date.now(),
|
|
3913
|
+
assistantText: "",
|
|
3914
|
+
plan: [],
|
|
3915
|
+
activities: [],
|
|
3916
|
+
completedTools: [],
|
|
3917
|
+
failedTools: [],
|
|
3918
|
+
fileSummary: [],
|
|
3919
|
+
}
|
|
3920
|
+
: undefined;
|
|
3921
|
+
const runningTurn = runningTurnId && runningSnapshot
|
|
3922
|
+
? {
|
|
3923
|
+
turnId: runningTurnId,
|
|
3924
|
+
parentTurnId: this.store.getTurnParent(runningTurnId, current.localSessionId),
|
|
3925
|
+
snapshot: runningSnapshot,
|
|
3926
|
+
updatedAt: runningAttempt?.updatedAt ?? new Date(runningSnapshot.startedAt).toISOString(),
|
|
3927
|
+
}
|
|
3928
|
+
: undefined;
|
|
3929
|
+
const allTurns = runningTurn ? [runningTurn, ...completedTurns] : completedTurns;
|
|
3801
3930
|
const graphRows = buildTurnGraphRows(allTurns.map((turn) => ({
|
|
3802
3931
|
turnId: turn.turnId,
|
|
3803
3932
|
parentTurnId: turn.parentTurnId,
|
|
@@ -3814,16 +3943,18 @@ export class ProxySessionController {
|
|
|
3814
3943
|
const graph = graphRows[offset + index];
|
|
3815
3944
|
const summary = truncateText((snapshot.prompt ?? snapshot.taskTitle ?? "未记录对话内容").replace(/\s+/g, " ").trim(), 100);
|
|
3816
3945
|
const isCurrent = current.lastTurnId === turn.turnId && current.lastTurnStatus === "completed";
|
|
3946
|
+
const isRunning = runningTurnId === turn.turnId;
|
|
3817
3947
|
return [{
|
|
3818
3948
|
sequence: graph.sequence,
|
|
3819
3949
|
graphNodeLine: graph.nodeLine,
|
|
3820
3950
|
graphConnectorLine: graph.connectorLine,
|
|
3821
3951
|
lines: [
|
|
3822
|
-
|
|
3823
|
-
`${formatResetTurnTime(snapshot.completedAt ?? snapshot.startedAt)} · ${
|
|
3952
|
+
cardText(summary || "未记录对话内容"),
|
|
3953
|
+
`${formatResetTurnTime(snapshot.completedAt ?? snapshot.startedAt)} · ${cardText(turn.turnId)}`,
|
|
3824
3954
|
],
|
|
3825
3955
|
current: isCurrent,
|
|
3826
|
-
|
|
3956
|
+
running: isRunning,
|
|
3957
|
+
actions: isCurrent || isRunning ? undefined : [{
|
|
3827
3958
|
text: "Reset",
|
|
3828
3959
|
value: {
|
|
3829
3960
|
action: "turn_reset",
|
|
@@ -3859,7 +3990,9 @@ export class ProxySessionController {
|
|
|
3859
3990
|
const card = this.cardRenderer.renderResetHistoryCard({
|
|
3860
3991
|
entries,
|
|
3861
3992
|
footerLines: [
|
|
3862
|
-
|
|
3993
|
+
runningTurn
|
|
3994
|
+
? `第 ${page + 1}/${totalPages} 页 · 共 ${total} 个 turn(${completedTurns.length} 个已完成,1 个运行中)`
|
|
3995
|
+
: `第 ${page + 1}/${totalPages} 页 · 共 ${total} 个已完成 turn`,
|
|
3863
3996
|
],
|
|
3864
3997
|
pageActions,
|
|
3865
3998
|
});
|
|
@@ -4202,8 +4335,8 @@ export class ProxySessionController {
|
|
|
4202
4335
|
reference: entry.reference,
|
|
4203
4336
|
summary: `${offset + index + 1}. ${marker}${title} · ${entry.agentName}`,
|
|
4204
4337
|
detailLines: [
|
|
4205
|
-
cardText(lastUserPrompt)
|
|
4206
|
-
|
|
4338
|
+
`**最新 Prompt**:${cardText(lastUserPrompt)}`,
|
|
4339
|
+
`<font color='grey'>最近更新:${cardText(entry.updatedLabel)}</font>`,
|
|
4207
4340
|
],
|
|
4208
4341
|
actions,
|
|
4209
4342
|
current: entry.current,
|
|
@@ -5716,7 +5849,8 @@ function isRetryableLlmTurnFailure(message) {
|
|
|
5716
5849
|
/service unavailable|internal server error|bad gateway|gateway timeout/u,
|
|
5717
5850
|
/(?:^|\D)(?:500|502|503|504)(?:\D|$)|upstream (?:error|failure|timeout)/u,
|
|
5718
5851
|
/deadline exceeded|request timed out|request timeout|response timeout/u,
|
|
5719
|
-
/connection (?:reset|closed|aborted)|network error|socket hang up/u,
|
|
5852
|
+
/connection (?:reset|closed|aborted|disconnected)|network error|socket hang up/u,
|
|
5853
|
+
/app server .*(?:closed|disconnected|exited|reset|aborted)/u,
|
|
5720
5854
|
/stream (?:closed|disconnected|interrupted|error)/u,
|
|
5721
5855
|
/\b(?:llm|model|provider|inference)\b.*\b(?:error|failed|failure|unavailable|timeout|timed out)\b/u,
|
|
5722
5856
|
].some((pattern) => pattern.test(normalized));
|