@keyou007/agent-bot 0.1.15-alpha.0 → 0.1.15-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.
Files changed (56) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +23 -20
  3. package/README.zh.md +23 -20
  4. package/config.example.yaml +1 -0
  5. package/dist/cli/FeishuAppConfiguration.js +42 -3
  6. package/dist/cli/FeishuAppConfiguration.js.map +1 -1
  7. package/dist/cli.js +4 -1
  8. package/dist/cli.js.map +1 -1
  9. package/dist/feishu/CardRenderer.js +51 -8
  10. package/dist/feishu/CardRenderer.js.map +1 -1
  11. package/dist/feishu/FeishuConnector.js +17 -5
  12. package/dist/feishu/FeishuConnector.js.map +1 -1
  13. package/dist/feishu/FeishuMarkdown.js +58 -6
  14. package/dist/feishu/FeishuMarkdown.js.map +1 -1
  15. package/dist/feishu/FeishuMessageClient.js +33 -4
  16. package/dist/feishu/FeishuMessageClient.js.map +1 -1
  17. package/dist/feishu/FeishuTurnPresenter.js +7 -6
  18. package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
  19. package/dist/feishu/FinalResponseBranding.js +15 -0
  20. package/dist/feishu/FinalResponseBranding.js.map +1 -0
  21. package/dist/logging/DailyLogStream.js +73 -0
  22. package/dist/logging/DailyLogStream.js.map +1 -0
  23. package/dist/logging/logger.js +2 -1
  24. package/dist/logging/logger.js.map +1 -1
  25. package/dist/proxy/ProxySessionController.js +192 -36
  26. package/dist/proxy/ProxySessionController.js.map +1 -1
  27. package/dist/state/StateStore.js +126 -0
  28. package/dist/state/StateStore.js.map +1 -1
  29. package/dist/state/migrations.js +11 -0
  30. package/dist/state/migrations.js.map +1 -1
  31. package/dist/supervision/SupervisorDiagnostics.js +37 -21
  32. package/dist/supervision/SupervisorDiagnostics.js.map +1 -1
  33. package/dist/supervisor.js +11 -4
  34. package/dist/supervisor.js.map +1 -1
  35. package/dist/utils/executeShellCommand.js +83 -16
  36. package/dist/utils/executeShellCommand.js.map +1 -1
  37. package/docs/technical-reference.md +9 -8
  38. package/docs/technical-reference.zh.md +9 -8
  39. package/npm-shrinkwrap.json +2 -2
  40. package/package.json +1 -1
  41. package/src/cli/FeishuAppConfiguration.ts +76 -3
  42. package/src/cli.ts +6 -1
  43. package/src/feishu/CardRenderer.ts +75 -9
  44. package/src/feishu/FeishuConnector.ts +28 -6
  45. package/src/feishu/FeishuMarkdown.ts +64 -5
  46. package/src/feishu/FeishuMessageClient.ts +41 -12
  47. package/src/feishu/FeishuTurnPresenter.ts +9 -6
  48. package/src/feishu/FinalResponseBranding.ts +18 -0
  49. package/src/logging/DailyLogStream.ts +88 -0
  50. package/src/logging/logger.ts +2 -1
  51. package/src/proxy/ProxySessionController.ts +208 -35
  52. package/src/state/StateStore.ts +180 -0
  53. package/src/state/migrations.ts +11 -0
  54. package/src/supervision/SupervisorDiagnostics.ts +41 -25
  55. package/src/supervisor.ts +10 -4
  56. package/src/utils/executeShellCommand.ts +103 -15
@@ -6,12 +6,13 @@ import { resolveUserPath } from "../config/paths.js";
6
6
  import { CommandRouter } from "../commands/CommandRouter.js";
7
7
  import { baseChatContextKey, isThreadContextKey } from "../feishu/contextKey.js";
8
8
  import { CardRenderer, } from "../feishu/CardRenderer.js";
9
+ import { CardUpdateScheduler } from "../feishu/CardUpdateScheduler.js";
9
10
  import { generateGroupAvatarPng, resolveGroupAvatarProjectName } from "../feishu/GroupAvatarGenerator.js";
10
11
  import { normalizeFeishuPostText } from "../feishu/InboundText.js";
11
12
  import { allowsFeishuUser } from "../feishu/ownerAccess.js";
12
13
  import { buildTurnGraphRows } from "../presentation/turnGraph.js";
13
14
  import { createId } from "../utils/id.js";
14
- import { asInlineCode, codeBlock, truncateMiddle, truncateText } from "../utils/markdown.js";
15
+ import { truncateMiddle, truncateText } from "../utils/markdown.js";
15
16
  import { normalizeTaskTitle } from "../utils/taskTitle.js";
16
17
  import { executeShellCommand, } from "../utils/executeShellCommand.js";
17
18
  const MESSAGE_RECEIVED_REACTION = "OnIt";
@@ -24,6 +25,7 @@ const RESET_HISTORY_PAGE_SIZE = 10;
24
25
  const MAX_LLM_TURN_RETRIES = 3;
25
26
  const RECOVERY_ACTIVITY_WINDOW_MS = 5 * 60 * 1_000;
26
27
  const RECOVERY_HEARTBEAT_INTERVAL_MS = 60 * 1_000;
28
+ const SHELL_COMMAND_ELAPSED_UPDATE_INTERVAL_MS = 3_000;
27
29
  const FORWARD_ATTACHMENT_WINDOW_MS = 800;
28
30
  const DEFAULT_MERGED_FORWARD_INSTRUCTION = "请参考以下内容回复用户";
29
31
  const DEFAULT_REFERENCED_MESSAGE_INSTRUCTION = "请参考引用消息回复用户";
@@ -417,7 +419,7 @@ export class ProxySessionController {
417
419
  if (isQueueIndependentCommand(command)) {
418
420
  await this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
419
421
  try {
420
- if (command.type !== "forkgroup" && command.type !== "dismiss")
422
+ if (command.type === "prompt")
421
423
  await this.ensureThreadFork(message);
422
424
  await this.execute(message.contextKey, command, message.messageId, replyTarget, localImagePaths, message.userId, message);
423
425
  if (!isPromptCommand(command))
@@ -433,7 +435,7 @@ export class ProxySessionController {
433
435
  const previous = this.messageQueues.get(message.contextKey) ?? Promise.resolve();
434
436
  const next = previous.catch(() => undefined).then(() => this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
435
437
  try {
436
- if (command.type !== "forkgroup")
438
+ if (command.type === "prompt")
437
439
  await this.ensureThreadFork(message);
438
440
  await this.execute(message.contextKey, command, message.messageId, replyTarget, localImagePaths, message.userId, message);
439
441
  if (!isPromptCommand(command))
@@ -1095,8 +1097,7 @@ export class ProxySessionController {
1095
1097
  if (!session.remoteSessionId || !this.isCodexSession(session)) {
1096
1098
  throw new Error("The task is not an App Server task that can be reset.");
1097
1099
  }
1098
- const contextKey = this.controlSessionContextKey(session);
1099
- const rows = this.store.listCompletedTurnGraph(session.localSessionId, contextKey);
1100
+ const rows = this.store.listTaskTurnGraph(session.localSessionId);
1100
1101
  const graph = buildTurnGraphRows(rows.map((turn) => ({ turnId: turn.turnId, parentTurnId: turn.parentTurnId })));
1101
1102
  return {
1102
1103
  session,
@@ -1219,6 +1220,9 @@ export class ProxySessionController {
1219
1220
  }
1220
1221
  async execute(contextKey, command, messageId, replyTarget, localImagePaths, userId, incomingMessage) {
1221
1222
  const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
1223
+ if (!this.currentSession(contextKey) && isThreadContextKey(contextKey) && commandRequiresCurrentSession(command)) {
1224
+ throw new Error(unboundThreadTaskMessage());
1225
+ }
1222
1226
  switch (command.type) {
1223
1227
  case "shell":
1224
1228
  await this.runShellCommand(contextKey, command.command);
@@ -2501,8 +2505,13 @@ export class ProxySessionController {
2501
2505
  if (existing)
2502
2506
  return { record, runtime, session: existing };
2503
2507
  const pending = this.sessionLoads.get(record.localSessionId);
2504
- if (pending)
2505
- return pending;
2508
+ if (pending) {
2509
+ const loaded = await pending;
2510
+ return {
2511
+ ...loaded,
2512
+ record: { ...loaded.record, contextKey: record.contextKey },
2513
+ };
2514
+ }
2506
2515
  this.outbound.registerSession(record.localSessionId, record.contextKey, record.title, record.cwd, this.agentLabel(record.agentName));
2507
2516
  const loading = (async () => {
2508
2517
  if (record.lastTurnId) {
@@ -2566,7 +2575,11 @@ export class ProxySessionController {
2566
2575
  }
2567
2576
  this.persistRuntimeSession(record, session, session.activeTurnId ? "running" : "ready");
2568
2577
  const saved = this.store.getSession(record.localSessionId) ?? record;
2569
- return { record: saved, runtime, session };
2578
+ return {
2579
+ record: { ...saved, contextKey: record.contextKey },
2580
+ runtime,
2581
+ session,
2582
+ };
2570
2583
  })();
2571
2584
  this.sessionLoads.set(record.localSessionId, loading);
2572
2585
  try {
@@ -2653,6 +2666,7 @@ export class ProxySessionController {
2653
2666
  presentationError = error;
2654
2667
  }
2655
2668
  if (event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed") {
2669
+ await this.refreshGoalCards(event.sessionId);
2656
2670
  const terminalStatus = event.type === "turn_completed"
2657
2671
  ? "completed"
2658
2672
  : event.type === "turn_cancelled"
@@ -2994,8 +3008,10 @@ export class ProxySessionController {
2994
3008
  if (!current.remoteSessionId || !this.isCodexSession(current)) {
2995
3009
  throw new Error("当前任务不是可 Reset 的 App Server 任务。");
2996
3010
  }
2997
- const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(turnId));
2998
- if (!snapshot || snapshot.sessionId !== current.localSessionId || snapshot.status !== "completed") {
3011
+ const historyTurn = this.store.listTaskTurnGraph(current.localSessionId)
3012
+ .find((turn) => turn.turnId === turnId);
3013
+ const snapshot = turnViewSnapshot(historyTurn?.snapshot);
3014
+ if (!snapshot || snapshot.status !== "completed") {
2999
3015
  throw new Error("只能将当前任务 Reset 到已成功完成的轮次。");
3000
3016
  }
3001
3017
  if (current.lastTurnId === turnId && current.lastTurnStatus === "completed") {
@@ -3015,7 +3031,7 @@ export class ProxySessionController {
3015
3031
  throw new Error("当前任务仍在执行。请等待任务完成或先停止任务,再点击 Reset。");
3016
3032
  }
3017
3033
  const origin = this.store.getTurnRuntimeOrigin(turnId);
3018
- if (origin && (origin.localSessionId !== current.localSessionId || origin.agentName !== current.agentName)) {
3034
+ if (origin && origin.agentName !== current.agentName) {
3019
3035
  throw new Error("这张思考卡片与当前任务不匹配,已拒绝 Reset。");
3020
3036
  }
3021
3037
  const sourceRemoteSessionId = origin?.remoteSessionId ?? current.remoteSessionId;
@@ -3383,6 +3399,21 @@ export class ProxySessionController {
3383
3399
  await this.sendGoalCard(contextKey, goal, command.action === "edit" ? "Goal 已更新。" : "Goal 已启动,Agent 会持续执行直到完成、暂停或遇到阻塞。", record);
3384
3400
  }
3385
3401
  async sendGoalCard(contextKey, goal, notice, record = this.currentSession(contextKey)) {
3402
+ const card = this.renderGoalCard(goal, notice, record);
3403
+ if (!record) {
3404
+ await this.outbound.sendInteractiveCard(contextKey, card);
3405
+ return;
3406
+ }
3407
+ const existing = this.store.getGoalCardDelivery(record.localSessionId, contextKey);
3408
+ if (existing) {
3409
+ await this.outbound.updateInteractiveCard(contextKey, existing.messageId, card);
3410
+ return;
3411
+ }
3412
+ const messageId = await this.outbound.sendInteractiveCard(contextKey, card);
3413
+ if (messageId)
3414
+ this.store.saveGoalCardDelivery(record.localSessionId, contextKey, messageId);
3415
+ }
3416
+ renderGoalCard(goal, notice, record) {
3386
3417
  const sections = [];
3387
3418
  if (notice)
3388
3419
  sections.push({ lines: [cardText(notice)] });
@@ -3405,23 +3436,133 @@ export class ProxySessionController {
3405
3436
  "**/goal edit <新目标>** 修改  **/goal clear** 清除",
3406
3437
  ],
3407
3438
  });
3408
- await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderSectionsCard("Agent Goal", sections));
3439
+ const title = goal ? `Agent Goal · ${goalStatusLabel(goal.status)}` : "Agent Goal";
3440
+ return this.cardRenderer.renderSectionsCard(title, sections);
3441
+ }
3442
+ async refreshGoalCards(localSessionId) {
3443
+ const deliveries = this.store.listGoalCardDeliveries(localSessionId);
3444
+ if (deliveries.length === 0)
3445
+ return;
3446
+ const record = this.store.getSession(localSessionId);
3447
+ if (!record)
3448
+ return;
3449
+ const runtime = this.runtimes.forAgent(record.agentName);
3450
+ if (runtime.kind !== "codex" || !runtime.getGoal)
3451
+ return;
3452
+ try {
3453
+ const loaded = runtime.getSession(localSessionId) ?? (await this.loadSession(record)).session;
3454
+ const goal = await runtime.getGoal(loaded.localSessionId);
3455
+ const card = this.renderGoalCard(goal, undefined, record);
3456
+ await Promise.all(deliveries.map(async (delivery) => {
3457
+ try {
3458
+ await this.outbound.updateInteractiveCard(delivery.contextKey, delivery.messageId, card);
3459
+ }
3460
+ catch (error) {
3461
+ this.logger.warn({ error, localSessionId, contextKey: delivery.contextKey, messageId: delivery.messageId }, "Failed to refresh an Agent Goal card.");
3462
+ }
3463
+ }));
3464
+ }
3465
+ catch (error) {
3466
+ this.logger.warn({ error, localSessionId }, "Failed to read the latest Agent Goal status.");
3467
+ }
3409
3468
  }
3410
3469
  async runShellCommand(contextKey, command) {
3411
3470
  const cwd = this.currentSession(contextKey)?.cwd ?? this.config.defaults.cwd;
3412
- const result = await this.shellCommandExecutor(command, cwd);
3413
- const outputParts = [
3414
- result.stdout.trimEnd(),
3415
- result.stderr.trimEnd() ? `[stderr]\n${result.stderr.trimEnd()}` : "",
3416
- ].filter(Boolean);
3417
- const output = truncateMiddle(outputParts.join("\n"), 6_000);
3418
- const status = result.timedOut
3419
- ? "已超时(120s)"
3420
- : `退出码 ${result.exitCode ?? "未知"}`;
3421
- await this.outbound.sendMarkdown(contextKey, [
3422
- codeBlock(`$ ${command.trim()}\n${output || "(无输出)"}`, "text"),
3423
- `${asInlineCode(cwd)} · ${status}${result.outputTruncated ? " · 输出已截断" : ""}`,
3424
- ].join("\n"));
3471
+ const startedAt = Date.now();
3472
+ let latestOutput = {
3473
+ stdout: "",
3474
+ stderr: "",
3475
+ outputTruncated: false,
3476
+ };
3477
+ let runningState = {
3478
+ command,
3479
+ cwd,
3480
+ ...latestOutput,
3481
+ status: "running",
3482
+ elapsedMs: 0,
3483
+ };
3484
+ const cardMessageId = await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderShellCommandCard(runningState));
3485
+ const scheduler = cardMessageId
3486
+ ? new CardUpdateScheduler({
3487
+ render: (state) => this.cardRenderer.renderShellCommandCard(state),
3488
+ write: (card) => this.outbound.updateInteractiveCard(contextKey, cardMessageId, card),
3489
+ onError: (error) => this.logger.warn({ error, contextKey, messageId: cardMessageId }, "Failed to update a shell command card."),
3490
+ })
3491
+ : undefined;
3492
+ scheduler?.seed(runningState);
3493
+ const elapsedTimer = scheduler ? setInterval(() => {
3494
+ runningState = { ...runningState, elapsedMs: Date.now() - startedAt };
3495
+ scheduler.update(runningState);
3496
+ }, SHELL_COMMAND_ELAPSED_UPDATE_INTERVAL_MS) : undefined;
3497
+ elapsedTimer?.unref?.();
3498
+ const updateRunningOutput = (snapshot) => {
3499
+ latestOutput = snapshot;
3500
+ runningState = {
3501
+ ...runningState,
3502
+ ...snapshot,
3503
+ elapsedMs: Date.now() - startedAt,
3504
+ };
3505
+ scheduler?.update(runningState);
3506
+ };
3507
+ let result;
3508
+ try {
3509
+ result = await this.shellCommandExecutor(command, cwd, { onOutput: updateRunningOutput });
3510
+ }
3511
+ catch (error) {
3512
+ if (elapsedTimer)
3513
+ clearInterval(elapsedTimer);
3514
+ const failedCard = this.cardRenderer.renderShellCommandCard({
3515
+ command,
3516
+ cwd,
3517
+ ...latestOutput,
3518
+ stderr: [latestOutput.stderr.trimEnd(), runtimeErrorMessage(error)].filter(Boolean).join("\n"),
3519
+ status: "failed",
3520
+ elapsedMs: Date.now() - startedAt,
3521
+ });
3522
+ if (scheduler) {
3523
+ try {
3524
+ await scheduler.flush({
3525
+ ...runningState,
3526
+ stderr: [latestOutput.stderr.trimEnd(), runtimeErrorMessage(error)].filter(Boolean).join("\n"),
3527
+ status: "failed",
3528
+ elapsedMs: Date.now() - startedAt,
3529
+ });
3530
+ }
3531
+ catch (cardError) {
3532
+ this.logger.warn({ error: cardError, contextKey, messageId: cardMessageId }, "Failed to finalize a shell command card.");
3533
+ }
3534
+ finally {
3535
+ scheduler.dispose();
3536
+ }
3537
+ }
3538
+ else {
3539
+ await this.outbound.sendInteractiveCard(contextKey, failedCard);
3540
+ }
3541
+ throw error;
3542
+ }
3543
+ if (elapsedTimer)
3544
+ clearInterval(elapsedTimer);
3545
+ const finalState = {
3546
+ command,
3547
+ cwd,
3548
+ stdout: result.stdout,
3549
+ stderr: result.stderr,
3550
+ status: result.timedOut ? "timed_out" : result.exitCode === 0 ? "completed" : "failed",
3551
+ exitCode: result.exitCode,
3552
+ elapsedMs: Date.now() - startedAt,
3553
+ outputTruncated: result.outputTruncated,
3554
+ };
3555
+ if (scheduler) {
3556
+ try {
3557
+ await scheduler.flush(finalState);
3558
+ }
3559
+ finally {
3560
+ scheduler.dispose();
3561
+ }
3562
+ }
3563
+ else {
3564
+ await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderShellCommandCard(finalState));
3565
+ }
3425
3566
  }
3426
3567
  async setTitle(contextKey, title) {
3427
3568
  const normalizedTitle = normalizeTaskTitle(title);
@@ -3532,7 +3673,7 @@ export class ProxySessionController {
3532
3673
  if (!current.remoteSessionId || !this.isCodexSession(current)) {
3533
3674
  throw new Error("当前任务不是可 Reset 的 App Server 任务。");
3534
3675
  }
3535
- const allTurns = this.store.listCompletedTurnGraph(current.localSessionId, contextKey);
3676
+ const allTurns = this.store.listTaskTurnGraph(current.localSessionId);
3536
3677
  const graphRows = buildTurnGraphRows(allTurns.map((turn) => ({
3537
3678
  turnId: turn.turnId,
3538
3679
  parentTurnId: turn.parentTurnId,
@@ -3668,7 +3809,6 @@ export class ProxySessionController {
3668
3809
  contextKey,
3669
3810
  },
3670
3811
  },
3671
- actions: directoryCreationActions(entry.path, contextKey),
3672
3812
  }
3673
3813
  : {
3674
3814
  name: entry.name,
@@ -3693,7 +3833,7 @@ export class ProxySessionController {
3693
3833
  footerLines: [
3694
3834
  `第 ${page + 1}/${totalPages} 页 · ${directoryCount} 个目录 · ${entries.length - directoryCount} 个文件`,
3695
3835
  "",
3696
- "> 点击目录名称进入,点击文件名称发送到当前会话;**New** 在该目录创建任务,**NewGroup** 在该目录创建新群和任务。",
3836
+ "> 点击目录名称进入,点击文件名称发送到当前会话;卡片顶部的 **New** 和 **NewGroup** 使用当前目录。",
3697
3837
  ],
3698
3838
  });
3699
3839
  if (options.updateMessageId) {
@@ -3722,7 +3862,6 @@ export class ProxySessionController {
3722
3862
  contextKey,
3723
3863
  },
3724
3864
  },
3725
- actions: directoryCreationActions(drive.root, contextKey),
3726
3865
  }));
3727
3866
  const card = this.cardRenderer.renderDirectoryBrowserCard({
3728
3867
  directory: "此电脑",
@@ -3732,7 +3871,7 @@ export class ProxySessionController {
3732
3871
  footerLines: [
3733
3872
  `第 ${page + 1}/${totalPages} 页 · ${drives.length} 个磁盘`,
3734
3873
  "",
3735
- "> 点击磁盘名称进入;**New** 在磁盘根目录创建任务,**NewGroup** 在磁盘根目录创建新群和任务。",
3874
+ "> 点击磁盘名称进入。",
3736
3875
  ],
3737
3876
  });
3738
3877
  if (options.updateMessageId) {
@@ -4350,7 +4489,9 @@ export class ProxySessionController {
4350
4489
  let activeTurnId;
4351
4490
  let queued = 0;
4352
4491
  if (!current) {
4353
- taskLines.push("无。直接发送消息即可创建一个未指定项目的 Agent 任务。");
4492
+ taskLines.push(isThreadContextKey(contextKey)
4493
+ ? unboundThreadTaskMessage()
4494
+ : "无。直接发送消息即可创建一个未指定项目的 Agent 任务。");
4354
4495
  }
4355
4496
  else {
4356
4497
  const agent = this.ensureAgent(current.agentName);
@@ -4581,8 +4722,11 @@ export class ProxySessionController {
4581
4722
  }
4582
4723
  requireCurrentSession(contextKey) {
4583
4724
  const record = this.currentSession(contextKey);
4584
- if (!record)
4585
- throw new Error("当前没有任务,直接发送一条消息即可自动创建。");
4725
+ if (!record) {
4726
+ throw new Error(isThreadContextKey(contextKey)
4727
+ ? unboundThreadTaskMessage()
4728
+ : "当前没有任务,直接发送一条消息即可自动创建。");
4729
+ }
4586
4730
  return record;
4587
4731
  }
4588
4732
  requireSession(contextKey, sessionId) {
@@ -5050,6 +5194,19 @@ function isQueueIndependentCommand(command) {
5050
5194
  return true;
5051
5195
  return false;
5052
5196
  }
5197
+ function commandRequiresCurrentSession(command) {
5198
+ if (["dismiss", "stop", "title", "turns", "model", "provider", "thinking", "permissions", "nosteer"].includes(command.type)) {
5199
+ return true;
5200
+ }
5201
+ if (command.type === "archive" || command.type === "fork")
5202
+ return command.sessionId === undefined;
5203
+ if (command.type === "goal")
5204
+ return command.action !== "set";
5205
+ return false;
5206
+ }
5207
+ function unboundThreadTaskMessage() {
5208
+ return "当前话题尚未绑定任务。发送普通消息后,Agent Bot 会从可识别的原始轮次创建分支;没有原始轮次时会创建全新任务。也可以使用 /new 创建全新任务,或使用 /sessions 绑定现有任务。";
5209
+ }
5053
5210
  function parseSessionPage(value) {
5054
5211
  const parsed = typeof value === "string" || typeof value === "number" ? Number(value) : Number.NaN;
5055
5212
  return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : 0;
@@ -5348,14 +5505,13 @@ const FEISHU_GROUP_NAME_MAX_LENGTH = 60;
5348
5505
  const GROUP_PROJECT_DIRECTORY_MAX_LENGTH = 15;
5349
5506
  function formatGroupProjectDirectory(projectCwd) {
5350
5507
  const value = abbreviateHomeDirectory(projectCwd);
5351
- if (Array.from(value).length <= GROUP_PROJECT_DIRECTORY_MAX_LENGTH)
5352
- return `[${value}]`;
5353
5508
  const levels = value
5354
5509
  .replace(/[\\/]+$/, "")
5355
5510
  .split(/[\\/]+/)
5356
5511
  .filter(Boolean)
5357
5512
  .slice(-2);
5358
- return `[${fitTrailingPathLevels(levels, GROUP_PROJECT_DIRECTORY_MAX_LENGTH, path.sep)}]`;
5513
+ const separator = value.includes("\\") ? path.win32.sep : path.posix.sep;
5514
+ return `[${fitTrailingPathLevels(levels, GROUP_PROJECT_DIRECTORY_MAX_LENGTH, separator)}]`;
5359
5515
  }
5360
5516
  function abbreviateHomeDirectory(value) {
5361
5517
  const homeDirectory = os.homedir();