@ganglion/xacpx 0.23.0 → 0.24.0-beta.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 +15 -8
- package/dist/bridge/bridge-main.js +67 -56
- package/dist/cli.js +186 -16
- package/dist/i18n/types.d.ts +4 -0
- package/dist/plugin-api.d.ts +1 -0
- package/dist/plugin-api.js +27 -2
- package/dist/transport/acpx-queue-overflow.d.ts +22 -0
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
> Remotely drive Codex, Claude Code, and other acpx sessions from WeChat, Feishu, or
|
|
5
|
+
> Remotely drive Codex, Claude Code, and other acpx sessions from WeChat, Feishu, Yuanbao, or Discord.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@ganglion/xacpx)
|
|
8
8
|
[](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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
8528
|
-
await
|
|
8529
|
-
|
|
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
|
-
|
|
8553
|
+
const held = acquiredChannelLocks;
|
|
8554
|
+
acquiredChannelLocks = [];
|
|
8555
|
+
for (const lock2 of held.reverse()) {
|
|
8540
8556
|
try {
|
|
8541
|
-
await
|
|
8557
|
+
await lock2.release();
|
|
8542
8558
|
} catch (error) {
|
|
8543
|
-
releaseError
|
|
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
|
-
|
|
66557
|
-
|
|
66558
|
-
onDiagnostic: async (event, context) => {
|
|
66559
|
-
await runtime.logger.info(`${
|
|
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
|
};
|
package/dist/i18n/types.d.ts
CHANGED
|
@@ -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;
|
package/dist/plugin-api.d.ts
CHANGED
|
@@ -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";
|
package/dist/plugin-api.js
CHANGED
|
@@ -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
|
|
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.
|
|
3
|
+
"version": "0.24.0-beta.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
|
-
"
|
|
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:
|
|
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
|
}
|