@ganglion/xacpx 0.23.0 → 0.24.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![xacpx.png](xacpx_banner.png)
4
4
 
5
- > Remotely drive Codex, Claude Code, and other acpx sessions from WeChat, Feishu, or Yuanbao.
5
+ > Remotely drive Codex, Claude Code, and other acpx sessions from WeChat, Feishu, Yuanbao, or Discord.
6
6
 
7
7
  [![npm](https://img.shields.io/npm/v/@ganglion/xacpx?style=flat-square)](https://www.npmjs.com/package/@ganglion/xacpx)
8
8
  [![Node.js Version](https://img.shields.io/node/v/@ganglion/xacpx?style=flat-square)](https://nodejs.org)
@@ -13,7 +13,7 @@ English · **[中文](./docs/zh/README_zh.md)**
13
13
 
14
14
  ## What is this
15
15
 
16
- `xacpx` is a tool that lets you control ACP agents such as Codex / Claude Code / Gemini / OpenCode directly from WeChat, Feishu, or Yuanbao. It connects chat messages to your agent CLI sessions through `acpx`, so you can, right from your phone:
16
+ `xacpx` is a tool that lets you control ACP agents such as Codex / Claude Code / Gemini / OpenCode directly from WeChat, Feishu, Yuanbao, or Discord. It connects chat messages to your agent CLI sessions through `acpx`, so you can, right from your phone:
17
17
 
18
18
  - Create and switch between sessions
19
19
  - Have the agent keep working in a specific project directory
@@ -33,9 +33,9 @@ Chat isn't the only entry point: xacpx also ships a self-hostable **[relay hub](
33
33
 
34
34
  - Node.js 22.13+ or Bun
35
35
  - A working agent CLI you intend to use, such as Codex / Claude Code / Gemini / OpenCode
36
- - A phone with WeChat, Feishu, or Yuanbao installed
36
+ - A phone with WeChat, Feishu, Yuanbao, or Discord installed
37
37
 
38
- > The WeChat channel works on top of `weixin-agent-sdk`, the Feishu channel uses Feishu custom-app credentials, and the Yuanbao channel uses `appKey` / `appSecret`; the underlying agent sessions are driven by `acpx`. Normally you don't need to install `acpx` globally.
38
+ > The WeChat channel works on top of `weixin-agent-sdk`, the Feishu channel uses Feishu custom-app credentials, the Yuanbao channel uses `appKey` / `appSecret`, and the Discord channel uses a Discord Bot Token over the Gateway (a bot installed into a server, not QR-paired); the underlying agent sessions are driven by `acpx`. Normally you don't need to install `acpx` globally.
39
39
 
40
40
  ### Install
41
41
 
@@ -73,7 +73,7 @@ If everything works, plain text goes into the current session and the agent's re
73
73
 
74
74
  ### Other channels
75
75
 
76
- WeChat is the built-in default channel. Feishu and Yuanbao are distributed as official plugin packages, and third-party channels follow the same plugin flow. If you can't remember the package names, run `xacpx plugin known` first.
76
+ WeChat is the built-in default channel. Feishu, Yuanbao, and Discord are distributed as official plugin packages, and third-party channels follow the same plugin flow. If you can't remember the package names, run `xacpx plugin known` first.
77
77
 
78
78
  ```bash
79
79
  # Feishu
@@ -85,8 +85,15 @@ xacpx restart
85
85
  xacpx plugin add @ganglion/xacpx-channel-yuanbao
86
86
  xacpx channel add yuanbao # enter appKey/appSecret when prompted
87
87
  xacpx restart
88
+
89
+ # Discord
90
+ xacpx plugin add @ganglion/xacpx-channel-discord
91
+ xacpx channel add discord # enter the Bot Token when prompted
92
+ xacpx restart
88
93
  ```
89
94
 
95
+ > Discord defaults to **allowlist** access with an empty `allowFrom`, so adding the token alone is not enough to chat — the bot connects but ignores every sender. Complete the Discord User ID allowlist step in [channel-management.md](./docs/channel-management.md) before expecting messages to be handled.
96
+
90
97
  Full credentials, parameters, and management commands (`enable/disable/rm`): [channel-management.md](./docs/channel-management.md). To write your own channel plugin: [plugin-development.md](./docs/plugin-development.md).
91
98
 
92
99
  ## Your everyday workflow
@@ -128,10 +135,10 @@ The essentials to get going. Full references: **CLI → [cli-reference.md](./doc
128
135
  | `xacpx update` | Update xacpx and installed plugins |
129
136
  | `xacpx adapter list` / `update <name>` / `registry` | Manage verified Codex/Claude ACP adapter pins and their npm registry |
130
137
  | `xacpx doctor` | Run environment diagnostics |
131
- | `xacpx channel add <name>` | Add a message channel (Feishu / Yuanbao / …) |
138
+ | `xacpx channel add <name>` | Add a message channel (Feishu / Yuanbao / Discord / …) |
132
139
  | `xacpx ws add` / `xacpx agent add <name>` | Register a workspace / agent |
133
140
 
134
- **Chat (in WeChat / Feishu / Yuanbao):**
141
+ **Chat (in WeChat / Feishu / Yuanbao / Discord):**
135
142
 
136
143
  | Command | Description |
137
144
  |------|------|
@@ -221,7 +228,7 @@ For development, debugging, and contribution details, see [developments.md](./do
221
228
  ## More docs
222
229
 
223
230
  **Install & configure**
224
- - [channel-management.md](./docs/channel-management.md) — configure WeChat / Feishu / Yuanbao / third-party channels
231
+ - [channel-management.md](./docs/channel-management.md) — configure WeChat / Feishu / Yuanbao / Discord / third-party channels
225
232
  - [plugin-development.md](./docs/plugin-development.md) — write your own channel plugin
226
233
  - [config-reference.md](./docs/config-reference.md) — full config field reference
227
234
  - [config-command.md](./docs/config-command.md) — change config from chat
@@ -2071,6 +2071,60 @@ var init_prompt_output = __esm(() => {
2071
2071
  };
2072
2072
  });
2073
2073
 
2074
+ // src/transport/acpx-queue-overflow.ts
2075
+ function isAcpxQueueMessageOverflow(error) {
2076
+ const seen = new Set;
2077
+ const diagnostics = [];
2078
+ const visit = (value, depth) => {
2079
+ if (value === null || value === undefined || depth > 2 || seen.has(value))
2080
+ return;
2081
+ if (typeof value === "string") {
2082
+ diagnostics.push(value);
2083
+ return;
2084
+ }
2085
+ if (typeof value !== "object")
2086
+ return;
2087
+ seen.add(value);
2088
+ const record = value;
2089
+ for (const key of ["message", "code", "stderr"]) {
2090
+ const field = record[key];
2091
+ if (typeof field === "string")
2092
+ diagnostics.push(field);
2093
+ }
2094
+ visit(record.cause, depth + 1);
2095
+ };
2096
+ visit(error, 0);
2097
+ return diagnostics.some((text) => text === ACPX_QUEUE_MESSAGE_OVERFLOW_CODE || ACPX_QUEUE_OVERFLOW_CODE_PATTERN.test(text) || QUEUE_BUFFER_OVERFLOW_PATTERN.test(text) || QUEUE_EVENT_TOO_LARGE_PATTERN.test(text) || QUEUE_MESSAGE_OVERFLOW_PATTERN.test(text));
2098
+ }
2099
+ var ACPX_QUEUE_MESSAGE_OVERFLOW_CODE = "ACPX_QUEUE_MESSAGE_OVERFLOW", QUEUE_BUFFER_OVERFLOW_PATTERN, QUEUE_EVENT_TOO_LARGE_PATTERN, QUEUE_MESSAGE_OVERFLOW_PATTERN, ACPX_QUEUE_OVERFLOW_CODE_PATTERN, MAX_CLEANUP_DIAGNOSTIC_LENGTH = 512, AcpxQueueOverflowError;
2100
+ var init_acpx_queue_overflow = __esm(() => {
2101
+ QUEUE_BUFFER_OVERFLOW_PATTERN = /Message buffer exceeded \d+ bytes/i;
2102
+ QUEUE_EVENT_TOO_LARGE_PATTERN = /\bQUEUE_EVENT_TOO_LARGE\b/i;
2103
+ QUEUE_MESSAGE_OVERFLOW_PATTERN = /\bQUEUE_MESSAGE_OVERFLOW\b/i;
2104
+ ACPX_QUEUE_OVERFLOW_CODE_PATTERN = /\bACPX_QUEUE_MESSAGE_OVERFLOW\b/i;
2105
+ AcpxQueueOverflowError = class AcpxQueueOverflowError extends Error {
2106
+ code = ACPX_QUEUE_MESSAGE_OVERFLOW_CODE;
2107
+ cleanupDiagnostic;
2108
+ cleanup;
2109
+ constructor(cleanup) {
2110
+ const normalizedCleanup = typeof cleanup === "string" ? {
2111
+ cancelAttempted: false,
2112
+ cancelSucceeded: false,
2113
+ ownerTerminationAttempted: false,
2114
+ ownerTerminationSucceeded: false,
2115
+ diagnostic: cleanup
2116
+ } : cleanup;
2117
+ const cleanupStatus = normalizedCleanup?.ownerTerminationSucceeded ? "The local agent queue was stopped to prevent the turn from continuing in the background." : normalizedCleanup?.cancelSucceeded ? "The running agent turn was cancelled, but local queue-owner termination was not confirmed." : "Cleanup of the running agent turn could not be confirmed.";
2118
+ const boundedDiagnostic = normalizedCleanup?.diagnostic ? normalizedCleanup.diagnostic.slice(0, MAX_CLEANUP_DIAGNOSTIC_LENGTH) : undefined;
2119
+ const baseMessage = `Agent emitted an oversized ACP event. ${cleanupStatus} ` + "The prompt was not retried automatically.";
2120
+ super(boundedDiagnostic ? `${baseMessage} Cleanup diagnostic: ${boundedDiagnostic}` : baseMessage);
2121
+ this.name = "AcpxQueueOverflowError";
2122
+ this.cleanupDiagnostic = boundedDiagnostic;
2123
+ this.cleanup = normalizedCleanup;
2124
+ }
2125
+ };
2126
+ });
2127
+
2074
2128
  // src/transport/message-injection.ts
2075
2129
  function isMessageInjectionErrorCode(value) {
2076
2130
  return typeof value === "string" && MESSAGE_INJECTION_ERROR_CODES.has(value);
@@ -4327,6 +4381,9 @@ var init_recovery = __esm(() => {
4327
4381
  sessionInterrupted: (alias) => `Session "${alias}" was interrupted; no final reply received.`,
4328
4382
  sessionInterruptedHint: "Retry directly. If the session remains unresponsive, send /cancel first, then retry.",
4329
4383
  sessionInterruptedError: (summary) => `Error: ${summary}`,
4384
+ queueOverflowWarning: "⚠️ Output too large",
4385
+ queueOverflowHint: "Ask the agent to summarize or send in smaller chunks, then retry. The session is ready for your next message.",
4386
+ queueOverflowUnconfirmedHint: "Automatic cleanup could not be confirmed. Please send /cancel first, then retry; if the issue persists, recreate the session (/session new ...).",
4330
4387
  autoInstallHeadlineFixed: "⚠️ Auto-install ran but could not fix the session startup problem",
4331
4388
  autoInstallHeadlineFailed: "❌ Auto-install failed",
4332
4389
  autoInstallOriginalError: "Original error:",
@@ -5129,6 +5186,7 @@ var init_misc = __esm(() => {
5129
5186
  defaultHomeWorkspaceDescription: "Home directory",
5130
5187
  pluginChannelFeishu: "Feishu channel",
5131
5188
  pluginChannelYuanbao: "Tencent Yuanbao channel",
5189
+ pluginChannelDiscord: "Discord channel",
5132
5190
  pluginChannelRelay: "Relay hub connector (drive this instance from a self-hosted relay hub)",
5133
5191
  pluginChannelInstallHint: (channelType, packageName) => `Channel ${channelType} requires a plugin: xacpx plugin add ${packageName}`,
5134
5192
  orchestrationSuggestion1: "Run /tasks --stuck to locate stuck tasks",
@@ -5454,6 +5512,9 @@ var init_recovery2 = __esm(() => {
5454
5512
  sessionInterrupted: (alias) => `当前会话「${alias}」执行中断,未收到最终回复。`,
5455
5513
  sessionInterruptedHint: "请直接重试;如果长时间无响应,可先发送 /cancel 后再重试。",
5456
5514
  sessionInterruptedError: (summary) => `错误信息:${summary}`,
5515
+ queueOverflowWarning: "⚠️ 单次输出过大",
5516
+ queueOverflowHint: "请让 agent 分步/摘要输出,或拆成多次发送;会话已就绪,可直接继续。",
5517
+ queueOverflowUnconfirmedHint: "自动清理未能确认完成,请先发送 /cancel 后重试;若仍异常,请重建会话(/session new ...)或稍后重试。",
5457
5518
  autoInstallHeadlineFixed: "⚠️ 自动安装已执行但未能修复会话启动问题",
5458
5519
  autoInstallHeadlineFailed: "❌ 自动安装失败",
5459
5520
  autoInstallOriginalError: "原始错误:",
@@ -6256,6 +6317,7 @@ var init_misc2 = __esm(() => {
6256
6317
  defaultHomeWorkspaceDescription: "用户主目录",
6257
6318
  pluginChannelFeishu: "飞书频道",
6258
6319
  pluginChannelYuanbao: "腾讯元宝频道",
6320
+ pluginChannelDiscord: "Discord 频道",
6259
6321
  pluginChannelRelay: "Relay hub 连接器(从自托管 relay hub 遥控这台实例)",
6260
6322
  pluginChannelInstallHint: (channelType, packageName) => `频道 ${channelType} 需要安装插件:xacpx plugin add ${packageName}`,
6261
6323
  orchestrationSuggestion1: "查看 /tasks --stuck 定位卡住的任务",
@@ -9694,61 +9756,7 @@ function normalizeBridgeQueueOwnerTtlSeconds(value) {
9694
9756
  init_acpx_bridge_protocol();
9695
9757
  init_claude_settings_policy();
9696
9758
  init_prompt_output();
9697
-
9698
- // src/transport/acpx-queue-overflow.ts
9699
- var ACPX_QUEUE_MESSAGE_OVERFLOW_CODE = "ACPX_QUEUE_MESSAGE_OVERFLOW";
9700
- var QUEUE_BUFFER_OVERFLOW_PATTERN = /Message buffer exceeded \d+ bytes/i;
9701
- var QUEUE_EVENT_TOO_LARGE_PATTERN = /\bQUEUE_EVENT_TOO_LARGE\b/i;
9702
- var QUEUE_MESSAGE_OVERFLOW_PATTERN = /\bQUEUE_MESSAGE_OVERFLOW\b/i;
9703
- var MAX_CLEANUP_DIAGNOSTIC_LENGTH = 512;
9704
- function isAcpxQueueMessageOverflow(error) {
9705
- const seen = new Set;
9706
- const diagnostics = [];
9707
- const visit = (value, depth) => {
9708
- if (value === null || value === undefined || depth > 2 || seen.has(value))
9709
- return;
9710
- if (typeof value === "string") {
9711
- diagnostics.push(value);
9712
- return;
9713
- }
9714
- if (typeof value !== "object")
9715
- return;
9716
- seen.add(value);
9717
- const record = value;
9718
- for (const key of ["message", "code", "stdout", "stderr"]) {
9719
- const field = record[key];
9720
- if (typeof field === "string")
9721
- diagnostics.push(field);
9722
- }
9723
- visit(record.cause, depth + 1);
9724
- };
9725
- visit(error, 0);
9726
- return diagnostics.some((text) => QUEUE_BUFFER_OVERFLOW_PATTERN.test(text) || QUEUE_EVENT_TOO_LARGE_PATTERN.test(text) || QUEUE_MESSAGE_OVERFLOW_PATTERN.test(text));
9727
- }
9728
-
9729
- class AcpxQueueOverflowError extends Error {
9730
- code = ACPX_QUEUE_MESSAGE_OVERFLOW_CODE;
9731
- cleanupDiagnostic;
9732
- cleanup;
9733
- constructor(cleanup) {
9734
- const normalizedCleanup = typeof cleanup === "string" ? {
9735
- cancelAttempted: false,
9736
- cancelSucceeded: false,
9737
- ownerTerminationAttempted: false,
9738
- ownerTerminationSucceeded: false,
9739
- diagnostic: cleanup
9740
- } : cleanup;
9741
- const cleanupStatus = normalizedCleanup?.ownerTerminationSucceeded ? "The local agent queue was stopped to prevent the turn from continuing in the background." : normalizedCleanup?.cancelSucceeded ? "The running agent turn was cancelled, but local queue-owner termination was not confirmed." : "Cleanup of the running agent turn could not be confirmed.";
9742
- const boundedDiagnostic = normalizedCleanup?.diagnostic ? normalizedCleanup.diagnostic.slice(0, MAX_CLEANUP_DIAGNOSTIC_LENGTH) : undefined;
9743
- const baseMessage = `Agent emitted an oversized ACP event. ${cleanupStatus} ` + "The prompt was not retried automatically.";
9744
- super(boundedDiagnostic ? `${baseMessage} Cleanup diagnostic: ${boundedDiagnostic}` : baseMessage);
9745
- this.name = "AcpxQueueOverflowError";
9746
- this.cleanupDiagnostic = boundedDiagnostic;
9747
- this.cleanup = normalizedCleanup;
9748
- }
9749
- }
9750
-
9751
- // src/bridge/bridge-server.ts
9759
+ init_acpx_queue_overflow();
9752
9760
  init_message_injection();
9753
9761
 
9754
9762
  // src/bridge/bridge-request-scheduler.ts
@@ -9815,6 +9823,7 @@ function parseMissingOptionalDep(text) {
9815
9823
  // src/bridge/bridge-runtime.ts
9816
9824
  init_discover_parent_package_paths();
9817
9825
  init_acpx_queue_owner_launcher();
9826
+ init_acpx_queue_overflow();
9818
9827
  init_adapter_catalog();
9819
9828
  init_acpx_session_argv_migration();
9820
9829
  init_agent_launch();
@@ -10775,6 +10784,7 @@ class BridgeServer {
10775
10784
  ...error.stderrTail ? { stderrTail: error.stderrTail } : {}
10776
10785
  }
10777
10786
  } : {};
10787
+ const queueOverflowCleanup = error instanceof AcpxQueueOverflowError && error.cleanup ? { queueOverflowCleanup: error.cleanup } : {};
10778
10788
  const errorCode = error instanceof AcpxQueueOverflowError ? error.code : error instanceof MessageInjectionError ? error.code : error instanceof BridgeInvalidRequestError ? "BRIDGE_INVALID_REQUEST" : "BRIDGE_INTERNAL_ERROR";
10779
10789
  return `${JSON.stringify({
10780
10790
  id: requestId,
@@ -10784,7 +10794,8 @@ class BridgeServer {
10784
10794
  message,
10785
10795
  ...ensureSessionFields,
10786
10796
  ...promptDetails,
10787
- ...timeoutDetails
10797
+ ...timeoutDetails,
10798
+ ...queueOverflowCleanup
10788
10799
  }
10789
10800
  })}
10790
10801
  `;
package/dist/cli.js CHANGED
@@ -302,6 +302,9 @@ var init_recovery = __esm(() => {
302
302
  sessionInterrupted: (alias) => `Session "${alias}" was interrupted; no final reply received.`,
303
303
  sessionInterruptedHint: "Retry directly. If the session remains unresponsive, send /cancel first, then retry.",
304
304
  sessionInterruptedError: (summary) => `Error: ${summary}`,
305
+ queueOverflowWarning: "⚠️ Output too large",
306
+ queueOverflowHint: "Ask the agent to summarize or send in smaller chunks, then retry. The session is ready for your next message.",
307
+ queueOverflowUnconfirmedHint: "Automatic cleanup could not be confirmed. Please send /cancel first, then retry; if the issue persists, recreate the session (/session new ...).",
305
308
  autoInstallHeadlineFixed: "⚠️ Auto-install ran but could not fix the session startup problem",
306
309
  autoInstallHeadlineFailed: "❌ Auto-install failed",
307
310
  autoInstallOriginalError: "Original error:",
@@ -1104,6 +1107,7 @@ var init_misc = __esm(() => {
1104
1107
  defaultHomeWorkspaceDescription: "Home directory",
1105
1108
  pluginChannelFeishu: "Feishu channel",
1106
1109
  pluginChannelYuanbao: "Tencent Yuanbao channel",
1110
+ pluginChannelDiscord: "Discord channel",
1107
1111
  pluginChannelRelay: "Relay hub connector (drive this instance from a self-hosted relay hub)",
1108
1112
  pluginChannelInstallHint: (channelType, packageName) => `Channel ${channelType} requires a plugin: xacpx plugin add ${packageName}`,
1109
1113
  orchestrationSuggestion1: "Run /tasks --stuck to locate stuck tasks",
@@ -1429,6 +1433,9 @@ var init_recovery2 = __esm(() => {
1429
1433
  sessionInterrupted: (alias) => `当前会话「${alias}」执行中断,未收到最终回复。`,
1430
1434
  sessionInterruptedHint: "请直接重试;如果长时间无响应,可先发送 /cancel 后再重试。",
1431
1435
  sessionInterruptedError: (summary) => `错误信息:${summary}`,
1436
+ queueOverflowWarning: "⚠️ 单次输出过大",
1437
+ queueOverflowHint: "请让 agent 分步/摘要输出,或拆成多次发送;会话已就绪,可直接继续。",
1438
+ queueOverflowUnconfirmedHint: "自动清理未能确认完成,请先发送 /cancel 后重试;若仍异常,请重建会话(/session new ...)或稍后重试。",
1432
1439
  autoInstallHeadlineFixed: "⚠️ 自动安装已执行但未能修复会话启动问题",
1433
1440
  autoInstallHeadlineFailed: "❌ 自动安装失败",
1434
1441
  autoInstallOriginalError: "原始错误:",
@@ -2231,6 +2238,7 @@ var init_misc2 = __esm(() => {
2231
2238
  defaultHomeWorkspaceDescription: "用户主目录",
2232
2239
  pluginChannelFeishu: "飞书频道",
2233
2240
  pluginChannelYuanbao: "腾讯元宝频道",
2241
+ pluginChannelDiscord: "Discord 频道",
2234
2242
  pluginChannelRelay: "Relay hub 连接器(从自托管 relay hub 遥控这台实例)",
2235
2243
  pluginChannelInstallHint: (channelType, packageName) => `频道 ${channelType} 需要安装插件:xacpx plugin add ${packageName}`,
2236
2244
  orchestrationSuggestion1: "查看 /tasks --stuck 定位卡住的任务",
@@ -8517,18 +8525,24 @@ import { mkdir as mkdir7, open as open5, readFile as readFile9, writeFile as wri
8517
8525
  import { dirname as dirname10 } from "node:path";
8518
8526
  function createRuntimeConsumerLock(options) {
8519
8527
  const coreLock = (options.createCoreLock ?? (() => createCoreRuntimeLock(options)))();
8528
+ const channelLocks = options.channelLocks ?? [];
8520
8529
  let coreAcquired = false;
8521
- let channelAcquired = false;
8530
+ let acquiredChannelLocks = [];
8522
8531
  return {
8523
8532
  async acquire(metadata) {
8524
8533
  await coreLock.acquire(metadata);
8525
8534
  coreAcquired = true;
8526
8535
  try {
8527
- if (options.channelLock) {
8528
- await options.channelLock.acquire(metadata);
8529
- channelAcquired = true;
8536
+ for (const lock2 of channelLocks) {
8537
+ await lock2.acquire(metadata);
8538
+ acquiredChannelLocks.push(lock2);
8530
8539
  }
8531
8540
  } catch (error) {
8541
+ const held = acquiredChannelLocks;
8542
+ acquiredChannelLocks = [];
8543
+ for (const lock2 of held.reverse()) {
8544
+ await lock2.release().catch(() => {});
8545
+ }
8532
8546
  await coreLock.release().catch(() => {});
8533
8547
  coreAcquired = false;
8534
8548
  throw error;
@@ -8536,13 +8550,14 @@ function createRuntimeConsumerLock(options) {
8536
8550
  },
8537
8551
  async release() {
8538
8552
  let releaseError;
8539
- if (channelAcquired) {
8553
+ const held = acquiredChannelLocks;
8554
+ acquiredChannelLocks = [];
8555
+ for (const lock2 of held.reverse()) {
8540
8556
  try {
8541
- await options.channelLock.release();
8557
+ await lock2.release();
8542
8558
  } catch (error) {
8543
- releaseError = error;
8559
+ releaseError ??= error;
8544
8560
  }
8545
- channelAcquired = false;
8546
8561
  }
8547
8562
  if (coreAcquired) {
8548
8563
  try {
@@ -37540,6 +37555,12 @@ var init_known_plugins = __esm(() => {
37540
37555
  descriptionKey: "pluginChannelYuanbao",
37541
37556
  official: true
37542
37557
  },
37558
+ {
37559
+ packageName: "@ganglion/xacpx-channel-discord",
37560
+ channels: ["discord"],
37561
+ descriptionKey: "pluginChannelDiscord",
37562
+ official: true
37563
+ },
37543
37564
  {
37544
37565
  packageName: "@ganglion/xacpx-channel-relay",
37545
37566
  channels: ["relay"],
@@ -41905,6 +41926,60 @@ function decorateUnread(label, hasUnread) {
41905
41926
  return hasUnread ? `● ${label}` : label;
41906
41927
  }
41907
41928
 
41929
+ // src/transport/acpx-queue-overflow.ts
41930
+ function isAcpxQueueMessageOverflow(error2) {
41931
+ const seen = new Set;
41932
+ const diagnostics = [];
41933
+ const visit = (value, depth) => {
41934
+ if (value === null || value === undefined || depth > 2 || seen.has(value))
41935
+ return;
41936
+ if (typeof value === "string") {
41937
+ diagnostics.push(value);
41938
+ return;
41939
+ }
41940
+ if (typeof value !== "object")
41941
+ return;
41942
+ seen.add(value);
41943
+ const record3 = value;
41944
+ for (const key of ["message", "code", "stderr"]) {
41945
+ const field = record3[key];
41946
+ if (typeof field === "string")
41947
+ diagnostics.push(field);
41948
+ }
41949
+ visit(record3.cause, depth + 1);
41950
+ };
41951
+ visit(error2, 0);
41952
+ return diagnostics.some((text) => text === ACPX_QUEUE_MESSAGE_OVERFLOW_CODE || ACPX_QUEUE_OVERFLOW_CODE_PATTERN.test(text) || QUEUE_BUFFER_OVERFLOW_PATTERN.test(text) || QUEUE_EVENT_TOO_LARGE_PATTERN.test(text) || QUEUE_MESSAGE_OVERFLOW_PATTERN.test(text));
41953
+ }
41954
+ var ACPX_QUEUE_MESSAGE_OVERFLOW_CODE = "ACPX_QUEUE_MESSAGE_OVERFLOW", QUEUE_BUFFER_OVERFLOW_PATTERN, QUEUE_EVENT_TOO_LARGE_PATTERN, QUEUE_MESSAGE_OVERFLOW_PATTERN, ACPX_QUEUE_OVERFLOW_CODE_PATTERN, MAX_CLEANUP_DIAGNOSTIC_LENGTH = 512, AcpxQueueOverflowError;
41955
+ var init_acpx_queue_overflow = __esm(() => {
41956
+ QUEUE_BUFFER_OVERFLOW_PATTERN = /Message buffer exceeded \d+ bytes/i;
41957
+ QUEUE_EVENT_TOO_LARGE_PATTERN = /\bQUEUE_EVENT_TOO_LARGE\b/i;
41958
+ QUEUE_MESSAGE_OVERFLOW_PATTERN = /\bQUEUE_MESSAGE_OVERFLOW\b/i;
41959
+ ACPX_QUEUE_OVERFLOW_CODE_PATTERN = /\bACPX_QUEUE_MESSAGE_OVERFLOW\b/i;
41960
+ AcpxQueueOverflowError = class AcpxQueueOverflowError extends Error {
41961
+ code = ACPX_QUEUE_MESSAGE_OVERFLOW_CODE;
41962
+ cleanupDiagnostic;
41963
+ cleanup;
41964
+ constructor(cleanup) {
41965
+ const normalizedCleanup = typeof cleanup === "string" ? {
41966
+ cancelAttempted: false,
41967
+ cancelSucceeded: false,
41968
+ ownerTerminationAttempted: false,
41969
+ ownerTerminationSucceeded: false,
41970
+ diagnostic: cleanup
41971
+ } : cleanup;
41972
+ const cleanupStatus = normalizedCleanup?.ownerTerminationSucceeded ? "The local agent queue was stopped to prevent the turn from continuing in the background." : normalizedCleanup?.cancelSucceeded ? "The running agent turn was cancelled, but local queue-owner termination was not confirmed." : "Cleanup of the running agent turn could not be confirmed.";
41973
+ const boundedDiagnostic = normalizedCleanup?.diagnostic ? normalizedCleanup.diagnostic.slice(0, MAX_CLEANUP_DIAGNOSTIC_LENGTH) : undefined;
41974
+ const baseMessage = `Agent emitted an oversized ACP event. ${cleanupStatus} ` + "The prompt was not retried automatically.";
41975
+ super(boundedDiagnostic ? `${baseMessage} Cleanup diagnostic: ${boundedDiagnostic}` : baseMessage);
41976
+ this.name = "AcpxQueueOverflowError";
41977
+ this.cleanupDiagnostic = boundedDiagnostic;
41978
+ this.cleanup = normalizedCleanup;
41979
+ }
41980
+ };
41981
+ });
41982
+
41908
41983
  // src/commands/handlers/session-handler.ts
41909
41984
  function sessionHelp() {
41910
41985
  const s = t().session;
@@ -42554,6 +42629,19 @@ async function handlePromptWithSession(context, session3, chatKey, text, reply,
42554
42629
  try {
42555
42630
  return await promptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage, onCommands);
42556
42631
  } catch (error2) {
42632
+ if (error2 instanceof AcpxQueueOverflowError) {
42633
+ const confirmed = error2.cleanup?.ownerTerminationSucceeded === true;
42634
+ await context.logger.warn(confirmed ? "transport.queue_overflow_downgraded" : "transport.queue_overflow_unconfirmed", confirmed ? "acpx queue overflow downgraded to soft warning" : "acpx queue overflow cleanup unconfirmed, downgraded without ready promise", {
42635
+ alias: session3.alias,
42636
+ transportSession: session3.transportSession,
42637
+ code: "ACPX_QUEUE_MESSAGE_OVERFLOW",
42638
+ ownerTerminationSucceeded: error2.cleanup?.ownerTerminationSucceeded,
42639
+ cancelSucceeded: error2.cleanup?.cancelSucceeded,
42640
+ diagnostic: error2.cleanupDiagnostic ?? (error2 instanceof Error ? error2.message.slice(0, 512) : String(error2).slice(0, 512)),
42641
+ confirmed
42642
+ });
42643
+ return context.recovery.renderTransportError(session3, error2);
42644
+ }
42557
42645
  const recovered = await context.recovery.tryRecoverMissingSession(session3, error2);
42558
42646
  if (recovered) {
42559
42647
  return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage, onCommands);
@@ -42650,6 +42738,7 @@ var init_session_handler = __esm(() => {
42650
42738
  init_resolve_reply_mode();
42651
42739
  init_workspace_name();
42652
42740
  init_i18n();
42741
+ init_acpx_queue_overflow();
42653
42742
  });
42654
42743
 
42655
42744
  // src/formatting/render-text.ts
@@ -44359,6 +44448,19 @@ var ACPX_PARTIAL_OUTPUT_SENTINEL = "未收到最终回复";
44359
44448
 
44360
44449
  // src/commands/handlers/session-recovery-handler.ts
44361
44450
  function renderTransportError(session3, error2) {
44451
+ if (error2 instanceof AcpxQueueOverflowError) {
44452
+ const confirmed = error2.cleanup?.ownerTerminationSucceeded === true;
44453
+ if (confirmed) {
44454
+ return {
44455
+ text: [t().recovery.queueOverflowWarning, t().recovery.queueOverflowHint].join(`
44456
+ `)
44457
+ };
44458
+ }
44459
+ return {
44460
+ text: [t().recovery.queueOverflowWarning, t().recovery.queueOverflowUnconfirmedHint].join(`
44461
+ `)
44462
+ };
44463
+ }
44362
44464
  const message = error2 instanceof Error ? error2.message : String(error2);
44363
44465
  const registryError = renderAdapterRegistryError(session3, message);
44364
44466
  if (registryError)
@@ -44464,6 +44566,8 @@ function renderSessionCreationFailure(session3, detail) {
44464
44566
  };
44465
44567
  }
44466
44568
  async function tryRecoverMissingSession(ops, session3, error2) {
44569
+ if (error2 instanceof AcpxQueueOverflowError)
44570
+ return null;
44467
44571
  const message = error2 instanceof Error ? error2.message : String(error2);
44468
44572
  if (!message.includes("No acpx session found")) {
44469
44573
  return null;
@@ -44484,6 +44588,7 @@ var init_session_recovery_handler = __esm(() => {
44484
44588
  init_i18n();
44485
44589
  init_adapter_registry();
44486
44590
  init_adapter_catalog();
44591
+ init_acpx_queue_overflow();
44487
44592
  });
44488
44593
 
44489
44594
  // src/recovery/auto-install-optional-dep.ts
@@ -54604,6 +54709,18 @@ class AcpxBridgeClient {
54604
54709
  pending.reject(new MessageInjectionError(response.error.code, response.error.message));
54605
54710
  return;
54606
54711
  }
54712
+ if (response.error.code === ACPX_QUEUE_MESSAGE_OVERFLOW_CODE) {
54713
+ const raw = response.error.queueOverflowCleanup;
54714
+ const cleanup = raw ? {
54715
+ cancelAttempted: Boolean(raw.cancelAttempted),
54716
+ cancelSucceeded: Boolean(raw.cancelSucceeded),
54717
+ ownerTerminationAttempted: Boolean(raw.ownerTerminationAttempted),
54718
+ ownerTerminationSucceeded: Boolean(raw.ownerTerminationSucceeded),
54719
+ ...raw.diagnostic !== undefined ? { diagnostic: raw.diagnostic } : {}
54720
+ } : undefined;
54721
+ pending.reject(new AcpxQueueOverflowError(cleanup ?? response.error.message));
54722
+ return;
54723
+ }
54607
54724
  const bridgeError = new Error(response.error.message);
54608
54725
  bridgeError.code = response.error.code;
54609
54726
  pending.reject(bridgeError);
@@ -54781,6 +54898,7 @@ var init_acpx_bridge_client = __esm(() => {
54781
54898
  init_terminate_process_tree();
54782
54899
  init_i18n();
54783
54900
  init_acpx_queue_owner_launcher();
54901
+ init_acpx_queue_overflow();
54784
54902
  init_command_timeouts();
54785
54903
  init_message_injection();
54786
54904
  });
@@ -57452,6 +57570,12 @@ ${baseText}` : "" };
57452
57570
  const spawnOptions = this.withSpawnEnvironment(session3);
57453
57571
  const result = spawnOptions ? await this.runCommand(this.command, args, spawnOptions) : await this.runCommand(this.command, args);
57454
57572
  return { text: getPromptText(result) };
57573
+ } catch (error2) {
57574
+ if (!isAcpxQueueMessageOverflow(error2)) {
57575
+ throw error2;
57576
+ }
57577
+ const cleanup = await this.cleanupOverflowedOwner(session3);
57578
+ throw new AcpxQueueOverflowError(cleanup);
57455
57579
  } finally {
57456
57580
  try {
57457
57581
  await structuredPrompt?.cleanup();
@@ -57741,6 +57865,38 @@ ${baseText}` : "" };
57741
57865
  return record3;
57742
57866
  throw new Error("failed to resolve acpx session record id");
57743
57867
  }
57868
+ async cleanupOverflowedOwner(session3) {
57869
+ const diagnostics = [];
57870
+ let cancelSucceeded = false;
57871
+ try {
57872
+ await this.cancel(session3);
57873
+ cancelSucceeded = true;
57874
+ } catch (error2) {
57875
+ diagnostics.push(`cancel failed: ${describeCleanupError(error2)}`);
57876
+ }
57877
+ let record3;
57878
+ try {
57879
+ record3 = await this.readSessionRecord(session3);
57880
+ } catch (error2) {
57881
+ diagnostics.push(`could not resolve the queue owner record: ${describeCleanupError(error2)}`);
57882
+ }
57883
+ let ownerTerminationSucceeded = false;
57884
+ if (record3) {
57885
+ try {
57886
+ await terminateAcpxQueueOwnerVerified(record3.acpxRecordId);
57887
+ ownerTerminationSucceeded = true;
57888
+ } catch (error2) {
57889
+ diagnostics.push(`owner termination failed: ${describeCleanupError(error2)}`);
57890
+ }
57891
+ }
57892
+ return {
57893
+ cancelAttempted: true,
57894
+ cancelSucceeded,
57895
+ ownerTerminationAttempted: Boolean(record3),
57896
+ ownerTerminationSucceeded,
57897
+ ...diagnostics.length > 0 ? { diagnostic: diagnostics.join("; ") } : {}
57898
+ };
57899
+ }
57744
57900
  async migrateLegacySessionArgvIfNeeded(session3) {
57745
57901
  if (!session3.agentArgv || session3.agentArgv.length === 0) {
57746
57902
  return;
@@ -58012,6 +58168,10 @@ ${baseText}` : "" };
58012
58168
  return buildPermissionArgs(this.permissionInput());
58013
58169
  }
58014
58170
  }
58171
+ function describeCleanupError(error2) {
58172
+ const message = error2 instanceof Error ? error2.message : String(error2);
58173
+ return message.length > 512 ? `${message.slice(0, 512)}…` : message;
58174
+ }
58015
58175
  function renderCommandForError(args) {
58016
58176
  const rendered = [];
58017
58177
  for (let index = 0;index < args.length; index += 1) {
@@ -58064,6 +58224,7 @@ var init_acpx_cli_transport = __esm(() => {
58064
58224
  init_command_timeouts();
58065
58225
  init_acpx_command_builder();
58066
58226
  init_message_injection();
58227
+ init_acpx_queue_overflow();
58067
58228
  require6 = createRequire7(import.meta.url);
58068
58229
  });
58069
58230
 
@@ -65773,7 +65934,8 @@ __export(exports_cli, {
65773
65934
  resolveLoginChannelForCli: () => resolveLoginChannelForCli,
65774
65935
  prepareMcpCoordinatorStartup: () => prepareMcpCoordinatorStartup,
65775
65936
  getUsageText: () => getUsageText,
65776
- createMcpStdioIdentityResolver: () => createMcpStdioIdentityResolver
65937
+ createMcpStdioIdentityResolver: () => createMcpStdioIdentityResolver,
65938
+ createChannelConsumerLocks: () => createChannelConsumerLocks
65777
65939
  });
65778
65940
  import { randomUUID as randomUUID16 } from "node:crypto";
65779
65941
  import { homedir as homedir23 } from "node:os";
@@ -66497,6 +66659,14 @@ async function defaultLoadConfiguredPluginsForChannelCli() {
66497
66659
  const { loadConfiguredPlugins: loadConfiguredPlugins2 } = await Promise.resolve().then(() => (init_plugin_loader(), exports_plugin_loader));
66498
66660
  await loadConfiguredPlugins2({ plugins: config4.plugins });
66499
66661
  }
66662
+ function createChannelConsumerLocks(lockCreators, input) {
66663
+ return [...lockCreators].sort((a, b) => a.channel.id.localeCompare(b.channel.id)).map(({ channel, create }) => create({
66664
+ lockFilePath: `${input.runtimeDir}${sep6}${channel.id}-consumer.lock.json`,
66665
+ onDiagnostic: async (event, context) => {
66666
+ await input.onDiagnostic(channel.id, event, context);
66667
+ }
66668
+ }));
66669
+ }
66500
66670
  async function runDefaultRuntime(options = {}) {
66501
66671
  const [{ buildApp: buildApp2, resolveRuntimePaths: resolveRuntimePaths2, prepareChannelMedia: prepareChannelMedia2 }, { runConsole: runConsole2 }] = await Promise.all([
66502
66672
  Promise.resolve().then(() => (init_main(), exports_main)),
@@ -66532,7 +66702,6 @@ async function runDefaultRuntime(options = {}) {
66532
66702
  const { channelDeps } = await prepareChannelMedia2(runtimePaths.configPath, config4);
66533
66703
  const channelRegistry = new MessageChannelRegistry2(createMessageChannels2(config4.channels, channelDeps));
66534
66704
  const lockCreators = channelRegistry.createConsumerLocks();
66535
- const firstLockCreator = lockCreators[0];
66536
66705
  const firstRunOnboarding = options.firstRunOnboarding ?? decodeFirstRunOnboarding(coreEnv("FIRST_RUN_ONBOARDING"));
66537
66706
  await runConsole2(runtimePaths, {
66538
66707
  buildApp: (paths) => buildApp2(paths, {
@@ -66553,12 +66722,12 @@ async function runDefaultRuntime(options = {}) {
66553
66722
  ...isDaemonRun ? { daemonRuntime } : {},
66554
66723
  consumerLockFactory: (runtime) => createRuntimeConsumerLock({
66555
66724
  lockFilePath: resolveRuntimeConsumerLockPath(daemonPaths.runtimeDir),
66556
- ...firstLockCreator ? { channelLock: firstLockCreator.create({
66557
- lockFilePath: `${daemonPaths.runtimeDir}${sep6}${firstLockCreator.channel.id}-consumer.lock.json`,
66558
- onDiagnostic: async (event, context) => {
66559
- await runtime.logger.info(`${firstLockCreator.channel.id}.consumer_lock.${event}`, `${firstLockCreator.channel.id} consumer lock diagnostic`, context);
66725
+ channelLocks: createChannelConsumerLocks(lockCreators, {
66726
+ runtimeDir: daemonPaths.runtimeDir,
66727
+ onDiagnostic: async (channelId, event, context) => {
66728
+ await runtime.logger.info(`${channelId}.consumer_lock.${event}`, `${channelId} consumer lock diagnostic`, context);
66560
66729
  }
66561
- }) } : {},
66730
+ }),
66562
66731
  onDiagnostic: async (event, context) => {
66563
66732
  await runtime.logger.info(`runtime.consumer_lock.${event}`, "runtime ownership lock diagnostic", context);
66564
66733
  }
@@ -67001,5 +67170,6 @@ export {
67001
67170
  resolveLoginChannelForCli,
67002
67171
  prepareMcpCoordinatorStartup,
67003
67172
  getUsageText,
67004
- createMcpStdioIdentityResolver
67173
+ createMcpStdioIdentityResolver,
67174
+ createChannelConsumerLocks
67005
67175
  };
@@ -196,6 +196,9 @@ export interface RecoveryMessages {
196
196
  sessionInterrupted: (alias: string) => string;
197
197
  sessionInterruptedHint: string;
198
198
  sessionInterruptedError: (summary: string) => string;
199
+ queueOverflowWarning: string;
200
+ queueOverflowHint: string;
201
+ queueOverflowUnconfirmedHint: string;
199
202
  autoInstallHeadlineFixed: string;
200
203
  autoInstallHeadlineFailed: string;
201
204
  autoInstallOriginalError: string;
@@ -837,6 +840,7 @@ export interface MiscMessages {
837
840
  defaultHomeWorkspaceDescription: string;
838
841
  pluginChannelFeishu: string;
839
842
  pluginChannelYuanbao: string;
843
+ pluginChannelDiscord: string;
840
844
  pluginChannelRelay: string;
841
845
  pluginChannelInstallHint: (channelType: string, packageName: string) => string;
842
846
  orchestrationSuggestion1: string;
@@ -1,3 +1,4 @@
1
+ export { ActiveConsumerLockError } from "./channels/types.js";
1
2
  export type { ChannelPluginDefinition, ChannelRetireContext, ChannelRetireDaemonState, ChannelRetireHook, } from "./channels/plugin.js";
2
3
  export type { ChannelFactory, CreateChannelDeps } from "./channels/create-channel.js";
3
4
  export type { ChannelStartInput, ConsumerLock, ConsumerLockMetadata, ConsumerLockOptions, ChannelStopReason, CoordinatorMessageInput, MessageChannelRuntime, ScheduledChannelMessageInput, OrchestrationDeliveryCallbacks, OutboundQuota, PlanEntry, PlanEntryStatus, ToolUseEvent, ToolUseKind, ToolUseStatus, } from "./channels/types.js";
@@ -46,6 +46,21 @@ var __export = (target, all) => {
46
46
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
47
47
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
48
48
 
49
+ // src/channels/types.ts
50
+ var ActiveConsumerLockError;
51
+ var init_types = __esm(() => {
52
+ ActiveConsumerLockError = class ActiveConsumerLockError extends Error {
53
+ lockFilePath;
54
+ existing;
55
+ constructor(message, lockFilePath, existing) {
56
+ super(message);
57
+ this.lockFilePath = lockFilePath;
58
+ this.existing = existing;
59
+ this.name = "ActiveConsumerLockError";
60
+ }
61
+ };
62
+ });
63
+
49
64
  // src/channels/subagent-notice.ts
50
65
  function statusWord(status) {
51
66
  return status === "running" ? "running" : status === "error" ? "failed" : "done";
@@ -302,6 +317,9 @@ var init_recovery = __esm(() => {
302
317
  sessionInterrupted: (alias) => `Session "${alias}" was interrupted; no final reply received.`,
303
318
  sessionInterruptedHint: "Retry directly. If the session remains unresponsive, send /cancel first, then retry.",
304
319
  sessionInterruptedError: (summary) => `Error: ${summary}`,
320
+ queueOverflowWarning: "⚠️ Output too large",
321
+ queueOverflowHint: "Ask the agent to summarize or send in smaller chunks, then retry. The session is ready for your next message.",
322
+ queueOverflowUnconfirmedHint: "Automatic cleanup could not be confirmed. Please send /cancel first, then retry; if the issue persists, recreate the session (/session new ...).",
305
323
  autoInstallHeadlineFixed: "⚠️ Auto-install ran but could not fix the session startup problem",
306
324
  autoInstallHeadlineFailed: "❌ Auto-install failed",
307
325
  autoInstallOriginalError: "Original error:",
@@ -1104,6 +1122,7 @@ var init_misc = __esm(() => {
1104
1122
  defaultHomeWorkspaceDescription: "Home directory",
1105
1123
  pluginChannelFeishu: "Feishu channel",
1106
1124
  pluginChannelYuanbao: "Tencent Yuanbao channel",
1125
+ pluginChannelDiscord: "Discord channel",
1107
1126
  pluginChannelRelay: "Relay hub connector (drive this instance from a self-hosted relay hub)",
1108
1127
  pluginChannelInstallHint: (channelType, packageName) => `Channel ${channelType} requires a plugin: xacpx plugin add ${packageName}`,
1109
1128
  orchestrationSuggestion1: "Run /tasks --stuck to locate stuck tasks",
@@ -1429,6 +1448,9 @@ var init_recovery2 = __esm(() => {
1429
1448
  sessionInterrupted: (alias) => `当前会话「${alias}」执行中断,未收到最终回复。`,
1430
1449
  sessionInterruptedHint: "请直接重试;如果长时间无响应,可先发送 /cancel 后再重试。",
1431
1450
  sessionInterruptedError: (summary) => `错误信息:${summary}`,
1451
+ queueOverflowWarning: "⚠️ 单次输出过大",
1452
+ queueOverflowHint: "请让 agent 分步/摘要输出,或拆成多次发送;会话已就绪,可直接继续。",
1453
+ queueOverflowUnconfirmedHint: "自动清理未能确认完成,请先发送 /cancel 后重试;若仍异常,请重建会话(/session new ...)或稍后重试。",
1432
1454
  autoInstallHeadlineFixed: "⚠️ 自动安装已执行但未能修复会话启动问题",
1433
1455
  autoInstallHeadlineFailed: "❌ 自动安装失败",
1434
1456
  autoInstallOriginalError: "原始错误:",
@@ -2231,6 +2253,7 @@ var init_misc2 = __esm(() => {
2231
2253
  defaultHomeWorkspaceDescription: "用户主目录",
2232
2254
  pluginChannelFeishu: "飞书频道",
2233
2255
  pluginChannelYuanbao: "腾讯元宝频道",
2256
+ pluginChannelDiscord: "Discord 频道",
2234
2257
  pluginChannelRelay: "Relay hub 连接器(从自托管 relay hub 遥控这台实例)",
2235
2258
  pluginChannelInstallHint: (channelType, packageName) => `频道 ${channelType} 需要安装插件:xacpx plugin add ${packageName}`,
2236
2259
  orchestrationSuggestion1: "查看 /tasks --stuck 定位卡住的任务",
@@ -2535,7 +2558,7 @@ var init_compatibility = __esm(() => {
2535
2558
  });
2536
2559
 
2537
2560
  // src/plugins/types.ts
2538
- var init_types = __esm(() => {
2561
+ var init_types2 = __esm(() => {
2539
2562
  init_compatibility();
2540
2563
  });
2541
2564
 
@@ -2709,6 +2732,7 @@ var init_core_home = () => {};
2709
2732
 
2710
2733
  // src/plugin-api.ts
2711
2734
  init_types();
2735
+ init_types2();
2712
2736
 
2713
2737
  // src/runtime/turn-lane.ts
2714
2738
  var CONTROL_COMMANDS = new Set(["/use", "/ss", "/cancel", "/stop"]);
@@ -2735,5 +2759,6 @@ export {
2735
2759
  WEACPX_PLUGIN_MIN_CORE_VERSION,
2736
2760
  WEACPX_PLUGIN_API_VERSION,
2737
2761
  WEACPX_PLUGIN_API_SUPPORTED_VERSIONS,
2738
- SubagentNoticeTracker
2762
+ SubagentNoticeTracker,
2763
+ ActiveConsumerLockError
2739
2764
  };
@@ -0,0 +1,22 @@
1
+ export declare const ACPX_QUEUE_MESSAGE_OVERFLOW_CODE: "ACPX_QUEUE_MESSAGE_OVERFLOW";
2
+ export interface AcpxQueueCleanupResult {
3
+ cancelAttempted: boolean;
4
+ cancelSucceeded: boolean;
5
+ ownerTerminationAttempted: boolean;
6
+ ownerTerminationSucceeded: boolean;
7
+ diagnostic?: string;
8
+ }
9
+ /**
10
+ * Detects both the acpx 0.13 queue client's legacy string error and the typed
11
+ * error emitted by a guarded queue owner. Prompt failures can be represented
12
+ * as Error instances, PromptCommandError-like objects, or plain JSON payloads
13
+ * after crossing the bridge, so inspect only the bounded diagnostic fields we
14
+ * control instead of depending on one concrete error class.
15
+ */
16
+ export declare function isAcpxQueueMessageOverflow(error: unknown): boolean;
17
+ export declare class AcpxQueueOverflowError extends Error {
18
+ readonly code: "ACPX_QUEUE_MESSAGE_OVERFLOW";
19
+ readonly cleanupDiagnostic?: string;
20
+ readonly cleanup?: AcpxQueueCleanupResult;
21
+ constructor(cleanup?: AcpxQueueCleanupResult | string);
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganglion/xacpx",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "随时随地通过聊天频道(微信 / 飞书 / 元宝等)远程控制 `acpx` 上的 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",
@@ -42,11 +42,13 @@
42
42
  "clean:dist": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
43
43
  "clean:channel-yuanbao": "node -e \"require('node:fs').rmSync('packages/channel-yuanbao/dist',{recursive:true,force:true})\"",
44
44
  "clean:channel-feishu": "node -e \"require('node:fs').rmSync('packages/channel-feishu/dist',{recursive:true,force:true})\"",
45
+ "clean:channel-discord": "node -e \"require('node:fs').rmSync('packages/channel-discord/dist',{recursive:true,force:true})\"",
46
+ "clean:relay-protocol": "node -e \"require('node:fs').rmSync('packages/relay-protocol/dist',{recursive:true,force:true})\"",
45
47
  "build": "bun run clean:dist && bun build ./src/cli.ts ./src/bridge/bridge-main.ts ./src/adapters/hermes-acp-shim.ts ./src/adapters/acp-output-guard-main.ts ./src/plugin-api.ts --outdir ./dist --target node --external node-pty --external fs-ext --external write-file-atomic && bun run build:plugin-api",
46
48
  "build:plugin-api": "tsc -p tsconfig.plugin-api.json",
47
49
  "build:channel-yuanbao": "bun run build:plugin-api && bun run clean:channel-yuanbao && bun build ./packages/channel-yuanbao/src/index.ts --outdir ./packages/channel-yuanbao/dist --target node --external xacpx && tsc -p packages/channel-yuanbao/tsconfig.json",
48
50
  "build:channel-feishu": "bun run build:plugin-api && bun run clean:channel-feishu && bun build ./packages/channel-feishu/src/index.ts --outdir ./packages/channel-feishu/dist --target node --external xacpx && tsc -p packages/channel-feishu/tsconfig.json",
49
- "clean:relay-protocol": "node -e \"require('node:fs').rmSync('packages/relay-protocol/dist',{recursive:true,force:true})\"",
51
+ "build:channel-discord": "bun run build:plugin-api && bun run clean:channel-discord && bun build ./packages/channel-discord/src/index.ts --outdir ./packages/channel-discord/dist --target node --external xacpx --external discord.js && tsc -p packages/channel-discord/tsconfig.json",
50
52
  "assert:relay-protocol": "node -e \"const m=require('./packages/relay-protocol/dist/index.js');if(!m.MSG||!m.MSG.sessionsCreate||typeof m.encodeEnvelope!=='function'){console.error('FATAL: relay-protocol dist is missing runtime exports — the barrel (export *) was likely tree-shaken to empty. Do NOT ship this build.');process.exit(1)}console.log('relay-protocol dist exports OK')\"",
51
53
  "build:relay-protocol": "bun run clean:relay-protocol && bun build ./packages/relay-protocol/src/index.ts --outdir ./packages/relay-protocol/dist --target node && tsc -p packages/relay-protocol/tsconfig.json && bun run assert:relay-protocol",
52
54
  "clean:relay": "node -e \"require('node:fs').rmSync('packages/relay/dist',{recursive:true,force:true})\"",
@@ -58,13 +60,14 @@
58
60
  "clean:relay-web": "node -e \"require('node:fs').rmSync('packages/relay-web/dist',{recursive:true,force:true})\"",
59
61
  "build:relay-web": "bun run build:relay-protocol && bun run clean:relay-web && bun run --cwd packages/relay-web build",
60
62
  "test:web": "bun run --cwd packages/relay-web test",
61
- "build:packages": "bun run build && bun run build:channel-yuanbao && bun run build:channel-feishu && bun run build:relay-protocol && bun run build:relay && bun run build:channel-relay",
63
+ "build:packages": "bun run build && bun run build:channel-yuanbao && bun run build:channel-feishu && bun run build:channel-discord && bun run build:relay-protocol && bun run build:relay && bun run build:channel-relay",
62
64
  "verify:publish": "bun run build:packages && node ./scripts/verify-publish.mjs",
63
65
  "publish:xacpx": "bun publish --cwd .",
64
66
  "publish:weacpx-compat": "bun publish --cwd weacpx-compat --access public",
65
67
  "publish:channel-feishu": "bun publish --cwd packages/channel-feishu --access public",
66
68
  "publish:channel-yuanbao": "bun publish --cwd packages/channel-yuanbao --access public",
67
- "publish:plugins": "bun run publish:channel-yuanbao && bun run publish:channel-feishu",
69
+ "publish:channel-discord": "bun publish --cwd packages/channel-discord --access public",
70
+ "publish:plugins": "bun run publish:channel-yuanbao && bun run publish:channel-feishu && bun run publish:channel-discord",
68
71
  "publish:relay-protocol": "bun publish --cwd packages/relay-protocol --access public",
69
72
  "publish:relay": "bun publish --cwd packages/relay --access public",
70
73
  "publish:channel-relay": "bun publish --cwd packages/channel-relay --access public",
@@ -114,6 +117,7 @@
114
117
  "@types/write-file-atomic": "^4.0.3",
115
118
  "@types/ws": "^8.18.1",
116
119
  "bun-types": "^1.3.11",
120
+ "discord.js": "^14.26.0",
117
121
  "typescript": "^6.0.2"
118
122
  }
119
123
  }