@ganglion/xacpx 0.15.1 → 0.15.3

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
@@ -820,6 +820,13 @@ var init_streaming_prompt = __esm(() => {
820
820
  ];
821
821
  });
822
822
 
823
+ // src/transport/model-not-advertised.ts
824
+ function isModelNotAdvertisedError(text) {
825
+ if (!text)
826
+ return false;
827
+ return /Cannot (?:apply --model|replay saved model)\b/i.test(text) || /did not advertise (?:that model|model support)/i.test(text);
828
+ }
829
+
823
830
  // src/recovery/discover-parent-package-paths.ts
824
831
  import { spawn as spawn2 } from "node:child_process";
825
832
  import { createRequire as createRequire2 } from "node:module";
@@ -3470,6 +3477,143 @@ var init_path = __esm(() => {
3470
3477
  ROOT_PATH_RE = /^(\/|[a-zA-Z]:\/?)$/;
3471
3478
  });
3472
3479
 
3480
+ // src/transport/codex-subagent-filter.ts
3481
+ import { closeSync, openSync, readdirSync, readSync, statSync } from "node:fs";
3482
+ import { homedir as homedir4 } from "node:os";
3483
+ import { join as join3 } from "node:path";
3484
+ function resolveCodexHome(env = process.env) {
3485
+ const fromEnv = env.CODEX_HOME?.trim();
3486
+ return fromEnv ? fromEnv : join3(homedir4(), ".codex");
3487
+ }
3488
+ function isPlainObject(value) {
3489
+ return !!value && typeof value === "object" && !Array.isArray(value);
3490
+ }
3491
+ function isSubagentSource(source) {
3492
+ if (!isPlainObject(source) || !Object.hasOwn(source, "subagent"))
3493
+ return false;
3494
+ const subagent = source.subagent;
3495
+ if (!isPlainObject(subagent))
3496
+ return false;
3497
+ return Object.values(subagent).some((variant) => isPlainObject(variant) && typeof variant.parent_thread_id === "string");
3498
+ }
3499
+ function sessionMetaLineIsSubagent(line) {
3500
+ if (!line)
3501
+ return false;
3502
+ let parsed;
3503
+ try {
3504
+ parsed = JSON.parse(line);
3505
+ } catch {
3506
+ return false;
3507
+ }
3508
+ const payload = parsed?.payload;
3509
+ return isSubagentSource(payload?.source);
3510
+ }
3511
+ function createSubagentPredicate(reader) {
3512
+ let index = null;
3513
+ const cache = new Map;
3514
+ return (sessionId) => {
3515
+ const cached = cache.get(sessionId);
3516
+ if (cached !== undefined)
3517
+ return cached;
3518
+ let result = false;
3519
+ try {
3520
+ if (!index) {
3521
+ index = new Map;
3522
+ for (const path4 of reader.listRolloutPaths()) {
3523
+ const id = path4.match(ROLLOUT_RE)?.[1];
3524
+ if (id)
3525
+ index.set(id.toLowerCase(), path4);
3526
+ }
3527
+ }
3528
+ const path3 = index.get(sessionId.toLowerCase());
3529
+ if (path3)
3530
+ result = sessionMetaLineIsSubagent(reader.readFirstLine(path3));
3531
+ } catch {
3532
+ result = false;
3533
+ }
3534
+ cache.set(sessionId, result);
3535
+ return result;
3536
+ };
3537
+ }
3538
+ function filterSubagentSessions(result, isSubagent) {
3539
+ return {
3540
+ ...result,
3541
+ sessions: result.sessions.filter((session3) => {
3542
+ try {
3543
+ return !isSubagent(session3.sessionId);
3544
+ } catch {
3545
+ return true;
3546
+ }
3547
+ })
3548
+ };
3549
+ }
3550
+ function nodeRolloutReader(home) {
3551
+ const root = join3(home, "sessions");
3552
+ return {
3553
+ listRolloutPaths() {
3554
+ const out = [];
3555
+ const walk = (dir) => {
3556
+ let entries;
3557
+ try {
3558
+ entries = readdirSync(dir, { withFileTypes: true });
3559
+ } catch {
3560
+ return;
3561
+ }
3562
+ for (const entry of entries) {
3563
+ const full = join3(dir, entry.name);
3564
+ if (entry.isDirectory())
3565
+ walk(full);
3566
+ else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl"))
3567
+ out.push(full);
3568
+ }
3569
+ };
3570
+ walk(root);
3571
+ return out;
3572
+ },
3573
+ readFirstLine(path3) {
3574
+ let fd;
3575
+ try {
3576
+ statSync(path3);
3577
+ fd = openSync(path3, "r");
3578
+ const buf = Buffer.alloc(READ_CHUNK);
3579
+ let acc = "";
3580
+ let total = 0;
3581
+ for (;; ) {
3582
+ const bytes = readSync(fd, buf, 0, READ_CHUNK, total);
3583
+ if (bytes <= 0)
3584
+ break;
3585
+ acc += buf.toString("utf8", 0, bytes);
3586
+ total += bytes;
3587
+ const nl = acc.indexOf(`
3588
+ `);
3589
+ if (nl !== -1)
3590
+ return acc.slice(0, nl);
3591
+ if (total >= FIRST_LINE_READ_CAP)
3592
+ return;
3593
+ }
3594
+ return acc.length ? acc : undefined;
3595
+ } catch {
3596
+ return;
3597
+ } finally {
3598
+ if (fd !== undefined) {
3599
+ try {
3600
+ closeSync(fd);
3601
+ } catch {}
3602
+ }
3603
+ }
3604
+ }
3605
+ };
3606
+ }
3607
+ function codexSubagentPredicate(env) {
3608
+ return createSubagentPredicate(nodeRolloutReader(resolveCodexHome(env)));
3609
+ }
3610
+ var CODEX_AGENT_NAME = "codex", FIRST_LINE_READ_CAP, READ_CHUNK, ROLLOUT_RE;
3611
+ var init_codex_subagent_filter = __esm(() => {
3612
+ FIRST_LINE_READ_CAP = 1024 * 1024;
3613
+ READ_CHUNK = 64 * 1024;
3614
+ ROLLOUT_RE = /rollout-.*-([0-9a-fA-F-]{36})\.jsonl$/;
3615
+ });
3616
+
3473
3617
  // src/transport/agent-session-list.ts
3474
3618
  function isUnknownFilterCwdOption(output) {
3475
3619
  return /(?:unknown|unrecognized) option/i.test(output) && output.includes("--filter-cwd");
@@ -3487,7 +3631,11 @@ async function runAgentSessionList(options) {
3487
3631
  }
3488
3632
  throw new Error(options.formatError(result));
3489
3633
  }
3490
- return parseAgentSessionListOutput(result.stdout, filterLocally ? options.filterCwd : undefined);
3634
+ const parsed = parseAgentSessionListOutput(result.stdout, filterLocally ? options.filterCwd : undefined);
3635
+ if (parsed && options.isSubagentSession) {
3636
+ return filterSubagentSessions(parsed, options.isSubagentSession);
3637
+ }
3638
+ return parsed;
3491
3639
  }
3492
3640
  function parseAgentSessionListOutput(stdout, filterCwd) {
3493
3641
  let parsed;
@@ -3522,16 +3670,17 @@ function filterAgentSessionListByCwd(result, cwd) {
3522
3670
  }
3523
3671
  var init_agent_session_list = __esm(() => {
3524
3672
  init_path();
3673
+ init_codex_subagent_filter();
3525
3674
  });
3526
3675
 
3527
3676
  // src/transport/acpx-session-files.ts
3528
3677
  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";
3678
+ import { homedir as homedir5 } from "node:os";
3679
+ import { join as join4 } from "node:path";
3531
3680
  async function deleteAcpxSessionFiles(options) {
3532
- const dir = options.sessionsDir ?? join3(homedir4(), ".acpx", "sessions");
3681
+ const dir = options.sessionsDir ?? join4(homedir5(), ".acpx", "sessions");
3533
3682
  const safeId = encodeURIComponent(options.acpxRecordId);
3534
- await unlink2(join3(dir, `${safeId}.json`)).catch(() => {
3683
+ await unlink2(join4(dir, `${safeId}.json`)).catch(() => {
3535
3684
  return;
3536
3685
  });
3537
3686
  let entries;
@@ -3542,7 +3691,7 @@ async function deleteAcpxSessionFiles(options) {
3542
3691
  }
3543
3692
  const streamFiles = entries.filter((name) => name.startsWith(`${safeId}.stream.`));
3544
3693
  for (const name of streamFiles) {
3545
- await unlink2(join3(dir, name)).catch(() => {
3694
+ await unlink2(join4(dir, name)).catch(() => {
3546
3695
  return;
3547
3696
  });
3548
3697
  }
@@ -3622,8 +3771,8 @@ init_prompt_output();
3622
3771
  init_prompt_media();
3623
3772
  init_streaming_prompt();
3624
3773
  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";
3774
+ import { homedir as homedir6 } from "node:os";
3775
+ import { dirname as dirname2, join as join5, win32 } from "node:path";
3627
3776
  import { spawn as spawn4 } from "node:child_process";
3628
3777
 
3629
3778
  // src/bridge/parse-missing-optional-dep.ts
@@ -3643,6 +3792,7 @@ function parseMissingOptionalDep(text) {
3643
3792
  init_discover_parent_package_paths();
3644
3793
  init_acpx_queue_owner_launcher();
3645
3794
  init_agent_session_list();
3795
+ init_codex_subagent_filter();
3646
3796
  init_acpx_session_files();
3647
3797
  class EnsureSessionFailedError extends Error {
3648
3798
  kind;
@@ -3708,7 +3858,8 @@ class BridgeRuntime {
3708
3858
  ], { format: "json" }));
3709
3859
  return await this.run(spec.command, spec.args);
3710
3860
  },
3711
- formatError: (result) => result.stderr || result.stdout || `sessions list failed with exit code ${result.code}`
3861
+ formatError: (result) => result.stderr || result.stdout || `sessions list failed with exit code ${result.code}`,
3862
+ isSubagentSession: (input.driver ?? input.agent) === CODEX_AGENT_NAME ? codexSubagentPredicate() : undefined
3712
3863
  });
3713
3864
  }
3714
3865
  async resumeAgentSession(input) {
@@ -3769,6 +3920,21 @@ class BridgeRuntime {
3769
3920
  throw new Error(message);
3770
3921
  }
3771
3922
  async ensureSession(input, onProgress) {
3923
+ try {
3924
+ return await this.attemptEnsureSession(input, onProgress);
3925
+ } catch (error) {
3926
+ const requestedModel = input.model?.trim();
3927
+ if (requestedModel && error instanceof EnsureSessionFailedError && isModelNotAdvertisedError(error.message)) {
3928
+ onProgress?.({
3929
+ kind: "note",
3930
+ text: `agent did not advertise model "${requestedModel}"; retrying with the agent's default model`
3931
+ });
3932
+ return await this.attemptEnsureSession({ ...input, model: undefined }, onProgress);
3933
+ }
3934
+ throw error;
3935
+ }
3936
+ }
3937
+ async attemptEnsureSession(input, onProgress) {
3772
3938
  onProgress?.("spawn");
3773
3939
  const timeoutMs = this.sessionInitTimeoutMs();
3774
3940
  const now = this.options.now ?? Date.now;
@@ -4218,11 +4384,11 @@ async function tryRepairAcpxSessionIndex(deps = {}) {
4218
4384
  if (platform !== "win32") {
4219
4385
  return false;
4220
4386
  }
4221
- const home = deps.home ?? process.env.HOME ?? process.env.USERPROFILE ?? homedir5();
4387
+ const home = deps.home ?? process.env.HOME ?? process.env.USERPROFILE ?? homedir6();
4222
4388
  if (!home) {
4223
4389
  return false;
4224
4390
  }
4225
- const pathJoin = platform === "win32" ? win32.join : join4;
4391
+ const pathJoin = platform === "win32" ? win32.join : join5;
4226
4392
  const sessionsDir = pathJoin(home, ".acpx", "sessions");
4227
4393
  const indexPath = pathJoin(sessionsDir, "index.json");
4228
4394
  const readdirFn = deps.readdirFn ?? readdir2;
@@ -4372,6 +4538,7 @@ class BridgeServer {
4372
4538
  return await this.runtime.listAgentSessions({
4373
4539
  agent: requireString(params, "agent"),
4374
4540
  agentCommand: asOptionalString(params.agentCommand),
4541
+ driver: asOptionalString(params.driver),
4375
4542
  cwd: requireString(params, "cwd"),
4376
4543
  cursor: asOptionalString(params.cursor),
4377
4544
  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
  });
@@ -31575,6 +31587,13 @@ var init_acpx_bridge_transport = __esm(() => {
31575
31587
  init_quota_gated_reply_sink();
31576
31588
  });
31577
31589
 
31590
+ // src/transport/model-not-advertised.ts
31591
+ function isModelNotAdvertisedError(text) {
31592
+ if (!text)
31593
+ return false;
31594
+ return /Cannot (?:apply --model|replay saved model)\b/i.test(text) || /did not advertise (?:that model|model support)/i.test(text);
31595
+ }
31596
+
31578
31597
  // src/transport/prompt-media.ts
31579
31598
  import { mkdtemp, open as open4, rm as rm9, writeFile as writeFile7 } from "node:fs/promises";
31580
31599
  import { tmpdir as defaultTmpdir } from "node:os";
@@ -32150,6 +32169,143 @@ function permissionModeToFlag(permissionMode) {
32150
32169
  }
32151
32170
  }
32152
32171
 
32172
+ // src/transport/codex-subagent-filter.ts
32173
+ import { closeSync, openSync, readdirSync, readSync, statSync as statSync2 } from "node:fs";
32174
+ import { homedir as homedir9 } from "node:os";
32175
+ import { join as join19 } from "node:path";
32176
+ function resolveCodexHome(env = process.env) {
32177
+ const fromEnv = env.CODEX_HOME?.trim();
32178
+ return fromEnv ? fromEnv : join19(homedir9(), ".codex");
32179
+ }
32180
+ function isPlainObject3(value) {
32181
+ return !!value && typeof value === "object" && !Array.isArray(value);
32182
+ }
32183
+ function isSubagentSource(source) {
32184
+ if (!isPlainObject3(source) || !Object.hasOwn(source, "subagent"))
32185
+ return false;
32186
+ const subagent = source.subagent;
32187
+ if (!isPlainObject3(subagent))
32188
+ return false;
32189
+ return Object.values(subagent).some((variant) => isPlainObject3(variant) && typeof variant.parent_thread_id === "string");
32190
+ }
32191
+ function sessionMetaLineIsSubagent(line) {
32192
+ if (!line)
32193
+ return false;
32194
+ let parsed;
32195
+ try {
32196
+ parsed = JSON.parse(line);
32197
+ } catch {
32198
+ return false;
32199
+ }
32200
+ const payload = parsed?.payload;
32201
+ return isSubagentSource(payload?.source);
32202
+ }
32203
+ function createSubagentPredicate(reader) {
32204
+ let index = null;
32205
+ const cache = new Map;
32206
+ return (sessionId) => {
32207
+ const cached2 = cache.get(sessionId);
32208
+ if (cached2 !== undefined)
32209
+ return cached2;
32210
+ let result = false;
32211
+ try {
32212
+ if (!index) {
32213
+ index = new Map;
32214
+ for (const path16 of reader.listRolloutPaths()) {
32215
+ const id = path16.match(ROLLOUT_RE)?.[1];
32216
+ if (id)
32217
+ index.set(id.toLowerCase(), path16);
32218
+ }
32219
+ }
32220
+ const path15 = index.get(sessionId.toLowerCase());
32221
+ if (path15)
32222
+ result = sessionMetaLineIsSubagent(reader.readFirstLine(path15));
32223
+ } catch {
32224
+ result = false;
32225
+ }
32226
+ cache.set(sessionId, result);
32227
+ return result;
32228
+ };
32229
+ }
32230
+ function filterSubagentSessions(result, isSubagent) {
32231
+ return {
32232
+ ...result,
32233
+ sessions: result.sessions.filter((session3) => {
32234
+ try {
32235
+ return !isSubagent(session3.sessionId);
32236
+ } catch {
32237
+ return true;
32238
+ }
32239
+ })
32240
+ };
32241
+ }
32242
+ function nodeRolloutReader(home) {
32243
+ const root = join19(home, "sessions");
32244
+ return {
32245
+ listRolloutPaths() {
32246
+ const out = [];
32247
+ const walk = (dir) => {
32248
+ let entries;
32249
+ try {
32250
+ entries = readdirSync(dir, { withFileTypes: true });
32251
+ } catch {
32252
+ return;
32253
+ }
32254
+ for (const entry of entries) {
32255
+ const full = join19(dir, entry.name);
32256
+ if (entry.isDirectory())
32257
+ walk(full);
32258
+ else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl"))
32259
+ out.push(full);
32260
+ }
32261
+ };
32262
+ walk(root);
32263
+ return out;
32264
+ },
32265
+ readFirstLine(path15) {
32266
+ let fd;
32267
+ try {
32268
+ statSync2(path15);
32269
+ fd = openSync(path15, "r");
32270
+ const buf = Buffer.alloc(READ_CHUNK);
32271
+ let acc = "";
32272
+ let total = 0;
32273
+ for (;; ) {
32274
+ const bytes = readSync(fd, buf, 0, READ_CHUNK, total);
32275
+ if (bytes <= 0)
32276
+ break;
32277
+ acc += buf.toString("utf8", 0, bytes);
32278
+ total += bytes;
32279
+ const nl = acc.indexOf(`
32280
+ `);
32281
+ if (nl !== -1)
32282
+ return acc.slice(0, nl);
32283
+ if (total >= FIRST_LINE_READ_CAP)
32284
+ return;
32285
+ }
32286
+ return acc.length ? acc : undefined;
32287
+ } catch {
32288
+ return;
32289
+ } finally {
32290
+ if (fd !== undefined) {
32291
+ try {
32292
+ closeSync(fd);
32293
+ } catch {}
32294
+ }
32295
+ }
32296
+ }
32297
+ };
32298
+ }
32299
+ function codexSubagentPredicate(env) {
32300
+ return createSubagentPredicate(nodeRolloutReader(resolveCodexHome(env)));
32301
+ }
32302
+ var CODEX_AGENT_NAME = "codex", FIRST_LINE_READ_CAP, READ_CHUNK, ROLLOUT_RE;
32303
+ var init_codex_subagent_filter = __esm(() => {
32304
+ FIRST_LINE_READ_CAP = 1024 * 1024;
32305
+ READ_CHUNK = 64 * 1024;
32306
+ ROLLOUT_RE = /rollout-.*-([0-9a-fA-F-]{36})\.jsonl$/;
32307
+ });
32308
+
32153
32309
  // src/transport/agent-session-list.ts
32154
32310
  function isUnknownFilterCwdOption(output) {
32155
32311
  return /(?:unknown|unrecognized) option/i.test(output) && output.includes("--filter-cwd");
@@ -32167,7 +32323,11 @@ async function runAgentSessionList(options) {
32167
32323
  }
32168
32324
  throw new Error(options.formatError(result));
32169
32325
  }
32170
- return parseAgentSessionListOutput(result.stdout, filterLocally ? options.filterCwd : undefined);
32326
+ const parsed = parseAgentSessionListOutput(result.stdout, filterLocally ? options.filterCwd : undefined);
32327
+ if (parsed && options.isSubagentSession) {
32328
+ return filterSubagentSessions(parsed, options.isSubagentSession);
32329
+ }
32330
+ return parsed;
32171
32331
  }
32172
32332
  function parseAgentSessionListOutput(stdout2, filterCwd) {
32173
32333
  let parsed;
@@ -32202,16 +32362,17 @@ function filterAgentSessionListByCwd(result, cwd) {
32202
32362
  }
32203
32363
  var init_agent_session_list = __esm(() => {
32204
32364
  init_path();
32365
+ init_codex_subagent_filter();
32205
32366
  });
32206
32367
 
32207
32368
  // src/transport/acpx-session-files.ts
32208
32369
  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";
32370
+ import { homedir as homedir10 } from "node:os";
32371
+ import { join as join20 } from "node:path";
32211
32372
  async function deleteAcpxSessionFiles(options) {
32212
- const dir = options.sessionsDir ?? join19(homedir9(), ".acpx", "sessions");
32373
+ const dir = options.sessionsDir ?? join20(homedir10(), ".acpx", "sessions");
32213
32374
  const safeId = encodeURIComponent(options.acpxRecordId);
32214
- await unlink2(join19(dir, `${safeId}.json`)).catch(() => {
32375
+ await unlink2(join20(dir, `${safeId}.json`)).catch(() => {
32215
32376
  return;
32216
32377
  });
32217
32378
  let entries;
@@ -32222,7 +32383,7 @@ async function deleteAcpxSessionFiles(options) {
32222
32383
  }
32223
32384
  const streamFiles = entries.filter((name) => name.startsWith(`${safeId}.stream.`));
32224
32385
  for (const name of streamFiles) {
32225
- await unlink2(join19(dir, name)).catch(() => {
32386
+ await unlink2(join20(dir, name)).catch(() => {
32226
32387
  return;
32227
32388
  });
32228
32389
  }
@@ -32327,6 +32488,18 @@ class AcpxCliTransport {
32327
32488
  this.streamingHooks = streamingHooks;
32328
32489
  }
32329
32490
  async ensureSession(session3, _onProgress) {
32491
+ try {
32492
+ await this.runEnsureSession(session3);
32493
+ } catch (error2) {
32494
+ const requestedModel = session3.model?.trim();
32495
+ if (requestedModel && isModelNotAdvertisedError(error2 instanceof Error ? error2.message : null)) {
32496
+ await this.runEnsureSession({ ...session3, model: undefined });
32497
+ return;
32498
+ }
32499
+ throw error2;
32500
+ }
32501
+ }
32502
+ async runEnsureSession(session3) {
32330
32503
  const args = this.buildArgs(session3, [
32331
32504
  "sessions",
32332
32505
  "new",
@@ -32352,7 +32525,8 @@ class AcpxCliTransport {
32352
32525
  timeoutMs: this.sessionInitTimeoutMs
32353
32526
  });
32354
32527
  },
32355
- formatError: (result) => normalizeCommandError(result) ?? `command failed with exit code ${result.code}`
32528
+ formatError: (result) => normalizeCommandError(result) ?? `command failed with exit code ${result.code}`,
32529
+ isSubagentSession: (query.driver ?? query.agent) === CODEX_AGENT_NAME ? codexSubagentPredicate() : undefined
32356
32530
  });
32357
32531
  }
32358
32532
  async tailSessionHistory(session3, lines) {
@@ -32812,6 +32986,7 @@ var init_acpx_cli_transport = __esm(() => {
32812
32986
  init_terminate_process_tree();
32813
32987
  init_acpx_queue_owner_launcher();
32814
32988
  init_agent_session_list();
32989
+ init_codex_subagent_filter();
32815
32990
  init_acpx_session_files();
32816
32991
  require4 = createRequire5(import.meta.url);
32817
32992
  });
@@ -33284,8 +33459,8 @@ function createControlEventBus(logger2) {
33284
33459
 
33285
33460
  // src/transport/native-session-history.ts
33286
33461
  import { readFile as readFile14 } from "node:fs/promises";
33287
- import { homedir as homedir10 } from "node:os";
33288
- import { join as join20 } from "node:path";
33462
+ import { homedir as homedir11 } from "node:os";
33463
+ import { join as join21 } from "node:path";
33289
33464
  function classifyToolKind(name) {
33290
33465
  const n = name.toLowerCase();
33291
33466
  if (/(^|[^a-z])(read|cat|view|open)([^a-z]|$)/.test(n))
@@ -33397,8 +33572,8 @@ function mapAcpxMessagesToHistory(raw) {
33397
33572
  }
33398
33573
  async function readNativeSessionHistory(opts) {
33399
33574
  try {
33400
- const dir = opts.sessionsDir ?? join20(opts.homeDir ?? homedir10(), ".acpx", "sessions");
33401
- const indexRaw = await readFile14(join20(dir, "index.json"), "utf8").catch(() => null);
33575
+ const dir = opts.sessionsDir ?? join21(opts.homeDir ?? homedir11(), ".acpx", "sessions");
33576
+ const indexRaw = await readFile14(join21(dir, "index.json"), "utf8").catch(() => null);
33402
33577
  if (!indexRaw)
33403
33578
  return [];
33404
33579
  const index = JSON.parse(indexRaw);
@@ -33407,7 +33582,7 @@ async function readNativeSessionHistory(opts) {
33407
33582
  for (const entry of candidates) {
33408
33583
  if (!entry.file)
33409
33584
  continue;
33410
- const recRaw = await readFile14(join20(dir, entry.file), "utf8").catch(() => null);
33585
+ const recRaw = await readFile14(join21(dir, entry.file), "utf8").catch(() => null);
33411
33586
  if (!recRaw)
33412
33587
  continue;
33413
33588
  const record3 = JSON.parse(recRaw);
@@ -33425,14 +33600,14 @@ var init_native_session_history = () => {};
33425
33600
  // src/control/workspace-fs.ts
33426
33601
  import { execFile } from "node:child_process";
33427
33602
  import { promisify } from "node:util";
33428
- import { homedir as homedir11 } from "node:os";
33603
+ import { homedir as homedir12 } from "node:os";
33429
33604
  import { readdir as readdir4, realpath, stat as stat3, open as open5 } from "node:fs/promises";
33430
33605
  import { isAbsolute as isAbsolute3, relative, resolve as resolve3, sep } from "node:path";
33431
33606
  function expandHome2(p) {
33432
33607
  if (p === "~")
33433
- return homedir11();
33608
+ return homedir12();
33434
33609
  if (p.startsWith("~/") || p.startsWith("~" + sep))
33435
- return resolve3(homedir11(), p.slice(2));
33610
+ return resolve3(homedir12(), p.slice(2));
33436
33611
  return p;
33437
33612
  }
33438
33613
 
@@ -33561,16 +33736,16 @@ class WorkspaceFs {
33561
33736
  }
33562
33737
  const files = [];
33563
33738
  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)
33739
+ const { stdout: stdout2 } = await execFileAsync("git", ["-C", root, "-c", "core.quotePath=false", "status", "--porcelain", "-z"], { maxBuffer: GIT_MAX_BUFFER });
33740
+ const fields = stdout2.split("\x00");
33741
+ for (let i = 0;i < fields.length; i++) {
33742
+ const field = fields[i];
33743
+ if (!field)
33568
33744
  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);
33745
+ const status = field.slice(0, 2);
33746
+ const path15 = field.slice(3);
33747
+ if (status[0] === "R" || status[0] === "C")
33748
+ i++;
33574
33749
  files.push({ path: path15, status });
33575
33750
  }
33576
33751
  } catch {}
@@ -33585,6 +33760,15 @@ class WorkspaceFs {
33585
33760
  diff = "";
33586
33761
  }
33587
33762
  }
33763
+ if (rel && !diff) {
33764
+ try {
33765
+ diff = (await execFileAsync("git", ["-C", root, "-c", "core.quotePath=false", "diff", "--no-index", "--", "/dev/null", rel], { maxBuffer: GIT_MAX_BUFFER })).stdout;
33766
+ } catch (e) {
33767
+ const out = e.stdout;
33768
+ if (typeof out === "string")
33769
+ diff = out;
33770
+ }
33771
+ }
33588
33772
  const truncated = diff.length > DIFF_CAP;
33589
33773
  return { workspace: workspace3, files, diff: truncated ? diff.slice(0, DIFF_CAP) : diff, truncated, ...await this.gitContext(root) };
33590
33774
  }
@@ -33676,7 +33860,8 @@ class ControlService {
33676
33860
  workspace: session3.workspace,
33677
33861
  transportSession: session3.transportSession,
33678
33862
  running: this.deps.activeTurns.isActiveAnywhere(session3.alias),
33679
- archived: session3.archived === true
33863
+ archived: session3.archived === true,
33864
+ ...session3.agentCommand ? { agentCommand: session3.agentCommand } : {}
33680
33865
  }));
33681
33866
  }
33682
33867
  async listNativeSessions(_chatKey, agent3, workspace3) {
@@ -34036,10 +34221,10 @@ var init_control_service = __esm(() => {
34036
34221
 
34037
34222
  // src/control/upload-store.ts
34038
34223
  import { mkdtemp as mkdtemp2, readdir as readdir5, rm as rm10, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
34039
- import { homedir as homedir12 } from "node:os";
34224
+ import { homedir as homedir13 } from "node:os";
34040
34225
  import path16 from "node:path";
34041
34226
  function defaultRootDir() {
34042
- const home = process.env.HOME ?? homedir12();
34227
+ const home = process.env.HOME ?? homedir13();
34043
34228
  return path16.join(coreHomeDir(home), "runtime", "uploads");
34044
34229
  }
34045
34230
  function sanitizeUploadFilename(raw) {
@@ -34115,7 +34300,7 @@ var init_upload_store = __esm(() => {
34115
34300
 
34116
34301
  // src/config/agent-catalog.ts
34117
34302
  import { existsSync as existsSync3 } from "node:fs";
34118
- import { delimiter as delimiter2, join as join21 } from "node:path";
34303
+ import { delimiter as delimiter2, join as join22 } from "node:path";
34119
34304
  function isBinaryOnPath(binary) {
34120
34305
  const path17 = process.env.PATH ?? "";
34121
34306
  const exts = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
@@ -34124,7 +34309,7 @@ function isBinaryOnPath(binary) {
34124
34309
  continue;
34125
34310
  for (const ext of exts) {
34126
34311
  try {
34127
- if (existsSync3(join21(dir, binary + ext)))
34312
+ if (existsSync3(join22(dir, binary + ext)))
34128
34313
  return true;
34129
34314
  } catch {}
34130
34315
  }
@@ -34215,8 +34400,8 @@ __export(exports_main, {
34215
34400
  buildApp: () => buildApp
34216
34401
  });
34217
34402
  import { randomUUID as randomUUID3 } from "node:crypto";
34218
- import { homedir as homedir13 } from "node:os";
34219
- import { dirname as dirname13, join as join22 } from "node:path";
34403
+ import { homedir as homedir14 } from "node:os";
34404
+ import { dirname as dirname13, join as join23 } from "node:path";
34220
34405
  import { fileURLToPath as fileURLToPath5 } from "node:url";
34221
34406
  function startProgressHeartbeat(orchestration3, config4, logger2, channel) {
34222
34407
  const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
@@ -34907,8 +35092,8 @@ async function main() {
34907
35092
  }
34908
35093
  }
34909
35094
  async function prepareChannelMedia(configPath, config4) {
34910
- const runtimeDir = join22(dirname13(configPath), "runtime");
34911
- const mediaRootDir = join22(runtimeDir, "media");
35095
+ const runtimeDir = join23(dirname13(configPath), "runtime");
35096
+ const mediaRootDir = join23(runtimeDir, "media");
34912
35097
  const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
34913
35098
  await mediaStore.cleanupExpired().catch((error2) => {
34914
35099
  console.error("[xacpx] media cleanup failed:", error2 instanceof Error ? error2.message : String(error2));
@@ -34917,16 +35102,16 @@ async function prepareChannelMedia(configPath, config4) {
34917
35102
  return { mediaStore, channelDeps: { mediaStore, allowedMediaRoots } };
34918
35103
  }
34919
35104
  function resolveRuntimePaths() {
34920
- const home = process.env.HOME ?? homedir13();
35105
+ const home = process.env.HOME ?? homedir14();
34921
35106
  if (!home) {
34922
35107
  throw new Error("Unable to resolve the current user home directory");
34923
35108
  }
34924
- const configPath = coreEnv("CONFIG") ?? join22(coreHomeDir(home), "config.json");
34925
- const runtimeDir = join22(dirname13(configPath), "runtime");
35109
+ const configPath = coreEnv("CONFIG") ?? join23(coreHomeDir(home), "config.json");
35110
+ const runtimeDir = join23(dirname13(configPath), "runtime");
34926
35111
  return {
34927
35112
  configPath,
34928
- statePath: coreEnv("STATE") ?? join22(coreHomeDir(home), "state.json"),
34929
- perfLogPath: join22(runtimeDir, "perf.log"),
35113
+ statePath: coreEnv("STATE") ?? join23(coreHomeDir(home), "state.json"),
35114
+ perfLogPath: join23(runtimeDir, "perf.log"),
34930
35115
  orchestrationSocketPath: coreEnv("ORCHESTRATION_SOCKET") ?? resolveDaemonOrchestrationSocketPath(runtimeDir)
34931
35116
  };
34932
35117
  }
@@ -34938,13 +35123,13 @@ function resolveBridgeEntryPath() {
34938
35123
  }
34939
35124
  function resolveAppLogPath(configPath) {
34940
35125
  const rootDir = dirname13(configPath);
34941
- const runtimeDir = join22(rootDir, "runtime");
34942
- return join22(runtimeDir, "app.log");
35126
+ const runtimeDir = join23(rootDir, "runtime");
35127
+ return join23(runtimeDir, "app.log");
34943
35128
  }
34944
35129
  function resolvePerfLogPath(configPath) {
34945
35130
  const rootDir = dirname13(configPath);
34946
- const runtimeDir = join22(rootDir, "runtime");
34947
- return join22(runtimeDir, "perf.log");
35131
+ const runtimeDir = join23(rootDir, "runtime");
35132
+ return join23(runtimeDir, "perf.log");
34948
35133
  }
34949
35134
  function resolveOrchestrationSocketPathFromConfigPath(configPath) {
34950
35135
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
@@ -35186,10 +35371,10 @@ var init_config_check = __esm(async () => {
35186
35371
  // src/doctor/checks/daemon-check.ts
35187
35372
  import { readdir as readdir6, readFile as readFile15, rm as rm11 } from "node:fs/promises";
35188
35373
  import { fileURLToPath as fileURLToPath6 } from "node:url";
35189
- import { homedir as homedir14 } from "node:os";
35190
- import { join as join23 } from "node:path";
35374
+ import { homedir as homedir15 } from "node:os";
35375
+ import { join as join24 } from "node:path";
35191
35376
  async function checkDaemon(options = {}) {
35192
- const home = options.home ?? process.env.HOME ?? homedir14();
35377
+ const home = options.home ?? process.env.HOME ?? homedir15();
35193
35378
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
35194
35379
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
35195
35380
  home,
@@ -35289,7 +35474,7 @@ async function detectStaleConsumerLockFix(runtimeDir, deps) {
35289
35474
  if (!fileName.endsWith(CONSUMER_LOCK_SUFFIX)) {
35290
35475
  continue;
35291
35476
  }
35292
- const lockPath = join23(runtimeDir, fileName);
35477
+ const lockPath = join24(runtimeDir, fileName);
35293
35478
  const lock2 = await deps.readConsumerLock(lockPath);
35294
35479
  if (lock2 && !deps.isProcessRunning(lock2.pid)) {
35295
35480
  stalePaths.push(lockPath);
@@ -35354,10 +35539,10 @@ var init_daemon_check = __esm(() => {
35354
35539
 
35355
35540
  // src/doctor/checks/logs-check.ts
35356
35541
  import { stat as stat5, readdir as readdir7 } from "node:fs/promises";
35357
- import { basename as basename4, join as join24 } from "node:path";
35358
- import { homedir as homedir15 } from "node:os";
35542
+ import { basename as basename4, join as join25 } from "node:path";
35543
+ import { homedir as homedir16 } from "node:os";
35359
35544
  async function checkLogs(options = {}) {
35360
- const home = options.home ?? process.env.HOME ?? homedir15();
35545
+ const home = options.home ?? process.env.HOME ?? homedir16();
35361
35546
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
35362
35547
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
35363
35548
  home,
@@ -35389,7 +35574,7 @@ async function checkLogs(options = {}) {
35389
35574
  const matched = entries.filter((entry) => isTrackedLogName(entry, tracked));
35390
35575
  const files = [];
35391
35576
  for (const name of matched) {
35392
- const path17 = join24(paths.runtimeDir, name);
35577
+ const path17 = join25(paths.runtimeDir, name);
35393
35578
  try {
35394
35579
  const fileStat = await probe.stat(path17);
35395
35580
  if (fileStat.isDirectory()) {
@@ -35539,9 +35724,9 @@ var init_orchestration_health = __esm(() => {
35539
35724
  });
35540
35725
 
35541
35726
  // src/doctor/checks/orchestration-socket-check.ts
35542
- import { homedir as homedir16 } from "node:os";
35727
+ import { homedir as homedir17 } from "node:os";
35543
35728
  async function checkOrchestrationSocket(options = {}) {
35544
- const home = options.home ?? process.env.HOME ?? homedir16();
35729
+ const home = options.home ?? process.env.HOME ?? homedir17();
35545
35730
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
35546
35731
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
35547
35732
  home,
@@ -35716,9 +35901,9 @@ var init_plugin_check = __esm(async () => {
35716
35901
  import { constants } from "node:fs";
35717
35902
  import { access as access4, stat as stat6 } from "node:fs/promises";
35718
35903
  import { dirname as dirname14 } from "node:path";
35719
- import { homedir as homedir17 } from "node:os";
35904
+ import { homedir as homedir18 } from "node:os";
35720
35905
  async function checkRuntime(options = {}) {
35721
- const home = options.home ?? process.env.HOME ?? homedir17();
35906
+ const home = options.home ?? process.env.HOME ?? homedir18();
35722
35907
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
35723
35908
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
35724
35909
  home,
@@ -36351,10 +36536,10 @@ var init_render_doctor = __esm(() => {
36351
36536
  });
36352
36537
 
36353
36538
  // src/doctor/doctor.ts
36354
- import { homedir as homedir18 } from "node:os";
36355
- import { join as join25 } from "node:path";
36539
+ import { homedir as homedir19 } from "node:os";
36540
+ import { join as join26 } from "node:path";
36356
36541
  async function runDoctor(options = {}, deps = {}) {
36357
- const home = deps.home ?? process.env.HOME ?? homedir18();
36542
+ const home = deps.home ?? process.env.HOME ?? homedir19();
36358
36543
  const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
36359
36544
  const sharedLoadConfig = createSharedLoadConfig(runtimePaths, deps.loadConfig ?? loadConfig);
36360
36545
  const runners = [
@@ -36507,8 +36692,8 @@ function resolveDoctorRuntimePaths(home, resolver) {
36507
36692
  return resolveRuntimePaths();
36508
36693
  }
36509
36694
  return {
36510
- configPath: join25(coreHomeDir(home), "config.json"),
36511
- statePath: join25(coreHomeDir(home), "state.json")
36695
+ configPath: join26(coreHomeDir(home), "config.json"),
36696
+ statePath: join26(coreHomeDir(home), "state.json")
36512
36697
  };
36513
36698
  }
36514
36699
  function depsUseExplicitRuntimeOverrides() {
@@ -36691,8 +36876,8 @@ var init_doctor2 = __esm(async () => {
36691
36876
  // src/cli.ts
36692
36877
  init_core_home();
36693
36878
  import { randomUUID as randomUUID4 } from "node:crypto";
36694
- import { homedir as homedir19 } from "node:os";
36695
- import { dirname as dirname15, join as join26, sep as sep2 } from "node:path";
36879
+ import { homedir as homedir20 } from "node:os";
36880
+ import { dirname as dirname15, join as join27, sep as sep2 } from "node:path";
36696
36881
  import { fileURLToPath as fileURLToPath7 } from "node:url";
36697
36882
 
36698
36883
  // src/runtime/migrate-core-home.ts
@@ -52946,7 +53131,7 @@ async function createCliScheduledTaskService() {
52946
53131
  return new ScheduledTaskService(state, stateStore);
52947
53132
  }
52948
53133
  function resolveConfigPathForCurrentEnv() {
52949
- return coreEnv("CONFIG") ?? join26(coreHomeDir(requireHome2()), "config.json");
53134
+ return coreEnv("CONFIG") ?? join27(coreHomeDir(requireHome2()), "config.json");
52950
53135
  }
52951
53136
  function resolveDaemonPathsForCurrentConfig() {
52952
53137
  const configPath = resolveConfigPathForCurrentEnv();
@@ -53297,7 +53482,7 @@ function decodeFirstRunOnboarding(raw) {
53297
53482
  return null;
53298
53483
  }
53299
53484
  function requireHome2() {
53300
- const home = process.env.HOME ?? homedir19();
53485
+ const home = process.env.HOME ?? homedir20();
53301
53486
  if (!home) {
53302
53487
  throw new Error("Unable to resolve the current user home directory");
53303
53488
  }
@@ -53321,7 +53506,7 @@ function safeDaemonLogPaths() {
53321
53506
  const configPath = resolveConfigPathForCurrentEnv();
53322
53507
  const paths = resolveDaemonPathsForCurrentConfig();
53323
53508
  return {
53324
- appLog: join26(dirname15(configPath), "runtime", "app.log"),
53509
+ appLog: join27(dirname15(configPath), "runtime", "app.log"),
53325
53510
  stderrLog: paths.stderrLog
53326
53511
  };
53327
53512
  } catch {
@@ -18,6 +18,11 @@ export interface ControlSessionInfo {
18
18
  transportSession: string;
19
19
  running: boolean;
20
20
  archived: boolean;
21
+ /** The agent adapter command this session runs (acpx-recorded, or the agent's resolved
22
+ * default). Surfaced so the web can avoid seeding a new session's model picker from a
23
+ * session on a different adapter version (whose advertised model ids may be in an
24
+ * incompatible format). Omitted when unknown. */
25
+ agentCommand?: string;
21
26
  }
22
27
  export interface ControlAgentInfo {
23
28
  name: string;
@@ -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.1",
3
+ "version": "0.15.3",
4
4
  "description": "随时随地通过聊天频道(微信 / 飞书 / 元宝等)远程控制 `acpx` 上的 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",