@keyou007/agent-bot 0.1.18-alpha.3 → 0.1.19-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.
Files changed (37) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/codex/CodexRuntime.js +8 -3
  3. package/dist/codex/CodexRuntime.js.map +1 -1
  4. package/dist/codex/ThreadWriterProcess.js +344 -0
  5. package/dist/codex/ThreadWriterProcess.js.map +1 -0
  6. package/dist/console/ConsoleTurnPresenter.js +1 -0
  7. package/dist/console/ConsoleTurnPresenter.js.map +1 -1
  8. package/dist/feishu/CardRenderer.js +83 -0
  9. package/dist/feishu/CardRenderer.js.map +1 -1
  10. package/dist/feishu/FeishuTurnPresenter.js +13 -3
  11. package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
  12. package/dist/index.js +2 -2
  13. package/dist/index.js.map +1 -1
  14. package/dist/logging/errorLogValue.js +18 -0
  15. package/dist/logging/errorLogValue.js.map +1 -0
  16. package/dist/logging/logger.js +4 -0
  17. package/dist/logging/logger.js.map +1 -1
  18. package/dist/presentation/OutboundRouter.js +2 -2
  19. package/dist/presentation/OutboundRouter.js.map +1 -1
  20. package/dist/proxy/ProxySessionController.js +329 -15
  21. package/dist/proxy/ProxySessionController.js.map +1 -1
  22. package/dist/supervision/SafeRestartScheduler.js +1 -1
  23. package/dist/supervision/SafeRestartScheduler.js.map +1 -1
  24. package/npm-shrinkwrap.json +2 -2
  25. package/package.json +1 -1
  26. package/src/codex/CodexRuntime.ts +8 -2
  27. package/src/codex/ThreadWriterProcess.ts +385 -0
  28. package/src/console/ConsoleTurnPresenter.ts +2 -1
  29. package/src/feishu/CardRenderer.ts +109 -0
  30. package/src/feishu/FeishuTurnPresenter.ts +16 -3
  31. package/src/index.ts +2 -2
  32. package/src/logging/errorLogValue.ts +26 -0
  33. package/src/logging/logger.ts +4 -0
  34. package/src/presentation/OutboundRouter.ts +15 -3
  35. package/src/proxy/ProxySessionController.ts +459 -24
  36. package/src/runtime/types.ts +1 -0
  37. package/src/supervision/SafeRestartScheduler.ts +1 -1
@@ -3,6 +3,7 @@ import { promises as fs } from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { createProjectlessWorkspace, detectProjectlessWorkspace } from "../codex/ProjectlessWorkspace.js";
6
+ import { SystemThreadWriterProcessController, } from "../codex/ThreadWriterProcess.js";
6
7
  import { resolveUserPath } from "../config/paths.js";
7
8
  import { DEFAULT_GROUP_NAME_FORMAT, } from "../config/schema.js";
8
9
  import { CommandRouter } from "../commands/CommandRouter.js";
@@ -14,12 +15,21 @@ import { formatGroupNameDate, formatNewGroupName, parseTaskNameFromGroupName, }
14
15
  import { normalizeFeishuPostText } from "../feishu/InboundText.js";
15
16
  import { allowsFeishuUser } from "../feishu/ownerAccess.js";
16
17
  import { classifyFileContent } from "../local-files/LocalFileViewerServer.js";
18
+ import { errorLogValue } from "../logging/errorLogValue.js";
17
19
  import { buildTurnGraphRows } from "../presentation/turnGraph.js";
18
20
  import { isActiveShellCommandJob, ShellCommandJobManager, } from "../shell/ShellCommandJobManager.js";
19
21
  import { createId } from "../utils/id.js";
20
22
  import { truncateMiddle, truncateText } from "../utils/markdown.js";
21
23
  import { normalizeTaskTitle } from "../utils/taskTitle.js";
22
24
  import { executeShellCommand, } from "../utils/executeShellCommand.js";
25
+ class PresentedRequestError extends Error {
26
+ originalError;
27
+ constructor(originalError) {
28
+ super(originalError instanceof Error ? originalError.message : String(originalError), { cause: originalError });
29
+ this.originalError = originalError;
30
+ this.name = "PresentedRequestError";
31
+ }
32
+ }
23
33
  const MESSAGE_RECEIVED_REACTION = "OnIt";
24
34
  const MESSAGE_COMPLETED_REACTION = "DONE";
25
35
  const MESSAGE_FAILED_REACTION = "ERROR";
@@ -178,6 +188,7 @@ export class ProxySessionController {
178
188
  shellCommandExecutor;
179
189
  windowsDriveLister;
180
190
  shellCommandJobs;
191
+ threadWriterProcesses;
181
192
  router = new CommandRouter();
182
193
  cardRenderer = new CardRenderer();
183
194
  messageQueues = new Map();
@@ -201,7 +212,7 @@ export class ProxySessionController {
201
212
  recoveryActivityHeartbeat;
202
213
  startupRecovery;
203
214
  queuedPromptsRestored = false;
204
- constructor(config, store, runtimes, outbound, logger, lifecycle, shellCommandExecutor = executeShellCommand, windowsDriveLister = listWindowsDriveRoots, shellCommandJobs = new ShellCommandJobManager(path.join(path.dirname(config.storage.sqlitePath), "command-jobs"))) {
215
+ constructor(config, store, runtimes, outbound, logger, lifecycle, shellCommandExecutor = executeShellCommand, windowsDriveLister = listWindowsDriveRoots, shellCommandJobs = new ShellCommandJobManager(path.join(path.dirname(config.storage.sqlitePath), "command-jobs")), threadWriterProcesses = new SystemThreadWriterProcessController()) {
205
216
  this.config = config;
206
217
  this.store = store;
207
218
  this.runtimes = runtimes;
@@ -211,6 +222,7 @@ export class ProxySessionController {
211
222
  this.shellCommandExecutor = shellCommandExecutor;
212
223
  this.windowsDriveLister = windowsDriveLister;
213
224
  this.shellCommandJobs = shellCommandJobs;
225
+ this.threadWriterProcesses = threadWriterProcesses;
214
226
  for (const [, runtime] of this.runtimes.entries()) {
215
227
  this.unsubscribe.push(runtime.onEvent((event) => {
216
228
  void this.handleRuntimeEvent(event).catch((error) => {
@@ -328,7 +340,11 @@ export class ProxySessionController {
328
340
  : undefined;
329
341
  await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
330
342
  const detail = result.error instanceof Error ? result.error.message : String(result.error);
331
- await this.outbound.withReplyTarget(message.contextKey, replyTarget, () => this.sendError(message.contextKey, new Error(`无法读取合并转发消息:${detail}`)));
343
+ await this.outbound.withReplyTarget(message.contextKey, replyTarget, () => this.sendError(message.contextKey, new Error(`无法读取合并转发消息:${detail}`), {
344
+ requestId: message.messageId,
345
+ messageId: message.messageId,
346
+ phase: "merged_forward_read",
347
+ }));
332
348
  return;
333
349
  }
334
350
  const instruction = message.displayText ?? DEFAULT_MERGED_FORWARD_INSTRUCTION;
@@ -360,7 +376,11 @@ export class ProxySessionController {
360
376
  : undefined;
361
377
  await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
362
378
  const detail = error instanceof Error ? error.message : String(error);
363
- await this.outbound.withReplyTarget(message.contextKey, replyTarget, () => this.sendError(message.contextKey, new Error(`无法读取引用消息:${detail}`)));
379
+ await this.outbound.withReplyTarget(message.contextKey, replyTarget, () => this.sendError(message.contextKey, new Error(`无法读取引用消息:${detail}`), {
380
+ requestId: message.messageId,
381
+ messageId: message.messageId,
382
+ phase: "referenced_message_read",
383
+ }));
364
384
  return;
365
385
  }
366
386
  }
@@ -394,7 +414,11 @@ export class ProxySessionController {
394
414
  }
395
415
  catch (error) {
396
416
  await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
397
- await this.outbound.withReplyTarget(message.contextKey, replyTarget, () => this.sendError(message.contextKey, error));
417
+ await this.outbound.withReplyTarget(message.contextKey, replyTarget, () => this.sendError(message.contextKey, error, {
418
+ requestId: message.messageId,
419
+ messageId: message.messageId,
420
+ phase: "image_download",
421
+ }));
398
422
  return;
399
423
  }
400
424
  }
@@ -413,7 +437,11 @@ export class ProxySessionController {
413
437
  }
414
438
  catch (error) {
415
439
  await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
416
- await this.outbound.withReplyTarget(message.contextKey, replyTarget, () => this.sendError(message.contextKey, error));
440
+ await this.outbound.withReplyTarget(message.contextKey, replyTarget, () => this.sendError(message.contextKey, error, {
441
+ requestId: message.messageId,
442
+ messageId: message.messageId,
443
+ phase: "file_download",
444
+ }));
417
445
  return;
418
446
  }
419
447
  }
@@ -426,9 +454,21 @@ export class ProxySessionController {
426
454
  }
427
455
  catch (error) {
428
456
  await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
429
- await this.outbound.withReplyTarget(message.contextKey, replyTarget, () => this.sendError(message.contextKey, error));
457
+ await this.outbound.withReplyTarget(message.contextKey, replyTarget, () => this.sendError(message.contextKey, error, {
458
+ requestId: message.messageId,
459
+ messageId: message.messageId,
460
+ phase: "command_parse",
461
+ }));
430
462
  return;
431
463
  }
464
+ if (command.type !== "prompt") {
465
+ this.logger.info({
466
+ requestId: message.messageId,
467
+ messageId: message.messageId,
468
+ contextKey: message.contextKey,
469
+ command: command.type,
470
+ }, "Processing Agent Bot command.");
471
+ }
432
472
  // Operational and read-only commands must remain available even if a prompt operation is slow.
433
473
  if (isQueueIndependentCommand(command)) {
434
474
  await this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
@@ -442,7 +482,12 @@ export class ProxySessionController {
442
482
  }
443
483
  catch (error) {
444
484
  await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
445
- await this.sendError(message.contextKey, error);
485
+ await this.sendError(message.contextKey, error, {
486
+ requestId: message.messageId,
487
+ messageId: message.messageId,
488
+ command: command.type,
489
+ phase: "command_execute",
490
+ });
446
491
  }
447
492
  });
448
493
  return;
@@ -459,7 +504,12 @@ export class ProxySessionController {
459
504
  }
460
505
  catch (error) {
461
506
  await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
462
- await this.sendError(message.contextKey, error);
507
+ await this.sendError(message.contextKey, error, {
508
+ requestId: message.messageId,
509
+ messageId: message.messageId,
510
+ command: command.type,
511
+ phase: "command_execute",
512
+ });
463
513
  }
464
514
  }));
465
515
  this.messageQueues.set(message.contextKey, next);
@@ -479,7 +529,11 @@ export class ProxySessionController {
479
529
  resolvedAction = this.resolveCardActionBinding(action);
480
530
  }
481
531
  catch (error) {
482
- await this.sendError(action.contextKey, error);
532
+ await this.sendError(action.contextKey, error, {
533
+ requestId: action.actionId,
534
+ actionId: action.actionId,
535
+ phase: "card_action_resolve",
536
+ });
483
537
  return;
484
538
  }
485
539
  const contextKey = this.cardActionContextKey(resolvedAction);
@@ -510,6 +564,18 @@ export class ProxySessionController {
510
564
  else if (kind === "shell_command_cancel") {
511
565
  await this.cancelShellCommandJob(contextKey, String(scopedAction.value.jobId ?? ""));
512
566
  }
567
+ else if (kind === "thread_writer_close") {
568
+ await this.closeThreadWriter(scopedAction, contextKey);
569
+ }
570
+ else if (kind === "thread_writer_fork") {
571
+ const sessionId = String(scopedAction.value.sessionId ?? "");
572
+ const threadId = String(scopedAction.value.threadId ?? "");
573
+ const record = this.requireSession(contextKey, sessionId);
574
+ if (!threadId || record.remoteSessionId !== threadId) {
575
+ throw new Error("任务占用卡片已经失效,请重新发送消息获取最新状态。");
576
+ }
577
+ await this.forkSessionReference(contextKey, sessionId);
578
+ }
513
579
  else if (kind === "turn_reset") {
514
580
  const sessionId = String(scopedAction.value.sessionId ?? "");
515
581
  const turnId = String(scopedAction.value.turnId ?? "");
@@ -734,7 +800,12 @@ export class ProxySessionController {
734
800
  }
735
801
  }
736
802
  catch (error) {
737
- await this.sendError(contextKey, error);
803
+ await this.sendError(contextKey, error, {
804
+ requestId: scopedAction.actionId,
805
+ actionId: scopedAction.actionId,
806
+ command: String(scopedAction.value.action ?? "unknown"),
807
+ phase: "card_action_execute",
808
+ });
738
809
  }
739
810
  });
740
811
  }
@@ -1809,6 +1880,9 @@ export class ProxySessionController {
1809
1880
  loaded = await this.loadSession(record);
1810
1881
  }
1811
1882
  catch (error) {
1883
+ if (await this.presentThreadWriterConflict(record, contextKey, error, true)) {
1884
+ throw new PresentedRequestError(error);
1885
+ }
1812
1886
  await this.outbound.failPendingTurn(record.localSessionId, error instanceof Error ? error.message : String(error));
1813
1887
  throw error;
1814
1888
  }
@@ -2900,6 +2974,8 @@ export class ProxySessionController {
2900
2974
  });
2901
2975
  }
2902
2976
  catch (error) {
2977
+ if (activeWriterThreadId(error))
2978
+ throw error;
2903
2979
  if (!(agent.kind === "app-server" && !record.lastTurnId && isMissingRolloutError(error)))
2904
2980
  throw error;
2905
2981
  this.logger.warn({ error, sessionId: record.localSessionId }, "App Server task has no rollout; creating a replacement task.");
@@ -5262,14 +5338,26 @@ export class ProxySessionController {
5262
5338
  description: command.description,
5263
5339
  })),
5264
5340
  }));
5265
- await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderHelpCard("Agent Bot 使用帮助", [
5341
+ const introLines = [
5266
5342
  "直接发送消息即可继续当前任务;执行中发送的新消息会追加到本次任务。",
5267
5343
  "点击命令按钮可执行默认形式;有必填参数的命令需要手动发送。",
5268
5344
  "> 命令前缀示例:/sess 等同于 /sessions。",
5269
5345
  "> 命令缩写示例:/fg 等同于 /forkgroup。",
5270
5346
  "前缀或缩写匹配多个命令时,需要输入更长的形式。",
5271
5347
  "**[参数]** 可选 **<参数>** 必填",
5272
- ], sections));
5348
+ ];
5349
+ try {
5350
+ await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderHelpCard("Agent Bot 使用帮助", introLines, sections));
5351
+ }
5352
+ catch (cardError) {
5353
+ this.logger.warn({ error: cardError, contextKey, command: "help", phase: "help_card_send" }, "Failed to send the help card; falling back to plain text.");
5354
+ try {
5355
+ await this.outbound.sendText(contextKey, renderPlainHelp(introLines, sections));
5356
+ }
5357
+ catch (textError) {
5358
+ throw new AggregateError([cardError, textError], "帮助卡片和纯文本帮助均发送失败。");
5359
+ }
5360
+ }
5273
5361
  }
5274
5362
  async setGroupMute(contextKey, enabled) {
5275
5363
  const chatContextKey = baseChatContextKey(contextKey);
@@ -5375,10 +5463,236 @@ export class ProxySessionController {
5375
5463
  throw new Error(`找不到任务:${sessionId}`);
5376
5464
  return record;
5377
5465
  }
5378
- async sendError(contextKey, error) {
5379
- this.logger.warn({ error, contextKey }, "Request failed.");
5380
- await this.outbound.sendText(contextKey, error instanceof Error ? error.message : String(error));
5466
+ async presentThreadWriterConflict(record, contextKey, error, replacePendingTurn) {
5467
+ if (contextKey.startsWith("console:"))
5468
+ return false;
5469
+ const threadId = activeWriterThreadId(error);
5470
+ if (!threadId || record.remoteSessionId !== threadId)
5471
+ return false;
5472
+ const runtime = this.runtimes.forAgent(record.agentName);
5473
+ const lockPath = runtime.getThreadWriterLockPath?.(threadId);
5474
+ let owners = [];
5475
+ if (lockPath) {
5476
+ try {
5477
+ owners = await this.threadWriterProcesses.inspect(lockPath);
5478
+ }
5479
+ catch (inspectionError) {
5480
+ this.logger.warn({ error: inspectionError, threadId, lockPath }, "Failed to inspect the process holding a thread writer lock.");
5481
+ }
5482
+ }
5483
+ const owner = owners.length === 1 ? owners[0] : undefined;
5484
+ const applicationIdle = owner && lockPath
5485
+ ? await this.inspectThreadWriterApplicationIdle(runtime, lockPath, threadId, owner)
5486
+ : undefined;
5487
+ const card = this.cardRenderer.renderThreadWriterConflict(this.threadWriterConflictView(record, contextKey, threadId, owners, "occupied", lockPath
5488
+ ? undefined
5489
+ : "无法识别占用进程。", applicationIdle));
5490
+ if (replacePendingTurn) {
5491
+ const replaced = await this.outbound.failPendingTurn(record.localSessionId, "任务被占用。", card);
5492
+ if (!replaced)
5493
+ await this.outbound.sendInteractiveCard(contextKey, card);
5494
+ }
5495
+ else {
5496
+ await this.outbound.sendInteractiveCard(contextKey, card);
5497
+ }
5498
+ return true;
5499
+ }
5500
+ threadWriterConflictView(record, contextKey, threadId, owners, status, notice, applicationIdle) {
5501
+ const owner = owners.length === 1 ? owners[0] : undefined;
5502
+ return {
5503
+ status,
5504
+ contextKey,
5505
+ sessionId: record.localSessionId,
5506
+ threadId,
5507
+ taskTitle: record.title,
5508
+ notice: notice ?? (owners.length > 1
5509
+ ? `检测到 ${owners.length} 个相关进程,无法安全地自动关闭。`
5510
+ : undefined),
5511
+ applicationIdle,
5512
+ ...(owner ? {
5513
+ owner: {
5514
+ displayName: owner.displayName,
5515
+ writerPid: owner.writerPid,
5516
+ writerProcessName: owner.writerProcessName,
5517
+ writerStartedAt: owner.writerStartedAt,
5518
+ applicationPid: owner.applicationPid,
5519
+ applicationProcessName: owner.applicationProcessName,
5520
+ applicationStartedAt: owner.applicationStartedAt,
5521
+ canClose: owner.canClose,
5522
+ },
5523
+ } : {}),
5524
+ };
5525
+ }
5526
+ async closeThreadWriter(action, contextKey) {
5527
+ const messageId = requiredCardMessageId(action.messageId);
5528
+ const sessionId = String(action.value.sessionId ?? "");
5529
+ const threadId = String(action.value.threadId ?? "");
5530
+ const expectedWriterPid = positiveActionPid(action.value.writerPid);
5531
+ const expectedApplicationPid = positiveActionPid(action.value.applicationPid);
5532
+ const expectedWriterStartedAt = String(action.value.writerStartedAt ?? "");
5533
+ const expectedApplicationStartedAt = String(action.value.applicationStartedAt ?? "");
5534
+ const force = action.value.force === true || action.value.force === "true";
5535
+ const record = this.requireSession(contextKey, sessionId);
5536
+ if (!threadId || record.remoteSessionId !== threadId) {
5537
+ throw new Error("任务占用卡片已经失效,请重新发送消息获取最新状态。");
5538
+ }
5539
+ const runtime = this.runtimes.forAgent(record.agentName);
5540
+ const lockPath = runtime.getThreadWriterLockPath?.(threadId);
5541
+ if (!lockPath)
5542
+ throw new Error("当前 Agent 不支持自动关闭占用程序。");
5543
+ let owners = await this.threadWriterProcesses.inspect(lockPath);
5544
+ const expectedOwner = owners.length === 1 && threadWriterMatches(owners[0], expectedWriterPid, expectedWriterStartedAt, expectedApplicationPid, expectedApplicationStartedAt) ? owners[0] : undefined;
5545
+ if (!expectedOwner) {
5546
+ const currentOwner = owners.length === 1 ? owners[0] : undefined;
5547
+ const applicationIdle = currentOwner
5548
+ ? await this.inspectThreadWriterApplicationIdle(runtime, lockPath, threadId, currentOwner)
5549
+ : undefined;
5550
+ await this.outbound.updateInteractiveCard(contextKey, messageId, this.cardRenderer.renderThreadWriterConflict(this.threadWriterConflictView(record, contextKey, threadId, owners, owners.length === 0 ? "released" : "occupied", owners.length === 0
5551
+ ? "请重新发送消息。"
5552
+ : "占用进程已变化,请重试。", applicationIdle)));
5553
+ return;
5554
+ }
5555
+ if (!expectedOwner.canClose)
5556
+ throw new Error("无法确认占用者是 Agent 程序,已拒绝关闭。");
5557
+ try {
5558
+ await this.threadWriterProcesses.close(expectedOwner, force);
5559
+ }
5560
+ catch (error) {
5561
+ owners = await this.threadWriterProcesses.inspect(lockPath);
5562
+ if (owners.some((owner) => threadWriterMatches(owner, expectedWriterPid, expectedWriterStartedAt, expectedApplicationPid, expectedApplicationStartedAt)))
5563
+ throw error;
5564
+ }
5565
+ owners = await this.waitForThreadWriterChange(lockPath, expectedOwner);
5566
+ const sameOwnerRemains = owners.some((owner) => threadWriterMatches(owner, expectedOwner.writerPid, expectedOwner.writerStartedAt ?? "", expectedOwner.applicationPid, expectedOwner.applicationStartedAt ?? ""));
5567
+ if (sameOwnerRemains && force) {
5568
+ throw new Error(`无法关闭 ${expectedOwner.displayName}(PID ${expectedOwner.applicationPid})。`);
5569
+ }
5570
+ const status = owners.length === 0
5571
+ ? "released"
5572
+ : sameOwnerRemains ? "force_required" : "occupied";
5573
+ const notice = owners.length === 0
5574
+ ? "请重新发送消息。"
5575
+ : sameOwnerRemains
5576
+ ? "普通关闭失败,可强制关闭。"
5577
+ : "占用进程已变化,请重试。";
5578
+ const currentOwner = owners.length === 1 ? owners[0] : undefined;
5579
+ const applicationIdle = currentOwner
5580
+ ? await this.inspectThreadWriterApplicationIdle(runtime, lockPath, threadId, currentOwner)
5581
+ : undefined;
5582
+ await this.outbound.updateInteractiveCard(contextKey, messageId, this.cardRenderer.renderThreadWriterConflict(this.threadWriterConflictView(record, contextKey, threadId, owners, status, notice, applicationIdle)));
5583
+ }
5584
+ async inspectThreadWriterApplicationIdle(runtime, lockPath, threadId, owner) {
5585
+ if (!owner.applicationStartedAt
5586
+ || !this.threadWriterProcesses.inspectApplicationThreadIds
5587
+ || !runtime.inspectRemoteSessionActivity)
5588
+ return undefined;
5589
+ try {
5590
+ const ownedThreadIds = await this.threadWriterProcesses.inspectApplicationThreadIds(lockPath, owner);
5591
+ if (!ownedThreadIds.includes(threadId))
5592
+ return undefined;
5593
+ const activities = await Promise.all(ownedThreadIds.map((ownedThreadId) => runtime.inspectRemoteSessionActivity(ownedThreadId)));
5594
+ return activities.every((activity) => !activity.active);
5595
+ }
5596
+ catch (error) {
5597
+ this.logger.warn({ error, threadId, applicationPid: owner.applicationPid }, "Failed to inspect thread-writer application activity.");
5598
+ return undefined;
5599
+ }
5600
+ }
5601
+ async waitForThreadWriterChange(lockPath, expectedOwner) {
5602
+ const deadline = Date.now() + 5_000;
5603
+ let owners = [];
5604
+ do {
5605
+ owners = await this.threadWriterProcesses.inspect(lockPath);
5606
+ if (!owners.some((owner) => threadWriterMatches(owner, expectedOwner.writerPid, expectedOwner.writerStartedAt ?? "", expectedOwner.applicationPid, expectedOwner.applicationStartedAt ?? "")))
5607
+ return owners;
5608
+ await delay(250);
5609
+ } while (Date.now() < deadline);
5610
+ return owners;
5611
+ }
5612
+ async sendError(contextKey, error, details = {}) {
5613
+ if (error instanceof PresentedRequestError) {
5614
+ this.logger.warn({ error: errorLogValue(error.originalError), contextKey, ...details }, "Request failed after a dedicated error card was presented.");
5615
+ return;
5616
+ }
5617
+ const record = this.currentSession(contextKey);
5618
+ if (record) {
5619
+ try {
5620
+ if (await this.presentThreadWriterConflict(record, contextKey, error, false))
5621
+ return;
5622
+ }
5623
+ catch (presentationError) {
5624
+ this.logger.warn({ error: presentationError, originalError: errorLogValue(error), contextKey, ...details }, "Failed to present the thread writer conflict card.");
5625
+ }
5626
+ }
5627
+ this.logger.warn({ error, contextKey, ...details }, "Request failed.");
5628
+ try {
5629
+ await this.outbound.sendText(contextKey, error instanceof Error ? error.message : String(error));
5630
+ }
5631
+ catch (deliveryError) {
5632
+ this.logger.error({
5633
+ error: deliveryError,
5634
+ originalError: errorLogValue(error),
5635
+ contextKey,
5636
+ ...details,
5637
+ phase: "error_response_send",
5638
+ }, "Failed to deliver the request failure to the user.");
5639
+ }
5640
+ }
5641
+ }
5642
+ function activeWriterThreadId(error) {
5643
+ const visited = new Set();
5644
+ let current = error;
5645
+ while (current !== undefined && current !== null && !visited.has(current)) {
5646
+ visited.add(current);
5647
+ const message = current instanceof Error
5648
+ ? current.message
5649
+ : typeof current === "string" ? current : undefined;
5650
+ const match = message?.match(/thread(?:\/resume failed:\s*thread)?\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\s+already has an active writer/iu);
5651
+ if (match?.[1])
5652
+ return match[1];
5653
+ current = current instanceof Error ? current.cause : undefined;
5654
+ }
5655
+ return undefined;
5656
+ }
5657
+ function positiveActionPid(value) {
5658
+ const pid = typeof value === "number" ? value : Number(value);
5659
+ if (!Number.isSafeInteger(pid) || pid <= 4) {
5660
+ throw new Error("任务占用卡片中的进程信息无效,请重新发送消息获取最新状态。");
5381
5661
  }
5662
+ return pid;
5663
+ }
5664
+ function threadWriterMatches(owner, writerPid, writerStartedAt, applicationPid, applicationStartedAt) {
5665
+ return Boolean(writerStartedAt)
5666
+ && Boolean(applicationStartedAt)
5667
+ && owner.writerPid === writerPid
5668
+ && owner.writerStartedAt === writerStartedAt
5669
+ && owner.applicationPid === applicationPid
5670
+ && owner.applicationStartedAt === applicationStartedAt;
5671
+ }
5672
+ function delay(milliseconds) {
5673
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
5674
+ }
5675
+ function renderPlainHelp(introLines, sections) {
5676
+ return [
5677
+ "Agent Bot 使用帮助",
5678
+ "",
5679
+ ...introLines.map((line) => decodeHelpEntities(line.replace(/^>\s*/u, "").replaceAll("**", ""))),
5680
+ ...sections.flatMap((section) => [
5681
+ "",
5682
+ section.title,
5683
+ ...section.commands.map((command) => [
5684
+ command.text,
5685
+ command.usage ? decodeHelpEntities(command.usage) : "",
5686
+ `- ${command.description}`,
5687
+ ].filter(Boolean).join(" ")),
5688
+ ]),
5689
+ ].join("\n");
5690
+ }
5691
+ function decodeHelpEntities(value) {
5692
+ return value
5693
+ .replaceAll("&#60;", "<")
5694
+ .replaceAll("&#62;", ">")
5695
+ .replaceAll("&amp;", "&");
5382
5696
  }
5383
5697
  function threadForkAnchorMessageIds(message) {
5384
5698
  return [...new Set([message.rootMessageId, message.parentMessageId]