@ganglion/xacpx 0.9.1 → 0.9.3

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
@@ -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
@@ -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.`,
@@ -1615,7 +1618,6 @@ var init_cli_update = __esm(() => {
1615
1618
  updateFailed: (name, error) => `${name} update failed: ${error}`,
1616
1619
  targetNotFound: (name) => `Update target not found: ${name}`,
1617
1620
  targetVersionUnknown: (name) => `${name}: cannot check latest version; skipped.`,
1618
- targetNotPinned: (name) => `${name} has no recorded version; use \`xacpx plugin update ${name}\` or specify a version explicitly.`,
1619
1621
  multiTargetNonInteractive: "Installed plugins detected; in non-interactive mode use `xacpx update --all` or `xacpx update <name>`.",
1620
1622
  selectionPrompt: "Select items to update (numbers, comma-separated; a=all; Enter to cancel): ",
1621
1623
  selectionInvalid: (part) => `Invalid selection: ${part}`,
@@ -1646,6 +1648,8 @@ var init_channel_cli = __esm(() => {
1646
1648
  channelRemoved: (id) => `Channel ${id} removed`,
1647
1649
  cannotDisableLastEnabled: "Cannot disable the last enabled channel.",
1648
1650
  channelEnabledToggled: (id, enabled) => `Channel ${id} ${enabled ? "enabled" : "disabled"}`,
1651
+ channelReplyModeSet: (id, mode) => `Channel ${id} default reply mode set to: ${mode}`,
1652
+ channelReplyModeInvalid: (mode) => `reply mode must be stream / final / verbose, got: ${mode}`,
1649
1653
  channelAccountAlreadyExists: (type, accountId) => `Account ${accountId} already exists on channel ${type}; run xacpx channel rm ${type} --account ${accountId} first`,
1650
1654
  channelAccountAdded: (type, accountId) => `Channel ${type} account ${accountId} added`,
1651
1655
  channelReEnabled: (type) => `Channel ${type} was disabled; it has been automatically re-enabled.`,
@@ -1946,10 +1950,11 @@ var init_session2 = __esm(() => {
1946
1950
  replyModeHeader: "当前 reply mode:",
1947
1951
  replyModeSessionLabel: (alias) => `- 会话:${alias}`,
1948
1952
  replyModeGlobalDefault: (value) => `- 全局默认:${value}`,
1953
+ replyModeChannelDefault: (value) => `- 频道默认:${value}`,
1949
1954
  replyModeSessionOverride: (value) => `- 当前会话覆盖:${value}`,
1950
1955
  replyModeEffective: (value) => `- 当前生效:${value}`,
1951
1956
  replyModeSet: (replyMode) => `已设置当前会话 reply mode:${replyMode}`,
1952
- replyModeReset: (globalDefault) => `已重置当前会话 reply mode,当前回退到全局默认:${globalDefault}`,
1957
+ replyModeReset: (effective) => `已重置当前会话 reply mode,当前生效:${effective}`,
1953
1958
  statusHeader: "当前会话:",
1954
1959
  statusNameLabel: (alias) => `- 名称:${alias}`,
1955
1960
  statusAgentLabel: (agent2) => `- Agent:${agent2}`,
@@ -2492,6 +2497,8 @@ var init_config2 = __esm(() => {
2492
2497
  mustBePositiveNumber: (path2) => `${path2} 必须是正数。`,
2493
2498
  channelTypeDisabled: "channel.type 是旧单频道字段,/config set 已禁用写入;请使用 `xacpx channel ...` 管理 channels[],然后重启 xacpx。",
2494
2499
  channelReplyModeInvalid: "channel.replyMode 只支持:stream、final、verbose",
2500
+ channelRuntimeNotFound: (id) => `频道「${id}」不存在;请先用 \`xacpx channel add ${id}\` 添加。`,
2501
+ channelRuntimeReplyModeInvalid: (id) => `channels.${id}.replyMode 只支持:stream、final、verbose`,
2495
2502
  wechatReplyModeInvalid: "wechat.replyMode 只支持:stream、final、verbose",
2496
2503
  wechatReplyModeMapped: (value) => `${value}(已映射到 channel.replyMode)`,
2497
2504
  agentNotFound: (name) => `Agent「${name}」不存在,请先创建。`,
@@ -2681,7 +2688,6 @@ var init_cli_update2 = __esm(() => {
2681
2688
  updateFailed: (name, error) => `${name} 更新失败:${error}`,
2682
2689
  targetNotFound: (name) => `没有找到更新项:${name}`,
2683
2690
  targetVersionUnknown: (name) => `${name} 无法检查最新版本,已跳过。`,
2684
- targetNotPinned: (name) => `${name} 未记录当前版本;请先使用 \`xacpx plugin update ${name}\` 或显式选择版本。`,
2685
2691
  multiTargetNonInteractive: "检测到已安装插件;非交互模式请使用 `xacpx update --all` 或 `xacpx update <name>`。",
2686
2692
  selectionPrompt: "请选择要更新的项目(数字,逗号分隔,a=全部,回车取消):",
2687
2693
  selectionInvalid: (part) => `无效选择:${part}`,
@@ -2712,6 +2718,8 @@ var init_channel_cli2 = __esm(() => {
2712
2718
  channelRemoved: (id) => `频道 ${id} 已删除`,
2713
2719
  cannotDisableLastEnabled: "不能禁用最后一个启用的频道。",
2714
2720
  channelEnabledToggled: (id, enabled) => `频道 ${id} 已${enabled ? "启用" : "禁用"}`,
2721
+ channelReplyModeSet: (id, mode) => `频道 ${id} 的默认 reply mode 已设置为:${mode}`,
2722
+ channelReplyModeInvalid: (mode) => `reply mode 只支持 stream / final / verbose,收到:${mode}`,
2715
2723
  channelAccountAlreadyExists: (type, accountId) => `频道 ${type} 的账号 ${accountId} 已存在;先 xacpx channel rm ${type} --account ${accountId}`,
2716
2724
  channelAccountAdded: (type, accountId) => `频道 ${type} 账号 ${accountId} 已添加`,
2717
2725
  channelReEnabled: (type) => `频道 ${type} 此前是 disabled 状态,已自动启用。`,
@@ -3030,10 +3038,10 @@ import { spawn as spawn3 } from "node:child_process";
3030
3038
  import { readFile, unlink } from "node:fs/promises";
3031
3039
  import { homedir as homedir2 } from "node:os";
3032
3040
  import { join as join2 } from "node:path";
3033
- function buildWeacpxMcpServerSpec(input) {
3034
- const { command, args } = splitCommandLine(input.weacpxCommand);
3041
+ function buildXacpxMcpServerSpec(input) {
3042
+ const { command, args } = splitCommandLine(input.xacpxCommand);
3035
3043
  return {
3036
- name: "weacpx",
3044
+ name: "xacpx",
3037
3045
  type: "stdio",
3038
3046
  command,
3039
3047
  args: [
@@ -3060,7 +3068,7 @@ function buildQueueOwnerPayload(input) {
3060
3068
 
3061
3069
  class AcpxQueueOwnerLauncher {
3062
3070
  acpxCommand;
3063
- weacpxCommand;
3071
+ xacpxCommand;
3064
3072
  spawnOwner;
3065
3073
  terminateOwner;
3066
3074
  baseEnv;
@@ -3069,7 +3077,7 @@ class AcpxQueueOwnerLauncher {
3069
3077
  launchLocks = new Map;
3070
3078
  constructor(options) {
3071
3079
  this.acpxCommand = options.acpxCommand;
3072
- this.weacpxCommand = options.weacpxCommand ?? resolveDefaultWeacpxCommand(options.baseEnv ?? process.env);
3080
+ this.xacpxCommand = options.xacpxCommand ?? resolveDefaultXacpxCommand(options.baseEnv ?? process.env);
3073
3081
  this.spawnOwner = options.spawnOwner ?? defaultQueueOwnerSpawner;
3074
3082
  this.terminateOwner = options.terminateOwner ?? createDefaultQueueOwnerTerminator(options.acpxCommand);
3075
3083
  this.baseEnv = options.baseEnv ?? process.env;
@@ -3097,8 +3105,8 @@ class AcpxQueueOwnerLauncher {
3097
3105
  nonInteractivePermissions: input.nonInteractivePermissions,
3098
3106
  ttlMs: this.ttlMs,
3099
3107
  maxQueueDepth: this.maxQueueDepth,
3100
- mcpServers: [buildWeacpxMcpServerSpec({
3101
- weacpxCommand: this.weacpxCommand,
3108
+ mcpServers: [buildXacpxMcpServerSpec({
3109
+ xacpxCommand: this.xacpxCommand,
3102
3110
  coordinatorSession: input.coordinatorSession,
3103
3111
  ...input.sourceHandle ? { sourceHandle: input.sourceHandle } : {}
3104
3112
  })]
@@ -3153,13 +3161,13 @@ function splitCommandLine(value) {
3153
3161
  current += "\\";
3154
3162
  }
3155
3163
  if (quote) {
3156
- throw new Error("weacpx MCP command has an unterminated quote");
3164
+ throw new Error("xacpx MCP command has an unterminated quote");
3157
3165
  }
3158
3166
  if (current.length > 0) {
3159
3167
  parts.push(current);
3160
3168
  }
3161
3169
  if (parts.length === 0) {
3162
- throw new Error("weacpx MCP command must not be empty");
3170
+ throw new Error("xacpx MCP command must not be empty");
3163
3171
  }
3164
3172
  return { command: parts[0], args: parts.slice(1) };
3165
3173
  }
@@ -3205,7 +3213,7 @@ function queueLockFilePath(sessionId) {
3205
3213
  function shortHash(value, length) {
3206
3214
  return createHash("sha256").update(value).digest("hex").slice(0, length);
3207
3215
  }
3208
- function resolveDefaultWeacpxCommand(env) {
3216
+ function resolveDefaultXacpxCommand(env) {
3209
3217
  const cliCommand = coreEnv("CLI_COMMAND", env);
3210
3218
  if (cliCommand?.trim()) {
3211
3219
  return cliCommand.trim();
@@ -3641,8 +3649,9 @@ class BridgeRuntime {
3641
3649
  } else if (typeof parsed.id === "string") {
3642
3650
  acpxRecordId = parsed.id;
3643
3651
  }
3652
+ const agentSessionId = typeof parsed.agentSessionId === "string" ? parsed.agentSessionId : undefined;
3644
3653
  if (acpxRecordId) {
3645
- return { acpxRecordId };
3654
+ return { acpxRecordId, agentSessionId };
3646
3655
  }
3647
3656
  } catch {
3648
3657
  const firstLine = result.stdout.trim().split(/\r?\n/, 1)[0];
@@ -3652,6 +3661,10 @@ class BridgeRuntime {
3652
3661
  }
3653
3662
  throw new Error("failed to resolve acpx session record id");
3654
3663
  }
3664
+ async getAgentSessionId(input) {
3665
+ const record = await this.readSessionRecord(input);
3666
+ return { agentSessionId: record.agentSessionId };
3667
+ }
3655
3668
  async setMode(input) {
3656
3669
  const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
3657
3670
  "set-mode",
@@ -3919,7 +3932,8 @@ var BRIDGE_METHODS = new Set([
3919
3932
  "prompt",
3920
3933
  "setMode",
3921
3934
  "cancel",
3922
- "removeSession"
3935
+ "removeSession",
3936
+ "getAgentSessionId"
3923
3937
  ]);
3924
3938
  var SESSION_SCOPED_METHODS = new Set([
3925
3939
  "hasSession",
@@ -3929,7 +3943,8 @@ var SESSION_SCOPED_METHODS = new Set([
3929
3943
  "prompt",
3930
3944
  "setMode",
3931
3945
  "cancel",
3932
- "removeSession"
3946
+ "removeSession",
3947
+ "getAgentSessionId"
3933
3948
  ]);
3934
3949
 
3935
3950
  class BridgeServer {
@@ -4105,6 +4120,13 @@ class BridgeServer {
4105
4120
  cwd: requireString(params, "cwd"),
4106
4121
  name: requireString(params, "name")
4107
4122
  });
4123
+ case "getAgentSessionId":
4124
+ return await this.runtime.getAgentSessionId({
4125
+ agent: requireString(params, "agent"),
4126
+ agentCommand: asOptionalString(params.agentCommand),
4127
+ cwd: requireString(params, "cwd"),
4128
+ name: requireString(params, "name")
4129
+ });
4108
4130
  default:
4109
4131
  throw new Error(`unsupported bridge method: ${method}`);
4110
4132
  }