weacpx 0.1.6 → 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
@@ -2829,20 +2829,26 @@ var init_slash_commands = __esm(() => {
2829
2829
  // src/weixin/messaging/process-message.ts
2830
2830
  import crypto4 from "node:crypto";
2831
2831
  import fs6 from "node:fs/promises";
2832
+ import { tmpdir } from "node:os";
2832
2833
  import path9 from "node:path";
2833
- async function saveMediaBuffer(buffer, contentType, subdir, _maxBytes, originalFilename) {
2834
- const dir = path9.join(MEDIA_TEMP_DIR, subdir ?? "");
2835
- await fs6.mkdir(dir, { recursive: true });
2836
- let ext = ".bin";
2837
- if (originalFilename) {
2838
- ext = path9.extname(originalFilename) || ".bin";
2839
- } else if (contentType) {
2840
- ext = getExtensionFromMime(contentType);
2841
- }
2842
- const name = `${Date.now()}-${crypto4.randomBytes(4).toString("hex")}${ext}`;
2843
- const filePath = path9.join(dir, name);
2844
- await fs6.writeFile(filePath, buffer);
2845
- 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
+ };
2846
2852
  }
2847
2853
  function extractTextBody(itemList) {
2848
2854
  if (!itemList?.length)
@@ -2857,10 +2863,10 @@ function extractTextBody(itemList) {
2857
2863
  function findMediaItem(itemList) {
2858
2864
  if (!itemList?.length)
2859
2865
  return;
2860
- 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);
2861
2867
  if (direct)
2862
2868
  return direct;
2863
- 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));
2864
2870
  return refItem?.ref_msg?.message_item ?? undefined;
2865
2871
  }
2866
2872
  async function processOneMessage(full, deps) {
@@ -2891,7 +2897,7 @@ async function processOneMessage(full, deps) {
2891
2897
  try {
2892
2898
  const downloaded = await downloadMediaFromItem(mediaItem, {
2893
2899
  cdnBaseUrl: deps.cdnBaseUrl,
2894
- saveMedia: saveMediaBuffer,
2900
+ saveMedia: createSaveMediaBuffer(deps.mediaTempDir),
2895
2901
  log: deps.log,
2896
2902
  errLog: deps.errLog,
2897
2903
  label: "inbound"
@@ -2914,7 +2920,7 @@ async function processOneMessage(full, deps) {
2914
2920
  };
2915
2921
  }
2916
2922
  } catch (err) {
2917
- logger.error(`media download failed: ${String(err)}`);
2923
+ deps.errLog(`media download failed: ${String(err)}`);
2918
2924
  }
2919
2925
  }
2920
2926
  const to = full.from_user_id ?? "";
@@ -2926,7 +2932,7 @@ async function processOneMessage(full, deps) {
2926
2932
  opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
2927
2933
  });
2928
2934
  } catch (err) {
2929
- logger.error(`intermediate reply failed: ${String(err)}`);
2935
+ deps.errLog(`intermediate reply failed: ${String(err)}`);
2930
2936
  }
2931
2937
  };
2932
2938
  const request = {
@@ -2959,7 +2965,7 @@ async function processOneMessage(full, deps) {
2959
2965
  let filePath;
2960
2966
  const mediaUrl = response.media.url;
2961
2967
  if (mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://")) {
2962
- filePath = await downloadRemoteImageToTemp(mediaUrl, path9.join(MEDIA_TEMP_DIR, "outbound"));
2968
+ filePath = await downloadRemoteImageToTemp(mediaUrl, path9.join(resolveMediaTempDir(deps.mediaTempDir), "outbound"));
2963
2969
  } else {
2964
2970
  filePath = path9.isAbsolute(mediaUrl) ? mediaUrl : path9.resolve(mediaUrl);
2965
2971
  }
@@ -2978,7 +2984,8 @@ async function processOneMessage(full, deps) {
2978
2984
  });
2979
2985
  }
2980
2986
  } catch (err) {
2981
- 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}`);
2982
2989
  sendWeixinErrorNotice({
2983
2990
  to,
2984
2991
  contextToken,
@@ -3003,14 +3010,13 @@ async function processOneMessage(full, deps) {
3003
3010
  }
3004
3011
  }
3005
3012
  }
3006
- 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;
3007
3014
  var init_process_message = __esm(() => {
3008
3015
  init_api();
3009
3016
  init_types();
3010
3017
  init_upload();
3011
3018
  init_media_download();
3012
3019
  init_mime();
3013
- init_logger();
3014
3020
  init_inbound();
3015
3021
  init_error_notice();
3016
3022
  init_send_media();
@@ -3327,90 +3333,6 @@ var init_weixin_sdk = __esm(() => {
3327
3333
  init_weixin();
3328
3334
  });
3329
3335
 
3330
- // src/config/agent-templates.ts
3331
- function getAgentTemplate(name) {
3332
- const template = TEMPLATES[name];
3333
- if (!template) {
3334
- return null;
3335
- }
3336
- return {
3337
- ...template
3338
- };
3339
- }
3340
- function listAgentTemplates() {
3341
- return Object.keys(TEMPLATES);
3342
- }
3343
- var TEMPLATES;
3344
- var init_agent_templates = __esm(() => {
3345
- TEMPLATES = {
3346
- codex: {
3347
- driver: "codex"
3348
- },
3349
- claude: {
3350
- driver: "claude"
3351
- }
3352
- };
3353
- });
3354
-
3355
- // src/formatting/render-text.ts
3356
- function renderHelpText() {
3357
- return [
3358
- "可用命令:",
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 - 停当前任务"
3394
- ].join(`
3395
- `);
3396
- }
3397
- function renderAgents(config) {
3398
- const names = Object.keys(config.agents);
3399
- if (names.length === 0) {
3400
- return "还没有注册任何 Agent。";
3401
- }
3402
- return ["已注册的 Agent:", ...names.map((name) => `- ${name}`)].join(`
3403
- `);
3404
- }
3405
- function renderWorkspaces(config) {
3406
- const names = Object.entries(config.workspaces);
3407
- if (names.length === 0) {
3408
- return "还没有注册任何工作区。";
3409
- }
3410
- return ["已注册的工作区:", ...names.map(([name, workspace]) => `- ${name}: ${workspace.cwd}`)].join(`
3411
- `);
3412
- }
3413
-
3414
3336
  // src/logging/app-logger.ts
3415
3337
  import { appendFile, mkdir as mkdir5, readdir, rename, rm as rm4, stat } from "node:fs/promises";
3416
3338
  import { basename, dirname as dirname4, join as join2 } from "node:path";
@@ -3898,11 +3820,640 @@ var init_parse_command = __esm(() => {
3898
3820
  ]);
3899
3821
  });
3900
3822
 
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: "当前没有加载可写入的配置。" };
3830
+ }
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: "当前没有加载可写入的配置。" };
3843
+ }
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>。" };
3862
+ }
3863
+ return {
3864
+ text: [
3865
+ "会话列表:",
3866
+ ...sessions.map((session) => `- ${session.alias} (${session.agent} @ ${session.workspace})${session.isCurrent ? " [当前]" : ""}`)
3867
+ ].join(`
3868
+ `)
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);
3878
+ }
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) {
3899
+ return {
3900
+ text: [
3901
+ "没有找到可绑定的已有会话。",
3902
+ `请确认会话名是否正确,然后重新执行:/session attach ${alias} --agent ${agent} --ws ${workspace} --name <会话名>`
3903
+ ].join(`
3904
+ `)
3905
+ };
3906
+ }
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 };
3930
+ }
3931
+ return {
3932
+ text: [
3933
+ "当前 mode:",
3934
+ `- 会话:${session.alias}`,
3935
+ `- mode:${session.modeId ?? "未设置"}`
3936
+ ].join(`
3937
+ `)
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 };
3944
+ }
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 };
3953
+ }
3954
+ return {
3955
+ text: [
3956
+ "当前会话:",
3957
+ `- 名称:${session.alias}`,
3958
+ `- Agent:${session.agent}`,
3959
+ `- 工作区:${session.workspace}`
3960
+ ].join(`
3961
+ `)
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 };
3968
+ }
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);
3974
+ }
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 };
3992
+ }
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;
4039
+ }
4040
+ }
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"
4145
+ }
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);
4263
+ }
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;
4343
+ }
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();
4354
+ }
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")) {
4363
+ return {
4364
+ text: [
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 <会话名>`
4368
+ ].join(`
4369
+ `)
4370
+ };
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
+
3901
4456
  // src/commands/command-router.ts
3902
- import { access } from "node:fs/promises";
3903
- import { basename as basename2, normalize } from "node:path";
3904
- import { homedir as homedir2 } from "node:os";
3905
-
3906
4457
  class CommandRouter {
3907
4458
  sessions;
3908
4459
  transport;
@@ -3941,388 +4492,132 @@ class CommandRouter {
3941
4492
  `)
3942
4493
  };
3943
4494
  case "help":
3944
- return { text: renderHelpText() };
4495
+ return handleHelp();
3945
4496
  case "agents":
3946
- return { text: this.config ? renderAgents(this.config) : "No config loaded." };
3947
- case "agent.add": {
3948
- if (!this.config || !this.configStore) {
3949
- return { text: "当前没有加载可写入的配置。" };
3950
- }
3951
- const template = getAgentTemplate(command.template);
3952
- if (!template) {
3953
- return { text: `暂不支持这个 Agent 模板。当前可用:${listAgentTemplates().join("、")}` };
3954
- }
3955
- const updated = await this.configStore.upsertAgent(command.template, template);
3956
- this.replaceConfig(updated);
3957
- return { text: `Agent「${command.template}」已保存` };
3958
- }
3959
- case "agent.rm": {
3960
- if (!this.config || !this.configStore) {
3961
- return { text: "当前没有加载可写入的配置。" };
3962
- }
3963
- if (!this.config.agents[command.name]) {
3964
- return { text: "没有找到这个 Agent。" };
3965
- }
3966
- const updated = await this.configStore.removeAgent(command.name);
3967
- this.replaceConfig(updated);
3968
- return { text: `Agent「${command.name}」已删除` };
3969
- }
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);
3970
4502
  case "permission.status":
3971
- return { text: this.renderPermissionStatus("当前权限模式:") };
3972
- case "permission.mode.set": {
3973
- if (!this.config || !this.configStore) {
3974
- return { text: "当前没有加载可写入的配置。" };
3975
- }
3976
- const updated = await this.configStore.updateTransport({
3977
- permissionMode: command.mode
3978
- });
3979
- this.replaceConfig(updated);
3980
- return { text: this.renderPermissionStatus("权限模式已更新:") };
3981
- }
4503
+ return handlePermissionStatus(this.createHandlerContext(), "当前权限模式:");
4504
+ case "permission.mode.set":
4505
+ return await handlePermissionModeSet(this.createHandlerContext(), command.mode);
3982
4506
  case "permission.auto.status":
3983
- return { text: this.renderPermissionStatus("当前非交互策略:") };
3984
- case "permission.auto.set": {
3985
- if (!this.config || !this.configStore) {
3986
- return { text: "当前没有加载可写入的配置。" };
3987
- }
3988
- const updated = await this.configStore.updateTransport({
3989
- nonInteractivePermissions: command.policy
3990
- });
3991
- this.replaceConfig(updated);
3992
- return { text: this.renderPermissionStatus("非交互策略已更新:") };
3993
- }
4507
+ return handlePermissionAutoStatus(this.createHandlerContext(), "当前非交互策略:");
4508
+ case "permission.auto.set":
4509
+ return await handlePermissionAutoSet(this.createHandlerContext(), command.policy);
3994
4510
  case "workspaces":
3995
- return { text: this.config ? renderWorkspaces(this.config) : "No config loaded." };
3996
- case "workspace.new": {
3997
- if (!this.config || !this.configStore) {
3998
- return { text: "当前没有加载可写入的配置。" };
3999
- }
4000
- const wsCwd = normalizePathForWorkspace(command.cwd);
4001
- if (!await pathExists(wsCwd)) {
4002
- return { text: `工作区路径不存在:${command.cwd}` };
4003
- }
4004
- const updated = await this.configStore.upsertWorkspace(command.name, wsCwd);
4005
- this.replaceConfig(updated);
4006
- return { text: `工作区「${command.name}」已保存` };
4007
- }
4008
- case "workspace.rm": {
4009
- if (!this.config || !this.configStore) {
4010
- return { text: "当前没有加载可写入的配置。" };
4011
- }
4012
- const updated = await this.configStore.removeWorkspace(command.name);
4013
- this.replaceConfig(updated);
4014
- return { text: `工作区「${command.name}」已删除` };
4015
- }
4016
- case "sessions": {
4017
- const sessions = await this.sessions.listSessions(chatKey);
4018
- if (sessions.length === 0) {
4019
- return { text: "还没有会话。请先执行 /session new <alias> --agent <name> --ws <name>。" };
4020
- }
4021
- return {
4022
- text: [
4023
- "会话列表:",
4024
- ...sessions.map((session) => `- ${session.alias} (${session.agent} @ ${session.workspace})${session.isCurrent ? " [当前]" : ""}`)
4025
- ].join(`
4026
- `)
4027
- };
4028
- }
4029
- case "session.new": {
4030
- const session = this.sessions.resolveSession(command.alias, command.agent, command.workspace, `${command.workspace}:${command.alias}`);
4031
- try {
4032
- await this.ensureTransportSession(session);
4033
- const exists = await this.checkTransportSession(session);
4034
- if (!exists) {
4035
- return this.renderSessionCreationVerificationError(session);
4036
- }
4037
- } catch (error) {
4038
- return this.renderSessionCreationError(session, error);
4039
- }
4040
- await this.sessions.attachSession(command.alias, command.agent, command.workspace, session.transportSession);
4041
- await this.refreshSessionTransportAgentCommand(command.alias);
4042
- await this.sessions.useSession(chatKey, command.alias);
4043
- await this.logger.info("session.created", "created and selected logical session", {
4044
- alias: command.alias,
4045
- agent: command.agent,
4046
- workspace: command.workspace
4047
- });
4048
- return { text: `会话「${command.alias}」已创建并切换` };
4049
- }
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);
4050
4520
  case "session.shortcut":
4051
- return await this.handleSessionShortcut(chatKey, command.agent, command.cwd, false);
4521
+ return await handleSessionShortcut(this.createSessionHandlerContext(), chatKey, command.agent, command.cwd, false);
4052
4522
  case "session.shortcut.new":
4053
- return await this.handleSessionShortcut(chatKey, command.agent, command.cwd, true);
4054
- case "session.attach": {
4055
- const attached = this.sessions.resolveSession(command.alias, command.agent, command.workspace, command.transportSession);
4056
- const exists = await this.checkTransportSession(attached);
4057
- if (!exists) {
4058
- return {
4059
- text: [
4060
- "没有找到可绑定的已有会话。",
4061
- `请确认会话名是否正确,然后重新执行:/session attach ${command.alias} --agent ${command.agent} --ws ${command.workspace} --name <会话名>`
4062
- ].join(`
4063
- `)
4064
- };
4065
- }
4066
- await this.sessions.attachSession(command.alias, command.agent, command.workspace, command.transportSession);
4067
- await this.refreshSessionTransportAgentCommand(command.alias);
4068
- await this.sessions.useSession(chatKey, command.alias);
4069
- await this.logger.info("session.attached", "attached existing transport session", {
4070
- alias: command.alias,
4071
- agent: command.agent,
4072
- workspace: command.workspace,
4073
- transportSession: command.transportSession
4074
- });
4075
- return { text: `会话「${command.alias}」已绑定并切换` };
4076
- }
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);
4077
4526
  case "session.use":
4078
- await this.sessions.useSession(chatKey, command.alias);
4079
- await this.logger.info("session.selected", "selected logical session", {
4080
- alias: command.alias,
4081
- chatKey
4082
- });
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
- }
4107
- case "status": {
4108
- const session = await this.sessions.getCurrentSession(chatKey);
4109
- if (!session) {
4110
- return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
4111
- }
4112
- return {
4113
- text: [
4114
- "当前会话:",
4115
- `- 名称:${session.alias}`,
4116
- `- Agent:${session.agent}`,
4117
- `- 工作区:${session.workspace}`
4118
- ].join(`
4119
- `)
4120
- };
4121
- }
4122
- case "cancel": {
4123
- const session = await this.sessions.getCurrentSession(chatKey);
4124
- if (!session) {
4125
- return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
4126
- }
4127
- try {
4128
- const result = await this.cancelTransportSession(session);
4129
- return { text: result.message || "cancelled" };
4130
- } catch (error) {
4131
- return this.renderTransportError(session, error);
4132
- }
4133
- }
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);
4134
4536
  case "session.reset":
4135
- return await this.resetCurrentSession(chatKey);
4136
- case "prompt": {
4137
- const session = await this.sessions.getCurrentSession(chatKey);
4138
- if (!session) {
4139
- return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
4140
- }
4141
- try {
4142
- const result = await this.promptTransportSession(session, command.text, reply);
4143
- return { text: result.text };
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
- }
4150
- return this.renderTransportError(session, error);
4151
- }
4152
- }
4537
+ return await handleSessionReset(this.createSessionHandlerContext(), chatKey);
4538
+ case "prompt":
4539
+ return await handlePrompt(this.createSessionHandlerContext(), chatKey, command.text, reply);
4153
4540
  }
4154
4541
  });
4155
4542
  }
4156
4543
  async clearSession(chatKey) {
4157
- await this.resetCurrentSession(chatKey);
4544
+ await handleSessionResetCommand(this.createHandlerContext(), this.createSessionResetOps(), chatKey);
4158
4545
  }
4159
- async handleSessionShortcut(chatKey, agent, cwdInput, createNew) {
4160
- if (!this.config || !this.configStore) {
4161
- return { text: "当前没有加载可写入的配置。" };
4162
- }
4163
- const cwd = normalizePathForWorkspace(cwdInput);
4164
- if (!await pathExists(cwd)) {
4165
- return { text: `工作区路径不存在:${cwdInput}` };
4166
- }
4167
- const workspace = await this.resolveShortcutWorkspace(cwd);
4168
- await this.logger.info("session.shortcut.workspace", "resolved shortcut workspace", {
4169
- workspace: workspace.name,
4170
- cwd: workspace.cwd,
4171
- reused: workspace.reused
4172
- });
4173
- const baseAlias = `${workspace.name}:${agent}`;
4174
- const alias = createNew ? await this.allocateUniqueSessionAlias(baseAlias, chatKey) : baseAlias;
4175
- if (!createNew && await this.hasLogicalSession(alias, chatKey)) {
4176
- await this.sessions.useSession(chatKey, alias);
4177
- await this.logger.info("session.shortcut.reused", "reused existing logical session", {
4178
- alias,
4179
- workspace: workspace.name,
4180
- agent
4181
- });
4182
- return {
4183
- text: [
4184
- `已切换到会话「${alias}」`,
4185
- `- 复用工作区:${workspace.name}`,
4186
- `- 复用会话:${alias}`
4187
- ].join(`
4188
- `)
4189
- };
4190
- }
4191
- const session = this.sessions.resolveSession(alias, agent, workspace.name, `${workspace.name}:${alias}`);
4192
- try {
4193
- await this.ensureTransportSession(session);
4194
- const exists = await this.checkTransportSession(session);
4195
- if (!exists) {
4196
- return this.renderShortcutSessionCreationError(workspace, alias);
4197
- }
4198
- } catch {
4199
- return this.renderShortcutSessionCreationError(workspace, alias);
4200
- }
4201
- await this.sessions.attachSession(alias, agent, workspace.name, session.transportSession);
4202
- await this.refreshSessionTransportAgentCommand(alias);
4203
- await this.sessions.useSession(chatKey, alias);
4204
- await this.logger.info("session.shortcut.created", "created new logical session from shortcut", {
4205
- alias,
4206
- workspace: workspace.name,
4207
- agent,
4208
- workspaceReused: workspace.reused
4209
- });
4546
+ createHandlerContext() {
4210
4547
  return {
4211
- text: [
4212
- `已创建并切换到会话「${alias}」`,
4213
- workspace.reused ? `- 复用工作区:${workspace.name}` : `- 新增工作区:${workspace.name} -> ${workspace.cwd}`,
4214
- `- 新增会话:${alias}`
4215
- ].join(`
4216
- `)
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)
4217
4554
  };
4218
4555
  }
4219
- replaceConfig(updated) {
4220
- if (!this.config) {
4221
- return;
4222
- }
4223
- this.config.transport = { ...updated.transport };
4224
- this.config.agents = { ...updated.agents };
4225
- this.config.workspaces = { ...updated.workspaces };
4226
- }
4227
- renderPermissionStatus(title) {
4228
- const permissionMode = this.config?.transport.permissionMode ?? "approve-all";
4229
- const nonInteractivePermissions = this.config?.transport.nonInteractivePermissions ?? "fail";
4230
- return [title, `- mode: ${permissionMode}`, `- auto: ${nonInteractivePermissions}`].join(`
4231
- `);
4232
- }
4233
- renderTransportError(session, error) {
4234
- const message = error instanceof Error ? error.message : String(error);
4235
- if (message.includes("No acpx session found")) {
4236
- return {
4237
- text: [
4238
- `当前会话「${session.alias}」暂时不可用。`,
4239
- `请先在微信里重新执行:/session new ${session.alias} --agent ${session.agent} --ws ${session.workspace}`,
4240
- `如果你要绑定一个已有会话,再执行:/session attach ${session.alias} --agent ${session.agent} --ws ${session.workspace} --name <会话名>`
4241
- ].join(`
4242
- `)
4243
- };
4244
- }
4245
- if (!isPartialPromptOutputError(message)) {
4246
- throw error;
4247
- }
4556
+ createSessionHandlerContext() {
4248
4557
  return {
4249
- text: [
4250
- `当前会话「${session.alias}」执行中断,未收到最终回复。`,
4251
- "请直接重试;如果长时间无响应,可先发送 /cancel 后再重试。",
4252
- `错误信息:${summarizeTransportError(message)}`
4253
- ].join(`
4254
- `)
4558
+ ...this.createHandlerContext(),
4559
+ lifecycle: this.createSessionLifecycleOps(),
4560
+ interaction: this.createSessionInteractionOps(),
4561
+ recovery: this.createSessionRenderRecoveryOps()
4255
4562
  };
4256
4563
  }
4257
- renderSessionCreationError(session, error) {
4258
- const message = error instanceof Error ? error.message : String(error);
4259
- if (message.includes("timed out") && message.includes("sessions new")) {
4260
- return this.renderSessionCreationVerificationError(session);
4261
- }
4262
- throw error;
4263
- }
4264
- renderSessionCreationVerificationError(session) {
4564
+ createSessionLifecycleOps() {
4265
4565
  return {
4266
- text: [
4267
- "当前还不能直接在微信里创建新会话。",
4268
- `请先准备好一个已有会话,然后在微信里执行:/session attach ${session.alias} --agent ${session.agent} --ws ${session.workspace} --name <会话名>`
4269
- ].join(`
4270
- `)
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)
4271
4572
  };
4272
4573
  }
4273
- async resolveShortcutWorkspace(cwd) {
4274
- const existingByPath = Object.entries(this.config?.workspaces ?? {}).find(([, workspace]) => sameWorkspacePath(workspace.cwd, cwd));
4275
- if (existingByPath) {
4276
- return {
4277
- name: existingByPath[0],
4278
- cwd: existingByPath[1].cwd,
4279
- reused: true
4280
- };
4281
- }
4282
- const baseName = basename2(cwd);
4283
- const workspaceName = this.allocateWorkspaceName(baseName, cwd);
4284
- const updated = await this.configStore.upsertWorkspace(workspaceName, cwd);
4285
- this.replaceConfig(updated);
4574
+ createSessionInteractionOps() {
4286
4575
  return {
4287
- name: workspaceName,
4288
- cwd,
4289
- reused: false
4576
+ setModeTransportSession: (session, modeId) => this.setModeTransportSession(session, modeId),
4577
+ cancelTransportSession: (session) => this.cancelTransportSession(session),
4578
+ promptTransportSession: (session, text, reply) => this.promptTransportSession(session, text, reply)
4290
4579
  };
4291
4580
  }
4292
- allocateWorkspaceName(baseName, cwd) {
4293
- if (!this.config?.workspaces[baseName]) {
4294
- return baseName;
4295
- }
4296
- let suffix = 2;
4297
- while (this.config.workspaces[`${baseName}-${suffix}`]) {
4298
- suffix += 1;
4299
- }
4300
- return `${baseName}-${suffix}`;
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
+ };
4301
4588
  }
4302
- async allocateUniqueSessionAlias(baseAlias, chatKey) {
4303
- if (!await this.hasLogicalSession(baseAlias, chatKey)) {
4304
- return baseAlias;
4305
- }
4306
- let suffix = 2;
4307
- while (await this.hasLogicalSession(`${baseAlias}-${suffix}`, chatKey)) {
4308
- suffix += 1;
4309
- }
4310
- return `${baseAlias}-${suffix}`;
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
+ };
4311
4597
  }
4312
- async hasLogicalSession(alias, chatKey) {
4313
- const sessions = await this.sessions.listSessions(chatKey);
4314
- return sessions.some((session) => session.alias === alias);
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
+ };
4315
4604
  }
4316
- renderShortcutSessionCreationError(workspace, alias) {
4605
+ createSessionShortcutOps() {
4317
4606
  return {
4318
- text: [
4319
- `会话「${alias}」创建失败。`,
4320
- workspace.reused ? `- 复用工作区:${workspace.name}` : `- 已新增工作区:${workspace.name} -> ${workspace.cwd}`,
4321
- "- 会话未创建,请重试。"
4322
- ].join(`
4323
- `)
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)
4324
4611
  };
4325
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
+ }
4326
4621
  async executeCommand(chatKey, kind, startedAt, operation) {
4327
4622
  try {
4328
4623
  const response = await operation();
@@ -4345,42 +4640,6 @@ class CommandRouter {
4345
4640
  async ensureTransportSession(session) {
4346
4641
  await this.measureTransportCall("ensure_session", session, () => this.transport.ensureSession(session));
4347
4642
  }
4348
- async resetCurrentSession(chatKey) {
4349
- const session = await this.sessions.getCurrentSession(chatKey);
4350
- if (!session) {
4351
- return { text: "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。" };
4352
- }
4353
- const resetSession = this.sessions.resolveSession(session.alias, session.agent, session.workspace, this.buildResetTransportSessionName(session));
4354
- try {
4355
- await this.ensureTransportSession(resetSession);
4356
- const exists = await this.checkTransportSession(resetSession);
4357
- if (!exists) {
4358
- return {
4359
- text: [
4360
- `会话「${session.alias}」重置失败。`,
4361
- "新的后端会话未创建成功,请稍后重试。"
4362
- ].join(`
4363
- `)
4364
- };
4365
- }
4366
- } catch (error) {
4367
- return this.renderTransportError(resetSession, error);
4368
- }
4369
- await this.sessions.attachSession(resetSession.alias, resetSession.agent, resetSession.workspace, resetSession.transportSession);
4370
- await this.refreshSessionTransportAgentCommand(resetSession.alias);
4371
- await this.sessions.useSession(chatKey, resetSession.alias);
4372
- await this.logger.info("session.reset", "reset current logical session", {
4373
- alias: resetSession.alias,
4374
- agent: resetSession.agent,
4375
- workspace: resetSession.workspace,
4376
- transportSession: resetSession.transportSession,
4377
- chatKey
4378
- });
4379
- return { text: `会话「${resetSession.alias}」已重置` };
4380
- }
4381
- buildResetTransportSessionName(session) {
4382
- return `${session.workspace}:${session.alias}:reset-${Date.now()}`;
4383
- }
4384
4643
  async checkTransportSession(session) {
4385
4644
  return await this.measureTransportCall("has_session", session, () => this.transport.hasSession(session));
4386
4645
  }
@@ -4404,18 +4663,6 @@ class CommandRouter {
4404
4663
  }
4405
4664
  await this.sessions.setSessionTransportAgentCommand(alias, transportAgentCommand);
4406
4665
  }
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
- }
4419
4666
  async measureTransportCall(operation, session, callback) {
4420
4667
  const startedAt = Date.now();
4421
4668
  try {
@@ -4453,91 +4700,17 @@ class CommandRouter {
4453
4700
  }
4454
4701
  }
4455
4702
  }
4456
- async function pathExists(path11) {
4457
- try {
4458
- await access(path11);
4459
- return true;
4460
- } catch {
4461
- return false;
4462
- }
4463
- }
4464
- function normalizePathForWorkspace(path11) {
4465
- const expanded = path11.startsWith("~") ? homedir2() + path11.slice(1) : path11;
4466
- return normalize(expanded);
4467
- }
4468
- function sameWorkspacePath(left, right) {
4469
- const normalizedLeft = normalizePathForWorkspace(left);
4470
- const normalizedRight = normalizePathForWorkspace(right);
4471
- if (process.platform === "win32") {
4472
- return normalizedLeft.toLowerCase() === normalizedRight.toLowerCase();
4473
- }
4474
- return normalizedLeft === normalizedRight;
4475
- }
4476
- function summarizeTransportError(message) {
4477
- return message.replace(/\s+/g, " ").trim().slice(0, 200);
4478
- }
4479
- function summarizeTransportDiagnostic(output) {
4480
- const trimmed = output.replace(/\s+/g, " ").trim();
4481
- if (trimmed.length === 0) {
4482
- return;
4483
- }
4484
- return trimmed.slice(0, 200);
4485
- }
4486
- function summarizeTransportDiagnosticTail(output) {
4487
- const trimmed = output.replace(/\s+/g, " ").trim();
4488
- if (trimmed.length === 0) {
4489
- return;
4490
- }
4491
- return trimmed.slice(-200);
4492
- }
4493
- function summarizeTransportNdjson(output, prefix) {
4494
- const lines = output.split(`
4495
- `).map((line) => line.trim()).filter((line) => line.length > 0);
4496
- if (lines.length === 0) {
4497
- return {};
4498
- }
4499
- const methods = new Set;
4500
- let agentMessageChunkCount = 0;
4501
- let stopReason;
4502
- for (const line of lines) {
4503
- try {
4504
- const payload = JSON.parse(line);
4505
- if (typeof payload.method === "string" && payload.method.length > 0) {
4506
- methods.add(payload.method);
4507
- }
4508
- if (payload.params?.update?.sessionUpdate === "agent_message_chunk") {
4509
- agentMessageChunkCount += 1;
4510
- }
4511
- if (typeof payload.result?.stopReason === "string" && payload.result.stopReason.length > 0) {
4512
- stopReason = payload.result.stopReason;
4513
- }
4514
- } catch {
4515
- continue;
4516
- }
4517
- }
4518
- const summary = {
4519
- [`${prefix}LineCount`]: lines.length
4520
- };
4521
- if (methods.size > 0) {
4522
- summary[`${prefix}Methods`] = [...methods].join(",");
4523
- }
4524
- if (agentMessageChunkCount > 0) {
4525
- summary[`${prefix}AgentMessageChunkCount`] = agentMessageChunkCount;
4526
- }
4527
- if (stopReason) {
4528
- summary[`${prefix}StopReason`] = stopReason;
4529
- }
4530
- return summary;
4531
- }
4532
- function isPartialPromptOutputError(message) {
4533
- return message.includes("未收到最终回复");
4534
- }
4535
4703
  var init_command_router = __esm(() => {
4536
- init_agent_templates();
4537
4704
  init_app_logger();
4538
4705
  init_acpx_session_index();
4539
4706
  init_prompt_output();
4540
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();
4541
4714
  });
4542
4715
 
4543
4716
  // src/config/resolve-agent-command.ts
@@ -5220,9 +5393,6 @@ class AcpxBridgeTransport {
5220
5393
  const result = await this.client.request("hasSession", this.toParams(session));
5221
5394
  return result.exists;
5222
5395
  }
5223
- async listSessions() {
5224
- return [];
5225
- }
5226
5396
  async dispose() {
5227
5397
  await this.client.dispose?.();
5228
5398
  }
@@ -5456,9 +5626,6 @@ class AcpxCliTransport {
5456
5626
  ]));
5457
5627
  return result.code === 0;
5458
5628
  }
5459
- async listSessions() {
5460
- return [];
5461
- }
5462
5629
  async run(args, options) {
5463
5630
  const result = await this.runCommandWithTimeout(this.runCommand, args, options);
5464
5631
  if (result.code !== 0) {
@@ -5607,7 +5774,7 @@ __export(exports_main, {
5607
5774
  main: () => main2,
5608
5775
  buildApp: () => buildApp
5609
5776
  });
5610
- import { homedir as homedir3 } from "node:os";
5777
+ import { homedir as homedir4 } from "node:os";
5611
5778
  import { dirname as dirname8, join as join4 } from "node:path";
5612
5779
  import { fileURLToPath as fileURLToPath3 } from "node:url";
5613
5780
  async function buildApp(paths, deps = {}) {
@@ -5670,7 +5837,7 @@ async function main2() {
5670
5837
  }
5671
5838
  }
5672
5839
  function resolveRuntimePaths() {
5673
- const home = process.env.HOME ?? homedir3();
5840
+ const home = process.env.HOME ?? homedir4();
5674
5841
  if (!home) {
5675
5842
  throw new Error("Unable to resolve the current user home directory");
5676
5843
  }
@@ -5707,7 +5874,7 @@ var init_main = __esm(async () => {
5707
5874
  });
5708
5875
 
5709
5876
  // src/cli.ts
5710
- import { homedir as homedir4 } from "node:os";
5877
+ import { homedir as homedir5 } from "node:os";
5711
5878
  import { sep } from "node:path";
5712
5879
  import { fileURLToPath as fileURLToPath4 } from "node:url";
5713
5880
 
@@ -6205,7 +6372,7 @@ function createDefaultController() {
6205
6372
  });
6206
6373
  }
6207
6374
  function requireHome() {
6208
- const home = process.env.HOME ?? homedir4();
6375
+ const home = process.env.HOME ?? homedir5();
6209
6376
  if (!home) {
6210
6377
  throw new Error("Unable to resolve the current user home directory");
6211
6378
  }