weacpx 0.5.2 → 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) {
@@ -16110,6 +16146,7 @@ var init_consumer_lock = __esm(() => {
16110
16146
  // src/channels/weixin-channel.ts
16111
16147
  class WeixinChannel {
16112
16148
  id = "weixin";
16149
+ nativeSessionListFormat = "cards";
16113
16150
  agent = null;
16114
16151
  quota = null;
16115
16152
  logger = null;
@@ -16917,6 +16954,7 @@ var init_command_list = __esm(() => {
16917
16954
  "/pm",
16918
16955
  "/session",
16919
16956
  "/ss",
16957
+ "/ssn",
16920
16958
  "/workspace",
16921
16959
  "/ws",
16922
16960
  "/use",
@@ -16999,6 +17037,51 @@ function parseCommand(input) {
16999
17037
  if (command === "/session" && parts[1] === "rm" && parts[2] && parts.length === 3) {
17000
17038
  return { kind: "session.rm", alias: parts[2] };
17001
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
+ }
17002
17085
  if (command === "/group" && parts[1] === "new" && parts.length > 2) {
17003
17086
  const title = parts.slice(2).join(" ");
17004
17087
  if (title.trim().length > 0) {
@@ -17264,6 +17347,97 @@ function readSessionShortcutTarget(parts, startIndex) {
17264
17347
  }
17265
17348
  return null;
17266
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
+ }
17267
17441
  function normalizeCommand(command) {
17268
17442
  if (command === "/ss")
17269
17443
  return "/session";
@@ -17501,6 +17675,9 @@ var init_command_policy = __esm(() => {
17501
17675
  "session.shortcut": "/session",
17502
17676
  "session.shortcut.new": "/session",
17503
17677
  "session.attach": "/session attach",
17678
+ "session.native.list": "/ssn",
17679
+ "session.native.select": "/ssn",
17680
+ "session.native.attach": "/ssn attach",
17504
17681
  "later.create": "/later",
17505
17682
  "later.list": "/later list",
17506
17683
  "later.cancel": "/later cancel"
@@ -18161,7 +18338,7 @@ async function handleSessions(context, chatKey) {
18161
18338
  }
18162
18339
  async function handleSessionNew(context, chatKey, alias, agent, workspace) {
18163
18340
  const channelId = getChannelIdFromChatKey(chatKey);
18164
- const internalAlias = channelId === "weixin" ? alias : toInternalSessionAlias(channelId, alias);
18341
+ const internalAlias = scopeDisplayAliasToInternal(channelId, alias);
18165
18342
  const session = context.lifecycle.resolveSession(internalAlias, agent, workspace, `${workspace}:${internalAlias}`);
18166
18343
  const releaseTransportReservation = await context.lifecycle.reserveTransportSession(session.transportSession);
18167
18344
  try {
@@ -18192,7 +18369,7 @@ async function handleSessionShortcut(context, chatKey, agent, target, createNew)
18192
18369
  }
18193
18370
  async function handleSessionAttach(context, chatKey, alias, agent, workspace, transportSession) {
18194
18371
  const channelId = getChannelIdFromChatKey(chatKey);
18195
- const internalAlias = channelId === "weixin" ? alias : toInternalSessionAlias(channelId, alias);
18372
+ const internalAlias = scopeDisplayAliasToInternal(channelId, alias);
18196
18373
  const attached = context.lifecycle.resolveSession(internalAlias, agent, workspace, transportSession);
18197
18374
  const releaseTransportReservation = await context.lifecycle.reserveTransportSession(attached.transportSession);
18198
18375
  try {
@@ -18559,7 +18736,7 @@ async function markCoordinatorResultsInjectionFailed(context, taskIds, groupIds,
18559
18736
  });
18560
18737
  }
18561
18738
  }
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;
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;
18563
18740
  var init_session_handler = __esm(() => {
18564
18741
  init_build_coordinator_prompt();
18565
18742
  init_channel_scope();
@@ -18567,7 +18744,7 @@ var init_session_handler = __esm(() => {
18567
18744
  sessionHelp = {
18568
18745
  topic: "session",
18569
18746
  aliases: ["ss", "sessions"],
18570
- summary: "创建、恢复、切换和重置逻辑会话。",
18747
+ summary: "创建、复用、切换和重置 weacpx 逻辑会话。",
18571
18748
  commands: [
18572
18749
  { usage: "/sessions", description: "查看当前会话列表" },
18573
18750
  { usage: "/session 或 /ss", description: "查看会话列表" },
@@ -18575,12 +18752,48 @@ var init_session_handler = __esm(() => {
18575
18752
  { usage: "/ss new <agent> (-d <path> | --ws <name>)", description: "强制新建会话" },
18576
18753
  { usage: "/ss new <alias> -a <name> --ws <name>", description: "按指定配置新建会话" },
18577
18754
  { usage: "/ss attach <alias> -a <name> --ws <name> --name <transport-session>", description: "绑定已有会话" },
18755
+ { usage: "/ssn 或 /help ssn", description: "接入本地 native 会话(Codex 等 Agent 原生会话)" },
18578
18756
  { usage: "/session tail [N]", description: "补拉当前会话的历史输出(默认 50 行)" },
18579
18757
  { usage: "/session rm <alias>", description: "删除逻辑会话" },
18580
18758
  { usage: "/use <alias>", description: "切换当前会话" },
18581
18759
  { usage: "/session reset 或 /clear", description: "重置当前会话上下文" }
18582
18760
  ],
18583
- 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
+ ]
18584
18797
  };
18585
18798
  modeHelp = {
18586
18799
  topic: "mode",
@@ -19543,6 +19756,7 @@ var init_help_registry = __esm(() => {
19543
19756
  init_later_handler();
19544
19757
  HELP_TOPICS = [
19545
19758
  sessionHelp,
19759
+ nativeSessionHelp,
19546
19760
  workspaceHelp,
19547
19761
  agentHelp,
19548
19762
  permissionHelp,
@@ -19579,6 +19793,7 @@ function renderHelpIndex() {
19579
19793
  return [
19580
19794
  "常用入口:",
19581
19795
  "- /ss <agent> (-d <path> | --ws <name>) - 快速新建或切到会话",
19796
+ "- /ssn <agent> (-d <path> | --ws <name>) - 接入本地 Agent 原生会话",
19582
19797
  "- /use <alias> - 切换当前会话",
19583
19798
  "- /status - 查看当前会话状态",
19584
19799
  "",
@@ -19587,7 +19802,7 @@ function renderHelpIndex() {
19587
19802
  "",
19588
19803
  "查看专题说明:",
19589
19804
  "- /help <topic>",
19590
- "- 例如:/help ss、/help ws、/help pm"
19805
+ "- 例如:/help ss、/help ssn、/help ws、/help pm"
19591
19806
  ].join(`
19592
19807
  `);
19593
19808
  }
@@ -19669,7 +19884,7 @@ async function handleSessionShortcutCommand(context, ops, chatKey, agent, target
19669
19884
  });
19670
19885
  const baseAlias = `${workspace.name}:${agent}`;
19671
19886
  const channelId = getChannelIdFromChatKey(chatKey);
19672
- const scopedBase = channelId === "weixin" ? baseAlias : `${channelId}:${baseAlias}`;
19887
+ const scopedBase = scopeDisplayAliasToInternal(channelId, baseAlias);
19673
19888
  const alias = createNew ? await allocateUniqueSessionAlias(context, scopedBase, chatKey) : scopedBase;
19674
19889
  const display = toDisplaySessionAlias(alias);
19675
19890
  if (!createNew && await hasLogicalSession(context, alias, chatKey)) {
@@ -19798,6 +20013,404 @@ var init_session_shortcut_handler = __esm(() => {
19798
20013
  init_channel_scope();
19799
20014
  });
19800
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
+
19801
20414
  // src/commands/handlers/session-recovery-handler.ts
19802
20415
  function renderTransportError(session, error2) {
19803
20416
  const message = error2 instanceof Error ? error2.message : String(error2);
@@ -20265,6 +20878,7 @@ class CommandRouter {
20265
20878
  quota;
20266
20879
  scheduled;
20267
20880
  scheduledDelivery;
20881
+ resolveNativeSessionListFormat;
20268
20882
  logger;
20269
20883
  autoInstall = autoInstallOptionalDep;
20270
20884
  discoverPaths = discoverParentPackagePaths;
@@ -20274,7 +20888,7 @@ class CommandRouter {
20274
20888
  __setDiscoverPathsForTest(fn) {
20275
20889
  this.discoverPaths = fn;
20276
20890
  }
20277
- 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) {
20278
20892
  this.sessions = sessions;
20279
20893
  this.transport = transport;
20280
20894
  this.config = config2;
@@ -20284,6 +20898,7 @@ class CommandRouter {
20284
20898
  this.quota = quota;
20285
20899
  this.scheduled = scheduled;
20286
20900
  this.scheduledDelivery = scheduledDelivery;
20901
+ this.resolveNativeSessionListFormat = resolveNativeSessionListFormat;
20287
20902
  this.logger = logger2 ?? createNoopAppLogger();
20288
20903
  }
20289
20904
  async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, onThought, perfSpan) {
@@ -20358,6 +20973,12 @@ class CommandRouter {
20358
20973
  return await handleSessionShortcut(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.agent, command, true);
20359
20974
  case "session.attach":
20360
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);
20361
20982
  case "session.use":
20362
20983
  return await handleSessionUse(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.alias);
20363
20984
  case "mode.show":
@@ -20459,7 +21080,8 @@ class CommandRouter {
20459
21080
  configStore: this.configStore,
20460
21081
  logger: this.logger,
20461
21082
  replaceConfig: (updated) => this.replaceConfig(updated),
20462
- ...this.quota ? { quota: this.quota } : {}
21083
+ ...this.quota ? { quota: this.quota } : {},
21084
+ ...this.resolveNativeSessionListFormat ? { resolveNativeSessionListFormat: this.resolveNativeSessionListFormat } : {}
20463
21085
  };
20464
21086
  }
20465
21087
  createSessionHandlerContext(reply, perfSpan) {
@@ -20821,6 +21443,7 @@ var init_command_router = __esm(() => {
20821
21443
  init_agent_handler();
20822
21444
  init_workspace_handler();
20823
21445
  init_session_shortcut_handler();
21446
+ init_native_session_handler();
20824
21447
  init_later_handler();
20825
21448
  init_session_recovery_handler();
20826
21449
  init_auto_install_optional_dep();
@@ -25144,11 +25767,13 @@ class SessionService {
25144
25767
  stateStore;
25145
25768
  state;
25146
25769
  stateMutex;
25770
+ now;
25147
25771
  constructor(config2, stateStore, state, options = {}) {
25148
25772
  this.config = config2;
25149
25773
  this.stateStore = stateStore;
25150
25774
  this.state = state;
25151
25775
  this.stateMutex = options.stateMutex ?? new AsyncMutex;
25776
+ this.now = options.now ?? (() => Date.now());
25152
25777
  }
25153
25778
  async createSession(alias, agent, workspace) {
25154
25779
  return await this.createLogicalSession(alias, agent, workspace, `${workspace}:${alias}`);
@@ -25182,6 +25807,14 @@ class SessionService {
25182
25807
  async attachSession(alias, agent, workspace, transportSession, transportAgentCommand) {
25183
25808
  return await this.createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand);
25184
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
+ }
25185
25818
  async getSession(alias) {
25186
25819
  const session = this.state.sessions[alias];
25187
25820
  if (!session) {
@@ -25196,6 +25829,22 @@ class SessionService {
25196
25829
  const preferred = matches.find((session) => session.alias === expectedAlias && session.workspace === expectedWorkspace) ?? matches[0];
25197
25830
  return preferred ? this.toResolvedSession(preferred) : null;
25198
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
+ }
25199
25848
  async useSession(chatKey, alias) {
25200
25849
  await this.mutate(async () => {
25201
25850
  const channelId = getChannelIdFromChatKey(chatKey);
@@ -25315,6 +25964,49 @@ class SessionService {
25315
25964
  return { wasActive };
25316
25965
  });
25317
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
+ }
25318
26010
  toResolvedSession(session) {
25319
26011
  const agentConfig = this.config.agents[session.agent];
25320
26012
  if (!agentConfig) {
@@ -25330,6 +26022,11 @@ class SessionService {
25330
26022
  agentCommand: session.transport_agent_command ?? resolveAgentCommand(agentConfig.driver, agentConfig.command),
25331
26023
  workspace: session.workspace,
25332
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,
25333
26030
  modeId: session.mode_id,
25334
26031
  replyMode: session.reply_mode,
25335
26032
  cwd: workspaceConfig.cwd
@@ -25357,20 +26054,25 @@ class SessionService {
25357
26054
  async persist() {
25358
26055
  await this.stateStore.save(this.state);
25359
26056
  }
25360
- async createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand) {
26057
+ async createLogicalSession(alias, agent, workspace, transportSession, transportAgentCommand, native) {
25361
26058
  return await this.mutate(async () => {
25362
26059
  this.validateSession(alias, agent, workspace);
25363
26060
  if (this.state.orchestration.externalCoordinators[transportSession]) {
25364
26061
  throw new Error(`transport session "${transportSession}" conflicts with an external coordinator`);
25365
26062
  }
25366
26063
  const existingSession = this.state.sessions[alias];
25367
- const now = new Date().toISOString();
26064
+ const now = new Date(this.now()).toISOString();
25368
26065
  const normalizedTransportAgentCommand = transportAgentCommand?.trim();
25369
26066
  const session = {
25370
26067
  alias,
25371
26068
  agent,
25372
26069
  workspace,
25373
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,
25374
26076
  ...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : existingSession?.transport_agent_command ? { transport_agent_command: existingSession.transport_agent_command } : {},
25375
26077
  mode_id: existingSession?.mode_id,
25376
26078
  reply_mode: existingSession?.reply_mode,
@@ -26167,6 +26869,18 @@ class AcpxBridgeTransport {
26167
26869
  lines
26168
26870
  });
26169
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
+ }
26170
26884
  async prompt(session, text, reply, replyContext, options) {
26171
26885
  const sink = reply ? createQuotaGatedReplySink({
26172
26886
  reply,
@@ -26962,6 +27676,76 @@ function permissionModeToFlag(permissionMode) {
26962
27676
  }
26963
27677
  }
26964
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
+
26965
27749
  // src/transport/acpx-cli/acpx-cli-transport.ts
26966
27750
  import { createRequire as createRequire5 } from "node:module";
26967
27751
  import { spawn as spawn9 } from "node:child_process";
@@ -27071,6 +27855,23 @@ class AcpxCliTransport {
27071
27855
  timeoutMs: this.sessionInitTimeoutMs
27072
27856
  });
27073
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
+ }
27074
27875
  async tailSessionHistory(session, lines) {
27075
27876
  const candidates = [
27076
27877
  ["sessions", "history", "quiet", "-s", session.transportSession, String(lines)],
@@ -27139,6 +27940,20 @@ ${baseText}` : "" };
27139
27940
  message: output.trim()
27140
27941
  };
27141
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
+ }
27142
27957
  async updatePermissionPolicy(policy) {
27143
27958
  this.permissionMode = policy.permissionMode;
27144
27959
  this.nonInteractivePermissions = policy.nonInteractivePermissions;
@@ -27372,6 +28187,13 @@ ${baseText}` : "" };
27372
28187
  }
27373
28188
  return [...prefix, session.agent, ...tail2];
27374
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
+ }
27375
28197
  buildPromptArgs(session, text, promptFile) {
27376
28198
  const prefix = [
27377
28199
  "--format",
@@ -27437,6 +28259,7 @@ var init_acpx_cli_transport = __esm(() => {
27437
28259
  init_node_pty_helper();
27438
28260
  init_terminate_process_tree();
27439
28261
  init_acpx_queue_owner_launcher();
28262
+ init_agent_session_list();
27440
28263
  require4 = createRequire5(import.meta.url);
27441
28264
  });
27442
28265
 
@@ -27650,6 +28473,9 @@ class MessageChannelRegistry {
27650
28473
  }
27651
28474
  await channel.sendScheduledMessage(input);
27652
28475
  }
28476
+ nativeSessionListFormat(chatKey) {
28477
+ return this.getByChatKey(chatKey)?.nativeSessionListFormat ?? "table";
28478
+ }
27653
28479
  createConsumerLocks() {
27654
28480
  const result = [];
27655
28481
  for (const channel of this.channels.values()) {
@@ -28234,7 +29060,7 @@ async function buildApp(paths, deps = {}) {
28234
29060
  listScheduledTasksFromRoute: async (input) => await listScheduledTasksFromRoute(input, { state, scheduled: scheduledService }),
28235
29061
  cancelScheduledTaskFromRoute: async (input) => await cancelScheduledTaskFromRoute(input, { state, scheduled: scheduledService })
28236
29062
  });
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);
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);
28238
29064
  const agent = new ConsoleAgent(router, logger2);
28239
29065
  const scheduledScheduler = new ScheduledTaskScheduler(scheduledService, {
28240
29066
  dispatchTask: buildScheduledDispatchTask({
@@ -28825,107 +29651,107 @@ async function checkRuntime(options = {}) {
28825
29651
  }
28826
29652
  function createRuntimeFsProbe() {
28827
29653
  return {
28828
- stat: async (path15) => await stat3(path15),
28829
- access: async (path15, mode) => await access4(path15, mode)
29654
+ stat: async (path16) => await stat3(path16),
29655
+ access: async (path16, mode) => await access4(path16, mode)
28830
29656
  };
28831
29657
  }
28832
- async function checkDirectoryCreatable(label, path15, probe, platform) {
29658
+ async function checkDirectoryCreatable(label, path16, probe, platform) {
28833
29659
  try {
28834
- const stats = await probe.stat(path15);
29660
+ const stats = await probe.stat(path16);
28835
29661
  if (!stats.isDirectory()) {
28836
29662
  return {
28837
29663
  ok: false,
28838
- detail: `${label}: ${path15} (exists but is not a directory)`
29664
+ detail: `${label}: ${path16} (exists but is not a directory)`
28839
29665
  };
28840
29666
  }
28841
- await probe.access(path15, directoryAccessMode(platform));
29667
+ await probe.access(path16, directoryAccessMode(platform));
28842
29668
  return {
28843
29669
  ok: true,
28844
- detail: `${label}: ${path15} (writable)`
29670
+ detail: `${label}: ${path16} (writable)`
28845
29671
  };
28846
29672
  } catch (error2) {
28847
29673
  if (!isMissingPathError(error2)) {
28848
29674
  return {
28849
29675
  ok: false,
28850
- detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
29676
+ detail: `${label}: ${path16} (unusable: ${formatError6(error2)})`
28851
29677
  };
28852
29678
  }
28853
- const parentCheck = await checkCreatableAncestorDirectory(path15, probe, platform);
29679
+ const parentCheck = await checkCreatableAncestorDirectory(path16, probe, platform);
28854
29680
  if (!parentCheck.ok) {
28855
29681
  return {
28856
29682
  ok: false,
28857
- detail: `${label}: ${path15} (parent not writable: ${parentCheck.blockingPath})`
29683
+ detail: `${label}: ${path16} (parent not writable: ${parentCheck.blockingPath})`
28858
29684
  };
28859
29685
  }
28860
29686
  return {
28861
29687
  ok: true,
28862
- detail: `${label}: ${path15} (creatable via ${parentCheck.creatableFrom})`
29688
+ detail: `${label}: ${path16} (creatable via ${parentCheck.creatableFrom})`
28863
29689
  };
28864
29690
  }
28865
29691
  }
28866
- async function checkFileCreatable(label, path15, probe, platform) {
29692
+ async function checkFileCreatable(label, path16, probe, platform) {
28867
29693
  try {
28868
- const stats = await probe.stat(path15);
29694
+ const stats = await probe.stat(path16);
28869
29695
  if (stats.isDirectory()) {
28870
29696
  return {
28871
29697
  ok: false,
28872
- detail: `${label}: ${path15} (exists but is a directory)`
29698
+ detail: `${label}: ${path16} (exists but is a directory)`
28873
29699
  };
28874
29700
  }
28875
- await probe.access(path15, constants.W_OK);
29701
+ await probe.access(path16, constants.W_OK);
28876
29702
  return {
28877
29703
  ok: true,
28878
- detail: `${label}: ${path15} (writable)`
29704
+ detail: `${label}: ${path16} (writable)`
28879
29705
  };
28880
29706
  } catch (error2) {
28881
29707
  if (!isMissingPathError(error2)) {
28882
29708
  return {
28883
29709
  ok: false,
28884
- detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
29710
+ detail: `${label}: ${path16} (unusable: ${formatError6(error2)})`
28885
29711
  };
28886
29712
  }
28887
- const parentCheck = await checkCreatableAncestorDirectory(dirname14(path15), probe, platform);
29713
+ const parentCheck = await checkCreatableAncestorDirectory(dirname14(path16), probe, platform);
28888
29714
  if (!parentCheck.ok) {
28889
29715
  return {
28890
29716
  ok: false,
28891
- detail: `${label}: ${path15} (parent not writable: ${parentCheck.blockingPath})`
29717
+ detail: `${label}: ${path16} (parent not writable: ${parentCheck.blockingPath})`
28892
29718
  };
28893
29719
  }
28894
29720
  return {
28895
29721
  ok: true,
28896
- detail: `${label}: ${path15} (creatable via ${parentCheck.creatableFrom})`
29722
+ detail: `${label}: ${path16} (creatable via ${parentCheck.creatableFrom})`
28897
29723
  };
28898
29724
  }
28899
29725
  }
28900
- async function checkCreatableAncestorDirectory(path15, probe, platform) {
29726
+ async function checkCreatableAncestorDirectory(path16, probe, platform) {
28901
29727
  try {
28902
- const stats = await probe.stat(path15);
29728
+ const stats = await probe.stat(path16);
28903
29729
  if (!stats.isDirectory()) {
28904
29730
  return {
28905
29731
  ok: false,
28906
- creatableFrom: path15,
28907
- blockingPath: path15
29732
+ creatableFrom: path16,
29733
+ blockingPath: path16
28908
29734
  };
28909
29735
  }
28910
- await probe.access(path15, directoryAccessMode(platform));
29736
+ await probe.access(path16, directoryAccessMode(platform));
28911
29737
  return {
28912
29738
  ok: true,
28913
- creatableFrom: path15
29739
+ creatableFrom: path16
28914
29740
  };
28915
29741
  } catch (error2) {
28916
29742
  if (!isMissingPathError(error2)) {
28917
29743
  return {
28918
29744
  ok: false,
28919
- creatableFrom: path15,
28920
- blockingPath: path15
29745
+ creatableFrom: path16,
29746
+ blockingPath: path16
28921
29747
  };
28922
29748
  }
28923
- const parent = dirname14(path15);
28924
- if (parent === path15) {
29749
+ const parent = dirname14(path16);
29750
+ if (parent === path16) {
28925
29751
  return {
28926
29752
  ok: false,
28927
- creatableFrom: path15,
28928
- blockingPath: path15
29753
+ creatableFrom: path16,
29754
+ blockingPath: path16
28929
29755
  };
28930
29756
  }
28931
29757
  const parentCheck = await checkCreatableAncestorDirectory(parent, probe, platform);