@ganglion/xacpx 0.9.0 → 0.9.2

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
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@ganglion/xacpx?style=flat-square)](https://www.npmjs.com/package/@ganglion/xacpx)
6
6
  [![Node.js Version](https://img.shields.io/node/v/@ganglion/xacpx?style=flat-square)](https://nodejs.org)
7
- [![zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat-square&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS42MDE1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/gadzan/weacpx)
7
+ [![zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat-square&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS42MDE1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/gadzan/xacpx)
8
8
  [![License](https://img.shields.io/npm/l/@ganglion/xacpx?style=flat-square)](./LICENSE)
9
9
 
10
10
  English · **[中文](./docs/zh/README_zh.md)**
@@ -13,6 +13,8 @@ English · **[中文](./docs/zh/README_zh.md)**
13
13
 
14
14
  `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:
15
15
 
16
+ [![xacpx.png](https://s41.ax1x.com/2026/06/05/pmZXIv6.png)](https://imgchr.com/i/pmZXIv6)
17
+
16
18
  - Create and switch between sessions
17
19
  - Have the agent keep working in a specific project directory
18
20
  - View streaming replies, final results, and tool-call summaries
@@ -281,7 +283,7 @@ Notes:
281
283
  - In a non-interactive environment, updating the core or plugins needs explicit confirmation: use `xacpx update --all`, or name the target with `xacpx update <name>`.
282
284
  - `update` covers the core package and channel plugins; to manage a single plugin's version directly, see `xacpx plugin update <name>` ([docs/plugin-development.md](./docs/plugin-development.md)).
283
285
  - After updating, run `xacpx restart` so a running daemon loads the new version.
284
- - Cross-package rename migration: once the project's renamed successor package is published, running `weacpx update` will offer to migrate you across to it automatically (you confirm the switch). Until then this is dormant and `update` behaves as a normal self-update.
286
+ - Cross-package rename migration: this project was renamed `weacpx` → `xacpx`. If you still have the legacy `weacpx` package installed, running `weacpx update` will offer to migrate you across to `xacpx` automatically (you confirm the switch). Already on `xacpx`? Just use `xacpx update` as a normal self-update.
285
287
 
286
288
  ## Common chat commands
287
289
 
@@ -427,7 +429,7 @@ Notes:
427
429
 
428
430
  If you want to first understand when to delegate and when to dispatch multiple subtasks in parallel, see:
429
431
 
430
- - [docs/weacpx-group-usage-guide.md](./docs/weacpx-group-usage-guide.md)
432
+ - [docs/xacpx-group-usage-guide.md](./docs/xacpx-group-usage-guide.md)
431
433
 
432
434
 
433
435
  ### MCP integration: external coordinator
@@ -579,7 +581,7 @@ If what you're about to do is one of the following, you can continue from here:
579
581
 
580
582
  - Want the full chat-command reference: [docs/commands.md](./docs/commands.md)
581
583
  - Want to schedule a one-time future message with scheduled tasks (`/later`): [docs/later-command.md](./docs/later-command.md)
582
- - Want to understand when to delegate and when to open a group: [docs/weacpx-group-usage-guide.md](./docs/weacpx-group-usage-guide.md)
584
+ - Want to understand when to delegate and when to open a group: [docs/xacpx-group-usage-guide.md](./docs/xacpx-group-usage-guide.md)
583
585
 
584
586
  ### Troubleshooting and verification
585
587
 
@@ -880,10 +880,11 @@ var init_session = __esm(() => {
880
880
  replyModeHeader: "Current reply mode:",
881
881
  replyModeSessionLabel: (alias) => `- Session: ${alias}`,
882
882
  replyModeGlobalDefault: (value) => `- Global default: ${value}`,
883
+ replyModeChannelDefault: (value) => `- Channel default: ${value}`,
883
884
  replyModeSessionOverride: (value) => `- Session override: ${value}`,
884
885
  replyModeEffective: (value) => `- Effective: ${value}`,
885
886
  replyModeSet: (replyMode) => `Current session reply mode set to: ${replyMode}`,
886
- replyModeReset: (globalDefault) => `Session reply mode reset. Falling back to global default: ${globalDefault}`,
887
+ replyModeReset: (effective) => `Session reply mode reset. Now effective: ${effective}`,
887
888
  statusHeader: "Current session:",
888
889
  statusNameLabel: (alias) => `- Name: ${alias}`,
889
890
  statusAgentLabel: (agent) => `- Agent: ${agent}`,
@@ -1426,6 +1427,8 @@ var init_config = __esm(() => {
1426
1427
  mustBePositiveNumber: (path2) => `${path2} must be a positive number.`,
1427
1428
  channelTypeDisabled: "channel.type is a legacy single-channel field; /config set writes are disabled. Use `xacpx channel ...` to manage channels[], then restart xacpx.",
1428
1429
  channelReplyModeInvalid: "channel.replyMode only supports: stream, final, verbose",
1430
+ channelRuntimeNotFound: (id) => `Channel "${id}" does not exist; add it first with \`xacpx channel add ${id}\`.`,
1431
+ channelRuntimeReplyModeInvalid: (id) => `channels.${id}.replyMode only supports: stream, final, verbose`,
1429
1432
  wechatReplyModeInvalid: "wechat.replyMode only supports: stream, final, verbose",
1430
1433
  wechatReplyModeMapped: (value) => `${value} (mapped to channel.replyMode)`,
1431
1434
  agentNotFound: (name) => `Agent "${name}" does not exist. Create it first.`,
@@ -1646,6 +1649,8 @@ var init_channel_cli = __esm(() => {
1646
1649
  channelRemoved: (id) => `Channel ${id} removed`,
1647
1650
  cannotDisableLastEnabled: "Cannot disable the last enabled channel.",
1648
1651
  channelEnabledToggled: (id, enabled) => `Channel ${id} ${enabled ? "enabled" : "disabled"}`,
1652
+ channelReplyModeSet: (id, mode) => `Channel ${id} default reply mode set to: ${mode}`,
1653
+ channelReplyModeInvalid: (mode) => `reply mode must be stream / final / verbose, got: ${mode}`,
1649
1654
  channelAccountAlreadyExists: (type, accountId) => `Account ${accountId} already exists on channel ${type}; run xacpx channel rm ${type} --account ${accountId} first`,
1650
1655
  channelAccountAdded: (type, accountId) => `Channel ${type} account ${accountId} added`,
1651
1656
  channelReEnabled: (type) => `Channel ${type} was disabled; it has been automatically re-enabled.`,
@@ -1946,10 +1951,11 @@ var init_session2 = __esm(() => {
1946
1951
  replyModeHeader: "当前 reply mode:",
1947
1952
  replyModeSessionLabel: (alias) => `- 会话:${alias}`,
1948
1953
  replyModeGlobalDefault: (value) => `- 全局默认:${value}`,
1954
+ replyModeChannelDefault: (value) => `- 频道默认:${value}`,
1949
1955
  replyModeSessionOverride: (value) => `- 当前会话覆盖:${value}`,
1950
1956
  replyModeEffective: (value) => `- 当前生效:${value}`,
1951
1957
  replyModeSet: (replyMode) => `已设置当前会话 reply mode:${replyMode}`,
1952
- replyModeReset: (globalDefault) => `已重置当前会话 reply mode,当前回退到全局默认:${globalDefault}`,
1958
+ replyModeReset: (effective) => `已重置当前会话 reply mode,当前生效:${effective}`,
1953
1959
  statusHeader: "当前会话:",
1954
1960
  statusNameLabel: (alias) => `- 名称:${alias}`,
1955
1961
  statusAgentLabel: (agent2) => `- Agent:${agent2}`,
@@ -2492,6 +2498,8 @@ var init_config2 = __esm(() => {
2492
2498
  mustBePositiveNumber: (path2) => `${path2} 必须是正数。`,
2493
2499
  channelTypeDisabled: "channel.type 是旧单频道字段,/config set 已禁用写入;请使用 `xacpx channel ...` 管理 channels[],然后重启 xacpx。",
2494
2500
  channelReplyModeInvalid: "channel.replyMode 只支持:stream、final、verbose",
2501
+ channelRuntimeNotFound: (id) => `频道「${id}」不存在;请先用 \`xacpx channel add ${id}\` 添加。`,
2502
+ channelRuntimeReplyModeInvalid: (id) => `channels.${id}.replyMode 只支持:stream、final、verbose`,
2495
2503
  wechatReplyModeInvalid: "wechat.replyMode 只支持:stream、final、verbose",
2496
2504
  wechatReplyModeMapped: (value) => `${value}(已映射到 channel.replyMode)`,
2497
2505
  agentNotFound: (name) => `Agent「${name}」不存在,请先创建。`,
@@ -2712,6 +2720,8 @@ var init_channel_cli2 = __esm(() => {
2712
2720
  channelRemoved: (id) => `频道 ${id} 已删除`,
2713
2721
  cannotDisableLastEnabled: "不能禁用最后一个启用的频道。",
2714
2722
  channelEnabledToggled: (id, enabled) => `频道 ${id} 已${enabled ? "启用" : "禁用"}`,
2723
+ channelReplyModeSet: (id, mode) => `频道 ${id} 的默认 reply mode 已设置为:${mode}`,
2724
+ channelReplyModeInvalid: (mode) => `reply mode 只支持 stream / final / verbose,收到:${mode}`,
2715
2725
  channelAccountAlreadyExists: (type, accountId) => `频道 ${type} 的账号 ${accountId} 已存在;先 xacpx channel rm ${type} --account ${accountId}`,
2716
2726
  channelAccountAdded: (type, accountId) => `频道 ${type} 账号 ${accountId} 已添加`,
2717
2727
  channelReEnabled: (type) => `频道 ${type} 此前是 disabled 状态,已自动启用。`,
@@ -2976,12 +2986,22 @@ var init_zh = __esm(() => {
2976
2986
  function isLocale(value) {
2977
2987
  return typeof value === "string" && VALID.includes(value);
2978
2988
  }
2989
+ function detectSystemLocale() {
2990
+ try {
2991
+ return Intl.DateTimeFormat().resolvedOptions().locale || "";
2992
+ } catch {
2993
+ return "";
2994
+ }
2995
+ }
2979
2996
  function resolveLocale(input = {}) {
2980
2997
  const { configLanguage, env = process.env } = input;
2981
2998
  if (isLocale(configLanguage))
2982
2999
  return configLanguage;
2983
3000
  const raw = env.LC_ALL || env.LC_MESSAGES || env.LANG || "";
2984
- return /^zh/i.test(raw) ? "zh" : "en";
3001
+ if (raw)
3002
+ return /^zh/i.test(raw) ? "zh" : "en";
3003
+ const systemLocale = input.systemLocale ?? detectSystemLocale();
3004
+ return /^zh/i.test(systemLocale) ? "zh" : "en";
2985
3005
  }
2986
3006
  var VALID;
2987
3007
  var init_resolve_locale = __esm(() => {
@@ -3020,10 +3040,10 @@ import { spawn as spawn3 } from "node:child_process";
3020
3040
  import { readFile, unlink } from "node:fs/promises";
3021
3041
  import { homedir as homedir2 } from "node:os";
3022
3042
  import { join as join2 } from "node:path";
3023
- function buildWeacpxMcpServerSpec(input) {
3024
- const { command, args } = splitCommandLine(input.weacpxCommand);
3043
+ function buildXacpxMcpServerSpec(input) {
3044
+ const { command, args } = splitCommandLine(input.xacpxCommand);
3025
3045
  return {
3026
- name: "weacpx",
3046
+ name: "xacpx",
3027
3047
  type: "stdio",
3028
3048
  command,
3029
3049
  args: [
@@ -3050,7 +3070,7 @@ function buildQueueOwnerPayload(input) {
3050
3070
 
3051
3071
  class AcpxQueueOwnerLauncher {
3052
3072
  acpxCommand;
3053
- weacpxCommand;
3073
+ xacpxCommand;
3054
3074
  spawnOwner;
3055
3075
  terminateOwner;
3056
3076
  baseEnv;
@@ -3059,7 +3079,7 @@ class AcpxQueueOwnerLauncher {
3059
3079
  launchLocks = new Map;
3060
3080
  constructor(options) {
3061
3081
  this.acpxCommand = options.acpxCommand;
3062
- this.weacpxCommand = options.weacpxCommand ?? resolveDefaultWeacpxCommand(options.baseEnv ?? process.env);
3082
+ this.xacpxCommand = options.xacpxCommand ?? resolveDefaultXacpxCommand(options.baseEnv ?? process.env);
3063
3083
  this.spawnOwner = options.spawnOwner ?? defaultQueueOwnerSpawner;
3064
3084
  this.terminateOwner = options.terminateOwner ?? createDefaultQueueOwnerTerminator(options.acpxCommand);
3065
3085
  this.baseEnv = options.baseEnv ?? process.env;
@@ -3087,8 +3107,8 @@ class AcpxQueueOwnerLauncher {
3087
3107
  nonInteractivePermissions: input.nonInteractivePermissions,
3088
3108
  ttlMs: this.ttlMs,
3089
3109
  maxQueueDepth: this.maxQueueDepth,
3090
- mcpServers: [buildWeacpxMcpServerSpec({
3091
- weacpxCommand: this.weacpxCommand,
3110
+ mcpServers: [buildXacpxMcpServerSpec({
3111
+ xacpxCommand: this.xacpxCommand,
3092
3112
  coordinatorSession: input.coordinatorSession,
3093
3113
  ...input.sourceHandle ? { sourceHandle: input.sourceHandle } : {}
3094
3114
  })]
@@ -3143,13 +3163,13 @@ function splitCommandLine(value) {
3143
3163
  current += "\\";
3144
3164
  }
3145
3165
  if (quote) {
3146
- throw new Error("weacpx MCP command has an unterminated quote");
3166
+ throw new Error("xacpx MCP command has an unterminated quote");
3147
3167
  }
3148
3168
  if (current.length > 0) {
3149
3169
  parts.push(current);
3150
3170
  }
3151
3171
  if (parts.length === 0) {
3152
- throw new Error("weacpx MCP command must not be empty");
3172
+ throw new Error("xacpx MCP command must not be empty");
3153
3173
  }
3154
3174
  return { command: parts[0], args: parts.slice(1) };
3155
3175
  }
@@ -3195,7 +3215,7 @@ function queueLockFilePath(sessionId) {
3195
3215
  function shortHash(value, length) {
3196
3216
  return createHash("sha256").update(value).digest("hex").slice(0, length);
3197
3217
  }
3198
- function resolveDefaultWeacpxCommand(env) {
3218
+ function resolveDefaultXacpxCommand(env) {
3199
3219
  const cliCommand = coreEnv("CLI_COMMAND", env);
3200
3220
  if (cliCommand?.trim()) {
3201
3221
  return cliCommand.trim();
@@ -3631,8 +3651,9 @@ class BridgeRuntime {
3631
3651
  } else if (typeof parsed.id === "string") {
3632
3652
  acpxRecordId = parsed.id;
3633
3653
  }
3654
+ const agentSessionId = typeof parsed.agentSessionId === "string" ? parsed.agentSessionId : undefined;
3634
3655
  if (acpxRecordId) {
3635
- return { acpxRecordId };
3656
+ return { acpxRecordId, agentSessionId };
3636
3657
  }
3637
3658
  } catch {
3638
3659
  const firstLine = result.stdout.trim().split(/\r?\n/, 1)[0];
@@ -3642,6 +3663,10 @@ class BridgeRuntime {
3642
3663
  }
3643
3664
  throw new Error("failed to resolve acpx session record id");
3644
3665
  }
3666
+ async getAgentSessionId(input) {
3667
+ const record = await this.readSessionRecord(input);
3668
+ return { agentSessionId: record.agentSessionId };
3669
+ }
3645
3670
  async setMode(input) {
3646
3671
  const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
3647
3672
  "set-mode",
@@ -3909,7 +3934,8 @@ var BRIDGE_METHODS = new Set([
3909
3934
  "prompt",
3910
3935
  "setMode",
3911
3936
  "cancel",
3912
- "removeSession"
3937
+ "removeSession",
3938
+ "getAgentSessionId"
3913
3939
  ]);
3914
3940
  var SESSION_SCOPED_METHODS = new Set([
3915
3941
  "hasSession",
@@ -3919,7 +3945,8 @@ var SESSION_SCOPED_METHODS = new Set([
3919
3945
  "prompt",
3920
3946
  "setMode",
3921
3947
  "cancel",
3922
- "removeSession"
3948
+ "removeSession",
3949
+ "getAgentSessionId"
3923
3950
  ]);
3924
3951
 
3925
3952
  class BridgeServer {
@@ -4095,6 +4122,13 @@ class BridgeServer {
4095
4122
  cwd: requireString(params, "cwd"),
4096
4123
  name: requireString(params, "name")
4097
4124
  });
4125
+ case "getAgentSessionId":
4126
+ return await this.runtime.getAgentSessionId({
4127
+ agent: requireString(params, "agent"),
4128
+ agentCommand: asOptionalString(params.agentCommand),
4129
+ cwd: requireString(params, "cwd"),
4130
+ name: requireString(params, "name")
4131
+ });
4098
4132
  default:
4099
4133
  throw new Error(`unsupported bridge method: ${method}`);
4100
4134
  }