weacpx 0.1.7 → 0.2.0

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
@@ -47,6 +47,129 @@ var __export = (target, all) => {
47
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
49
49
 
50
+ // src/weixin/monitor/consumer-lock.ts
51
+ import { mkdir as mkdir5, open as open2, readFile as readFile3, rm as rm4 } from "node:fs/promises";
52
+ import { dirname as dirname4, join as join2 } from "node:path";
53
+ import { homedir } from "node:os";
54
+ function createWeixinConsumerLock(options = {}) {
55
+ const lockFilePath = options.lockFilePath ?? join2(homedir(), ".weacpx", "runtime", "weixin-consumer.lock.json");
56
+ const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning2;
57
+ const onDiagnostic = options.onDiagnostic;
58
+ return {
59
+ async acquire(meta) {
60
+ await mkdir5(dirname4(lockFilePath), { recursive: true });
61
+ while (true) {
62
+ try {
63
+ const handle = await open2(lockFilePath, "wx");
64
+ try {
65
+ await handle.writeFile(`${JSON.stringify(meta, null, 2)}
66
+ `, "utf8");
67
+ } finally {
68
+ await handle.close();
69
+ }
70
+ await onDiagnostic?.("lock_acquired", {
71
+ lockFilePath,
72
+ pid: meta.pid,
73
+ mode: meta.mode,
74
+ configPath: meta.configPath,
75
+ statePath: meta.statePath,
76
+ hostname: meta.hostname
77
+ });
78
+ return;
79
+ } catch (error) {
80
+ const code = error.code;
81
+ if (code !== "EEXIST") {
82
+ throw error;
83
+ }
84
+ await onDiagnostic?.("lock_exists", {
85
+ lockFilePath,
86
+ pid: meta.pid,
87
+ mode: meta.mode
88
+ });
89
+ const existing = await loadLockMetadata(lockFilePath);
90
+ if (!existing) {
91
+ await rm4(lockFilePath, { force: true });
92
+ await onDiagnostic?.("lock_invalid_removed", {
93
+ lockFilePath,
94
+ reason: "invalid_or_unreadable_metadata"
95
+ });
96
+ continue;
97
+ }
98
+ if (!isProcessRunning(existing.pid)) {
99
+ await rm4(lockFilePath, { force: true });
100
+ await onDiagnostic?.("lock_stale_removed", {
101
+ lockFilePath,
102
+ stalePid: existing.pid,
103
+ staleMode: existing.mode,
104
+ staleConfigPath: existing.configPath,
105
+ staleStatePath: existing.statePath,
106
+ reason: "owner_process_not_running"
107
+ });
108
+ continue;
109
+ }
110
+ await onDiagnostic?.("lock_active_conflict", {
111
+ lockFilePath,
112
+ activePid: existing.pid,
113
+ activeMode: existing.mode,
114
+ activeConfigPath: existing.configPath,
115
+ activeStatePath: existing.statePath,
116
+ requestedPid: meta.pid,
117
+ requestedMode: meta.mode
118
+ });
119
+ throw new ActiveWeixinConsumerLockError(lockFilePath, existing);
120
+ }
121
+ }
122
+ },
123
+ async release() {
124
+ await rm4(lockFilePath, { force: true });
125
+ await onDiagnostic?.("lock_released", {
126
+ lockFilePath
127
+ });
128
+ }
129
+ };
130
+ }
131
+ async function loadLockMetadata(path) {
132
+ try {
133
+ const raw = await readFile3(path, "utf8");
134
+ const parsed = JSON.parse(raw);
135
+ if (!parsed || typeof parsed.pid !== "number" || !parsed.mode || !parsed.configPath || !parsed.statePath) {
136
+ return null;
137
+ }
138
+ return parsed;
139
+ } catch {
140
+ return null;
141
+ }
142
+ }
143
+ function defaultIsProcessRunning2(pid) {
144
+ try {
145
+ process.kill(pid, 0);
146
+ return true;
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
151
+ var ActiveWeixinConsumerLockError;
152
+ var init_consumer_lock = __esm(() => {
153
+ ActiveWeixinConsumerLockError = class ActiveWeixinConsumerLockError extends Error {
154
+ existing;
155
+ lockFilePath;
156
+ constructor(lockFilePath, existing) {
157
+ super([
158
+ "weacpx Weixin consumer is already running.",
159
+ `pid: ${existing.pid}`,
160
+ `mode: ${existing.mode}`,
161
+ `config: ${existing.configPath}`,
162
+ `state: ${existing.statePath}`,
163
+ "Try stopping the existing instance or close the foreground `weacpx run` process before starting a new one."
164
+ ].join(`
165
+ `));
166
+ this.name = "ActiveWeixinConsumerLockError";
167
+ this.lockFilePath = lockFilePath;
168
+ this.existing = existing;
169
+ }
170
+ };
171
+ });
172
+
50
173
  // node_modules/qrcode-terminal/vendor/QRCode/QRMode.js
51
174
  var require_QRMode = __commonJS((exports, module) => {
52
175
  module.exports = {
@@ -2428,6 +2551,25 @@ var init_media_download = __esm(() => {
2428
2551
  WEIXIN_MEDIA_MAX_BYTES = 100 * 1024 * 1024;
2429
2552
  });
2430
2553
 
2554
+ // src/weixin/messaging/execute-chat-turn.ts
2555
+ async function executeChatTurn(params) {
2556
+ let usedReply = false;
2557
+ const response = await params.agent.chat({
2558
+ ...params.request,
2559
+ reply: async (text) => {
2560
+ const delivered = await params.onReplySegment?.(text);
2561
+ if (delivered !== false) {
2562
+ usedReply = true;
2563
+ }
2564
+ }
2565
+ });
2566
+ return {
2567
+ text: usedReply ? undefined : response.text,
2568
+ media: response.media,
2569
+ usedReply
2570
+ };
2571
+ }
2572
+
2431
2573
  // src/weixin/messaging/inbound.ts
2432
2574
  function contextTokenKey(accountId, userId) {
2433
2575
  return `${accountId}:${userId}`;
@@ -2826,7 +2968,7 @@ var init_slash_commands = __esm(() => {
2826
2968
  init_send();
2827
2969
  });
2828
2970
 
2829
- // src/weixin/messaging/process-message.ts
2971
+ // src/weixin/messaging/handle-weixin-message-turn.ts
2830
2972
  import crypto4 from "node:crypto";
2831
2973
  import fs6 from "node:fs/promises";
2832
2974
  import { tmpdir } from "node:os";
@@ -2869,7 +3011,7 @@ function findMediaItem(itemList) {
2869
3011
  const refItem = itemList.find((item) => item.type === MessageItemType.TEXT && item.ref_msg?.message_item && isMediaItem(item.ref_msg.message_item));
2870
3012
  return refItem?.ref_msg?.message_item ?? undefined;
2871
3013
  }
2872
- async function processOneMessage(full, deps) {
3014
+ async function handleWeixinMessageTurn(full, deps) {
2873
3015
  const receivedAt = Date.now();
2874
3016
  const textBody = extractTextBody(full.item_list);
2875
3017
  if (textBody.startsWith("/")) {
@@ -2924,22 +3066,27 @@ async function processOneMessage(full, deps) {
2924
3066
  }
2925
3067
  }
2926
3068
  const to = full.from_user_id ?? "";
2927
- const reply = async (text) => {
3069
+ const sendReplySegment = async (text) => {
3070
+ const plainText = markdownToPlainText(text).trim();
3071
+ if (plainText.length === 0) {
3072
+ return false;
3073
+ }
2928
3074
  try {
2929
3075
  await sendMessageWeixin({
2930
3076
  to,
2931
- text: markdownToPlainText(text),
3077
+ text: plainText,
2932
3078
  opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
2933
3079
  });
3080
+ return true;
2934
3081
  } catch (err) {
2935
3082
  deps.errLog(`intermediate reply failed: ${String(err)}`);
3083
+ return false;
2936
3084
  }
2937
3085
  };
2938
3086
  const request = {
2939
3087
  conversationId: full.from_user_id ?? "",
2940
3088
  text: bodyFromItemList(full.item_list),
2941
- media,
2942
- reply
3089
+ media
2943
3090
  };
2944
3091
  let typingTimer;
2945
3092
  const startTyping = () => {
@@ -2960,10 +3107,14 @@ async function processOneMessage(full, deps) {
2960
3107
  typingTimer = setInterval(startTyping, 1e4);
2961
3108
  }
2962
3109
  try {
2963
- const response = await deps.agent.chat(request);
2964
- if (response.media) {
3110
+ const turn = await executeChatTurn({
3111
+ agent: deps.agent,
3112
+ request,
3113
+ onReplySegment: sendReplySegment
3114
+ });
3115
+ if (turn.media) {
2965
3116
  let filePath;
2966
- const mediaUrl = response.media.url;
3117
+ const mediaUrl = turn.media.url;
2967
3118
  if (mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://")) {
2968
3119
  filePath = await downloadRemoteImageToTemp(mediaUrl, path9.join(resolveMediaTempDir(deps.mediaTempDir), "outbound"));
2969
3120
  } else {
@@ -2972,20 +3123,24 @@ async function processOneMessage(full, deps) {
2972
3123
  await sendWeixinMediaFile({
2973
3124
  filePath,
2974
3125
  to,
2975
- text: response.text ? markdownToPlainText(response.text) : "",
3126
+ text: turn.text ? markdownToPlainText(turn.text) : "",
2976
3127
  opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken },
2977
3128
  cdnBaseUrl: deps.cdnBaseUrl
2978
3129
  });
2979
- } else if (response.text) {
3130
+ } else if (turn.text) {
3131
+ const finalText = markdownToPlainText(turn.text).trim();
3132
+ if (finalText.length === 0) {
3133
+ return;
3134
+ }
2980
3135
  await sendMessageWeixin({
2981
3136
  to,
2982
- text: markdownToPlainText(response.text),
3137
+ text: finalText,
2983
3138
  opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
2984
3139
  });
2985
3140
  }
2986
3141
  } catch (err) {
2987
3142
  const errorText = err instanceof Error ? err.stack ?? err.message : JSON.stringify(err);
2988
- deps.errLog(`processOneMessage: agent or send failed: ${errorText}`);
3143
+ deps.errLog(`handleWeixinMessageTurn: agent or send failed: ${errorText}`);
2989
3144
  sendWeixinErrorNotice({
2990
3145
  to,
2991
3146
  contextToken,
@@ -3011,7 +3166,7 @@ async function processOneMessage(full, deps) {
3011
3166
  }
3012
3167
  }
3013
3168
  var hasDownloadableMedia = (media) => media?.encrypt_query_param || media?.full_url;
3014
- var init_process_message = __esm(() => {
3169
+ var init_handle_weixin_message_turn = __esm(() => {
3015
3170
  init_api();
3016
3171
  init_types();
3017
3172
  init_upload();
@@ -3144,7 +3299,7 @@ async function monitorWeixinProvider(opts) {
3144
3299
  aLog.info(`inbound: from=${full.from_user_id} types=${full.item_list?.map((i) => i.type).join(",") ?? "none"}`);
3145
3300
  const fromUserId = full.from_user_id ?? "";
3146
3301
  const cachedConfig = await configManager.getForUser(fromUserId, full.context_token);
3147
- await processOneMessage(full, {
3302
+ await handleWeixinMessageTurn(full, {
3148
3303
  accountId,
3149
3304
  agent,
3150
3305
  baseUrl,
@@ -3186,7 +3341,7 @@ var init_monitor = __esm(() => {
3186
3341
  init_api();
3187
3342
  init_config_cache();
3188
3343
  init_session_guard();
3189
- init_process_message();
3344
+ init_handle_weixin_message_turn();
3190
3345
  init_sync_buf();
3191
3346
  init_logger();
3192
3347
  });
@@ -3334,8 +3489,8 @@ var init_weixin_sdk = __esm(() => {
3334
3489
  });
3335
3490
 
3336
3491
  // src/logging/app-logger.ts
3337
- import { appendFile, mkdir as mkdir5, readdir, rename, rm as rm4, stat } from "node:fs/promises";
3338
- import { basename, dirname as dirname4, join as join2 } from "node:path";
3492
+ import { appendFile, mkdir as mkdir6, readdir, rename, rm as rm5, stat } from "node:fs/promises";
3493
+ import { basename, dirname as dirname5, join as join3 } from "node:path";
3339
3494
  function createNoopAppLogger() {
3340
3495
  return {
3341
3496
  debug: async () => {},
@@ -3365,7 +3520,7 @@ function createAppLogger(options) {
3365
3520
  return;
3366
3521
  }
3367
3522
  const line = formatLogLine(now(), level, event, message, context);
3368
- await mkdir5(dirname4(options.filePath), { recursive: true });
3523
+ await mkdir6(dirname5(options.filePath), { recursive: true });
3369
3524
  await rotateIfNeeded(options.filePath, Buffer.byteLength(line), options.maxSizeBytes, options.maxFiles);
3370
3525
  await appendFile(options.filePath, line, "utf8");
3371
3526
  }
@@ -3386,10 +3541,10 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
3386
3541
  return;
3387
3542
  }
3388
3543
  if (maxFiles <= 0) {
3389
- await rm4(filePath, { force: true });
3544
+ await rm5(filePath, { force: true });
3390
3545
  return;
3391
3546
  }
3392
- await rm4(`${filePath}.${maxFiles}`, { force: true });
3547
+ await rm5(`${filePath}.${maxFiles}`, { force: true });
3393
3548
  for (let index = maxFiles - 1;index >= 1; index -= 1) {
3394
3549
  const source = `${filePath}.${index}`;
3395
3550
  try {
@@ -3403,7 +3558,7 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
3403
3558
  await rename(filePath, `${filePath}.1`);
3404
3559
  }
3405
3560
  async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
3406
- const parentDir = dirname4(filePath);
3561
+ const parentDir = dirname5(filePath);
3407
3562
  const prefix = `${basename(filePath)}.`;
3408
3563
  const cutoff = now().getTime() - retentionDays * 24 * 60 * 60 * 1000;
3409
3564
  let files = [];
@@ -3419,10 +3574,10 @@ async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
3419
3574
  if (!file.startsWith(prefix) || !/^\d+$/.test(file.slice(prefix.length))) {
3420
3575
  continue;
3421
3576
  }
3422
- const candidate = join2(parentDir, file);
3577
+ const candidate = join3(parentDir, file);
3423
3578
  const details = await stat(candidate);
3424
3579
  if (details.mtime.getTime() < cutoff) {
3425
- await rm4(candidate, { force: true });
3580
+ await rm5(candidate, { force: true });
3426
3581
  }
3427
3582
  }
3428
3583
  }
@@ -3454,16 +3609,16 @@ var init_app_logger = __esm(() => {
3454
3609
  });
3455
3610
 
3456
3611
  // src/transport/acpx-session-index.ts
3457
- import { readFile as readFile3 } from "node:fs/promises";
3458
- import { homedir } from "node:os";
3612
+ import { readFile as readFile4 } from "node:fs/promises";
3613
+ import { homedir as homedir2 } from "node:os";
3459
3614
  import { resolve } from "node:path";
3460
3615
  async function resolveSessionAgentCommandFromIndex(session) {
3461
- const home = process.env.HOME ?? homedir();
3616
+ const home = process.env.HOME ?? homedir2();
3462
3617
  if (!home) {
3463
3618
  return;
3464
3619
  }
3465
3620
  try {
3466
- const raw = await readFile3(resolve(home, ".acpx", "sessions", "index.json"), "utf8");
3621
+ const raw = await readFile4(resolve(home, ".acpx", "sessions", "index.json"), "utf8");
3467
3622
  const parsed = JSON.parse(raw);
3468
3623
  const targetCwd = resolve(session.cwd);
3469
3624
  const match = parsed.entries?.find((entry) => entry.name === session.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
@@ -3614,8 +3769,10 @@ function parseCommand(input) {
3614
3769
  }
3615
3770
  const parts = tokenizeCommand(trimmed);
3616
3771
  const command = normalizeCommand(parts[0] ?? "");
3617
- if (command === "/help")
3772
+ if (command === "/help" && parts.length === 1)
3618
3773
  return { kind: "help" };
3774
+ if (command === "/help" && parts.length === 2)
3775
+ return { kind: "help", topic: parts[1] };
3619
3776
  if (command === "/agents")
3620
3777
  return { kind: "agents" };
3621
3778
  if (command === "/workspaces")
@@ -3630,6 +3787,10 @@ function parseCommand(input) {
3630
3787
  return { kind: "session.reset" };
3631
3788
  if (command === "/mode" && parts.length === 1)
3632
3789
  return { kind: "mode.show" };
3790
+ if (command === "/replymode" && parts.length === 1)
3791
+ return { kind: "replymode.show" };
3792
+ if (command === "/config" && parts.length === 1)
3793
+ return { kind: "config.show" };
3633
3794
  if (command === "/permission" && parts.length === 1)
3634
3795
  return { kind: "permission.status" };
3635
3796
  if (command === "/session" && parts.length === 1)
@@ -3653,12 +3814,21 @@ function parseCommand(input) {
3653
3814
  return { kind: "permission.auto.set", policy };
3654
3815
  }
3655
3816
  }
3817
+ if (command === "/config" && parts[1] === "set" && parts.length === 4) {
3818
+ return { kind: "config.set", path: parts[2] ?? "", value: parts[3] ?? "" };
3819
+ }
3656
3820
  if (command === "/use" && parts[1]) {
3657
3821
  return { kind: "session.use", alias: parts[1] };
3658
3822
  }
3659
3823
  if (command === "/mode" && parts[1]) {
3660
3824
  return { kind: "mode.set", modeId: parts[1] };
3661
3825
  }
3826
+ if (command === "/replymode" && parts[1] === "reset" && parts.length === 2) {
3827
+ return { kind: "replymode.reset" };
3828
+ }
3829
+ if (command === "/replymode" && (parts[1] === "stream" || parts[1] === "final") && parts.length === 2) {
3830
+ return { kind: "replymode.set", replyMode: parts[1] };
3831
+ }
3662
3832
  if (command === "/agent" && parts[1] === "add" && parts[2]) {
3663
3833
  return { kind: "agent.add", template: parts[2] };
3664
3834
  }
@@ -3668,13 +3838,23 @@ function parseCommand(input) {
3668
3838
  if (command === "/workspace" && parts[1] === "new" && parts[2]) {
3669
3839
  const name = parts[2];
3670
3840
  let cwd = "";
3841
+ let invalid = false;
3671
3842
  for (let index = 3;index < parts.length; index += 1) {
3672
3843
  if (parts[index] === "--cwd" || parts[index] === "-d") {
3844
+ if (index + 1 >= parts.length) {
3845
+ invalid = true;
3846
+ break;
3847
+ }
3673
3848
  cwd = parts[index + 1] ?? "";
3674
3849
  index += 1;
3850
+ continue;
3675
3851
  }
3852
+ invalid = true;
3853
+ break;
3854
+ }
3855
+ if (!invalid && name.trim().length > 0 && cwd.trim().length > 0) {
3856
+ return { kind: "workspace.new", name, cwd };
3676
3857
  }
3677
- return { kind: "workspace.new", name, cwd };
3678
3858
  }
3679
3859
  if (command === "/workspace" && parts[1] === "rm" && parts[2]) {
3680
3860
  return { kind: "workspace.rm", name: parts[2] };
@@ -3684,26 +3864,41 @@ function parseCommand(input) {
3684
3864
  const alias = parts[2];
3685
3865
  let agent = "";
3686
3866
  let workspace = "";
3867
+ let invalid = false;
3687
3868
  for (let index = 3;index < parts.length; index += 1) {
3688
3869
  if (parts[index] === "--agent" || parts[index] === "-a") {
3870
+ if (index + 1 >= parts.length) {
3871
+ invalid = true;
3872
+ break;
3873
+ }
3689
3874
  agent = parts[index + 1] ?? "";
3690
3875
  index += 1;
3876
+ continue;
3691
3877
  } else if (parts[index] === "--ws" || parts[index] === "-ws") {
3878
+ if (index + 1 >= parts.length) {
3879
+ invalid = true;
3880
+ break;
3881
+ }
3692
3882
  workspace = parts[index + 1] ?? "";
3693
3883
  index += 1;
3884
+ continue;
3694
3885
  }
3886
+ invalid = true;
3887
+ break;
3888
+ }
3889
+ if (!invalid && alias.trim().length > 0 && agent.trim().length > 0 && workspace.trim().length > 0) {
3890
+ return { kind: "session.new", alias, agent, workspace };
3695
3891
  }
3696
- return { kind: "session.new", alias, agent, workspace };
3697
3892
  }
3698
- const cwd = readFlagValue(parts, ["--cwd", "-d"]);
3699
- if (cwd) {
3700
- return { kind: "session.shortcut.new", agent: parts[2], cwd };
3893
+ const shortcutTarget = readSessionShortcutTarget(parts, 3);
3894
+ if (shortcutTarget) {
3895
+ return { kind: "session.shortcut.new", agent: parts[2], ...shortcutTarget };
3701
3896
  }
3702
3897
  }
3703
3898
  if (command === "/session" && parts[1] && parts[1] !== "new" && parts[1] !== "attach" && parts[1] !== "reset") {
3704
- const cwd = readFlagValue(parts, ["--cwd", "-d"]);
3705
- if (cwd) {
3706
- return { kind: "session.shortcut", agent: parts[1], cwd };
3899
+ const shortcutTarget = readSessionShortcutTarget(parts, 2);
3900
+ if (shortcutTarget) {
3901
+ return { kind: "session.shortcut", agent: parts[1], ...shortcutTarget };
3707
3902
  }
3708
3903
  }
3709
3904
  if (command === "/session" && parts[1] === "attach" && parts[2]) {
@@ -3711,19 +3906,39 @@ function parseCommand(input) {
3711
3906
  let agent = "";
3712
3907
  let workspace = "";
3713
3908
  let transportSession = "";
3909
+ let invalid = false;
3714
3910
  for (let index = 3;index < parts.length; index += 1) {
3715
3911
  if (parts[index] === "--agent" || parts[index] === "-a") {
3912
+ if (index + 1 >= parts.length) {
3913
+ invalid = true;
3914
+ break;
3915
+ }
3716
3916
  agent = parts[index + 1] ?? "";
3717
3917
  index += 1;
3918
+ continue;
3718
3919
  } else if (parts[index] === "--ws" || parts[index] === "-ws") {
3920
+ if (index + 1 >= parts.length) {
3921
+ invalid = true;
3922
+ break;
3923
+ }
3719
3924
  workspace = parts[index + 1] ?? "";
3720
3925
  index += 1;
3926
+ continue;
3721
3927
  } else if (parts[index] === "--name") {
3928
+ if (index + 1 >= parts.length) {
3929
+ invalid = true;
3930
+ break;
3931
+ }
3722
3932
  transportSession = parts[index + 1] ?? "";
3723
3933
  index += 1;
3934
+ continue;
3724
3935
  }
3936
+ invalid = true;
3937
+ break;
3938
+ }
3939
+ if (!invalid && alias.trim().length > 0 && agent.trim().length > 0 && workspace.trim().length > 0 && transportSession.trim().length > 0) {
3940
+ return { kind: "session.attach", alias, agent, workspace, transportSession };
3725
3941
  }
3726
- return { kind: "session.attach", alias, agent, workspace, transportSession };
3727
3942
  }
3728
3943
  if (command.startsWith("/") && isRecognizedCommand(command)) {
3729
3944
  return { kind: "invalid", text: trimmed, recognizedCommand: command };
@@ -3733,13 +3948,42 @@ function parseCommand(input) {
3733
3948
  function hasAnyFlag(parts, flags) {
3734
3949
  return parts.some((part) => flags.includes(part));
3735
3950
  }
3736
- function readFlagValue(parts, flags) {
3737
- for (let index = 0;index < parts.length; index += 1) {
3738
- if (flags.includes(parts[index] ?? "")) {
3739
- return parts[index + 1] ?? "";
3951
+ function readSessionShortcutTarget(parts, startIndex) {
3952
+ let cwd = "";
3953
+ let workspace = "";
3954
+ let invalid = false;
3955
+ for (let index = startIndex;index < parts.length; index += 1) {
3956
+ if (parts[index] === "--cwd" || parts[index] === "-d") {
3957
+ if (index + 1 >= parts.length || workspace) {
3958
+ invalid = true;
3959
+ break;
3960
+ }
3961
+ cwd = parts[index + 1] ?? "";
3962
+ index += 1;
3963
+ continue;
3740
3964
  }
3965
+ if (parts[index] === "--ws" || parts[index] === "-ws") {
3966
+ if (index + 1 >= parts.length || cwd) {
3967
+ invalid = true;
3968
+ break;
3969
+ }
3970
+ workspace = parts[index + 1] ?? "";
3971
+ index += 1;
3972
+ continue;
3973
+ }
3974
+ invalid = true;
3975
+ break;
3741
3976
  }
3742
- return "";
3977
+ if (invalid) {
3978
+ return null;
3979
+ }
3980
+ if (cwd.trim().length > 0) {
3981
+ return { cwd };
3982
+ }
3983
+ if (workspace.trim().length > 0) {
3984
+ return { workspace };
3985
+ }
3986
+ return null;
3743
3987
  }
3744
3988
  function normalizeCommand(command) {
3745
3989
  if (command === "/ss")
@@ -3765,7 +4009,7 @@ function toPermissionMode(value) {
3765
4009
  return null;
3766
4010
  }
3767
4011
  function toNonInteractivePermission(value) {
3768
- if (value === "allow" || value === "deny" || value === "fail") {
4012
+ if (value === "deny" || value === "fail") {
3769
4013
  return value;
3770
4014
  }
3771
4015
  return null;
@@ -3812,6 +4056,8 @@ var init_parse_command = __esm(() => {
3812
4056
  "/cancel",
3813
4057
  "/clear",
3814
4058
  "/mode",
4059
+ "/replymode",
4060
+ "/config",
3815
4061
  "/permission",
3816
4062
  "/session",
3817
4063
  "/workspace",
@@ -3820,6 +4066,17 @@ var init_parse_command = __esm(() => {
3820
4066
  ]);
3821
4067
  });
3822
4068
 
4069
+ // src/commands/config-clone.ts
4070
+ function cloneAppConfig(config) {
4071
+ return {
4072
+ transport: { ...config.transport },
4073
+ logging: { ...config.logging },
4074
+ wechat: { ...config.wechat },
4075
+ agents: Object.fromEntries(Object.entries(config.agents).map(([name, agent]) => [name, { ...agent }])),
4076
+ workspaces: Object.fromEntries(Object.entries(config.workspaces).map(([name, workspace]) => [name, { ...workspace }]))
4077
+ };
4078
+ }
4079
+
3823
4080
  // src/commands/handlers/permission-handler.ts
3824
4081
  function handlePermissionStatus(context, title) {
3825
4082
  return { text: renderPermissionStatus(context.config, title) };
@@ -3828,9 +4085,17 @@ async function handlePermissionModeSet(context, mode) {
3828
4085
  if (!context.config || !context.configStore) {
3829
4086
  return { text: "当前没有加载可写入的配置。" };
3830
4087
  }
4088
+ const previous = cloneAppConfig(context.config);
3831
4089
  const updated = await context.configStore.updateTransport({
3832
4090
  permissionMode: mode
3833
4091
  });
4092
+ try {
4093
+ await context.transport.updatePermissionPolicy?.(updated.transport);
4094
+ } catch (error) {
4095
+ await context.configStore.save(previous);
4096
+ context.replaceConfig(previous);
4097
+ throw error;
4098
+ }
3834
4099
  context.replaceConfig(updated);
3835
4100
  return { text: renderPermissionStatus(context.config, "权限模式已更新:") };
3836
4101
  }
@@ -3841,18 +4106,226 @@ async function handlePermissionAutoSet(context, policy) {
3841
4106
  if (!context.config || !context.configStore) {
3842
4107
  return { text: "当前没有加载可写入的配置。" };
3843
4108
  }
4109
+ const previous = cloneAppConfig(context.config);
3844
4110
  const updated = await context.configStore.updateTransport({
3845
4111
  nonInteractivePermissions: policy
3846
4112
  });
4113
+ try {
4114
+ await context.transport.updatePermissionPolicy?.(updated.transport);
4115
+ } catch (error) {
4116
+ await context.configStore.save(previous);
4117
+ context.replaceConfig(previous);
4118
+ throw error;
4119
+ }
3847
4120
  context.replaceConfig(updated);
3848
4121
  return { text: renderPermissionStatus(context.config, "非交互策略已更新:") };
3849
4122
  }
3850
4123
  function renderPermissionStatus(config, title) {
3851
4124
  const permissionMode = config?.transport.permissionMode ?? "approve-all";
3852
- const nonInteractivePermissions = config?.transport.nonInteractivePermissions ?? "fail";
4125
+ const nonInteractivePermissions = config?.transport.nonInteractivePermissions ?? "deny";
3853
4126
  return [title, `- mode: ${permissionMode}`, `- auto: ${nonInteractivePermissions}`].join(`
3854
4127
  `);
3855
4128
  }
4129
+ var permissionHelp;
4130
+ var init_permission_handler = __esm(() => {
4131
+ permissionHelp = {
4132
+ topic: "permission",
4133
+ aliases: ["pm"],
4134
+ summary: "查看和修改 transport 权限策略。",
4135
+ commands: [
4136
+ { usage: "/pm 或 /permission", description: "查看当前权限模式" },
4137
+ { usage: "/pm set <allow|read|deny>", description: "设置审批级别" },
4138
+ { usage: "/pm auto", description: "查看当前非交互策略" },
4139
+ { usage: "/pm auto <deny|fail>", description: "设置非交互策略" }
4140
+ ],
4141
+ examples: ["/pm set read", "/pm auto deny"]
4142
+ };
4143
+ });
4144
+
4145
+ // src/commands/handlers/config-handler.ts
4146
+ function handleConfigShow(context) {
4147
+ const lines = ["支持修改的配置字段:", ...SUPPORTED_CONFIG_PATHS.map((path11) => `- ${path11}`)];
4148
+ if (context.config) {
4149
+ lines.push("", "示例:", "- /config set wechat.replyMode final", "- /config set logging.level debug");
4150
+ }
4151
+ return { text: lines.join(`
4152
+ `) };
4153
+ }
4154
+ async function handleConfigSet(context, path11, rawValue) {
4155
+ if (!context.config || !context.configStore) {
4156
+ return { text: "当前没有加载可写入的配置。" };
4157
+ }
4158
+ const previous = cloneAppConfig(context.config);
4159
+ const updated = cloneAppConfig(context.config);
4160
+ const result = applySupportedConfigUpdate(updated, path11, rawValue);
4161
+ if ("error" in result) {
4162
+ return { text: result.error };
4163
+ }
4164
+ await context.configStore.save(updated);
4165
+ if (path11 === "transport.permissionMode" || path11 === "transport.nonInteractivePermissions") {
4166
+ try {
4167
+ await context.transport.updatePermissionPolicy?.(updated.transport);
4168
+ } catch (error) {
4169
+ await context.configStore.save(previous);
4170
+ context.replaceConfig(previous);
4171
+ throw error;
4172
+ }
4173
+ }
4174
+ context.replaceConfig(updated);
4175
+ return { text: `配置已更新:${path11} = ${result.renderedValue}` };
4176
+ }
4177
+ function applySupportedConfigUpdate(config, path11, rawValue) {
4178
+ switch (path11) {
4179
+ case "transport.type": {
4180
+ const parsed = parseEnum(rawValue, ["acpx-cli", "acpx-bridge"]);
4181
+ if (!parsed)
4182
+ return { error: "transport.type 只支持:acpx-cli、acpx-bridge" };
4183
+ config.transport.type = parsed;
4184
+ return { renderedValue: parsed };
4185
+ }
4186
+ case "transport.command":
4187
+ if (!rawValue.trim())
4188
+ return { error: "transport.command 不能为空。" };
4189
+ config.transport.command = rawValue;
4190
+ return { renderedValue: rawValue };
4191
+ case "transport.sessionInitTimeoutMs": {
4192
+ const parsed = parsePositiveNumber(rawValue, "transport.sessionInitTimeoutMs");
4193
+ if ("error" in parsed)
4194
+ return parsed;
4195
+ config.transport.sessionInitTimeoutMs = parsed.value;
4196
+ return { renderedValue: String(parsed.value) };
4197
+ }
4198
+ case "transport.permissionMode": {
4199
+ const parsed = parseEnum(rawValue, ["approve-all", "approve-reads", "deny-all"]);
4200
+ if (!parsed)
4201
+ return { error: "transport.permissionMode 只支持:approve-all、approve-reads、deny-all" };
4202
+ config.transport.permissionMode = parsed;
4203
+ return { renderedValue: parsed };
4204
+ }
4205
+ case "transport.nonInteractivePermissions": {
4206
+ const parsed = parseEnum(rawValue, ["deny", "fail"]);
4207
+ if (!parsed)
4208
+ return { error: "transport.nonInteractivePermissions 只支持:deny、fail" };
4209
+ config.transport.nonInteractivePermissions = parsed;
4210
+ return { renderedValue: parsed };
4211
+ }
4212
+ case "logging.level": {
4213
+ const parsed = parseEnum(rawValue, ["error", "info", "debug"]);
4214
+ if (!parsed)
4215
+ return { error: "logging.level 只支持:error、info、debug" };
4216
+ config.logging.level = parsed;
4217
+ return { renderedValue: parsed };
4218
+ }
4219
+ case "logging.maxSizeBytes": {
4220
+ const parsed = parsePositiveNumber(rawValue, "logging.maxSizeBytes");
4221
+ if ("error" in parsed)
4222
+ return parsed;
4223
+ config.logging.maxSizeBytes = parsed.value;
4224
+ return { renderedValue: String(parsed.value) };
4225
+ }
4226
+ case "logging.maxFiles": {
4227
+ const parsed = parsePositiveNumber(rawValue, "logging.maxFiles");
4228
+ if ("error" in parsed)
4229
+ return parsed;
4230
+ config.logging.maxFiles = parsed.value;
4231
+ return { renderedValue: String(parsed.value) };
4232
+ }
4233
+ case "logging.retentionDays": {
4234
+ const parsed = parsePositiveNumber(rawValue, "logging.retentionDays");
4235
+ if ("error" in parsed)
4236
+ return parsed;
4237
+ config.logging.retentionDays = parsed.value;
4238
+ return { renderedValue: String(parsed.value) };
4239
+ }
4240
+ case "wechat.replyMode": {
4241
+ const parsed = parseEnum(rawValue, ["stream", "final"]);
4242
+ if (!parsed)
4243
+ return { error: "wechat.replyMode 只支持:stream、final" };
4244
+ config.wechat.replyMode = parsed;
4245
+ return { renderedValue: parsed };
4246
+ }
4247
+ }
4248
+ const agentMatch = path11.match(/^agents\.([^.]+)\.(driver|command)$/);
4249
+ if (agentMatch) {
4250
+ const [, name, field] = agentMatch;
4251
+ if (!name || !field) {
4252
+ return { error: `不支持修改这个配置路径:${path11}` };
4253
+ }
4254
+ const agent = config.agents[name];
4255
+ if (!agent) {
4256
+ return { error: `Agent「${name}」不存在,请先创建。` };
4257
+ }
4258
+ if (!rawValue.trim()) {
4259
+ return { error: `${path11} 不能为空。` };
4260
+ }
4261
+ if (field === "driver") {
4262
+ agent.driver = rawValue;
4263
+ } else {
4264
+ agent.command = rawValue;
4265
+ }
4266
+ return { renderedValue: rawValue };
4267
+ }
4268
+ const workspaceMatch = path11.match(/^workspaces\.([^.]+)\.(cwd|description)$/);
4269
+ if (workspaceMatch) {
4270
+ const [, name, field] = workspaceMatch;
4271
+ if (!name || !field) {
4272
+ return { error: `不支持修改这个配置路径:${path11}` };
4273
+ }
4274
+ const workspace = config.workspaces[name];
4275
+ if (!workspace) {
4276
+ return { error: `工作区「${name}」不存在,请先创建。` };
4277
+ }
4278
+ if (!rawValue.trim()) {
4279
+ return { error: `${path11} 不能为空。` };
4280
+ }
4281
+ if (field === "cwd") {
4282
+ workspace.cwd = rawValue;
4283
+ } else {
4284
+ workspace.description = rawValue;
4285
+ }
4286
+ return { renderedValue: rawValue };
4287
+ }
4288
+ return { error: `不支持修改这个配置路径:${path11}` };
4289
+ }
4290
+ function parseEnum(value, allowed) {
4291
+ return allowed.includes(value) ? value : null;
4292
+ }
4293
+ function parsePositiveNumber(rawValue, path11) {
4294
+ const value = Number(rawValue);
4295
+ if (!Number.isFinite(value) || value <= 0) {
4296
+ return { error: `${path11} 必须是正数。` };
4297
+ }
4298
+ return { value };
4299
+ }
4300
+ var SUPPORTED_CONFIG_PATHS, configHelp;
4301
+ var init_config_handler = __esm(() => {
4302
+ SUPPORTED_CONFIG_PATHS = [
4303
+ "transport.type",
4304
+ "transport.command",
4305
+ "transport.sessionInitTimeoutMs",
4306
+ "transport.permissionMode",
4307
+ "transport.nonInteractivePermissions",
4308
+ "logging.level",
4309
+ "logging.maxSizeBytes",
4310
+ "logging.maxFiles",
4311
+ "logging.retentionDays",
4312
+ "wechat.replyMode",
4313
+ "agents.<name>.driver",
4314
+ "agents.<name>.command",
4315
+ "workspaces.<name>.cwd",
4316
+ "workspaces.<name>.description"
4317
+ ];
4318
+ configHelp = {
4319
+ topic: "config",
4320
+ aliases: [],
4321
+ summary: "查看和修改受支持的配置字段。",
4322
+ commands: [
4323
+ { usage: "/config", description: "查看当前支持修改的配置路径" },
4324
+ { usage: "/config set <path> <value>", description: "修改一个受支持的配置值" }
4325
+ ],
4326
+ examples: ["/config set wechat.replyMode final", "/config set logging.level debug"]
4327
+ };
4328
+ });
3856
4329
 
3857
4330
  // src/commands/handlers/session-handler.ts
3858
4331
  async function handleSessions(context, chatKey) {
@@ -3889,8 +4362,8 @@ async function handleSessionNew(context, chatKey, alias, agent, workspace) {
3889
4362
  });
3890
4363
  return { text: `会话「${alias}」已创建并切换` };
3891
4364
  }
3892
- async function handleSessionShortcut(context, chatKey, agent, cwdInput, createNew) {
3893
- return await context.lifecycle.handleSessionShortcut(chatKey, agent, cwdInput, createNew);
4365
+ async function handleSessionShortcut(context, chatKey, agent, target, createNew) {
4366
+ return await context.lifecycle.handleSessionShortcut(chatKey, agent, target, createNew);
3894
4367
  }
3895
4368
  async function handleSessionAttach(context, chatKey, alias, agent, workspace, transportSession) {
3896
4369
  const attached = context.lifecycle.resolveSession(alias, agent, workspace, transportSession);
@@ -3946,6 +4419,42 @@ async function handleModeSet(context, chatKey, modeId) {
3946
4419
  await context.sessions.setCurrentSessionMode(chatKey, modeId);
3947
4420
  return { text: `已设置当前会话 mode:${modeId}` };
3948
4421
  }
4422
+ async function handleReplyModeShow(context, chatKey) {
4423
+ const session = await context.sessions.getCurrentSession(chatKey);
4424
+ if (!session) {
4425
+ return { text: NO_CURRENT_SESSION_TEXT };
4426
+ }
4427
+ const globalDefault = context.config?.wechat.replyMode ?? "stream";
4428
+ const sessionOverride = session.replyMode;
4429
+ const effective = sessionOverride ?? globalDefault;
4430
+ return {
4431
+ text: [
4432
+ "当前 reply mode:",
4433
+ `- 会话:${session.alias}`,
4434
+ `- 全局默认:${globalDefault}`,
4435
+ `- 当前会话覆盖:${sessionOverride ?? "未设置"}`,
4436
+ `- 当前生效:${effective}`
4437
+ ].join(`
4438
+ `)
4439
+ };
4440
+ }
4441
+ async function handleReplyModeSet(context, chatKey, replyMode) {
4442
+ const session = await context.sessions.getCurrentSession(chatKey);
4443
+ if (!session) {
4444
+ return { text: NO_CURRENT_SESSION_TEXT };
4445
+ }
4446
+ await context.sessions.setCurrentSessionReplyMode(chatKey, replyMode);
4447
+ return { text: `已设置当前会话 reply mode:${replyMode}` };
4448
+ }
4449
+ async function handleReplyModeReset(context, chatKey) {
4450
+ const session = await context.sessions.getCurrentSession(chatKey);
4451
+ if (!session) {
4452
+ return { text: NO_CURRENT_SESSION_TEXT };
4453
+ }
4454
+ await context.sessions.setCurrentSessionReplyMode(chatKey, undefined);
4455
+ const globalDefault = context.config?.wechat.replyMode ?? "stream";
4456
+ return { text: `已重置当前会话 reply mode,当前回退到全局默认:${globalDefault}` };
4457
+ }
3949
4458
  async function handleStatus(context, chatKey) {
3950
4459
  const session = await context.sessions.getCurrentSession(chatKey);
3951
4460
  if (!session) {
@@ -3982,18 +4491,79 @@ async function handlePrompt(context, chatKey, text, reply) {
3982
4491
  return { text: NO_CURRENT_SESSION_TEXT };
3983
4492
  }
3984
4493
  try {
3985
- const result = await context.interaction.promptTransportSession(session, text, reply);
4494
+ const effectiveReplyMode = session.replyMode ?? context.config?.wechat.replyMode ?? "stream";
4495
+ const transportReply = effectiveReplyMode === "stream" ? reply : undefined;
4496
+ const result = await context.interaction.promptTransportSession(session, text, transportReply);
3986
4497
  return { text: result.text };
3987
4498
  } catch (error) {
3988
4499
  const recovered = await context.recovery.tryRecoverMissingSession(session, error);
3989
4500
  if (recovered) {
3990
- const result = await context.interaction.promptTransportSession(recovered, text, reply);
4501
+ const effectiveReplyMode = recovered.replyMode ?? context.config?.wechat.replyMode ?? "stream";
4502
+ const transportReply = effectiveReplyMode === "stream" ? reply : undefined;
4503
+ const result = await context.interaction.promptTransportSession(recovered, text, transportReply);
3991
4504
  return { text: result.text };
3992
4505
  }
3993
4506
  return context.recovery.renderTransportError(session, error);
3994
4507
  }
3995
4508
  }
3996
- var NO_CURRENT_SESSION_TEXT = "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。";
4509
+ var NO_CURRENT_SESSION_TEXT = "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。", sessionHelp, modeHelp, replyModeHelp, statusHelp, cancelHelp;
4510
+ var init_session_handler = __esm(() => {
4511
+ sessionHelp = {
4512
+ topic: "session",
4513
+ aliases: ["ss", "sessions"],
4514
+ summary: "创建、恢复、切换和重置逻辑会话。",
4515
+ commands: [
4516
+ { usage: "/sessions", description: "查看当前会话列表" },
4517
+ { usage: "/session 或 /ss", description: "查看会话列表" },
4518
+ { usage: "/ss <agent> (-d <path> | --ws <name>)", description: "快速新建或复用一个会话" },
4519
+ { usage: "/ss new <agent> (-d <path> | --ws <name>)", description: "强制新建会话" },
4520
+ { usage: "/ss new <alias> -a <name> --ws <name>", description: "按指定配置新建会话" },
4521
+ { usage: "/ss attach <alias> -a <name> --ws <name> --name <transport-session>", description: "绑定已有会话" },
4522
+ { usage: "/use <alias>", description: "切换当前会话" },
4523
+ { usage: "/session reset 或 /clear", description: "重置当前会话上下文" }
4524
+ ],
4525
+ examples: ["/ss codex -d /absolute/path/to/repo", "/use backend-fix", "/session reset"]
4526
+ };
4527
+ modeHelp = {
4528
+ topic: "mode",
4529
+ aliases: [],
4530
+ summary: "查看或设置当前会话 mode。",
4531
+ commands: [
4532
+ { usage: "/mode", description: "查看当前会话已保存的 mode" },
4533
+ { usage: "/mode <id>", description: "设置当前会话 mode" }
4534
+ ],
4535
+ examples: ["/mode", "/mode plan"]
4536
+ };
4537
+ replyModeHelp = {
4538
+ topic: "replymode",
4539
+ aliases: [],
4540
+ summary: "查看或设置当前逻辑会话的回复输出模式。",
4541
+ commands: [
4542
+ { usage: "/replymode", description: "查看全局默认、当前覆盖和实际生效值" },
4543
+ { usage: "/replymode stream", description: "当前会话使用流式回复" },
4544
+ { usage: "/replymode final", description: "当前会话只发送最终文本" },
4545
+ { usage: "/replymode reset", description: "清除当前会话覆盖并回退到全局默认" }
4546
+ ],
4547
+ examples: ["/replymode", "/replymode final"]
4548
+ };
4549
+ statusHelp = {
4550
+ topic: "status",
4551
+ aliases: [],
4552
+ summary: "查看当前选中会话的状态。",
4553
+ commands: [{ usage: "/status", description: "查看当前会话状态" }],
4554
+ examples: ["/status"]
4555
+ };
4556
+ cancelHelp = {
4557
+ topic: "cancel",
4558
+ aliases: ["stop"],
4559
+ summary: "取消当前会话里正在执行的任务。",
4560
+ commands: [
4561
+ { usage: "/cancel", description: "取消当前任务" },
4562
+ { usage: "/stop", description: "取消当前任务(/cancel 别名)" }
4563
+ ],
4564
+ examples: ["/cancel"]
4565
+ };
4566
+ });
3997
4567
 
3998
4568
  // src/commands/transport-diagnostics.ts
3999
4569
  function summarizeTransportError(message) {
@@ -4057,47 +4627,6 @@ function isPartialPromptOutputError(message) {
4057
4627
  }
4058
4628
 
4059
4629
  // 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
4630
  function renderAgents(config) {
4102
4631
  const names = Object.keys(config.agents);
4103
4632
  if (names.length === 0) {
@@ -4115,12 +4644,6 @@ function renderWorkspaces(config) {
4115
4644
  `);
4116
4645
  }
4117
4646
 
4118
- // src/commands/handlers/help-handler.ts
4119
- function handleHelp() {
4120
- return { text: renderHelpText() };
4121
- }
4122
- var init_help_handler = () => {};
4123
-
4124
4647
  // src/config/agent-templates.ts
4125
4648
  function getAgentTemplate(name) {
4126
4649
  const template = TEMPLATES[name];
@@ -4173,13 +4696,25 @@ async function handleAgentRemove(context, agentName) {
4173
4696
  context.replaceConfig(updated);
4174
4697
  return { text: `Agent「${agentName}」已删除` };
4175
4698
  }
4699
+ var agentHelp;
4176
4700
  var init_agent_handler = __esm(() => {
4177
4701
  init_agent_templates();
4702
+ agentHelp = {
4703
+ topic: "agent",
4704
+ aliases: ["agents"],
4705
+ summary: "管理已注册的 Agent。",
4706
+ commands: [
4707
+ { usage: "/agents", description: "查看当前已注册的 Agent" },
4708
+ { usage: "/agent add <codex|claude>", description: "添加内置 Agent 模板" },
4709
+ { usage: "/agent rm <name>", description: "删除一个 Agent" }
4710
+ ],
4711
+ examples: ["/agent add claude", "/agent rm codex"]
4712
+ };
4178
4713
  });
4179
4714
 
4180
4715
  // src/commands/handlers/workspace-handler.ts
4181
4716
  import { access } from "node:fs/promises";
4182
- import { homedir as homedir2 } from "node:os";
4717
+ import { homedir as homedir3 } from "node:os";
4183
4718
  import { normalize } from "node:path";
4184
4719
  function handleWorkspaces(context) {
4185
4720
  return { text: context.config ? renderWorkspaces(context.config) : "No config loaded." };
@@ -4213,24 +4748,127 @@ async function pathExists(path11) {
4213
4748
  }
4214
4749
  }
4215
4750
  function normalizePathForWorkspace(path11) {
4216
- const expanded = path11.startsWith("~") ? homedir2() + path11.slice(1) : path11;
4751
+ const expanded = path11.startsWith("~") ? homedir3() + path11.slice(1) : path11;
4217
4752
  return normalize(expanded);
4218
4753
  }
4219
- var init_workspace_handler = () => {};
4754
+ var workspaceHelp;
4755
+ var init_workspace_handler = __esm(() => {
4756
+ workspaceHelp = {
4757
+ topic: "workspace",
4758
+ aliases: ["ws", "workspaces"],
4759
+ summary: "管理已注册的工作区。",
4760
+ commands: [
4761
+ { usage: "/workspaces", description: "查看当前已注册的工作区" },
4762
+ { usage: "/workspace 或 /ws", description: "查看工作区列表" },
4763
+ { usage: "/ws new <name> -d <path>", description: "添加工作区" },
4764
+ { usage: "/workspace rm <name>", description: "删除工作区" }
4765
+ ],
4766
+ examples: ['/ws new backend -d "/tmp/backend"', "/workspace rm backend"]
4767
+ };
4768
+ });
4769
+
4770
+ // src/commands/help/help-registry.ts
4771
+ function getHelpTopic(topic) {
4772
+ return HELP_TOPIC_MAP.get(topic) ?? null;
4773
+ }
4774
+ function listHelpTopics() {
4775
+ return HELP_TOPICS;
4776
+ }
4777
+ var HELP_TOPICS, HELP_TOPIC_MAP;
4778
+ var init_help_registry = __esm(() => {
4779
+ init_agent_handler();
4780
+ init_config_handler();
4781
+ init_permission_handler();
4782
+ init_session_handler();
4783
+ init_workspace_handler();
4784
+ HELP_TOPICS = [
4785
+ sessionHelp,
4786
+ workspaceHelp,
4787
+ agentHelp,
4788
+ permissionHelp,
4789
+ configHelp,
4790
+ modeHelp,
4791
+ replyModeHelp,
4792
+ statusHelp,
4793
+ cancelHelp
4794
+ ];
4795
+ HELP_TOPIC_MAP = new Map;
4796
+ for (const topic of HELP_TOPICS) {
4797
+ HELP_TOPIC_MAP.set(topic.topic, topic);
4798
+ for (const alias of topic.aliases) {
4799
+ HELP_TOPIC_MAP.set(alias, topic);
4800
+ }
4801
+ }
4802
+ });
4803
+
4804
+ // src/commands/handlers/help-handler.ts
4805
+ function handleHelp(topic) {
4806
+ if (!topic) {
4807
+ return { text: renderHelpIndex() };
4808
+ }
4809
+ const entry = getHelpTopic(topic);
4810
+ if (!entry) {
4811
+ return { text: renderUnknownHelpTopic(topic) };
4812
+ }
4813
+ return { text: renderHelpTopic(entry) };
4814
+ }
4815
+ function renderHelpIndex() {
4816
+ const topics = listHelpTopics();
4817
+ return [
4818
+ "常用入口:",
4819
+ "- /ss <agent> (-d <path> | --ws <name>) - 快速新建或切到会话",
4820
+ "- /use <alias> - 切换当前会话",
4821
+ "- /status - 查看当前会话状态",
4822
+ "",
4823
+ "顶级命令:",
4824
+ ...topics.map((topic) => `- ${topic.topic} - ${topic.summary}`),
4825
+ "",
4826
+ "查看专题说明:",
4827
+ "- /help <topic>",
4828
+ "- 例如:/help ss、/help ws、/help pm"
4829
+ ].join(`
4830
+ `);
4831
+ }
4832
+ function renderHelpTopic(topic) {
4833
+ return [
4834
+ `帮助主题:${topic.topic}`,
4835
+ `说明:${topic.summary}`,
4836
+ ...topic.aliases.length > 0 ? [`别名:${topic.aliases.join("、")}`] : [],
4837
+ "",
4838
+ "命令:",
4839
+ ...topic.commands.map((command) => `- ${command.usage} - ${command.description}`),
4840
+ ...topic.examples && topic.examples.length > 0 ? ["", "示例:", ...topic.examples.map((example) => `- ${example}`)] : []
4841
+ ].join(`
4842
+ `);
4843
+ }
4844
+ function renderUnknownHelpTopic(topic) {
4845
+ return [
4846
+ `未知帮助主题:${topic}`,
4847
+ "",
4848
+ "可用主题:",
4849
+ ...listHelpTopics().map((entry) => `- ${entry.topic}`)
4850
+ ].join(`
4851
+ `);
4852
+ }
4853
+ var init_help_handler = __esm(() => {
4854
+ init_help_registry();
4855
+ });
4220
4856
 
4221
4857
  // src/commands/handlers/session-shortcut-handler.ts
4222
4858
  import { access as access2 } from "node:fs/promises";
4223
4859
  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) {
4860
+ import { homedir as homedir4 } from "node:os";
4861
+ async function handleSessionShortcutCommand(context, ops, chatKey, agent, target, createNew) {
4226
4862
  if (!context.config || !context.configStore) {
4227
4863
  return { text: "当前没有加载可写入的配置。" };
4228
4864
  }
4229
- const cwd = normalizePathForWorkspace2(cwdInput);
4230
- if (!await pathExists2(cwd)) {
4231
- return { text: `工作区路径不存在:${cwdInput}` };
4865
+ if (!context.config.agents[agent]) {
4866
+ return { text: `agent "${agent}" is not registered` };
4867
+ }
4868
+ const workspace = await resolveShortcutWorkspace(context, target);
4869
+ if ("error" in workspace) {
4870
+ return { text: workspace.error };
4232
4871
  }
4233
- const workspace = await resolveShortcutWorkspace(context, cwd);
4234
4872
  await context.logger.info("session.shortcut.workspace", "resolved shortcut workspace", {
4235
4873
  workspace: workspace.name,
4236
4874
  cwd: workspace.cwd,
@@ -4282,7 +4920,23 @@ async function handleSessionShortcutCommand(context, ops, chatKey, agent, cwdInp
4282
4920
  `)
4283
4921
  };
4284
4922
  }
4285
- async function resolveShortcutWorkspace(context, cwd) {
4923
+ async function resolveShortcutWorkspace(context, target) {
4924
+ if (target.workspace) {
4925
+ const workspace = context.config?.workspaces[target.workspace];
4926
+ if (!workspace) {
4927
+ return { error: `workspace "${target.workspace}" is not registered` };
4928
+ }
4929
+ return {
4930
+ name: target.workspace,
4931
+ cwd: workspace.cwd,
4932
+ reused: true
4933
+ };
4934
+ }
4935
+ const cwdInput = target.cwd ?? "";
4936
+ const cwd = normalizePathForWorkspace2(cwdInput);
4937
+ if (!await pathExists2(cwd)) {
4938
+ return { error: `工作区路径不存在:${cwdInput}` };
4939
+ }
4286
4940
  const existingByPath = Object.entries(context.config?.workspaces ?? {}).find(([, workspace]) => sameWorkspacePath(workspace.cwd, cwd));
4287
4941
  if (existingByPath) {
4288
4942
  return {
@@ -4343,7 +4997,7 @@ async function pathExists2(path11) {
4343
4997
  }
4344
4998
  }
4345
4999
  function normalizePathForWorkspace2(path11) {
4346
- const expanded = path11.startsWith("~") ? homedir3() + path11.slice(1) : path11;
5000
+ const expanded = path11.startsWith("~") ? homedir4() + path11.slice(1) : path11;
4347
5001
  return normalize2(expanded);
4348
5002
  }
4349
5003
  function sameWorkspacePath(left, right) {
@@ -4384,15 +5038,19 @@ function renderTransportError(session, error) {
4384
5038
  function renderSessionCreationError(session, error) {
4385
5039
  const message = error instanceof Error ? error.message : String(error);
4386
5040
  if (message.includes("timed out") && message.includes("sessions new")) {
4387
- return renderSessionCreationVerificationError(session);
5041
+ return renderSessionCreationFailure(session, message);
4388
5042
  }
4389
5043
  throw error;
4390
5044
  }
4391
5045
  function renderSessionCreationVerificationError(session) {
5046
+ return renderSessionCreationFailure(session, "未检测到可用的后端会话。");
5047
+ }
5048
+ function renderSessionCreationFailure(session, detail) {
4392
5049
  return {
4393
5050
  text: [
4394
- "当前还不能直接在微信里创建新会话。",
4395
- `请先准备好一个已有会话,然后在微信里执行:/session attach ${session.alias} --agent ${session.agent} --ws ${session.workspace} --name <会话名>`
5051
+ "会话创建失败。",
5052
+ `错误信息:${summarizeTransportError(detail)}`,
5053
+ `如果你要先绑定一个已有会话,可以执行:/session attach ${session.alias} --agent ${session.agent} --ws ${session.workspace} --name <会话名>`
4396
5054
  ].join(`
4397
5055
  `)
4398
5056
  };
@@ -4492,7 +5150,7 @@ class CommandRouter {
4492
5150
  `)
4493
5151
  };
4494
5152
  case "help":
4495
- return handleHelp();
5153
+ return handleHelp(command.topic);
4496
5154
  case "agents":
4497
5155
  return handleAgents(this.createHandlerContext());
4498
5156
  case "agent.add":
@@ -4507,6 +5165,10 @@ class CommandRouter {
4507
5165
  return handlePermissionAutoStatus(this.createHandlerContext(), "当前非交互策略:");
4508
5166
  case "permission.auto.set":
4509
5167
  return await handlePermissionAutoSet(this.createHandlerContext(), command.policy);
5168
+ case "config.show":
5169
+ return handleConfigShow(this.createHandlerContext());
5170
+ case "config.set":
5171
+ return await handleConfigSet(this.createHandlerContext(), command.path, command.value);
4510
5172
  case "workspaces":
4511
5173
  return handleWorkspaces(this.createHandlerContext());
4512
5174
  case "workspace.new":
@@ -4518,9 +5180,9 @@ class CommandRouter {
4518
5180
  case "session.new":
4519
5181
  return await handleSessionNew(this.createSessionHandlerContext(), chatKey, command.alias, command.agent, command.workspace);
4520
5182
  case "session.shortcut":
4521
- return await handleSessionShortcut(this.createSessionHandlerContext(), chatKey, command.agent, command.cwd, false);
5183
+ return await handleSessionShortcut(this.createSessionHandlerContext(), chatKey, command.agent, command, false);
4522
5184
  case "session.shortcut.new":
4523
- return await handleSessionShortcut(this.createSessionHandlerContext(), chatKey, command.agent, command.cwd, true);
5185
+ return await handleSessionShortcut(this.createSessionHandlerContext(), chatKey, command.agent, command, true);
4524
5186
  case "session.attach":
4525
5187
  return await handleSessionAttach(this.createSessionHandlerContext(), chatKey, command.alias, command.agent, command.workspace, command.transportSession);
4526
5188
  case "session.use":
@@ -4529,6 +5191,12 @@ class CommandRouter {
4529
5191
  return await handleModeShow(this.createSessionHandlerContext(), chatKey);
4530
5192
  case "mode.set":
4531
5193
  return await handleModeSet(this.createSessionHandlerContext(), chatKey, command.modeId);
5194
+ case "replymode.show":
5195
+ return await handleReplyModeShow(this.createSessionHandlerContext(), chatKey);
5196
+ case "replymode.set":
5197
+ return await handleReplyModeSet(this.createSessionHandlerContext(), chatKey, command.replyMode);
5198
+ case "replymode.reset":
5199
+ return await handleReplyModeReset(this.createSessionHandlerContext(), chatKey);
4532
5200
  case "status":
4533
5201
  return await handleStatus(this.createSessionHandlerContext(), chatKey);
4534
5202
  case "cancel":
@@ -4566,7 +5234,7 @@ class CommandRouter {
4566
5234
  resolveSession: (alias, agent, workspace, transportSession) => this.sessions.resolveSession(alias, agent, workspace, transportSession),
4567
5235
  ensureTransportSession: (session) => this.ensureTransportSession(session),
4568
5236
  checkTransportSession: (session) => this.checkTransportSession(session),
4569
- handleSessionShortcut: (chatKey, agent, cwdInput, createNew) => handleSessionShortcutCommand(this.createHandlerContext(), this.createSessionShortcutOps(), chatKey, agent, cwdInput, createNew),
5237
+ handleSessionShortcut: (chatKey, agent, target, createNew) => handleSessionShortcutCommand(this.createHandlerContext(), this.createSessionShortcutOps(), chatKey, agent, target, createNew),
4570
5238
  resetCurrentSession: (chatKey) => handleSessionResetCommand(this.createHandlerContext(), this.createSessionResetOps(), chatKey),
4571
5239
  refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
4572
5240
  };
@@ -4615,6 +5283,8 @@ class CommandRouter {
4615
5283
  return;
4616
5284
  }
4617
5285
  this.config.transport = { ...updated.transport };
5286
+ this.config.logging = { ...updated.logging };
5287
+ this.config.wechat = { ...updated.wechat };
4618
5288
  this.config.agents = { ...updated.agents };
4619
5289
  this.config.workspaces = { ...updated.workspaces };
4620
5290
  }
@@ -4705,6 +5375,9 @@ var init_command_router = __esm(() => {
4705
5375
  init_acpx_session_index();
4706
5376
  init_prompt_output();
4707
5377
  init_parse_command();
5378
+ init_permission_handler();
5379
+ init_config_handler();
5380
+ init_session_handler();
4708
5381
  init_help_handler();
4709
5382
  init_agent_handler();
4710
5383
  init_workspace_handler();
@@ -4729,12 +5402,12 @@ function isLegacyCodexCommand(command) {
4729
5402
  }
4730
5403
 
4731
5404
  // src/config/load-config.ts
4732
- import { readFile as readFile4 } from "node:fs/promises";
5405
+ import { readFile as readFile5 } from "node:fs/promises";
4733
5406
  function isRecord(value) {
4734
5407
  return typeof value === "object" && value !== null;
4735
5408
  }
4736
5409
  async function loadConfig(path11, options = {}) {
4737
- const raw = JSON.parse(await readFile4(path11, "utf8"));
5410
+ const raw = JSON.parse(await readFile5(path11, "utf8"));
4738
5411
  return parseConfig(raw, options);
4739
5412
  }
4740
5413
  function parseConfig(raw, options = {}) {
@@ -4754,8 +5427,8 @@ function parseConfig(raw, options = {}) {
4754
5427
  if ("permissionMode" in transport && transport.permissionMode !== "approve-all" && transport.permissionMode !== "approve-reads" && transport.permissionMode !== "deny-all") {
4755
5428
  throw new Error("transport.permissionMode must be approve-all, approve-reads, or deny-all");
4756
5429
  }
4757
- if ("nonInteractivePermissions" in transport && transport.nonInteractivePermissions !== "allow" && transport.nonInteractivePermissions !== "deny" && transport.nonInteractivePermissions !== "fail") {
4758
- throw new Error("transport.nonInteractivePermissions must be allow, deny, or fail");
5430
+ if ("nonInteractivePermissions" in transport && transport.nonInteractivePermissions !== "deny" && transport.nonInteractivePermissions !== "fail") {
5431
+ throw new Error("transport.nonInteractivePermissions must be deny or fail");
4759
5432
  }
4760
5433
  if (!isRecord(raw.agents)) {
4761
5434
  throw new Error("agents must be an object");
@@ -4764,9 +5437,13 @@ function parseConfig(raw, options = {}) {
4764
5437
  throw new Error("workspaces must be an object");
4765
5438
  }
4766
5439
  const logging = raw.logging;
5440
+ const wechat = raw.wechat;
4767
5441
  if (logging !== undefined && !isRecord(logging)) {
4768
5442
  throw new Error("logging must be an object");
4769
5443
  }
5444
+ if (wechat !== undefined && !isRecord(wechat)) {
5445
+ throw new Error("wechat must be an object");
5446
+ }
4770
5447
  if (isRecord(logging) && "level" in logging && logging.level !== "error" && logging.level !== "info" && logging.level !== "debug") {
4771
5448
  throw new Error("logging.level must be error, info, or debug");
4772
5449
  }
@@ -4775,6 +5452,9 @@ function parseConfig(raw, options = {}) {
4775
5452
  throw new Error(`logging.${field} must be a positive number`);
4776
5453
  }
4777
5454
  }
5455
+ if (isRecord(wechat) && "replyMode" in wechat && wechat.replyMode !== "stream" && wechat.replyMode !== "final") {
5456
+ throw new Error("wechat.replyMode must be stream or final");
5457
+ }
4778
5458
  for (const [name, agent] of Object.entries(raw.agents)) {
4779
5459
  if (!isRecord(agent) || typeof agent.driver !== "string" || agent.driver.length === 0) {
4780
5460
  throw new Error(`agent "${name}" must define a non-empty driver`);
@@ -4811,9 +5491,10 @@ function parseConfig(raw, options = {}) {
4811
5491
  }
4812
5492
  const transportType = transport.type === "acpx-cli" || transport.type === "acpx-bridge" ? transport.type : "acpx-bridge";
4813
5493
  const permissionMode = transport.permissionMode === "approve-all" || transport.permissionMode === "approve-reads" || transport.permissionMode === "deny-all" ? transport.permissionMode : DEFAULT_PERMISSION_MODE;
4814
- const nonInteractivePermissions = transport.nonInteractivePermissions === "allow" || transport.nonInteractivePermissions === "deny" || transport.nonInteractivePermissions === "fail" ? transport.nonInteractivePermissions : DEFAULT_NON_INTERACTIVE_PERMISSIONS;
5494
+ const nonInteractivePermissions = transport.nonInteractivePermissions === "deny" || transport.nonInteractivePermissions === "fail" ? transport.nonInteractivePermissions : DEFAULT_NON_INTERACTIVE_PERMISSIONS;
4815
5495
  const loggingLevel = logging?.level;
4816
5496
  const resolvedLoggingLevel = loggingLevel === "error" || loggingLevel === "info" || loggingLevel === "debug" ? loggingLevel : options.defaultLoggingLevel ?? DEFAULT_LOGGING_CONFIG.level;
5497
+ const replyMode = wechat?.replyMode === "stream" || wechat?.replyMode === "final" ? wechat.replyMode : DEFAULT_WECHAT_REPLY_MODE;
4817
5498
  return {
4818
5499
  transport: {
4819
5500
  ...typeof transport.command === "string" ? { command: transport.command } : {},
@@ -4828,11 +5509,14 @@ function parseConfig(raw, options = {}) {
4828
5509
  maxFiles: typeof logging?.maxFiles === "number" ? logging.maxFiles : DEFAULT_LOGGING_CONFIG.maxFiles,
4829
5510
  retentionDays: typeof logging?.retentionDays === "number" ? logging.retentionDays : DEFAULT_LOGGING_CONFIG.retentionDays
4830
5511
  },
5512
+ wechat: {
5513
+ replyMode
5514
+ },
4831
5515
  agents,
4832
5516
  workspaces
4833
5517
  };
4834
5518
  }
4835
- var DEFAULT_LOGGING_CONFIG, DEFAULT_PERMISSION_MODE = "approve-all", DEFAULT_NON_INTERACTIVE_PERMISSIONS = "fail";
5519
+ var DEFAULT_LOGGING_CONFIG, DEFAULT_PERMISSION_MODE = "approve-all", DEFAULT_NON_INTERACTIVE_PERMISSIONS = "deny", DEFAULT_WECHAT_REPLY_MODE = "stream";
4836
5520
  var init_load_config = __esm(() => {
4837
5521
  DEFAULT_LOGGING_CONFIG = {
4838
5522
  level: "info",
@@ -4843,8 +5527,8 @@ var init_load_config = __esm(() => {
4843
5527
  });
4844
5528
 
4845
5529
  // src/config/config-store.ts
4846
- import { mkdir as mkdir6, writeFile as writeFile4 } from "node:fs/promises";
4847
- import { dirname as dirname5 } from "node:path";
5530
+ import { mkdir as mkdir7, writeFile as writeFile5 } from "node:fs/promises";
5531
+ import { dirname as dirname6 } from "node:path";
4848
5532
 
4849
5533
  class ConfigStore {
4850
5534
  path;
@@ -4855,8 +5539,8 @@ class ConfigStore {
4855
5539
  return await loadConfig(this.path);
4856
5540
  }
4857
5541
  async save(config) {
4858
- await mkdir6(dirname5(this.path), { recursive: true });
4859
- await writeFile4(this.path, `${JSON.stringify(config, null, 2)}
5542
+ await mkdir7(dirname6(this.path), { recursive: true });
5543
+ await writeFile5(this.path, `${JSON.stringify(config, null, 2)}
4860
5544
  `, "utf8");
4861
5545
  }
4862
5546
  async upsertWorkspace(name, cwd, description) {
@@ -4896,13 +5580,22 @@ class ConfigStore {
4896
5580
  await this.save(config);
4897
5581
  return config;
4898
5582
  }
5583
+ async updateWechat(wechat) {
5584
+ const config = await this.load();
5585
+ config.wechat = {
5586
+ ...config.wechat,
5587
+ ...wechat
5588
+ };
5589
+ await this.save(config);
5590
+ return config;
5591
+ }
4899
5592
  }
4900
5593
  var init_config_store = __esm(() => {
4901
5594
  init_load_config();
4902
5595
  });
4903
5596
 
4904
5597
  // src/config/ensure-config.ts
4905
- import { readFile as readFile5 } from "node:fs/promises";
5598
+ import { readFile as readFile6 } from "node:fs/promises";
4906
5599
  async function ensureConfigExists(path11) {
4907
5600
  try {
4908
5601
  await loadConfig(path11);
@@ -4916,7 +5609,10 @@ async function ensureConfigExists(path11) {
4916
5609
  }
4917
5610
  async function loadDefaultConfigTemplate() {
4918
5611
  const templatePath = new URL("../../config.example.json", import.meta.url);
4919
- const template = JSON.parse(await readFile5(templatePath, "utf8"));
5612
+ return normalizeDefaultConfigTemplate(JSON.parse(await readFile6(templatePath, "utf8")));
5613
+ }
5614
+ function normalizeDefaultConfigTemplate(raw) {
5615
+ const template = parseConfig(raw);
4920
5616
  return {
4921
5617
  ...template,
4922
5618
  agents: Object.fromEntries(Object.entries(template.agents).map(([name, agent]) => [
@@ -5061,6 +5757,23 @@ class SessionService {
5061
5757
  session.last_used_at = new Date().toISOString();
5062
5758
  await this.persist();
5063
5759
  }
5760
+ async setCurrentSessionReplyMode(chatKey, replyMode) {
5761
+ const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
5762
+ if (!currentAlias) {
5763
+ throw new Error("no current session selected");
5764
+ }
5765
+ const session = this.state.sessions[currentAlias];
5766
+ if (!session) {
5767
+ throw new Error("no current session selected");
5768
+ }
5769
+ if (replyMode) {
5770
+ session.reply_mode = replyMode;
5771
+ } else {
5772
+ delete session.reply_mode;
5773
+ }
5774
+ session.last_used_at = new Date().toISOString();
5775
+ await this.persist();
5776
+ }
5064
5777
  async getCurrentSession(chatKey) {
5065
5778
  const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
5066
5779
  if (!currentAlias) {
@@ -5085,6 +5798,13 @@ class SessionService {
5085
5798
  }
5086
5799
  toResolvedSession(session) {
5087
5800
  const agentConfig = this.config.agents[session.agent];
5801
+ if (!agentConfig) {
5802
+ throw new Error(`session "${session.alias}" references agent "${session.agent}", but that agent is no longer registered`);
5803
+ }
5804
+ const workspaceConfig = this.config.workspaces[session.workspace];
5805
+ if (!workspaceConfig) {
5806
+ throw new Error(`session "${session.alias}" references workspace "${session.workspace}", but that workspace is no longer registered`);
5807
+ }
5088
5808
  return {
5089
5809
  alias: session.alias,
5090
5810
  agent: session.agent,
@@ -5092,7 +5812,8 @@ class SessionService {
5092
5812
  workspace: session.workspace,
5093
5813
  transportSession: session.transport_session,
5094
5814
  modeId: session.mode_id,
5095
- cwd: this.config.workspaces[session.workspace].cwd
5815
+ replyMode: session.reply_mode,
5816
+ cwd: workspaceConfig.cwd
5096
5817
  };
5097
5818
  }
5098
5819
  async setSessionTransportAgentCommand(alias, transportAgentCommand) {
@@ -5124,6 +5845,7 @@ class SessionService {
5124
5845
  transport_session: transportSession,
5125
5846
  ...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : existingSession?.transport_agent_command ? { transport_agent_command: existingSession.transport_agent_command } : {},
5126
5847
  mode_id: existingSession?.mode_id,
5848
+ reply_mode: existingSession?.reply_mode,
5127
5849
  created_at: existingSession?.created_at ?? now,
5128
5850
  last_used_at: now
5129
5851
  };
@@ -5132,6 +5854,15 @@ class SessionService {
5132
5854
  return this.toResolvedSession(session);
5133
5855
  }
5134
5856
  validateSession(alias, agent, workspace) {
5857
+ if (alias.trim().length === 0) {
5858
+ throw new Error("session alias must be a non-empty string");
5859
+ }
5860
+ if (agent.trim().length === 0) {
5861
+ throw new Error("agent must be a non-empty string");
5862
+ }
5863
+ if (workspace.trim().length === 0) {
5864
+ throw new Error("workspace must be a non-empty string");
5865
+ }
5135
5866
  if (!this.config.workspaces[workspace]) {
5136
5867
  throw new Error(`workspace "${workspace}" is not registered`);
5137
5868
  }
@@ -5151,8 +5882,28 @@ function createEmptyState() {
5151
5882
  }
5152
5883
 
5153
5884
  // src/state/state-store.ts
5154
- import { mkdir as mkdir7, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
5155
- import { dirname as dirname6 } from "node:path";
5885
+ import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile6 } from "node:fs/promises";
5886
+ import { dirname as dirname7 } from "node:path";
5887
+ function isRecord2(value) {
5888
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5889
+ }
5890
+ function parseState(raw, path11) {
5891
+ if (!isRecord2(raw)) {
5892
+ throw new Error(`state file "${path11}" must contain a JSON object`);
5893
+ }
5894
+ const sessions = raw.sessions;
5895
+ if (!isRecord2(sessions)) {
5896
+ throw new Error(`state file "${path11}" must contain an object field "sessions"`);
5897
+ }
5898
+ const chatContexts = raw.chat_contexts;
5899
+ if (!isRecord2(chatContexts)) {
5900
+ throw new Error(`state file "${path11}" must contain an object field "chat_contexts"`);
5901
+ }
5902
+ return {
5903
+ sessions,
5904
+ chat_contexts: chatContexts
5905
+ };
5906
+ }
5156
5907
 
5157
5908
  class StateStore {
5158
5909
  path;
@@ -5161,11 +5912,19 @@ class StateStore {
5161
5912
  }
5162
5913
  async load() {
5163
5914
  try {
5164
- const content = await readFile6(this.path, "utf8");
5915
+ const content = await readFile7(this.path, "utf8");
5165
5916
  if (content.trim() === "") {
5166
5917
  return createEmptyState();
5167
5918
  }
5168
- return JSON.parse(content);
5919
+ let parsed;
5920
+ try {
5921
+ parsed = JSON.parse(content);
5922
+ } catch (error) {
5923
+ throw new Error(`failed to parse state file "${this.path}"`, {
5924
+ cause: error
5925
+ });
5926
+ }
5927
+ return parseState(parsed, this.path);
5169
5928
  } catch (error) {
5170
5929
  if (error.code === "ENOENT") {
5171
5930
  return createEmptyState();
@@ -5174,8 +5933,8 @@ class StateStore {
5174
5933
  }
5175
5934
  }
5176
5935
  async save(state) {
5177
- await mkdir7(dirname6(this.path), { recursive: true });
5178
- await writeFile5(this.path, JSON.stringify(state, null, 2));
5936
+ await mkdir8(dirname7(this.path), { recursive: true });
5937
+ await writeFile6(this.path, JSON.stringify(state, null, 2));
5179
5938
  }
5180
5939
  }
5181
5940
  var init_state_store = () => {};
@@ -5187,12 +5946,17 @@ __export(exports_run_console, {
5187
5946
  });
5188
5947
  async function runConsole(paths, deps) {
5189
5948
  const runtime = await deps.buildApp(paths);
5949
+ const consumerLock = deps.consumerLock ?? deps.consumerLockFactory?.(runtime);
5190
5950
  const sdk = await deps.loadWeixinSdk();
5191
5951
  const setIntervalFn = deps.setInterval ?? ((fn, delay) => setInterval(fn, delay));
5192
5952
  const clearIntervalFn = deps.clearInterval ?? ((timer) => clearInterval(timer));
5193
5953
  const addProcessListener = deps.addProcessListener ?? ((signal, handler) => process.on(signal, handler));
5194
5954
  const removeProcessListener = deps.removeProcessListener ?? ((signal, handler) => process.off(signal, handler));
5955
+ const processPid = deps.processPid ?? process.pid;
5956
+ const now = deps.now ?? (() => new Date().toISOString());
5957
+ const hostname = deps.hostname ?? (() => "");
5195
5958
  let heartbeatTimer = null;
5959
+ let consumerLockAcquired = false;
5196
5960
  const shutdownController = new AbortController;
5197
5961
  const signalHandler = () => {
5198
5962
  shutdownController.abort();
@@ -5209,6 +5973,53 @@ async function runConsole(paths, deps) {
5209
5973
  deps.daemonRuntime?.heartbeat().catch(() => {});
5210
5974
  }, deps.heartbeatIntervalMs ?? 30000);
5211
5975
  }
5976
+ if (consumerLock) {
5977
+ const lockMeta = {
5978
+ pid: processPid,
5979
+ mode: deps.daemonRuntime ? "daemon" : "foreground",
5980
+ startedAt: now(),
5981
+ configPath: paths.configPath,
5982
+ statePath: paths.statePath,
5983
+ hostname: hostname() || undefined
5984
+ };
5985
+ await runtime.logger.info("weixin.consumer_lock.acquire_attempt", "attempting to acquire weixin consumer lock", {
5986
+ pid: lockMeta.pid,
5987
+ mode: lockMeta.mode,
5988
+ configPath: lockMeta.configPath,
5989
+ statePath: lockMeta.statePath,
5990
+ hostname: lockMeta.hostname
5991
+ });
5992
+ try {
5993
+ await consumerLock.acquire(lockMeta);
5994
+ consumerLockAcquired = true;
5995
+ await runtime.logger.info("weixin.consumer_lock.acquired", "acquired weixin consumer lock", {
5996
+ pid: lockMeta.pid,
5997
+ mode: lockMeta.mode,
5998
+ configPath: lockMeta.configPath,
5999
+ statePath: lockMeta.statePath
6000
+ });
6001
+ } catch (error) {
6002
+ if (error instanceof ActiveWeixinConsumerLockError) {
6003
+ await runtime.logger.error("weixin.consumer_lock.acquire_failed", "weixin consumer lock is already held by another process", {
6004
+ conflictType: "active_lock_holder",
6005
+ activePid: error.existing.pid,
6006
+ activeMode: error.existing.mode,
6007
+ activeConfigPath: error.existing.configPath,
6008
+ activeStatePath: error.existing.statePath,
6009
+ requestedPid: lockMeta.pid,
6010
+ requestedMode: lockMeta.mode
6011
+ });
6012
+ } else {
6013
+ await runtime.logger.error("weixin.consumer_lock.acquire_failed", "failed to acquire weixin consumer lock", {
6014
+ conflictType: deps.daemonRuntime ? "daemon_startup_lock_failure" : "foreground_startup_lock_failure",
6015
+ requestedPid: lockMeta.pid,
6016
+ requestedMode: lockMeta.mode,
6017
+ error: error instanceof Error ? error.message : String(error)
6018
+ });
6019
+ }
6020
+ throw error;
6021
+ }
6022
+ }
5212
6023
  if (!sdk.isLoggedIn()) {
5213
6024
  console.log("[weacpx] 未检测到登录凭证,正在启动扫码登录...");
5214
6025
  await sdk.login();
@@ -5229,17 +6040,30 @@ async function runConsole(paths, deps) {
5229
6040
  if (deps.daemonRuntime) {
5230
6041
  await deps.daemonRuntime.stop();
5231
6042
  }
6043
+ if (consumerLockAcquired) {
6044
+ await consumerLock?.release();
6045
+ await runtime.logger.info("weixin.consumer_lock.released", "released weixin consumer lock", {
6046
+ pid: processPid
6047
+ });
6048
+ }
5232
6049
  if (disposeError) {
5233
6050
  throw disposeError;
5234
6051
  }
5235
6052
  }
5236
6053
  }
6054
+ var init_run_console = __esm(() => {
6055
+ init_consumer_lock();
6056
+ });
5237
6057
 
5238
6058
  // src/transport/acpx-bridge/acpx-bridge-protocol.ts
5239
6059
  function encodeBridgeRequest(request) {
5240
6060
  return `${JSON.stringify(request)}
5241
6061
  `;
5242
6062
  }
6063
+ function encodeBridgePromptSegmentEvent(event) {
6064
+ return `${JSON.stringify(event)}
6065
+ `;
6066
+ }
5243
6067
 
5244
6068
  // src/transport/acpx-bridge/acpx-bridge-client.ts
5245
6069
  import { spawn as spawn2 } from "node:child_process";
@@ -5250,30 +6074,59 @@ class AcpxBridgeClient {
5250
6074
  writeLine;
5251
6075
  nextId = 1;
5252
6076
  pending = new Map;
6077
+ terminalError = null;
5253
6078
  constructor(writeLine) {
5254
6079
  this.writeLine = writeLine;
5255
6080
  }
5256
- request(method, params) {
6081
+ request(method, params, onEvent) {
6082
+ if (this.terminalError) {
6083
+ return Promise.reject(this.terminalError);
6084
+ }
5257
6085
  const id = String(this.nextId);
5258
6086
  this.nextId += 1;
5259
6087
  return awaitable((resolve2, reject) => {
5260
6088
  this.pending.set(id, {
5261
6089
  resolve: (value) => resolve2(value),
5262
- reject
6090
+ reject,
6091
+ onEvent
5263
6092
  });
5264
- this.writeLine(encodeBridgeRequest({
5265
- id,
5266
- method,
5267
- params
5268
- }));
6093
+ try {
6094
+ const didWrite = this.writeLine(encodeBridgeRequest({
6095
+ id,
6096
+ method,
6097
+ params
6098
+ }));
6099
+ if (didWrite === false) {
6100
+ this.pending.delete(id);
6101
+ reject(new Error("bridge write buffer is full"));
6102
+ }
6103
+ } catch (error) {
6104
+ this.pending.delete(id);
6105
+ reject(error);
6106
+ }
5269
6107
  });
5270
6108
  }
5271
6109
  handleLine(line) {
5272
- const response = JSON.parse(line);
5273
- const pending = this.pending.get(response.id);
6110
+ let message;
6111
+ try {
6112
+ message = JSON.parse(line);
6113
+ } catch {
6114
+ return;
6115
+ }
6116
+ const pending = this.pending.get(message.id);
5274
6117
  if (!pending) {
5275
6118
  return;
5276
6119
  }
6120
+ if ("event" in message) {
6121
+ if (message.event === "prompt.segment") {
6122
+ pending.onEvent?.({
6123
+ type: "prompt.segment",
6124
+ text: message.text
6125
+ });
6126
+ }
6127
+ return;
6128
+ }
6129
+ const response = message;
5277
6130
  this.pending.delete(response.id);
5278
6131
  if (response.ok) {
5279
6132
  pending.resolve(response.result);
@@ -5290,6 +6143,7 @@ class AcpxBridgeClient {
5290
6143
  pending.reject(new Error(response.error.message));
5291
6144
  }
5292
6145
  handleExit(error) {
6146
+ this.terminalError = error;
5293
6147
  const pendingRequests = [...this.pending.values()];
5294
6148
  this.pending.clear();
5295
6149
  for (const pending of pendingRequests) {
@@ -5321,13 +6175,11 @@ async function spawnAcpxBridgeClient(options = {}) {
5321
6175
  ...process.env,
5322
6176
  WEACPX_BRIDGE_ACPX_COMMAND: options.acpxCommand ?? "acpx",
5323
6177
  WEACPX_BRIDGE_PERMISSION_MODE: options.permissionMode ?? "approve-all",
5324
- WEACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS: options.nonInteractivePermissions ?? "fail"
6178
+ WEACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS: options.nonInteractivePermissions ?? "deny"
5325
6179
  },
5326
6180
  stdio: ["pipe", "pipe", "inherit"]
5327
6181
  });
5328
- const client = new AcpxBridgeClient((line) => {
5329
- child.stdin.write(line);
5330
- });
6182
+ const client = new AcpxBridgeClient((line) => child.stdin.write(line));
5331
6183
  const output = createInterface({
5332
6184
  input: child.stdout,
5333
6185
  crlfDelay: Infinity
@@ -5374,10 +6226,14 @@ class AcpxBridgeTransport {
5374
6226
  async ensureSession(session) {
5375
6227
  await this.client.request("ensureSession", this.toParams(session));
5376
6228
  }
5377
- async prompt(session, text, _reply) {
6229
+ async prompt(session, text, reply) {
5378
6230
  return await this.client.request("prompt", {
5379
6231
  ...this.toParams(session),
5380
6232
  text
6233
+ }, (event) => {
6234
+ if (event.type === "prompt.segment") {
6235
+ reply?.(event.text);
6236
+ }
5381
6237
  });
5382
6238
  }
5383
6239
  async setMode(session, modeId) {
@@ -5393,6 +6249,9 @@ class AcpxBridgeTransport {
5393
6249
  const result = await this.client.request("hasSession", this.toParams(session));
5394
6250
  return result.exists;
5395
6251
  }
6252
+ async updatePermissionPolicy(policy) {
6253
+ await this.client.request("updatePermissionPolicy", { ...policy });
6254
+ }
5396
6255
  async dispose() {
5397
6256
  await this.client.dispose?.();
5398
6257
  }
@@ -5481,12 +6340,12 @@ function parseStreamingChunks(state, line) {
5481
6340
 
5482
6341
  // src/transport/acpx-cli/node-pty-helper.ts
5483
6342
  import { chmod as chmodFs } from "node:fs/promises";
5484
- import { dirname as dirname7, join as join3 } from "node:path";
6343
+ import { dirname as dirname8, join as join4 } from "node:path";
5485
6344
  function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
5486
6345
  if (platform === "win32") {
5487
6346
  return null;
5488
6347
  }
5489
- return join3(dirname7(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
6348
+ return join4(dirname8(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
5490
6349
  }
5491
6350
  async function ensureNodePtyHelperExecutable(helperPath, chmod = chmodFs) {
5492
6351
  if (!helperPath) {
@@ -5574,7 +6433,7 @@ class AcpxCliTransport {
5574
6433
  this.command = options.command ?? "acpx";
5575
6434
  this.sessionInitTimeoutMs = options.sessionInitTimeoutMs ?? 120000;
5576
6435
  this.permissionMode = options.permissionMode ?? "approve-all";
5577
- this.nonInteractivePermissions = options.nonInteractivePermissions ?? "fail";
6436
+ this.nonInteractivePermissions = options.nonInteractivePermissions ?? "deny";
5578
6437
  this.runCommand = runCommand;
5579
6438
  this.runPtyCommand = runPtyCommand;
5580
6439
  }
@@ -5618,6 +6477,10 @@ class AcpxCliTransport {
5618
6477
  message: output.trim()
5619
6478
  };
5620
6479
  }
6480
+ async updatePermissionPolicy(policy) {
6481
+ this.permissionMode = policy.permissionMode;
6482
+ this.nonInteractivePermissions = policy.nonInteractivePermissions;
6483
+ }
5621
6484
  async hasSession(session) {
5622
6485
  const result = await this.runCommand(this.command, this.buildArgs(session, [
5623
6486
  "sessions",
@@ -5774,8 +6637,8 @@ __export(exports_main, {
5774
6637
  main: () => main2,
5775
6638
  buildApp: () => buildApp
5776
6639
  });
5777
- import { homedir as homedir4 } from "node:os";
5778
- import { dirname as dirname8, join as join4 } from "node:path";
6640
+ import { homedir as homedir5 } from "node:os";
6641
+ import { dirname as dirname9, join as join5 } from "node:path";
5779
6642
  import { fileURLToPath as fileURLToPath3 } from "node:url";
5780
6643
  async function buildApp(paths, deps = {}) {
5781
6644
  await ensureConfigExists(paths.configPath);
@@ -5837,7 +6700,7 @@ async function main2() {
5837
6700
  }
5838
6701
  }
5839
6702
  function resolveRuntimePaths() {
5840
- const home = process.env.HOME ?? homedir4();
6703
+ const home = process.env.HOME ?? homedir5();
5841
6704
  if (!home) {
5842
6705
  throw new Error("Unable to resolve the current user home directory");
5843
6706
  }
@@ -5853,9 +6716,9 @@ function resolveBridgeEntryPath() {
5853
6716
  return fileURLToPath3(new URL("./bridge/bridge-main.ts", import.meta.url));
5854
6717
  }
5855
6718
  function resolveAppLogPath(configPath) {
5856
- const rootDir = dirname8(configPath);
5857
- const runtimeDir = join4(rootDir, "runtime");
5858
- return join4(runtimeDir, "app.log");
6719
+ const rootDir = dirname9(configPath);
6720
+ const runtimeDir = join5(rootDir, "runtime");
6721
+ return join5(runtimeDir, "app.log");
5859
6722
  }
5860
6723
  var init_main = __esm(async () => {
5861
6724
  init_command_router();
@@ -5867,6 +6730,7 @@ var init_main = __esm(async () => {
5867
6730
  init_app_logger();
5868
6731
  init_session_service();
5869
6732
  init_state_store();
6733
+ init_run_console();
5870
6734
  init_acpx_bridge_client();
5871
6735
  init_acpx_cli_transport();
5872
6736
  init_weixin_sdk();
@@ -5874,7 +6738,7 @@ var init_main = __esm(async () => {
5874
6738
  });
5875
6739
 
5876
6740
  // src/cli.ts
5877
- import { homedir as homedir5 } from "node:os";
6741
+ import { homedir as homedir6 } from "node:os";
5878
6742
  import { sep } from "node:path";
5879
6743
  import { fileURLToPath as fileURLToPath4 } from "node:url";
5880
6744
 
@@ -5955,7 +6819,7 @@ class DaemonController {
5955
6819
  return { state: "stopped", stale: true };
5956
6820
  }
5957
6821
  if (!status) {
5958
- return { state: "stopped" };
6822
+ return { state: "indeterminate", pid, reason: "missing-status" };
5959
6823
  }
5960
6824
  return {
5961
6825
  state: "running",
@@ -5968,6 +6832,9 @@ class DaemonController {
5968
6832
  if (current.state === "running") {
5969
6833
  return { state: "already-running", pid: current.pid };
5970
6834
  }
6835
+ if (current.state === "indeterminate") {
6836
+ throw new Error(`weacpx daemon process is already running (pid ${current.pid}) but status metadata is missing`);
6837
+ }
5971
6838
  await this.statusStore.clear();
5972
6839
  const pid = await this.deps.spawnDetached();
5973
6840
  await this.writePid(pid);
@@ -6175,27 +7042,30 @@ async function spawnWindowsHiddenProcess(request) {
6175
7042
  async function defaultTerminateProcess(pid) {
6176
7043
  await terminateProcessTree(pid);
6177
7044
  }
6178
- async function terminateProcessTree(pid, platform = process.platform, runCommand = defaultRunProcessCommand) {
7045
+ async function terminateProcessTree(pid, platform = process.platform, runCommand = defaultRunProcessCommand, killProcess = (targetPid, signal) => {
7046
+ process.kill(targetPid, signal);
7047
+ }, isProcessRunning = defaultIsProcessRunning) {
6179
7048
  if (platform === "win32") {
6180
7049
  try {
6181
7050
  await runCommand("taskkill", ["/PID", String(pid), "/T", "/F"]);
6182
7051
  } catch {}
6183
7052
  return;
6184
7053
  }
7054
+ const targetPid = pid > 0 ? -pid : pid;
6185
7055
  try {
6186
- process.kill(pid, "SIGTERM");
7056
+ killProcess(targetPid, "SIGTERM");
6187
7057
  } catch {
6188
7058
  return;
6189
7059
  }
6190
7060
  const deadline = Date.now() + 5000;
6191
7061
  while (Date.now() < deadline) {
6192
- if (!defaultIsProcessRunning(pid)) {
7062
+ if (!isProcessRunning(targetPid)) {
6193
7063
  return;
6194
7064
  }
6195
7065
  await new Promise((resolve) => setTimeout(resolve, 100));
6196
7066
  }
6197
7067
  try {
6198
- process.kill(pid, "SIGKILL");
7068
+ killProcess(targetPid, "SIGKILL");
6199
7069
  } catch {}
6200
7070
  }
6201
7071
  async function defaultRunProcessCommand(command, args) {
@@ -6270,6 +7140,7 @@ class DaemonRuntime {
6270
7140
  }
6271
7141
 
6272
7142
  // src/cli.ts
7143
+ init_consumer_lock();
6273
7144
  var HELP_LINES = [
6274
7145
  "用法:",
6275
7146
  "weacpx login - 微信登录",
@@ -6306,6 +7177,11 @@ async function runCli(args, deps = {}) {
6306
7177
  }
6307
7178
  case "status": {
6308
7179
  const status = await controller.getStatus();
7180
+ if (status.state === "indeterminate") {
7181
+ print("weacpx 进程仍在运行,但状态元数据缺失");
7182
+ print(`PID: ${status.pid}`);
7183
+ return 1;
7184
+ }
6309
7185
  if (status.state !== "running") {
6310
7186
  print("weacpx 未运行");
6311
7187
  return 0;
@@ -6349,7 +7225,7 @@ async function defaultRun() {
6349
7225
  const [{ buildApp: buildApp2, resolveRuntimePaths: resolveRuntimePaths2 }, { loadWeixinSdk: loadWeixinSdk2 }, { runConsole: runConsole2 }] = await Promise.all([
6350
7226
  init_main().then(() => exports_main),
6351
7227
  Promise.resolve().then(() => (init_weixin_sdk(), exports_weixin_sdk)),
6352
- Promise.resolve().then(() => exports_run_console)
7228
+ Promise.resolve().then(() => (init_run_console(), exports_run_console))
6353
7229
  ]);
6354
7230
  const runtimePaths = resolveRuntimePaths2();
6355
7231
  const daemonPaths = resolveDaemonPaths({ home: requireHome() });
@@ -6359,7 +7235,13 @@ async function defaultRun() {
6359
7235
  defaultLoggingLevel: resolveCliEntryPath().includes(`${sep}src${sep}`) ? "debug" : "info"
6360
7236
  }),
6361
7237
  loadWeixinSdk: loadWeixinSdk2,
6362
- daemonRuntime
7238
+ daemonRuntime,
7239
+ consumerLockFactory: (runtime) => createWeixinConsumerLock({
7240
+ lockFilePath: `${daemonPaths.runtimeDir}${sep}weixin-consumer.lock.json`,
7241
+ onDiagnostic: async (event, context) => {
7242
+ await runtime.logger.info(`weixin.consumer_lock.${event}`, "weixin consumer lock diagnostic", context);
7243
+ }
7244
+ })
6363
7245
  });
6364
7246
  }
6365
7247
  function createDefaultController() {
@@ -6372,7 +7254,7 @@ function createDefaultController() {
6372
7254
  });
6373
7255
  }
6374
7256
  function requireHome() {
6375
- const home = process.env.HOME ?? homedir5();
7257
+ const home = process.env.HOME ?? homedir6();
6376
7258
  if (!home) {
6377
7259
  throw new Error("Unable to resolve the current user home directory");
6378
7260
  }