weacpx 0.6.0 → 0.7.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.
Files changed (63) hide show
  1. package/dist/bridge/bridge-main.js +66 -24
  2. package/dist/channels/types.d.ts +14 -0
  3. package/dist/cli.js +1268 -407
  4. package/dist/commands/command-hints.d.ts +11 -0
  5. package/dist/commands/command-list.d.ts +3 -0
  6. package/dist/commands/config-clone.d.ts +2 -0
  7. package/dist/commands/handlers/agent-handler.d.ts +6 -0
  8. package/dist/commands/handlers/config-handler.d.ts +5 -0
  9. package/dist/commands/handlers/later-handler.d.ts +21 -0
  10. package/dist/commands/handlers/orchestration-handler.d.ts +16 -0
  11. package/dist/commands/handlers/permission-handler.d.ts +9 -0
  12. package/dist/commands/handlers/session-handler.d.ts +39 -0
  13. package/dist/commands/handlers/session-list-marker.d.ts +1 -0
  14. package/dist/commands/handlers/workspace-handler.d.ts +8 -0
  15. package/dist/commands/help/help-registry.d.ts +4 -0
  16. package/dist/commands/help/help-types.d.ts +12 -0
  17. package/dist/commands/parse-command.d.ts +178 -0
  18. package/dist/commands/router-types.d.ts +144 -0
  19. package/dist/commands/workspace-name.d.ts +4 -0
  20. package/dist/commands/workspace-path.d.ts +4 -0
  21. package/dist/config/agent-templates.d.ts +4 -0
  22. package/dist/config/config-store.d.ts +13 -0
  23. package/dist/config/load-config.d.ts +10 -0
  24. package/dist/config/resolve-agent-command.d.ts +2 -0
  25. package/dist/formatting/render-text.d.ts +23 -0
  26. package/dist/orchestration/async-mutex.d.ts +4 -0
  27. package/dist/orchestration/build-coordinator-prompt.d.ts +66 -0
  28. package/dist/orchestration/orchestration-service.d.ts +471 -0
  29. package/dist/orchestration/progress-line-parser.d.ts +19 -0
  30. package/dist/orchestration/render-delegate-group-result.d.ts +6 -0
  31. package/dist/orchestration/render-delegate-question-package.d.ts +21 -0
  32. package/dist/orchestration/render-delegate-result.d.ts +2 -0
  33. package/dist/orchestration/task-watch-timeouts.d.ts +5 -0
  34. package/dist/plugin-api.d.ts +10 -0
  35. package/dist/plugin-api.js +157 -0
  36. package/dist/{weixin/messaging → runtime}/conversation-executor.d.ts +1 -1
  37. package/dist/runtime/core-home.d.ts +26 -0
  38. package/dist/runtime/turn-lane.d.ts +2 -0
  39. package/dist/scheduled/parse-later-time.d.ts +11 -0
  40. package/dist/scheduled/scheduled-render.d.ts +7 -0
  41. package/dist/scheduled/scheduled-service.d.ts +41 -0
  42. package/dist/scheduled/scheduled-types.d.ts +29 -0
  43. package/dist/sessions/active-turn-registry.d.ts +6 -0
  44. package/dist/sessions/session-service.d.ts +118 -0
  45. package/dist/state/state-store.d.ts +8 -0
  46. package/dist/state/types.d.ts +51 -0
  47. package/dist/transport/tool-event-mode.d.ts +14 -0
  48. package/dist/transport/types.d.ts +129 -0
  49. package/dist/util/path.d.ts +4 -0
  50. package/dist/util/sanitize.d.ts +10 -0
  51. package/dist/util/text.d.ts +3 -0
  52. package/dist/version.d.ts +2 -0
  53. package/dist/weixin/agent/interface.d.ts +1 -0
  54. package/dist/weixin/api/config-cache.d.ts +18 -1
  55. package/dist/weixin/auth/accounts.d.ts +0 -1
  56. package/dist/weixin/bot.d.ts +11 -0
  57. package/dist/weixin/messaging/completion-notice.d.ts +2 -0
  58. package/dist/weixin/messaging/foreground-gate.d.ts +3 -0
  59. package/dist/weixin/messaging/handle-weixin-message-turn.d.ts +4 -0
  60. package/dist/weixin/messaging/inbound.d.ts +7 -0
  61. package/dist/weixin/messaging/quota-manager.d.ts +15 -1
  62. package/dist/weixin/monitor/monitor.d.ts +8 -0
  63. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -48,6 +48,17 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __promiseAll = (args) => Promise.all(args);
49
49
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
50
50
 
51
+ // src/runtime/core-home.ts
52
+ import { join } from "node:path";
53
+ function coreHomeDir(home) {
54
+ return join(home, CORE_HOME_DIR_NAME);
55
+ }
56
+ function coreHomeDisplayPath(...segments) {
57
+ return ["~", CORE_HOME_DIR_NAME, ...segments].join("/");
58
+ }
59
+ var CORE_HOME_DIR_NAME = ".weacpx";
60
+ var init_core_home = () => {};
61
+
51
62
  // node_modules/graceful-fs/polyfills.js
52
63
  var require_polyfills = __commonJS((exports, module) => {
53
64
  var constants = __require("constants");
@@ -2088,36 +2099,56 @@ var init_private_file = __esm(() => {
2088
2099
  import_write_file_atomic = __toESM(require_lib(), 1);
2089
2100
  });
2090
2101
 
2091
- // src/commands/workspace-path.ts
2092
- import { access } from "node:fs/promises";
2093
- import { homedir } from "node:os";
2102
+ // src/util/path.ts
2094
2103
  import path from "node:path";
2095
- function normalizeWorkspacePath(input) {
2104
+ import { homedir } from "node:os";
2105
+ function normalizePath(input) {
2096
2106
  const expanded = expandHome(input);
2097
2107
  if (isWindowsLikePath(expanded)) {
2098
2108
  return path.win32.normalize(expanded).replace(/\\/g, "/");
2099
2109
  }
2100
2110
  return path.posix.normalize(expanded.replace(/\\/g, "/"));
2101
2111
  }
2102
- function basenameForWorkspacePath(input) {
2103
- const normalized = normalizeWorkspacePath(input);
2112
+ function basenameForPath(input) {
2113
+ const normalized = normalizePath(input);
2104
2114
  if (ROOT_PATH_RE.test(normalized)) {
2105
2115
  return normalized;
2106
2116
  }
2107
2117
  const base = path.posix.basename(normalized);
2108
2118
  return base || normalized;
2109
2119
  }
2110
- function sameWorkspacePath(left, right) {
2111
- const normalizedLeft = normalizeWorkspacePath(left);
2112
- const normalizedRight = normalizeWorkspacePath(right);
2120
+ function isSamePath(left, right) {
2121
+ const normalizedLeft = normalizePath(left);
2122
+ const normalizedRight = normalizePath(right);
2113
2123
  if (isWindowsLikePath(normalizedLeft) || isWindowsLikePath(normalizedRight)) {
2114
2124
  return normalizedLeft.toLowerCase() === normalizedRight.toLowerCase();
2115
2125
  }
2116
2126
  return normalizedLeft === normalizedRight;
2117
2127
  }
2128
+ function isWindowsLikePath(input) {
2129
+ return WINDOWS_DRIVE_PATH_RE.test(input) || WINDOWS_UNC_PATH_RE.test(input);
2130
+ }
2118
2131
  function expandHome(input) {
2119
2132
  return input.startsWith("~") ? homedir() + input.slice(1) : input;
2120
2133
  }
2134
+ var WINDOWS_DRIVE_PATH_RE, WINDOWS_UNC_PATH_RE, ROOT_PATH_RE;
2135
+ var init_path = __esm(() => {
2136
+ WINDOWS_DRIVE_PATH_RE = /^[a-zA-Z]:[\\/]/;
2137
+ WINDOWS_UNC_PATH_RE = /^\\\\/;
2138
+ ROOT_PATH_RE = /^(\/|[a-zA-Z]:\/?)$/;
2139
+ });
2140
+
2141
+ // src/commands/workspace-path.ts
2142
+ import { access } from "node:fs/promises";
2143
+ function normalizeWorkspacePath(input) {
2144
+ return normalizePath(input);
2145
+ }
2146
+ function basenameForWorkspacePath(input) {
2147
+ return basenameForPath(input);
2148
+ }
2149
+ function sameWorkspacePath(left, right) {
2150
+ return isSamePath(left, right);
2151
+ }
2121
2152
  async function pathExists(filePath) {
2122
2153
  try {
2123
2154
  await access(filePath);
@@ -2126,14 +2157,8 @@ async function pathExists(filePath) {
2126
2157
  return false;
2127
2158
  }
2128
2159
  }
2129
- function isWindowsLikePath(input) {
2130
- return WINDOWS_DRIVE_PATH_RE.test(input) || WINDOWS_UNC_PATH_RE.test(input);
2131
- }
2132
- var WINDOWS_DRIVE_PATH_RE, WINDOWS_UNC_PATH_RE, ROOT_PATH_RE;
2133
2160
  var init_workspace_path = __esm(() => {
2134
- WINDOWS_DRIVE_PATH_RE = /^[a-zA-Z]:[\\/]/;
2135
- WINDOWS_UNC_PATH_RE = /^\\\\/;
2136
- ROOT_PATH_RE = /^(\/|[a-zA-Z]:\/?)$/;
2161
+ init_path();
2137
2162
  });
2138
2163
 
2139
2164
  // src/config/resolve-agent-command.ts
@@ -2763,6 +2788,9 @@ class DaemonStatusStore {
2763
2788
  if (error.code === "ENOENT") {
2764
2789
  return null;
2765
2790
  }
2791
+ if (error instanceof SyntaxError) {
2792
+ return null;
2793
+ }
2766
2794
  throw error;
2767
2795
  }
2768
2796
  }
@@ -3137,7 +3165,7 @@ var init_create_daemon_controller = __esm(() => {
3137
3165
 
3138
3166
  // src/orchestration/orchestration-ipc.ts
3139
3167
  import { createHash } from "node:crypto";
3140
- import { join } from "node:path";
3168
+ import { join as join2 } from "node:path";
3141
3169
  function resolveOrchestrationEndpoint(runtimeDir, platform = process.platform) {
3142
3170
  if (platform === "win32") {
3143
3171
  const suffix = createHash("sha256").update(runtimeDir).digest("hex").slice(0, 12);
@@ -3148,7 +3176,7 @@ function resolveOrchestrationEndpoint(runtimeDir, platform = process.platform) {
3148
3176
  }
3149
3177
  return {
3150
3178
  kind: "unix",
3151
- path: join(runtimeDir, "orchestration.sock")
3179
+ path: join2(runtimeDir, "orchestration.sock")
3152
3180
  };
3153
3181
  }
3154
3182
  function createOrchestrationEndpoint(path2, platform = process.platform) {
@@ -3168,25 +3196,26 @@ function encodeOrchestrationRpcResponse(response) {
3168
3196
  var init_orchestration_ipc = () => {};
3169
3197
 
3170
3198
  // src/daemon/daemon-files.ts
3171
- import { dirname as dirname4, join as join2 } from "node:path";
3199
+ import { dirname as dirname4, join as join3 } from "node:path";
3172
3200
  function resolveDaemonPaths(options) {
3173
- const runtimeDir = options.runtimeDir ?? (options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : join2(options.home, ".weacpx", "runtime"));
3201
+ const runtimeDir = options.runtimeDir ?? (options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : join3(coreHomeDir(options.home), "runtime"));
3174
3202
  return {
3175
3203
  runtimeDir,
3176
- pidFile: join2(runtimeDir, "daemon.pid"),
3177
- statusFile: join2(runtimeDir, "status.json"),
3178
- stdoutLog: join2(runtimeDir, "stdout.log"),
3179
- stderrLog: join2(runtimeDir, "stderr.log"),
3180
- appLog: join2(runtimeDir, "app.log")
3204
+ pidFile: join3(runtimeDir, "daemon.pid"),
3205
+ statusFile: join3(runtimeDir, "status.json"),
3206
+ stdoutLog: join3(runtimeDir, "stdout.log"),
3207
+ stderrLog: join3(runtimeDir, "stderr.log"),
3208
+ appLog: join3(runtimeDir, "app.log")
3181
3209
  };
3182
3210
  }
3183
3211
  function resolveRuntimeDirFromConfigPath(configPath) {
3184
- return join2(dirname4(configPath), "runtime");
3212
+ return join3(dirname4(configPath), "runtime");
3185
3213
  }
3186
3214
  function resolveDaemonOrchestrationSocketPath(runtimeDir, platform = process.platform) {
3187
3215
  return resolveOrchestrationEndpoint(runtimeDir, platform).path;
3188
3216
  }
3189
3217
  var init_daemon_files = __esm(() => {
3218
+ init_core_home();
3190
3219
  init_orchestration_ipc();
3191
3220
  });
3192
3221
 
@@ -9688,8 +9717,10 @@ function readVersion(moduleUrl = import.meta.url) {
9688
9717
  }
9689
9718
  return "unknown";
9690
9719
  }
9691
- var PACKAGE_NAME = "weacpx";
9692
- var init_version = () => {};
9720
+ var PACKAGE_NAME = "weacpx", WEACPX_CORE_VERSION;
9721
+ var init_version = __esm(() => {
9722
+ WEACPX_CORE_VERSION = readVersion();
9723
+ });
9693
9724
 
9694
9725
  // src/orchestration/task-watch-timeouts.ts
9695
9726
  var DEFAULT_TASK_WATCH_TIMEOUT_MS = 60000, MAX_TASK_WATCH_TIMEOUT_MS, DEFAULT_TASK_WATCH_POLL_INTERVAL_MS = 1000, MAX_TASK_WATCH_POLL_INTERVAL_MS = 1e4, TASK_WATCH_RPC_TIMEOUT_PADDING_MS = 5000;
@@ -9713,10 +9744,60 @@ var init_quota_errors = __esm(() => {
9713
9744
  };
9714
9745
  });
9715
9746
 
9747
+ // src/util/sanitize.ts
9748
+ function sanitizeString(input, options = {}) {
9749
+ const {
9750
+ replacement = "-",
9751
+ collapse = false,
9752
+ trim = false,
9753
+ lowercase: lowercase2 = false,
9754
+ fallback
9755
+ } = options;
9756
+ let result = lowercase2 ? input.toLowerCase() : input;
9757
+ if (options.allow) {
9758
+ const pattern = new RegExp(`[^${options.allow.source.slice(1, -1)}]+`, "g");
9759
+ result = result.replace(pattern, replacement);
9760
+ } else if (options.deny) {
9761
+ result = result.replace(options.deny, replacement);
9762
+ }
9763
+ if (collapse) {
9764
+ const escaped = escapeRegExp(replacement);
9765
+ result = result.replace(new RegExp(`${escaped}+`, "g"), replacement);
9766
+ }
9767
+ if (trim) {
9768
+ const escaped = escapeRegExp(replacement);
9769
+ result = result.replace(new RegExp(`^${escaped}+|${escaped}+$`, "g"), "");
9770
+ }
9771
+ if (fallback !== undefined && result.length === 0) {
9772
+ return fallback;
9773
+ }
9774
+ return result;
9775
+ }
9776
+ function escapeRegExp(s) {
9777
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9778
+ }
9779
+
9780
+ // src/util/text.ts
9781
+ function truncateText(text, maxLength, ellipsis = "…") {
9782
+ if (text.length <= maxLength)
9783
+ return text;
9784
+ return text.slice(0, maxLength - ellipsis.length) + ellipsis;
9785
+ }
9786
+ function escapeForDoubleQuotes(input) {
9787
+ return input.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
9788
+ }
9789
+ function quoteIfNeeded(input) {
9790
+ return `"${escapeForDoubleQuotes(input)}"`;
9791
+ }
9792
+
9716
9793
  // src/commands/workspace-name.ts
9717
9794
  function sanitizeWorkspaceName(input, fallback = "workspace") {
9718
- const sanitized = input.trim().replace(UNSAFE_RUN_RE, "-").replace(TRIM_DASHES_RE, "");
9719
- return sanitized.length > 0 ? sanitized : fallback;
9795
+ return sanitizeString(input.trim(), {
9796
+ allow: /[a-zA-Z0-9._-]/,
9797
+ replacement: "-",
9798
+ trim: true,
9799
+ fallback
9800
+ });
9720
9801
  }
9721
9802
  function allocateWorkspaceName(base, existing) {
9722
9803
  if (!Object.prototype.hasOwnProperty.call(existing, base))
@@ -9732,13 +9813,11 @@ function isWorkspaceNameValid(input) {
9732
9813
  function quoteWorkspaceNameIfNeeded(input) {
9733
9814
  if (isWorkspaceNameValid(input))
9734
9815
  return input;
9735
- return `"${input.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
9816
+ return quoteIfNeeded(input);
9736
9817
  }
9737
- var VALID_WORKSPACE_NAME_RE, UNSAFE_RUN_RE, TRIM_DASHES_RE;
9818
+ var VALID_WORKSPACE_NAME_RE;
9738
9819
  var init_workspace_name = __esm(() => {
9739
9820
  VALID_WORKSPACE_NAME_RE = /^[a-zA-Z0-9._-]+$/;
9740
- UNSAFE_RUN_RE = /[^a-zA-Z0-9._-]+/g;
9741
- TRIM_DASHES_RE = /^-+|-+$/g;
9742
9821
  });
9743
9822
 
9744
9823
  // src/orchestration/orchestration-types.ts
@@ -10288,7 +10367,7 @@ function renderLaterList(tasks, displaySession) {
10288
10367
  `).trimEnd();
10289
10368
  }
10290
10369
  function preview(text) {
10291
- return text.length <= LATER_MESSAGE_PREVIEW_CHARS ? text : `${text.slice(0, LATER_MESSAGE_PREVIEW_CHARS - 1)}…`;
10370
+ return truncateText(text, LATER_MESSAGE_PREVIEW_CHARS);
10292
10371
  }
10293
10372
  function formatLocalDateTime(date4) {
10294
10373
  const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
@@ -10434,7 +10513,10 @@ class ScheduledTaskService {
10434
10513
  }
10435
10514
  nextId() {
10436
10515
  for (let attempt = 0;attempt < 20; attempt += 1) {
10437
- const id = normalizeId(this.generateId()).replace(/[^0-9a-z]/g, "").slice(0, 6);
10516
+ const id = sanitizeString(normalizeId(this.generateId()), {
10517
+ allow: /[0-9a-z]/,
10518
+ replacement: ""
10519
+ }).slice(0, 6);
10438
10520
  if (id.length >= 4 && !this.state.scheduled_tasks[id])
10439
10521
  return id;
10440
10522
  }
@@ -10448,14 +10530,65 @@ class ScheduledTaskService {
10448
10530
  }
10449
10531
  }
10450
10532
  function normalizeId(input) {
10451
- return input.trim().replace(/^#/, "").toLowerCase();
10533
+ return sanitizeString(input.trim(), {
10534
+ deny: /^#/,
10535
+ replacement: "",
10536
+ lowercase: true
10537
+ });
10452
10538
  }
10453
10539
  var init_scheduled_service = () => {};
10454
10540
 
10455
10541
  // src/plugins/plugin-home.ts
10456
- import { mkdir as mkdir6, writeFile as writeFile4 } from "node:fs/promises";
10542
+ import { readFileSync } from "node:fs";
10543
+ import { copyFile, mkdir as mkdir6, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
10457
10544
  import { homedir as homedir3 } from "node:os";
10458
- import { join as join3 } from "node:path";
10545
+ import { dirname as dirname6, join as join5 } from "node:path";
10546
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
10547
+ function resolveCoreRoot() {
10548
+ try {
10549
+ let dir = dirname6(fileURLToPath2(import.meta.url));
10550
+ for (let depth = 0;depth < 12; depth++) {
10551
+ try {
10552
+ const pkg = JSON.parse(readFileSync(join5(dir, "package.json"), "utf-8"));
10553
+ if (pkg.name && CORE_PACKAGE_NAMES.includes(pkg.name))
10554
+ return dir;
10555
+ } catch {}
10556
+ const parent = dirname6(dir);
10557
+ if (parent === dir)
10558
+ break;
10559
+ dir = parent;
10560
+ }
10561
+ return null;
10562
+ } catch {
10563
+ return null;
10564
+ }
10565
+ }
10566
+ async function ensureCoreResolution(pluginHome) {
10567
+ const root = resolveCoreRoot();
10568
+ if (!root)
10569
+ return;
10570
+ const srcJs = join5(root, "dist", "plugin-api.js");
10571
+ for (const name of CORE_PACKAGE_NAMES) {
10572
+ const targetDir = join5(pluginHome, "node_modules", name);
10573
+ const dstJs = join5(targetDir, "plugin-api.js");
10574
+ await mkdir6(targetDir, { recursive: true });
10575
+ try {
10576
+ await copyFile(srcJs, dstJs);
10577
+ } catch (error2) {
10578
+ const message = error2 instanceof Error ? error2.message : String(error2);
10579
+ console.warn(`weacpx: skipped plugin-api resolution shim for "${name}" — could not copy ${srcJs} (${message}). ` + `Channel plugins importing "${name}/plugin-api" at runtime may fail to load.`);
10580
+ continue;
10581
+ }
10582
+ await writeFile4(join5(targetDir, "package.json"), JSON.stringify({
10583
+ name,
10584
+ type: "module",
10585
+ exports: {
10586
+ "./plugin-api": "./plugin-api.js"
10587
+ }
10588
+ }, null, 2) + `
10589
+ `);
10590
+ }
10591
+ }
10459
10592
  function coerceMissing(value) {
10460
10593
  if (value === undefined)
10461
10594
  return;
@@ -10475,17 +10608,43 @@ function resolvePluginHome(input = {}) {
10475
10608
  if (envOverride)
10476
10609
  return envOverride;
10477
10610
  const home = coerceMissing(input.home) ?? coerceMissing(process.env.HOME) ?? homedir3();
10478
- return join3(home, ".weacpx", "plugins");
10611
+ return join5(coreHomeDir(home), "plugins");
10612
+ }
10613
+ async function normalizePluginHomeManifest(pluginHome) {
10614
+ const manifestPath = join5(pluginHome, "package.json");
10615
+ let raw;
10616
+ try {
10617
+ raw = await readFile6(manifestPath, "utf8");
10618
+ } catch {
10619
+ return false;
10620
+ }
10621
+ let parsed;
10622
+ try {
10623
+ parsed = JSON.parse(raw);
10624
+ } catch {
10625
+ return false;
10626
+ }
10627
+ const normalized = JSON.stringify(parsed, null, 2) + `
10628
+ `;
10629
+ if (normalized === raw)
10630
+ return false;
10631
+ await writeFile4(manifestPath, normalized, { mode: 384 });
10632
+ return true;
10479
10633
  }
10480
10634
  async function ensurePluginHome(pluginHome) {
10481
10635
  await mkdir6(pluginHome, { recursive: true, mode: 448 });
10482
- await writeFile4(join3(pluginHome, "package.json"), JSON.stringify({ private: true, type: "module" }, null, 2) + `
10636
+ await writeFile4(join5(pluginHome, "package.json"), JSON.stringify({ private: true, type: "module" }, null, 2) + `
10483
10637
  `, { flag: "wx" }).catch((error2) => {
10484
10638
  if (error2.code !== "EEXIST")
10485
10639
  throw error2;
10486
10640
  });
10641
+ await ensureCoreResolution(pluginHome);
10487
10642
  }
10488
- var init_plugin_home = () => {};
10643
+ var CORE_PACKAGE_NAMES;
10644
+ var init_plugin_home = __esm(() => {
10645
+ init_core_home();
10646
+ CORE_PACKAGE_NAMES = ["weacpx", "xacpx"];
10647
+ });
10489
10648
 
10490
10649
  // src/weixin/storage/ensure-dir.ts
10491
10650
  import fs2 from "node:fs";
@@ -10515,7 +10674,11 @@ var init_state_dir = () => {};
10515
10674
  import fs3 from "node:fs";
10516
10675
  import path4 from "node:path";
10517
10676
  function normalizeAccountId(raw) {
10518
- return raw.trim().toLowerCase().replace(/[@.]/g, "-");
10677
+ return sanitizeString(raw.trim(), {
10678
+ deny: /[@.]/g,
10679
+ replacement: "-",
10680
+ lowercase: true
10681
+ });
10519
10682
  }
10520
10683
  function deriveRawAccountId(normalizedId) {
10521
10684
  if (normalizedId.endsWith("-im-bot")) {
@@ -12724,11 +12887,13 @@ function resolveContextTokenFilePath(accountId) {
12724
12887
  return path6.join(resolveStateDir(), "openclaw-weixin", "accounts", `${accountId}.context-tokens.json`);
12725
12888
  }
12726
12889
  function persistContextTokens(accountId) {
12890
+ pruneContextTokensForAccount(accountId);
12727
12891
  const prefix = `${accountId}:`;
12728
12892
  const tokens = {};
12729
- for (const [k, v] of contextTokenStore) {
12730
- if (k.startsWith(prefix))
12731
- tokens[k.slice(prefix.length)] = v;
12893
+ for (const [k, entry] of contextTokenStore) {
12894
+ if (k.startsWith(prefix)) {
12895
+ tokens[k.slice(prefix.length)] = { token: entry.token, updatedAt: entry.updatedAt };
12896
+ }
12732
12897
  }
12733
12898
  const filePath = resolveContextTokenFilePath(accountId);
12734
12899
  try {
@@ -12745,12 +12910,15 @@ function restoreContextTokens(accountId) {
12745
12910
  const raw = fs5.readFileSync(filePath, "utf-8");
12746
12911
  const tokens = JSON.parse(raw);
12747
12912
  let count = 0;
12748
- for (const [userId, token] of Object.entries(tokens)) {
12749
- if (typeof token === "string" && token) {
12750
- contextTokenStore.set(contextTokenKey(accountId, userId), token);
12913
+ for (const [userId, value] of Object.entries(tokens)) {
12914
+ const entry = parsePersistedContextToken(value);
12915
+ if (entry) {
12916
+ contextTokenStore.set(contextTokenKey(accountId, userId), entry);
12751
12917
  count++;
12752
12918
  }
12753
12919
  }
12920
+ pruneContextTokensForAccount(accountId);
12921
+ persistContextTokens(accountId);
12754
12922
  logger.info(`restoreContextTokens: restored ${count} tokens for account=${accountId}`);
12755
12923
  } catch (err) {
12756
12924
  logger.warn(`restoreContextTokens: failed to read ${filePath}: ${String(err)}`);
@@ -12774,15 +12942,64 @@ function clearContextTokensForAccount(accountId) {
12774
12942
  function setContextToken(accountId, userId, token) {
12775
12943
  const k = contextTokenKey(accountId, userId);
12776
12944
  logger.debug(`setContextToken: key=${k}`);
12777
- contextTokenStore.set(k, token);
12945
+ contextTokenStore.set(k, { token, updatedAt: contextTokenRetention.now() });
12778
12946
  persistContextTokens(accountId);
12779
12947
  }
12780
12948
  function getContextToken(accountId, userId) {
12781
12949
  const k = contextTokenKey(accountId, normalizeWeixinUserIdFromChatKey(userId));
12782
- const val = contextTokenStore.get(k);
12950
+ pruneContextTokensForAccount(accountId);
12951
+ const val = contextTokenStore.get(k)?.token;
12783
12952
  logger.debug(`getContextToken: key=${k} found=${val !== undefined} storeSize=${contextTokenStore.size}`);
12784
12953
  return val;
12785
12954
  }
12955
+ function parsePersistedContextToken(value) {
12956
+ if (typeof value === "string" && value) {
12957
+ return { token: value, updatedAt: contextTokenRetention.now() };
12958
+ }
12959
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
12960
+ return null;
12961
+ }
12962
+ const record3 = value;
12963
+ if (typeof record3.token !== "string" || record3.token.length === 0) {
12964
+ return null;
12965
+ }
12966
+ const updatedAt = typeof record3.updatedAt === "number" && Number.isFinite(record3.updatedAt) ? record3.updatedAt : contextTokenRetention.now();
12967
+ return { token: record3.token, updatedAt };
12968
+ }
12969
+ function pruneContextTokensForAccount(accountId) {
12970
+ const prefix = `${accountId}:`;
12971
+ const now = contextTokenRetention.now();
12972
+ const entries = [...contextTokenStore.entries()].filter(([key]) => key.startsWith(prefix));
12973
+ for (const [key, entry] of entries) {
12974
+ if (now - entry.updatedAt > contextTokenRetention.tokenTtlMs) {
12975
+ contextTokenStore.delete(key);
12976
+ }
12977
+ }
12978
+ const freshEntries = [...contextTokenStore.entries()].filter(([key]) => key.startsWith(prefix)).sort((a, b) => a[1].updatedAt - b[1].updatedAt);
12979
+ const excess = freshEntries.length - contextTokenRetention.maxTokensPerAccount;
12980
+ for (let i = 0;i < excess; i++) {
12981
+ const key = freshEntries[i]?.[0];
12982
+ if (key)
12983
+ contextTokenStore.delete(key);
12984
+ }
12985
+ }
12986
+ function normalizeContextTokenRetention(options = {}) {
12987
+ return {
12988
+ maxTokensPerAccount: normalizePositiveInt(options.maxTokensPerAccount, DEFAULT_CONTEXT_TOKEN_MAX_PER_ACCOUNT),
12989
+ tokenTtlMs: normalizeNonNegativeMs(options.tokenTtlMs, DEFAULT_CONTEXT_TOKEN_TTL_MS),
12990
+ now: options.now ?? (() => Date.now())
12991
+ };
12992
+ }
12993
+ function normalizePositiveInt(value, fallback) {
12994
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1)
12995
+ return fallback;
12996
+ return Math.floor(value);
12997
+ }
12998
+ function normalizeNonNegativeMs(value, fallback) {
12999
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
13000
+ return fallback;
13001
+ return value;
13002
+ }
12786
13003
  function normalizeWeixinUserIdFromChatKey(chatKey) {
12787
13004
  const parts = chatKey.split(":");
12788
13005
  if (parts[0] === "weixin" && parts[2]) {
@@ -12849,14 +13066,16 @@ function descriptorFromItem(item) {
12849
13066
  return { item, kind: "audio" };
12850
13067
  return;
12851
13068
  }
12852
- var contextTokenStore;
13069
+ var DEFAULT_CONTEXT_TOKEN_MAX_PER_ACCOUNT = 5000, DEFAULT_CONTEXT_TOKEN_TTL_MS, contextTokenStore, contextTokenRetention;
12853
13070
  var init_inbound = __esm(() => {
12854
13071
  init_logger();
12855
13072
  init_random();
12856
13073
  init_types2();
12857
13074
  init_state_dir();
12858
13075
  init_private_file();
13076
+ DEFAULT_CONTEXT_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000;
12859
13077
  contextTokenStore = new Map;
13078
+ contextTokenRetention = normalizeContextTokenRetention();
12860
13079
  });
12861
13080
 
12862
13081
  // src/weixin/api/config-cache.ts
@@ -12864,18 +13083,27 @@ class WeixinConfigManager {
12864
13083
  apiOpts;
12865
13084
  log;
12866
13085
  cache = new Map;
12867
- constructor(apiOpts, log) {
13086
+ maxEntries;
13087
+ entryTtlMs;
13088
+ now;
13089
+ fetchConfig;
13090
+ constructor(apiOpts, log, options = {}) {
12868
13091
  this.apiOpts = apiOpts;
12869
13092
  this.log = log;
13093
+ this.maxEntries = normalizePositiveInt2(options.maxEntries, CONFIG_CACHE_DEFAULT_MAX_ENTRIES);
13094
+ this.entryTtlMs = normalizeNonNegativeMs2(options.entryTtlMs, CONFIG_CACHE_ENTRY_TTL_MS);
13095
+ this.now = options.now ?? (() => Date.now());
13096
+ this.fetchConfig = options.getConfig ?? getConfig;
12870
13097
  }
12871
13098
  async getForUser(userId, contextToken) {
12872
- const now = Date.now();
13099
+ const now = this.now();
13100
+ this.prune(now);
12873
13101
  const entry = this.cache.get(userId);
12874
13102
  const shouldFetch = !entry || now >= entry.nextFetchAt;
12875
13103
  if (shouldFetch) {
12876
13104
  let fetchOk = false;
12877
13105
  try {
12878
- const resp = await getConfig({
13106
+ const resp = await this.fetchConfig({
12879
13107
  baseUrl: this.apiOpts.baseUrl,
12880
13108
  token: this.apiOpts.token,
12881
13109
  ilinkUserId: userId,
@@ -12886,7 +13114,8 @@ class WeixinConfigManager {
12886
13114
  config: { typingTicket: resp.typing_ticket ?? "" },
12887
13115
  everSucceeded: true,
12888
13116
  nextFetchAt: now + Math.random() * CONFIG_CACHE_TTL_MS,
12889
- retryDelayMs: CONFIG_CACHE_INITIAL_RETRY_MS
13117
+ retryDelayMs: CONFIG_CACHE_INITIAL_RETRY_MS,
13118
+ lastTouchedAt: now
12890
13119
  });
12891
13120
  this.log(`[weixin] config ${entry?.everSucceeded ? "refreshed" : "cached"} for ${userId}`);
12892
13121
  fetchOk = true;
@@ -12900,24 +13129,72 @@ class WeixinConfigManager {
12900
13129
  if (entry) {
12901
13130
  entry.nextFetchAt = now + nextDelay;
12902
13131
  entry.retryDelayMs = nextDelay;
13132
+ entry.lastTouchedAt = now;
12903
13133
  } else {
12904
13134
  this.cache.set(userId, {
12905
13135
  config: { typingTicket: "" },
12906
13136
  everSucceeded: false,
12907
13137
  nextFetchAt: now + CONFIG_CACHE_INITIAL_RETRY_MS,
12908
- retryDelayMs: CONFIG_CACHE_INITIAL_RETRY_MS
13138
+ retryDelayMs: CONFIG_CACHE_INITIAL_RETRY_MS,
13139
+ lastTouchedAt: now
12909
13140
  });
12910
13141
  }
12911
13142
  }
13143
+ } else {
13144
+ entry.lastTouchedAt = now;
12912
13145
  }
13146
+ this.enforceMaxEntries();
12913
13147
  return this.cache.get(userId)?.config ?? { typingTicket: "" };
12914
13148
  }
13149
+ cacheSizeForTests() {
13150
+ this.prune(this.now());
13151
+ return this.cache.size;
13152
+ }
13153
+ hasCachedUserForTests(userId) {
13154
+ this.prune(this.now());
13155
+ return this.cache.has(userId);
13156
+ }
13157
+ prune(now) {
13158
+ const cutoff = now - this.entryTtlMs;
13159
+ for (const [key, entry] of this.cache) {
13160
+ if (entry.lastTouchedAt < cutoff) {
13161
+ this.cache.delete(key);
13162
+ }
13163
+ }
13164
+ this.enforceMaxEntries();
13165
+ }
13166
+ enforceMaxEntries() {
13167
+ while (this.cache.size > this.maxEntries) {
13168
+ let oldestKey;
13169
+ let oldestTouchedAt = Number.POSITIVE_INFINITY;
13170
+ for (const [key, entry] of this.cache) {
13171
+ if (entry.lastTouchedAt < oldestTouchedAt) {
13172
+ oldestTouchedAt = entry.lastTouchedAt;
13173
+ oldestKey = key;
13174
+ }
13175
+ }
13176
+ if (oldestKey === undefined)
13177
+ return;
13178
+ this.cache.delete(oldestKey);
13179
+ }
13180
+ }
13181
+ }
13182
+ function normalizePositiveInt2(value, fallback) {
13183
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1)
13184
+ return fallback;
13185
+ return Math.floor(value);
12915
13186
  }
12916
- var CONFIG_CACHE_TTL_MS, CONFIG_CACHE_INITIAL_RETRY_MS = 2000, CONFIG_CACHE_MAX_RETRY_MS;
13187
+ function normalizeNonNegativeMs2(value, fallback) {
13188
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
13189
+ return fallback;
13190
+ return value;
13191
+ }
13192
+ var CONFIG_CACHE_TTL_MS, CONFIG_CACHE_INITIAL_RETRY_MS = 2000, CONFIG_CACHE_MAX_RETRY_MS, CONFIG_CACHE_DEFAULT_MAX_ENTRIES = 5000, CONFIG_CACHE_ENTRY_TTL_MS;
12917
13193
  var init_config_cache = __esm(() => {
12918
13194
  init_api();
12919
13195
  CONFIG_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
12920
13196
  CONFIG_CACHE_MAX_RETRY_MS = 60 * 60 * 1000;
13197
+ CONFIG_CACHE_ENTRY_TTL_MS = 7 * 24 * 60 * 60 * 1000;
12921
13198
  });
12922
13199
 
12923
13200
  // src/weixin/api/session-guard.ts
@@ -12936,24 +13213,24 @@ var init_session_guard = __esm(() => {
12936
13213
  pauseUntilMap = new Map;
12937
13214
  });
12938
13215
 
12939
- // src/weixin/messaging/conversation-executor.ts
13216
+ // src/runtime/conversation-executor.ts
12940
13217
  function createConversationExecutor() {
12941
13218
  const states = new Map;
12942
13219
  const getState = (conversationId) => {
12943
13220
  const existing = states.get(conversationId);
12944
13221
  if (existing)
12945
13222
  return existing;
12946
- const created = { activeControls: 0 };
13223
+ const created = { normalTails: new Map, activeControls: 0 };
12947
13224
  states.set(conversationId, created);
12948
13225
  return created;
12949
13226
  };
12950
13227
  const cleanupState = (conversationId, state) => {
12951
- if (!state.normalTail && state.activeControls === 0) {
13228
+ if (state.normalTails.size === 0 && state.activeControls === 0) {
12952
13229
  states.delete(conversationId);
12953
13230
  }
12954
13231
  };
12955
13232
  return {
12956
- run(conversationId, lane, task) {
13233
+ run(conversationId, lane, task, sessionKey) {
12957
13234
  const state = getState(conversationId);
12958
13235
  if (lane === "control") {
12959
13236
  state.activeControls += 1;
@@ -12962,23 +13239,23 @@ function createConversationExecutor() {
12962
13239
  cleanupState(conversationId, state);
12963
13240
  });
12964
13241
  }
12965
- const previous = state.normalTail ?? Promise.resolve();
12966
- const next = previous.catch(() => {
12967
- return;
12968
- }).then(task);
12969
- state.normalTail = next;
13242
+ const key = sessionKey ?? DEFAULT_SESSION_KEY;
13243
+ const previous = state.normalTails.get(key) ?? Promise.resolve();
13244
+ const next = previous.then(() => task(), () => task());
13245
+ state.normalTails.set(key, next);
12970
13246
  return next.finally(() => {
12971
- if (state.normalTail === next) {
12972
- state.normalTail = undefined;
13247
+ if (state.normalTails.get(key) === next) {
13248
+ state.normalTails.delete(key);
12973
13249
  }
12974
13250
  cleanupState(conversationId, state);
12975
13251
  });
12976
13252
  }
12977
13253
  };
12978
13254
  }
13255
+ var DEFAULT_SESSION_KEY = "__chat__";
12979
13256
 
12980
13257
  // src/channels/media-store.ts
12981
- import { access as access2, mkdir as mkdir7, readdir, rm as rm4, stat, writeFile as writeFile5 } from "node:fs/promises";
13258
+ import { access as access2, mkdir as mkdir7, readdir, rm as rm5, stat, writeFile as writeFile5 } from "node:fs/promises";
12982
13259
  import path7 from "node:path";
12983
13260
 
12984
13261
  class RuntimeMediaStore {
@@ -13024,16 +13301,24 @@ class RuntimeMediaStore {
13024
13301
  }
13025
13302
  function sanitizeMediaFileName(fileName, mimeType) {
13026
13303
  const base = path7.basename(fileName.trim() || "attachment");
13027
- const replaced = base.replace(/[\\/:*?"<>|\s]+/g, "-").replace(/^-+|-+$/g, "");
13028
- const safe = replaced || "attachment";
13304
+ const safe = sanitizeString(base, {
13305
+ deny: /[\\/:*?"<>|\s]+/g,
13306
+ replacement: "-",
13307
+ trim: true,
13308
+ fallback: "attachment"
13309
+ });
13029
13310
  const ext = path7.extname(safe);
13030
13311
  if (ext)
13031
13312
  return safe;
13032
13313
  return `${safe}${extensionFromMime(mimeType)}`;
13033
13314
  }
13034
13315
  function safePathSegment(value) {
13035
- const safe = value.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
13036
- return safe || "unknown";
13316
+ return sanitizeString(value, {
13317
+ allow: /[A-Za-z0-9._-]/,
13318
+ replacement: "_",
13319
+ trim: true,
13320
+ fallback: "unknown"
13321
+ });
13037
13322
  }
13038
13323
  async function uniqueFileName(dir, baseName) {
13039
13324
  const ext = path7.extname(baseName);
@@ -13089,7 +13374,7 @@ async function cleanupDir(dir, cutoffMs) {
13089
13374
  if (entry.isDirectory()) {
13090
13375
  const childEmpty = await cleanupDir(full, cutoffMs);
13091
13376
  if (childEmpty) {
13092
- await rm4(full, { recursive: true, force: true });
13377
+ await rm5(full, { recursive: true, force: true });
13093
13378
  } else {
13094
13379
  empty = false;
13095
13380
  }
@@ -13099,7 +13384,7 @@ async function cleanupDir(dir, cutoffMs) {
13099
13384
  if (!s)
13100
13385
  continue;
13101
13386
  if (s.mtimeMs < cutoffMs) {
13102
- await rm4(full, { force: true });
13387
+ await rm5(full, { force: true });
13103
13388
  } else {
13104
13389
  empty = false;
13105
13390
  }
@@ -13464,6 +13749,18 @@ var init_media_download = __esm(() => {
13464
13749
  WEIXIN_MEDIA_MAX_BYTES = 100 * 1024 * 1024;
13465
13750
  });
13466
13751
 
13752
+ // src/weixin/messaging/completion-notice.ts
13753
+ function buildBackgroundCompletionNotice(internalAlias, status) {
13754
+ const display = toDisplaySessionAlias(internalAlias);
13755
+ return status === "done" ? `✅ ${display} 已完成,/use ${display} 查看结果` : `⚠️ ${display} 失败,/use ${display} 查看详情`;
13756
+ }
13757
+ function shouldSendBackgroundNotice(reserve) {
13758
+ return reserve ? reserve() : true;
13759
+ }
13760
+ var init_completion_notice = __esm(() => {
13761
+ init_channel_scope();
13762
+ });
13763
+
13467
13764
  // src/weixin/messaging/execute-chat-turn.ts
13468
13765
  async function executeChatTurn(params) {
13469
13766
  let usedReply = false;
@@ -13491,6 +13788,16 @@ function buildFinalHeadsUp(input) {
13491
13788
  \uD83D\uDCC4 结果共 ${total} 段,已发 ${sentSoFar} 段。回复 /jx 续看后 ${remaining} 段。`;
13492
13789
  }
13493
13790
 
13791
+ // src/weixin/messaging/foreground-gate.ts
13792
+ function shouldDeliverSegment(isForeground) {
13793
+ return isForeground ? isForeground() : true;
13794
+ }
13795
+ function resolveFinalDisposition(isForeground, canStore) {
13796
+ if (isForeground)
13797
+ return "send";
13798
+ return canStore ? "store" : "drop";
13799
+ }
13800
+
13494
13801
  // src/weixin/messaging/markdown-filter.ts
13495
13802
  class StreamingMarkdownFilter {
13496
13803
  buf = "";
@@ -14387,8 +14694,8 @@ function normalizeMediaArray(media) {
14387
14694
  }
14388
14695
 
14389
14696
  // src/logging/rotating-file-writer.ts
14390
- import { readdir as readdir2, rename, rm as rm5, stat as stat2 } from "node:fs/promises";
14391
- import { basename, dirname as dirname6, join as join4 } from "node:path";
14697
+ import { readdir as readdir2, rename, rm as rm6, stat as stat2 } from "node:fs/promises";
14698
+ import { basename, dirname as dirname7, join as join7 } from "node:path";
14392
14699
  async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
14393
14700
  let currentSize = 0;
14394
14701
  try {
@@ -14405,10 +14712,10 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
14405
14712
  return;
14406
14713
  }
14407
14714
  if (maxFiles <= 0) {
14408
- await rm5(filePath, { force: true });
14715
+ await rm6(filePath, { force: true });
14409
14716
  return;
14410
14717
  }
14411
- await rm5(`${filePath}.${maxFiles}`, { force: true });
14718
+ await rm6(`${filePath}.${maxFiles}`, { force: true });
14412
14719
  for (let index = maxFiles - 1;index >= 1; index -= 1) {
14413
14720
  const source = `${filePath}.${index}`;
14414
14721
  try {
@@ -14422,7 +14729,7 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
14422
14729
  await rename(filePath, `${filePath}.1`);
14423
14730
  }
14424
14731
  async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
14425
- const parentDir = dirname6(filePath);
14732
+ const parentDir = dirname7(filePath);
14426
14733
  const prefix = `${basename(filePath)}.`;
14427
14734
  const cutoff = now().getTime() - retentionDays * 24 * 60 * 60 * 1000;
14428
14735
  let files = [];
@@ -14438,10 +14745,10 @@ async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
14438
14745
  if (!file.startsWith(prefix) || !/^\d+$/.test(file.slice(prefix.length))) {
14439
14746
  continue;
14440
14747
  }
14441
- const candidate = join4(parentDir, file);
14748
+ const candidate = join7(parentDir, file);
14442
14749
  const details = await stat2(candidate);
14443
14750
  if (details.mtime.getTime() < cutoff) {
14444
- await rm5(candidate, { force: true });
14751
+ await rm6(candidate, { force: true });
14445
14752
  }
14446
14753
  }
14447
14754
  }
@@ -14452,7 +14759,7 @@ var init_rotating_file_writer = () => {};
14452
14759
 
14453
14760
  // src/perf/perf-log-writer.ts
14454
14761
  import { appendFile as fsAppendFile, mkdir as fsMkdir } from "node:fs/promises";
14455
- import { dirname as dirname7 } from "node:path";
14762
+ import { dirname as dirname8 } from "node:path";
14456
14763
  function createPerfLogWriter(options) {
14457
14764
  const append = options.appendImpl ?? ((p, d) => fsAppendFile(p, d, "utf8"));
14458
14765
  const mkdir8 = options.mkdirImpl ?? ((p, o) => fsMkdir(p, o).then(() => {
@@ -14502,7 +14809,7 @@ function createPerfLogWriter(options) {
14502
14809
  return;
14503
14810
  const data = batch.join("");
14504
14811
  try {
14505
- await mkdir8(dirname7(options.filePath), { recursive: true });
14812
+ await mkdir8(dirname8(options.filePath), { recursive: true });
14506
14813
  await rotateIfNeeded(options.filePath, Buffer.byteLength(data), options.maxSizeBytes, options.maxFiles);
14507
14814
  await append(options.filePath, data);
14508
14815
  consecutiveFailures = 0;
@@ -14812,7 +15119,9 @@ function isSlashCommandText(textBody) {
14812
15119
  }
14813
15120
  function getWeixinMessageTurnLane(full) {
14814
15121
  const textBody = extractTextBody(full.item_list).trim().toLowerCase();
14815
- return textBody === "/cancel" || textBody === "/stop" || textBody === "/jx" ? "control" : "normal";
15122
+ const command = textBody.split(/\s+/)[0] ?? "";
15123
+ const isSwitch = command === "/use" || command === "/ss";
15124
+ return command === "/cancel" || command === "/stop" || command === "/jx" || isSwitch ? "control" : "normal";
14816
15125
  }
14817
15126
  function buildWeixinChatKey(accountId, userId) {
14818
15127
  return `weixin:${accountId}:${userId}`;
@@ -14962,6 +15271,9 @@ async function handleWeixinMessageTurn(full, deps) {
14962
15271
  }
14963
15272
  let midFirstSent = false;
14964
15273
  const sendReplySegment = async (text) => {
15274
+ if (!shouldDeliverSegment(deps.isForeground)) {
15275
+ return false;
15276
+ }
14965
15277
  const plainText = markdownToPlainText(text).trim();
14966
15278
  if (plainText.length === 0) {
14967
15279
  return false;
@@ -14993,7 +15305,8 @@ async function handleWeixinMessageTurn(full, deps) {
14993
15305
  channel: "weixin",
14994
15306
  chatType: full.group_id ? "group" : "direct",
14995
15307
  ...full.from_user_id ? { senderId: full.from_user_id } : {},
14996
- ...full.group_id ? { groupId: full.group_id } : {}
15308
+ ...full.group_id ? { groupId: full.group_id } : {},
15309
+ ...deps.boundSessionAlias ? { boundSessionAlias: deps.boundSessionAlias } : {}
14997
15310
  },
14998
15311
  perfSpan
14999
15312
  };
@@ -15011,80 +15324,96 @@ async function handleWeixinMessageTurn(full, deps) {
15011
15324
  if (turn.text) {
15012
15325
  const finalText = markdownToPlainText(turn.text).trim();
15013
15326
  if (finalText.length > 0) {
15014
- const rawChunks = chunkFinalText(finalText, MAX_FINAL_CHUNK_BYTES);
15015
- if (rawChunks.length > 0) {
15016
- const total = rawChunks.length;
15017
- if (total === 1) {
15018
- const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
15019
- if (!reserved) {
15020
- finalDropped = true;
15021
- deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text chatKey=${to}`);
15022
- } else {
15023
- await sendMessageWeixin({
15024
- to,
15025
- text: rawChunks[0],
15026
- opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
15027
- });
15028
- finalChunksSent += 1;
15029
- if (!finalFirstSent) {
15030
- finalFirstSent = true;
15031
- perfSpan.mark("reply.final_first_sent", { bytes: utf8ByteLength(rawChunks[0]), chunkIndex: 1 });
15032
- }
15033
- }
15327
+ const disposition = resolveFinalDisposition(shouldDeliverSegment(deps.isForeground), Boolean(deps.boundSessionAlias && deps.onBackgroundFinal));
15328
+ if (disposition === "store") {
15329
+ await deps.onBackgroundFinal(deps.boundSessionAlias, finalText, "done");
15330
+ if (shouldSendBackgroundNotice(deps.reserveFinal ? () => deps.reserveFinal(to) : undefined)) {
15331
+ await sendMessageWeixin({
15332
+ to,
15333
+ text: buildBackgroundCompletionNotice(deps.boundSessionAlias, "done"),
15334
+ opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
15335
+ }).catch((e) => deps.errLog(`bg completion notice failed: ${String(e)}`));
15034
15336
  } else {
15035
- const prefixed = rawChunks.map((body, i) => `(${i + 1}/${total}) ${body}`);
15036
- const available = deps.finalRemaining ? deps.finalRemaining(to) : total;
15037
- const waveSize = Math.max(Math.min(available, total), 0);
15038
- const wave = prefixed.slice(0, waveSize);
15039
- const rest = prefixed.slice(waveSize);
15040
- if (wave.length > 0 && rest.length > 0) {
15041
- const sentSoFar = wave.length;
15042
- wave[wave.length - 1] = `${wave[wave.length - 1]}
15043
-
15044
- ${buildFinalHeadsUp({
15045
- total,
15046
- sentSoFar
15047
- })}`;
15048
- }
15049
- let sent = 0;
15050
- for (let i = 0;i < wave.length; i += 1) {
15337
+ deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=bg_notice chatKey=${to}`);
15338
+ }
15339
+ } else if (disposition === "drop") {
15340
+ deps.errLog(`weixin.final.dropped reason=backgrounded_no_store kind=text chatKey=${to}`);
15341
+ } else {
15342
+ const rawChunks = chunkFinalText(finalText, MAX_FINAL_CHUNK_BYTES);
15343
+ if (rawChunks.length > 0) {
15344
+ const total = rawChunks.length;
15345
+ if (total === 1) {
15051
15346
  const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
15052
15347
  if (!reserved) {
15053
15348
  finalDropped = true;
15054
- deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text_paginated chatKey=${to} chunk=${i + 1}/${total}`);
15055
- break;
15056
- }
15057
- try {
15349
+ deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text chatKey=${to}`);
15350
+ } else {
15058
15351
  await sendMessageWeixin({
15059
15352
  to,
15060
- text: wave[i],
15353
+ text: rawChunks[0],
15061
15354
  opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
15062
15355
  });
15063
- sent += 1;
15064
15356
  finalChunksSent += 1;
15065
15357
  if (!finalFirstSent) {
15066
15358
  finalFirstSent = true;
15067
- perfSpan.mark("reply.final_first_sent", { bytes: utf8ByteLength(wave[i]), chunkIndex: i + 1 });
15359
+ perfSpan.mark("reply.final_first_sent", { bytes: utf8ByteLength(rawChunks[0]), chunkIndex: 1 });
15068
15360
  }
15069
- } catch (sendErr) {
15070
- finalDropped = true;
15071
- deps.errLog(`weixin.final.dropped reason=send_failed kind=text_paginated chatKey=${to} chunk=${i + 1}/${total} err=${String(sendErr)}`);
15072
- break;
15073
15361
  }
15074
- }
15075
- const restToPark = prefixed.slice(sent);
15076
- finalChunksPending = restToPark.length;
15077
- if (restToPark.length > 0 && deps.enqueuePendingFinal) {
15078
- const pending = restToPark.map((text, idx) => {
15079
- const seq = sent + idx + 1;
15080
- const entry = { text, seq, total };
15081
- if (contextToken !== undefined)
15082
- entry.contextToken = contextToken;
15083
- if (deps.accountId !== undefined)
15084
- entry.accountId = deps.accountId;
15085
- return entry;
15086
- });
15087
- deps.enqueuePendingFinal(to, pending);
15362
+ } else {
15363
+ const prefixed = rawChunks.map((body, i) => `(${i + 1}/${total}) ${body}`);
15364
+ const available = deps.finalRemaining ? deps.finalRemaining(to) : total;
15365
+ const waveSize = Math.max(Math.min(available, total), 0);
15366
+ const wave = prefixed.slice(0, waveSize);
15367
+ const rest = prefixed.slice(waveSize);
15368
+ if (wave.length > 0 && rest.length > 0) {
15369
+ const sentSoFar = wave.length;
15370
+ wave[wave.length - 1] = `${wave[wave.length - 1]}
15371
+
15372
+ ${buildFinalHeadsUp({
15373
+ total,
15374
+ sentSoFar
15375
+ })}`;
15376
+ }
15377
+ let sent = 0;
15378
+ for (let i = 0;i < wave.length; i += 1) {
15379
+ const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
15380
+ if (!reserved) {
15381
+ finalDropped = true;
15382
+ deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text_paginated chatKey=${to} chunk=${i + 1}/${total}`);
15383
+ break;
15384
+ }
15385
+ try {
15386
+ await sendMessageWeixin({
15387
+ to,
15388
+ text: wave[i],
15389
+ opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
15390
+ });
15391
+ sent += 1;
15392
+ finalChunksSent += 1;
15393
+ if (!finalFirstSent) {
15394
+ finalFirstSent = true;
15395
+ perfSpan.mark("reply.final_first_sent", { bytes: utf8ByteLength(wave[i]), chunkIndex: i + 1 });
15396
+ }
15397
+ } catch (sendErr) {
15398
+ finalDropped = true;
15399
+ deps.errLog(`weixin.final.dropped reason=send_failed kind=text_paginated chatKey=${to} chunk=${i + 1}/${total} err=${String(sendErr)}`);
15400
+ break;
15401
+ }
15402
+ }
15403
+ const restToPark = prefixed.slice(sent);
15404
+ finalChunksPending = restToPark.length;
15405
+ if (restToPark.length > 0 && deps.enqueuePendingFinal) {
15406
+ const pending = restToPark.map((text, idx) => {
15407
+ const seq = sent + idx + 1;
15408
+ const entry = { text, seq, total };
15409
+ if (contextToken !== undefined)
15410
+ entry.contextToken = contextToken;
15411
+ if (deps.accountId !== undefined)
15412
+ entry.accountId = deps.accountId;
15413
+ return entry;
15414
+ });
15415
+ deps.enqueuePendingFinal(to, pending);
15416
+ }
15088
15417
  }
15089
15418
  }
15090
15419
  }
@@ -15155,18 +15484,35 @@ ${buildFinalHeadsUp({
15155
15484
  perfSpan.setOutcome("error", { reason: "turn_error" });
15156
15485
  const errorText = err instanceof Error ? err.stack ?? err.message : JSON.stringify(err);
15157
15486
  deps.errLog(`handleWeixinMessageTurn: agent or send failed: ${errorText}`);
15158
- const reservedErr = deps.reserveFinal ? deps.reserveFinal(to) : true;
15159
- if (!reservedErr) {
15160
- deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=error_notice chatKey=${to}`);
15487
+ const errMessage = `⚠️ 执行出错:${err instanceof Error ? err.message : JSON.stringify(err)}`;
15488
+ const errDisposition = resolveFinalDisposition(shouldDeliverSegment(deps.isForeground), Boolean(deps.boundSessionAlias && deps.onBackgroundFinal));
15489
+ if (errDisposition === "store") {
15490
+ await deps.onBackgroundFinal(deps.boundSessionAlias, errMessage, "error");
15491
+ if (shouldSendBackgroundNotice(deps.reserveFinal ? () => deps.reserveFinal(to) : undefined)) {
15492
+ await sendMessageWeixin({
15493
+ to,
15494
+ text: buildBackgroundCompletionNotice(deps.boundSessionAlias, "error"),
15495
+ opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
15496
+ }).catch((e) => deps.errLog(`bg completion notice failed: ${String(e)}`));
15497
+ } else {
15498
+ deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=bg_notice chatKey=${to}`);
15499
+ }
15500
+ } else if (errDisposition === "drop") {
15501
+ deps.errLog(`weixin.final.dropped reason=backgrounded_no_store kind=error_notice chatKey=${to}`);
15161
15502
  } else {
15162
- sendWeixinErrorNotice({
15163
- to,
15164
- contextToken,
15165
- message: `⚠️ 执行出错:${err instanceof Error ? err.message : JSON.stringify(err)}`,
15166
- baseUrl: deps.baseUrl,
15167
- token: deps.token,
15168
- errLog: deps.errLog
15169
- });
15503
+ const reservedErr = deps.reserveFinal ? deps.reserveFinal(to) : true;
15504
+ if (!reservedErr) {
15505
+ deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=error_notice chatKey=${to}`);
15506
+ } else {
15507
+ sendWeixinErrorNotice({
15508
+ to,
15509
+ contextToken,
15510
+ message: errMessage,
15511
+ baseUrl: deps.baseUrl,
15512
+ token: deps.token,
15513
+ errLog: deps.errLog
15514
+ });
15515
+ }
15170
15516
  }
15171
15517
  } finally {
15172
15518
  stopTypingIndicator();
@@ -15184,6 +15530,7 @@ var init_handle_weixin_message_turn = __esm(() => {
15184
15530
  init_outbound_media_safety();
15185
15531
  init_media_download();
15186
15532
  init_mime();
15533
+ init_completion_notice();
15187
15534
  init_inbound();
15188
15535
  init_error_notice();
15189
15536
  init_send_media();
@@ -15385,7 +15732,15 @@ async function monitorWeixinProvider(opts) {
15385
15732
  }
15386
15733
  }
15387
15734
  }
15388
- conversationExecutor.run(full.from_user_id ?? "", getWeixinMessageTurnLane(full), () => handleWeixinMessageTurn(full, {
15735
+ const chatKey = buildWeixinChatKey(accountId, fromUserId);
15736
+ const isSlash = inboundText.trim().startsWith("/");
15737
+ const boundAlias = isSlash ? undefined : opts.peekCurrentSessionAlias?.(chatKey);
15738
+ const sessionKey = boundAlias ?? "__chat__";
15739
+ const isForeground = boundAlias ? () => opts.peekCurrentSessionAlias?.(chatKey) === boundAlias : undefined;
15740
+ if (boundAlias) {
15741
+ opts.activeTurns?.markActive(chatKey, boundAlias);
15742
+ }
15743
+ const runPromise = conversationExecutor.run(full.from_user_id ?? "", getWeixinMessageTurnLane(full), () => handleWeixinMessageTurn(full, {
15389
15744
  accountId,
15390
15745
  agent,
15391
15746
  baseUrl,
@@ -15403,9 +15758,25 @@ async function monitorWeixinProvider(opts) {
15403
15758
  ...opts.prependPendingFinal ? { prependPendingFinal: opts.prependPendingFinal } : {},
15404
15759
  ...opts.mediaStore ? { mediaStore: opts.mediaStore } : {},
15405
15760
  ...opts.allowedMediaRoots ? { allowedMediaRoots: opts.allowedMediaRoots } : {},
15406
- ...opts.perfTracer ? { perfTracer: opts.perfTracer } : {}
15407
- })).catch((err) => {
15761
+ ...opts.perfTracer ? { perfTracer: opts.perfTracer } : {},
15762
+ ...boundAlias ? { boundSessionAlias: boundAlias } : {},
15763
+ ...isForeground ? { isForeground } : {},
15764
+ ...opts.setBackgroundResult ? {
15765
+ onBackgroundFinal: async (alias, text, status) => {
15766
+ await opts.setBackgroundResult(chatKey, alias, {
15767
+ text,
15768
+ status,
15769
+ finished_at: new Date().toISOString()
15770
+ });
15771
+ }
15772
+ } : {}
15773
+ }), sessionKey);
15774
+ runPromise.catch((err) => {
15408
15775
  errLog(`[weixin] message turn failed: ${String(err)}`);
15776
+ }).finally(() => {
15777
+ if (boundAlias) {
15778
+ opts.activeTurns?.markInactive(chatKey, boundAlias);
15779
+ }
15409
15780
  });
15410
15781
  }
15411
15782
  } catch (err) {
@@ -15589,7 +15960,10 @@ async function start(agent, opts) {
15589
15960
  ...opts?.enqueuePendingFinal ? { enqueuePendingFinal: opts.enqueuePendingFinal } : {},
15590
15961
  ...opts?.dropPendingFinal ? { dropPendingFinal: opts.dropPendingFinal } : {},
15591
15962
  ...opts?.mediaStore ? { mediaStore: opts.mediaStore } : {},
15592
- ...opts?.perfTracer ? { perfTracer: opts.perfTracer } : {}
15963
+ ...opts?.perfTracer ? { perfTracer: opts.perfTracer } : {},
15964
+ ...opts?.peekCurrentSessionAlias ? { peekCurrentSessionAlias: opts.peekCurrentSessionAlias } : {},
15965
+ ...opts?.setBackgroundResult ? { setBackgroundResult: opts.setBackgroundResult } : {},
15966
+ ...opts?.activeTurns ? { activeTurns: opts.activeTurns } : {}
15593
15967
  });
15594
15968
  }
15595
15969
  var init_bot = __esm(() => {
@@ -16021,16 +16395,16 @@ var init_scheduled_turn = __esm(() => {
16021
16395
  });
16022
16396
 
16023
16397
  // src/weixin/monitor/consumer-lock.ts
16024
- import { mkdir as mkdir8, open as open3, readFile as readFile6, rm as rm6 } from "node:fs/promises";
16025
- import { dirname as dirname8, join as join5 } from "node:path";
16398
+ import { mkdir as mkdir8, open as open3, readFile as readFile7, rm as rm7 } from "node:fs/promises";
16399
+ import { dirname as dirname9, join as join8 } from "node:path";
16026
16400
  import { homedir as homedir4 } from "node:os";
16027
16401
  function createWeixinConsumerLock(options = {}) {
16028
- const lockFilePath = options.lockFilePath ?? join5(homedir4(), ".weacpx", "runtime", "weixin-consumer.lock.json");
16402
+ const lockFilePath = options.lockFilePath ?? join8(coreHomeDir(homedir4()), "runtime", "weixin-consumer.lock.json");
16029
16403
  const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning4;
16030
16404
  const onDiagnostic = options.onDiagnostic;
16031
16405
  return {
16032
16406
  async acquire(meta2) {
16033
- await mkdir8(dirname8(lockFilePath), { recursive: true });
16407
+ await mkdir8(dirname9(lockFilePath), { recursive: true });
16034
16408
  while (true) {
16035
16409
  try {
16036
16410
  const handle = await open3(lockFilePath, "wx");
@@ -16061,7 +16435,7 @@ function createWeixinConsumerLock(options = {}) {
16061
16435
  });
16062
16436
  const existing = await loadLockMetadata(lockFilePath);
16063
16437
  if (!existing) {
16064
- await rm6(lockFilePath, { force: true });
16438
+ await rm7(lockFilePath, { force: true });
16065
16439
  await onDiagnostic?.("lock_invalid_removed", {
16066
16440
  lockFilePath,
16067
16441
  reason: "invalid_or_unreadable_metadata"
@@ -16069,7 +16443,7 @@ function createWeixinConsumerLock(options = {}) {
16069
16443
  continue;
16070
16444
  }
16071
16445
  if (!isProcessRunning(existing.pid)) {
16072
- await rm6(lockFilePath, { force: true });
16446
+ await rm7(lockFilePath, { force: true });
16073
16447
  await onDiagnostic?.("lock_stale_removed", {
16074
16448
  lockFilePath,
16075
16449
  stalePid: existing.pid,
@@ -16094,7 +16468,7 @@ function createWeixinConsumerLock(options = {}) {
16094
16468
  }
16095
16469
  },
16096
16470
  async release() {
16097
- await rm6(lockFilePath, { force: true });
16471
+ await rm7(lockFilePath, { force: true });
16098
16472
  await onDiagnostic?.("lock_released", {
16099
16473
  lockFilePath
16100
16474
  });
@@ -16103,7 +16477,7 @@ function createWeixinConsumerLock(options = {}) {
16103
16477
  }
16104
16478
  async function loadLockMetadata(path14) {
16105
16479
  try {
16106
- const raw = await readFile6(path14, "utf8");
16480
+ const raw = await readFile7(path14, "utf8");
16107
16481
  const parsed = JSON.parse(raw);
16108
16482
  if (!parsed || typeof parsed.pid !== "number" || !parsed.mode || !parsed.configPath || !parsed.statePath) {
16109
16483
  return null;
@@ -16123,6 +16497,7 @@ function defaultIsProcessRunning4(pid) {
16123
16497
  }
16124
16498
  var ActiveWeixinConsumerLockError;
16125
16499
  var init_consumer_lock = __esm(() => {
16500
+ init_core_home();
16126
16501
  ActiveWeixinConsumerLockError = class ActiveWeixinConsumerLockError extends Error {
16127
16502
  existing;
16128
16503
  lockFilePath;
@@ -16185,6 +16560,7 @@ class WeixinChannel {
16185
16560
  console.log("[weacpx] 未检测到登录凭证,正在启动扫码登录...");
16186
16561
  await this.login();
16187
16562
  }
16563
+ const sessions = input.sessions;
16188
16564
  await start(input.agent, {
16189
16565
  abortSignal: input.abortSignal,
16190
16566
  ...this.mediaStore ? { mediaStore: this.mediaStore } : {},
@@ -16197,7 +16573,12 @@ class WeixinChannel {
16197
16573
  prependPendingFinal: (chatKey, chunks) => input.quota.prependPendingFinal(chatKey, chunks),
16198
16574
  enqueuePendingFinal: (chatKey, chunks) => input.quota.enqueuePendingFinal(chatKey, chunks),
16199
16575
  dropPendingFinal: (chatKey) => input.quota.clearPendingFinal(chatKey),
16200
- ...input.perfTracer ? { perfTracer: input.perfTracer } : {}
16576
+ ...input.perfTracer ? { perfTracer: input.perfTracer } : {},
16577
+ ...sessions ? {
16578
+ peekCurrentSessionAlias: (chatKey) => sessions.peekCurrentSessionAlias(chatKey),
16579
+ setBackgroundResult: (chatKey, alias, result) => sessions.setBackgroundResult(chatKey, alias, result)
16580
+ } : {},
16581
+ ...input.activeTurns ? { activeTurns: input.activeTurns } : {}
16201
16582
  });
16202
16583
  }
16203
16584
  async notifyTaskCompletion(task) {
@@ -16652,9 +17033,9 @@ __export(exports_plugin_loader, {
16652
17033
  });
16653
17034
  import { createRequire as createRequire2 } from "node:module";
16654
17035
  import { pathToFileURL } from "node:url";
16655
- import { join as join6 } from "node:path";
17036
+ import { join as join9 } from "node:path";
16656
17037
  async function importPluginFromHome(packageName, pluginHome) {
16657
- const requireFromHome = createRequire2(join6(pluginHome, "package.json"));
17038
+ const requireFromHome = createRequire2(join9(pluginHome, "package.json"));
16658
17039
  const entry = requireFromHome.resolve(packageName);
16659
17040
  return await import(pathToFileURL(entry).href);
16660
17041
  }
@@ -16709,7 +17090,7 @@ var init_bootstrap = __esm(() => {
16709
17090
 
16710
17091
  // src/logging/app-logger.ts
16711
17092
  import { appendFile, chmod as chmod2, mkdir as mkdir9 } from "node:fs/promises";
16712
- import { dirname as dirname10 } from "node:path";
17093
+ import { dirname as dirname11 } from "node:path";
16713
17094
  function createNoopAppLogger() {
16714
17095
  return {
16715
17096
  debug: async () => {},
@@ -16750,7 +17131,7 @@ function createAppLogger(options) {
16750
17131
  return;
16751
17132
  }
16752
17133
  const line = formatLogLine(now(), level, event, message, context);
16753
- await mkdir9(dirname10(options.filePath), { recursive: true });
17134
+ await mkdir9(dirname11(options.filePath), { recursive: true });
16754
17135
  if (!modeEnsured) {
16755
17136
  modeEnsured = true;
16756
17137
  await chmod2(options.filePath, 384).catch(() => {});
@@ -16785,7 +17166,7 @@ var init_app_logger = __esm(() => {
16785
17166
  });
16786
17167
 
16787
17168
  // src/transport/acpx-session-index.ts
16788
- import { readFile as readFile10 } from "node:fs/promises";
17169
+ import { readFile as readFile11 } from "node:fs/promises";
16789
17170
  import { homedir as homedir5 } from "node:os";
16790
17171
  import { resolve as resolve2 } from "node:path";
16791
17172
  async function resolveSessionAgentCommandFromIndex(session) {
@@ -16794,7 +17175,7 @@ async function resolveSessionAgentCommandFromIndex(session) {
16794
17175
  return;
16795
17176
  }
16796
17177
  try {
16797
- const raw = await readFile10(resolve2(home, ".acpx", "sessions", "index.json"), "utf8");
17178
+ const raw = await readFile11(resolve2(home, ".acpx", "sessions", "index.json"), "utf8");
16798
17179
  const parsed = JSON.parse(raw);
16799
17180
  const targetCwd = resolve2(session.cwd);
16800
17181
  const match = parsed.entries?.find((entry) => entry.name === session.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
@@ -17004,8 +17385,10 @@ function parseCommand(input) {
17004
17385
  }
17005
17386
  if (command === "/status")
17006
17387
  return { kind: "status" };
17007
- if (command === "/cancel")
17008
- return { kind: "cancel" };
17388
+ if (command === "/cancel") {
17389
+ const alias = parts[1];
17390
+ return alias ? { kind: "cancel", alias } : { kind: "cancel" };
17391
+ }
17009
17392
  if (command === "/clear")
17010
17393
  return { kind: "session.reset" };
17011
17394
  if (command === "/mode" && parts.length === 1)
@@ -17139,6 +17522,9 @@ function parseCommand(input) {
17139
17522
  if (command === "/config" && parts[1] === "set" && parts.length === 4) {
17140
17523
  return { kind: "config.set", path: parts[2] ?? "", value: parts[3] ?? "" };
17141
17524
  }
17525
+ if (command === "/use" && parts[1] === "-") {
17526
+ return { kind: "session.use.previous" };
17527
+ }
17142
17528
  if (command === "/use" && parts[1]) {
17143
17529
  return { kind: "session.use", alias: parts[1] };
17144
17530
  }
@@ -18295,7 +18681,7 @@ async function buildCoordinatorPrompt(input) {
18295
18681
 
18296
18682
  `) : input.userText ?? "";
18297
18683
  if (input.maxPromptLength && promptText.length > input.maxPromptLength) {
18298
- promptText = promptText.slice(0, input.maxPromptLength - 3) + "...";
18684
+ promptText = truncateText(promptText, input.maxPromptLength, "...");
18299
18685
  }
18300
18686
  return {
18301
18687
  promptText,
@@ -18306,6 +18692,11 @@ async function buildCoordinatorPrompt(input) {
18306
18692
  }
18307
18693
  var init_build_coordinator_prompt = () => {};
18308
18694
 
18695
+ // src/commands/handlers/session-list-marker.ts
18696
+ function decorateUnread(label, hasUnread) {
18697
+ return hasUnread ? `● ${label}` : label;
18698
+ }
18699
+
18309
18700
  // src/commands/handlers/session-handler.ts
18310
18701
  async function handleSessions(context, chatKey) {
18311
18702
  const sessions = await context.sessions.listSessions(chatKey);
@@ -18328,10 +18719,11 @@ async function handleSessions(context, chatKey) {
18328
18719
  return { text: lines.join(`
18329
18720
  `) };
18330
18721
  }
18722
+ const unread = new Set(context.sessions.listBackgroundResultAliases(chatKey));
18331
18723
  return {
18332
18724
  text: [
18333
18725
  "会话列表:",
18334
- ...sessions.map((session) => `- ${session.alias} (${session.agent} @ ${session.workspace})${session.isCurrent ? " [当前]" : ""}`)
18726
+ ...sessions.map((session) => `- ${decorateUnread(session.alias, unread.has(session.internalAlias))} (${session.agent} @ ${session.workspace})${session.isCurrent ? " [当前]" : ""}`)
18335
18727
  ].join(`
18336
18728
  `)
18337
18729
  };
@@ -18408,13 +18800,55 @@ async function refreshSessionTransportAgentCommandBestEffort(context, alias, eve
18408
18800
  });
18409
18801
  }
18410
18802
  }
18411
- async function handleSessionUse(context, chatKey, alias) {
18412
- await context.sessions.useSession(chatKey, alias);
18803
+ function renderSwitched(switched) {
18804
+ const base = `已切到 ${switched.alias} · ${switched.agent} · ${switched.workspace}`;
18805
+ return switched.previousAlias ? `${base}(上一个:${switched.previousAlias})` : base;
18806
+ }
18807
+ async function appendSwitchBackContext(context, chatKey, internalAlias, baseText) {
18808
+ const result = await context.sessions.takeBackgroundResult(chatKey, internalAlias);
18809
+ if (result) {
18810
+ return `${baseText}
18811
+
18812
+ ${result.text}`;
18813
+ }
18814
+ if (context.activeTurns?.isActive(chatKey, internalAlias)) {
18815
+ return `${baseText}
18816
+
18817
+ ⏳ ${toDisplaySessionAlias(internalAlias)} 仍在执行中…`;
18818
+ }
18819
+ return baseText;
18820
+ }
18821
+ async function handleSessionUse(context, chatKey, input) {
18822
+ const result = context.sessions.resolveFuzzyAlias(chatKey, input);
18823
+ if (result.kind === "none") {
18824
+ return { text: `没有匹配「${input}」的会话。发 /sessions 看看有哪些。` };
18825
+ }
18826
+ if (result.kind === "ambiguous") {
18827
+ const lines = result.candidates.map((candidate) => `• ${candidate.alias} · ${candidate.agent} · ${candidate.workspace}`);
18828
+ return { text: [`「${input}」匹配到多个会话,请指定:`, ...lines].join(`
18829
+ `) };
18830
+ }
18831
+ const switched = await context.sessions.useSession(chatKey, result.alias);
18413
18832
  await context.logger.info("session.selected", "selected logical session", {
18414
- alias,
18833
+ alias: switched.alias,
18834
+ chatKey
18835
+ });
18836
+ const internalAlias = context.sessions.peekCurrentSessionAlias(chatKey) ?? result.alias;
18837
+ const text = await appendSwitchBackContext(context, chatKey, internalAlias, renderSwitched(switched));
18838
+ return { text };
18839
+ }
18840
+ async function handleSessionUsePrevious(context, chatKey) {
18841
+ const switched = await context.sessions.usePreviousSession(chatKey);
18842
+ if (!switched) {
18843
+ return { text: "还没有上一个会话,发 /sessions 看看有哪些。" };
18844
+ }
18845
+ await context.logger.info("session.selected", "selected previous logical session", {
18846
+ alias: switched.alias,
18415
18847
  chatKey
18416
18848
  });
18417
- return { text: `已切换到会话「${alias}」` };
18849
+ const internalAlias = context.sessions.peekCurrentSessionAlias(chatKey) ?? switched.alias;
18850
+ const text = await appendSwitchBackContext(context, chatKey, internalAlias, renderSwitched(switched));
18851
+ return { text };
18418
18852
  }
18419
18853
  async function handleModeShow(context, chatKey) {
18420
18854
  const session = await context.sessions.getCurrentSession(chatKey);
@@ -18490,7 +18924,34 @@ async function handleStatus(context, chatKey) {
18490
18924
  `)
18491
18925
  };
18492
18926
  }
18493
- async function handleCancel(context, chatKey) {
18927
+ async function handleCancel(context, chatKey, alias) {
18928
+ if (alias !== undefined) {
18929
+ const result = context.sessions.resolveFuzzyAlias(chatKey, alias);
18930
+ if (result.kind === "none") {
18931
+ return { text: `没有匹配「${alias}」的会话。发 /sessions 看看有哪些。` };
18932
+ }
18933
+ if (result.kind === "ambiguous") {
18934
+ const lines = result.candidates.map((candidate) => `• ${candidate.alias} · ${candidate.agent} · ${candidate.workspace}`);
18935
+ return { text: [`「${alias}」匹配到多个会话,请指定:`, ...lines].join(`
18936
+ `) };
18937
+ }
18938
+ const internalAlias = await context.sessions.resolveAliasForChat(chatKey, result.alias);
18939
+ const target = await context.sessions.getSession(internalAlias);
18940
+ if (!target) {
18941
+ return { text: `没有匹配「${alias}」的会话。发 /sessions 看看有哪些。` };
18942
+ }
18943
+ try {
18944
+ const cancelResult = await context.interaction.cancelTransportSession(target);
18945
+ return { text: cancelResult.message || "cancelled" };
18946
+ } catch (error2) {
18947
+ const recovered = await context.recovery.tryRecoverMissingSession(target, error2);
18948
+ if (recovered) {
18949
+ const cancelResult = await context.interaction.cancelTransportSession(recovered);
18950
+ return { text: cancelResult.message || "cancelled" };
18951
+ }
18952
+ return context.recovery.renderTransportError(target, error2);
18953
+ }
18954
+ }
18494
18955
  const session = await context.sessions.getCurrentSession(chatKey);
18495
18956
  if (!session) {
18496
18957
  return { text: NO_CURRENT_SESSION_TEXT };
@@ -18655,7 +19116,7 @@ async function handlePromptWithSession(context, session, chatKey, text, reply, r
18655
19116
  }
18656
19117
  }
18657
19118
  async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
18658
- const session = await context.sessions.getCurrentSession(chatKey);
19119
+ const session = metadata?.boundSessionAlias ? context.sessions.getResolvedSessionByInternalAlias(metadata.boundSessionAlias) : await context.sessions.getCurrentSession(chatKey);
18659
19120
  if (!session) {
18660
19121
  return { text: NO_CURRENT_SESSION_TEXT };
18661
19122
  }
@@ -18756,6 +19217,8 @@ var init_session_handler = __esm(() => {
18756
19217
  { usage: "/session tail [N]", description: "补拉当前会话的历史输出(默认 50 行)" },
18757
19218
  { usage: "/session rm <alias>", description: "删除逻辑会话" },
18758
19219
  { usage: "/use <alias>", description: "切换当前会话" },
19220
+ { usage: "/use <片段>", description: "按别名片段切换(精确>前缀>子串;多命中会列候选)" },
19221
+ { usage: "/use -", description: "切回上一个会话(像 shell 的 cd -)" },
18759
19222
  { usage: "/session reset 或 /clear", description: "重置当前会话上下文" }
18760
19223
  ],
18761
19224
  examples: [
@@ -18763,6 +19226,8 @@ var init_session_handler = __esm(() => {
18763
19226
  "/ssn",
18764
19227
  "/ssn 1",
18765
19228
  "/use backend-fix",
19229
+ "/use back",
19230
+ "/use -",
18766
19231
  "/session rm old-session",
18767
19232
  "/session reset"
18768
19233
  ]
@@ -18828,12 +19293,14 @@ var init_session_handler = __esm(() => {
18828
19293
  cancelHelp = {
18829
19294
  topic: "cancel",
18830
19295
  aliases: ["stop"],
18831
- summary: "取消当前会话里正在执行的任务。",
19296
+ summary: "取消会话里正在执行的任务。",
18832
19297
  commands: [
18833
- { usage: "/cancel", description: "取消当前任务" },
18834
- { usage: "/stop", description: "取消当前任务(/cancel 别名)" }
19298
+ { usage: "/cancel", description: "取消当前前台会话的任务" },
19299
+ { usage: "/cancel <alias>", description: "取消指定(含后台)会话的任务" },
19300
+ { usage: "/stop", description: "取消当前任务(/cancel 别名)" },
19301
+ { usage: "/stop <alias>", description: "取消指定会话的任务(/cancel <alias> 别名)" }
18835
19302
  ],
18836
- examples: ["/cancel"]
19303
+ examples: ["/cancel", "/cancel backend"]
18837
19304
  };
18838
19305
  });
18839
19306
 
@@ -19788,6 +20255,27 @@ function handleHelp(topic) {
19788
20255
  }
19789
20256
  return { text: renderHelpTopic(entry) };
19790
20257
  }
20258
+ function handleInvalidCommand(recognizedCommand) {
20259
+ const topicName = recognizedCommand.replace(/^\//, "");
20260
+ const entry = getHelpTopic(topicName);
20261
+ if (entry) {
20262
+ return { text: `命令格式不正确,请参考下面的用法:
20263
+
20264
+ ${renderHelpTopic(entry)}` };
20265
+ }
20266
+ return {
20267
+ text: [
20268
+ "无法识别的命令格式。",
20269
+ "",
20270
+ "正确的会话创建格式:",
20271
+ "/session new <别名> --agent <Agent名> --ws <工作区名>",
20272
+ "",
20273
+ "例如:",
20274
+ "/session new demo --agent claude --ws weacpx"
20275
+ ].join(`
20276
+ `)
20277
+ };
20278
+ }
19791
20279
  function renderHelpIndex() {
19792
20280
  const topics = listHelpTopics();
19793
20281
  return [
@@ -20520,7 +21008,7 @@ import { spawn as spawn5 } from "node:child_process";
20520
21008
  import { createWriteStream } from "node:fs";
20521
21009
  import { mkdir as mkdir10 } from "node:fs/promises";
20522
21010
  import { homedir as homedir6 } from "node:os";
20523
- import { join as join10 } from "node:path";
21011
+ import { join as join13 } from "node:path";
20524
21012
  async function autoInstallOptionalDep(pkg, parentPackages, options = {}) {
20525
21013
  const runCli = options.runCli ?? defaultRunCli;
20526
21014
  const openLog = options.openLog ?? defaultLogSink;
@@ -20635,10 +21123,10 @@ ${err.message}`, reason: "spawn" });
20635
21123
  });
20636
21124
  });
20637
21125
  }, defaultLogSink = async () => {
20638
- const dir = join10(homedir6(), ".weacpx", "logs");
21126
+ const dir = join13(coreHomeDir(homedir6()), "logs");
20639
21127
  await mkdir10(dir, { recursive: true });
20640
21128
  const timestamp = new Date().toISOString().replace(/[:.]/g, "").replace(/-/g, "");
20641
- const path14 = join10(dir, `auto-install-${timestamp}.log`);
21129
+ const path14 = join13(dir, `auto-install-${timestamp}.log`);
20642
21130
  const stream = createWriteStream(path14, { flags: "a" });
20643
21131
  return {
20644
21132
  path: path14,
@@ -20651,6 +21139,7 @@ ${err.message}`, reason: "spawn" });
20651
21139
  };
20652
21140
  };
20653
21141
  var init_auto_install_optional_dep = __esm(() => {
21142
+ init_core_home();
20654
21143
  PRECISE_COMMANDS = {
20655
21144
  npm: { cmd: "npm", args: (pkg) => ["install", pkg, "--no-save", "--no-audit", "--no-fund"] },
20656
21145
  bun: { cmd: "bun", args: (pkg) => ["add", pkg] },
@@ -20664,7 +21153,7 @@ import { spawn as spawn6 } from "node:child_process";
20664
21153
  import { createRequire as createRequire3 } from "node:module";
20665
21154
  import { access as access3 } from "node:fs/promises";
20666
21155
  import { homedir as homedir7 } from "node:os";
20667
- import { dirname as dirname11, join as join11 } from "node:path";
21156
+ import { dirname as dirname12, join as join14 } from "node:path";
20668
21157
  function deriveParentPackageName(platformPackage) {
20669
21158
  return platformPackage.replace(/-(?:linux|darwin|win32|windows|freebsd|openbsd|sunos|aix)(?:-(?:x64|arm64|ia32|arm|ppc64|s390x))?(?:-(?:baseline|musl|gnu|gnueabihf|musleabihf|msvc))?$/, "");
20670
21159
  }
@@ -20677,7 +21166,7 @@ async function discoverParentPackagePaths(platformPackage, seedPath, deps = {})
20677
21166
  const queryRoot = deps.queryPackageManagerRoot ?? defaultQueryPackageManagerRoot;
20678
21167
  const parentName = deriveParentPackageName(platformPackage);
20679
21168
  const rawCandidates = [];
20680
- const bunGlobalRoot = env.BUN_INSTALL ? join11(env.BUN_INSTALL, "install", "global", "node_modules") : join11(home, ".bun", "install", "global", "node_modules");
21169
+ const bunGlobalRoot = env.BUN_INSTALL ? join14(env.BUN_INSTALL, "install", "global", "node_modules") : join14(home, ".bun", "install", "global", "node_modules");
20681
21170
  const [npmRoot, pnpmRoot, yarnRoot] = await Promise.all([
20682
21171
  queryRoot("npm"),
20683
21172
  queryRoot("pnpm"),
@@ -20700,20 +21189,20 @@ async function discoverParentPackagePaths(platformPackage, seedPath, deps = {})
20700
21189
  if (resolved)
20701
21190
  rawCandidates.push({ path: resolved, manager: classify(resolved) });
20702
21191
  }
20703
- rawCandidates.push({ path: join11(bunGlobalRoot, parentName), manager: "bun" });
21192
+ rawCandidates.push({ path: join14(bunGlobalRoot, parentName), manager: "bun" });
20704
21193
  if (npmRoot)
20705
- rawCandidates.push({ path: join11(npmRoot, parentName), manager: "npm" });
21194
+ rawCandidates.push({ path: join14(npmRoot, parentName), manager: "npm" });
20706
21195
  if (pnpmRoot)
20707
- rawCandidates.push({ path: join11(pnpmRoot, parentName), manager: "pnpm" });
21196
+ rawCandidates.push({ path: join14(pnpmRoot, parentName), manager: "pnpm" });
20708
21197
  if (yarnRoot)
20709
- rawCandidates.push({ path: join11(yarnRoot, parentName), manager: "yarn" });
21198
+ rawCandidates.push({ path: join14(yarnRoot, parentName), manager: "yarn" });
20710
21199
  const seen = new Set;
20711
21200
  const verified = [];
20712
21201
  for (const candidate of rawCandidates) {
20713
21202
  if (seen.has(candidate.path))
20714
21203
  continue;
20715
21204
  seen.add(candidate.path);
20716
- if (await fsExists(join11(candidate.path, "package.json"))) {
21205
+ if (await fsExists(join14(candidate.path, "package.json"))) {
20717
21206
  verified.push(candidate);
20718
21207
  }
20719
21208
  }
@@ -20737,7 +21226,7 @@ function defaultResolveFromCwd(name, cwd) {
20737
21226
  const pkgJson = require2.resolve(`${name}/package.json`, {
20738
21227
  paths: [cwd, ...require2.resolve.paths(name) ?? []]
20739
21228
  });
20740
- return dirname11(pkgJson);
21229
+ return dirname12(pkgJson);
20741
21230
  } catch {
20742
21231
  return null;
20743
21232
  }
@@ -20784,7 +21273,7 @@ async function defaultQueryPackageManagerRoot(tool) {
20784
21273
  const trimmed = stdout2.trim().split(/\r?\n/).pop()?.trim() ?? "";
20785
21274
  if (!trimmed)
20786
21275
  return done(null);
20787
- done(spec.postfix ? join11(trimmed, spec.postfix) : trimmed);
21276
+ done(spec.postfix ? join14(trimmed, spec.postfix) : trimmed);
20788
21277
  });
20789
21278
  });
20790
21279
  }
@@ -20888,7 +21377,8 @@ class CommandRouter {
20888
21377
  __setDiscoverPathsForTest(fn) {
20889
21378
  this.discoverPaths = fn;
20890
21379
  }
20891
- constructor(sessions, transport, config2, configStore, logger2, resolveSessionAgentCommand = resolveSessionAgentCommandFromIndex, orchestration, quota, scheduled, scheduledDelivery, resolveNativeSessionListFormat) {
21380
+ activeTurns;
21381
+ constructor(sessions, transport, config2, configStore, logger2, resolveSessionAgentCommand = resolveSessionAgentCommandFromIndex, orchestration, quota, scheduled, scheduledDelivery, resolveNativeSessionListFormat, activeTurns) {
20892
21382
  this.sessions = sessions;
20893
21383
  this.transport = transport;
20894
21384
  this.config = config2;
@@ -20900,6 +21390,7 @@ class CommandRouter {
20900
21390
  this.scheduledDelivery = scheduledDelivery;
20901
21391
  this.resolveNativeSessionListFormat = resolveNativeSessionListFormat;
20902
21392
  this.logger = logger2 ?? createNoopAppLogger();
21393
+ this.activeTurns = activeTurns;
20903
21394
  }
20904
21395
  async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, onThought, perfSpan) {
20905
21396
  const startedAt = Date.now();
@@ -20925,18 +21416,7 @@ class CommandRouter {
20925
21416
  return await this.executeCommand(chatKey, command.kind, startedAt, async () => {
20926
21417
  switch (command.kind) {
20927
21418
  case "invalid":
20928
- return {
20929
- text: [
20930
- "无法识别的命令格式。",
20931
- "",
20932
- "正确的会话创建格式:",
20933
- "/session new <别名> --agent <Agent名> --ws <工作区名>",
20934
- "",
20935
- "例如:",
20936
- "/session new demo --agent claude --ws weacpx"
20937
- ].join(`
20938
- `)
20939
- };
21419
+ return handleInvalidCommand(command.recognizedCommand);
20940
21420
  case "help":
20941
21421
  return handleHelp(command.topic);
20942
21422
  case "agents":
@@ -20981,6 +21461,8 @@ class CommandRouter {
20981
21461
  return await handleNativeSessionSelect(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.identifier, command.alias);
20982
21462
  case "session.use":
20983
21463
  return await handleSessionUse(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
21464
+ case "session.use.previous":
21465
+ return await handleSessionUsePrevious(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
20984
21466
  case "mode.show":
20985
21467
  return await handleModeShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
20986
21468
  case "mode.set":
@@ -20994,7 +21476,7 @@ class CommandRouter {
20994
21476
  case "status":
20995
21477
  return await handleStatus(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
20996
21478
  case "cancel":
20997
- return await handleCancel(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
21479
+ return await handleCancel(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
20998
21480
  case "session.reset":
20999
21481
  return await handleSessionReset(this.createSessionHandlerContext(reply, perfSpan), chatKey);
21000
21482
  case "session.tail":
@@ -21089,7 +21571,8 @@ class CommandRouter {
21089
21571
  ...this.createHandlerContext(),
21090
21572
  lifecycle: this.createSessionLifecycleOps(reply, perfSpan),
21091
21573
  interaction: this.createSessionInteractionOps(perfSpan),
21092
- recovery: this.createSessionRenderRecoveryOps()
21574
+ recovery: this.createSessionRenderRecoveryOps(),
21575
+ ...this.activeTurns ? { activeTurns: this.activeTurns } : {}
21093
21576
  };
21094
21577
  }
21095
21578
  createSessionLifecycleOps(reply, perfSpan) {
@@ -21454,7 +21937,7 @@ var init_command_router = __esm(() => {
21454
21937
  });
21455
21938
 
21456
21939
  // src/config/resolve-acpx-command.ts
21457
- import { readFileSync } from "node:fs";
21940
+ import { readFileSync as readFileSync2 } from "node:fs";
21458
21941
  import { createRequire as createRequire4 } from "node:module";
21459
21942
  import { posix, win32 } from "node:path";
21460
21943
  function resolveAcpxCommand(options = {}) {
@@ -21470,7 +21953,7 @@ function resolveAcpxCommandMetadata(options = {}) {
21470
21953
  }
21471
21954
  const platform = options.platform ?? process.platform;
21472
21955
  const resolvePackageJson = options.resolvePackageJson ?? ((id) => require3.resolve(id));
21473
- const readPackageJson = options.readPackageJson ?? ((path14) => JSON.parse(readFileSync(path14, "utf8")));
21956
+ const readPackageJson = options.readPackageJson ?? ((path14) => JSON.parse(readFileSync2(path14, "utf8")));
21474
21957
  try {
21475
21958
  const packageJsonPath = resolvePackageJson("acpx/package.json");
21476
21959
  const pkg = readPackageJson(packageJsonPath);
@@ -21544,7 +22027,7 @@ var init_console_agent = __esm(() => {
21544
22027
  });
21545
22028
 
21546
22029
  // src/orchestration/orchestration-server.ts
21547
- import { rm as rm7 } from "node:fs/promises";
22030
+ import { rm as rm8 } from "node:fs/promises";
21548
22031
  import { createConnection as createConnection2, createServer } from "node:net";
21549
22032
 
21550
22033
  class OrchestrationServer {
@@ -21877,7 +22360,7 @@ class OrchestrationServer {
21877
22360
  return;
21878
22361
  }
21879
22362
  const removeFile = this.deps.removeFile ?? (async (path14) => {
21880
- await rm7(path14, { force: true });
22363
+ await rm8(path14, { force: true });
21881
22364
  });
21882
22365
  await removeFile(this.endpoint.path);
21883
22366
  }
@@ -24588,7 +25071,11 @@ class OrchestrationService {
24588
25071
  }
24589
25072
  workspaceLabelFromCwd(cwd) {
24590
25073
  const base = basename2(cwd).trim() || "cwd";
24591
- return base.replace(/[^a-zA-Z0-9._-]+/g, "_") || "cwd";
25074
+ return sanitizeString(base, {
25075
+ allow: /[a-zA-Z0-9._-]/,
25076
+ replacement: "_",
25077
+ fallback: "cwd"
25078
+ });
24592
25079
  }
24593
25080
  cwdWorkerSessionPart(cwd) {
24594
25081
  const label = this.workspaceLabelFromCwd(cwd);
@@ -25822,6 +26309,20 @@ class SessionService {
25822
26309
  }
25823
26310
  return this.toResolvedSession(session);
25824
26311
  }
26312
+ getResolvedSessionByInternalAlias(alias) {
26313
+ const session = this.state.sessions[alias];
26314
+ if (!session) {
26315
+ return null;
26316
+ }
26317
+ try {
26318
+ return this.toResolvedSession(session);
26319
+ } catch {
26320
+ return null;
26321
+ }
26322
+ }
26323
+ peekCurrentSessionAlias(chatKey) {
26324
+ return this.state.chat_contexts[chatKey]?.current_session;
26325
+ }
25825
26326
  async getPreferredSessionForTransport(transportSession) {
25826
26327
  const matches = Object.values(this.state.sessions).filter((session) => session.transport_session === transportSession).sort((left, right) => right.last_used_at.localeCompare(left.last_used_at));
25827
26328
  const expectedAlias = transportSession.split(":").at(-1);
@@ -25846,18 +26347,123 @@ class SessionService {
25846
26347
  return null;
25847
26348
  }
25848
26349
  async useSession(chatKey, alias) {
25849
- await this.mutate(async () => {
26350
+ return await this.mutate(async () => {
25850
26351
  const channelId = getChannelIdFromChatKey(chatKey);
25851
26352
  const internalAlias = resolveSessionAliasForInput(channelId, alias, Object.keys(this.state.sessions));
25852
26353
  const session = this.state.sessions[internalAlias];
25853
26354
  if (!session) {
25854
26355
  throw new Error(`session "${alias}" does not exist`);
25855
26356
  }
26357
+ const prevCtx = this.state.chat_contexts[chatKey];
26358
+ const previousCurrent = prevCtx?.current_session;
26359
+ const carriedPrevious = previousCurrent && previousCurrent !== internalAlias ? previousCurrent : prevCtx?.previous_session;
25856
26360
  session.last_used_at = new Date().toISOString();
25857
- this.state.chat_contexts[chatKey] = { current_session: internalAlias };
26361
+ this.state.chat_contexts[chatKey] = {
26362
+ current_session: internalAlias,
26363
+ ...carriedPrevious ? { previous_session: carriedPrevious } : {}
26364
+ };
25858
26365
  await this.persist();
26366
+ return {
26367
+ alias: toDisplaySessionAlias(session.alias),
26368
+ agent: session.agent,
26369
+ workspace: session.workspace,
26370
+ previousAlias: carriedPrevious ? toDisplaySessionAlias(carriedPrevious) : undefined
26371
+ };
25859
26372
  });
25860
26373
  }
26374
+ async usePreviousSession(chatKey) {
26375
+ return await this.mutate(async () => {
26376
+ const ctx = this.state.chat_contexts[chatKey];
26377
+ const prevInternal = ctx?.previous_session;
26378
+ if (!prevInternal) {
26379
+ return null;
26380
+ }
26381
+ const prevSession = this.state.sessions[prevInternal];
26382
+ if (!prevSession) {
26383
+ if (ctx) {
26384
+ delete ctx.previous_session;
26385
+ await this.persist();
26386
+ }
26387
+ return null;
26388
+ }
26389
+ const currentInternal = ctx?.current_session;
26390
+ prevSession.last_used_at = new Date().toISOString();
26391
+ this.state.chat_contexts[chatKey] = {
26392
+ current_session: prevInternal,
26393
+ ...currentInternal && currentInternal !== prevInternal ? { previous_session: currentInternal } : {}
26394
+ };
26395
+ await this.persist();
26396
+ return {
26397
+ alias: toDisplaySessionAlias(prevSession.alias),
26398
+ agent: prevSession.agent,
26399
+ workspace: prevSession.workspace,
26400
+ previousAlias: currentInternal && currentInternal !== prevInternal ? toDisplaySessionAlias(currentInternal) : undefined
26401
+ };
26402
+ });
26403
+ }
26404
+ async setBackgroundResult(chatKey, alias, result) {
26405
+ await this.mutate(async () => {
26406
+ const ctx = this.state.chat_contexts[chatKey] ?? { current_session: "" };
26407
+ const results = { ...ctx.background_results ?? {}, [alias]: result };
26408
+ this.state.chat_contexts[chatKey] = { ...ctx, background_results: results };
26409
+ await this.persist();
26410
+ });
26411
+ }
26412
+ async takeBackgroundResult(chatKey, alias) {
26413
+ return await this.mutate(async () => {
26414
+ const ctx = this.state.chat_contexts[chatKey];
26415
+ const found = ctx?.background_results?.[alias];
26416
+ if (!ctx || !found)
26417
+ return null;
26418
+ const remaining = { ...ctx.background_results };
26419
+ delete remaining[alias];
26420
+ if (Object.keys(remaining).length > 0) {
26421
+ this.state.chat_contexts[chatKey] = { ...ctx, background_results: remaining };
26422
+ } else {
26423
+ const { background_results: _omit, ...rest } = ctx;
26424
+ this.state.chat_contexts[chatKey] = rest;
26425
+ }
26426
+ await this.persist();
26427
+ return found;
26428
+ });
26429
+ }
26430
+ listBackgroundResultAliases(chatKey) {
26431
+ const results = this.state.chat_contexts[chatKey]?.background_results;
26432
+ return results ? Object.keys(results) : [];
26433
+ }
26434
+ resolveFuzzyAlias(chatKey, fragment) {
26435
+ const channelId = getChannelIdFromChatKey(chatKey);
26436
+ const frag = fragment.trim();
26437
+ const items = Object.values(this.state.sessions).filter((session) => isSessionAliasVisibleInChannel(session.alias, channelId)).map((session) => ({
26438
+ display: toDisplaySessionAlias(session.alias),
26439
+ agent: session.agent,
26440
+ workspace: session.workspace
26441
+ }));
26442
+ const toCandidate = (item) => ({
26443
+ alias: item.display,
26444
+ agent: item.agent,
26445
+ workspace: item.workspace
26446
+ });
26447
+ const exact = items.find((item) => item.display === frag);
26448
+ if (exact) {
26449
+ return { kind: "match", alias: exact.display };
26450
+ }
26451
+ const prefix = items.filter((item) => item.display.startsWith(frag));
26452
+ if (prefix.length === 1) {
26453
+ return { kind: "match", alias: prefix[0].display };
26454
+ }
26455
+ if (prefix.length > 1) {
26456
+ return { kind: "ambiguous", candidates: prefix.map(toCandidate) };
26457
+ }
26458
+ const substring = items.filter((item) => item.display.includes(frag));
26459
+ if (substring.length === 1) {
26460
+ return { kind: "match", alias: substring[0].display };
26461
+ }
26462
+ if (substring.length > 1) {
26463
+ return { kind: "ambiguous", candidates: substring.map(toCandidate) };
26464
+ }
26465
+ return { kind: "none" };
26466
+ }
25861
26467
  async resolveAliasForChat(chatKey, displayAlias) {
25862
26468
  const channelId = getChannelIdFromChatKey(chatKey);
25863
26469
  const candidate = resolveSessionAliasForInput(channelId, displayAlias, Object.keys(this.state.sessions));
@@ -25958,6 +26564,10 @@ class SessionService {
25958
26564
  for (const [chatKey, ctx] of Object.entries(this.state.chat_contexts)) {
25959
26565
  if (ctx.current_session === alias) {
25960
26566
  delete this.state.chat_contexts[chatKey];
26567
+ continue;
26568
+ }
26569
+ if (ctx.previous_session === alias) {
26570
+ delete ctx.previous_session;
25961
26571
  }
25962
26572
  }
25963
26573
  await this.persist();
@@ -25989,9 +26599,11 @@ class SessionService {
25989
26599
  }
25990
26600
  const createdAt = Date.parse(cached2.created_at);
25991
26601
  if (Number.isNaN(createdAt)) {
26602
+ await this.deleteNativeSessionListIfCurrent(chatKey, cached2);
25992
26603
  return null;
25993
26604
  }
25994
26605
  if (this.now() - createdAt > ttlMs) {
26606
+ await this.deleteNativeSessionListIfCurrent(chatKey, cached2);
25995
26607
  return null;
25996
26608
  }
25997
26609
  return {
@@ -26007,6 +26619,15 @@ class SessionService {
26007
26619
  ...cached2.next_cursor !== undefined ? { nextCursor: cached2.next_cursor } : {}
26008
26620
  };
26009
26621
  }
26622
+ async deleteNativeSessionListIfCurrent(chatKey, cached2) {
26623
+ await this.mutate(async () => {
26624
+ if (this.state.native_session_lists[chatKey] !== cached2) {
26625
+ return;
26626
+ }
26627
+ delete this.state.native_session_lists[chatKey];
26628
+ await this.persist();
26629
+ });
26630
+ }
26010
26631
  toResolvedSession(session) {
26011
26632
  const agentConfig = this.config.agents[session.agent];
26012
26633
  if (!agentConfig) {
@@ -26106,6 +26727,29 @@ var init_session_service = __esm(() => {
26106
26727
  init_channel_scope();
26107
26728
  });
26108
26729
 
26730
+ // src/sessions/active-turn-registry.ts
26731
+ function createActiveTurnRegistry() {
26732
+ const byChat = new Map;
26733
+ return {
26734
+ markActive(chatKey, alias) {
26735
+ const set2 = byChat.get(chatKey) ?? new Set;
26736
+ set2.add(alias);
26737
+ byChat.set(chatKey, set2);
26738
+ },
26739
+ markInactive(chatKey, alias) {
26740
+ const set2 = byChat.get(chatKey);
26741
+ if (!set2)
26742
+ return;
26743
+ set2.delete(alias);
26744
+ if (set2.size === 0)
26745
+ byChat.delete(chatKey);
26746
+ },
26747
+ isActive(chatKey, alias) {
26748
+ return byChat.get(chatKey)?.has(alias) ?? false;
26749
+ }
26750
+ };
26751
+ }
26752
+
26109
26753
  // src/state/debounced-state-store.ts
26110
26754
  class DebouncedStateStore {
26111
26755
  deps;
@@ -26185,6 +26829,37 @@ class DebouncedStateStore {
26185
26829
  }
26186
26830
  }
26187
26831
 
26832
+ // src/commands/command-hints.ts
26833
+ function listWeacpxCommandHints() {
26834
+ const hints = [{ name: "/help", description: "查看命令帮助。" }];
26835
+ for (const topic of HELP_TOPICS) {
26836
+ const name = PRIMARY_COMMAND_BY_TOPIC[topic.topic];
26837
+ if (!name) {
26838
+ throw new Error(`command-hints: 未登记 help topic 的主命令: ${topic.topic}`);
26839
+ }
26840
+ hints.push({ name, description: topic.summary });
26841
+ }
26842
+ return hints;
26843
+ }
26844
+ var PRIMARY_COMMAND_BY_TOPIC;
26845
+ var init_command_hints = __esm(() => {
26846
+ init_help_registry();
26847
+ PRIMARY_COMMAND_BY_TOPIC = {
26848
+ session: "/session",
26849
+ native: "/ssn",
26850
+ workspace: "/workspace",
26851
+ agent: "/agent",
26852
+ permission: "/permission",
26853
+ config: "/config",
26854
+ orchestration: "/delegate",
26855
+ mode: "/mode",
26856
+ replymode: "/replymode",
26857
+ status: "/status",
26858
+ cancel: "/cancel",
26859
+ later: "/later"
26860
+ };
26861
+ });
26862
+
26188
26863
  // src/run-console.ts
26189
26864
  var exports_run_console = {};
26190
26865
  __export(exports_run_console, {
@@ -26298,8 +26973,12 @@ async function runConsole(paths, deps) {
26298
26973
  agent: runtime.agent,
26299
26974
  abortSignal: shutdownController.signal,
26300
26975
  quota: runtime.quota,
26976
+ sessions: runtime.sessions,
26977
+ activeTurns: runtime.activeTurns,
26301
26978
  logger: runtime.logger,
26302
- perfTracer: runtime.perfTracer
26979
+ perfTracer: runtime.perfTracer,
26980
+ commandHints: listWeacpxCommandHints(),
26981
+ coreVersion: WEACPX_CORE_VERSION
26303
26982
  });
26304
26983
  channelStartPromise.catch(() => {});
26305
26984
  let channelStartSettled = false;
@@ -26417,6 +27096,8 @@ async function runCleanupSequence(input) {
26417
27096
  }
26418
27097
  var init_run_console = __esm(() => {
26419
27098
  init_consumer_lock();
27099
+ init_command_hints();
27100
+ init_version();
26420
27101
  });
26421
27102
 
26422
27103
  // src/transport/acpx-bridge/acpx-bridge-protocol.ts
@@ -26447,7 +27128,7 @@ function encodeBridgeSessionNoteEvent(event) {
26447
27128
 
26448
27129
  // src/transport/acpx-bridge/acpx-bridge-client.ts
26449
27130
  import { spawn as spawn7 } from "node:child_process";
26450
- import { fileURLToPath as fileURLToPath3 } from "node:url";
27131
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
26451
27132
  import { createInterface } from "node:readline";
26452
27133
 
26453
27134
  class AcpxBridgeClient {
@@ -26572,7 +27253,7 @@ function buildBridgeSpawnSpec(options) {
26572
27253
  };
26573
27254
  }
26574
27255
  async function spawnAcpxBridgeClient(options = {}) {
26575
- const bridgeEntryPath = options.bridgeEntryPath ?? fileURLToPath3(new URL("../../bridge/bridge-main.ts", import.meta.url));
27256
+ const bridgeEntryPath = options.bridgeEntryPath ?? fileURLToPath4(new URL("../../bridge/bridge-main.ts", import.meta.url));
26576
27257
  const spawnSpec = buildBridgeSpawnSpec({
26577
27258
  execPath: process.execPath,
26578
27259
  bridgeEntryPath
@@ -27024,7 +27705,7 @@ var init_spawn_command = __esm(() => {
27024
27705
  });
27025
27706
 
27026
27707
  // src/transport/prompt-media.ts
27027
- import { mkdtemp, open as open4, rm as rm8, writeFile as writeFile7 } from "node:fs/promises";
27708
+ import { mkdtemp, open as open4, rm as rm9, writeFile as writeFile7 } from "node:fs/promises";
27028
27709
  import { tmpdir as defaultTmpdir } from "node:os";
27029
27710
  import path14 from "node:path";
27030
27711
  import { pathToFileURL as pathToFileURL2 } from "node:url";
@@ -27149,7 +27830,7 @@ var init_prompt_media = __esm(() => {
27149
27830
  readImageFile: readImageFileBounded,
27150
27831
  mkdtemp,
27151
27832
  writeFile: writeFile7,
27152
- rm: rm8,
27833
+ rm: rm9,
27153
27834
  tmpdir: defaultTmpdir
27154
27835
  };
27155
27836
  });
@@ -27448,12 +28129,12 @@ var init_streaming_prompt = __esm(() => {
27448
28129
 
27449
28130
  // src/transport/acpx-cli/node-pty-helper.ts
27450
28131
  import { chmod as chmodFs } from "node:fs/promises";
27451
- import { dirname as dirname12, join as join12 } from "node:path";
28132
+ import { dirname as dirname13, join as join15 } from "node:path";
27452
28133
  function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
27453
28134
  if (platform === "win32") {
27454
28135
  return null;
27455
28136
  }
27456
- return join12(dirname12(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
28137
+ return join15(dirname13(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
27457
28138
  }
27458
28139
  async function ensureNodePtyHelperExecutable(helperPath, chmod3 = chmodFs) {
27459
28140
  if (!helperPath) {
@@ -27473,9 +28154,9 @@ var init_node_pty_helper = () => {};
27473
28154
  // src/transport/acpx-queue-owner-launcher.ts
27474
28155
  import { createHash as createHash3 } from "node:crypto";
27475
28156
  import { spawn as spawn8 } from "node:child_process";
27476
- import { readFile as readFile11, unlink } from "node:fs/promises";
28157
+ import { readFile as readFile12, unlink } from "node:fs/promises";
27477
28158
  import { homedir as homedir8 } from "node:os";
27478
- import { join as join13 } from "node:path";
28159
+ import { join as join16 } from "node:path";
27479
28160
  function buildWeacpxMcpServerSpec(input) {
27480
28161
  const { command, args } = splitCommandLine(input.weacpxCommand);
27481
28162
  return {
@@ -27526,7 +28207,13 @@ class AcpxQueueOwnerLauncher {
27526
28207
  const key = input.acpxRecordId;
27527
28208
  const previous = this.launchLocks.get(key) ?? Promise.resolve();
27528
28209
  const next = previous.then(() => this.doLaunch(input), () => this.doLaunch(input));
27529
- this.launchLocks.set(key, next.catch(() => {}));
28210
+ const tracked = next.catch(() => {});
28211
+ this.launchLocks.set(key, tracked);
28212
+ tracked.finally(() => {
28213
+ if (this.launchLocks.get(key) === tracked) {
28214
+ this.launchLocks.delete(key);
28215
+ }
28216
+ });
27530
28217
  return next;
27531
28218
  }
27532
28219
  async doLaunch(input) {
@@ -27629,7 +28316,7 @@ async function terminateAcpxQueueOwner(sessionId) {
27629
28316
  const lockPath = queueLockFilePath(sessionId);
27630
28317
  let owner;
27631
28318
  try {
27632
- owner = JSON.parse(await readFile11(lockPath, "utf8"));
28319
+ owner = JSON.parse(await readFile12(lockPath, "utf8"));
27633
28320
  } catch {
27634
28321
  return;
27635
28322
  }
@@ -27639,7 +28326,7 @@ async function terminateAcpxQueueOwner(sessionId) {
27639
28326
  await unlink(lockPath).catch(() => {});
27640
28327
  }
27641
28328
  function queueLockFilePath(sessionId) {
27642
- return join13(homedir8(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
28329
+ return join16(homedir8(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
27643
28330
  }
27644
28331
  function shortHash(value, length) {
27645
28332
  return createHash3("sha256").update(value).digest("hex").slice(0, length);
@@ -27657,7 +28344,7 @@ function resolveDefaultWeacpxCommand(env) {
27657
28344
  return "weacpx";
27658
28345
  }
27659
28346
  function quoteCommandPart(value) {
27660
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
28347
+ return quoteIfNeeded(value);
27661
28348
  }
27662
28349
  var init_acpx_queue_owner_launcher = __esm(() => {
27663
28350
  init_spawn_command();
@@ -27677,7 +28364,6 @@ function permissionModeToFlag(permissionMode) {
27677
28364
  }
27678
28365
 
27679
28366
  // src/transport/agent-session-list.ts
27680
- import path15 from "node:path";
27681
28367
  function isUnknownFilterCwdOption(output) {
27682
28368
  return /(?:unknown|unrecognized) option/i.test(output) && output.includes("--filter-cwd");
27683
28369
  }
@@ -27724,27 +28410,12 @@ function isAgentSessionListResult(value) {
27724
28410
  function filterAgentSessionListByCwd(result, cwd) {
27725
28411
  return {
27726
28412
  ...result,
27727
- sessions: result.sessions.filter((session) => session.cwd && sameAgentSessionCwd(session.cwd, cwd))
28413
+ sessions: result.sessions.filter((session) => session.cwd && isSamePath(session.cwd, cwd))
27728
28414
  };
27729
28415
  }
27730
- function sameAgentSessionCwd(left, right) {
27731
- const normalizedLeft = normalizeAgentSessionCwd(left);
27732
- const normalizedRight = normalizeAgentSessionCwd(right);
27733
- if (isWindowsLikePath2(normalizedLeft) || isWindowsLikePath2(normalizedRight)) {
27734
- return normalizedLeft.toLowerCase() === normalizedRight.toLowerCase();
27735
- }
27736
- return normalizedLeft === normalizedRight;
27737
- }
27738
- function normalizeAgentSessionCwd(input) {
27739
- if (isWindowsLikePath2(input)) {
27740
- return path15.win32.normalize(input).replace(/\\/g, "/");
27741
- }
27742
- return path15.posix.normalize(input.replace(/\\/g, "/"));
27743
- }
27744
- function isWindowsLikePath2(input) {
27745
- return /^[a-zA-Z]:[\\/]/.test(input) || input.startsWith("\\\\");
27746
- }
27747
- var init_agent_session_list = () => {};
28416
+ var init_agent_session_list = __esm(() => {
28417
+ init_path();
28418
+ });
27748
28419
 
27749
28420
  // src/transport/acpx-cli/acpx-cli-transport.ts
27750
28421
  import { createRequire as createRequire5 } from "node:module";
@@ -28263,6 +28934,30 @@ var init_acpx_cli_transport = __esm(() => {
28263
28934
  require4 = createRequire5(import.meta.url);
28264
28935
  });
28265
28936
 
28937
+ // src/util/async.ts
28938
+ function settleWithinTimeout(work, timeoutMs) {
28939
+ return new Promise((resolve3) => {
28940
+ let settled = false;
28941
+ const finish = () => {
28942
+ if (!settled) {
28943
+ settled = true;
28944
+ resolve3();
28945
+ }
28946
+ };
28947
+ const timer = setTimeout(finish, timeoutMs);
28948
+ if (typeof timer.unref === "function") {
28949
+ timer.unref();
28950
+ }
28951
+ work.then(() => {
28952
+ clearTimeout(timer);
28953
+ finish();
28954
+ }, () => {
28955
+ clearTimeout(timer);
28956
+ finish();
28957
+ });
28958
+ });
28959
+ }
28960
+
28266
28961
  // src/transport/queue-owner-reaper.ts
28267
28962
  import { spawn as spawn10 } from "node:child_process";
28268
28963
  async function reapQueueOwners(acpxCommand, targets, deps = {}) {
@@ -28293,28 +28988,6 @@ async function reapQueueOwners(acpxCommand, targets, deps = {}) {
28293
28988
  await settleWithinTimeout(Promise.all(unique.map(reapOne)), timeoutMs);
28294
28989
  return { terminated, attempted: unique.length };
28295
28990
  }
28296
- function settleWithinTimeout(work, timeoutMs) {
28297
- return new Promise((resolve3) => {
28298
- let settled = false;
28299
- const finish = () => {
28300
- if (!settled) {
28301
- settled = true;
28302
- resolve3();
28303
- }
28304
- };
28305
- const timer = setTimeout(finish, timeoutMs);
28306
- if (typeof timer.unref === "function") {
28307
- timer.unref();
28308
- }
28309
- work.then(() => {
28310
- clearTimeout(timer);
28311
- finish();
28312
- }, () => {
28313
- clearTimeout(timer);
28314
- finish();
28315
- });
28316
- });
28317
- }
28318
28991
  async function defaultResolveRecordId(acpxCommand, target) {
28319
28992
  const args = [
28320
28993
  "--format",
@@ -28498,23 +29171,33 @@ var init_channel_registry = __esm(() => {
28498
29171
  });
28499
29172
 
28500
29173
  // src/weixin/messaging/quota-manager.ts
28501
- function freshState() {
28502
- return { midUsed: 0, finalUsed: 0, pendingFinalChunks: [] };
29174
+ function freshState(now) {
29175
+ return { midUsed: 0, finalUsed: 0, pendingFinalChunks: [], lastTouchedAt: now };
28503
29176
  }
28504
29177
 
28505
29178
  class QuotaManager {
28506
29179
  states = new Map;
28507
29180
  observer;
28508
29181
  normalizeKey;
28509
- constructor(observer, normalizeKey) {
29182
+ maxStates;
29183
+ stateTtlMs;
29184
+ maxPendingFinalChunksPerChat;
29185
+ now;
29186
+ constructor(observer, normalizeKey, options = {}) {
28510
29187
  this.observer = observer;
28511
29188
  this.normalizeKey = normalizeKey ?? ((key) => key);
29189
+ this.maxStates = normalizePositiveInt3(options.maxStates, DEFAULT_MAX_STATES);
29190
+ this.stateTtlMs = normalizeNonNegativeMs3(options.stateTtlMs, DEFAULT_STATE_TTL_MS);
29191
+ this.maxPendingFinalChunksPerChat = normalizePositiveInt3(options.maxPendingFinalChunksPerChat, DEFAULT_MAX_PENDING_FINAL_CHUNKS_PER_CHAT);
29192
+ this.now = options.now ?? (() => Date.now());
28512
29193
  }
28513
29194
  onInbound(chatKey) {
28514
29195
  const key = this.normalizeKey(chatKey);
29196
+ this.prune();
28515
29197
  const existing = this.states.get(key);
28516
29198
  const pending = existing?.pendingFinalChunks ?? [];
28517
- this.states.set(key, { midUsed: 0, finalUsed: 0, pendingFinalChunks: pending });
29199
+ this.states.set(key, { midUsed: 0, finalUsed: 0, pendingFinalChunks: pending, lastTouchedAt: this.now() });
29200
+ this.enforceMaxStates();
28518
29201
  this.observer?.onInbound?.(key);
28519
29202
  }
28520
29203
  reserveMidSegment(chatKey) {
@@ -28547,36 +29230,48 @@ class QuotaManager {
28547
29230
  return;
28548
29231
  const state = this.getOrCreate(this.normalizeKey(chatKey));
28549
29232
  state.pendingFinalChunks.push(...chunks);
29233
+ this.trimPendingFinalChunks(state, "front");
28550
29234
  }
28551
29235
  prependPendingFinal(chatKey, chunks) {
28552
29236
  if (chunks.length === 0)
28553
29237
  return;
28554
29238
  const state = this.getOrCreate(this.normalizeKey(chatKey));
28555
29239
  state.pendingFinalChunks.unshift(...chunks);
29240
+ this.trimPendingFinalChunks(state, "back");
28556
29241
  }
28557
29242
  drainPendingFinalUpToBudget(chatKey, available) {
28558
29243
  if (available <= 0)
28559
29244
  return [];
28560
- const state = this.getOrCreate(this.normalizeKey(chatKey));
29245
+ const key = this.normalizeKey(chatKey);
29246
+ const state = this.getOrCreate(key);
28561
29247
  if (state.pendingFinalChunks.length === 0)
28562
29248
  return [];
28563
- return state.pendingFinalChunks.splice(0, available);
29249
+ const drained = state.pendingFinalChunks.splice(0, available);
29250
+ state.lastTouchedAt = this.now();
29251
+ this.deleteIfEmpty(key, state);
29252
+ return drained;
28564
29253
  }
28565
29254
  hasPendingFinal(chatKey) {
29255
+ this.prune();
28566
29256
  return (this.states.get(this.normalizeKey(chatKey))?.pendingFinalChunks.length ?? 0) > 0;
28567
29257
  }
28568
29258
  countPendingFinal(chatKey) {
29259
+ this.prune();
28569
29260
  return this.states.get(this.normalizeKey(chatKey))?.pendingFinalChunks.length ?? 0;
28570
29261
  }
28571
29262
  clearPendingFinal(chatKey) {
28572
- const state = this.states.get(this.normalizeKey(chatKey));
29263
+ const key = this.normalizeKey(chatKey);
29264
+ const state = this.states.get(key);
28573
29265
  if (!state)
28574
29266
  return;
28575
29267
  state.pendingFinalChunks = [];
29268
+ state.lastTouchedAt = this.now();
29269
+ this.deleteIfEmpty(key, state);
28576
29270
  }
28577
29271
  snapshot(chatKey) {
28578
29272
  const key = this.normalizeKey(chatKey);
28579
- const state = this.states.get(key) ?? freshState();
29273
+ this.prune();
29274
+ const state = this.states.get(key) ?? freshState(this.now());
28580
29275
  const midRemaining = MID_BUDGET - state.midUsed;
28581
29276
  const finalRemaining = FINAL_BUDGET - state.finalUsed;
28582
29277
  return {
@@ -28588,15 +29283,72 @@ class QuotaManager {
28588
29283
  };
28589
29284
  }
28590
29285
  getOrCreate(key) {
29286
+ this.prune();
28591
29287
  let state = this.states.get(key);
28592
29288
  if (!state) {
28593
- state = freshState();
29289
+ state = freshState(this.now());
28594
29290
  this.states.set(key, state);
29291
+ this.enforceMaxStates();
29292
+ } else {
29293
+ state.lastTouchedAt = this.now();
28595
29294
  }
28596
29295
  return state;
28597
29296
  }
29297
+ prune() {
29298
+ const cutoff = this.now() - this.stateTtlMs;
29299
+ for (const [key, state] of this.states) {
29300
+ if (state.lastTouchedAt < cutoff) {
29301
+ this.states.delete(key);
29302
+ }
29303
+ }
29304
+ this.enforceMaxStates();
29305
+ }
29306
+ enforceMaxStates() {
29307
+ while (this.states.size > this.maxStates) {
29308
+ let oldestKey;
29309
+ let oldestTouchedAt = Number.POSITIVE_INFINITY;
29310
+ for (const [key, state] of this.states) {
29311
+ if (state.lastTouchedAt < oldestTouchedAt) {
29312
+ oldestTouchedAt = state.lastTouchedAt;
29313
+ oldestKey = key;
29314
+ }
29315
+ }
29316
+ if (oldestKey === undefined)
29317
+ return;
29318
+ this.states.delete(oldestKey);
29319
+ }
29320
+ }
29321
+ trimPendingFinalChunks(state, side) {
29322
+ state.lastTouchedAt = this.now();
29323
+ const excess = state.pendingFinalChunks.length - this.maxPendingFinalChunksPerChat;
29324
+ if (excess <= 0)
29325
+ return;
29326
+ if (side === "front") {
29327
+ state.pendingFinalChunks.splice(0, excess);
29328
+ } else {
29329
+ state.pendingFinalChunks.splice(state.pendingFinalChunks.length - excess, excess);
29330
+ }
29331
+ }
29332
+ deleteIfEmpty(key, state) {
29333
+ if (state.midUsed === 0 && state.finalUsed === 0 && state.pendingFinalChunks.length === 0) {
29334
+ this.states.delete(key);
29335
+ }
29336
+ }
29337
+ }
29338
+ function normalizePositiveInt3(value, fallback) {
29339
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1)
29340
+ return fallback;
29341
+ return Math.floor(value);
28598
29342
  }
28599
- var MID_BUDGET = 6, FINAL_BUDGET = 4;
29343
+ function normalizeNonNegativeMs3(value, fallback) {
29344
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
29345
+ return fallback;
29346
+ return value;
29347
+ }
29348
+ var MID_BUDGET = 6, FINAL_BUDGET = 4, DEFAULT_MAX_STATES = 5000, DEFAULT_STATE_TTL_MS, DEFAULT_MAX_PENDING_FINAL_CHUNKS_PER_CHAT = 40;
29349
+ var init_quota_manager = __esm(() => {
29350
+ DEFAULT_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
29351
+ });
28600
29352
 
28601
29353
  // src/main.ts
28602
29354
  var exports_main = {};
@@ -28609,8 +29361,8 @@ __export(exports_main, {
28609
29361
  });
28610
29362
  import { randomUUID as randomUUID3 } from "node:crypto";
28611
29363
  import { homedir as homedir9 } from "node:os";
28612
- import { dirname as dirname13, join as join14 } from "node:path";
28613
- import { fileURLToPath as fileURLToPath4 } from "node:url";
29364
+ import { dirname as dirname14, join as join17 } from "node:path";
29365
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
28614
29366
  function startProgressHeartbeat(orchestration, config2, logger2, channel) {
28615
29367
  const thresholdSeconds = config2.orchestration.progressHeartbeatSeconds;
28616
29368
  if (thresholdSeconds <= 0) {
@@ -28690,6 +29442,7 @@ async function buildApp(paths, deps = {}) {
28690
29442
  }
28691
29443
  });
28692
29444
  const sessions = new SessionService(config2, debouncedStateStore, state, { stateMutex });
29445
+ const activeTurns = createActiveTurnRegistry();
28693
29446
  const scheduledService = new ScheduledTaskService(state, debouncedStateStore, { stateMutex });
28694
29447
  const pendingWorkerDispatches = new Set;
28695
29448
  const transport = config2.transport.type === "acpx-bridge" ? await (deps.createBridgeTransport?.() ?? Promise.resolve(new AcpxBridgeTransport(await spawnAcpxBridgeClient({
@@ -29060,7 +29813,7 @@ async function buildApp(paths, deps = {}) {
29060
29813
  listScheduledTasksFromRoute: async (input) => await listScheduledTasksFromRoute(input, { state, scheduled: scheduledService }),
29061
29814
  cancelScheduledTaskFromRoute: async (input) => await cancelScheduledTaskFromRoute(input, { state, scheduled: scheduledService })
29062
29815
  });
29063
- const router = new CommandRouter(sessions, transport, config2, configStore, logger2, undefined, orchestration, quota, scheduledService, deps.channel?.supportsScheduledMessages ? { supportsScheduledMessages: deps.channel.supportsScheduledMessages.bind(deps.channel) } : undefined, deps.channel?.nativeSessionListFormat ? deps.channel.nativeSessionListFormat.bind(deps.channel) : undefined);
29816
+ const router = new CommandRouter(sessions, transport, config2, configStore, logger2, undefined, orchestration, quota, scheduledService, deps.channel?.supportsScheduledMessages ? { supportsScheduledMessages: deps.channel.supportsScheduledMessages.bind(deps.channel) } : undefined, deps.channel?.nativeSessionListFormat ? deps.channel.nativeSessionListFormat.bind(deps.channel) : undefined, activeTurns);
29064
29817
  const agent = new ConsoleAgent(router, logger2);
29065
29818
  const scheduledScheduler = new ScheduledTaskScheduler(scheduledService, {
29066
29819
  dispatchTask: buildScheduledDispatchTask({
@@ -29081,6 +29834,7 @@ async function buildApp(paths, deps = {}) {
29081
29834
  agent,
29082
29835
  router,
29083
29836
  sessions,
29837
+ activeTurns,
29084
29838
  stateStore,
29085
29839
  configStore,
29086
29840
  logger: logger2,
@@ -29186,8 +29940,8 @@ async function main() {
29186
29940
  }
29187
29941
  }
29188
29942
  async function prepareChannelMedia(configPath, config2) {
29189
- const runtimeDir = join14(dirname13(configPath), "runtime");
29190
- const mediaRootDir = join14(runtimeDir, "media");
29943
+ const runtimeDir = join17(dirname14(configPath), "runtime");
29944
+ const mediaRootDir = join17(runtimeDir, "media");
29191
29945
  const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
29192
29946
  await mediaStore.cleanupExpired().catch((error2) => {
29193
29947
  console.error("[weacpx] media cleanup failed:", error2 instanceof Error ? error2.message : String(error2));
@@ -29200,30 +29954,30 @@ function resolveRuntimePaths() {
29200
29954
  if (!home) {
29201
29955
  throw new Error("Unable to resolve the current user home directory");
29202
29956
  }
29203
- const configPath = process.env.WEACPX_CONFIG ?? `${home}/.weacpx/config.json`;
29204
- const runtimeDir = join14(dirname13(configPath), "runtime");
29957
+ const configPath = process.env.WEACPX_CONFIG ?? join17(coreHomeDir(home), "config.json");
29958
+ const runtimeDir = join17(dirname14(configPath), "runtime");
29205
29959
  return {
29206
29960
  configPath,
29207
- statePath: process.env.WEACPX_STATE ?? `${home}/.weacpx/state.json`,
29208
- perfLogPath: join14(runtimeDir, "perf.log"),
29961
+ statePath: process.env.WEACPX_STATE ?? join17(coreHomeDir(home), "state.json"),
29962
+ perfLogPath: join17(runtimeDir, "perf.log"),
29209
29963
  orchestrationSocketPath: process.env.WEACPX_ORCHESTRATION_SOCKET ?? resolveDaemonOrchestrationSocketPath(runtimeDir)
29210
29964
  };
29211
29965
  }
29212
29966
  function resolveBridgeEntryPath() {
29213
29967
  if (import.meta.url.includes("/dist/")) {
29214
- return fileURLToPath4(new URL("./bridge/bridge-main.js", import.meta.url));
29968
+ return fileURLToPath5(new URL("./bridge/bridge-main.js", import.meta.url));
29215
29969
  }
29216
- return fileURLToPath4(new URL("./bridge/bridge-main.ts", import.meta.url));
29970
+ return fileURLToPath5(new URL("./bridge/bridge-main.ts", import.meta.url));
29217
29971
  }
29218
29972
  function resolveAppLogPath(configPath) {
29219
- const rootDir = dirname13(configPath);
29220
- const runtimeDir = join14(rootDir, "runtime");
29221
- return join14(runtimeDir, "app.log");
29973
+ const rootDir = dirname14(configPath);
29974
+ const runtimeDir = join17(rootDir, "runtime");
29975
+ return join17(runtimeDir, "app.log");
29222
29976
  }
29223
29977
  function resolvePerfLogPath(configPath) {
29224
- const rootDir = dirname13(configPath);
29225
- const runtimeDir = join14(rootDir, "runtime");
29226
- return join14(runtimeDir, "perf.log");
29978
+ const rootDir = dirname14(configPath);
29979
+ const runtimeDir = join17(rootDir, "runtime");
29980
+ return join17(runtimeDir, "perf.log");
29227
29981
  }
29228
29982
  function resolveOrchestrationSocketPathFromConfigPath(configPath) {
29229
29983
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
@@ -29233,6 +29987,7 @@ function shouldNotifyTaskCompletion(task) {
29233
29987
  return Boolean(task.chatKey && task.replyContextToken && (task.status === "completed" || task.status === "failed"));
29234
29988
  }
29235
29989
  var init_main = __esm(async () => {
29990
+ init_core_home();
29236
29991
  init_command_router();
29237
29992
  init_config_store();
29238
29993
  init_ensure_config();
@@ -29262,6 +30017,7 @@ var init_main = __esm(async () => {
29262
30017
  init_media_store();
29263
30018
  init_quota_errors();
29264
30019
  init_inbound();
30020
+ init_quota_manager();
29265
30021
  init_perf_tracer();
29266
30022
  init_bootstrap();
29267
30023
  if (false) {}
@@ -29453,7 +30209,7 @@ var init_config_check = __esm(async () => {
29453
30209
  });
29454
30210
 
29455
30211
  // src/doctor/checks/daemon-check.ts
29456
- import { fileURLToPath as fileURLToPath5 } from "node:url";
30212
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
29457
30213
  import { homedir as homedir10 } from "node:os";
29458
30214
  async function checkDaemon(options = {}) {
29459
30215
  const home = options.home ?? process.env.HOME ?? homedir10();
@@ -29547,7 +30303,7 @@ function defaultIsProcessRunning5(pid) {
29547
30303
  }
29548
30304
  }
29549
30305
  function resolveCliEntryPath() {
29550
- return process.argv[1] ?? fileURLToPath5(import.meta.url);
30306
+ return process.argv[1] ?? fileURLToPath6(import.meta.url);
29551
30307
  }
29552
30308
  function formatError5(error2) {
29553
30309
  return error2 instanceof Error ? error2.message : String(error2);
@@ -29609,7 +30365,7 @@ async function checkOrchestrationHealth(options) {
29609
30365
  // src/doctor/checks/runtime-check.ts
29610
30366
  import { constants } from "node:fs";
29611
30367
  import { access as access4, stat as stat3 } from "node:fs/promises";
29612
- import { dirname as dirname14 } from "node:path";
30368
+ import { dirname as dirname15 } from "node:path";
29613
30369
  import { homedir as homedir11 } from "node:os";
29614
30370
  async function checkRuntime(options = {}) {
29615
30371
  const home = options.home ?? process.env.HOME ?? homedir11();
@@ -29651,107 +30407,107 @@ async function checkRuntime(options = {}) {
29651
30407
  }
29652
30408
  function createRuntimeFsProbe() {
29653
30409
  return {
29654
- stat: async (path16) => await stat3(path16),
29655
- access: async (path16, mode) => await access4(path16, mode)
30410
+ stat: async (path15) => await stat3(path15),
30411
+ access: async (path15, mode) => await access4(path15, mode)
29656
30412
  };
29657
30413
  }
29658
- async function checkDirectoryCreatable(label, path16, probe, platform) {
30414
+ async function checkDirectoryCreatable(label, path15, probe, platform) {
29659
30415
  try {
29660
- const stats = await probe.stat(path16);
30416
+ const stats = await probe.stat(path15);
29661
30417
  if (!stats.isDirectory()) {
29662
30418
  return {
29663
30419
  ok: false,
29664
- detail: `${label}: ${path16} (exists but is not a directory)`
30420
+ detail: `${label}: ${path15} (exists but is not a directory)`
29665
30421
  };
29666
30422
  }
29667
- await probe.access(path16, directoryAccessMode(platform));
30423
+ await probe.access(path15, directoryAccessMode(platform));
29668
30424
  return {
29669
30425
  ok: true,
29670
- detail: `${label}: ${path16} (writable)`
30426
+ detail: `${label}: ${path15} (writable)`
29671
30427
  };
29672
30428
  } catch (error2) {
29673
30429
  if (!isMissingPathError(error2)) {
29674
30430
  return {
29675
30431
  ok: false,
29676
- detail: `${label}: ${path16} (unusable: ${formatError6(error2)})`
30432
+ detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
29677
30433
  };
29678
30434
  }
29679
- const parentCheck = await checkCreatableAncestorDirectory(path16, probe, platform);
30435
+ const parentCheck = await checkCreatableAncestorDirectory(path15, probe, platform);
29680
30436
  if (!parentCheck.ok) {
29681
30437
  return {
29682
30438
  ok: false,
29683
- detail: `${label}: ${path16} (parent not writable: ${parentCheck.blockingPath})`
30439
+ detail: `${label}: ${path15} (parent not writable: ${parentCheck.blockingPath})`
29684
30440
  };
29685
30441
  }
29686
30442
  return {
29687
30443
  ok: true,
29688
- detail: `${label}: ${path16} (creatable via ${parentCheck.creatableFrom})`
30444
+ detail: `${label}: ${path15} (creatable via ${parentCheck.creatableFrom})`
29689
30445
  };
29690
30446
  }
29691
30447
  }
29692
- async function checkFileCreatable(label, path16, probe, platform) {
30448
+ async function checkFileCreatable(label, path15, probe, platform) {
29693
30449
  try {
29694
- const stats = await probe.stat(path16);
30450
+ const stats = await probe.stat(path15);
29695
30451
  if (stats.isDirectory()) {
29696
30452
  return {
29697
30453
  ok: false,
29698
- detail: `${label}: ${path16} (exists but is a directory)`
30454
+ detail: `${label}: ${path15} (exists but is a directory)`
29699
30455
  };
29700
30456
  }
29701
- await probe.access(path16, constants.W_OK);
30457
+ await probe.access(path15, constants.W_OK);
29702
30458
  return {
29703
30459
  ok: true,
29704
- detail: `${label}: ${path16} (writable)`
30460
+ detail: `${label}: ${path15} (writable)`
29705
30461
  };
29706
30462
  } catch (error2) {
29707
30463
  if (!isMissingPathError(error2)) {
29708
30464
  return {
29709
30465
  ok: false,
29710
- detail: `${label}: ${path16} (unusable: ${formatError6(error2)})`
30466
+ detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
29711
30467
  };
29712
30468
  }
29713
- const parentCheck = await checkCreatableAncestorDirectory(dirname14(path16), probe, platform);
30469
+ const parentCheck = await checkCreatableAncestorDirectory(dirname15(path15), probe, platform);
29714
30470
  if (!parentCheck.ok) {
29715
30471
  return {
29716
30472
  ok: false,
29717
- detail: `${label}: ${path16} (parent not writable: ${parentCheck.blockingPath})`
30473
+ detail: `${label}: ${path15} (parent not writable: ${parentCheck.blockingPath})`
29718
30474
  };
29719
30475
  }
29720
30476
  return {
29721
30477
  ok: true,
29722
- detail: `${label}: ${path16} (creatable via ${parentCheck.creatableFrom})`
30478
+ detail: `${label}: ${path15} (creatable via ${parentCheck.creatableFrom})`
29723
30479
  };
29724
30480
  }
29725
30481
  }
29726
- async function checkCreatableAncestorDirectory(path16, probe, platform) {
30482
+ async function checkCreatableAncestorDirectory(path15, probe, platform) {
29727
30483
  try {
29728
- const stats = await probe.stat(path16);
30484
+ const stats = await probe.stat(path15);
29729
30485
  if (!stats.isDirectory()) {
29730
30486
  return {
29731
30487
  ok: false,
29732
- creatableFrom: path16,
29733
- blockingPath: path16
30488
+ creatableFrom: path15,
30489
+ blockingPath: path15
29734
30490
  };
29735
30491
  }
29736
- await probe.access(path16, directoryAccessMode(platform));
30492
+ await probe.access(path15, directoryAccessMode(platform));
29737
30493
  return {
29738
30494
  ok: true,
29739
- creatableFrom: path16
30495
+ creatableFrom: path15
29740
30496
  };
29741
30497
  } catch (error2) {
29742
30498
  if (!isMissingPathError(error2)) {
29743
30499
  return {
29744
30500
  ok: false,
29745
- creatableFrom: path16,
29746
- blockingPath: path16
30501
+ creatableFrom: path15,
30502
+ blockingPath: path15
29747
30503
  };
29748
30504
  }
29749
- const parent = dirname14(path16);
29750
- if (parent === path16) {
30505
+ const parent = dirname15(path15);
30506
+ if (parent === path15) {
29751
30507
  return {
29752
30508
  ok: false,
29753
- creatableFrom: path16,
29754
- blockingPath: path16
30509
+ creatableFrom: path15,
30510
+ blockingPath: path15
29755
30511
  };
29756
30512
  }
29757
30513
  const parentCheck = await checkCreatableAncestorDirectory(parent, probe, platform);
@@ -30169,7 +30925,7 @@ var init_render_doctor = __esm(() => {
30169
30925
 
30170
30926
  // src/doctor/doctor.ts
30171
30927
  import { homedir as homedir12 } from "node:os";
30172
- import { join as join15 } from "node:path";
30928
+ import { join as join18 } from "node:path";
30173
30929
  async function runDoctor(options = {}, deps = {}) {
30174
30930
  const home = deps.home ?? process.env.HOME ?? homedir12();
30175
30931
  const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
@@ -30224,8 +30980,8 @@ function resolveDoctorRuntimePaths(home, resolver) {
30224
30980
  return resolveRuntimePaths();
30225
30981
  }
30226
30982
  return {
30227
- configPath: join15(home, ".weacpx", "config.json"),
30228
- statePath: join15(home, ".weacpx", "state.json")
30983
+ configPath: join18(coreHomeDir(home), "config.json"),
30984
+ statePath: join18(coreHomeDir(home), "state.json")
30229
30985
  };
30230
30986
  }
30231
30987
  function depsUseExplicitRuntimeOverrides() {
@@ -30290,6 +31046,7 @@ function formatError9(error2) {
30290
31046
  return error2 instanceof Error ? error2.message : String(error2);
30291
31047
  }
30292
31048
  var init_doctor = __esm(async () => {
31049
+ init_core_home();
30293
31050
  init_load_config();
30294
31051
  init_state_store();
30295
31052
  init_daemon_check();
@@ -30323,6 +31080,7 @@ var init_doctor2 = __esm(async () => {
30323
31080
  });
30324
31081
 
30325
31082
  // src/cli.ts
31083
+ init_core_home();
30326
31084
  init_config_store();
30327
31085
  init_load_config();
30328
31086
  init_ensure_config();
@@ -30331,8 +31089,8 @@ init_create_daemon_controller();
30331
31089
  init_daemon_files();
30332
31090
  import { randomUUID as randomUUID4 } from "node:crypto";
30333
31091
  import { homedir as homedir13 } from "node:os";
30334
- import { dirname as dirname15, join as join16, sep } from "node:path";
30335
- import { fileURLToPath as fileURLToPath6 } from "node:url";
31092
+ import { dirname as dirname16, join as join19, sep } from "node:path";
31093
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
30336
31094
 
30337
31095
  // src/daemon/daemon-runtime.ts
30338
31096
  init_daemon_status();
@@ -42745,15 +43503,17 @@ class StdioServerTransport {
42745
43503
  init_version();
42746
43504
 
42747
43505
  // src/mcp/resolve-endpoint.ts
43506
+ init_core_home();
42748
43507
  init_daemon_files();
42749
43508
  init_orchestration_ipc();
42750
43509
  import { homedir as homedir2 } from "node:os";
43510
+ import { join as join4 } from "node:path";
42751
43511
  function resolveDefaultOrchestrationEndpoint(env = process.env, platform = process.platform) {
42752
43512
  if (typeof env.WEACPX_ORCHESTRATION_SOCKET === "string" && env.WEACPX_ORCHESTRATION_SOCKET.trim().length > 0) {
42753
43513
  return createOrchestrationEndpoint(env.WEACPX_ORCHESTRATION_SOCKET.trim(), platform);
42754
43514
  }
42755
43515
  const home = requireHome(env);
42756
- const configPath = typeof env.WEACPX_CONFIG === "string" && env.WEACPX_CONFIG.trim().length > 0 ? env.WEACPX_CONFIG.trim() : `${home}/.weacpx/config.json`;
43516
+ const configPath = typeof env.WEACPX_CONFIG === "string" && env.WEACPX_CONFIG.trim().length > 0 ? env.WEACPX_CONFIG.trim() : join4(coreHomeDir(home), "config.json");
42757
43517
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
42758
43518
  return resolveOrchestrationEndpoint(runtimeDir, platform);
42759
43519
  }
@@ -42853,16 +43613,15 @@ function buildWeacpxMcpToolRegistry(input) {
42853
43613
  },
42854
43614
  {
42855
43615
  name: "task_get",
42856
- description: "Fetch a single task under the current coordinator, including the worker's final result and any pending question. Use to inspect a task snapshot non-blockingly, read the actual output after completion, or inspect a task that requires attention.",
43616
+ description: "Fetch a single task under the current coordinator: its summary, latest progress, and — once terminal — the worker's final result. Prefer task_watch to follow a task; its terminal result already carries the output, so a follow-up task_get is unnecessary. Reach for task_get to recover a task you lost track of, to inspect one that requires attention, or to re-read the original delegated prompt. The full prompt is included only for needs_confirmation tasks unless you pass includePrompt:true.",
42857
43617
  inputSchema: exports_external.object({
42858
- taskId: exports_external.string().min(1)
43618
+ taskId: exports_external.string().min(1),
43619
+ includePrompt: exports_external.boolean().optional()
42859
43620
  }).strict(),
42860
43621
  handler: async (args) => await asToolResult(async () => {
42861
- const task = await transport.getTask({
42862
- coordinatorSession,
42863
- taskId: args.taskId
42864
- });
42865
- return createSuccessResult(task ? renderTaskSummary(task) : "Task not found.", { task });
43622
+ const { taskId, includePrompt } = args;
43623
+ const task = await transport.getTask({ coordinatorSession, taskId });
43624
+ return createSuccessResult(task ? renderTaskSummary(task, { includePrompt: includePrompt ?? false }) : "Task not found.", { task });
42866
43625
  })
42867
43626
  },
42868
43627
  {
@@ -43101,12 +43860,23 @@ function renderTaskWatchResult(result) {
43101
43860
  const events = result.events.length > 0 ? [
43102
43861
  "- Events:",
43103
43862
  ...result.events.map((event) => {
43104
- const detail = event.summary ?? event.message ?? event.status ?? "";
43105
- return ` - #${event.seq} ${event.type} at ${event.at}${detail ? `: ${detail}` : ""}`;
43863
+ const detail2 = event.summary ?? event.message ?? event.status ?? "";
43864
+ return ` - #${event.seq} ${event.type} at ${event.at}${detail2 ? `: ${detail2}` : ""}`;
43106
43865
  })
43107
43866
  ] : ["- Events: none"];
43108
- const next = result.status === "terminal" ? "Next: call task_get to read the final result." : result.status === "attention_required" ? "Next: call task_get to inspect openQuestion / reviewPending, then resolve it with the recommended action tool." : `Next: call task_watch again with afterSeq=${result.nextAfterSeq} to keep watching, preferably as an MCP task if your client supports background task execution.`;
43109
- return [...header, ...events, next].join(`
43867
+ const detail = [];
43868
+ if (result.status === "terminal") {
43869
+ const resultText = result.task.resultText?.trim() ?? "";
43870
+ const summary = result.task.summary?.trim() ?? "";
43871
+ if (resultText.length > 0)
43872
+ detail.push(`- Result: ${resultText}`);
43873
+ else if (summary.length > 0)
43874
+ detail.push(`- Summary: ${summary}`);
43875
+ } else if (result.status === "attention_required" && result.task.openQuestion) {
43876
+ detail.push(`- Open question: ${result.task.openQuestion.question}`);
43877
+ }
43878
+ const next = result.status === "terminal" ? "Next: summarize this result for the user." : result.status === "attention_required" ? "Next: resolve the pending question / contested review with the recommended action tool (coordinator_answer_question or coordinator_review_contested_result); call task_get only if you need more detail." : `Next: call task_watch again with afterSeq=${result.nextAfterSeq} to keep watching, preferably as an MCP task if your client supports background task execution.`;
43879
+ return [...header, ...events, ...detail, next].join(`
43110
43880
  `);
43111
43881
  }
43112
43882
  function createSuccessResult(text, structuredContent) {
@@ -43122,7 +43892,7 @@ function createErrorResult(message) {
43122
43892
  };
43123
43893
  }
43124
43894
  function renderDelegateSuccess(result) {
43125
- const next = result.status === "needs_confirmation" ? `Next: this delegation requires user approval. Tell the user, then call task_approve or task_cancel based on their response.` : result.status === "queued" ? `Next: task "${result.taskId}" is queued (agent at parallel capacity). It will start automatically when a slot frees. Call task_watch to long-poll for the transition to running, or task_get/task_list for non-blocking snapshots.` : `Next: task "${result.taskId}" is running. Return this taskId to the user, call task_get/task_list for non-blocking progress snapshots, or task_watch to long-poll for the next event or terminal state.`;
43895
+ const next = result.status === "needs_confirmation" ? `Next: this delegation requires user approval. Tell the user, then call task_approve or task_cancel based on their response.` : result.status === "queued" ? `Next: task "${result.taskId}" is queued (agent at parallel capacity). It will start automatically when a slot frees. Call task_watch to long-poll until it runs and then finishes — the terminal watch carries the result. Use task_list only to resurvey in-flight tasks.` : `Next: task "${result.taskId}" is running. Return this taskId to the user, then call task_watch to long-poll until it finishes the terminal watch carries the worker's result, so no follow-up task_get is needed. Use task_list only to resurvey in-flight tasks.`;
43126
43896
  return [`Delegation task "${result.taskId}" created.`, `- Status: ${result.status}`, next].join(`
43127
43897
  `);
43128
43898
  }
@@ -43166,7 +43936,7 @@ function renderTaskListItem(task) {
43166
43936
  ].filter(Boolean).map((item) => `; ${item}`).join("");
43167
43937
  return `- ${task.taskId} [${task.status}] ${task.targetAgent}${role} -> ${task.workerSession ?? "unassigned"}${group}${source}${summary}${reliability}`;
43168
43938
  }
43169
- function renderTaskSummary(task) {
43939
+ function renderTaskSummary(task, options = {}) {
43170
43940
  const header = [
43171
43941
  `Task "${task.taskId}"`,
43172
43942
  `- Status: ${task.status}`,
@@ -43181,7 +43951,9 @@ function renderTaskSummary(task) {
43181
43951
  if (task.status === "needs_confirmation") {
43182
43952
  header.push(`- Source: ${task.sourceKind} / ${task.sourceHandle}${task.role ? ` / ${task.role}` : ""}`);
43183
43953
  }
43184
- header.push(`- Task: ${task.task}`);
43954
+ if (task.status === "needs_confirmation" || options.includePrompt) {
43955
+ header.push(`- Task: ${task.task}`);
43956
+ }
43185
43957
  if (task.summary.trim().length > 0)
43186
43958
  header.push(`- Summary: ${task.summary}`);
43187
43959
  if (task.lastProgressSummary)
@@ -43232,7 +44004,7 @@ function renderTaskApprovalSuccess(task) {
43232
44004
  return [
43233
44005
  `Task "${task.taskId}" approved.`,
43234
44006
  `- Current status: ${task.status}`,
43235
- `Next: use task_get/task_list for non-blocking progress snapshots, or task_watch to long-poll until the worker finishes; then task_get to read the final result.`
44007
+ `Next: call task_watch to long-poll until the worker finishes the terminal watch returns the result directly, so no follow-up task_get is needed. Use task_list only to resurvey in-flight tasks.`
43236
44008
  ].join(`
43237
44009
  `);
43238
44010
  }
@@ -43730,13 +44502,24 @@ function renderWatchMcpTaskResult(result, watchTaskId) {
43730
44502
  const events = result.events.length > 0 ? [
43731
44503
  "Events:",
43732
44504
  ...result.events.map((event) => {
43733
- const detail = event.summary ?? event.message ?? event.status ?? "";
43734
- return `- #${event.seq} ${event.type} at ${event.at}${detail ? `: ${detail}` : ""}`;
44505
+ const detail2 = event.summary ?? event.message ?? event.status ?? "";
44506
+ return `- #${event.seq} ${event.type} at ${event.at}${detail2 ? `: ${detail2}` : ""}`;
43735
44507
  })
43736
44508
  ] : ["Events: none"];
43737
- const next = result.status === "terminal" ? "Next: call task_get on the watched task to read the final result." : result.status === "attention_required" ? "Next: call task_get on the watched task, then resolve openQuestion / reviewPending with the recommended action tool." : `Next: call task_watch again with afterSeq=${result.nextAfterSeq} to keep watching.`;
44509
+ const detail = [];
44510
+ if (result.status === "terminal") {
44511
+ const resultText = result.task.resultText.trim();
44512
+ const summary = result.task.summary.trim();
44513
+ if (resultText.length > 0)
44514
+ detail.push(`Result: ${resultText}`);
44515
+ else if (summary.length > 0)
44516
+ detail.push(`Summary: ${summary}`);
44517
+ } else if (result.status === "attention_required" && result.task.openQuestion) {
44518
+ detail.push(`Open question: ${result.task.openQuestion.question}`);
44519
+ }
44520
+ const next = result.status === "terminal" ? "Next: summarize this result for the user." : result.status === "attention_required" ? "Next: resolve the pending question / contested review with the recommended action tool (coordinator_answer_question or coordinator_review_contested_result)." : `Next: call task_watch again with afterSeq=${result.nextAfterSeq} to keep watching.`;
43738
44521
  return {
43739
- content: [{ type: "text", text: [...header, ...events, next].join(`
44522
+ content: [{ type: "text", text: [...header, ...events, ...detail, next].join(`
43740
44523
  `) }],
43741
44524
  structuredContent: { watchTaskId, ...result }
43742
44525
  };
@@ -44096,8 +44879,14 @@ function inferExternalCoordinatorSession(input) {
44096
44879
  return `external_${sanitizeMcpClientName(input.clientName)}:${suffix}`;
44097
44880
  }
44098
44881
  function sanitizeMcpClientName(input) {
44099
- const normalized = (input ?? "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
44100
- return normalized.length > 0 ? normalized : "mcp-host";
44882
+ return sanitizeString((input ?? "").trim(), {
44883
+ allow: /[a-z0-9._-]/,
44884
+ replacement: "-",
44885
+ lowercase: true,
44886
+ collapse: true,
44887
+ trim: true,
44888
+ fallback: "mcp-host"
44889
+ });
44101
44890
  }
44102
44891
 
44103
44892
  // src/mcp/parse-string-flag.ts
@@ -44222,12 +45011,15 @@ function resolveTemplateChoice(answer, names) {
44222
45011
  // src/cli-update.ts
44223
45012
  init_plugin_home();
44224
45013
  import { spawn as spawn4 } from "node:child_process";
44225
- import { readFile as readFile7 } from "node:fs/promises";
44226
- import { dirname as dirname9, join as join7 } from "node:path";
44227
- import { fileURLToPath as fileURLToPath2 } from "node:url";
45014
+ import { readFile as readFile8 } from "node:fs/promises";
45015
+ import { dirname as dirname10, join as join10 } from "node:path";
45016
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
44228
45017
 
44229
45018
  // src/plugins/package-manager.ts
45019
+ init_plugin_home();
44230
45020
  import { spawn as spawn3 } from "node:child_process";
45021
+ import { rm as rm4 } from "node:fs/promises";
45022
+ import { join as join6 } from "node:path";
44231
45023
  async function defaultRunCommand(command, args, options) {
44232
45024
  await new Promise((resolve, reject) => {
44233
45025
  const child = spawn3(command, args, { cwd: options.cwd, stdio: "inherit" });
@@ -44267,6 +45059,10 @@ async function detectPackageManager(runCommand) {
44267
45059
  async function installPluginPackage(input) {
44268
45060
  const runCommand = input.runCommand ?? defaultRunCommand;
44269
45061
  const packageManager = input.packageManager ?? await detectPackageManager();
45062
+ await normalizePluginHomeManifest(input.pluginHome);
45063
+ if (packageManager === "bun") {
45064
+ await rm4(join6(input.pluginHome, "bun.lock"), { force: true }).catch(() => {});
45065
+ }
44270
45066
  const spec = input.version ? `${input.packageName}@${input.version}` : input.packageName;
44271
45067
  if (packageManager === "bun") {
44272
45068
  await runCommand("bun", ["add", spec], { cwd: input.pluginHome });
@@ -44290,6 +45086,7 @@ async function removePluginPackage(input) {
44290
45086
  // src/cli-update.ts
44291
45087
  init_plugin_loader();
44292
45088
  init_validate_plugin();
45089
+ var SUCCESSOR = { from: "weacpx", package: "xacpx", minVersion: "0.8.0" };
44293
45090
  async function handleUpdateCli(args, deps) {
44294
45091
  let all = false;
44295
45092
  const explicitTargets = [];
@@ -44306,14 +45103,20 @@ async function handleUpdateCli(args, deps) {
44306
45103
  const config2 = await deps.loadConfig();
44307
45104
  const packageName = deps.packageName ?? await readPackageName();
44308
45105
  const latestOf = deps.getLatestVersion ?? getLatestNpmVersion;
44309
- const targets = [
44310
- {
44311
- kind: "self",
44312
- name: packageName,
44313
- currentVersion: deps.readCurrentVersion(),
44314
- latestVersion: await latestOf(packageName)
44315
- }
44316
- ];
45106
+ const successor = await resolveSuccessor(packageName, latestOf);
45107
+ const selfTarget = successor ? {
45108
+ kind: "self",
45109
+ name: packageName,
45110
+ currentVersion: deps.readCurrentVersion(),
45111
+ latestVersion: successor.version,
45112
+ successorPackage: successor.package
45113
+ } : {
45114
+ kind: "self",
45115
+ name: packageName,
45116
+ currentVersion: deps.readCurrentVersion(),
45117
+ latestVersion: await latestOf(packageName)
45118
+ };
45119
+ const targets = [selfTarget];
44317
45120
  for (const plugin of config2.plugins ?? []) {
44318
45121
  targets.push({
44319
45122
  kind: "plugin",
@@ -44333,7 +45136,7 @@ async function handleUpdateCli(args, deps) {
44333
45136
  deps.print(`以下项目无法检查最新版本,已取消更新:${unavailable.map((target) => target.name).join(", ")}`);
44334
45137
  return 1;
44335
45138
  }
44336
- const candidates = targets.filter((target) => target.latestVersion && (target.kind !== "plugin" || target.pinned) && target.currentVersion !== target.latestVersion);
45139
+ const candidates = targets.filter((target) => target.latestVersion && (target.kind !== "plugin" || target.pinned) && (target.successorPackage ? true : target.currentVersion !== target.latestVersion));
44337
45140
  const selected = await selectTargets(targets, candidates, { all, explicitTarget: explicitTargets[0], deps });
44338
45141
  if (!selected.ok) {
44339
45142
  deps.print(selected.message);
@@ -44344,6 +45147,8 @@ async function handleUpdateCli(args, deps) {
44344
45147
  return 0;
44345
45148
  }
44346
45149
  const selfUpdater = deps.updateSelf ?? defaultUpdateSelf;
45150
+ const selfMigrator = deps.migrateSelf ?? defaultMigrateSelf;
45151
+ const stopDaemon = deps.stopDaemon ?? (async () => {});
44347
45152
  const pluginHome = deps.pluginHome ?? resolvePluginHome();
44348
45153
  const pluginUpdater = deps.updatePlugin ?? (async (input) => {
44349
45154
  await ensurePluginHome(pluginHome);
@@ -44354,17 +45159,25 @@ async function handleUpdateCli(args, deps) {
44354
45159
  for (const target of selected.targets) {
44355
45160
  try {
44356
45161
  if (target.kind === "self") {
45162
+ const successorPackage = target.successorPackage;
44357
45163
  if (!all && !explicitTargets[0]) {
44358
45164
  if (!deps.isInteractive()) {
44359
- deps.print("更新 weacpx 本体需要确认;非交互模式请使用 `weacpx update --all` 或 `weacpx update weacpx`。");
45165
+ deps.print(successorPackage ? `weacpx 已更名为 ${successorPackage};非交互模式请使用 \`weacpx update --all\` 或 \`weacpx update weacpx\` 确认迁移。` : "更新 weacpx 本体需要确认;非交互模式请使用 `weacpx update --all` 或 `weacpx update weacpx`。");
44360
45166
  return 1;
44361
45167
  }
44362
- const answer = (await deps.promptText("确认更新 weacpx 本体?[y/N] ")).trim().toLowerCase();
45168
+ const question = successorPackage ? `weacpx 已更名为 ${successorPackage},确认迁移到 ${successorPackage}?[y/N] ` : "确认更新 weacpx 本体?[y/N] ";
45169
+ const answer = (await deps.promptText(question)).trim().toLowerCase();
44363
45170
  if (answer !== "y" && answer !== "yes") {
44364
- deps.print("已取消更新 weacpx 本体。");
45171
+ deps.print(successorPackage ? `已取消迁移到 ${successorPackage}。` : "已取消更新 weacpx 本体。");
44365
45172
  continue;
44366
45173
  }
44367
45174
  }
45175
+ if (successorPackage) {
45176
+ await stopDaemon();
45177
+ await selfMigrator({ from: target.name, to: successorPackage, toVersion: target.latestVersion ?? undefined });
45178
+ deps.print(`weacpx 已更名为 ${successorPackage},已迁移至 ${successorPackage} ${target.latestVersion ?? "latest"}。今后请使用 \`${successorPackage}\` 命令;若此前在后台运行,请用 \`${successorPackage} start\` 重新启动。`);
45179
+ continue;
45180
+ }
44368
45181
  await selfUpdater(target.name);
44369
45182
  deps.print(`weacpx 已更新:${target.latestVersion ?? "latest"}`);
44370
45183
  continue;
@@ -44404,19 +45217,21 @@ async function handleUpdateCli(args, deps) {
44404
45217
  function formatTarget(target) {
44405
45218
  const current = target.currentVersion ?? "未锁定";
44406
45219
  const latest = target.latestVersion ?? "无法检查";
44407
- const label = target.kind === "self" ? "weacpx" : `插件 ${target.name}`;
44408
- return `${label} (${current} -> ${latest})`;
45220
+ if (target.kind === "self") {
45221
+ return target.successorPackage ? `weacpx → ${target.successorPackage} (${current} -> ${latest},改名)` : `weacpx (${current} -> ${latest})`;
45222
+ }
45223
+ return `插件 ${target.name} (${current} -> ${latest})`;
44409
45224
  }
44410
45225
  async function selectTargets(targets, candidates, input) {
44411
45226
  if (input.explicitTarget) {
44412
- const target = targets.find((entry) => entry.name === input.explicitTarget || input.explicitTarget === "weacpx" && entry.kind === "self");
45227
+ const target = targets.find((entry) => entry.name === input.explicitTarget || entry.kind === "self" && (input.explicitTarget === "weacpx" || input.explicitTarget === entry.successorPackage));
44413
45228
  if (!target)
44414
45229
  return { ok: false, message: `没有找到更新项:${input.explicitTarget}`, exitCode: 1 };
44415
45230
  if (!target.latestVersion)
44416
45231
  return { ok: false, message: `${target.name} 无法检查最新版本,已跳过。`, exitCode: 1 };
44417
45232
  if (target.kind === "plugin" && !target.pinned)
44418
45233
  return { ok: false, message: `${target.name} 未记录当前版本;请先使用 \`weacpx plugin update ${target.name}\` 或显式选择版本。`, exitCode: 1 };
44419
- if (target.currentVersion === target.latestVersion)
45234
+ if (!target.successorPackage && target.currentVersion === target.latestVersion)
44420
45235
  return { ok: true, targets: [] };
44421
45236
  return { ok: true, targets: [target] };
44422
45237
  }
@@ -44441,7 +45256,7 @@ async function selectTargets(targets, candidates, input) {
44441
45256
  return { ok: false, message: `${target.name} 无法检查最新版本,已跳过。`, exitCode: 1 };
44442
45257
  if (target.kind === "plugin" && !target.pinned)
44443
45258
  return { ok: false, message: `${target.name} 未记录当前版本;请先使用 \`weacpx plugin update ${target.name}\` 或显式选择版本。`, exitCode: 1 };
44444
- if (target.currentVersion === target.latestVersion)
45259
+ if (!target.successorPackage && target.currentVersion === target.latestVersion)
44445
45260
  continue;
44446
45261
  if (!selected.includes(target))
44447
45262
  selected.push(target);
@@ -44470,6 +45285,45 @@ async function defaultUpdateSelf(packageName) {
44470
45285
  }
44471
45286
  await runInherit("npm", ["install", "-g", packageName]);
44472
45287
  }
45288
+ async function defaultMigrateSelf(input) {
45289
+ const manager = process.env.WEACPX_PACKAGE_MANAGER?.trim().toLowerCase() === "bun" ? "bun" : "npm";
45290
+ const spec = input.toVersion ? `${input.to}@${input.toVersion}` : `${input.to}@latest`;
45291
+ if (manager === "bun") {
45292
+ await runInherit("bun", ["add", "-g", spec]);
45293
+ await runInherit("bun", ["remove", "-g", input.from]);
45294
+ return;
45295
+ }
45296
+ await runInherit("npm", ["install", "-g", spec]);
45297
+ await runInherit("npm", ["uninstall", "-g", input.from]);
45298
+ }
45299
+ async function resolveSuccessor(currentPackage, latestOf) {
45300
+ if (currentPackage !== SUCCESSOR.from)
45301
+ return null;
45302
+ const version2 = await latestOf(SUCCESSOR.package);
45303
+ if (!version2 || !meetsMinVersion(version2, SUCCESSOR.minVersion))
45304
+ return null;
45305
+ return { package: SUCCESSOR.package, version: version2 };
45306
+ }
45307
+ function meetsMinVersion(candidate, min) {
45308
+ return compareSemver2(candidate, min) >= 0;
45309
+ }
45310
+ function compareSemver2(a, b) {
45311
+ const parse5 = (value) => {
45312
+ const match = /^\s*v?(\d+)\.(\d+)\.(\d+)(-[^\s]*)?/.exec(value);
45313
+ if (!match)
45314
+ return { nums: [0, 0, 0], prerelease: false };
45315
+ return { nums: [Number(match[1]), Number(match[2]), Number(match[3])], prerelease: Boolean(match[4]) };
45316
+ };
45317
+ const left = parse5(a);
45318
+ const right = parse5(b);
45319
+ for (let i = 0;i < 3; i += 1) {
45320
+ if (left.nums[i] !== right.nums[i])
45321
+ return left.nums[i] < right.nums[i] ? -1 : 1;
45322
+ }
45323
+ if (left.prerelease === right.prerelease)
45324
+ return 0;
45325
+ return left.prerelease ? -1 : 1;
45326
+ }
44473
45327
  async function runCapture(command, args) {
44474
45328
  return await new Promise((resolve, reject) => {
44475
45329
  const child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"] });
@@ -44501,10 +45355,10 @@ async function runInherit(command, args) {
44501
45355
  }
44502
45356
  async function readPackageName() {
44503
45357
  try {
44504
- const here = dirname9(fileURLToPath2(import.meta.url));
44505
- for (const candidate of [join7(here, "..", "package.json"), join7(here, "..", "..", "package.json")]) {
45358
+ const here = dirname10(fileURLToPath3(import.meta.url));
45359
+ for (const candidate of [join10(here, "..", "package.json"), join10(here, "..", "..", "package.json")]) {
44506
45360
  try {
44507
- const parsed = JSON.parse(await readFile7(candidate, "utf8"));
45361
+ const parsed = JSON.parse(await readFile8(candidate, "utf8"));
44508
45362
  if (typeof parsed.name === "string" && parsed.name.trim())
44509
45363
  return parsed.name.trim();
44510
45364
  } catch {}
@@ -44521,6 +45375,7 @@ async function validatePluginDefault(packageName, pluginHome) {
44521
45375
  init_version();
44522
45376
 
44523
45377
  // src/channels/cli/channel-cli.ts
45378
+ init_core_home();
44524
45379
  init_registry();
44525
45380
  init_create_channel();
44526
45381
  import { isDeepStrictEqual } from "node:util";
@@ -44815,7 +45670,7 @@ async function runRestart(deps) {
44815
45670
  } catch (error2) {
44816
45671
  const message = error2 instanceof Error ? error2.message : String(error2);
44817
45672
  deps.print(`配置已保存,但重启失败:${message}`);
44818
- deps.print("请查看日志:~/.weacpx/runtime/stderr.log");
45673
+ deps.print(`请查看日志:${coreHomeDisplayPath("runtime", "stderr.log")}`);
44819
45674
  deps.print("也可以稍后执行:weacpx start");
44820
45675
  return 1;
44821
45676
  }
@@ -45126,9 +45981,10 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
45126
45981
  }
45127
45982
 
45128
45983
  // src/plugins/plugin-cli.ts
45984
+ init_core_home();
45129
45985
  init_plugin_home();
45130
- import { readFile as readFile9 } from "node:fs/promises";
45131
- import { isAbsolute, join as join9, resolve } from "node:path";
45986
+ import { readFile as readFile10 } from "node:fs/promises";
45987
+ import { isAbsolute, join as join12, resolve } from "node:path";
45132
45988
  init_plugin_loader();
45133
45989
  init_validate_plugin();
45134
45990
 
@@ -45137,14 +45993,14 @@ init_channel_scope();
45137
45993
  init_plugin_loader();
45138
45994
  init_validate_plugin();
45139
45995
  init_known_plugins();
45140
- import { readFile as readFile8 } from "node:fs/promises";
45141
- import { join as join8 } from "node:path";
45996
+ import { readFile as readFile9 } from "node:fs/promises";
45997
+ import { join as join11 } from "node:path";
45142
45998
  function suggestedPluginPackageForChannel(type) {
45143
45999
  return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
45144
46000
  }
45145
46001
  async function readDependencyEntries(pluginHome) {
45146
46002
  try {
45147
- const raw = await readFile8(join8(pluginHome, "package.json"), "utf8");
46003
+ const raw = await readFile9(join11(pluginHome, "package.json"), "utf8");
45148
46004
  const parsed = JSON.parse(raw);
45149
46005
  const out = {};
45150
46006
  for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
@@ -45242,11 +46098,11 @@ async function inspectPlugins(input) {
45242
46098
  // src/plugins/plugin-cli.ts
45243
46099
  init_known_plugins();
45244
46100
  function looksLikePath(spec) {
45245
- return spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/") || isAbsolute(spec) || spec === ".";
46101
+ return spec === "." || spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/") || spec.startsWith(".\\") || spec.startsWith("..\\") || spec.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(spec) || isAbsolute(spec);
45246
46102
  }
45247
46103
  async function readDependencyEntries2(pluginHome) {
45248
46104
  try {
45249
- const raw = await readFile9(join9(pluginHome, "package.json"), "utf8");
46105
+ const raw = await readFile10(join12(pluginHome, "package.json"), "utf8");
45250
46106
  const parsed = JSON.parse(raw);
45251
46107
  const out = {};
45252
46108
  for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
@@ -45272,7 +46128,7 @@ async function resolveLocalPluginName(installSpec, pluginHome, namesBeforeInstal
45272
46128
  return name;
45273
46129
  }
45274
46130
  try {
45275
- const raw = await readFile9(join9(installSpec, "package.json"), "utf8");
46131
+ const raw = await readFile10(join12(installSpec, "package.json"), "utf8");
45276
46132
  const parsed = JSON.parse(raw);
45277
46133
  if (typeof parsed.name === "string" && parsed.name.trim())
45278
46134
  return parsed.name.trim();
@@ -45694,7 +46550,7 @@ async function runRestart2(deps) {
45694
46550
  return await deps.restartDaemon();
45695
46551
  } catch (error2) {
45696
46552
  deps.print(`配置已保存,但重启失败:${describeError(error2)}`);
45697
- deps.print("请查看日志:~/.weacpx/runtime/stderr.log");
46553
+ deps.print(`请查看日志:${coreHomeDisplayPath("runtime", "stderr.log")}`);
45698
46554
  deps.print("也可以稍后执行:weacpx start");
45699
46555
  return 1;
45700
46556
  }
@@ -45927,7 +46783,12 @@ async function runCli(args, deps = {}) {
45927
46783
  print,
45928
46784
  isInteractive: deps.isInteractive,
45929
46785
  promptText: deps.promptText,
45930
- overrides: deps.updateCliDeps
46786
+ overrides: {
46787
+ stopDaemon: async () => {
46788
+ await (deps.controller ?? createDefaultController(deps)).stop();
46789
+ },
46790
+ ...deps.updateCliDeps
46791
+ }
45931
46792
  })))(args.slice(1));
45932
46793
  if (result === null) {
45933
46794
  for (const line of HELP_LINES)
@@ -46373,7 +47234,7 @@ async function createCliScheduledTaskService() {
46373
47234
  return new ScheduledTaskService(state, stateStore);
46374
47235
  }
46375
47236
  function resolveConfigPathForCurrentEnv() {
46376
- return process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
47237
+ return process.env.WEACPX_CONFIG ?? join19(coreHomeDir(requireHome2()), "config.json");
46377
47238
  }
46378
47239
  function resolveDaemonPathsForCurrentConfig() {
46379
47240
  const configPath = resolveConfigPathForCurrentEnv();
@@ -46740,7 +47601,7 @@ function safeDaemonLogPaths() {
46740
47601
  const configPath = resolveConfigPathForCurrentEnv();
46741
47602
  const paths = resolveDaemonPathsForCurrentConfig();
46742
47603
  return {
46743
- appLog: join16(dirname15(configPath), "runtime", "app.log"),
47604
+ appLog: join19(dirname16(configPath), "runtime", "app.log"),
46744
47605
  stderrLog: paths.stderrLog
46745
47606
  };
46746
47607
  } catch {
@@ -46751,7 +47612,7 @@ function resolveCliEntryPath2() {
46751
47612
  if (process.argv[1]) {
46752
47613
  return process.argv[1];
46753
47614
  }
46754
- return fileURLToPath6(import.meta.url);
47615
+ return fileURLToPath7(import.meta.url);
46755
47616
  }
46756
47617
  function parseDoctorArgs(args) {
46757
47618
  const options = {};