@ganglion/xacpx 0.10.1 → 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"
@@ -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,20 +5769,20 @@ 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;
@@ -13109,6 +13200,14 @@ class ScheduledTaskService {
13109
13200
  listPending(chatKey) {
13110
13201
  return this.listPendingAllChats().filter((task) => task.chat_key === chatKey);
13111
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
+ }
13112
13211
  listPendingAllChats() {
13113
13212
  return Object.values(this.state.scheduled_tasks).filter((task) => task.status === "pending").sort((left, right) => left.execute_at.localeCompare(right.execute_at));
13114
13213
  }
@@ -13232,14 +13331,14 @@ var init_scheduled_service = () => {};
13232
13331
  import { readFileSync as readFileSync2 } from "node:fs";
13233
13332
  import { copyFile, mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
13234
13333
  import { homedir as homedir3 } from "node:os";
13235
- import { dirname as dirname4, join as join6 } from "node:path";
13334
+ import { dirname as dirname4, join as join7 } from "node:path";
13236
13335
  import { fileURLToPath as fileURLToPath2 } from "node:url";
13237
13336
  function resolveCoreRoot() {
13238
13337
  try {
13239
13338
  let dir = dirname4(fileURLToPath2(import.meta.url));
13240
13339
  for (let depth = 0;depth < 12; depth++) {
13241
13340
  try {
13242
- const pkg = JSON.parse(readFileSync2(join6(dir, "package.json"), "utf-8"));
13341
+ const pkg = JSON.parse(readFileSync2(join7(dir, "package.json"), "utf-8"));
13243
13342
  if (pkg.name && CORE_ROOT_NAMES.includes(pkg.name))
13244
13343
  return dir;
13245
13344
  } catch {}
@@ -13257,10 +13356,10 @@ async function ensureCoreResolution(pluginHome) {
13257
13356
  const root = resolveCoreRoot();
13258
13357
  if (!root)
13259
13358
  return;
13260
- const srcJs = join6(root, "dist", "plugin-api.js");
13359
+ const srcJs = join7(root, "dist", "plugin-api.js");
13261
13360
  for (const name of SHIM_SPECIFIERS) {
13262
- const targetDir = join6(pluginHome, "node_modules", name);
13263
- const dstJs = join6(targetDir, "plugin-api.js");
13361
+ const targetDir = join7(pluginHome, "node_modules", name);
13362
+ const dstJs = join7(targetDir, "plugin-api.js");
13264
13363
  await mkdir4(targetDir, { recursive: true });
13265
13364
  try {
13266
13365
  await copyFile(srcJs, dstJs);
@@ -13269,7 +13368,7 @@ async function ensureCoreResolution(pluginHome) {
13269
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.`);
13270
13369
  continue;
13271
13370
  }
13272
- await writeFile4(join6(targetDir, "package.json"), JSON.stringify({
13371
+ await writeFile4(join7(targetDir, "package.json"), JSON.stringify({
13273
13372
  name,
13274
13373
  type: "module",
13275
13374
  exports: {
@@ -13298,10 +13397,10 @@ function resolvePluginHome(input = {}) {
13298
13397
  if (envOverride)
13299
13398
  return envOverride;
13300
13399
  const home = coerceMissing(input.home) ?? coerceMissing(process.env.HOME) ?? homedir3();
13301
- return join6(coreHomeDir(home), "plugins");
13400
+ return join7(coreHomeDir(home), "plugins");
13302
13401
  }
13303
13402
  async function normalizePluginHomeManifest(pluginHome) {
13304
- const manifestPath = join6(pluginHome, "package.json");
13403
+ const manifestPath = join7(pluginHome, "package.json");
13305
13404
  let raw;
13306
13405
  try {
13307
13406
  raw = await readFile7(manifestPath, "utf8");
@@ -13323,7 +13422,7 @@ async function normalizePluginHomeManifest(pluginHome) {
13323
13422
  }
13324
13423
  async function ensurePluginHome(pluginHome) {
13325
13424
  await mkdir4(pluginHome, { recursive: true, mode: 448 });
13326
- 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) + `
13327
13426
  `, { flag: "wx" }).catch((error2) => {
13328
13427
  if (error2.code !== "EEXIST")
13329
13428
  throw error2;
@@ -17373,7 +17472,7 @@ function normalizeMediaArray(media) {
17373
17472
 
17374
17473
  // src/logging/rotating-file-writer.ts
17375
17474
  import { readdir as readdir2, rename as rename2, rm as rm6, stat as stat2 } from "node:fs/promises";
17376
- import { basename, dirname as dirname5, join as join8 } from "node:path";
17475
+ import { basename, dirname as dirname5, join as join9 } from "node:path";
17377
17476
  async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
17378
17477
  let currentSize = 0;
17379
17478
  try {
@@ -17423,7 +17522,7 @@ async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
17423
17522
  if (!file.startsWith(prefix) || !/^\d+$/.test(file.slice(prefix.length))) {
17424
17523
  continue;
17425
17524
  }
17426
- const candidate = join8(parentDir, file);
17525
+ const candidate = join9(parentDir, file);
17427
17526
  let details;
17428
17527
  try {
17429
17528
  details = await stat2(candidate);
@@ -19101,10 +19200,10 @@ var init_scheduled_turn = __esm(() => {
19101
19200
 
19102
19201
  // src/weixin/monitor/consumer-lock.ts
19103
19202
  import { mkdir as mkdir6, open as open3, readFile as readFile8, rm as rm7 } from "node:fs/promises";
19104
- import { dirname as dirname7, join as join9 } from "node:path";
19203
+ import { dirname as dirname7, join as join10 } from "node:path";
19105
19204
  import { homedir as homedir4 } from "node:os";
19106
19205
  function createWeixinConsumerLock(options = {}) {
19107
- 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");
19108
19207
  const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning4;
19109
19208
  const onDiagnostic = options.onDiagnostic;
19110
19209
  return {
@@ -19778,9 +19877,9 @@ __export(exports_plugin_loader, {
19778
19877
  });
19779
19878
  import { createRequire as createRequire2 } from "node:module";
19780
19879
  import { pathToFileURL } from "node:url";
19781
- import { join as join10 } from "node:path";
19880
+ import { join as join11 } from "node:path";
19782
19881
  async function importPluginFromHome(packageName, pluginHome) {
19783
- const requireFromHome = createRequire2(join10(pluginHome, "package.json"));
19882
+ const requireFromHome = createRequire2(join11(pluginHome, "package.json"));
19784
19883
  const entry = requireFromHome.resolve(packageName);
19785
19884
  return await import(pathToFileURL(entry).href);
19786
19885
  }
@@ -19825,13 +19924,13 @@ var init_plugin_loader = __esm(() => {
19825
19924
 
19826
19925
  // src/plugins/plugin-doctor.ts
19827
19926
  import { readFile as readFile10 } from "node:fs/promises";
19828
- import { join as join12 } from "node:path";
19927
+ import { join as join13 } from "node:path";
19829
19928
  function suggestedPluginPackageForChannel(type) {
19830
19929
  return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
19831
19930
  }
19832
19931
  async function readDependencyEntries(pluginHome) {
19833
19932
  try {
19834
- const raw = await readFile10(join12(pluginHome, "package.json"), "utf8");
19933
+ const raw = await readFile10(join13(pluginHome, "package.json"), "utf8");
19835
19934
  const parsed = JSON.parse(raw);
19836
19935
  const out = {};
19837
19936
  for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
@@ -20265,6 +20364,8 @@ function parseCommand(input) {
20265
20364
  return { kind: "session.reset" };
20266
20365
  if (command === "/mode" && parts.length === 1)
20267
20366
  return { kind: "mode.show" };
20367
+ if (command === "/model" && parts.length === 1)
20368
+ return { kind: "model.show" };
20268
20369
  if (command === "/replymode" && parts.length === 1)
20269
20370
  return { kind: "replymode.show" };
20270
20371
  if (command === "/config" && parts.length === 1)
@@ -20403,6 +20504,9 @@ function parseCommand(input) {
20403
20504
  if (command === "/mode" && parts[1]) {
20404
20505
  return { kind: "mode.set", modeId: parts[1] };
20405
20506
  }
20507
+ if (command === "/model" && parts[1]) {
20508
+ return { kind: "model.set", modelId: parts.slice(1).join(" ") };
20509
+ }
20406
20510
  if (command === "/replymode" && parts[1] === "reset" && parts.length === 2) {
20407
20511
  return { kind: "replymode.reset" };
20408
20512
  }
@@ -20410,7 +20514,14 @@ function parseCommand(input) {
20410
20514
  return { kind: "replymode.set", replyMode: parts[1] };
20411
20515
  }
20412
20516
  if (command === "/agent" && parts[1] === "add" && parts[2]) {
20413
- 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] };
20414
20525
  }
20415
20526
  if (command === "/agent" && parts[1] === "rm" && parts[2]) {
20416
20527
  return { kind: "agent.rm", name: parts[2] };
@@ -20488,6 +20599,7 @@ function parseCommand(input) {
20488
20599
  const alias = parts[2];
20489
20600
  let agent3 = "";
20490
20601
  let workspace3 = "";
20602
+ let model = "";
20491
20603
  let invalid = false;
20492
20604
  for (let index = 3;index < parts.length; index += 1) {
20493
20605
  if (parts[index] === "--agent" || parts[index] === "-a") {
@@ -20506,12 +20618,20 @@ function parseCommand(input) {
20506
20618
  workspace3 = parts[index + 1] ?? "";
20507
20619
  index += 1;
20508
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;
20509
20629
  }
20510
20630
  invalid = true;
20511
20631
  break;
20512
20632
  }
20513
20633
  if (!invalid && alias.trim().length > 0 && agent3.trim().length > 0 && workspace3.trim().length > 0) {
20514
- 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 };
20515
20635
  }
20516
20636
  }
20517
20637
  const shortcutTarget = readSessionShortcutTarget(parts, 3);
@@ -20967,6 +21087,7 @@ var init_command_policy = __esm(() => {
20967
21087
  "session.tail",
20968
21088
  "status",
20969
21089
  "mode.show",
21090
+ "model.show",
20970
21091
  "replymode.show",
20971
21092
  "config.show",
20972
21093
  "permission.status",
@@ -20986,6 +21107,7 @@ var init_command_policy = __esm(() => {
20986
21107
  "replymode.set": "/replymode",
20987
21108
  "replymode.reset": "/replymode reset",
20988
21109
  "mode.set": "/mode",
21110
+ "model.set": "/model",
20989
21111
  "permission.mode.set": "/permission",
20990
21112
  "permission.auto.set": "/permission auto",
20991
21113
  "config.set": "/config set",
@@ -21772,6 +21894,19 @@ function modeHelp() {
21772
21894
  examples: ["/mode", "/mode plan"]
21773
21895
  };
21774
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
+ }
21775
21910
  function replyModeHelp() {
21776
21911
  const s = t().session;
21777
21912
  return {
@@ -21845,7 +21980,7 @@ async function handleSessions(context, chatKey) {
21845
21980
  `)
21846
21981
  };
21847
21982
  }
21848
- async function handleSessionNew(context, chatKey, alias, agent3, workspace3) {
21983
+ async function handleSessionNew(context, chatKey, alias, agent3, workspace3, model) {
21849
21984
  const channelId = getChannelIdFromChatKey(chatKey);
21850
21985
  const internalAlias = scopeDisplayAliasToInternal(channelId, alias);
21851
21986
  const existing = context.sessions.getResolvedSessionByInternalAlias(internalAlias);
@@ -21853,6 +21988,10 @@ async function handleSessionNew(context, chatKey, alias, agent3, workspace3) {
21853
21988
  return { text: t().session.sessionAlreadyExists(alias, existing.agent, existing.workspace) };
21854
21989
  }
21855
21990
  const session3 = context.lifecycle.resolveSession(internalAlias, agent3, workspace3, `${workspace3}:${internalAlias}`);
21991
+ const normalizedModel = model?.trim();
21992
+ if (normalizedModel) {
21993
+ session3.model = normalizedModel;
21994
+ }
21856
21995
  const releaseTransportReservation = await context.lifecycle.reserveTransportSession(session3.transportSession);
21857
21996
  try {
21858
21997
  try {
@@ -21865,6 +22004,9 @@ async function handleSessionNew(context, chatKey, alias, agent3, workspace3) {
21865
22004
  return context.recovery.renderSessionCreationError(session3, error2);
21866
22005
  }
21867
22006
  await context.sessions.attachSession(internalAlias, agent3, workspace3, session3.transportSession);
22007
+ if (normalizedModel) {
22008
+ await context.sessions.setSessionModel(internalAlias, normalizedModel);
22009
+ }
21868
22010
  await context.sessions.useSession(chatKey, internalAlias);
21869
22011
  await refreshSessionTransportAgentCommandBestEffort(context, internalAlias, "session.agent_command_refresh_failed");
21870
22012
  await context.logger.info("session.created", "created and selected logical session", {
@@ -21991,6 +22133,43 @@ async function handleModeSet(context, chatKey, modeId) {
21991
22133
  await context.sessions.setCurrentSessionMode(chatKey, modeId);
21992
22134
  return { text: t().session.modeSet(modeId) };
21993
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
+ }
21994
22173
  async function handleReplyModeShow(context, chatKey) {
21995
22174
  const session3 = await context.sessions.getCurrentSession(chatKey);
21996
22175
  if (!session3) {
@@ -22176,7 +22355,7 @@ async function handleSessionRemove(context, chatKey, alias) {
22176
22355
  return { text: lines.join(`
22177
22356
  `) };
22178
22357
  }
22179
- 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) {
22180
22359
  const effectiveReplyMode = resolveEffectiveReplyMode(context.config, chatKey, session3.replyMode);
22181
22360
  if (!session3.replyMode)
22182
22361
  session3.replyMode = effectiveReplyMode;
@@ -22208,7 +22387,7 @@ async function promptWithSession(context, session3, chatKey, text, reply, replyC
22208
22387
  const { promptText, taskIds, groupIds, claimHumanReply } = await preparePromptWithFallback(context, session3, chatKey, text, replyContextToken, accountId);
22209
22388
  try {
22210
22389
  const replyContext = transportReply && context.quota && getChannelIdFromChatKey(chatKey) === "weixin" ? { chatKey, quota: context.quota } : undefined;
22211
- 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);
22212
22391
  if (claimHumanReply) {
22213
22392
  try {
22214
22393
  await context.orchestration?.claimActiveHumanReply?.(claimHumanReply);
@@ -22228,23 +22407,23 @@ async function promptWithSession(context, session3, chatKey, text, reply, replyC
22228
22407
  throw error2;
22229
22408
  }
22230
22409
  }
22231
- 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) {
22232
22411
  try {
22233
- 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);
22234
22413
  } catch (error2) {
22235
22414
  const recovered = await context.recovery.tryRecoverMissingSession(session3, error2);
22236
22415
  if (recovered) {
22237
- 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);
22238
22417
  }
22239
22418
  return context.recovery.renderTransportError(session3, error2);
22240
22419
  }
22241
22420
  }
22242
- 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) {
22243
22422
  const session3 = metadata?.boundSessionAlias ? context.sessions.getResolvedSessionByInternalAlias(metadata.boundSessionAlias) : await context.sessions.getCurrentSession(chatKey);
22244
22423
  if (!session3) {
22245
22424
  return { text: t().session.noCurrent };
22246
22425
  }
22247
- 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);
22248
22427
  }
22249
22428
  function toCoordinatorRouteChatMetadata(metadata) {
22250
22429
  if (!metadata) {
@@ -22917,7 +23096,7 @@ function agentHelp() {
22917
23096
  function handleAgents(context) {
22918
23097
  return { text: context.config ? renderAgents(context.config) : "No config loaded." };
22919
23098
  }
22920
- async function handleAgentAdd(context, templateName) {
23099
+ async function handleAgentAdd(context, templateName, model) {
22921
23100
  const a = t().agent;
22922
23101
  if (!context.config || !context.configStore) {
22923
23102
  return { text: a.noWritableConfig };
@@ -22927,13 +23106,18 @@ async function handleAgentAdd(context, templateName) {
22927
23106
  return { text: a.unsupportedTemplate(listAgentTemplates().join("、")) };
22928
23107
  }
22929
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;
22930
23111
  if (existing) {
22931
- if (sameAgentConfig(existing, template)) {
22932
- 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) };
22933
23118
  }
22934
- return { text: a.alreadyExistsDifferent(templateName) };
22935
23119
  }
22936
- const updated = await context.configStore.upsertAgent(templateName, template);
23120
+ const updated = await context.configStore.upsertAgent(templateName, desired);
22937
23121
  context.replaceConfig(updated);
22938
23122
  return { text: a.saved(templateName) };
22939
23123
  }
@@ -23297,6 +23481,7 @@ function buildHelpTopics() {
23297
23481
  configHelp(),
23298
23482
  orchestrationHelp(),
23299
23483
  modeHelp(),
23484
+ modelHelp(),
23300
23485
  replyModeHelp(),
23301
23486
  statusHelp(),
23302
23487
  cancelHelp(),
@@ -23727,7 +23912,7 @@ async function resolveNativeTarget(context, chatKey, input) {
23727
23912
  return {
23728
23913
  agent: agent3,
23729
23914
  agentDisplayName: displayAgentName(agent3),
23730
- agentCommand: resolveAgentCommand(agentConfig.driver, agentConfig.command),
23915
+ agentCommand: resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, context.config?.transport.preferLocalAgents !== false),
23731
23916
  workspace: workspaceResolution.workspace,
23732
23917
  workspaceLabel: workspaceResolution.workspaceLabel,
23733
23918
  cwd: workspaceResolution.cwd,
@@ -23965,6 +24150,7 @@ function displayAgentName(agent3) {
23965
24150
  }
23966
24151
  var NATIVE_SESSION_CACHE_TTL_MS;
23967
24152
  var init_native_session_handler = __esm(() => {
24153
+ init_resolve_agent_command();
23968
24154
  init_channel_scope();
23969
24155
  init_workspace_name();
23970
24156
  init_workspace_path();
@@ -24084,7 +24270,7 @@ import { spawn as spawn5 } from "node:child_process";
24084
24270
  import { createWriteStream } from "node:fs";
24085
24271
  import { mkdir as mkdir8 } from "node:fs/promises";
24086
24272
  import { homedir as homedir6 } from "node:os";
24087
- import { join as join14 } from "node:path";
24273
+ import { join as join15 } from "node:path";
24088
24274
  async function autoInstallOptionalDep(pkg, parentPackages, options = {}) {
24089
24275
  const runCli = options.runCli ?? defaultRunCli;
24090
24276
  const openLog = options.openLog ?? defaultLogSink;
@@ -24199,10 +24385,10 @@ ${err.message}`, reason: "spawn" });
24199
24385
  });
24200
24386
  });
24201
24387
  }, defaultLogSink = async () => {
24202
- const dir = join14(coreHomeDir(homedir6()), "logs");
24388
+ const dir = join15(coreHomeDir(homedir6()), "logs");
24203
24389
  await mkdir8(dir, { recursive: true });
24204
24390
  const timestamp = new Date().toISOString().replace(/[:.]/g, "").replace(/-/g, "");
24205
- const path14 = join14(dir, `auto-install-${timestamp}.log`);
24391
+ const path14 = join15(dir, `auto-install-${timestamp}.log`);
24206
24392
  const stream = createWriteStream(path14, { flags: "a" });
24207
24393
  return {
24208
24394
  path: path14,
@@ -24229,7 +24415,7 @@ import { spawn as spawn6 } from "node:child_process";
24229
24415
  import { createRequire as createRequire3 } from "node:module";
24230
24416
  import { access as access3 } from "node:fs/promises";
24231
24417
  import { homedir as homedir7 } from "node:os";
24232
- import { dirname as dirname10, join as join15 } from "node:path";
24418
+ import { dirname as dirname10, join as join16 } from "node:path";
24233
24419
  function deriveParentPackageName(platformPackage) {
24234
24420
  return platformPackage.replace(/-(?:linux|darwin|win32|windows|freebsd|openbsd|sunos|aix)(?:-(?:x64|arm64|ia32|arm|ppc64|s390x))?(?:-(?:baseline|musl|gnu|gnueabihf|musleabihf|msvc))?$/, "");
24235
24421
  }
@@ -24242,7 +24428,7 @@ async function discoverParentPackagePaths(platformPackage, seedPath, deps = {})
24242
24428
  const queryRoot = deps.queryPackageManagerRoot ?? defaultQueryPackageManagerRoot;
24243
24429
  const parentName = deriveParentPackageName(platformPackage);
24244
24430
  const rawCandidates = [];
24245
- 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");
24246
24432
  const [npmRoot, pnpmRoot, yarnRoot] = await Promise.all([
24247
24433
  queryRoot("npm"),
24248
24434
  queryRoot("pnpm"),
@@ -24265,20 +24451,20 @@ async function discoverParentPackagePaths(platformPackage, seedPath, deps = {})
24265
24451
  if (resolved)
24266
24452
  rawCandidates.push({ path: resolved, manager: classify(resolved) });
24267
24453
  }
24268
- rawCandidates.push({ path: join15(bunGlobalRoot, parentName), manager: "bun" });
24454
+ rawCandidates.push({ path: join16(bunGlobalRoot, parentName), manager: "bun" });
24269
24455
  if (npmRoot)
24270
- rawCandidates.push({ path: join15(npmRoot, parentName), manager: "npm" });
24456
+ rawCandidates.push({ path: join16(npmRoot, parentName), manager: "npm" });
24271
24457
  if (pnpmRoot)
24272
- rawCandidates.push({ path: join15(pnpmRoot, parentName), manager: "pnpm" });
24458
+ rawCandidates.push({ path: join16(pnpmRoot, parentName), manager: "pnpm" });
24273
24459
  if (yarnRoot)
24274
- rawCandidates.push({ path: join15(yarnRoot, parentName), manager: "yarn" });
24460
+ rawCandidates.push({ path: join16(yarnRoot, parentName), manager: "yarn" });
24275
24461
  const seen = new Set;
24276
24462
  const verified = [];
24277
24463
  for (const candidate of rawCandidates) {
24278
24464
  if (seen.has(candidate.path))
24279
24465
  continue;
24280
24466
  seen.add(candidate.path);
24281
- if (await fsExists(join15(candidate.path, "package.json"))) {
24467
+ if (await fsExists(join16(candidate.path, "package.json"))) {
24282
24468
  verified.push(candidate);
24283
24469
  }
24284
24470
  }
@@ -24349,7 +24535,7 @@ async function defaultQueryPackageManagerRoot(tool) {
24349
24535
  const trimmed = stdout2.trim().split(/\r?\n/).pop()?.trim() ?? "";
24350
24536
  if (!trimmed)
24351
24537
  return done(null);
24352
- done(spec.postfix ? join15(trimmed, spec.postfix) : trimmed);
24538
+ done(spec.postfix ? join16(trimmed, spec.postfix) : trimmed);
24353
24539
  });
24354
24540
  });
24355
24541
  }
@@ -24496,9 +24682,12 @@ class CommandRouter {
24496
24682
  this.logger = logger2 ?? createNoopAppLogger();
24497
24683
  this.activeTurns = activeTurns;
24498
24684
  }
24499
- 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) {
24500
24686
  const startedAt = Date.now();
24501
- 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
+ }
24502
24691
  await this.logger.debug("command.parsed", "parsed inbound command", {
24503
24692
  chatKey,
24504
24693
  kind: command.kind
@@ -24535,7 +24724,7 @@ class CommandRouter {
24535
24724
  case "agents":
24536
24725
  return handleAgents(this.createHandlerContext());
24537
24726
  case "agent.add":
24538
- return await handleAgentAdd(this.createHandlerContext(), command.template);
24727
+ return await handleAgentAdd(this.createHandlerContext(), command.template, command.model);
24539
24728
  case "agent.rm":
24540
24729
  return await handleAgentRemove(this.createHandlerContext(), command.name);
24541
24730
  case "permission.status":
@@ -24559,7 +24748,7 @@ class CommandRouter {
24559
24748
  case "sessions":
24560
24749
  return await handleSessions(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
24561
24750
  case "session.new":
24562
- 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);
24563
24752
  case "session.shortcut":
24564
24753
  return await handleSessionShortcut(this.createSessionHandlerContext(reply, perfSpan), chatKey, command.agent, command, false);
24565
24754
  case "session.shortcut.new":
@@ -24580,6 +24769,10 @@ class CommandRouter {
24580
24769
  return await handleModeShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
24581
24770
  case "mode.set":
24582
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);
24583
24776
  case "replymode.show":
24584
24777
  return await handleReplyModeShow(this.createSessionHandlerContext(undefined, perfSpan), chatKey);
24585
24778
  case "replymode.set":
@@ -24649,16 +24842,16 @@ class CommandRouter {
24649
24842
  ...this.sessions.resolveSession(descriptor.alias, descriptor.agent, descriptor.workspace, descriptor.transportSession),
24650
24843
  transient: true
24651
24844
  };
24652
- 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);
24653
24846
  }
24654
24847
  if (metadata?.scheduledSessionAlias) {
24655
24848
  const scheduledSession = await this.sessions.getSession(metadata.scheduledSessionAlias);
24656
24849
  if (!scheduledSession) {
24657
24850
  throw new Error(`session "${metadata.scheduledSessionAlias}" not found for scheduled prompt`);
24658
24851
  }
24659
- 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);
24660
24853
  }
24661
- 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);
24662
24855
  }
24663
24856
  }
24664
24857
  });
@@ -24710,11 +24903,103 @@ class CommandRouter {
24710
24903
  refreshSessionTransportAgentCommand: (alias) => this.refreshSessionTransportAgentCommand(alias)
24711
24904
  };
24712
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
+ }
24713
24996
  createSessionInteractionOps(perfSpan) {
24714
24997
  return {
24715
24998
  setModeTransportSession: (session3, modeId) => this.setModeTransportSession(session3, modeId),
24999
+ setModelTransportSession: (session3, modelId) => this.setModelTransportSession(session3, modelId),
25000
+ getModelTransportSession: (session3) => this.getModelTransportSession(session3),
24716
25001
  cancelTransportSession: (session3) => this.cancelTransportSession(session3),
24717
- 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)
24718
25003
  };
24719
25004
  }
24720
25005
  createSessionRenderRecoveryOps() {
@@ -24893,7 +25178,7 @@ class CommandRouter {
24893
25178
  async checkTransportSession(session3) {
24894
25179
  return await this.measureTransportCall("has_session", session3, () => this.transport.hasSession(session3));
24895
25180
  }
24896
- async promptTransportSession(session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan) {
25181
+ async promptTransportSession(session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan, onPlan) {
24897
25182
  session3.mcpCoordinatorSession ??= stableCoordinatorSession(session3.transportSession);
24898
25183
  let done = false;
24899
25184
  let abortRequested = false;
@@ -24950,7 +25235,8 @@ class CommandRouter {
24950
25235
  ...media ? { media } : {},
24951
25236
  ...reply ? { onSegment } : {},
24952
25237
  ...onToolEvent ? { onToolEvent } : {},
24953
- ...onThought ? { onThought } : {}
25238
+ ...onThought ? { onThought } : {},
25239
+ ...onPlan ? { onPlan } : {}
24954
25240
  }));
24955
25241
  } catch (error2) {
24956
25242
  localOutcome = isAbortError2(error2) || abortRequested ? "aborted" : "error";
@@ -24969,6 +25255,20 @@ class CommandRouter {
24969
25255
  async setModeTransportSession(session3, modeId) {
24970
25256
  return await this.measureTransportCall("set_mode", session3, () => this.transport.setMode(session3, modeId));
24971
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
+ }
24972
25272
  async cancelTransportSession(session3) {
24973
25273
  return await this.measureTransportCall("cancel", session3, () => this.transport.cancel(session3));
24974
25274
  }
@@ -25028,6 +25328,7 @@ function inferTransportKind(transport) {
25028
25328
  }
25029
25329
  var init_command_router = __esm(() => {
25030
25330
  init_app_logger();
25331
+ init_resolve_agent_command();
25031
25332
  init_acpx_session_index();
25032
25333
  init_prompt_output();
25033
25334
  init_parse_command();
@@ -25121,7 +25422,7 @@ class ConsoleAgent {
25121
25422
  ...m.fileName ? { fileName: m.fileName } : {}
25122
25423
  })) : undefined;
25123
25424
  request.perfSpan?.mark("agent.dispatched");
25124
- 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);
25125
25426
  }
25126
25427
  isKnownCommand(text) {
25127
25428
  return isKnownXacpxCommandText(text);
@@ -28935,12 +29236,14 @@ class ScheduledTaskScheduler {
28935
29236
  setIntervalFn;
28936
29237
  clearIntervalFn;
28937
29238
  dispatchTask;
29239
+ onSettled;
28938
29240
  logger;
28939
29241
  intervalHandle = null;
28940
29242
  ticking = false;
28941
29243
  constructor(service, deps) {
28942
29244
  this.service = service;
28943
29245
  this.dispatchTask = deps.dispatchTask;
29246
+ this.onSettled = deps.onSettled;
28944
29247
  this.intervalMs = deps.intervalMs ?? 5000;
28945
29248
  this.dispatchTimeoutMs = deps.dispatchTimeoutMs ?? DEFAULT_DISPATCH_TIMEOUT_MS;
28946
29249
  this.setIntervalFn = deps.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
@@ -28985,6 +29288,7 @@ class ScheduledTaskScheduler {
28985
29288
  });
28986
29289
  try {
28987
29290
  await this.service.markFailed(task.id, error2);
29291
+ this.notifySettled(task);
28988
29292
  } catch (markError) {
28989
29293
  await this.logger?.error("scheduled.dispatch.mark_failed", "markFailed threw; task state may be stale", {
28990
29294
  taskId: task.id,
@@ -28995,6 +29299,7 @@ class ScheduledTaskScheduler {
28995
29299
  }
28996
29300
  try {
28997
29301
  await this.service.markExecuted(task.id);
29302
+ this.notifySettled(task);
28998
29303
  } catch (markError) {
28999
29304
  await this.logger?.error("scheduled.dispatch.mark_executed_failed", "markExecuted threw after a successful dispatch; leaving task state for startup reconciliation", {
29000
29305
  taskId: task.id,
@@ -29006,6 +29311,18 @@ class ScheduledTaskScheduler {
29006
29311
  this.ticking = false;
29007
29312
  }
29008
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
+ }
29009
29326
  async dispatchWithTimeout(task) {
29010
29327
  const controller = new AbortController;
29011
29328
  let timer;
@@ -29041,7 +29358,8 @@ function buildScheduledDispatchTask(deps) {
29041
29358
  };
29042
29359
  }
29043
29360
  async function dispatchBound(task, abortSignal, deps) {
29044
- 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);
29045
29363
  if (!session3) {
29046
29364
  throw new Error(`session "${task.session_alias}" not found for scheduled task`);
29047
29365
  }
@@ -29050,6 +29368,7 @@ async function dispatchBound(task, abortSignal, deps) {
29050
29368
  chatKey: task.chat_key,
29051
29369
  taskId: task.id,
29052
29370
  sessionAlias: task.session_alias,
29371
+ executeAt: task.execute_at,
29053
29372
  noticeText,
29054
29373
  promptText: task.message,
29055
29374
  abortSignal,
@@ -29070,6 +29389,7 @@ async function dispatchTemp(task, abortSignal, deps) {
29070
29389
  chatKey: task.chat_key,
29071
29390
  taskId: task.id,
29072
29391
  sessionAlias: task.session_alias,
29392
+ executeAt: task.execute_at,
29073
29393
  sessionDescriptor: { alias, agent: task.agent, workspace: task.workspace, transportSession },
29074
29394
  noticeText,
29075
29395
  promptText: task.message,
@@ -29259,6 +29579,7 @@ class SessionService {
29259
29579
  workspace: workspace3,
29260
29580
  transport_session: transportSession,
29261
29581
  transport_agent_command: sameAgentExisting?.transport_agent_command,
29582
+ model: sameAgentExisting?.model,
29262
29583
  created_at: existing?.created_at ?? new Date().toISOString(),
29263
29584
  last_used_at: new Date().toISOString()
29264
29585
  });
@@ -29629,10 +29950,13 @@ class SessionService {
29629
29950
  if (!workspaceConfig) {
29630
29951
  throw new Error(`session "${session3.alias}" references workspace "${session3.workspace}", but that workspace is no longer registered`);
29631
29952
  }
29953
+ const channelId = getChannelIdFromChatKey(session3.alias);
29954
+ const effectiveReplyMode = channelId === "relay" ? "stream" : undefined;
29632
29955
  return {
29633
29956
  alias: session3.alias,
29634
29957
  agent: session3.agent,
29635
- 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,
29636
29960
  workspace: session3.workspace,
29637
29961
  transportSession: session3.transport_session,
29638
29962
  source: session3.source,
@@ -29642,9 +29966,46 @@ class SessionService {
29642
29966
  attachedAt: session3.attached_at,
29643
29967
  modeId: session3.mode_id,
29644
29968
  replyMode: session3.reply_mode,
29969
+ effectiveReplyMode,
29645
29970
  cwd: workspaceConfig.cwd
29646
29971
  };
29647
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
+ }
29648
30009
  async setSessionTransportAgentCommand(alias, transportAgentCommand) {
29649
30010
  await this.mutate(async () => {
29650
30011
  const session3 = this.state.sessions[alias];
@@ -29689,6 +30050,7 @@ class SessionService {
29689
30050
  attached_at: native ? now : undefined,
29690
30051
  ...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : sameAgentExisting?.transport_agent_command ? { transport_agent_command: sameAgentExisting.transport_agent_command } : {},
29691
30052
  mode_id: sameAgentExisting?.mode_id,
30053
+ model: sameAgentExisting?.model,
29692
30054
  reply_mode: sameAgentExisting?.reply_mode,
29693
30055
  created_at: existingSession?.created_at ?? now,
29694
30056
  last_used_at: now
@@ -29717,6 +30079,7 @@ class SessionService {
29717
30079
  }
29718
30080
  }
29719
30081
  var init_session_service = __esm(() => {
30082
+ init_resolve_agent_command();
29720
30083
  init_i18n();
29721
30084
  init_channel_scope();
29722
30085
  });
@@ -29740,6 +30103,13 @@ function createActiveTurnRegistry() {
29740
30103
  },
29741
30104
  isActive(chatKey, alias) {
29742
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;
29743
30113
  }
29744
30114
  };
29745
30115
  }
@@ -29848,6 +30218,7 @@ var init_command_hints = __esm(() => {
29848
30218
  config: "/config",
29849
30219
  orchestration: "/delegate",
29850
30220
  mode: "/mode",
30221
+ model: "/model",
29851
30222
  replymode: "/replymode",
29852
30223
  status: "/status",
29853
30224
  cancel: "/cancel",
@@ -29966,7 +30337,8 @@ async function runConsole(paths, deps) {
29966
30337
  perfTracer: runtime.perfTracer,
29967
30338
  commandHints: listXacpxCommandHints(),
29968
30339
  coreVersion: XACPX_CORE_VERSION,
29969
- locale: getLocale()
30340
+ locale: getLocale(),
30341
+ control: runtime.control
29970
30342
  });
29971
30343
  channelStartPromise.catch(() => {});
29972
30344
  let channelStartSettled = false;
@@ -30102,6 +30474,10 @@ function encodeBridgePromptThoughtEvent(event) {
30102
30474
  return `${JSON.stringify(event)}
30103
30475
  `;
30104
30476
  }
30477
+ function encodeBridgePromptPlanEvent(event) {
30478
+ return `${JSON.stringify(event)}
30479
+ `;
30480
+ }
30105
30481
  function encodeBridgeSessionProgressEvent(event) {
30106
30482
  return `${JSON.stringify(event)}
30107
30483
  `;
@@ -30111,8 +30487,228 @@ function encodeBridgeSessionNoteEvent(event) {
30111
30487
  `;
30112
30488
  }
30113
30489
 
30114
- // 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";
30115
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";
30116
30712
  import { fileURLToPath as fileURLToPath4 } from "node:url";
30117
30713
  import { createInterface } from "node:readline";
30118
30714
 
@@ -30179,6 +30775,11 @@ class AcpxBridgeClient {
30179
30775
  type: "prompt.thought",
30180
30776
  text: message.text
30181
30777
  });
30778
+ } else if (message.event === "prompt.plan") {
30779
+ pending.onEvent?.({
30780
+ type: "prompt.plan",
30781
+ entries: message.entries
30782
+ });
30182
30783
  } else if (message.event === "session.progress") {
30183
30784
  pending.onEvent?.({
30184
30785
  type: "session.progress",
@@ -30228,6 +30829,7 @@ class AcpxBridgeClient {
30228
30829
  function buildBridgeSpawnEnv(options = {}) {
30229
30830
  return {
30230
30831
  XACPX_LANG: getLocale(),
30832
+ XACPX_CLI_COMMAND: options.cliCommand ?? resolveDefaultXacpxCommand(process.env),
30231
30833
  XACPX_BRIDGE_ACPX_COMMAND: options.acpxCommand ?? "acpx",
30232
30834
  XACPX_BRIDGE_PERMISSION_MODE: options.permissionMode ?? "approve-all",
30233
30835
  XACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS: options.nonInteractivePermissions ?? "deny",
@@ -30254,7 +30856,7 @@ async function spawnAcpxBridgeClient(options = {}) {
30254
30856
  execPath: process.execPath,
30255
30857
  bridgeEntryPath
30256
30858
  });
30257
- const child = spawn7(spawnSpec.command, spawnSpec.args, {
30859
+ const child = spawn8(spawnSpec.command, spawnSpec.args, {
30258
30860
  cwd: options.cwd ?? process.cwd(),
30259
30861
  env: {
30260
30862
  ...process.env,
@@ -30306,6 +30908,7 @@ var init_acpx_bridge_client = __esm(() => {
30306
30908
  init_errors();
30307
30909
  init_terminate_process_tree();
30308
30910
  init_i18n();
30911
+ init_acpx_queue_owner_launcher();
30309
30912
  });
30310
30913
 
30311
30914
  // src/transport/segment-aggregator.ts
@@ -30508,6 +31111,64 @@ ${headsUpText}`);
30508
31111
  }
30509
31112
  };
30510
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
+ }
30511
31172
  function buildOverflowSummary(overflowCount) {
30512
31173
  if (overflowCount <= 0)
30513
31174
  return;
@@ -30565,7 +31226,8 @@ class AcpxBridgeTransport {
30565
31226
  });
30566
31227
  }
30567
31228
  async prompt(session3, text, reply, replyContext, options) {
30568
- const sink = reply ? createQuotaGatedReplySink({
31229
+ const streamMode = (session3.effectiveReplyMode ?? session3.replyMode) === "stream";
31230
+ const sink = reply ? streamMode ? createVerbatimReplySink(reply) : createQuotaGatedReplySink({
30569
31231
  reply,
30570
31232
  ...replyContext ? { replyContext } : {}
30571
31233
  }) : null;
@@ -30575,6 +31237,8 @@ class AcpxBridgeTransport {
30575
31237
  let toolEventChain = Promise.resolve();
30576
31238
  let thoughtError;
30577
31239
  let thoughtChain = Promise.resolve();
31240
+ let planError;
31241
+ let planChain = Promise.resolve();
30578
31242
  let toolEventMode = resolveToolEventMode(options);
30579
31243
  if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
30580
31244
  toolEventMode = "text";
@@ -30617,10 +31281,21 @@ class AcpxBridgeTransport {
30617
31281
  }
30618
31282
  return;
30619
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
+ }
30620
31294
  });
30621
31295
  await segmentChain;
30622
31296
  await toolEventChain;
30623
31297
  await thoughtChain;
31298
+ await planChain;
30624
31299
  if (sink) {
30625
31300
  const { overflowCount } = sink.finalize();
30626
31301
  await sink.drain({ timeoutMs: 30000 });
@@ -30638,6 +31313,9 @@ class AcpxBridgeTransport {
30638
31313
  if (thoughtError) {
30639
31314
  throw thoughtError;
30640
31315
  }
31316
+ if (planError) {
31317
+ throw planError;
31318
+ }
30641
31319
  return { text: summary ? `${summary}
30642
31320
 
30643
31321
  ${result.text}` : "" };
@@ -30651,6 +31329,9 @@ ${result.text}` : "" };
30651
31329
  if (thoughtError) {
30652
31330
  throw thoughtError;
30653
31331
  }
31332
+ if (planError) {
31333
+ throw planError;
31334
+ }
30654
31335
  return result;
30655
31336
  }
30656
31337
  async setMode(session3, modeId) {
@@ -30659,6 +31340,15 @@ ${result.text}` : "" };
30659
31340
  modeId
30660
31341
  });
30661
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
+ }
30662
31352
  async cancel(session3) {
30663
31353
  return await this.client.request("cancel", this.toParams(session3));
30664
31354
  }
@@ -30687,7 +31377,8 @@ ${result.text}` : "" };
30687
31377
  name: session3.transportSession,
30688
31378
  mcpCoordinatorSession: session3.mcpCoordinatorSession,
30689
31379
  mcpSourceHandle: session3.mcpSourceHandle,
30690
- replyMode: session3.replyMode ?? "verbose"
31380
+ replyMode: session3.effectiveReplyMode ?? session3.replyMode ?? "verbose",
31381
+ ...session3.model?.trim() ? { model: session3.model.trim() } : {}
30691
31382
  };
30692
31383
  }
30693
31384
  }
@@ -30695,21 +31386,6 @@ var init_acpx_bridge_transport = __esm(() => {
30695
31386
  init_quota_gated_reply_sink();
30696
31387
  });
30697
31388
 
30698
- // src/process/spawn-command.ts
30699
- function resolveSpawnCommand(command, args) {
30700
- if (SCRIPT_FILE_PATTERN.test(command)) {
30701
- return {
30702
- command: process.execPath,
30703
- args: [command, ...args]
30704
- };
30705
- }
30706
- return { command, args };
30707
- }
30708
- var SCRIPT_FILE_PATTERN;
30709
- var init_spawn_command = __esm(() => {
30710
- SCRIPT_FILE_PATTERN = /\.(c|m)?js$/i;
30711
- });
30712
-
30713
31389
  // src/transport/prompt-media.ts
30714
31390
  import { mkdtemp, open as open4, rm as rm9, writeFile as writeFile7 } from "node:fs/promises";
30715
31391
  import { tmpdir as defaultTmpdir } from "node:os";
@@ -30860,6 +31536,8 @@ function createStreamingPromptState(formatToolCalls = false, options) {
30860
31536
  let toolEventMode;
30861
31537
  let onToolEvent;
30862
31538
  let onThought;
31539
+ let onPlan;
31540
+ let rawStream = false;
30863
31541
  if (options === undefined) {
30864
31542
  toolEventMode = "text";
30865
31543
  onToolEvent = undefined;
@@ -30869,6 +31547,8 @@ function createStreamingPromptState(formatToolCalls = false, options) {
30869
31547
  } else {
30870
31548
  onToolEvent = options.onToolEvent;
30871
31549
  onThought = options.onThought;
31550
+ onPlan = options.onPlan;
31551
+ rawStream = options.rawStream ?? false;
30872
31552
  toolEventMode = resolveToolEventMode({
30873
31553
  toolEventMode: options.mode,
30874
31554
  onToolEvent
@@ -30882,13 +31562,15 @@ function createStreamingPromptState(formatToolCalls = false, options) {
30882
31562
  formatToolCalls,
30883
31563
  emittedToolCallIds: new Set,
30884
31564
  toolEventMode,
31565
+ rawStream,
30885
31566
  onToolEvent,
30886
31567
  onThought,
31568
+ onPlan,
30887
31569
  finalize() {
30888
31570
  if (this.pendingLine.trim().length > 0) {
30889
31571
  parseStreamingChunks(this, this.pendingLine);
30890
31572
  }
30891
- const remaining = this.buffer.trim();
31573
+ const remaining = this.rawStream ? this.buffer : this.buffer.trim();
30892
31574
  this.buffer = "";
30893
31575
  this.pendingLine = "";
30894
31576
  return remaining;
@@ -30920,9 +31602,9 @@ function parseStreamingChunks(state, line) {
30920
31602
  const update = event.params?.update;
30921
31603
  if (!update)
30922
31604
  return;
30923
- if (state.formatToolCalls && (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update")) {
31605
+ if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") {
30924
31606
  const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
30925
- const wantsText = state.toolEventMode === "text" || state.toolEventMode === "both";
31607
+ const wantsText = (state.toolEventMode === "text" || state.toolEventMode === "both") && state.formatToolCalls;
30926
31608
  if (wantsStructured && state.onToolEvent) {
30927
31609
  const toolEvent = buildToolUseEvent(update);
30928
31610
  if (toolEvent)
@@ -30942,6 +31624,12 @@ function parseStreamingChunks(state, line) {
30942
31624
  }
30943
31625
  return;
30944
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
+ }
30945
31633
  const isThoughtChunk = update.sessionUpdate === "agent_thought_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
30946
31634
  if (isThoughtChunk) {
30947
31635
  const chunk2 = update.content.text;
@@ -30958,6 +31646,8 @@ function parseStreamingChunks(state, line) {
30958
31646
  if (chunk.length === 0)
30959
31647
  return;
30960
31648
  state.buffer += chunk;
31649
+ if (state.rawStream)
31650
+ return;
30961
31651
  let boundary;
30962
31652
  while ((boundary = state.buffer.indexOf(`
30963
31653
 
@@ -31135,12 +31825,12 @@ var init_streaming_prompt = __esm(() => {
31135
31825
 
31136
31826
  // src/transport/acpx-cli/node-pty-helper.ts
31137
31827
  import { chmod as chmodFs } from "node:fs/promises";
31138
- import { dirname as dirname11, join as join16 } from "node:path";
31828
+ import { dirname as dirname11, join as join18 } from "node:path";
31139
31829
  function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
31140
31830
  if (platform === "win32") {
31141
31831
  return null;
31142
31832
  }
31143
- return join16(dirname11(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
31833
+ return join18(dirname11(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
31144
31834
  }
31145
31835
  async function ensureNodePtyHelperExecutable(helperPath, chmod5 = chmodFs) {
31146
31836
  if (!helperPath) {
@@ -31157,210 +31847,6 @@ async function ensureNodePtyHelperExecutable(helperPath, chmod5 = chmodFs) {
31157
31847
  }
31158
31848
  var init_node_pty_helper = () => {};
31159
31849
 
31160
- // src/transport/acpx-queue-owner-launcher.ts
31161
- import { createHash as createHash3 } from "node:crypto";
31162
- import { spawn as spawn8 } from "node:child_process";
31163
- import { readFile as readFile13, unlink } from "node:fs/promises";
31164
- import { homedir as homedir8 } from "node:os";
31165
- import { join as join17 } from "node:path";
31166
- function buildXacpxMcpServerSpec(input) {
31167
- const { command, args } = splitCommandLine(input.xacpxCommand);
31168
- return {
31169
- name: "xacpx",
31170
- type: "stdio",
31171
- command,
31172
- args: [
31173
- ...args,
31174
- "mcp-stdio",
31175
- "--coordinator-session",
31176
- input.coordinatorSession,
31177
- ...input.sourceHandle ? ["--source-handle", input.sourceHandle] : ["--internal-session-tools"]
31178
- ]
31179
- };
31180
- }
31181
- function buildQueueOwnerPayload(input) {
31182
- return {
31183
- sessionId: input.sessionId,
31184
- permissionMode: input.permissionMode,
31185
- nonInteractivePermissions: input.nonInteractivePermissions,
31186
- ttlMs: input.ttlMs ?? 300000,
31187
- maxQueueDepth: input.maxQueueDepth ?? 16,
31188
- ...Number.isFinite(input.promptRetries) ? { promptRetries: input.promptRetries } : {},
31189
- ...input.sessionOptions ? { sessionOptions: input.sessionOptions } : {},
31190
- mcpServers: input.mcpServers
31191
- };
31192
- }
31193
-
31194
- class AcpxQueueOwnerLauncher {
31195
- acpxCommand;
31196
- xacpxCommand;
31197
- spawnOwner;
31198
- terminateOwner;
31199
- baseEnv;
31200
- ttlMs;
31201
- maxQueueDepth;
31202
- launchLocks = new Map;
31203
- constructor(options) {
31204
- this.acpxCommand = options.acpxCommand;
31205
- this.xacpxCommand = options.xacpxCommand ?? resolveDefaultXacpxCommand(options.baseEnv ?? process.env);
31206
- this.spawnOwner = options.spawnOwner ?? defaultQueueOwnerSpawner;
31207
- this.terminateOwner = options.terminateOwner ?? createDefaultQueueOwnerTerminator(options.acpxCommand);
31208
- this.baseEnv = options.baseEnv ?? process.env;
31209
- this.ttlMs = options.ttlMs;
31210
- this.maxQueueDepth = options.maxQueueDepth;
31211
- }
31212
- async launch(input) {
31213
- const key = input.acpxRecordId;
31214
- const previous = this.launchLocks.get(key) ?? Promise.resolve();
31215
- const next = previous.then(() => this.doLaunch(input), () => this.doLaunch(input));
31216
- const tracked = next.catch(() => {});
31217
- this.launchLocks.set(key, tracked);
31218
- tracked.finally(() => {
31219
- if (this.launchLocks.get(key) === tracked) {
31220
- this.launchLocks.delete(key);
31221
- }
31222
- });
31223
- return next;
31224
- }
31225
- async doLaunch(input) {
31226
- await this.terminateOwner(input.acpxRecordId);
31227
- const payload = buildQueueOwnerPayload({
31228
- sessionId: input.acpxRecordId,
31229
- permissionMode: input.permissionMode,
31230
- nonInteractivePermissions: input.nonInteractivePermissions,
31231
- ttlMs: this.ttlMs,
31232
- maxQueueDepth: this.maxQueueDepth,
31233
- mcpServers: [buildXacpxMcpServerSpec({
31234
- xacpxCommand: this.xacpxCommand,
31235
- coordinatorSession: input.coordinatorSession,
31236
- ...input.sourceHandle ? { sourceHandle: input.sourceHandle } : {}
31237
- })]
31238
- });
31239
- const spawnSpec = resolveSpawnCommand(this.acpxCommand, ["__queue-owner"]);
31240
- await this.spawnOwner(spawnSpec.command, spawnSpec.args, {
31241
- env: {
31242
- ...stringEnv(this.baseEnv),
31243
- XACPX_LANG: getLocale(),
31244
- ACPX_QUEUE_OWNER_PAYLOAD: JSON.stringify(payload)
31245
- }
31246
- });
31247
- }
31248
- }
31249
- function splitCommandLine(value) {
31250
- const parts = [];
31251
- let current = "";
31252
- let quote = null;
31253
- let escaping = false;
31254
- for (const char of value) {
31255
- if (escaping) {
31256
- current += char;
31257
- escaping = false;
31258
- continue;
31259
- }
31260
- if (char === "\\" && quote !== "'") {
31261
- escaping = true;
31262
- continue;
31263
- }
31264
- if (quote) {
31265
- if (char === quote) {
31266
- quote = null;
31267
- } else {
31268
- current += char;
31269
- }
31270
- continue;
31271
- }
31272
- if (char === "'" || char === '"') {
31273
- quote = char;
31274
- continue;
31275
- }
31276
- if (/\s/.test(char)) {
31277
- if (current.length > 0) {
31278
- parts.push(current);
31279
- current = "";
31280
- }
31281
- continue;
31282
- }
31283
- current += char;
31284
- }
31285
- if (escaping) {
31286
- current += "\\";
31287
- }
31288
- if (quote) {
31289
- throw new Error("xacpx MCP command has an unterminated quote");
31290
- }
31291
- if (current.length > 0) {
31292
- parts.push(current);
31293
- }
31294
- if (parts.length === 0) {
31295
- throw new Error("xacpx MCP command must not be empty");
31296
- }
31297
- return { command: parts[0], args: parts.slice(1) };
31298
- }
31299
- function stringEnv(env) {
31300
- return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
31301
- }
31302
- async function defaultQueueOwnerSpawner(command, args, options) {
31303
- await new Promise((resolve3, reject) => {
31304
- const child = spawn8(command, args, {
31305
- detached: true,
31306
- stdio: "ignore",
31307
- env: options.env,
31308
- windowsHide: true
31309
- });
31310
- child.once("error", reject);
31311
- child.once("spawn", () => {
31312
- child.unref();
31313
- resolve3();
31314
- });
31315
- });
31316
- }
31317
- function createDefaultQueueOwnerTerminator(_acpxCommand) {
31318
- return async (sessionId) => {
31319
- await terminateAcpxQueueOwner(sessionId);
31320
- };
31321
- }
31322
- async function terminateAcpxQueueOwner(sessionId) {
31323
- const lockPath = queueLockFilePath(sessionId);
31324
- let owner;
31325
- try {
31326
- owner = JSON.parse(await readFile13(lockPath, "utf8"));
31327
- } catch {
31328
- return;
31329
- }
31330
- if (typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0) {
31331
- await terminateProcessTree(owner.pid, { detachedProcessGroup: true });
31332
- }
31333
- await unlink(lockPath).catch(() => {});
31334
- }
31335
- function queueLockFilePath(sessionId) {
31336
- return join17(homedir8(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
31337
- }
31338
- function shortHash(value, length) {
31339
- return createHash3("sha256").update(value).digest("hex").slice(0, length);
31340
- }
31341
- function resolveDefaultXacpxCommand(env) {
31342
- const cliCommand = coreEnv("CLI_COMMAND", env);
31343
- if (cliCommand?.trim()) {
31344
- return cliCommand.trim();
31345
- }
31346
- const daemonArg0 = coreEnv("DAEMON_ARG0", env);
31347
- if (daemonArg0?.trim()) {
31348
- return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(daemonArg0.trim())}`;
31349
- }
31350
- if (process.argv[1]) {
31351
- return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(process.argv[1])}`;
31352
- }
31353
- return "xacpx";
31354
- }
31355
- function quoteCommandPart(value) {
31356
- return quoteIfNeeded(value);
31357
- }
31358
- var init_acpx_queue_owner_launcher = __esm(() => {
31359
- init_spawn_command();
31360
- init_terminate_process_tree();
31361
- init_i18n();
31362
- });
31363
-
31364
31850
  // src/transport/permission-mode-flag.ts
31365
31851
  function permissionModeToFlag(permissionMode) {
31366
31852
  switch (permissionMode) {
@@ -31578,13 +32064,15 @@ class AcpxCliTransport {
31578
32064
  const structuredPrompt = await createStructuredPromptFile(text, options?.media);
31579
32065
  const args = this.buildPromptArgs(session3, text, structuredPrompt?.filePath);
31580
32066
  try {
31581
- if (reply || options?.onSegment || options?.onToolEvent || options?.onThought) {
31582
- 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";
31583
32071
  let toolEventMode = resolveToolEventMode(options);
31584
32072
  if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
31585
32073
  toolEventMode = "text";
31586
32074
  }
31587
- 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);
31588
32076
  const baseText = getPromptText(result2);
31589
32077
  if (!reply) {
31590
32078
  return { text: baseText };
@@ -31610,6 +32098,34 @@ ${baseText}` : "" };
31610
32098
  modeId
31611
32099
  ]));
31612
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
+ }
31613
32129
  async cancel(session3) {
31614
32130
  const output = await this.run(this.buildArgs(session3, [
31615
32131
  "cancel",
@@ -31673,7 +32189,8 @@ ${baseText}` : "" };
31673
32189
  coordinatorSession: session3.mcpCoordinatorSession,
31674
32190
  ...session3.mcpSourceHandle ? { sourceHandle: session3.mcpSourceHandle } : {},
31675
32191
  permissionMode: this.permissionMode,
31676
- nonInteractivePermissions: this.nonInteractivePermissions
32192
+ nonInteractivePermissions: this.nonInteractivePermissions,
32193
+ ...session3.model?.trim() ? { sessionOptions: { model: session3.model.trim() } } : {}
31677
32194
  });
31678
32195
  }
31679
32196
  async readSessionRecord(session3) {
@@ -31741,13 +32258,13 @@ ${baseText}` : "" };
31741
32258
  })
31742
32259
  ]);
31743
32260
  }
31744
- 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) {
31745
32262
  const hooks = this.streamingHooks;
31746
32263
  const doSpawn = hooks.spawnPrompt ?? ((cmd, spawnArgs) => spawn9(cmd, spawnArgs, { stdio: ["ignore", "pipe", "pipe"] }));
31747
32264
  const setIntervalFn = hooks.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
31748
32265
  const clearIntervalFn = hooks.clearIntervalFn ?? ((timer) => clearInterval(timer));
31749
- const maxSegmentWaitMs = hooks.maxSegmentWaitMs ?? 30000;
31750
- const flushCheckIntervalMs = hooks.flushCheckIntervalMs ?? 5000;
32266
+ const maxSegmentWaitMs = hooks.maxSegmentWaitMs ?? (rawStream ? 200 : 30000);
32267
+ const flushCheckIntervalMs = hooks.flushCheckIntervalMs ?? (rawStream ? 80 : 5000);
31751
32268
  const now = hooks.now ?? (() => Date.now());
31752
32269
  return await new Promise((resolve3, reject) => {
31753
32270
  const spawnSpec = resolveSpawnCommand(command, args);
@@ -31761,10 +32278,14 @@ ${baseText}` : "" };
31761
32278
  let toolEventError;
31762
32279
  let thoughtChain = Promise.resolve();
31763
32280
  let thoughtError;
32281
+ let planChain = Promise.resolve();
32282
+ let planError;
31764
32283
  const userOnToolEvent = onToolEvent;
31765
32284
  const userOnThought = onThought;
32285
+ const userOnPlan = onPlan;
31766
32286
  const state = createStreamingPromptState(formatToolCalls, {
31767
32287
  mode: toolEventMode,
32288
+ rawStream,
31768
32289
  ...userOnToolEvent ? {
31769
32290
  onToolEvent: (event) => {
31770
32291
  toolEventChain = toolEventChain.then(() => userOnToolEvent(event)).catch((error2) => {
@@ -31778,9 +32299,16 @@ ${baseText}` : "" };
31778
32299
  thoughtError ??= error2;
31779
32300
  });
31780
32301
  }
32302
+ } : {},
32303
+ ...userOnPlan ? {
32304
+ onPlan: (entries) => {
32305
+ planChain = planChain.then(() => userOnPlan(entries)).catch((error2) => {
32306
+ planError ??= error2;
32307
+ });
32308
+ }
31781
32309
  } : {}
31782
32310
  });
31783
- const sink = reply ? createQuotaGatedReplySink({
32311
+ const sink = reply ? rawStream ? createVerbatimReplySink(reply) : createQuotaGatedReplySink({
31784
32312
  reply,
31785
32313
  ...replyContext ? { replyContext } : {}
31786
32314
  }) : null;
@@ -31794,7 +32322,7 @@ ${baseText}` : "" };
31794
32322
  lastReplyAt = now();
31795
32323
  };
31796
32324
  const flushBuffer = () => {
31797
- const remaining = state.buffer.trim();
32325
+ const remaining = rawStream ? state.buffer : state.buffer.trim();
31798
32326
  if (remaining.length > 0) {
31799
32327
  state.buffer = "";
31800
32328
  feedSegment(remaining);
@@ -31831,7 +32359,8 @@ ${baseText}` : "" };
31831
32359
  sink?.drain({ timeoutMs: 30000 }) ?? Promise.resolve(),
31832
32360
  segmentChain,
31833
32361
  toolEventChain,
31834
- thoughtChain
32362
+ thoughtChain,
32363
+ planChain
31835
32364
  ]).then(() => {
31836
32365
  const deferred = sink?.getPendingError();
31837
32366
  if (deferred) {
@@ -31850,6 +32379,10 @@ ${baseText}` : "" };
31850
32379
  reject(thoughtError);
31851
32380
  return;
31852
32381
  }
32382
+ if (planError) {
32383
+ reject(planError);
32384
+ return;
32385
+ }
31853
32386
  resolve3({
31854
32387
  result: { code: code ?? 1, stdout: stdout2, stderr },
31855
32388
  overflowCount
@@ -31866,7 +32399,8 @@ ${baseText}` : "" };
31866
32399
  "quiet",
31867
32400
  "--cwd",
31868
32401
  session3.cwd,
31869
- ...this.buildPermissionArgs()
32402
+ ...this.buildPermissionArgs(),
32403
+ ...this.buildModelArgs(session3)
31870
32404
  ];
31871
32405
  if (session3.agentCommand) {
31872
32406
  return [...prefix, "--agent", session3.agentCommand, ...tail2];
@@ -31888,6 +32422,7 @@ ${baseText}` : "" };
31888
32422
  "--cwd",
31889
32423
  session3.cwd,
31890
32424
  ...this.buildPermissionArgs(),
32425
+ ...this.buildModelArgs(session3),
31891
32426
  ...this.buildQueueOwnerTtlArgs()
31892
32427
  ];
31893
32428
  const tail2 = promptFile ? ["prompt", "-s", session3.transportSession, "--file", promptFile] : ["prompt", "-s", session3.transportSession, text];
@@ -31896,6 +32431,10 @@ ${baseText}` : "" };
31896
32431
  }
31897
32432
  return [...prefix, session3.agent, ...tail2];
31898
32433
  }
32434
+ buildModelArgs(session3) {
32435
+ const model = session3.model?.trim();
32436
+ return model ? ["--model", model] : [];
32437
+ }
31899
32438
  buildQueueOwnerTtlArgs() {
31900
32439
  if (typeof this.queueOwnerTtlSeconds !== "number" || !Number.isFinite(this.queueOwnerTtlSeconds)) {
31901
32440
  return [];
@@ -32092,7 +32631,7 @@ function workerBindingReapTargets(orchestration3, config4) {
32092
32631
  if (!cwd) {
32093
32632
  continue;
32094
32633
  }
32095
- const agentCommand = resolveAgentCommand(agentConfig.driver, agentConfig.command);
32634
+ const agentCommand = resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, config4.transport.preferLocalAgents !== false);
32096
32635
  targets.push({
32097
32636
  agent: binding.targetAgent,
32098
32637
  ...agentCommand ? { agentCommand } : {},
@@ -32102,7 +32641,9 @@ function workerBindingReapTargets(orchestration3, config4) {
32102
32641
  }
32103
32642
  return targets;
32104
32643
  }
32105
- var init_collect_reap_targets = () => {};
32644
+ var init_collect_reap_targets = __esm(() => {
32645
+ init_resolve_agent_command();
32646
+ });
32106
32647
 
32107
32648
  // src/channels/channel-registry.ts
32108
32649
  var exports_channel_registry = {};
@@ -32389,6 +32930,730 @@ var init_quota_manager = __esm(() => {
32389
32930
  DEFAULT_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
32390
32931
  });
32391
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
+
32392
33657
  // src/main.ts
32393
33658
  var exports_main = {};
32394
33659
  __export(exports_main, {
@@ -32399,8 +33664,8 @@ __export(exports_main, {
32399
33664
  buildApp: () => buildApp
32400
33665
  });
32401
33666
  import { randomUUID as randomUUID3 } from "node:crypto";
32402
- import { homedir as homedir9 } from "node:os";
32403
- 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";
32404
33669
  import { fileURLToPath as fileURLToPath5 } from "node:url";
32405
33670
  function startProgressHeartbeat(orchestration3, config4, logger2, channel) {
32406
33671
  const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
@@ -32676,7 +33941,7 @@ async function buildApp(paths, deps = {}) {
32676
33941
  return {
32677
33942
  alias: input.workerSession,
32678
33943
  agent: input.targetAgent,
32679
- agentCommand: resolveAgentCommand(agentConfig.driver, agentConfig.command),
33944
+ agentCommand: resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, config4.transport.preferLocalAgents !== false),
32680
33945
  workspace: input.workspace,
32681
33946
  transportSession: input.workerSession,
32682
33947
  cwd: input.cwd
@@ -32890,9 +34155,52 @@ async function buildApp(paths, deps = {}) {
32890
34155
  });
32891
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);
32892
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
+ });
32893
34200
  const scheduledScheduler = new ScheduledTaskScheduler(scheduledService, {
32894
34201
  dispatchTask: buildScheduledDispatchTask({
32895
34202
  getSession: (alias) => sessions.getSession(alias),
34203
+ resolveAliasForChat: (chatKey, alias) => sessions.resolveAliasForChat(chatKey, alias),
32896
34204
  resolveSession: (alias, agent4, workspace3, transportSession) => sessions.resolveSession(alias, agent4, workspace3, transportSession),
32897
34205
  sendScheduledMessage: async (input) => {
32898
34206
  if (!deps.channel?.sendScheduledMessage) {
@@ -32903,6 +34211,7 @@ async function buildApp(paths, deps = {}) {
32903
34211
  ...transport.removeSession ? { removeSession: (session3) => transport.removeSession(session3) } : {},
32904
34212
  logger: logger2
32905
34213
  }),
34214
+ onSettled: (task) => controlEvents.emit({ type: "scheduled-changed", chatKey: task.chat_key }),
32906
34215
  logger: logger2
32907
34216
  });
32908
34217
  const reapWarmQueueOwners = async (phase) => {
@@ -32952,6 +34261,7 @@ async function buildApp(paths, deps = {}) {
32952
34261
  service: scheduledService,
32953
34262
  scheduler: scheduledScheduler
32954
34263
  },
34264
+ control,
32955
34265
  reapStaleQueueOwners: () => reapWarmQueueOwners("startup"),
32956
34266
  dispose: async () => {
32957
34267
  scheduledScheduler.stop();
@@ -33015,8 +34325,8 @@ async function main() {
33015
34325
  }
33016
34326
  }
33017
34327
  async function prepareChannelMedia(configPath, config4) {
33018
- const runtimeDir = join18(dirname12(configPath), "runtime");
33019
- const mediaRootDir = join18(runtimeDir, "media");
34328
+ const runtimeDir = join21(dirname12(configPath), "runtime");
34329
+ const mediaRootDir = join21(runtimeDir, "media");
33020
34330
  const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
33021
34331
  await mediaStore.cleanupExpired().catch((error2) => {
33022
34332
  console.error("[xacpx] media cleanup failed:", error2 instanceof Error ? error2.message : String(error2));
@@ -33025,16 +34335,16 @@ async function prepareChannelMedia(configPath, config4) {
33025
34335
  return { mediaStore, channelDeps: { mediaStore, allowedMediaRoots } };
33026
34336
  }
33027
34337
  function resolveRuntimePaths() {
33028
- const home = process.env.HOME ?? homedir9();
34338
+ const home = process.env.HOME ?? homedir11();
33029
34339
  if (!home) {
33030
34340
  throw new Error("Unable to resolve the current user home directory");
33031
34341
  }
33032
- const configPath = coreEnv("CONFIG") ?? join18(coreHomeDir(home), "config.json");
33033
- const runtimeDir = join18(dirname12(configPath), "runtime");
34342
+ const configPath = coreEnv("CONFIG") ?? join21(coreHomeDir(home), "config.json");
34343
+ const runtimeDir = join21(dirname12(configPath), "runtime");
33034
34344
  return {
33035
34345
  configPath,
33036
- statePath: coreEnv("STATE") ?? join18(coreHomeDir(home), "state.json"),
33037
- perfLogPath: join18(runtimeDir, "perf.log"),
34346
+ statePath: coreEnv("STATE") ?? join21(coreHomeDir(home), "state.json"),
34347
+ perfLogPath: join21(runtimeDir, "perf.log"),
33038
34348
  orchestrationSocketPath: coreEnv("ORCHESTRATION_SOCKET") ?? resolveDaemonOrchestrationSocketPath(runtimeDir)
33039
34349
  };
33040
34350
  }
@@ -33046,13 +34356,13 @@ function resolveBridgeEntryPath() {
33046
34356
  }
33047
34357
  function resolveAppLogPath(configPath) {
33048
34358
  const rootDir = dirname12(configPath);
33049
- const runtimeDir = join18(rootDir, "runtime");
33050
- return join18(runtimeDir, "app.log");
34359
+ const runtimeDir = join21(rootDir, "runtime");
34360
+ return join21(runtimeDir, "app.log");
33051
34361
  }
33052
34362
  function resolvePerfLogPath(configPath) {
33053
34363
  const rootDir = dirname12(configPath);
33054
- const runtimeDir = join18(rootDir, "runtime");
33055
- return join18(runtimeDir, "perf.log");
34364
+ const runtimeDir = join21(rootDir, "runtime");
34365
+ return join21(runtimeDir, "perf.log");
33056
34366
  }
33057
34367
  function resolveOrchestrationSocketPathFromConfigPath(configPath) {
33058
34368
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
@@ -33068,6 +34378,7 @@ var init_main = __esm(async () => {
33068
34378
  init_ensure_config();
33069
34379
  init_load_config();
33070
34380
  init_resolve_acpx_command();
34381
+ init_resolve_agent_command();
33071
34382
  init_console_agent();
33072
34383
  init_app_logger();
33073
34384
  init_daemon_files();
@@ -33095,6 +34406,8 @@ var init_main = __esm(async () => {
33095
34406
  init_inbound();
33096
34407
  init_render_text();
33097
34408
  init_quota_manager();
34409
+ init_control_service();
34410
+ init_agent_catalog();
33098
34411
  init_perf_tracer();
33099
34412
  init_bootstrap();
33100
34413
  init_i18n();
@@ -33148,7 +34461,7 @@ function buildDetails(metadata, version2, verbose) {
33148
34461
  }
33149
34462
  async function defaultRunVersion(command) {
33150
34463
  const spawnSpec = resolveSpawnCommand(command, ["--version"]);
33151
- return await new Promise((resolve3, reject) => {
34464
+ return await new Promise((resolve4, reject) => {
33152
34465
  const child = spawn11(spawnSpec.command, spawnSpec.args, {
33153
34466
  stdio: ["ignore", "pipe", "pipe"]
33154
34467
  });
@@ -33165,7 +34478,7 @@ async function defaultRunVersion(command) {
33165
34478
  if (code === 0) {
33166
34479
  const version2 = stdout2.trim() || stderr.trim();
33167
34480
  if (version2.length > 0) {
33168
- resolve3(version2);
34481
+ resolve4(version2);
33169
34482
  return;
33170
34483
  }
33171
34484
  }
@@ -33287,12 +34600,12 @@ var init_config_check = __esm(async () => {
33287
34600
  });
33288
34601
 
33289
34602
  // src/doctor/checks/daemon-check.ts
33290
- import { readdir as readdir3, readFile as readFile14, rm as rm10 } from "node:fs/promises";
34603
+ import { readdir as readdir4, readFile as readFile15, rm as rm10 } from "node:fs/promises";
33291
34604
  import { fileURLToPath as fileURLToPath6 } from "node:url";
33292
- import { homedir as homedir10 } from "node:os";
33293
- import { join as join19 } from "node:path";
34605
+ import { homedir as homedir12 } from "node:os";
34606
+ import { join as join22 } from "node:path";
33294
34607
  async function checkDaemon(options = {}) {
33295
- const home = options.home ?? process.env.HOME ?? homedir10();
34608
+ const home = options.home ?? process.env.HOME ?? homedir12();
33296
34609
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
33297
34610
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
33298
34611
  home,
@@ -33392,7 +34705,7 @@ async function detectStaleConsumerLockFix(runtimeDir, deps) {
33392
34705
  if (!fileName.endsWith(CONSUMER_LOCK_SUFFIX)) {
33393
34706
  continue;
33394
34707
  }
33395
- const lockPath = join19(runtimeDir, fileName);
34708
+ const lockPath = join22(runtimeDir, fileName);
33396
34709
  const lock2 = await deps.readConsumerLock(lockPath);
33397
34710
  if (lock2 && !deps.isProcessRunning(lock2.pid)) {
33398
34711
  stalePaths.push(lockPath);
@@ -33426,14 +34739,14 @@ async function detectStaleConsumerLockFix(runtimeDir, deps) {
33426
34739
  }
33427
34740
  async function defaultListConsumerLocks(runtimeDir) {
33428
34741
  try {
33429
- return await readdir3(runtimeDir);
34742
+ return await readdir4(runtimeDir);
33430
34743
  } catch {
33431
34744
  return [];
33432
34745
  }
33433
34746
  }
33434
34747
  async function defaultReadConsumerLock(path15) {
33435
34748
  try {
33436
- const raw = await readFile14(path15, "utf8");
34749
+ const raw = await readFile15(path15, "utf8");
33437
34750
  const parsed = JSON.parse(raw);
33438
34751
  return typeof parsed.pid === "number" ? { pid: parsed.pid } : null;
33439
34752
  } catch {
@@ -33456,11 +34769,11 @@ var init_daemon_check = __esm(() => {
33456
34769
  });
33457
34770
 
33458
34771
  // src/doctor/checks/logs-check.ts
33459
- import { stat as stat3, readdir as readdir4 } from "node:fs/promises";
33460
- import { basename as basename3, join as join20 } from "node:path";
33461
- import { homedir as homedir11 } from "node:os";
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";
33462
34775
  async function checkLogs(options = {}) {
33463
- const home = options.home ?? process.env.HOME ?? homedir11();
34776
+ const home = options.home ?? process.env.HOME ?? homedir13();
33464
34777
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
33465
34778
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
33466
34779
  home,
@@ -33492,7 +34805,7 @@ async function checkLogs(options = {}) {
33492
34805
  const matched = entries.filter((entry) => isTrackedLogName(entry, tracked));
33493
34806
  const files = [];
33494
34807
  for (const name of matched) {
33495
- const path15 = join20(paths.runtimeDir, name);
34808
+ const path15 = join23(paths.runtimeDir, name);
33496
34809
  try {
33497
34810
  const fileStat = await probe.stat(path15);
33498
34811
  if (fileStat.isDirectory()) {
@@ -33572,8 +34885,8 @@ function formatBytes(bytes) {
33572
34885
  }
33573
34886
  function createLogsFsProbe() {
33574
34887
  return {
33575
- stat: async (path15) => await stat3(path15),
33576
- readdir: async (path15) => await readdir4(path15)
34888
+ stat: async (path15) => await stat4(path15),
34889
+ readdir: async (path15) => await readdir5(path15)
33577
34890
  };
33578
34891
  }
33579
34892
  function formatError6(error2) {
@@ -33642,9 +34955,9 @@ var init_orchestration_health = __esm(() => {
33642
34955
  });
33643
34956
 
33644
34957
  // src/doctor/checks/orchestration-socket-check.ts
33645
- import { homedir as homedir12 } from "node:os";
34958
+ import { homedir as homedir14 } from "node:os";
33646
34959
  async function checkOrchestrationSocket(options = {}) {
33647
- const home = options.home ?? process.env.HOME ?? homedir12();
34960
+ const home = options.home ?? process.env.HOME ?? homedir14();
33648
34961
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
33649
34962
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
33650
34963
  home,
@@ -33817,11 +35130,11 @@ var init_plugin_check = __esm(async () => {
33817
35130
 
33818
35131
  // src/doctor/checks/runtime-check.ts
33819
35132
  import { constants } from "node:fs";
33820
- import { access as access4, stat as stat4 } from "node:fs/promises";
35133
+ import { access as access4, stat as stat5 } from "node:fs/promises";
33821
35134
  import { dirname as dirname13 } from "node:path";
33822
- import { homedir as homedir13 } from "node:os";
35135
+ import { homedir as homedir15 } from "node:os";
33823
35136
  async function checkRuntime(options = {}) {
33824
- const home = options.home ?? process.env.HOME ?? homedir13();
35137
+ const home = options.home ?? process.env.HOME ?? homedir15();
33825
35138
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
33826
35139
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
33827
35140
  home,
@@ -33915,7 +35228,7 @@ function formatMode(mode) {
33915
35228
  }
33916
35229
  function createRuntimeFsProbe() {
33917
35230
  return {
33918
- stat: async (path15) => await stat4(path15),
35231
+ stat: async (path15) => await stat5(path15),
33919
35232
  access: async (path15, mode) => await access4(path15, mode)
33920
35233
  };
33921
35234
  }
@@ -34247,7 +35560,10 @@ function buildSession(options) {
34247
35560
  return {
34248
35561
  alias: "xacpx-doctor",
34249
35562
  agent: options.agent,
34250
- ...agentConfig.command ? { agentCommand: agentConfig.command } : {},
35563
+ ...(() => {
35564
+ const agentCommand = resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, options.config.transport.preferLocalAgents !== false);
35565
+ return agentCommand ? { agentCommand } : {};
35566
+ })(),
34251
35567
  workspace: options.workspace,
34252
35568
  transportSession: `xacpx-doctor-${timestamp}`,
34253
35569
  replyMode: options.config.channel.replyMode,
@@ -34292,6 +35608,7 @@ var SMOKE_PROMPT = "Reply with exactly: ok";
34292
35608
  var init_smoke_check = __esm(async () => {
34293
35609
  init_load_config();
34294
35610
  init_resolve_acpx_command();
35611
+ init_resolve_agent_command();
34295
35612
  init_acpx_bridge_client();
34296
35613
  init_acpx_bridge_transport();
34297
35614
  init_acpx_cli_transport();
@@ -34450,10 +35767,10 @@ var init_render_doctor = __esm(() => {
34450
35767
  });
34451
35768
 
34452
35769
  // src/doctor/doctor.ts
34453
- import { homedir as homedir14 } from "node:os";
34454
- import { join as join21 } from "node:path";
35770
+ import { homedir as homedir16 } from "node:os";
35771
+ import { join as join24 } from "node:path";
34455
35772
  async function runDoctor(options = {}, deps = {}) {
34456
- const home = deps.home ?? process.env.HOME ?? homedir14();
35773
+ const home = deps.home ?? process.env.HOME ?? homedir16();
34457
35774
  const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
34458
35775
  const sharedLoadConfig = createSharedLoadConfig(runtimePaths, deps.loadConfig ?? loadConfig);
34459
35776
  const runners = [
@@ -34606,8 +35923,8 @@ function resolveDoctorRuntimePaths(home, resolver) {
34606
35923
  return resolveRuntimePaths();
34607
35924
  }
34608
35925
  return {
34609
- configPath: join21(coreHomeDir(home), "config.json"),
34610
- statePath: join21(coreHomeDir(home), "state.json")
35926
+ configPath: join24(coreHomeDir(home), "config.json"),
35927
+ statePath: join24(coreHomeDir(home), "state.json")
34611
35928
  };
34612
35929
  }
34613
35930
  function depsUseExplicitRuntimeOverrides() {
@@ -34790,8 +36107,8 @@ var init_doctor2 = __esm(async () => {
34790
36107
  // src/cli.ts
34791
36108
  init_core_home();
34792
36109
  import { randomUUID as randomUUID4 } from "node:crypto";
34793
- import { homedir as homedir15 } from "node:os";
34794
- import { dirname as dirname14, join as join22, 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";
34795
36112
  import { fileURLToPath as fileURLToPath7 } from "node:url";
34796
36113
 
34797
36114
  // src/runtime/migrate-core-home.ts
@@ -47288,7 +48605,7 @@ init_core_home();
47288
48605
  init_daemon_files();
47289
48606
  init_orchestration_ipc();
47290
48607
  import { homedir as homedir2 } from "node:os";
47291
- import { join as join5 } from "node:path";
48608
+ import { join as join6 } from "node:path";
47292
48609
  function resolveDefaultOrchestrationEndpoint(env = process.env, platform = process.platform) {
47293
48610
  const orchestrationSocket = coreEnv("ORCHESTRATION_SOCKET", env);
47294
48611
  if (typeof orchestrationSocket === "string" && orchestrationSocket.trim().length > 0) {
@@ -47296,7 +48613,7 @@ function resolveDefaultOrchestrationEndpoint(env = process.env, platform = proce
47296
48613
  }
47297
48614
  const home = requireHome(env);
47298
48615
  const configOverride = coreEnv("CONFIG", env);
47299
- 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");
47300
48617
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
47301
48618
  return resolveOrchestrationEndpoint(runtimeDir, platform);
47302
48619
  }
@@ -48833,14 +50150,14 @@ function resolveTemplateChoice(answer, names) {
48833
50150
  init_plugin_home();
48834
50151
  import { spawn as spawn4 } from "node:child_process";
48835
50152
  import { readFile as readFile9 } from "node:fs/promises";
48836
- import { dirname as dirname8, join as join11 } from "node:path";
50153
+ import { dirname as dirname8, join as join12 } from "node:path";
48837
50154
  import { fileURLToPath as fileURLToPath3 } from "node:url";
48838
50155
 
48839
50156
  // src/plugins/package-manager.ts
48840
50157
  init_plugin_home();
48841
50158
  import { spawn as spawn3 } from "node:child_process";
48842
50159
  import { rm as rm4 } from "node:fs/promises";
48843
- import { join as join7 } from "node:path";
50160
+ import { join as join8 } from "node:path";
48844
50161
  function shellSpawnPlan(args) {
48845
50162
  const shell = process.platform === "win32";
48846
50163
  return { shell, args: shell ? args.map((arg) => `"${arg}"`) : args };
@@ -48888,7 +50205,7 @@ async function installPluginPackage(input) {
48888
50205
  const packageManager = input.packageManager ?? await detectPackageManager();
48889
50206
  await normalizePluginHomeManifest(input.pluginHome);
48890
50207
  if (packageManager === "bun") {
48891
- await rm4(join7(input.pluginHome, "bun.lock"), { force: true }).catch(() => {});
50208
+ await rm4(join8(input.pluginHome, "bun.lock"), { force: true }).catch(() => {});
48892
50209
  }
48893
50210
  const spec = input.version ? `${input.packageName}@${input.version}` : input.packageName;
48894
50211
  if (packageManager === "bun") {
@@ -49180,7 +50497,7 @@ async function runInherit(command, args) {
49180
50497
  async function readPackageName() {
49181
50498
  try {
49182
50499
  const here = dirname8(fileURLToPath3(import.meta.url));
49183
- 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")]) {
49184
50501
  try {
49185
50502
  const parsed = JSON.parse(await readFile9(candidate, "utf8"));
49186
50503
  if (typeof parsed.name === "string" && parsed.name.trim())
@@ -49835,7 +51152,7 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
49835
51152
  init_core_home();
49836
51153
  init_plugin_home();
49837
51154
  import { readFile as readFile11 } from "node:fs/promises";
49838
- import { isAbsolute, join as join13, resolve } from "node:path";
51155
+ import { isAbsolute, join as join14, resolve } from "node:path";
49839
51156
  init_plugin_loader();
49840
51157
  init_validate_plugin();
49841
51158
  init_plugin_doctor();
@@ -49866,7 +51183,7 @@ function looksLikePath(spec) {
49866
51183
  }
49867
51184
  async function readDependencyEntries2(pluginHome) {
49868
51185
  try {
49869
- const raw = await readFile11(join13(pluginHome, "package.json"), "utf8");
51186
+ const raw = await readFile11(join14(pluginHome, "package.json"), "utf8");
49870
51187
  const parsed = JSON.parse(raw);
49871
51188
  const out = {};
49872
51189
  for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
@@ -49892,7 +51209,7 @@ async function resolveLocalPluginName(installSpec, pluginHome, namesBeforeInstal
49892
51209
  return name;
49893
51210
  }
49894
51211
  try {
49895
- const raw = await readFile11(join13(installSpec, "package.json"), "utf8");
51212
+ const raw = await readFile11(join14(installSpec, "package.json"), "utf8");
49896
51213
  const parsed = JSON.parse(raw);
49897
51214
  if (typeof parsed.name === "string" && parsed.name.trim())
49898
51215
  return parsed.name.trim();
@@ -51034,7 +52351,7 @@ async function createCliScheduledTaskService() {
51034
52351
  return new ScheduledTaskService(state, stateStore);
51035
52352
  }
51036
52353
  function resolveConfigPathForCurrentEnv() {
51037
- return coreEnv("CONFIG") ?? join22(coreHomeDir(requireHome2()), "config.json");
52354
+ return coreEnv("CONFIG") ?? join25(coreHomeDir(requireHome2()), "config.json");
51038
52355
  }
51039
52356
  function resolveDaemonPathsForCurrentConfig() {
51040
52357
  const configPath = resolveConfigPathForCurrentEnv();
@@ -51093,7 +52410,7 @@ async function defaultRun(options = {}) {
51093
52410
  const firstRunOnboarding = options.firstRunOnboarding ?? decodeFirstRunOnboarding(coreEnv("FIRST_RUN_ONBOARDING"));
51094
52411
  await runConsole2(runtimePaths, {
51095
52412
  buildApp: (paths) => buildApp2(paths, {
51096
- defaultLoggingLevel: resolveCliEntryPath2().includes(`${sep}src${sep}`) ? "debug" : "info",
52413
+ defaultLoggingLevel: resolveCliEntryPath2().includes(`${sep2}src${sep2}`) ? "debug" : "info",
51097
52414
  channel: channelRegistry
51098
52415
  }),
51099
52416
  beforeReady: firstRunOnboarding ? async (runtime) => {
@@ -51104,7 +52421,7 @@ async function defaultRun(options = {}) {
51104
52421
  daemonRuntime,
51105
52422
  ...firstLockCreator ? {
51106
52423
  consumerLockFactory: (runtime) => firstLockCreator.create({
51107
- lockFilePath: `${daemonPaths.runtimeDir}${sep}${firstLockCreator.channel.id}-consumer.lock.json`,
52424
+ lockFilePath: `${daemonPaths.runtimeDir}${sep2}${firstLockCreator.channel.id}-consumer.lock.json`,
51108
52425
  onDiagnostic: async (event, context) => {
51109
52426
  await runtime.logger.info(`${firstLockCreator.channel.id}.consumer_lock.${event}`, `${firstLockCreator.channel.id} consumer lock diagnostic`, context);
51110
52427
  }
@@ -51295,7 +52612,7 @@ async function defaultPromptSecret(message) {
51295
52612
  process.stdout.write(message);
51296
52613
  process.stdin.setRawMode(true);
51297
52614
  process.stdin.resume();
51298
- return await new Promise((resolve3, reject) => {
52615
+ return await new Promise((resolve4, reject) => {
51299
52616
  const chunks = [];
51300
52617
  let inEscape = false;
51301
52618
  const cleanup = () => {
@@ -51326,7 +52643,7 @@ async function defaultPromptSecret(message) {
51326
52643
  if (char === "\r" || char === `
51327
52644
  `) {
51328
52645
  cleanup();
51329
- resolve3(chunks.join(""));
52646
+ resolve4(chunks.join(""));
51330
52647
  return;
51331
52648
  }
51332
52649
  if (char === "" || char === "\b") {
@@ -51381,7 +52698,7 @@ function decodeFirstRunOnboarding(raw) {
51381
52698
  return null;
51382
52699
  }
51383
52700
  function requireHome2() {
51384
- const home = process.env.HOME ?? homedir15();
52701
+ const home = process.env.HOME ?? homedir17();
51385
52702
  if (!home) {
51386
52703
  throw new Error("Unable to resolve the current user home directory");
51387
52704
  }
@@ -51405,7 +52722,7 @@ function safeDaemonLogPaths() {
51405
52722
  const configPath = resolveConfigPathForCurrentEnv();
51406
52723
  const paths = resolveDaemonPathsForCurrentConfig();
51407
52724
  return {
51408
- appLog: join22(dirname14(configPath), "runtime", "app.log"),
52725
+ appLog: join25(dirname14(configPath), "runtime", "app.log"),
51409
52726
  stderrLog: paths.stderrLog
51410
52727
  };
51411
52728
  } catch {