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/README.md CHANGED
@@ -25,7 +25,7 @@
25
25
 
26
26
  `weacpx` 适合轻量临时使用多 Agent 办公的用户。你可以用微信、飞书或元宝盯任务、发指令、看结果,并在同一个聊天里管理多个会话。
27
27
 
28
- > `weacpx` 的会话是跟本地隔离的,它目前还不能使用 CLI 已有的会话,你在 weacpx 也无法看到本地的 CLI 会话记录。
28
+ > 日常使用优先记 `/ss`:它负责创建或复用 weacpx 逻辑会话。如果你想接入本地 Codex Agent 已有的原生会话,再用 `/ssn`;进阶说明见 [docs/native-sessions.md](./docs/native-sessions.md)。
29
29
 
30
30
  ## 5 分钟快速开始
31
31
 
@@ -305,6 +305,7 @@ opencode, qoder, qwen, trae
305
305
  | `/sessions` / `/session` / `/ss` | 查看会话列表 |
306
306
  | `/ss <agent> (-d <path> \| --ws <name>)` | 创建或复用当前最常用的会话 |
307
307
  | `/ss new <agent> (-d <path> \| --ws <name>)` | 强制新建会话 |
308
+ | `/ssn <agent> (-d <path> \| --ws <name>)` | 接入本地已有的 Agent 原生会话,详见 [native sessions](./docs/native-sessions.md) |
308
309
  | `/use <alias>` | 切换当前会话 |
309
310
  | `/status` | 查看当前会话状态 |
310
311
  | `/mode` / `/mode <id>` | 查看或设置底层 `acpx` mode |
@@ -325,6 +326,8 @@ opencode, qoder, qwen, trae
325
326
  /cancel
326
327
  ```
327
328
 
329
+ 如果要接入本地 Codex 等 Agent 已有的原生会话,用 `/ssn codex -d /absolute/path/to/repo`;完整语义见 [docs/native-sessions.md](./docs/native-sessions.md)。
330
+
328
331
  ### 定时任务(/later)
329
332
 
330
333
  让 agent 在未来某个时间自动收到一条消息。**默认在一个为该任务新建的临时会话里执行**(沿用创建时当前会话的 agent 与工作区,对话历史全新,跑完即销毁);加 `--bind` 则发送到创建时绑定的当前会话。到点后把消息作为普通 prompt 投递,结果推回原聊天。
@@ -449,9 +452,9 @@ Windows 上如果 MCP host 不会帮你解析带参数的 `command`,把 `node.
449
452
  ```json
450
453
  {
451
454
  "type": "stdio",
452
- "command": "D:\\Users\\you\\.nvmd\\versions\\22.19.0\\node.exe",
455
+ "command": "C:\\Program Files\\nodejs\\node.exe",
453
456
  "args": [
454
- "E:\\projects\\weacpx\\dist\\cli.js",
457
+ "C:\\path\\to\\weacpx\\dist\\cli.js",
455
458
  "mcp-stdio"
456
459
  ]
457
460
  }
@@ -478,6 +481,15 @@ Windows 上如果 MCP host 不会帮你解析带参数的 `command`,把 `node.
478
481
  /use frontend:codex
479
482
  ```
480
483
 
484
+ ### 接入本地已有 Codex 原生会话
485
+
486
+ ```text
487
+ /ssn codex -d /absolute/path/to/backend
488
+ /ssn 1
489
+ ```
490
+
491
+ 更多筛选、别名和故障排查见 [docs/native-sessions.md](./docs/native-sessions.md)。
492
+
481
493
  ## 配置与运行文件
482
494
 
483
495
  默认文件位置:
@@ -1032,6 +1032,76 @@ function permissionModeToFlag(permissionMode) {
1032
1032
  }
1033
1033
  }
1034
1034
 
1035
+ // src/transport/agent-session-list.ts
1036
+ import path2 from "node:path";
1037
+ function isUnknownFilterCwdOption(output) {
1038
+ return /(?:unknown|unrecognized) option/i.test(output) && output.includes("--filter-cwd");
1039
+ }
1040
+ async function runAgentSessionList(options) {
1041
+ let result = await options.runList(true);
1042
+ let filterLocally = false;
1043
+ if (result.code !== 0 && options.filterCwd && isUnknownFilterCwdOption(result.stdout + result.stderr)) {
1044
+ result = await options.runList(false);
1045
+ filterLocally = true;
1046
+ }
1047
+ if (result.code !== 0) {
1048
+ if ((result.stdout + result.stderr).includes("sessionCapabilities.list")) {
1049
+ return;
1050
+ }
1051
+ throw new Error(options.formatError(result));
1052
+ }
1053
+ return parseAgentSessionListOutput(result.stdout, filterLocally ? options.filterCwd : undefined);
1054
+ }
1055
+ function parseAgentSessionListOutput(stdout, filterCwd) {
1056
+ let parsed;
1057
+ try {
1058
+ parsed = JSON.parse(stdout);
1059
+ } catch {
1060
+ throw new Error("failed to parse acpx sessions list output");
1061
+ }
1062
+ if (!isAgentSessionListResult(parsed)) {
1063
+ return;
1064
+ }
1065
+ return filterCwd ? filterAgentSessionListByCwd(parsed, filterCwd) : parsed;
1066
+ }
1067
+ function isAgentSessionListResult(value) {
1068
+ if (!value || typeof value !== "object" || Array.isArray(value))
1069
+ return false;
1070
+ const record = value;
1071
+ if (record.source !== "agent" || !Array.isArray(record.sessions))
1072
+ return false;
1073
+ return record.sessions.every((session) => {
1074
+ if (!session || typeof session !== "object" || Array.isArray(session))
1075
+ return false;
1076
+ const item = session;
1077
+ return typeof item.sessionId === "string";
1078
+ });
1079
+ }
1080
+ function filterAgentSessionListByCwd(result, cwd) {
1081
+ return {
1082
+ ...result,
1083
+ sessions: result.sessions.filter((session) => session.cwd && sameAgentSessionCwd(session.cwd, cwd))
1084
+ };
1085
+ }
1086
+ function sameAgentSessionCwd(left, right) {
1087
+ const normalizedLeft = normalizeAgentSessionCwd(left);
1088
+ const normalizedRight = normalizeAgentSessionCwd(right);
1089
+ if (isWindowsLikePath(normalizedLeft) || isWindowsLikePath(normalizedRight)) {
1090
+ return normalizedLeft.toLowerCase() === normalizedRight.toLowerCase();
1091
+ }
1092
+ return normalizedLeft === normalizedRight;
1093
+ }
1094
+ function normalizeAgentSessionCwd(input) {
1095
+ if (isWindowsLikePath(input)) {
1096
+ return path2.win32.normalize(input).replace(/\\/g, "/");
1097
+ }
1098
+ return path2.posix.normalize(input.replace(/\\/g, "/"));
1099
+ }
1100
+ function isWindowsLikePath(input) {
1101
+ return /^[a-zA-Z]:[\\/]/.test(input) || input.startsWith("\\\\");
1102
+ }
1103
+ var init_agent_session_list = () => {};
1104
+
1035
1105
  // src/bridge/bridge-main.ts
1036
1106
  import { createInterface } from "node:readline";
1037
1107
 
@@ -1111,6 +1181,7 @@ function parseMissingOptionalDep(text) {
1111
1181
  // src/bridge/bridge-runtime.ts
1112
1182
  init_discover_parent_package_paths();
1113
1183
  init_acpx_queue_owner_launcher();
1184
+ init_agent_session_list();
1114
1185
  class EnsureSessionFailedError extends Error {
1115
1186
  kind;
1116
1187
  data;
@@ -1149,6 +1220,36 @@ class BridgeRuntime {
1149
1220
  this.options.permissionPolicy = policy.permissionPolicy;
1150
1221
  return {};
1151
1222
  }
1223
+ async listAgentSessions(input) {
1224
+ return await runAgentSessionList({
1225
+ filterCwd: input.filterCwd,
1226
+ runList: async (includeFilterCwd) => {
1227
+ const spec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
1228
+ "sessions",
1229
+ "list",
1230
+ ...includeFilterCwd && input.filterCwd ? ["--filter-cwd", input.filterCwd] : [],
1231
+ ...input.cursor ? ["--cursor", input.cursor] : []
1232
+ ], { format: "json" }));
1233
+ return await this.run(spec.command, spec.args);
1234
+ },
1235
+ formatError: (result) => result.stderr || result.stdout || `sessions list failed with exit code ${result.code}`
1236
+ });
1237
+ }
1238
+ async resumeAgentSession(input) {
1239
+ const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
1240
+ "sessions",
1241
+ "new",
1242
+ "--name",
1243
+ input.name,
1244
+ "--resume-session",
1245
+ input.agentSessionId
1246
+ ], { format: "quiet" }));
1247
+ const result = await this.runSessionCreate(spawnSpec.command, spawnSpec.args, input.cwd);
1248
+ if (result.code !== 0) {
1249
+ throw new Error(result.stderr || result.stdout || "sessions resume failed");
1250
+ }
1251
+ return {};
1252
+ }
1152
1253
  async hasSession(input) {
1153
1254
  const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
1154
1255
  "sessions",
@@ -1369,7 +1470,7 @@ class BridgeRuntime {
1369
1470
  buildSessionArgs(input, tail, options = {}) {
1370
1471
  const prefix = [
1371
1472
  "--format",
1372
- "quiet",
1473
+ options.format ?? "quiet",
1373
1474
  "--cwd",
1374
1475
  input.cwd,
1375
1476
  ...this.buildPermissionArgs()
@@ -1582,6 +1683,8 @@ var BRIDGE_METHODS = new Set([
1582
1683
  "hasSession",
1583
1684
  "ensureSession",
1584
1685
  "tailSessionHistory",
1686
+ "listAgentSessions",
1687
+ "resumeAgentSession",
1585
1688
  "prompt",
1586
1689
  "setMode",
1587
1690
  "cancel",
@@ -1591,6 +1694,7 @@ var SESSION_SCOPED_METHODS = new Set([
1591
1694
  "hasSession",
1592
1695
  "ensureSession",
1593
1696
  "tailSessionHistory",
1697
+ "resumeAgentSession",
1594
1698
  "prompt",
1595
1699
  "setMode",
1596
1700
  "cancel",
@@ -1673,6 +1777,14 @@ class BridgeServer {
1673
1777
  name: requireString(params, "name"),
1674
1778
  lines: requirePositiveInt(params, "lines")
1675
1779
  });
1780
+ case "listAgentSessions":
1781
+ return await this.runtime.listAgentSessions({
1782
+ agent: requireString(params, "agent"),
1783
+ agentCommand: asOptionalString(params.agentCommand),
1784
+ cwd: requireString(params, "cwd"),
1785
+ cursor: asOptionalString(params.cursor),
1786
+ filterCwd: asOptionalString(params.filterCwd)
1787
+ });
1676
1788
  case "ensureSession":
1677
1789
  return await this.runtime.ensureSession({
1678
1790
  agent: requireString(params, "agent"),
@@ -1732,6 +1844,14 @@ class BridgeServer {
1732
1844
  }));
1733
1845
  }
1734
1846
  });
1847
+ case "resumeAgentSession":
1848
+ return await this.runtime.resumeAgentSession({
1849
+ agent: requireString(params, "agent"),
1850
+ agentCommand: asOptionalString(params.agentCommand),
1851
+ cwd: requireString(params, "cwd"),
1852
+ name: requireString(params, "name"),
1853
+ agentSessionId: requireString(params, "agentSessionId")
1854
+ });
1735
1855
  case "setMode":
1736
1856
  return await this.runtime.setMode({
1737
1857
  agent: requireString(params, "agent"),
@@ -6,4 +6,12 @@ export declare function toInternalSessionAlias(channelId: string, displayAlias:
6
6
  export declare function toDisplaySessionAlias(internalAlias: string): string;
7
7
  export declare function isSessionAliasVisibleInChannel(alias: string, channelId: string): boolean;
8
8
  export declare function resolveSessionAliasForInput(channelId: string, displayAlias: string, existingAliases: Iterable<string>): string;
9
+ /**
10
+ * Internal alias for a display alias entered in `channelId`. The default
11
+ * channel (weixin) stays unprefixed for backwards compatibility; every other
12
+ * channel is namespaced as `channelId:alias`. Idempotent — an already-scoped
13
+ * alias is not double-prefixed. This is the single home for the rule that
14
+ * handlers must not re-implement inline.
15
+ */
16
+ export declare function scopeDisplayAliasToInternal(channelId: string, displayAlias: string): string;
9
17
  export declare function buildDefaultTransportSession(channelId: string, displayAlias: string): string;
@@ -83,6 +83,12 @@ export interface MessageChannelRuntime {
83
83
  notifyTaskProgress(task: OrchestrationTaskRecord, text: string): Promise<void>;
84
84
  sendCoordinatorMessage(input: CoordinatorMessageInput): Promise<void>;
85
85
  sendScheduledMessage?(input: ScheduledChannelMessageInput): Promise<void>;
86
+ /**
87
+ * Preferred render format for `/ssn` native session lists. weixin renders
88
+ * markdown tables poorly and declares "cards"; channels that omit this are
89
+ * treated as "table".
90
+ */
91
+ nativeSessionListFormat?: "cards" | "table";
86
92
  }
87
93
  export type ToolUseStatus = "running" | "success" | "error";
88
94
  export type ToolUseKind = "read" | "search" | "execute" | "edit" | "think" | "other";
@@ -3,6 +3,7 @@ import type { RuntimeMediaStore } from "./media-store.js";
3
3
  import type { OrchestrationTaskRecord } from "../orchestration/orchestration-types.js";
4
4
  export declare class WeixinChannel implements MessageChannelRuntime {
5
5
  readonly id = "weixin";
6
+ readonly nativeSessionListFormat: "cards" | "table";
6
7
  private agent;
7
8
  private quota;
8
9
  private logger;