@ganglion/xacpx 0.15.0 → 0.15.2

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
@@ -1,5 +1,7 @@
1
1
  # xacpx
2
2
 
3
+ ![xacpx.png](xacpx_banner.png)
4
+
3
5
  > Remotely drive Codex, Claude Code, and other acpx sessions from WeChat, Feishu, or Yuanbao.
4
6
 
5
7
  [![npm](https://img.shields.io/npm/v/@ganglion/xacpx?style=flat-square)](https://www.npmjs.com/package/@ganglion/xacpx)
@@ -13,8 +15,6 @@ English · **[中文](./docs/zh/README_zh.md)**
13
15
 
14
16
  `xacpx` is a tool that lets you control ACP agents such as Codex / Claude Code / Gemini / OpenCode directly from WeChat, Feishu, or Yuanbao. It connects chat messages to your agent CLI sessions through `acpx`, so you can, right from your phone:
15
17
 
16
- [![xacpx.png](https://s41.ax1x.com/2026/06/05/pmZXIv6.png)](https://imgchr.com/i/pmZXIv6)
17
-
18
18
  - Create and switch between sessions
19
19
  - Have the agent keep working in a specific project directory
20
20
  - View streaming replies, final results, and tool-call summaries
@@ -3470,6 +3470,143 @@ var init_path = __esm(() => {
3470
3470
  ROOT_PATH_RE = /^(\/|[a-zA-Z]:\/?)$/;
3471
3471
  });
3472
3472
 
3473
+ // src/transport/codex-subagent-filter.ts
3474
+ import { closeSync, openSync, readdirSync, readSync, statSync } from "node:fs";
3475
+ import { homedir as homedir4 } from "node:os";
3476
+ import { join as join3 } from "node:path";
3477
+ function resolveCodexHome(env = process.env) {
3478
+ const fromEnv = env.CODEX_HOME?.trim();
3479
+ return fromEnv ? fromEnv : join3(homedir4(), ".codex");
3480
+ }
3481
+ function isPlainObject(value) {
3482
+ return !!value && typeof value === "object" && !Array.isArray(value);
3483
+ }
3484
+ function isSubagentSource(source) {
3485
+ if (!isPlainObject(source) || !Object.hasOwn(source, "subagent"))
3486
+ return false;
3487
+ const subagent = source.subagent;
3488
+ if (!isPlainObject(subagent))
3489
+ return false;
3490
+ return Object.values(subagent).some((variant) => isPlainObject(variant) && typeof variant.parent_thread_id === "string");
3491
+ }
3492
+ function sessionMetaLineIsSubagent(line) {
3493
+ if (!line)
3494
+ return false;
3495
+ let parsed;
3496
+ try {
3497
+ parsed = JSON.parse(line);
3498
+ } catch {
3499
+ return false;
3500
+ }
3501
+ const payload = parsed?.payload;
3502
+ return isSubagentSource(payload?.source);
3503
+ }
3504
+ function createSubagentPredicate(reader) {
3505
+ let index = null;
3506
+ const cache = new Map;
3507
+ return (sessionId) => {
3508
+ const cached = cache.get(sessionId);
3509
+ if (cached !== undefined)
3510
+ return cached;
3511
+ let result = false;
3512
+ try {
3513
+ if (!index) {
3514
+ index = new Map;
3515
+ for (const path4 of reader.listRolloutPaths()) {
3516
+ const id = path4.match(ROLLOUT_RE)?.[1];
3517
+ if (id)
3518
+ index.set(id.toLowerCase(), path4);
3519
+ }
3520
+ }
3521
+ const path3 = index.get(sessionId.toLowerCase());
3522
+ if (path3)
3523
+ result = sessionMetaLineIsSubagent(reader.readFirstLine(path3));
3524
+ } catch {
3525
+ result = false;
3526
+ }
3527
+ cache.set(sessionId, result);
3528
+ return result;
3529
+ };
3530
+ }
3531
+ function filterSubagentSessions(result, isSubagent) {
3532
+ return {
3533
+ ...result,
3534
+ sessions: result.sessions.filter((session3) => {
3535
+ try {
3536
+ return !isSubagent(session3.sessionId);
3537
+ } catch {
3538
+ return true;
3539
+ }
3540
+ })
3541
+ };
3542
+ }
3543
+ function nodeRolloutReader(home) {
3544
+ const root = join3(home, "sessions");
3545
+ return {
3546
+ listRolloutPaths() {
3547
+ const out = [];
3548
+ const walk = (dir) => {
3549
+ let entries;
3550
+ try {
3551
+ entries = readdirSync(dir, { withFileTypes: true });
3552
+ } catch {
3553
+ return;
3554
+ }
3555
+ for (const entry of entries) {
3556
+ const full = join3(dir, entry.name);
3557
+ if (entry.isDirectory())
3558
+ walk(full);
3559
+ else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl"))
3560
+ out.push(full);
3561
+ }
3562
+ };
3563
+ walk(root);
3564
+ return out;
3565
+ },
3566
+ readFirstLine(path3) {
3567
+ let fd;
3568
+ try {
3569
+ statSync(path3);
3570
+ fd = openSync(path3, "r");
3571
+ const buf = Buffer.alloc(READ_CHUNK);
3572
+ let acc = "";
3573
+ let total = 0;
3574
+ for (;; ) {
3575
+ const bytes = readSync(fd, buf, 0, READ_CHUNK, total);
3576
+ if (bytes <= 0)
3577
+ break;
3578
+ acc += buf.toString("utf8", 0, bytes);
3579
+ total += bytes;
3580
+ const nl = acc.indexOf(`
3581
+ `);
3582
+ if (nl !== -1)
3583
+ return acc.slice(0, nl);
3584
+ if (total >= FIRST_LINE_READ_CAP)
3585
+ return;
3586
+ }
3587
+ return acc.length ? acc : undefined;
3588
+ } catch {
3589
+ return;
3590
+ } finally {
3591
+ if (fd !== undefined) {
3592
+ try {
3593
+ closeSync(fd);
3594
+ } catch {}
3595
+ }
3596
+ }
3597
+ }
3598
+ };
3599
+ }
3600
+ function codexSubagentPredicate(env) {
3601
+ return createSubagentPredicate(nodeRolloutReader(resolveCodexHome(env)));
3602
+ }
3603
+ var CODEX_AGENT_NAME = "codex", FIRST_LINE_READ_CAP, READ_CHUNK, ROLLOUT_RE;
3604
+ var init_codex_subagent_filter = __esm(() => {
3605
+ FIRST_LINE_READ_CAP = 1024 * 1024;
3606
+ READ_CHUNK = 64 * 1024;
3607
+ ROLLOUT_RE = /rollout-.*-([0-9a-fA-F-]{36})\.jsonl$/;
3608
+ });
3609
+
3473
3610
  // src/transport/agent-session-list.ts
3474
3611
  function isUnknownFilterCwdOption(output) {
3475
3612
  return /(?:unknown|unrecognized) option/i.test(output) && output.includes("--filter-cwd");
@@ -3487,7 +3624,11 @@ async function runAgentSessionList(options) {
3487
3624
  }
3488
3625
  throw new Error(options.formatError(result));
3489
3626
  }
3490
- return parseAgentSessionListOutput(result.stdout, filterLocally ? options.filterCwd : undefined);
3627
+ const parsed = parseAgentSessionListOutput(result.stdout, filterLocally ? options.filterCwd : undefined);
3628
+ if (parsed && options.isSubagentSession) {
3629
+ return filterSubagentSessions(parsed, options.isSubagentSession);
3630
+ }
3631
+ return parsed;
3491
3632
  }
3492
3633
  function parseAgentSessionListOutput(stdout, filterCwd) {
3493
3634
  let parsed;
@@ -3522,16 +3663,17 @@ function filterAgentSessionListByCwd(result, cwd) {
3522
3663
  }
3523
3664
  var init_agent_session_list = __esm(() => {
3524
3665
  init_path();
3666
+ init_codex_subagent_filter();
3525
3667
  });
3526
3668
 
3527
3669
  // src/transport/acpx-session-files.ts
3528
3670
  import { readdir, unlink as unlink2 } from "node:fs/promises";
3529
- import { homedir as homedir4 } from "node:os";
3530
- import { join as join3 } from "node:path";
3671
+ import { homedir as homedir5 } from "node:os";
3672
+ import { join as join4 } from "node:path";
3531
3673
  async function deleteAcpxSessionFiles(options) {
3532
- const dir = options.sessionsDir ?? join3(homedir4(), ".acpx", "sessions");
3674
+ const dir = options.sessionsDir ?? join4(homedir5(), ".acpx", "sessions");
3533
3675
  const safeId = encodeURIComponent(options.acpxRecordId);
3534
- await unlink2(join3(dir, `${safeId}.json`)).catch(() => {
3676
+ await unlink2(join4(dir, `${safeId}.json`)).catch(() => {
3535
3677
  return;
3536
3678
  });
3537
3679
  let entries;
@@ -3542,7 +3684,7 @@ async function deleteAcpxSessionFiles(options) {
3542
3684
  }
3543
3685
  const streamFiles = entries.filter((name) => name.startsWith(`${safeId}.stream.`));
3544
3686
  for (const name of streamFiles) {
3545
- await unlink2(join3(dir, name)).catch(() => {
3687
+ await unlink2(join4(dir, name)).catch(() => {
3546
3688
  return;
3547
3689
  });
3548
3690
  }
@@ -3622,8 +3764,8 @@ init_prompt_output();
3622
3764
  init_prompt_media();
3623
3765
  init_streaming_prompt();
3624
3766
  import { copyFile, readdir as readdir2 } from "node:fs/promises";
3625
- import { homedir as homedir5 } from "node:os";
3626
- import { dirname as dirname2, join as join4, win32 } from "node:path";
3767
+ import { homedir as homedir6 } from "node:os";
3768
+ import { dirname as dirname2, join as join5, win32 } from "node:path";
3627
3769
  import { spawn as spawn4 } from "node:child_process";
3628
3770
 
3629
3771
  // src/bridge/parse-missing-optional-dep.ts
@@ -3643,6 +3785,7 @@ function parseMissingOptionalDep(text) {
3643
3785
  init_discover_parent_package_paths();
3644
3786
  init_acpx_queue_owner_launcher();
3645
3787
  init_agent_session_list();
3788
+ init_codex_subagent_filter();
3646
3789
  init_acpx_session_files();
3647
3790
  class EnsureSessionFailedError extends Error {
3648
3791
  kind;
@@ -3708,7 +3851,8 @@ class BridgeRuntime {
3708
3851
  ], { format: "json" }));
3709
3852
  return await this.run(spec.command, spec.args);
3710
3853
  },
3711
- formatError: (result) => result.stderr || result.stdout || `sessions list failed with exit code ${result.code}`
3854
+ formatError: (result) => result.stderr || result.stdout || `sessions list failed with exit code ${result.code}`,
3855
+ isSubagentSession: (input.driver ?? input.agent) === CODEX_AGENT_NAME ? codexSubagentPredicate() : undefined
3712
3856
  });
3713
3857
  }
3714
3858
  async resumeAgentSession(input) {
@@ -4218,11 +4362,11 @@ async function tryRepairAcpxSessionIndex(deps = {}) {
4218
4362
  if (platform !== "win32") {
4219
4363
  return false;
4220
4364
  }
4221
- const home = deps.home ?? process.env.HOME ?? process.env.USERPROFILE ?? homedir5();
4365
+ const home = deps.home ?? process.env.HOME ?? process.env.USERPROFILE ?? homedir6();
4222
4366
  if (!home) {
4223
4367
  return false;
4224
4368
  }
4225
- const pathJoin = platform === "win32" ? win32.join : join4;
4369
+ const pathJoin = platform === "win32" ? win32.join : join5;
4226
4370
  const sessionsDir = pathJoin(home, ".acpx", "sessions");
4227
4371
  const indexPath = pathJoin(sessionsDir, "index.json");
4228
4372
  const readdirFn = deps.readdirFn ?? readdir2;
@@ -4372,6 +4516,7 @@ class BridgeServer {
4372
4516
  return await this.runtime.listAgentSessions({
4373
4517
  agent: requireString(params, "agent"),
4374
4518
  agentCommand: asOptionalString(params.agentCommand),
4519
+ driver: asOptionalString(params.driver),
4375
4520
  cwd: requireString(params, "cwd"),
4376
4521
  cursor: asOptionalString(params.cursor),
4377
4522
  filterCwd: asOptionalString(params.filterCwd)
package/dist/cli.js CHANGED
@@ -23806,6 +23806,15 @@ var init_session_shortcut_handler = __esm(() => {
23806
23806
  });
23807
23807
 
23808
23808
  // src/commands/handlers/native-session-handler.ts
23809
+ async function listFirstNonEmptyPage(listAgentSessions, query) {
23810
+ let result = await listAgentSessions(query);
23811
+ let guard = 0;
23812
+ while (result && result.sessions.length === 0 && result.nextCursor && guard < MAX_EMPTY_PAGE_ADVANCE) {
23813
+ guard++;
23814
+ result = await listAgentSessions({ ...query, cursor: result.nextCursor });
23815
+ }
23816
+ return result;
23817
+ }
23809
23818
  async function handleNativeSessionList(context, chatKey, input) {
23810
23819
  const target = await resolveNativeTarget(context, chatKey, input);
23811
23820
  if (isRouterResponse(target)) {
@@ -23818,13 +23827,14 @@ async function handleNativeSessionList(context, chatKey, input) {
23818
23827
  const query = {
23819
23828
  agent: target.agent,
23820
23829
  agentCommand: target.agentCommand,
23830
+ ...target.driver ? { driver: target.driver } : {},
23821
23831
  cwd: target.cwd,
23822
23832
  ...input.cursor ? { cursor: input.cursor } : {},
23823
23833
  ...input.all ? {} : { filterCwd: target.cwd }
23824
23834
  };
23825
23835
  let result;
23826
23836
  try {
23827
- result = await listAgentSessions(query);
23837
+ result = await listFirstNonEmptyPage(listAgentSessions, query);
23828
23838
  } catch (error2) {
23829
23839
  return { text: renderNativeListError(target, error2) };
23830
23840
  }
@@ -23950,6 +23960,7 @@ async function resolveNativeTarget(context, chatKey, input) {
23950
23960
  agent: agent3,
23951
23961
  agentDisplayName: displayAgentName(agent3),
23952
23962
  agentCommand: resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, context.config?.transport.preferLocalAgents !== false),
23963
+ driver: agentConfig.driver,
23953
23964
  workspace: workspaceResolution.workspace,
23954
23965
  workspaceLabel: workspaceResolution.workspaceLabel,
23955
23966
  cwd: workspaceResolution.cwd,
@@ -24185,7 +24196,7 @@ function displayAgentName(agent3) {
24185
24196
  }
24186
24197
  return agent3.charAt(0).toUpperCase() + agent3.slice(1);
24187
24198
  }
24188
- var NATIVE_SESSION_CACHE_TTL_MS;
24199
+ var NATIVE_SESSION_CACHE_TTL_MS, MAX_EMPTY_PAGE_ADVANCE = 25;
24189
24200
  var init_native_session_handler = __esm(() => {
24190
24201
  init_resolve_agent_command();
24191
24202
  init_channel_scope();
@@ -25054,6 +25065,7 @@ class CommandRouter {
25054
25065
  const result = await listAgentSessions({
25055
25066
  agent: agent3,
25056
25067
  ...agentCommand ? { agentCommand } : {},
25068
+ ...agentConfig.driver ? { driver: agentConfig.driver } : {},
25057
25069
  cwd: workspaceConfig.cwd,
25058
25070
  filterCwd: workspaceConfig.cwd
25059
25071
  });
@@ -32150,6 +32162,143 @@ function permissionModeToFlag(permissionMode) {
32150
32162
  }
32151
32163
  }
32152
32164
 
32165
+ // src/transport/codex-subagent-filter.ts
32166
+ import { closeSync, openSync, readdirSync, readSync, statSync as statSync2 } from "node:fs";
32167
+ import { homedir as homedir9 } from "node:os";
32168
+ import { join as join19 } from "node:path";
32169
+ function resolveCodexHome(env = process.env) {
32170
+ const fromEnv = env.CODEX_HOME?.trim();
32171
+ return fromEnv ? fromEnv : join19(homedir9(), ".codex");
32172
+ }
32173
+ function isPlainObject3(value) {
32174
+ return !!value && typeof value === "object" && !Array.isArray(value);
32175
+ }
32176
+ function isSubagentSource(source) {
32177
+ if (!isPlainObject3(source) || !Object.hasOwn(source, "subagent"))
32178
+ return false;
32179
+ const subagent = source.subagent;
32180
+ if (!isPlainObject3(subagent))
32181
+ return false;
32182
+ return Object.values(subagent).some((variant) => isPlainObject3(variant) && typeof variant.parent_thread_id === "string");
32183
+ }
32184
+ function sessionMetaLineIsSubagent(line) {
32185
+ if (!line)
32186
+ return false;
32187
+ let parsed;
32188
+ try {
32189
+ parsed = JSON.parse(line);
32190
+ } catch {
32191
+ return false;
32192
+ }
32193
+ const payload = parsed?.payload;
32194
+ return isSubagentSource(payload?.source);
32195
+ }
32196
+ function createSubagentPredicate(reader) {
32197
+ let index = null;
32198
+ const cache = new Map;
32199
+ return (sessionId) => {
32200
+ const cached2 = cache.get(sessionId);
32201
+ if (cached2 !== undefined)
32202
+ return cached2;
32203
+ let result = false;
32204
+ try {
32205
+ if (!index) {
32206
+ index = new Map;
32207
+ for (const path16 of reader.listRolloutPaths()) {
32208
+ const id = path16.match(ROLLOUT_RE)?.[1];
32209
+ if (id)
32210
+ index.set(id.toLowerCase(), path16);
32211
+ }
32212
+ }
32213
+ const path15 = index.get(sessionId.toLowerCase());
32214
+ if (path15)
32215
+ result = sessionMetaLineIsSubagent(reader.readFirstLine(path15));
32216
+ } catch {
32217
+ result = false;
32218
+ }
32219
+ cache.set(sessionId, result);
32220
+ return result;
32221
+ };
32222
+ }
32223
+ function filterSubagentSessions(result, isSubagent) {
32224
+ return {
32225
+ ...result,
32226
+ sessions: result.sessions.filter((session3) => {
32227
+ try {
32228
+ return !isSubagent(session3.sessionId);
32229
+ } catch {
32230
+ return true;
32231
+ }
32232
+ })
32233
+ };
32234
+ }
32235
+ function nodeRolloutReader(home) {
32236
+ const root = join19(home, "sessions");
32237
+ return {
32238
+ listRolloutPaths() {
32239
+ const out = [];
32240
+ const walk = (dir) => {
32241
+ let entries;
32242
+ try {
32243
+ entries = readdirSync(dir, { withFileTypes: true });
32244
+ } catch {
32245
+ return;
32246
+ }
32247
+ for (const entry of entries) {
32248
+ const full = join19(dir, entry.name);
32249
+ if (entry.isDirectory())
32250
+ walk(full);
32251
+ else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl"))
32252
+ out.push(full);
32253
+ }
32254
+ };
32255
+ walk(root);
32256
+ return out;
32257
+ },
32258
+ readFirstLine(path15) {
32259
+ let fd;
32260
+ try {
32261
+ statSync2(path15);
32262
+ fd = openSync(path15, "r");
32263
+ const buf = Buffer.alloc(READ_CHUNK);
32264
+ let acc = "";
32265
+ let total = 0;
32266
+ for (;; ) {
32267
+ const bytes = readSync(fd, buf, 0, READ_CHUNK, total);
32268
+ if (bytes <= 0)
32269
+ break;
32270
+ acc += buf.toString("utf8", 0, bytes);
32271
+ total += bytes;
32272
+ const nl = acc.indexOf(`
32273
+ `);
32274
+ if (nl !== -1)
32275
+ return acc.slice(0, nl);
32276
+ if (total >= FIRST_LINE_READ_CAP)
32277
+ return;
32278
+ }
32279
+ return acc.length ? acc : undefined;
32280
+ } catch {
32281
+ return;
32282
+ } finally {
32283
+ if (fd !== undefined) {
32284
+ try {
32285
+ closeSync(fd);
32286
+ } catch {}
32287
+ }
32288
+ }
32289
+ }
32290
+ };
32291
+ }
32292
+ function codexSubagentPredicate(env) {
32293
+ return createSubagentPredicate(nodeRolloutReader(resolveCodexHome(env)));
32294
+ }
32295
+ var CODEX_AGENT_NAME = "codex", FIRST_LINE_READ_CAP, READ_CHUNK, ROLLOUT_RE;
32296
+ var init_codex_subagent_filter = __esm(() => {
32297
+ FIRST_LINE_READ_CAP = 1024 * 1024;
32298
+ READ_CHUNK = 64 * 1024;
32299
+ ROLLOUT_RE = /rollout-.*-([0-9a-fA-F-]{36})\.jsonl$/;
32300
+ });
32301
+
32153
32302
  // src/transport/agent-session-list.ts
32154
32303
  function isUnknownFilterCwdOption(output) {
32155
32304
  return /(?:unknown|unrecognized) option/i.test(output) && output.includes("--filter-cwd");
@@ -32167,7 +32316,11 @@ async function runAgentSessionList(options) {
32167
32316
  }
32168
32317
  throw new Error(options.formatError(result));
32169
32318
  }
32170
- return parseAgentSessionListOutput(result.stdout, filterLocally ? options.filterCwd : undefined);
32319
+ const parsed = parseAgentSessionListOutput(result.stdout, filterLocally ? options.filterCwd : undefined);
32320
+ if (parsed && options.isSubagentSession) {
32321
+ return filterSubagentSessions(parsed, options.isSubagentSession);
32322
+ }
32323
+ return parsed;
32171
32324
  }
32172
32325
  function parseAgentSessionListOutput(stdout2, filterCwd) {
32173
32326
  let parsed;
@@ -32202,16 +32355,17 @@ function filterAgentSessionListByCwd(result, cwd) {
32202
32355
  }
32203
32356
  var init_agent_session_list = __esm(() => {
32204
32357
  init_path();
32358
+ init_codex_subagent_filter();
32205
32359
  });
32206
32360
 
32207
32361
  // src/transport/acpx-session-files.ts
32208
32362
  import { readdir as readdir3, unlink as unlink2 } from "node:fs/promises";
32209
- import { homedir as homedir9 } from "node:os";
32210
- import { join as join19 } from "node:path";
32363
+ import { homedir as homedir10 } from "node:os";
32364
+ import { join as join20 } from "node:path";
32211
32365
  async function deleteAcpxSessionFiles(options) {
32212
- const dir = options.sessionsDir ?? join19(homedir9(), ".acpx", "sessions");
32366
+ const dir = options.sessionsDir ?? join20(homedir10(), ".acpx", "sessions");
32213
32367
  const safeId = encodeURIComponent(options.acpxRecordId);
32214
- await unlink2(join19(dir, `${safeId}.json`)).catch(() => {
32368
+ await unlink2(join20(dir, `${safeId}.json`)).catch(() => {
32215
32369
  return;
32216
32370
  });
32217
32371
  let entries;
@@ -32222,7 +32376,7 @@ async function deleteAcpxSessionFiles(options) {
32222
32376
  }
32223
32377
  const streamFiles = entries.filter((name) => name.startsWith(`${safeId}.stream.`));
32224
32378
  for (const name of streamFiles) {
32225
- await unlink2(join19(dir, name)).catch(() => {
32379
+ await unlink2(join20(dir, name)).catch(() => {
32226
32380
  return;
32227
32381
  });
32228
32382
  }
@@ -32352,7 +32506,8 @@ class AcpxCliTransport {
32352
32506
  timeoutMs: this.sessionInitTimeoutMs
32353
32507
  });
32354
32508
  },
32355
- formatError: (result) => normalizeCommandError(result) ?? `command failed with exit code ${result.code}`
32509
+ formatError: (result) => normalizeCommandError(result) ?? `command failed with exit code ${result.code}`,
32510
+ isSubagentSession: (query.driver ?? query.agent) === CODEX_AGENT_NAME ? codexSubagentPredicate() : undefined
32356
32511
  });
32357
32512
  }
32358
32513
  async tailSessionHistory(session3, lines) {
@@ -32812,6 +32967,7 @@ var init_acpx_cli_transport = __esm(() => {
32812
32967
  init_terminate_process_tree();
32813
32968
  init_acpx_queue_owner_launcher();
32814
32969
  init_agent_session_list();
32970
+ init_codex_subagent_filter();
32815
32971
  init_acpx_session_files();
32816
32972
  require4 = createRequire5(import.meta.url);
32817
32973
  });
@@ -33284,8 +33440,8 @@ function createControlEventBus(logger2) {
33284
33440
 
33285
33441
  // src/transport/native-session-history.ts
33286
33442
  import { readFile as readFile14 } from "node:fs/promises";
33287
- import { homedir as homedir10 } from "node:os";
33288
- import { join as join20 } from "node:path";
33443
+ import { homedir as homedir11 } from "node:os";
33444
+ import { join as join21 } from "node:path";
33289
33445
  function classifyToolKind(name) {
33290
33446
  const n = name.toLowerCase();
33291
33447
  if (/(^|[^a-z])(read|cat|view|open)([^a-z]|$)/.test(n))
@@ -33397,8 +33553,8 @@ function mapAcpxMessagesToHistory(raw) {
33397
33553
  }
33398
33554
  async function readNativeSessionHistory(opts) {
33399
33555
  try {
33400
- const dir = opts.sessionsDir ?? join20(opts.homeDir ?? homedir10(), ".acpx", "sessions");
33401
- const indexRaw = await readFile14(join20(dir, "index.json"), "utf8").catch(() => null);
33556
+ const dir = opts.sessionsDir ?? join21(opts.homeDir ?? homedir11(), ".acpx", "sessions");
33557
+ const indexRaw = await readFile14(join21(dir, "index.json"), "utf8").catch(() => null);
33402
33558
  if (!indexRaw)
33403
33559
  return [];
33404
33560
  const index = JSON.parse(indexRaw);
@@ -33407,7 +33563,7 @@ async function readNativeSessionHistory(opts) {
33407
33563
  for (const entry of candidates) {
33408
33564
  if (!entry.file)
33409
33565
  continue;
33410
- const recRaw = await readFile14(join20(dir, entry.file), "utf8").catch(() => null);
33566
+ const recRaw = await readFile14(join21(dir, entry.file), "utf8").catch(() => null);
33411
33567
  if (!recRaw)
33412
33568
  continue;
33413
33569
  const record3 = JSON.parse(recRaw);
@@ -33425,14 +33581,14 @@ var init_native_session_history = () => {};
33425
33581
  // src/control/workspace-fs.ts
33426
33582
  import { execFile } from "node:child_process";
33427
33583
  import { promisify } from "node:util";
33428
- import { homedir as homedir11 } from "node:os";
33584
+ import { homedir as homedir12 } from "node:os";
33429
33585
  import { readdir as readdir4, realpath, stat as stat3, open as open5 } from "node:fs/promises";
33430
33586
  import { isAbsolute as isAbsolute3, relative, resolve as resolve3, sep } from "node:path";
33431
33587
  function expandHome2(p) {
33432
33588
  if (p === "~")
33433
- return homedir11();
33589
+ return homedir12();
33434
33590
  if (p.startsWith("~/") || p.startsWith("~" + sep))
33435
- return resolve3(homedir11(), p.slice(2));
33591
+ return resolve3(homedir12(), p.slice(2));
33436
33592
  return p;
33437
33593
  }
33438
33594
 
@@ -33561,16 +33717,16 @@ class WorkspaceFs {
33561
33717
  }
33562
33718
  const files = [];
33563
33719
  try {
33564
- const { stdout: stdout2 } = await execFileAsync("git", ["-C", root, "status", "--porcelain"], { maxBuffer: GIT_MAX_BUFFER });
33565
- for (const line of stdout2.split(`
33566
- `)) {
33567
- if (!line)
33720
+ const { stdout: stdout2 } = await execFileAsync("git", ["-C", root, "-c", "core.quotePath=false", "status", "--porcelain", "-z"], { maxBuffer: GIT_MAX_BUFFER });
33721
+ const fields = stdout2.split("\x00");
33722
+ for (let i = 0;i < fields.length; i++) {
33723
+ const field = fields[i];
33724
+ if (!field)
33568
33725
  continue;
33569
- const status = line.slice(0, 2);
33570
- let path15 = line.slice(3);
33571
- const arrow = path15.indexOf(" -> ");
33572
- if (arrow >= 0)
33573
- path15 = path15.slice(arrow + 4);
33726
+ const status = field.slice(0, 2);
33727
+ const path15 = field.slice(3);
33728
+ if (status[0] === "R" || status[0] === "C")
33729
+ i++;
33574
33730
  files.push({ path: path15, status });
33575
33731
  }
33576
33732
  } catch {}
@@ -33585,6 +33741,15 @@ class WorkspaceFs {
33585
33741
  diff = "";
33586
33742
  }
33587
33743
  }
33744
+ if (rel && !diff) {
33745
+ try {
33746
+ diff = (await execFileAsync("git", ["-C", root, "-c", "core.quotePath=false", "diff", "--no-index", "--", "/dev/null", rel], { maxBuffer: GIT_MAX_BUFFER })).stdout;
33747
+ } catch (e) {
33748
+ const out = e.stdout;
33749
+ if (typeof out === "string")
33750
+ diff = out;
33751
+ }
33752
+ }
33588
33753
  const truncated = diff.length > DIFF_CAP;
33589
33754
  return { workspace: workspace3, files, diff: truncated ? diff.slice(0, DIFF_CAP) : diff, truncated, ...await this.gitContext(root) };
33590
33755
  }
@@ -33827,6 +33992,11 @@ class ControlService {
33827
33992
  resolveSettled = resolve4;
33828
33993
  });
33829
33994
  this.inFlight.set(key, { controller, settled });
33995
+ let wasArchived = false;
33996
+ try {
33997
+ const internalAlias = await this.deps.sessions.resolveAliasForChat(params.chatKey, params.sessionAlias);
33998
+ wasArchived = (await this.deps.sessions.getSession(internalAlias))?.archived === true;
33999
+ } catch {}
33830
34000
  try {
33831
34001
  await this.deps.sessions.useSession(params.chatKey, params.sessionAlias);
33832
34002
  } catch (error2) {
@@ -33834,6 +34004,9 @@ class ControlService {
33834
34004
  resolveSettled();
33835
34005
  return { ok: false, errorMessage: toErrorMessage(error2) };
33836
34006
  }
34007
+ if (wasArchived) {
34008
+ this.deps.events.emit({ type: "sessions-changed" });
34009
+ }
33837
34010
  this.deps.events.emit({
33838
34011
  type: "turn-started",
33839
34012
  chatKey: params.chatKey,
@@ -34028,10 +34201,10 @@ var init_control_service = __esm(() => {
34028
34201
 
34029
34202
  // src/control/upload-store.ts
34030
34203
  import { mkdtemp as mkdtemp2, readdir as readdir5, rm as rm10, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
34031
- import { homedir as homedir12 } from "node:os";
34204
+ import { homedir as homedir13 } from "node:os";
34032
34205
  import path16 from "node:path";
34033
34206
  function defaultRootDir() {
34034
- const home = process.env.HOME ?? homedir12();
34207
+ const home = process.env.HOME ?? homedir13();
34035
34208
  return path16.join(coreHomeDir(home), "runtime", "uploads");
34036
34209
  }
34037
34210
  function sanitizeUploadFilename(raw) {
@@ -34107,7 +34280,7 @@ var init_upload_store = __esm(() => {
34107
34280
 
34108
34281
  // src/config/agent-catalog.ts
34109
34282
  import { existsSync as existsSync3 } from "node:fs";
34110
- import { delimiter as delimiter2, join as join21 } from "node:path";
34283
+ import { delimiter as delimiter2, join as join22 } from "node:path";
34111
34284
  function isBinaryOnPath(binary) {
34112
34285
  const path17 = process.env.PATH ?? "";
34113
34286
  const exts = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
@@ -34116,7 +34289,7 @@ function isBinaryOnPath(binary) {
34116
34289
  continue;
34117
34290
  for (const ext of exts) {
34118
34291
  try {
34119
- if (existsSync3(join21(dir, binary + ext)))
34292
+ if (existsSync3(join22(dir, binary + ext)))
34120
34293
  return true;
34121
34294
  } catch {}
34122
34295
  }
@@ -34207,8 +34380,8 @@ __export(exports_main, {
34207
34380
  buildApp: () => buildApp
34208
34381
  });
34209
34382
  import { randomUUID as randomUUID3 } from "node:crypto";
34210
- import { homedir as homedir13 } from "node:os";
34211
- import { dirname as dirname13, join as join22 } from "node:path";
34383
+ import { homedir as homedir14 } from "node:os";
34384
+ import { dirname as dirname13, join as join23 } from "node:path";
34212
34385
  import { fileURLToPath as fileURLToPath5 } from "node:url";
34213
34386
  function startProgressHeartbeat(orchestration3, config4, logger2, channel) {
34214
34387
  const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
@@ -34899,8 +35072,8 @@ async function main() {
34899
35072
  }
34900
35073
  }
34901
35074
  async function prepareChannelMedia(configPath, config4) {
34902
- const runtimeDir = join22(dirname13(configPath), "runtime");
34903
- const mediaRootDir = join22(runtimeDir, "media");
35075
+ const runtimeDir = join23(dirname13(configPath), "runtime");
35076
+ const mediaRootDir = join23(runtimeDir, "media");
34904
35077
  const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
34905
35078
  await mediaStore.cleanupExpired().catch((error2) => {
34906
35079
  console.error("[xacpx] media cleanup failed:", error2 instanceof Error ? error2.message : String(error2));
@@ -34909,16 +35082,16 @@ async function prepareChannelMedia(configPath, config4) {
34909
35082
  return { mediaStore, channelDeps: { mediaStore, allowedMediaRoots } };
34910
35083
  }
34911
35084
  function resolveRuntimePaths() {
34912
- const home = process.env.HOME ?? homedir13();
35085
+ const home = process.env.HOME ?? homedir14();
34913
35086
  if (!home) {
34914
35087
  throw new Error("Unable to resolve the current user home directory");
34915
35088
  }
34916
- const configPath = coreEnv("CONFIG") ?? join22(coreHomeDir(home), "config.json");
34917
- const runtimeDir = join22(dirname13(configPath), "runtime");
35089
+ const configPath = coreEnv("CONFIG") ?? join23(coreHomeDir(home), "config.json");
35090
+ const runtimeDir = join23(dirname13(configPath), "runtime");
34918
35091
  return {
34919
35092
  configPath,
34920
- statePath: coreEnv("STATE") ?? join22(coreHomeDir(home), "state.json"),
34921
- perfLogPath: join22(runtimeDir, "perf.log"),
35093
+ statePath: coreEnv("STATE") ?? join23(coreHomeDir(home), "state.json"),
35094
+ perfLogPath: join23(runtimeDir, "perf.log"),
34922
35095
  orchestrationSocketPath: coreEnv("ORCHESTRATION_SOCKET") ?? resolveDaemonOrchestrationSocketPath(runtimeDir)
34923
35096
  };
34924
35097
  }
@@ -34930,13 +35103,13 @@ function resolveBridgeEntryPath() {
34930
35103
  }
34931
35104
  function resolveAppLogPath(configPath) {
34932
35105
  const rootDir = dirname13(configPath);
34933
- const runtimeDir = join22(rootDir, "runtime");
34934
- return join22(runtimeDir, "app.log");
35106
+ const runtimeDir = join23(rootDir, "runtime");
35107
+ return join23(runtimeDir, "app.log");
34935
35108
  }
34936
35109
  function resolvePerfLogPath(configPath) {
34937
35110
  const rootDir = dirname13(configPath);
34938
- const runtimeDir = join22(rootDir, "runtime");
34939
- return join22(runtimeDir, "perf.log");
35111
+ const runtimeDir = join23(rootDir, "runtime");
35112
+ return join23(runtimeDir, "perf.log");
34940
35113
  }
34941
35114
  function resolveOrchestrationSocketPathFromConfigPath(configPath) {
34942
35115
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
@@ -35178,10 +35351,10 @@ var init_config_check = __esm(async () => {
35178
35351
  // src/doctor/checks/daemon-check.ts
35179
35352
  import { readdir as readdir6, readFile as readFile15, rm as rm11 } from "node:fs/promises";
35180
35353
  import { fileURLToPath as fileURLToPath6 } from "node:url";
35181
- import { homedir as homedir14 } from "node:os";
35182
- import { join as join23 } from "node:path";
35354
+ import { homedir as homedir15 } from "node:os";
35355
+ import { join as join24 } from "node:path";
35183
35356
  async function checkDaemon(options = {}) {
35184
- const home = options.home ?? process.env.HOME ?? homedir14();
35357
+ const home = options.home ?? process.env.HOME ?? homedir15();
35185
35358
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
35186
35359
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
35187
35360
  home,
@@ -35281,7 +35454,7 @@ async function detectStaleConsumerLockFix(runtimeDir, deps) {
35281
35454
  if (!fileName.endsWith(CONSUMER_LOCK_SUFFIX)) {
35282
35455
  continue;
35283
35456
  }
35284
- const lockPath = join23(runtimeDir, fileName);
35457
+ const lockPath = join24(runtimeDir, fileName);
35285
35458
  const lock2 = await deps.readConsumerLock(lockPath);
35286
35459
  if (lock2 && !deps.isProcessRunning(lock2.pid)) {
35287
35460
  stalePaths.push(lockPath);
@@ -35346,10 +35519,10 @@ var init_daemon_check = __esm(() => {
35346
35519
 
35347
35520
  // src/doctor/checks/logs-check.ts
35348
35521
  import { stat as stat5, readdir as readdir7 } from "node:fs/promises";
35349
- import { basename as basename4, join as join24 } from "node:path";
35350
- import { homedir as homedir15 } from "node:os";
35522
+ import { basename as basename4, join as join25 } from "node:path";
35523
+ import { homedir as homedir16 } from "node:os";
35351
35524
  async function checkLogs(options = {}) {
35352
- const home = options.home ?? process.env.HOME ?? homedir15();
35525
+ const home = options.home ?? process.env.HOME ?? homedir16();
35353
35526
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
35354
35527
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
35355
35528
  home,
@@ -35381,7 +35554,7 @@ async function checkLogs(options = {}) {
35381
35554
  const matched = entries.filter((entry) => isTrackedLogName(entry, tracked));
35382
35555
  const files = [];
35383
35556
  for (const name of matched) {
35384
- const path17 = join24(paths.runtimeDir, name);
35557
+ const path17 = join25(paths.runtimeDir, name);
35385
35558
  try {
35386
35559
  const fileStat = await probe.stat(path17);
35387
35560
  if (fileStat.isDirectory()) {
@@ -35531,9 +35704,9 @@ var init_orchestration_health = __esm(() => {
35531
35704
  });
35532
35705
 
35533
35706
  // src/doctor/checks/orchestration-socket-check.ts
35534
- import { homedir as homedir16 } from "node:os";
35707
+ import { homedir as homedir17 } from "node:os";
35535
35708
  async function checkOrchestrationSocket(options = {}) {
35536
- const home = options.home ?? process.env.HOME ?? homedir16();
35709
+ const home = options.home ?? process.env.HOME ?? homedir17();
35537
35710
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
35538
35711
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
35539
35712
  home,
@@ -35708,9 +35881,9 @@ var init_plugin_check = __esm(async () => {
35708
35881
  import { constants } from "node:fs";
35709
35882
  import { access as access4, stat as stat6 } from "node:fs/promises";
35710
35883
  import { dirname as dirname14 } from "node:path";
35711
- import { homedir as homedir17 } from "node:os";
35884
+ import { homedir as homedir18 } from "node:os";
35712
35885
  async function checkRuntime(options = {}) {
35713
- const home = options.home ?? process.env.HOME ?? homedir17();
35886
+ const home = options.home ?? process.env.HOME ?? homedir18();
35714
35887
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
35715
35888
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
35716
35889
  home,
@@ -36343,10 +36516,10 @@ var init_render_doctor = __esm(() => {
36343
36516
  });
36344
36517
 
36345
36518
  // src/doctor/doctor.ts
36346
- import { homedir as homedir18 } from "node:os";
36347
- import { join as join25 } from "node:path";
36519
+ import { homedir as homedir19 } from "node:os";
36520
+ import { join as join26 } from "node:path";
36348
36521
  async function runDoctor(options = {}, deps = {}) {
36349
- const home = deps.home ?? process.env.HOME ?? homedir18();
36522
+ const home = deps.home ?? process.env.HOME ?? homedir19();
36350
36523
  const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
36351
36524
  const sharedLoadConfig = createSharedLoadConfig(runtimePaths, deps.loadConfig ?? loadConfig);
36352
36525
  const runners = [
@@ -36499,8 +36672,8 @@ function resolveDoctorRuntimePaths(home, resolver) {
36499
36672
  return resolveRuntimePaths();
36500
36673
  }
36501
36674
  return {
36502
- configPath: join25(coreHomeDir(home), "config.json"),
36503
- statePath: join25(coreHomeDir(home), "state.json")
36675
+ configPath: join26(coreHomeDir(home), "config.json"),
36676
+ statePath: join26(coreHomeDir(home), "state.json")
36504
36677
  };
36505
36678
  }
36506
36679
  function depsUseExplicitRuntimeOverrides() {
@@ -36683,8 +36856,8 @@ var init_doctor2 = __esm(async () => {
36683
36856
  // src/cli.ts
36684
36857
  init_core_home();
36685
36858
  import { randomUUID as randomUUID4 } from "node:crypto";
36686
- import { homedir as homedir19 } from "node:os";
36687
- import { dirname as dirname15, join as join26, sep as sep2 } from "node:path";
36859
+ import { homedir as homedir20 } from "node:os";
36860
+ import { dirname as dirname15, join as join27, sep as sep2 } from "node:path";
36688
36861
  import { fileURLToPath as fileURLToPath7 } from "node:url";
36689
36862
 
36690
36863
  // src/runtime/migrate-core-home.ts
@@ -52938,7 +53111,7 @@ async function createCliScheduledTaskService() {
52938
53111
  return new ScheduledTaskService(state, stateStore);
52939
53112
  }
52940
53113
  function resolveConfigPathForCurrentEnv() {
52941
- return coreEnv("CONFIG") ?? join26(coreHomeDir(requireHome2()), "config.json");
53114
+ return coreEnv("CONFIG") ?? join27(coreHomeDir(requireHome2()), "config.json");
52942
53115
  }
52943
53116
  function resolveDaemonPathsForCurrentConfig() {
52944
53117
  const configPath = resolveConfigPathForCurrentEnv();
@@ -53289,7 +53462,7 @@ function decodeFirstRunOnboarding(raw) {
53289
53462
  return null;
53290
53463
  }
53291
53464
  function requireHome2() {
53292
- const home = process.env.HOME ?? homedir19();
53465
+ const home = process.env.HOME ?? homedir20();
53293
53466
  if (!home) {
53294
53467
  throw new Error("Unable to resolve the current user home directory");
53295
53468
  }
@@ -53313,7 +53486,7 @@ function safeDaemonLogPaths() {
53313
53486
  const configPath = resolveConfigPathForCurrentEnv();
53314
53487
  const paths = resolveDaemonPathsForCurrentConfig();
53315
53488
  return {
53316
- appLog: join26(dirname15(configPath), "runtime", "app.log"),
53489
+ appLog: join27(dirname15(configPath), "runtime", "app.log"),
53317
53490
  stderrLog: paths.stderrLog
53318
53491
  };
53319
53492
  } catch {
@@ -102,6 +102,8 @@ export interface AgentSession {
102
102
  export interface AgentSessionListQuery {
103
103
  agent: string;
104
104
  agentCommand?: string;
105
+ /** Resolved acpx driver for `agent` (e.g. a custom `my-codex` agent has driver `codex`). Used to gate driver-specific list filtering. */
106
+ driver?: string;
105
107
  cwd: string;
106
108
  cursor?: string;
107
109
  filterCwd?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganglion/xacpx",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "随时随地通过聊天频道(微信 / 飞书 / 元宝等)远程控制 `acpx` 上的 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",