weacpx 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -9759,6 +9759,7 @@ function createEmptyState() {
9759
9759
  return {
9760
9760
  sessions: {},
9761
9761
  chat_contexts: {},
9762
+ native_session_lists: {},
9762
9763
  orchestration: createEmptyOrchestrationState(),
9763
9764
  scheduled_tasks: {}
9764
9765
  };
@@ -9977,11 +9978,14 @@ function parseOrchestrationState(raw, path3) {
9977
9978
  function isReplyMode2(value) {
9978
9979
  return value === "stream" || value === "final" || value === "verbose";
9979
9980
  }
9981
+ function isSessionSource(value) {
9982
+ return value === undefined || value === "weacpx" || value === "agent-side";
9983
+ }
9980
9984
  function isSessionRecord(value) {
9981
9985
  if (!isRecord2(value)) {
9982
9986
  return false;
9983
9987
  }
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);
9988
+ 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
9989
  }
9986
9990
  function parseSessions(raw, path3) {
9987
9991
  const sessions = {};
@@ -10006,6 +10010,30 @@ function parseChatContexts(raw, path3) {
10006
10010
  }
10007
10011
  return chatContexts;
10008
10012
  }
10013
+ function isNativeSessionCacheEntry(value) {
10014
+ if (!isRecord2(value)) {
10015
+ return false;
10016
+ }
10017
+ return isString(value.session_id) && isOptionalString(value.cwd) && (value.title === undefined || value.title === null || isString(value.title)) && isOptionalString(value.updated_at);
10018
+ }
10019
+ function isNativeSessionListCacheRecord(value) {
10020
+ if (!isRecord2(value)) {
10021
+ return false;
10022
+ }
10023
+ 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));
10024
+ }
10025
+ function parseNativeSessionLists(raw) {
10026
+ if (!isRecord2(raw)) {
10027
+ return {};
10028
+ }
10029
+ const nativeSessionLists = {};
10030
+ for (const [chatKey, value] of Object.entries(raw)) {
10031
+ if (isNativeSessionListCacheRecord(value)) {
10032
+ nativeSessionLists[chatKey] = value;
10033
+ }
10034
+ }
10035
+ return nativeSessionLists;
10036
+ }
10009
10037
  function isScheduledTaskStatus(value) {
10010
10038
  return value === "pending" || value === "triggering" || value === "executed" || value === "cancelled" || value === "missed" || value === "failed";
10011
10039
  }
@@ -10050,6 +10078,7 @@ function parseState(raw, path3) {
10050
10078
  return {
10051
10079
  sessions: parsedSessions,
10052
10080
  chat_contexts: parseChatContexts(chatContexts, path3),
10081
+ native_session_lists: parseNativeSessionLists(raw.native_session_lists),
10053
10082
  orchestration,
10054
10083
  scheduled_tasks: parseScheduledTasks(raw.scheduled_tasks, path3)
10055
10084
  };
@@ -10167,6 +10196,13 @@ function resolveSessionAliasForInput(channelId, displayAlias, existingAliases) {
10167
10196
  }
10168
10197
  return scopedAlias;
10169
10198
  }
10199
+ function scopeDisplayAliasToInternal(channelId, displayAlias) {
10200
+ const normalized = displayAlias.trim();
10201
+ if (normalized.length === 0) {
10202
+ throw new Error("display session alias must be non-empty");
10203
+ }
10204
+ return channelId === "weixin" ? normalized : toInternalSessionAlias(channelId, normalized);
10205
+ }
10170
10206
  function buildDefaultTransportSession(channelId, displayAlias) {
10171
10207
  const normalized = displayAlias.trim();
10172
10208
  if (normalized.length === 0) {
@@ -14953,6 +14989,12 @@ async function handleWeixinMessageTurn(full, deps) {
14953
14989
  text: requestText,
14954
14990
  ...media.length > 0 ? { media } : {},
14955
14991
  replyContextToken: contextToken,
14992
+ metadata: {
14993
+ channel: "weixin",
14994
+ chatType: full.group_id ? "group" : "direct",
14995
+ ...full.from_user_id ? { senderId: full.from_user_id } : {},
14996
+ ...full.group_id ? { groupId: full.group_id } : {}
14997
+ },
14956
14998
  perfSpan
14957
14999
  };
14958
15000
  try {
@@ -16104,6 +16146,7 @@ var init_consumer_lock = __esm(() => {
16104
16146
  // src/channels/weixin-channel.ts
16105
16147
  class WeixinChannel {
16106
16148
  id = "weixin";
16149
+ nativeSessionListFormat = "cards";
16107
16150
  agent = null;
16108
16151
  quota = null;
16109
16152
  logger = null;
@@ -16911,6 +16954,7 @@ var init_command_list = __esm(() => {
16911
16954
  "/pm",
16912
16955
  "/session",
16913
16956
  "/ss",
16957
+ "/ssn",
16914
16958
  "/workspace",
16915
16959
  "/ws",
16916
16960
  "/use",
@@ -16993,6 +17037,51 @@ function parseCommand(input) {
16993
17037
  if (command === "/session" && parts[1] === "rm" && parts[2] && parts.length === 3) {
16994
17038
  return { kind: "session.rm", alias: parts[2] };
16995
17039
  }
17040
+ if (command === "/ssn") {
17041
+ if (parts.length === 1) {
17042
+ return { kind: "session.native.list" };
17043
+ }
17044
+ const identifier = parts[1] ?? "";
17045
+ if (/^\d+$/.test(identifier)) {
17046
+ const selected = readNativeAttachCommand(parts, 1);
17047
+ if (!selected) {
17048
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/ssn" };
17049
+ }
17050
+ return selected.alias ? { kind: "session.native.select", identifier, alias: selected.alias } : { kind: "session.native.select", identifier };
17051
+ }
17052
+ if (parts[1] === "attach") {
17053
+ if (!parts[2]) {
17054
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/ssn" };
17055
+ }
17056
+ const attached = readNativeAttachCommand(parts, 2);
17057
+ if (attached) {
17058
+ return attached;
17059
+ }
17060
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/ssn" };
17061
+ }
17062
+ const nativeList = readNativeListCommand(parts, 1);
17063
+ if (nativeList) {
17064
+ return nativeList;
17065
+ }
17066
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/ssn" };
17067
+ }
17068
+ if (command === "/session" && parts[1] === "native") {
17069
+ const nativeList = readNativeListCommand(parts, 2);
17070
+ if (nativeList) {
17071
+ return nativeList;
17072
+ }
17073
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/session" };
17074
+ }
17075
+ if (command === "/session" && parts[1] === "attach" && parts[2] === "native") {
17076
+ if (!parts[3]) {
17077
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/session" };
17078
+ }
17079
+ const attached = readNativeAttachCommand(parts, 3);
17080
+ if (attached) {
17081
+ return attached;
17082
+ }
17083
+ return { kind: "invalid", text: trimmed, recognizedCommand: "/session" };
17084
+ }
16996
17085
  if (command === "/group" && parts[1] === "new" && parts.length > 2) {
16997
17086
  const title = parts.slice(2).join(" ");
16998
17087
  if (title.trim().length > 0) {
@@ -17258,6 +17347,97 @@ function readSessionShortcutTarget(parts, startIndex) {
17258
17347
  }
17259
17348
  return null;
17260
17349
  }
17350
+ function readNativeListCommand(parts, startIndex) {
17351
+ let agent = "";
17352
+ let cwd = "";
17353
+ let workspace = "";
17354
+ let all = false;
17355
+ let cursor = "";
17356
+ let invalid = false;
17357
+ if (startIndex < parts.length && !parts[startIndex]?.startsWith("-")) {
17358
+ agent = parts[startIndex] ?? "";
17359
+ startIndex += 1;
17360
+ }
17361
+ for (let index = startIndex;index < parts.length; index += 1) {
17362
+ const part = parts[index];
17363
+ if (part === "--all") {
17364
+ all = true;
17365
+ continue;
17366
+ }
17367
+ if (part === "--cursor") {
17368
+ const value = parts[index + 1] ?? "";
17369
+ if (index + 1 >= parts.length || value.startsWith("-")) {
17370
+ invalid = true;
17371
+ break;
17372
+ }
17373
+ cursor = value;
17374
+ index += 1;
17375
+ continue;
17376
+ }
17377
+ if (part === "--cwd" || part === "-d") {
17378
+ const value = parts[index + 1] ?? "";
17379
+ if (index + 1 >= parts.length || value.startsWith("-") || workspace) {
17380
+ invalid = true;
17381
+ break;
17382
+ }
17383
+ cwd = value;
17384
+ index += 1;
17385
+ continue;
17386
+ }
17387
+ if (part === "--ws" || part === "-ws") {
17388
+ const value = parts[index + 1] ?? "";
17389
+ if (index + 1 >= parts.length || value.startsWith("-") || cwd) {
17390
+ invalid = true;
17391
+ break;
17392
+ }
17393
+ workspace = value;
17394
+ index += 1;
17395
+ continue;
17396
+ }
17397
+ invalid = true;
17398
+ break;
17399
+ }
17400
+ if (invalid) {
17401
+ return null;
17402
+ }
17403
+ const result = {
17404
+ kind: "session.native.list"
17405
+ };
17406
+ if (agent.trim().length > 0)
17407
+ result.agent = agent;
17408
+ if (cwd.trim().length > 0)
17409
+ result.cwd = cwd;
17410
+ if (workspace.trim().length > 0)
17411
+ result.workspace = workspace;
17412
+ if (all)
17413
+ result.all = true;
17414
+ if (cursor.trim().length > 0)
17415
+ result.cursor = cursor;
17416
+ return Object.keys(result).length > 1 ? result : { kind: "session.native.list" };
17417
+ }
17418
+ function readNativeAttachCommand(parts, identifierIndex) {
17419
+ const identifier = parts[identifierIndex] ?? "";
17420
+ let alias = "";
17421
+ for (let index = identifierIndex + 1;index < parts.length; index += 1) {
17422
+ if (parts[index] === "-a" || parts[index] === "--alias") {
17423
+ const value = parts[index + 1] ?? "";
17424
+ if (index + 1 >= parts.length || value.startsWith("-")) {
17425
+ return null;
17426
+ }
17427
+ alias = value;
17428
+ index += 1;
17429
+ continue;
17430
+ }
17431
+ return null;
17432
+ }
17433
+ if (identifier.trim().length === 0 || identifier.startsWith("-")) {
17434
+ return null;
17435
+ }
17436
+ if (alias.trim().length > 0) {
17437
+ return { kind: "session.native.attach", identifier, alias };
17438
+ }
17439
+ return { kind: "session.native.attach", identifier };
17440
+ }
17261
17441
  function normalizeCommand(command) {
17262
17442
  if (command === "/ss")
17263
17443
  return "/session";
@@ -17495,6 +17675,9 @@ var init_command_policy = __esm(() => {
17495
17675
  "session.shortcut": "/session",
17496
17676
  "session.shortcut.new": "/session",
17497
17677
  "session.attach": "/session attach",
17678
+ "session.native.list": "/ssn",
17679
+ "session.native.select": "/ssn",
17680
+ "session.native.attach": "/ssn attach",
17498
17681
  "later.create": "/later",
17499
17682
  "later.list": "/later list",
17500
17683
  "later.cancel": "/later cancel"
@@ -18155,7 +18338,7 @@ async function handleSessions(context, chatKey) {
18155
18338
  }
18156
18339
  async function handleSessionNew(context, chatKey, alias, agent, workspace) {
18157
18340
  const channelId = getChannelIdFromChatKey(chatKey);
18158
- const internalAlias = channelId === "weixin" ? alias : toInternalSessionAlias(channelId, alias);
18341
+ const internalAlias = scopeDisplayAliasToInternal(channelId, alias);
18159
18342
  const session = context.lifecycle.resolveSession(internalAlias, agent, workspace, `${workspace}:${internalAlias}`);
18160
18343
  const releaseTransportReservation = await context.lifecycle.reserveTransportSession(session.transportSession);
18161
18344
  try {
@@ -18186,7 +18369,7 @@ async function handleSessionShortcut(context, chatKey, agent, target, createNew)
18186
18369
  }
18187
18370
  async function handleSessionAttach(context, chatKey, alias, agent, workspace, transportSession) {
18188
18371
  const channelId = getChannelIdFromChatKey(chatKey);
18189
- const internalAlias = channelId === "weixin" ? alias : toInternalSessionAlias(channelId, alias);
18372
+ const internalAlias = scopeDisplayAliasToInternal(channelId, alias);
18190
18373
  const attached = context.lifecycle.resolveSession(internalAlias, agent, workspace, transportSession);
18191
18374
  const releaseTransportReservation = await context.lifecycle.reserveTransportSession(attached.transportSession);
18192
18375
  try {
@@ -18553,7 +18736,7 @@ async function markCoordinatorResultsInjectionFailed(context, taskIds, groupIds,
18553
18736
  });
18554
18737
  }
18555
18738
  }
18556
- var NO_CURRENT_SESSION_TEXT = "当前还没有选中的会话。请先执行 /session new ... 或 /use <alias>。", DEFAULT_SESSION_TAIL_LINES = 50, MAX_SESSION_TAIL_LINES = 500, sessionHelp, modeHelp, replyModeHelp, statusHelp, cancelHelp;
18739
+ 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;
18557
18740
  var init_session_handler = __esm(() => {
18558
18741
  init_build_coordinator_prompt();
18559
18742
  init_channel_scope();
@@ -18561,7 +18744,7 @@ var init_session_handler = __esm(() => {
18561
18744
  sessionHelp = {
18562
18745
  topic: "session",
18563
18746
  aliases: ["ss", "sessions"],
18564
- summary: "创建、恢复、切换和重置逻辑会话。",
18747
+ summary: "创建、复用、切换和重置 weacpx 逻辑会话。",
18565
18748
  commands: [
18566
18749
  { usage: "/sessions", description: "查看当前会话列表" },
18567
18750
  { usage: "/session 或 /ss", description: "查看会话列表" },
@@ -18569,12 +18752,48 @@ var init_session_handler = __esm(() => {
18569
18752
  { usage: "/ss new <agent> (-d <path> | --ws <name>)", description: "强制新建会话" },
18570
18753
  { usage: "/ss new <alias> -a <name> --ws <name>", description: "按指定配置新建会话" },
18571
18754
  { usage: "/ss attach <alias> -a <name> --ws <name> --name <transport-session>", description: "绑定已有会话" },
18755
+ { usage: "/ssn 或 /help ssn", description: "接入本地 native 会话(Codex 等 Agent 原生会话)" },
18572
18756
  { usage: "/session tail [N]", description: "补拉当前会话的历史输出(默认 50 行)" },
18573
18757
  { usage: "/session rm <alias>", description: "删除逻辑会话" },
18574
18758
  { usage: "/use <alias>", description: "切换当前会话" },
18575
18759
  { usage: "/session reset 或 /clear", description: "重置当前会话上下文" }
18576
18760
  ],
18577
- examples: ["/ss codex -d /absolute/path/to/repo", "/use backend-fix", "/session rm old-session", "/session reset"]
18761
+ examples: [
18762
+ "/ss codex -d /absolute/path/to/repo",
18763
+ "/ssn",
18764
+ "/ssn 1",
18765
+ "/use backend-fix",
18766
+ "/session rm old-session",
18767
+ "/session reset"
18768
+ ]
18769
+ };
18770
+ nativeSessionHelp = {
18771
+ topic: "native",
18772
+ aliases: ["ssn", "native-session"],
18773
+ summary: "接入 Codex 等 Agent 的本地原生会话。",
18774
+ commands: [
18775
+ { usage: "/ssn", description: "按当前 weacpx 会话上下文查看本地 native 会话" },
18776
+ { usage: "/ssn <agent> --ws <workspace>", description: "查询指定工作区的本地 native 会话;只有一个候选时自动接入" },
18777
+ { usage: "/ssn <agent> -d <path>", description: "按本机绝对路径查询;只有一个候选时自动接入" },
18778
+ { usage: "/ssn <agent> --ws <workspace> --all", description: "跨 cwd 查看该 agent 的 native 会话" },
18779
+ { usage: "/ssn 1", description: "接入或切换到最近一次列表里的第 1 个候选" },
18780
+ { usage: "/ssn 1 -a <alias>", description: "接入第 1 个候选并指定 weacpx 别名(推荐,无需完整 sessionId)" },
18781
+ { usage: "/ssn attach <sessionId> -a <alias>", description: "按原生 sessionId 接入(适合已知完整 id),并指定 weacpx 别名" },
18782
+ { usage: "/ss attach native <sessionId> -a <alias>", description: "/ssn attach 的长写法" }
18783
+ ],
18784
+ examples: [
18785
+ "/ssn codex --ws backend",
18786
+ "/ssn codex -d /absolute/path/to/repo",
18787
+ "/ssn",
18788
+ "/ssn 1",
18789
+ "/ssn 1 -a fix-ci"
18790
+ ],
18791
+ notes: [
18792
+ "/ss 管 weacpx 逻辑会话;/ssn 只负责查询和接入 Agent 原生会话。",
18793
+ "接入后继续发普通消息,会继续同一个 Agent 原生会话,不是复制一份新上下文。",
18794
+ "如果当前 acpx 或 Agent 不支持 native 会话,请继续使用 /ss。",
18795
+ "完整说明见 docs/native-sessions.md。"
18796
+ ]
18578
18797
  };
18579
18798
  modeHelp = {
18580
18799
  topic: "mode",
@@ -19537,6 +19756,7 @@ var init_help_registry = __esm(() => {
19537
19756
  init_later_handler();
19538
19757
  HELP_TOPICS = [
19539
19758
  sessionHelp,
19759
+ nativeSessionHelp,
19540
19760
  workspaceHelp,
19541
19761
  agentHelp,
19542
19762
  permissionHelp,
@@ -19573,6 +19793,7 @@ function renderHelpIndex() {
19573
19793
  return [
19574
19794
  "常用入口:",
19575
19795
  "- /ss <agent> (-d <path> | --ws <name>) - 快速新建或切到会话",
19796
+ "- /ssn <agent> (-d <path> | --ws <name>) - 接入本地 Agent 原生会话",
19576
19797
  "- /use <alias> - 切换当前会话",
19577
19798
  "- /status - 查看当前会话状态",
19578
19799
  "",
@@ -19581,7 +19802,7 @@ function renderHelpIndex() {
19581
19802
  "",
19582
19803
  "查看专题说明:",
19583
19804
  "- /help <topic>",
19584
- "- 例如:/help ss、/help ws、/help pm"
19805
+ "- 例如:/help ss、/help ssn、/help ws、/help pm"
19585
19806
  ].join(`
19586
19807
  `);
19587
19808
  }
@@ -19663,7 +19884,7 @@ async function handleSessionShortcutCommand(context, ops, chatKey, agent, target
19663
19884
  });
19664
19885
  const baseAlias = `${workspace.name}:${agent}`;
19665
19886
  const channelId = getChannelIdFromChatKey(chatKey);
19666
- const scopedBase = channelId === "weixin" ? baseAlias : `${channelId}:${baseAlias}`;
19887
+ const scopedBase = scopeDisplayAliasToInternal(channelId, baseAlias);
19667
19888
  const alias = createNew ? await allocateUniqueSessionAlias(context, scopedBase, chatKey) : scopedBase;
19668
19889
  const display = toDisplaySessionAlias(alias);
19669
19890
  if (!createNew && await hasLogicalSession(context, alias, chatKey)) {
@@ -19792,6 +20013,404 @@ var init_session_shortcut_handler = __esm(() => {
19792
20013
  init_channel_scope();
19793
20014
  });
19794
20015
 
20016
+ // src/commands/handlers/native-session-handler.ts
20017
+ async function handleNativeSessionList(context, chatKey, input) {
20018
+ const target = await resolveNativeTarget(context, chatKey, input);
20019
+ if (isRouterResponse(target)) {
20020
+ return target;
20021
+ }
20022
+ const listAgentSessions = context.transport.listAgentSessions?.bind(context.transport);
20023
+ if (!listAgentSessions) {
20024
+ return { text: `当前 transport 不支持列出本地会话,请继续使用 /ss。
20025
+ 说明:/help ssn` };
20026
+ }
20027
+ const query = {
20028
+ agent: target.agent,
20029
+ agentCommand: target.agentCommand,
20030
+ cwd: target.cwd,
20031
+ ...input.cursor ? { cursor: input.cursor } : {},
20032
+ ...input.all ? {} : { filterCwd: target.cwd }
20033
+ };
20034
+ let result;
20035
+ try {
20036
+ result = await listAgentSessions(query);
20037
+ } catch (error2) {
20038
+ return { text: renderNativeListError(target, error2) };
20039
+ }
20040
+ if (!result) {
20041
+ return { text: `当前 transport 不支持列出本地会话,请继续使用 /ss。
20042
+ 说明:/help ssn` };
20043
+ }
20044
+ await context.sessions.cacheNativeSessionList(chatKey, {
20045
+ agent: target.agent,
20046
+ workspace: target.workspace,
20047
+ cwd: target.cwd,
20048
+ sessions: result.sessions,
20049
+ ...result.nextCursor !== undefined ? { nextCursor: result.nextCursor } : {}
20050
+ });
20051
+ if (result.sessions.length === 0) {
20052
+ return {
20053
+ text: [
20054
+ `没有找到本地 ${target.agentDisplayName} 会话(${target.workspaceLabel})。`,
20055
+ `你可以稍后再试,或先通过 /ss 保持当前逻辑会话。`
20056
+ ].join(`
20057
+ `)
20058
+ };
20059
+ }
20060
+ const explicitAttachTarget = Boolean(input.workspace || input.cwd);
20061
+ if (explicitAttachTarget && !input.all && !input.cursor && result.sessions.length === 1) {
20062
+ return await attachNativeSession(context, chatKey, target, result.sessions[0], undefined);
20063
+ }
20064
+ const attachedEntries = await buildAttachedEntries(context, chatKey, target.agent, result.sessions);
20065
+ const nativeSessionListOptions = { format: context.resolveNativeSessionListFormat?.(chatKey) ?? "table" };
20066
+ return {
20067
+ text: renderNativeSessionList(target, result, attachedEntries, Boolean(input.all), nativeSessionListOptions)
20068
+ };
20069
+ }
20070
+ async function handleNativeSessionSelect(context, chatKey, identifier, alias) {
20071
+ const trimmed = identifier.trim();
20072
+ if (!trimmed) {
20073
+ return { text: `请选择要切换的 native 会话编号或 sessionId。
20074
+ 说明:/help ssn` };
20075
+ }
20076
+ if (/^[0-9]+$/.test(trimmed)) {
20077
+ const cached2 = await context.sessions.getNativeSessionList(chatKey, NATIVE_SESSION_CACHE_TTL_MS);
20078
+ if (!cached2 || cached2.sessions.length === 0) {
20079
+ return { text: `当前没有可用的 native 会话列表,请先执行 /ssn 再选择。
20080
+ 说明:/help ssn` };
20081
+ }
20082
+ const index = Number(trimmed) - 1;
20083
+ const session = cached2.sessions[index];
20084
+ if (!session) {
20085
+ return { text: "编号超出范围,请先执行 /ssn 重新获取列表。" };
20086
+ }
20087
+ const target2 = await resolveTargetFromCachedSession(context, chatKey, cached2, session);
20088
+ if (isRouterResponse(target2)) {
20089
+ return target2;
20090
+ }
20091
+ return await attachNativeSession(context, chatKey, target2, session, alias);
20092
+ }
20093
+ const target = await resolveNativeTarget(context, chatKey, {});
20094
+ if (isRouterResponse(target)) {
20095
+ return target;
20096
+ }
20097
+ return await attachNativeSession(context, chatKey, target, { sessionId: trimmed }, alias);
20098
+ }
20099
+ async function attachNativeSession(context, chatKey, target, session, alias) {
20100
+ if (!context.transport.resumeAgentSession) {
20101
+ return { text: "当前 transport 不支持接入本地会话,请继续使用 /ss。" };
20102
+ }
20103
+ const nativeTarget = target;
20104
+ const existing = await context.sessions.findAttachedNativeSession(chatKey, nativeTarget.agent, session.sessionId);
20105
+ if (existing) {
20106
+ await context.sessions.useSession(chatKey, existing.alias);
20107
+ const displayAlias2 = toDisplaySessionAlias(existing.alias);
20108
+ return {
20109
+ text: `已切换到已接入的本地会话:${nativeTarget.agentDisplayName} · ${displayAlias2}`
20110
+ };
20111
+ }
20112
+ const requestedAlias = alias?.trim() || buildDefaultNativeAlias(nativeTarget.agent, session.sessionId);
20113
+ const displayAlias = await allocateUniqueNativeAlias(context, chatKey, requestedAlias);
20114
+ const internalAlias = scopeDisplayAliasToInternal(getChannelIdFromChatKey(chatKey), displayAlias);
20115
+ const transportSession = context.sessions.buildDefaultTransportSessionForChat(chatKey, displayAlias);
20116
+ const resolvedSession = context.lifecycle.resolveSession(internalAlias, nativeTarget.agent, nativeTarget.workspace, transportSession);
20117
+ const releaseReservation = await context.lifecycle.reserveTransportSession(resolvedSession.transportSession);
20118
+ try {
20119
+ try {
20120
+ await context.transport.resumeAgentSession(resolvedSession, session.sessionId);
20121
+ } catch (error2) {
20122
+ return { text: renderNativeResumeError(target, error2) };
20123
+ }
20124
+ const verified = await context.lifecycle.checkTransportSession(resolvedSession);
20125
+ if (!verified) {
20126
+ return { text: `本地 ${target.agentDisplayName} 会话接入失败:未检测到已恢复的后端会话。` };
20127
+ }
20128
+ await context.sessions.attachNativeSession({
20129
+ alias: internalAlias,
20130
+ agent: nativeTarget.agent,
20131
+ workspace: nativeTarget.workspace,
20132
+ transportSession,
20133
+ ...target.agentCommand ? { transportAgentCommand: target.agentCommand } : {},
20134
+ agentSessionId: session.sessionId,
20135
+ title: session.title,
20136
+ updatedAt: session.updatedAt
20137
+ });
20138
+ await context.sessions.useSession(chatKey, internalAlias);
20139
+ await refreshAgentCommandBestEffort(context, internalAlias);
20140
+ return {
20141
+ text: `已接入本地 ${target.agentDisplayName} 会话并切换:${toDisplaySessionAlias(internalAlias)}`
20142
+ };
20143
+ } finally {
20144
+ await releaseReservation();
20145
+ }
20146
+ }
20147
+ async function resolveNativeTarget(context, chatKey, input) {
20148
+ const currentSession = await context.sessions.getCurrentSession(chatKey);
20149
+ const agent = input.agent?.trim() || currentSession?.agent || "";
20150
+ if (!agent) {
20151
+ return {
20152
+ text: `请先选择上下文,例如:
20153
+ /ssn codex --ws project
20154
+ /ssn codex -d /Users/me/project
20155
+ 说明:/help ssn`
20156
+ };
20157
+ }
20158
+ const agentConfig = context.config?.agents[agent];
20159
+ if (!agentConfig) {
20160
+ return { text: `Agent「${agent}」未注册。` };
20161
+ }
20162
+ const workspaceResolution = await resolveNativeWorkspace(context, input, currentSession);
20163
+ if (isRouterResponse(workspaceResolution)) {
20164
+ return workspaceResolution;
20165
+ }
20166
+ return {
20167
+ agent,
20168
+ agentDisplayName: displayAgentName(agent),
20169
+ agentCommand: resolveAgentCommand(agentConfig.driver, agentConfig.command),
20170
+ workspace: workspaceResolution.workspace,
20171
+ workspaceLabel: workspaceResolution.workspaceLabel,
20172
+ cwd: workspaceResolution.cwd,
20173
+ source: workspaceResolution.source
20174
+ };
20175
+ }
20176
+ async function resolveTargetFromCachedSession(context, chatKey, cached2, session) {
20177
+ if (session.cwd && !sameWorkspacePath(session.cwd, cached2.cwd)) {
20178
+ return await resolveNativeTarget(context, chatKey, {
20179
+ agent: cached2.agent,
20180
+ cwd: session.cwd
20181
+ });
20182
+ }
20183
+ return await resolveNativeTarget(context, chatKey, {
20184
+ agent: cached2.agent,
20185
+ ...cached2.workspace ? { workspace: cached2.workspace } : { cwd: cached2.cwd }
20186
+ });
20187
+ }
20188
+ async function resolveNativeWorkspace(context, input, currentSession) {
20189
+ if (input.workspace) {
20190
+ const workspaceConfig = context.config?.workspaces[input.workspace];
20191
+ if (!workspaceConfig) {
20192
+ return { text: `工作区「${input.workspace}」未注册。` };
20193
+ }
20194
+ return {
20195
+ workspace: input.workspace,
20196
+ workspaceLabel: input.workspace,
20197
+ cwd: workspaceConfig.cwd,
20198
+ source: "workspace"
20199
+ };
20200
+ }
20201
+ if (input.cwd) {
20202
+ const cwd = normalizeWorkspacePath(input.cwd);
20203
+ const existing = Object.entries(context.config?.workspaces ?? {}).find(([, workspace]) => sameWorkspacePath(workspace.cwd, cwd));
20204
+ if (existing) {
20205
+ return {
20206
+ workspace: existing[0],
20207
+ workspaceLabel: existing[0],
20208
+ cwd: existing[1].cwd,
20209
+ source: "cwd"
20210
+ };
20211
+ }
20212
+ if (!await pathExists(cwd)) {
20213
+ return { text: `工作区路径不存在:${input.cwd}` };
20214
+ }
20215
+ if (!context.configStore || !context.config) {
20216
+ return { text: "当前没有加载可写入的配置,无法根据路径创建工作区。" };
20217
+ }
20218
+ const workspaceName = allocateWorkspaceName(sanitizeWorkspaceName(basenameForWorkspacePath(cwd)), context.config.workspaces);
20219
+ const updated = await context.configStore.upsertWorkspace(workspaceName, cwd);
20220
+ context.replaceConfig(updated);
20221
+ return {
20222
+ workspace: workspaceName,
20223
+ workspaceLabel: workspaceName,
20224
+ cwd,
20225
+ source: "cwd"
20226
+ };
20227
+ }
20228
+ if (currentSession) {
20229
+ return {
20230
+ workspace: currentSession.workspace,
20231
+ workspaceLabel: currentSession.workspace,
20232
+ cwd: currentSession.cwd,
20233
+ source: "workspace"
20234
+ };
20235
+ }
20236
+ return {
20237
+ text: `请先选择上下文,例如:
20238
+ /ssn codex --ws project
20239
+ /ssn codex -d /Users/me/project
20240
+ 说明:/help ssn`
20241
+ };
20242
+ }
20243
+ async function buildAttachedEntries(context, chatKey, agent, sessions) {
20244
+ const currentSession = await context.sessions.getCurrentSession(chatKey);
20245
+ return await Promise.all(sessions.map(async (session) => {
20246
+ const attached = await context.sessions.findAttachedNativeSession(chatKey, agent, session.sessionId);
20247
+ if (!attached) {
20248
+ return { session };
20249
+ }
20250
+ return {
20251
+ session,
20252
+ attached: {
20253
+ alias: attached.alias,
20254
+ displayAlias: toDisplaySessionAlias(attached.alias),
20255
+ isCurrent: currentSession?.alias === attached.alias
20256
+ }
20257
+ };
20258
+ }));
20259
+ }
20260
+ function renderNativeSessionList(target, result, entries, includeAll, options = {}) {
20261
+ if (options.format === "cards") {
20262
+ return renderNativeSessionCardList(target, result, entries, includeAll);
20263
+ }
20264
+ return renderNativeSessionTableList(target, result, entries, includeAll);
20265
+ }
20266
+ function renderNativeSessionTableList(target, result, entries, includeAll) {
20267
+ const lines = [`本地 ${target.agentDisplayName} 会话(${target.workspaceLabel}):`];
20268
+ lines.push("| # | 标题 | 更新时间 | ID |");
20269
+ lines.push("|---|---|---|---|");
20270
+ entries.forEach((entry, index) => {
20271
+ const title = escapeMarkdownTableCell(renderNativeSessionTitle(entry.session.title, entry.session.sessionId));
20272
+ const updatedAt = entry.session.updatedAt ? formatNativeSessionTime(entry.session.updatedAt) : "-";
20273
+ const idParts = [entry.session.sessionId];
20274
+ if (entry.attached) {
20275
+ idParts.push(`已接入:${entry.attached.displayAlias}${entry.attached.isCurrent ? " [当前]" : ""}`);
20276
+ }
20277
+ lines.push(`| ${index + 1} | ${title} | ${escapeMarkdownTableCell(updatedAt)} | ${escapeMarkdownTableCell(idParts.join(" · "))} |`);
20278
+ });
20279
+ lines.push("");
20280
+ lines.push("操作:");
20281
+ lines.push("接入:/ssn 1");
20282
+ lines.push("指定别名:/ssn 1 -a fix-ci");
20283
+ lines.push("说明:/help ssn");
20284
+ if (result.nextCursor) {
20285
+ lines.push(`更多:${renderNextPageCommand(target, result.nextCursor, includeAll)}`);
20286
+ }
20287
+ return lines.join(`
20288
+ `);
20289
+ }
20290
+ function renderNativeSessionCardList(target, result, entries, includeAll) {
20291
+ const lines = [
20292
+ `本地 ${target.agentDisplayName} 会话(${target.workspaceLabel}):`,
20293
+ "回复编号接入,ID 尾号用于区分。"
20294
+ ];
20295
+ entries.forEach((entry, index) => {
20296
+ const title = renderNativeSessionTitle(entry.session.title, entry.session.sessionId);
20297
+ const updatedAt = entry.session.updatedAt ? formatNativeSessionTime(entry.session.updatedAt) : "-";
20298
+ lines.push("");
20299
+ lines.push(`【${index + 1}】 ${title}`);
20300
+ lines.push(`时间:${updatedAt}`);
20301
+ lines.push(`ID:${formatSessionIdTail(entry.session.sessionId)}`);
20302
+ if (entry.attached) {
20303
+ lines.push(`已接入:${entry.attached.displayAlias}${entry.attached.isCurrent ? " [当前]" : ""}`);
20304
+ }
20305
+ });
20306
+ lines.push("");
20307
+ lines.push("操作:");
20308
+ lines.push("接入:/ssn 1");
20309
+ lines.push("指定别名:/ssn 1 -a fix-ci");
20310
+ lines.push("说明:/help ssn");
20311
+ if (result.nextCursor) {
20312
+ lines.push(`更多:${renderNextPageCommand(target, result.nextCursor, includeAll)}`);
20313
+ }
20314
+ return lines.join(`
20315
+ `);
20316
+ }
20317
+ function renderNativeSessionTitle(title, fallback) {
20318
+ const normalized = (title?.trim() || fallback).replace(/\s+/g, " ");
20319
+ const maxLength = 60;
20320
+ return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1)}…` : normalized;
20321
+ }
20322
+ function buildDefaultNativeAlias(agent, sessionId) {
20323
+ return `${agent}-${sessionIdTail(sessionId)}`;
20324
+ }
20325
+ function formatSessionIdTail(sessionId) {
20326
+ const tail = sessionIdTail(sessionId);
20327
+ return tail.length < sessionId.trim().length ? `…${tail}` : tail;
20328
+ }
20329
+ function sessionIdTail(sessionId) {
20330
+ const trimmed = sessionId.trim();
20331
+ if (trimmed.length <= 8) {
20332
+ return trimmed;
20333
+ }
20334
+ return trimmed.slice(-8);
20335
+ }
20336
+ function formatNativeSessionTime(value) {
20337
+ const date4 = new Date(value);
20338
+ if (Number.isNaN(date4.getTime())) {
20339
+ return value;
20340
+ }
20341
+ const pad = (input) => String(input).padStart(2, "0");
20342
+ return `${date4.getFullYear()}-${pad(date4.getMonth() + 1)}-${pad(date4.getDate())} ${pad(date4.getHours())}:${pad(date4.getMinutes())}`;
20343
+ }
20344
+ function escapeMarkdownTableCell(value) {
20345
+ return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
20346
+ }
20347
+ function renderNextPageCommand(target, nextCursor, includeAll) {
20348
+ const scope = target.source === "workspace" && target.workspace ? `--ws ${target.workspace}` : `-d ${target.cwd}`;
20349
+ const allFlag = includeAll ? " --all" : "";
20350
+ return `/ssn ${target.agent} ${scope}${allFlag} --cursor ${nextCursor}`;
20351
+ }
20352
+ async function allocateUniqueNativeAlias(context, chatKey, baseDisplayAlias) {
20353
+ const channelId = getChannelIdFromChatKey(chatKey);
20354
+ const visible = await context.sessions.listSessions(chatKey);
20355
+ const existing = new Set(visible.map((session) => session.internalAlias));
20356
+ const base = baseDisplayAlias.trim() || "native-session";
20357
+ const transportFor = (candidate) => context.sessions.buildDefaultTransportSessionForChat(chatKey, candidate);
20358
+ const isFree = (candidate) => !existing.has(scopeDisplayAliasToInternal(channelId, candidate)) && context.sessions.countAliasesSharingTransport(transportFor(candidate)) === 0;
20359
+ if (isFree(base)) {
20360
+ return base;
20361
+ }
20362
+ let suffix = 2;
20363
+ while (!isFree(`${base}-${suffix}`)) {
20364
+ suffix += 1;
20365
+ }
20366
+ return `${base}-${suffix}`;
20367
+ }
20368
+ async function refreshAgentCommandBestEffort(context, alias) {
20369
+ try {
20370
+ await context.lifecycle.refreshSessionTransportAgentCommand(alias);
20371
+ } catch (error2) {
20372
+ await context.logger.error("session.native.agent_command_refresh_failed", "failed to refresh native session agent command", {
20373
+ alias,
20374
+ error: error2 instanceof Error ? error2.message : String(error2)
20375
+ });
20376
+ }
20377
+ }
20378
+ function renderNativeListError(target, error2) {
20379
+ return [
20380
+ `本地 ${target.agentDisplayName} 会话查询失败:${formatErrorMessage(error2)}`,
20381
+ "请确认 acpx/Agent 支持 native 会话查询,或继续使用 /ss。",
20382
+ "说明:/help ssn"
20383
+ ].join(`
20384
+ `);
20385
+ }
20386
+ function renderNativeResumeError(target, error2) {
20387
+ return [
20388
+ `本地 ${target.agentDisplayName} 会话接入失败:${formatErrorMessage(error2)}`,
20389
+ "请确认 acpx/Agent 支持 native 会话恢复,或继续使用 /ss。",
20390
+ "说明:/help ssn"
20391
+ ].join(`
20392
+ `);
20393
+ }
20394
+ function formatErrorMessage(error2) {
20395
+ return error2 instanceof Error ? error2.message : String(error2);
20396
+ }
20397
+ function isRouterResponse(value) {
20398
+ return typeof value.text === "string";
20399
+ }
20400
+ function displayAgentName(agent) {
20401
+ if (!agent) {
20402
+ return agent;
20403
+ }
20404
+ return agent.charAt(0).toUpperCase() + agent.slice(1);
20405
+ }
20406
+ var NATIVE_SESSION_CACHE_TTL_MS;
20407
+ var init_native_session_handler = __esm(() => {
20408
+ init_channel_scope();
20409
+ init_workspace_name();
20410
+ init_workspace_path();
20411
+ NATIVE_SESSION_CACHE_TTL_MS = 10 * 60 * 1000;
20412
+ });
20413
+
19795
20414
  // src/commands/handlers/session-recovery-handler.ts
19796
20415
  function renderTransportError(session, error2) {
19797
20416
  const message = error2 instanceof Error ? error2.message : String(error2);
@@ -20259,6 +20878,7 @@ class CommandRouter {
20259
20878
  quota;
20260
20879
  scheduled;
20261
20880
  scheduledDelivery;
20881
+ resolveNativeSessionListFormat;
20262
20882
  logger;
20263
20883
  autoInstall = autoInstallOptionalDep;
20264
20884
  discoverPaths = discoverParentPackagePaths;
@@ -20268,7 +20888,7 @@ class CommandRouter {
20268
20888
  __setDiscoverPathsForTest(fn) {
20269
20889
  this.discoverPaths = fn;
20270
20890
  }
20271
- constructor(sessions, transport, config2, configStore, logger2, resolveSessionAgentCommand = resolveSessionAgentCommandFromIndex, orchestration, quota, scheduled, scheduledDelivery) {
20891
+ constructor(sessions, transport, config2, configStore, logger2, resolveSessionAgentCommand = resolveSessionAgentCommandFromIndex, orchestration, quota, scheduled, scheduledDelivery, resolveNativeSessionListFormat) {
20272
20892
  this.sessions = sessions;
20273
20893
  this.transport = transport;
20274
20894
  this.config = config2;
@@ -20278,6 +20898,7 @@ class CommandRouter {
20278
20898
  this.quota = quota;
20279
20899
  this.scheduled = scheduled;
20280
20900
  this.scheduledDelivery = scheduledDelivery;
20901
+ this.resolveNativeSessionListFormat = resolveNativeSessionListFormat;
20281
20902
  this.logger = logger2 ?? createNoopAppLogger();
20282
20903
  }
20283
20904
  async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, onThought, perfSpan) {
@@ -20352,6 +20973,12 @@ class CommandRouter {
20352
20973
  return await handleSessionShortcut(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.agent, command, true);
20353
20974
  case "session.attach":
20354
20975
  return await handleSessionAttach(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.alias, command.agent, command.workspace, command.transportSession);
20976
+ case "session.native.list":
20977
+ return await handleNativeSessionList(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command);
20978
+ case "session.native.select":
20979
+ return await handleNativeSessionSelect(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.identifier, command.alias);
20980
+ case "session.native.attach":
20981
+ return await handleNativeSessionSelect(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.identifier, command.alias);
20355
20982
  case "session.use":
20356
20983
  return await handleSessionUse(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
20357
20984
  case "mode.show":
@@ -20453,7 +21080,8 @@ class CommandRouter {
20453
21080
  configStore: this.configStore,
20454
21081
  logger: this.logger,
20455
21082
  replaceConfig: (updated) => this.replaceConfig(updated),
20456
- ...this.quota ? { quota: this.quota } : {}
21083
+ ...this.quota ? { quota: this.quota } : {},
21084
+ ...this.resolveNativeSessionListFormat ? { resolveNativeSessionListFormat: this.resolveNativeSessionListFormat } : {}
20457
21085
  };
20458
21086
  }
20459
21087
  createSessionHandlerContext(reply, perfSpan) {
@@ -20815,6 +21443,7 @@ var init_command_router = __esm(() => {
20815
21443
  init_agent_handler();
20816
21444
  init_workspace_handler();
20817
21445
  init_session_shortcut_handler();
21446
+ init_native_session_handler();
20818
21447
  init_later_handler();
20819
21448
  init_session_recovery_handler();
20820
21449
  init_auto_install_optional_dep();
@@ -25138,11 +25767,13 @@ class SessionService {
25138
25767
  stateStore;
25139
25768
  state;
25140
25769
  stateMutex;
25770
+ now;
25141
25771
  constructor(config2, stateStore, state, options = {}) {
25142
25772
  this.config = config2;
25143
25773
  this.stateStore = stateStore;
25144
25774
  this.state = state;
25145
25775
  this.stateMutex = options.stateMutex ?? new AsyncMutex;
25776
+ this.now = options.now ?? (() => Date.now());
25146
25777
  }
25147
25778
  async createSession(alias, agent, workspace) {
25148
25779
  return await this.createLogicalSession(alias, agent, workspace, `${workspace}:${alias}`);
@@ -25176,6 +25807,14 @@ class SessionService {
25176
25807
  async attachSession(alias, agent, workspace, transportSession, transportAgentCommand) {
25177
25808
  return await this.createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand);
25178
25809
  }
25810
+ async attachNativeSession(input) {
25811
+ return await this.createLogicalSession(input.alias, input.agent, input.workspace, input.transportSession, input.transportAgentCommand, {
25812
+ source: "agent-side",
25813
+ agentSessionId: input.agentSessionId,
25814
+ title: input.title,
25815
+ updatedAt: input.updatedAt
25816
+ });
25817
+ }
25179
25818
  async getSession(alias) {
25180
25819
  const session = this.state.sessions[alias];
25181
25820
  if (!session) {
@@ -25190,6 +25829,22 @@ class SessionService {
25190
25829
  const preferred = matches.find((session) => session.alias === expectedAlias && session.workspace === expectedWorkspace) ?? matches[0];
25191
25830
  return preferred ? this.toResolvedSession(preferred) : null;
25192
25831
  }
25832
+ async findAttachedNativeSession(chatKey, agent, agentSessionId) {
25833
+ const channelId = getChannelIdFromChatKey(chatKey);
25834
+ for (const session of Object.values(this.state.sessions)) {
25835
+ if (session.source !== "agent-side") {
25836
+ continue;
25837
+ }
25838
+ if (session.agent !== agent || session.agent_session_id !== agentSessionId) {
25839
+ continue;
25840
+ }
25841
+ if (!isSessionAliasVisibleInChannel(session.alias, channelId)) {
25842
+ continue;
25843
+ }
25844
+ return this.toResolvedSession(session);
25845
+ }
25846
+ return null;
25847
+ }
25193
25848
  async useSession(chatKey, alias) {
25194
25849
  await this.mutate(async () => {
25195
25850
  const channelId = getChannelIdFromChatKey(chatKey);
@@ -25309,6 +25964,49 @@ class SessionService {
25309
25964
  return { wasActive };
25310
25965
  });
25311
25966
  }
25967
+ async cacheNativeSessionList(chatKey, input) {
25968
+ await this.mutate(async () => {
25969
+ this.state.native_session_lists[chatKey] = {
25970
+ created_at: new Date(this.now()).toISOString(),
25971
+ agent: input.agent,
25972
+ ...input.workspace !== undefined ? { workspace: input.workspace } : {},
25973
+ cwd: input.cwd,
25974
+ sessions: input.sessions.map((session) => ({
25975
+ session_id: session.sessionId,
25976
+ ...session.cwd !== undefined ? { cwd: session.cwd } : {},
25977
+ ...session.title !== undefined ? { title: session.title } : {},
25978
+ ...session.updatedAt !== undefined ? { updated_at: session.updatedAt } : {}
25979
+ })),
25980
+ ...input.nextCursor !== undefined ? { next_cursor: input.nextCursor } : {}
25981
+ };
25982
+ await this.persist();
25983
+ });
25984
+ }
25985
+ async getNativeSessionList(chatKey, ttlMs = 10 * 60 * 1000) {
25986
+ const cached2 = this.state.native_session_lists[chatKey];
25987
+ if (!cached2) {
25988
+ return null;
25989
+ }
25990
+ const createdAt = Date.parse(cached2.created_at);
25991
+ if (Number.isNaN(createdAt)) {
25992
+ return null;
25993
+ }
25994
+ if (this.now() - createdAt > ttlMs) {
25995
+ return null;
25996
+ }
25997
+ return {
25998
+ agent: cached2.agent,
25999
+ ...cached2.workspace !== undefined ? { workspace: cached2.workspace } : {},
26000
+ cwd: cached2.cwd,
26001
+ sessions: cached2.sessions.map((session) => ({
26002
+ sessionId: session.session_id,
26003
+ ...session.cwd !== undefined ? { cwd: session.cwd } : {},
26004
+ ...session.title !== undefined ? { title: session.title } : {},
26005
+ ...session.updated_at !== undefined ? { updatedAt: session.updated_at } : {}
26006
+ })),
26007
+ ...cached2.next_cursor !== undefined ? { nextCursor: cached2.next_cursor } : {}
26008
+ };
26009
+ }
25312
26010
  toResolvedSession(session) {
25313
26011
  const agentConfig = this.config.agents[session.agent];
25314
26012
  if (!agentConfig) {
@@ -25324,6 +26022,11 @@ class SessionService {
25324
26022
  agentCommand: session.transport_agent_command ?? resolveAgentCommand(agentConfig.driver, agentConfig.command),
25325
26023
  workspace: session.workspace,
25326
26024
  transportSession: session.transport_session,
26025
+ source: session.source,
26026
+ agentSessionId: session.agent_session_id,
26027
+ agentSessionTitle: session.agent_session_title,
26028
+ agentSessionUpdatedAt: session.agent_session_updated_at,
26029
+ attachedAt: session.attached_at,
25327
26030
  modeId: session.mode_id,
25328
26031
  replyMode: session.reply_mode,
25329
26032
  cwd: workspaceConfig.cwd
@@ -25351,20 +26054,25 @@ class SessionService {
25351
26054
  async persist() {
25352
26055
  await this.stateStore.save(this.state);
25353
26056
  }
25354
- async createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand) {
26057
+ async createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand, native) {
25355
26058
  return await this.mutate(async () => {
25356
26059
  this.validateSession(alias, agent, workspace);
25357
26060
  if (this.state.orchestration.externalCoordinators[transportSession]) {
25358
26061
  throw new Error(`transport session "${transportSession}" conflicts with an external coordinator`);
25359
26062
  }
25360
26063
  const existingSession = this.state.sessions[alias];
25361
- const now = new Date().toISOString();
26064
+ const now = new Date(this.now()).toISOString();
25362
26065
  const normalizedTransportAgentCommand = transportAgentCommand?.trim();
25363
26066
  const session = {
25364
26067
  alias,
25365
26068
  agent,
25366
26069
  workspace,
25367
26070
  transport_session: transportSession,
26071
+ source: native?.source,
26072
+ agent_session_id: native?.agentSessionId,
26073
+ agent_session_title: native?.title ?? undefined,
26074
+ agent_session_updated_at: native?.updatedAt,
26075
+ attached_at: native ? now : undefined,
25368
26076
  ...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : existingSession?.transport_agent_command ? { transport_agent_command: existingSession.transport_agent_command } : {},
25369
26077
  mode_id: existingSession?.mode_id,
25370
26078
  reply_mode: existingSession?.reply_mode,
@@ -26161,6 +26869,18 @@ class AcpxBridgeTransport {
26161
26869
  lines
26162
26870
  });
26163
26871
  }
26872
+ async listAgentSessions(query) {
26873
+ return await this.client.request("listAgentSessions", { ...query });
26874
+ }
26875
+ async resumeAgentSession(session, agentSessionId) {
26876
+ await this.client.request("resumeAgentSession", {
26877
+ agent: session.agent,
26878
+ ...session.agentCommand ? { agentCommand: session.agentCommand } : {},
26879
+ cwd: session.cwd,
26880
+ name: session.transportSession,
26881
+ agentSessionId
26882
+ });
26883
+ }
26164
26884
  async prompt(session, text, reply, replyContext, options) {
26165
26885
  const sink = reply ? createQuotaGatedReplySink({
26166
26886
  reply,
@@ -26759,7 +27479,7 @@ import { join as join13 } from "node:path";
26759
27479
  function buildWeacpxMcpServerSpec(input) {
26760
27480
  const { command, args } = splitCommandLine(input.weacpxCommand);
26761
27481
  return {
26762
- name: "weacpx-orchestration",
27482
+ name: "weacpx",
26763
27483
  type: "stdio",
26764
27484
  command,
26765
27485
  args: [
@@ -26956,6 +27676,76 @@ function permissionModeToFlag(permissionMode) {
26956
27676
  }
26957
27677
  }
26958
27678
 
27679
+ // src/transport/agent-session-list.ts
27680
+ import path15 from "node:path";
27681
+ function isUnknownFilterCwdOption(output) {
27682
+ return /(?:unknown|unrecognized) option/i.test(output) && output.includes("--filter-cwd");
27683
+ }
27684
+ async function runAgentSessionList(options) {
27685
+ let result = await options.runList(true);
27686
+ let filterLocally = false;
27687
+ if (result.code !== 0 && options.filterCwd && isUnknownFilterCwdOption(result.stdout + result.stderr)) {
27688
+ result = await options.runList(false);
27689
+ filterLocally = true;
27690
+ }
27691
+ if (result.code !== 0) {
27692
+ if ((result.stdout + result.stderr).includes("sessionCapabilities.list")) {
27693
+ return;
27694
+ }
27695
+ throw new Error(options.formatError(result));
27696
+ }
27697
+ return parseAgentSessionListOutput(result.stdout, filterLocally ? options.filterCwd : undefined);
27698
+ }
27699
+ function parseAgentSessionListOutput(stdout2, filterCwd) {
27700
+ let parsed;
27701
+ try {
27702
+ parsed = JSON.parse(stdout2);
27703
+ } catch {
27704
+ throw new Error("failed to parse acpx sessions list output");
27705
+ }
27706
+ if (!isAgentSessionListResult(parsed)) {
27707
+ return;
27708
+ }
27709
+ return filterCwd ? filterAgentSessionListByCwd(parsed, filterCwd) : parsed;
27710
+ }
27711
+ function isAgentSessionListResult(value) {
27712
+ if (!value || typeof value !== "object" || Array.isArray(value))
27713
+ return false;
27714
+ const record3 = value;
27715
+ if (record3.source !== "agent" || !Array.isArray(record3.sessions))
27716
+ return false;
27717
+ return record3.sessions.every((session) => {
27718
+ if (!session || typeof session !== "object" || Array.isArray(session))
27719
+ return false;
27720
+ const item = session;
27721
+ return typeof item.sessionId === "string";
27722
+ });
27723
+ }
27724
+ function filterAgentSessionListByCwd(result, cwd) {
27725
+ return {
27726
+ ...result,
27727
+ sessions: result.sessions.filter((session) => session.cwd && sameAgentSessionCwd(session.cwd, cwd))
27728
+ };
27729
+ }
27730
+ function sameAgentSessionCwd(left, right) {
27731
+ const normalizedLeft = normalizeAgentSessionCwd(left);
27732
+ const normalizedRight = normalizeAgentSessionCwd(right);
27733
+ if (isWindowsLikePath2(normalizedLeft) || isWindowsLikePath2(normalizedRight)) {
27734
+ return normalizedLeft.toLowerCase() === normalizedRight.toLowerCase();
27735
+ }
27736
+ return normalizedLeft === normalizedRight;
27737
+ }
27738
+ function normalizeAgentSessionCwd(input) {
27739
+ if (isWindowsLikePath2(input)) {
27740
+ return path15.win32.normalize(input).replace(/\\/g, "/");
27741
+ }
27742
+ return path15.posix.normalize(input.replace(/\\/g, "/"));
27743
+ }
27744
+ function isWindowsLikePath2(input) {
27745
+ return /^[a-zA-Z]:[\\/]/.test(input) || input.startsWith("\\\\");
27746
+ }
27747
+ var init_agent_session_list = () => {};
27748
+
26959
27749
  // src/transport/acpx-cli/acpx-cli-transport.ts
26960
27750
  import { createRequire as createRequire5 } from "node:module";
26961
27751
  import { spawn as spawn9 } from "node:child_process";
@@ -27065,6 +27855,23 @@ class AcpxCliTransport {
27065
27855
  timeoutMs: this.sessionInitTimeoutMs
27066
27856
  });
27067
27857
  }
27858
+ async listAgentSessions(query) {
27859
+ return await runAgentSessionList({
27860
+ filterCwd: query.filterCwd,
27861
+ runList: async (includeFilterCwd) => {
27862
+ const args = this.buildAgentQueryArgs(query, "json", [
27863
+ "sessions",
27864
+ "list",
27865
+ ...includeFilterCwd && query.filterCwd ? ["--filter-cwd", query.filterCwd] : [],
27866
+ ...query.cursor ? ["--cursor", query.cursor] : []
27867
+ ]);
27868
+ return await this.runCommandWithTimeout(this.runCommand, args, {
27869
+ timeoutMs: this.sessionInitTimeoutMs
27870
+ });
27871
+ },
27872
+ formatError: (result) => normalizeCommandError(result) ?? `command failed with exit code ${result.code}`
27873
+ });
27874
+ }
27068
27875
  async tailSessionHistory(session, lines) {
27069
27876
  const candidates = [
27070
27877
  ["sessions", "history", "quiet", "-s", session.transportSession, String(lines)],
@@ -27133,6 +27940,20 @@ ${baseText}` : "" };
27133
27940
  message: output.trim()
27134
27941
  };
27135
27942
  }
27943
+ async resumeAgentSession(session, agentSessionId) {
27944
+ const args = this.buildArgs(session, [
27945
+ "sessions",
27946
+ "new",
27947
+ "--name",
27948
+ session.transportSession,
27949
+ "--resume-session",
27950
+ agentSessionId
27951
+ ]);
27952
+ const runResume = session.agentCommand ? this.run : this.runWithPty;
27953
+ await runResume.call(this, args, {
27954
+ timeoutMs: this.sessionInitTimeoutMs
27955
+ });
27956
+ }
27136
27957
  async updatePermissionPolicy(policy) {
27137
27958
  this.permissionMode = policy.permissionMode;
27138
27959
  this.nonInteractivePermissions = policy.nonInteractivePermissions;
@@ -27366,6 +28187,13 @@ ${baseText}` : "" };
27366
28187
  }
27367
28188
  return [...prefix, session.agent, ...tail2];
27368
28189
  }
28190
+ buildAgentQueryArgs(query, format, tail2) {
28191
+ const prefix = ["--format", format, "--cwd", query.cwd, ...this.buildPermissionArgs()];
28192
+ if (query.agentCommand) {
28193
+ return [...prefix, "--agent", query.agentCommand, ...tail2];
28194
+ }
28195
+ return [...prefix, query.agent, ...tail2];
28196
+ }
27369
28197
  buildPromptArgs(session, text, promptFile) {
27370
28198
  const prefix = [
27371
28199
  "--format",
@@ -27431,6 +28259,7 @@ var init_acpx_cli_transport = __esm(() => {
27431
28259
  init_node_pty_helper();
27432
28260
  init_terminate_process_tree();
27433
28261
  init_acpx_queue_owner_launcher();
28262
+ init_agent_session_list();
27434
28263
  require4 = createRequire5(import.meta.url);
27435
28264
  });
27436
28265
 
@@ -27644,6 +28473,9 @@ class MessageChannelRegistry {
27644
28473
  }
27645
28474
  await channel.sendScheduledMessage(input);
27646
28475
  }
28476
+ nativeSessionListFormat(chatKey) {
28477
+ return this.getByChatKey(chatKey)?.nativeSessionListFormat ?? "table";
28478
+ }
27647
28479
  createConsumerLocks() {
27648
28480
  const result = [];
27649
28481
  for (const channel of this.channels.values()) {
@@ -28228,7 +29060,7 @@ async function buildApp(paths, deps = {}) {
28228
29060
  listScheduledTasksFromRoute: async (input) => await listScheduledTasksFromRoute(input, { state, scheduled: scheduledService }),
28229
29061
  cancelScheduledTaskFromRoute: async (input) => await cancelScheduledTaskFromRoute(input, { state, scheduled: scheduledService })
28230
29062
  });
28231
- const router = new CommandRouter(sessions, transport, config2, configStore, logger2, undefined, orchestration, quota, scheduledService, deps.channel?.supportsScheduledMessages ? { supportsScheduledMessages: deps.channel.supportsScheduledMessages.bind(deps.channel) } : undefined);
29063
+ const router = new CommandRouter(sessions, transport, config2, configStore, logger2, undefined, orchestration, quota, scheduledService, deps.channel?.supportsScheduledMessages ? { supportsScheduledMessages: deps.channel.supportsScheduledMessages.bind(deps.channel) } : undefined, deps.channel?.nativeSessionListFormat ? deps.channel.nativeSessionListFormat.bind(deps.channel) : undefined);
28232
29064
  const agent = new ConsoleAgent(router, logger2);
28233
29065
  const scheduledScheduler = new ScheduledTaskScheduler(scheduledService, {
28234
29066
  dispatchTask: buildScheduledDispatchTask({
@@ -28819,107 +29651,107 @@ async function checkRuntime(options = {}) {
28819
29651
  }
28820
29652
  function createRuntimeFsProbe() {
28821
29653
  return {
28822
- stat: async (path15) => await stat3(path15),
28823
- access: async (path15, mode) => await access4(path15, mode)
29654
+ stat: async (path16) => await stat3(path16),
29655
+ access: async (path16, mode) => await access4(path16, mode)
28824
29656
  };
28825
29657
  }
28826
- async function checkDirectoryCreatable(label, path15, probe, platform) {
29658
+ async function checkDirectoryCreatable(label, path16, probe, platform) {
28827
29659
  try {
28828
- const stats = await probe.stat(path15);
29660
+ const stats = await probe.stat(path16);
28829
29661
  if (!stats.isDirectory()) {
28830
29662
  return {
28831
29663
  ok: false,
28832
- detail: `${label}: ${path15} (exists but is not a directory)`
29664
+ detail: `${label}: ${path16} (exists but is not a directory)`
28833
29665
  };
28834
29666
  }
28835
- await probe.access(path15, directoryAccessMode(platform));
29667
+ await probe.access(path16, directoryAccessMode(platform));
28836
29668
  return {
28837
29669
  ok: true,
28838
- detail: `${label}: ${path15} (writable)`
29670
+ detail: `${label}: ${path16} (writable)`
28839
29671
  };
28840
29672
  } catch (error2) {
28841
29673
  if (!isMissingPathError(error2)) {
28842
29674
  return {
28843
29675
  ok: false,
28844
- detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
29676
+ detail: `${label}: ${path16} (unusable: ${formatError6(error2)})`
28845
29677
  };
28846
29678
  }
28847
- const parentCheck = await checkCreatableAncestorDirectory(path15, probe, platform);
29679
+ const parentCheck = await checkCreatableAncestorDirectory(path16, probe, platform);
28848
29680
  if (!parentCheck.ok) {
28849
29681
  return {
28850
29682
  ok: false,
28851
- detail: `${label}: ${path15} (parent not writable: ${parentCheck.blockingPath})`
29683
+ detail: `${label}: ${path16} (parent not writable: ${parentCheck.blockingPath})`
28852
29684
  };
28853
29685
  }
28854
29686
  return {
28855
29687
  ok: true,
28856
- detail: `${label}: ${path15} (creatable via ${parentCheck.creatableFrom})`
29688
+ detail: `${label}: ${path16} (creatable via ${parentCheck.creatableFrom})`
28857
29689
  };
28858
29690
  }
28859
29691
  }
28860
- async function checkFileCreatable(label, path15, probe, platform) {
29692
+ async function checkFileCreatable(label, path16, probe, platform) {
28861
29693
  try {
28862
- const stats = await probe.stat(path15);
29694
+ const stats = await probe.stat(path16);
28863
29695
  if (stats.isDirectory()) {
28864
29696
  return {
28865
29697
  ok: false,
28866
- detail: `${label}: ${path15} (exists but is a directory)`
29698
+ detail: `${label}: ${path16} (exists but is a directory)`
28867
29699
  };
28868
29700
  }
28869
- await probe.access(path15, constants.W_OK);
29701
+ await probe.access(path16, constants.W_OK);
28870
29702
  return {
28871
29703
  ok: true,
28872
- detail: `${label}: ${path15} (writable)`
29704
+ detail: `${label}: ${path16} (writable)`
28873
29705
  };
28874
29706
  } catch (error2) {
28875
29707
  if (!isMissingPathError(error2)) {
28876
29708
  return {
28877
29709
  ok: false,
28878
- detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
29710
+ detail: `${label}: ${path16} (unusable: ${formatError6(error2)})`
28879
29711
  };
28880
29712
  }
28881
- const parentCheck = await checkCreatableAncestorDirectory(dirname14(path15), probe, platform);
29713
+ const parentCheck = await checkCreatableAncestorDirectory(dirname14(path16), probe, platform);
28882
29714
  if (!parentCheck.ok) {
28883
29715
  return {
28884
29716
  ok: false,
28885
- detail: `${label}: ${path15} (parent not writable: ${parentCheck.blockingPath})`
29717
+ detail: `${label}: ${path16} (parent not writable: ${parentCheck.blockingPath})`
28886
29718
  };
28887
29719
  }
28888
29720
  return {
28889
29721
  ok: true,
28890
- detail: `${label}: ${path15} (creatable via ${parentCheck.creatableFrom})`
29722
+ detail: `${label}: ${path16} (creatable via ${parentCheck.creatableFrom})`
28891
29723
  };
28892
29724
  }
28893
29725
  }
28894
- async function checkCreatableAncestorDirectory(path15, probe, platform) {
29726
+ async function checkCreatableAncestorDirectory(path16, probe, platform) {
28895
29727
  try {
28896
- const stats = await probe.stat(path15);
29728
+ const stats = await probe.stat(path16);
28897
29729
  if (!stats.isDirectory()) {
28898
29730
  return {
28899
29731
  ok: false,
28900
- creatableFrom: path15,
28901
- blockingPath: path15
29732
+ creatableFrom: path16,
29733
+ blockingPath: path16
28902
29734
  };
28903
29735
  }
28904
- await probe.access(path15, directoryAccessMode(platform));
29736
+ await probe.access(path16, directoryAccessMode(platform));
28905
29737
  return {
28906
29738
  ok: true,
28907
- creatableFrom: path15
29739
+ creatableFrom: path16
28908
29740
  };
28909
29741
  } catch (error2) {
28910
29742
  if (!isMissingPathError(error2)) {
28911
29743
  return {
28912
29744
  ok: false,
28913
- creatableFrom: path15,
28914
- blockingPath: path15
29745
+ creatableFrom: path16,
29746
+ blockingPath: path16
28915
29747
  };
28916
29748
  }
28917
- const parent = dirname14(path15);
28918
- if (parent === path15) {
29749
+ const parent = dirname14(path16);
29750
+ if (parent === path16) {
28919
29751
  return {
28920
29752
  ok: false,
28921
- creatableFrom: path15,
28922
- blockingPath: path15
29753
+ creatableFrom: path16,
29754
+ blockingPath: path16
28923
29755
  };
28924
29756
  }
28925
29757
  const parentCheck = await checkCreatableAncestorDirectory(parent, probe, platform);
@@ -42176,11 +43008,11 @@ function buildWeacpxMcpToolRegistry(input) {
42176
43008
  if (internalSessionTools && !isExternalCoordinator && !sourceHandle) {
42177
43009
  tools.push({
42178
43010
  name: "scheduled_create",
42179
- description: "Create a one-shot scheduled task for the current conversation session using the recorded chat route. Provide only the time expression and message; routing/session/account details are resolved by weacpx.",
43011
+ description: "Schedule a one-shot task to run a natural-language message at a future time, using the recorded chat route. By default — and like /later — the task runs in a FRESH TEMPORARY session (it snapshots the current agent and workspace but starts with brand-new history and is destroyed after running, so it does not pollute this conversation); the reply is still pushed back to this chat. Provide only timeText and message and OMIT mode to get this default. Routing, session, and account are resolved by weacpx.",
42180
43012
  inputSchema: exports_external.object({
42181
43013
  timeText: exports_external.string().min(1).describe("Time expression, e.g. 'in 2h', '30分钟后', 'tomorrow 09:00', or '周五 09:00'."),
42182
- message: exports_external.string().min(1).describe("Natural-language message to send to the current session at the scheduled time."),
42183
- mode: scheduledModeSchema.describe("'temp' creates a temporary one-shot session; 'bound' sends to the current bound session.").optional()
43014
+ message: exports_external.string().min(1).describe("Natural-language message to run at the scheduled time."),
43015
+ mode: scheduledModeSchema.describe("Optional; leave UNSET for the default temporary session (recommended). Set 'bound' ONLY when the user explicitly asks for the task to run inside this conversation's current session and share its context. 'temp' forces the temporary session.").optional()
42184
43016
  }).strict(),
42185
43017
  handler: async (args) => await asToolResult(async () => {
42186
43018
  const input2 = args;
@@ -42655,7 +43487,7 @@ function createWeacpxMcpServer(options) {
42655
43487
  const taskOptionsById = new Map;
42656
43488
  const watchTasksById = new Map;
42657
43489
  const server = new Server({
42658
- name: "weacpx-orchestration",
43490
+ name: "weacpx",
42659
43491
  version: readVersion()
42660
43492
  }, {
42661
43493
  capabilities: {