weacpx 0.1.5 → 0.1.7

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/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);
@@ -2811,20 +2829,26 @@ var init_slash_commands = __esm(() => {
2811
2829
  // src/weixin/messaging/process-message.ts
2812
2830
  import crypto4 from "node:crypto";
2813
2831
  import fs6 from "node:fs/promises";
2832
+ import { tmpdir } from "node:os";
2814
2833
  import path9 from "node:path";
2815
- async function saveMediaBuffer(buffer, contentType, subdir, _maxBytes, originalFilename) {
2816
- const dir = path9.join(MEDIA_TEMP_DIR, subdir ?? "");
2817
- await fs6.mkdir(dir, { recursive: true });
2818
- let ext = ".bin";
2819
- if (originalFilename) {
2820
- ext = path9.extname(originalFilename) || ".bin";
2821
- } else if (contentType) {
2822
- ext = getExtensionFromMime(contentType);
2823
- }
2824
- const name = `${Date.now()}-${crypto4.randomBytes(4).toString("hex")}${ext}`;
2825
- const filePath = path9.join(dir, name);
2826
- await fs6.writeFile(filePath, buffer);
2827
- return { path: filePath };
2834
+ function resolveMediaTempDir(customRoot) {
2835
+ return customRoot ?? path9.join(tmpdir(), "weacpx", "media");
2836
+ }
2837
+ function createSaveMediaBuffer(mediaTempDir) {
2838
+ return async function saveMediaBuffer(buffer, contentType, subdir, _maxBytes, originalFilename) {
2839
+ const dir = path9.join(resolveMediaTempDir(mediaTempDir), subdir ?? "");
2840
+ await fs6.mkdir(dir, { recursive: true });
2841
+ let ext = ".bin";
2842
+ if (originalFilename) {
2843
+ ext = path9.extname(originalFilename) || ".bin";
2844
+ } else if (contentType) {
2845
+ ext = getExtensionFromMime(contentType);
2846
+ }
2847
+ const name = `${Date.now()}-${crypto4.randomBytes(4).toString("hex")}${ext}`;
2848
+ const filePath = path9.join(dir, name);
2849
+ await fs6.writeFile(filePath, buffer);
2850
+ return { path: filePath };
2851
+ };
2828
2852
  }
2829
2853
  function extractTextBody(itemList) {
2830
2854
  if (!itemList?.length)
@@ -2839,10 +2863,10 @@ function extractTextBody(itemList) {
2839
2863
  function findMediaItem(itemList) {
2840
2864
  if (!itemList?.length)
2841
2865
  return;
2842
- const direct = itemList.find((i) => i.type === MessageItemType.IMAGE && hasDownloadableMedia(i.image_item?.media)) ?? itemList.find((i) => i.type === MessageItemType.VIDEO && hasDownloadableMedia(i.video_item?.media)) ?? itemList.find((i) => i.type === MessageItemType.FILE && hasDownloadableMedia(i.file_item?.media)) ?? itemList.find((i) => i.type === MessageItemType.VOICE && hasDownloadableMedia(i.voice_item?.media) && !i.voice_item?.text);
2866
+ const direct = itemList.find((item) => item.type === MessageItemType.IMAGE && hasDownloadableMedia(item.image_item?.media)) ?? itemList.find((item) => item.type === MessageItemType.VIDEO && hasDownloadableMedia(item.video_item?.media)) ?? itemList.find((item) => item.type === MessageItemType.FILE && hasDownloadableMedia(item.file_item?.media)) ?? itemList.find((item) => item.type === MessageItemType.VOICE && hasDownloadableMedia(item.voice_item?.media) && !item.voice_item?.text);
2843
2867
  if (direct)
2844
2868
  return direct;
2845
- const refItem = itemList.find((i) => i.type === MessageItemType.TEXT && i.ref_msg?.message_item && isMediaItem(i.ref_msg.message_item));
2869
+ const refItem = itemList.find((item) => item.type === MessageItemType.TEXT && item.ref_msg?.message_item && isMediaItem(item.ref_msg.message_item));
2846
2870
  return refItem?.ref_msg?.message_item ?? undefined;
2847
2871
  }
2848
2872
  async function processOneMessage(full, deps) {
@@ -2873,7 +2897,7 @@ async function processOneMessage(full, deps) {
2873
2897
  try {
2874
2898
  const downloaded = await downloadMediaFromItem(mediaItem, {
2875
2899
  cdnBaseUrl: deps.cdnBaseUrl,
2876
- saveMedia: saveMediaBuffer,
2900
+ saveMedia: createSaveMediaBuffer(deps.mediaTempDir),
2877
2901
  log: deps.log,
2878
2902
  errLog: deps.errLog,
2879
2903
  label: "inbound"
@@ -2896,7 +2920,7 @@ async function processOneMessage(full, deps) {
2896
2920
  };
2897
2921
  }
2898
2922
  } catch (err) {
2899
- logger.error(`media download failed: ${String(err)}`);
2923
+ deps.errLog(`media download failed: ${String(err)}`);
2900
2924
  }
2901
2925
  }
2902
2926
  const to = full.from_user_id ?? "";
@@ -2908,7 +2932,7 @@ async function processOneMessage(full, deps) {
2908
2932
  opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
2909
2933
  });
2910
2934
  } catch (err) {
2911
- logger.error(`intermediate reply failed: ${String(err)}`);
2935
+ deps.errLog(`intermediate reply failed: ${String(err)}`);
2912
2936
  }
2913
2937
  };
2914
2938
  const request = {
@@ -2941,7 +2965,7 @@ async function processOneMessage(full, deps) {
2941
2965
  let filePath;
2942
2966
  const mediaUrl = response.media.url;
2943
2967
  if (mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://")) {
2944
- filePath = await downloadRemoteImageToTemp(mediaUrl, path9.join(MEDIA_TEMP_DIR, "outbound"));
2968
+ filePath = await downloadRemoteImageToTemp(mediaUrl, path9.join(resolveMediaTempDir(deps.mediaTempDir), "outbound"));
2945
2969
  } else {
2946
2970
  filePath = path9.isAbsolute(mediaUrl) ? mediaUrl : path9.resolve(mediaUrl);
2947
2971
  }
@@ -2960,7 +2984,8 @@ async function processOneMessage(full, deps) {
2960
2984
  });
2961
2985
  }
2962
2986
  } catch (err) {
2963
- logger.error(`processOneMessage: agent or send failed: ${err instanceof Error ? err.stack ?? err.message : JSON.stringify(err)}`);
2987
+ const errorText = err instanceof Error ? err.stack ?? err.message : JSON.stringify(err);
2988
+ deps.errLog(`processOneMessage: agent or send failed: ${errorText}`);
2964
2989
  sendWeixinErrorNotice({
2965
2990
  to,
2966
2991
  contextToken,
@@ -2985,14 +3010,13 @@ async function processOneMessage(full, deps) {
2985
3010
  }
2986
3011
  }
2987
3012
  }
2988
- var MEDIA_TEMP_DIR = "/tmp/weixin-agent/media", hasDownloadableMedia = (m) => m?.encrypt_query_param || m?.full_url;
3013
+ var hasDownloadableMedia = (media) => media?.encrypt_query_param || media?.full_url;
2989
3014
  var init_process_message = __esm(() => {
2990
3015
  init_api();
2991
3016
  init_types();
2992
3017
  init_upload();
2993
3018
  init_media_download();
2994
3019
  init_mime();
2995
- init_logger();
2996
3020
  init_inbound();
2997
3021
  init_error_notice();
2998
3022
  init_send_media();
@@ -3309,75 +3333,6 @@ var init_weixin_sdk = __esm(() => {
3309
3333
  init_weixin();
3310
3334
  });
3311
3335
 
3312
- // src/config/agent-templates.ts
3313
- function getAgentTemplate(name) {
3314
- const template = TEMPLATES[name];
3315
- if (!template) {
3316
- return null;
3317
- }
3318
- return {
3319
- ...template
3320
- };
3321
- }
3322
- function listAgentTemplates() {
3323
- return Object.keys(TEMPLATES);
3324
- }
3325
- var TEMPLATES;
3326
- var init_agent_templates = __esm(() => {
3327
- TEMPLATES = {
3328
- codex: {
3329
- driver: "codex"
3330
- },
3331
- claude: {
3332
- driver: "claude"
3333
- }
3334
- };
3335
- });
3336
-
3337
- // src/formatting/render-text.ts
3338
- function renderHelpText() {
3339
- return [
3340
- "可用命令:",
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"
3361
- ].join(`
3362
- `);
3363
- }
3364
- function renderAgents(config) {
3365
- const names = Object.keys(config.agents);
3366
- if (names.length === 0) {
3367
- return "还没有注册任何 Agent。";
3368
- }
3369
- return ["已注册的 Agent:", ...names.map((name) => `- ${name}`)].join(`
3370
- `);
3371
- }
3372
- function renderWorkspaces(config) {
3373
- const names = Object.entries(config.workspaces);
3374
- if (names.length === 0) {
3375
- return "还没有注册任何工作区。";
3376
- }
3377
- return ["已注册的工作区:", ...names.map(([name, workspace]) => `- ${name}: ${workspace.cwd}`)].join(`
3378
- `);
3379
- }
3380
-
3381
3336
  // src/logging/app-logger.ts
3382
3337
  import { appendFile, mkdir as mkdir5, readdir, rename, rm as rm4, stat } from "node:fs/promises";
3383
3338
  import { basename, dirname as dirname4, join as join2 } from "node:path";
@@ -3498,6 +3453,27 @@ var init_app_logger = __esm(() => {
3498
3453
  };
3499
3454
  });
3500
3455
 
3456
+ // src/transport/acpx-session-index.ts
3457
+ import { readFile as readFile3 } from "node:fs/promises";
3458
+ import { homedir } from "node:os";
3459
+ import { resolve } from "node:path";
3460
+ async function resolveSessionAgentCommandFromIndex(session) {
3461
+ const home = process.env.HOME ?? homedir();
3462
+ if (!home) {
3463
+ return;
3464
+ }
3465
+ try {
3466
+ const raw = await readFile3(resolve(home, ".acpx", "sessions", "index.json"), "utf8");
3467
+ const parsed = JSON.parse(raw);
3468
+ const targetCwd = resolve(session.cwd);
3469
+ const match = parsed.entries?.find((entry) => entry.name === session.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
3470
+ return match?.agentCommand?.trim();
3471
+ } catch {
3472
+ return;
3473
+ }
3474
+ }
3475
+ var init_acpx_session_index = () => {};
3476
+
3501
3477
  // src/transport/prompt-output.ts
3502
3478
  function getPromptText(result) {
3503
3479
  const stdoutOutput = extractPromptOutput(result.stdout);
@@ -3652,6 +3628,8 @@ function parseCommand(input) {
3652
3628
  return { kind: "cancel" };
3653
3629
  if (command === "/clear")
3654
3630
  return { kind: "session.reset" };
3631
+ if (command === "/mode" && parts.length === 1)
3632
+ return { kind: "mode.show" };
3655
3633
  if (command === "/permission" && parts.length === 1)
3656
3634
  return { kind: "permission.status" };
3657
3635
  if (command === "/session" && parts.length === 1)
@@ -3678,6 +3656,9 @@ function parseCommand(input) {
3678
3656
  if (command === "/use" && parts[1]) {
3679
3657
  return { kind: "session.use", alias: parts[1] };
3680
3658
  }
3659
+ if (command === "/mode" && parts[1]) {
3660
+ return { kind: "mode.set", modeId: parts[1] };
3661
+ }
3681
3662
  if (command === "/agent" && parts[1] === "add" && parts[2]) {
3682
3663
  return { kind: "agent.add", template: parts[2] };
3683
3664
  }
@@ -3830,6 +3811,7 @@ var init_parse_command = __esm(() => {
3830
3811
  "/status",
3831
3812
  "/cancel",
3832
3813
  "/clear",
3814
+ "/mode",
3833
3815
  "/permission",
3834
3816
  "/session",
3835
3817
  "/workspace",
@@ -3838,398 +3820,804 @@ var init_parse_command = __esm(() => {
3838
3820
  ]);
3839
3821
  });
3840
3822
 
3841
- // src/commands/command-router.ts
3842
- import { access } from "node:fs/promises";
3843
- import { basename as basename2, normalize } from "node:path";
3844
- import { homedir } from "node:os";
3845
-
3846
- class CommandRouter {
3847
- sessions;
3848
- transport;
3849
- config;
3850
- configStore;
3851
- logger;
3852
- constructor(sessions, transport, config, configStore, logger2) {
3853
- this.sessions = sessions;
3854
- this.transport = transport;
3855
- this.config = config;
3856
- this.configStore = configStore;
3857
- this.logger = logger2 ?? createNoopAppLogger();
3823
+ // src/commands/handlers/permission-handler.ts
3824
+ function handlePermissionStatus(context, title) {
3825
+ return { text: renderPermissionStatus(context.config, title) };
3826
+ }
3827
+ async function handlePermissionModeSet(context, mode) {
3828
+ if (!context.config || !context.configStore) {
3829
+ return { text: "当前没有加载可写入的配置。" };
3858
3830
  }
3859
- async handle(chatKey, input, reply) {
3860
- const startedAt = Date.now();
3861
- const command = parseCommand(input);
3862
- await this.logger.debug("command.parsed", "parsed inbound command", {
3863
- chatKey,
3864
- kind: command.kind
3865
- });
3866
- return await this.executeCommand(chatKey, command.kind, startedAt, async () => {
3867
- switch (command.kind) {
3868
- case "invalid":
3869
- return {
3870
- text: [
3871
- "无法识别的命令格式。",
3872
- "",
3873
- "正确的会话创建格式:",
3874
- "/session new <别名> --agent <Agent名> --ws <工作区名>",
3875
- "",
3876
- "例如:",
3877
- "/session new demo --agent claude --ws weacpx"
3878
- ].join(`
3879
- `)
3880
- };
3881
- case "help":
3882
- return { text: renderHelpText() };
3883
- case "agents":
3884
- return { text: this.config ? renderAgents(this.config) : "No config loaded." };
3885
- case "agent.add": {
3886
- if (!this.config || !this.configStore) {
3887
- return { text: "当前没有加载可写入的配置。" };
3888
- }
3889
- const template = getAgentTemplate(command.template);
3890
- if (!template) {
3891
- return { text: `暂不支持这个 Agent 模板。当前可用:${listAgentTemplates().join("、")}` };
3892
- }
3893
- const updated = await this.configStore.upsertAgent(command.template, template);
3894
- this.replaceConfig(updated);
3895
- return { text: `Agent「${command.template}」已保存` };
3896
- }
3897
- case "agent.rm": {
3898
- if (!this.config || !this.configStore) {
3899
- return { text: "当前没有加载可写入的配置。" };
3900
- }
3901
- if (!this.config.agents[command.name]) {
3902
- return { text: "没有找到这个 Agent。" };
3903
- }
3904
- const updated = await this.configStore.removeAgent(command.name);
3905
- this.replaceConfig(updated);
3906
- return { text: `Agent「${command.name}」已删除` };
3907
- }
3908
- case "permission.status":
3909
- return { text: this.renderPermissionStatus("当前权限模式:") };
3910
- case "permission.mode.set": {
3911
- if (!this.config || !this.configStore) {
3912
- return { text: "当前没有加载可写入的配置。" };
3913
- }
3914
- const updated = await this.configStore.updateTransport({
3915
- permissionMode: command.mode
3916
- });
3917
- this.replaceConfig(updated);
3918
- return { text: this.renderPermissionStatus("权限模式已更新:") };
3919
- }
3920
- case "permission.auto.status":
3921
- return { text: this.renderPermissionStatus("当前非交互策略:") };
3922
- case "permission.auto.set": {
3923
- if (!this.config || !this.configStore) {
3924
- return { text: "当前没有加载可写入的配置。" };
3925
- }
3926
- const updated = await this.configStore.updateTransport({
3927
- nonInteractivePermissions: command.policy
3928
- });
3929
- this.replaceConfig(updated);
3930
- return { text: this.renderPermissionStatus("非交互策略已更新:") };
3931
- }
3932
- case "workspaces":
3933
- return { text: this.config ? renderWorkspaces(this.config) : "No config loaded." };
3934
- case "workspace.new": {
3935
- if (!this.config || !this.configStore) {
3936
- return { text: "当前没有加载可写入的配置。" };
3937
- }
3938
- const wsCwd = normalizePathForWorkspace(command.cwd);
3939
- if (!await pathExists(wsCwd)) {
3940
- return { text: `工作区路径不存在:${command.cwd}` };
3941
- }
3942
- const updated = await this.configStore.upsertWorkspace(command.name, wsCwd);
3943
- this.replaceConfig(updated);
3944
- return { text: `工作区「${command.name}」已保存` };
3945
- }
3946
- case "workspace.rm": {
3947
- if (!this.config || !this.configStore) {
3948
- return { text: "当前没有加载可写入的配置。" };
3949
- }
3950
- const updated = await this.configStore.removeWorkspace(command.name);
3951
- this.replaceConfig(updated);
3952
- return { text: `工作区「${command.name}」已删除` };
3953
- }
3954
- case "sessions": {
3955
- const sessions = await this.sessions.listSessions(chatKey);
3956
- if (sessions.length === 0) {
3957
- return { text: "还没有会话。请先执行 /session new <alias> --agent <name> --ws <name>。" };
3958
- }
3959
- return {
3960
- text: [
3961
- "会话列表:",
3962
- ...sessions.map((session) => `- ${session.alias} (${session.agent} @ ${session.workspace})${session.isCurrent ? " [当前]" : ""}`)
3963
- ].join(`
3964
- `)
3965
- };
3966
- }
3967
- case "session.new": {
3968
- const session = this.sessions.resolveSession(command.alias, command.agent, command.workspace, `${command.workspace}:${command.alias}`);
3969
- try {
3970
- await this.ensureTransportSession(session);
3971
- const exists = await this.checkTransportSession(session);
3972
- if (!exists) {
3973
- return this.renderSessionCreationVerificationError(session);
3974
- }
3975
- } catch (error) {
3976
- return this.renderSessionCreationError(session, error);
3977
- }
3978
- await this.sessions.attachSession(command.alias, command.agent, command.workspace, session.transportSession);
3979
- await this.sessions.useSession(chatKey, command.alias);
3980
- await this.logger.info("session.created", "created and selected logical session", {
3981
- alias: command.alias,
3982
- agent: command.agent,
3983
- workspace: command.workspace
3984
- });
3985
- return { text: `会话「${command.alias}」已创建并切换` };
3986
- }
3987
- case "session.shortcut":
3988
- return await this.handleSessionShortcut(chatKey, command.agent, command.cwd, false);
3989
- case "session.shortcut.new":
3990
- return await this.handleSessionShortcut(chatKey, command.agent, command.cwd, true);
3991
- case "session.attach": {
3992
- const attached = this.sessions.resolveSession(command.alias, command.agent, command.workspace, command.transportSession);
3993
- const exists = await this.checkTransportSession(attached);
3994
- if (!exists) {
3995
- return {
3996
- text: [
3997
- "没有找到可绑定的已有会话。",
3998
- `请确认会话名是否正确,然后重新执行:/session attach ${command.alias} --agent ${command.agent} --ws ${command.workspace} --name <会话名>`
3999
- ].join(`
4000
- `)
4001
- };
4002
- }
4003
- await this.sessions.attachSession(command.alias, command.agent, command.workspace, command.transportSession);
4004
- await this.sessions.useSession(chatKey, command.alias);
4005
- await this.logger.info("session.attached", "attached existing transport session", {
4006
- alias: command.alias,
4007
- agent: command.agent,
4008
- workspace: command.workspace,
4009
- transportSession: command.transportSession
4010
- });
4011
- return { text: `会话「${command.alias}」已绑定并切换` };
4012
- }
4013
- case "session.use":
4014
- await this.sessions.useSession(chatKey, command.alias);
4015
- await this.logger.info("session.selected", "selected logical session", {
4016
- alias: command.alias,
4017
- chatKey
4018
- });
4019
- return { text: `已切换到会话「${command.alias}」` };
4020
- case "status": {
4021
- const session = await this.sessions.getCurrentSession(chatKey);
4022
- if (!session) {
4023
- return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
4024
- }
4025
- return {
4026
- text: [
4027
- "当前会话:",
4028
- `- 名称:${session.alias}`,
4029
- `- Agent:${session.agent}`,
4030
- `- 工作区:${session.workspace}`
4031
- ].join(`
4032
- `)
4033
- };
4034
- }
4035
- case "cancel": {
4036
- const session = await this.sessions.getCurrentSession(chatKey);
4037
- if (!session) {
4038
- return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
4039
- }
4040
- try {
4041
- const result = await this.cancelTransportSession(session);
4042
- return { text: result.message || "cancelled" };
4043
- } catch (error) {
4044
- return this.renderTransportError(session, error);
4045
- }
4046
- }
4047
- case "session.reset":
4048
- return await this.resetCurrentSession(chatKey);
4049
- case "prompt": {
4050
- const session = await this.sessions.getCurrentSession(chatKey);
4051
- if (!session) {
4052
- return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
4053
- }
4054
- try {
4055
- const result = await this.promptTransportSession(session, command.text, reply);
4056
- return { text: result.text };
4057
- } catch (error) {
4058
- return this.renderTransportError(session, error);
4059
- }
4060
- }
4061
- }
4062
- });
3831
+ const updated = await context.configStore.updateTransport({
3832
+ permissionMode: mode
3833
+ });
3834
+ context.replaceConfig(updated);
3835
+ return { text: renderPermissionStatus(context.config, "权限模式已更新:") };
3836
+ }
3837
+ function handlePermissionAutoStatus(context, title) {
3838
+ return { text: renderPermissionStatus(context.config, title) };
3839
+ }
3840
+ async function handlePermissionAutoSet(context, policy) {
3841
+ if (!context.config || !context.configStore) {
3842
+ return { text: "当前没有加载可写入的配置。" };
4063
3843
  }
4064
- async clearSession(chatKey) {
4065
- await this.resetCurrentSession(chatKey);
3844
+ const updated = await context.configStore.updateTransport({
3845
+ nonInteractivePermissions: policy
3846
+ });
3847
+ context.replaceConfig(updated);
3848
+ return { text: renderPermissionStatus(context.config, "非交互策略已更新:") };
3849
+ }
3850
+ function renderPermissionStatus(config, title) {
3851
+ const permissionMode = config?.transport.permissionMode ?? "approve-all";
3852
+ const nonInteractivePermissions = config?.transport.nonInteractivePermissions ?? "fail";
3853
+ return [title, `- mode: ${permissionMode}`, `- auto: ${nonInteractivePermissions}`].join(`
3854
+ `);
3855
+ }
3856
+
3857
+ // src/commands/handlers/session-handler.ts
3858
+ async function handleSessions(context, chatKey) {
3859
+ const sessions = await context.sessions.listSessions(chatKey);
3860
+ if (sessions.length === 0) {
3861
+ return { text: "还没有会话。请先执行 /session new <alias> --agent <name> --ws <name>。" };
4066
3862
  }
4067
- async handleSessionShortcut(chatKey, agent, cwdInput, createNew) {
4068
- if (!this.config || !this.configStore) {
4069
- return { text: "当前没有加载可写入的配置。" };
4070
- }
4071
- const cwd = normalizePathForWorkspace(cwdInput);
4072
- if (!await pathExists(cwd)) {
4073
- return { text: `工作区路径不存在:${cwdInput}` };
4074
- }
4075
- const workspace = await this.resolveShortcutWorkspace(cwd);
4076
- await this.logger.info("session.shortcut.workspace", "resolved shortcut workspace", {
4077
- workspace: workspace.name,
4078
- cwd: workspace.cwd,
4079
- reused: workspace.reused
4080
- });
4081
- const baseAlias = `${workspace.name}:${agent}`;
4082
- const alias = createNew ? await this.allocateUniqueSessionAlias(baseAlias, chatKey) : baseAlias;
4083
- if (!createNew && await this.hasLogicalSession(alias, chatKey)) {
4084
- await this.sessions.useSession(chatKey, alias);
4085
- await this.logger.info("session.shortcut.reused", "reused existing logical session", {
4086
- alias,
4087
- workspace: workspace.name,
4088
- agent
4089
- });
4090
- return {
4091
- text: [
4092
- `已切换到会话「${alias}」`,
4093
- `- 复用工作区:${workspace.name}`,
4094
- `- 复用会话:${alias}`
4095
- ].join(`
3863
+ return {
3864
+ text: [
3865
+ "会话列表:",
3866
+ ...sessions.map((session) => `- ${session.alias} (${session.agent} @ ${session.workspace})${session.isCurrent ? " [当前]" : ""}`)
3867
+ ].join(`
4096
3868
  `)
4097
- };
4098
- }
4099
- const session = this.sessions.resolveSession(alias, agent, workspace.name, `${workspace.name}:${alias}`);
4100
- try {
4101
- await this.ensureTransportSession(session);
4102
- const exists = await this.checkTransportSession(session);
4103
- if (!exists) {
4104
- return this.renderShortcutSessionCreationError(workspace, alias);
4105
- }
4106
- } catch {
4107
- return this.renderShortcutSessionCreationError(workspace, alias);
3869
+ };
3870
+ }
3871
+ async function handleSessionNew(context, chatKey, alias, agent, workspace) {
3872
+ const session = context.lifecycle.resolveSession(alias, agent, workspace, `${workspace}:${alias}`);
3873
+ try {
3874
+ await context.lifecycle.ensureTransportSession(session);
3875
+ const exists = await context.lifecycle.checkTransportSession(session);
3876
+ if (!exists) {
3877
+ return context.recovery.renderSessionCreationVerificationError(session);
4108
3878
  }
4109
- await this.sessions.attachSession(alias, agent, workspace.name, session.transportSession);
4110
- await this.sessions.useSession(chatKey, alias);
4111
- await this.logger.info("session.shortcut.created", "created new logical session from shortcut", {
4112
- alias,
4113
- workspace: workspace.name,
4114
- agent,
4115
- workspaceReused: workspace.reused
4116
- });
3879
+ } catch (error) {
3880
+ return context.recovery.renderSessionCreationError(session, error);
3881
+ }
3882
+ await context.sessions.attachSession(alias, agent, workspace, session.transportSession);
3883
+ await context.lifecycle.refreshSessionTransportAgentCommand(alias);
3884
+ await context.sessions.useSession(chatKey, alias);
3885
+ await context.logger.info("session.created", "created and selected logical session", {
3886
+ alias,
3887
+ agent,
3888
+ workspace
3889
+ });
3890
+ return { text: `会话「${alias}」已创建并切换` };
3891
+ }
3892
+ async function handleSessionShortcut(context, chatKey, agent, cwdInput, createNew) {
3893
+ return await context.lifecycle.handleSessionShortcut(chatKey, agent, cwdInput, createNew);
3894
+ }
3895
+ async function handleSessionAttach(context, chatKey, alias, agent, workspace, transportSession) {
3896
+ const attached = context.lifecycle.resolveSession(alias, agent, workspace, transportSession);
3897
+ const exists = await context.lifecycle.checkTransportSession(attached);
3898
+ if (!exists) {
4117
3899
  return {
4118
3900
  text: [
4119
- `已创建并切换到会话「${alias}」`,
4120
- workspace.reused ? `- 复用工作区:${workspace.name}` : `- 新增工作区:${workspace.name} -> ${workspace.cwd}`,
4121
- `- 新增会话:${alias}`
3901
+ "没有找到可绑定的已有会话。",
3902
+ `请确认会话名是否正确,然后重新执行:/session attach ${alias} --agent ${agent} --ws ${workspace} --name <会话名>`
4122
3903
  ].join(`
4123
3904
  `)
4124
3905
  };
4125
3906
  }
4126
- replaceConfig(updated) {
4127
- if (!this.config) {
4128
- return;
4129
- }
4130
- this.config.transport = { ...updated.transport };
4131
- this.config.agents = { ...updated.agents };
4132
- this.config.workspaces = { ...updated.workspaces };
4133
- }
4134
- renderPermissionStatus(title) {
4135
- const permissionMode = this.config?.transport.permissionMode ?? "approve-all";
4136
- const nonInteractivePermissions = this.config?.transport.nonInteractivePermissions ?? "fail";
4137
- return [title, `- mode: ${permissionMode}`, `- auto: ${nonInteractivePermissions}`].join(`
4138
- `);
3907
+ await context.sessions.attachSession(alias, agent, workspace, transportSession);
3908
+ await context.lifecycle.refreshSessionTransportAgentCommand(alias);
3909
+ await context.sessions.useSession(chatKey, alias);
3910
+ await context.logger.info("session.attached", "attached existing transport session", {
3911
+ alias,
3912
+ agent,
3913
+ workspace,
3914
+ transportSession
3915
+ });
3916
+ return { text: `会话「${alias}」已绑定并切换` };
3917
+ }
3918
+ async function handleSessionUse(context, chatKey, alias) {
3919
+ await context.sessions.useSession(chatKey, alias);
3920
+ await context.logger.info("session.selected", "selected logical session", {
3921
+ alias,
3922
+ chatKey
3923
+ });
3924
+ return { text: `已切换到会话「${alias}」` };
3925
+ }
3926
+ async function handleModeShow(context, chatKey) {
3927
+ const session = await context.sessions.getCurrentSession(chatKey);
3928
+ if (!session) {
3929
+ return { text: NO_CURRENT_SESSION_TEXT };
4139
3930
  }
4140
- renderTransportError(session, error) {
4141
- const message = error instanceof Error ? error.message : String(error);
4142
- if (message.includes("No acpx session found")) {
4143
- return {
4144
- text: [
4145
- `当前会话「${session.alias}」暂时不可用。`,
4146
- `请先在微信里重新执行:/session new ${session.alias} --agent ${session.agent} --ws ${session.workspace}`,
4147
- `如果你要绑定一个已有会话,再执行:/session attach ${session.alias} --agent ${session.agent} --ws ${session.workspace} --name <会话名>`
4148
- ].join(`
4149
- `)
4150
- };
4151
- }
4152
- if (!isPartialPromptOutputError(message)) {
4153
- throw error;
4154
- }
4155
- return {
4156
- text: [
4157
- `当前会话「${session.alias}」执行中断,未收到最终回复。`,
4158
- "请直接重试;如果长时间无响应,可先发送 /cancel 后再重试。",
4159
- `错误信息:${summarizeTransportError(message)}`
4160
- ].join(`
3931
+ return {
3932
+ text: [
3933
+ "当前 mode:",
3934
+ `- 会话:${session.alias}`,
3935
+ `- mode:${session.modeId ?? "未设置"}`
3936
+ ].join(`
4161
3937
  `)
4162
- };
3938
+ };
3939
+ }
3940
+ async function handleModeSet(context, chatKey, modeId) {
3941
+ const session = await context.sessions.getCurrentSession(chatKey);
3942
+ if (!session) {
3943
+ return { text: NO_CURRENT_SESSION_TEXT };
4163
3944
  }
4164
- renderSessionCreationError(session, error) {
4165
- const message = error instanceof Error ? error.message : String(error);
4166
- if (message.includes("timed out") && message.includes("sessions new")) {
4167
- return this.renderSessionCreationVerificationError(session);
4168
- }
4169
- throw error;
3945
+ await context.interaction.setModeTransportSession(session, modeId);
3946
+ await context.sessions.setCurrentSessionMode(chatKey, modeId);
3947
+ return { text: `已设置当前会话 mode:${modeId}` };
3948
+ }
3949
+ async function handleStatus(context, chatKey) {
3950
+ const session = await context.sessions.getCurrentSession(chatKey);
3951
+ if (!session) {
3952
+ return { text: NO_CURRENT_SESSION_TEXT };
4170
3953
  }
4171
- renderSessionCreationVerificationError(session) {
4172
- return {
4173
- text: [
4174
- "当前还不能直接在微信里创建新会话。",
4175
- `请先准备好一个已有会话,然后在微信里执行:/session attach ${session.alias} --agent ${session.agent} --ws ${session.workspace} --name <会话名>`
4176
- ].join(`
3954
+ return {
3955
+ text: [
3956
+ "当前会话:",
3957
+ `- 名称:${session.alias}`,
3958
+ `- Agent:${session.agent}`,
3959
+ `- 工作区:${session.workspace}`
3960
+ ].join(`
4177
3961
  `)
4178
- };
3962
+ };
3963
+ }
3964
+ async function handleCancel(context, chatKey) {
3965
+ const session = await context.sessions.getCurrentSession(chatKey);
3966
+ if (!session) {
3967
+ return { text: NO_CURRENT_SESSION_TEXT };
4179
3968
  }
4180
- async resolveShortcutWorkspace(cwd) {
4181
- const existingByPath = Object.entries(this.config?.workspaces ?? {}).find(([, workspace]) => sameWorkspacePath(workspace.cwd, cwd));
4182
- if (existingByPath) {
4183
- return {
4184
- name: existingByPath[0],
4185
- cwd: existingByPath[1].cwd,
4186
- reused: true
4187
- };
4188
- }
4189
- const baseName = basename2(cwd);
4190
- const workspaceName = this.allocateWorkspaceName(baseName, cwd);
4191
- const updated = await this.configStore.upsertWorkspace(workspaceName, cwd);
4192
- this.replaceConfig(updated);
4193
- return {
4194
- name: workspaceName,
4195
- cwd,
4196
- reused: false
4197
- };
3969
+ try {
3970
+ const result = await context.interaction.cancelTransportSession(session);
3971
+ return { text: result.message || "cancelled" };
3972
+ } catch (error) {
3973
+ return context.recovery.renderTransportError(session, error);
4198
3974
  }
4199
- allocateWorkspaceName(baseName, cwd) {
4200
- if (!this.config?.workspaces[baseName]) {
4201
- return baseName;
3975
+ }
3976
+ async function handleSessionReset(context, chatKey) {
3977
+ return await context.lifecycle.resetCurrentSession(chatKey);
3978
+ }
3979
+ async function handlePrompt(context, chatKey, text, reply) {
3980
+ const session = await context.sessions.getCurrentSession(chatKey);
3981
+ if (!session) {
3982
+ return { text: NO_CURRENT_SESSION_TEXT };
3983
+ }
3984
+ try {
3985
+ const result = await context.interaction.promptTransportSession(session, text, reply);
3986
+ return { text: result.text };
3987
+ } catch (error) {
3988
+ const recovered = await context.recovery.tryRecoverMissingSession(session, error);
3989
+ if (recovered) {
3990
+ const result = await context.interaction.promptTransportSession(recovered, text, reply);
3991
+ return { text: result.text };
4202
3992
  }
4203
- let suffix = 2;
4204
- while (this.config.workspaces[`${baseName}-${suffix}`]) {
4205
- suffix += 1;
3993
+ return context.recovery.renderTransportError(session, error);
3994
+ }
3995
+ }
3996
+ var NO_CURRENT_SESSION_TEXT = "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。";
3997
+
3998
+ // src/commands/transport-diagnostics.ts
3999
+ function summarizeTransportError(message) {
4000
+ return message.replace(/\s+/g, " ").trim().slice(0, 200);
4001
+ }
4002
+ function summarizeTransportDiagnostic(output) {
4003
+ const trimmed = output.replace(/\s+/g, " ").trim();
4004
+ if (trimmed.length === 0) {
4005
+ return;
4006
+ }
4007
+ return trimmed.slice(0, 200);
4008
+ }
4009
+ function summarizeTransportDiagnosticTail(output) {
4010
+ const trimmed = output.replace(/\s+/g, " ").trim();
4011
+ if (trimmed.length === 0) {
4012
+ return;
4013
+ }
4014
+ return trimmed.slice(-200);
4015
+ }
4016
+ function summarizeTransportNdjson(output, prefix) {
4017
+ const lines = output.split(`
4018
+ `).map((line) => line.trim()).filter((line) => line.length > 0);
4019
+ if (lines.length === 0) {
4020
+ return {};
4021
+ }
4022
+ const methods = new Set;
4023
+ let agentMessageChunkCount = 0;
4024
+ let stopReason;
4025
+ for (const line of lines) {
4026
+ try {
4027
+ const payload = JSON.parse(line);
4028
+ if (typeof payload.method === "string" && payload.method.length > 0) {
4029
+ methods.add(payload.method);
4030
+ }
4031
+ if (payload.params?.update?.sessionUpdate === "agent_message_chunk") {
4032
+ agentMessageChunkCount += 1;
4033
+ }
4034
+ if (typeof payload.result?.stopReason === "string" && payload.result.stopReason.length > 0) {
4035
+ stopReason = payload.result.stopReason;
4036
+ }
4037
+ } catch {
4038
+ continue;
4206
4039
  }
4207
- return `${baseName}-${suffix}`;
4208
4040
  }
4209
- async allocateUniqueSessionAlias(baseAlias, chatKey) {
4210
- if (!await this.hasLogicalSession(baseAlias, chatKey)) {
4211
- return baseAlias;
4041
+ const summary = {
4042
+ [`${prefix}LineCount`]: lines.length
4043
+ };
4044
+ if (methods.size > 0) {
4045
+ summary[`${prefix}Methods`] = [...methods].join(",");
4046
+ }
4047
+ if (agentMessageChunkCount > 0) {
4048
+ summary[`${prefix}AgentMessageChunkCount`] = agentMessageChunkCount;
4049
+ }
4050
+ if (stopReason) {
4051
+ summary[`${prefix}StopReason`] = stopReason;
4052
+ }
4053
+ return summary;
4054
+ }
4055
+ function isPartialPromptOutputError(message) {
4056
+ return message.includes("未收到最终回复");
4057
+ }
4058
+
4059
+ // src/formatting/render-text.ts
4060
+ function renderHelpText() {
4061
+ return [
4062
+ "可用命令:",
4063
+ "",
4064
+ "先看这 3 个:",
4065
+ "/ss new <agent> -d <path> - 新建会话",
4066
+ "/use <alias> - 切会话",
4067
+ "/status - 看状态",
4068
+ "",
4069
+ "Agent:",
4070
+ "/agents - 看 Agent",
4071
+ "/agent add <codex|claude> - 加 Agent",
4072
+ "/agent rm <name> - 删 Agent",
4073
+ "",
4074
+ "工作区:",
4075
+ "/workspaces - 看工作区",
4076
+ "/workspace 或 /ws - 工作区命令",
4077
+ "/ws new <name> -d <path> - 加工作区",
4078
+ "/workspace rm <name> - 删工作区",
4079
+ "",
4080
+ "会话:",
4081
+ "/sessions - 看会话",
4082
+ "/session 或 /ss - 会话命令",
4083
+ "/ss <agent> -d <path> - 快速新建",
4084
+ "/ss new <agent> -d <path> - 新建会话",
4085
+ "/ss new <alias> -a <name> --ws <name> - 指定配置新建",
4086
+ "/ss attach <alias> -a <name> --ws <name> --name <transport-session> - 挂已有会话",
4087
+ "/use <alias> - 切会话",
4088
+ "/session reset 或 /clear - 清上下文",
4089
+ "",
4090
+ "权限:",
4091
+ "/pm 或 /permission - 权限设置",
4092
+ "/pm set <allow|read|deny> - 设审批级别",
4093
+ "/pm auto [allow|deny|fail] - 设自动处理",
4094
+ "",
4095
+ "常用:",
4096
+ "/status - 看状态",
4097
+ "/cancel 或 /stop - 停当前任务"
4098
+ ].join(`
4099
+ `);
4100
+ }
4101
+ function renderAgents(config) {
4102
+ const names = Object.keys(config.agents);
4103
+ if (names.length === 0) {
4104
+ return "还没有注册任何 Agent。";
4105
+ }
4106
+ return ["已注册的 Agent:", ...names.map((name) => `- ${name}`)].join(`
4107
+ `);
4108
+ }
4109
+ function renderWorkspaces(config) {
4110
+ const names = Object.entries(config.workspaces);
4111
+ if (names.length === 0) {
4112
+ return "还没有注册任何工作区。";
4113
+ }
4114
+ return ["已注册的工作区:", ...names.map(([name, workspace]) => `- ${name}: ${workspace.cwd}`)].join(`
4115
+ `);
4116
+ }
4117
+
4118
+ // src/commands/handlers/help-handler.ts
4119
+ function handleHelp() {
4120
+ return { text: renderHelpText() };
4121
+ }
4122
+ var init_help_handler = () => {};
4123
+
4124
+ // src/config/agent-templates.ts
4125
+ function getAgentTemplate(name) {
4126
+ const template = TEMPLATES[name];
4127
+ if (!template) {
4128
+ return null;
4129
+ }
4130
+ return {
4131
+ ...template
4132
+ };
4133
+ }
4134
+ function listAgentTemplates() {
4135
+ return Object.keys(TEMPLATES);
4136
+ }
4137
+ var TEMPLATES;
4138
+ var init_agent_templates = __esm(() => {
4139
+ TEMPLATES = {
4140
+ codex: {
4141
+ driver: "codex"
4142
+ },
4143
+ claude: {
4144
+ driver: "claude"
4212
4145
  }
4213
- let suffix = 2;
4214
- while (await this.hasLogicalSession(`${baseAlias}-${suffix}`, chatKey)) {
4215
- suffix += 1;
4146
+ };
4147
+ });
4148
+
4149
+ // src/commands/handlers/agent-handler.ts
4150
+ function handleAgents(context) {
4151
+ return { text: context.config ? renderAgents(context.config) : "No config loaded." };
4152
+ }
4153
+ async function handleAgentAdd(context, templateName) {
4154
+ if (!context.config || !context.configStore) {
4155
+ return { text: "当前没有加载可写入的配置。" };
4156
+ }
4157
+ const template = getAgentTemplate(templateName);
4158
+ if (!template) {
4159
+ return { text: `暂不支持这个 Agent 模板。当前可用:${listAgentTemplates().join("、")}` };
4160
+ }
4161
+ const updated = await context.configStore.upsertAgent(templateName, template);
4162
+ context.replaceConfig(updated);
4163
+ return { text: `Agent「${templateName}」已保存` };
4164
+ }
4165
+ async function handleAgentRemove(context, agentName) {
4166
+ if (!context.config || !context.configStore) {
4167
+ return { text: "当前没有加载可写入的配置。" };
4168
+ }
4169
+ if (!context.config.agents[agentName]) {
4170
+ return { text: "没有找到这个 Agent。" };
4171
+ }
4172
+ const updated = await context.configStore.removeAgent(agentName);
4173
+ context.replaceConfig(updated);
4174
+ return { text: `Agent「${agentName}」已删除` };
4175
+ }
4176
+ var init_agent_handler = __esm(() => {
4177
+ init_agent_templates();
4178
+ });
4179
+
4180
+ // src/commands/handlers/workspace-handler.ts
4181
+ import { access } from "node:fs/promises";
4182
+ import { homedir as homedir2 } from "node:os";
4183
+ import { normalize } from "node:path";
4184
+ function handleWorkspaces(context) {
4185
+ return { text: context.config ? renderWorkspaces(context.config) : "No config loaded." };
4186
+ }
4187
+ async function handleWorkspaceCreate(context, workspaceName, cwd) {
4188
+ if (!context.config || !context.configStore) {
4189
+ return { text: "当前没有加载可写入的配置。" };
4190
+ }
4191
+ const normalizedCwd = normalizePathForWorkspace(cwd);
4192
+ if (!await pathExists(normalizedCwd)) {
4193
+ return { text: `工作区路径不存在:${cwd}` };
4194
+ }
4195
+ const updated = await context.configStore.upsertWorkspace(workspaceName, normalizedCwd);
4196
+ context.replaceConfig(updated);
4197
+ return { text: `工作区「${workspaceName}」已保存` };
4198
+ }
4199
+ async function handleWorkspaceRemove(context, workspaceName) {
4200
+ if (!context.config || !context.configStore) {
4201
+ return { text: "当前没有加载可写入的配置。" };
4202
+ }
4203
+ const updated = await context.configStore.removeWorkspace(workspaceName);
4204
+ context.replaceConfig(updated);
4205
+ return { text: `工作区「${workspaceName}」已删除` };
4206
+ }
4207
+ async function pathExists(path11) {
4208
+ try {
4209
+ await access(path11);
4210
+ return true;
4211
+ } catch {
4212
+ return false;
4213
+ }
4214
+ }
4215
+ function normalizePathForWorkspace(path11) {
4216
+ const expanded = path11.startsWith("~") ? homedir2() + path11.slice(1) : path11;
4217
+ return normalize(expanded);
4218
+ }
4219
+ var init_workspace_handler = () => {};
4220
+
4221
+ // src/commands/handlers/session-shortcut-handler.ts
4222
+ import { access as access2 } from "node:fs/promises";
4223
+ import { basename as basename2, normalize as normalize2 } from "node:path";
4224
+ import { homedir as homedir3 } from "node:os";
4225
+ async function handleSessionShortcutCommand(context, ops, chatKey, agent, cwdInput, createNew) {
4226
+ if (!context.config || !context.configStore) {
4227
+ return { text: "当前没有加载可写入的配置。" };
4228
+ }
4229
+ const cwd = normalizePathForWorkspace2(cwdInput);
4230
+ if (!await pathExists2(cwd)) {
4231
+ return { text: `工作区路径不存在:${cwdInput}` };
4232
+ }
4233
+ const workspace = await resolveShortcutWorkspace(context, cwd);
4234
+ await context.logger.info("session.shortcut.workspace", "resolved shortcut workspace", {
4235
+ workspace: workspace.name,
4236
+ cwd: workspace.cwd,
4237
+ reused: workspace.reused
4238
+ });
4239
+ const baseAlias = `${workspace.name}:${agent}`;
4240
+ const alias = createNew ? await allocateUniqueSessionAlias(context, baseAlias, chatKey) : baseAlias;
4241
+ if (!createNew && await hasLogicalSession(context, alias, chatKey)) {
4242
+ await context.sessions.useSession(chatKey, alias);
4243
+ await context.logger.info("session.shortcut.reused", "reused existing logical session", {
4244
+ alias,
4245
+ workspace: workspace.name,
4246
+ agent
4247
+ });
4248
+ return {
4249
+ text: [
4250
+ `已切换到会话「${alias}」`,
4251
+ `- 复用工作区:${workspace.name}`,
4252
+ `- 复用会话:${alias}`
4253
+ ].join(`
4254
+ `)
4255
+ };
4256
+ }
4257
+ const session = ops.resolveSession(alias, agent, workspace.name, `${workspace.name}:${alias}`);
4258
+ try {
4259
+ await ops.ensureTransportSession(session);
4260
+ const exists = await ops.checkTransportSession(session);
4261
+ if (!exists) {
4262
+ return renderShortcutSessionCreationError(workspace, alias);
4216
4263
  }
4217
- return `${baseAlias}-${suffix}`;
4264
+ } catch {
4265
+ return renderShortcutSessionCreationError(workspace, alias);
4266
+ }
4267
+ await context.sessions.attachSession(alias, agent, workspace.name, session.transportSession);
4268
+ await ops.refreshSessionTransportAgentCommand(alias);
4269
+ await context.sessions.useSession(chatKey, alias);
4270
+ await context.logger.info("session.shortcut.created", "created new logical session from shortcut", {
4271
+ alias,
4272
+ workspace: workspace.name,
4273
+ agent,
4274
+ workspaceReused: workspace.reused
4275
+ });
4276
+ return {
4277
+ text: [
4278
+ `已创建并切换到会话「${alias}」`,
4279
+ workspace.reused ? `- 复用工作区:${workspace.name}` : `- 新增工作区:${workspace.name} -> ${workspace.cwd}`,
4280
+ `- 新增会话:${alias}`
4281
+ ].join(`
4282
+ `)
4283
+ };
4284
+ }
4285
+ async function resolveShortcutWorkspace(context, cwd) {
4286
+ const existingByPath = Object.entries(context.config?.workspaces ?? {}).find(([, workspace]) => sameWorkspacePath(workspace.cwd, cwd));
4287
+ if (existingByPath) {
4288
+ return {
4289
+ name: existingByPath[0],
4290
+ cwd: existingByPath[1].cwd,
4291
+ reused: true
4292
+ };
4293
+ }
4294
+ const workspaceName = allocateWorkspaceName(context, basename2(cwd));
4295
+ const updated = await context.configStore.upsertWorkspace(workspaceName, cwd);
4296
+ context.replaceConfig(updated);
4297
+ return {
4298
+ name: workspaceName,
4299
+ cwd,
4300
+ reused: false
4301
+ };
4302
+ }
4303
+ function allocateWorkspaceName(context, baseName) {
4304
+ if (!context.config?.workspaces[baseName]) {
4305
+ return baseName;
4306
+ }
4307
+ let suffix = 2;
4308
+ while (context.config.workspaces[`${baseName}-${suffix}`]) {
4309
+ suffix += 1;
4310
+ }
4311
+ return `${baseName}-${suffix}`;
4312
+ }
4313
+ async function allocateUniqueSessionAlias(context, baseAlias, chatKey) {
4314
+ if (!await hasLogicalSession(context, baseAlias, chatKey)) {
4315
+ return baseAlias;
4316
+ }
4317
+ let suffix = 2;
4318
+ while (await hasLogicalSession(context, `${baseAlias}-${suffix}`, chatKey)) {
4319
+ suffix += 1;
4320
+ }
4321
+ return `${baseAlias}-${suffix}`;
4322
+ }
4323
+ async function hasLogicalSession(context, alias, chatKey) {
4324
+ const sessions = await context.sessions.listSessions(chatKey);
4325
+ return sessions.some((session) => session.alias === alias);
4326
+ }
4327
+ function renderShortcutSessionCreationError(workspace, alias) {
4328
+ return {
4329
+ text: [
4330
+ `会话「${alias}」创建失败。`,
4331
+ workspace.reused ? `- 复用工作区:${workspace.name}` : `- 已新增工作区:${workspace.name} -> ${workspace.cwd}`,
4332
+ "- 会话未创建,请重试。"
4333
+ ].join(`
4334
+ `)
4335
+ };
4336
+ }
4337
+ async function pathExists2(path11) {
4338
+ try {
4339
+ await access2(path11);
4340
+ return true;
4341
+ } catch {
4342
+ return false;
4218
4343
  }
4219
- async hasLogicalSession(alias, chatKey) {
4220
- const sessions = await this.sessions.listSessions(chatKey);
4221
- return sessions.some((session) => session.alias === alias);
4344
+ }
4345
+ function normalizePathForWorkspace2(path11) {
4346
+ const expanded = path11.startsWith("~") ? homedir3() + path11.slice(1) : path11;
4347
+ return normalize2(expanded);
4348
+ }
4349
+ function sameWorkspacePath(left, right) {
4350
+ const normalizedLeft = normalizePathForWorkspace2(left);
4351
+ const normalizedRight = normalizePathForWorkspace2(right);
4352
+ if (process.platform === "win32") {
4353
+ return normalizedLeft.toLowerCase() === normalizedRight.toLowerCase();
4222
4354
  }
4223
- renderShortcutSessionCreationError(workspace, alias) {
4355
+ return normalizedLeft === normalizedRight;
4356
+ }
4357
+ var init_session_shortcut_handler = () => {};
4358
+
4359
+ // src/commands/handlers/session-recovery-handler.ts
4360
+ function renderTransportError(session, error) {
4361
+ const message = error instanceof Error ? error.message : String(error);
4362
+ if (message.includes("No acpx session found")) {
4224
4363
  return {
4225
4364
  text: [
4226
- `会话「${alias}」创建失败。`,
4227
- workspace.reused ? `- 复用工作区:${workspace.name}` : `- 已新增工作区:${workspace.name} -> ${workspace.cwd}`,
4228
- "- 会话未创建,请重试。"
4365
+ `当前会话「${session.alias}」暂时不可用。`,
4366
+ `请先在微信里重新执行:/session new ${session.alias} --agent ${session.agent} --ws ${session.workspace}`,
4367
+ `如果你要绑定一个已有会话,再执行:/session attach ${session.alias} --agent ${session.agent} --ws ${session.workspace} --name <会话名>`
4229
4368
  ].join(`
4230
4369
  `)
4231
4370
  };
4232
4371
  }
4372
+ if (!isPartialPromptOutputError(message)) {
4373
+ throw error;
4374
+ }
4375
+ return {
4376
+ text: [
4377
+ `当前会话「${session.alias}」执行中断,未收到最终回复。`,
4378
+ "请直接重试;如果长时间无响应,可先发送 /cancel 后再重试。",
4379
+ `错误信息:${summarizeTransportError(message)}`
4380
+ ].join(`
4381
+ `)
4382
+ };
4383
+ }
4384
+ function renderSessionCreationError(session, error) {
4385
+ const message = error instanceof Error ? error.message : String(error);
4386
+ if (message.includes("timed out") && message.includes("sessions new")) {
4387
+ return renderSessionCreationVerificationError(session);
4388
+ }
4389
+ throw error;
4390
+ }
4391
+ function renderSessionCreationVerificationError(session) {
4392
+ return {
4393
+ text: [
4394
+ "当前还不能直接在微信里创建新会话。",
4395
+ `请先准备好一个已有会话,然后在微信里执行:/session attach ${session.alias} --agent ${session.agent} --ws ${session.workspace} --name <会话名>`
4396
+ ].join(`
4397
+ `)
4398
+ };
4399
+ }
4400
+ async function tryRecoverMissingSession(ops, session, error) {
4401
+ const message = error instanceof Error ? error.message : String(error);
4402
+ if (!message.includes("No acpx session found")) {
4403
+ return null;
4404
+ }
4405
+ const transportAgentCommand = await ops.resolveSessionAgentCommand(session);
4406
+ if (!transportAgentCommand || transportAgentCommand === session.agentCommand) {
4407
+ return null;
4408
+ }
4409
+ await ops.setSessionTransportAgentCommand(session.alias, transportAgentCommand);
4410
+ return await ops.getSession(session.alias);
4411
+ }
4412
+ var init_session_recovery_handler = () => {};
4413
+
4414
+ // src/commands/handlers/session-reset-handler.ts
4415
+ async function handleSessionResetCommand(context, ops, chatKey) {
4416
+ const session = await context.sessions.getCurrentSession(chatKey);
4417
+ if (!session) {
4418
+ return { text: NO_CURRENT_SESSION_TEXT2 };
4419
+ }
4420
+ const resetSession = ops.resolveSession(session.alias, session.agent, session.workspace, buildResetTransportSessionName(session, ops.now()));
4421
+ try {
4422
+ await ops.ensureTransportSession(resetSession);
4423
+ const exists = await ops.checkTransportSession(resetSession);
4424
+ if (!exists) {
4425
+ return {
4426
+ text: [
4427
+ `会话「${session.alias}」重置失败。`,
4428
+ "新的后端会话未创建成功,请稍后重试。"
4429
+ ].join(`
4430
+ `)
4431
+ };
4432
+ }
4433
+ } catch (error) {
4434
+ return renderTransportError(resetSession, error);
4435
+ }
4436
+ await context.sessions.attachSession(resetSession.alias, resetSession.agent, resetSession.workspace, resetSession.transportSession);
4437
+ await ops.refreshSessionTransportAgentCommand(resetSession.alias);
4438
+ await context.sessions.useSession(chatKey, resetSession.alias);
4439
+ await context.logger.info("session.reset", "reset current logical session", {
4440
+ alias: resetSession.alias,
4441
+ agent: resetSession.agent,
4442
+ workspace: resetSession.workspace,
4443
+ transportSession: resetSession.transportSession,
4444
+ chatKey
4445
+ });
4446
+ return { text: `会话「${resetSession.alias}」已重置` };
4447
+ }
4448
+ function buildResetTransportSessionName(session, now) {
4449
+ return `${session.workspace}:${session.alias}:reset-${now}`;
4450
+ }
4451
+ var NO_CURRENT_SESSION_TEXT2 = "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。";
4452
+ var init_session_reset_handler = __esm(() => {
4453
+ init_session_recovery_handler();
4454
+ });
4455
+
4456
+ // src/commands/command-router.ts
4457
+ class CommandRouter {
4458
+ sessions;
4459
+ transport;
4460
+ config;
4461
+ configStore;
4462
+ resolveSessionAgentCommand;
4463
+ logger;
4464
+ constructor(sessions, transport, config, configStore, logger2, resolveSessionAgentCommand = resolveSessionAgentCommandFromIndex) {
4465
+ this.sessions = sessions;
4466
+ this.transport = transport;
4467
+ this.config = config;
4468
+ this.configStore = configStore;
4469
+ this.resolveSessionAgentCommand = resolveSessionAgentCommand;
4470
+ this.logger = logger2 ?? createNoopAppLogger();
4471
+ }
4472
+ async handle(chatKey, input, reply) {
4473
+ const startedAt = Date.now();
4474
+ const command = parseCommand(input);
4475
+ await this.logger.debug("command.parsed", "parsed inbound command", {
4476
+ chatKey,
4477
+ kind: command.kind
4478
+ });
4479
+ return await this.executeCommand(chatKey, command.kind, startedAt, async () => {
4480
+ switch (command.kind) {
4481
+ case "invalid":
4482
+ return {
4483
+ text: [
4484
+ "无法识别的命令格式。",
4485
+ "",
4486
+ "正确的会话创建格式:",
4487
+ "/session new <别名> --agent <Agent名> --ws <工作区名>",
4488
+ "",
4489
+ "例如:",
4490
+ "/session new demo --agent claude --ws weacpx"
4491
+ ].join(`
4492
+ `)
4493
+ };
4494
+ case "help":
4495
+ return handleHelp();
4496
+ case "agents":
4497
+ return handleAgents(this.createHandlerContext());
4498
+ case "agent.add":
4499
+ return await handleAgentAdd(this.createHandlerContext(), command.template);
4500
+ case "agent.rm":
4501
+ return await handleAgentRemove(this.createHandlerContext(), command.name);
4502
+ case "permission.status":
4503
+ return handlePermissionStatus(this.createHandlerContext(), "当前权限模式:");
4504
+ case "permission.mode.set":
4505
+ return await handlePermissionModeSet(this.createHandlerContext(), command.mode);
4506
+ case "permission.auto.status":
4507
+ return handlePermissionAutoStatus(this.createHandlerContext(), "当前非交互策略:");
4508
+ case "permission.auto.set":
4509
+ return await handlePermissionAutoSet(this.createHandlerContext(), command.policy);
4510
+ case "workspaces":
4511
+ return handleWorkspaces(this.createHandlerContext());
4512
+ case "workspace.new":
4513
+ return await handleWorkspaceCreate(this.createHandlerContext(), command.name, command.cwd);
4514
+ case "workspace.rm":
4515
+ return await handleWorkspaceRemove(this.createHandlerContext(), command.name);
4516
+ case "sessions":
4517
+ return await handleSessions(this.createSessionHandlerContext(), chatKey);
4518
+ case "session.new":
4519
+ return await handleSessionNew(this.createSessionHandlerContext(), chatKey, command.alias, command.agent, command.workspace);
4520
+ case "session.shortcut":
4521
+ return await handleSessionShortcut(this.createSessionHandlerContext(), chatKey, command.agent, command.cwd, false);
4522
+ case "session.shortcut.new":
4523
+ return await handleSessionShortcut(this.createSessionHandlerContext(), chatKey, command.agent, command.cwd, true);
4524
+ case "session.attach":
4525
+ return await handleSessionAttach(this.createSessionHandlerContext(), chatKey, command.alias, command.agent, command.workspace, command.transportSession);
4526
+ case "session.use":
4527
+ return await handleSessionUse(this.createSessionHandlerContext(), chatKey, command.alias);
4528
+ case "mode.show":
4529
+ return await handleModeShow(this.createSessionHandlerContext(), chatKey);
4530
+ case "mode.set":
4531
+ return await handleModeSet(this.createSessionHandlerContext(), chatKey, command.modeId);
4532
+ case "status":
4533
+ return await handleStatus(this.createSessionHandlerContext(), chatKey);
4534
+ case "cancel":
4535
+ return await handleCancel(this.createSessionHandlerContext(), chatKey);
4536
+ case "session.reset":
4537
+ return await handleSessionReset(this.createSessionHandlerContext(), chatKey);
4538
+ case "prompt":
4539
+ return await handlePrompt(this.createSessionHandlerContext(), chatKey, command.text, reply);
4540
+ }
4541
+ });
4542
+ }
4543
+ async clearSession(chatKey) {
4544
+ await handleSessionResetCommand(this.createHandlerContext(), this.createSessionResetOps(), chatKey);
4545
+ }
4546
+ createHandlerContext() {
4547
+ return {
4548
+ sessions: this.sessions,
4549
+ transport: this.transport,
4550
+ config: this.config,
4551
+ configStore: this.configStore,
4552
+ logger: this.logger,
4553
+ replaceConfig: (updated) => this.replaceConfig(updated)
4554
+ };
4555
+ }
4556
+ createSessionHandlerContext() {
4557
+ return {
4558
+ ...this.createHandlerContext(),
4559
+ lifecycle: this.createSessionLifecycleOps(),
4560
+ interaction: this.createSessionInteractionOps(),
4561
+ recovery: this.createSessionRenderRecoveryOps()
4562
+ };
4563
+ }
4564
+ createSessionLifecycleOps() {
4565
+ return {
4566
+ resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
4567
+ ensureTransportSession: (session) => this.ensureTransportSession(session),
4568
+ checkTransportSession: (session) => this.checkTransportSession(session),
4569
+ handleSessionShortcut: (chatKey, agent, cwdInput, createNew) => handleSessionShortcutCommand(this.createHandlerContext(), this.createSessionShortcutOps(), chatKey, agent, cwdInput, createNew),
4570
+ resetCurrentSession: (chatKey) => handleSessionResetCommand(this.createHandlerContext(), this.createSessionResetOps(), chatKey),
4571
+ refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
4572
+ };
4573
+ }
4574
+ createSessionInteractionOps() {
4575
+ return {
4576
+ setModeTransportSession: (session, modeId) => this.setModeTransportSession(session, modeId),
4577
+ cancelTransportSession: (session) => this.cancelTransportSession(session),
4578
+ promptTransportSession: (session, text, reply) => this.promptTransportSession(session, text, reply)
4579
+ };
4580
+ }
4581
+ createSessionRenderRecoveryOps() {
4582
+ return {
4583
+ renderSessionCreationError: (session, error) => renderSessionCreationError(session, error),
4584
+ renderSessionCreationVerificationError: (session) => renderSessionCreationVerificationError(session),
4585
+ tryRecoverMissingSession: (session, error) => tryRecoverMissingSession(this.createSessionRecoveryOps(), session, error),
4586
+ renderTransportError: (session, error) => renderTransportError(session, error)
4587
+ };
4588
+ }
4589
+ createSessionResetOps() {
4590
+ return {
4591
+ ensureTransportSession: (session) => this.ensureTransportSession(session),
4592
+ checkTransportSession: (session) => this.checkTransportSession(session),
4593
+ resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
4594
+ refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias),
4595
+ now: () => Date.now()
4596
+ };
4597
+ }
4598
+ createSessionRecoveryOps() {
4599
+ return {
4600
+ resolveSessionAgentCommand: (session) => this.resolveSessionAgentCommand(session),
4601
+ setSessionTransportAgentCommand: (alias, command) => this.sessions.setSessionTransportAgentCommand(alias, command),
4602
+ getSession: (alias) => this.sessions.getSession(alias)
4603
+ };
4604
+ }
4605
+ createSessionShortcutOps() {
4606
+ return {
4607
+ resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
4608
+ ensureTransportSession: (session) => this.ensureTransportSession(session),
4609
+ checkTransportSession: (session) => this.checkTransportSession(session),
4610
+ refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
4611
+ };
4612
+ }
4613
+ replaceConfig(updated) {
4614
+ if (!this.config) {
4615
+ return;
4616
+ }
4617
+ this.config.transport = { ...updated.transport };
4618
+ this.config.agents = { ...updated.agents };
4619
+ this.config.workspaces = { ...updated.workspaces };
4620
+ }
4233
4621
  async executeCommand(chatKey, kind, startedAt, operation) {
4234
4622
  try {
4235
4623
  const response = await operation();
@@ -4252,50 +4640,29 @@ class CommandRouter {
4252
4640
  async ensureTransportSession(session) {
4253
4641
  await this.measureTransportCall("ensure_session", session, () => this.transport.ensureSession(session));
4254
4642
  }
4255
- async resetCurrentSession(chatKey) {
4256
- const session = await this.sessions.getCurrentSession(chatKey);
4257
- if (!session) {
4258
- return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
4259
- }
4260
- const resetSession = this.sessions.resolveSession(session.alias, session.agent, session.workspace, this.buildResetTransportSessionName(session));
4261
- try {
4262
- await this.ensureTransportSession(resetSession);
4263
- const exists = await this.checkTransportSession(resetSession);
4264
- if (!exists) {
4265
- return {
4266
- text: [
4267
- `会话「${session.alias}」重置失败。`,
4268
- "新的后端会话未创建成功,请稍后重试。"
4269
- ].join(`
4270
- `)
4271
- };
4272
- }
4273
- } catch (error) {
4274
- return this.renderTransportError(resetSession, error);
4275
- }
4276
- await this.sessions.attachSession(resetSession.alias, resetSession.agent, resetSession.workspace, resetSession.transportSession);
4277
- await this.sessions.useSession(chatKey, resetSession.alias);
4278
- await this.logger.info("session.reset", "reset current logical session", {
4279
- alias: resetSession.alias,
4280
- agent: resetSession.agent,
4281
- workspace: resetSession.workspace,
4282
- transportSession: resetSession.transportSession,
4283
- chatKey
4284
- });
4285
- return { text: `会话「${resetSession.alias}」已重置` };
4286
- }
4287
- buildResetTransportSessionName(session) {
4288
- return `${session.workspace}:${session.alias}:reset-${Date.now()}`;
4289
- }
4290
4643
  async checkTransportSession(session) {
4291
4644
  return await this.measureTransportCall("has_session", session, () => this.transport.hasSession(session));
4292
4645
  }
4293
4646
  async promptTransportSession(session, text, reply) {
4294
4647
  return await this.measureTransportCall("prompt", session, () => this.transport.prompt(session, text, reply));
4295
4648
  }
4649
+ async setModeTransportSession(session, modeId) {
4650
+ return await this.measureTransportCall("set_mode", session, () => this.transport.setMode(session, modeId));
4651
+ }
4296
4652
  async cancelTransportSession(session) {
4297
4653
  return await this.measureTransportCall("cancel", session, () => this.transport.cancel(session));
4298
4654
  }
4655
+ async refreshSessionTransportAgentCommand(alias) {
4656
+ const session = await this.sessions.getSession(alias);
4657
+ if (!session) {
4658
+ return;
4659
+ }
4660
+ const transportAgentCommand = await this.resolveSessionAgentCommand(session);
4661
+ if (!transportAgentCommand) {
4662
+ return;
4663
+ }
4664
+ await this.sessions.setSessionTransportAgentCommand(alias, transportAgentCommand);
4665
+ }
4299
4666
  async measureTransportCall(operation, session, callback) {
4300
4667
  const startedAt = Date.now();
4301
4668
  try {
@@ -4333,90 +4700,17 @@ class CommandRouter {
4333
4700
  }
4334
4701
  }
4335
4702
  }
4336
- async function pathExists(path11) {
4337
- try {
4338
- await access(path11);
4339
- return true;
4340
- } catch {
4341
- return false;
4342
- }
4343
- }
4344
- function normalizePathForWorkspace(path11) {
4345
- const expanded = path11.startsWith("~") ? homedir() + path11.slice(1) : path11;
4346
- return normalize(expanded);
4347
- }
4348
- function sameWorkspacePath(left, right) {
4349
- const normalizedLeft = normalizePathForWorkspace(left);
4350
- const normalizedRight = normalizePathForWorkspace(right);
4351
- if (process.platform === "win32") {
4352
- return normalizedLeft.toLowerCase() === normalizedRight.toLowerCase();
4353
- }
4354
- return normalizedLeft === normalizedRight;
4355
- }
4356
- function summarizeTransportError(message) {
4357
- return message.replace(/\s+/g, " ").trim().slice(0, 200);
4358
- }
4359
- function summarizeTransportDiagnostic(output) {
4360
- const trimmed = output.replace(/\s+/g, " ").trim();
4361
- if (trimmed.length === 0) {
4362
- return;
4363
- }
4364
- return trimmed.slice(0, 200);
4365
- }
4366
- function summarizeTransportDiagnosticTail(output) {
4367
- const trimmed = output.replace(/\s+/g, " ").trim();
4368
- if (trimmed.length === 0) {
4369
- return;
4370
- }
4371
- return trimmed.slice(-200);
4372
- }
4373
- function summarizeTransportNdjson(output, prefix) {
4374
- const lines = output.split(`
4375
- `).map((line) => line.trim()).filter((line) => line.length > 0);
4376
- if (lines.length === 0) {
4377
- return {};
4378
- }
4379
- const methods = new Set;
4380
- let agentMessageChunkCount = 0;
4381
- let stopReason;
4382
- for (const line of lines) {
4383
- try {
4384
- const payload = JSON.parse(line);
4385
- if (typeof payload.method === "string" && payload.method.length > 0) {
4386
- methods.add(payload.method);
4387
- }
4388
- if (payload.params?.update?.sessionUpdate === "agent_message_chunk") {
4389
- agentMessageChunkCount += 1;
4390
- }
4391
- if (typeof payload.result?.stopReason === "string" && payload.result.stopReason.length > 0) {
4392
- stopReason = payload.result.stopReason;
4393
- }
4394
- } catch {
4395
- continue;
4396
- }
4397
- }
4398
- const summary = {
4399
- [`${prefix}LineCount`]: lines.length
4400
- };
4401
- if (methods.size > 0) {
4402
- summary[`${prefix}Methods`] = [...methods].join(",");
4403
- }
4404
- if (agentMessageChunkCount > 0) {
4405
- summary[`${prefix}AgentMessageChunkCount`] = agentMessageChunkCount;
4406
- }
4407
- if (stopReason) {
4408
- summary[`${prefix}StopReason`] = stopReason;
4409
- }
4410
- return summary;
4411
- }
4412
- function isPartialPromptOutputError(message) {
4413
- return message.includes("未收到最终回复");
4414
- }
4415
4703
  var init_command_router = __esm(() => {
4416
- init_agent_templates();
4417
4704
  init_app_logger();
4705
+ init_acpx_session_index();
4418
4706
  init_prompt_output();
4419
4707
  init_parse_command();
4708
+ init_help_handler();
4709
+ init_agent_handler();
4710
+ init_workspace_handler();
4711
+ init_session_shortcut_handler();
4712
+ init_session_recovery_handler();
4713
+ init_session_reset_handler();
4420
4714
  });
4421
4715
 
4422
4716
  // src/config/resolve-agent-command.ts
@@ -4435,12 +4729,12 @@ function isLegacyCodexCommand(command) {
4435
4729
  }
4436
4730
 
4437
4731
  // src/config/load-config.ts
4438
- import { readFile as readFile3 } from "node:fs/promises";
4732
+ import { readFile as readFile4 } from "node:fs/promises";
4439
4733
  function isRecord(value) {
4440
4734
  return typeof value === "object" && value !== null;
4441
4735
  }
4442
4736
  async function loadConfig(path11, options = {}) {
4443
- const raw = JSON.parse(await readFile3(path11, "utf8"));
4737
+ const raw = JSON.parse(await readFile4(path11, "utf8"));
4444
4738
  return parseConfig(raw, options);
4445
4739
  }
4446
4740
  function parseConfig(raw, options = {}) {
@@ -4608,7 +4902,7 @@ var init_config_store = __esm(() => {
4608
4902
  });
4609
4903
 
4610
4904
  // src/config/ensure-config.ts
4611
- import { readFile as readFile4 } from "node:fs/promises";
4905
+ import { readFile as readFile5 } from "node:fs/promises";
4612
4906
  async function ensureConfigExists(path11) {
4613
4907
  try {
4614
4908
  await loadConfig(path11);
@@ -4622,7 +4916,7 @@ async function ensureConfigExists(path11) {
4622
4916
  }
4623
4917
  async function loadDefaultConfigTemplate() {
4624
4918
  const templatePath = new URL("../../config.example.json", import.meta.url);
4625
- const template = JSON.parse(await readFile4(templatePath, "utf8"));
4919
+ const template = JSON.parse(await readFile5(templatePath, "utf8"));
4626
4920
  return {
4627
4921
  ...template,
4628
4922
  agents: Object.fromEntries(Object.entries(template.agents).map(([name, agent]) => [
@@ -4725,12 +5019,20 @@ class SessionService {
4725
5019
  agent,
4726
5020
  workspace,
4727
5021
  transport_session: transportSession,
5022
+ transport_agent_command: this.state.sessions[alias]?.transport_agent_command,
4728
5023
  created_at: this.state.sessions[alias]?.created_at ?? new Date().toISOString(),
4729
5024
  last_used_at: new Date().toISOString()
4730
5025
  });
4731
5026
  }
4732
- async attachSession(alias, agent, workspace, transportSession) {
4733
- return await this.createLogicalSession(alias, agent, workspace, transportSession);
5027
+ async attachSession(alias, agent, workspace, transportSession, transportAgentCommand) {
5028
+ return await this.createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand);
5029
+ }
5030
+ async getSession(alias) {
5031
+ const session = this.state.sessions[alias];
5032
+ if (!session) {
5033
+ return null;
5034
+ }
5035
+ return this.toResolvedSession(session);
4734
5036
  }
4735
5037
  async useSession(chatKey, alias) {
4736
5038
  const session = this.state.sessions[alias];
@@ -4741,6 +5043,24 @@ class SessionService {
4741
5043
  this.state.chat_contexts[chatKey] = { current_session: alias };
4742
5044
  await this.persist();
4743
5045
  }
5046
+ async setCurrentSessionMode(chatKey, modeId) {
5047
+ const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
5048
+ if (!currentAlias) {
5049
+ throw new Error("no current session selected");
5050
+ }
5051
+ const session = this.state.sessions[currentAlias];
5052
+ if (!session) {
5053
+ throw new Error("no current session selected");
5054
+ }
5055
+ const normalizedModeId = modeId?.trim();
5056
+ if (normalizedModeId) {
5057
+ session.mode_id = normalizedModeId;
5058
+ } else {
5059
+ delete session.mode_id;
5060
+ }
5061
+ session.last_used_at = new Date().toISOString();
5062
+ await this.persist();
5063
+ }
4744
5064
  async getCurrentSession(chatKey) {
4745
5065
  const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
4746
5066
  if (!currentAlias) {
@@ -4768,24 +5088,42 @@ class SessionService {
4768
5088
  return {
4769
5089
  alias: session.alias,
4770
5090
  agent: session.agent,
4771
- agentCommand: resolveAgentCommand(agentConfig.driver, agentConfig.command),
5091
+ agentCommand: session.transport_agent_command ?? resolveAgentCommand(agentConfig.driver, agentConfig.command),
4772
5092
  workspace: session.workspace,
4773
5093
  transportSession: session.transport_session,
5094
+ modeId: session.mode_id,
4774
5095
  cwd: this.config.workspaces[session.workspace].cwd
4775
5096
  };
4776
5097
  }
5098
+ async setSessionTransportAgentCommand(alias, transportAgentCommand) {
5099
+ const session = this.state.sessions[alias];
5100
+ if (!session) {
5101
+ throw new Error(`session "${alias}" does not exist`);
5102
+ }
5103
+ const normalized = transportAgentCommand?.trim();
5104
+ if (normalized) {
5105
+ session.transport_agent_command = normalized;
5106
+ } else {
5107
+ delete session.transport_agent_command;
5108
+ }
5109
+ session.last_used_at = new Date().toISOString();
5110
+ await this.persist();
5111
+ }
4777
5112
  async persist() {
4778
5113
  await this.stateStore.save(this.state);
4779
5114
  }
4780
- async createLogicalSession(alias, agent, workspace, transportSession) {
5115
+ async createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand) {
4781
5116
  this.validateSession(alias, agent, workspace);
4782
5117
  const existingSession = this.state.sessions[alias];
4783
5118
  const now = new Date().toISOString();
5119
+ const normalizedTransportAgentCommand = transportAgentCommand?.trim();
4784
5120
  const session = {
4785
5121
  alias,
4786
5122
  agent,
4787
5123
  workspace,
4788
5124
  transport_session: transportSession,
5125
+ ...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : existingSession?.transport_agent_command ? { transport_agent_command: existingSession.transport_agent_command } : {},
5126
+ mode_id: existingSession?.mode_id,
4789
5127
  created_at: existingSession?.created_at ?? now,
4790
5128
  last_used_at: now
4791
5129
  };
@@ -4813,7 +5151,7 @@ function createEmptyState() {
4813
5151
  }
4814
5152
 
4815
5153
  // src/state/state-store.ts
4816
- import { mkdir as mkdir7, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
5154
+ import { mkdir as mkdir7, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
4817
5155
  import { dirname as dirname6 } from "node:path";
4818
5156
 
4819
5157
  class StateStore {
@@ -4823,7 +5161,7 @@ class StateStore {
4823
5161
  }
4824
5162
  async load() {
4825
5163
  try {
4826
- const content = await readFile5(this.path, "utf8");
5164
+ const content = await readFile6(this.path, "utf8");
4827
5165
  if (content.trim() === "") {
4828
5166
  return createEmptyState();
4829
5167
  }
@@ -4918,9 +5256,9 @@ class AcpxBridgeClient {
4918
5256
  request(method, params) {
4919
5257
  const id = String(this.nextId);
4920
5258
  this.nextId += 1;
4921
- return awaitable((resolve, reject) => {
5259
+ return awaitable((resolve2, reject) => {
4922
5260
  this.pending.set(id, {
4923
- resolve: (value) => resolve(value),
5261
+ resolve: (value) => resolve2(value),
4924
5262
  reject
4925
5263
  });
4926
5264
  this.writeLine(encodeBridgeRequest({
@@ -5019,8 +5357,8 @@ async function spawnAcpxBridgeClient(options = {}) {
5019
5357
  return client;
5020
5358
  }
5021
5359
  function awaitable(executor) {
5022
- return new Promise((resolve, reject) => {
5023
- executor(resolve, reject);
5360
+ return new Promise((resolve2, reject) => {
5361
+ executor(resolve2, reject);
5024
5362
  });
5025
5363
  }
5026
5364
  var init_acpx_bridge_client = __esm(() => {
@@ -5042,6 +5380,12 @@ class AcpxBridgeTransport {
5042
5380
  text
5043
5381
  });
5044
5382
  }
5383
+ async setMode(session, modeId) {
5384
+ await this.client.request("setMode", {
5385
+ ...this.toParams(session),
5386
+ modeId
5387
+ });
5388
+ }
5045
5389
  async cancel(session) {
5046
5390
  return await this.client.request("cancel", this.toParams(session));
5047
5391
  }
@@ -5049,9 +5393,6 @@ class AcpxBridgeTransport {
5049
5393
  const result = await this.client.request("hasSession", this.toParams(session));
5050
5394
  return result.exists;
5051
5395
  }
5052
- async listSessions() {
5053
- return [];
5054
- }
5055
5396
  async dispose() {
5056
5397
  await this.client.dispose?.();
5057
5398
  }
@@ -5167,7 +5508,7 @@ import { createRequire as createRequire3 } from "node:module";
5167
5508
  import { spawn as spawn3 } from "node:child_process";
5168
5509
  import { spawn as spawnPty } from "node-pty";
5169
5510
  async function defaultRunner(command, args, options) {
5170
- return await new Promise((resolve, reject) => {
5511
+ return await new Promise((resolve2, reject) => {
5171
5512
  const spawnSpec = resolveSpawnCommand(command, args);
5172
5513
  const child = spawn3(spawnSpec.command, spawnSpec.args, { stdio: ["ignore", "pipe", "pipe"] });
5173
5514
  let stdout = "";
@@ -5190,14 +5531,14 @@ async function defaultRunner(command, args, options) {
5190
5531
  child.on("close", (code) => {
5191
5532
  if (timeoutId)
5192
5533
  clearTimeout(timeoutId);
5193
- resolve({ code: code ?? 1, stdout, stderr });
5534
+ resolve2({ code: code ?? 1, stdout, stderr });
5194
5535
  });
5195
5536
  });
5196
5537
  }
5197
5538
  async function defaultPtyRunner(command, args, options) {
5198
5539
  const helperPath = resolveNodePtyHelperPath(require3.resolve("node-pty/package.json"), process.platform, process.arch);
5199
5540
  await ensureNodePtyHelperExecutable(helperPath);
5200
- return await new Promise((resolve, reject) => {
5541
+ return await new Promise((resolve2, reject) => {
5201
5542
  const spawnSpec = resolveSpawnCommand(command, args);
5202
5543
  const child = spawnPty(spawnSpec.command, spawnSpec.args, {
5203
5544
  name: "xterm-color",
@@ -5217,7 +5558,7 @@ async function defaultPtyRunner(command, args, options) {
5217
5558
  child.onExit(({ exitCode }) => {
5218
5559
  if (timeoutId)
5219
5560
  clearTimeout(timeoutId);
5220
- resolve({ code: exitCode, stdout: output, stderr: "" });
5561
+ resolve2({ code: exitCode, stdout: output, stderr: "" });
5221
5562
  });
5222
5563
  });
5223
5564
  }
@@ -5258,6 +5599,14 @@ class AcpxCliTransport {
5258
5599
  const result = await this.runCommand(this.command, args);
5259
5600
  return { text: getPromptText(result) };
5260
5601
  }
5602
+ async setMode(session, modeId) {
5603
+ await this.run(this.buildArgs(session, [
5604
+ "set-mode",
5605
+ "-s",
5606
+ session.transportSession,
5607
+ modeId
5608
+ ]));
5609
+ }
5261
5610
  async cancel(session) {
5262
5611
  const output = await this.run(this.buildArgs(session, [
5263
5612
  "cancel",
@@ -5277,9 +5626,6 @@ class AcpxCliTransport {
5277
5626
  ]));
5278
5627
  return result.code === 0;
5279
5628
  }
5280
- async listSessions() {
5281
- return [];
5282
- }
5283
5629
  async run(args, options) {
5284
5630
  const result = await this.runCommandWithTimeout(this.runCommand, args, options);
5285
5631
  if (result.code !== 0) {
@@ -5315,7 +5661,7 @@ class AcpxCliTransport {
5315
5661
  ]);
5316
5662
  }
5317
5663
  async runStreamingPrompt(command, args, reply, maxSegmentWaitMs = 30000) {
5318
- return await new Promise((resolve, reject) => {
5664
+ return await new Promise((resolve2, reject) => {
5319
5665
  const spawnSpec = resolveSpawnCommand(command, args);
5320
5666
  const child = spawn3(spawnSpec.command, spawnSpec.args, { stdio: ["ignore", "pipe", "pipe"] });
5321
5667
  let stdout = "";
@@ -5357,7 +5703,7 @@ class AcpxCliTransport {
5357
5703
  if (remaining.length > 0) {
5358
5704
  reply(remaining).catch(() => {});
5359
5705
  }
5360
- resolve({ code: code ?? 1, stdout, stderr });
5706
+ resolve2({ code: code ?? 1, stdout, stderr });
5361
5707
  });
5362
5708
  });
5363
5709
  }
@@ -5428,7 +5774,7 @@ __export(exports_main, {
5428
5774
  main: () => main2,
5429
5775
  buildApp: () => buildApp
5430
5776
  });
5431
- import { homedir as homedir2 } from "node:os";
5777
+ import { homedir as homedir4 } from "node:os";
5432
5778
  import { dirname as dirname8, join as join4 } from "node:path";
5433
5779
  import { fileURLToPath as fileURLToPath3 } from "node:url";
5434
5780
  async function buildApp(paths, deps = {}) {
@@ -5491,7 +5837,7 @@ async function main2() {
5491
5837
  }
5492
5838
  }
5493
5839
  function resolveRuntimePaths() {
5494
- const home = process.env.HOME ?? homedir2();
5840
+ const home = process.env.HOME ?? homedir4();
5495
5841
  if (!home) {
5496
5842
  throw new Error("Unable to resolve the current user home directory");
5497
5843
  }
@@ -5528,7 +5874,7 @@ var init_main = __esm(async () => {
5528
5874
  });
5529
5875
 
5530
5876
  // src/cli.ts
5531
- import { homedir as homedir3 } from "node:os";
5877
+ import { homedir as homedir5 } from "node:os";
5532
5878
  import { sep } from "node:path";
5533
5879
  import { fileURLToPath as fileURLToPath4 } from "node:url";
5534
5880
 
@@ -5924,7 +6270,15 @@ class DaemonRuntime {
5924
6270
  }
5925
6271
 
5926
6272
  // src/cli.ts
5927
- var HELP_LINES = ["用法:", "weacpx login", "weacpx logout", "weacpx run", "weacpx start", "weacpx status", "weacpx stop"];
6273
+ var HELP_LINES = [
6274
+ "用法:",
6275
+ "weacpx login - 微信登录",
6276
+ "weacpx logout - 退出登录",
6277
+ "weacpx run - 前台运行",
6278
+ "weacpx start - 后台启动",
6279
+ "weacpx status - 查看状态",
6280
+ "weacpx stop - 停止服务"
6281
+ ];
5928
6282
  async function runCli(args, deps = {}) {
5929
6283
  const command = args[0];
5930
6284
  const print = deps.print ?? ((line) => console.log(line));
@@ -6018,7 +6372,7 @@ function createDefaultController() {
6018
6372
  });
6019
6373
  }
6020
6374
  function requireHome() {
6021
- const home = process.env.HOME ?? homedir3();
6375
+ const home = process.env.HOME ?? homedir5();
6022
6376
  if (!home) {
6023
6377
  throw new Error("Unable to resolve the current user home directory");
6024
6378
  }