@keyou007/agent-bot 0.1.19 → 0.1.20-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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@keyou007/agent-bot",
3
- "version": "0.1.19",
3
+ "version": "0.1.20-alpha.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@keyou007/agent-bot",
9
- "version": "0.1.19",
9
+ "version": "0.1.20-alpha.0",
10
10
  "license": "GPL-3.0-only",
11
11
  "dependencies": {
12
12
  "@agentclientprotocol/sdk": "1.2.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyou007/agent-bot",
3
- "version": "0.1.19",
3
+ "version": "0.1.20-alpha.0",
4
4
  "type": "module",
5
5
  "description": "Use Codex, TraeX, and compatible coding agents from Lark/Feishu.",
6
6
  "license": "GPL-3.0-only",
@@ -74,9 +74,10 @@ agentbot task title "<title>"
74
74
  - `prompt` posts the Prompt to the task's Feishu conversation before submitting it.
75
75
  - `queue` creates a later turn instead of steering the active turn. `nosteer` is an alias.
76
76
  - `stop` requests an Agent interrupt; it does not kill the Agent process.
77
+ - `release` sends a Lark confirmation card for releasing the Agent's shared App Server. It never releases automatically; the user must click **Release** or **Release Now** on the card.
77
78
  - `dismiss --yes` irreversibly dissolves the task's current Feishu group and archives its current task. Use it only after the user explicitly confirms.
78
79
 
79
- From Feishu, `/release` hands App Server tasks back to Codex Desktop without archiving them. When idle, it sends a release card and counts down for five seconds; otherwise it waits for the current Agent's running and queued work. **Release Now** interrupts active work and clears queued Prompts, while **Cancel** cancels the pending release. Because each App Server Agent is shared, releasing affects every task loaded by that Agent process.
80
+ From Feishu, `/release` sends a confirmation card for handing App Server tasks back to Codex Desktop without archiving them. It lists the current Agent's running and queued work and never releases automatically. **Release** confirms an idle release, **Release Now** interrupts active work and clears queued Prompts, and **Cancel** cancels the pending release. Because each App Server Agent is shared, releasing affects every task loaded by that Agent process.
80
81
 
81
82
  Create or branch work in the same conversation:
82
83
 
@@ -113,7 +113,6 @@ export interface TaskReleaseControlData {
113
113
  agentName: string;
114
114
  status: "waiting" | "released";
115
115
  blockingTaskCount: number;
116
- releaseInSeconds?: number;
117
116
  }
118
117
 
119
118
  export interface TaskForkControlData {
package/src/cli/help.ts CHANGED
@@ -45,7 +45,7 @@ export function renderCliHelp(version: string, language: CliLanguage = cliLangua
45
45
  task shell [任务] <命令> 在任务目录执行本地命令
46
46
  task stop [任务] 停止任务
47
47
  task archive [任务] 归档任务
48
- task release [任务] 释放任务所属 Agent 的共享 App Server
48
+ task release [任务] 发送共享 App Server 释放确认卡片
49
49
  task dismiss [任务] --yes 归档任务并解散其当前群聊
50
50
  task title [任务] <标题> 重命名任务
51
51
  task restart [任务] [--force] 从任务会话请求安全或立即重启
@@ -138,7 +138,7 @@ Task commands:
138
138
  task shell [task] <command> Run a local command in the task directory
139
139
  task stop [task] Stop a task
140
140
  task archive [task] Archive a task
141
- task release [task] Release the task Agent's shared App Server
141
+ task release [task] Send a confirmation card to release the shared App Server
142
142
  task dismiss [task] --yes Archive a task and dissolve its current group
143
143
  task title [task] <title> Rename a task
144
144
  task restart [task] [--force] Request a safe or immediate restart from a task
package/src/cli.ts CHANGED
@@ -1337,15 +1337,14 @@ async function taskCommand(input: string[]): Promise<void> {
1337
1337
  `App Server tasks released for ${released.agentName}.\n`,
1338
1338
  `已释放 ${released.agentName} 的 App Server 任务。\n`,
1339
1339
  ));
1340
- } else if (released.releaseInSeconds !== undefined) {
1341
- process.stdout.write(cliText(
1342
- `App Server release scheduled for ${released.agentName} in ${released.releaseInSeconds} seconds.\n`,
1343
- `已安排在 ${released.releaseInSeconds} 秒后释放 ${released.agentName} 的 App Server。\n`,
1344
- ));
1345
1340
  } else {
1346
1341
  process.stdout.write(cliText(
1347
- `App Server release scheduled for ${released.agentName} (${released.blockingTaskCount} waiting).\n`,
1348
- `已安排释放 ${released.agentName} 的 App Server(等待 ${released.blockingTaskCount} 个任务)。\n`,
1342
+ released.blockingTaskCount > 0
1343
+ ? `App Server release card sent for ${released.agentName} (${released.blockingTaskCount} blocking). Confirm in Lark to release.\n`
1344
+ : `App Server release card sent for ${released.agentName}. Confirm in Lark to release.\n`,
1345
+ released.blockingTaskCount > 0
1346
+ ? `已发送 ${released.agentName} 的 App Server 释放卡片(${released.blockingTaskCount} 个任务阻塞)。请在飞书中确认释放。\n`
1347
+ : `已发送 ${released.agentName} 的 App Server 释放卡片。请在飞书中确认释放。\n`,
1349
1348
  ));
1350
1349
  }
1351
1350
  return;
@@ -281,7 +281,7 @@ export interface AppServerReleaseCardView {
281
281
  scheduleId: number;
282
282
  agentName: string;
283
283
  blockingTaskCount?: number;
284
- releaseInSeconds?: number;
284
+ blockingTaskTitles?: string[];
285
285
  forced?: boolean;
286
286
  error?: string;
287
287
  }
@@ -405,16 +405,20 @@ export class CardRenderer {
405
405
  }
406
406
 
407
407
  const blockingTaskCount = Math.max(0, view.blockingTaskCount ?? 0);
408
- const releaseInSeconds = Math.max(1, view.releaseInSeconds ?? 1);
409
- return sectionCard("等待释放", [
408
+ const blockingTaskLines = (view.blockingTaskTitles ?? []).map((title, index) => {
409
+ const normalizedTitle = title.replace(/\s+/g, " ").trim() || "未命名任务";
410
+ return `${index + 1}. ${inlineCode(truncateText(normalizedTitle, 80))}`;
411
+ });
412
+ return sectionCard(blockingTaskCount > 0 ? "等待释放" : "确认释放", [
410
413
  markdown([
411
414
  `**Agent**:${agent}`,
412
415
  blockingTaskCount > 0
413
416
  ? `**等待任务**:${blockingTaskCount}`
414
- : `**自动释放**:${releaseInSeconds} 秒后`,
417
+ : "**状态**:可以释放",
418
+ ...blockingTaskLines,
415
419
  blockingTaskCount > 0
416
- ? "> 任务结束后自动释放;立即释放会中断执行。"
417
- : "> 可立即释放或取消。",
420
+ ? "> 点击 **Release Now** 会中断执行;也可等待任务结束后再释放。"
421
+ : "> 点击 **Release** 释放,或取消。",
418
422
  ].join("\n")),
419
423
  {
420
424
  tag: "column_set",
@@ -428,7 +432,7 @@ export class CardRenderer {
428
432
  vertical_align: "center",
429
433
  elements: [{
430
434
  tag: "button",
431
- text: { tag: "plain_text", content: "Release Now" },
435
+ text: { tag: "plain_text", content: blockingTaskCount > 0 ? "Release Now" : "Release" },
432
436
  type: "danger",
433
437
  size: "small",
434
438
  behaviors: [{
@@ -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
 
@@ -6974,7 +6970,6 @@ export class ProxySessionController {
6974
6970
  agentName: string;
6975
6971
  status: "waiting" | "released" | "failed";
6976
6972
  blockingTaskCount: number;
6977
- releaseInSeconds?: number;
6978
6973
  error?: string;
6979
6974
  }> {
6980
6975
  const record = requestedRecord ?? this.requireCurrentSession(contextKey);
@@ -6986,20 +6981,17 @@ export class ProxySessionController {
6986
6981
  let schedule = this.appServerReleaseSchedules.get(record.agentName);
6987
6982
  const created = !schedule;
6988
6983
  if (!schedule) {
6989
- const blockingTaskCount = this.appServerReleaseBlockers(record.agentName, runtime).length;
6984
+ const blockers = this.appServerReleaseBlockers(record.agentName, runtime);
6985
+ const blockingTaskCount = blockers.length;
6990
6986
  schedule = {
6991
6987
  id: this.nextAppServerReleaseScheduleId++,
6992
6988
  agentName: record.agentName,
6993
6989
  runtime,
6994
6990
  status: "waiting",
6995
6991
  blockingTaskCount,
6992
+ blockingTaskTitles: this.appServerReleaseTaskTitles(blockers),
6996
6993
  targets: new Map(),
6997
6994
  forced: false,
6998
- ...(blockingTaskCount === 0
6999
- ? {
7000
- releaseCountdownSeconds: Math.ceil(APP_SERVER_RELEASE_IDLE_DELAY_MS / 1_000),
7001
- }
7002
- : {}),
7003
6995
  };
7004
6996
  this.appServerReleaseSchedules.set(record.agentName, schedule);
7005
6997
  }
@@ -7012,14 +7004,6 @@ export class ProxySessionController {
7012
7004
  if (messageId) {
7013
7005
  schedule.targets.set(`${contextKey}\u0000${messageId}`, { contextKey, messageId });
7014
7006
  }
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
7007
  } catch (error) {
7024
7008
  if (created && schedule.targets.size === 0) {
7025
7009
  this.clearAppServerReleaseSchedule(schedule);
@@ -7032,9 +7016,6 @@ export class ProxySessionController {
7032
7016
  agentName: schedule.agentName,
7033
7017
  status: schedule.status === "failed" ? "failed" : schedule.status === "released" ? "released" : "waiting",
7034
7018
  blockingTaskCount: schedule.blockingTaskCount,
7035
- ...(schedule.releaseCountdownSeconds !== undefined
7036
- ? { releaseInSeconds: schedule.releaseCountdownSeconds }
7037
- : {}),
7038
7019
  ...(schedule.error ? { error: schedule.error } : {}),
7039
7020
  };
7040
7021
  }
@@ -7046,7 +7027,7 @@ export class ProxySessionController {
7046
7027
  if (!Number.isSafeInteger(scheduleId) || !schedule || schedule.id !== scheduleId || schedule.status !== "waiting") {
7047
7028
  throw new Error("释放卡片已失效,请重新发送 /release。");
7048
7029
  }
7049
- await this.performAppServerRelease(schedule, true);
7030
+ await this.performAppServerRelease(schedule);
7050
7031
  }
7051
7032
 
7052
7033
  private async cancelAppServerRelease(action: CardAction): Promise<void> {
@@ -7081,6 +7062,10 @@ export class ProxySessionController {
7081
7062
  });
7082
7063
  }
7083
7064
 
7065
+ private appServerReleaseTaskTitles(blockers: SessionRecord[]): string[] {
7066
+ return blockers.map((session) => session.title?.trim() || "未命名任务");
7067
+ }
7068
+
7084
7069
  private armAppServerReleaseCheck(schedule: AppServerReleaseSchedule): void {
7085
7070
  if (schedule.status !== "waiting" || schedule.timer || schedule.inFlight) return;
7086
7071
  schedule.timer = setTimeout(() => {
@@ -7095,36 +7080,22 @@ export class ProxySessionController {
7095
7080
 
7096
7081
  private async refreshAppServerReleaseSchedule(schedule: AppServerReleaseSchedule): Promise<void> {
7097
7082
  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
- }
7083
+ const blockers = this.appServerReleaseBlockers(schedule.agentName, schedule.runtime);
7084
+ const blockingTaskCount = blockers.length;
7085
+ const blockingTaskTitles = this.appServerReleaseTaskTitles(blockers);
7086
+ const titlesChanged = schedule.blockingTaskTitles.length !== blockingTaskTitles.length
7087
+ || schedule.blockingTaskTitles.some((title, index) => title !== blockingTaskTitles[index]);
7088
+ if (schedule.blockingTaskCount !== blockingTaskCount || titlesChanged) {
7089
+ schedule.blockingTaskCount = blockingTaskCount;
7090
+ schedule.blockingTaskTitles = blockingTaskTitles;
7091
+ await this.publishAppServerReleaseSchedule(schedule);
7121
7092
  }
7122
- await this.performAppServerRelease(schedule, false);
7093
+ this.armAppServerReleaseCheck(schedule);
7123
7094
  }
7124
7095
 
7125
- private async performAppServerRelease(schedule: AppServerReleaseSchedule, force: boolean): Promise<void> {
7096
+ private async performAppServerRelease(schedule: AppServerReleaseSchedule): Promise<void> {
7126
7097
  if (schedule.inFlight) return schedule.inFlight;
7127
- const operation = this.performAppServerReleaseNow(schedule, force);
7098
+ const operation = this.performAppServerReleaseNow(schedule);
7128
7099
  schedule.inFlight = operation;
7129
7100
  try {
7130
7101
  await operation;
@@ -7134,31 +7105,17 @@ export class ProxySessionController {
7134
7105
  }
7135
7106
  }
7136
7107
 
7137
- private async performAppServerReleaseNow(schedule: AppServerReleaseSchedule, force: boolean): Promise<void> {
7108
+ private async performAppServerReleaseNow(schedule: AppServerReleaseSchedule): Promise<void> {
7138
7109
  if (schedule.timer) {
7139
7110
  clearTimeout(schedule.timer);
7140
7111
  schedule.timer = undefined;
7141
7112
  }
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
- }
7113
+ const interruptedWork = this.appServerReleaseBlockers(schedule.agentName, schedule.runtime).length > 0;
7114
+ await this.cancelQueuedPromptsForAppServerRelease(schedule.agentName);
7157
7115
 
7158
7116
  schedule.status = "releasing";
7159
7117
  schedule.blockingTaskCount = 0;
7160
- schedule.releaseAt = undefined;
7161
- schedule.releaseCountdownSeconds = undefined;
7118
+ schedule.blockingTaskTitles = [];
7162
7119
  schedule.releaseStarted = false;
7163
7120
  schedule.forced = interruptedWork;
7164
7121
  await this.publishAppServerReleaseSchedule(schedule);
@@ -7168,18 +7125,18 @@ export class ProxySessionController {
7168
7125
  ) return;
7169
7126
  schedule.releaseStarted = true;
7170
7127
  try {
7171
- const result = await schedule.runtime.release?.({ force });
7128
+ const result = await schedule.runtime.release?.({ force: true });
7172
7129
  if (!result) throw new Error("当前 Agent 不支持释放 App Server。");
7173
7130
  if (result.status === "busy") {
7131
+ const blockers = this.appServerReleaseBlockers(schedule.agentName, schedule.runtime);
7174
7132
  schedule.status = "waiting";
7175
7133
  schedule.blockingTaskCount = Math.max(
7176
7134
  result.activeSessionIds?.length ?? 0,
7177
- this.appServerReleaseBlockers(schedule.agentName, schedule.runtime).length,
7135
+ blockers.length,
7178
7136
  1,
7179
7137
  );
7138
+ schedule.blockingTaskTitles = this.appServerReleaseTaskTitles(blockers);
7180
7139
  schedule.forced = false;
7181
- schedule.releaseAt = undefined;
7182
- schedule.releaseCountdownSeconds = undefined;
7183
7140
  schedule.releaseStarted = false;
7184
7141
  await this.publishAppServerReleaseSchedule(schedule);
7185
7142
  this.armAppServerReleaseCheck(schedule);
@@ -7239,9 +7196,7 @@ export class ProxySessionController {
7239
7196
  scheduleId: schedule.id,
7240
7197
  agentName: schedule.agentName,
7241
7198
  ...(schedule.blockingTaskCount > 0 ? { blockingTaskCount: schedule.blockingTaskCount } : {}),
7242
- ...(schedule.releaseCountdownSeconds !== undefined
7243
- ? { releaseInSeconds: schedule.releaseCountdownSeconds }
7244
- : {}),
7199
+ ...(schedule.blockingTaskTitles.length > 0 ? { blockingTaskTitles: schedule.blockingTaskTitles } : {}),
7245
7200
  ...(schedule.forced ? { forced: true } : {}),
7246
7201
  ...(schedule.error ? { error: schedule.error } : {}),
7247
7202
  };