weacpx 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -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");
@@ -2777,6 +2788,9 @@ class DaemonStatusStore {
2777
2788
  if (error.code === "ENOENT") {
2778
2789
  return null;
2779
2790
  }
2791
+ if (error instanceof SyntaxError) {
2792
+ return null;
2793
+ }
2780
2794
  throw error;
2781
2795
  }
2782
2796
  }
@@ -3151,7 +3165,7 @@ var init_create_daemon_controller = __esm(() => {
3151
3165
 
3152
3166
  // src/orchestration/orchestration-ipc.ts
3153
3167
  import { createHash } from "node:crypto";
3154
- import { join } from "node:path";
3168
+ import { join as join2 } from "node:path";
3155
3169
  function resolveOrchestrationEndpoint(runtimeDir, platform = process.platform) {
3156
3170
  if (platform === "win32") {
3157
3171
  const suffix = createHash("sha256").update(runtimeDir).digest("hex").slice(0, 12);
@@ -3162,7 +3176,7 @@ function resolveOrchestrationEndpoint(runtimeDir, platform = process.platform) {
3162
3176
  }
3163
3177
  return {
3164
3178
  kind: "unix",
3165
- path: join(runtimeDir, "orchestration.sock")
3179
+ path: join2(runtimeDir, "orchestration.sock")
3166
3180
  };
3167
3181
  }
3168
3182
  function createOrchestrationEndpoint(path2, platform = process.platform) {
@@ -3182,25 +3196,26 @@ function encodeOrchestrationRpcResponse(response) {
3182
3196
  var init_orchestration_ipc = () => {};
3183
3197
 
3184
3198
  // src/daemon/daemon-files.ts
3185
- import { dirname as dirname4, join as join2 } from "node:path";
3199
+ import { dirname as dirname4, join as join3 } from "node:path";
3186
3200
  function resolveDaemonPaths(options) {
3187
- 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"));
3188
3202
  return {
3189
3203
  runtimeDir,
3190
- pidFile: join2(runtimeDir, "daemon.pid"),
3191
- statusFile: join2(runtimeDir, "status.json"),
3192
- stdoutLog: join2(runtimeDir, "stdout.log"),
3193
- stderrLog: join2(runtimeDir, "stderr.log"),
3194
- 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")
3195
3209
  };
3196
3210
  }
3197
3211
  function resolveRuntimeDirFromConfigPath(configPath) {
3198
- return join2(dirname4(configPath), "runtime");
3212
+ return join3(dirname4(configPath), "runtime");
3199
3213
  }
3200
3214
  function resolveDaemonOrchestrationSocketPath(runtimeDir, platform = process.platform) {
3201
3215
  return resolveOrchestrationEndpoint(runtimeDir, platform).path;
3202
3216
  }
3203
3217
  var init_daemon_files = __esm(() => {
3218
+ init_core_home();
3204
3219
  init_orchestration_ipc();
3205
3220
  });
3206
3221
 
@@ -10524,9 +10539,56 @@ function normalizeId(input) {
10524
10539
  var init_scheduled_service = () => {};
10525
10540
 
10526
10541
  // src/plugins/plugin-home.ts
10527
- import { mkdir as mkdir6, readFile as readFile6, 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";
10528
10544
  import { homedir as homedir3 } from "node:os";
10529
- 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
+ }
10530
10592
  function coerceMissing(value) {
10531
10593
  if (value === undefined)
10532
10594
  return;
@@ -10546,10 +10608,10 @@ function resolvePluginHome(input = {}) {
10546
10608
  if (envOverride)
10547
10609
  return envOverride;
10548
10610
  const home = coerceMissing(input.home) ?? coerceMissing(process.env.HOME) ?? homedir3();
10549
- return join3(home, ".weacpx", "plugins");
10611
+ return join5(coreHomeDir(home), "plugins");
10550
10612
  }
10551
10613
  async function normalizePluginHomeManifest(pluginHome) {
10552
- const manifestPath = join3(pluginHome, "package.json");
10614
+ const manifestPath = join5(pluginHome, "package.json");
10553
10615
  let raw;
10554
10616
  try {
10555
10617
  raw = await readFile6(manifestPath, "utf8");
@@ -10571,13 +10633,18 @@ async function normalizePluginHomeManifest(pluginHome) {
10571
10633
  }
10572
10634
  async function ensurePluginHome(pluginHome) {
10573
10635
  await mkdir6(pluginHome, { recursive: true, mode: 448 });
10574
- 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) + `
10575
10637
  `, { flag: "wx" }).catch((error2) => {
10576
10638
  if (error2.code !== "EEXIST")
10577
10639
  throw error2;
10578
10640
  });
10641
+ await ensureCoreResolution(pluginHome);
10579
10642
  }
10580
- 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
+ });
10581
10648
 
10582
10649
  // src/weixin/storage/ensure-dir.ts
10583
10650
  import fs2 from "node:fs";
@@ -12820,11 +12887,13 @@ function resolveContextTokenFilePath(accountId) {
12820
12887
  return path6.join(resolveStateDir(), "openclaw-weixin", "accounts", `${accountId}.context-tokens.json`);
12821
12888
  }
12822
12889
  function persistContextTokens(accountId) {
12890
+ pruneContextTokensForAccount(accountId);
12823
12891
  const prefix = `${accountId}:`;
12824
12892
  const tokens = {};
12825
- for (const [k, v] of contextTokenStore) {
12826
- if (k.startsWith(prefix))
12827
- 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
+ }
12828
12897
  }
12829
12898
  const filePath = resolveContextTokenFilePath(accountId);
12830
12899
  try {
@@ -12841,12 +12910,15 @@ function restoreContextTokens(accountId) {
12841
12910
  const raw = fs5.readFileSync(filePath, "utf-8");
12842
12911
  const tokens = JSON.parse(raw);
12843
12912
  let count = 0;
12844
- for (const [userId, token] of Object.entries(tokens)) {
12845
- if (typeof token === "string" && token) {
12846
- 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);
12847
12917
  count++;
12848
12918
  }
12849
12919
  }
12920
+ pruneContextTokensForAccount(accountId);
12921
+ persistContextTokens(accountId);
12850
12922
  logger.info(`restoreContextTokens: restored ${count} tokens for account=${accountId}`);
12851
12923
  } catch (err) {
12852
12924
  logger.warn(`restoreContextTokens: failed to read ${filePath}: ${String(err)}`);
@@ -12870,15 +12942,64 @@ function clearContextTokensForAccount(accountId) {
12870
12942
  function setContextToken(accountId, userId, token) {
12871
12943
  const k = contextTokenKey(accountId, userId);
12872
12944
  logger.debug(`setContextToken: key=${k}`);
12873
- contextTokenStore.set(k, token);
12945
+ contextTokenStore.set(k, { token, updatedAt: contextTokenRetention.now() });
12874
12946
  persistContextTokens(accountId);
12875
12947
  }
12876
12948
  function getContextToken(accountId, userId) {
12877
12949
  const k = contextTokenKey(accountId, normalizeWeixinUserIdFromChatKey(userId));
12878
- const val = contextTokenStore.get(k);
12950
+ pruneContextTokensForAccount(accountId);
12951
+ const val = contextTokenStore.get(k)?.token;
12879
12952
  logger.debug(`getContextToken: key=${k} found=${val !== undefined} storeSize=${contextTokenStore.size}`);
12880
12953
  return val;
12881
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
+ }
12882
13003
  function normalizeWeixinUserIdFromChatKey(chatKey) {
12883
13004
  const parts = chatKey.split(":");
12884
13005
  if (parts[0] === "weixin" && parts[2]) {
@@ -12945,14 +13066,16 @@ function descriptorFromItem(item) {
12945
13066
  return { item, kind: "audio" };
12946
13067
  return;
12947
13068
  }
12948
- var contextTokenStore;
13069
+ var DEFAULT_CONTEXT_TOKEN_MAX_PER_ACCOUNT = 5000, DEFAULT_CONTEXT_TOKEN_TTL_MS, contextTokenStore, contextTokenRetention;
12949
13070
  var init_inbound = __esm(() => {
12950
13071
  init_logger();
12951
13072
  init_random();
12952
13073
  init_types2();
12953
13074
  init_state_dir();
12954
13075
  init_private_file();
13076
+ DEFAULT_CONTEXT_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000;
12955
13077
  contextTokenStore = new Map;
13078
+ contextTokenRetention = normalizeContextTokenRetention();
12956
13079
  });
12957
13080
 
12958
13081
  // src/weixin/api/config-cache.ts
@@ -12960,18 +13083,27 @@ class WeixinConfigManager {
12960
13083
  apiOpts;
12961
13084
  log;
12962
13085
  cache = new Map;
12963
- constructor(apiOpts, log) {
13086
+ maxEntries;
13087
+ entryTtlMs;
13088
+ now;
13089
+ fetchConfig;
13090
+ constructor(apiOpts, log, options = {}) {
12964
13091
  this.apiOpts = apiOpts;
12965
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;
12966
13097
  }
12967
13098
  async getForUser(userId, contextToken) {
12968
- const now = Date.now();
13099
+ const now = this.now();
13100
+ this.prune(now);
12969
13101
  const entry = this.cache.get(userId);
12970
13102
  const shouldFetch = !entry || now >= entry.nextFetchAt;
12971
13103
  if (shouldFetch) {
12972
13104
  let fetchOk = false;
12973
13105
  try {
12974
- const resp = await getConfig({
13106
+ const resp = await this.fetchConfig({
12975
13107
  baseUrl: this.apiOpts.baseUrl,
12976
13108
  token: this.apiOpts.token,
12977
13109
  ilinkUserId: userId,
@@ -12982,7 +13114,8 @@ class WeixinConfigManager {
12982
13114
  config: { typingTicket: resp.typing_ticket ?? "" },
12983
13115
  everSucceeded: true,
12984
13116
  nextFetchAt: now + Math.random() * CONFIG_CACHE_TTL_MS,
12985
- retryDelayMs: CONFIG_CACHE_INITIAL_RETRY_MS
13117
+ retryDelayMs: CONFIG_CACHE_INITIAL_RETRY_MS,
13118
+ lastTouchedAt: now
12986
13119
  });
12987
13120
  this.log(`[weixin] config ${entry?.everSucceeded ? "refreshed" : "cached"} for ${userId}`);
12988
13121
  fetchOk = true;
@@ -12996,24 +13129,72 @@ class WeixinConfigManager {
12996
13129
  if (entry) {
12997
13130
  entry.nextFetchAt = now + nextDelay;
12998
13131
  entry.retryDelayMs = nextDelay;
13132
+ entry.lastTouchedAt = now;
12999
13133
  } else {
13000
13134
  this.cache.set(userId, {
13001
13135
  config: { typingTicket: "" },
13002
13136
  everSucceeded: false,
13003
13137
  nextFetchAt: now + CONFIG_CACHE_INITIAL_RETRY_MS,
13004
- retryDelayMs: CONFIG_CACHE_INITIAL_RETRY_MS
13138
+ retryDelayMs: CONFIG_CACHE_INITIAL_RETRY_MS,
13139
+ lastTouchedAt: now
13005
13140
  });
13006
13141
  }
13007
13142
  }
13143
+ } else {
13144
+ entry.lastTouchedAt = now;
13008
13145
  }
13146
+ this.enforceMaxEntries();
13009
13147
  return this.cache.get(userId)?.config ?? { typingTicket: "" };
13010
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);
13186
+ }
13187
+ function normalizeNonNegativeMs2(value, fallback) {
13188
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
13189
+ return fallback;
13190
+ return value;
13011
13191
  }
13012
- var CONFIG_CACHE_TTL_MS, CONFIG_CACHE_INITIAL_RETRY_MS = 2000, CONFIG_CACHE_MAX_RETRY_MS;
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;
13013
13193
  var init_config_cache = __esm(() => {
13014
13194
  init_api();
13015
13195
  CONFIG_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
13016
13196
  CONFIG_CACHE_MAX_RETRY_MS = 60 * 60 * 1000;
13197
+ CONFIG_CACHE_ENTRY_TTL_MS = 7 * 24 * 60 * 60 * 1000;
13017
13198
  });
13018
13199
 
13019
13200
  // src/weixin/api/session-guard.ts
@@ -13032,24 +13213,24 @@ var init_session_guard = __esm(() => {
13032
13213
  pauseUntilMap = new Map;
13033
13214
  });
13034
13215
 
13035
- // src/weixin/messaging/conversation-executor.ts
13216
+ // src/runtime/conversation-executor.ts
13036
13217
  function createConversationExecutor() {
13037
13218
  const states = new Map;
13038
13219
  const getState = (conversationId) => {
13039
13220
  const existing = states.get(conversationId);
13040
13221
  if (existing)
13041
13222
  return existing;
13042
- const created = { activeControls: 0 };
13223
+ const created = { normalTails: new Map, activeControls: 0 };
13043
13224
  states.set(conversationId, created);
13044
13225
  return created;
13045
13226
  };
13046
13227
  const cleanupState = (conversationId, state) => {
13047
- if (!state.normalTail && state.activeControls === 0) {
13228
+ if (state.normalTails.size === 0 && state.activeControls === 0) {
13048
13229
  states.delete(conversationId);
13049
13230
  }
13050
13231
  };
13051
13232
  return {
13052
- run(conversationId, lane, task) {
13233
+ run(conversationId, lane, task, sessionKey) {
13053
13234
  const state = getState(conversationId);
13054
13235
  if (lane === "control") {
13055
13236
  state.activeControls += 1;
@@ -13058,23 +13239,23 @@ function createConversationExecutor() {
13058
13239
  cleanupState(conversationId, state);
13059
13240
  });
13060
13241
  }
13061
- const previous = state.normalTail ?? Promise.resolve();
13062
- const next = previous.catch(() => {
13063
- return;
13064
- }).then(task);
13065
- 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);
13066
13246
  return next.finally(() => {
13067
- if (state.normalTail === next) {
13068
- state.normalTail = undefined;
13247
+ if (state.normalTails.get(key) === next) {
13248
+ state.normalTails.delete(key);
13069
13249
  }
13070
13250
  cleanupState(conversationId, state);
13071
13251
  });
13072
13252
  }
13073
13253
  };
13074
13254
  }
13255
+ var DEFAULT_SESSION_KEY = "__chat__";
13075
13256
 
13076
13257
  // src/channels/media-store.ts
13077
- 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";
13078
13259
  import path7 from "node:path";
13079
13260
 
13080
13261
  class RuntimeMediaStore {
@@ -13193,7 +13374,7 @@ async function cleanupDir(dir, cutoffMs) {
13193
13374
  if (entry.isDirectory()) {
13194
13375
  const childEmpty = await cleanupDir(full, cutoffMs);
13195
13376
  if (childEmpty) {
13196
- await rm4(full, { recursive: true, force: true });
13377
+ await rm5(full, { recursive: true, force: true });
13197
13378
  } else {
13198
13379
  empty = false;
13199
13380
  }
@@ -13203,7 +13384,7 @@ async function cleanupDir(dir, cutoffMs) {
13203
13384
  if (!s)
13204
13385
  continue;
13205
13386
  if (s.mtimeMs < cutoffMs) {
13206
- await rm4(full, { force: true });
13387
+ await rm5(full, { force: true });
13207
13388
  } else {
13208
13389
  empty = false;
13209
13390
  }
@@ -13568,6 +13749,18 @@ var init_media_download = __esm(() => {
13568
13749
  WEIXIN_MEDIA_MAX_BYTES = 100 * 1024 * 1024;
13569
13750
  });
13570
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
+
13571
13764
  // src/weixin/messaging/execute-chat-turn.ts
13572
13765
  async function executeChatTurn(params) {
13573
13766
  let usedReply = false;
@@ -13595,6 +13788,16 @@ function buildFinalHeadsUp(input) {
13595
13788
  \uD83D\uDCC4 结果共 ${total} 段,已发 ${sentSoFar} 段。回复 /jx 续看后 ${remaining} 段。`;
13596
13789
  }
13597
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
+
13598
13801
  // src/weixin/messaging/markdown-filter.ts
13599
13802
  class StreamingMarkdownFilter {
13600
13803
  buf = "";
@@ -14491,8 +14694,8 @@ function normalizeMediaArray(media) {
14491
14694
  }
14492
14695
 
14493
14696
  // src/logging/rotating-file-writer.ts
14494
- import { readdir as readdir2, rename, rm as rm5, stat as stat2 } from "node:fs/promises";
14495
- 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";
14496
14699
  async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
14497
14700
  let currentSize = 0;
14498
14701
  try {
@@ -14509,10 +14712,10 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
14509
14712
  return;
14510
14713
  }
14511
14714
  if (maxFiles <= 0) {
14512
- await rm5(filePath, { force: true });
14715
+ await rm6(filePath, { force: true });
14513
14716
  return;
14514
14717
  }
14515
- await rm5(`${filePath}.${maxFiles}`, { force: true });
14718
+ await rm6(`${filePath}.${maxFiles}`, { force: true });
14516
14719
  for (let index = maxFiles - 1;index >= 1; index -= 1) {
14517
14720
  const source = `${filePath}.${index}`;
14518
14721
  try {
@@ -14526,7 +14729,7 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
14526
14729
  await rename(filePath, `${filePath}.1`);
14527
14730
  }
14528
14731
  async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
14529
- const parentDir = dirname6(filePath);
14732
+ const parentDir = dirname7(filePath);
14530
14733
  const prefix = `${basename(filePath)}.`;
14531
14734
  const cutoff = now().getTime() - retentionDays * 24 * 60 * 60 * 1000;
14532
14735
  let files = [];
@@ -14542,10 +14745,10 @@ async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
14542
14745
  if (!file.startsWith(prefix) || !/^\d+$/.test(file.slice(prefix.length))) {
14543
14746
  continue;
14544
14747
  }
14545
- const candidate = join4(parentDir, file);
14748
+ const candidate = join7(parentDir, file);
14546
14749
  const details = await stat2(candidate);
14547
14750
  if (details.mtime.getTime() < cutoff) {
14548
- await rm5(candidate, { force: true });
14751
+ await rm6(candidate, { force: true });
14549
14752
  }
14550
14753
  }
14551
14754
  }
@@ -14556,7 +14759,7 @@ var init_rotating_file_writer = () => {};
14556
14759
 
14557
14760
  // src/perf/perf-log-writer.ts
14558
14761
  import { appendFile as fsAppendFile, mkdir as fsMkdir } from "node:fs/promises";
14559
- import { dirname as dirname7 } from "node:path";
14762
+ import { dirname as dirname8 } from "node:path";
14560
14763
  function createPerfLogWriter(options) {
14561
14764
  const append = options.appendImpl ?? ((p, d) => fsAppendFile(p, d, "utf8"));
14562
14765
  const mkdir8 = options.mkdirImpl ?? ((p, o) => fsMkdir(p, o).then(() => {
@@ -14606,7 +14809,7 @@ function createPerfLogWriter(options) {
14606
14809
  return;
14607
14810
  const data = batch.join("");
14608
14811
  try {
14609
- await mkdir8(dirname7(options.filePath), { recursive: true });
14812
+ await mkdir8(dirname8(options.filePath), { recursive: true });
14610
14813
  await rotateIfNeeded(options.filePath, Buffer.byteLength(data), options.maxSizeBytes, options.maxFiles);
14611
14814
  await append(options.filePath, data);
14612
14815
  consecutiveFailures = 0;
@@ -14916,7 +15119,9 @@ function isSlashCommandText(textBody) {
14916
15119
  }
14917
15120
  function getWeixinMessageTurnLane(full) {
14918
15121
  const textBody = extractTextBody(full.item_list).trim().toLowerCase();
14919
- 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";
14920
15125
  }
14921
15126
  function buildWeixinChatKey(accountId, userId) {
14922
15127
  return `weixin:${accountId}:${userId}`;
@@ -15066,6 +15271,9 @@ async function handleWeixinMessageTurn(full, deps) {
15066
15271
  }
15067
15272
  let midFirstSent = false;
15068
15273
  const sendReplySegment = async (text) => {
15274
+ if (!shouldDeliverSegment(deps.isForeground)) {
15275
+ return false;
15276
+ }
15069
15277
  const plainText = markdownToPlainText(text).trim();
15070
15278
  if (plainText.length === 0) {
15071
15279
  return false;
@@ -15097,7 +15305,8 @@ async function handleWeixinMessageTurn(full, deps) {
15097
15305
  channel: "weixin",
15098
15306
  chatType: full.group_id ? "group" : "direct",
15099
15307
  ...full.from_user_id ? { senderId: full.from_user_id } : {},
15100
- ...full.group_id ? { groupId: full.group_id } : {}
15308
+ ...full.group_id ? { groupId: full.group_id } : {},
15309
+ ...deps.boundSessionAlias ? { boundSessionAlias: deps.boundSessionAlias } : {}
15101
15310
  },
15102
15311
  perfSpan
15103
15312
  };
@@ -15115,80 +15324,96 @@ async function handleWeixinMessageTurn(full, deps) {
15115
15324
  if (turn.text) {
15116
15325
  const finalText = markdownToPlainText(turn.text).trim();
15117
15326
  if (finalText.length > 0) {
15118
- const rawChunks = chunkFinalText(finalText, MAX_FINAL_CHUNK_BYTES);
15119
- if (rawChunks.length > 0) {
15120
- const total = rawChunks.length;
15121
- if (total === 1) {
15122
- const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
15123
- if (!reserved) {
15124
- finalDropped = true;
15125
- deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text chatKey=${to}`);
15126
- } else {
15127
- await sendMessageWeixin({
15128
- to,
15129
- text: rawChunks[0],
15130
- opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
15131
- });
15132
- finalChunksSent += 1;
15133
- if (!finalFirstSent) {
15134
- finalFirstSent = true;
15135
- perfSpan.mark("reply.final_first_sent", { bytes: utf8ByteLength(rawChunks[0]), chunkIndex: 1 });
15136
- }
15137
- }
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)}`));
15138
15336
  } else {
15139
- const prefixed = rawChunks.map((body, i) => `(${i + 1}/${total}) ${body}`);
15140
- const available = deps.finalRemaining ? deps.finalRemaining(to) : total;
15141
- const waveSize = Math.max(Math.min(available, total), 0);
15142
- const wave = prefixed.slice(0, waveSize);
15143
- const rest = prefixed.slice(waveSize);
15144
- if (wave.length > 0 && rest.length > 0) {
15145
- const sentSoFar = wave.length;
15146
- wave[wave.length - 1] = `${wave[wave.length - 1]}
15147
-
15148
- ${buildFinalHeadsUp({
15149
- total,
15150
- sentSoFar
15151
- })}`;
15152
- }
15153
- let sent = 0;
15154
- 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) {
15155
15346
  const reserved = deps.reserveFinal ? deps.reserveFinal(to) : true;
15156
15347
  if (!reserved) {
15157
15348
  finalDropped = true;
15158
- deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text_paginated chatKey=${to} chunk=${i + 1}/${total}`);
15159
- break;
15160
- }
15161
- try {
15349
+ deps.errLog(`weixin.final.dropped reason=quota_exhausted kind=text chatKey=${to}`);
15350
+ } else {
15162
15351
  await sendMessageWeixin({
15163
15352
  to,
15164
- text: wave[i],
15353
+ text: rawChunks[0],
15165
15354
  opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken }
15166
15355
  });
15167
- sent += 1;
15168
15356
  finalChunksSent += 1;
15169
15357
  if (!finalFirstSent) {
15170
15358
  finalFirstSent = true;
15171
- 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 });
15172
15360
  }
15173
- } catch (sendErr) {
15174
- finalDropped = true;
15175
- deps.errLog(`weixin.final.dropped reason=send_failed kind=text_paginated chatKey=${to} chunk=${i + 1}/${total} err=${String(sendErr)}`);
15176
- break;
15177
15361
  }
15178
- }
15179
- const restToPark = prefixed.slice(sent);
15180
- finalChunksPending = restToPark.length;
15181
- if (restToPark.length > 0 && deps.enqueuePendingFinal) {
15182
- const pending = restToPark.map((text, idx) => {
15183
- const seq = sent + idx + 1;
15184
- const entry = { text, seq, total };
15185
- if (contextToken !== undefined)
15186
- entry.contextToken = contextToken;
15187
- if (deps.accountId !== undefined)
15188
- entry.accountId = deps.accountId;
15189
- return entry;
15190
- });
15191
- 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
+ }
15192
15417
  }
15193
15418
  }
15194
15419
  }
@@ -15259,18 +15484,35 @@ ${buildFinalHeadsUp({
15259
15484
  perfSpan.setOutcome("error", { reason: "turn_error" });
15260
15485
  const errorText = err instanceof Error ? err.stack ?? err.message : JSON.stringify(err);
15261
15486
  deps.errLog(`handleWeixinMessageTurn: agent or send failed: ${errorText}`);
15262
- const reservedErr = deps.reserveFinal ? deps.reserveFinal(to) : true;
15263
- if (!reservedErr) {
15264
- 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}`);
15265
15502
  } else {
15266
- sendWeixinErrorNotice({
15267
- to,
15268
- contextToken,
15269
- message: `⚠️ 执行出错:${err instanceof Error ? err.message : JSON.stringify(err)}`,
15270
- baseUrl: deps.baseUrl,
15271
- token: deps.token,
15272
- errLog: deps.errLog
15273
- });
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
+ }
15274
15516
  }
15275
15517
  } finally {
15276
15518
  stopTypingIndicator();
@@ -15288,6 +15530,7 @@ var init_handle_weixin_message_turn = __esm(() => {
15288
15530
  init_outbound_media_safety();
15289
15531
  init_media_download();
15290
15532
  init_mime();
15533
+ init_completion_notice();
15291
15534
  init_inbound();
15292
15535
  init_error_notice();
15293
15536
  init_send_media();
@@ -15489,7 +15732,15 @@ async function monitorWeixinProvider(opts) {
15489
15732
  }
15490
15733
  }
15491
15734
  }
15492
- 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, {
15493
15744
  accountId,
15494
15745
  agent,
15495
15746
  baseUrl,
@@ -15507,9 +15758,25 @@ async function monitorWeixinProvider(opts) {
15507
15758
  ...opts.prependPendingFinal ? { prependPendingFinal: opts.prependPendingFinal } : {},
15508
15759
  ...opts.mediaStore ? { mediaStore: opts.mediaStore } : {},
15509
15760
  ...opts.allowedMediaRoots ? { allowedMediaRoots: opts.allowedMediaRoots } : {},
15510
- ...opts.perfTracer ? { perfTracer: opts.perfTracer } : {}
15511
- })).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) => {
15512
15775
  errLog(`[weixin] message turn failed: ${String(err)}`);
15776
+ }).finally(() => {
15777
+ if (boundAlias) {
15778
+ opts.activeTurns?.markInactive(chatKey, boundAlias);
15779
+ }
15513
15780
  });
15514
15781
  }
15515
15782
  } catch (err) {
@@ -15693,7 +15960,10 @@ async function start(agent, opts) {
15693
15960
  ...opts?.enqueuePendingFinal ? { enqueuePendingFinal: opts.enqueuePendingFinal } : {},
15694
15961
  ...opts?.dropPendingFinal ? { dropPendingFinal: opts.dropPendingFinal } : {},
15695
15962
  ...opts?.mediaStore ? { mediaStore: opts.mediaStore } : {},
15696
- ...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 } : {}
15697
15967
  });
15698
15968
  }
15699
15969
  var init_bot = __esm(() => {
@@ -16125,16 +16395,16 @@ var init_scheduled_turn = __esm(() => {
16125
16395
  });
16126
16396
 
16127
16397
  // src/weixin/monitor/consumer-lock.ts
16128
- import { mkdir as mkdir8, open as open3, readFile as readFile7, rm as rm6 } from "node:fs/promises";
16129
- 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";
16130
16400
  import { homedir as homedir4 } from "node:os";
16131
16401
  function createWeixinConsumerLock(options = {}) {
16132
- 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");
16133
16403
  const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning4;
16134
16404
  const onDiagnostic = options.onDiagnostic;
16135
16405
  return {
16136
16406
  async acquire(meta2) {
16137
- await mkdir8(dirname8(lockFilePath), { recursive: true });
16407
+ await mkdir8(dirname9(lockFilePath), { recursive: true });
16138
16408
  while (true) {
16139
16409
  try {
16140
16410
  const handle = await open3(lockFilePath, "wx");
@@ -16165,7 +16435,7 @@ function createWeixinConsumerLock(options = {}) {
16165
16435
  });
16166
16436
  const existing = await loadLockMetadata(lockFilePath);
16167
16437
  if (!existing) {
16168
- await rm6(lockFilePath, { force: true });
16438
+ await rm7(lockFilePath, { force: true });
16169
16439
  await onDiagnostic?.("lock_invalid_removed", {
16170
16440
  lockFilePath,
16171
16441
  reason: "invalid_or_unreadable_metadata"
@@ -16173,7 +16443,7 @@ function createWeixinConsumerLock(options = {}) {
16173
16443
  continue;
16174
16444
  }
16175
16445
  if (!isProcessRunning(existing.pid)) {
16176
- await rm6(lockFilePath, { force: true });
16446
+ await rm7(lockFilePath, { force: true });
16177
16447
  await onDiagnostic?.("lock_stale_removed", {
16178
16448
  lockFilePath,
16179
16449
  stalePid: existing.pid,
@@ -16198,7 +16468,7 @@ function createWeixinConsumerLock(options = {}) {
16198
16468
  }
16199
16469
  },
16200
16470
  async release() {
16201
- await rm6(lockFilePath, { force: true });
16471
+ await rm7(lockFilePath, { force: true });
16202
16472
  await onDiagnostic?.("lock_released", {
16203
16473
  lockFilePath
16204
16474
  });
@@ -16227,6 +16497,7 @@ function defaultIsProcessRunning4(pid) {
16227
16497
  }
16228
16498
  var ActiveWeixinConsumerLockError;
16229
16499
  var init_consumer_lock = __esm(() => {
16500
+ init_core_home();
16230
16501
  ActiveWeixinConsumerLockError = class ActiveWeixinConsumerLockError extends Error {
16231
16502
  existing;
16232
16503
  lockFilePath;
@@ -16289,6 +16560,7 @@ class WeixinChannel {
16289
16560
  console.log("[weacpx] 未检测到登录凭证,正在启动扫码登录...");
16290
16561
  await this.login();
16291
16562
  }
16563
+ const sessions = input.sessions;
16292
16564
  await start(input.agent, {
16293
16565
  abortSignal: input.abortSignal,
16294
16566
  ...this.mediaStore ? { mediaStore: this.mediaStore } : {},
@@ -16301,7 +16573,12 @@ class WeixinChannel {
16301
16573
  prependPendingFinal: (chatKey, chunks) => input.quota.prependPendingFinal(chatKey, chunks),
16302
16574
  enqueuePendingFinal: (chatKey, chunks) => input.quota.enqueuePendingFinal(chatKey, chunks),
16303
16575
  dropPendingFinal: (chatKey) => input.quota.clearPendingFinal(chatKey),
16304
- ...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 } : {}
16305
16582
  });
16306
16583
  }
16307
16584
  async notifyTaskCompletion(task) {
@@ -16756,9 +17033,9 @@ __export(exports_plugin_loader, {
16756
17033
  });
16757
17034
  import { createRequire as createRequire2 } from "node:module";
16758
17035
  import { pathToFileURL } from "node:url";
16759
- import { join as join6 } from "node:path";
17036
+ import { join as join9 } from "node:path";
16760
17037
  async function importPluginFromHome(packageName, pluginHome) {
16761
- const requireFromHome = createRequire2(join6(pluginHome, "package.json"));
17038
+ const requireFromHome = createRequire2(join9(pluginHome, "package.json"));
16762
17039
  const entry = requireFromHome.resolve(packageName);
16763
17040
  return await import(pathToFileURL(entry).href);
16764
17041
  }
@@ -16813,7 +17090,7 @@ var init_bootstrap = __esm(() => {
16813
17090
 
16814
17091
  // src/logging/app-logger.ts
16815
17092
  import { appendFile, chmod as chmod2, mkdir as mkdir9 } from "node:fs/promises";
16816
- import { dirname as dirname10 } from "node:path";
17093
+ import { dirname as dirname11 } from "node:path";
16817
17094
  function createNoopAppLogger() {
16818
17095
  return {
16819
17096
  debug: async () => {},
@@ -16854,7 +17131,7 @@ function createAppLogger(options) {
16854
17131
  return;
16855
17132
  }
16856
17133
  const line = formatLogLine(now(), level, event, message, context);
16857
- await mkdir9(dirname10(options.filePath), { recursive: true });
17134
+ await mkdir9(dirname11(options.filePath), { recursive: true });
16858
17135
  if (!modeEnsured) {
16859
17136
  modeEnsured = true;
16860
17137
  await chmod2(options.filePath, 384).catch(() => {});
@@ -17108,8 +17385,10 @@ function parseCommand(input) {
17108
17385
  }
17109
17386
  if (command === "/status")
17110
17387
  return { kind: "status" };
17111
- if (command === "/cancel")
17112
- return { kind: "cancel" };
17388
+ if (command === "/cancel") {
17389
+ const alias = parts[1];
17390
+ return alias ? { kind: "cancel", alias } : { kind: "cancel" };
17391
+ }
17113
17392
  if (command === "/clear")
17114
17393
  return { kind: "session.reset" };
17115
17394
  if (command === "/mode" && parts.length === 1)
@@ -17243,6 +17522,9 @@ function parseCommand(input) {
17243
17522
  if (command === "/config" && parts[1] === "set" && parts.length === 4) {
17244
17523
  return { kind: "config.set", path: parts[2] ?? "", value: parts[3] ?? "" };
17245
17524
  }
17525
+ if (command === "/use" && parts[1] === "-") {
17526
+ return { kind: "session.use.previous" };
17527
+ }
17246
17528
  if (command === "/use" && parts[1]) {
17247
17529
  return { kind: "session.use", alias: parts[1] };
17248
17530
  }
@@ -18410,6 +18692,11 @@ async function buildCoordinatorPrompt(input) {
18410
18692
  }
18411
18693
  var init_build_coordinator_prompt = () => {};
18412
18694
 
18695
+ // src/commands/handlers/session-list-marker.ts
18696
+ function decorateUnread(label, hasUnread) {
18697
+ return hasUnread ? `● ${label}` : label;
18698
+ }
18699
+
18413
18700
  // src/commands/handlers/session-handler.ts
18414
18701
  async function handleSessions(context, chatKey) {
18415
18702
  const sessions = await context.sessions.listSessions(chatKey);
@@ -18432,10 +18719,11 @@ async function handleSessions(context, chatKey) {
18432
18719
  return { text: lines.join(`
18433
18720
  `) };
18434
18721
  }
18722
+ const unread = new Set(context.sessions.listBackgroundResultAliases(chatKey));
18435
18723
  return {
18436
18724
  text: [
18437
18725
  "会话列表:",
18438
- ...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 ? " [当前]" : ""}`)
18439
18727
  ].join(`
18440
18728
  `)
18441
18729
  };
@@ -18512,13 +18800,55 @@ async function refreshSessionTransportAgentCommandBestEffort(context, alias, eve
18512
18800
  });
18513
18801
  }
18514
18802
  }
18515
- async function handleSessionUse(context, chatKey, alias) {
18516
- 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);
18517
18832
  await context.logger.info("session.selected", "selected logical session", {
18518
- 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,
18519
18847
  chatKey
18520
18848
  });
18521
- 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 };
18522
18852
  }
18523
18853
  async function handleModeShow(context, chatKey) {
18524
18854
  const session = await context.sessions.getCurrentSession(chatKey);
@@ -18594,7 +18924,34 @@ async function handleStatus(context, chatKey) {
18594
18924
  `)
18595
18925
  };
18596
18926
  }
18597
- 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
+ }
18598
18955
  const session = await context.sessions.getCurrentSession(chatKey);
18599
18956
  if (!session) {
18600
18957
  return { text: NO_CURRENT_SESSION_TEXT };
@@ -18759,7 +19116,7 @@ async function handlePromptWithSession(context, session, chatKey, text, reply, r
18759
19116
  }
18760
19117
  }
18761
19118
  async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
18762
- const session = await context.sessions.getCurrentSession(chatKey);
19119
+ const session = metadata?.boundSessionAlias ? context.sessions.getResolvedSessionByInternalAlias(metadata.boundSessionAlias) : await context.sessions.getCurrentSession(chatKey);
18763
19120
  if (!session) {
18764
19121
  return { text: NO_CURRENT_SESSION_TEXT };
18765
19122
  }
@@ -18860,6 +19217,8 @@ var init_session_handler = __esm(() => {
18860
19217
  { usage: "/session tail [N]", description: "补拉当前会话的历史输出(默认 50 行)" },
18861
19218
  { usage: "/session rm <alias>", description: "删除逻辑会话" },
18862
19219
  { usage: "/use <alias>", description: "切换当前会话" },
19220
+ { usage: "/use <片段>", description: "按别名片段切换(精确>前缀>子串;多命中会列候选)" },
19221
+ { usage: "/use -", description: "切回上一个会话(像 shell 的 cd -)" },
18863
19222
  { usage: "/session reset 或 /clear", description: "重置当前会话上下文" }
18864
19223
  ],
18865
19224
  examples: [
@@ -18867,6 +19226,8 @@ var init_session_handler = __esm(() => {
18867
19226
  "/ssn",
18868
19227
  "/ssn 1",
18869
19228
  "/use backend-fix",
19229
+ "/use back",
19230
+ "/use -",
18870
19231
  "/session rm old-session",
18871
19232
  "/session reset"
18872
19233
  ]
@@ -18932,12 +19293,14 @@ var init_session_handler = __esm(() => {
18932
19293
  cancelHelp = {
18933
19294
  topic: "cancel",
18934
19295
  aliases: ["stop"],
18935
- summary: "取消当前会话里正在执行的任务。",
19296
+ summary: "取消会话里正在执行的任务。",
18936
19297
  commands: [
18937
- { usage: "/cancel", description: "取消当前任务" },
18938
- { 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> 别名)" }
18939
19302
  ],
18940
- examples: ["/cancel"]
19303
+ examples: ["/cancel", "/cancel backend"]
18941
19304
  };
18942
19305
  });
18943
19306
 
@@ -20645,7 +21008,7 @@ import { spawn as spawn5 } from "node:child_process";
20645
21008
  import { createWriteStream } from "node:fs";
20646
21009
  import { mkdir as mkdir10 } from "node:fs/promises";
20647
21010
  import { homedir as homedir6 } from "node:os";
20648
- import { join as join10 } from "node:path";
21011
+ import { join as join13 } from "node:path";
20649
21012
  async function autoInstallOptionalDep(pkg, parentPackages, options = {}) {
20650
21013
  const runCli = options.runCli ?? defaultRunCli;
20651
21014
  const openLog = options.openLog ?? defaultLogSink;
@@ -20760,10 +21123,10 @@ ${err.message}`, reason: "spawn" });
20760
21123
  });
20761
21124
  });
20762
21125
  }, defaultLogSink = async () => {
20763
- const dir = join10(homedir6(), ".weacpx", "logs");
21126
+ const dir = join13(coreHomeDir(homedir6()), "logs");
20764
21127
  await mkdir10(dir, { recursive: true });
20765
21128
  const timestamp = new Date().toISOString().replace(/[:.]/g, "").replace(/-/g, "");
20766
- const path14 = join10(dir, `auto-install-${timestamp}.log`);
21129
+ const path14 = join13(dir, `auto-install-${timestamp}.log`);
20767
21130
  const stream = createWriteStream(path14, { flags: "a" });
20768
21131
  return {
20769
21132
  path: path14,
@@ -20776,6 +21139,7 @@ ${err.message}`, reason: "spawn" });
20776
21139
  };
20777
21140
  };
20778
21141
  var init_auto_install_optional_dep = __esm(() => {
21142
+ init_core_home();
20779
21143
  PRECISE_COMMANDS = {
20780
21144
  npm: { cmd: "npm", args: (pkg) => ["install", pkg, "--no-save", "--no-audit", "--no-fund"] },
20781
21145
  bun: { cmd: "bun", args: (pkg) => ["add", pkg] },
@@ -20789,7 +21153,7 @@ import { spawn as spawn6 } from "node:child_process";
20789
21153
  import { createRequire as createRequire3 } from "node:module";
20790
21154
  import { access as access3 } from "node:fs/promises";
20791
21155
  import { homedir as homedir7 } from "node:os";
20792
- import { dirname as dirname11, join as join11 } from "node:path";
21156
+ import { dirname as dirname12, join as join14 } from "node:path";
20793
21157
  function deriveParentPackageName(platformPackage) {
20794
21158
  return platformPackage.replace(/-(?:linux|darwin|win32|windows|freebsd|openbsd|sunos|aix)(?:-(?:x64|arm64|ia32|arm|ppc64|s390x))?(?:-(?:baseline|musl|gnu|gnueabihf|musleabihf|msvc))?$/, "");
20795
21159
  }
@@ -20802,7 +21166,7 @@ async function discoverParentPackagePaths(platformPackage, seedPath, deps = {})
20802
21166
  const queryRoot = deps.queryPackageManagerRoot ?? defaultQueryPackageManagerRoot;
20803
21167
  const parentName = deriveParentPackageName(platformPackage);
20804
21168
  const rawCandidates = [];
20805
- 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");
20806
21170
  const [npmRoot, pnpmRoot, yarnRoot] = await Promise.all([
20807
21171
  queryRoot("npm"),
20808
21172
  queryRoot("pnpm"),
@@ -20825,20 +21189,20 @@ async function discoverParentPackagePaths(platformPackage, seedPath, deps = {})
20825
21189
  if (resolved)
20826
21190
  rawCandidates.push({ path: resolved, manager: classify(resolved) });
20827
21191
  }
20828
- rawCandidates.push({ path: join11(bunGlobalRoot, parentName), manager: "bun" });
21192
+ rawCandidates.push({ path: join14(bunGlobalRoot, parentName), manager: "bun" });
20829
21193
  if (npmRoot)
20830
- rawCandidates.push({ path: join11(npmRoot, parentName), manager: "npm" });
21194
+ rawCandidates.push({ path: join14(npmRoot, parentName), manager: "npm" });
20831
21195
  if (pnpmRoot)
20832
- rawCandidates.push({ path: join11(pnpmRoot, parentName), manager: "pnpm" });
21196
+ rawCandidates.push({ path: join14(pnpmRoot, parentName), manager: "pnpm" });
20833
21197
  if (yarnRoot)
20834
- rawCandidates.push({ path: join11(yarnRoot, parentName), manager: "yarn" });
21198
+ rawCandidates.push({ path: join14(yarnRoot, parentName), manager: "yarn" });
20835
21199
  const seen = new Set;
20836
21200
  const verified = [];
20837
21201
  for (const candidate of rawCandidates) {
20838
21202
  if (seen.has(candidate.path))
20839
21203
  continue;
20840
21204
  seen.add(candidate.path);
20841
- if (await fsExists(join11(candidate.path, "package.json"))) {
21205
+ if (await fsExists(join14(candidate.path, "package.json"))) {
20842
21206
  verified.push(candidate);
20843
21207
  }
20844
21208
  }
@@ -20862,7 +21226,7 @@ function defaultResolveFromCwd(name, cwd) {
20862
21226
  const pkgJson = require2.resolve(`${name}/package.json`, {
20863
21227
  paths: [cwd, ...require2.resolve.paths(name) ?? []]
20864
21228
  });
20865
- return dirname11(pkgJson);
21229
+ return dirname12(pkgJson);
20866
21230
  } catch {
20867
21231
  return null;
20868
21232
  }
@@ -20909,7 +21273,7 @@ async function defaultQueryPackageManagerRoot(tool) {
20909
21273
  const trimmed = stdout2.trim().split(/\r?\n/).pop()?.trim() ?? "";
20910
21274
  if (!trimmed)
20911
21275
  return done(null);
20912
- done(spec.postfix ? join11(trimmed, spec.postfix) : trimmed);
21276
+ done(spec.postfix ? join14(trimmed, spec.postfix) : trimmed);
20913
21277
  });
20914
21278
  });
20915
21279
  }
@@ -21013,7 +21377,8 @@ class CommandRouter {
21013
21377
  __setDiscoverPathsForTest(fn) {
21014
21378
  this.discoverPaths = fn;
21015
21379
  }
21016
- 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) {
21017
21382
  this.sessions = sessions;
21018
21383
  this.transport = transport;
21019
21384
  this.config = config2;
@@ -21025,6 +21390,7 @@ class CommandRouter {
21025
21390
  this.scheduledDelivery = scheduledDelivery;
21026
21391
  this.resolveNativeSessionListFormat = resolveNativeSessionListFormat;
21027
21392
  this.logger = logger2 ?? createNoopAppLogger();
21393
+ this.activeTurns = activeTurns;
21028
21394
  }
21029
21395
  async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, onThought, perfSpan) {
21030
21396
  const startedAt = Date.now();
@@ -21095,6 +21461,8 @@ class CommandRouter {
21095
21461
  return await handleNativeSessionSelect(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.identifier, command.alias);
21096
21462
  case "session.use":
21097
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);
21098
21466
  case "mode.show":
21099
21467
  return await handleModeShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
21100
21468
  case "mode.set":
@@ -21108,7 +21476,7 @@ class CommandRouter {
21108
21476
  case "status":
21109
21477
  return await handleStatus(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
21110
21478
  case "cancel":
21111
- return await handleCancel(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
21479
+ return await handleCancel(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
21112
21480
  case "session.reset":
21113
21481
  return await handleSessionReset(this.createSessionHandlerContext(reply, perfSpan), chatKey);
21114
21482
  case "session.tail":
@@ -21203,7 +21571,8 @@ class CommandRouter {
21203
21571
  ...this.createHandlerContext(),
21204
21572
  lifecycle: this.createSessionLifecycleOps(reply, perfSpan),
21205
21573
  interaction: this.createSessionInteractionOps(perfSpan),
21206
- recovery: this.createSessionRenderRecoveryOps()
21574
+ recovery: this.createSessionRenderRecoveryOps(),
21575
+ ...this.activeTurns ? { activeTurns: this.activeTurns } : {}
21207
21576
  };
21208
21577
  }
21209
21578
  createSessionLifecycleOps(reply, perfSpan) {
@@ -21568,7 +21937,7 @@ var init_command_router = __esm(() => {
21568
21937
  });
21569
21938
 
21570
21939
  // src/config/resolve-acpx-command.ts
21571
- import { readFileSync } from "node:fs";
21940
+ import { readFileSync as readFileSync2 } from "node:fs";
21572
21941
  import { createRequire as createRequire4 } from "node:module";
21573
21942
  import { posix, win32 } from "node:path";
21574
21943
  function resolveAcpxCommand(options = {}) {
@@ -21584,7 +21953,7 @@ function resolveAcpxCommandMetadata(options = {}) {
21584
21953
  }
21585
21954
  const platform = options.platform ?? process.platform;
21586
21955
  const resolvePackageJson = options.resolvePackageJson ?? ((id) => require3.resolve(id));
21587
- const readPackageJson = options.readPackageJson ?? ((path14) => JSON.parse(readFileSync(path14, "utf8")));
21956
+ const readPackageJson = options.readPackageJson ?? ((path14) => JSON.parse(readFileSync2(path14, "utf8")));
21588
21957
  try {
21589
21958
  const packageJsonPath = resolvePackageJson("acpx/package.json");
21590
21959
  const pkg = readPackageJson(packageJsonPath);
@@ -21658,7 +22027,7 @@ var init_console_agent = __esm(() => {
21658
22027
  });
21659
22028
 
21660
22029
  // src/orchestration/orchestration-server.ts
21661
- import { rm as rm7 } from "node:fs/promises";
22030
+ import { rm as rm8 } from "node:fs/promises";
21662
22031
  import { createConnection as createConnection2, createServer } from "node:net";
21663
22032
 
21664
22033
  class OrchestrationServer {
@@ -21991,7 +22360,7 @@ class OrchestrationServer {
21991
22360
  return;
21992
22361
  }
21993
22362
  const removeFile = this.deps.removeFile ?? (async (path14) => {
21994
- await rm7(path14, { force: true });
22363
+ await rm8(path14, { force: true });
21995
22364
  });
21996
22365
  await removeFile(this.endpoint.path);
21997
22366
  }
@@ -25940,6 +26309,20 @@ class SessionService {
25940
26309
  }
25941
26310
  return this.toResolvedSession(session);
25942
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
+ }
25943
26326
  async getPreferredSessionForTransport(transportSession) {
25944
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));
25945
26328
  const expectedAlias = transportSession.split(":").at(-1);
@@ -25964,17 +26347,122 @@ class SessionService {
25964
26347
  return null;
25965
26348
  }
25966
26349
  async useSession(chatKey, alias) {
25967
- await this.mutate(async () => {
26350
+ return await this.mutate(async () => {
25968
26351
  const channelId = getChannelIdFromChatKey(chatKey);
25969
26352
  const internalAlias = resolveSessionAliasForInput(channelId, alias, Object.keys(this.state.sessions));
25970
26353
  const session = this.state.sessions[internalAlias];
25971
26354
  if (!session) {
25972
26355
  throw new Error(`session "${alias}" does not exist`);
25973
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;
25974
26360
  session.last_used_at = new Date().toISOString();
25975
- this.state.chat_contexts[chatKey] = { current_session: internalAlias };
26361
+ this.state.chat_contexts[chatKey] = {
26362
+ current_session: internalAlias,
26363
+ ...carriedPrevious ? { previous_session: carriedPrevious } : {}
26364
+ };
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
+ };
26372
+ });
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
+ };
25976
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
25977
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" };
25978
26466
  }
25979
26467
  async resolveAliasForChat(chatKey, displayAlias) {
25980
26468
  const channelId = getChannelIdFromChatKey(chatKey);
@@ -26076,6 +26564,10 @@ class SessionService {
26076
26564
  for (const [chatKey, ctx] of Object.entries(this.state.chat_contexts)) {
26077
26565
  if (ctx.current_session === alias) {
26078
26566
  delete this.state.chat_contexts[chatKey];
26567
+ continue;
26568
+ }
26569
+ if (ctx.previous_session === alias) {
26570
+ delete ctx.previous_session;
26079
26571
  }
26080
26572
  }
26081
26573
  await this.persist();
@@ -26107,9 +26599,11 @@ class SessionService {
26107
26599
  }
26108
26600
  const createdAt = Date.parse(cached2.created_at);
26109
26601
  if (Number.isNaN(createdAt)) {
26602
+ await this.deleteNativeSessionListIfCurrent(chatKey, cached2);
26110
26603
  return null;
26111
26604
  }
26112
26605
  if (this.now() - createdAt > ttlMs) {
26606
+ await this.deleteNativeSessionListIfCurrent(chatKey, cached2);
26113
26607
  return null;
26114
26608
  }
26115
26609
  return {
@@ -26125,6 +26619,15 @@ class SessionService {
26125
26619
  ...cached2.next_cursor !== undefined ? { nextCursor: cached2.next_cursor } : {}
26126
26620
  };
26127
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
+ }
26128
26631
  toResolvedSession(session) {
26129
26632
  const agentConfig = this.config.agents[session.agent];
26130
26633
  if (!agentConfig) {
@@ -26224,6 +26727,29 @@ var init_session_service = __esm(() => {
26224
26727
  init_channel_scope();
26225
26728
  });
26226
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
+
26227
26753
  // src/state/debounced-state-store.ts
26228
26754
  class DebouncedStateStore {
26229
26755
  deps;
@@ -26447,6 +26973,8 @@ async function runConsole(paths, deps) {
26447
26973
  agent: runtime.agent,
26448
26974
  abortSignal: shutdownController.signal,
26449
26975
  quota: runtime.quota,
26976
+ sessions: runtime.sessions,
26977
+ activeTurns: runtime.activeTurns,
26450
26978
  logger: runtime.logger,
26451
26979
  perfTracer: runtime.perfTracer,
26452
26980
  commandHints: listWeacpxCommandHints(),
@@ -26600,7 +27128,7 @@ function encodeBridgeSessionNoteEvent(event) {
26600
27128
 
26601
27129
  // src/transport/acpx-bridge/acpx-bridge-client.ts
26602
27130
  import { spawn as spawn7 } from "node:child_process";
26603
- import { fileURLToPath as fileURLToPath3 } from "node:url";
27131
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
26604
27132
  import { createInterface } from "node:readline";
26605
27133
 
26606
27134
  class AcpxBridgeClient {
@@ -26725,7 +27253,7 @@ function buildBridgeSpawnSpec(options) {
26725
27253
  };
26726
27254
  }
26727
27255
  async function spawnAcpxBridgeClient(options = {}) {
26728
- 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));
26729
27257
  const spawnSpec = buildBridgeSpawnSpec({
26730
27258
  execPath: process.execPath,
26731
27259
  bridgeEntryPath
@@ -27177,7 +27705,7 @@ var init_spawn_command = __esm(() => {
27177
27705
  });
27178
27706
 
27179
27707
  // src/transport/prompt-media.ts
27180
- 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";
27181
27709
  import { tmpdir as defaultTmpdir } from "node:os";
27182
27710
  import path14 from "node:path";
27183
27711
  import { pathToFileURL as pathToFileURL2 } from "node:url";
@@ -27302,7 +27830,7 @@ var init_prompt_media = __esm(() => {
27302
27830
  readImageFile: readImageFileBounded,
27303
27831
  mkdtemp,
27304
27832
  writeFile: writeFile7,
27305
- rm: rm8,
27833
+ rm: rm9,
27306
27834
  tmpdir: defaultTmpdir
27307
27835
  };
27308
27836
  });
@@ -27601,12 +28129,12 @@ var init_streaming_prompt = __esm(() => {
27601
28129
 
27602
28130
  // src/transport/acpx-cli/node-pty-helper.ts
27603
28131
  import { chmod as chmodFs } from "node:fs/promises";
27604
- import { dirname as dirname12, join as join12 } from "node:path";
28132
+ import { dirname as dirname13, join as join15 } from "node:path";
27605
28133
  function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
27606
28134
  if (platform === "win32") {
27607
28135
  return null;
27608
28136
  }
27609
- return join12(dirname12(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
28137
+ return join15(dirname13(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
27610
28138
  }
27611
28139
  async function ensureNodePtyHelperExecutable(helperPath, chmod3 = chmodFs) {
27612
28140
  if (!helperPath) {
@@ -27628,7 +28156,7 @@ import { createHash as createHash3 } from "node:crypto";
27628
28156
  import { spawn as spawn8 } from "node:child_process";
27629
28157
  import { readFile as readFile12, unlink } from "node:fs/promises";
27630
28158
  import { homedir as homedir8 } from "node:os";
27631
- import { join as join13 } from "node:path";
28159
+ import { join as join16 } from "node:path";
27632
28160
  function buildWeacpxMcpServerSpec(input) {
27633
28161
  const { command, args } = splitCommandLine(input.weacpxCommand);
27634
28162
  return {
@@ -27679,7 +28207,13 @@ class AcpxQueueOwnerLauncher {
27679
28207
  const key = input.acpxRecordId;
27680
28208
  const previous = this.launchLocks.get(key) ?? Promise.resolve();
27681
28209
  const next = previous.then(() => this.doLaunch(input), () => this.doLaunch(input));
27682
- 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
+ });
27683
28217
  return next;
27684
28218
  }
27685
28219
  async doLaunch(input) {
@@ -27792,7 +28326,7 @@ async function terminateAcpxQueueOwner(sessionId) {
27792
28326
  await unlink(lockPath).catch(() => {});
27793
28327
  }
27794
28328
  function queueLockFilePath(sessionId) {
27795
- return join13(homedir8(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
28329
+ return join16(homedir8(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
27796
28330
  }
27797
28331
  function shortHash(value, length) {
27798
28332
  return createHash3("sha256").update(value).digest("hex").slice(0, length);
@@ -28637,23 +29171,33 @@ var init_channel_registry = __esm(() => {
28637
29171
  });
28638
29172
 
28639
29173
  // src/weixin/messaging/quota-manager.ts
28640
- function freshState() {
28641
- return { midUsed: 0, finalUsed: 0, pendingFinalChunks: [] };
29174
+ function freshState(now) {
29175
+ return { midUsed: 0, finalUsed: 0, pendingFinalChunks: [], lastTouchedAt: now };
28642
29176
  }
28643
29177
 
28644
29178
  class QuotaManager {
28645
29179
  states = new Map;
28646
29180
  observer;
28647
29181
  normalizeKey;
28648
- constructor(observer, normalizeKey) {
29182
+ maxStates;
29183
+ stateTtlMs;
29184
+ maxPendingFinalChunksPerChat;
29185
+ now;
29186
+ constructor(observer, normalizeKey, options = {}) {
28649
29187
  this.observer = observer;
28650
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());
28651
29193
  }
28652
29194
  onInbound(chatKey) {
28653
29195
  const key = this.normalizeKey(chatKey);
29196
+ this.prune();
28654
29197
  const existing = this.states.get(key);
28655
29198
  const pending = existing?.pendingFinalChunks ?? [];
28656
- 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();
28657
29201
  this.observer?.onInbound?.(key);
28658
29202
  }
28659
29203
  reserveMidSegment(chatKey) {
@@ -28686,36 +29230,48 @@ class QuotaManager {
28686
29230
  return;
28687
29231
  const state = this.getOrCreate(this.normalizeKey(chatKey));
28688
29232
  state.pendingFinalChunks.push(...chunks);
29233
+ this.trimPendingFinalChunks(state, "front");
28689
29234
  }
28690
29235
  prependPendingFinal(chatKey, chunks) {
28691
29236
  if (chunks.length === 0)
28692
29237
  return;
28693
29238
  const state = this.getOrCreate(this.normalizeKey(chatKey));
28694
29239
  state.pendingFinalChunks.unshift(...chunks);
29240
+ this.trimPendingFinalChunks(state, "back");
28695
29241
  }
28696
29242
  drainPendingFinalUpToBudget(chatKey, available) {
28697
29243
  if (available <= 0)
28698
29244
  return [];
28699
- const state = this.getOrCreate(this.normalizeKey(chatKey));
29245
+ const key = this.normalizeKey(chatKey);
29246
+ const state = this.getOrCreate(key);
28700
29247
  if (state.pendingFinalChunks.length === 0)
28701
29248
  return [];
28702
- 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;
28703
29253
  }
28704
29254
  hasPendingFinal(chatKey) {
29255
+ this.prune();
28705
29256
  return (this.states.get(this.normalizeKey(chatKey))?.pendingFinalChunks.length ?? 0) > 0;
28706
29257
  }
28707
29258
  countPendingFinal(chatKey) {
29259
+ this.prune();
28708
29260
  return this.states.get(this.normalizeKey(chatKey))?.pendingFinalChunks.length ?? 0;
28709
29261
  }
28710
29262
  clearPendingFinal(chatKey) {
28711
- const state = this.states.get(this.normalizeKey(chatKey));
29263
+ const key = this.normalizeKey(chatKey);
29264
+ const state = this.states.get(key);
28712
29265
  if (!state)
28713
29266
  return;
28714
29267
  state.pendingFinalChunks = [];
29268
+ state.lastTouchedAt = this.now();
29269
+ this.deleteIfEmpty(key, state);
28715
29270
  }
28716
29271
  snapshot(chatKey) {
28717
29272
  const key = this.normalizeKey(chatKey);
28718
- const state = this.states.get(key) ?? freshState();
29273
+ this.prune();
29274
+ const state = this.states.get(key) ?? freshState(this.now());
28719
29275
  const midRemaining = MID_BUDGET - state.midUsed;
28720
29276
  const finalRemaining = FINAL_BUDGET - state.finalUsed;
28721
29277
  return {
@@ -28727,15 +29283,72 @@ class QuotaManager {
28727
29283
  };
28728
29284
  }
28729
29285
  getOrCreate(key) {
29286
+ this.prune();
28730
29287
  let state = this.states.get(key);
28731
29288
  if (!state) {
28732
- state = freshState();
29289
+ state = freshState(this.now());
28733
29290
  this.states.set(key, state);
29291
+ this.enforceMaxStates();
29292
+ } else {
29293
+ state.lastTouchedAt = this.now();
28734
29294
  }
28735
29295
  return state;
28736
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);
29342
+ }
29343
+ function normalizeNonNegativeMs3(value, fallback) {
29344
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
29345
+ return fallback;
29346
+ return value;
28737
29347
  }
28738
- var MID_BUDGET = 6, FINAL_BUDGET = 4;
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
+ });
28739
29352
 
28740
29353
  // src/main.ts
28741
29354
  var exports_main = {};
@@ -28748,8 +29361,8 @@ __export(exports_main, {
28748
29361
  });
28749
29362
  import { randomUUID as randomUUID3 } from "node:crypto";
28750
29363
  import { homedir as homedir9 } from "node:os";
28751
- import { dirname as dirname13, join as join14 } from "node:path";
28752
- 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";
28753
29366
  function startProgressHeartbeat(orchestration, config2, logger2, channel) {
28754
29367
  const thresholdSeconds = config2.orchestration.progressHeartbeatSeconds;
28755
29368
  if (thresholdSeconds <= 0) {
@@ -28829,6 +29442,7 @@ async function buildApp(paths, deps = {}) {
28829
29442
  }
28830
29443
  });
28831
29444
  const sessions = new SessionService(config2, debouncedStateStore, state, { stateMutex });
29445
+ const activeTurns = createActiveTurnRegistry();
28832
29446
  const scheduledService = new ScheduledTaskService(state, debouncedStateStore, { stateMutex });
28833
29447
  const pendingWorkerDispatches = new Set;
28834
29448
  const transport = config2.transport.type === "acpx-bridge" ? await (deps.createBridgeTransport?.() ?? Promise.resolve(new AcpxBridgeTransport(await spawnAcpxBridgeClient({
@@ -29199,7 +29813,7 @@ async function buildApp(paths, deps = {}) {
29199
29813
  listScheduledTasksFromRoute: async (input) => await listScheduledTasksFromRoute(input, { state, scheduled: scheduledService }),
29200
29814
  cancelScheduledTaskFromRoute: async (input) => await cancelScheduledTaskFromRoute(input, { state, scheduled: scheduledService })
29201
29815
  });
29202
- 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);
29203
29817
  const agent = new ConsoleAgent(router, logger2);
29204
29818
  const scheduledScheduler = new ScheduledTaskScheduler(scheduledService, {
29205
29819
  dispatchTask: buildScheduledDispatchTask({
@@ -29220,6 +29834,7 @@ async function buildApp(paths, deps = {}) {
29220
29834
  agent,
29221
29835
  router,
29222
29836
  sessions,
29837
+ activeTurns,
29223
29838
  stateStore,
29224
29839
  configStore,
29225
29840
  logger: logger2,
@@ -29325,8 +29940,8 @@ async function main() {
29325
29940
  }
29326
29941
  }
29327
29942
  async function prepareChannelMedia(configPath, config2) {
29328
- const runtimeDir = join14(dirname13(configPath), "runtime");
29329
- const mediaRootDir = join14(runtimeDir, "media");
29943
+ const runtimeDir = join17(dirname14(configPath), "runtime");
29944
+ const mediaRootDir = join17(runtimeDir, "media");
29330
29945
  const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
29331
29946
  await mediaStore.cleanupExpired().catch((error2) => {
29332
29947
  console.error("[weacpx] media cleanup failed:", error2 instanceof Error ? error2.message : String(error2));
@@ -29339,30 +29954,30 @@ function resolveRuntimePaths() {
29339
29954
  if (!home) {
29340
29955
  throw new Error("Unable to resolve the current user home directory");
29341
29956
  }
29342
- const configPath = process.env.WEACPX_CONFIG ?? `${home}/.weacpx/config.json`;
29343
- 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");
29344
29959
  return {
29345
29960
  configPath,
29346
- statePath: process.env.WEACPX_STATE ?? `${home}/.weacpx/state.json`,
29347
- perfLogPath: join14(runtimeDir, "perf.log"),
29961
+ statePath: process.env.WEACPX_STATE ?? join17(coreHomeDir(home), "state.json"),
29962
+ perfLogPath: join17(runtimeDir, "perf.log"),
29348
29963
  orchestrationSocketPath: process.env.WEACPX_ORCHESTRATION_SOCKET ?? resolveDaemonOrchestrationSocketPath(runtimeDir)
29349
29964
  };
29350
29965
  }
29351
29966
  function resolveBridgeEntryPath() {
29352
29967
  if (import.meta.url.includes("/dist/")) {
29353
- return fileURLToPath4(new URL("./bridge/bridge-main.js", import.meta.url));
29968
+ return fileURLToPath5(new URL("./bridge/bridge-main.js", import.meta.url));
29354
29969
  }
29355
- return fileURLToPath4(new URL("./bridge/bridge-main.ts", import.meta.url));
29970
+ return fileURLToPath5(new URL("./bridge/bridge-main.ts", import.meta.url));
29356
29971
  }
29357
29972
  function resolveAppLogPath(configPath) {
29358
- const rootDir = dirname13(configPath);
29359
- const runtimeDir = join14(rootDir, "runtime");
29360
- return join14(runtimeDir, "app.log");
29973
+ const rootDir = dirname14(configPath);
29974
+ const runtimeDir = join17(rootDir, "runtime");
29975
+ return join17(runtimeDir, "app.log");
29361
29976
  }
29362
29977
  function resolvePerfLogPath(configPath) {
29363
- const rootDir = dirname13(configPath);
29364
- const runtimeDir = join14(rootDir, "runtime");
29365
- return join14(runtimeDir, "perf.log");
29978
+ const rootDir = dirname14(configPath);
29979
+ const runtimeDir = join17(rootDir, "runtime");
29980
+ return join17(runtimeDir, "perf.log");
29366
29981
  }
29367
29982
  function resolveOrchestrationSocketPathFromConfigPath(configPath) {
29368
29983
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
@@ -29372,6 +29987,7 @@ function shouldNotifyTaskCompletion(task) {
29372
29987
  return Boolean(task.chatKey && task.replyContextToken && (task.status === "completed" || task.status === "failed"));
29373
29988
  }
29374
29989
  var init_main = __esm(async () => {
29990
+ init_core_home();
29375
29991
  init_command_router();
29376
29992
  init_config_store();
29377
29993
  init_ensure_config();
@@ -29401,6 +30017,7 @@ var init_main = __esm(async () => {
29401
30017
  init_media_store();
29402
30018
  init_quota_errors();
29403
30019
  init_inbound();
30020
+ init_quota_manager();
29404
30021
  init_perf_tracer();
29405
30022
  init_bootstrap();
29406
30023
  if (false) {}
@@ -29592,7 +30209,7 @@ var init_config_check = __esm(async () => {
29592
30209
  });
29593
30210
 
29594
30211
  // src/doctor/checks/daemon-check.ts
29595
- import { fileURLToPath as fileURLToPath5 } from "node:url";
30212
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
29596
30213
  import { homedir as homedir10 } from "node:os";
29597
30214
  async function checkDaemon(options = {}) {
29598
30215
  const home = options.home ?? process.env.HOME ?? homedir10();
@@ -29686,7 +30303,7 @@ function defaultIsProcessRunning5(pid) {
29686
30303
  }
29687
30304
  }
29688
30305
  function resolveCliEntryPath() {
29689
- return process.argv[1] ?? fileURLToPath5(import.meta.url);
30306
+ return process.argv[1] ?? fileURLToPath6(import.meta.url);
29690
30307
  }
29691
30308
  function formatError5(error2) {
29692
30309
  return error2 instanceof Error ? error2.message : String(error2);
@@ -29748,7 +30365,7 @@ async function checkOrchestrationHealth(options) {
29748
30365
  // src/doctor/checks/runtime-check.ts
29749
30366
  import { constants } from "node:fs";
29750
30367
  import { access as access4, stat as stat3 } from "node:fs/promises";
29751
- import { dirname as dirname14 } from "node:path";
30368
+ import { dirname as dirname15 } from "node:path";
29752
30369
  import { homedir as homedir11 } from "node:os";
29753
30370
  async function checkRuntime(options = {}) {
29754
30371
  const home = options.home ?? process.env.HOME ?? homedir11();
@@ -29849,7 +30466,7 @@ async function checkFileCreatable(label, path15, probe, platform) {
29849
30466
  detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
29850
30467
  };
29851
30468
  }
29852
- const parentCheck = await checkCreatableAncestorDirectory(dirname14(path15), probe, platform);
30469
+ const parentCheck = await checkCreatableAncestorDirectory(dirname15(path15), probe, platform);
29853
30470
  if (!parentCheck.ok) {
29854
30471
  return {
29855
30472
  ok: false,
@@ -29885,7 +30502,7 @@ async function checkCreatableAncestorDirectory(path15, probe, platform) {
29885
30502
  blockingPath: path15
29886
30503
  };
29887
30504
  }
29888
- const parent = dirname14(path15);
30505
+ const parent = dirname15(path15);
29889
30506
  if (parent === path15) {
29890
30507
  return {
29891
30508
  ok: false,
@@ -30308,7 +30925,7 @@ var init_render_doctor = __esm(() => {
30308
30925
 
30309
30926
  // src/doctor/doctor.ts
30310
30927
  import { homedir as homedir12 } from "node:os";
30311
- import { join as join15 } from "node:path";
30928
+ import { join as join18 } from "node:path";
30312
30929
  async function runDoctor(options = {}, deps = {}) {
30313
30930
  const home = deps.home ?? process.env.HOME ?? homedir12();
30314
30931
  const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
@@ -30363,8 +30980,8 @@ function resolveDoctorRuntimePaths(home, resolver) {
30363
30980
  return resolveRuntimePaths();
30364
30981
  }
30365
30982
  return {
30366
- configPath: join15(home, ".weacpx", "config.json"),
30367
- statePath: join15(home, ".weacpx", "state.json")
30983
+ configPath: join18(coreHomeDir(home), "config.json"),
30984
+ statePath: join18(coreHomeDir(home), "state.json")
30368
30985
  };
30369
30986
  }
30370
30987
  function depsUseExplicitRuntimeOverrides() {
@@ -30429,6 +31046,7 @@ function formatError9(error2) {
30429
31046
  return error2 instanceof Error ? error2.message : String(error2);
30430
31047
  }
30431
31048
  var init_doctor = __esm(async () => {
31049
+ init_core_home();
30432
31050
  init_load_config();
30433
31051
  init_state_store();
30434
31052
  init_daemon_check();
@@ -30462,6 +31080,7 @@ var init_doctor2 = __esm(async () => {
30462
31080
  });
30463
31081
 
30464
31082
  // src/cli.ts
31083
+ init_core_home();
30465
31084
  init_config_store();
30466
31085
  init_load_config();
30467
31086
  init_ensure_config();
@@ -30470,8 +31089,8 @@ init_create_daemon_controller();
30470
31089
  init_daemon_files();
30471
31090
  import { randomUUID as randomUUID4 } from "node:crypto";
30472
31091
  import { homedir as homedir13 } from "node:os";
30473
- import { dirname as dirname15, join as join16, sep } from "node:path";
30474
- 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";
30475
31094
 
30476
31095
  // src/daemon/daemon-runtime.ts
30477
31096
  init_daemon_status();
@@ -42884,15 +43503,17 @@ class StdioServerTransport {
42884
43503
  init_version();
42885
43504
 
42886
43505
  // src/mcp/resolve-endpoint.ts
43506
+ init_core_home();
42887
43507
  init_daemon_files();
42888
43508
  init_orchestration_ipc();
42889
43509
  import { homedir as homedir2 } from "node:os";
43510
+ import { join as join4 } from "node:path";
42890
43511
  function resolveDefaultOrchestrationEndpoint(env = process.env, platform = process.platform) {
42891
43512
  if (typeof env.WEACPX_ORCHESTRATION_SOCKET === "string" && env.WEACPX_ORCHESTRATION_SOCKET.trim().length > 0) {
42892
43513
  return createOrchestrationEndpoint(env.WEACPX_ORCHESTRATION_SOCKET.trim(), platform);
42893
43514
  }
42894
43515
  const home = requireHome(env);
42895
- 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");
42896
43517
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
42897
43518
  return resolveOrchestrationEndpoint(runtimeDir, platform);
42898
43519
  }
@@ -44391,12 +45012,14 @@ function resolveTemplateChoice(answer, names) {
44391
45012
  init_plugin_home();
44392
45013
  import { spawn as spawn4 } from "node:child_process";
44393
45014
  import { readFile as readFile8 } from "node:fs/promises";
44394
- import { dirname as dirname9, join as join7 } from "node:path";
44395
- import { fileURLToPath as fileURLToPath2 } from "node:url";
45015
+ import { dirname as dirname10, join as join10 } from "node:path";
45016
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
44396
45017
 
44397
45018
  // src/plugins/package-manager.ts
44398
45019
  init_plugin_home();
44399
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";
44400
45023
  async function defaultRunCommand(command, args, options) {
44401
45024
  await new Promise((resolve, reject) => {
44402
45025
  const child = spawn3(command, args, { cwd: options.cwd, stdio: "inherit" });
@@ -44437,6 +45060,9 @@ async function installPluginPackage(input) {
44437
45060
  const runCommand = input.runCommand ?? defaultRunCommand;
44438
45061
  const packageManager = input.packageManager ?? await detectPackageManager();
44439
45062
  await normalizePluginHomeManifest(input.pluginHome);
45063
+ if (packageManager === "bun") {
45064
+ await rm4(join6(input.pluginHome, "bun.lock"), { force: true }).catch(() => {});
45065
+ }
44440
45066
  const spec = input.version ? `${input.packageName}@${input.version}` : input.packageName;
44441
45067
  if (packageManager === "bun") {
44442
45068
  await runCommand("bun", ["add", spec], { cwd: input.pluginHome });
@@ -44460,6 +45086,7 @@ async function removePluginPackage(input) {
44460
45086
  // src/cli-update.ts
44461
45087
  init_plugin_loader();
44462
45088
  init_validate_plugin();
45089
+ var SUCCESSOR = { from: "weacpx", package: "@ganglion/xacpx", command: "xacpx", minVersion: "0.8.0" };
44463
45090
  async function handleUpdateCli(args, deps) {
44464
45091
  let all = false;
44465
45092
  const explicitTargets = [];
@@ -44476,14 +45103,20 @@ async function handleUpdateCli(args, deps) {
44476
45103
  const config2 = await deps.loadConfig();
44477
45104
  const packageName = deps.packageName ?? await readPackageName();
44478
45105
  const latestOf = deps.getLatestVersion ?? getLatestNpmVersion;
44479
- const targets = [
44480
- {
44481
- kind: "self",
44482
- name: packageName,
44483
- currentVersion: deps.readCurrentVersion(),
44484
- latestVersion: await latestOf(packageName)
44485
- }
44486
- ];
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];
44487
45120
  for (const plugin of config2.plugins ?? []) {
44488
45121
  targets.push({
44489
45122
  kind: "plugin",
@@ -44503,7 +45136,7 @@ async function handleUpdateCli(args, deps) {
44503
45136
  deps.print(`以下项目无法检查最新版本,已取消更新:${unavailable.map((target) => target.name).join(", ")}`);
44504
45137
  return 1;
44505
45138
  }
44506
- 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));
44507
45140
  const selected = await selectTargets(targets, candidates, { all, explicitTarget: explicitTargets[0], deps });
44508
45141
  if (!selected.ok) {
44509
45142
  deps.print(selected.message);
@@ -44514,6 +45147,8 @@ async function handleUpdateCli(args, deps) {
44514
45147
  return 0;
44515
45148
  }
44516
45149
  const selfUpdater = deps.updateSelf ?? defaultUpdateSelf;
45150
+ const selfMigrator = deps.migrateSelf ?? defaultMigrateSelf;
45151
+ const stopDaemon = deps.stopDaemon ?? (async () => {});
44517
45152
  const pluginHome = deps.pluginHome ?? resolvePluginHome();
44518
45153
  const pluginUpdater = deps.updatePlugin ?? (async (input) => {
44519
45154
  await ensurePluginHome(pluginHome);
@@ -44524,17 +45159,25 @@ async function handleUpdateCli(args, deps) {
44524
45159
  for (const target of selected.targets) {
44525
45160
  try {
44526
45161
  if (target.kind === "self") {
45162
+ const successorPackage = target.successorPackage;
44527
45163
  if (!all && !explicitTargets[0]) {
44528
45164
  if (!deps.isInteractive()) {
44529
- 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`。");
44530
45166
  return 1;
44531
45167
  }
44532
- 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();
44533
45170
  if (answer !== "y" && answer !== "yes") {
44534
- deps.print("已取消更新 weacpx 本体。");
45171
+ deps.print(successorPackage ? `已取消迁移到 ${successorPackage}。` : "已取消更新 weacpx 本体。");
44535
45172
  continue;
44536
45173
  }
44537
45174
  }
45175
+ if (successorPackage) {
45176
+ await stopDaemon();
45177
+ await selfMigrator({ from: target.name, to: successorPackage, toVersion: target.latestVersion ?? undefined });
45178
+ deps.print(`weacpx 已更名为 ${successorPackage}(命令为 \`${SUCCESSOR.command}\`),已安装 ${successorPackage} ${target.latestVersion ?? "latest"} 并移除旧的 weacpx。今后请使用 \`${SUCCESSOR.command}\` 命令;若此前在后台运行,请用 \`${SUCCESSOR.command} start\` 重新启动。`);
45179
+ continue;
45180
+ }
44538
45181
  await selfUpdater(target.name);
44539
45182
  deps.print(`weacpx 已更新:${target.latestVersion ?? "latest"}`);
44540
45183
  continue;
@@ -44574,19 +45217,21 @@ async function handleUpdateCli(args, deps) {
44574
45217
  function formatTarget(target) {
44575
45218
  const current = target.currentVersion ?? "未锁定";
44576
45219
  const latest = target.latestVersion ?? "无法检查";
44577
- const label = target.kind === "self" ? "weacpx" : `插件 ${target.name}`;
44578
- 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})`;
44579
45224
  }
44580
45225
  async function selectTargets(targets, candidates, input) {
44581
45226
  if (input.explicitTarget) {
44582
- 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 === SUCCESSOR.command || input.explicitTarget === entry.successorPackage));
44583
45228
  if (!target)
44584
45229
  return { ok: false, message: `没有找到更新项:${input.explicitTarget}`, exitCode: 1 };
44585
45230
  if (!target.latestVersion)
44586
45231
  return { ok: false, message: `${target.name} 无法检查最新版本,已跳过。`, exitCode: 1 };
44587
45232
  if (target.kind === "plugin" && !target.pinned)
44588
45233
  return { ok: false, message: `${target.name} 未记录当前版本;请先使用 \`weacpx plugin update ${target.name}\` 或显式选择版本。`, exitCode: 1 };
44589
- if (target.currentVersion === target.latestVersion)
45234
+ if (!target.successorPackage && target.currentVersion === target.latestVersion)
44590
45235
  return { ok: true, targets: [] };
44591
45236
  return { ok: true, targets: [target] };
44592
45237
  }
@@ -44611,7 +45256,7 @@ async function selectTargets(targets, candidates, input) {
44611
45256
  return { ok: false, message: `${target.name} 无法检查最新版本,已跳过。`, exitCode: 1 };
44612
45257
  if (target.kind === "plugin" && !target.pinned)
44613
45258
  return { ok: false, message: `${target.name} 未记录当前版本;请先使用 \`weacpx plugin update ${target.name}\` 或显式选择版本。`, exitCode: 1 };
44614
- if (target.currentVersion === target.latestVersion)
45259
+ if (!target.successorPackage && target.currentVersion === target.latestVersion)
44615
45260
  continue;
44616
45261
  if (!selected.includes(target))
44617
45262
  selected.push(target);
@@ -44640,6 +45285,45 @@ async function defaultUpdateSelf(packageName) {
44640
45285
  }
44641
45286
  await runInherit("npm", ["install", "-g", packageName]);
44642
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
+ }
44643
45327
  async function runCapture(command, args) {
44644
45328
  return await new Promise((resolve, reject) => {
44645
45329
  const child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"] });
@@ -44671,8 +45355,8 @@ async function runInherit(command, args) {
44671
45355
  }
44672
45356
  async function readPackageName() {
44673
45357
  try {
44674
- const here = dirname9(fileURLToPath2(import.meta.url));
44675
- 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")]) {
44676
45360
  try {
44677
45361
  const parsed = JSON.parse(await readFile8(candidate, "utf8"));
44678
45362
  if (typeof parsed.name === "string" && parsed.name.trim())
@@ -44691,6 +45375,7 @@ async function validatePluginDefault(packageName, pluginHome) {
44691
45375
  init_version();
44692
45376
 
44693
45377
  // src/channels/cli/channel-cli.ts
45378
+ init_core_home();
44694
45379
  init_registry();
44695
45380
  init_create_channel();
44696
45381
  import { isDeepStrictEqual } from "node:util";
@@ -44985,7 +45670,7 @@ async function runRestart(deps) {
44985
45670
  } catch (error2) {
44986
45671
  const message = error2 instanceof Error ? error2.message : String(error2);
44987
45672
  deps.print(`配置已保存,但重启失败:${message}`);
44988
- deps.print("请查看日志:~/.weacpx/runtime/stderr.log");
45673
+ deps.print(`请查看日志:${coreHomeDisplayPath("runtime", "stderr.log")}`);
44989
45674
  deps.print("也可以稍后执行:weacpx start");
44990
45675
  return 1;
44991
45676
  }
@@ -45296,9 +45981,10 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
45296
45981
  }
45297
45982
 
45298
45983
  // src/plugins/plugin-cli.ts
45984
+ init_core_home();
45299
45985
  init_plugin_home();
45300
45986
  import { readFile as readFile10 } from "node:fs/promises";
45301
- import { isAbsolute, join as join9, resolve } from "node:path";
45987
+ import { isAbsolute, join as join12, resolve } from "node:path";
45302
45988
  init_plugin_loader();
45303
45989
  init_validate_plugin();
45304
45990
 
@@ -45308,13 +45994,13 @@ init_plugin_loader();
45308
45994
  init_validate_plugin();
45309
45995
  init_known_plugins();
45310
45996
  import { readFile as readFile9 } from "node:fs/promises";
45311
- import { join as join8 } from "node:path";
45997
+ import { join as join11 } from "node:path";
45312
45998
  function suggestedPluginPackageForChannel(type) {
45313
45999
  return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
45314
46000
  }
45315
46001
  async function readDependencyEntries(pluginHome) {
45316
46002
  try {
45317
- const raw = await readFile9(join8(pluginHome, "package.json"), "utf8");
46003
+ const raw = await readFile9(join11(pluginHome, "package.json"), "utf8");
45318
46004
  const parsed = JSON.parse(raw);
45319
46005
  const out = {};
45320
46006
  for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
@@ -45416,7 +46102,7 @@ function looksLikePath(spec) {
45416
46102
  }
45417
46103
  async function readDependencyEntries2(pluginHome) {
45418
46104
  try {
45419
- const raw = await readFile10(join9(pluginHome, "package.json"), "utf8");
46105
+ const raw = await readFile10(join12(pluginHome, "package.json"), "utf8");
45420
46106
  const parsed = JSON.parse(raw);
45421
46107
  const out = {};
45422
46108
  for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
@@ -45442,7 +46128,7 @@ async function resolveLocalPluginName(installSpec, pluginHome, namesBeforeInstal
45442
46128
  return name;
45443
46129
  }
45444
46130
  try {
45445
- const raw = await readFile10(join9(installSpec, "package.json"), "utf8");
46131
+ const raw = await readFile10(join12(installSpec, "package.json"), "utf8");
45446
46132
  const parsed = JSON.parse(raw);
45447
46133
  if (typeof parsed.name === "string" && parsed.name.trim())
45448
46134
  return parsed.name.trim();
@@ -45864,7 +46550,7 @@ async function runRestart2(deps) {
45864
46550
  return await deps.restartDaemon();
45865
46551
  } catch (error2) {
45866
46552
  deps.print(`配置已保存,但重启失败:${describeError(error2)}`);
45867
- deps.print("请查看日志:~/.weacpx/runtime/stderr.log");
46553
+ deps.print(`请查看日志:${coreHomeDisplayPath("runtime", "stderr.log")}`);
45868
46554
  deps.print("也可以稍后执行:weacpx start");
45869
46555
  return 1;
45870
46556
  }
@@ -46097,7 +46783,12 @@ async function runCli(args, deps = {}) {
46097
46783
  print,
46098
46784
  isInteractive: deps.isInteractive,
46099
46785
  promptText: deps.promptText,
46100
- overrides: deps.updateCliDeps
46786
+ overrides: {
46787
+ stopDaemon: async () => {
46788
+ await (deps.controller ?? createDefaultController(deps)).stop();
46789
+ },
46790
+ ...deps.updateCliDeps
46791
+ }
46101
46792
  })))(args.slice(1));
46102
46793
  if (result === null) {
46103
46794
  for (const line of HELP_LINES)
@@ -46543,7 +47234,7 @@ async function createCliScheduledTaskService() {
46543
47234
  return new ScheduledTaskService(state, stateStore);
46544
47235
  }
46545
47236
  function resolveConfigPathForCurrentEnv() {
46546
- return process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
47237
+ return process.env.WEACPX_CONFIG ?? join19(coreHomeDir(requireHome2()), "config.json");
46547
47238
  }
46548
47239
  function resolveDaemonPathsForCurrentConfig() {
46549
47240
  const configPath = resolveConfigPathForCurrentEnv();
@@ -46910,7 +47601,7 @@ function safeDaemonLogPaths() {
46910
47601
  const configPath = resolveConfigPathForCurrentEnv();
46911
47602
  const paths = resolveDaemonPathsForCurrentConfig();
46912
47603
  return {
46913
- appLog: join16(dirname15(configPath), "runtime", "app.log"),
47604
+ appLog: join19(dirname16(configPath), "runtime", "app.log"),
46914
47605
  stderrLog: paths.stderrLog
46915
47606
  };
46916
47607
  } catch {
@@ -46921,7 +47612,7 @@ function resolveCliEntryPath2() {
46921
47612
  if (process.argv[1]) {
46922
47613
  return process.argv[1];
46923
47614
  }
46924
- return fileURLToPath6(import.meta.url);
47615
+ return fileURLToPath7(import.meta.url);
46925
47616
  }
46926
47617
  function parseDoctorArgs(args) {
46927
47618
  const options = {};