weacpx 0.1.5 → 0.1.6

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
@@ -155,6 +155,8 @@ bun run dev
155
155
  | `/ss attach <alias> -a <name> --ws <name> --name <transport-session>` | 恢复已存在的会话 |
156
156
  | `/use <alias>` | 切换当前会话 |
157
157
  | `/status` | 查看当前会话状态 |
158
+ | `/mode` | 查看当前会话已保存的 mode |
159
+ | `/mode <id>` | 设置当前会话 mode,例如 `/mode plan` |
158
160
  | `/session reset` | 重置当前会话上下文,保留 alias/agent/workspace,但重新绑定到一个新的后端 session |
159
161
  | `/clear` | `/session reset` 的快捷别名 |
160
162
  | `/cancel` | 取消当前会话 |
@@ -165,6 +167,8 @@ bun run dev
165
167
  - `/ss <agent> -d <path>` 是最常用入口,会自动按目录名推导并创建或复用 workspace,再创建或复用 session
166
168
  - `/ss new <agent> -d <path>` 表示强制新建 session
167
169
  - `/use <alias>` 用来切换当前会话
170
+ - `/mode` 会显示当前逻辑会话里保存的 mode;如果还没设置过,会显示“未设置”
171
+ - `/mode <id>` 会把 mode 透传给底层 `acpx set-mode`,成功后再写回当前逻辑会话
168
172
  - `/session reset` 和 `/clear` 会保留当前逻辑会话名,但重新创建一个新的后端 session,从空上下文重新开始
169
173
  - 非 `/` 开头的文本会发送到当前 session
170
174
 
@@ -330,6 +334,26 @@ bun run dry-run --chat-key wx:test -- \
330
334
  /ss attach demo -a codex --ws backend --name existing-demo
331
335
  ```
332
336
 
337
+ ### Adapter mode 参考
338
+
339
+ `acpx set-mode` / 计划中的 `/mode <id>` 本质上都是给底层 ACP session 发送 `session/set_mode`。
340
+ 这里的 `<id>` 不是 `weacpx` 或 `acpx` 统一规定的枚举,而是**各 adapter 自己定义**的值;填错时通常会收到 adapter 返回的 `Invalid params` 一类错误。
341
+
342
+ 基于 `acpx` 内置 adapter 文档和各上游公开文档,当前能确认的信息如下:
343
+
344
+ | adapter | 已确认可用的 mode id | 说明 |
345
+ |------|------|------|
346
+ | `codex` | `plan` | `acpx` 自身示例明确使用过 `acpx codex set-mode plan`。`codex-acp` 还暴露了 `mode` 运行时配置项,但上游目前没有公开一份完整、稳定的 mode id 列表。 |
347
+ | `cursor` | `agent`、`plan`、`ask` | Cursor 官方文档/更新日志公开提到 `Plan mode`、`Ask mode`;Cursor 官方论坛在 ACP `session/configure` 示例中展示过 `availableModes` 为 `agent` / `plan` / `ask`。 |
348
+ | 其他内置 adapter | 暂无公开、稳定的 mode id 列表 | 包括 `claude`、`copilot`、`gemini`、`qoder`、`qwen`、`kimi`、`kiro`、`iflow`、`opencode`、`trae`、`droid`、`kilocode` 等。即使某些产品本身有“Ask / Agent / Plan”之类概念,其 ACP `set-mode` 可接受的精确字符串也往往没有在官方文档中写死。 |
349
+
350
+ 建议:
351
+
352
+ - 对 `codex`,优先把 `plan` 当作已知可用值。
353
+ - 对 `cursor`,优先使用 `agent`、`plan`、`ask`。
354
+ - 对其他 adapter,不要在 `weacpx` 里写死候选值;最好把 `/mode <id>` 设计成透传,由 adapter 自己决定是否接受。
355
+ - 如果某个 adapter 后续补充了官方 mode 文档,再把它们补进这里。
356
+
333
357
  ## 更多文档
334
358
 
335
359
  - 配置参考:[docs/config-reference.md](./docs/config-reference.md)
@@ -1,7 +1,9 @@
1
1
  {
2
2
  "transport": {
3
3
  "type": "acpx-bridge",
4
- "sessionInitTimeoutMs": 120000
4
+ "sessionInitTimeoutMs": 120000,
5
+ "permissionMode": "approve-all",
6
+ "nonInteractivePermissions": "fail"
5
7
  },
6
8
  "logging": {
7
9
  "level": "info",
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
7
12
  var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
8
20
  target = mod != null ? __create(__getProtoOf(mod)) : {};
9
21
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
22
  for (let key of __getOwnPropNames(mod))
11
23
  if (!__hasOwnProp.call(to, key))
12
24
  __defProp(to, key, {
13
- get: () => mod[key],
25
+ get: __accessProp.bind(mod, key),
14
26
  enumerable: true
15
27
  });
28
+ if (canCache)
29
+ cache.set(mod, to);
16
30
  return to;
17
31
  };
18
32
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __returnValue = (v) => v;
34
+ function __exportSetter(name, newValue) {
35
+ this[name] = __returnValue.bind(null, newValue);
36
+ }
19
37
  var __export = (target, all) => {
20
38
  for (var name in all)
21
39
  __defProp(target, name, {
22
40
  get: all[name],
23
41
  enumerable: true,
24
42
  configurable: true,
25
- set: (newValue) => all[name] = () => newValue
43
+ set: __exportSetter.bind(all, name)
26
44
  });
27
45
  };
28
46
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -244,6 +262,14 @@ class BridgeServer {
244
262
  name: String(params.name),
245
263
  text: String(params.text)
246
264
  });
265
+ case "setMode":
266
+ return await this.runtime.setMode({
267
+ agent: String(params.agent),
268
+ agentCommand: asOptionalString(params.agentCommand),
269
+ cwd: String(params.cwd),
270
+ name: String(params.name),
271
+ modeId: String(params.modeId)
272
+ });
247
273
  case "cancel":
248
274
  return await this.runtime.cancel({
249
275
  agent: String(params.agent),
@@ -322,6 +348,19 @@ class BridgeRuntime {
322
348
  const result = await this.run(spawnSpec.command, spawnSpec.args);
323
349
  return { text: getPromptText(result) };
324
350
  }
351
+ async setMode(input) {
352
+ const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
353
+ "set-mode",
354
+ "-s",
355
+ input.name,
356
+ input.modeId
357
+ ]));
358
+ const result = await this.run(spawnSpec.command, spawnSpec.args);
359
+ if (result.code !== 0) {
360
+ throw new Error(result.stderr || result.stdout || "set-mode failed");
361
+ }
362
+ return {};
363
+ }
325
364
  async cancel(input) {
326
365
  const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
327
366
  "cancel",
package/dist/cli.js CHANGED
@@ -5,25 +5,43 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
+ var __returnValue = (v) => v;
35
+ function __exportSetter(name, newValue) {
36
+ this[name] = __returnValue.bind(null, newValue);
37
+ }
20
38
  var __export = (target, all) => {
21
39
  for (var name in all)
22
40
  __defProp(target, name, {
23
41
  get: all[name],
24
42
  enumerable: true,
25
43
  configurable: true,
26
- set: (newValue) => all[name] = () => newValue
44
+ set: __exportSetter.bind(all, name)
27
45
  });
28
46
  };
29
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -3338,26 +3356,41 @@ var init_agent_templates = __esm(() => {
3338
3356
  function renderHelpText() {
3339
3357
  return [
3340
3358
  "可用命令:",
3341
- "/agents",
3342
- "/agent add <codex|claude>",
3343
- "/agent rm <name>",
3344
- "/workspaces",
3345
- "/workspace /ws",
3346
- "/ws new <name> -d <path>",
3347
- "/workspace rm <name>",
3348
- "/sessions",
3349
- "/session /ss",
3350
- "/ss <agent> -d <path>",
3351
- "/ss new <agent> -d <path>",
3352
- "/ss new <alias> -a <name> --ws <name>",
3353
- "/ss attach <alias> -a <name> --ws <name> --name <transport-session>",
3354
- "/pm 或 /permission",
3355
- "/pm set <allow|read|deny>",
3356
- "/pm auto [allow|deny|fail]",
3357
- "/use <alias>",
3358
- "/status",
3359
- "/cancel /stop",
3360
- "/session reset 或 /clear"
3359
+ "",
3360
+ "先看这 3 个:",
3361
+ "/ss new <agent> -d <path> - 新建会话",
3362
+ "/use <alias> - 切会话",
3363
+ "/status - 看状态",
3364
+ "",
3365
+ "Agent:",
3366
+ "/agents - 看 Agent",
3367
+ "/agent add <codex|claude> - 加 Agent",
3368
+ "/agent rm <name> - 删 Agent",
3369
+ "",
3370
+ "工作区:",
3371
+ "/workspaces - 看工作区",
3372
+ "/workspace 或 /ws - 工作区命令",
3373
+ "/ws new <name> -d <path> - 加工作区",
3374
+ "/workspace rm <name> - 删工作区",
3375
+ "",
3376
+ "会话:",
3377
+ "/sessions - 看会话",
3378
+ "/session 或 /ss - 会话命令",
3379
+ "/ss <agent> -d <path> - 快速新建",
3380
+ "/ss new <agent> -d <path> - 新建会话",
3381
+ "/ss new <alias> -a <name> --ws <name> - 指定配置新建",
3382
+ "/ss attach <alias> -a <name> --ws <name> --name <transport-session> - 挂已有会话",
3383
+ "/use <alias> - 切会话",
3384
+ "/session reset 或 /clear - 清上下文",
3385
+ "",
3386
+ "权限:",
3387
+ "/pm 或 /permission - 权限设置",
3388
+ "/pm set <allow|read|deny> - 设审批级别",
3389
+ "/pm auto [allow|deny|fail] - 设自动处理",
3390
+ "",
3391
+ "常用:",
3392
+ "/status - 看状态",
3393
+ "/cancel 或 /stop - 停当前任务"
3361
3394
  ].join(`
3362
3395
  `);
3363
3396
  }
@@ -3498,6 +3531,27 @@ var init_app_logger = __esm(() => {
3498
3531
  };
3499
3532
  });
3500
3533
 
3534
+ // src/transport/acpx-session-index.ts
3535
+ import { readFile as readFile3 } from "node:fs/promises";
3536
+ import { homedir } from "node:os";
3537
+ import { resolve } from "node:path";
3538
+ async function resolveSessionAgentCommandFromIndex(session) {
3539
+ const home = process.env.HOME ?? homedir();
3540
+ if (!home) {
3541
+ return;
3542
+ }
3543
+ try {
3544
+ const raw = await readFile3(resolve(home, ".acpx", "sessions", "index.json"), "utf8");
3545
+ const parsed = JSON.parse(raw);
3546
+ const targetCwd = resolve(session.cwd);
3547
+ const match = parsed.entries?.find((entry) => entry.name === session.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
3548
+ return match?.agentCommand?.trim();
3549
+ } catch {
3550
+ return;
3551
+ }
3552
+ }
3553
+ var init_acpx_session_index = () => {};
3554
+
3501
3555
  // src/transport/prompt-output.ts
3502
3556
  function getPromptText(result) {
3503
3557
  const stdoutOutput = extractPromptOutput(result.stdout);
@@ -3652,6 +3706,8 @@ function parseCommand(input) {
3652
3706
  return { kind: "cancel" };
3653
3707
  if (command === "/clear")
3654
3708
  return { kind: "session.reset" };
3709
+ if (command === "/mode" && parts.length === 1)
3710
+ return { kind: "mode.show" };
3655
3711
  if (command === "/permission" && parts.length === 1)
3656
3712
  return { kind: "permission.status" };
3657
3713
  if (command === "/session" && parts.length === 1)
@@ -3678,6 +3734,9 @@ function parseCommand(input) {
3678
3734
  if (command === "/use" && parts[1]) {
3679
3735
  return { kind: "session.use", alias: parts[1] };
3680
3736
  }
3737
+ if (command === "/mode" && parts[1]) {
3738
+ return { kind: "mode.set", modeId: parts[1] };
3739
+ }
3681
3740
  if (command === "/agent" && parts[1] === "add" && parts[2]) {
3682
3741
  return { kind: "agent.add", template: parts[2] };
3683
3742
  }
@@ -3830,6 +3889,7 @@ var init_parse_command = __esm(() => {
3830
3889
  "/status",
3831
3890
  "/cancel",
3832
3891
  "/clear",
3892
+ "/mode",
3833
3893
  "/permission",
3834
3894
  "/session",
3835
3895
  "/workspace",
@@ -3841,19 +3901,21 @@ var init_parse_command = __esm(() => {
3841
3901
  // src/commands/command-router.ts
3842
3902
  import { access } from "node:fs/promises";
3843
3903
  import { basename as basename2, normalize } from "node:path";
3844
- import { homedir } from "node:os";
3904
+ import { homedir as homedir2 } from "node:os";
3845
3905
 
3846
3906
  class CommandRouter {
3847
3907
  sessions;
3848
3908
  transport;
3849
3909
  config;
3850
3910
  configStore;
3911
+ resolveSessionAgentCommand;
3851
3912
  logger;
3852
- constructor(sessions, transport, config, configStore, logger2) {
3913
+ constructor(sessions, transport, config, configStore, logger2, resolveSessionAgentCommand = resolveSessionAgentCommandFromIndex) {
3853
3914
  this.sessions = sessions;
3854
3915
  this.transport = transport;
3855
3916
  this.config = config;
3856
3917
  this.configStore = configStore;
3918
+ this.resolveSessionAgentCommand = resolveSessionAgentCommand;
3857
3919
  this.logger = logger2 ?? createNoopAppLogger();
3858
3920
  }
3859
3921
  async handle(chatKey, input, reply) {
@@ -3976,6 +4038,7 @@ class CommandRouter {
3976
4038
  return this.renderSessionCreationError(session, error);
3977
4039
  }
3978
4040
  await this.sessions.attachSession(command.alias, command.agent, command.workspace, session.transportSession);
4041
+ await this.refreshSessionTransportAgentCommand(command.alias);
3979
4042
  await this.sessions.useSession(chatKey, command.alias);
3980
4043
  await this.logger.info("session.created", "created and selected logical session", {
3981
4044
  alias: command.alias,
@@ -4001,6 +4064,7 @@ class CommandRouter {
4001
4064
  };
4002
4065
  }
4003
4066
  await this.sessions.attachSession(command.alias, command.agent, command.workspace, command.transportSession);
4067
+ await this.refreshSessionTransportAgentCommand(command.alias);
4004
4068
  await this.sessions.useSession(chatKey, command.alias);
4005
4069
  await this.logger.info("session.attached", "attached existing transport session", {
4006
4070
  alias: command.alias,
@@ -4017,6 +4081,29 @@ class CommandRouter {
4017
4081
  chatKey
4018
4082
  });
4019
4083
  return { text: `已切换到会话「${command.alias}」` };
4084
+ case "mode.show": {
4085
+ const session = await this.sessions.getCurrentSession(chatKey);
4086
+ if (!session) {
4087
+ return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
4088
+ }
4089
+ return {
4090
+ text: [
4091
+ "当前 mode:",
4092
+ `- 会话:${session.alias}`,
4093
+ `- mode:${session.modeId ?? "未设置"}`
4094
+ ].join(`
4095
+ `)
4096
+ };
4097
+ }
4098
+ case "mode.set": {
4099
+ const session = await this.sessions.getCurrentSession(chatKey);
4100
+ if (!session) {
4101
+ return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
4102
+ }
4103
+ await this.setModeTransportSession(session, command.modeId);
4104
+ await this.sessions.setCurrentSessionMode(chatKey, command.modeId);
4105
+ return { text: `已设置当前会话 mode:${command.modeId}` };
4106
+ }
4020
4107
  case "status": {
4021
4108
  const session = await this.sessions.getCurrentSession(chatKey);
4022
4109
  if (!session) {
@@ -4055,6 +4142,11 @@ class CommandRouter {
4055
4142
  const result = await this.promptTransportSession(session, command.text, reply);
4056
4143
  return { text: result.text };
4057
4144
  } catch (error) {
4145
+ const recovered = await this.tryRecoverMissingSession(session, error);
4146
+ if (recovered) {
4147
+ const result = await this.promptTransportSession(recovered, command.text, reply);
4148
+ return { text: result.text };
4149
+ }
4058
4150
  return this.renderTransportError(session, error);
4059
4151
  }
4060
4152
  }
@@ -4107,6 +4199,7 @@ class CommandRouter {
4107
4199
  return this.renderShortcutSessionCreationError(workspace, alias);
4108
4200
  }
4109
4201
  await this.sessions.attachSession(alias, agent, workspace.name, session.transportSession);
4202
+ await this.refreshSessionTransportAgentCommand(alias);
4110
4203
  await this.sessions.useSession(chatKey, alias);
4111
4204
  await this.logger.info("session.shortcut.created", "created new logical session from shortcut", {
4112
4205
  alias,
@@ -4274,6 +4367,7 @@ class CommandRouter {
4274
4367
  return this.renderTransportError(resetSession, error);
4275
4368
  }
4276
4369
  await this.sessions.attachSession(resetSession.alias, resetSession.agent, resetSession.workspace, resetSession.transportSession);
4370
+ await this.refreshSessionTransportAgentCommand(resetSession.alias);
4277
4371
  await this.sessions.useSession(chatKey, resetSession.alias);
4278
4372
  await this.logger.info("session.reset", "reset current logical session", {
4279
4373
  alias: resetSession.alias,
@@ -4293,9 +4387,35 @@ class CommandRouter {
4293
4387
  async promptTransportSession(session, text, reply) {
4294
4388
  return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply));
4295
4389
  }
4390
+ async setModeTransportSession(session, modeId) {
4391
+ return await this.measureTransportCall("set_mode", session, () => this.transport.setMode(session, modeId));
4392
+ }
4296
4393
  async cancelTransportSession(session) {
4297
4394
  return await this.measureTransportCall("cancel", session, () => this.transport.cancel(session));
4298
4395
  }
4396
+ async refreshSessionTransportAgentCommand(alias) {
4397
+ const session = await this.sessions.getSession(alias);
4398
+ if (!session) {
4399
+ return;
4400
+ }
4401
+ const transportAgentCommand = await this.resolveSessionAgentCommand(session);
4402
+ if (!transportAgentCommand) {
4403
+ return;
4404
+ }
4405
+ await this.sessions.setSessionTransportAgentCommand(alias, transportAgentCommand);
4406
+ }
4407
+ async tryRecoverMissingSession(session, error) {
4408
+ const message = error instanceof Error ? error.message : String(error);
4409
+ if (!message.includes("No acpx session found")) {
4410
+ return null;
4411
+ }
4412
+ const transportAgentCommand = await this.resolveSessionAgentCommand(session);
4413
+ if (!transportAgentCommand || transportAgentCommand === session.agentCommand) {
4414
+ return null;
4415
+ }
4416
+ await this.sessions.setSessionTransportAgentCommand(session.alias, transportAgentCommand);
4417
+ return await this.sessions.getSession(session.alias);
4418
+ }
4299
4419
  async measureTransportCall(operation, session, callback) {
4300
4420
  const startedAt = Date.now();
4301
4421
  try {
@@ -4342,7 +4462,7 @@ async function pathExists(path11) {
4342
4462
  }
4343
4463
  }
4344
4464
  function normalizePathForWorkspace(path11) {
4345
- const expanded = path11.startsWith("~") ? homedir() + path11.slice(1) : path11;
4465
+ const expanded = path11.startsWith("~") ? homedir2() + path11.slice(1) : path11;
4346
4466
  return normalize(expanded);
4347
4467
  }
4348
4468
  function sameWorkspacePath(left, right) {
@@ -4415,6 +4535,7 @@ function isPartialPromptOutputError(message) {
4415
4535
  var init_command_router = __esm(() => {
4416
4536
  init_agent_templates();
4417
4537
  init_app_logger();
4538
+ init_acpx_session_index();
4418
4539
  init_prompt_output();
4419
4540
  init_parse_command();
4420
4541
  });
@@ -4435,12 +4556,12 @@ function isLegacyCodexCommand(command) {
4435
4556
  }
4436
4557
 
4437
4558
  // src/config/load-config.ts
4438
- import { readFile as readFile3 } from "node:fs/promises";
4559
+ import { readFile as readFile4 } from "node:fs/promises";
4439
4560
  function isRecord(value) {
4440
4561
  return typeof value === "object" && value !== null;
4441
4562
  }
4442
4563
  async function loadConfig(path11, options = {}) {
4443
- const raw = JSON.parse(await readFile3(path11, "utf8"));
4564
+ const raw = JSON.parse(await readFile4(path11, "utf8"));
4444
4565
  return parseConfig(raw, options);
4445
4566
  }
4446
4567
  function parseConfig(raw, options = {}) {
@@ -4608,7 +4729,7 @@ var init_config_store = __esm(() => {
4608
4729
  });
4609
4730
 
4610
4731
  // src/config/ensure-config.ts
4611
- import { readFile as readFile4 } from "node:fs/promises";
4732
+ import { readFile as readFile5 } from "node:fs/promises";
4612
4733
  async function ensureConfigExists(path11) {
4613
4734
  try {
4614
4735
  await loadConfig(path11);
@@ -4622,7 +4743,7 @@ async function ensureConfigExists(path11) {
4622
4743
  }
4623
4744
  async function loadDefaultConfigTemplate() {
4624
4745
  const templatePath = new URL("../../config.example.json", import.meta.url);
4625
- const template = JSON.parse(await readFile4(templatePath, "utf8"));
4746
+ const template = JSON.parse(await readFile5(templatePath, "utf8"));
4626
4747
  return {
4627
4748
  ...template,
4628
4749
  agents: Object.fromEntries(Object.entries(template.agents).map(([name, agent]) => [
@@ -4725,12 +4846,20 @@ class SessionService {
4725
4846
  agent,
4726
4847
  workspace,
4727
4848
  transport_session: transportSession,
4849
+ transport_agent_command: this.state.sessions[alias]?.transport_agent_command,
4728
4850
  created_at: this.state.sessions[alias]?.created_at ?? new Date().toISOString(),
4729
4851
  last_used_at: new Date().toISOString()
4730
4852
  });
4731
4853
  }
4732
- async attachSession(alias, agent, workspace, transportSession) {
4733
- return await this.createLogicalSession(alias, agent, workspace, transportSession);
4854
+ async attachSession(alias, agent, workspace, transportSession, transportAgentCommand) {
4855
+ return await this.createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand);
4856
+ }
4857
+ async getSession(alias) {
4858
+ const session = this.state.sessions[alias];
4859
+ if (!session) {
4860
+ return null;
4861
+ }
4862
+ return this.toResolvedSession(session);
4734
4863
  }
4735
4864
  async useSession(chatKey, alias) {
4736
4865
  const session = this.state.sessions[alias];
@@ -4741,6 +4870,24 @@ class SessionService {
4741
4870
  this.state.chat_contexts[chatKey] = { current_session: alias };
4742
4871
  await this.persist();
4743
4872
  }
4873
+ async setCurrentSessionMode(chatKey, modeId) {
4874
+ const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
4875
+ if (!currentAlias) {
4876
+ throw new Error("no current session selected");
4877
+ }
4878
+ const session = this.state.sessions[currentAlias];
4879
+ if (!session) {
4880
+ throw new Error("no current session selected");
4881
+ }
4882
+ const normalizedModeId = modeId?.trim();
4883
+ if (normalizedModeId) {
4884
+ session.mode_id = normalizedModeId;
4885
+ } else {
4886
+ delete session.mode_id;
4887
+ }
4888
+ session.last_used_at = new Date().toISOString();
4889
+ await this.persist();
4890
+ }
4744
4891
  async getCurrentSession(chatKey) {
4745
4892
  const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
4746
4893
  if (!currentAlias) {
@@ -4768,24 +4915,42 @@ class SessionService {
4768
4915
  return {
4769
4916
  alias: session.alias,
4770
4917
  agent: session.agent,
4771
- agentCommand: resolveAgentCommand(agentConfig.driver, agentConfig.command),
4918
+ agentCommand: session.transport_agent_command ?? resolveAgentCommand(agentConfig.driver, agentConfig.command),
4772
4919
  workspace: session.workspace,
4773
4920
  transportSession: session.transport_session,
4921
+ modeId: session.mode_id,
4774
4922
  cwd: this.config.workspaces[session.workspace].cwd
4775
4923
  };
4776
4924
  }
4925
+ async setSessionTransportAgentCommand(alias, transportAgentCommand) {
4926
+ const session = this.state.sessions[alias];
4927
+ if (!session) {
4928
+ throw new Error(`session "${alias}" does not exist`);
4929
+ }
4930
+ const normalized = transportAgentCommand?.trim();
4931
+ if (normalized) {
4932
+ session.transport_agent_command = normalized;
4933
+ } else {
4934
+ delete session.transport_agent_command;
4935
+ }
4936
+ session.last_used_at = new Date().toISOString();
4937
+ await this.persist();
4938
+ }
4777
4939
  async persist() {
4778
4940
  await this.stateStore.save(this.state);
4779
4941
  }
4780
- async createLogicalSession(alias, agent, workspace, transportSession) {
4942
+ async createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand) {
4781
4943
  this.validateSession(alias, agent, workspace);
4782
4944
  const existingSession = this.state.sessions[alias];
4783
4945
  const now = new Date().toISOString();
4946
+ const normalizedTransportAgentCommand = transportAgentCommand?.trim();
4784
4947
  const session = {
4785
4948
  alias,
4786
4949
  agent,
4787
4950
  workspace,
4788
4951
  transport_session: transportSession,
4952
+ ...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : existingSession?.transport_agent_command ? { transport_agent_command: existingSession.transport_agent_command } : {},
4953
+ mode_id: existingSession?.mode_id,
4789
4954
  created_at: existingSession?.created_at ?? now,
4790
4955
  last_used_at: now
4791
4956
  };
@@ -4813,7 +4978,7 @@ function createEmptyState() {
4813
4978
  }
4814
4979
 
4815
4980
  // src/state/state-store.ts
4816
- import { mkdir as mkdir7, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
4981
+ import { mkdir as mkdir7, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
4817
4982
  import { dirname as dirname6 } from "node:path";
4818
4983
 
4819
4984
  class StateStore {
@@ -4823,7 +4988,7 @@ class StateStore {
4823
4988
  }
4824
4989
  async load() {
4825
4990
  try {
4826
- const content = await readFile5(this.path, "utf8");
4991
+ const content = await readFile6(this.path, "utf8");
4827
4992
  if (content.trim() === "") {
4828
4993
  return createEmptyState();
4829
4994
  }
@@ -4918,9 +5083,9 @@ class AcpxBridgeClient {
4918
5083
  request(method, params) {
4919
5084
  const id = String(this.nextId);
4920
5085
  this.nextId += 1;
4921
- return awaitable((resolve, reject) => {
5086
+ return awaitable((resolve2, reject) => {
4922
5087
  this.pending.set(id, {
4923
- resolve: (value) => resolve(value),
5088
+ resolve: (value) => resolve2(value),
4924
5089
  reject
4925
5090
  });
4926
5091
  this.writeLine(encodeBridgeRequest({
@@ -5019,8 +5184,8 @@ async function spawnAcpxBridgeClient(options = {}) {
5019
5184
  return client;
5020
5185
  }
5021
5186
  function awaitable(executor) {
5022
- return new Promise((resolve, reject) => {
5023
- executor(resolve, reject);
5187
+ return new Promise((resolve2, reject) => {
5188
+ executor(resolve2, reject);
5024
5189
  });
5025
5190
  }
5026
5191
  var init_acpx_bridge_client = __esm(() => {
@@ -5042,6 +5207,12 @@ class AcpxBridgeTransport {
5042
5207
  text
5043
5208
  });
5044
5209
  }
5210
+ async setMode(session, modeId) {
5211
+ await this.client.request("setMode", {
5212
+ ...this.toParams(session),
5213
+ modeId
5214
+ });
5215
+ }
5045
5216
  async cancel(session) {
5046
5217
  return await this.client.request("cancel", this.toParams(session));
5047
5218
  }
@@ -5167,7 +5338,7 @@ import { createRequire as createRequire3 } from "node:module";
5167
5338
  import { spawn as spawn3 } from "node:child_process";
5168
5339
  import { spawn as spawnPty } from "node-pty";
5169
5340
  async function defaultRunner(command, args, options) {
5170
- return await new Promise((resolve, reject) => {
5341
+ return await new Promise((resolve2, reject) => {
5171
5342
  const spawnSpec = resolveSpawnCommand(command, args);
5172
5343
  const child = spawn3(spawnSpec.command, spawnSpec.args, { stdio: ["ignore", "pipe", "pipe"] });
5173
5344
  let stdout = "";
@@ -5190,14 +5361,14 @@ async function defaultRunner(command, args, options) {
5190
5361
  child.on("close", (code) => {
5191
5362
  if (timeoutId)
5192
5363
  clearTimeout(timeoutId);
5193
- resolve({ code: code ?? 1, stdout, stderr });
5364
+ resolve2({ code: code ?? 1, stdout, stderr });
5194
5365
  });
5195
5366
  });
5196
5367
  }
5197
5368
  async function defaultPtyRunner(command, args, options) {
5198
5369
  const helperPath = resolveNodePtyHelperPath(require3.resolve("node-pty/package.json"), process.platform, process.arch);
5199
5370
  await ensureNodePtyHelperExecutable(helperPath);
5200
- return await new Promise((resolve, reject) => {
5371
+ return await new Promise((resolve2, reject) => {
5201
5372
  const spawnSpec = resolveSpawnCommand(command, args);
5202
5373
  const child = spawnPty(spawnSpec.command, spawnSpec.args, {
5203
5374
  name: "xterm-color",
@@ -5217,7 +5388,7 @@ async function defaultPtyRunner(command, args, options) {
5217
5388
  child.onExit(({ exitCode }) => {
5218
5389
  if (timeoutId)
5219
5390
  clearTimeout(timeoutId);
5220
- resolve({ code: exitCode, stdout: output, stderr: "" });
5391
+ resolve2({ code: exitCode, stdout: output, stderr: "" });
5221
5392
  });
5222
5393
  });
5223
5394
  }
@@ -5258,6 +5429,14 @@ class AcpxCliTransport {
5258
5429
  const result = await this.runCommand(this.command, args);
5259
5430
  return { text: getPromptText(result) };
5260
5431
  }
5432
+ async setMode(session, modeId) {
5433
+ await this.run(this.buildArgs(session, [
5434
+ "set-mode",
5435
+ "-s",
5436
+ session.transportSession,
5437
+ modeId
5438
+ ]));
5439
+ }
5261
5440
  async cancel(session) {
5262
5441
  const output = await this.run(this.buildArgs(session, [
5263
5442
  "cancel",
@@ -5315,7 +5494,7 @@ class AcpxCliTransport {
5315
5494
  ]);
5316
5495
  }
5317
5496
  async runStreamingPrompt(command, args, reply, maxSegmentWaitMs = 30000) {
5318
- return await new Promise((resolve, reject) => {
5497
+ return await new Promise((resolve2, reject) => {
5319
5498
  const spawnSpec = resolveSpawnCommand(command, args);
5320
5499
  const child = spawn3(spawnSpec.command, spawnSpec.args, { stdio: ["ignore", "pipe", "pipe"] });
5321
5500
  let stdout = "";
@@ -5357,7 +5536,7 @@ class AcpxCliTransport {
5357
5536
  if (remaining.length > 0) {
5358
5537
  reply(remaining).catch(() => {});
5359
5538
  }
5360
- resolve({ code: code ?? 1, stdout, stderr });
5539
+ resolve2({ code: code ?? 1, stdout, stderr });
5361
5540
  });
5362
5541
  });
5363
5542
  }
@@ -5428,7 +5607,7 @@ __export(exports_main, {
5428
5607
  main: () => main2,
5429
5608
  buildApp: () => buildApp
5430
5609
  });
5431
- import { homedir as homedir2 } from "node:os";
5610
+ import { homedir as homedir3 } from "node:os";
5432
5611
  import { dirname as dirname8, join as join4 } from "node:path";
5433
5612
  import { fileURLToPath as fileURLToPath3 } from "node:url";
5434
5613
  async function buildApp(paths, deps = {}) {
@@ -5491,7 +5670,7 @@ async function main2() {
5491
5670
  }
5492
5671
  }
5493
5672
  function resolveRuntimePaths() {
5494
- const home = process.env.HOME ?? homedir2();
5673
+ const home = process.env.HOME ?? homedir3();
5495
5674
  if (!home) {
5496
5675
  throw new Error("Unable to resolve the current user home directory");
5497
5676
  }
@@ -5528,7 +5707,7 @@ var init_main = __esm(async () => {
5528
5707
  });
5529
5708
 
5530
5709
  // src/cli.ts
5531
- import { homedir as homedir3 } from "node:os";
5710
+ import { homedir as homedir4 } from "node:os";
5532
5711
  import { sep } from "node:path";
5533
5712
  import { fileURLToPath as fileURLToPath4 } from "node:url";
5534
5713
 
@@ -5924,7 +6103,15 @@ class DaemonRuntime {
5924
6103
  }
5925
6104
 
5926
6105
  // src/cli.ts
5927
- var HELP_LINES = ["用法:", "weacpx login", "weacpx logout", "weacpx run", "weacpx start", "weacpx status", "weacpx stop"];
6106
+ var HELP_LINES = [
6107
+ "用法:",
6108
+ "weacpx login - 微信登录",
6109
+ "weacpx logout - 退出登录",
6110
+ "weacpx run - 前台运行",
6111
+ "weacpx start - 后台启动",
6112
+ "weacpx status - 查看状态",
6113
+ "weacpx stop - 停止服务"
6114
+ ];
5928
6115
  async function runCli(args, deps = {}) {
5929
6116
  const command = args[0];
5930
6117
  const print = deps.print ?? ((line) => console.log(line));
@@ -6018,7 +6205,7 @@ function createDefaultController() {
6018
6205
  });
6019
6206
  }
6020
6207
  function requireHome() {
6021
- const home = process.env.HOME ?? homedir3();
6208
+ const home = process.env.HOME ?? homedir4();
6022
6209
  if (!home) {
6023
6210
  throw new Error("Unable to resolve the current user home directory");
6024
6211
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weacpx",
3
3
  "description": "用微信远程控制 `acpx` 会话的控制台, 底层基于 `weixin-agent-sdk` 与 `acpx`",
4
- "version": "0.1.5",
4
+ "version": "0.1.6",
5
5
  "main": "index.js",
6
6
  "directories": {
7
7
  "doc": "docs",
@@ -46,7 +46,7 @@
46
46
  "test:smoke": "node ./scripts/run-tests.mjs tests/smoke"
47
47
  },
48
48
  "dependencies": {
49
- "acpx": "^0.3.1",
49
+ "acpx": "^0.4.0",
50
50
  "node-pty": "^1.1.0",
51
51
  "qrcode-terminal": "^0.12.0"
52
52
  },
@@ -55,5 +55,10 @@
55
55
  },
56
56
  "engines": {
57
57
  "node": ">=22"
58
+ },
59
+ "devDependencies": {
60
+ "@types/bun": "^1.3.11",
61
+ "bun-types": "^1.3.11",
62
+ "typescript": "^6.0.2"
58
63
  }
59
64
  }