@keyou007/agent-bot 0.1.19 → 0.1.20-alpha.1

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.
@@ -143,10 +143,9 @@ interface AppServerReleaseSchedule {
143
143
  runtime: AgentRuntime;
144
144
  status: AppServerReleaseCardView["status"];
145
145
  blockingTaskCount: number;
146
+ blockingTaskTitles: string[];
146
147
  targets: Map<string, AppServerReleaseCardTarget>;
147
148
  forced: boolean;
148
- releaseAt?: number;
149
- releaseCountdownSeconds?: number;
150
149
  releaseStarted?: boolean;
151
150
  error?: string;
152
151
  timer?: NodeJS.Timeout;
@@ -184,7 +183,6 @@ const RECOVERY_HEARTBEAT_INTERVAL_MS = 60 * 1_000;
184
183
  const SHELL_COMMAND_JOB_POLL_INTERVAL_MS = 1_000;
185
184
  const SHELL_COMMAND_JOB_RELAUNCH_DELAY_MS = 5_000;
186
185
  const APP_SERVER_RELEASE_POLL_INTERVAL_MS = 500;
187
- const APP_SERVER_RELEASE_IDLE_DELAY_MS = 5_000;
188
186
  const FORWARD_ATTACHMENT_WINDOW_MS = 800;
189
187
  const DEFAULT_MERGED_FORWARD_INSTRUCTION = "请参考以下内容回复用户";
190
188
  const DEFAULT_REFERENCED_MESSAGE_INSTRUCTION = "请参考引用消息回复用户";
@@ -454,7 +452,6 @@ export interface ControlTaskReleaseResult {
454
452
  agentName: string;
455
453
  status: "waiting" | "released";
456
454
  blockingTaskCount: number;
457
- releaseInSeconds?: number;
458
455
  }
459
456
 
460
457
  export interface ProxyLifecycle {
@@ -1287,7 +1284,6 @@ export class ProxySessionController {
1287
1284
  agentName: result.agentName,
1288
1285
  status: result.status,
1289
1286
  blockingTaskCount: result.blockingTaskCount,
1290
- ...(result.releaseInSeconds !== undefined ? { releaseInSeconds: result.releaseInSeconds } : {}),
1291
1287
  };
1292
1288
  }
1293
1289
 
@@ -2888,8 +2884,7 @@ export class ProxySessionController {
2888
2884
  const anchor = this.store.findTurnAnchorByMessageId(messageId);
2889
2885
  if (!anchor) return false;
2890
2886
  if (record.lastTurnId === anchor.turnId) return true;
2891
- return this.store.listTaskTurnGraph(record.localSessionId)
2892
- .some((turn) => turn.turnId === anchor.turnId);
2887
+ return this.store.hasTaskHistoryTurn(record.localSessionId, anchor.turnId);
2893
2888
  }
2894
2889
 
2895
2890
  private async forkThreadSession(message: IncomingMessage): Promise<void> {
@@ -6974,7 +6969,6 @@ export class ProxySessionController {
6974
6969
  agentName: string;
6975
6970
  status: "waiting" | "released" | "failed";
6976
6971
  blockingTaskCount: number;
6977
- releaseInSeconds?: number;
6978
6972
  error?: string;
6979
6973
  }> {
6980
6974
  const record = requestedRecord ?? this.requireCurrentSession(contextKey);
@@ -6986,20 +6980,17 @@ export class ProxySessionController {
6986
6980
  let schedule = this.appServerReleaseSchedules.get(record.agentName);
6987
6981
  const created = !schedule;
6988
6982
  if (!schedule) {
6989
- const blockingTaskCount = this.appServerReleaseBlockers(record.agentName, runtime).length;
6983
+ const blockers = this.appServerReleaseBlockers(record.agentName, runtime);
6984
+ const blockingTaskCount = blockers.length;
6990
6985
  schedule = {
6991
6986
  id: this.nextAppServerReleaseScheduleId++,
6992
6987
  agentName: record.agentName,
6993
6988
  runtime,
6994
6989
  status: "waiting",
6995
6990
  blockingTaskCount,
6991
+ blockingTaskTitles: this.appServerReleaseTaskTitles(blockers),
6996
6992
  targets: new Map(),
6997
6993
  forced: false,
6998
- ...(blockingTaskCount === 0
6999
- ? {
7000
- releaseCountdownSeconds: Math.ceil(APP_SERVER_RELEASE_IDLE_DELAY_MS / 1_000),
7001
- }
7002
- : {}),
7003
6994
  };
7004
6995
  this.appServerReleaseSchedules.set(record.agentName, schedule);
7005
6996
  }
@@ -7012,14 +7003,6 @@ export class ProxySessionController {
7012
7003
  if (messageId) {
7013
7004
  schedule.targets.set(`${contextKey}\u0000${messageId}`, { contextKey, messageId });
7014
7005
  }
7015
- if (
7016
- schedule.status === "waiting"
7017
- && schedule.blockingTaskCount === 0
7018
- && schedule.releaseCountdownSeconds !== undefined
7019
- && schedule.releaseAt === undefined
7020
- ) {
7021
- schedule.releaseAt = Date.now() + APP_SERVER_RELEASE_IDLE_DELAY_MS;
7022
- }
7023
7006
  } catch (error) {
7024
7007
  if (created && schedule.targets.size === 0) {
7025
7008
  this.clearAppServerReleaseSchedule(schedule);
@@ -7032,9 +7015,6 @@ export class ProxySessionController {
7032
7015
  agentName: schedule.agentName,
7033
7016
  status: schedule.status === "failed" ? "failed" : schedule.status === "released" ? "released" : "waiting",
7034
7017
  blockingTaskCount: schedule.blockingTaskCount,
7035
- ...(schedule.releaseCountdownSeconds !== undefined
7036
- ? { releaseInSeconds: schedule.releaseCountdownSeconds }
7037
- : {}),
7038
7018
  ...(schedule.error ? { error: schedule.error } : {}),
7039
7019
  };
7040
7020
  }
@@ -7046,7 +7026,7 @@ export class ProxySessionController {
7046
7026
  if (!Number.isSafeInteger(scheduleId) || !schedule || schedule.id !== scheduleId || schedule.status !== "waiting") {
7047
7027
  throw new Error("释放卡片已失效,请重新发送 /release。");
7048
7028
  }
7049
- await this.performAppServerRelease(schedule, true);
7029
+ await this.performAppServerRelease(schedule);
7050
7030
  }
7051
7031
 
7052
7032
  private async cancelAppServerRelease(action: CardAction): Promise<void> {
@@ -7081,6 +7061,10 @@ export class ProxySessionController {
7081
7061
  });
7082
7062
  }
7083
7063
 
7064
+ private appServerReleaseTaskTitles(blockers: SessionRecord[]): string[] {
7065
+ return blockers.map((session) => session.title?.trim() || "未命名任务");
7066
+ }
7067
+
7084
7068
  private armAppServerReleaseCheck(schedule: AppServerReleaseSchedule): void {
7085
7069
  if (schedule.status !== "waiting" || schedule.timer || schedule.inFlight) return;
7086
7070
  schedule.timer = setTimeout(() => {
@@ -7095,36 +7079,22 @@ export class ProxySessionController {
7095
7079
 
7096
7080
  private async refreshAppServerReleaseSchedule(schedule: AppServerReleaseSchedule): Promise<void> {
7097
7081
  if (this.appServerReleaseSchedules.get(schedule.agentName) !== schedule || schedule.status !== "waiting") return;
7098
- const blockingTaskCount = this.appServerReleaseBlockers(schedule.agentName, schedule.runtime).length;
7099
- if (blockingTaskCount > 0) {
7100
- const changed = schedule.blockingTaskCount !== blockingTaskCount || schedule.releaseAt !== undefined;
7101
- schedule.releaseAt = undefined;
7102
- schedule.releaseCountdownSeconds = undefined;
7103
- if (changed) {
7104
- schedule.blockingTaskCount = blockingTaskCount;
7105
- await this.publishAppServerReleaseSchedule(schedule);
7106
- }
7107
- this.armAppServerReleaseCheck(schedule);
7108
- return;
7109
- }
7110
- schedule.blockingTaskCount = 0;
7111
- if (schedule.releaseAt !== undefined) {
7112
- const releaseCountdownSeconds = Math.max(0, Math.ceil((schedule.releaseAt - Date.now()) / 1_000));
7113
- if (releaseCountdownSeconds > 0) {
7114
- if (schedule.releaseCountdownSeconds !== releaseCountdownSeconds) {
7115
- schedule.releaseCountdownSeconds = releaseCountdownSeconds;
7116
- await this.publishAppServerReleaseSchedule(schedule);
7117
- }
7118
- this.armAppServerReleaseCheck(schedule);
7119
- return;
7120
- }
7082
+ const blockers = this.appServerReleaseBlockers(schedule.agentName, schedule.runtime);
7083
+ const blockingTaskCount = blockers.length;
7084
+ const blockingTaskTitles = this.appServerReleaseTaskTitles(blockers);
7085
+ const titlesChanged = schedule.blockingTaskTitles.length !== blockingTaskTitles.length
7086
+ || schedule.blockingTaskTitles.some((title, index) => title !== blockingTaskTitles[index]);
7087
+ if (schedule.blockingTaskCount !== blockingTaskCount || titlesChanged) {
7088
+ schedule.blockingTaskCount = blockingTaskCount;
7089
+ schedule.blockingTaskTitles = blockingTaskTitles;
7090
+ await this.publishAppServerReleaseSchedule(schedule);
7121
7091
  }
7122
- await this.performAppServerRelease(schedule, false);
7092
+ this.armAppServerReleaseCheck(schedule);
7123
7093
  }
7124
7094
 
7125
- private async performAppServerRelease(schedule: AppServerReleaseSchedule, force: boolean): Promise<void> {
7095
+ private async performAppServerRelease(schedule: AppServerReleaseSchedule): Promise<void> {
7126
7096
  if (schedule.inFlight) return schedule.inFlight;
7127
- const operation = this.performAppServerReleaseNow(schedule, force);
7097
+ const operation = this.performAppServerReleaseNow(schedule);
7128
7098
  schedule.inFlight = operation;
7129
7099
  try {
7130
7100
  await operation;
@@ -7134,31 +7104,17 @@ export class ProxySessionController {
7134
7104
  }
7135
7105
  }
7136
7106
 
7137
- private async performAppServerReleaseNow(schedule: AppServerReleaseSchedule, force: boolean): Promise<void> {
7107
+ private async performAppServerReleaseNow(schedule: AppServerReleaseSchedule): Promise<void> {
7138
7108
  if (schedule.timer) {
7139
7109
  clearTimeout(schedule.timer);
7140
7110
  schedule.timer = undefined;
7141
7111
  }
7142
- const interruptedWork = force && this.appServerReleaseBlockers(schedule.agentName, schedule.runtime).length > 0;
7143
- if (force) await this.cancelQueuedPromptsForAppServerRelease(schedule.agentName);
7144
- else {
7145
- const blockingTaskCount = this.appServerReleaseBlockers(schedule.agentName, schedule.runtime).length;
7146
- if (blockingTaskCount > 0) {
7147
- schedule.status = "waiting";
7148
- schedule.blockingTaskCount = blockingTaskCount;
7149
- schedule.releaseAt = undefined;
7150
- schedule.releaseCountdownSeconds = undefined;
7151
- schedule.releaseStarted = false;
7152
- await this.publishAppServerReleaseSchedule(schedule);
7153
- this.armAppServerReleaseCheck(schedule);
7154
- return;
7155
- }
7156
- }
7112
+ const interruptedWork = this.appServerReleaseBlockers(schedule.agentName, schedule.runtime).length > 0;
7113
+ await this.cancelQueuedPromptsForAppServerRelease(schedule.agentName);
7157
7114
 
7158
7115
  schedule.status = "releasing";
7159
7116
  schedule.blockingTaskCount = 0;
7160
- schedule.releaseAt = undefined;
7161
- schedule.releaseCountdownSeconds = undefined;
7117
+ schedule.blockingTaskTitles = [];
7162
7118
  schedule.releaseStarted = false;
7163
7119
  schedule.forced = interruptedWork;
7164
7120
  await this.publishAppServerReleaseSchedule(schedule);
@@ -7168,18 +7124,18 @@ export class ProxySessionController {
7168
7124
  ) return;
7169
7125
  schedule.releaseStarted = true;
7170
7126
  try {
7171
- const result = await schedule.runtime.release?.({ force });
7127
+ const result = await schedule.runtime.release?.({ force: true });
7172
7128
  if (!result) throw new Error("当前 Agent 不支持释放 App Server。");
7173
7129
  if (result.status === "busy") {
7130
+ const blockers = this.appServerReleaseBlockers(schedule.agentName, schedule.runtime);
7174
7131
  schedule.status = "waiting";
7175
7132
  schedule.blockingTaskCount = Math.max(
7176
7133
  result.activeSessionIds?.length ?? 0,
7177
- this.appServerReleaseBlockers(schedule.agentName, schedule.runtime).length,
7134
+ blockers.length,
7178
7135
  1,
7179
7136
  );
7137
+ schedule.blockingTaskTitles = this.appServerReleaseTaskTitles(blockers);
7180
7138
  schedule.forced = false;
7181
- schedule.releaseAt = undefined;
7182
- schedule.releaseCountdownSeconds = undefined;
7183
7139
  schedule.releaseStarted = false;
7184
7140
  await this.publishAppServerReleaseSchedule(schedule);
7185
7141
  this.armAppServerReleaseCheck(schedule);
@@ -7239,9 +7195,7 @@ export class ProxySessionController {
7239
7195
  scheduleId: schedule.id,
7240
7196
  agentName: schedule.agentName,
7241
7197
  ...(schedule.blockingTaskCount > 0 ? { blockingTaskCount: schedule.blockingTaskCount } : {}),
7242
- ...(schedule.releaseCountdownSeconds !== undefined
7243
- ? { releaseInSeconds: schedule.releaseCountdownSeconds }
7244
- : {}),
7198
+ ...(schedule.blockingTaskTitles.length > 0 ? { blockingTaskTitles: schedule.blockingTaskTitles } : {}),
7245
7199
  ...(schedule.forced ? { forced: true } : {}),
7246
7200
  ...(schedule.error ? { error: schedule.error } : {}),
7247
7201
  };
@@ -1255,6 +1255,46 @@ export class StateStore {
1255
1255
  }));
1256
1256
  }
1257
1257
 
1258
+ hasTaskHistoryTurn(localSessionId: string, turnId: string): boolean {
1259
+ const readParent = this.db.prepare(`
1260
+ SELECT ts.local_session_id, tpl.parent_turn_id
1261
+ FROM turn_snapshots ts
1262
+ LEFT JOIN turn_parent_links tpl ON tpl.turn_id = ts.turn_id
1263
+ WHERE ts.turn_id = ?
1264
+ AND json_extract(ts.snapshot_json, '$.status') = 'completed'
1265
+ `);
1266
+ type ParentRow = { local_session_id: string; parent_turn_id: string | null };
1267
+ const target = readParent.get(turnId) as ParentRow | undefined;
1268
+ if (!target) return false;
1269
+ if (target.local_session_id === localSessionId) return true;
1270
+
1271
+ const backfilledSessions = new Set<string>();
1272
+ let ancestorTurnId = this.findForkSourceTurnId(localSessionId);
1273
+ if (!ancestorTurnId) {
1274
+ // Reset-only legacy tasks may have partially saved cross-session links.
1275
+ this.backfillTurnParents(localSessionId);
1276
+ backfilledSessions.add(localSessionId);
1277
+ ancestorTurnId = this.findCrossSessionParentTurnId(localSessionId);
1278
+ }
1279
+ const visited = new Set<string>();
1280
+ while (ancestorTurnId && !visited.has(ancestorTurnId)) {
1281
+ if (ancestorTurnId === turnId) return true;
1282
+ visited.add(ancestorTurnId);
1283
+ const ancestor = readParent.get(ancestorTurnId) as ParentRow | undefined;
1284
+ if (!ancestor) return false;
1285
+ let parentTurnId = ancestor.parent_turn_id;
1286
+ // Older snapshots may lack parent links. Repair each owner at most once,
1287
+ // without loading full snapshots or rebuilding already linked ancestry.
1288
+ if (!parentTurnId && !backfilledSessions.has(ancestor.local_session_id)) {
1289
+ this.backfillTurnParents(ancestor.local_session_id);
1290
+ backfilledSessions.add(ancestor.local_session_id);
1291
+ parentTurnId = (readParent.get(ancestorTurnId) as ParentRow | undefined)?.parent_turn_id ?? null;
1292
+ }
1293
+ ancestorTurnId = parentTurnId ?? undefined;
1294
+ }
1295
+ return false;
1296
+ }
1297
+
1258
1298
  listTaskTurnGraph(localSessionId: string): CompletedTurnSnapshotRecord[] {
1259
1299
  this.backfillTurnParents(localSessionId);
1260
1300
  const records = new Map(