weacpx 0.5.2 → 0.6.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.
Files changed (51) hide show
  1. package/README.md +15 -3
  2. package/dist/bridge/bridge-main.js +160 -4
  3. package/dist/channels/channel-scope.d.ts +8 -0
  4. package/dist/channels/types.d.ts +11 -0
  5. package/dist/channels/weixin-channel.d.ts +1 -0
  6. package/dist/cli.js +1115 -119
  7. package/dist/commands/command-hints.d.ts +11 -0
  8. package/dist/commands/command-list.d.ts +3 -0
  9. package/dist/commands/config-clone.d.ts +2 -0
  10. package/dist/commands/handlers/agent-handler.d.ts +6 -0
  11. package/dist/commands/handlers/config-handler.d.ts +5 -0
  12. package/dist/commands/handlers/later-handler.d.ts +21 -0
  13. package/dist/commands/handlers/orchestration-handler.d.ts +16 -0
  14. package/dist/commands/handlers/permission-handler.d.ts +9 -0
  15. package/dist/commands/handlers/session-handler.d.ts +37 -0
  16. package/dist/commands/handlers/workspace-handler.d.ts +8 -0
  17. package/dist/commands/help/help-registry.d.ts +4 -0
  18. package/dist/commands/help/help-types.d.ts +12 -0
  19. package/dist/commands/parse-command.d.ts +175 -0
  20. package/dist/commands/router-types.d.ts +144 -0
  21. package/dist/commands/workspace-name.d.ts +4 -0
  22. package/dist/commands/workspace-path.d.ts +4 -0
  23. package/dist/config/agent-templates.d.ts +4 -0
  24. package/dist/config/config-store.d.ts +13 -0
  25. package/dist/config/load-config.d.ts +10 -0
  26. package/dist/config/resolve-agent-command.d.ts +2 -0
  27. package/dist/formatting/render-text.d.ts +23 -0
  28. package/dist/orchestration/async-mutex.d.ts +4 -0
  29. package/dist/orchestration/build-coordinator-prompt.d.ts +66 -0
  30. package/dist/orchestration/orchestration-service.d.ts +471 -0
  31. package/dist/orchestration/progress-line-parser.d.ts +19 -0
  32. package/dist/orchestration/render-delegate-group-result.d.ts +6 -0
  33. package/dist/orchestration/render-delegate-question-package.d.ts +21 -0
  34. package/dist/orchestration/render-delegate-result.d.ts +2 -0
  35. package/dist/orchestration/task-watch-timeouts.d.ts +5 -0
  36. package/dist/plugin-api.d.ts +1 -0
  37. package/dist/scheduled/parse-later-time.d.ts +11 -0
  38. package/dist/scheduled/scheduled-render.d.ts +7 -0
  39. package/dist/scheduled/scheduled-service.d.ts +41 -0
  40. package/dist/scheduled/scheduled-types.d.ts +29 -0
  41. package/dist/sessions/session-service.d.ts +83 -0
  42. package/dist/state/state-store.d.ts +8 -0
  43. package/dist/state/types.d.ts +44 -0
  44. package/dist/transport/tool-event-mode.d.ts +14 -0
  45. package/dist/transport/types.d.ts +129 -0
  46. package/dist/util/path.d.ts +4 -0
  47. package/dist/util/sanitize.d.ts +10 -0
  48. package/dist/util/text.d.ts +3 -0
  49. package/dist/version.d.ts +2 -0
  50. package/dist/weixin/auth/accounts.d.ts +0 -1
  51. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2088,36 +2088,56 @@ var init_private_file = __esm(() => {
2088
2088
  import_write_file_atomic = __toESM(require_lib(), 1);
2089
2089
  });
2090
2090
 
2091
- // src/commands/workspace-path.ts
2092
- import { access } from "node:fs/promises";
2093
- import { homedir } from "node:os";
2091
+ // src/util/path.ts
2094
2092
  import path from "node:path";
2095
- function normalizeWorkspacePath(input) {
2093
+ import { homedir } from "node:os";
2094
+ function normalizePath(input) {
2096
2095
  const expanded = expandHome(input);
2097
2096
  if (isWindowsLikePath(expanded)) {
2098
2097
  return path.win32.normalize(expanded).replace(/\\/g, "/");
2099
2098
  }
2100
2099
  return path.posix.normalize(expanded.replace(/\\/g, "/"));
2101
2100
  }
2102
- function basenameForWorkspacePath(input) {
2103
- const normalized = normalizeWorkspacePath(input);
2101
+ function basenameForPath(input) {
2102
+ const normalized = normalizePath(input);
2104
2103
  if (ROOT_PATH_RE.test(normalized)) {
2105
2104
  return normalized;
2106
2105
  }
2107
2106
  const base = path.posix.basename(normalized);
2108
2107
  return base || normalized;
2109
2108
  }
2110
- function sameWorkspacePath(left, right) {
2111
- const normalizedLeft = normalizeWorkspacePath(left);
2112
- const normalizedRight = normalizeWorkspacePath(right);
2109
+ function isSamePath(left, right) {
2110
+ const normalizedLeft = normalizePath(left);
2111
+ const normalizedRight = normalizePath(right);
2113
2112
  if (isWindowsLikePath(normalizedLeft) || isWindowsLikePath(normalizedRight)) {
2114
2113
  return normalizedLeft.toLowerCase() === normalizedRight.toLowerCase();
2115
2114
  }
2116
2115
  return normalizedLeft === normalizedRight;
2117
2116
  }
2117
+ function isWindowsLikePath(input) {
2118
+ return WINDOWS_DRIVE_PATH_RE.test(input) || WINDOWS_UNC_PATH_RE.test(input);
2119
+ }
2118
2120
  function expandHome(input) {
2119
2121
  return input.startsWith("~") ? homedir() + input.slice(1) : input;
2120
2122
  }
2123
+ var WINDOWS_DRIVE_PATH_RE, WINDOWS_UNC_PATH_RE, ROOT_PATH_RE;
2124
+ var init_path = __esm(() => {
2125
+ WINDOWS_DRIVE_PATH_RE = /^[a-zA-Z]:[\\/]/;
2126
+ WINDOWS_UNC_PATH_RE = /^\\\\/;
2127
+ ROOT_PATH_RE = /^(\/|[a-zA-Z]:\/?)$/;
2128
+ });
2129
+
2130
+ // src/commands/workspace-path.ts
2131
+ import { access } from "node:fs/promises";
2132
+ function normalizeWorkspacePath(input) {
2133
+ return normalizePath(input);
2134
+ }
2135
+ function basenameForWorkspacePath(input) {
2136
+ return basenameForPath(input);
2137
+ }
2138
+ function sameWorkspacePath(left, right) {
2139
+ return isSamePath(left, right);
2140
+ }
2121
2141
  async function pathExists(filePath) {
2122
2142
  try {
2123
2143
  await access(filePath);
@@ -2126,14 +2146,8 @@ async function pathExists(filePath) {
2126
2146
  return false;
2127
2147
  }
2128
2148
  }
2129
- function isWindowsLikePath(input) {
2130
- return WINDOWS_DRIVE_PATH_RE.test(input) || WINDOWS_UNC_PATH_RE.test(input);
2131
- }
2132
- var WINDOWS_DRIVE_PATH_RE, WINDOWS_UNC_PATH_RE, ROOT_PATH_RE;
2133
2149
  var init_workspace_path = __esm(() => {
2134
- WINDOWS_DRIVE_PATH_RE = /^[a-zA-Z]:[\\/]/;
2135
- WINDOWS_UNC_PATH_RE = /^\\\\/;
2136
- ROOT_PATH_RE = /^(\/|[a-zA-Z]:\/?)$/;
2150
+ init_path();
2137
2151
  });
2138
2152
 
2139
2153
  // src/config/resolve-agent-command.ts
@@ -9688,8 +9702,10 @@ function readVersion(moduleUrl = import.meta.url) {
9688
9702
  }
9689
9703
  return "unknown";
9690
9704
  }
9691
- var PACKAGE_NAME = "weacpx";
9692
- var init_version = () => {};
9705
+ var PACKAGE_NAME = "weacpx", WEACPX_CORE_VERSION;
9706
+ var init_version = __esm(() => {
9707
+ WEACPX_CORE_VERSION = readVersion();
9708
+ });
9693
9709
 
9694
9710
  // src/orchestration/task-watch-timeouts.ts
9695
9711
  var DEFAULT_TASK_WATCH_TIMEOUT_MS = 60000, MAX_TASK_WATCH_TIMEOUT_MS, DEFAULT_TASK_WATCH_POLL_INTERVAL_MS = 1000, MAX_TASK_WATCH_POLL_INTERVAL_MS = 1e4, TASK_WATCH_RPC_TIMEOUT_PADDING_MS = 5000;
@@ -9713,10 +9729,60 @@ var init_quota_errors = __esm(() => {
9713
9729
  };
9714
9730
  });
9715
9731
 
9732
+ // src/util/sanitize.ts
9733
+ function sanitizeString(input, options = {}) {
9734
+ const {
9735
+ replacement = "-",
9736
+ collapse = false,
9737
+ trim = false,
9738
+ lowercase: lowercase2 = false,
9739
+ fallback
9740
+ } = options;
9741
+ let result = lowercase2 ? input.toLowerCase() : input;
9742
+ if (options.allow) {
9743
+ const pattern = new RegExp(`[^${options.allow.source.slice(1, -1)}]+`, "g");
9744
+ result = result.replace(pattern, replacement);
9745
+ } else if (options.deny) {
9746
+ result = result.replace(options.deny, replacement);
9747
+ }
9748
+ if (collapse) {
9749
+ const escaped = escapeRegExp(replacement);
9750
+ result = result.replace(new RegExp(`${escaped}+`, "g"), replacement);
9751
+ }
9752
+ if (trim) {
9753
+ const escaped = escapeRegExp(replacement);
9754
+ result = result.replace(new RegExp(`^${escaped}+|${escaped}+$`, "g"), "");
9755
+ }
9756
+ if (fallback !== undefined && result.length === 0) {
9757
+ return fallback;
9758
+ }
9759
+ return result;
9760
+ }
9761
+ function escapeRegExp(s) {
9762
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9763
+ }
9764
+
9765
+ // src/util/text.ts
9766
+ function truncateText(text, maxLength, ellipsis = "…") {
9767
+ if (text.length <= maxLength)
9768
+ return text;
9769
+ return text.slice(0, maxLength - ellipsis.length) + ellipsis;
9770
+ }
9771
+ function escapeForDoubleQuotes(input) {
9772
+ return input.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
9773
+ }
9774
+ function quoteIfNeeded(input) {
9775
+ return `"${escapeForDoubleQuotes(input)}"`;
9776
+ }
9777
+
9716
9778
  // src/commands/workspace-name.ts
9717
9779
  function sanitizeWorkspaceName(input, fallback = "workspace") {
9718
- const sanitized = input.trim().replace(UNSAFE_RUN_RE, "-").replace(TRIM_DASHES_RE, "");
9719
- return sanitized.length > 0 ? sanitized : fallback;
9780
+ return sanitizeString(input.trim(), {
9781
+ allow: /[a-zA-Z0-9._-]/,
9782
+ replacement: "-",
9783
+ trim: true,
9784
+ fallback
9785
+ });
9720
9786
  }
9721
9787
  function allocateWorkspaceName(base, existing) {
9722
9788
  if (!Object.prototype.hasOwnProperty.call(existing, base))
@@ -9732,13 +9798,11 @@ function isWorkspaceNameValid(input) {
9732
9798
  function quoteWorkspaceNameIfNeeded(input) {
9733
9799
  if (isWorkspaceNameValid(input))
9734
9800
  return input;
9735
- return `"${input.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
9801
+ return quoteIfNeeded(input);
9736
9802
  }
9737
- var VALID_WORKSPACE_NAME_RE, UNSAFE_RUN_RE, TRIM_DASHES_RE;
9803
+ var VALID_WORKSPACE_NAME_RE;
9738
9804
  var init_workspace_name = __esm(() => {
9739
9805
  VALID_WORKSPACE_NAME_RE = /^[a-zA-Z0-9._-]+$/;
9740
- UNSAFE_RUN_RE = /[^a-zA-Z0-9._-]+/g;
9741
- TRIM_DASHES_RE = /^-+|-+$/g;
9742
9806
  });
9743
9807
 
9744
9808
  // src/orchestration/orchestration-types.ts
@@ -9759,6 +9823,7 @@ function createEmptyState() {
9759
9823
  return {
9760
9824
  sessions: {},
9761
9825
  chat_contexts: {},
9826
+ native_session_lists: {},
9762
9827
  orchestration: createEmptyOrchestrationState(),
9763
9828
  scheduled_tasks: {}
9764
9829
  };
@@ -9977,11 +10042,14 @@ function parseOrchestrationState(raw, path3) {
9977
10042
  function isReplyMode2(value) {
9978
10043
  return value === "stream" || value === "final" || value === "verbose";
9979
10044
  }
10045
+ function isSessionSource(value) {
10046
+ return value === undefined || value === "weacpx" || value === "agent-side";
10047
+ }
9980
10048
  function isSessionRecord(value) {
9981
10049
  if (!isRecord2(value)) {
9982
10050
  return false;
9983
10051
  }
9984
- return isString(value.alias) && isString(value.agent) && isString(value.workspace) && isString(value.transport_session) && isOptionalString(value.transport_agent_command) && isOptionalString(value.mode_id) && (value.reply_mode === undefined || isReplyMode2(value.reply_mode)) && isString(value.created_at) && isString(value.last_used_at);
10052
+ return isString(value.alias) && isString(value.agent) && isString(value.workspace) && isString(value.transport_session) && isSessionSource(value.source) && isOptionalString(value.agent_session_id) && isOptionalString(value.agent_session_title) && isOptionalString(value.agent_session_updated_at) && isOptionalString(value.attached_at) && isOptionalString(value.transport_agent_command) && isOptionalString(value.mode_id) && (value.reply_mode === undefined || isReplyMode2(value.reply_mode)) && isString(value.created_at) && isString(value.last_used_at);
9985
10053
  }
9986
10054
  function parseSessions(raw, path3) {
9987
10055
  const sessions = {};
@@ -10006,6 +10074,30 @@ function parseChatContexts(raw, path3) {
10006
10074
  }
10007
10075
  return chatContexts;
10008
10076
  }
10077
+ function isNativeSessionCacheEntry(value) {
10078
+ if (!isRecord2(value)) {
10079
+ return false;
10080
+ }
10081
+ return isString(value.session_id) && isOptionalString(value.cwd) && (value.title === undefined || value.title === null || isString(value.title)) && isOptionalString(value.updated_at);
10082
+ }
10083
+ function isNativeSessionListCacheRecord(value) {
10084
+ if (!isRecord2(value)) {
10085
+ return false;
10086
+ }
10087
+ return isString(value.created_at) && isString(value.agent) && isOptionalString(value.workspace) && isString(value.cwd) && Array.isArray(value.sessions) && value.sessions.every(isNativeSessionCacheEntry) && (value.next_cursor === undefined || value.next_cursor === null || isString(value.next_cursor));
10088
+ }
10089
+ function parseNativeSessionLists(raw) {
10090
+ if (!isRecord2(raw)) {
10091
+ return {};
10092
+ }
10093
+ const nativeSessionLists = {};
10094
+ for (const [chatKey, value] of Object.entries(raw)) {
10095
+ if (isNativeSessionListCacheRecord(value)) {
10096
+ nativeSessionLists[chatKey] = value;
10097
+ }
10098
+ }
10099
+ return nativeSessionLists;
10100
+ }
10009
10101
  function isScheduledTaskStatus(value) {
10010
10102
  return value === "pending" || value === "triggering" || value === "executed" || value === "cancelled" || value === "missed" || value === "failed";
10011
10103
  }
@@ -10050,6 +10142,7 @@ function parseState(raw, path3) {
10050
10142
  return {
10051
10143
  sessions: parsedSessions,
10052
10144
  chat_contexts: parseChatContexts(chatContexts, path3),
10145
+ native_session_lists: parseNativeSessionLists(raw.native_session_lists),
10053
10146
  orchestration,
10054
10147
  scheduled_tasks: parseScheduledTasks(raw.scheduled_tasks, path3)
10055
10148
  };
@@ -10167,6 +10260,13 @@ function resolveSessionAliasForInput(channelId, displayAlias, existingAliases) {
10167
10260
  }
10168
10261
  return scopedAlias;
10169
10262
  }
10263
+ function scopeDisplayAliasToInternal(channelId, displayAlias) {
10264
+ const normalized = displayAlias.trim();
10265
+ if (normalized.length === 0) {
10266
+ throw new Error("display session alias must be non-empty");
10267
+ }
10268
+ return channelId === "weixin" ? normalized : toInternalSessionAlias(channelId, normalized);
10269
+ }
10170
10270
  function buildDefaultTransportSession(channelId, displayAlias) {
10171
10271
  const normalized = displayAlias.trim();
10172
10272
  if (normalized.length === 0) {
@@ -10252,7 +10352,7 @@ function renderLaterList(tasks, displaySession) {
10252
10352
  `).trimEnd();
10253
10353
  }
10254
10354
  function preview(text) {
10255
- return text.length <= LATER_MESSAGE_PREVIEW_CHARS ? text : `${text.slice(0, LATER_MESSAGE_PREVIEW_CHARS - 1)}…`;
10355
+ return truncateText(text, LATER_MESSAGE_PREVIEW_CHARS);
10256
10356
  }
10257
10357
  function formatLocalDateTime(date4) {
10258
10358
  const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
@@ -10398,7 +10498,10 @@ class ScheduledTaskService {
10398
10498
  }
10399
10499
  nextId() {
10400
10500
  for (let attempt = 0;attempt < 20; attempt += 1) {
10401
- const id = normalizeId(this.generateId()).replace(/[^0-9a-z]/g, "").slice(0, 6);
10501
+ const id = sanitizeString(normalizeId(this.generateId()), {
10502
+ allow: /[0-9a-z]/,
10503
+ replacement: ""
10504
+ }).slice(0, 6);
10402
10505
  if (id.length >= 4 && !this.state.scheduled_tasks[id])
10403
10506
  return id;
10404
10507
  }
@@ -10412,12 +10515,16 @@ class ScheduledTaskService {
10412
10515
  }
10413
10516
  }
10414
10517
  function normalizeId(input) {
10415
- return input.trim().replace(/^#/, "").toLowerCase();
10518
+ return sanitizeString(input.trim(), {
10519
+ deny: /^#/,
10520
+ replacement: "",
10521
+ lowercase: true
10522
+ });
10416
10523
  }
10417
10524
  var init_scheduled_service = () => {};
10418
10525
 
10419
10526
  // src/plugins/plugin-home.ts
10420
- import { mkdir as mkdir6, writeFile as writeFile4 } from "node:fs/promises";
10527
+ import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
10421
10528
  import { homedir as homedir3 } from "node:os";
10422
10529
  import { join as join3 } from "node:path";
10423
10530
  function coerceMissing(value) {
@@ -10441,6 +10548,27 @@ function resolvePluginHome(input = {}) {
10441
10548
  const home = coerceMissing(input.home) ?? coerceMissing(process.env.HOME) ?? homedir3();
10442
10549
  return join3(home, ".weacpx", "plugins");
10443
10550
  }
10551
+ async function normalizePluginHomeManifest(pluginHome) {
10552
+ const manifestPath = join3(pluginHome, "package.json");
10553
+ let raw;
10554
+ try {
10555
+ raw = await readFile6(manifestPath, "utf8");
10556
+ } catch {
10557
+ return false;
10558
+ }
10559
+ let parsed;
10560
+ try {
10561
+ parsed = JSON.parse(raw);
10562
+ } catch {
10563
+ return false;
10564
+ }
10565
+ const normalized = JSON.stringify(parsed, null, 2) + `
10566
+ `;
10567
+ if (normalized === raw)
10568
+ return false;
10569
+ await writeFile4(manifestPath, normalized, { mode: 384 });
10570
+ return true;
10571
+ }
10444
10572
  async function ensurePluginHome(pluginHome) {
10445
10573
  await mkdir6(pluginHome, { recursive: true, mode: 448 });
10446
10574
  await writeFile4(join3(pluginHome, "package.json"), JSON.stringify({ private: true, type: "module" }, null, 2) + `
@@ -10479,7 +10607,11 @@ var init_state_dir = () => {};
10479
10607
  import fs3 from "node:fs";
10480
10608
  import path4 from "node:path";
10481
10609
  function normalizeAccountId(raw) {
10482
- return raw.trim().toLowerCase().replace(/[@.]/g, "-");
10610
+ return sanitizeString(raw.trim(), {
10611
+ deny: /[@.]/g,
10612
+ replacement: "-",
10613
+ lowercase: true
10614
+ });
10483
10615
  }
10484
10616
  function deriveRawAccountId(normalizedId) {
10485
10617
  if (normalizedId.endsWith("-im-bot")) {
@@ -12988,16 +13120,24 @@ class RuntimeMediaStore {
12988
13120
  }
12989
13121
  function sanitizeMediaFileName(fileName, mimeType) {
12990
13122
  const base = path7.basename(fileName.trim() || "attachment");
12991
- const replaced = base.replace(/[\\/:*?"<>|\s]+/g, "-").replace(/^-+|-+$/g, "");
12992
- const safe = replaced || "attachment";
13123
+ const safe = sanitizeString(base, {
13124
+ deny: /[\\/:*?"<>|\s]+/g,
13125
+ replacement: "-",
13126
+ trim: true,
13127
+ fallback: "attachment"
13128
+ });
12993
13129
  const ext = path7.extname(safe);
12994
13130
  if (ext)
12995
13131
  return safe;
12996
13132
  return `${safe}${extensionFromMime(mimeType)}`;
12997
13133
  }
12998
13134
  function safePathSegment(value) {
12999
- const safe = value.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
13000
- return safe || "unknown";
13135
+ return sanitizeString(value, {
13136
+ allow: /[A-Za-z0-9._-]/,
13137
+ replacement: "_",
13138
+ trim: true,
13139
+ fallback: "unknown"
13140
+ });
13001
13141
  }
13002
13142
  async function uniqueFileName(dir, baseName) {
13003
13143
  const ext = path7.extname(baseName);
@@ -15985,7 +16125,7 @@ var init_scheduled_turn = __esm(() => {
15985
16125
  });
15986
16126
 
15987
16127
  // src/weixin/monitor/consumer-lock.ts
15988
- import { mkdir as mkdir8, open as open3, readFile as readFile6, rm as rm6 } from "node:fs/promises";
16128
+ import { mkdir as mkdir8, open as open3, readFile as readFile7, rm as rm6 } from "node:fs/promises";
15989
16129
  import { dirname as dirname8, join as join5 } from "node:path";
15990
16130
  import { homedir as homedir4 } from "node:os";
15991
16131
  function createWeixinConsumerLock(options = {}) {
@@ -16067,7 +16207,7 @@ function createWeixinConsumerLock(options = {}) {
16067
16207
  }
16068
16208
  async function loadLockMetadata(path14) {
16069
16209
  try {
16070
- const raw = await readFile6(path14, "utf8");
16210
+ const raw = await readFile7(path14, "utf8");
16071
16211
  const parsed = JSON.parse(raw);
16072
16212
  if (!parsed || typeof parsed.pid !== "number" || !parsed.mode || !parsed.configPath || !parsed.statePath) {
16073
16213
  return null;
@@ -16110,6 +16250,7 @@ var init_consumer_lock = __esm(() => {
16110
16250
  // src/channels/weixin-channel.ts
16111
16251
  class WeixinChannel {
16112
16252
  id = "weixin";
16253
+ nativeSessionListFormat = "cards";
16113
16254
  agent = null;
16114
16255
  quota = null;
16115
16256
  logger = null;
@@ -16748,7 +16889,7 @@ var init_app_logger = __esm(() => {
16748
16889
  });
16749
16890
 
16750
16891
  // src/transport/acpx-session-index.ts
16751
- import { readFile as readFile10 } from "node:fs/promises";
16892
+ import { readFile as readFile11 } from "node:fs/promises";
16752
16893
  import { homedir as homedir5 } from "node:os";
16753
16894
  import { resolve as resolve2 } from "node:path";
16754
16895
  async function resolveSessionAgentCommandFromIndex(session) {
@@ -16757,7 +16898,7 @@ async function resolveSessionAgentCommandFromIndex(session) {
16757
16898
  return;
16758
16899
  }
16759
16900
  try {
16760
- const raw = await readFile10(resolve2(home, ".acpx", "sessions", "index.json"), "utf8");
16901
+ const raw = await readFile11(resolve2(home, ".acpx", "sessions", "index.json"), "utf8");
16761
16902
  const parsed = JSON.parse(raw);
16762
16903
  const targetCwd = resolve2(session.cwd);
16763
16904
  const match = parsed.entries?.find((entry) => entry.name === session.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
@@ -16917,6 +17058,7 @@ var init_command_list = __esm(() => {
16917
17058
  "/pm",
16918
17059
  "/session",
16919
17060
  "/ss",
17061
+ "/ssn",
16920
17062
  "/workspace",
16921
17063
  "/ws",
16922
17064
  "/use",
@@ -16999,6 +17141,51 @@ function parseCommand(input) {
16999
17141
  if (command === "/session" && parts[1] === "rm" && parts[2] && parts.length === 3) {
17000
17142
  return { kind: "session.rm", alias: parts[2] };
17001
17143
  }
17144
+ if (command === "/ssn") {
17145
+ if (parts.length === 1) {
17146
+ return { kind: "session.native.list" };
17147
+ }
17148
+ const identifier = parts[1] ?? "";
17149
+ if (/^\d+$/.test(identifier)) {
17150
+ const selected = readNativeAttachCommand(parts, 1);
17151
+ if (!selected) {
17152
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/ssn" };
17153
+ }
17154
+ return selected.alias ? { kind: "session.native.select", identifier, alias: selected.alias } : { kind: "session.native.select", identifier };
17155
+ }
17156
+ if (parts[1] === "attach") {
17157
+ if (!parts[2]) {
17158
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/ssn" };
17159
+ }
17160
+ const attached = readNativeAttachCommand(parts, 2);
17161
+ if (attached) {
17162
+ return attached;
17163
+ }
17164
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/ssn" };
17165
+ }
17166
+ const nativeList = readNativeListCommand(parts, 1);
17167
+ if (nativeList) {
17168
+ return nativeList;
17169
+ }
17170
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/ssn" };
17171
+ }
17172
+ if (command === "/session" && parts[1] === "native") {
17173
+ const nativeList = readNativeListCommand(parts, 2);
17174
+ if (nativeList) {
17175
+ return nativeList;
17176
+ }
17177
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/session" };
17178
+ }
17179
+ if (command === "/session" && parts[1] === "attach" && parts[2] === "native") {
17180
+ if (!parts[3]) {
17181
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/session" };
17182
+ }
17183
+ const attached = readNativeAttachCommand(parts, 3);
17184
+ if (attached) {
17185
+ return attached;
17186
+ }
17187
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/session" };
17188
+ }
17002
17189
  if (command === "/group" && parts[1] === "new" && parts.length > 2) {
17003
17190
  const title = parts.slice(2).join(" ");
17004
17191
  if (title.trim().length > 0) {
@@ -17264,6 +17451,97 @@ function readSessionShortcutTarget(parts, startIndex) {
17264
17451
  }
17265
17452
  return null;
17266
17453
  }
17454
+ function readNativeListCommand(parts, startIndex) {
17455
+ let agent = "";
17456
+ let cwd = "";
17457
+ let workspace = "";
17458
+ let all = false;
17459
+ let cursor = "";
17460
+ let invalid = false;
17461
+ if (startIndex < parts.length && !parts[startIndex]?.startsWith("-")) {
17462
+ agent = parts[startIndex] ?? "";
17463
+ startIndex += 1;
17464
+ }
17465
+ for (let index = startIndex;index < parts.length; index += 1) {
17466
+ const part = parts[index];
17467
+ if (part === "--all") {
17468
+ all = true;
17469
+ continue;
17470
+ }
17471
+ if (part === "--cursor") {
17472
+ const value = parts[index + 1] ?? "";
17473
+ if (index + 1 >= parts.length || value.startsWith("-")) {
17474
+ invalid = true;
17475
+ break;
17476
+ }
17477
+ cursor = value;
17478
+ index += 1;
17479
+ continue;
17480
+ }
17481
+ if (part === "--cwd" || part === "-d") {
17482
+ const value = parts[index + 1] ?? "";
17483
+ if (index + 1 >= parts.length || value.startsWith("-") || workspace) {
17484
+ invalid = true;
17485
+ break;
17486
+ }
17487
+ cwd = value;
17488
+ index += 1;
17489
+ continue;
17490
+ }
17491
+ if (part === "--ws" || part === "-ws") {
17492
+ const value = parts[index + 1] ?? "";
17493
+ if (index + 1 >= parts.length || value.startsWith("-") || cwd) {
17494
+ invalid = true;
17495
+ break;
17496
+ }
17497
+ workspace = value;
17498
+ index += 1;
17499
+ continue;
17500
+ }
17501
+ invalid = true;
17502
+ break;
17503
+ }
17504
+ if (invalid) {
17505
+ return null;
17506
+ }
17507
+ const result = {
17508
+ kind: "session.native.list"
17509
+ };
17510
+ if (agent.trim().length > 0)
17511
+ result.agent = agent;
17512
+ if (cwd.trim().length > 0)
17513
+ result.cwd = cwd;
17514
+ if (workspace.trim().length > 0)
17515
+ result.workspace = workspace;
17516
+ if (all)
17517
+ result.all = true;
17518
+ if (cursor.trim().length > 0)
17519
+ result.cursor = cursor;
17520
+ return Object.keys(result).length > 1 ? result : { kind: "session.native.list" };
17521
+ }
17522
+ function readNativeAttachCommand(parts, identifierIndex) {
17523
+ const identifier = parts[identifierIndex] ?? "";
17524
+ let alias = "";
17525
+ for (let index = identifierIndex + 1;index < parts.length; index += 1) {
17526
+ if (parts[index] === "-a" || parts[index] === "--alias") {
17527
+ const value = parts[index + 1] ?? "";
17528
+ if (index + 1 >= parts.length || value.startsWith("-")) {
17529
+ return null;
17530
+ }
17531
+ alias = value;
17532
+ index += 1;
17533
+ continue;
17534
+ }
17535
+ return null;
17536
+ }
17537
+ if (identifier.trim().length === 0 || identifier.startsWith("-")) {
17538
+ return null;
17539
+ }
17540
+ if (alias.trim().length > 0) {
17541
+ return { kind: "session.native.attach", identifier, alias };
17542
+ }
17543
+ return { kind: "session.native.attach", identifier };
17544
+ }
17267
17545
  function normalizeCommand(command) {
17268
17546
  if (command === "/ss")
17269
17547
  return "/session";
@@ -17501,6 +17779,9 @@ var init_command_policy = __esm(() => {
17501
17779
  "session.shortcut": "/session",
17502
17780
  "session.shortcut.new": "/session",
17503
17781
  "session.attach": "/session attach",
17782
+ "session.native.list": "/ssn",
17783
+ "session.native.select": "/ssn",
17784
+ "session.native.attach": "/ssn attach",
17504
17785
  "later.create": "/later",
17505
17786
  "later.list": "/later list",
17506
17787
  "later.cancel": "/later cancel"
@@ -18118,7 +18399,7 @@ async function buildCoordinatorPrompt(input) {
18118
18399
 
18119
18400
  `) : input.userText ?? "";
18120
18401
  if (input.maxPromptLength && promptText.length > input.maxPromptLength) {
18121
- promptText = promptText.slice(0, input.maxPromptLength - 3) + "...";
18402
+ promptText = truncateText(promptText, input.maxPromptLength, "...");
18122
18403
  }
18123
18404
  return {
18124
18405
  promptText,
@@ -18161,7 +18442,7 @@ async function handleSessions(context, chatKey) {
18161
18442
  }
18162
18443
  async function handleSessionNew(context, chatKey, alias, agent, workspace) {
18163
18444
  const channelId = getChannelIdFromChatKey(chatKey);
18164
- const internalAlias = channelId === "weixin" ? alias : toInternalSessionAlias(channelId, alias);
18445
+ const internalAlias = scopeDisplayAliasToInternal(channelId, alias);
18165
18446
  const session = context.lifecycle.resolveSession(internalAlias, agent, workspace, `${workspace}:${internalAlias}`);
18166
18447
  const releaseTransportReservation = await context.lifecycle.reserveTransportSession(session.transportSession);
18167
18448
  try {
@@ -18192,7 +18473,7 @@ async function handleSessionShortcut(context, chatKey, agent, target, createNew)
18192
18473
  }
18193
18474
  async function handleSessionAttach(context, chatKey, alias, agent, workspace, transportSession) {
18194
18475
  const channelId = getChannelIdFromChatKey(chatKey);
18195
- const internalAlias = channelId === "weixin" ? alias : toInternalSessionAlias(channelId, alias);
18476
+ const internalAlias = scopeDisplayAliasToInternal(channelId, alias);
18196
18477
  const attached = context.lifecycle.resolveSession(internalAlias, agent, workspace, transportSession);
18197
18478
  const releaseTransportReservation = await context.lifecycle.reserveTransportSession(attached.transportSession);
18198
18479
  try {
@@ -18559,7 +18840,7 @@ async function markCoordinatorResultsInjectionFailed(context, taskIds, groupIds,
18559
18840
  });
18560
18841
  }
18561
18842
  }
18562
- var NO_CURRENT_SESSION_TEXT = "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。", DEFAULT_SESSION_TAIL_LINES = 50, MAX_SESSION_TAIL_LINES = 500, sessionHelp, modeHelp, replyModeHelp, statusHelp, cancelHelp;
18843
+ var NO_CURRENT_SESSION_TEXT = "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。", DEFAULT_SESSION_TAIL_LINES = 50, MAX_SESSION_TAIL_LINES = 500, sessionHelp, nativeSessionHelp, modeHelp, replyModeHelp, statusHelp, cancelHelp;
18563
18844
  var init_session_handler = __esm(() => {
18564
18845
  init_build_coordinator_prompt();
18565
18846
  init_channel_scope();
@@ -18567,7 +18848,7 @@ var init_session_handler = __esm(() => {
18567
18848
  sessionHelp = {
18568
18849
  topic: "session",
18569
18850
  aliases: ["ss", "sessions"],
18570
- summary: "创建、恢复、切换和重置逻辑会话。",
18851
+ summary: "创建、复用、切换和重置 weacpx 逻辑会话。",
18571
18852
  commands: [
18572
18853
  { usage: "/sessions", description: "查看当前会话列表" },
18573
18854
  { usage: "/session 或 /ss", description: "查看会话列表" },
@@ -18575,12 +18856,48 @@ var init_session_handler = __esm(() => {
18575
18856
  { usage: "/ss new <agent> (-d <path> | --ws <name>)", description: "强制新建会话" },
18576
18857
  { usage: "/ss new <alias> -a <name> --ws <name>", description: "按指定配置新建会话" },
18577
18858
  { usage: "/ss attach <alias> -a <name> --ws <name> --name <transport-session>", description: "绑定已有会话" },
18859
+ { usage: "/ssn 或 /help ssn", description: "接入本地 native 会话(Codex 等 Agent 原生会话)" },
18578
18860
  { usage: "/session tail [N]", description: "补拉当前会话的历史输出(默认 50 行)" },
18579
18861
  { usage: "/session rm <alias>", description: "删除逻辑会话" },
18580
18862
  { usage: "/use <alias>", description: "切换当前会话" },
18581
18863
  { usage: "/session reset 或 /clear", description: "重置当前会话上下文" }
18582
18864
  ],
18583
- examples: ["/ss codex -d /absolute/path/to/repo", "/use backend-fix", "/session rm old-session", "/session reset"]
18865
+ examples: [
18866
+ "/ss codex -d /absolute/path/to/repo",
18867
+ "/ssn",
18868
+ "/ssn 1",
18869
+ "/use backend-fix",
18870
+ "/session rm old-session",
18871
+ "/session reset"
18872
+ ]
18873
+ };
18874
+ nativeSessionHelp = {
18875
+ topic: "native",
18876
+ aliases: ["ssn", "native-session"],
18877
+ summary: "接入 Codex 等 Agent 的本地原生会话。",
18878
+ commands: [
18879
+ { usage: "/ssn", description: "按当前 weacpx 会话上下文查看本地 native 会话" },
18880
+ { usage: "/ssn <agent> --ws <workspace>", description: "查询指定工作区的本地 native 会话;只有一个候选时自动接入" },
18881
+ { usage: "/ssn <agent> -d <path>", description: "按本机绝对路径查询;只有一个候选时自动接入" },
18882
+ { usage: "/ssn <agent> --ws <workspace> --all", description: "跨 cwd 查看该 agent 的 native 会话" },
18883
+ { usage: "/ssn 1", description: "接入或切换到最近一次列表里的第 1 个候选" },
18884
+ { usage: "/ssn 1 -a <alias>", description: "接入第 1 个候选并指定 weacpx 别名(推荐,无需完整 sessionId)" },
18885
+ { usage: "/ssn attach <sessionId> -a <alias>", description: "按原生 sessionId 接入(适合已知完整 id),并指定 weacpx 别名" },
18886
+ { usage: "/ss attach native <sessionId> -a <alias>", description: "/ssn attach 的长写法" }
18887
+ ],
18888
+ examples: [
18889
+ "/ssn codex --ws backend",
18890
+ "/ssn codex -d /absolute/path/to/repo",
18891
+ "/ssn",
18892
+ "/ssn 1",
18893
+ "/ssn 1 -a fix-ci"
18894
+ ],
18895
+ notes: [
18896
+ "/ss 管 weacpx 逻辑会话;/ssn 只负责查询和接入 Agent 原生会话。",
18897
+ "接入后继续发普通消息,会继续同一个 Agent 原生会话,不是复制一份新上下文。",
18898
+ "如果当前 acpx 或 Agent 不支持 native 会话,请继续使用 /ss。",
18899
+ "完整说明见 docs/native-sessions.md。"
18900
+ ]
18584
18901
  };
18585
18902
  modeHelp = {
18586
18903
  topic: "mode",
@@ -19543,6 +19860,7 @@ var init_help_registry = __esm(() => {
19543
19860
  init_later_handler();
19544
19861
  HELP_TOPICS = [
19545
19862
  sessionHelp,
19863
+ nativeSessionHelp,
19546
19864
  workspaceHelp,
19547
19865
  agentHelp,
19548
19866
  permissionHelp,
@@ -19574,11 +19892,33 @@ function handleHelp(topic) {
19574
19892
  }
19575
19893
  return { text: renderHelpTopic(entry) };
19576
19894
  }
19895
+ function handleInvalidCommand(recognizedCommand) {
19896
+ const topicName = recognizedCommand.replace(/^\//, "");
19897
+ const entry = getHelpTopic(topicName);
19898
+ if (entry) {
19899
+ return { text: `命令格式不正确,请参考下面的用法:
19900
+
19901
+ ${renderHelpTopic(entry)}` };
19902
+ }
19903
+ return {
19904
+ text: [
19905
+ "无法识别的命令格式。",
19906
+ "",
19907
+ "正确的会话创建格式:",
19908
+ "/session new <别名> --agent <Agent名> --ws <工作区名>",
19909
+ "",
19910
+ "例如:",
19911
+ "/session new demo --agent claude --ws weacpx"
19912
+ ].join(`
19913
+ `)
19914
+ };
19915
+ }
19577
19916
  function renderHelpIndex() {
19578
19917
  const topics = listHelpTopics();
19579
19918
  return [
19580
19919
  "常用入口:",
19581
19920
  "- /ss <agent> (-d <path> | --ws <name>) - 快速新建或切到会话",
19921
+ "- /ssn <agent> (-d <path> | --ws <name>) - 接入本地 Agent 原生会话",
19582
19922
  "- /use <alias> - 切换当前会话",
19583
19923
  "- /status - 查看当前会话状态",
19584
19924
  "",
@@ -19587,7 +19927,7 @@ function renderHelpIndex() {
19587
19927
  "",
19588
19928
  "查看专题说明:",
19589
19929
  "- /help <topic>",
19590
- "- 例如:/help ss、/help ws、/help pm"
19930
+ "- 例如:/help ss、/help ssn、/help ws、/help pm"
19591
19931
  ].join(`
19592
19932
  `);
19593
19933
  }
@@ -19669,7 +20009,7 @@ async function handleSessionShortcutCommand(context, ops, chatKey, agent, target
19669
20009
  });
19670
20010
  const baseAlias = `${workspace.name}:${agent}`;
19671
20011
  const channelId = getChannelIdFromChatKey(chatKey);
19672
- const scopedBase = channelId === "weixin" ? baseAlias : `${channelId}:${baseAlias}`;
20012
+ const scopedBase = scopeDisplayAliasToInternal(channelId, baseAlias);
19673
20013
  const alias = createNew ? await allocateUniqueSessionAlias(context, scopedBase, chatKey) : scopedBase;
19674
20014
  const display = toDisplaySessionAlias(alias);
19675
20015
  if (!createNew && await hasLogicalSession(context, alias, chatKey)) {
@@ -19798,6 +20138,404 @@ var init_session_shortcut_handler = __esm(() => {
19798
20138
  init_channel_scope();
19799
20139
  });
19800
20140
 
20141
+ // src/commands/handlers/native-session-handler.ts
20142
+ async function handleNativeSessionList(context, chatKey, input) {
20143
+ const target = await resolveNativeTarget(context, chatKey, input);
20144
+ if (isRouterResponse(target)) {
20145
+ return target;
20146
+ }
20147
+ const listAgentSessions = context.transport.listAgentSessions?.bind(context.transport);
20148
+ if (!listAgentSessions) {
20149
+ return { text: `当前 transport 不支持列出本地会话,请继续使用 /ss。
20150
+ 说明:/help ssn` };
20151
+ }
20152
+ const query = {
20153
+ agent: target.agent,
20154
+ agentCommand: target.agentCommand,
20155
+ cwd: target.cwd,
20156
+ ...input.cursor ? { cursor: input.cursor } : {},
20157
+ ...input.all ? {} : { filterCwd: target.cwd }
20158
+ };
20159
+ let result;
20160
+ try {
20161
+ result = await listAgentSessions(query);
20162
+ } catch (error2) {
20163
+ return { text: renderNativeListError(target, error2) };
20164
+ }
20165
+ if (!result) {
20166
+ return { text: `当前 transport 不支持列出本地会话,请继续使用 /ss。
20167
+ 说明:/help ssn` };
20168
+ }
20169
+ await context.sessions.cacheNativeSessionList(chatKey, {
20170
+ agent: target.agent,
20171
+ workspace: target.workspace,
20172
+ cwd: target.cwd,
20173
+ sessions: result.sessions,
20174
+ ...result.nextCursor !== undefined ? { nextCursor: result.nextCursor } : {}
20175
+ });
20176
+ if (result.sessions.length === 0) {
20177
+ return {
20178
+ text: [
20179
+ `没有找到本地 ${target.agentDisplayName} 会话(${target.workspaceLabel})。`,
20180
+ `你可以稍后再试,或先通过 /ss 保持当前逻辑会话。`
20181
+ ].join(`
20182
+ `)
20183
+ };
20184
+ }
20185
+ const explicitAttachTarget = Boolean(input.workspace || input.cwd);
20186
+ if (explicitAttachTarget && !input.all && !input.cursor && result.sessions.length === 1) {
20187
+ return await attachNativeSession(context, chatKey, target, result.sessions[0], undefined);
20188
+ }
20189
+ const attachedEntries = await buildAttachedEntries(context, chatKey, target.agent, result.sessions);
20190
+ const nativeSessionListOptions = { format: context.resolveNativeSessionListFormat?.(chatKey) ?? "table" };
20191
+ return {
20192
+ text: renderNativeSessionList(target, result, attachedEntries, Boolean(input.all), nativeSessionListOptions)
20193
+ };
20194
+ }
20195
+ async function handleNativeSessionSelect(context, chatKey, identifier, alias) {
20196
+ const trimmed = identifier.trim();
20197
+ if (!trimmed) {
20198
+ return { text: `请选择要切换的 native 会话编号或 sessionId。
20199
+ 说明:/help ssn` };
20200
+ }
20201
+ if (/^[0-9]+$/.test(trimmed)) {
20202
+ const cached2 = await context.sessions.getNativeSessionList(chatKey, NATIVE_SESSION_CACHE_TTL_MS);
20203
+ if (!cached2 || cached2.sessions.length === 0) {
20204
+ return { text: `当前没有可用的 native 会话列表,请先执行 /ssn 再选择。
20205
+ 说明:/help ssn` };
20206
+ }
20207
+ const index = Number(trimmed) - 1;
20208
+ const session = cached2.sessions[index];
20209
+ if (!session) {
20210
+ return { text: "编号超出范围,请先执行 /ssn 重新获取列表。" };
20211
+ }
20212
+ const target2 = await resolveTargetFromCachedSession(context, chatKey, cached2, session);
20213
+ if (isRouterResponse(target2)) {
20214
+ return target2;
20215
+ }
20216
+ return await attachNativeSession(context, chatKey, target2, session, alias);
20217
+ }
20218
+ const target = await resolveNativeTarget(context, chatKey, {});
20219
+ if (isRouterResponse(target)) {
20220
+ return target;
20221
+ }
20222
+ return await attachNativeSession(context, chatKey, target, { sessionId: trimmed }, alias);
20223
+ }
20224
+ async function attachNativeSession(context, chatKey, target, session, alias) {
20225
+ if (!context.transport.resumeAgentSession) {
20226
+ return { text: "当前 transport 不支持接入本地会话,请继续使用 /ss。" };
20227
+ }
20228
+ const nativeTarget = target;
20229
+ const existing = await context.sessions.findAttachedNativeSession(chatKey, nativeTarget.agent, session.sessionId);
20230
+ if (existing) {
20231
+ await context.sessions.useSession(chatKey, existing.alias);
20232
+ const displayAlias2 = toDisplaySessionAlias(existing.alias);
20233
+ return {
20234
+ text: `已切换到已接入的本地会话:${nativeTarget.agentDisplayName} · ${displayAlias2}`
20235
+ };
20236
+ }
20237
+ const requestedAlias = alias?.trim() || buildDefaultNativeAlias(nativeTarget.agent, session.sessionId);
20238
+ const displayAlias = await allocateUniqueNativeAlias(context, chatKey, requestedAlias);
20239
+ const internalAlias = scopeDisplayAliasToInternal(getChannelIdFromChatKey(chatKey), displayAlias);
20240
+ const transportSession = context.sessions.buildDefaultTransportSessionForChat(chatKey, displayAlias);
20241
+ const resolvedSession = context.lifecycle.resolveSession(internalAlias, nativeTarget.agent, nativeTarget.workspace, transportSession);
20242
+ const releaseReservation = await context.lifecycle.reserveTransportSession(resolvedSession.transportSession);
20243
+ try {
20244
+ try {
20245
+ await context.transport.resumeAgentSession(resolvedSession, session.sessionId);
20246
+ } catch (error2) {
20247
+ return { text: renderNativeResumeError(target, error2) };
20248
+ }
20249
+ const verified = await context.lifecycle.checkTransportSession(resolvedSession);
20250
+ if (!verified) {
20251
+ return { text: `本地 ${target.agentDisplayName} 会话接入失败:未检测到已恢复的后端会话。` };
20252
+ }
20253
+ await context.sessions.attachNativeSession({
20254
+ alias: internalAlias,
20255
+ agent: nativeTarget.agent,
20256
+ workspace: nativeTarget.workspace,
20257
+ transportSession,
20258
+ ...target.agentCommand ? { transportAgentCommand: target.agentCommand } : {},
20259
+ agentSessionId: session.sessionId,
20260
+ title: session.title,
20261
+ updatedAt: session.updatedAt
20262
+ });
20263
+ await context.sessions.useSession(chatKey, internalAlias);
20264
+ await refreshAgentCommandBestEffort(context, internalAlias);
20265
+ return {
20266
+ text: `已接入本地 ${target.agentDisplayName} 会话并切换:${toDisplaySessionAlias(internalAlias)}`
20267
+ };
20268
+ } finally {
20269
+ await releaseReservation();
20270
+ }
20271
+ }
20272
+ async function resolveNativeTarget(context, chatKey, input) {
20273
+ const currentSession = await context.sessions.getCurrentSession(chatKey);
20274
+ const agent = input.agent?.trim() || currentSession?.agent || "";
20275
+ if (!agent) {
20276
+ return {
20277
+ text: `请先选择上下文,例如:
20278
+ /ssn codex --ws project
20279
+ /ssn codex -d /Users/me/project
20280
+ 说明:/help ssn`
20281
+ };
20282
+ }
20283
+ const agentConfig = context.config?.agents[agent];
20284
+ if (!agentConfig) {
20285
+ return { text: `Agent「${agent}」未注册。` };
20286
+ }
20287
+ const workspaceResolution = await resolveNativeWorkspace(context, input, currentSession);
20288
+ if (isRouterResponse(workspaceResolution)) {
20289
+ return workspaceResolution;
20290
+ }
20291
+ return {
20292
+ agent,
20293
+ agentDisplayName: displayAgentName(agent),
20294
+ agentCommand: resolveAgentCommand(agentConfig.driver, agentConfig.command),
20295
+ workspace: workspaceResolution.workspace,
20296
+ workspaceLabel: workspaceResolution.workspaceLabel,
20297
+ cwd: workspaceResolution.cwd,
20298
+ source: workspaceResolution.source
20299
+ };
20300
+ }
20301
+ async function resolveTargetFromCachedSession(context, chatKey, cached2, session) {
20302
+ if (session.cwd && !sameWorkspacePath(session.cwd, cached2.cwd)) {
20303
+ return await resolveNativeTarget(context, chatKey, {
20304
+ agent: cached2.agent,
20305
+ cwd: session.cwd
20306
+ });
20307
+ }
20308
+ return await resolveNativeTarget(context, chatKey, {
20309
+ agent: cached2.agent,
20310
+ ...cached2.workspace ? { workspace: cached2.workspace } : { cwd: cached2.cwd }
20311
+ });
20312
+ }
20313
+ async function resolveNativeWorkspace(context, input, currentSession) {
20314
+ if (input.workspace) {
20315
+ const workspaceConfig = context.config?.workspaces[input.workspace];
20316
+ if (!workspaceConfig) {
20317
+ return { text: `工作区「${input.workspace}」未注册。` };
20318
+ }
20319
+ return {
20320
+ workspace: input.workspace,
20321
+ workspaceLabel: input.workspace,
20322
+ cwd: workspaceConfig.cwd,
20323
+ source: "workspace"
20324
+ };
20325
+ }
20326
+ if (input.cwd) {
20327
+ const cwd = normalizeWorkspacePath(input.cwd);
20328
+ const existing = Object.entries(context.config?.workspaces ?? {}).find(([, workspace]) => sameWorkspacePath(workspace.cwd, cwd));
20329
+ if (existing) {
20330
+ return {
20331
+ workspace: existing[0],
20332
+ workspaceLabel: existing[0],
20333
+ cwd: existing[1].cwd,
20334
+ source: "cwd"
20335
+ };
20336
+ }
20337
+ if (!await pathExists(cwd)) {
20338
+ return { text: `工作区路径不存在:${input.cwd}` };
20339
+ }
20340
+ if (!context.configStore || !context.config) {
20341
+ return { text: "当前没有加载可写入的配置,无法根据路径创建工作区。" };
20342
+ }
20343
+ const workspaceName = allocateWorkspaceName(sanitizeWorkspaceName(basenameForWorkspacePath(cwd)), context.config.workspaces);
20344
+ const updated = await context.configStore.upsertWorkspace(workspaceName, cwd);
20345
+ context.replaceConfig(updated);
20346
+ return {
20347
+ workspace: workspaceName,
20348
+ workspaceLabel: workspaceName,
20349
+ cwd,
20350
+ source: "cwd"
20351
+ };
20352
+ }
20353
+ if (currentSession) {
20354
+ return {
20355
+ workspace: currentSession.workspace,
20356
+ workspaceLabel: currentSession.workspace,
20357
+ cwd: currentSession.cwd,
20358
+ source: "workspace"
20359
+ };
20360
+ }
20361
+ return {
20362
+ text: `请先选择上下文,例如:
20363
+ /ssn codex --ws project
20364
+ /ssn codex -d /Users/me/project
20365
+ 说明:/help ssn`
20366
+ };
20367
+ }
20368
+ async function buildAttachedEntries(context, chatKey, agent, sessions) {
20369
+ const currentSession = await context.sessions.getCurrentSession(chatKey);
20370
+ return await Promise.all(sessions.map(async (session) => {
20371
+ const attached = await context.sessions.findAttachedNativeSession(chatKey, agent, session.sessionId);
20372
+ if (!attached) {
20373
+ return { session };
20374
+ }
20375
+ return {
20376
+ session,
20377
+ attached: {
20378
+ alias: attached.alias,
20379
+ displayAlias: toDisplaySessionAlias(attached.alias),
20380
+ isCurrent: currentSession?.alias === attached.alias
20381
+ }
20382
+ };
20383
+ }));
20384
+ }
20385
+ function renderNativeSessionList(target, result, entries, includeAll, options = {}) {
20386
+ if (options.format === "cards") {
20387
+ return renderNativeSessionCardList(target, result, entries, includeAll);
20388
+ }
20389
+ return renderNativeSessionTableList(target, result, entries, includeAll);
20390
+ }
20391
+ function renderNativeSessionTableList(target, result, entries, includeAll) {
20392
+ const lines = [`本地 ${target.agentDisplayName} 会话(${target.workspaceLabel}):`];
20393
+ lines.push("| # | 标题 | 更新时间 | ID |");
20394
+ lines.push("|---|---|---|---|");
20395
+ entries.forEach((entry, index) => {
20396
+ const title = escapeMarkdownTableCell(renderNativeSessionTitle(entry.session.title, entry.session.sessionId));
20397
+ const updatedAt = entry.session.updatedAt ? formatNativeSessionTime(entry.session.updatedAt) : "-";
20398
+ const idParts = [entry.session.sessionId];
20399
+ if (entry.attached) {
20400
+ idParts.push(`已接入:${entry.attached.displayAlias}${entry.attached.isCurrent ? " [当前]" : ""}`);
20401
+ }
20402
+ lines.push(`| ${index + 1} | ${title} | ${escapeMarkdownTableCell(updatedAt)} | ${escapeMarkdownTableCell(idParts.join(" · "))} |`);
20403
+ });
20404
+ lines.push("");
20405
+ lines.push("操作:");
20406
+ lines.push("接入:/ssn 1");
20407
+ lines.push("指定别名:/ssn 1 -a fix-ci");
20408
+ lines.push("说明:/help ssn");
20409
+ if (result.nextCursor) {
20410
+ lines.push(`更多:${renderNextPageCommand(target, result.nextCursor, includeAll)}`);
20411
+ }
20412
+ return lines.join(`
20413
+ `);
20414
+ }
20415
+ function renderNativeSessionCardList(target, result, entries, includeAll) {
20416
+ const lines = [
20417
+ `本地 ${target.agentDisplayName} 会话(${target.workspaceLabel}):`,
20418
+ "回复编号接入,ID 尾号用于区分。"
20419
+ ];
20420
+ entries.forEach((entry, index) => {
20421
+ const title = renderNativeSessionTitle(entry.session.title, entry.session.sessionId);
20422
+ const updatedAt = entry.session.updatedAt ? formatNativeSessionTime(entry.session.updatedAt) : "-";
20423
+ lines.push("");
20424
+ lines.push(`【${index + 1}】 ${title}`);
20425
+ lines.push(`时间:${updatedAt}`);
20426
+ lines.push(`ID:${formatSessionIdTail(entry.session.sessionId)}`);
20427
+ if (entry.attached) {
20428
+ lines.push(`已接入:${entry.attached.displayAlias}${entry.attached.isCurrent ? " [当前]" : ""}`);
20429
+ }
20430
+ });
20431
+ lines.push("");
20432
+ lines.push("操作:");
20433
+ lines.push("接入:/ssn 1");
20434
+ lines.push("指定别名:/ssn 1 -a fix-ci");
20435
+ lines.push("说明:/help ssn");
20436
+ if (result.nextCursor) {
20437
+ lines.push(`更多:${renderNextPageCommand(target, result.nextCursor, includeAll)}`);
20438
+ }
20439
+ return lines.join(`
20440
+ `);
20441
+ }
20442
+ function renderNativeSessionTitle(title, fallback) {
20443
+ const normalized = (title?.trim() || fallback).replace(/\s+/g, " ");
20444
+ const maxLength = 60;
20445
+ return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1)}…` : normalized;
20446
+ }
20447
+ function buildDefaultNativeAlias(agent, sessionId) {
20448
+ return `${agent}-${sessionIdTail(sessionId)}`;
20449
+ }
20450
+ function formatSessionIdTail(sessionId) {
20451
+ const tail = sessionIdTail(sessionId);
20452
+ return tail.length < sessionId.trim().length ? `…${tail}` : tail;
20453
+ }
20454
+ function sessionIdTail(sessionId) {
20455
+ const trimmed = sessionId.trim();
20456
+ if (trimmed.length <= 8) {
20457
+ return trimmed;
20458
+ }
20459
+ return trimmed.slice(-8);
20460
+ }
20461
+ function formatNativeSessionTime(value) {
20462
+ const date4 = new Date(value);
20463
+ if (Number.isNaN(date4.getTime())) {
20464
+ return value;
20465
+ }
20466
+ const pad = (input) => String(input).padStart(2, "0");
20467
+ return `${date4.getFullYear()}-${pad(date4.getMonth() + 1)}-${pad(date4.getDate())} ${pad(date4.getHours())}:${pad(date4.getMinutes())}`;
20468
+ }
20469
+ function escapeMarkdownTableCell(value) {
20470
+ return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
20471
+ }
20472
+ function renderNextPageCommand(target, nextCursor, includeAll) {
20473
+ const scope = target.source === "workspace" && target.workspace ? `--ws ${target.workspace}` : `-d ${target.cwd}`;
20474
+ const allFlag = includeAll ? " --all" : "";
20475
+ return `/ssn ${target.agent} ${scope}${allFlag} --cursor ${nextCursor}`;
20476
+ }
20477
+ async function allocateUniqueNativeAlias(context, chatKey, baseDisplayAlias) {
20478
+ const channelId = getChannelIdFromChatKey(chatKey);
20479
+ const visible = await context.sessions.listSessions(chatKey);
20480
+ const existing = new Set(visible.map((session) => session.internalAlias));
20481
+ const base = baseDisplayAlias.trim() || "native-session";
20482
+ const transportFor = (candidate) => context.sessions.buildDefaultTransportSessionForChat(chatKey, candidate);
20483
+ const isFree = (candidate) => !existing.has(scopeDisplayAliasToInternal(channelId, candidate)) && context.sessions.countAliasesSharingTransport(transportFor(candidate)) === 0;
20484
+ if (isFree(base)) {
20485
+ return base;
20486
+ }
20487
+ let suffix = 2;
20488
+ while (!isFree(`${base}-${suffix}`)) {
20489
+ suffix += 1;
20490
+ }
20491
+ return `${base}-${suffix}`;
20492
+ }
20493
+ async function refreshAgentCommandBestEffort(context, alias) {
20494
+ try {
20495
+ await context.lifecycle.refreshSessionTransportAgentCommand(alias);
20496
+ } catch (error2) {
20497
+ await context.logger.error("session.native.agent_command_refresh_failed", "failed to refresh native session agent command", {
20498
+ alias,
20499
+ error: error2 instanceof Error ? error2.message : String(error2)
20500
+ });
20501
+ }
20502
+ }
20503
+ function renderNativeListError(target, error2) {
20504
+ return [
20505
+ `本地 ${target.agentDisplayName} 会话查询失败:${formatErrorMessage(error2)}`,
20506
+ "请确认 acpx/Agent 支持 native 会话查询,或继续使用 /ss。",
20507
+ "说明:/help ssn"
20508
+ ].join(`
20509
+ `);
20510
+ }
20511
+ function renderNativeResumeError(target, error2) {
20512
+ return [
20513
+ `本地 ${target.agentDisplayName} 会话接入失败:${formatErrorMessage(error2)}`,
20514
+ "请确认 acpx/Agent 支持 native 会话恢复,或继续使用 /ss。",
20515
+ "说明:/help ssn"
20516
+ ].join(`
20517
+ `);
20518
+ }
20519
+ function formatErrorMessage(error2) {
20520
+ return error2 instanceof Error ? error2.message : String(error2);
20521
+ }
20522
+ function isRouterResponse(value) {
20523
+ return typeof value.text === "string";
20524
+ }
20525
+ function displayAgentName(agent) {
20526
+ if (!agent) {
20527
+ return agent;
20528
+ }
20529
+ return agent.charAt(0).toUpperCase() + agent.slice(1);
20530
+ }
20531
+ var NATIVE_SESSION_CACHE_TTL_MS;
20532
+ var init_native_session_handler = __esm(() => {
20533
+ init_channel_scope();
20534
+ init_workspace_name();
20535
+ init_workspace_path();
20536
+ NATIVE_SESSION_CACHE_TTL_MS = 10 * 60 * 1000;
20537
+ });
20538
+
19801
20539
  // src/commands/handlers/session-recovery-handler.ts
19802
20540
  function renderTransportError(session, error2) {
19803
20541
  const message = error2 instanceof Error ? error2.message : String(error2);
@@ -20265,6 +21003,7 @@ class CommandRouter {
20265
21003
  quota;
20266
21004
  scheduled;
20267
21005
  scheduledDelivery;
21006
+ resolveNativeSessionListFormat;
20268
21007
  logger;
20269
21008
  autoInstall = autoInstallOptionalDep;
20270
21009
  discoverPaths = discoverParentPackagePaths;
@@ -20274,7 +21013,7 @@ class CommandRouter {
20274
21013
  __setDiscoverPathsForTest(fn) {
20275
21014
  this.discoverPaths = fn;
20276
21015
  }
20277
- constructor(sessions, transport, config2, configStore, logger2, resolveSessionAgentCommand = resolveSessionAgentCommandFromIndex, orchestration, quota, scheduled, scheduledDelivery) {
21016
+ constructor(sessions, transport, config2, configStore, logger2, resolveSessionAgentCommand = resolveSessionAgentCommandFromIndex, orchestration, quota, scheduled, scheduledDelivery, resolveNativeSessionListFormat) {
20278
21017
  this.sessions = sessions;
20279
21018
  this.transport = transport;
20280
21019
  this.config = config2;
@@ -20284,6 +21023,7 @@ class CommandRouter {
20284
21023
  this.quota = quota;
20285
21024
  this.scheduled = scheduled;
20286
21025
  this.scheduledDelivery = scheduledDelivery;
21026
+ this.resolveNativeSessionListFormat = resolveNativeSessionListFormat;
20287
21027
  this.logger = logger2 ?? createNoopAppLogger();
20288
21028
  }
20289
21029
  async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, onThought, perfSpan) {
@@ -20310,18 +21050,7 @@ class CommandRouter {
20310
21050
  return await this.executeCommand(chatKey, command.kind, startedAt, async () => {
20311
21051
  switch (command.kind) {
20312
21052
  case "invalid":
20313
- return {
20314
- text: [
20315
- "无法识别的命令格式。",
20316
- "",
20317
- "正确的会话创建格式:",
20318
- "/session new <别名> --agent <Agent名> --ws <工作区名>",
20319
- "",
20320
- "例如:",
20321
- "/session new demo --agent claude --ws weacpx"
20322
- ].join(`
20323
- `)
20324
- };
21053
+ return handleInvalidCommand(command.recognizedCommand);
20325
21054
  case "help":
20326
21055
  return handleHelp(command.topic);
20327
21056
  case "agents":
@@ -20358,6 +21087,12 @@ class CommandRouter {
20358
21087
  return await handleSessionShortcut(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.agent, command, true);
20359
21088
  case "session.attach":
20360
21089
  return await handleSessionAttach(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.alias, command.agent, command.workspace, command.transportSession);
21090
+ case "session.native.list":
21091
+ return await handleNativeSessionList(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command);
21092
+ case "session.native.select":
21093
+ return await handleNativeSessionSelect(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.identifier, command.alias);
21094
+ case "session.native.attach":
21095
+ return await handleNativeSessionSelect(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.identifier, command.alias);
20361
21096
  case "session.use":
20362
21097
  return await handleSessionUse(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
20363
21098
  case "mode.show":
@@ -20459,7 +21194,8 @@ class CommandRouter {
20459
21194
  configStore: this.configStore,
20460
21195
  logger: this.logger,
20461
21196
  replaceConfig: (updated) => this.replaceConfig(updated),
20462
- ...this.quota ? { quota: this.quota } : {}
21197
+ ...this.quota ? { quota: this.quota } : {},
21198
+ ...this.resolveNativeSessionListFormat ? { resolveNativeSessionListFormat: this.resolveNativeSessionListFormat } : {}
20463
21199
  };
20464
21200
  }
20465
21201
  createSessionHandlerContext(reply, perfSpan) {
@@ -20821,6 +21557,7 @@ var init_command_router = __esm(() => {
20821
21557
  init_agent_handler();
20822
21558
  init_workspace_handler();
20823
21559
  init_session_shortcut_handler();
21560
+ init_native_session_handler();
20824
21561
  init_later_handler();
20825
21562
  init_session_recovery_handler();
20826
21563
  init_auto_install_optional_dep();
@@ -23965,7 +24702,11 @@ class OrchestrationService {
23965
24702
  }
23966
24703
  workspaceLabelFromCwd(cwd) {
23967
24704
  const base = basename2(cwd).trim() || "cwd";
23968
- return base.replace(/[^a-zA-Z0-9._-]+/g, "_") || "cwd";
24705
+ return sanitizeString(base, {
24706
+ allow: /[a-zA-Z0-9._-]/,
24707
+ replacement: "_",
24708
+ fallback: "cwd"
24709
+ });
23969
24710
  }
23970
24711
  cwdWorkerSessionPart(cwd) {
23971
24712
  const label = this.workspaceLabelFromCwd(cwd);
@@ -25144,11 +25885,13 @@ class SessionService {
25144
25885
  stateStore;
25145
25886
  state;
25146
25887
  stateMutex;
25888
+ now;
25147
25889
  constructor(config2, stateStore, state, options = {}) {
25148
25890
  this.config = config2;
25149
25891
  this.stateStore = stateStore;
25150
25892
  this.state = state;
25151
25893
  this.stateMutex = options.stateMutex ?? new AsyncMutex;
25894
+ this.now = options.now ?? (() => Date.now());
25152
25895
  }
25153
25896
  async createSession(alias, agent, workspace) {
25154
25897
  return await this.createLogicalSession(alias, agent, workspace, `${workspace}:${alias}`);
@@ -25182,6 +25925,14 @@ class SessionService {
25182
25925
  async attachSession(alias, agent, workspace, transportSession, transportAgentCommand) {
25183
25926
  return await this.createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand);
25184
25927
  }
25928
+ async attachNativeSession(input) {
25929
+ return await this.createLogicalSession(input.alias, input.agent, input.workspace, input.transportSession, input.transportAgentCommand, {
25930
+ source: "agent-side",
25931
+ agentSessionId: input.agentSessionId,
25932
+ title: input.title,
25933
+ updatedAt: input.updatedAt
25934
+ });
25935
+ }
25185
25936
  async getSession(alias) {
25186
25937
  const session = this.state.sessions[alias];
25187
25938
  if (!session) {
@@ -25196,6 +25947,22 @@ class SessionService {
25196
25947
  const preferred = matches.find((session) => session.alias === expectedAlias && session.workspace === expectedWorkspace) ?? matches[0];
25197
25948
  return preferred ? this.toResolvedSession(preferred) : null;
25198
25949
  }
25950
+ async findAttachedNativeSession(chatKey, agent, agentSessionId) {
25951
+ const channelId = getChannelIdFromChatKey(chatKey);
25952
+ for (const session of Object.values(this.state.sessions)) {
25953
+ if (session.source !== "agent-side") {
25954
+ continue;
25955
+ }
25956
+ if (session.agent !== agent || session.agent_session_id !== agentSessionId) {
25957
+ continue;
25958
+ }
25959
+ if (!isSessionAliasVisibleInChannel(session.alias, channelId)) {
25960
+ continue;
25961
+ }
25962
+ return this.toResolvedSession(session);
25963
+ }
25964
+ return null;
25965
+ }
25199
25966
  async useSession(chatKey, alias) {
25200
25967
  await this.mutate(async () => {
25201
25968
  const channelId = getChannelIdFromChatKey(chatKey);
@@ -25315,6 +26082,49 @@ class SessionService {
25315
26082
  return { wasActive };
25316
26083
  });
25317
26084
  }
26085
+ async cacheNativeSessionList(chatKey, input) {
26086
+ await this.mutate(async () => {
26087
+ this.state.native_session_lists[chatKey] = {
26088
+ created_at: new Date(this.now()).toISOString(),
26089
+ agent: input.agent,
26090
+ ...input.workspace !== undefined ? { workspace: input.workspace } : {},
26091
+ cwd: input.cwd,
26092
+ sessions: input.sessions.map((session) => ({
26093
+ session_id: session.sessionId,
26094
+ ...session.cwd !== undefined ? { cwd: session.cwd } : {},
26095
+ ...session.title !== undefined ? { title: session.title } : {},
26096
+ ...session.updatedAt !== undefined ? { updated_at: session.updatedAt } : {}
26097
+ })),
26098
+ ...input.nextCursor !== undefined ? { next_cursor: input.nextCursor } : {}
26099
+ };
26100
+ await this.persist();
26101
+ });
26102
+ }
26103
+ async getNativeSessionList(chatKey, ttlMs = 10 * 60 * 1000) {
26104
+ const cached2 = this.state.native_session_lists[chatKey];
26105
+ if (!cached2) {
26106
+ return null;
26107
+ }
26108
+ const createdAt = Date.parse(cached2.created_at);
26109
+ if (Number.isNaN(createdAt)) {
26110
+ return null;
26111
+ }
26112
+ if (this.now() - createdAt > ttlMs) {
26113
+ return null;
26114
+ }
26115
+ return {
26116
+ agent: cached2.agent,
26117
+ ...cached2.workspace !== undefined ? { workspace: cached2.workspace } : {},
26118
+ cwd: cached2.cwd,
26119
+ sessions: cached2.sessions.map((session) => ({
26120
+ sessionId: session.session_id,
26121
+ ...session.cwd !== undefined ? { cwd: session.cwd } : {},
26122
+ ...session.title !== undefined ? { title: session.title } : {},
26123
+ ...session.updated_at !== undefined ? { updatedAt: session.updated_at } : {}
26124
+ })),
26125
+ ...cached2.next_cursor !== undefined ? { nextCursor: cached2.next_cursor } : {}
26126
+ };
26127
+ }
25318
26128
  toResolvedSession(session) {
25319
26129
  const agentConfig = this.config.agents[session.agent];
25320
26130
  if (!agentConfig) {
@@ -25330,6 +26140,11 @@ class SessionService {
25330
26140
  agentCommand: session.transport_agent_command ?? resolveAgentCommand(agentConfig.driver, agentConfig.command),
25331
26141
  workspace: session.workspace,
25332
26142
  transportSession: session.transport_session,
26143
+ source: session.source,
26144
+ agentSessionId: session.agent_session_id,
26145
+ agentSessionTitle: session.agent_session_title,
26146
+ agentSessionUpdatedAt: session.agent_session_updated_at,
26147
+ attachedAt: session.attached_at,
25333
26148
  modeId: session.mode_id,
25334
26149
  replyMode: session.reply_mode,
25335
26150
  cwd: workspaceConfig.cwd
@@ -25357,20 +26172,25 @@ class SessionService {
25357
26172
  async persist() {
25358
26173
  await this.stateStore.save(this.state);
25359
26174
  }
25360
- async createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand) {
26175
+ async createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand, native) {
25361
26176
  return await this.mutate(async () => {
25362
26177
  this.validateSession(alias, agent, workspace);
25363
26178
  if (this.state.orchestration.externalCoordinators[transportSession]) {
25364
26179
  throw new Error(`transport session "${transportSession}" conflicts with an external coordinator`);
25365
26180
  }
25366
26181
  const existingSession = this.state.sessions[alias];
25367
- const now = new Date().toISOString();
26182
+ const now = new Date(this.now()).toISOString();
25368
26183
  const normalizedTransportAgentCommand = transportAgentCommand?.trim();
25369
26184
  const session = {
25370
26185
  alias,
25371
26186
  agent,
25372
26187
  workspace,
25373
26188
  transport_session: transportSession,
26189
+ source: native?.source,
26190
+ agent_session_id: native?.agentSessionId,
26191
+ agent_session_title: native?.title ?? undefined,
26192
+ agent_session_updated_at: native?.updatedAt,
26193
+ attached_at: native ? now : undefined,
25374
26194
  ...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : existingSession?.transport_agent_command ? { transport_agent_command: existingSession.transport_agent_command } : {},
25375
26195
  mode_id: existingSession?.mode_id,
25376
26196
  reply_mode: existingSession?.reply_mode,
@@ -25483,6 +26303,37 @@ class DebouncedStateStore {
25483
26303
  }
25484
26304
  }
25485
26305
 
26306
+ // src/commands/command-hints.ts
26307
+ function listWeacpxCommandHints() {
26308
+ const hints = [{ name: "/help", description: "查看命令帮助。" }];
26309
+ for (const topic of HELP_TOPICS) {
26310
+ const name = PRIMARY_COMMAND_BY_TOPIC[topic.topic];
26311
+ if (!name) {
26312
+ throw new Error(`command-hints: 未登记 help topic 的主命令: ${topic.topic}`);
26313
+ }
26314
+ hints.push({ name, description: topic.summary });
26315
+ }
26316
+ return hints;
26317
+ }
26318
+ var PRIMARY_COMMAND_BY_TOPIC;
26319
+ var init_command_hints = __esm(() => {
26320
+ init_help_registry();
26321
+ PRIMARY_COMMAND_BY_TOPIC = {
26322
+ session: "/session",
26323
+ native: "/ssn",
26324
+ workspace: "/workspace",
26325
+ agent: "/agent",
26326
+ permission: "/permission",
26327
+ config: "/config",
26328
+ orchestration: "/delegate",
26329
+ mode: "/mode",
26330
+ replymode: "/replymode",
26331
+ status: "/status",
26332
+ cancel: "/cancel",
26333
+ later: "/later"
26334
+ };
26335
+ });
26336
+
25486
26337
  // src/run-console.ts
25487
26338
  var exports_run_console = {};
25488
26339
  __export(exports_run_console, {
@@ -25597,7 +26448,9 @@ async function runConsole(paths, deps) {
25597
26448
  abortSignal: shutdownController.signal,
25598
26449
  quota: runtime.quota,
25599
26450
  logger: runtime.logger,
25600
- perfTracer: runtime.perfTracer
26451
+ perfTracer: runtime.perfTracer,
26452
+ commandHints: listWeacpxCommandHints(),
26453
+ coreVersion: WEACPX_CORE_VERSION
25601
26454
  });
25602
26455
  channelStartPromise.catch(() => {});
25603
26456
  let channelStartSettled = false;
@@ -25715,6 +26568,8 @@ async function runCleanupSequence(input) {
25715
26568
  }
25716
26569
  var init_run_console = __esm(() => {
25717
26570
  init_consumer_lock();
26571
+ init_command_hints();
26572
+ init_version();
25718
26573
  });
25719
26574
 
25720
26575
  // src/transport/acpx-bridge/acpx-bridge-protocol.ts
@@ -26167,6 +27022,18 @@ class AcpxBridgeTransport {
26167
27022
  lines
26168
27023
  });
26169
27024
  }
27025
+ async listAgentSessions(query) {
27026
+ return await this.client.request("listAgentSessions", { ...query });
27027
+ }
27028
+ async resumeAgentSession(session, agentSessionId) {
27029
+ await this.client.request("resumeAgentSession", {
27030
+ agent: session.agent,
27031
+ ...session.agentCommand ? { agentCommand: session.agentCommand } : {},
27032
+ cwd: session.cwd,
27033
+ name: session.transportSession,
27034
+ agentSessionId
27035
+ });
27036
+ }
26170
27037
  async prompt(session, text, reply, replyContext, options) {
26171
27038
  const sink = reply ? createQuotaGatedReplySink({
26172
27039
  reply,
@@ -26759,7 +27626,7 @@ var init_node_pty_helper = () => {};
26759
27626
  // src/transport/acpx-queue-owner-launcher.ts
26760
27627
  import { createHash as createHash3 } from "node:crypto";
26761
27628
  import { spawn as spawn8 } from "node:child_process";
26762
- import { readFile as readFile11, unlink } from "node:fs/promises";
27629
+ import { readFile as readFile12, unlink } from "node:fs/promises";
26763
27630
  import { homedir as homedir8 } from "node:os";
26764
27631
  import { join as join13 } from "node:path";
26765
27632
  function buildWeacpxMcpServerSpec(input) {
@@ -26915,7 +27782,7 @@ async function terminateAcpxQueueOwner(sessionId) {
26915
27782
  const lockPath = queueLockFilePath(sessionId);
26916
27783
  let owner;
26917
27784
  try {
26918
- owner = JSON.parse(await readFile11(lockPath, "utf8"));
27785
+ owner = JSON.parse(await readFile12(lockPath, "utf8"));
26919
27786
  } catch {
26920
27787
  return;
26921
27788
  }
@@ -26943,7 +27810,7 @@ function resolveDefaultWeacpxCommand(env) {
26943
27810
  return "weacpx";
26944
27811
  }
26945
27812
  function quoteCommandPart(value) {
26946
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
27813
+ return quoteIfNeeded(value);
26947
27814
  }
26948
27815
  var init_acpx_queue_owner_launcher = __esm(() => {
26949
27816
  init_spawn_command();
@@ -26962,6 +27829,60 @@ function permissionModeToFlag(permissionMode) {
26962
27829
  }
26963
27830
  }
26964
27831
 
27832
+ // src/transport/agent-session-list.ts
27833
+ function isUnknownFilterCwdOption(output) {
27834
+ return /(?:unknown|unrecognized) option/i.test(output) && output.includes("--filter-cwd");
27835
+ }
27836
+ async function runAgentSessionList(options) {
27837
+ let result = await options.runList(true);
27838
+ let filterLocally = false;
27839
+ if (result.code !== 0 && options.filterCwd && isUnknownFilterCwdOption(result.stdout + result.stderr)) {
27840
+ result = await options.runList(false);
27841
+ filterLocally = true;
27842
+ }
27843
+ if (result.code !== 0) {
27844
+ if ((result.stdout + result.stderr).includes("sessionCapabilities.list")) {
27845
+ return;
27846
+ }
27847
+ throw new Error(options.formatError(result));
27848
+ }
27849
+ return parseAgentSessionListOutput(result.stdout, filterLocally ? options.filterCwd : undefined);
27850
+ }
27851
+ function parseAgentSessionListOutput(stdout2, filterCwd) {
27852
+ let parsed;
27853
+ try {
27854
+ parsed = JSON.parse(stdout2);
27855
+ } catch {
27856
+ throw new Error("failed to parse acpx sessions list output");
27857
+ }
27858
+ if (!isAgentSessionListResult(parsed)) {
27859
+ return;
27860
+ }
27861
+ return filterCwd ? filterAgentSessionListByCwd(parsed, filterCwd) : parsed;
27862
+ }
27863
+ function isAgentSessionListResult(value) {
27864
+ if (!value || typeof value !== "object" || Array.isArray(value))
27865
+ return false;
27866
+ const record3 = value;
27867
+ if (record3.source !== "agent" || !Array.isArray(record3.sessions))
27868
+ return false;
27869
+ return record3.sessions.every((session) => {
27870
+ if (!session || typeof session !== "object" || Array.isArray(session))
27871
+ return false;
27872
+ const item = session;
27873
+ return typeof item.sessionId === "string";
27874
+ });
27875
+ }
27876
+ function filterAgentSessionListByCwd(result, cwd) {
27877
+ return {
27878
+ ...result,
27879
+ sessions: result.sessions.filter((session) => session.cwd && isSamePath(session.cwd, cwd))
27880
+ };
27881
+ }
27882
+ var init_agent_session_list = __esm(() => {
27883
+ init_path();
27884
+ });
27885
+
26965
27886
  // src/transport/acpx-cli/acpx-cli-transport.ts
26966
27887
  import { createRequire as createRequire5 } from "node:module";
26967
27888
  import { spawn as spawn9 } from "node:child_process";
@@ -27071,6 +27992,23 @@ class AcpxCliTransport {
27071
27992
  timeoutMs: this.sessionInitTimeoutMs
27072
27993
  });
27073
27994
  }
27995
+ async listAgentSessions(query) {
27996
+ return await runAgentSessionList({
27997
+ filterCwd: query.filterCwd,
27998
+ runList: async (includeFilterCwd) => {
27999
+ const args = this.buildAgentQueryArgs(query, "json", [
28000
+ "sessions",
28001
+ "list",
28002
+ ...includeFilterCwd && query.filterCwd ? ["--filter-cwd", query.filterCwd] : [],
28003
+ ...query.cursor ? ["--cursor", query.cursor] : []
28004
+ ]);
28005
+ return await this.runCommandWithTimeout(this.runCommand, args, {
28006
+ timeoutMs: this.sessionInitTimeoutMs
28007
+ });
28008
+ },
28009
+ formatError: (result) => normalizeCommandError(result) ?? `command failed with exit code ${result.code}`
28010
+ });
28011
+ }
27074
28012
  async tailSessionHistory(session, lines) {
27075
28013
  const candidates = [
27076
28014
  ["sessions", "history", "quiet", "-s", session.transportSession, String(lines)],
@@ -27139,6 +28077,20 @@ ${baseText}` : "" };
27139
28077
  message: output.trim()
27140
28078
  };
27141
28079
  }
28080
+ async resumeAgentSession(session, agentSessionId) {
28081
+ const args = this.buildArgs(session, [
28082
+ "sessions",
28083
+ "new",
28084
+ "--name",
28085
+ session.transportSession,
28086
+ "--resume-session",
28087
+ agentSessionId
28088
+ ]);
28089
+ const runResume = session.agentCommand ? this.run : this.runWithPty;
28090
+ await runResume.call(this, args, {
28091
+ timeoutMs: this.sessionInitTimeoutMs
28092
+ });
28093
+ }
27142
28094
  async updatePermissionPolicy(policy) {
27143
28095
  this.permissionMode = policy.permissionMode;
27144
28096
  this.nonInteractivePermissions = policy.nonInteractivePermissions;
@@ -27372,6 +28324,13 @@ ${baseText}` : "" };
27372
28324
  }
27373
28325
  return [...prefix, session.agent, ...tail2];
27374
28326
  }
28327
+ buildAgentQueryArgs(query, format, tail2) {
28328
+ const prefix = ["--format", format, "--cwd", query.cwd, ...this.buildPermissionArgs()];
28329
+ if (query.agentCommand) {
28330
+ return [...prefix, "--agent", query.agentCommand, ...tail2];
28331
+ }
28332
+ return [...prefix, query.agent, ...tail2];
28333
+ }
27375
28334
  buildPromptArgs(session, text, promptFile) {
27376
28335
  const prefix = [
27377
28336
  "--format",
@@ -27437,9 +28396,34 @@ var init_acpx_cli_transport = __esm(() => {
27437
28396
  init_node_pty_helper();
27438
28397
  init_terminate_process_tree();
27439
28398
  init_acpx_queue_owner_launcher();
28399
+ init_agent_session_list();
27440
28400
  require4 = createRequire5(import.meta.url);
27441
28401
  });
27442
28402
 
28403
+ // src/util/async.ts
28404
+ function settleWithinTimeout(work, timeoutMs) {
28405
+ return new Promise((resolve3) => {
28406
+ let settled = false;
28407
+ const finish = () => {
28408
+ if (!settled) {
28409
+ settled = true;
28410
+ resolve3();
28411
+ }
28412
+ };
28413
+ const timer = setTimeout(finish, timeoutMs);
28414
+ if (typeof timer.unref === "function") {
28415
+ timer.unref();
28416
+ }
28417
+ work.then(() => {
28418
+ clearTimeout(timer);
28419
+ finish();
28420
+ }, () => {
28421
+ clearTimeout(timer);
28422
+ finish();
28423
+ });
28424
+ });
28425
+ }
28426
+
27443
28427
  // src/transport/queue-owner-reaper.ts
27444
28428
  import { spawn as spawn10 } from "node:child_process";
27445
28429
  async function reapQueueOwners(acpxCommand, targets, deps = {}) {
@@ -27470,28 +28454,6 @@ async function reapQueueOwners(acpxCommand, targets, deps = {}) {
27470
28454
  await settleWithinTimeout(Promise.all(unique.map(reapOne)), timeoutMs);
27471
28455
  return { terminated, attempted: unique.length };
27472
28456
  }
27473
- function settleWithinTimeout(work, timeoutMs) {
27474
- return new Promise((resolve3) => {
27475
- let settled = false;
27476
- const finish = () => {
27477
- if (!settled) {
27478
- settled = true;
27479
- resolve3();
27480
- }
27481
- };
27482
- const timer = setTimeout(finish, timeoutMs);
27483
- if (typeof timer.unref === "function") {
27484
- timer.unref();
27485
- }
27486
- work.then(() => {
27487
- clearTimeout(timer);
27488
- finish();
27489
- }, () => {
27490
- clearTimeout(timer);
27491
- finish();
27492
- });
27493
- });
27494
- }
27495
28457
  async function defaultResolveRecordId(acpxCommand, target) {
27496
28458
  const args = [
27497
28459
  "--format",
@@ -27650,6 +28612,9 @@ class MessageChannelRegistry {
27650
28612
  }
27651
28613
  await channel.sendScheduledMessage(input);
27652
28614
  }
28615
+ nativeSessionListFormat(chatKey) {
28616
+ return this.getByChatKey(chatKey)?.nativeSessionListFormat ?? "table";
28617
+ }
27653
28618
  createConsumerLocks() {
27654
28619
  const result = [];
27655
28620
  for (const channel of this.channels.values()) {
@@ -28234,7 +29199,7 @@ async function buildApp(paths, deps = {}) {
28234
29199
  listScheduledTasksFromRoute: async (input) => await listScheduledTasksFromRoute(input, { state, scheduled: scheduledService }),
28235
29200
  cancelScheduledTaskFromRoute: async (input) => await cancelScheduledTaskFromRoute(input, { state, scheduled: scheduledService })
28236
29201
  });
28237
- const router = new CommandRouter(sessions, transport, config2, configStore, logger2, undefined, orchestration, quota, scheduledService, deps.channel?.supportsScheduledMessages ? { supportsScheduledMessages: deps.channel.supportsScheduledMessages.bind(deps.channel) } : undefined);
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);
28238
29203
  const agent = new ConsoleAgent(router, logger2);
28239
29204
  const scheduledScheduler = new ScheduledTaskScheduler(scheduledService, {
28240
29205
  dispatchTask: buildScheduledDispatchTask({
@@ -42027,16 +42992,15 @@ function buildWeacpxMcpToolRegistry(input) {
42027
42992
  },
42028
42993
  {
42029
42994
  name: "task_get",
42030
- description: "Fetch a single task under the current coordinator, including the worker's final result and any pending question. Use to inspect a task snapshot non-blockingly, read the actual output after completion, or inspect a task that requires attention.",
42995
+ description: "Fetch a single task under the current coordinator: its summary, latest progress, and — once terminal — the worker's final result. Prefer task_watch to follow a task; its terminal result already carries the output, so a follow-up task_get is unnecessary. Reach for task_get to recover a task you lost track of, to inspect one that requires attention, or to re-read the original delegated prompt. The full prompt is included only for needs_confirmation tasks unless you pass includePrompt:true.",
42031
42996
  inputSchema: exports_external.object({
42032
- taskId: exports_external.string().min(1)
42997
+ taskId: exports_external.string().min(1),
42998
+ includePrompt: exports_external.boolean().optional()
42033
42999
  }).strict(),
42034
43000
  handler: async (args) => await asToolResult(async () => {
42035
- const task = await transport.getTask({
42036
- coordinatorSession,
42037
- taskId: args.taskId
42038
- });
42039
- return createSuccessResult(task ? renderTaskSummary(task) : "Task not found.", { task });
43001
+ const { taskId, includePrompt } = args;
43002
+ const task = await transport.getTask({ coordinatorSession, taskId });
43003
+ return createSuccessResult(task ? renderTaskSummary(task, { includePrompt: includePrompt ?? false }) : "Task not found.", { task });
42040
43004
  })
42041
43005
  },
42042
43006
  {
@@ -42275,12 +43239,23 @@ function renderTaskWatchResult(result) {
42275
43239
  const events = result.events.length > 0 ? [
42276
43240
  "- Events:",
42277
43241
  ...result.events.map((event) => {
42278
- const detail = event.summary ?? event.message ?? event.status ?? "";
42279
- return ` - #${event.seq} ${event.type} at ${event.at}${detail ? `: ${detail}` : ""}`;
43242
+ const detail2 = event.summary ?? event.message ?? event.status ?? "";
43243
+ return ` - #${event.seq} ${event.type} at ${event.at}${detail2 ? `: ${detail2}` : ""}`;
42280
43244
  })
42281
43245
  ] : ["- Events: none"];
42282
- const next = result.status === "terminal" ? "Next: call task_get to read the final result." : result.status === "attention_required" ? "Next: call task_get to inspect openQuestion / reviewPending, then resolve it with the recommended action tool." : `Next: call task_watch again with afterSeq=${result.nextAfterSeq} to keep watching, preferably as an MCP task if your client supports background task execution.`;
42283
- return [...header, ...events, next].join(`
43246
+ const detail = [];
43247
+ if (result.status === "terminal") {
43248
+ const resultText = result.task.resultText?.trim() ?? "";
43249
+ const summary = result.task.summary?.trim() ?? "";
43250
+ if (resultText.length > 0)
43251
+ detail.push(`- Result: ${resultText}`);
43252
+ else if (summary.length > 0)
43253
+ detail.push(`- Summary: ${summary}`);
43254
+ } else if (result.status === "attention_required" && result.task.openQuestion) {
43255
+ detail.push(`- Open question: ${result.task.openQuestion.question}`);
43256
+ }
43257
+ const next = result.status === "terminal" ? "Next: summarize this result for the user." : result.status === "attention_required" ? "Next: resolve the pending question / contested review with the recommended action tool (coordinator_answer_question or coordinator_review_contested_result); call task_get only if you need more detail." : `Next: call task_watch again with afterSeq=${result.nextAfterSeq} to keep watching, preferably as an MCP task if your client supports background task execution.`;
43258
+ return [...header, ...events, ...detail, next].join(`
42284
43259
  `);
42285
43260
  }
42286
43261
  function createSuccessResult(text, structuredContent) {
@@ -42296,7 +43271,7 @@ function createErrorResult(message) {
42296
43271
  };
42297
43272
  }
42298
43273
  function renderDelegateSuccess(result) {
42299
- const next = result.status === "needs_confirmation" ? `Next: this delegation requires user approval. Tell the user, then call task_approve or task_cancel based on their response.` : result.status === "queued" ? `Next: task "${result.taskId}" is queued (agent at parallel capacity). It will start automatically when a slot frees. Call task_watch to long-poll for the transition to running, or task_get/task_list for non-blocking snapshots.` : `Next: task "${result.taskId}" is running. Return this taskId to the user, call task_get/task_list for non-blocking progress snapshots, or task_watch to long-poll for the next event or terminal state.`;
43274
+ const next = result.status === "needs_confirmation" ? `Next: this delegation requires user approval. Tell the user, then call task_approve or task_cancel based on their response.` : result.status === "queued" ? `Next: task "${result.taskId}" is queued (agent at parallel capacity). It will start automatically when a slot frees. Call task_watch to long-poll until it runs and then finishes — the terminal watch carries the result. Use task_list only to resurvey in-flight tasks.` : `Next: task "${result.taskId}" is running. Return this taskId to the user, then call task_watch to long-poll until it finishes the terminal watch carries the worker's result, so no follow-up task_get is needed. Use task_list only to resurvey in-flight tasks.`;
42300
43275
  return [`Delegation task "${result.taskId}" created.`, `- Status: ${result.status}`, next].join(`
42301
43276
  `);
42302
43277
  }
@@ -42340,7 +43315,7 @@ function renderTaskListItem(task) {
42340
43315
  ].filter(Boolean).map((item) => `; ${item}`).join("");
42341
43316
  return `- ${task.taskId} [${task.status}] ${task.targetAgent}${role} -> ${task.workerSession ?? "unassigned"}${group}${source}${summary}${reliability}`;
42342
43317
  }
42343
- function renderTaskSummary(task) {
43318
+ function renderTaskSummary(task, options = {}) {
42344
43319
  const header = [
42345
43320
  `Task "${task.taskId}"`,
42346
43321
  `- Status: ${task.status}`,
@@ -42355,7 +43330,9 @@ function renderTaskSummary(task) {
42355
43330
  if (task.status === "needs_confirmation") {
42356
43331
  header.push(`- Source: ${task.sourceKind} / ${task.sourceHandle}${task.role ? ` / ${task.role}` : ""}`);
42357
43332
  }
42358
- header.push(`- Task: ${task.task}`);
43333
+ if (task.status === "needs_confirmation" || options.includePrompt) {
43334
+ header.push(`- Task: ${task.task}`);
43335
+ }
42359
43336
  if (task.summary.trim().length > 0)
42360
43337
  header.push(`- Summary: ${task.summary}`);
42361
43338
  if (task.lastProgressSummary)
@@ -42406,7 +43383,7 @@ function renderTaskApprovalSuccess(task) {
42406
43383
  return [
42407
43384
  `Task "${task.taskId}" approved.`,
42408
43385
  `- Current status: ${task.status}`,
42409
- `Next: use task_get/task_list for non-blocking progress snapshots, or task_watch to long-poll until the worker finishes; then task_get to read the final result.`
43386
+ `Next: call task_watch to long-poll until the worker finishes the terminal watch returns the result directly, so no follow-up task_get is needed. Use task_list only to resurvey in-flight tasks.`
42410
43387
  ].join(`
42411
43388
  `);
42412
43389
  }
@@ -42904,13 +43881,24 @@ function renderWatchMcpTaskResult(result, watchTaskId) {
42904
43881
  const events = result.events.length > 0 ? [
42905
43882
  "Events:",
42906
43883
  ...result.events.map((event) => {
42907
- const detail = event.summary ?? event.message ?? event.status ?? "";
42908
- return `- #${event.seq} ${event.type} at ${event.at}${detail ? `: ${detail}` : ""}`;
43884
+ const detail2 = event.summary ?? event.message ?? event.status ?? "";
43885
+ return `- #${event.seq} ${event.type} at ${event.at}${detail2 ? `: ${detail2}` : ""}`;
42909
43886
  })
42910
43887
  ] : ["Events: none"];
42911
- const next = result.status === "terminal" ? "Next: call task_get on the watched task to read the final result." : result.status === "attention_required" ? "Next: call task_get on the watched task, then resolve openQuestion / reviewPending with the recommended action tool." : `Next: call task_watch again with afterSeq=${result.nextAfterSeq} to keep watching.`;
43888
+ const detail = [];
43889
+ if (result.status === "terminal") {
43890
+ const resultText = result.task.resultText.trim();
43891
+ const summary = result.task.summary.trim();
43892
+ if (resultText.length > 0)
43893
+ detail.push(`Result: ${resultText}`);
43894
+ else if (summary.length > 0)
43895
+ detail.push(`Summary: ${summary}`);
43896
+ } else if (result.status === "attention_required" && result.task.openQuestion) {
43897
+ detail.push(`Open question: ${result.task.openQuestion.question}`);
43898
+ }
43899
+ const next = result.status === "terminal" ? "Next: summarize this result for the user." : result.status === "attention_required" ? "Next: resolve the pending question / contested review with the recommended action tool (coordinator_answer_question or coordinator_review_contested_result)." : `Next: call task_watch again with afterSeq=${result.nextAfterSeq} to keep watching.`;
42912
43900
  return {
42913
- content: [{ type: "text", text: [...header, ...events, next].join(`
43901
+ content: [{ type: "text", text: [...header, ...events, ...detail, next].join(`
42914
43902
  `) }],
42915
43903
  structuredContent: { watchTaskId, ...result }
42916
43904
  };
@@ -43270,8 +44258,14 @@ function inferExternalCoordinatorSession(input) {
43270
44258
  return `external_${sanitizeMcpClientName(input.clientName)}:${suffix}`;
43271
44259
  }
43272
44260
  function sanitizeMcpClientName(input) {
43273
- const normalized = (input ?? "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
43274
- return normalized.length > 0 ? normalized : "mcp-host";
44261
+ return sanitizeString((input ?? "").trim(), {
44262
+ allow: /[a-z0-9._-]/,
44263
+ replacement: "-",
44264
+ lowercase: true,
44265
+ collapse: true,
44266
+ trim: true,
44267
+ fallback: "mcp-host"
44268
+ });
43275
44269
  }
43276
44270
 
43277
44271
  // src/mcp/parse-string-flag.ts
@@ -43396,11 +44390,12 @@ function resolveTemplateChoice(answer, names) {
43396
44390
  // src/cli-update.ts
43397
44391
  init_plugin_home();
43398
44392
  import { spawn as spawn4 } from "node:child_process";
43399
- import { readFile as readFile7 } from "node:fs/promises";
44393
+ import { readFile as readFile8 } from "node:fs/promises";
43400
44394
  import { dirname as dirname9, join as join7 } from "node:path";
43401
44395
  import { fileURLToPath as fileURLToPath2 } from "node:url";
43402
44396
 
43403
44397
  // src/plugins/package-manager.ts
44398
+ init_plugin_home();
43404
44399
  import { spawn as spawn3 } from "node:child_process";
43405
44400
  async function defaultRunCommand(command, args, options) {
43406
44401
  await new Promise((resolve, reject) => {
@@ -43441,6 +44436,7 @@ async function detectPackageManager(runCommand) {
43441
44436
  async function installPluginPackage(input) {
43442
44437
  const runCommand = input.runCommand ?? defaultRunCommand;
43443
44438
  const packageManager = input.packageManager ?? await detectPackageManager();
44439
+ await normalizePluginHomeManifest(input.pluginHome);
43444
44440
  const spec = input.version ? `${input.packageName}@${input.version}` : input.packageName;
43445
44441
  if (packageManager === "bun") {
43446
44442
  await runCommand("bun", ["add", spec], { cwd: input.pluginHome });
@@ -43678,7 +44674,7 @@ async function readPackageName() {
43678
44674
  const here = dirname9(fileURLToPath2(import.meta.url));
43679
44675
  for (const candidate of [join7(here, "..", "package.json"), join7(here, "..", "..", "package.json")]) {
43680
44676
  try {
43681
- const parsed = JSON.parse(await readFile7(candidate, "utf8"));
44677
+ const parsed = JSON.parse(await readFile8(candidate, "utf8"));
43682
44678
  if (typeof parsed.name === "string" && parsed.name.trim())
43683
44679
  return parsed.name.trim();
43684
44680
  } catch {}
@@ -44301,7 +45297,7 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
44301
45297
 
44302
45298
  // src/plugins/plugin-cli.ts
44303
45299
  init_plugin_home();
44304
- import { readFile as readFile9 } from "node:fs/promises";
45300
+ import { readFile as readFile10 } from "node:fs/promises";
44305
45301
  import { isAbsolute, join as join9, resolve } from "node:path";
44306
45302
  init_plugin_loader();
44307
45303
  init_validate_plugin();
@@ -44311,14 +45307,14 @@ init_channel_scope();
44311
45307
  init_plugin_loader();
44312
45308
  init_validate_plugin();
44313
45309
  init_known_plugins();
44314
- import { readFile as readFile8 } from "node:fs/promises";
45310
+ import { readFile as readFile9 } from "node:fs/promises";
44315
45311
  import { join as join8 } from "node:path";
44316
45312
  function suggestedPluginPackageForChannel(type) {
44317
45313
  return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
44318
45314
  }
44319
45315
  async function readDependencyEntries(pluginHome) {
44320
45316
  try {
44321
- const raw = await readFile8(join8(pluginHome, "package.json"), "utf8");
45317
+ const raw = await readFile9(join8(pluginHome, "package.json"), "utf8");
44322
45318
  const parsed = JSON.parse(raw);
44323
45319
  const out = {};
44324
45320
  for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
@@ -44416,11 +45412,11 @@ async function inspectPlugins(input) {
44416
45412
  // src/plugins/plugin-cli.ts
44417
45413
  init_known_plugins();
44418
45414
  function looksLikePath(spec) {
44419
- return spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/") || isAbsolute(spec) || spec === ".";
45415
+ return spec === "." || spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/") || spec.startsWith(".\\") || spec.startsWith("..\\") || spec.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(spec) || isAbsolute(spec);
44420
45416
  }
44421
45417
  async function readDependencyEntries2(pluginHome) {
44422
45418
  try {
44423
- const raw = await readFile9(join9(pluginHome, "package.json"), "utf8");
45419
+ const raw = await readFile10(join9(pluginHome, "package.json"), "utf8");
44424
45420
  const parsed = JSON.parse(raw);
44425
45421
  const out = {};
44426
45422
  for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
@@ -44446,7 +45442,7 @@ async function resolveLocalPluginName(installSpec, pluginHome, namesBeforeInstal
44446
45442
  return name;
44447
45443
  }
44448
45444
  try {
44449
- const raw = await readFile9(join9(installSpec, "package.json"), "utf8");
45445
+ const raw = await readFile10(join9(installSpec, "package.json"), "utf8");
44450
45446
  const parsed = JSON.parse(raw);
44451
45447
  if (typeof parsed.name === "string" && parsed.name.trim())
44452
45448
  return parsed.name.trim();