@ganglion/xacpx 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -115,6 +115,14 @@ var init_session = __esm(() => {
115
115
  modeModeLabel: (modeId) => `- mode: ${modeId}`,
116
116
  modeNotSet: "not set",
117
117
  modeSet: (modeId) => `Current session mode set to: ${modeId}`,
118
+ modelHeader: "Current model:",
119
+ modelSessionLabel: (alias) => `- Session: ${alias}`,
120
+ modelModelLabel: (modelId) => `- model: ${modelId}`,
121
+ modelNotSet: "not set (using agent default)",
122
+ modelAvailableLabel: (models) => `- available: ${models}`,
123
+ modelSet: (modelId) => `Current session model switched to: ${modelId}`,
124
+ modelSetFailed: (modelId, detail) => `Failed to switch model: ${modelId}
125
+ ${detail}`,
118
126
  replyModeHeader: "Current reply mode:",
119
127
  replyModeSessionLabel: (alias) => `- Session: ${alias}`,
120
128
  replyModeGlobalDefault: (value) => `- Global default: ${value}`,
@@ -191,6 +199,11 @@ var init_session = __esm(() => {
191
199
  modeHelpCmdShowDesc: "Show the saved mode of the current session",
192
200
  modeHelpCmdSet: "/mode <id>",
193
201
  modeHelpCmdSetDesc: "Set the current session mode",
202
+ modelHelpSummary: "View or switch the LLM model for the current session.",
203
+ modelHelpCmdShow: "/model",
204
+ modelHelpCmdShowDesc: "Show the current session model and the available ones",
205
+ modelHelpCmdSet: "/model <id>",
206
+ modelHelpCmdSetDesc: "Switch the current session model (e.g. gpt-5.2[high])",
194
207
  replyModeHelpSummary: "View or set the reply output mode for the current logical session.",
195
208
  replyModeHelpCmdShow: "/replymode",
196
209
  replyModeHelpCmdShowDesc: "Show global default, current override, and effective value",
@@ -1194,6 +1207,14 @@ var init_session2 = __esm(() => {
1194
1207
  modeModeLabel: (modeId) => `- mode:${modeId}`,
1195
1208
  modeNotSet: "未设置",
1196
1209
  modeSet: (modeId) => `已设置当前会话 mode:${modeId}`,
1210
+ modelHeader: "当前 model:",
1211
+ modelSessionLabel: (alias) => `- 会话:${alias}`,
1212
+ modelModelLabel: (modelId) => `- model:${modelId}`,
1213
+ modelNotSet: "未设置(使用 agent 默认)",
1214
+ modelAvailableLabel: (models) => `- 可选:${models}`,
1215
+ modelSet: (modelId) => `已切换当前会话 model:${modelId}`,
1216
+ modelSetFailed: (modelId, detail) => `切换 model 失败:${modelId}
1217
+ ${detail}`,
1197
1218
  replyModeHeader: "当前 reply mode:",
1198
1219
  replyModeSessionLabel: (alias) => `- 会话:${alias}`,
1199
1220
  replyModeGlobalDefault: (value) => `- 全局默认:${value}`,
@@ -1270,6 +1291,11 @@ var init_session2 = __esm(() => {
1270
1291
  modeHelpCmdShowDesc: "查看当前会话已保存的 mode",
1271
1292
  modeHelpCmdSet: "/mode <id>",
1272
1293
  modeHelpCmdSetDesc: "设置当前会话 mode",
1294
+ modelHelpSummary: "查看或切换当前会话的 LLM model。",
1295
+ modelHelpCmdShow: "/model",
1296
+ modelHelpCmdShowDesc: "查看当前会话 model 及可选项",
1297
+ modelHelpCmdSet: "/model <id>",
1298
+ modelHelpCmdSetDesc: "切换当前会话 model(如 gpt-5.2[high])",
1273
1299
  replyModeHelpSummary: "查看或设置当前逻辑会话的回复输出模式。",
1274
1300
  replyModeHelpCmdShow: "/replymode",
1275
1301
  replyModeHelpCmdShowDesc: "查看全局默认、当前覆盖和实际生效值",
@@ -4402,6 +4428,53 @@ var init_plugin_renames = __esm(() => {
4402
4428
  ]);
4403
4429
  });
4404
4430
 
4431
+ // src/config/local-agent-bin.ts
4432
+ import { statSync } from "node:fs";
4433
+ import { delimiter, join as join3 } from "node:path";
4434
+ function executableExtensions(platform, env) {
4435
+ return platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter((e) => e.length > 0) : [""];
4436
+ }
4437
+ function defaultIsExecutableFile(p) {
4438
+ try {
4439
+ const st = statSync(p);
4440
+ if (!st.isFile())
4441
+ return false;
4442
+ return process.platform === "win32" || (st.mode & 73) !== 0;
4443
+ } catch {
4444
+ return false;
4445
+ }
4446
+ }
4447
+ function isExecutableOnPath(name, env = process.env, isExecutableFile = defaultIsExecutableFile) {
4448
+ const pathValue = env.PATH ?? env.Path ?? "";
4449
+ if (!pathValue)
4450
+ return false;
4451
+ const exts = executableExtensions(process.platform, env);
4452
+ for (const dir of pathValue.split(delimiter)) {
4453
+ if (!dir)
4454
+ continue;
4455
+ for (const ext of exts) {
4456
+ if (isExecutableFile(join3(dir, name + ext)))
4457
+ return true;
4458
+ }
4459
+ }
4460
+ return false;
4461
+ }
4462
+ function resolveLocalAgentCommand(driver, onPath = (name) => isExecutableOnPath(name)) {
4463
+ const spec = LOCAL_AGENT_BINS[driver];
4464
+ if (!spec)
4465
+ return;
4466
+ if (!onPath(spec.bin))
4467
+ return;
4468
+ return [spec.bin, ...spec.args].join(" ");
4469
+ }
4470
+ var LOCAL_AGENT_BINS;
4471
+ var init_local_agent_bin = __esm(() => {
4472
+ LOCAL_AGENT_BINS = {
4473
+ opencode: { bin: "opencode", args: ["acp"] },
4474
+ kilocode: { bin: "kilocode", args: ["acp"] }
4475
+ };
4476
+ });
4477
+
4405
4478
  // src/config/resolve-agent-command.ts
4406
4479
  function resolveAgentCommand(driver, command) {
4407
4480
  if (!command) {
@@ -4412,10 +4485,20 @@ function resolveAgentCommand(driver, command) {
4412
4485
  }
4413
4486
  return command;
4414
4487
  }
4488
+ function resolveRuntimeAgentCommand(driver, command, preferLocal = true) {
4489
+ const explicit = resolveAgentCommand(driver, command);
4490
+ if (explicit) {
4491
+ return explicit;
4492
+ }
4493
+ return preferLocal ? resolveLocalAgentCommand(driver) : undefined;
4494
+ }
4415
4495
  function isLegacyCodexCommand(command) {
4416
4496
  const normalized = command.trim().replaceAll("\\", "/").toLowerCase();
4417
4497
  return normalized === "./node_modules/.bin/codex-acp" || normalized === "./node_modules/.bin/codex-acp.exe" || normalized.endsWith("/node_modules/.bin/codex-acp") || normalized.endsWith("/node_modules/.bin/codex-acp.exe") || normalized.includes("/@zed-industries/codex-acp/bin/codex-acp.js") || normalized.includes("@zed-industries/codex-acp/bin/codex-acp.js");
4418
4498
  }
4499
+ var init_resolve_agent_command = __esm(() => {
4500
+ init_local_agent_bin();
4501
+ });
4419
4502
 
4420
4503
  // src/config/load-config.ts
4421
4504
  import { readFile } from "node:fs/promises";
@@ -4505,6 +4588,9 @@ function parseConfig(raw, options = {}) {
4505
4588
  if ("queueOwnerTtlSeconds" in transport && (typeof transport.queueOwnerTtlSeconds !== "number" || !Number.isFinite(transport.queueOwnerTtlSeconds) || transport.queueOwnerTtlSeconds < 0)) {
4506
4589
  throw new Error("transport.queueOwnerTtlSeconds must be a non-negative number (0 = keep alive forever)");
4507
4590
  }
4591
+ if ("preferLocalAgents" in transport && typeof transport.preferLocalAgents !== "boolean") {
4592
+ throw new Error("transport.preferLocalAgents must be a boolean");
4593
+ }
4508
4594
  if (!isRecord(raw.agents)) {
4509
4595
  throw new Error("agents must be an object");
4510
4596
  }
@@ -4572,9 +4658,11 @@ function parseConfig(raw, options = {}) {
4572
4658
  for (const [name, agent3] of Object.entries(rawAgents)) {
4573
4659
  const driver = agent3.driver;
4574
4660
  const command = typeof agent3.command === "string" ? resolveAgentCommand(driver, agent3.command) : undefined;
4661
+ const model = typeof agent3.model === "string" && agent3.model.trim().length > 0 ? agent3.model.trim() : undefined;
4575
4662
  agents[name] = {
4576
4663
  driver,
4577
- ...command ? { command } : {}
4664
+ ...command ? { command } : {},
4665
+ ...model ? { model } : {}
4578
4666
  };
4579
4667
  }
4580
4668
  const rawWorkspaces = raw.workspaces;
@@ -4759,6 +4847,7 @@ var DEFAULT_PERF_LOG_CONFIG, DEFAULT_LOGGING_CONFIG, DEFAULT_PERMISSION_MODE = "
4759
4847
  var init_load_config = __esm(() => {
4760
4848
  init_workspace_path();
4761
4849
  init_plugin_renames();
4850
+ init_resolve_agent_command();
4762
4851
  init_resolve_locale();
4763
4852
  DEFAULT_PERF_LOG_CONFIG = {
4764
4853
  enabled: false,
@@ -4836,7 +4925,8 @@ class ConfigStore {
4836
4925
  const agents = ensureRecordAt(raw, "agents");
4837
4926
  agents[name] = {
4838
4927
  driver: agent3.driver,
4839
- ...agent3.command ? { command: agent3.command } : {}
4928
+ ...agent3.command ? { command: agent3.command } : {},
4929
+ ...agent3.model ? { model: agent3.model } : {}
4840
4930
  };
4841
4931
  });
4842
4932
  }
@@ -5131,6 +5221,7 @@ var init_ensure_config = __esm(() => {
5131
5221
  init_config_store();
5132
5222
  init_default_workspace();
5133
5223
  init_load_config();
5224
+ init_resolve_agent_command();
5134
5225
  BUILTIN_DEFAULT_CONFIG_TEMPLATE = {
5135
5226
  transport: {
5136
5227
  type: "acpx-bridge"
@@ -5301,7 +5392,7 @@ class DaemonController {
5301
5392
  this.deps = deps;
5302
5393
  this.statusStore = new DaemonStatusStore(paths.statusFile);
5303
5394
  this.startupPollIntervalMs = deps.startupPollIntervalMs ?? 50;
5304
- this.startupTimeoutMs = deps.startupTimeoutMs ?? 5000;
5395
+ this.startupTimeoutMs = deps.startupTimeoutMs ?? 1e4;
5305
5396
  this.onboardingStartupTimeoutMs = deps.onboardingStartupTimeoutMs ?? 300000;
5306
5397
  this.shutdownPollIntervalMs = deps.shutdownPollIntervalMs ?? 50;
5307
5398
  this.shutdownTimeoutMs = deps.shutdownTimeoutMs ?? 5000;
@@ -5647,7 +5738,7 @@ var init_create_daemon_controller = __esm(() => {
5647
5738
 
5648
5739
  // src/orchestration/orchestration-ipc.ts
5649
5740
  import { createHash } from "node:crypto";
5650
- import { join as join3 } from "node:path";
5741
+ import { join as join4 } from "node:path";
5651
5742
  function resolveOrchestrationEndpoint(runtimeDir, platform = process.platform) {
5652
5743
  if (platform === "win32") {
5653
5744
  const suffix = createHash("sha256").update(runtimeDir).digest("hex").slice(0, 12);
@@ -5658,7 +5749,7 @@ function resolveOrchestrationEndpoint(runtimeDir, platform = process.platform) {
5658
5749
  }
5659
5750
  return {
5660
5751
  kind: "unix",
5661
- path: join3(runtimeDir, "orchestration.sock")
5752
+ path: join4(runtimeDir, "orchestration.sock")
5662
5753
  };
5663
5754
  }
5664
5755
  function createOrchestrationEndpoint(path2, platform = process.platform) {
@@ -5678,24 +5769,32 @@ function encodeOrchestrationRpcResponse(response) {
5678
5769
  var init_orchestration_ipc = () => {};
5679
5770
 
5680
5771
  // src/daemon/daemon-files.ts
5681
- import { dirname as dirname3, join as join4 } from "node:path";
5772
+ import { dirname as dirname3, join as join5 } from "node:path";
5682
5773
  function resolveDaemonPaths(options) {
5683
- const runtimeDir = options.runtimeDir ?? (options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : join4(coreHomeDir(options.home), "runtime"));
5774
+ const runtimeDir = options.runtimeDir ?? (options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : join5(coreHomeDir(options.home), "runtime"));
5684
5775
  return {
5685
5776
  runtimeDir,
5686
- pidFile: join4(runtimeDir, "daemon.pid"),
5687
- statusFile: join4(runtimeDir, "status.json"),
5688
- stdoutLog: join4(runtimeDir, "stdout.log"),
5689
- stderrLog: join4(runtimeDir, "stderr.log"),
5690
- appLog: join4(runtimeDir, "app.log")
5777
+ pidFile: join5(runtimeDir, "daemon.pid"),
5778
+ statusFile: join5(runtimeDir, "status.json"),
5779
+ stdoutLog: join5(runtimeDir, "stdout.log"),
5780
+ stderrLog: join5(runtimeDir, "stderr.log"),
5781
+ appLog: join5(runtimeDir, "app.log")
5691
5782
  };
5692
5783
  }
5693
5784
  function resolveRuntimeDirFromConfigPath(configPath) {
5694
- return join4(dirname3(configPath), "runtime");
5785
+ return join5(dirname3(configPath), "runtime");
5695
5786
  }
5696
5787
  function resolveDaemonOrchestrationSocketPath(runtimeDir, platform = process.platform) {
5697
5788
  return resolveOrchestrationEndpoint(runtimeDir, platform).path;
5698
5789
  }
5790
+ function isProcessAlive(pid) {
5791
+ try {
5792
+ process.kill(pid, 0);
5793
+ return true;
5794
+ } catch (error) {
5795
+ return error.code === "EPERM";
5796
+ }
5797
+ }
5699
5798
  var init_daemon_files = __esm(() => {
5700
5799
  init_core_home();
5701
5800
  init_orchestration_ipc();
@@ -13101,6 +13200,14 @@ class ScheduledTaskService {
13101
13200
  listPending(chatKey) {
13102
13201
  return this.listPendingAllChats().filter((task) => task.chat_key === chatKey);
13103
13202
  }
13203
+ listRecentForChat(chatKey, opts) {
13204
+ const terminalLimit = opts?.terminalLimit ?? 20;
13205
+ const mine = Object.values(this.state.scheduled_tasks).filter((task) => task.chat_key === chatKey);
13206
+ const upcoming = mine.filter((task) => task.status === "pending" || task.status === "triggering").sort((left, right) => left.execute_at.localeCompare(right.execute_at));
13207
+ const settledAt = (task) => task.executed_at ?? task.failed_at ?? task.cancelled_at ?? task.missed_at ?? task.triggered_at ?? task.created_at;
13208
+ const terminal = mine.filter((task) => task.status === "executed" || task.status === "failed" || task.status === "missed" || task.status === "cancelled").sort((left, right) => settledAt(right).localeCompare(settledAt(left))).slice(0, terminalLimit);
13209
+ return [...upcoming, ...terminal];
13210
+ }
13104
13211
  listPendingAllChats() {
13105
13212
  return Object.values(this.state.scheduled_tasks).filter((task) => task.status === "pending").sort((left, right) => left.execute_at.localeCompare(right.execute_at));
13106
13213
  }
@@ -13224,14 +13331,14 @@ var init_scheduled_service = () => {};
13224
13331
  import { readFileSync as readFileSync2 } from "node:fs";
13225
13332
  import { copyFile, mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
13226
13333
  import { homedir as homedir3 } from "node:os";
13227
- import { dirname as dirname4, join as join6 } from "node:path";
13334
+ import { dirname as dirname4, join as join7 } from "node:path";
13228
13335
  import { fileURLToPath as fileURLToPath2 } from "node:url";
13229
13336
  function resolveCoreRoot() {
13230
13337
  try {
13231
13338
  let dir = dirname4(fileURLToPath2(import.meta.url));
13232
13339
  for (let depth = 0;depth < 12; depth++) {
13233
13340
  try {
13234
- const pkg = JSON.parse(readFileSync2(join6(dir, "package.json"), "utf-8"));
13341
+ const pkg = JSON.parse(readFileSync2(join7(dir, "package.json"), "utf-8"));
13235
13342
  if (pkg.name && CORE_ROOT_NAMES.includes(pkg.name))
13236
13343
  return dir;
13237
13344
  } catch {}
@@ -13249,10 +13356,10 @@ async function ensureCoreResolution(pluginHome) {
13249
13356
  const root = resolveCoreRoot();
13250
13357
  if (!root)
13251
13358
  return;
13252
- const srcJs = join6(root, "dist", "plugin-api.js");
13359
+ const srcJs = join7(root, "dist", "plugin-api.js");
13253
13360
  for (const name of SHIM_SPECIFIERS) {
13254
- const targetDir = join6(pluginHome, "node_modules", name);
13255
- const dstJs = join6(targetDir, "plugin-api.js");
13361
+ const targetDir = join7(pluginHome, "node_modules", name);
13362
+ const dstJs = join7(targetDir, "plugin-api.js");
13256
13363
  await mkdir4(targetDir, { recursive: true });
13257
13364
  try {
13258
13365
  await copyFile(srcJs, dstJs);
@@ -13261,7 +13368,7 @@ async function ensureCoreResolution(pluginHome) {
13261
13368
  console.warn(`xacpx: skipped plugin-api resolution shim for "${name}" — could not copy ${srcJs} (${message}). ` + `Channel plugins importing "${name}/plugin-api" at runtime may fail to load.`);
13262
13369
  continue;
13263
13370
  }
13264
- await writeFile4(join6(targetDir, "package.json"), JSON.stringify({
13371
+ await writeFile4(join7(targetDir, "package.json"), JSON.stringify({
13265
13372
  name,
13266
13373
  type: "module",
13267
13374
  exports: {
@@ -13290,10 +13397,10 @@ function resolvePluginHome(input = {}) {
13290
13397
  if (envOverride)
13291
13398
  return envOverride;
13292
13399
  const home = coerceMissing(input.home) ?? coerceMissing(process.env.HOME) ?? homedir3();
13293
- return join6(coreHomeDir(home), "plugins");
13400
+ return join7(coreHomeDir(home), "plugins");
13294
13401
  }
13295
13402
  async function normalizePluginHomeManifest(pluginHome) {
13296
- const manifestPath = join6(pluginHome, "package.json");
13403
+ const manifestPath = join7(pluginHome, "package.json");
13297
13404
  let raw;
13298
13405
  try {
13299
13406
  raw = await readFile7(manifestPath, "utf8");
@@ -13315,7 +13422,7 @@ async function normalizePluginHomeManifest(pluginHome) {
13315
13422
  }
13316
13423
  async function ensurePluginHome(pluginHome) {
13317
13424
  await mkdir4(pluginHome, { recursive: true, mode: 448 });
13318
- await writeFile4(join6(pluginHome, "package.json"), JSON.stringify({ private: true, type: "module" }, null, 2) + `
13425
+ await writeFile4(join7(pluginHome, "package.json"), JSON.stringify({ private: true, type: "module" }, null, 2) + `
13319
13426
  `, { flag: "wx" }).catch((error2) => {
13320
13427
  if (error2.code !== "EEXIST")
13321
13428
  throw error2;
@@ -17365,7 +17472,7 @@ function normalizeMediaArray(media) {
17365
17472
 
17366
17473
  // src/logging/rotating-file-writer.ts
17367
17474
  import { readdir as readdir2, rename as rename2, rm as rm6, stat as stat2 } from "node:fs/promises";
17368
- import { basename, dirname as dirname5, join as join8 } from "node:path";
17475
+ import { basename, dirname as dirname5, join as join9 } from "node:path";
17369
17476
  async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
17370
17477
  let currentSize = 0;
17371
17478
  try {
@@ -17415,7 +17522,7 @@ async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
17415
17522
  if (!file.startsWith(prefix) || !/^\d+$/.test(file.slice(prefix.length))) {
17416
17523
  continue;
17417
17524
  }
17418
- const candidate = join8(parentDir, file);
17525
+ const candidate = join9(parentDir, file);
17419
17526
  let details;
17420
17527
  try {
17421
17528
  details = await stat2(candidate);
@@ -19093,10 +19200,10 @@ var init_scheduled_turn = __esm(() => {
19093
19200
 
19094
19201
  // src/weixin/monitor/consumer-lock.ts
19095
19202
  import { mkdir as mkdir6, open as open3, readFile as readFile8, rm as rm7 } from "node:fs/promises";
19096
- import { dirname as dirname7, join as join9 } from "node:path";
19203
+ import { dirname as dirname7, join as join10 } from "node:path";
19097
19204
  import { homedir as homedir4 } from "node:os";
19098
19205
  function createWeixinConsumerLock(options = {}) {
19099
- const lockFilePath = options.lockFilePath ?? join9(coreHomeDir(homedir4()), "runtime", "weixin-consumer.lock.json");
19206
+ const lockFilePath = options.lockFilePath ?? join10(coreHomeDir(homedir4()), "runtime", "weixin-consumer.lock.json");
19100
19207
  const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning4;
19101
19208
  const onDiagnostic = options.onDiagnostic;
19102
19209
  return {
@@ -19770,9 +19877,9 @@ __export(exports_plugin_loader, {
19770
19877
  });
19771
19878
  import { createRequire as createRequire2 } from "node:module";
19772
19879
  import { pathToFileURL } from "node:url";
19773
- import { join as join10 } from "node:path";
19880
+ import { join as join11 } from "node:path";
19774
19881
  async function importPluginFromHome(packageName, pluginHome) {
19775
- const requireFromHome = createRequire2(join10(pluginHome, "package.json"));
19882
+ const requireFromHome = createRequire2(join11(pluginHome, "package.json"));
19776
19883
  const entry = requireFromHome.resolve(packageName);
19777
19884
  return await import(pathToFileURL(entry).href);
19778
19885
  }
@@ -19815,6 +19922,122 @@ var init_plugin_loader = __esm(() => {
19815
19922
  init_plugin_home();
19816
19923
  });
19817
19924
 
19925
+ // src/plugins/plugin-doctor.ts
19926
+ import { readFile as readFile10 } from "node:fs/promises";
19927
+ import { join as join13 } from "node:path";
19928
+ function suggestedPluginPackageForChannel(type) {
19929
+ return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
19930
+ }
19931
+ async function readDependencyEntries(pluginHome) {
19932
+ try {
19933
+ const raw = await readFile10(join13(pluginHome, "package.json"), "utf8");
19934
+ const parsed = JSON.parse(raw);
19935
+ const out = {};
19936
+ for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
19937
+ if (typeof value === "string")
19938
+ out[name] = value;
19939
+ }
19940
+ return out;
19941
+ } catch (error2) {
19942
+ const message = error2 instanceof Error ? error2.message : String(error2);
19943
+ throw new Error(`failed to read plugin home package.json: ${message}`);
19944
+ }
19945
+ }
19946
+ async function inspectPlugins(input) {
19947
+ const issues = [];
19948
+ let dependencies;
19949
+ try {
19950
+ dependencies = await readDependencyEntries(input.pluginHome);
19951
+ } catch (error2) {
19952
+ const message = error2 instanceof Error ? error2.message : String(error2);
19953
+ return [{ level: "error", message }];
19954
+ }
19955
+ const importPlugin = input.importPlugin ?? importPluginFromHome;
19956
+ const allConfigured = input.config.plugins;
19957
+ const filterByName = input.pluginName ? normalizePluginPackageName(input.pluginName) : null;
19958
+ if (filterByName && !allConfigured.some((plugin) => normalizePluginPackageName(plugin.name) === filterByName)) {
19959
+ return [{ level: "error", plugin: filterByName, message: `plugin is not configured; run xacpx plugin add ${filterByName}` }];
19960
+ }
19961
+ const pushIfRelevant = (issue2) => {
19962
+ if (!filterByName || issue2.plugin === filterByName)
19963
+ issues.push(issue2);
19964
+ };
19965
+ const channelProviders = new Map;
19966
+ for (const configPlugin of allConfigured) {
19967
+ if (!(configPlugin.name in dependencies)) {
19968
+ pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `package not installed in plugin home; run xacpx plugin add ${configPlugin.name}`, suggestion: `xacpx plugin add ${configPlugin.name} && xacpx restart` });
19969
+ continue;
19970
+ }
19971
+ let moduleValue;
19972
+ try {
19973
+ moduleValue = await importPlugin(configPlugin.name, input.pluginHome);
19974
+ } catch (error2) {
19975
+ const message = error2 instanceof Error ? error2.message : String(error2);
19976
+ pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `failed to import plugin: ${message}`, suggestion: `xacpx plugin add ${configPlugin.name} && xacpx restart` });
19977
+ continue;
19978
+ }
19979
+ try {
19980
+ const plugin = validateWeacpxPlugin(moduleValue, configPlugin.name, {
19981
+ ...input.currentXacpxVersion !== undefined ? { currentXacpxVersion: input.currentXacpxVersion } : {}
19982
+ });
19983
+ const channels = plugin.channels ?? [];
19984
+ const channelTypes = channels.map((channel) => channel.type);
19985
+ for (const type of channelTypes) {
19986
+ const existing = channelProviders.get(type);
19987
+ if (existing) {
19988
+ pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `channel type ${type} is already provided by ${existing.plugin}` });
19989
+ } else {
19990
+ channelProviders.set(type, { plugin: configPlugin.name, enabled: configPlugin.enabled });
19991
+ }
19992
+ }
19993
+ pushIfRelevant({
19994
+ level: configPlugin.enabled ? "ok" : "warn",
19995
+ plugin: configPlugin.name,
19996
+ message: configPlugin.enabled ? `plugin is installed and valid; channels: ${channelTypes.length > 0 ? channelTypes.join(", ") : "none"}` : `plugin is installed and valid but disabled; run xacpx plugin enable ${configPlugin.name}`,
19997
+ ...configPlugin.enabled ? {} : { suggestion: `xacpx plugin enable ${configPlugin.name}` }
19998
+ });
19999
+ } catch (error2) {
20000
+ const message = error2 instanceof Error ? error2.message : String(error2);
20001
+ pushIfRelevant({ level: "error", plugin: configPlugin.name, message });
20002
+ }
20003
+ }
20004
+ const builtInChannelTypes = new Set(listKnownChannelIds());
20005
+ for (const channel of input.config.channels) {
20006
+ if (channel.enabled === false)
20007
+ continue;
20008
+ if (builtInChannelTypes.has(channel.type))
20009
+ continue;
20010
+ const provider = channelProviders.get(channel.type);
20011
+ if (!provider) {
20012
+ if (!filterByName) {
20013
+ const suggestedPackage = suggestedPluginPackageForChannel(channel.type);
20014
+ issues.push({
20015
+ level: "error",
20016
+ message: `channel ${channel.type} is configured but no enabled plugin provides it; run xacpx plugin add ${suggestedPackage} or another plugin that provides type "${channel.type}"`,
20017
+ suggestion: `xacpx plugin add ${suggestedPackage}`
20018
+ });
20019
+ }
20020
+ continue;
20021
+ }
20022
+ if (!provider.enabled) {
20023
+ pushIfRelevant({
20024
+ level: "error",
20025
+ plugin: provider.plugin,
20026
+ message: `channel ${channel.type} is configured but provider plugin is disabled; run xacpx plugin enable ${provider.plugin}`,
20027
+ suggestion: `xacpx plugin enable ${provider.plugin}`
20028
+ });
20029
+ }
20030
+ }
20031
+ return issues;
20032
+ }
20033
+ var init_plugin_doctor = __esm(() => {
20034
+ init_channel_scope();
20035
+ init_plugin_loader();
20036
+ init_validate_plugin();
20037
+ init_known_plugins();
20038
+ init_plugin_renames();
20039
+ });
20040
+
19818
20041
  // src/channels/bootstrap.ts
19819
20042
  function bootstrapBuiltinChannels() {
19820
20043
  bootstrapBuiltinChannelFactories();
@@ -20141,6 +20364,8 @@ function parseCommand(input) {
20141
20364
  return { kind: "session.reset" };
20142
20365
  if (command === "/mode" && parts.length === 1)
20143
20366
  return { kind: "mode.show" };
20367
+ if (command === "/model" && parts.length === 1)
20368
+ return { kind: "model.show" };
20144
20369
  if (command === "/replymode" && parts.length === 1)
20145
20370
  return { kind: "replymode.show" };
20146
20371
  if (command === "/config" && parts.length === 1)
@@ -20279,6 +20504,9 @@ function parseCommand(input) {
20279
20504
  if (command === "/mode" && parts[1]) {
20280
20505
  return { kind: "mode.set", modeId: parts[1] };
20281
20506
  }
20507
+ if (command === "/model" && parts[1]) {
20508
+ return { kind: "model.set", modelId: parts.slice(1).join(" ") };
20509
+ }
20282
20510
  if (command === "/replymode" && parts[1] === "reset" && parts.length === 2) {
20283
20511
  return { kind: "replymode.reset" };
20284
20512
  }
@@ -20286,7 +20514,14 @@ function parseCommand(input) {
20286
20514
  return { kind: "replymode.set", replyMode: parts[1] };
20287
20515
  }
20288
20516
  if (command === "/agent" && parts[1] === "add" && parts[2]) {
20289
- return { kind: "agent.add", template: parts[2] };
20517
+ let model = "";
20518
+ for (let index = 3;index < parts.length; index += 1) {
20519
+ if ((parts[index] === "--model" || parts[index] === "-m") && index + 1 < parts.length) {
20520
+ model = parts[index + 1] ?? "";
20521
+ index += 1;
20522
+ }
20523
+ }
20524
+ return model.trim().length > 0 ? { kind: "agent.add", template: parts[2], model: model.trim() } : { kind: "agent.add", template: parts[2] };
20290
20525
  }
20291
20526
  if (command === "/agent" && parts[1] === "rm" && parts[2]) {
20292
20527
  return { kind: "agent.rm", name: parts[2] };
@@ -20364,6 +20599,7 @@ function parseCommand(input) {
20364
20599
  const alias = parts[2];
20365
20600
  let agent3 = "";
20366
20601
  let workspace3 = "";
20602
+ let model = "";
20367
20603
  let invalid = false;
20368
20604
  for (let index = 3;index < parts.length; index += 1) {
20369
20605
  if (parts[index] === "--agent" || parts[index] === "-a") {
@@ -20382,12 +20618,20 @@ function parseCommand(input) {
20382
20618
  workspace3 = parts[index + 1] ?? "";
20383
20619
  index += 1;
20384
20620
  continue;
20621
+ } else if (parts[index] === "--model" || parts[index] === "-m") {
20622
+ if (index + 1 >= parts.length) {
20623
+ invalid = true;
20624
+ break;
20625
+ }
20626
+ model = parts[index + 1] ?? "";
20627
+ index += 1;
20628
+ continue;
20385
20629
  }
20386
20630
  invalid = true;
20387
20631
  break;
20388
20632
  }
20389
20633
  if (!invalid && alias.trim().length > 0 && agent3.trim().length > 0 && workspace3.trim().length > 0) {
20390
- return { kind: "session.new", alias, agent: agent3, workspace: workspace3 };
20634
+ return model.trim().length > 0 ? { kind: "session.new", alias, agent: agent3, workspace: workspace3, model: model.trim() } : { kind: "session.new", alias, agent: agent3, workspace: workspace3 };
20391
20635
  }
20392
20636
  }
20393
20637
  const shortcutTarget = readSessionShortcutTarget(parts, 3);
@@ -20843,6 +21087,7 @@ var init_command_policy = __esm(() => {
20843
21087
  "session.tail",
20844
21088
  "status",
20845
21089
  "mode.show",
21090
+ "model.show",
20846
21091
  "replymode.show",
20847
21092
  "config.show",
20848
21093
  "permission.status",
@@ -20862,6 +21107,7 @@ var init_command_policy = __esm(() => {
20862
21107
  "replymode.set": "/replymode",
20863
21108
  "replymode.reset": "/replymode reset",
20864
21109
  "mode.set": "/mode",
21110
+ "model.set": "/model",
20865
21111
  "permission.mode.set": "/permission",
20866
21112
  "permission.auto.set": "/permission auto",
20867
21113
  "config.set": "/config set",
@@ -21648,6 +21894,19 @@ function modeHelp() {
21648
21894
  examples: ["/mode", "/mode plan"]
21649
21895
  };
21650
21896
  }
21897
+ function modelHelp() {
21898
+ const s = t().session;
21899
+ return {
21900
+ topic: "model",
21901
+ aliases: [],
21902
+ summary: s.modelHelpSummary,
21903
+ commands: [
21904
+ { usage: s.modelHelpCmdShow, description: s.modelHelpCmdShowDesc },
21905
+ { usage: s.modelHelpCmdSet, description: s.modelHelpCmdSetDesc }
21906
+ ],
21907
+ examples: ["/model", "/model gpt-5.2[high]"]
21908
+ };
21909
+ }
21651
21910
  function replyModeHelp() {
21652
21911
  const s = t().session;
21653
21912
  return {
@@ -21721,7 +21980,7 @@ async function handleSessions(context, chatKey) {
21721
21980
  `)
21722
21981
  };
21723
21982
  }
21724
- async function handleSessionNew(context, chatKey, alias, agent3, workspace3) {
21983
+ async function handleSessionNew(context, chatKey, alias, agent3, workspace3, model) {
21725
21984
  const channelId = getChannelIdFromChatKey(chatKey);
21726
21985
  const internalAlias = scopeDisplayAliasToInternal(channelId, alias);
21727
21986
  const existing = context.sessions.getResolvedSessionByInternalAlias(internalAlias);
@@ -21729,6 +21988,10 @@ async function handleSessionNew(context, chatKey, alias, agent3, workspace3) {
21729
21988
  return { text: t().session.sessionAlreadyExists(alias, existing.agent, existing.workspace) };
21730
21989
  }
21731
21990
  const session3 = context.lifecycle.resolveSession(internalAlias, agent3, workspace3, `${workspace3}:${internalAlias}`);
21991
+ const normalizedModel = model?.trim();
21992
+ if (normalizedModel) {
21993
+ session3.model = normalizedModel;
21994
+ }
21732
21995
  const releaseTransportReservation = await context.lifecycle.reserveTransportSession(session3.transportSession);
21733
21996
  try {
21734
21997
  try {
@@ -21741,6 +22004,9 @@ async function handleSessionNew(context, chatKey, alias, agent3, workspace3) {
21741
22004
  return context.recovery.renderSessionCreationError(session3, error2);
21742
22005
  }
21743
22006
  await context.sessions.attachSession(internalAlias, agent3, workspace3, session3.transportSession);
22007
+ if (normalizedModel) {
22008
+ await context.sessions.setSessionModel(internalAlias, normalizedModel);
22009
+ }
21744
22010
  await context.sessions.useSession(chatKey, internalAlias);
21745
22011
  await refreshSessionTransportAgentCommandBestEffort(context, internalAlias, "session.agent_command_refresh_failed");
21746
22012
  await context.logger.info("session.created", "created and selected logical session", {
@@ -21867,6 +22133,43 @@ async function handleModeSet(context, chatKey, modeId) {
21867
22133
  await context.sessions.setCurrentSessionMode(chatKey, modeId);
21868
22134
  return { text: t().session.modeSet(modeId) };
21869
22135
  }
22136
+ async function handleModelShow(context, chatKey) {
22137
+ const session3 = await context.sessions.getCurrentSession(chatKey);
22138
+ if (!session3) {
22139
+ return { text: t().session.noCurrent };
22140
+ }
22141
+ const s = t().session;
22142
+ let current = session3.model;
22143
+ let available = [];
22144
+ try {
22145
+ const queried = await context.interaction.getModelTransportSession(session3);
22146
+ current = queried.current ?? session3.model;
22147
+ available = queried.available;
22148
+ } catch {}
22149
+ const lines = [
22150
+ s.modelHeader,
22151
+ s.modelSessionLabel(toDisplaySessionAlias(session3.alias)),
22152
+ s.modelModelLabel(current ?? s.modelNotSet)
22153
+ ];
22154
+ if (available.length > 0) {
22155
+ lines.push(s.modelAvailableLabel(available.join(", ")));
22156
+ }
22157
+ return { text: lines.join(`
22158
+ `) };
22159
+ }
22160
+ async function handleModelSet(context, chatKey, modelId) {
22161
+ const session3 = await context.sessions.getCurrentSession(chatKey);
22162
+ if (!session3) {
22163
+ return { text: t().session.noCurrent };
22164
+ }
22165
+ try {
22166
+ await context.interaction.setModelTransportSession(session3, modelId);
22167
+ } catch (error2) {
22168
+ return { text: t().session.modelSetFailed(modelId, error2 instanceof Error ? error2.message : String(error2)) };
22169
+ }
22170
+ await context.sessions.setCurrentSessionModel(chatKey, modelId);
22171
+ return { text: t().session.modelSet(modelId) };
22172
+ }
21870
22173
  async function handleReplyModeShow(context, chatKey) {
21871
22174
  const session3 = await context.sessions.getCurrentSession(chatKey);
21872
22175
  if (!session3) {
@@ -22052,7 +22355,7 @@ async function handleSessionRemove(context, chatKey, alias) {
22052
22355
  return { text: lines.join(`
22053
22356
  `) };
22054
22357
  }
22055
- async function promptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
22358
+ async function promptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan) {
22056
22359
  const effectiveReplyMode = resolveEffectiveReplyMode(context.config, chatKey, session3.replyMode);
22057
22360
  if (!session3.replyMode)
22058
22361
  session3.replyMode = effectiveReplyMode;
@@ -22084,7 +22387,7 @@ async function promptWithSession(context, session3, chatKey, text, reply, replyC
22084
22387
  const { promptText, taskIds, groupIds, claimHumanReply } = await preparePromptWithFallback(context, session3, chatKey, text, replyContextToken, accountId);
22085
22388
  try {
22086
22389
  const replyContext = transportReply && context.quota && getChannelIdFromChatKey(chatKey) === "weixin" ? { chatKey, quota: context.quota } : undefined;
22087
- const result = await context.interaction.promptTransportSession(session3, promptText, transportReply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan);
22390
+ const result = await context.interaction.promptTransportSession(session3, promptText, transportReply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan, onPlan);
22088
22391
  if (claimHumanReply) {
22089
22392
  try {
22090
22393
  await context.orchestration?.claimActiveHumanReply?.(claimHumanReply);
@@ -22104,23 +22407,23 @@ async function promptWithSession(context, session3, chatKey, text, reply, replyC
22104
22407
  throw error2;
22105
22408
  }
22106
22409
  }
22107
- async function handlePromptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
22410
+ async function handlePromptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan) {
22108
22411
  try {
22109
- return await promptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
22412
+ return await promptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan);
22110
22413
  } catch (error2) {
22111
22414
  const recovered = await context.recovery.tryRecoverMissingSession(session3, error2);
22112
22415
  if (recovered) {
22113
- return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
22416
+ return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan);
22114
22417
  }
22115
22418
  return context.recovery.renderTransportError(session3, error2);
22116
22419
  }
22117
22420
  }
22118
- async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata) {
22421
+ async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan) {
22119
22422
  const session3 = metadata?.boundSessionAlias ? context.sessions.getResolvedSessionByInternalAlias(metadata.boundSessionAlias) : await context.sessions.getCurrentSession(chatKey);
22120
22423
  if (!session3) {
22121
22424
  return { text: t().session.noCurrent };
22122
22425
  }
22123
- return await handlePromptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
22426
+ return await handlePromptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan);
22124
22427
  }
22125
22428
  function toCoordinatorRouteChatMetadata(metadata) {
22126
22429
  if (!metadata) {
@@ -22793,7 +23096,7 @@ function agentHelp() {
22793
23096
  function handleAgents(context) {
22794
23097
  return { text: context.config ? renderAgents(context.config) : "No config loaded." };
22795
23098
  }
22796
- async function handleAgentAdd(context, templateName) {
23099
+ async function handleAgentAdd(context, templateName, model) {
22797
23100
  const a = t().agent;
22798
23101
  if (!context.config || !context.configStore) {
22799
23102
  return { text: a.noWritableConfig };
@@ -22803,13 +23106,18 @@ async function handleAgentAdd(context, templateName) {
22803
23106
  return { text: a.unsupportedTemplate(listAgentTemplates().join("、")) };
22804
23107
  }
22805
23108
  const existing = context.config.agents[templateName];
23109
+ const normalizedModel = model?.trim();
23110
+ const desired = normalizedModel ? { ...template, model: normalizedModel } : existing?.model ? { ...template, model: existing.model } : template;
22806
23111
  if (existing) {
22807
- if (sameAgentConfig(existing, template)) {
22808
- return { text: a.alreadyExists(templateName) };
23112
+ if (sameAgentConfig(existing, desired)) {
23113
+ if ((existing.model ?? undefined) === (desired.model ?? undefined)) {
23114
+ return { text: a.alreadyExists(templateName) };
23115
+ }
23116
+ } else {
23117
+ return { text: a.alreadyExistsDifferent(templateName) };
22809
23118
  }
22810
- return { text: a.alreadyExistsDifferent(templateName) };
22811
23119
  }
22812
- const updated = await context.configStore.upsertAgent(templateName, template);
23120
+ const updated = await context.configStore.upsertAgent(templateName, desired);
22813
23121
  context.replaceConfig(updated);
22814
23122
  return { text: a.saved(templateName) };
22815
23123
  }
@@ -23173,6 +23481,7 @@ function buildHelpTopics() {
23173
23481
  configHelp(),
23174
23482
  orchestrationHelp(),
23175
23483
  modeHelp(),
23484
+ modelHelp(),
23176
23485
  replyModeHelp(),
23177
23486
  statusHelp(),
23178
23487
  cancelHelp(),
@@ -23603,7 +23912,7 @@ async function resolveNativeTarget(context, chatKey, input) {
23603
23912
  return {
23604
23913
  agent: agent3,
23605
23914
  agentDisplayName: displayAgentName(agent3),
23606
- agentCommand: resolveAgentCommand(agentConfig.driver, agentConfig.command),
23915
+ agentCommand: resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, context.config?.transport.preferLocalAgents !== false),
23607
23916
  workspace: workspaceResolution.workspace,
23608
23917
  workspaceLabel: workspaceResolution.workspaceLabel,
23609
23918
  cwd: workspaceResolution.cwd,
@@ -23841,6 +24150,7 @@ function displayAgentName(agent3) {
23841
24150
  }
23842
24151
  var NATIVE_SESSION_CACHE_TTL_MS;
23843
24152
  var init_native_session_handler = __esm(() => {
24153
+ init_resolve_agent_command();
23844
24154
  init_channel_scope();
23845
24155
  init_workspace_name();
23846
24156
  init_workspace_path();
@@ -23960,7 +24270,7 @@ import { spawn as spawn5 } from "node:child_process";
23960
24270
  import { createWriteStream } from "node:fs";
23961
24271
  import { mkdir as mkdir8 } from "node:fs/promises";
23962
24272
  import { homedir as homedir6 } from "node:os";
23963
- import { join as join14 } from "node:path";
24273
+ import { join as join15 } from "node:path";
23964
24274
  async function autoInstallOptionalDep(pkg, parentPackages, options = {}) {
23965
24275
  const runCli = options.runCli ?? defaultRunCli;
23966
24276
  const openLog = options.openLog ?? defaultLogSink;
@@ -24075,10 +24385,10 @@ ${err.message}`, reason: "spawn" });
24075
24385
  });
24076
24386
  });
24077
24387
  }, defaultLogSink = async () => {
24078
- const dir = join14(coreHomeDir(homedir6()), "logs");
24388
+ const dir = join15(coreHomeDir(homedir6()), "logs");
24079
24389
  await mkdir8(dir, { recursive: true });
24080
24390
  const timestamp = new Date().toISOString().replace(/[:.]/g, "").replace(/-/g, "");
24081
- const path14 = join14(dir, `auto-install-${timestamp}.log`);
24391
+ const path14 = join15(dir, `auto-install-${timestamp}.log`);
24082
24392
  const stream = createWriteStream(path14, { flags: "a" });
24083
24393
  return {
24084
24394
  path: path14,
@@ -24105,7 +24415,7 @@ import { spawn as spawn6 } from "node:child_process";
24105
24415
  import { createRequire as createRequire3 } from "node:module";
24106
24416
  import { access as access3 } from "node:fs/promises";
24107
24417
  import { homedir as homedir7 } from "node:os";
24108
- import { dirname as dirname10, join as join15 } from "node:path";
24418
+ import { dirname as dirname10, join as join16 } from "node:path";
24109
24419
  function deriveParentPackageName(platformPackage) {
24110
24420
  return platformPackage.replace(/-(?:linux|darwin|win32|windows|freebsd|openbsd|sunos|aix)(?:-(?:x64|arm64|ia32|arm|ppc64|s390x))?(?:-(?:baseline|musl|gnu|gnueabihf|musleabihf|msvc))?$/, "");
24111
24421
  }
@@ -24118,7 +24428,7 @@ async function discoverParentPackagePaths(platformPackage, seedPath, deps = {})
24118
24428
  const queryRoot = deps.queryPackageManagerRoot ?? defaultQueryPackageManagerRoot;
24119
24429
  const parentName = deriveParentPackageName(platformPackage);
24120
24430
  const rawCandidates = [];
24121
- const bunGlobalRoot = env.BUN_INSTALL ? join15(env.BUN_INSTALL, "install", "global", "node_modules") : join15(home, ".bun", "install", "global", "node_modules");
24431
+ const bunGlobalRoot = env.BUN_INSTALL ? join16(env.BUN_INSTALL, "install", "global", "node_modules") : join16(home, ".bun", "install", "global", "node_modules");
24122
24432
  const [npmRoot, pnpmRoot, yarnRoot] = await Promise.all([
24123
24433
  queryRoot("npm"),
24124
24434
  queryRoot("pnpm"),
@@ -24141,20 +24451,20 @@ async function discoverParentPackagePaths(platformPackage, seedPath, deps = {})
24141
24451
  if (resolved)
24142
24452
  rawCandidates.push({ path: resolved, manager: classify(resolved) });
24143
24453
  }
24144
- rawCandidates.push({ path: join15(bunGlobalRoot, parentName), manager: "bun" });
24454
+ rawCandidates.push({ path: join16(bunGlobalRoot, parentName), manager: "bun" });
24145
24455
  if (npmRoot)
24146
- rawCandidates.push({ path: join15(npmRoot, parentName), manager: "npm" });
24456
+ rawCandidates.push({ path: join16(npmRoot, parentName), manager: "npm" });
24147
24457
  if (pnpmRoot)
24148
- rawCandidates.push({ path: join15(pnpmRoot, parentName), manager: "pnpm" });
24458
+ rawCandidates.push({ path: join16(pnpmRoot, parentName), manager: "pnpm" });
24149
24459
  if (yarnRoot)
24150
- rawCandidates.push({ path: join15(yarnRoot, parentName), manager: "yarn" });
24460
+ rawCandidates.push({ path: join16(yarnRoot, parentName), manager: "yarn" });
24151
24461
  const seen = new Set;
24152
24462
  const verified = [];
24153
24463
  for (const candidate of rawCandidates) {
24154
24464
  if (seen.has(candidate.path))
24155
24465
  continue;
24156
24466
  seen.add(candidate.path);
24157
- if (await fsExists(join15(candidate.path, "package.json"))) {
24467
+ if (await fsExists(join16(candidate.path, "package.json"))) {
24158
24468
  verified.push(candidate);
24159
24469
  }
24160
24470
  }
@@ -24225,7 +24535,7 @@ async function defaultQueryPackageManagerRoot(tool) {
24225
24535
  const trimmed = stdout2.trim().split(/\r?\n/).pop()?.trim() ?? "";
24226
24536
  if (!trimmed)
24227
24537
  return done(null);
24228
- done(spec.postfix ? join15(trimmed, spec.postfix) : trimmed);
24538
+ done(spec.postfix ? join16(trimmed, spec.postfix) : trimmed);
24229
24539
  });
24230
24540
  });
24231
24541
  }
@@ -24372,9 +24682,12 @@ class CommandRouter {
24372
24682
  this.logger = logger2 ?? createNoopAppLogger();
24373
24683
  this.activeTurns = activeTurns;
24374
24684
  }
24375
- async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, onThought, perfSpan) {
24685
+ async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, onThought, perfSpan, onPlan) {
24376
24686
  const startedAt = Date.now();
24377
- const command = parseCommand(input);
24687
+ let command = parseCommand(input);
24688
+ if (metadata?.channel === "control" && command.kind !== "prompt") {
24689
+ command = { kind: "prompt", text: input.trim() };
24690
+ }
24378
24691
  await this.logger.debug("command.parsed", "parsed inbound command", {
24379
24692
  chatKey,
24380
24693
  kind: command.kind
@@ -24411,7 +24724,7 @@ class CommandRouter {
24411
24724
  case "agents":
24412
24725
  return handleAgents(this.createHandlerContext());
24413
24726
  case "agent.add":
24414
- return await handleAgentAdd(this.createHandlerContext(), command.template);
24727
+ return await handleAgentAdd(this.createHandlerContext(), command.template, command.model);
24415
24728
  case "agent.rm":
24416
24729
  return await handleAgentRemove(this.createHandlerContext(), command.name);
24417
24730
  case "permission.status":
@@ -24435,7 +24748,7 @@ class CommandRouter {
24435
24748
  case "sessions":
24436
24749
  return await handleSessions(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
24437
24750
  case "session.new":
24438
- return await handleSessionNew(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.alias, command.agent, command.workspace);
24751
+ return await handleSessionNew(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.alias, command.agent, command.workspace, command.model);
24439
24752
  case "session.shortcut":
24440
24753
  return await handleSessionShortcut(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.agent, command, false);
24441
24754
  case "session.shortcut.new":
@@ -24456,6 +24769,10 @@ class CommandRouter {
24456
24769
  return await handleModeShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
24457
24770
  case "mode.set":
24458
24771
  return await handleModeSet(this.createSessionHandlerContext(undefined, perfSpan), chatKey, command.modeId);
24772
+ case "model.show":
24773
+ return await handleModelShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
24774
+ case "model.set":
24775
+ return await handleModelSet(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.modelId);
24459
24776
  case "replymode.show":
24460
24777
  return await handleReplyModeShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
24461
24778
  case "replymode.set":
@@ -24525,16 +24842,16 @@ class CommandRouter {
24525
24842
  ...this.sessions.resolveSession(descriptor.alias, descriptor.agent, descriptor.workspace, descriptor.transportSession),
24526
24843
  transient: true
24527
24844
  };
24528
- return await handlePromptWithSession(sessionContext, transientSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
24845
+ return await handlePromptWithSession(sessionContext, transientSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan);
24529
24846
  }
24530
24847
  if (metadata?.scheduledSessionAlias) {
24531
24848
  const scheduledSession = await this.sessions.getSession(metadata.scheduledSessionAlias);
24532
24849
  if (!scheduledSession) {
24533
24850
  throw new Error(`session "${metadata.scheduledSessionAlias}" not found for scheduled prompt`);
24534
24851
  }
24535
- return await handlePromptWithSession(sessionContext, scheduledSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
24852
+ return await handlePromptWithSession(sessionContext, scheduledSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan);
24536
24853
  }
24537
- return await handlePrompt(sessionContext, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata);
24854
+ return await handlePrompt(sessionContext, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan);
24538
24855
  }
24539
24856
  }
24540
24857
  });
@@ -24586,11 +24903,103 @@ class CommandRouter {
24586
24903
  refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
24587
24904
  };
24588
24905
  }
24906
+ async createSessionWithTransport(internalAlias, agent3, workspace3, model) {
24907
+ const existing = this.sessions.getResolvedSessionByInternalAlias(internalAlias);
24908
+ if (existing) {
24909
+ throw new Error(`session "${internalAlias}" already exists`);
24910
+ }
24911
+ const session3 = this.sessions.resolveSession(internalAlias, agent3, workspace3, `${workspace3}:${internalAlias}`);
24912
+ const normalizedModel = model?.trim();
24913
+ if (normalizedModel) {
24914
+ session3.model = normalizedModel;
24915
+ }
24916
+ const release = await this.reserveLogicalTransportSession(session3.transportSession);
24917
+ try {
24918
+ await this.ensureTransportSession(session3);
24919
+ const exists = await this.checkTransportSession(session3);
24920
+ if (!exists) {
24921
+ throw new Error(`transport session "${session3.transportSession}" could not be verified`);
24922
+ }
24923
+ await this.sessions.attachSession(internalAlias, agent3, workspace3, session3.transportSession);
24924
+ if (normalizedModel) {
24925
+ await this.sessions.setSessionModel(internalAlias, normalizedModel);
24926
+ }
24927
+ try {
24928
+ await this.refreshSessionTransportAgentCommand(internalAlias);
24929
+ } catch (error2) {
24930
+ await this.logger.error("session.agent_command_refresh_failed", "failed to refresh session agent command", {
24931
+ alias: internalAlias,
24932
+ error: error2 instanceof Error ? error2.message : String(error2)
24933
+ });
24934
+ }
24935
+ return session3;
24936
+ } finally {
24937
+ await release();
24938
+ }
24939
+ }
24940
+ async listNativeSessionsForControl(agent3, workspace3) {
24941
+ const listAgentSessions = this.transport.listAgentSessions?.bind(this.transport);
24942
+ if (!listAgentSessions)
24943
+ return [];
24944
+ const agentConfig = this.config?.agents[agent3];
24945
+ const workspaceConfig = this.config?.workspaces[workspace3];
24946
+ if (!agentConfig || !workspaceConfig) {
24947
+ throw new Error(`unknown agent "${agent3}" or workspace "${workspace3}"`);
24948
+ }
24949
+ const agentCommand = resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, this.config?.transport.preferLocalAgents !== false);
24950
+ const result = await listAgentSessions({
24951
+ agent: agent3,
24952
+ ...agentCommand ? { agentCommand } : {},
24953
+ cwd: workspaceConfig.cwd,
24954
+ filterCwd: workspaceConfig.cwd
24955
+ });
24956
+ return result?.sessions ?? [];
24957
+ }
24958
+ async attachNativeSessionWithTransport(internalAlias, agent3, workspace3, agentSessionId, nativeMeta) {
24959
+ if (!this.transport.resumeAgentSession) {
24960
+ throw new Error("the active transport does not support native sessions");
24961
+ }
24962
+ const existing = this.sessions.getResolvedSessionByInternalAlias(internalAlias);
24963
+ if (existing) {
24964
+ throw new Error(`session "${internalAlias}" already exists`);
24965
+ }
24966
+ const session3 = this.sessions.resolveSession(internalAlias, agent3, workspace3, `${workspace3}:${internalAlias}`);
24967
+ const release = await this.reserveLogicalTransportSession(session3.transportSession);
24968
+ try {
24969
+ await this.transport.resumeAgentSession(session3, agentSessionId);
24970
+ const exists = await this.checkTransportSession(session3);
24971
+ if (!exists) {
24972
+ throw new Error(`transport session "${session3.transportSession}" could not be verified`);
24973
+ }
24974
+ await this.sessions.attachNativeSession({
24975
+ alias: internalAlias,
24976
+ agent: agent3,
24977
+ workspace: workspace3,
24978
+ transportSession: session3.transportSession,
24979
+ agentSessionId,
24980
+ ...nativeMeta?.title !== undefined ? { title: nativeMeta.title } : {},
24981
+ ...nativeMeta?.updatedAt !== undefined ? { updatedAt: nativeMeta.updatedAt } : {}
24982
+ });
24983
+ try {
24984
+ await this.refreshSessionTransportAgentCommand(internalAlias);
24985
+ } catch (error2) {
24986
+ await this.logger.error("session.native.agent_command_refresh_failed", "failed to refresh native session agent command", {
24987
+ alias: internalAlias,
24988
+ error: error2 instanceof Error ? error2.message : String(error2)
24989
+ });
24990
+ }
24991
+ return session3;
24992
+ } finally {
24993
+ await release();
24994
+ }
24995
+ }
24589
24996
  createSessionInteractionOps(perfSpan) {
24590
24997
  return {
24591
24998
  setModeTransportSession: (session3, modeId) => this.setModeTransportSession(session3, modeId),
24999
+ setModelTransportSession: (session3, modelId) => this.setModelTransportSession(session3, modelId),
25000
+ getModelTransportSession: (session3) => this.getModelTransportSession(session3),
24592
25001
  cancelTransportSession: (session3) => this.cancelTransportSession(session3),
24593
- promptTransportSession: (session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpanOverride) => this.promptTransportSession(session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpanOverride ?? perfSpan)
25002
+ promptTransportSession: (session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpanOverride, onPlan) => this.promptTransportSession(session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpanOverride ?? perfSpan, onPlan)
24594
25003
  };
24595
25004
  }
24596
25005
  createSessionRenderRecoveryOps() {
@@ -24769,7 +25178,7 @@ class CommandRouter {
24769
25178
  async checkTransportSession(session3) {
24770
25179
  return await this.measureTransportCall("has_session", session3, () => this.transport.hasSession(session3));
24771
25180
  }
24772
- async promptTransportSession(session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan) {
25181
+ async promptTransportSession(session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan, onPlan) {
24773
25182
  session3.mcpCoordinatorSession ??= stableCoordinatorSession(session3.transportSession);
24774
25183
  let done = false;
24775
25184
  let abortRequested = false;
@@ -24826,7 +25235,8 @@ class CommandRouter {
24826
25235
  ...media ? { media } : {},
24827
25236
  ...reply ? { onSegment } : {},
24828
25237
  ...onToolEvent ? { onToolEvent } : {},
24829
- ...onThought ? { onThought } : {}
25238
+ ...onThought ? { onThought } : {},
25239
+ ...onPlan ? { onPlan } : {}
24830
25240
  }));
24831
25241
  } catch (error2) {
24832
25242
  localOutcome = isAbortError2(error2) || abortRequested ? "aborted" : "error";
@@ -24845,6 +25255,20 @@ class CommandRouter {
24845
25255
  async setModeTransportSession(session3, modeId) {
24846
25256
  return await this.measureTransportCall("set_mode", session3, () => this.transport.setMode(session3, modeId));
24847
25257
  }
25258
+ async setModelTransportSession(session3, modelId) {
25259
+ if (!this.transport.setModel) {
25260
+ throw new Error("the active transport does not support switching models");
25261
+ }
25262
+ const setModel = this.transport.setModel.bind(this.transport);
25263
+ return await this.measureTransportCall("set_model", session3, () => setModel(session3, modelId));
25264
+ }
25265
+ async getModelTransportSession(session3) {
25266
+ if (!this.transport.getSessionModel) {
25267
+ return { current: session3.model, available: [] };
25268
+ }
25269
+ const getSessionModel = this.transport.getSessionModel.bind(this.transport);
25270
+ return await this.measureTransportCall("get_model", session3, () => getSessionModel(session3));
25271
+ }
24848
25272
  async cancelTransportSession(session3) {
24849
25273
  return await this.measureTransportCall("cancel", session3, () => this.transport.cancel(session3));
24850
25274
  }
@@ -24904,6 +25328,7 @@ function inferTransportKind(transport) {
24904
25328
  }
24905
25329
  var init_command_router = __esm(() => {
24906
25330
  init_app_logger();
25331
+ init_resolve_agent_command();
24907
25332
  init_acpx_session_index();
24908
25333
  init_prompt_output();
24909
25334
  init_parse_command();
@@ -24997,7 +25422,7 @@ class ConsoleAgent {
24997
25422
  ...m.fileName ? { fileName: m.fileName } : {}
24998
25423
  })) : undefined;
24999
25424
  request.perfSpan?.mark("agent.dispatched");
25000
- return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, promptMedia, request.metadata, request.abortSignal, request.onToolEvent, request.onThought, request.perfSpan);
25425
+ return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, promptMedia, request.metadata, request.abortSignal, request.onToolEvent, request.onThought, request.perfSpan, request.onPlan);
25001
25426
  }
25002
25427
  isKnownCommand(text) {
25003
25428
  return isKnownXacpxCommandText(text);
@@ -28811,12 +29236,14 @@ class ScheduledTaskScheduler {
28811
29236
  setIntervalFn;
28812
29237
  clearIntervalFn;
28813
29238
  dispatchTask;
29239
+ onSettled;
28814
29240
  logger;
28815
29241
  intervalHandle = null;
28816
29242
  ticking = false;
28817
29243
  constructor(service, deps) {
28818
29244
  this.service = service;
28819
29245
  this.dispatchTask = deps.dispatchTask;
29246
+ this.onSettled = deps.onSettled;
28820
29247
  this.intervalMs = deps.intervalMs ?? 5000;
28821
29248
  this.dispatchTimeoutMs = deps.dispatchTimeoutMs ?? DEFAULT_DISPATCH_TIMEOUT_MS;
28822
29249
  this.setIntervalFn = deps.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
@@ -28861,6 +29288,7 @@ class ScheduledTaskScheduler {
28861
29288
  });
28862
29289
  try {
28863
29290
  await this.service.markFailed(task.id, error2);
29291
+ this.notifySettled(task);
28864
29292
  } catch (markError) {
28865
29293
  await this.logger?.error("scheduled.dispatch.mark_failed", "markFailed threw; task state may be stale", {
28866
29294
  taskId: task.id,
@@ -28871,6 +29299,7 @@ class ScheduledTaskScheduler {
28871
29299
  }
28872
29300
  try {
28873
29301
  await this.service.markExecuted(task.id);
29302
+ this.notifySettled(task);
28874
29303
  } catch (markError) {
28875
29304
  await this.logger?.error("scheduled.dispatch.mark_executed_failed", "markExecuted threw after a successful dispatch; leaving task state for startup reconciliation", {
28876
29305
  taskId: task.id,
@@ -28882,6 +29311,18 @@ class ScheduledTaskScheduler {
28882
29311
  this.ticking = false;
28883
29312
  }
28884
29313
  }
29314
+ notifySettled(task) {
29315
+ if (!this.onSettled)
29316
+ return;
29317
+ try {
29318
+ this.onSettled(task);
29319
+ } catch (error2) {
29320
+ this.logger?.error("scheduled.on_settled.failed", "scheduled onSettled listener threw", {
29321
+ taskId: task.id,
29322
+ message: error2 instanceof Error ? error2.message : String(error2)
29323
+ });
29324
+ }
29325
+ }
28885
29326
  async dispatchWithTimeout(task) {
28886
29327
  const controller = new AbortController;
28887
29328
  let timer;
@@ -28917,7 +29358,8 @@ function buildScheduledDispatchTask(deps) {
28917
29358
  };
28918
29359
  }
28919
29360
  async function dispatchBound(task, abortSignal, deps) {
28920
- const session3 = await deps.getSession(task.session_alias);
29361
+ const internalAlias = deps.resolveAliasForChat ? await deps.resolveAliasForChat(task.chat_key, task.session_alias) : task.session_alias;
29362
+ const session3 = await deps.getSession(internalAlias);
28921
29363
  if (!session3) {
28922
29364
  throw new Error(`session "${task.session_alias}" not found for scheduled task`);
28923
29365
  }
@@ -28926,6 +29368,7 @@ async function dispatchBound(task, abortSignal, deps) {
28926
29368
  chatKey: task.chat_key,
28927
29369
  taskId: task.id,
28928
29370
  sessionAlias: task.session_alias,
29371
+ executeAt: task.execute_at,
28929
29372
  noticeText,
28930
29373
  promptText: task.message,
28931
29374
  abortSignal,
@@ -28946,6 +29389,7 @@ async function dispatchTemp(task, abortSignal, deps) {
28946
29389
  chatKey: task.chat_key,
28947
29390
  taskId: task.id,
28948
29391
  sessionAlias: task.session_alias,
29392
+ executeAt: task.execute_at,
28949
29393
  sessionDescriptor: { alias, agent: task.agent, workspace: task.workspace, transportSession },
28950
29394
  noticeText,
28951
29395
  promptText: task.message,
@@ -29135,6 +29579,7 @@ class SessionService {
29135
29579
  workspace: workspace3,
29136
29580
  transport_session: transportSession,
29137
29581
  transport_agent_command: sameAgentExisting?.transport_agent_command,
29582
+ model: sameAgentExisting?.model,
29138
29583
  created_at: existing?.created_at ?? new Date().toISOString(),
29139
29584
  last_used_at: new Date().toISOString()
29140
29585
  });
@@ -29505,10 +29950,13 @@ class SessionService {
29505
29950
  if (!workspaceConfig) {
29506
29951
  throw new Error(`session "${session3.alias}" references workspace "${session3.workspace}", but that workspace is no longer registered`);
29507
29952
  }
29953
+ const channelId = getChannelIdFromChatKey(session3.alias);
29954
+ const effectiveReplyMode = channelId === "relay" ? "stream" : undefined;
29508
29955
  return {
29509
29956
  alias: session3.alias,
29510
29957
  agent: session3.agent,
29511
- agentCommand: session3.transport_agent_command ?? resolveAgentCommand(agentConfig.driver, agentConfig.command),
29958
+ agentCommand: session3.transport_agent_command ?? resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, this.config.transport.preferLocalAgents !== false),
29959
+ model: session3.model ?? agentConfig.model,
29512
29960
  workspace: session3.workspace,
29513
29961
  transportSession: session3.transport_session,
29514
29962
  source: session3.source,
@@ -29518,9 +29966,46 @@ class SessionService {
29518
29966
  attachedAt: session3.attached_at,
29519
29967
  modeId: session3.mode_id,
29520
29968
  replyMode: session3.reply_mode,
29969
+ effectiveReplyMode,
29521
29970
  cwd: workspaceConfig.cwd
29522
29971
  };
29523
29972
  }
29973
+ async setSessionModel(alias, modelId) {
29974
+ await this.mutate(async () => {
29975
+ const session3 = this.state.sessions[alias];
29976
+ if (!session3) {
29977
+ throw new Error(`session "${alias}" does not exist`);
29978
+ }
29979
+ const normalized = modelId?.trim();
29980
+ if (normalized) {
29981
+ session3.model = normalized;
29982
+ } else {
29983
+ delete session3.model;
29984
+ }
29985
+ session3.last_used_at = new Date(this.now()).toISOString();
29986
+ await this.persist();
29987
+ });
29988
+ }
29989
+ async setCurrentSessionModel(chatKey, modelId) {
29990
+ await this.mutate(async () => {
29991
+ const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
29992
+ if (!currentAlias) {
29993
+ throw new Error("no current session selected");
29994
+ }
29995
+ const session3 = this.state.sessions[currentAlias];
29996
+ if (!session3) {
29997
+ throw new Error("no current session selected");
29998
+ }
29999
+ const normalized = modelId?.trim();
30000
+ if (normalized) {
30001
+ session3.model = normalized;
30002
+ } else {
30003
+ delete session3.model;
30004
+ }
30005
+ session3.last_used_at = new Date(this.now()).toISOString();
30006
+ await this.persist();
30007
+ });
30008
+ }
29524
30009
  async setSessionTransportAgentCommand(alias, transportAgentCommand) {
29525
30010
  await this.mutate(async () => {
29526
30011
  const session3 = this.state.sessions[alias];
@@ -29565,6 +30050,7 @@ class SessionService {
29565
30050
  attached_at: native ? now : undefined,
29566
30051
  ...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : sameAgentExisting?.transport_agent_command ? { transport_agent_command: sameAgentExisting.transport_agent_command } : {},
29567
30052
  mode_id: sameAgentExisting?.mode_id,
30053
+ model: sameAgentExisting?.model,
29568
30054
  reply_mode: sameAgentExisting?.reply_mode,
29569
30055
  created_at: existingSession?.created_at ?? now,
29570
30056
  last_used_at: now
@@ -29593,6 +30079,7 @@ class SessionService {
29593
30079
  }
29594
30080
  }
29595
30081
  var init_session_service = __esm(() => {
30082
+ init_resolve_agent_command();
29596
30083
  init_i18n();
29597
30084
  init_channel_scope();
29598
30085
  });
@@ -29616,6 +30103,13 @@ function createActiveTurnRegistry() {
29616
30103
  },
29617
30104
  isActive(chatKey, alias) {
29618
30105
  return byChat.get(chatKey)?.has(alias) ?? false;
30106
+ },
30107
+ isActiveAnywhere(alias) {
30108
+ for (const set2 of byChat.values()) {
30109
+ if (set2.has(alias))
30110
+ return true;
30111
+ }
30112
+ return false;
29619
30113
  }
29620
30114
  };
29621
30115
  }
@@ -29724,6 +30218,7 @@ var init_command_hints = __esm(() => {
29724
30218
  config: "/config",
29725
30219
  orchestration: "/delegate",
29726
30220
  mode: "/mode",
30221
+ model: "/model",
29727
30222
  replymode: "/replymode",
29728
30223
  status: "/status",
29729
30224
  cancel: "/cancel",
@@ -29815,8 +30310,9 @@ async function runConsole(paths, deps) {
29815
30310
  throw error2;
29816
30311
  }
29817
30312
  }
29818
- await runtime.reapStaleQueueOwners();
30313
+ const reapPromise = Promise.resolve(runtime.reapStaleQueueOwners()).catch(() => {});
29819
30314
  if (deps.beforeReady) {
30315
+ await reapPromise;
29820
30316
  await deps.beforeReady(runtime);
29821
30317
  }
29822
30318
  if (deps.daemonRuntime) {
@@ -29830,6 +30326,7 @@ async function runConsole(paths, deps) {
29830
30326
  deps.daemonRuntime?.heartbeat().catch(() => {});
29831
30327
  }, deps.heartbeatIntervalMs ?? 30000);
29832
30328
  }
30329
+ await reapPromise;
29833
30330
  const channelStartPromise = deps.channels.startAll({
29834
30331
  agent: runtime.agent,
29835
30332
  abortSignal: shutdownController.signal,
@@ -29840,7 +30337,8 @@ async function runConsole(paths, deps) {
29840
30337
  perfTracer: runtime.perfTracer,
29841
30338
  commandHints: listXacpxCommandHints(),
29842
30339
  coreVersion: XACPX_CORE_VERSION,
29843
- locale: getLocale()
30340
+ locale: getLocale(),
30341
+ control: runtime.control
29844
30342
  });
29845
30343
  channelStartPromise.catch(() => {});
29846
30344
  let channelStartSettled = false;
@@ -29976,6 +30474,10 @@ function encodeBridgePromptThoughtEvent(event) {
29976
30474
  return `${JSON.stringify(event)}
29977
30475
  `;
29978
30476
  }
30477
+ function encodeBridgePromptPlanEvent(event) {
30478
+ return `${JSON.stringify(event)}
30479
+ `;
30480
+ }
29979
30481
  function encodeBridgeSessionProgressEvent(event) {
29980
30482
  return `${JSON.stringify(event)}
29981
30483
  `;
@@ -29985,8 +30487,228 @@ function encodeBridgeSessionNoteEvent(event) {
29985
30487
  `;
29986
30488
  }
29987
30489
 
29988
- // src/transport/acpx-bridge/acpx-bridge-client.ts
30490
+ // src/process/spawn-command.ts
30491
+ function resolveSpawnCommand(command, args) {
30492
+ if (SCRIPT_FILE_PATTERN.test(command)) {
30493
+ return {
30494
+ command: process.execPath,
30495
+ args: [command, ...args]
30496
+ };
30497
+ }
30498
+ return { command, args };
30499
+ }
30500
+ var SCRIPT_FILE_PATTERN;
30501
+ var init_spawn_command = __esm(() => {
30502
+ SCRIPT_FILE_PATTERN = /\.(c|m)?js$/i;
30503
+ });
30504
+
30505
+ // src/transport/acpx-queue-owner-launcher.ts
30506
+ import { createHash as createHash3 } from "node:crypto";
29989
30507
  import { spawn as spawn7 } from "node:child_process";
30508
+ import { readFile as readFile13, unlink } from "node:fs/promises";
30509
+ import { homedir as homedir8 } from "node:os";
30510
+ import { join as join17 } from "node:path";
30511
+ function buildXacpxMcpServerSpec(input) {
30512
+ const { command, args } = splitCommandLine(input.xacpxCommand);
30513
+ return {
30514
+ name: "xacpx",
30515
+ type: "stdio",
30516
+ command,
30517
+ args: [
30518
+ ...args,
30519
+ "mcp-stdio",
30520
+ "--coordinator-session",
30521
+ input.coordinatorSession,
30522
+ ...input.sourceHandle ? ["--source-handle", input.sourceHandle] : ["--internal-session-tools"]
30523
+ ]
30524
+ };
30525
+ }
30526
+ function buildQueueOwnerPayload(input) {
30527
+ return {
30528
+ sessionId: input.sessionId,
30529
+ permissionMode: input.permissionMode,
30530
+ nonInteractivePermissions: input.nonInteractivePermissions,
30531
+ ttlMs: input.ttlMs ?? 300000,
30532
+ maxQueueDepth: input.maxQueueDepth ?? 16,
30533
+ ...Number.isFinite(input.promptRetries) ? { promptRetries: input.promptRetries } : {},
30534
+ ...input.sessionOptions ? { sessionOptions: input.sessionOptions } : {},
30535
+ mcpServers: input.mcpServers
30536
+ };
30537
+ }
30538
+
30539
+ class AcpxQueueOwnerLauncher {
30540
+ acpxCommand;
30541
+ xacpxCommand;
30542
+ spawnOwner;
30543
+ terminateOwner;
30544
+ baseEnv;
30545
+ ttlMs;
30546
+ maxQueueDepth;
30547
+ launchLocks = new Map;
30548
+ constructor(options) {
30549
+ this.acpxCommand = options.acpxCommand;
30550
+ this.xacpxCommand = options.xacpxCommand ?? resolveDefaultXacpxCommand(options.baseEnv ?? process.env);
30551
+ this.spawnOwner = options.spawnOwner ?? defaultQueueOwnerSpawner;
30552
+ this.terminateOwner = options.terminateOwner ?? createDefaultQueueOwnerTerminator(options.acpxCommand);
30553
+ this.baseEnv = options.baseEnv ?? process.env;
30554
+ this.ttlMs = options.ttlMs;
30555
+ this.maxQueueDepth = options.maxQueueDepth;
30556
+ }
30557
+ async launch(input) {
30558
+ const key = input.acpxRecordId;
30559
+ const previous = this.launchLocks.get(key) ?? Promise.resolve();
30560
+ const next = previous.then(() => this.doLaunch(input), () => this.doLaunch(input));
30561
+ const tracked = next.catch(() => {});
30562
+ this.launchLocks.set(key, tracked);
30563
+ tracked.finally(() => {
30564
+ if (this.launchLocks.get(key) === tracked) {
30565
+ this.launchLocks.delete(key);
30566
+ }
30567
+ });
30568
+ return next;
30569
+ }
30570
+ async doLaunch(input) {
30571
+ await this.terminateOwner(input.acpxRecordId);
30572
+ const payload = buildQueueOwnerPayload({
30573
+ sessionId: input.acpxRecordId,
30574
+ permissionMode: input.permissionMode,
30575
+ nonInteractivePermissions: input.nonInteractivePermissions,
30576
+ ttlMs: this.ttlMs,
30577
+ maxQueueDepth: this.maxQueueDepth,
30578
+ ...input.sessionOptions ? { sessionOptions: input.sessionOptions } : {},
30579
+ mcpServers: [buildXacpxMcpServerSpec({
30580
+ xacpxCommand: this.xacpxCommand,
30581
+ coordinatorSession: input.coordinatorSession,
30582
+ ...input.sourceHandle ? { sourceHandle: input.sourceHandle } : {}
30583
+ })]
30584
+ });
30585
+ const spawnSpec = resolveSpawnCommand(this.acpxCommand, ["__queue-owner"]);
30586
+ await this.spawnOwner(spawnSpec.command, spawnSpec.args, {
30587
+ env: {
30588
+ ...stringEnv(this.baseEnv),
30589
+ XACPX_LANG: getLocale(),
30590
+ ACPX_QUEUE_OWNER_PAYLOAD: JSON.stringify(payload)
30591
+ }
30592
+ });
30593
+ }
30594
+ }
30595
+ function splitCommandLine(value) {
30596
+ const parts = [];
30597
+ let current = "";
30598
+ let quote = null;
30599
+ let escaping = false;
30600
+ for (const char of value) {
30601
+ if (escaping) {
30602
+ current += char;
30603
+ escaping = false;
30604
+ continue;
30605
+ }
30606
+ if (char === "\\" && quote !== "'") {
30607
+ escaping = true;
30608
+ continue;
30609
+ }
30610
+ if (quote) {
30611
+ if (char === quote) {
30612
+ quote = null;
30613
+ } else {
30614
+ current += char;
30615
+ }
30616
+ continue;
30617
+ }
30618
+ if (char === "'" || char === '"') {
30619
+ quote = char;
30620
+ continue;
30621
+ }
30622
+ if (/\s/.test(char)) {
30623
+ if (current.length > 0) {
30624
+ parts.push(current);
30625
+ current = "";
30626
+ }
30627
+ continue;
30628
+ }
30629
+ current += char;
30630
+ }
30631
+ if (escaping) {
30632
+ current += "\\";
30633
+ }
30634
+ if (quote) {
30635
+ throw new Error("xacpx MCP command has an unterminated quote");
30636
+ }
30637
+ if (current.length > 0) {
30638
+ parts.push(current);
30639
+ }
30640
+ if (parts.length === 0) {
30641
+ throw new Error("xacpx MCP command must not be empty");
30642
+ }
30643
+ return { command: parts[0], args: parts.slice(1) };
30644
+ }
30645
+ function stringEnv(env) {
30646
+ return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
30647
+ }
30648
+ async function defaultQueueOwnerSpawner(command, args, options) {
30649
+ await new Promise((resolve3, reject) => {
30650
+ const child = spawn7(command, args, {
30651
+ detached: true,
30652
+ stdio: "ignore",
30653
+ env: options.env,
30654
+ windowsHide: true
30655
+ });
30656
+ child.once("error", reject);
30657
+ child.once("spawn", () => {
30658
+ child.unref();
30659
+ resolve3();
30660
+ });
30661
+ });
30662
+ }
30663
+ function createDefaultQueueOwnerTerminator(_acpxCommand) {
30664
+ return async (sessionId) => {
30665
+ await terminateAcpxQueueOwner(sessionId);
30666
+ };
30667
+ }
30668
+ async function terminateAcpxQueueOwner(sessionId) {
30669
+ const lockPath = queueLockFilePath(sessionId);
30670
+ let owner;
30671
+ try {
30672
+ owner = JSON.parse(await readFile13(lockPath, "utf8"));
30673
+ } catch {
30674
+ return;
30675
+ }
30676
+ if (typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0) {
30677
+ await terminateProcessTree(owner.pid, { detachedProcessGroup: true });
30678
+ }
30679
+ await unlink(lockPath).catch(() => {});
30680
+ }
30681
+ function queueLockFilePath(sessionId) {
30682
+ return join17(homedir8(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
30683
+ }
30684
+ function shortHash(value, length) {
30685
+ return createHash3("sha256").update(value).digest("hex").slice(0, length);
30686
+ }
30687
+ function resolveDefaultXacpxCommand(env) {
30688
+ const cliCommand = coreEnv("CLI_COMMAND", env);
30689
+ if (cliCommand?.trim()) {
30690
+ return cliCommand.trim();
30691
+ }
30692
+ const daemonArg0 = coreEnv("DAEMON_ARG0", env);
30693
+ if (daemonArg0?.trim()) {
30694
+ return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(daemonArg0.trim())}`;
30695
+ }
30696
+ if (process.argv[1]) {
30697
+ return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(process.argv[1])}`;
30698
+ }
30699
+ return "xacpx";
30700
+ }
30701
+ function quoteCommandPart(value) {
30702
+ return quoteIfNeeded(value);
30703
+ }
30704
+ var init_acpx_queue_owner_launcher = __esm(() => {
30705
+ init_spawn_command();
30706
+ init_terminate_process_tree();
30707
+ init_i18n();
30708
+ });
30709
+
30710
+ // src/transport/acpx-bridge/acpx-bridge-client.ts
30711
+ import { spawn as spawn8 } from "node:child_process";
29990
30712
  import { fileURLToPath as fileURLToPath4 } from "node:url";
29991
30713
  import { createInterface } from "node:readline";
29992
30714
 
@@ -30053,6 +30775,11 @@ class AcpxBridgeClient {
30053
30775
  type: "prompt.thought",
30054
30776
  text: message.text
30055
30777
  });
30778
+ } else if (message.event === "prompt.plan") {
30779
+ pending.onEvent?.({
30780
+ type: "prompt.plan",
30781
+ entries: message.entries
30782
+ });
30056
30783
  } else if (message.event === "session.progress") {
30057
30784
  pending.onEvent?.({
30058
30785
  type: "session.progress",
@@ -30102,6 +30829,7 @@ class AcpxBridgeClient {
30102
30829
  function buildBridgeSpawnEnv(options = {}) {
30103
30830
  return {
30104
30831
  XACPX_LANG: getLocale(),
30832
+ XACPX_CLI_COMMAND: options.cliCommand ?? resolveDefaultXacpxCommand(process.env),
30105
30833
  XACPX_BRIDGE_ACPX_COMMAND: options.acpxCommand ?? "acpx",
30106
30834
  XACPX_BRIDGE_PERMISSION_MODE: options.permissionMode ?? "approve-all",
30107
30835
  XACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS: options.nonInteractivePermissions ?? "deny",
@@ -30128,7 +30856,7 @@ async function spawnAcpxBridgeClient(options = {}) {
30128
30856
  execPath: process.execPath,
30129
30857
  bridgeEntryPath
30130
30858
  });
30131
- const child = spawn7(spawnSpec.command, spawnSpec.args, {
30859
+ const child = spawn8(spawnSpec.command, spawnSpec.args, {
30132
30860
  cwd: options.cwd ?? process.cwd(),
30133
30861
  env: {
30134
30862
  ...process.env,
@@ -30180,6 +30908,7 @@ var init_acpx_bridge_client = __esm(() => {
30180
30908
  init_errors();
30181
30909
  init_terminate_process_tree();
30182
30910
  init_i18n();
30911
+ init_acpx_queue_owner_launcher();
30183
30912
  });
30184
30913
 
30185
30914
  // src/transport/segment-aggregator.ts
@@ -30382,6 +31111,64 @@ ${headsUpText}`);
30382
31111
  }
30383
31112
  };
30384
31113
  }
31114
+ function createVerbatimReplySink(reply) {
31115
+ let pendingError;
31116
+ const inFlight = new Set;
31117
+ const send = (text) => {
31118
+ const p = reply(text).catch((err) => {
31119
+ if (isQuotaDeferredError(err)) {
31120
+ if (!pendingError) {
31121
+ pendingError = err;
31122
+ }
31123
+ return;
31124
+ }
31125
+ });
31126
+ inFlight.add(p);
31127
+ p.finally(() => {
31128
+ inFlight.delete(p);
31129
+ });
31130
+ };
31131
+ return {
31132
+ feedSegment(segment) {
31133
+ if (segment.length === 0)
31134
+ return;
31135
+ send(segment);
31136
+ },
31137
+ finalize() {
31138
+ return { trailing: "", overflowCount: 0 };
31139
+ },
31140
+ getOverflowCount() {
31141
+ return 0;
31142
+ },
31143
+ getPendingError() {
31144
+ return pendingError;
31145
+ },
31146
+ async drain(opts) {
31147
+ const timeoutMs = opts?.timeoutMs ?? 30000;
31148
+ const deadline = Date.now() + timeoutMs;
31149
+ while (inFlight.size > 0) {
31150
+ const remaining = deadline - Date.now();
31151
+ if (remaining <= 0)
31152
+ return;
31153
+ let timer;
31154
+ const timeout = new Promise((resolve3) => {
31155
+ timer = setTimeout(resolve3, remaining);
31156
+ });
31157
+ try {
31158
+ await Promise.race([
31159
+ Promise.allSettled(Array.from(inFlight)).then(() => {
31160
+ return;
31161
+ }),
31162
+ timeout
31163
+ ]);
31164
+ } finally {
31165
+ if (timer)
31166
+ clearTimeout(timer);
31167
+ }
31168
+ }
31169
+ }
31170
+ };
31171
+ }
30385
31172
  function buildOverflowSummary(overflowCount) {
30386
31173
  if (overflowCount <= 0)
30387
31174
  return;
@@ -30439,7 +31226,8 @@ class AcpxBridgeTransport {
30439
31226
  });
30440
31227
  }
30441
31228
  async prompt(session3, text, reply, replyContext, options) {
30442
- const sink = reply ? createQuotaGatedReplySink({
31229
+ const streamMode = (session3.effectiveReplyMode ?? session3.replyMode) === "stream";
31230
+ const sink = reply ? streamMode ? createVerbatimReplySink(reply) : createQuotaGatedReplySink({
30443
31231
  reply,
30444
31232
  ...replyContext ? { replyContext } : {}
30445
31233
  }) : null;
@@ -30449,6 +31237,8 @@ class AcpxBridgeTransport {
30449
31237
  let toolEventChain = Promise.resolve();
30450
31238
  let thoughtError;
30451
31239
  let thoughtChain = Promise.resolve();
31240
+ let planError;
31241
+ let planChain = Promise.resolve();
30452
31242
  let toolEventMode = resolveToolEventMode(options);
30453
31243
  if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
30454
31244
  toolEventMode = "text";
@@ -30491,10 +31281,21 @@ class AcpxBridgeTransport {
30491
31281
  }
30492
31282
  return;
30493
31283
  }
31284
+ if (event.type === "prompt.plan") {
31285
+ const onPlan = options?.onPlan;
31286
+ if (onPlan) {
31287
+ const entries = event.entries;
31288
+ planChain = planChain.then(() => onPlan(entries)).catch((error2) => {
31289
+ planError ??= error2;
31290
+ });
31291
+ }
31292
+ return;
31293
+ }
30494
31294
  });
30495
31295
  await segmentChain;
30496
31296
  await toolEventChain;
30497
31297
  await thoughtChain;
31298
+ await planChain;
30498
31299
  if (sink) {
30499
31300
  const { overflowCount } = sink.finalize();
30500
31301
  await sink.drain({ timeoutMs: 30000 });
@@ -30512,6 +31313,9 @@ class AcpxBridgeTransport {
30512
31313
  if (thoughtError) {
30513
31314
  throw thoughtError;
30514
31315
  }
31316
+ if (planError) {
31317
+ throw planError;
31318
+ }
30515
31319
  return { text: summary ? `${summary}
30516
31320
 
30517
31321
  ${result.text}` : "" };
@@ -30525,6 +31329,9 @@ ${result.text}` : "" };
30525
31329
  if (thoughtError) {
30526
31330
  throw thoughtError;
30527
31331
  }
31332
+ if (planError) {
31333
+ throw planError;
31334
+ }
30528
31335
  return result;
30529
31336
  }
30530
31337
  async setMode(session3, modeId) {
@@ -30533,6 +31340,15 @@ ${result.text}` : "" };
30533
31340
  modeId
30534
31341
  });
30535
31342
  }
31343
+ async setModel(session3, modelId) {
31344
+ await this.client.request("setModel", {
31345
+ ...this.toParams({ ...session3, model: modelId }),
31346
+ modelId
31347
+ });
31348
+ }
31349
+ async getSessionModel(session3) {
31350
+ return await this.client.request("getSessionModel", this.toParams(session3));
31351
+ }
30536
31352
  async cancel(session3) {
30537
31353
  return await this.client.request("cancel", this.toParams(session3));
30538
31354
  }
@@ -30561,7 +31377,8 @@ ${result.text}` : "" };
30561
31377
  name: session3.transportSession,
30562
31378
  mcpCoordinatorSession: session3.mcpCoordinatorSession,
30563
31379
  mcpSourceHandle: session3.mcpSourceHandle,
30564
- replyMode: session3.replyMode ?? "verbose"
31380
+ replyMode: session3.effectiveReplyMode ?? session3.replyMode ?? "verbose",
31381
+ ...session3.model?.trim() ? { model: session3.model.trim() } : {}
30565
31382
  };
30566
31383
  }
30567
31384
  }
@@ -30569,21 +31386,6 @@ var init_acpx_bridge_transport = __esm(() => {
30569
31386
  init_quota_gated_reply_sink();
30570
31387
  });
30571
31388
 
30572
- // src/process/spawn-command.ts
30573
- function resolveSpawnCommand(command, args) {
30574
- if (SCRIPT_FILE_PATTERN.test(command)) {
30575
- return {
30576
- command: process.execPath,
30577
- args: [command, ...args]
30578
- };
30579
- }
30580
- return { command, args };
30581
- }
30582
- var SCRIPT_FILE_PATTERN;
30583
- var init_spawn_command = __esm(() => {
30584
- SCRIPT_FILE_PATTERN = /\.(c|m)?js$/i;
30585
- });
30586
-
30587
31389
  // src/transport/prompt-media.ts
30588
31390
  import { mkdtemp, open as open4, rm as rm9, writeFile as writeFile7 } from "node:fs/promises";
30589
31391
  import { tmpdir as defaultTmpdir } from "node:os";
@@ -30734,6 +31536,8 @@ function createStreamingPromptState(formatToolCalls = false, options) {
30734
31536
  let toolEventMode;
30735
31537
  let onToolEvent;
30736
31538
  let onThought;
31539
+ let onPlan;
31540
+ let rawStream = false;
30737
31541
  if (options === undefined) {
30738
31542
  toolEventMode = "text";
30739
31543
  onToolEvent = undefined;
@@ -30743,6 +31547,8 @@ function createStreamingPromptState(formatToolCalls = false, options) {
30743
31547
  } else {
30744
31548
  onToolEvent = options.onToolEvent;
30745
31549
  onThought = options.onThought;
31550
+ onPlan = options.onPlan;
31551
+ rawStream = options.rawStream ?? false;
30746
31552
  toolEventMode = resolveToolEventMode({
30747
31553
  toolEventMode: options.mode,
30748
31554
  onToolEvent
@@ -30756,13 +31562,15 @@ function createStreamingPromptState(formatToolCalls = false, options) {
30756
31562
  formatToolCalls,
30757
31563
  emittedToolCallIds: new Set,
30758
31564
  toolEventMode,
31565
+ rawStream,
30759
31566
  onToolEvent,
30760
31567
  onThought,
31568
+ onPlan,
30761
31569
  finalize() {
30762
31570
  if (this.pendingLine.trim().length > 0) {
30763
31571
  parseStreamingChunks(this, this.pendingLine);
30764
31572
  }
30765
- const remaining = this.buffer.trim();
31573
+ const remaining = this.rawStream ? this.buffer : this.buffer.trim();
30766
31574
  this.buffer = "";
30767
31575
  this.pendingLine = "";
30768
31576
  return remaining;
@@ -30794,9 +31602,9 @@ function parseStreamingChunks(state, line) {
30794
31602
  const update = event.params?.update;
30795
31603
  if (!update)
30796
31604
  return;
30797
- if (state.formatToolCalls && (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update")) {
31605
+ if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") {
30798
31606
  const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
30799
- const wantsText = state.toolEventMode === "text" || state.toolEventMode === "both";
31607
+ const wantsText = (state.toolEventMode === "text" || state.toolEventMode === "both") && state.formatToolCalls;
30800
31608
  if (wantsStructured && state.onToolEvent) {
30801
31609
  const toolEvent = buildToolUseEvent(update);
30802
31610
  if (toolEvent)
@@ -30816,6 +31624,12 @@ function parseStreamingChunks(state, line) {
30816
31624
  }
30817
31625
  return;
30818
31626
  }
31627
+ if (update.sessionUpdate === "plan") {
31628
+ const entries = Array.isArray(update.entries) ? update.entries.filter((x) => !!x && typeof x === "object" && typeof x.content === "string" && typeof x.status === "string") : [];
31629
+ if (entries.length > 0)
31630
+ state.onPlan?.(entries);
31631
+ return;
31632
+ }
30819
31633
  const isThoughtChunk = update.sessionUpdate === "agent_thought_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
30820
31634
  if (isThoughtChunk) {
30821
31635
  const chunk2 = update.content.text;
@@ -30832,6 +31646,8 @@ function parseStreamingChunks(state, line) {
30832
31646
  if (chunk.length === 0)
30833
31647
  return;
30834
31648
  state.buffer += chunk;
31649
+ if (state.rawStream)
31650
+ return;
30835
31651
  let boundary;
30836
31652
  while ((boundary = state.buffer.indexOf(`
30837
31653
 
@@ -31009,12 +31825,12 @@ var init_streaming_prompt = __esm(() => {
31009
31825
 
31010
31826
  // src/transport/acpx-cli/node-pty-helper.ts
31011
31827
  import { chmod as chmodFs } from "node:fs/promises";
31012
- import { dirname as dirname11, join as join16 } from "node:path";
31828
+ import { dirname as dirname11, join as join18 } from "node:path";
31013
31829
  function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
31014
31830
  if (platform === "win32") {
31015
31831
  return null;
31016
31832
  }
31017
- return join16(dirname11(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
31833
+ return join18(dirname11(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
31018
31834
  }
31019
31835
  async function ensureNodePtyHelperExecutable(helperPath, chmod5 = chmodFs) {
31020
31836
  if (!helperPath) {
@@ -31031,210 +31847,6 @@ async function ensureNodePtyHelperExecutable(helperPath, chmod5 = chmodFs) {
31031
31847
  }
31032
31848
  var init_node_pty_helper = () => {};
31033
31849
 
31034
- // src/transport/acpx-queue-owner-launcher.ts
31035
- import { createHash as createHash3 } from "node:crypto";
31036
- import { spawn as spawn8 } from "node:child_process";
31037
- import { readFile as readFile13, unlink } from "node:fs/promises";
31038
- import { homedir as homedir8 } from "node:os";
31039
- import { join as join17 } from "node:path";
31040
- function buildXacpxMcpServerSpec(input) {
31041
- const { command, args } = splitCommandLine(input.xacpxCommand);
31042
- return {
31043
- name: "xacpx",
31044
- type: "stdio",
31045
- command,
31046
- args: [
31047
- ...args,
31048
- "mcp-stdio",
31049
- "--coordinator-session",
31050
- input.coordinatorSession,
31051
- ...input.sourceHandle ? ["--source-handle", input.sourceHandle] : ["--internal-session-tools"]
31052
- ]
31053
- };
31054
- }
31055
- function buildQueueOwnerPayload(input) {
31056
- return {
31057
- sessionId: input.sessionId,
31058
- permissionMode: input.permissionMode,
31059
- nonInteractivePermissions: input.nonInteractivePermissions,
31060
- ttlMs: input.ttlMs ?? 300000,
31061
- maxQueueDepth: input.maxQueueDepth ?? 16,
31062
- ...Number.isFinite(input.promptRetries) ? { promptRetries: input.promptRetries } : {},
31063
- ...input.sessionOptions ? { sessionOptions: input.sessionOptions } : {},
31064
- mcpServers: input.mcpServers
31065
- };
31066
- }
31067
-
31068
- class AcpxQueueOwnerLauncher {
31069
- acpxCommand;
31070
- xacpxCommand;
31071
- spawnOwner;
31072
- terminateOwner;
31073
- baseEnv;
31074
- ttlMs;
31075
- maxQueueDepth;
31076
- launchLocks = new Map;
31077
- constructor(options) {
31078
- this.acpxCommand = options.acpxCommand;
31079
- this.xacpxCommand = options.xacpxCommand ?? resolveDefaultXacpxCommand(options.baseEnv ?? process.env);
31080
- this.spawnOwner = options.spawnOwner ?? defaultQueueOwnerSpawner;
31081
- this.terminateOwner = options.terminateOwner ?? createDefaultQueueOwnerTerminator(options.acpxCommand);
31082
- this.baseEnv = options.baseEnv ?? process.env;
31083
- this.ttlMs = options.ttlMs;
31084
- this.maxQueueDepth = options.maxQueueDepth;
31085
- }
31086
- async launch(input) {
31087
- const key = input.acpxRecordId;
31088
- const previous = this.launchLocks.get(key) ?? Promise.resolve();
31089
- const next = previous.then(() => this.doLaunch(input), () => this.doLaunch(input));
31090
- const tracked = next.catch(() => {});
31091
- this.launchLocks.set(key, tracked);
31092
- tracked.finally(() => {
31093
- if (this.launchLocks.get(key) === tracked) {
31094
- this.launchLocks.delete(key);
31095
- }
31096
- });
31097
- return next;
31098
- }
31099
- async doLaunch(input) {
31100
- await this.terminateOwner(input.acpxRecordId);
31101
- const payload = buildQueueOwnerPayload({
31102
- sessionId: input.acpxRecordId,
31103
- permissionMode: input.permissionMode,
31104
- nonInteractivePermissions: input.nonInteractivePermissions,
31105
- ttlMs: this.ttlMs,
31106
- maxQueueDepth: this.maxQueueDepth,
31107
- mcpServers: [buildXacpxMcpServerSpec({
31108
- xacpxCommand: this.xacpxCommand,
31109
- coordinatorSession: input.coordinatorSession,
31110
- ...input.sourceHandle ? { sourceHandle: input.sourceHandle } : {}
31111
- })]
31112
- });
31113
- const spawnSpec = resolveSpawnCommand(this.acpxCommand, ["__queue-owner"]);
31114
- await this.spawnOwner(spawnSpec.command, spawnSpec.args, {
31115
- env: {
31116
- ...stringEnv(this.baseEnv),
31117
- XACPX_LANG: getLocale(),
31118
- ACPX_QUEUE_OWNER_PAYLOAD: JSON.stringify(payload)
31119
- }
31120
- });
31121
- }
31122
- }
31123
- function splitCommandLine(value) {
31124
- const parts = [];
31125
- let current = "";
31126
- let quote = null;
31127
- let escaping = false;
31128
- for (const char of value) {
31129
- if (escaping) {
31130
- current += char;
31131
- escaping = false;
31132
- continue;
31133
- }
31134
- if (char === "\\" && quote !== "'") {
31135
- escaping = true;
31136
- continue;
31137
- }
31138
- if (quote) {
31139
- if (char === quote) {
31140
- quote = null;
31141
- } else {
31142
- current += char;
31143
- }
31144
- continue;
31145
- }
31146
- if (char === "'" || char === '"') {
31147
- quote = char;
31148
- continue;
31149
- }
31150
- if (/\s/.test(char)) {
31151
- if (current.length > 0) {
31152
- parts.push(current);
31153
- current = "";
31154
- }
31155
- continue;
31156
- }
31157
- current += char;
31158
- }
31159
- if (escaping) {
31160
- current += "\\";
31161
- }
31162
- if (quote) {
31163
- throw new Error("xacpx MCP command has an unterminated quote");
31164
- }
31165
- if (current.length > 0) {
31166
- parts.push(current);
31167
- }
31168
- if (parts.length === 0) {
31169
- throw new Error("xacpx MCP command must not be empty");
31170
- }
31171
- return { command: parts[0], args: parts.slice(1) };
31172
- }
31173
- function stringEnv(env) {
31174
- return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
31175
- }
31176
- async function defaultQueueOwnerSpawner(command, args, options) {
31177
- await new Promise((resolve3, reject) => {
31178
- const child = spawn8(command, args, {
31179
- detached: true,
31180
- stdio: "ignore",
31181
- env: options.env,
31182
- windowsHide: true
31183
- });
31184
- child.once("error", reject);
31185
- child.once("spawn", () => {
31186
- child.unref();
31187
- resolve3();
31188
- });
31189
- });
31190
- }
31191
- function createDefaultQueueOwnerTerminator(_acpxCommand) {
31192
- return async (sessionId) => {
31193
- await terminateAcpxQueueOwner(sessionId);
31194
- };
31195
- }
31196
- async function terminateAcpxQueueOwner(sessionId) {
31197
- const lockPath = queueLockFilePath(sessionId);
31198
- let owner;
31199
- try {
31200
- owner = JSON.parse(await readFile13(lockPath, "utf8"));
31201
- } catch {
31202
- return;
31203
- }
31204
- if (typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0) {
31205
- await terminateProcessTree(owner.pid, { detachedProcessGroup: true });
31206
- }
31207
- await unlink(lockPath).catch(() => {});
31208
- }
31209
- function queueLockFilePath(sessionId) {
31210
- return join17(homedir8(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
31211
- }
31212
- function shortHash(value, length) {
31213
- return createHash3("sha256").update(value).digest("hex").slice(0, length);
31214
- }
31215
- function resolveDefaultXacpxCommand(env) {
31216
- const cliCommand = coreEnv("CLI_COMMAND", env);
31217
- if (cliCommand?.trim()) {
31218
- return cliCommand.trim();
31219
- }
31220
- const daemonArg0 = coreEnv("DAEMON_ARG0", env);
31221
- if (daemonArg0?.trim()) {
31222
- return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(daemonArg0.trim())}`;
31223
- }
31224
- if (process.argv[1]) {
31225
- return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(process.argv[1])}`;
31226
- }
31227
- return "xacpx";
31228
- }
31229
- function quoteCommandPart(value) {
31230
- return quoteIfNeeded(value);
31231
- }
31232
- var init_acpx_queue_owner_launcher = __esm(() => {
31233
- init_spawn_command();
31234
- init_terminate_process_tree();
31235
- init_i18n();
31236
- });
31237
-
31238
31850
  // src/transport/permission-mode-flag.ts
31239
31851
  function permissionModeToFlag(permissionMode) {
31240
31852
  switch (permissionMode) {
@@ -31452,13 +32064,15 @@ class AcpxCliTransport {
31452
32064
  const structuredPrompt = await createStructuredPromptFile(text, options?.media);
31453
32065
  const args = this.buildPromptArgs(session3, text, structuredPrompt?.filePath);
31454
32066
  try {
31455
- if (reply || options?.onSegment || options?.onToolEvent || options?.onThought) {
31456
- const formatToolCalls = (session3.replyMode ?? "verbose") === "verbose";
32067
+ if (reply || options?.onSegment || options?.onToolEvent || options?.onThought || options?.onPlan) {
32068
+ const effectiveReplyMode = session3.effectiveReplyMode ?? session3.replyMode;
32069
+ const formatToolCalls = (effectiveReplyMode ?? "verbose") === "verbose";
32070
+ const rawStream = effectiveReplyMode === "stream";
31457
32071
  let toolEventMode = resolveToolEventMode(options);
31458
32072
  if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
31459
32073
  toolEventMode = "text";
31460
32074
  }
31461
- const { result: result2, overflowCount } = await this.runStreamingPrompt(this.command, args, reply, formatToolCalls, toolEventMode, replyContext, options?.onSegment, options?.onToolEvent, options?.onThought);
32075
+ const { result: result2, overflowCount } = await this.runStreamingPrompt(this.command, args, reply, formatToolCalls, toolEventMode, replyContext, options?.onSegment, options?.onToolEvent, options?.onThought, options?.onPlan, rawStream);
31462
32076
  const baseText = getPromptText(result2);
31463
32077
  if (!reply) {
31464
32078
  return { text: baseText };
@@ -31484,6 +32098,34 @@ ${baseText}` : "" };
31484
32098
  modeId
31485
32099
  ]));
31486
32100
  }
32101
+ async setModel(session3, modelId) {
32102
+ await this.run(this.buildArgs({ ...session3, model: modelId }, [
32103
+ "set",
32104
+ "-s",
32105
+ session3.transportSession,
32106
+ "model",
32107
+ modelId
32108
+ ]));
32109
+ }
32110
+ async getSessionModel(session3) {
32111
+ const prefix = ["--format", "json", "--cwd", session3.cwd, ...this.buildPermissionArgs()];
32112
+ const tail2 = ["status", "-s", session3.transportSession];
32113
+ const args = session3.agentCommand ? [...prefix, "--agent", session3.agentCommand, ...tail2] : [...prefix, session3.agent, ...tail2];
32114
+ const result = await this.runCommandWithTimeout(this.runCommand, args);
32115
+ if (result.code !== 0) {
32116
+ const detail = normalizeCommandError(result) ?? `command failed with exit code ${result.code}`;
32117
+ throw new Error(detail);
32118
+ }
32119
+ try {
32120
+ const json = JSON.parse(result.stdout);
32121
+ return {
32122
+ current: typeof json.model === "string" ? json.model : undefined,
32123
+ available: Array.isArray(json.availableModels) ? json.availableModels.filter((m) => typeof m === "string") : []
32124
+ };
32125
+ } catch {
32126
+ return { available: [] };
32127
+ }
32128
+ }
31487
32129
  async cancel(session3) {
31488
32130
  const output = await this.run(this.buildArgs(session3, [
31489
32131
  "cancel",
@@ -31547,7 +32189,8 @@ ${baseText}` : "" };
31547
32189
  coordinatorSession: session3.mcpCoordinatorSession,
31548
32190
  ...session3.mcpSourceHandle ? { sourceHandle: session3.mcpSourceHandle } : {},
31549
32191
  permissionMode: this.permissionMode,
31550
- nonInteractivePermissions: this.nonInteractivePermissions
32192
+ nonInteractivePermissions: this.nonInteractivePermissions,
32193
+ ...session3.model?.trim() ? { sessionOptions: { model: session3.model.trim() } } : {}
31551
32194
  });
31552
32195
  }
31553
32196
  async readSessionRecord(session3) {
@@ -31615,13 +32258,13 @@ ${baseText}` : "" };
31615
32258
  })
31616
32259
  ]);
31617
32260
  }
31618
- async runStreamingPrompt(command, args, reply, formatToolCalls = false, toolEventMode = "text", replyContext, onSegment, onToolEvent, onThought) {
32261
+ async runStreamingPrompt(command, args, reply, formatToolCalls = false, toolEventMode = "text", replyContext, onSegment, onToolEvent, onThought, onPlan, rawStream = false) {
31619
32262
  const hooks = this.streamingHooks;
31620
32263
  const doSpawn = hooks.spawnPrompt ?? ((cmd, spawnArgs) => spawn9(cmd, spawnArgs, { stdio: ["ignore", "pipe", "pipe"] }));
31621
32264
  const setIntervalFn = hooks.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
31622
32265
  const clearIntervalFn = hooks.clearIntervalFn ?? ((timer) => clearInterval(timer));
31623
- const maxSegmentWaitMs = hooks.maxSegmentWaitMs ?? 30000;
31624
- const flushCheckIntervalMs = hooks.flushCheckIntervalMs ?? 5000;
32266
+ const maxSegmentWaitMs = hooks.maxSegmentWaitMs ?? (rawStream ? 200 : 30000);
32267
+ const flushCheckIntervalMs = hooks.flushCheckIntervalMs ?? (rawStream ? 80 : 5000);
31625
32268
  const now = hooks.now ?? (() => Date.now());
31626
32269
  return await new Promise((resolve3, reject) => {
31627
32270
  const spawnSpec = resolveSpawnCommand(command, args);
@@ -31635,10 +32278,14 @@ ${baseText}` : "" };
31635
32278
  let toolEventError;
31636
32279
  let thoughtChain = Promise.resolve();
31637
32280
  let thoughtError;
32281
+ let planChain = Promise.resolve();
32282
+ let planError;
31638
32283
  const userOnToolEvent = onToolEvent;
31639
32284
  const userOnThought = onThought;
32285
+ const userOnPlan = onPlan;
31640
32286
  const state = createStreamingPromptState(formatToolCalls, {
31641
32287
  mode: toolEventMode,
32288
+ rawStream,
31642
32289
  ...userOnToolEvent ? {
31643
32290
  onToolEvent: (event) => {
31644
32291
  toolEventChain = toolEventChain.then(() => userOnToolEvent(event)).catch((error2) => {
@@ -31652,9 +32299,16 @@ ${baseText}` : "" };
31652
32299
  thoughtError ??= error2;
31653
32300
  });
31654
32301
  }
32302
+ } : {},
32303
+ ...userOnPlan ? {
32304
+ onPlan: (entries) => {
32305
+ planChain = planChain.then(() => userOnPlan(entries)).catch((error2) => {
32306
+ planError ??= error2;
32307
+ });
32308
+ }
31655
32309
  } : {}
31656
32310
  });
31657
- const sink = reply ? createQuotaGatedReplySink({
32311
+ const sink = reply ? rawStream ? createVerbatimReplySink(reply) : createQuotaGatedReplySink({
31658
32312
  reply,
31659
32313
  ...replyContext ? { replyContext } : {}
31660
32314
  }) : null;
@@ -31668,7 +32322,7 @@ ${baseText}` : "" };
31668
32322
  lastReplyAt = now();
31669
32323
  };
31670
32324
  const flushBuffer = () => {
31671
- const remaining = state.buffer.trim();
32325
+ const remaining = rawStream ? state.buffer : state.buffer.trim();
31672
32326
  if (remaining.length > 0) {
31673
32327
  state.buffer = "";
31674
32328
  feedSegment(remaining);
@@ -31705,7 +32359,8 @@ ${baseText}` : "" };
31705
32359
  sink?.drain({ timeoutMs: 30000 }) ?? Promise.resolve(),
31706
32360
  segmentChain,
31707
32361
  toolEventChain,
31708
- thoughtChain
32362
+ thoughtChain,
32363
+ planChain
31709
32364
  ]).then(() => {
31710
32365
  const deferred = sink?.getPendingError();
31711
32366
  if (deferred) {
@@ -31724,6 +32379,10 @@ ${baseText}` : "" };
31724
32379
  reject(thoughtError);
31725
32380
  return;
31726
32381
  }
32382
+ if (planError) {
32383
+ reject(planError);
32384
+ return;
32385
+ }
31727
32386
  resolve3({
31728
32387
  result: { code: code ?? 1, stdout: stdout2, stderr },
31729
32388
  overflowCount
@@ -31740,7 +32399,8 @@ ${baseText}` : "" };
31740
32399
  "quiet",
31741
32400
  "--cwd",
31742
32401
  session3.cwd,
31743
- ...this.buildPermissionArgs()
32402
+ ...this.buildPermissionArgs(),
32403
+ ...this.buildModelArgs(session3)
31744
32404
  ];
31745
32405
  if (session3.agentCommand) {
31746
32406
  return [...prefix, "--agent", session3.agentCommand, ...tail2];
@@ -31762,6 +32422,7 @@ ${baseText}` : "" };
31762
32422
  "--cwd",
31763
32423
  session3.cwd,
31764
32424
  ...this.buildPermissionArgs(),
32425
+ ...this.buildModelArgs(session3),
31765
32426
  ...this.buildQueueOwnerTtlArgs()
31766
32427
  ];
31767
32428
  const tail2 = promptFile ? ["prompt", "-s", session3.transportSession, "--file", promptFile] : ["prompt", "-s", session3.transportSession, text];
@@ -31770,6 +32431,10 @@ ${baseText}` : "" };
31770
32431
  }
31771
32432
  return [...prefix, session3.agent, ...tail2];
31772
32433
  }
32434
+ buildModelArgs(session3) {
32435
+ const model = session3.model?.trim();
32436
+ return model ? ["--model", model] : [];
32437
+ }
31773
32438
  buildQueueOwnerTtlArgs() {
31774
32439
  if (typeof this.queueOwnerTtlSeconds !== "number" || !Number.isFinite(this.queueOwnerTtlSeconds)) {
31775
32440
  return [];
@@ -31966,7 +32631,7 @@ function workerBindingReapTargets(orchestration3, config4) {
31966
32631
  if (!cwd) {
31967
32632
  continue;
31968
32633
  }
31969
- const agentCommand = resolveAgentCommand(agentConfig.driver, agentConfig.command);
32634
+ const agentCommand = resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, config4.transport.preferLocalAgents !== false);
31970
32635
  targets.push({
31971
32636
  agent: binding.targetAgent,
31972
32637
  ...agentCommand ? { agentCommand } : {},
@@ -31976,7 +32641,9 @@ function workerBindingReapTargets(orchestration3, config4) {
31976
32641
  }
31977
32642
  return targets;
31978
32643
  }
31979
- var init_collect_reap_targets = () => {};
32644
+ var init_collect_reap_targets = __esm(() => {
32645
+ init_resolve_agent_command();
32646
+ });
31980
32647
 
31981
32648
  // src/channels/channel-registry.ts
31982
32649
  var exports_channel_registry = {};
@@ -32263,6 +32930,730 @@ var init_quota_manager = __esm(() => {
32263
32930
  DEFAULT_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
32264
32931
  });
32265
32932
 
32933
+ // src/control/control-event-bus.ts
32934
+ function createControlEventBus(logger2) {
32935
+ const listeners = new Set;
32936
+ return {
32937
+ subscribe(listener) {
32938
+ listeners.add(listener);
32939
+ return () => {
32940
+ listeners.delete(listener);
32941
+ };
32942
+ },
32943
+ emit(event) {
32944
+ for (const listener of [...listeners]) {
32945
+ try {
32946
+ listener(event);
32947
+ } catch (error2) {
32948
+ logger2?.error("control.event_listener_failed", "control event listener threw", {
32949
+ eventType: event.type,
32950
+ error: error2 instanceof Error ? error2.message : String(error2)
32951
+ });
32952
+ }
32953
+ }
32954
+ }
32955
+ };
32956
+ }
32957
+
32958
+ // src/transport/native-session-history.ts
32959
+ import { readFile as readFile14 } from "node:fs/promises";
32960
+ import { homedir as homedir9 } from "node:os";
32961
+ import { join as join19 } from "node:path";
32962
+ function classifyToolKind(name) {
32963
+ const n = name.toLowerCase();
32964
+ if (/(^|[^a-z])(read|cat|view|open)([^a-z]|$)/.test(n))
32965
+ return "read";
32966
+ if (/(grep|search|find|glob|ripgrep|rg)/.test(n))
32967
+ return "search";
32968
+ if (/(edit|write|apply|patch|replace|create)/.test(n))
32969
+ return "edit";
32970
+ if (/(bash|shell|exec|run|terminal|command)/.test(n))
32971
+ return "execute";
32972
+ if (/(think|reason|plan)/.test(n))
32973
+ return "think";
32974
+ return "other";
32975
+ }
32976
+ function textOfUserContent(content) {
32977
+ if (!Array.isArray(content))
32978
+ return "";
32979
+ const out = [];
32980
+ for (const c of content) {
32981
+ if (c && typeof c === "object") {
32982
+ const o = c;
32983
+ if (typeof o.Text === "string")
32984
+ out.push(o.Text);
32985
+ else if (o.Mention && typeof o.Mention.content === "string")
32986
+ out.push(String(o.Mention.content));
32987
+ else if (o.Image)
32988
+ out.push("[image]");
32989
+ else if (o.Audio)
32990
+ out.push("[audio]");
32991
+ }
32992
+ }
32993
+ return out.join(`
32994
+ `);
32995
+ }
32996
+ function toolResultText(result) {
32997
+ if (!result || typeof result !== "object")
32998
+ return { isError: false };
32999
+ const r = result;
33000
+ const isError = r.is_error === true;
33001
+ if (typeof r.output === "string")
33002
+ return { text: r.output, isError };
33003
+ const content = r.content;
33004
+ if (content && typeof content.Text === "string")
33005
+ return { text: content.Text, isError };
33006
+ return { isError };
33007
+ }
33008
+ function toolUseEventOf(toolUse, result) {
33009
+ const id = typeof toolUse.id === "string" ? toolUse.id : "";
33010
+ const name = typeof toolUse.name === "string" ? toolUse.name : "tool";
33011
+ const rawInput = toolUse.input ?? (typeof toolUse.raw_input === "string" ? safeParse4(toolUse.raw_input) : undefined);
33012
+ const res = toolResultText(result);
33013
+ return {
33014
+ toolCallId: id,
33015
+ toolName: name,
33016
+ kind: classifyToolKind(name),
33017
+ ...rawInput !== undefined ? { rawInput } : {},
33018
+ ...res.text !== undefined ? { rawOutput: res.text } : {},
33019
+ status: result ? res.isError ? "error" : "success" : "success"
33020
+ };
33021
+ }
33022
+ function safeParse4(s) {
33023
+ try {
33024
+ return JSON.parse(s);
33025
+ } catch {
33026
+ return s;
33027
+ }
33028
+ }
33029
+ function mapAcpxMessagesToHistory(raw) {
33030
+ if (!Array.isArray(raw))
33031
+ return [];
33032
+ const out = [];
33033
+ for (const msg of raw) {
33034
+ if (msg === "Resume" || !msg || typeof msg !== "object")
33035
+ continue;
33036
+ const m = msg;
33037
+ if (m.User && typeof m.User === "object") {
33038
+ const text = textOfUserContent(m.User.content);
33039
+ out.push({ role: "user", text });
33040
+ continue;
33041
+ }
33042
+ if (m.Agent && typeof m.Agent === "object") {
33043
+ const agent3 = m.Agent;
33044
+ const toolResults = agent3.tool_results ?? {};
33045
+ const parts = [];
33046
+ const textChunks = [];
33047
+ for (const c of Array.isArray(agent3.content) ? agent3.content : []) {
33048
+ if (!c || typeof c !== "object")
33049
+ continue;
33050
+ const o = c;
33051
+ if (typeof o.Text === "string") {
33052
+ parts.push({ kind: "text", text: o.Text });
33053
+ textChunks.push(o.Text);
33054
+ } else if (o.Thinking && typeof o.Thinking.text === "string")
33055
+ parts.push({ kind: "reasoning", text: String(o.Thinking.text) });
33056
+ else if (typeof o.RedactedThinking === "string")
33057
+ parts.push({ kind: "reasoning", text: "[redacted reasoning]" });
33058
+ else if (o.ToolUse && typeof o.ToolUse === "object") {
33059
+ const tu = o.ToolUse;
33060
+ const result = typeof tu.id === "string" ? toolResults[tu.id] : undefined;
33061
+ parts.push({ kind: "tool", tool: toolUseEventOf(tu, result) });
33062
+ }
33063
+ }
33064
+ out.push({ role: "agent", text: textChunks.join(`
33065
+
33066
+ `), ...parts.length ? { parts } : {} });
33067
+ }
33068
+ }
33069
+ return out;
33070
+ }
33071
+ async function readNativeSessionHistory(opts) {
33072
+ try {
33073
+ const dir = opts.sessionsDir ?? join19(opts.homeDir ?? homedir9(), ".acpx", "sessions");
33074
+ const indexRaw = await readFile14(join19(dir, "index.json"), "utf8").catch(() => null);
33075
+ if (!indexRaw)
33076
+ return [];
33077
+ const index = JSON.parse(indexRaw);
33078
+ const candidates = (index.entries ?? []).filter((e) => e.acpSessionId === opts.agentSessionId && (!opts.agentCommand || !e.agentCommand || e.agentCommand === opts.agentCommand));
33079
+ let best = [];
33080
+ for (const entry of candidates) {
33081
+ if (!entry.file)
33082
+ continue;
33083
+ const recRaw = await readFile14(join19(dir, entry.file), "utf8").catch(() => null);
33084
+ if (!recRaw)
33085
+ continue;
33086
+ const record3 = JSON.parse(recRaw);
33087
+ const mapped = mapAcpxMessagesToHistory(record3.messages);
33088
+ if (mapped.length > best.length)
33089
+ best = mapped;
33090
+ }
33091
+ return best;
33092
+ } catch {
33093
+ return [];
33094
+ }
33095
+ }
33096
+ var init_native_session_history = () => {};
33097
+
33098
+ // src/control/workspace-fs.ts
33099
+ import { execFile } from "node:child_process";
33100
+ import { promisify } from "node:util";
33101
+ import { homedir as homedir10 } from "node:os";
33102
+ import { readdir as readdir3, realpath, stat as stat3, open as open5 } from "node:fs/promises";
33103
+ import { isAbsolute as isAbsolute3, relative, resolve as resolve3, sep } from "node:path";
33104
+ function expandHome2(p) {
33105
+ if (p === "~")
33106
+ return homedir10();
33107
+ if (p.startsWith("~/") || p.startsWith("~" + sep))
33108
+ return resolve3(homedir10(), p.slice(2));
33109
+ return p;
33110
+ }
33111
+
33112
+ class WorkspaceFs {
33113
+ listWorkspaces;
33114
+ constructor(listWorkspaces) {
33115
+ this.listWorkspaces = listWorkspaces;
33116
+ }
33117
+ async resolve(workspace3, relPath) {
33118
+ const ref = this.listWorkspaces().find((w) => w.name === workspace3);
33119
+ if (!ref)
33120
+ throw new Error("unknown-workspace");
33121
+ if (relPath && isAbsolute3(relPath))
33122
+ throw new Error("path-must-be-relative");
33123
+ let root;
33124
+ try {
33125
+ root = await realpath(expandHome2(ref.cwd));
33126
+ } catch {
33127
+ throw new Error("workspace-root-missing");
33128
+ }
33129
+ const requested = resolve3(root, relPath ?? ".");
33130
+ let abs;
33131
+ try {
33132
+ abs = await realpath(requested);
33133
+ } catch {
33134
+ throw new Error("not-found");
33135
+ }
33136
+ if (abs !== root && !abs.startsWith(root + sep))
33137
+ throw new Error("path-escapes-workspace");
33138
+ const rel = abs === root ? "" : relative(root, abs).split(sep).join("/");
33139
+ return { root, abs, rel };
33140
+ }
33141
+ async listDirectory(workspace3, relPath) {
33142
+ const { abs, rel } = await this.resolve(workspace3, relPath);
33143
+ const dirents = await readdir3(abs, { withFileTypes: true });
33144
+ const entries = [];
33145
+ for (const d of dirents.slice(0, MAX_ENTRIES)) {
33146
+ if (d.isDirectory()) {
33147
+ entries.push({ name: d.name, type: "dir" });
33148
+ } else if (d.isFile()) {
33149
+ let size;
33150
+ try {
33151
+ size = (await stat3(resolve3(abs, d.name))).size;
33152
+ } catch {
33153
+ size = undefined;
33154
+ }
33155
+ entries.push({ name: d.name, type: "file", size });
33156
+ }
33157
+ }
33158
+ entries.sort((a, b) => a.type !== b.type ? a.type === "dir" ? -1 : 1 : a.name.localeCompare(b.name));
33159
+ return { workspace: workspace3, path: rel, entries };
33160
+ }
33161
+ async readFile(workspace3, relPath) {
33162
+ const { abs, rel } = await this.resolve(workspace3, relPath);
33163
+ const info = await stat3(abs);
33164
+ if (!info.isFile())
33165
+ throw new Error("not-a-file");
33166
+ const fh = await open5(abs, "r");
33167
+ try {
33168
+ const buf = Buffer.alloc(Math.min(info.size, FILE_READ_CAP));
33169
+ const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
33170
+ const slice = buf.subarray(0, bytesRead);
33171
+ const binary = slice.includes(0);
33172
+ return {
33173
+ workspace: workspace3,
33174
+ path: rel,
33175
+ content: binary ? "" : slice.toString("utf8"),
33176
+ size: info.size,
33177
+ truncated: info.size > FILE_READ_CAP,
33178
+ binary
33179
+ };
33180
+ } finally {
33181
+ await fh.close();
33182
+ }
33183
+ }
33184
+ async search(workspace3, query) {
33185
+ const { root } = await this.resolve(workspace3, undefined);
33186
+ const needle = query.trim().toLowerCase();
33187
+ const matches = [];
33188
+ if (!needle)
33189
+ return { workspace: workspace3, query, matches, truncated: false };
33190
+ let scanned = 0;
33191
+ let truncated = false;
33192
+ const queue = [root];
33193
+ while (queue.length) {
33194
+ const dir = queue.shift();
33195
+ let dirents;
33196
+ try {
33197
+ dirents = await readdir3(dir, { withFileTypes: true });
33198
+ } catch {
33199
+ continue;
33200
+ }
33201
+ for (const d of dirents) {
33202
+ if (++scanned > SEARCH_MAX_SCAN) {
33203
+ truncated = true;
33204
+ break;
33205
+ }
33206
+ if (d.isSymbolicLink())
33207
+ continue;
33208
+ if (d.isDirectory()) {
33209
+ if (!SEARCH_SKIP_DIRS.has(d.name))
33210
+ queue.push(resolve3(dir, d.name));
33211
+ } else if (d.isFile()) {
33212
+ const rel = relative(root, resolve3(dir, d.name)).split(sep).join("/");
33213
+ if (rel.toLowerCase().includes(needle)) {
33214
+ matches.push(rel);
33215
+ if (matches.length >= SEARCH_MAX_RESULTS) {
33216
+ truncated = true;
33217
+ break;
33218
+ }
33219
+ }
33220
+ }
33221
+ }
33222
+ if (truncated)
33223
+ break;
33224
+ }
33225
+ matches.sort();
33226
+ return { workspace: workspace3, query, matches, truncated };
33227
+ }
33228
+ async gitDiff(workspace3, relPath) {
33229
+ const { root, rel } = await this.resolve(workspace3, relPath);
33230
+ try {
33231
+ await execFileAsync("git", ["-C", root, "rev-parse", "--is-inside-work-tree"], { maxBuffer: GIT_MAX_BUFFER });
33232
+ } catch {
33233
+ throw new Error("not-a-git-repo");
33234
+ }
33235
+ const files = [];
33236
+ try {
33237
+ const { stdout: stdout2 } = await execFileAsync("git", ["-C", root, "status", "--porcelain"], { maxBuffer: GIT_MAX_BUFFER });
33238
+ for (const line of stdout2.split(`
33239
+ `)) {
33240
+ if (!line)
33241
+ continue;
33242
+ const status = line.slice(0, 2);
33243
+ let path15 = line.slice(3);
33244
+ const arrow = path15.indexOf(" -> ");
33245
+ if (arrow >= 0)
33246
+ path15 = path15.slice(arrow + 4);
33247
+ files.push({ path: path15, status });
33248
+ }
33249
+ } catch {}
33250
+ const diffArgs = (base) => ["-C", root, ...base, ...rel ? ["--", rel] : []];
33251
+ let diff = "";
33252
+ try {
33253
+ diff = (await execFileAsync("git", diffArgs(["diff", "HEAD"]), { maxBuffer: GIT_MAX_BUFFER })).stdout;
33254
+ } catch {
33255
+ try {
33256
+ diff = (await execFileAsync("git", diffArgs(["diff"]), { maxBuffer: GIT_MAX_BUFFER })).stdout;
33257
+ } catch {
33258
+ diff = "";
33259
+ }
33260
+ }
33261
+ const truncated = diff.length > DIFF_CAP;
33262
+ return { workspace: workspace3, files, diff: truncated ? diff.slice(0, DIFF_CAP) : diff, truncated };
33263
+ }
33264
+ }
33265
+ var execFileAsync, MAX_ENTRIES = 2000, FILE_READ_CAP, DIFF_CAP, GIT_MAX_BUFFER, SEARCH_MAX_RESULTS = 200, SEARCH_MAX_SCAN = 20000, SEARCH_SKIP_DIRS;
33266
+ var init_workspace_fs = __esm(() => {
33267
+ execFileAsync = promisify(execFile);
33268
+ FILE_READ_CAP = 256 * 1024;
33269
+ DIFF_CAP = 512 * 1024;
33270
+ GIT_MAX_BUFFER = 32 * 1024 * 1024;
33271
+ SEARCH_SKIP_DIRS = new Set([".git", "node_modules"]);
33272
+ });
33273
+
33274
+ // src/control/control-service.ts
33275
+ class ControlService {
33276
+ deps;
33277
+ constructor(deps) {
33278
+ this.deps = deps;
33279
+ }
33280
+ workspaceFs = new WorkspaceFs(() => this.deps.workspaces.list().map((w) => ({ name: w.name, cwd: w.cwd })));
33281
+ listDirectory(workspace3, path15) {
33282
+ return this.workspaceFs.listDirectory(workspace3, path15);
33283
+ }
33284
+ readWorkspaceFile(workspace3, path15) {
33285
+ return this.workspaceFs.readFile(workspace3, path15);
33286
+ }
33287
+ workspaceGitDiff(workspace3, path15) {
33288
+ return this.workspaceFs.gitDiff(workspace3, path15);
33289
+ }
33290
+ searchWorkspace(workspace3, query) {
33291
+ return this.workspaceFs.search(workspace3, query);
33292
+ }
33293
+ async getSessionModel(chatKey, alias) {
33294
+ const session3 = await this.resolveControlSession(chatKey, alias);
33295
+ if (!session3)
33296
+ return { available: [] };
33297
+ if (!this.deps.transport.getSessionModel)
33298
+ return { current: session3.model, available: [] };
33299
+ return await this.deps.transport.getSessionModel(session3);
33300
+ }
33301
+ async setSessionModel(chatKey, alias, modelId) {
33302
+ const session3 = await this.resolveControlSession(chatKey, alias);
33303
+ if (!session3)
33304
+ throw new Error("session not found");
33305
+ if (!this.deps.transport.setModel)
33306
+ throw new Error("the active transport does not support switching models");
33307
+ await this.deps.transport.setModel(session3, modelId);
33308
+ await this.deps.sessions.setSessionModel(session3.alias, modelId);
33309
+ }
33310
+ async resolveControlSession(chatKey, alias) {
33311
+ const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
33312
+ return await this.deps.sessions.getSession(internalAlias);
33313
+ }
33314
+ get events() {
33315
+ return this.deps.events;
33316
+ }
33317
+ listSessions(chatKey) {
33318
+ const channelId = getChannelIdFromChatKey(chatKey);
33319
+ return this.deps.sessions.listAllResolvedSessions().filter((session3) => isSessionAliasVisibleInChannel(session3.alias, channelId)).map((session3) => ({
33320
+ alias: toDisplaySessionAlias(session3.alias),
33321
+ agent: session3.agent,
33322
+ workspace: session3.workspace,
33323
+ transportSession: session3.transportSession,
33324
+ running: this.deps.activeTurns.isActiveAnywhere(session3.alias)
33325
+ }));
33326
+ }
33327
+ async listNativeSessions(_chatKey, agent3, workspace3) {
33328
+ const sessions = await this.deps.listNativeSessions(agent3, workspace3);
33329
+ return sessions.map((s) => ({
33330
+ sessionId: s.sessionId,
33331
+ title: s.title ?? null,
33332
+ ...s.updatedAt !== undefined ? { updatedAt: s.updatedAt } : {},
33333
+ ...s.cwd !== undefined ? { cwd: s.cwd } : {}
33334
+ }));
33335
+ }
33336
+ async createSession(chatKey, alias, agent3, workspace3, agentSessionId, model) {
33337
+ const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
33338
+ let nativeHistory = [];
33339
+ if (agentSessionId) {
33340
+ try {
33341
+ nativeHistory = await readNativeSessionHistory({ agentSessionId });
33342
+ } catch {}
33343
+ }
33344
+ const session3 = agentSessionId ? await this.deps.attachNativeSessionWithTransport(internalAlias, agent3, workspace3, agentSessionId) : await this.deps.createSessionWithTransport(internalAlias, agent3, workspace3, model);
33345
+ this.deps.events.emit({ type: "sessions-changed" });
33346
+ if (nativeHistory.length > 0) {
33347
+ this.deps.events.emit({ type: "session-history", chatKey, sessionAlias: alias, messages: nativeHistory });
33348
+ }
33349
+ return {
33350
+ alias: toDisplaySessionAlias(session3.alias),
33351
+ agent: session3.agent,
33352
+ workspace: session3.workspace,
33353
+ transportSession: session3.transportSession,
33354
+ running: false
33355
+ };
33356
+ }
33357
+ async removeSession(chatKey, alias) {
33358
+ const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
33359
+ const result = await this.deps.sessions.removeSession(internalAlias);
33360
+ this.deps.events.emit({ type: "sessions-changed" });
33361
+ return result;
33362
+ }
33363
+ listAgents() {
33364
+ return this.deps.agents.list();
33365
+ }
33366
+ listWorkspaces() {
33367
+ return this.deps.workspaces.list();
33368
+ }
33369
+ createWorkspace(name, cwd, description) {
33370
+ return this.deps.workspaces.create(name, cwd, description);
33371
+ }
33372
+ listAgentCatalog() {
33373
+ return this.deps.agents.catalog();
33374
+ }
33375
+ createAgent(name, driver) {
33376
+ return this.deps.agents.create(name, driver);
33377
+ }
33378
+ async removeAgent(name) {
33379
+ if (this.deps.sessions.listAllResolvedSessions().some((s) => s.agent === name)) {
33380
+ throw new Error(`agent "${name}" is in use by an existing session`);
33381
+ }
33382
+ await this.deps.agents.remove(name);
33383
+ }
33384
+ async removeWorkspace(name) {
33385
+ if (this.deps.sessions.listAllResolvedSessions().some((s) => s.workspace === name)) {
33386
+ throw new Error(`workspace "${name}" is in use by an existing session`);
33387
+ }
33388
+ await this.deps.workspaces.remove(name);
33389
+ }
33390
+ listScheduledTasks(chatKey) {
33391
+ return this.deps.scheduled.listRecentForChat(chatKey);
33392
+ }
33393
+ async createScheduledTask(input) {
33394
+ const task = await this.deps.scheduled.createTask(input);
33395
+ this.deps.events.emit({ type: "scheduled-changed", chatKey: input.chatKey });
33396
+ return task;
33397
+ }
33398
+ async cancelScheduledTask(id, chatKey) {
33399
+ const cancelled = await this.deps.scheduled.cancelPending(id, chatKey);
33400
+ if (cancelled) {
33401
+ this.deps.events.emit({ type: "scheduled-changed", chatKey });
33402
+ }
33403
+ return cancelled;
33404
+ }
33405
+ listOrchestrationTasks(filter) {
33406
+ return this.deps.orchestration.listTasks(filter);
33407
+ }
33408
+ getOrchestrationTask(taskId) {
33409
+ return this.deps.orchestration.getTask(taskId);
33410
+ }
33411
+ async cancelOrchestrationTask(input) {
33412
+ const task = await this.deps.orchestration.requestTaskCancellation(input);
33413
+ this.deps.events.emit({ type: "orchestration-changed" });
33414
+ return task;
33415
+ }
33416
+ inFlight = new Map;
33417
+ async prompt(input) {
33418
+ return this.executeTurn({
33419
+ chatKey: input.chatKey,
33420
+ sessionAlias: input.sessionAlias,
33421
+ text: input.text,
33422
+ senderId: input.senderId,
33423
+ ...input.isOwner !== undefined ? { isOwner: input.isOwner } : {},
33424
+ ...input.accountId !== undefined ? { accountId: input.accountId } : {}
33425
+ });
33426
+ }
33427
+ async runScheduledTurn(input) {
33428
+ return this.executeTurn({
33429
+ chatKey: input.chatKey,
33430
+ sessionAlias: input.sessionAlias,
33431
+ text: input.promptText,
33432
+ senderId: "scheduler",
33433
+ isOwner: true,
33434
+ ...input.accountId !== undefined ? { accountId: input.accountId } : {},
33435
+ ...input.abortSignal ? { abortSignal: input.abortSignal } : {},
33436
+ turnStarted: { prompt: input.promptText, scheduled: { taskId: input.taskId, executeAt: input.executeAt } }
33437
+ });
33438
+ }
33439
+ async executeTurn(params) {
33440
+ const key = turnKey(params.chatKey, params.sessionAlias);
33441
+ const existing = this.inFlight.get(key);
33442
+ if (existing) {
33443
+ if (!existing.controller.signal.aborted) {
33444
+ return { ok: false, errorMessage: "turn-already-running" };
33445
+ }
33446
+ await raceWithTimeout(existing.settled, CANCEL_DRAIN_TIMEOUT_MS);
33447
+ if (this.inFlight.has(key)) {
33448
+ return { ok: false, errorMessage: "turn-already-running" };
33449
+ }
33450
+ }
33451
+ const controller = new AbortController;
33452
+ if (params.abortSignal) {
33453
+ if (params.abortSignal.aborted)
33454
+ controller.abort();
33455
+ else
33456
+ params.abortSignal.addEventListener("abort", () => controller.abort(), { once: true });
33457
+ }
33458
+ let resolveSettled;
33459
+ const settled = new Promise((resolve4) => {
33460
+ resolveSettled = resolve4;
33461
+ });
33462
+ this.inFlight.set(key, { controller, settled });
33463
+ try {
33464
+ await this.deps.sessions.useSession(params.chatKey, params.sessionAlias);
33465
+ } catch (error2) {
33466
+ this.inFlight.delete(key);
33467
+ resolveSettled();
33468
+ return { ok: false, errorMessage: toErrorMessage(error2) };
33469
+ }
33470
+ this.deps.events.emit({
33471
+ type: "turn-started",
33472
+ chatKey: params.chatKey,
33473
+ sessionAlias: params.sessionAlias,
33474
+ ...params.turnStarted?.prompt ? { prompt: params.turnStarted.prompt } : {},
33475
+ ...params.turnStarted?.scheduled ? { scheduled: params.turnStarted.scheduled } : {}
33476
+ });
33477
+ let streamMode = false;
33478
+ try {
33479
+ const resolved = await this.resolveControlSession(params.chatKey, params.sessionAlias);
33480
+ streamMode = (resolved?.effectiveReplyMode ?? resolved?.replyMode) === "stream";
33481
+ } catch {}
33482
+ let emittedChunk = false;
33483
+ const emitChunk = (chunk) => {
33484
+ if (!chunk)
33485
+ return;
33486
+ this.deps.events.emit({
33487
+ type: "turn-output",
33488
+ chatKey: params.chatKey,
33489
+ sessionAlias: params.sessionAlias,
33490
+ chunk: !streamMode && emittedChunk ? `
33491
+
33492
+ ${chunk}` : chunk
33493
+ });
33494
+ emittedChunk = true;
33495
+ };
33496
+ try {
33497
+ const response = await this.deps.agent.chat({
33498
+ accountId: params.accountId ?? "control",
33499
+ conversationId: params.chatKey,
33500
+ text: params.text,
33501
+ metadata: buildControlMetadata(params.senderId, params.isOwner),
33502
+ abortSignal: controller.signal,
33503
+ reply: async (chunk) => {
33504
+ emitChunk(chunk);
33505
+ },
33506
+ onToolEvent: (event) => {
33507
+ this.deps.events.emit({
33508
+ type: "tool-event",
33509
+ chatKey: params.chatKey,
33510
+ sessionAlias: params.sessionAlias,
33511
+ event
33512
+ });
33513
+ },
33514
+ onThought: (chunk) => {
33515
+ this.deps.events.emit({
33516
+ type: "turn-thought",
33517
+ chatKey: params.chatKey,
33518
+ sessionAlias: params.sessionAlias,
33519
+ chunk
33520
+ });
33521
+ },
33522
+ onPlan: (entries) => {
33523
+ this.deps.events.emit({
33524
+ type: "plan",
33525
+ chatKey: params.chatKey,
33526
+ sessionAlias: params.sessionAlias,
33527
+ entries
33528
+ });
33529
+ }
33530
+ });
33531
+ if (response.text) {
33532
+ emitChunk(response.text);
33533
+ }
33534
+ this.deps.events.emit({
33535
+ type: "turn-finished",
33536
+ chatKey: params.chatKey,
33537
+ sessionAlias: params.sessionAlias,
33538
+ ok: true
33539
+ });
33540
+ return { ok: true, text: response.text };
33541
+ } catch (error2) {
33542
+ const errorMessage = toErrorMessage(error2);
33543
+ this.deps.events.emit({
33544
+ type: "turn-finished",
33545
+ chatKey: params.chatKey,
33546
+ sessionAlias: params.sessionAlias,
33547
+ ok: false,
33548
+ errorMessage,
33549
+ ...controller.signal.aborted ? { cancelled: true } : {}
33550
+ });
33551
+ return { ok: false, errorMessage };
33552
+ } finally {
33553
+ this.inFlight.delete(key);
33554
+ resolveSettled();
33555
+ }
33556
+ }
33557
+ cancelTurn(chatKey, sessionAlias) {
33558
+ const entry = this.inFlight.get(turnKey(chatKey, sessionAlias));
33559
+ if (!entry) {
33560
+ return false;
33561
+ }
33562
+ entry.controller.abort();
33563
+ return true;
33564
+ }
33565
+ async executeCommand(input) {
33566
+ const chunks = [];
33567
+ const response = await this.deps.agent.chat({
33568
+ accountId: input.accountId ?? "control",
33569
+ conversationId: input.chatKey,
33570
+ text: input.text,
33571
+ metadata: buildControlMetadata(input.senderId, input.isOwner),
33572
+ reply: async (chunk) => {
33573
+ chunks.push(chunk);
33574
+ }
33575
+ });
33576
+ if (response.text) {
33577
+ chunks.push(response.text);
33578
+ }
33579
+ return chunks.join(`
33580
+ `);
33581
+ }
33582
+ }
33583
+ async function raceWithTimeout(promise2, ms) {
33584
+ let timer;
33585
+ const timeout = new Promise((resolve4) => {
33586
+ timer = setTimeout(resolve4, ms);
33587
+ });
33588
+ try {
33589
+ await Promise.race([promise2, timeout]);
33590
+ } finally {
33591
+ if (timer)
33592
+ clearTimeout(timer);
33593
+ }
33594
+ }
33595
+ function turnKey(chatKey, sessionAlias) {
33596
+ return `${chatKey} ${sessionAlias}`;
33597
+ }
33598
+ function toErrorMessage(error2) {
33599
+ return error2 instanceof Error ? error2.message : String(error2);
33600
+ }
33601
+ function buildControlMetadata(senderId, isOwner) {
33602
+ return {
33603
+ channel: "control",
33604
+ chatType: "direct",
33605
+ senderId,
33606
+ ...isOwner === undefined ? {} : { isOwner }
33607
+ };
33608
+ }
33609
+ var CANCEL_DRAIN_TIMEOUT_MS = 5000;
33610
+ var init_control_service = __esm(() => {
33611
+ init_channel_scope();
33612
+ init_native_session_history();
33613
+ init_workspace_fs();
33614
+ });
33615
+
33616
+ // src/config/agent-catalog.ts
33617
+ import { existsSync as existsSync3 } from "node:fs";
33618
+ import { delimiter as delimiter2, join as join20 } from "node:path";
33619
+ function isBinaryOnPath(binary) {
33620
+ const path15 = process.env.PATH ?? "";
33621
+ const exts = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
33622
+ for (const dir of path15.split(delimiter2)) {
33623
+ if (!dir)
33624
+ continue;
33625
+ for (const ext of exts) {
33626
+ try {
33627
+ if (existsSync3(join20(dir, binary + ext)))
33628
+ return true;
33629
+ } catch {}
33630
+ }
33631
+ }
33632
+ return false;
33633
+ }
33634
+ function listAgentCatalog(config4, probe = isBinaryOnPath) {
33635
+ const agents = config4.agents ?? {};
33636
+ const driverConfigured = (driver) => Object.entries(agents).some(([name, a]) => name === driver || a.driver === driver);
33637
+ return listAgentTemplates().map((driver) => {
33638
+ let installed;
33639
+ if (BUILTIN_DRIVERS.has(driver)) {
33640
+ installed = "builtin";
33641
+ } else {
33642
+ const binary = DRIVER_BINARIES[driver] ?? driver;
33643
+ installed = probe(binary) ? "yes" : "unknown";
33644
+ }
33645
+ return { driver, configured: driverConfigured(driver), installed };
33646
+ });
33647
+ }
33648
+ var BUILTIN_DRIVERS, DRIVER_BINARIES;
33649
+ var init_agent_catalog = __esm(() => {
33650
+ init_agent_templates();
33651
+ BUILTIN_DRIVERS = new Set(["codex", "claude"]);
33652
+ DRIVER_BINARIES = {
33653
+ cursor: "cursor-agent"
33654
+ };
33655
+ });
33656
+
32266
33657
  // src/main.ts
32267
33658
  var exports_main = {};
32268
33659
  __export(exports_main, {
@@ -32273,8 +33664,8 @@ __export(exports_main, {
32273
33664
  buildApp: () => buildApp
32274
33665
  });
32275
33666
  import { randomUUID as randomUUID3 } from "node:crypto";
32276
- import { homedir as homedir9 } from "node:os";
32277
- import { dirname as dirname12, join as join18 } from "node:path";
33667
+ import { homedir as homedir11 } from "node:os";
33668
+ import { dirname as dirname12, join as join21 } from "node:path";
32278
33669
  import { fileURLToPath as fileURLToPath5 } from "node:url";
32279
33670
  function startProgressHeartbeat(orchestration3, config4, logger2, channel) {
32280
33671
  const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
@@ -32550,7 +33941,7 @@ async function buildApp(paths, deps = {}) {
32550
33941
  return {
32551
33942
  alias: input.workerSession,
32552
33943
  agent: input.targetAgent,
32553
- agentCommand: resolveAgentCommand(agentConfig.driver, agentConfig.command),
33944
+ agentCommand: resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, config4.transport.preferLocalAgents !== false),
32554
33945
  workspace: input.workspace,
32555
33946
  transportSession: input.workerSession,
32556
33947
  cwd: input.cwd
@@ -32764,9 +34155,52 @@ async function buildApp(paths, deps = {}) {
32764
34155
  });
32765
34156
  const router3 = new CommandRouter(sessions, transport, config4, configStore, logger2, undefined, orchestration3, quota, scheduledService, deps.channel?.supportsScheduledMessages ? { supportsScheduledMessages: deps.channel.supportsScheduledMessages.bind(deps.channel) } : undefined, deps.channel?.nativeSessionListFormat ? deps.channel.nativeSessionListFormat.bind(deps.channel) : undefined, activeTurns);
32766
34157
  const agent3 = new ConsoleAgent(router3, logger2);
34158
+ const controlEvents = createControlEventBus(logger2);
34159
+ const control = new ControlService({
34160
+ agent: agent3,
34161
+ sessions,
34162
+ transport,
34163
+ createSessionWithTransport: (internalAlias, agent4, workspace3, model) => router3.createSessionWithTransport(internalAlias, agent4, workspace3, model),
34164
+ listNativeSessions: (agent4, workspace3) => router3.listNativeSessionsForControl(agent4, workspace3),
34165
+ attachNativeSessionWithTransport: (internalAlias, agent4, workspace3, agentSessionId, nativeMeta) => router3.attachNativeSessionWithTransport(internalAlias, agent4, workspace3, agentSessionId, nativeMeta),
34166
+ activeTurns,
34167
+ scheduled: scheduledService,
34168
+ orchestration: orchestration3,
34169
+ events: controlEvents,
34170
+ agents: {
34171
+ list: () => Object.entries(config4.agents).map(([name, agentConfig]) => ({ name, driver: agentConfig.driver })),
34172
+ catalog: () => listAgentCatalog(config4),
34173
+ create: async (name, driver) => {
34174
+ const updated = await configStore.upsertAgent(name, { driver });
34175
+ replaceRuntimeConfig(config4, updated);
34176
+ return { name, driver };
34177
+ },
34178
+ remove: async (name) => {
34179
+ const updated = await configStore.removeAgent(name);
34180
+ replaceRuntimeConfig(config4, updated);
34181
+ }
34182
+ },
34183
+ workspaces: {
34184
+ list: () => Object.entries(config4.workspaces).map(([name, workspace3]) => ({
34185
+ name,
34186
+ cwd: workspace3.cwd,
34187
+ ...workspace3.description ? { description: workspace3.description } : {}
34188
+ })),
34189
+ create: async (name, cwd, description) => {
34190
+ const updated = await configStore.upsertWorkspace(name, cwd, description);
34191
+ replaceRuntimeConfig(config4, updated);
34192
+ return { name, cwd, ...description ? { description } : {} };
34193
+ },
34194
+ remove: async (name) => {
34195
+ const updated = await configStore.removeWorkspace(name);
34196
+ replaceRuntimeConfig(config4, updated);
34197
+ }
34198
+ }
34199
+ });
32767
34200
  const scheduledScheduler = new ScheduledTaskScheduler(scheduledService, {
32768
34201
  dispatchTask: buildScheduledDispatchTask({
32769
34202
  getSession: (alias) => sessions.getSession(alias),
34203
+ resolveAliasForChat: (chatKey, alias) => sessions.resolveAliasForChat(chatKey, alias),
32770
34204
  resolveSession: (alias, agent4, workspace3, transportSession) => sessions.resolveSession(alias, agent4, workspace3, transportSession),
32771
34205
  sendScheduledMessage: async (input) => {
32772
34206
  if (!deps.channel?.sendScheduledMessage) {
@@ -32777,6 +34211,7 @@ async function buildApp(paths, deps = {}) {
32777
34211
  ...transport.removeSession ? { removeSession: (session3) => transport.removeSession(session3) } : {},
32778
34212
  logger: logger2
32779
34213
  }),
34214
+ onSettled: (task) => controlEvents.emit({ type: "scheduled-changed", chatKey: task.chat_key }),
32780
34215
  logger: logger2
32781
34216
  });
32782
34217
  const reapWarmQueueOwners = async (phase) => {
@@ -32826,6 +34261,7 @@ async function buildApp(paths, deps = {}) {
32826
34261
  service: scheduledService,
32827
34262
  scheduler: scheduledScheduler
32828
34263
  },
34264
+ control,
32829
34265
  reapStaleQueueOwners: () => reapWarmQueueOwners("startup"),
32830
34266
  dispose: async () => {
32831
34267
  scheduledScheduler.stop();
@@ -32889,8 +34325,8 @@ async function main() {
32889
34325
  }
32890
34326
  }
32891
34327
  async function prepareChannelMedia(configPath, config4) {
32892
- const runtimeDir = join18(dirname12(configPath), "runtime");
32893
- const mediaRootDir = join18(runtimeDir, "media");
34328
+ const runtimeDir = join21(dirname12(configPath), "runtime");
34329
+ const mediaRootDir = join21(runtimeDir, "media");
32894
34330
  const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
32895
34331
  await mediaStore.cleanupExpired().catch((error2) => {
32896
34332
  console.error("[xacpx] media cleanup failed:", error2 instanceof Error ? error2.message : String(error2));
@@ -32899,16 +34335,16 @@ async function prepareChannelMedia(configPath, config4) {
32899
34335
  return { mediaStore, channelDeps: { mediaStore, allowedMediaRoots } };
32900
34336
  }
32901
34337
  function resolveRuntimePaths() {
32902
- const home = process.env.HOME ?? homedir9();
34338
+ const home = process.env.HOME ?? homedir11();
32903
34339
  if (!home) {
32904
34340
  throw new Error("Unable to resolve the current user home directory");
32905
34341
  }
32906
- const configPath = coreEnv("CONFIG") ?? join18(coreHomeDir(home), "config.json");
32907
- const runtimeDir = join18(dirname12(configPath), "runtime");
34342
+ const configPath = coreEnv("CONFIG") ?? join21(coreHomeDir(home), "config.json");
34343
+ const runtimeDir = join21(dirname12(configPath), "runtime");
32908
34344
  return {
32909
34345
  configPath,
32910
- statePath: coreEnv("STATE") ?? join18(coreHomeDir(home), "state.json"),
32911
- perfLogPath: join18(runtimeDir, "perf.log"),
34346
+ statePath: coreEnv("STATE") ?? join21(coreHomeDir(home), "state.json"),
34347
+ perfLogPath: join21(runtimeDir, "perf.log"),
32912
34348
  orchestrationSocketPath: coreEnv("ORCHESTRATION_SOCKET") ?? resolveDaemonOrchestrationSocketPath(runtimeDir)
32913
34349
  };
32914
34350
  }
@@ -32920,13 +34356,13 @@ function resolveBridgeEntryPath() {
32920
34356
  }
32921
34357
  function resolveAppLogPath(configPath) {
32922
34358
  const rootDir = dirname12(configPath);
32923
- const runtimeDir = join18(rootDir, "runtime");
32924
- return join18(runtimeDir, "app.log");
34359
+ const runtimeDir = join21(rootDir, "runtime");
34360
+ return join21(runtimeDir, "app.log");
32925
34361
  }
32926
34362
  function resolvePerfLogPath(configPath) {
32927
34363
  const rootDir = dirname12(configPath);
32928
- const runtimeDir = join18(rootDir, "runtime");
32929
- return join18(runtimeDir, "perf.log");
34364
+ const runtimeDir = join21(rootDir, "runtime");
34365
+ return join21(runtimeDir, "perf.log");
32930
34366
  }
32931
34367
  function resolveOrchestrationSocketPathFromConfigPath(configPath) {
32932
34368
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
@@ -32942,6 +34378,7 @@ var init_main = __esm(async () => {
32942
34378
  init_ensure_config();
32943
34379
  init_load_config();
32944
34380
  init_resolve_acpx_command();
34381
+ init_resolve_agent_command();
32945
34382
  init_console_agent();
32946
34383
  init_app_logger();
32947
34384
  init_daemon_files();
@@ -32969,6 +34406,8 @@ var init_main = __esm(async () => {
32969
34406
  init_inbound();
32970
34407
  init_render_text();
32971
34408
  init_quota_manager();
34409
+ init_control_service();
34410
+ init_agent_catalog();
32972
34411
  init_perf_tracer();
32973
34412
  init_bootstrap();
32974
34413
  init_i18n();
@@ -33022,7 +34461,7 @@ function buildDetails(metadata, version2, verbose) {
33022
34461
  }
33023
34462
  async function defaultRunVersion(command) {
33024
34463
  const spawnSpec = resolveSpawnCommand(command, ["--version"]);
33025
- return await new Promise((resolve3, reject) => {
34464
+ return await new Promise((resolve4, reject) => {
33026
34465
  const child = spawn11(spawnSpec.command, spawnSpec.args, {
33027
34466
  stdio: ["ignore", "pipe", "pipe"]
33028
34467
  });
@@ -33039,7 +34478,7 @@ async function defaultRunVersion(command) {
33039
34478
  if (code === 0) {
33040
34479
  const version2 = stdout2.trim() || stderr.trim();
33041
34480
  if (version2.length > 0) {
33042
- resolve3(version2);
34481
+ resolve4(version2);
33043
34482
  return;
33044
34483
  }
33045
34484
  }
@@ -33161,24 +34600,36 @@ var init_config_check = __esm(async () => {
33161
34600
  });
33162
34601
 
33163
34602
  // src/doctor/checks/daemon-check.ts
34603
+ import { readdir as readdir4, readFile as readFile15, rm as rm10 } from "node:fs/promises";
33164
34604
  import { fileURLToPath as fileURLToPath6 } from "node:url";
33165
- import { homedir as homedir10 } from "node:os";
34605
+ import { homedir as homedir12 } from "node:os";
34606
+ import { join as join22 } from "node:path";
33166
34607
  async function checkDaemon(options = {}) {
33167
- const home = options.home ?? process.env.HOME ?? homedir10();
34608
+ const home = options.home ?? process.env.HOME ?? homedir12();
33168
34609
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
33169
34610
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
33170
34611
  home,
33171
34612
  ...runtimeDir ? { runtimeDir } : {}
33172
34613
  });
34614
+ const isProcessRunning = options.isProcessRunning ?? isProcessAlive;
34615
+ const listConsumerLocks = options.listConsumerLocks ?? defaultListConsumerLocks;
34616
+ const readConsumerLock = options.readConsumerLock ?? defaultReadConsumerLock;
34617
+ const removeConsumerLock = options.removeConsumerLock ?? defaultRemoveConsumerLock;
33173
34618
  const controller = createDaemonController(paths, {
33174
34619
  processExecPath: options.processExecPath ?? process.execPath,
33175
34620
  cliEntryPath: options.cliEntryPath ?? resolveCliEntryPath(),
33176
34621
  cwd: options.cwd ?? process.cwd(),
33177
34622
  env: options.env ?? process.env,
33178
- isProcessRunning: options.isProcessRunning ?? defaultIsProcessRunning5
34623
+ isProcessRunning
33179
34624
  });
33180
34625
  try {
33181
34626
  const status = await controller.getStatus();
34627
+ const staleLockFix = status.state === "stopped" ? await detectStaleConsumerLockFix(paths.runtimeDir, {
34628
+ isProcessRunning,
34629
+ listConsumerLocks,
34630
+ readConsumerLock,
34631
+ removeConsumerLock
34632
+ }) : undefined;
33182
34633
  switch (status.state) {
33183
34634
  case "running":
33184
34635
  return {
@@ -33200,6 +34651,7 @@ async function checkDaemon(options = {}) {
33200
34651
  summary: status.stale ? "daemon was stopped and stale runtime files were cleared" : "daemon is not running",
33201
34652
  details: status.stale ? ["stale runtime files were cleared"] : undefined,
33202
34653
  suggestions: ["run: xacpx start"],
34654
+ ...staleLockFix ? { fixes: [staleLockFix] } : {},
33203
34655
  metadata: {
33204
34656
  paths,
33205
34657
  status
@@ -33246,25 +34698,210 @@ async function checkDaemon(options = {}) {
33246
34698
  };
33247
34699
  }
33248
34700
  }
33249
- function defaultIsProcessRunning5(pid) {
34701
+ async function detectStaleConsumerLockFix(runtimeDir, deps) {
34702
+ const lockFiles = await deps.listConsumerLocks(runtimeDir);
34703
+ const stalePaths = [];
34704
+ for (const fileName of lockFiles) {
34705
+ if (!fileName.endsWith(CONSUMER_LOCK_SUFFIX)) {
34706
+ continue;
34707
+ }
34708
+ const lockPath = join22(runtimeDir, fileName);
34709
+ const lock2 = await deps.readConsumerLock(lockPath);
34710
+ if (lock2 && !deps.isProcessRunning(lock2.pid)) {
34711
+ stalePaths.push(lockPath);
34712
+ }
34713
+ }
34714
+ if (stalePaths.length === 0) {
34715
+ return;
34716
+ }
34717
+ return {
34718
+ id: "daemon.clear-stale-lock",
34719
+ title: "remove stale consumer lock(s)",
34720
+ run: async () => {
34721
+ const removed = [];
34722
+ let skipped = 0;
34723
+ for (const lockPath of stalePaths) {
34724
+ const lock2 = await deps.readConsumerLock(lockPath);
34725
+ if (!lock2 || deps.isProcessRunning(lock2.pid)) {
34726
+ skipped += 1;
34727
+ continue;
34728
+ }
34729
+ await deps.removeConsumerLock(lockPath);
34730
+ removed.push(lockPath);
34731
+ }
34732
+ const skippedNote = skipped > 0 ? `; left ${skipped} no-longer-stale lock(s) alone` : "";
34733
+ return {
34734
+ ok: true,
34735
+ message: removed.length > 0 ? `removed ${removed.length} stale consumer lock(s): ${removed.join(", ")}${skippedNote}` : `no locks removed${skippedNote}`
34736
+ };
34737
+ }
34738
+ };
34739
+ }
34740
+ async function defaultListConsumerLocks(runtimeDir) {
33250
34741
  try {
33251
- process.kill(pid, 0);
33252
- return true;
34742
+ return await readdir4(runtimeDir);
33253
34743
  } catch {
33254
- return false;
34744
+ return [];
33255
34745
  }
33256
34746
  }
34747
+ async function defaultReadConsumerLock(path15) {
34748
+ try {
34749
+ const raw = await readFile15(path15, "utf8");
34750
+ const parsed = JSON.parse(raw);
34751
+ return typeof parsed.pid === "number" ? { pid: parsed.pid } : null;
34752
+ } catch {
34753
+ return null;
34754
+ }
34755
+ }
34756
+ async function defaultRemoveConsumerLock(path15) {
34757
+ await rm10(path15, { force: true });
34758
+ }
33257
34759
  function resolveCliEntryPath() {
33258
34760
  return process.argv[1] ?? fileURLToPath6(import.meta.url);
33259
34761
  }
33260
34762
  function formatError5(error2) {
33261
34763
  return error2 instanceof Error ? error2.message : String(error2);
33262
34764
  }
34765
+ var CONSUMER_LOCK_SUFFIX = "-consumer.lock.json";
33263
34766
  var init_daemon_check = __esm(() => {
33264
34767
  init_create_daemon_controller();
33265
34768
  init_daemon_files();
33266
34769
  });
33267
34770
 
34771
+ // src/doctor/checks/logs-check.ts
34772
+ import { stat as stat4, readdir as readdir5 } from "node:fs/promises";
34773
+ import { basename as basename3, join as join23 } from "node:path";
34774
+ import { homedir as homedir13 } from "node:os";
34775
+ async function checkLogs(options = {}) {
34776
+ const home = options.home ?? process.env.HOME ?? homedir13();
34777
+ const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
34778
+ const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
34779
+ home,
34780
+ ...runtimeDir ? { runtimeDir } : {}
34781
+ });
34782
+ const probe = options.probe ?? createLogsFsProbe();
34783
+ const singleFileWarnBytes = options.singleFileWarnBytes ?? DEFAULT_SINGLE_FILE_WARN_BYTES;
34784
+ const totalWarnBytes = options.totalWarnBytes ?? DEFAULT_TOTAL_WARN_BYTES;
34785
+ let entries;
34786
+ try {
34787
+ const dirStat = await probe.stat(paths.runtimeDir);
34788
+ if (!dirStat.isDirectory()) {
34789
+ return skip("runtime log directory could not be read", [
34790
+ `runtimeDir: ${paths.runtimeDir} (exists but is not a directory)`
34791
+ ]);
34792
+ }
34793
+ entries = await probe.readdir(paths.runtimeDir);
34794
+ } catch (error2) {
34795
+ if (isMissingPathError(error2)) {
34796
+ return skip("no runtime logs yet", [`runtimeDir: ${paths.runtimeDir} (missing)`]);
34797
+ }
34798
+ return skip("runtime log directory could not be read", [
34799
+ `runtimeDir: ${paths.runtimeDir}`,
34800
+ `error: ${formatError6(error2)}`
34801
+ ]);
34802
+ }
34803
+ const baseNames = [basename3(paths.appLog), basename3(paths.stdoutLog), basename3(paths.stderrLog)];
34804
+ const tracked = new Set(baseNames);
34805
+ const matched = entries.filter((entry) => isTrackedLogName(entry, tracked));
34806
+ const files = [];
34807
+ for (const name of matched) {
34808
+ const path15 = join23(paths.runtimeDir, name);
34809
+ try {
34810
+ const fileStat = await probe.stat(path15);
34811
+ if (fileStat.isDirectory()) {
34812
+ continue;
34813
+ }
34814
+ files.push({ name, path: path15, size: fileStat.size });
34815
+ } catch {
34816
+ continue;
34817
+ }
34818
+ }
34819
+ const total = files.reduce((sum, file) => sum + file.size, 0);
34820
+ const largestSingle = files.reduce((max, file) => Math.max(max, file.size), 0);
34821
+ const overSingle = files.some((file) => file.size > singleFileWarnBytes);
34822
+ const overTotal = total > totalWarnBytes;
34823
+ const sorted = [...files].sort((a, b) => b.size - a.size);
34824
+ const details = [
34825
+ ...sorted.map((file) => `${file.name}: ${formatBytes(file.size)}`),
34826
+ `total: ${formatBytes(total)}`
34827
+ ];
34828
+ if (overSingle || overTotal) {
34829
+ const reason = overSingle ? `largest single log is ${formatBytes(largestSingle)}` : `total is ${formatBytes(total)}`;
34830
+ return {
34831
+ id: "logs",
34832
+ label: "Logs",
34833
+ severity: "warn",
34834
+ summary: `log growth high: ${reason} (total ${formatBytes(total)})`,
34835
+ details,
34836
+ suggestions: [
34837
+ "logs are large; check disk space and that log rotation is configured (logging.maxSizeBytes / maxFiles)"
34838
+ ]
34839
+ };
34840
+ }
34841
+ return {
34842
+ id: "logs",
34843
+ label: "Logs",
34844
+ severity: "pass",
34845
+ summary: `logs total ${formatBytes(total)}`,
34846
+ details
34847
+ };
34848
+ }
34849
+ function skip(summary, details) {
34850
+ return {
34851
+ id: "logs",
34852
+ label: "Logs",
34853
+ severity: "skip",
34854
+ summary,
34855
+ details
34856
+ };
34857
+ }
34858
+ function isTrackedLogName(name, baseNames) {
34859
+ if (baseNames.has(name)) {
34860
+ return true;
34861
+ }
34862
+ for (const base of baseNames) {
34863
+ const prefix = `${base}.`;
34864
+ if (name.startsWith(prefix)) {
34865
+ const suffix = name.slice(prefix.length);
34866
+ if (/^\d+$/.test(suffix) && Number(suffix) > 0) {
34867
+ return true;
34868
+ }
34869
+ }
34870
+ }
34871
+ return false;
34872
+ }
34873
+ function formatBytes(bytes) {
34874
+ if (bytes < 1024) {
34875
+ return `${bytes} B`;
34876
+ }
34877
+ const units = ["KB", "MB", "GB", "TB"];
34878
+ let value = bytes / 1024;
34879
+ let unitIndex = 0;
34880
+ while (value >= 1024 && unitIndex < units.length - 1) {
34881
+ value /= 1024;
34882
+ unitIndex += 1;
34883
+ }
34884
+ return `${value.toFixed(1)} ${units[unitIndex]}`;
34885
+ }
34886
+ function createLogsFsProbe() {
34887
+ return {
34888
+ stat: async (path15) => await stat4(path15),
34889
+ readdir: async (path15) => await readdir5(path15)
34890
+ };
34891
+ }
34892
+ function formatError6(error2) {
34893
+ return error2 instanceof Error ? error2.message : String(error2);
34894
+ }
34895
+ function isMissingPathError(error2) {
34896
+ return typeof error2 === "object" && error2 !== null && "code" in error2 && (error2.code === "ENOENT" || error2.code === "ENOTDIR");
34897
+ }
34898
+ var DEFAULT_SINGLE_FILE_WARN_BYTES, DEFAULT_TOTAL_WARN_BYTES;
34899
+ var init_logs_check = __esm(() => {
34900
+ init_daemon_files();
34901
+ DEFAULT_SINGLE_FILE_WARN_BYTES = 50 * 1024 * 1024;
34902
+ DEFAULT_TOTAL_WARN_BYTES = 200 * 1024 * 1024;
34903
+ });
34904
+
33268
34905
  // src/doctor/checks/orchestration-health.ts
33269
34906
  async function checkOrchestrationHealth(options) {
33270
34907
  const state = await options.loadState();
@@ -33317,13 +34954,187 @@ var init_orchestration_health = __esm(() => {
33317
34954
  init_i18n();
33318
34955
  });
33319
34956
 
34957
+ // src/doctor/checks/orchestration-socket-check.ts
34958
+ import { homedir as homedir14 } from "node:os";
34959
+ async function checkOrchestrationSocket(options = {}) {
34960
+ const home = options.home ?? process.env.HOME ?? homedir14();
34961
+ const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
34962
+ const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
34963
+ home,
34964
+ ...runtimeDir ? { runtimeDir } : {}
34965
+ });
34966
+ const getDaemonStatus = options.getDaemonStatus ?? ((p) => defaultGetDaemonStatus(p, options));
34967
+ const probe = options.canConnectToEndpoint ?? canConnectToEndpoint;
34968
+ const resolveEndpoint = options.resolveOrchestrationEndpoint ?? ((dir) => resolveOrchestrationEndpoint(dir));
34969
+ let status;
34970
+ try {
34971
+ status = await getDaemonStatus(paths);
34972
+ } catch (error2) {
34973
+ return {
34974
+ id: "orchestration-socket",
34975
+ label: "Orchestration IPC",
34976
+ severity: "skip",
34977
+ summary: "daemon status could not be read",
34978
+ details: [`runtime dir: ${paths.runtimeDir}`, `error: ${formatError7(error2)}`]
34979
+ };
34980
+ }
34981
+ if (status.state === "stopped") {
34982
+ return {
34983
+ id: "orchestration-socket",
34984
+ label: "Orchestration IPC",
34985
+ severity: "skip",
34986
+ summary: "daemon stopped"
34987
+ };
34988
+ }
34989
+ let endpoint;
34990
+ let reachable;
34991
+ try {
34992
+ endpoint = resolveEndpoint(paths.runtimeDir);
34993
+ reachable = await probe(endpoint.path);
34994
+ } catch (error2) {
34995
+ return {
34996
+ id: "orchestration-socket",
34997
+ label: "Orchestration IPC",
34998
+ severity: "skip",
34999
+ summary: "orchestration IPC liveness could not be probed",
35000
+ details: [`runtime dir: ${paths.runtimeDir}`, `error: ${formatError7(error2)}`]
35001
+ };
35002
+ }
35003
+ if (reachable) {
35004
+ return {
35005
+ id: "orchestration-socket",
35006
+ label: "Orchestration IPC",
35007
+ severity: "pass",
35008
+ summary: "orchestration IPC is accepting connections",
35009
+ details: [`endpoint: ${endpoint.path}`]
35010
+ };
35011
+ }
35012
+ return {
35013
+ id: "orchestration-socket",
35014
+ label: "Orchestration IPC",
35015
+ severity: "fail",
35016
+ summary: "daemon is running but orchestration IPC is not accepting connections",
35017
+ details: [`endpoint: ${endpoint.path}`],
35018
+ suggestions: ["run: xacpx restart"]
35019
+ };
35020
+ }
35021
+ async function defaultGetDaemonStatus(paths, options) {
35022
+ const controller = createDaemonController(paths, {
35023
+ processExecPath: options.processExecPath ?? process.execPath,
35024
+ cliEntryPath: options.cliEntryPath ?? process.argv[1] ?? "",
35025
+ cwd: options.cwd ?? process.cwd(),
35026
+ env: options.env ?? process.env,
35027
+ isProcessRunning: options.isProcessRunning ?? isProcessAlive
35028
+ });
35029
+ return await controller.getStatus();
35030
+ }
35031
+ function formatError7(error2) {
35032
+ return error2 instanceof Error ? error2.message : String(error2);
35033
+ }
35034
+ var init_orchestration_socket_check = __esm(() => {
35035
+ init_create_daemon_controller();
35036
+ init_daemon_files();
35037
+ init_endpoint_probe();
35038
+ init_orchestration_ipc();
35039
+ });
35040
+
35041
+ // src/doctor/checks/plugin-check.ts
35042
+ async function checkPlugins(options = {}) {
35043
+ const runtimePaths = (options.resolveRuntimePaths ?? resolveRuntimePaths)();
35044
+ let config4;
35045
+ try {
35046
+ config4 = await (options.loadConfig ?? loadConfig)(runtimePaths.configPath);
35047
+ } catch (error2) {
35048
+ return {
35049
+ id: "plugins",
35050
+ label: "Plugins",
35051
+ severity: "skip",
35052
+ summary: "plugin check skipped because configuration could not be loaded",
35053
+ details: [`config path: ${runtimePaths.configPath}`, `error: ${formatError8(error2)}`],
35054
+ suggestions: ["fix the Config check first, then run: xacpx doctor"]
35055
+ };
35056
+ }
35057
+ if (!hasPluginSurface(config4)) {
35058
+ return {
35059
+ id: "plugins",
35060
+ label: "Plugins",
35061
+ severity: "skip",
35062
+ summary: "no plugins configured"
35063
+ };
35064
+ }
35065
+ const pluginHome = (options.resolvePluginHome ?? resolvePluginHome)({ home: options.home });
35066
+ const inspect = options.inspectPlugins ?? inspectPlugins;
35067
+ let issues;
35068
+ try {
35069
+ issues = await inspect({
35070
+ config: config4,
35071
+ pluginHome,
35072
+ currentXacpxVersion: options.currentXacpxVersion ?? XACPX_CORE_VERSION
35073
+ });
35074
+ } catch (error2) {
35075
+ return {
35076
+ id: "plugins",
35077
+ label: "Plugins",
35078
+ severity: "fail",
35079
+ summary: "plugin health check failed",
35080
+ details: [`plugin home: ${pluginHome}`, `error: ${formatError8(error2)}`]
35081
+ };
35082
+ }
35083
+ const errorCount = issues.filter((issue2) => issue2.level === "error").length;
35084
+ const warnCount = issues.filter((issue2) => issue2.level === "warn").length;
35085
+ const severity = errorCount > 0 ? "fail" : warnCount > 0 ? "warn" : "pass";
35086
+ const problemCount = errorCount + warnCount;
35087
+ return {
35088
+ id: "plugins",
35089
+ label: "Plugins",
35090
+ severity,
35091
+ summary: problemCount > 0 ? `${problemCount} plugin issue(s)` : "all plugins healthy",
35092
+ details: issues.filter((issue2) => issue2.level !== "ok").map(formatIssueDetail),
35093
+ suggestions: collectSuggestions(issues),
35094
+ metadata: { pluginHome, errorCount, warnCount }
35095
+ };
35096
+ }
35097
+ function hasPluginSurface(config4) {
35098
+ if ((config4.plugins ?? []).length > 0) {
35099
+ return true;
35100
+ }
35101
+ const builtInChannelTypes = new Set(listKnownChannelIds());
35102
+ return (config4.channels ?? []).some((channel) => channel.enabled !== false && !builtInChannelTypes.has(channel.type));
35103
+ }
35104
+ function formatIssueDetail(issue2) {
35105
+ return issue2.plugin ? `${issue2.plugin}: ${issue2.message}` : issue2.message;
35106
+ }
35107
+ function collectSuggestions(issues) {
35108
+ const suggestions = [];
35109
+ const seen = new Set;
35110
+ for (const issue2 of issues) {
35111
+ const suggestion = issue2.suggestion;
35112
+ if (suggestion && !seen.has(suggestion)) {
35113
+ seen.add(suggestion);
35114
+ suggestions.push(`run: ${suggestion}`);
35115
+ }
35116
+ }
35117
+ return suggestions;
35118
+ }
35119
+ function formatError8(error2) {
35120
+ return error2 instanceof Error ? error2.message : String(error2);
35121
+ }
35122
+ var init_plugin_check = __esm(async () => {
35123
+ init_load_config();
35124
+ init_channel_scope();
35125
+ init_plugin_doctor();
35126
+ init_plugin_home();
35127
+ init_version();
35128
+ await init_main();
35129
+ });
35130
+
33320
35131
  // src/doctor/checks/runtime-check.ts
33321
35132
  import { constants } from "node:fs";
33322
- import { access as access4, stat as stat3 } from "node:fs/promises";
35133
+ import { access as access4, stat as stat5 } from "node:fs/promises";
33323
35134
  import { dirname as dirname13 } from "node:path";
33324
- import { homedir as homedir11 } from "node:os";
35135
+ import { homedir as homedir15 } from "node:os";
33325
35136
  async function checkRuntime(options = {}) {
33326
- const home = options.home ?? process.env.HOME ?? homedir11();
35137
+ const home = options.home ?? process.env.HOME ?? homedir15();
33327
35138
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
33328
35139
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
33329
35140
  home,
@@ -33349,6 +35160,20 @@ async function checkRuntime(options = {}) {
33349
35160
  details: checks3.map((check) => check.detail)
33350
35161
  };
33351
35162
  }
35163
+ const privacy = await inspectRuntimeDirPrivacy(paths.runtimeDir, probe, platform);
35164
+ if (privacy.needsRepair) {
35165
+ return {
35166
+ id: "runtime",
35167
+ label: "Runtime",
35168
+ severity: "warn",
35169
+ summary: "daemon runtime dir should be private (mode 0700)",
35170
+ details: [...checks3.map((check) => check.detail), privacy.detail],
35171
+ fixes: [createEnsurePrivateDirFix(paths.runtimeDir, options.ensurePrivateRuntimeDir)],
35172
+ metadata: {
35173
+ paths
35174
+ }
35175
+ };
35176
+ }
33352
35177
  return {
33353
35178
  id: "runtime",
33354
35179
  label: "Runtime",
@@ -33360,9 +35185,50 @@ async function checkRuntime(options = {}) {
33360
35185
  }
33361
35186
  };
33362
35187
  }
35188
+ async function inspectRuntimeDirPrivacy(runtimeDir, probe, platform) {
35189
+ if (platform === "win32") {
35190
+ return { needsRepair: false, detail: "" };
35191
+ }
35192
+ try {
35193
+ const stats = await probe.stat(runtimeDir);
35194
+ if (typeof stats.mode !== "number") {
35195
+ return { needsRepair: false, detail: "" };
35196
+ }
35197
+ const mode = stats.mode & 511;
35198
+ if (mode === PRIVATE_DIR_MODE) {
35199
+ return { needsRepair: false, detail: "" };
35200
+ }
35201
+ return {
35202
+ needsRepair: true,
35203
+ detail: `runtimeDir: ${runtimeDir} (mode ${formatMode(mode)} is not 0700; group/other access should be removed)`
35204
+ };
35205
+ } catch (error2) {
35206
+ if (isMissingPathError2(error2)) {
35207
+ return {
35208
+ needsRepair: true,
35209
+ detail: `runtimeDir: ${runtimeDir} (missing; will be created with mode 0700)`
35210
+ };
35211
+ }
35212
+ return { needsRepair: false, detail: "" };
35213
+ }
35214
+ }
35215
+ function createEnsurePrivateDirFix(runtimeDir, ensureImpl) {
35216
+ const ensure = ensureImpl ?? ((dir) => ensurePrivateRuntimeDir(dir));
35217
+ return {
35218
+ id: "runtime.ensure-private-dir",
35219
+ title: "create/repair runtime dir with mode 0700",
35220
+ run: async () => {
35221
+ await ensure(runtimeDir);
35222
+ return { ok: true, message: `runtime dir ${runtimeDir} created/repaired with mode 0700` };
35223
+ }
35224
+ };
35225
+ }
35226
+ function formatMode(mode) {
35227
+ return `0${(mode & 511).toString(8)}`;
35228
+ }
33363
35229
  function createRuntimeFsProbe() {
33364
35230
  return {
33365
- stat: async (path15) => await stat3(path15),
35231
+ stat: async (path15) => await stat5(path15),
33366
35232
  access: async (path15, mode) => await access4(path15, mode)
33367
35233
  };
33368
35234
  }
@@ -33381,10 +35247,10 @@ async function checkDirectoryCreatable(label, path15, probe, platform) {
33381
35247
  detail: `${label}: ${path15} (writable)`
33382
35248
  };
33383
35249
  } catch (error2) {
33384
- if (!isMissingPathError(error2)) {
35250
+ if (!isMissingPathError2(error2)) {
33385
35251
  return {
33386
35252
  ok: false,
33387
- detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
35253
+ detail: `${label}: ${path15} (unusable: ${formatError9(error2)})`
33388
35254
  };
33389
35255
  }
33390
35256
  const parentCheck = await checkCreatableAncestorDirectory(path15, probe, platform);
@@ -33415,10 +35281,10 @@ async function checkFileCreatable(label, path15, probe, platform) {
33415
35281
  detail: `${label}: ${path15} (writable)`
33416
35282
  };
33417
35283
  } catch (error2) {
33418
- if (!isMissingPathError(error2)) {
35284
+ if (!isMissingPathError2(error2)) {
33419
35285
  return {
33420
35286
  ok: false,
33421
- detail: `${label}: ${path15} (unusable: ${formatError6(error2)})`
35287
+ detail: `${label}: ${path15} (unusable: ${formatError9(error2)})`
33422
35288
  };
33423
35289
  }
33424
35290
  const parentCheck = await checkCreatableAncestorDirectory(dirname13(path15), probe, platform);
@@ -33450,7 +35316,7 @@ async function checkCreatableAncestorDirectory(path15, probe, platform) {
33450
35316
  creatableFrom: path15
33451
35317
  };
33452
35318
  } catch (error2) {
33453
- if (!isMissingPathError(error2)) {
35319
+ if (!isMissingPathError2(error2)) {
33454
35320
  return {
33455
35321
  ok: false,
33456
35322
  creatableFrom: path15,
@@ -33478,21 +35344,22 @@ async function checkCreatableAncestorDirectory(path15, probe, platform) {
33478
35344
  function directoryAccessMode(platform) {
33479
35345
  return platform === "win32" ? constants.W_OK : DIRECTORY_USABLE;
33480
35346
  }
33481
- function isMissingPathError(error2) {
35347
+ function isMissingPathError2(error2) {
33482
35348
  return isErrnoError(error2) && (error2.code === "ENOENT" || error2.code === "ENOTDIR");
33483
35349
  }
33484
35350
  function isErrnoError(error2) {
33485
35351
  return typeof error2 === "object" && error2 !== null && "code" in error2;
33486
35352
  }
33487
- function formatError6(error2) {
35353
+ function formatError9(error2) {
33488
35354
  if (error2 instanceof Error) {
33489
35355
  return error2.message;
33490
35356
  }
33491
35357
  return String(error2);
33492
35358
  }
33493
- var DIRECTORY_USABLE;
35359
+ var DIRECTORY_USABLE, PRIVATE_DIR_MODE = 448;
33494
35360
  var init_runtime_check = __esm(() => {
33495
35361
  init_daemon_files();
35362
+ init_private_runtime_dir();
33496
35363
  DIRECTORY_USABLE = constants.W_OK | constants.X_OK;
33497
35364
  });
33498
35365
 
@@ -33604,7 +35471,7 @@ async function checkSmoke(options = {}, deps = {}) {
33604
35471
  label: "Smoke",
33605
35472
  severity: "fail",
33606
35473
  summary: "smoke transport probe failed",
33607
- details: [`config path: ${runtimePaths.configPath}`, `error: ${formatError7(error2)}`]
35474
+ details: [`config path: ${runtimePaths.configPath}`, `error: ${formatError10(error2)}`]
33608
35475
  };
33609
35476
  }
33610
35477
  }
@@ -33693,7 +35560,10 @@ function buildSession(options) {
33693
35560
  return {
33694
35561
  alias: "xacpx-doctor",
33695
35562
  agent: options.agent,
33696
- ...agentConfig.command ? { agentCommand: agentConfig.command } : {},
35563
+ ...(() => {
35564
+ const agentCommand = resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, options.config.transport.preferLocalAgents !== false);
35565
+ return agentCommand ? { agentCommand } : {};
35566
+ })(),
33697
35567
  workspace: options.workspace,
33698
35568
  transportSession: `xacpx-doctor-${timestamp}`,
33699
35569
  replyMode: options.config.channel.replyMode,
@@ -33731,13 +35601,14 @@ function buildDetails3(options) {
33731
35601
  }
33732
35602
  return details;
33733
35603
  }
33734
- function formatError7(error2) {
35604
+ function formatError10(error2) {
33735
35605
  return error2 instanceof Error ? error2.message : String(error2);
33736
35606
  }
33737
35607
  var SMOKE_PROMPT = "Reply with exactly: ok";
33738
35608
  var init_smoke_check = __esm(async () => {
33739
35609
  init_load_config();
33740
35610
  init_resolve_acpx_command();
35611
+ init_resolve_agent_command();
33741
35612
  init_acpx_bridge_client();
33742
35613
  init_acpx_bridge_transport();
33743
35614
  init_acpx_cli_transport();
@@ -33756,7 +35627,7 @@ async function checkWechat(options = {}) {
33756
35627
  } catch (error2) {
33757
35628
  return {
33758
35629
  accountId,
33759
- error: formatError8(error2)
35630
+ error: formatError11(error2)
33760
35631
  };
33761
35632
  }
33762
35633
  });
@@ -33797,7 +35668,7 @@ function buildVerboseDetails(loggedIn, verbose, accounts) {
33797
35668
  }
33798
35669
  return details;
33799
35670
  }
33800
- function formatError8(error2) {
35671
+ function formatError11(error2) {
33801
35672
  return error2 instanceof Error ? error2.message : String(error2);
33802
35673
  }
33803
35674
  var init_wechat_check = __esm(() => {
@@ -33806,43 +35677,60 @@ var init_wechat_check = __esm(() => {
33806
35677
 
33807
35678
  // src/doctor/render-doctor.ts
33808
35679
  function renderDoctor(report, options = {}) {
33809
- return options.verbose ? renderVerboseDoctor(report) : renderDefaultDoctor(report);
35680
+ const fixMode = options.fix === true;
35681
+ return options.verbose ? renderVerboseDoctor(report, fixMode) : renderDefaultDoctor(report, fixMode);
33810
35682
  }
33811
- function renderDefaultDoctor(report) {
35683
+ function renderDefaultDoctor(report, fixMode) {
33812
35684
  const lines = [];
33813
35685
  for (const check of report.checks) {
33814
- lines.push(renderCheckLine(check));
35686
+ lines.push(renderCheckLine(check, fixMode));
33815
35687
  }
35688
+ appendRepairs(lines, report, fixMode);
33816
35689
  lines.push(renderSummaryLine(report.checks));
33817
- const suggestions = collectSuggestions(report.checks);
33818
- if (suggestions.length > 0) {
33819
- lines.push("Next steps:");
33820
- for (const suggestion of suggestions) {
33821
- lines.push(`- ${suggestion}`);
33822
- }
33823
- }
35690
+ appendNextSteps(lines, report.checks);
33824
35691
  return lines;
33825
35692
  }
33826
- function renderVerboseDoctor(report) {
35693
+ function renderVerboseDoctor(report, fixMode) {
33827
35694
  const lines = [];
33828
35695
  for (const check of report.checks) {
33829
- lines.push(renderCheckLine(check));
35696
+ lines.push(renderCheckLine(check, fixMode));
33830
35697
  for (const detail of check.details ?? []) {
33831
35698
  lines.push(` detail: ${detail}`);
33832
35699
  }
33833
35700
  }
35701
+ appendRepairs(lines, report, fixMode);
33834
35702
  lines.push(renderSummaryLine(report.checks));
33835
- const suggestions = collectSuggestions(report.checks);
35703
+ appendNextSteps(lines, report.checks);
35704
+ return lines;
35705
+ }
35706
+ function appendNextSteps(lines, checks3) {
35707
+ const suggestions = collectSuggestions2(checks3);
33836
35708
  if (suggestions.length > 0) {
33837
35709
  lines.push("Next steps:");
33838
35710
  for (const suggestion of suggestions) {
33839
35711
  lines.push(`- ${suggestion}`);
33840
35712
  }
33841
35713
  }
33842
- return lines;
33843
35714
  }
33844
- function renderCheckLine(check) {
33845
- return `${SEVERITY_LABELS[check.severity]} ${check.label}: ${check.summary}`;
35715
+ function appendRepairs(lines, report, fixMode) {
35716
+ if (!fixMode) {
35717
+ return;
35718
+ }
35719
+ const repairs = report.repairs ?? [];
35720
+ if (repairs.length === 0) {
35721
+ return;
35722
+ }
35723
+ lines.push("Repairs:");
35724
+ for (const repair of repairs) {
35725
+ lines.push(`- ${repair.title}: ${repair.status} (${repair.message})`);
35726
+ }
35727
+ }
35728
+ function renderCheckLine(check, fixMode) {
35729
+ const base = `${SEVERITY_LABELS[check.severity]} ${check.label}: ${check.summary}`;
35730
+ if (!fixMode && (check.fixes?.length ?? 0) > 0) {
35731
+ return `${base} (fixable — run: xacpx doctor --fix)`;
35732
+ }
35733
+ return base;
33846
35734
  }
33847
35735
  function renderSummaryLine(checks3) {
33848
35736
  const counts = summarizeChecks(checks3);
@@ -33854,7 +35742,7 @@ function summarizeChecks(checks3) {
33854
35742
  return counts;
33855
35743
  }, { pass: 0, warn: 0, fail: 0, skip: 0 });
33856
35744
  }
33857
- function collectSuggestions(checks3) {
35745
+ function collectSuggestions2(checks3) {
33858
35746
  const seen = new Set;
33859
35747
  const suggestions = [];
33860
35748
  for (const check of checks3) {
@@ -33879,47 +35767,112 @@ var init_render_doctor = __esm(() => {
33879
35767
  });
33880
35768
 
33881
35769
  // src/doctor/doctor.ts
33882
- import { homedir as homedir12 } from "node:os";
33883
- import { join as join19 } from "node:path";
35770
+ import { homedir as homedir16 } from "node:os";
35771
+ import { join as join24 } from "node:path";
33884
35772
  async function runDoctor(options = {}, deps = {}) {
33885
- const home = deps.home ?? process.env.HOME ?? homedir12();
35773
+ const home = deps.home ?? process.env.HOME ?? homedir16();
33886
35774
  const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
33887
35775
  const sharedLoadConfig = createSharedLoadConfig(runtimePaths, deps.loadConfig ?? loadConfig);
35776
+ const runners = [
35777
+ {
35778
+ id: "config",
35779
+ run: () => (deps.checkConfig ?? checkConfig)({
35780
+ loadConfig: sharedLoadConfig,
35781
+ resolveRuntimePaths: () => runtimePaths
35782
+ })
35783
+ },
35784
+ {
35785
+ id: "runtime",
35786
+ run: () => (deps.checkRuntime ?? checkRuntime)({
35787
+ home,
35788
+ configPath: runtimePaths.configPath
35789
+ })
35790
+ },
35791
+ {
35792
+ id: "logs",
35793
+ run: () => (deps.checkLogs ?? checkLogs)({
35794
+ home,
35795
+ configPath: runtimePaths.configPath
35796
+ })
35797
+ },
35798
+ {
35799
+ id: "daemon",
35800
+ run: () => (deps.checkDaemon ?? checkDaemon)({
35801
+ home,
35802
+ configPath: runtimePaths.configPath
35803
+ })
35804
+ },
35805
+ {
35806
+ id: "wechat",
35807
+ run: () => (deps.checkWechat ?? checkWechat)({
35808
+ verbose: options.verbose
35809
+ })
35810
+ },
35811
+ {
35812
+ id: "acpx",
35813
+ run: () => (deps.checkAcpx ?? checkAcpx)({
35814
+ verbose: options.verbose,
35815
+ loadConfig: sharedLoadConfig,
35816
+ resolveRuntimePaths: () => runtimePaths
35817
+ })
35818
+ },
35819
+ {
35820
+ id: "bridge",
35821
+ run: () => (deps.checkBridge ?? checkBridge)({
35822
+ verbose: options.verbose,
35823
+ loadConfig: sharedLoadConfig,
35824
+ resolveRuntimePaths: () => runtimePaths
35825
+ })
35826
+ },
35827
+ {
35828
+ id: "plugins",
35829
+ run: () => (deps.checkPlugins ?? checkPlugins)({
35830
+ home,
35831
+ loadConfig: sharedLoadConfig,
35832
+ resolveRuntimePaths: () => runtimePaths
35833
+ })
35834
+ },
35835
+ {
35836
+ id: "orchestration",
35837
+ run: () => (deps.checkOrchestrationHealth ?? (() => defaultCheckOrchestrationHealth({
35838
+ runtimePaths,
35839
+ loadConfig: sharedLoadConfig,
35840
+ isDaemonRunning: deps.isDaemonRunning ?? (() => defaultIsDaemonRunning(home, runtimePaths, deps.getDaemonStatus))
35841
+ })))()
35842
+ },
35843
+ {
35844
+ id: "orchestration-socket",
35845
+ run: () => (deps.checkOrchestrationSocket ?? checkOrchestrationSocket)({
35846
+ home,
35847
+ configPath: runtimePaths.configPath
35848
+ })
35849
+ },
35850
+ {
35851
+ id: "smoke",
35852
+ run: () => options.smoke === true ? (deps.checkSmoke ?? ((runOptions) => defaultCheckSmoke(runOptions, {
35853
+ resolveRuntimePaths: () => runtimePaths,
35854
+ loadConfig: sharedLoadConfig
35855
+ })))(options) : Promise.resolve(createSmokeSkipResult("smoke probe not requested"))
35856
+ }
35857
+ ];
35858
+ const runnersById = new Map(runners.map((runner) => [runner.id, runner.run]));
33888
35859
  const checks3 = [];
33889
- checks3.push(await (deps.checkConfig ?? checkConfig)({
33890
- loadConfig: sharedLoadConfig,
33891
- resolveRuntimePaths: () => runtimePaths
33892
- }));
33893
- checks3.push(await (deps.checkRuntime ?? checkRuntime)({
33894
- home,
33895
- configPath: runtimePaths.configPath
33896
- }));
33897
- checks3.push(await (deps.checkDaemon ?? checkDaemon)({
33898
- home,
33899
- configPath: runtimePaths.configPath
33900
- }));
33901
- checks3.push(await (deps.checkWechat ?? checkWechat)({
33902
- verbose: options.verbose
33903
- }));
33904
- checks3.push(await (deps.checkAcpx ?? checkAcpx)({
33905
- verbose: options.verbose,
33906
- loadConfig: sharedLoadConfig,
33907
- resolveRuntimePaths: () => runtimePaths
33908
- }));
33909
- checks3.push(await (deps.checkBridge ?? checkBridge)({
33910
- verbose: options.verbose,
33911
- loadConfig: sharedLoadConfig,
33912
- resolveRuntimePaths: () => runtimePaths
33913
- }));
33914
- checks3.push(await (deps.checkOrchestrationHealth ?? (() => defaultCheckOrchestrationHealth({
33915
- runtimePaths,
33916
- loadConfig: sharedLoadConfig
33917
- })))());
33918
- checks3.push(options.smoke === true ? await (deps.checkSmoke ?? ((runOptions) => defaultCheckSmoke(runOptions, {
33919
- resolveRuntimePaths: () => runtimePaths,
33920
- loadConfig: sharedLoadConfig
33921
- })))(options) : createSmokeSkipResult("smoke probe not requested"));
35860
+ for (const runner of runners) {
35861
+ checks3.push(await runner.run());
35862
+ }
33922
35863
  const report = { checks: checks3 };
35864
+ if (options.fix === true) {
35865
+ const { repairs, repairedCheckIds } = await applyRepairs(checks3);
35866
+ report.repairs = repairs;
35867
+ for (const checkId of repairedCheckIds) {
35868
+ const index = checks3.findIndex((check) => check.id === checkId);
35869
+ const rerun = runnersById.get(checkId);
35870
+ if (index === -1 || !rerun) {
35871
+ continue;
35872
+ }
35873
+ checks3[index] = await rerun();
35874
+ }
35875
+ }
33923
35876
  const output = (deps.renderDoctor ?? renderDoctor)(report, options);
33924
35877
  return {
33925
35878
  report,
@@ -33927,6 +35880,41 @@ async function runDoctor(options = {}, deps = {}) {
33927
35880
  exitCode: checks3.some((check) => check.severity === "fail") ? 1 : 0
33928
35881
  };
33929
35882
  }
35883
+ async function applyRepairs(checks3) {
35884
+ const repairs = [];
35885
+ const repairedCheckIds = [];
35886
+ for (const check of checks3) {
35887
+ for (const fix of check.fixes ?? []) {
35888
+ if (fix.withheld !== undefined) {
35889
+ repairs.push({
35890
+ checkId: check.id,
35891
+ fixId: fix.id,
35892
+ title: fix.title,
35893
+ status: "skipped",
35894
+ message: fix.withheld
35895
+ });
35896
+ continue;
35897
+ }
35898
+ let outcome;
35899
+ try {
35900
+ outcome = await fix.run();
35901
+ } catch (error2) {
35902
+ outcome = { ok: false, message: formatError12(error2) };
35903
+ }
35904
+ repairs.push({
35905
+ checkId: check.id,
35906
+ fixId: fix.id,
35907
+ title: fix.title,
35908
+ status: outcome.ok ? "applied" : "failed",
35909
+ message: outcome.message
35910
+ });
35911
+ if (outcome.ok && !repairedCheckIds.includes(check.id)) {
35912
+ repairedCheckIds.push(check.id);
35913
+ }
35914
+ }
35915
+ }
35916
+ return { repairs, repairedCheckIds };
35917
+ }
33930
35918
  function resolveDoctorRuntimePaths(home, resolver) {
33931
35919
  if (resolver) {
33932
35920
  return resolver();
@@ -33935,8 +35923,8 @@ function resolveDoctorRuntimePaths(home, resolver) {
33935
35923
  return resolveRuntimePaths();
33936
35924
  }
33937
35925
  return {
33938
- configPath: join19(coreHomeDir(home), "config.json"),
33939
- statePath: join19(coreHomeDir(home), "state.json")
35926
+ configPath: join24(coreHomeDir(home), "config.json"),
35927
+ statePath: join24(coreHomeDir(home), "state.json")
33940
35928
  };
33941
35929
  }
33942
35930
  function depsUseExplicitRuntimeOverrides() {
@@ -33976,7 +35964,7 @@ async function defaultCheckOrchestrationHealth(deps) {
33976
35964
  label: "Orchestration",
33977
35965
  severity: "skip",
33978
35966
  summary: "orchestration check skipped because configuration could not be loaded",
33979
- details: [`config path: ${deps.runtimePaths.configPath}`, `error: ${formatError9(error2)}`],
35967
+ details: [`config path: ${deps.runtimePaths.configPath}`, `error: ${formatError12(error2)}`],
33980
35968
  suggestions: ["fix the Config check first, then run: xacpx doctor"]
33981
35969
  };
33982
35970
  }
@@ -33988,18 +35976,19 @@ async function defaultCheckOrchestrationHealth(deps) {
33988
35976
  now: () => new Date,
33989
35977
  heartbeatThresholdSeconds: config4.orchestration.progressHeartbeatSeconds
33990
35978
  });
33991
- return applyStateInspectionReport(result, inspection.report, deps.runtimePaths.statePath);
35979
+ const daemonRunning = inspection.report ? await deps.isDaemonRunning() : false;
35980
+ return applyStateInspectionReport(result, inspection.report, deps.runtimePaths.statePath, daemonRunning, deps.isDaemonRunning);
33992
35981
  } catch (error2) {
33993
35982
  return {
33994
35983
  id: "orchestration",
33995
35984
  label: "Orchestration",
33996
35985
  severity: "fail",
33997
35986
  summary: "orchestration health check failed",
33998
- details: [`state path: ${deps.runtimePaths.statePath}`, `error: ${formatError9(error2)}`]
35987
+ details: [`state path: ${deps.runtimePaths.statePath}`, `error: ${formatError12(error2)}`]
33999
35988
  };
34000
35989
  }
34001
35990
  }
34002
- function applyStateInspectionReport(result, report, statePath) {
35991
+ function applyStateInspectionReport(result, report, statePath, daemonRunning, isDaemonRunning) {
34003
35992
  if (!report) {
34004
35993
  return result;
34005
35994
  }
@@ -34017,18 +36006,74 @@ function applyStateInspectionReport(result, report, statePath) {
34017
36006
  suggestions: [
34018
36007
  ...result.suggestions ?? [],
34019
36008
  fileCorrupt ? "back up the state file before the next daemon start if you want to attempt manual recovery" : "the daemon backs the original file up as state.json.quarantine-* before dropping these records"
34020
- ]
36009
+ ],
36010
+ fixes: [createStateQuarantineFix(statePath, daemonRunning, isDaemonRunning)]
34021
36011
  };
34022
36012
  }
34023
- function formatError9(error2) {
36013
+ function createStateQuarantineFix(statePath, daemonRunning, isDaemonRunning) {
36014
+ return {
36015
+ id: "state.quarantine",
36016
+ title: "quarantine invalid state.json records",
36017
+ ...daemonRunning ? { withheld: "stop the daemon first: xacpx stop" } : {},
36018
+ run: async () => {
36019
+ if (await isDaemonRunning()) {
36020
+ return {
36021
+ ok: false,
36022
+ message: "a daemon started since detection; stop it first: xacpx stop"
36023
+ };
36024
+ }
36025
+ const store = new StateStore(statePath);
36026
+ await store.load();
36027
+ const report = store.lastLoadReport;
36028
+ if (!report) {
36029
+ return { ok: true, message: "state.json was already valid; nothing to quarantine" };
36030
+ }
36031
+ if (report.corruptPath) {
36032
+ return { ok: true, message: `state.json was unreadable; renamed to ${report.corruptPath} and reset` };
36033
+ }
36034
+ const backup = report.quarantinePath ? ` (original backed up to ${report.quarantinePath})` : "";
36035
+ return {
36036
+ ok: true,
36037
+ message: `quarantined ${report.dropped.length} invalid state.json record(s)${backup}`
36038
+ };
36039
+ }
36040
+ };
36041
+ }
36042
+ async function defaultIsDaemonRunning(home, runtimePaths, getDaemonStatus = () => defaultGetDaemonStatus2(home, runtimePaths)) {
36043
+ try {
36044
+ const status = await getDaemonStatus();
36045
+ return status.state === "running" || status.state === "indeterminate";
36046
+ } catch {
36047
+ return true;
36048
+ }
36049
+ }
36050
+ async function defaultGetDaemonStatus2(home, runtimePaths) {
36051
+ const paths = resolveDaemonPaths({
36052
+ home,
36053
+ runtimeDir: resolveRuntimeDirFromConfigPath(runtimePaths.configPath)
36054
+ });
36055
+ const controller = createDaemonController(paths, {
36056
+ processExecPath: process.execPath,
36057
+ cliEntryPath: process.argv[1] ?? "",
36058
+ cwd: process.cwd(),
36059
+ env: process.env,
36060
+ isProcessRunning: isProcessAlive
36061
+ });
36062
+ return await controller.getStatus();
36063
+ }
36064
+ function formatError12(error2) {
34024
36065
  return error2 instanceof Error ? error2.message : String(error2);
34025
36066
  }
34026
36067
  var init_doctor = __esm(async () => {
34027
36068
  init_core_home();
34028
36069
  init_load_config();
36070
+ init_create_daemon_controller();
36071
+ init_daemon_files();
34029
36072
  init_state_store();
34030
36073
  init_daemon_check();
36074
+ init_logs_check();
34031
36075
  init_orchestration_health();
36076
+ init_orchestration_socket_check();
34032
36077
  init_runtime_check();
34033
36078
  init_wechat_check();
34034
36079
  init_render_doctor();
@@ -34037,6 +36082,7 @@ var init_doctor = __esm(async () => {
34037
36082
  init_acpx_check(),
34038
36083
  init_bridge_check(),
34039
36084
  init_config_check(),
36085
+ init_plugin_check(),
34040
36086
  init_smoke_check()
34041
36087
  ]);
34042
36088
  });
@@ -34061,8 +36107,8 @@ var init_doctor2 = __esm(async () => {
34061
36107
  // src/cli.ts
34062
36108
  init_core_home();
34063
36109
  import { randomUUID as randomUUID4 } from "node:crypto";
34064
- import { homedir as homedir13 } from "node:os";
34065
- import { dirname as dirname14, join as join20, sep } from "node:path";
36110
+ import { homedir as homedir17 } from "node:os";
36111
+ import { dirname as dirname14, join as join25, sep as sep2 } from "node:path";
34066
36112
  import { fileURLToPath as fileURLToPath7 } from "node:url";
34067
36113
 
34068
36114
  // src/runtime/migrate-core-home.ts
@@ -46559,7 +48605,7 @@ init_core_home();
46559
48605
  init_daemon_files();
46560
48606
  init_orchestration_ipc();
46561
48607
  import { homedir as homedir2 } from "node:os";
46562
- import { join as join5 } from "node:path";
48608
+ import { join as join6 } from "node:path";
46563
48609
  function resolveDefaultOrchestrationEndpoint(env = process.env, platform = process.platform) {
46564
48610
  const orchestrationSocket = coreEnv("ORCHESTRATION_SOCKET", env);
46565
48611
  if (typeof orchestrationSocket === "string" && orchestrationSocket.trim().length > 0) {
@@ -46567,7 +48613,7 @@ function resolveDefaultOrchestrationEndpoint(env = process.env, platform = proce
46567
48613
  }
46568
48614
  const home = requireHome(env);
46569
48615
  const configOverride = coreEnv("CONFIG", env);
46570
- const configPath = typeof configOverride === "string" && configOverride.trim().length > 0 ? configOverride.trim() : join5(coreHomeDir(home), "config.json");
48616
+ const configPath = typeof configOverride === "string" && configOverride.trim().length > 0 ? configOverride.trim() : join6(coreHomeDir(home), "config.json");
46571
48617
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
46572
48618
  return resolveOrchestrationEndpoint(runtimeDir, platform);
46573
48619
  }
@@ -48104,14 +50150,14 @@ function resolveTemplateChoice(answer, names) {
48104
50150
  init_plugin_home();
48105
50151
  import { spawn as spawn4 } from "node:child_process";
48106
50152
  import { readFile as readFile9 } from "node:fs/promises";
48107
- import { dirname as dirname8, join as join11 } from "node:path";
50153
+ import { dirname as dirname8, join as join12 } from "node:path";
48108
50154
  import { fileURLToPath as fileURLToPath3 } from "node:url";
48109
50155
 
48110
50156
  // src/plugins/package-manager.ts
48111
50157
  init_plugin_home();
48112
50158
  import { spawn as spawn3 } from "node:child_process";
48113
50159
  import { rm as rm4 } from "node:fs/promises";
48114
- import { join as join7 } from "node:path";
50160
+ import { join as join8 } from "node:path";
48115
50161
  function shellSpawnPlan(args) {
48116
50162
  const shell = process.platform === "win32";
48117
50163
  return { shell, args: shell ? args.map((arg) => `"${arg}"`) : args };
@@ -48159,7 +50205,7 @@ async function installPluginPackage(input) {
48159
50205
  const packageManager = input.packageManager ?? await detectPackageManager();
48160
50206
  await normalizePluginHomeManifest(input.pluginHome);
48161
50207
  if (packageManager === "bun") {
48162
- await rm4(join7(input.pluginHome, "bun.lock"), { force: true }).catch(() => {});
50208
+ await rm4(join8(input.pluginHome, "bun.lock"), { force: true }).catch(() => {});
48163
50209
  }
48164
50210
  const spec = input.version ? `${input.packageName}@${input.version}` : input.packageName;
48165
50211
  if (packageManager === "bun") {
@@ -48451,7 +50497,7 @@ async function runInherit(command, args) {
48451
50497
  async function readPackageName() {
48452
50498
  try {
48453
50499
  const here = dirname8(fileURLToPath3(import.meta.url));
48454
- for (const candidate of [join11(here, "..", "package.json"), join11(here, "..", "..", "package.json")]) {
50500
+ for (const candidate of [join12(here, "..", "package.json"), join12(here, "..", "..", "package.json")]) {
48455
50501
  try {
48456
50502
  const parsed = JSON.parse(await readFile9(candidate, "utf8"));
48457
50503
  if (typeof parsed.name === "string" && parsed.name.trim())
@@ -49106,121 +51152,10 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
49106
51152
  init_core_home();
49107
51153
  init_plugin_home();
49108
51154
  import { readFile as readFile11 } from "node:fs/promises";
49109
- import { isAbsolute, join as join13, resolve } from "node:path";
51155
+ import { isAbsolute, join as join14, resolve } from "node:path";
49110
51156
  init_plugin_loader();
49111
51157
  init_validate_plugin();
49112
-
49113
- // src/plugins/plugin-doctor.ts
49114
- init_channel_scope();
49115
- init_plugin_loader();
49116
- init_validate_plugin();
49117
- init_known_plugins();
49118
- init_plugin_renames();
49119
- import { readFile as readFile10 } from "node:fs/promises";
49120
- import { join as join12 } from "node:path";
49121
- function suggestedPluginPackageForChannel(type) {
49122
- return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
49123
- }
49124
- async function readDependencyEntries(pluginHome) {
49125
- try {
49126
- const raw = await readFile10(join12(pluginHome, "package.json"), "utf8");
49127
- const parsed = JSON.parse(raw);
49128
- const out = {};
49129
- for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
49130
- if (typeof value === "string")
49131
- out[name] = value;
49132
- }
49133
- return out;
49134
- } catch (error2) {
49135
- const message = error2 instanceof Error ? error2.message : String(error2);
49136
- throw new Error(`failed to read plugin home package.json: ${message}`);
49137
- }
49138
- }
49139
- async function inspectPlugins(input) {
49140
- const issues = [];
49141
- let dependencies;
49142
- try {
49143
- dependencies = await readDependencyEntries(input.pluginHome);
49144
- } catch (error2) {
49145
- const message = error2 instanceof Error ? error2.message : String(error2);
49146
- return [{ level: "error", message }];
49147
- }
49148
- const importPlugin = input.importPlugin ?? importPluginFromHome;
49149
- const allConfigured = input.config.plugins;
49150
- const filterByName = input.pluginName ? normalizePluginPackageName(input.pluginName) : null;
49151
- if (filterByName && !allConfigured.some((plugin) => normalizePluginPackageName(plugin.name) === filterByName)) {
49152
- return [{ level: "error", plugin: filterByName, message: `plugin is not configured; run xacpx plugin add ${filterByName}` }];
49153
- }
49154
- const pushIfRelevant = (issue2) => {
49155
- if (!filterByName || issue2.plugin === filterByName)
49156
- issues.push(issue2);
49157
- };
49158
- const channelProviders = new Map;
49159
- for (const configPlugin of allConfigured) {
49160
- if (!(configPlugin.name in dependencies)) {
49161
- pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `package not installed in plugin home; run xacpx plugin add ${configPlugin.name}` });
49162
- continue;
49163
- }
49164
- let moduleValue;
49165
- try {
49166
- moduleValue = await importPlugin(configPlugin.name, input.pluginHome);
49167
- } catch (error2) {
49168
- const message = error2 instanceof Error ? error2.message : String(error2);
49169
- pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `failed to import plugin: ${message}` });
49170
- continue;
49171
- }
49172
- try {
49173
- const plugin = validateWeacpxPlugin(moduleValue, configPlugin.name, {
49174
- ...input.currentXacpxVersion !== undefined ? { currentXacpxVersion: input.currentXacpxVersion } : {}
49175
- });
49176
- const channels = plugin.channels ?? [];
49177
- const channelTypes = channels.map((channel) => channel.type);
49178
- for (const type of channelTypes) {
49179
- const existing = channelProviders.get(type);
49180
- if (existing) {
49181
- pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `channel type ${type} is already provided by ${existing.plugin}` });
49182
- } else {
49183
- channelProviders.set(type, { plugin: configPlugin.name, enabled: configPlugin.enabled });
49184
- }
49185
- }
49186
- pushIfRelevant({
49187
- level: configPlugin.enabled ? "ok" : "warn",
49188
- plugin: configPlugin.name,
49189
- message: configPlugin.enabled ? `plugin is installed and valid; channels: ${channelTypes.length > 0 ? channelTypes.join(", ") : "none"}` : `plugin is installed and valid but disabled; run xacpx plugin enable ${configPlugin.name}`
49190
- });
49191
- } catch (error2) {
49192
- const message = error2 instanceof Error ? error2.message : String(error2);
49193
- pushIfRelevant({ level: "error", plugin: configPlugin.name, message });
49194
- }
49195
- }
49196
- const builtInChannelTypes = new Set(listKnownChannelIds());
49197
- for (const channel of input.config.channels) {
49198
- if (channel.enabled === false)
49199
- continue;
49200
- if (builtInChannelTypes.has(channel.type))
49201
- continue;
49202
- const provider = channelProviders.get(channel.type);
49203
- if (!provider) {
49204
- if (!filterByName) {
49205
- issues.push({
49206
- level: "error",
49207
- message: `channel ${channel.type} is configured but no enabled plugin provides it; run xacpx plugin add ${suggestedPluginPackageForChannel(channel.type)} or another plugin that provides type "${channel.type}"`
49208
- });
49209
- }
49210
- continue;
49211
- }
49212
- if (!provider.enabled) {
49213
- pushIfRelevant({
49214
- level: "error",
49215
- plugin: provider.plugin,
49216
- message: `channel ${channel.type} is configured but provider plugin is disabled; run xacpx plugin enable ${provider.plugin}`
49217
- });
49218
- }
49219
- }
49220
- return issues;
49221
- }
49222
-
49223
- // src/plugins/plugin-cli.ts
51158
+ init_plugin_doctor();
49224
51159
  init_known_plugins();
49225
51160
  init_plugin_renames();
49226
51161
  init_i18n();
@@ -49248,7 +51183,7 @@ function looksLikePath(spec) {
49248
51183
  }
49249
51184
  async function readDependencyEntries2(pluginHome) {
49250
51185
  try {
49251
- const raw = await readFile11(join13(pluginHome, "package.json"), "utf8");
51186
+ const raw = await readFile11(join14(pluginHome, "package.json"), "utf8");
49252
51187
  const parsed = JSON.parse(raw);
49253
51188
  const out = {};
49254
51189
  for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
@@ -49274,7 +51209,7 @@ async function resolveLocalPluginName(installSpec, pluginHome, namesBeforeInstal
49274
51209
  return name;
49275
51210
  }
49276
51211
  try {
49277
- const raw = await readFile11(join13(installSpec, "package.json"), "utf8");
51212
+ const raw = await readFile11(join14(installSpec, "package.json"), "utf8");
49278
51213
  const parsed = JSON.parse(raw);
49279
51214
  if (typeof parsed.name === "string" && parsed.name.trim())
49280
51215
  return parsed.name.trim();
@@ -50416,7 +52351,7 @@ async function createCliScheduledTaskService() {
50416
52351
  return new ScheduledTaskService(state, stateStore);
50417
52352
  }
50418
52353
  function resolveConfigPathForCurrentEnv() {
50419
- return coreEnv("CONFIG") ?? join20(coreHomeDir(requireHome2()), "config.json");
52354
+ return coreEnv("CONFIG") ?? join25(coreHomeDir(requireHome2()), "config.json");
50420
52355
  }
50421
52356
  function resolveDaemonPathsForCurrentConfig() {
50422
52357
  const configPath = resolveConfigPathForCurrentEnv();
@@ -50475,7 +52410,7 @@ async function defaultRun(options = {}) {
50475
52410
  const firstRunOnboarding = options.firstRunOnboarding ?? decodeFirstRunOnboarding(coreEnv("FIRST_RUN_ONBOARDING"));
50476
52411
  await runConsole2(runtimePaths, {
50477
52412
  buildApp: (paths) => buildApp2(paths, {
50478
- defaultLoggingLevel: resolveCliEntryPath2().includes(`${sep}src${sep}`) ? "debug" : "info",
52413
+ defaultLoggingLevel: resolveCliEntryPath2().includes(`${sep2}src${sep2}`) ? "debug" : "info",
50479
52414
  channel: channelRegistry
50480
52415
  }),
50481
52416
  beforeReady: firstRunOnboarding ? async (runtime) => {
@@ -50486,7 +52421,7 @@ async function defaultRun(options = {}) {
50486
52421
  daemonRuntime,
50487
52422
  ...firstLockCreator ? {
50488
52423
  consumerLockFactory: (runtime) => firstLockCreator.create({
50489
- lockFilePath: `${daemonPaths.runtimeDir}${sep}${firstLockCreator.channel.id}-consumer.lock.json`,
52424
+ lockFilePath: `${daemonPaths.runtimeDir}${sep2}${firstLockCreator.channel.id}-consumer.lock.json`,
50490
52425
  onDiagnostic: async (event, context) => {
50491
52426
  await runtime.logger.info(`${firstLockCreator.channel.id}.consumer_lock.${event}`, `${firstLockCreator.channel.id} consumer lock diagnostic`, context);
50492
52427
  }
@@ -50677,7 +52612,7 @@ async function defaultPromptSecret(message) {
50677
52612
  process.stdout.write(message);
50678
52613
  process.stdin.setRawMode(true);
50679
52614
  process.stdin.resume();
50680
- return await new Promise((resolve3, reject) => {
52615
+ return await new Promise((resolve4, reject) => {
50681
52616
  const chunks = [];
50682
52617
  let inEscape = false;
50683
52618
  const cleanup = () => {
@@ -50708,7 +52643,7 @@ async function defaultPromptSecret(message) {
50708
52643
  if (char === "\r" || char === `
50709
52644
  `) {
50710
52645
  cleanup();
50711
- resolve3(chunks.join(""));
52646
+ resolve4(chunks.join(""));
50712
52647
  return;
50713
52648
  }
50714
52649
  if (char === "" || char === "\b") {
@@ -50763,7 +52698,7 @@ function decodeFirstRunOnboarding(raw) {
50763
52698
  return null;
50764
52699
  }
50765
52700
  function requireHome2() {
50766
- const home = process.env.HOME ?? homedir13();
52701
+ const home = process.env.HOME ?? homedir17();
50767
52702
  if (!home) {
50768
52703
  throw new Error("Unable to resolve the current user home directory");
50769
52704
  }
@@ -50787,7 +52722,7 @@ function safeDaemonLogPaths() {
50787
52722
  const configPath = resolveConfigPathForCurrentEnv();
50788
52723
  const paths = resolveDaemonPathsForCurrentConfig();
50789
52724
  return {
50790
- appLog: join20(dirname14(configPath), "runtime", "app.log"),
52725
+ appLog: join25(dirname14(configPath), "runtime", "app.log"),
50791
52726
  stderrLog: paths.stderrLog
50792
52727
  };
50793
52728
  } catch {
@@ -50811,6 +52746,9 @@ function parseDoctorArgs(args) {
50811
52746
  case "--smoke":
50812
52747
  options.smoke = true;
50813
52748
  break;
52749
+ case "--fix":
52750
+ options.fix = true;
52751
+ break;
50814
52752
  case "--agent": {
50815
52753
  const value = args[index + 1];
50816
52754
  if (!value || value.startsWith("--")) {