@keyou007/agent-bot 0.1.16-alpha.1 → 0.1.16-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.
@@ -2503,17 +2503,28 @@ export class ProxySessionController {
2503
2503
 
2504
2504
  private async presentPromptQueueCard(localSessionId: string, contextKey: string): Promise<void> {
2505
2505
  await this.serializePromptQueueCardWrite(localSessionId, async () => {
2506
- const card = this.renderPromptQueueCard(localSessionId, contextKey);
2507
- const existing = this.queuedPromptCards.get(localSessionId)?.get(contextKey);
2506
+ const cards = this.queuedPromptCards.get(localSessionId);
2507
+ const existing = cards?.get(contextKey);
2508
2508
  if (existing) {
2509
2509
  try {
2510
- await this.outbound.updateInteractiveCard(contextKey, existing, card);
2511
- return;
2510
+ await this.outbound.updateInteractiveCard(
2511
+ contextKey,
2512
+ existing,
2513
+ this.renderPromptQueueCard(localSessionId, contextKey, "superseded"),
2514
+ );
2512
2515
  } catch (error) {
2513
- this.logger.warn({ error, localSessionId, contextKey, messageId: existing }, "Failed to update prompt queue card; sending a replacement.");
2516
+ this.logger.warn(
2517
+ { error, localSessionId, contextKey, messageId: existing },
2518
+ "Failed to stop the previous prompt queue card; sending a replacement.",
2519
+ );
2514
2520
  }
2521
+ cards?.delete(contextKey);
2522
+ if (cards?.size === 0) this.queuedPromptCards.delete(localSessionId);
2515
2523
  }
2516
- const messageId = await this.outbound.sendInteractiveCard(contextKey, card);
2524
+ const messageId = await this.outbound.sendInteractiveCard(
2525
+ contextKey,
2526
+ this.renderPromptQueueCard(localSessionId, contextKey),
2527
+ );
2517
2528
  if (messageId) this.rememberPromptQueueCard(localSessionId, contextKey, messageId);
2518
2529
  });
2519
2530
  }
@@ -2536,10 +2547,15 @@ export class ProxySessionController {
2536
2547
  });
2537
2548
  }
2538
2549
 
2539
- private renderPromptQueueCard(localSessionId: string, contextKey: string): Record<string, unknown> {
2550
+ private renderPromptQueueCard(
2551
+ localSessionId: string,
2552
+ contextKey: string,
2553
+ phase: "active" | "superseded" = "active",
2554
+ ): Record<string, unknown> {
2540
2555
  return this.cardRenderer.renderPromptQueue({
2541
2556
  sessionId: localSessionId,
2542
2557
  contextKey,
2558
+ phase,
2543
2559
  prompts: this.store.listQueuedPrompts(localSessionId).map((prompt) => ({
2544
2560
  id: prompt.promptId,
2545
2561
  text: prompt.displayPrompt ?? prompt.text,
@@ -4835,7 +4851,43 @@ export class ProxySessionController {
4835
4851
  if (!current.remoteSessionId || !this.isCodexSession(current)) {
4836
4852
  throw new Error("当前任务不是可 Reset 的 App Server 任务。");
4837
4853
  }
4838
- const allTurns = this.store.listTaskTurnGraph(current.localSessionId);
4854
+ const completedTurns = this.store.listTaskTurnGraph(current.localSessionId);
4855
+ const completedTurnIds = new Set(completedTurns.map((turn) => turn.turnId));
4856
+ const runningTurnId = current.lastTurnStatus === "running" && current.lastTurnId
4857
+ && !completedTurnIds.has(current.lastTurnId)
4858
+ ? current.lastTurnId
4859
+ : undefined;
4860
+ const persistedRunningSnapshot = runningTurnId
4861
+ ? turnViewSnapshot(this.store.getTurnSnapshot(runningTurnId))
4862
+ : undefined;
4863
+ const runningAttempt = runningTurnId
4864
+ ? this.store.findIncompleteTurnAttemptByTurnId(runningTurnId)
4865
+ : undefined;
4866
+ const attemptStartedAt = Date.parse(runningAttempt?.createdAt ?? "");
4867
+ const runningSnapshot: TurnViewState | undefined = runningTurnId
4868
+ ? persistedRunningSnapshot ?? {
4869
+ sessionId: current.localSessionId,
4870
+ turnId: runningTurnId,
4871
+ prompt: runningAttempt?.promptText ?? current.title,
4872
+ status: "running",
4873
+ startedAt: Number.isFinite(attemptStartedAt) ? attemptStartedAt : Date.now(),
4874
+ assistantText: "",
4875
+ plan: [],
4876
+ activities: [],
4877
+ completedTools: [],
4878
+ failedTools: [],
4879
+ fileSummary: [],
4880
+ }
4881
+ : undefined;
4882
+ const runningTurn = runningTurnId && runningSnapshot
4883
+ ? {
4884
+ turnId: runningTurnId,
4885
+ parentTurnId: this.store.getTurnParent(runningTurnId, current.localSessionId),
4886
+ snapshot: runningSnapshot,
4887
+ updatedAt: runningAttempt?.updatedAt ?? new Date(runningSnapshot.startedAt).toISOString(),
4888
+ }
4889
+ : undefined;
4890
+ const allTurns = runningTurn ? [runningTurn, ...completedTurns] : completedTurns;
4839
4891
  const graphRows = buildTurnGraphRows(allTurns.map((turn) => ({
4840
4892
  turnId: turn.turnId,
4841
4893
  parentTurnId: turn.parentTurnId,
@@ -4854,16 +4906,18 @@ export class ProxySessionController {
4854
4906
  100,
4855
4907
  );
4856
4908
  const isCurrent = current.lastTurnId === turn.turnId && current.lastTurnStatus === "completed";
4909
+ const isRunning = runningTurnId === turn.turnId;
4857
4910
  return [{
4858
4911
  sequence: graph.sequence,
4859
4912
  graphNodeLine: graph.nodeLine,
4860
4913
  graphConnectorLine: graph.connectorLine,
4861
4914
  lines: [
4862
- `**${cardText(summary || "未记录对话内容")}**`,
4863
- `${formatResetTurnTime(snapshot.completedAt ?? snapshot.startedAt)} · ${cardCode(turn.turnId)}`,
4915
+ cardText(summary || "未记录对话内容"),
4916
+ `${formatResetTurnTime(snapshot.completedAt ?? snapshot.startedAt)} · ${cardText(turn.turnId)}`,
4864
4917
  ],
4865
4918
  current: isCurrent,
4866
- actions: isCurrent ? undefined : [{
4919
+ running: isRunning,
4920
+ actions: isCurrent || isRunning ? undefined : [{
4867
4921
  text: "Reset",
4868
4922
  value: {
4869
4923
  action: "turn_reset",
@@ -4899,7 +4953,9 @@ export class ProxySessionController {
4899
4953
  const card = this.cardRenderer.renderResetHistoryCard({
4900
4954
  entries,
4901
4955
  footerLines: [
4902
- `第 ${page + 1}/${totalPages} 页 · 共 ${total} 个已完成 turn`,
4956
+ runningTurn
4957
+ ? `第 ${page + 1}/${totalPages} 页 · 共 ${total} 个 turn(${completedTurns.length} 个已完成,1 个运行中)`
4958
+ : `第 ${page + 1}/${totalPages} 页 · 共 ${total} 个已完成 turn`,
4903
4959
  ],
4904
4960
  pageActions,
4905
4961
  });
@@ -5266,8 +5322,8 @@ export class ProxySessionController {
5266
5322
  reference: entry.reference,
5267
5323
  summary: `${offset + index + 1}. ${marker}${title} · ${entry.agentName}`,
5268
5324
  detailLines: [
5269
- cardText(lastUserPrompt),
5270
- `**更新时间**:${cardText(entry.updatedLabel)}`,
5325
+ `**最新 Prompt**:${cardText(lastUserPrompt)}`,
5326
+ `<font color='grey'>最近更新:${cardText(entry.updatedLabel)}</font>`,
5271
5327
  ],
5272
5328
  actions,
5273
5329
  current: entry.current,
@@ -1057,6 +1057,15 @@ export class StateStore {
1057
1057
  `).run(turnId, localSessionId, parentTurnId ?? null, new Date().toISOString());
1058
1058
  }
1059
1059
 
1060
+ getTurnParent(turnId: string, localSessionId: string): string | undefined {
1061
+ const row = this.db.prepare(`
1062
+ SELECT parent_turn_id
1063
+ FROM turn_parent_links
1064
+ WHERE turn_id = ? AND local_session_id = ?
1065
+ `).get(turnId, localSessionId) as { parent_turn_id: string | null } | undefined;
1066
+ return row?.parent_turn_id ?? undefined;
1067
+ }
1068
+
1060
1069
  findLatestCompletedTurnId(localSessionId: string, contextKey: string): string | undefined {
1061
1070
  const row = this.db
1062
1071
  .prepare(`
@@ -17,6 +17,7 @@ export class SafeRestartNotifier {
17
17
  private readonly notificationTargets = new Map<string, RestartNotificationTarget>();
18
18
  private activeScheduleId?: number;
19
19
  private terminalScheduleId?: number;
20
+ private lastStatus?: SafeRestartStatus;
20
21
  private pendingInitialStatus?: SafeRestartStatus;
21
22
  private initialCardTimer?: NodeJS.Timeout;
22
23
  private queue = Promise.resolve();
@@ -40,12 +41,24 @@ export class SafeRestartNotifier {
40
41
  this.terminalScheduleId = Math.max(this.terminalScheduleId ?? 0, status.scheduleId);
41
42
  }
42
43
  if (this.activeScheduleId !== status.scheduleId) {
44
+ const previousStatus = this.lastStatus ?? this.pendingInitialStatus;
45
+ const previousMessageIds = [...this.messageIds];
46
+ const previousTargets = new Map(this.notificationTargets);
47
+ if (
48
+ previousStatus
49
+ && previousStatus.phase !== "cancelled"
50
+ && previousStatus.phase !== "restarting"
51
+ && previousMessageIds.length > 0
52
+ ) {
53
+ this.enqueueSupersededCards(previousStatus, previousMessageIds, previousTargets);
54
+ }
43
55
  this.activeScheduleId = status.scheduleId;
44
56
  this.messageIds.clear();
45
57
  this.cardHashes.clear();
46
58
  this.notificationTargets.clear();
47
59
  this.clearInitialCardTimer();
48
60
  this.pendingInitialStatus = status;
61
+ this.lastStatus = status;
49
62
  this.collectNotificationTargets(status);
50
63
  const delayMs = status.phase === "cancelled"
51
64
  ? 0
@@ -59,6 +72,7 @@ export class SafeRestartNotifier {
59
72
  }, delayMs);
60
73
  return Promise.resolve();
61
74
  }
75
+ this.lastStatus = status;
62
76
  this.collectNotificationTargets(status);
63
77
  if (this.pendingInitialStatus) {
64
78
  this.pendingInitialStatus = status;
@@ -149,9 +163,13 @@ export class SafeRestartNotifier {
149
163
  } else {
150
164
  created = await this.outbound.sendInteractiveCard(contextKey, card);
151
165
  }
152
- if (created && this.activeScheduleId === status.scheduleId) {
153
- this.messageIds.set(contextKey, created);
154
- this.cardHashes.set(contextKey, cardHash);
166
+ if (created) {
167
+ if (this.activeScheduleId === status.scheduleId) {
168
+ this.messageIds.set(contextKey, created);
169
+ this.cardHashes.set(contextKey, cardHash);
170
+ } else {
171
+ await this.stopCard(created, status, reason ?? status.reason);
172
+ }
155
173
  }
156
174
  } catch (error) {
157
175
  this.logger.warn({ error, contextKey }, "Failed to send safe restart status card.");
@@ -159,6 +177,37 @@ export class SafeRestartNotifier {
159
177
  }));
160
178
  }
161
179
 
180
+ private enqueueSupersededCards(
181
+ status: SafeRestartStatus,
182
+ messageIds: Array<[string, string]>,
183
+ targets: Map<string, RestartNotificationTarget>,
184
+ ): void {
185
+ const queued = this.queue.catch(() => undefined).then(async () => {
186
+ await Promise.all(messageIds.map(async ([contextKey, messageId]) => {
187
+ const reason = targets.get(contextKey)?.reason ?? status.reason;
188
+ await this.stopCard(messageId, status, reason);
189
+ }));
190
+ });
191
+ this.queue = queued;
192
+ }
193
+
194
+ private async stopCard(messageId: string, status: SafeRestartStatus, reason: string): Promise<void> {
195
+ try {
196
+ await this.outbound.updateInteractiveCard(messageId, this.renderer.renderSafeRestartStatus({
197
+ scheduleId: status.scheduleId,
198
+ reason,
199
+ phase: "superseded",
200
+ pendingFinalDeliveries: status.activity.pendingFinalDeliveries,
201
+ waitingTasks: [],
202
+ }));
203
+ } catch (error) {
204
+ this.logger.warn(
205
+ { error, messageId, scheduleId: status.scheduleId },
206
+ "Failed to stop a superseded safe restart status card.",
207
+ );
208
+ }
209
+ }
210
+
162
211
  private collectNotificationTargets(status: SafeRestartStatus): void {
163
212
  for (const target of status.notificationTargets ?? []) {
164
213
  const contextKey = target.contextKey.trim();
@@ -58,9 +58,9 @@ export class SafeRestartScheduler {
58
58
  schedule(reason: string, notificationTarget?: RestartNotificationTarget): boolean {
59
59
  const newlyScheduled = !this.reason;
60
60
  if (newlyScheduled) {
61
- this.scheduleId += 1;
62
61
  this.notificationTargets = [];
63
62
  }
63
+ this.scheduleId += 1;
64
64
  this.addNotificationTarget(notificationTarget, reason);
65
65
  this.reason = reason;
66
66
  this.idleSince = undefined;
@@ -109,12 +109,13 @@ export class SafeRestartScheduler {
109
109
  private async poll(): Promise<void> {
110
110
  const reason = this.reason;
111
111
  if (!reason) return;
112
+ const scheduleId = this.scheduleId;
112
113
  const state = this.options.readActivity();
113
114
  if (state.runningSessions > 0 || state.pendingFinalDeliveries > 0) {
114
115
  this.idleSince = undefined;
115
116
  this.idleInboundAt = undefined;
116
117
  await this.emitStatus({
117
- scheduleId: this.scheduleId,
118
+ scheduleId,
118
119
  reason,
119
120
  notificationTargets: this.notificationTargetsSnapshot(),
120
121
  phase: state.runningSessions > 0 ? "waiting_tasks" : "waiting_delivery",
@@ -128,7 +129,7 @@ export class SafeRestartScheduler {
128
129
  this.idleSince = Date.now();
129
130
  this.idleInboundAt = latestInboundAt;
130
131
  await this.emitStatus({
131
- scheduleId: this.scheduleId,
132
+ scheduleId,
132
133
  reason,
133
134
  notificationTargets: this.notificationTargetsSnapshot(),
134
135
  phase: "countdown",
@@ -140,7 +141,7 @@ export class SafeRestartScheduler {
140
141
  const remainingMs = Math.max(0, quietPeriodMs - (Date.now() - this.idleSince));
141
142
  if (remainingMs > 0) {
142
143
  await this.emitStatus({
143
- scheduleId: this.scheduleId,
144
+ scheduleId,
144
145
  reason,
145
146
  notificationTargets: this.notificationTargetsSnapshot(),
146
147
  phase: "countdown",
@@ -159,7 +160,7 @@ export class SafeRestartScheduler {
159
160
  this.idleInboundAt = undefined;
160
161
  if (confirmed.runningSessions > 0 || confirmed.pendingFinalDeliveries > 0) {
161
162
  await this.emitStatus({
162
- scheduleId: this.scheduleId,
163
+ scheduleId,
163
164
  reason,
164
165
  notificationTargets: this.notificationTargetsSnapshot(),
165
166
  phase: confirmed.runningSessions > 0 ? "waiting_tasks" : "waiting_delivery",
@@ -167,7 +168,7 @@ export class SafeRestartScheduler {
167
168
  });
168
169
  } else {
169
170
  await this.emitStatus({
170
- scheduleId: this.scheduleId,
171
+ scheduleId,
171
172
  reason,
172
173
  notificationTargets: this.notificationTargetsSnapshot(),
173
174
  phase: "countdown",
@@ -177,7 +178,6 @@ export class SafeRestartScheduler {
177
178
  }
178
179
  return;
179
180
  }
180
- const scheduleId = this.scheduleId;
181
181
  const notificationTargets = this.notificationTargetsSnapshot();
182
182
  this.clear();
183
183
  await this.emitStatus({
@@ -230,7 +230,7 @@ export class SafeRestartScheduler {
230
230
  this.notificationTargets[existingIndex] = {
231
231
  ...existing,
232
232
  reason: target.reason,
233
- ...(!existing.replyMessageId && target.replyMessageId
233
+ ...(target.replyMessageId
234
234
  ? { replyMessageId: target.replyMessageId }
235
235
  : {}),
236
236
  };