@ganglion/xacpx 0.10.1 → 0.12.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, onUsage) {
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, onUsage);
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, onUsage) {
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, onUsage);
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, onUsage);
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, onUsage) {
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, onUsage);
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, onUsage) {
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, onUsage);
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, onUsage);
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, onUsage);
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, onUsage) => this.promptTransportSession(session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpanOverride ?? perfSpan, onPlan, onUsage)
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, onUsage) {
24897
25182
  session3.mcpCoordinatorSession ??= stableCoordinatorSession(session3.transportSession);
24898
25183
  let done = false;
24899
25184
  let abortRequested = false;
@@ -24950,7 +25235,9 @@ class CommandRouter {
24950
25235
  ...media ? { media } : {},
24951
25236
  ...reply ? { onSegment } : {},
24952
25237
  ...onToolEvent ? { onToolEvent } : {},
24953
- ...onThought ? { onThought } : {}
25238
+ ...onThought ? { onThought } : {},
25239
+ ...onPlan ? { onPlan } : {},
25240
+ ...onUsage ? { onUsage } : {}
24954
25241
  }));
24955
25242
  } catch (error2) {
24956
25243
  localOutcome = isAbortError2(error2) || abortRequested ? "aborted" : "error";
@@ -24969,6 +25256,20 @@ class CommandRouter {
24969
25256
  async setModeTransportSession(session3, modeId) {
24970
25257
  return await this.measureTransportCall("set_mode", session3, () => this.transport.setMode(session3, modeId));
24971
25258
  }
25259
+ async setModelTransportSession(session3, modelId) {
25260
+ if (!this.transport.setModel) {
25261
+ throw new Error("the active transport does not support switching models");
25262
+ }
25263
+ const setModel = this.transport.setModel.bind(this.transport);
25264
+ return await this.measureTransportCall("set_model", session3, () => setModel(session3, modelId));
25265
+ }
25266
+ async getModelTransportSession(session3) {
25267
+ if (!this.transport.getSessionModel) {
25268
+ return { current: session3.model, available: [] };
25269
+ }
25270
+ const getSessionModel = this.transport.getSessionModel.bind(this.transport);
25271
+ return await this.measureTransportCall("get_model", session3, () => getSessionModel(session3));
25272
+ }
24972
25273
  async cancelTransportSession(session3) {
24973
25274
  return await this.measureTransportCall("cancel", session3, () => this.transport.cancel(session3));
24974
25275
  }
@@ -25028,6 +25329,7 @@ function inferTransportKind(transport) {
25028
25329
  }
25029
25330
  var init_command_router = __esm(() => {
25030
25331
  init_app_logger();
25332
+ init_resolve_agent_command();
25031
25333
  init_acpx_session_index();
25032
25334
  init_prompt_output();
25033
25335
  init_parse_command();
@@ -25121,7 +25423,7 @@ class ConsoleAgent {
25121
25423
  ...m.fileName ? { fileName: m.fileName } : {}
25122
25424
  })) : undefined;
25123
25425
  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);
25426
+ 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, request.onUsage);
25125
25427
  }
25126
25428
  isKnownCommand(text) {
25127
25429
  return isKnownXacpxCommandText(text);
@@ -28935,12 +29237,14 @@ class ScheduledTaskScheduler {
28935
29237
  setIntervalFn;
28936
29238
  clearIntervalFn;
28937
29239
  dispatchTask;
29240
+ onSettled;
28938
29241
  logger;
28939
29242
  intervalHandle = null;
28940
29243
  ticking = false;
28941
29244
  constructor(service, deps) {
28942
29245
  this.service = service;
28943
29246
  this.dispatchTask = deps.dispatchTask;
29247
+ this.onSettled = deps.onSettled;
28944
29248
  this.intervalMs = deps.intervalMs ?? 5000;
28945
29249
  this.dispatchTimeoutMs = deps.dispatchTimeoutMs ?? DEFAULT_DISPATCH_TIMEOUT_MS;
28946
29250
  this.setIntervalFn = deps.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
@@ -28985,6 +29289,7 @@ class ScheduledTaskScheduler {
28985
29289
  });
28986
29290
  try {
28987
29291
  await this.service.markFailed(task.id, error2);
29292
+ this.notifySettled(task);
28988
29293
  } catch (markError) {
28989
29294
  await this.logger?.error("scheduled.dispatch.mark_failed", "markFailed threw; task state may be stale", {
28990
29295
  taskId: task.id,
@@ -28995,6 +29300,7 @@ class ScheduledTaskScheduler {
28995
29300
  }
28996
29301
  try {
28997
29302
  await this.service.markExecuted(task.id);
29303
+ this.notifySettled(task);
28998
29304
  } catch (markError) {
28999
29305
  await this.logger?.error("scheduled.dispatch.mark_executed_failed", "markExecuted threw after a successful dispatch; leaving task state for startup reconciliation", {
29000
29306
  taskId: task.id,
@@ -29006,6 +29312,18 @@ class ScheduledTaskScheduler {
29006
29312
  this.ticking = false;
29007
29313
  }
29008
29314
  }
29315
+ notifySettled(task) {
29316
+ if (!this.onSettled)
29317
+ return;
29318
+ try {
29319
+ this.onSettled(task);
29320
+ } catch (error2) {
29321
+ this.logger?.error("scheduled.on_settled.failed", "scheduled onSettled listener threw", {
29322
+ taskId: task.id,
29323
+ message: error2 instanceof Error ? error2.message : String(error2)
29324
+ });
29325
+ }
29326
+ }
29009
29327
  async dispatchWithTimeout(task) {
29010
29328
  const controller = new AbortController;
29011
29329
  let timer;
@@ -29041,7 +29359,8 @@ function buildScheduledDispatchTask(deps) {
29041
29359
  };
29042
29360
  }
29043
29361
  async function dispatchBound(task, abortSignal, deps) {
29044
- const session3 = await deps.getSession(task.session_alias);
29362
+ const internalAlias = deps.resolveAliasForChat ? await deps.resolveAliasForChat(task.chat_key, task.session_alias) : task.session_alias;
29363
+ const session3 = await deps.getSession(internalAlias);
29045
29364
  if (!session3) {
29046
29365
  throw new Error(`session "${task.session_alias}" not found for scheduled task`);
29047
29366
  }
@@ -29050,6 +29369,7 @@ async function dispatchBound(task, abortSignal, deps) {
29050
29369
  chatKey: task.chat_key,
29051
29370
  taskId: task.id,
29052
29371
  sessionAlias: task.session_alias,
29372
+ executeAt: task.execute_at,
29053
29373
  noticeText,
29054
29374
  promptText: task.message,
29055
29375
  abortSignal,
@@ -29070,6 +29390,7 @@ async function dispatchTemp(task, abortSignal, deps) {
29070
29390
  chatKey: task.chat_key,
29071
29391
  taskId: task.id,
29072
29392
  sessionAlias: task.session_alias,
29393
+ executeAt: task.execute_at,
29073
29394
  sessionDescriptor: { alias, agent: task.agent, workspace: task.workspace, transportSession },
29074
29395
  noticeText,
29075
29396
  promptText: task.message,
@@ -29259,6 +29580,7 @@ class SessionService {
29259
29580
  workspace: workspace3,
29260
29581
  transport_session: transportSession,
29261
29582
  transport_agent_command: sameAgentExisting?.transport_agent_command,
29583
+ model: sameAgentExisting?.model,
29262
29584
  created_at: existing?.created_at ?? new Date().toISOString(),
29263
29585
  last_used_at: new Date().toISOString()
29264
29586
  });
@@ -29629,10 +29951,13 @@ class SessionService {
29629
29951
  if (!workspaceConfig) {
29630
29952
  throw new Error(`session "${session3.alias}" references workspace "${session3.workspace}", but that workspace is no longer registered`);
29631
29953
  }
29954
+ const channelId = getChannelIdFromChatKey(session3.alias);
29955
+ const effectiveReplyMode = channelId === "relay" ? "stream" : undefined;
29632
29956
  return {
29633
29957
  alias: session3.alias,
29634
29958
  agent: session3.agent,
29635
- agentCommand: session3.transport_agent_command ?? resolveAgentCommand(agentConfig.driver, agentConfig.command),
29959
+ agentCommand: session3.transport_agent_command ?? resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, this.config.transport.preferLocalAgents !== false),
29960
+ model: session3.model ?? agentConfig.model,
29636
29961
  workspace: session3.workspace,
29637
29962
  transportSession: session3.transport_session,
29638
29963
  source: session3.source,
@@ -29642,9 +29967,46 @@ class SessionService {
29642
29967
  attachedAt: session3.attached_at,
29643
29968
  modeId: session3.mode_id,
29644
29969
  replyMode: session3.reply_mode,
29970
+ effectiveReplyMode,
29645
29971
  cwd: workspaceConfig.cwd
29646
29972
  };
29647
29973
  }
29974
+ async setSessionModel(alias, modelId) {
29975
+ await this.mutate(async () => {
29976
+ const session3 = this.state.sessions[alias];
29977
+ if (!session3) {
29978
+ throw new Error(`session "${alias}" does not exist`);
29979
+ }
29980
+ const normalized = modelId?.trim();
29981
+ if (normalized) {
29982
+ session3.model = normalized;
29983
+ } else {
29984
+ delete session3.model;
29985
+ }
29986
+ session3.last_used_at = new Date(this.now()).toISOString();
29987
+ await this.persist();
29988
+ });
29989
+ }
29990
+ async setCurrentSessionModel(chatKey, modelId) {
29991
+ await this.mutate(async () => {
29992
+ const currentAlias = this.state.chat_contexts[chatKey]?.current_session;
29993
+ if (!currentAlias) {
29994
+ throw new Error("no current session selected");
29995
+ }
29996
+ const session3 = this.state.sessions[currentAlias];
29997
+ if (!session3) {
29998
+ throw new Error("no current session selected");
29999
+ }
30000
+ const normalized = modelId?.trim();
30001
+ if (normalized) {
30002
+ session3.model = normalized;
30003
+ } else {
30004
+ delete session3.model;
30005
+ }
30006
+ session3.last_used_at = new Date(this.now()).toISOString();
30007
+ await this.persist();
30008
+ });
30009
+ }
29648
30010
  async setSessionTransportAgentCommand(alias, transportAgentCommand) {
29649
30011
  await this.mutate(async () => {
29650
30012
  const session3 = this.state.sessions[alias];
@@ -29689,6 +30051,7 @@ class SessionService {
29689
30051
  attached_at: native ? now : undefined,
29690
30052
  ...normalizedTransportAgentCommand ? { transport_agent_command: normalizedTransportAgentCommand } : sameAgentExisting?.transport_agent_command ? { transport_agent_command: sameAgentExisting.transport_agent_command } : {},
29691
30053
  mode_id: sameAgentExisting?.mode_id,
30054
+ model: sameAgentExisting?.model,
29692
30055
  reply_mode: sameAgentExisting?.reply_mode,
29693
30056
  created_at: existingSession?.created_at ?? now,
29694
30057
  last_used_at: now
@@ -29717,6 +30080,7 @@ class SessionService {
29717
30080
  }
29718
30081
  }
29719
30082
  var init_session_service = __esm(() => {
30083
+ init_resolve_agent_command();
29720
30084
  init_i18n();
29721
30085
  init_channel_scope();
29722
30086
  });
@@ -29740,6 +30104,13 @@ function createActiveTurnRegistry() {
29740
30104
  },
29741
30105
  isActive(chatKey, alias) {
29742
30106
  return byChat.get(chatKey)?.has(alias) ?? false;
30107
+ },
30108
+ isActiveAnywhere(alias) {
30109
+ for (const set2 of byChat.values()) {
30110
+ if (set2.has(alias))
30111
+ return true;
30112
+ }
30113
+ return false;
29743
30114
  }
29744
30115
  };
29745
30116
  }
@@ -29848,6 +30219,7 @@ var init_command_hints = __esm(() => {
29848
30219
  config: "/config",
29849
30220
  orchestration: "/delegate",
29850
30221
  mode: "/mode",
30222
+ model: "/model",
29851
30223
  replymode: "/replymode",
29852
30224
  status: "/status",
29853
30225
  cancel: "/cancel",
@@ -29966,7 +30338,8 @@ async function runConsole(paths, deps) {
29966
30338
  perfTracer: runtime.perfTracer,
29967
30339
  commandHints: listXacpxCommandHints(),
29968
30340
  coreVersion: XACPX_CORE_VERSION,
29969
- locale: getLocale()
30341
+ locale: getLocale(),
30342
+ control: runtime.control
29970
30343
  });
29971
30344
  channelStartPromise.catch(() => {});
29972
30345
  let channelStartSettled = false;
@@ -30102,6 +30475,14 @@ function encodeBridgePromptThoughtEvent(event) {
30102
30475
  return `${JSON.stringify(event)}
30103
30476
  `;
30104
30477
  }
30478
+ function encodeBridgePromptPlanEvent(event) {
30479
+ return `${JSON.stringify(event)}
30480
+ `;
30481
+ }
30482
+ function encodeBridgePromptUsageEvent(event) {
30483
+ return `${JSON.stringify(event)}
30484
+ `;
30485
+ }
30105
30486
  function encodeBridgeSessionProgressEvent(event) {
30106
30487
  return `${JSON.stringify(event)}
30107
30488
  `;
@@ -30111,8 +30492,228 @@ function encodeBridgeSessionNoteEvent(event) {
30111
30492
  `;
30112
30493
  }
30113
30494
 
30114
- // src/transport/acpx-bridge/acpx-bridge-client.ts
30495
+ // src/process/spawn-command.ts
30496
+ function resolveSpawnCommand(command, args) {
30497
+ if (SCRIPT_FILE_PATTERN.test(command)) {
30498
+ return {
30499
+ command: process.execPath,
30500
+ args: [command, ...args]
30501
+ };
30502
+ }
30503
+ return { command, args };
30504
+ }
30505
+ var SCRIPT_FILE_PATTERN;
30506
+ var init_spawn_command = __esm(() => {
30507
+ SCRIPT_FILE_PATTERN = /\.(c|m)?js$/i;
30508
+ });
30509
+
30510
+ // src/transport/acpx-queue-owner-launcher.ts
30511
+ import { createHash as createHash3 } from "node:crypto";
30115
30512
  import { spawn as spawn7 } from "node:child_process";
30513
+ import { readFile as readFile13, unlink } from "node:fs/promises";
30514
+ import { homedir as homedir8 } from "node:os";
30515
+ import { join as join17 } from "node:path";
30516
+ function buildXacpxMcpServerSpec(input) {
30517
+ const { command, args } = splitCommandLine(input.xacpxCommand);
30518
+ return {
30519
+ name: "xacpx",
30520
+ type: "stdio",
30521
+ command,
30522
+ args: [
30523
+ ...args,
30524
+ "mcp-stdio",
30525
+ "--coordinator-session",
30526
+ input.coordinatorSession,
30527
+ ...input.sourceHandle ? ["--source-handle", input.sourceHandle] : ["--internal-session-tools"]
30528
+ ]
30529
+ };
30530
+ }
30531
+ function buildQueueOwnerPayload(input) {
30532
+ return {
30533
+ sessionId: input.sessionId,
30534
+ permissionMode: input.permissionMode,
30535
+ nonInteractivePermissions: input.nonInteractivePermissions,
30536
+ ttlMs: input.ttlMs ?? 300000,
30537
+ maxQueueDepth: input.maxQueueDepth ?? 16,
30538
+ ...Number.isFinite(input.promptRetries) ? { promptRetries: input.promptRetries } : {},
30539
+ ...input.sessionOptions ? { sessionOptions: input.sessionOptions } : {},
30540
+ mcpServers: input.mcpServers
30541
+ };
30542
+ }
30543
+
30544
+ class AcpxQueueOwnerLauncher {
30545
+ acpxCommand;
30546
+ xacpxCommand;
30547
+ spawnOwner;
30548
+ terminateOwner;
30549
+ baseEnv;
30550
+ ttlMs;
30551
+ maxQueueDepth;
30552
+ launchLocks = new Map;
30553
+ constructor(options) {
30554
+ this.acpxCommand = options.acpxCommand;
30555
+ this.xacpxCommand = options.xacpxCommand ?? resolveDefaultXacpxCommand(options.baseEnv ?? process.env);
30556
+ this.spawnOwner = options.spawnOwner ?? defaultQueueOwnerSpawner;
30557
+ this.terminateOwner = options.terminateOwner ?? createDefaultQueueOwnerTerminator(options.acpxCommand);
30558
+ this.baseEnv = options.baseEnv ?? process.env;
30559
+ this.ttlMs = options.ttlMs;
30560
+ this.maxQueueDepth = options.maxQueueDepth;
30561
+ }
30562
+ async launch(input) {
30563
+ const key = input.acpxRecordId;
30564
+ const previous = this.launchLocks.get(key) ?? Promise.resolve();
30565
+ const next = previous.then(() => this.doLaunch(input), () => this.doLaunch(input));
30566
+ const tracked = next.catch(() => {});
30567
+ this.launchLocks.set(key, tracked);
30568
+ tracked.finally(() => {
30569
+ if (this.launchLocks.get(key) === tracked) {
30570
+ this.launchLocks.delete(key);
30571
+ }
30572
+ });
30573
+ return next;
30574
+ }
30575
+ async doLaunch(input) {
30576
+ await this.terminateOwner(input.acpxRecordId);
30577
+ const payload = buildQueueOwnerPayload({
30578
+ sessionId: input.acpxRecordId,
30579
+ permissionMode: input.permissionMode,
30580
+ nonInteractivePermissions: input.nonInteractivePermissions,
30581
+ ttlMs: this.ttlMs,
30582
+ maxQueueDepth: this.maxQueueDepth,
30583
+ ...input.sessionOptions ? { sessionOptions: input.sessionOptions } : {},
30584
+ mcpServers: [buildXacpxMcpServerSpec({
30585
+ xacpxCommand: this.xacpxCommand,
30586
+ coordinatorSession: input.coordinatorSession,
30587
+ ...input.sourceHandle ? { sourceHandle: input.sourceHandle } : {}
30588
+ })]
30589
+ });
30590
+ const spawnSpec = resolveSpawnCommand(this.acpxCommand, ["__queue-owner"]);
30591
+ await this.spawnOwner(spawnSpec.command, spawnSpec.args, {
30592
+ env: {
30593
+ ...stringEnv(this.baseEnv),
30594
+ XACPX_LANG: getLocale(),
30595
+ ACPX_QUEUE_OWNER_PAYLOAD: JSON.stringify(payload)
30596
+ }
30597
+ });
30598
+ }
30599
+ }
30600
+ function splitCommandLine(value) {
30601
+ const parts = [];
30602
+ let current = "";
30603
+ let quote = null;
30604
+ let escaping = false;
30605
+ for (const char of value) {
30606
+ if (escaping) {
30607
+ current += char;
30608
+ escaping = false;
30609
+ continue;
30610
+ }
30611
+ if (char === "\\" && quote !== "'") {
30612
+ escaping = true;
30613
+ continue;
30614
+ }
30615
+ if (quote) {
30616
+ if (char === quote) {
30617
+ quote = null;
30618
+ } else {
30619
+ current += char;
30620
+ }
30621
+ continue;
30622
+ }
30623
+ if (char === "'" || char === '"') {
30624
+ quote = char;
30625
+ continue;
30626
+ }
30627
+ if (/\s/.test(char)) {
30628
+ if (current.length > 0) {
30629
+ parts.push(current);
30630
+ current = "";
30631
+ }
30632
+ continue;
30633
+ }
30634
+ current += char;
30635
+ }
30636
+ if (escaping) {
30637
+ current += "\\";
30638
+ }
30639
+ if (quote) {
30640
+ throw new Error("xacpx MCP command has an unterminated quote");
30641
+ }
30642
+ if (current.length > 0) {
30643
+ parts.push(current);
30644
+ }
30645
+ if (parts.length === 0) {
30646
+ throw new Error("xacpx MCP command must not be empty");
30647
+ }
30648
+ return { command: parts[0], args: parts.slice(1) };
30649
+ }
30650
+ function stringEnv(env) {
30651
+ return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
30652
+ }
30653
+ async function defaultQueueOwnerSpawner(command, args, options) {
30654
+ await new Promise((resolve3, reject) => {
30655
+ const child = spawn7(command, args, {
30656
+ detached: true,
30657
+ stdio: "ignore",
30658
+ env: options.env,
30659
+ windowsHide: true
30660
+ });
30661
+ child.once("error", reject);
30662
+ child.once("spawn", () => {
30663
+ child.unref();
30664
+ resolve3();
30665
+ });
30666
+ });
30667
+ }
30668
+ function createDefaultQueueOwnerTerminator(_acpxCommand) {
30669
+ return async (sessionId) => {
30670
+ await terminateAcpxQueueOwner(sessionId);
30671
+ };
30672
+ }
30673
+ async function terminateAcpxQueueOwner(sessionId) {
30674
+ const lockPath = queueLockFilePath(sessionId);
30675
+ let owner;
30676
+ try {
30677
+ owner = JSON.parse(await readFile13(lockPath, "utf8"));
30678
+ } catch {
30679
+ return;
30680
+ }
30681
+ if (typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0) {
30682
+ await terminateProcessTree(owner.pid, { detachedProcessGroup: true });
30683
+ }
30684
+ await unlink(lockPath).catch(() => {});
30685
+ }
30686
+ function queueLockFilePath(sessionId) {
30687
+ return join17(homedir8(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
30688
+ }
30689
+ function shortHash(value, length) {
30690
+ return createHash3("sha256").update(value).digest("hex").slice(0, length);
30691
+ }
30692
+ function resolveDefaultXacpxCommand(env) {
30693
+ const cliCommand = coreEnv("CLI_COMMAND", env);
30694
+ if (cliCommand?.trim()) {
30695
+ return cliCommand.trim();
30696
+ }
30697
+ const daemonArg0 = coreEnv("DAEMON_ARG0", env);
30698
+ if (daemonArg0?.trim()) {
30699
+ return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(daemonArg0.trim())}`;
30700
+ }
30701
+ if (process.argv[1]) {
30702
+ return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(process.argv[1])}`;
30703
+ }
30704
+ return "xacpx";
30705
+ }
30706
+ function quoteCommandPart(value) {
30707
+ return quoteIfNeeded(value);
30708
+ }
30709
+ var init_acpx_queue_owner_launcher = __esm(() => {
30710
+ init_spawn_command();
30711
+ init_terminate_process_tree();
30712
+ init_i18n();
30713
+ });
30714
+
30715
+ // src/transport/acpx-bridge/acpx-bridge-client.ts
30716
+ import { spawn as spawn8 } from "node:child_process";
30116
30717
  import { fileURLToPath as fileURLToPath4 } from "node:url";
30117
30718
  import { createInterface } from "node:readline";
30118
30719
 
@@ -30179,6 +30780,17 @@ class AcpxBridgeClient {
30179
30780
  type: "prompt.thought",
30180
30781
  text: message.text
30181
30782
  });
30783
+ } else if (message.event === "prompt.plan") {
30784
+ pending.onEvent?.({
30785
+ type: "prompt.plan",
30786
+ entries: message.entries
30787
+ });
30788
+ } else if (message.event === "prompt.usage") {
30789
+ pending.onEvent?.({
30790
+ type: "prompt.usage",
30791
+ used: message.used,
30792
+ size: message.size
30793
+ });
30182
30794
  } else if (message.event === "session.progress") {
30183
30795
  pending.onEvent?.({
30184
30796
  type: "session.progress",
@@ -30228,6 +30840,7 @@ class AcpxBridgeClient {
30228
30840
  function buildBridgeSpawnEnv(options = {}) {
30229
30841
  return {
30230
30842
  XACPX_LANG: getLocale(),
30843
+ XACPX_CLI_COMMAND: options.cliCommand ?? resolveDefaultXacpxCommand(process.env),
30231
30844
  XACPX_BRIDGE_ACPX_COMMAND: options.acpxCommand ?? "acpx",
30232
30845
  XACPX_BRIDGE_PERMISSION_MODE: options.permissionMode ?? "approve-all",
30233
30846
  XACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS: options.nonInteractivePermissions ?? "deny",
@@ -30254,7 +30867,7 @@ async function spawnAcpxBridgeClient(options = {}) {
30254
30867
  execPath: process.execPath,
30255
30868
  bridgeEntryPath
30256
30869
  });
30257
- const child = spawn7(spawnSpec.command, spawnSpec.args, {
30870
+ const child = spawn8(spawnSpec.command, spawnSpec.args, {
30258
30871
  cwd: options.cwd ?? process.cwd(),
30259
30872
  env: {
30260
30873
  ...process.env,
@@ -30306,6 +30919,7 @@ var init_acpx_bridge_client = __esm(() => {
30306
30919
  init_errors();
30307
30920
  init_terminate_process_tree();
30308
30921
  init_i18n();
30922
+ init_acpx_queue_owner_launcher();
30309
30923
  });
30310
30924
 
30311
30925
  // src/transport/segment-aggregator.ts
@@ -30508,6 +31122,64 @@ ${headsUpText}`);
30508
31122
  }
30509
31123
  };
30510
31124
  }
31125
+ function createVerbatimReplySink(reply) {
31126
+ let pendingError;
31127
+ const inFlight = new Set;
31128
+ const send = (text) => {
31129
+ const p = reply(text).catch((err) => {
31130
+ if (isQuotaDeferredError(err)) {
31131
+ if (!pendingError) {
31132
+ pendingError = err;
31133
+ }
31134
+ return;
31135
+ }
31136
+ });
31137
+ inFlight.add(p);
31138
+ p.finally(() => {
31139
+ inFlight.delete(p);
31140
+ });
31141
+ };
31142
+ return {
31143
+ feedSegment(segment) {
31144
+ if (segment.length === 0)
31145
+ return;
31146
+ send(segment);
31147
+ },
31148
+ finalize() {
31149
+ return { trailing: "", overflowCount: 0 };
31150
+ },
31151
+ getOverflowCount() {
31152
+ return 0;
31153
+ },
31154
+ getPendingError() {
31155
+ return pendingError;
31156
+ },
31157
+ async drain(opts) {
31158
+ const timeoutMs = opts?.timeoutMs ?? 30000;
31159
+ const deadline = Date.now() + timeoutMs;
31160
+ while (inFlight.size > 0) {
31161
+ const remaining = deadline - Date.now();
31162
+ if (remaining <= 0)
31163
+ return;
31164
+ let timer;
31165
+ const timeout = new Promise((resolve3) => {
31166
+ timer = setTimeout(resolve3, remaining);
31167
+ });
31168
+ try {
31169
+ await Promise.race([
31170
+ Promise.allSettled(Array.from(inFlight)).then(() => {
31171
+ return;
31172
+ }),
31173
+ timeout
31174
+ ]);
31175
+ } finally {
31176
+ if (timer)
31177
+ clearTimeout(timer);
31178
+ }
31179
+ }
31180
+ }
31181
+ };
31182
+ }
30511
31183
  function buildOverflowSummary(overflowCount) {
30512
31184
  if (overflowCount <= 0)
30513
31185
  return;
@@ -30565,7 +31237,8 @@ class AcpxBridgeTransport {
30565
31237
  });
30566
31238
  }
30567
31239
  async prompt(session3, text, reply, replyContext, options) {
30568
- const sink = reply ? createQuotaGatedReplySink({
31240
+ const streamMode = (session3.effectiveReplyMode ?? session3.replyMode) === "stream";
31241
+ const sink = reply ? streamMode ? createVerbatimReplySink(reply) : createQuotaGatedReplySink({
30569
31242
  reply,
30570
31243
  ...replyContext ? { replyContext } : {}
30571
31244
  }) : null;
@@ -30575,6 +31248,10 @@ class AcpxBridgeTransport {
30575
31248
  let toolEventChain = Promise.resolve();
30576
31249
  let thoughtError;
30577
31250
  let thoughtChain = Promise.resolve();
31251
+ let planError;
31252
+ let planChain = Promise.resolve();
31253
+ let usageError;
31254
+ let usageChain = Promise.resolve();
30578
31255
  let toolEventMode = resolveToolEventMode(options);
30579
31256
  if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
30580
31257
  toolEventMode = "text";
@@ -30617,10 +31294,32 @@ class AcpxBridgeTransport {
30617
31294
  }
30618
31295
  return;
30619
31296
  }
31297
+ if (event.type === "prompt.plan") {
31298
+ const onPlan = options?.onPlan;
31299
+ if (onPlan) {
31300
+ const entries = event.entries;
31301
+ planChain = planChain.then(() => onPlan(entries)).catch((error2) => {
31302
+ planError ??= error2;
31303
+ });
31304
+ }
31305
+ return;
31306
+ }
31307
+ if (event.type === "prompt.usage") {
31308
+ const onUsage = options?.onUsage;
31309
+ if (onUsage) {
31310
+ const usage = { used: event.used, size: event.size };
31311
+ usageChain = usageChain.then(() => onUsage(usage)).catch((error2) => {
31312
+ usageError ??= error2;
31313
+ });
31314
+ }
31315
+ return;
31316
+ }
30620
31317
  });
30621
31318
  await segmentChain;
30622
31319
  await toolEventChain;
30623
31320
  await thoughtChain;
31321
+ await planChain;
31322
+ await usageChain;
30624
31323
  if (sink) {
30625
31324
  const { overflowCount } = sink.finalize();
30626
31325
  await sink.drain({ timeoutMs: 30000 });
@@ -30638,6 +31337,12 @@ class AcpxBridgeTransport {
30638
31337
  if (thoughtError) {
30639
31338
  throw thoughtError;
30640
31339
  }
31340
+ if (planError) {
31341
+ throw planError;
31342
+ }
31343
+ if (usageError) {
31344
+ throw usageError;
31345
+ }
30641
31346
  return { text: summary ? `${summary}
30642
31347
 
30643
31348
  ${result.text}` : "" };
@@ -30651,6 +31356,12 @@ ${result.text}` : "" };
30651
31356
  if (thoughtError) {
30652
31357
  throw thoughtError;
30653
31358
  }
31359
+ if (planError) {
31360
+ throw planError;
31361
+ }
31362
+ if (usageError) {
31363
+ throw usageError;
31364
+ }
30654
31365
  return result;
30655
31366
  }
30656
31367
  async setMode(session3, modeId) {
@@ -30659,6 +31370,15 @@ ${result.text}` : "" };
30659
31370
  modeId
30660
31371
  });
30661
31372
  }
31373
+ async setModel(session3, modelId) {
31374
+ await this.client.request("setModel", {
31375
+ ...this.toParams({ ...session3, model: modelId }),
31376
+ modelId
31377
+ });
31378
+ }
31379
+ async getSessionModel(session3) {
31380
+ return await this.client.request("getSessionModel", this.toParams(session3));
31381
+ }
30662
31382
  async cancel(session3) {
30663
31383
  return await this.client.request("cancel", this.toParams(session3));
30664
31384
  }
@@ -30687,7 +31407,8 @@ ${result.text}` : "" };
30687
31407
  name: session3.transportSession,
30688
31408
  mcpCoordinatorSession: session3.mcpCoordinatorSession,
30689
31409
  mcpSourceHandle: session3.mcpSourceHandle,
30690
- replyMode: session3.replyMode ?? "verbose"
31410
+ replyMode: session3.effectiveReplyMode ?? session3.replyMode ?? "verbose",
31411
+ ...session3.model?.trim() ? { model: session3.model.trim() } : {}
30691
31412
  };
30692
31413
  }
30693
31414
  }
@@ -30695,21 +31416,6 @@ var init_acpx_bridge_transport = __esm(() => {
30695
31416
  init_quota_gated_reply_sink();
30696
31417
  });
30697
31418
 
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
31419
  // src/transport/prompt-media.ts
30714
31420
  import { mkdtemp, open as open4, rm as rm9, writeFile as writeFile7 } from "node:fs/promises";
30715
31421
  import { tmpdir as defaultTmpdir } from "node:os";
@@ -30860,6 +31566,9 @@ function createStreamingPromptState(formatToolCalls = false, options) {
30860
31566
  let toolEventMode;
30861
31567
  let onToolEvent;
30862
31568
  let onThought;
31569
+ let onPlan;
31570
+ let onUsage;
31571
+ let rawStream = false;
30863
31572
  if (options === undefined) {
30864
31573
  toolEventMode = "text";
30865
31574
  onToolEvent = undefined;
@@ -30869,6 +31578,9 @@ function createStreamingPromptState(formatToolCalls = false, options) {
30869
31578
  } else {
30870
31579
  onToolEvent = options.onToolEvent;
30871
31580
  onThought = options.onThought;
31581
+ onPlan = options.onPlan;
31582
+ onUsage = options.onUsage;
31583
+ rawStream = options.rawStream ?? false;
30872
31584
  toolEventMode = resolveToolEventMode({
30873
31585
  toolEventMode: options.mode,
30874
31586
  onToolEvent
@@ -30882,13 +31594,16 @@ function createStreamingPromptState(formatToolCalls = false, options) {
30882
31594
  formatToolCalls,
30883
31595
  emittedToolCallIds: new Set,
30884
31596
  toolEventMode,
31597
+ rawStream,
30885
31598
  onToolEvent,
30886
31599
  onThought,
31600
+ onPlan,
31601
+ onUsage,
30887
31602
  finalize() {
30888
31603
  if (this.pendingLine.trim().length > 0) {
30889
31604
  parseStreamingChunks(this, this.pendingLine);
30890
31605
  }
30891
- const remaining = this.buffer.trim();
31606
+ const remaining = this.rawStream ? this.buffer : this.buffer.trim();
30892
31607
  this.buffer = "";
30893
31608
  this.pendingLine = "";
30894
31609
  return remaining;
@@ -30920,9 +31635,9 @@ function parseStreamingChunks(state, line) {
30920
31635
  const update = event.params?.update;
30921
31636
  if (!update)
30922
31637
  return;
30923
- if (state.formatToolCalls && (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update")) {
31638
+ if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") {
30924
31639
  const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
30925
- const wantsText = state.toolEventMode === "text" || state.toolEventMode === "both";
31640
+ const wantsText = (state.toolEventMode === "text" || state.toolEventMode === "both") && state.formatToolCalls;
30926
31641
  if (wantsStructured && state.onToolEvent) {
30927
31642
  const toolEvent = buildToolUseEvent(update);
30928
31643
  if (toolEvent)
@@ -30942,6 +31657,19 @@ function parseStreamingChunks(state, line) {
30942
31657
  }
30943
31658
  return;
30944
31659
  }
31660
+ if (update.sessionUpdate === "plan") {
31661
+ const entries = Array.isArray(update.entries) ? update.entries.filter((x) => !!x && typeof x === "object" && typeof x.content === "string" && typeof x.status === "string") : [];
31662
+ if (entries.length > 0)
31663
+ state.onPlan?.(entries);
31664
+ return;
31665
+ }
31666
+ if (update.sessionUpdate === "usage_update") {
31667
+ const used = typeof update.used === "number" && Number.isFinite(update.used) ? update.used : undefined;
31668
+ const size = typeof update.size === "number" && Number.isFinite(update.size) ? update.size : undefined;
31669
+ if (used !== undefined && size !== undefined && size > 0)
31670
+ state.onUsage?.({ used, size });
31671
+ return;
31672
+ }
30945
31673
  const isThoughtChunk = update.sessionUpdate === "agent_thought_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
30946
31674
  if (isThoughtChunk) {
30947
31675
  const chunk2 = update.content.text;
@@ -30958,6 +31686,8 @@ function parseStreamingChunks(state, line) {
30958
31686
  if (chunk.length === 0)
30959
31687
  return;
30960
31688
  state.buffer += chunk;
31689
+ if (state.rawStream)
31690
+ return;
30961
31691
  let boundary;
30962
31692
  while ((boundary = state.buffer.indexOf(`
30963
31693
 
@@ -31135,12 +31865,12 @@ var init_streaming_prompt = __esm(() => {
31135
31865
 
31136
31866
  // src/transport/acpx-cli/node-pty-helper.ts
31137
31867
  import { chmod as chmodFs } from "node:fs/promises";
31138
- import { dirname as dirname11, join as join16 } from "node:path";
31868
+ import { dirname as dirname11, join as join18 } from "node:path";
31139
31869
  function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
31140
31870
  if (platform === "win32") {
31141
31871
  return null;
31142
31872
  }
31143
- return join16(dirname11(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
31873
+ return join18(dirname11(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
31144
31874
  }
31145
31875
  async function ensureNodePtyHelperExecutable(helperPath, chmod5 = chmodFs) {
31146
31876
  if (!helperPath) {
@@ -31157,210 +31887,6 @@ async function ensureNodePtyHelperExecutable(helperPath, chmod5 = chmodFs) {
31157
31887
  }
31158
31888
  var init_node_pty_helper = () => {};
31159
31889
 
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
31890
  // src/transport/permission-mode-flag.ts
31365
31891
  function permissionModeToFlag(permissionMode) {
31366
31892
  switch (permissionMode) {
@@ -31578,13 +32104,15 @@ class AcpxCliTransport {
31578
32104
  const structuredPrompt = await createStructuredPromptFile(text, options?.media);
31579
32105
  const args = this.buildPromptArgs(session3, text, structuredPrompt?.filePath);
31580
32106
  try {
31581
- if (reply || options?.onSegment || options?.onToolEvent || options?.onThought) {
31582
- const formatToolCalls = (session3.replyMode ?? "verbose") === "verbose";
32107
+ if (reply || options?.onSegment || options?.onToolEvent || options?.onThought || options?.onPlan) {
32108
+ const effectiveReplyMode = session3.effectiveReplyMode ?? session3.replyMode;
32109
+ const formatToolCalls = (effectiveReplyMode ?? "verbose") === "verbose";
32110
+ const rawStream = effectiveReplyMode === "stream";
31583
32111
  let toolEventMode = resolveToolEventMode(options);
31584
32112
  if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
31585
32113
  toolEventMode = "text";
31586
32114
  }
31587
- const { result: result2, overflowCount } = await this.runStreamingPrompt(this.command, args, reply, formatToolCalls, toolEventMode, replyContext, options?.onSegment, options?.onToolEvent, options?.onThought);
32115
+ const { result: result2, overflowCount } = await this.runStreamingPrompt(this.command, args, reply, formatToolCalls, toolEventMode, replyContext, options?.onSegment, options?.onToolEvent, options?.onThought, options?.onPlan, rawStream);
31588
32116
  const baseText = getPromptText(result2);
31589
32117
  if (!reply) {
31590
32118
  return { text: baseText };
@@ -31610,6 +32138,34 @@ ${baseText}` : "" };
31610
32138
  modeId
31611
32139
  ]));
31612
32140
  }
32141
+ async setModel(session3, modelId) {
32142
+ await this.run(this.buildArgs({ ...session3, model: modelId }, [
32143
+ "set",
32144
+ "-s",
32145
+ session3.transportSession,
32146
+ "model",
32147
+ modelId
32148
+ ]));
32149
+ }
32150
+ async getSessionModel(session3) {
32151
+ const prefix = ["--format", "json", "--cwd", session3.cwd, ...this.buildPermissionArgs()];
32152
+ const tail2 = ["status", "-s", session3.transportSession];
32153
+ const args = session3.agentCommand ? [...prefix, "--agent", session3.agentCommand, ...tail2] : [...prefix, session3.agent, ...tail2];
32154
+ const result = await this.runCommandWithTimeout(this.runCommand, args);
32155
+ if (result.code !== 0) {
32156
+ const detail = normalizeCommandError(result) ?? `command failed with exit code ${result.code}`;
32157
+ throw new Error(detail);
32158
+ }
32159
+ try {
32160
+ const json = JSON.parse(result.stdout);
32161
+ return {
32162
+ current: typeof json.model === "string" ? json.model : undefined,
32163
+ available: Array.isArray(json.availableModels) ? json.availableModels.filter((m) => typeof m === "string") : []
32164
+ };
32165
+ } catch {
32166
+ return { available: [] };
32167
+ }
32168
+ }
31613
32169
  async cancel(session3) {
31614
32170
  const output = await this.run(this.buildArgs(session3, [
31615
32171
  "cancel",
@@ -31673,7 +32229,8 @@ ${baseText}` : "" };
31673
32229
  coordinatorSession: session3.mcpCoordinatorSession,
31674
32230
  ...session3.mcpSourceHandle ? { sourceHandle: session3.mcpSourceHandle } : {},
31675
32231
  permissionMode: this.permissionMode,
31676
- nonInteractivePermissions: this.nonInteractivePermissions
32232
+ nonInteractivePermissions: this.nonInteractivePermissions,
32233
+ ...session3.model?.trim() ? { sessionOptions: { model: session3.model.trim() } } : {}
31677
32234
  });
31678
32235
  }
31679
32236
  async readSessionRecord(session3) {
@@ -31741,13 +32298,13 @@ ${baseText}` : "" };
31741
32298
  })
31742
32299
  ]);
31743
32300
  }
31744
- async runStreamingPrompt(command, args, reply, formatToolCalls = false, toolEventMode = "text", replyContext, onSegment, onToolEvent, onThought) {
32301
+ async runStreamingPrompt(command, args, reply, formatToolCalls = false, toolEventMode = "text", replyContext, onSegment, onToolEvent, onThought, onPlan, rawStream = false) {
31745
32302
  const hooks = this.streamingHooks;
31746
32303
  const doSpawn = hooks.spawnPrompt ?? ((cmd, spawnArgs) => spawn9(cmd, spawnArgs, { stdio: ["ignore", "pipe", "pipe"] }));
31747
32304
  const setIntervalFn = hooks.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
31748
32305
  const clearIntervalFn = hooks.clearIntervalFn ?? ((timer) => clearInterval(timer));
31749
- const maxSegmentWaitMs = hooks.maxSegmentWaitMs ?? 30000;
31750
- const flushCheckIntervalMs = hooks.flushCheckIntervalMs ?? 5000;
32306
+ const maxSegmentWaitMs = hooks.maxSegmentWaitMs ?? (rawStream ? 200 : 30000);
32307
+ const flushCheckIntervalMs = hooks.flushCheckIntervalMs ?? (rawStream ? 80 : 5000);
31751
32308
  const now = hooks.now ?? (() => Date.now());
31752
32309
  return await new Promise((resolve3, reject) => {
31753
32310
  const spawnSpec = resolveSpawnCommand(command, args);
@@ -31761,10 +32318,14 @@ ${baseText}` : "" };
31761
32318
  let toolEventError;
31762
32319
  let thoughtChain = Promise.resolve();
31763
32320
  let thoughtError;
32321
+ let planChain = Promise.resolve();
32322
+ let planError;
31764
32323
  const userOnToolEvent = onToolEvent;
31765
32324
  const userOnThought = onThought;
32325
+ const userOnPlan = onPlan;
31766
32326
  const state = createStreamingPromptState(formatToolCalls, {
31767
32327
  mode: toolEventMode,
32328
+ rawStream,
31768
32329
  ...userOnToolEvent ? {
31769
32330
  onToolEvent: (event) => {
31770
32331
  toolEventChain = toolEventChain.then(() => userOnToolEvent(event)).catch((error2) => {
@@ -31778,9 +32339,16 @@ ${baseText}` : "" };
31778
32339
  thoughtError ??= error2;
31779
32340
  });
31780
32341
  }
32342
+ } : {},
32343
+ ...userOnPlan ? {
32344
+ onPlan: (entries) => {
32345
+ planChain = planChain.then(() => userOnPlan(entries)).catch((error2) => {
32346
+ planError ??= error2;
32347
+ });
32348
+ }
31781
32349
  } : {}
31782
32350
  });
31783
- const sink = reply ? createQuotaGatedReplySink({
32351
+ const sink = reply ? rawStream ? createVerbatimReplySink(reply) : createQuotaGatedReplySink({
31784
32352
  reply,
31785
32353
  ...replyContext ? { replyContext } : {}
31786
32354
  }) : null;
@@ -31794,7 +32362,7 @@ ${baseText}` : "" };
31794
32362
  lastReplyAt = now();
31795
32363
  };
31796
32364
  const flushBuffer = () => {
31797
- const remaining = state.buffer.trim();
32365
+ const remaining = rawStream ? state.buffer : state.buffer.trim();
31798
32366
  if (remaining.length > 0) {
31799
32367
  state.buffer = "";
31800
32368
  feedSegment(remaining);
@@ -31831,7 +32399,8 @@ ${baseText}` : "" };
31831
32399
  sink?.drain({ timeoutMs: 30000 }) ?? Promise.resolve(),
31832
32400
  segmentChain,
31833
32401
  toolEventChain,
31834
- thoughtChain
32402
+ thoughtChain,
32403
+ planChain
31835
32404
  ]).then(() => {
31836
32405
  const deferred = sink?.getPendingError();
31837
32406
  if (deferred) {
@@ -31850,6 +32419,10 @@ ${baseText}` : "" };
31850
32419
  reject(thoughtError);
31851
32420
  return;
31852
32421
  }
32422
+ if (planError) {
32423
+ reject(planError);
32424
+ return;
32425
+ }
31853
32426
  resolve3({
31854
32427
  result: { code: code ?? 1, stdout: stdout2, stderr },
31855
32428
  overflowCount
@@ -31866,7 +32439,8 @@ ${baseText}` : "" };
31866
32439
  "quiet",
31867
32440
  "--cwd",
31868
32441
  session3.cwd,
31869
- ...this.buildPermissionArgs()
32442
+ ...this.buildPermissionArgs(),
32443
+ ...this.buildModelArgs(session3)
31870
32444
  ];
31871
32445
  if (session3.agentCommand) {
31872
32446
  return [...prefix, "--agent", session3.agentCommand, ...tail2];
@@ -31888,6 +32462,7 @@ ${baseText}` : "" };
31888
32462
  "--cwd",
31889
32463
  session3.cwd,
31890
32464
  ...this.buildPermissionArgs(),
32465
+ ...this.buildModelArgs(session3),
31891
32466
  ...this.buildQueueOwnerTtlArgs()
31892
32467
  ];
31893
32468
  const tail2 = promptFile ? ["prompt", "-s", session3.transportSession, "--file", promptFile] : ["prompt", "-s", session3.transportSession, text];
@@ -31896,6 +32471,10 @@ ${baseText}` : "" };
31896
32471
  }
31897
32472
  return [...prefix, session3.agent, ...tail2];
31898
32473
  }
32474
+ buildModelArgs(session3) {
32475
+ const model = session3.model?.trim();
32476
+ return model ? ["--model", model] : [];
32477
+ }
31899
32478
  buildQueueOwnerTtlArgs() {
31900
32479
  if (typeof this.queueOwnerTtlSeconds !== "number" || !Number.isFinite(this.queueOwnerTtlSeconds)) {
31901
32480
  return [];
@@ -32092,7 +32671,7 @@ function workerBindingReapTargets(orchestration3, config4) {
32092
32671
  if (!cwd) {
32093
32672
  continue;
32094
32673
  }
32095
- const agentCommand = resolveAgentCommand(agentConfig.driver, agentConfig.command);
32674
+ const agentCommand = resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, config4.transport.preferLocalAgents !== false);
32096
32675
  targets.push({
32097
32676
  agent: binding.targetAgent,
32098
32677
  ...agentCommand ? { agentCommand } : {},
@@ -32102,7 +32681,9 @@ function workerBindingReapTargets(orchestration3, config4) {
32102
32681
  }
32103
32682
  return targets;
32104
32683
  }
32105
- var init_collect_reap_targets = () => {};
32684
+ var init_collect_reap_targets = __esm(() => {
32685
+ init_resolve_agent_command();
32686
+ });
32106
32687
 
32107
32688
  // src/channels/channel-registry.ts
32108
32689
  var exports_channel_registry = {};
@@ -32389,6 +32970,761 @@ var init_quota_manager = __esm(() => {
32389
32970
  DEFAULT_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
32390
32971
  });
32391
32972
 
32973
+ // src/control/control-event-bus.ts
32974
+ function createControlEventBus(logger2) {
32975
+ const listeners = new Set;
32976
+ return {
32977
+ subscribe(listener) {
32978
+ listeners.add(listener);
32979
+ return () => {
32980
+ listeners.delete(listener);
32981
+ };
32982
+ },
32983
+ emit(event) {
32984
+ for (const listener of [...listeners]) {
32985
+ try {
32986
+ listener(event);
32987
+ } catch (error2) {
32988
+ logger2?.error("control.event_listener_failed", "control event listener threw", {
32989
+ eventType: event.type,
32990
+ error: error2 instanceof Error ? error2.message : String(error2)
32991
+ });
32992
+ }
32993
+ }
32994
+ }
32995
+ };
32996
+ }
32997
+
32998
+ // src/transport/native-session-history.ts
32999
+ import { readFile as readFile14 } from "node:fs/promises";
33000
+ import { homedir as homedir9 } from "node:os";
33001
+ import { join as join19 } from "node:path";
33002
+ function classifyToolKind(name) {
33003
+ const n = name.toLowerCase();
33004
+ if (/(^|[^a-z])(read|cat|view|open)([^a-z]|$)/.test(n))
33005
+ return "read";
33006
+ if (/(grep|search|find|glob|ripgrep|rg)/.test(n))
33007
+ return "search";
33008
+ if (/(edit|write|apply|patch|replace|create)/.test(n))
33009
+ return "edit";
33010
+ if (/(bash|shell|exec|run|terminal|command)/.test(n))
33011
+ return "execute";
33012
+ if (/(think|reason|plan)/.test(n))
33013
+ return "think";
33014
+ return "other";
33015
+ }
33016
+ function textOfUserContent(content) {
33017
+ if (!Array.isArray(content))
33018
+ return "";
33019
+ const out = [];
33020
+ for (const c of content) {
33021
+ if (c && typeof c === "object") {
33022
+ const o = c;
33023
+ if (typeof o.Text === "string")
33024
+ out.push(o.Text);
33025
+ else if (o.Mention && typeof o.Mention.content === "string")
33026
+ out.push(String(o.Mention.content));
33027
+ else if (o.Image)
33028
+ out.push("[image]");
33029
+ else if (o.Audio)
33030
+ out.push("[audio]");
33031
+ }
33032
+ }
33033
+ return out.join(`
33034
+ `);
33035
+ }
33036
+ function toolResultText(result) {
33037
+ if (!result || typeof result !== "object")
33038
+ return { isError: false };
33039
+ const r = result;
33040
+ const isError = r.is_error === true;
33041
+ if (typeof r.output === "string")
33042
+ return { text: r.output, isError };
33043
+ const content = r.content;
33044
+ if (content && typeof content.Text === "string")
33045
+ return { text: content.Text, isError };
33046
+ return { isError };
33047
+ }
33048
+ function toolUseEventOf(toolUse, result) {
33049
+ const id = typeof toolUse.id === "string" ? toolUse.id : "";
33050
+ const name = typeof toolUse.name === "string" ? toolUse.name : "tool";
33051
+ const rawInput = toolUse.input ?? (typeof toolUse.raw_input === "string" ? safeParse4(toolUse.raw_input) : undefined);
33052
+ const res = toolResultText(result);
33053
+ return {
33054
+ toolCallId: id,
33055
+ toolName: name,
33056
+ kind: classifyToolKind(name),
33057
+ ...rawInput !== undefined ? { rawInput } : {},
33058
+ ...res.text !== undefined ? { rawOutput: res.text } : {},
33059
+ status: result ? res.isError ? "error" : "success" : "success"
33060
+ };
33061
+ }
33062
+ function safeParse4(s) {
33063
+ try {
33064
+ return JSON.parse(s);
33065
+ } catch {
33066
+ return s;
33067
+ }
33068
+ }
33069
+ function mapAcpxMessagesToHistory(raw) {
33070
+ if (!Array.isArray(raw))
33071
+ return [];
33072
+ const out = [];
33073
+ for (const msg of raw) {
33074
+ if (msg === "Resume" || !msg || typeof msg !== "object")
33075
+ continue;
33076
+ const m = msg;
33077
+ if (m.User && typeof m.User === "object") {
33078
+ const text = textOfUserContent(m.User.content);
33079
+ out.push({ role: "user", text });
33080
+ continue;
33081
+ }
33082
+ if (m.Agent && typeof m.Agent === "object") {
33083
+ const agent3 = m.Agent;
33084
+ const toolResults = agent3.tool_results ?? {};
33085
+ const parts = [];
33086
+ const textChunks = [];
33087
+ for (const c of Array.isArray(agent3.content) ? agent3.content : []) {
33088
+ if (!c || typeof c !== "object")
33089
+ continue;
33090
+ const o = c;
33091
+ if (typeof o.Text === "string") {
33092
+ parts.push({ kind: "text", text: o.Text });
33093
+ textChunks.push(o.Text);
33094
+ } else if (o.Thinking && typeof o.Thinking.text === "string")
33095
+ parts.push({ kind: "reasoning", text: String(o.Thinking.text) });
33096
+ else if (typeof o.RedactedThinking === "string")
33097
+ parts.push({ kind: "reasoning", text: "[redacted reasoning]" });
33098
+ else if (o.ToolUse && typeof o.ToolUse === "object") {
33099
+ const tu = o.ToolUse;
33100
+ const result = typeof tu.id === "string" ? toolResults[tu.id] : undefined;
33101
+ parts.push({ kind: "tool", tool: toolUseEventOf(tu, result) });
33102
+ }
33103
+ }
33104
+ out.push({ role: "agent", text: textChunks.join(`
33105
+
33106
+ `), ...parts.length ? { parts } : {} });
33107
+ }
33108
+ }
33109
+ return out;
33110
+ }
33111
+ async function readNativeSessionHistory(opts) {
33112
+ try {
33113
+ const dir = opts.sessionsDir ?? join19(opts.homeDir ?? homedir9(), ".acpx", "sessions");
33114
+ const indexRaw = await readFile14(join19(dir, "index.json"), "utf8").catch(() => null);
33115
+ if (!indexRaw)
33116
+ return [];
33117
+ const index = JSON.parse(indexRaw);
33118
+ const candidates = (index.entries ?? []).filter((e) => e.acpSessionId === opts.agentSessionId && (!opts.agentCommand || !e.agentCommand || e.agentCommand === opts.agentCommand));
33119
+ let best = [];
33120
+ for (const entry of candidates) {
33121
+ if (!entry.file)
33122
+ continue;
33123
+ const recRaw = await readFile14(join19(dir, entry.file), "utf8").catch(() => null);
33124
+ if (!recRaw)
33125
+ continue;
33126
+ const record3 = JSON.parse(recRaw);
33127
+ const mapped = mapAcpxMessagesToHistory(record3.messages);
33128
+ if (mapped.length > best.length)
33129
+ best = mapped;
33130
+ }
33131
+ return best;
33132
+ } catch {
33133
+ return [];
33134
+ }
33135
+ }
33136
+ var init_native_session_history = () => {};
33137
+
33138
+ // src/control/workspace-fs.ts
33139
+ import { execFile } from "node:child_process";
33140
+ import { promisify } from "node:util";
33141
+ import { homedir as homedir10 } from "node:os";
33142
+ import { readdir as readdir3, realpath, stat as stat3, open as open5 } from "node:fs/promises";
33143
+ import { isAbsolute as isAbsolute3, relative, resolve as resolve3, sep } from "node:path";
33144
+ function expandHome2(p) {
33145
+ if (p === "~")
33146
+ return homedir10();
33147
+ if (p.startsWith("~/") || p.startsWith("~" + sep))
33148
+ return resolve3(homedir10(), p.slice(2));
33149
+ return p;
33150
+ }
33151
+
33152
+ class WorkspaceFs {
33153
+ listWorkspaces;
33154
+ constructor(listWorkspaces) {
33155
+ this.listWorkspaces = listWorkspaces;
33156
+ }
33157
+ async resolve(workspace3, relPath) {
33158
+ const ref = this.listWorkspaces().find((w) => w.name === workspace3);
33159
+ if (!ref)
33160
+ throw new Error("unknown-workspace");
33161
+ if (relPath && isAbsolute3(relPath))
33162
+ throw new Error("path-must-be-relative");
33163
+ let root;
33164
+ try {
33165
+ root = await realpath(expandHome2(ref.cwd));
33166
+ } catch {
33167
+ throw new Error("workspace-root-missing");
33168
+ }
33169
+ const requested = resolve3(root, relPath ?? ".");
33170
+ let abs;
33171
+ try {
33172
+ abs = await realpath(requested);
33173
+ } catch {
33174
+ throw new Error("not-found");
33175
+ }
33176
+ if (abs !== root && !abs.startsWith(root + sep))
33177
+ throw new Error("path-escapes-workspace");
33178
+ const rel = abs === root ? "" : relative(root, abs).split(sep).join("/");
33179
+ return { root, abs, rel };
33180
+ }
33181
+ async listDirectory(workspace3, relPath) {
33182
+ const { abs, rel } = await this.resolve(workspace3, relPath);
33183
+ const dirents = await readdir3(abs, { withFileTypes: true });
33184
+ const entries = [];
33185
+ for (const d of dirents.slice(0, MAX_ENTRIES)) {
33186
+ if (d.isDirectory()) {
33187
+ entries.push({ name: d.name, type: "dir" });
33188
+ } else if (d.isFile()) {
33189
+ let size;
33190
+ try {
33191
+ size = (await stat3(resolve3(abs, d.name))).size;
33192
+ } catch {
33193
+ size = undefined;
33194
+ }
33195
+ entries.push({ name: d.name, type: "file", size });
33196
+ }
33197
+ }
33198
+ entries.sort((a, b) => a.type !== b.type ? a.type === "dir" ? -1 : 1 : a.name.localeCompare(b.name));
33199
+ return { workspace: workspace3, path: rel, entries };
33200
+ }
33201
+ async readFile(workspace3, relPath) {
33202
+ const { abs, rel } = await this.resolve(workspace3, relPath);
33203
+ const info = await stat3(abs);
33204
+ if (!info.isFile())
33205
+ throw new Error("not-a-file");
33206
+ const fh = await open5(abs, "r");
33207
+ try {
33208
+ const buf = Buffer.alloc(Math.min(info.size, FILE_READ_CAP));
33209
+ const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
33210
+ const slice = buf.subarray(0, bytesRead);
33211
+ const binary = slice.includes(0);
33212
+ return {
33213
+ workspace: workspace3,
33214
+ path: rel,
33215
+ content: binary ? "" : slice.toString("utf8"),
33216
+ size: info.size,
33217
+ truncated: info.size > FILE_READ_CAP,
33218
+ binary
33219
+ };
33220
+ } finally {
33221
+ await fh.close();
33222
+ }
33223
+ }
33224
+ async search(workspace3, query) {
33225
+ const { root } = await this.resolve(workspace3, undefined);
33226
+ const needle = query.trim().toLowerCase();
33227
+ const matches = [];
33228
+ if (!needle)
33229
+ return { workspace: workspace3, query, matches, truncated: false };
33230
+ let scanned = 0;
33231
+ let truncated = false;
33232
+ const queue = [root];
33233
+ while (queue.length) {
33234
+ const dir = queue.shift();
33235
+ let dirents;
33236
+ try {
33237
+ dirents = await readdir3(dir, { withFileTypes: true });
33238
+ } catch {
33239
+ continue;
33240
+ }
33241
+ for (const d of dirents) {
33242
+ if (++scanned > SEARCH_MAX_SCAN) {
33243
+ truncated = true;
33244
+ break;
33245
+ }
33246
+ if (d.isSymbolicLink())
33247
+ continue;
33248
+ if (d.isDirectory()) {
33249
+ if (!SEARCH_SKIP_DIRS.has(d.name))
33250
+ queue.push(resolve3(dir, d.name));
33251
+ } else if (d.isFile()) {
33252
+ const rel = relative(root, resolve3(dir, d.name)).split(sep).join("/");
33253
+ if (rel.toLowerCase().includes(needle)) {
33254
+ matches.push(rel);
33255
+ if (matches.length >= SEARCH_MAX_RESULTS) {
33256
+ truncated = true;
33257
+ break;
33258
+ }
33259
+ }
33260
+ }
33261
+ }
33262
+ if (truncated)
33263
+ break;
33264
+ }
33265
+ matches.sort();
33266
+ return { workspace: workspace3, query, matches, truncated };
33267
+ }
33268
+ async gitDiff(workspace3, relPath) {
33269
+ const { root, rel } = await this.resolve(workspace3, relPath);
33270
+ try {
33271
+ await execFileAsync("git", ["-C", root, "rev-parse", "--is-inside-work-tree"], { maxBuffer: GIT_MAX_BUFFER });
33272
+ } catch {
33273
+ throw new Error("not-a-git-repo");
33274
+ }
33275
+ const files = [];
33276
+ try {
33277
+ const { stdout: stdout2 } = await execFileAsync("git", ["-C", root, "status", "--porcelain"], { maxBuffer: GIT_MAX_BUFFER });
33278
+ for (const line of stdout2.split(`
33279
+ `)) {
33280
+ if (!line)
33281
+ continue;
33282
+ const status = line.slice(0, 2);
33283
+ let path15 = line.slice(3);
33284
+ const arrow = path15.indexOf(" -> ");
33285
+ if (arrow >= 0)
33286
+ path15 = path15.slice(arrow + 4);
33287
+ files.push({ path: path15, status });
33288
+ }
33289
+ } catch {}
33290
+ const diffArgs = (base) => ["-C", root, ...base, ...rel ? ["--", rel] : []];
33291
+ let diff = "";
33292
+ try {
33293
+ diff = (await execFileAsync("git", diffArgs(["diff", "HEAD"]), { maxBuffer: GIT_MAX_BUFFER })).stdout;
33294
+ } catch {
33295
+ try {
33296
+ diff = (await execFileAsync("git", diffArgs(["diff"]), { maxBuffer: GIT_MAX_BUFFER })).stdout;
33297
+ } catch {
33298
+ diff = "";
33299
+ }
33300
+ }
33301
+ const truncated = diff.length > DIFF_CAP;
33302
+ return { workspace: workspace3, files, diff: truncated ? diff.slice(0, DIFF_CAP) : diff, truncated, ...await this.gitContext(root) };
33303
+ }
33304
+ async gitContext(root) {
33305
+ const run = async (...args) => {
33306
+ try {
33307
+ return (await execFileAsync("git", ["-C", root, ...args], { maxBuffer: GIT_MAX_BUFFER })).stdout.trim();
33308
+ } catch {
33309
+ return null;
33310
+ }
33311
+ };
33312
+ const ctx = {};
33313
+ const head = await run("rev-parse", "--abbrev-ref", "HEAD");
33314
+ if (head === "HEAD")
33315
+ ctx.detached = true;
33316
+ else if (head)
33317
+ ctx.branch = head;
33318
+ const top = await run("rev-parse", "--show-toplevel");
33319
+ if (top) {
33320
+ const gitDir = await run("rev-parse", "--absolute-git-dir");
33321
+ const commonDir = await run("rev-parse", "--path-format=absolute", "--git-common-dir");
33322
+ ctx.worktree = { root: top, linked: !!gitDir && !!commonDir && gitDir !== commonDir };
33323
+ }
33324
+ return ctx;
33325
+ }
33326
+ }
33327
+ var execFileAsync, MAX_ENTRIES = 2000, FILE_READ_CAP, DIFF_CAP, GIT_MAX_BUFFER, SEARCH_MAX_RESULTS = 200, SEARCH_MAX_SCAN = 20000, SEARCH_SKIP_DIRS;
33328
+ var init_workspace_fs = __esm(() => {
33329
+ execFileAsync = promisify(execFile);
33330
+ FILE_READ_CAP = 256 * 1024;
33331
+ DIFF_CAP = 512 * 1024;
33332
+ GIT_MAX_BUFFER = 32 * 1024 * 1024;
33333
+ SEARCH_SKIP_DIRS = new Set([".git", "node_modules"]);
33334
+ });
33335
+
33336
+ // src/control/control-service.ts
33337
+ class ControlService {
33338
+ deps;
33339
+ constructor(deps) {
33340
+ this.deps = deps;
33341
+ }
33342
+ workspaceFs = new WorkspaceFs(() => this.deps.workspaces.list().map((w) => ({ name: w.name, cwd: w.cwd })));
33343
+ listDirectory(workspace3, path15) {
33344
+ return this.workspaceFs.listDirectory(workspace3, path15);
33345
+ }
33346
+ readWorkspaceFile(workspace3, path15) {
33347
+ return this.workspaceFs.readFile(workspace3, path15);
33348
+ }
33349
+ workspaceGitDiff(workspace3, path15) {
33350
+ return this.workspaceFs.gitDiff(workspace3, path15);
33351
+ }
33352
+ searchWorkspace(workspace3, query) {
33353
+ return this.workspaceFs.search(workspace3, query);
33354
+ }
33355
+ async getSessionModel(chatKey, alias) {
33356
+ const session3 = await this.resolveControlSession(chatKey, alias);
33357
+ if (!session3)
33358
+ return { available: [] };
33359
+ if (!this.deps.transport.getSessionModel)
33360
+ return { current: session3.model, available: [] };
33361
+ return await this.deps.transport.getSessionModel(session3);
33362
+ }
33363
+ async setSessionModel(chatKey, alias, modelId) {
33364
+ const session3 = await this.resolveControlSession(chatKey, alias);
33365
+ if (!session3)
33366
+ throw new Error("session not found");
33367
+ if (!this.deps.transport.setModel)
33368
+ throw new Error("the active transport does not support switching models");
33369
+ await this.deps.transport.setModel(session3, modelId);
33370
+ await this.deps.sessions.setSessionModel(session3.alias, modelId);
33371
+ }
33372
+ async resolveControlSession(chatKey, alias) {
33373
+ const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
33374
+ return await this.deps.sessions.getSession(internalAlias);
33375
+ }
33376
+ get events() {
33377
+ return this.deps.events;
33378
+ }
33379
+ listSessions(chatKey) {
33380
+ const channelId = getChannelIdFromChatKey(chatKey);
33381
+ return this.deps.sessions.listAllResolvedSessions().filter((session3) => isSessionAliasVisibleInChannel(session3.alias, channelId)).map((session3) => ({
33382
+ alias: toDisplaySessionAlias(session3.alias),
33383
+ agent: session3.agent,
33384
+ workspace: session3.workspace,
33385
+ transportSession: session3.transportSession,
33386
+ running: this.deps.activeTurns.isActiveAnywhere(session3.alias)
33387
+ }));
33388
+ }
33389
+ async listNativeSessions(_chatKey, agent3, workspace3) {
33390
+ const sessions = await this.deps.listNativeSessions(agent3, workspace3);
33391
+ return sessions.map((s) => ({
33392
+ sessionId: s.sessionId,
33393
+ title: s.title ?? null,
33394
+ ...s.updatedAt !== undefined ? { updatedAt: s.updatedAt } : {},
33395
+ ...s.cwd !== undefined ? { cwd: s.cwd } : {}
33396
+ }));
33397
+ }
33398
+ async createSession(chatKey, alias, agent3, workspace3, agentSessionId, model) {
33399
+ const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
33400
+ let nativeHistory = [];
33401
+ if (agentSessionId) {
33402
+ try {
33403
+ nativeHistory = await readNativeSessionHistory({ agentSessionId });
33404
+ } catch {}
33405
+ }
33406
+ const session3 = agentSessionId ? await this.deps.attachNativeSessionWithTransport(internalAlias, agent3, workspace3, agentSessionId) : await this.deps.createSessionWithTransport(internalAlias, agent3, workspace3, model);
33407
+ this.deps.events.emit({ type: "sessions-changed" });
33408
+ if (nativeHistory.length > 0) {
33409
+ this.deps.events.emit({ type: "session-history", chatKey, sessionAlias: alias, messages: nativeHistory });
33410
+ }
33411
+ return {
33412
+ alias: toDisplaySessionAlias(session3.alias),
33413
+ agent: session3.agent,
33414
+ workspace: session3.workspace,
33415
+ transportSession: session3.transportSession,
33416
+ running: false
33417
+ };
33418
+ }
33419
+ async removeSession(chatKey, alias) {
33420
+ const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
33421
+ const result = await this.deps.sessions.removeSession(internalAlias);
33422
+ this.deps.events.emit({ type: "sessions-changed" });
33423
+ return result;
33424
+ }
33425
+ listAgents() {
33426
+ return this.deps.agents.list();
33427
+ }
33428
+ listWorkspaces() {
33429
+ return this.deps.workspaces.list();
33430
+ }
33431
+ createWorkspace(name, cwd, description) {
33432
+ return this.deps.workspaces.create(name, cwd, description);
33433
+ }
33434
+ listAgentCatalog() {
33435
+ return this.deps.agents.catalog();
33436
+ }
33437
+ createAgent(name, driver) {
33438
+ return this.deps.agents.create(name, driver);
33439
+ }
33440
+ async removeAgent(name) {
33441
+ if (this.deps.sessions.listAllResolvedSessions().some((s) => s.agent === name)) {
33442
+ throw new Error(`agent "${name}" is in use by an existing session`);
33443
+ }
33444
+ await this.deps.agents.remove(name);
33445
+ }
33446
+ async removeWorkspace(name) {
33447
+ if (this.deps.sessions.listAllResolvedSessions().some((s) => s.workspace === name)) {
33448
+ throw new Error(`workspace "${name}" is in use by an existing session`);
33449
+ }
33450
+ await this.deps.workspaces.remove(name);
33451
+ }
33452
+ listScheduledTasks(chatKey) {
33453
+ return this.deps.scheduled.listRecentForChat(chatKey);
33454
+ }
33455
+ async createScheduledTask(input) {
33456
+ const task = await this.deps.scheduled.createTask(input);
33457
+ this.deps.events.emit({ type: "scheduled-changed", chatKey: input.chatKey });
33458
+ return task;
33459
+ }
33460
+ async cancelScheduledTask(id, chatKey) {
33461
+ const cancelled = await this.deps.scheduled.cancelPending(id, chatKey);
33462
+ if (cancelled) {
33463
+ this.deps.events.emit({ type: "scheduled-changed", chatKey });
33464
+ }
33465
+ return cancelled;
33466
+ }
33467
+ listOrchestrationTasks(filter) {
33468
+ return this.deps.orchestration.listTasks(filter);
33469
+ }
33470
+ getOrchestrationTask(taskId) {
33471
+ return this.deps.orchestration.getTask(taskId);
33472
+ }
33473
+ async cancelOrchestrationTask(input) {
33474
+ const task = await this.deps.orchestration.requestTaskCancellation(input);
33475
+ this.deps.events.emit({ type: "orchestration-changed" });
33476
+ return task;
33477
+ }
33478
+ inFlight = new Map;
33479
+ async prompt(input) {
33480
+ return this.executeTurn({
33481
+ chatKey: input.chatKey,
33482
+ sessionAlias: input.sessionAlias,
33483
+ text: input.text,
33484
+ senderId: input.senderId,
33485
+ ...input.isOwner !== undefined ? { isOwner: input.isOwner } : {},
33486
+ ...input.accountId !== undefined ? { accountId: input.accountId } : {}
33487
+ });
33488
+ }
33489
+ async runScheduledTurn(input) {
33490
+ return this.executeTurn({
33491
+ chatKey: input.chatKey,
33492
+ sessionAlias: input.sessionAlias,
33493
+ text: input.promptText,
33494
+ senderId: "scheduler",
33495
+ isOwner: true,
33496
+ ...input.accountId !== undefined ? { accountId: input.accountId } : {},
33497
+ ...input.abortSignal ? { abortSignal: input.abortSignal } : {},
33498
+ turnStarted: { prompt: input.promptText, scheduled: { taskId: input.taskId, executeAt: input.executeAt } }
33499
+ });
33500
+ }
33501
+ async executeTurn(params) {
33502
+ const key = turnKey(params.chatKey, params.sessionAlias);
33503
+ const existing = this.inFlight.get(key);
33504
+ if (existing) {
33505
+ if (!existing.controller.signal.aborted) {
33506
+ return { ok: false, errorMessage: "turn-already-running" };
33507
+ }
33508
+ await raceWithTimeout(existing.settled, CANCEL_DRAIN_TIMEOUT_MS);
33509
+ if (this.inFlight.has(key)) {
33510
+ return { ok: false, errorMessage: "turn-already-running" };
33511
+ }
33512
+ }
33513
+ const controller = new AbortController;
33514
+ if (params.abortSignal) {
33515
+ if (params.abortSignal.aborted)
33516
+ controller.abort();
33517
+ else
33518
+ params.abortSignal.addEventListener("abort", () => controller.abort(), { once: true });
33519
+ }
33520
+ let resolveSettled;
33521
+ const settled = new Promise((resolve4) => {
33522
+ resolveSettled = resolve4;
33523
+ });
33524
+ this.inFlight.set(key, { controller, settled });
33525
+ try {
33526
+ await this.deps.sessions.useSession(params.chatKey, params.sessionAlias);
33527
+ } catch (error2) {
33528
+ this.inFlight.delete(key);
33529
+ resolveSettled();
33530
+ return { ok: false, errorMessage: toErrorMessage(error2) };
33531
+ }
33532
+ this.deps.events.emit({
33533
+ type: "turn-started",
33534
+ chatKey: params.chatKey,
33535
+ sessionAlias: params.sessionAlias,
33536
+ ...params.turnStarted?.prompt ? { prompt: params.turnStarted.prompt } : {},
33537
+ ...params.turnStarted?.scheduled ? { scheduled: params.turnStarted.scheduled } : {}
33538
+ });
33539
+ let streamMode = false;
33540
+ try {
33541
+ const resolved = await this.resolveControlSession(params.chatKey, params.sessionAlias);
33542
+ streamMode = (resolved?.effectiveReplyMode ?? resolved?.replyMode) === "stream";
33543
+ } catch {}
33544
+ let emittedChunk = false;
33545
+ const emitChunk = (chunk) => {
33546
+ if (!chunk)
33547
+ return;
33548
+ this.deps.events.emit({
33549
+ type: "turn-output",
33550
+ chatKey: params.chatKey,
33551
+ sessionAlias: params.sessionAlias,
33552
+ chunk: !streamMode && emittedChunk ? `
33553
+
33554
+ ${chunk}` : chunk
33555
+ });
33556
+ emittedChunk = true;
33557
+ };
33558
+ try {
33559
+ const response = await this.deps.agent.chat({
33560
+ accountId: params.accountId ?? "control",
33561
+ conversationId: params.chatKey,
33562
+ text: params.text,
33563
+ metadata: buildControlMetadata(params.senderId, params.isOwner),
33564
+ abortSignal: controller.signal,
33565
+ reply: async (chunk) => {
33566
+ emitChunk(chunk);
33567
+ },
33568
+ onToolEvent: (event) => {
33569
+ this.deps.events.emit({
33570
+ type: "tool-event",
33571
+ chatKey: params.chatKey,
33572
+ sessionAlias: params.sessionAlias,
33573
+ event
33574
+ });
33575
+ },
33576
+ onThought: (chunk) => {
33577
+ this.deps.events.emit({
33578
+ type: "turn-thought",
33579
+ chatKey: params.chatKey,
33580
+ sessionAlias: params.sessionAlias,
33581
+ chunk
33582
+ });
33583
+ },
33584
+ onPlan: (entries) => {
33585
+ this.deps.events.emit({
33586
+ type: "plan",
33587
+ chatKey: params.chatKey,
33588
+ sessionAlias: params.sessionAlias,
33589
+ entries
33590
+ });
33591
+ },
33592
+ onUsage: (usage) => {
33593
+ this.deps.events.emit({
33594
+ type: "turn-usage",
33595
+ chatKey: params.chatKey,
33596
+ sessionAlias: params.sessionAlias,
33597
+ used: usage.used,
33598
+ size: usage.size
33599
+ });
33600
+ }
33601
+ });
33602
+ if (response.text) {
33603
+ emitChunk(response.text);
33604
+ }
33605
+ this.deps.events.emit({
33606
+ type: "turn-finished",
33607
+ chatKey: params.chatKey,
33608
+ sessionAlias: params.sessionAlias,
33609
+ ok: true
33610
+ });
33611
+ return { ok: true, text: response.text };
33612
+ } catch (error2) {
33613
+ const errorMessage = toErrorMessage(error2);
33614
+ this.deps.events.emit({
33615
+ type: "turn-finished",
33616
+ chatKey: params.chatKey,
33617
+ sessionAlias: params.sessionAlias,
33618
+ ok: false,
33619
+ errorMessage,
33620
+ ...controller.signal.aborted ? { cancelled: true } : {}
33621
+ });
33622
+ return { ok: false, errorMessage };
33623
+ } finally {
33624
+ this.inFlight.delete(key);
33625
+ resolveSettled();
33626
+ }
33627
+ }
33628
+ cancelTurn(chatKey, sessionAlias) {
33629
+ const entry = this.inFlight.get(turnKey(chatKey, sessionAlias));
33630
+ if (!entry) {
33631
+ return false;
33632
+ }
33633
+ entry.controller.abort();
33634
+ return true;
33635
+ }
33636
+ async executeCommand(input) {
33637
+ const chunks = [];
33638
+ const response = await this.deps.agent.chat({
33639
+ accountId: input.accountId ?? "control",
33640
+ conversationId: input.chatKey,
33641
+ text: input.text,
33642
+ metadata: buildControlMetadata(input.senderId, input.isOwner),
33643
+ reply: async (chunk) => {
33644
+ chunks.push(chunk);
33645
+ }
33646
+ });
33647
+ if (response.text) {
33648
+ chunks.push(response.text);
33649
+ }
33650
+ return chunks.join(`
33651
+ `);
33652
+ }
33653
+ }
33654
+ async function raceWithTimeout(promise2, ms) {
33655
+ let timer;
33656
+ const timeout = new Promise((resolve4) => {
33657
+ timer = setTimeout(resolve4, ms);
33658
+ });
33659
+ try {
33660
+ await Promise.race([promise2, timeout]);
33661
+ } finally {
33662
+ if (timer)
33663
+ clearTimeout(timer);
33664
+ }
33665
+ }
33666
+ function turnKey(chatKey, sessionAlias) {
33667
+ return `${chatKey} ${sessionAlias}`;
33668
+ }
33669
+ function toErrorMessage(error2) {
33670
+ return error2 instanceof Error ? error2.message : String(error2);
33671
+ }
33672
+ function buildControlMetadata(senderId, isOwner) {
33673
+ return {
33674
+ channel: "control",
33675
+ chatType: "direct",
33676
+ senderId,
33677
+ ...isOwner === undefined ? {} : { isOwner }
33678
+ };
33679
+ }
33680
+ var CANCEL_DRAIN_TIMEOUT_MS = 5000;
33681
+ var init_control_service = __esm(() => {
33682
+ init_channel_scope();
33683
+ init_native_session_history();
33684
+ init_workspace_fs();
33685
+ });
33686
+
33687
+ // src/config/agent-catalog.ts
33688
+ import { existsSync as existsSync3 } from "node:fs";
33689
+ import { delimiter as delimiter2, join as join20 } from "node:path";
33690
+ function isBinaryOnPath(binary) {
33691
+ const path15 = process.env.PATH ?? "";
33692
+ const exts = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
33693
+ for (const dir of path15.split(delimiter2)) {
33694
+ if (!dir)
33695
+ continue;
33696
+ for (const ext of exts) {
33697
+ try {
33698
+ if (existsSync3(join20(dir, binary + ext)))
33699
+ return true;
33700
+ } catch {}
33701
+ }
33702
+ }
33703
+ return false;
33704
+ }
33705
+ function listAgentCatalog(config4, probe = isBinaryOnPath) {
33706
+ const agents = config4.agents ?? {};
33707
+ const driverConfigured = (driver) => Object.entries(agents).some(([name, a]) => name === driver || a.driver === driver);
33708
+ return listAgentTemplates().map((driver) => {
33709
+ let installed;
33710
+ if (BUILTIN_DRIVERS.has(driver)) {
33711
+ installed = "builtin";
33712
+ } else {
33713
+ const binary = DRIVER_BINARIES[driver] ?? driver;
33714
+ installed = probe(binary) ? "yes" : "unknown";
33715
+ }
33716
+ return { driver, configured: driverConfigured(driver), installed };
33717
+ });
33718
+ }
33719
+ var BUILTIN_DRIVERS, DRIVER_BINARIES;
33720
+ var init_agent_catalog = __esm(() => {
33721
+ init_agent_templates();
33722
+ BUILTIN_DRIVERS = new Set(["codex", "claude"]);
33723
+ DRIVER_BINARIES = {
33724
+ cursor: "cursor-agent"
33725
+ };
33726
+ });
33727
+
32392
33728
  // src/main.ts
32393
33729
  var exports_main = {};
32394
33730
  __export(exports_main, {
@@ -32399,8 +33735,8 @@ __export(exports_main, {
32399
33735
  buildApp: () => buildApp
32400
33736
  });
32401
33737
  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";
33738
+ import { homedir as homedir11 } from "node:os";
33739
+ import { dirname as dirname12, join as join21 } from "node:path";
32404
33740
  import { fileURLToPath as fileURLToPath5 } from "node:url";
32405
33741
  function startProgressHeartbeat(orchestration3, config4, logger2, channel) {
32406
33742
  const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
@@ -32676,7 +34012,7 @@ async function buildApp(paths, deps = {}) {
32676
34012
  return {
32677
34013
  alias: input.workerSession,
32678
34014
  agent: input.targetAgent,
32679
- agentCommand: resolveAgentCommand(agentConfig.driver, agentConfig.command),
34015
+ agentCommand: resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, config4.transport.preferLocalAgents !== false),
32680
34016
  workspace: input.workspace,
32681
34017
  transportSession: input.workerSession,
32682
34018
  cwd: input.cwd
@@ -32890,9 +34226,52 @@ async function buildApp(paths, deps = {}) {
32890
34226
  });
32891
34227
  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
34228
  const agent3 = new ConsoleAgent(router3, logger2);
34229
+ const controlEvents = createControlEventBus(logger2);
34230
+ const control = new ControlService({
34231
+ agent: agent3,
34232
+ sessions,
34233
+ transport,
34234
+ createSessionWithTransport: (internalAlias, agent4, workspace3, model) => router3.createSessionWithTransport(internalAlias, agent4, workspace3, model),
34235
+ listNativeSessions: (agent4, workspace3) => router3.listNativeSessionsForControl(agent4, workspace3),
34236
+ attachNativeSessionWithTransport: (internalAlias, agent4, workspace3, agentSessionId, nativeMeta) => router3.attachNativeSessionWithTransport(internalAlias, agent4, workspace3, agentSessionId, nativeMeta),
34237
+ activeTurns,
34238
+ scheduled: scheduledService,
34239
+ orchestration: orchestration3,
34240
+ events: controlEvents,
34241
+ agents: {
34242
+ list: () => Object.entries(config4.agents).map(([name, agentConfig]) => ({ name, driver: agentConfig.driver })),
34243
+ catalog: () => listAgentCatalog(config4),
34244
+ create: async (name, driver) => {
34245
+ const updated = await configStore.upsertAgent(name, { driver });
34246
+ replaceRuntimeConfig(config4, updated);
34247
+ return { name, driver };
34248
+ },
34249
+ remove: async (name) => {
34250
+ const updated = await configStore.removeAgent(name);
34251
+ replaceRuntimeConfig(config4, updated);
34252
+ }
34253
+ },
34254
+ workspaces: {
34255
+ list: () => Object.entries(config4.workspaces).map(([name, workspace3]) => ({
34256
+ name,
34257
+ cwd: workspace3.cwd,
34258
+ ...workspace3.description ? { description: workspace3.description } : {}
34259
+ })),
34260
+ create: async (name, cwd, description) => {
34261
+ const updated = await configStore.upsertWorkspace(name, cwd, description);
34262
+ replaceRuntimeConfig(config4, updated);
34263
+ return { name, cwd, ...description ? { description } : {} };
34264
+ },
34265
+ remove: async (name) => {
34266
+ const updated = await configStore.removeWorkspace(name);
34267
+ replaceRuntimeConfig(config4, updated);
34268
+ }
34269
+ }
34270
+ });
32893
34271
  const scheduledScheduler = new ScheduledTaskScheduler(scheduledService, {
32894
34272
  dispatchTask: buildScheduledDispatchTask({
32895
34273
  getSession: (alias) => sessions.getSession(alias),
34274
+ resolveAliasForChat: (chatKey, alias) => sessions.resolveAliasForChat(chatKey, alias),
32896
34275
  resolveSession: (alias, agent4, workspace3, transportSession) => sessions.resolveSession(alias, agent4, workspace3, transportSession),
32897
34276
  sendScheduledMessage: async (input) => {
32898
34277
  if (!deps.channel?.sendScheduledMessage) {
@@ -32903,6 +34282,7 @@ async function buildApp(paths, deps = {}) {
32903
34282
  ...transport.removeSession ? { removeSession: (session3) => transport.removeSession(session3) } : {},
32904
34283
  logger: logger2
32905
34284
  }),
34285
+ onSettled: (task) => controlEvents.emit({ type: "scheduled-changed", chatKey: task.chat_key }),
32906
34286
  logger: logger2
32907
34287
  });
32908
34288
  const reapWarmQueueOwners = async (phase) => {
@@ -32952,6 +34332,7 @@ async function buildApp(paths, deps = {}) {
32952
34332
  service: scheduledService,
32953
34333
  scheduler: scheduledScheduler
32954
34334
  },
34335
+ control,
32955
34336
  reapStaleQueueOwners: () => reapWarmQueueOwners("startup"),
32956
34337
  dispose: async () => {
32957
34338
  scheduledScheduler.stop();
@@ -33015,8 +34396,8 @@ async function main() {
33015
34396
  }
33016
34397
  }
33017
34398
  async function prepareChannelMedia(configPath, config4) {
33018
- const runtimeDir = join18(dirname12(configPath), "runtime");
33019
- const mediaRootDir = join18(runtimeDir, "media");
34399
+ const runtimeDir = join21(dirname12(configPath), "runtime");
34400
+ const mediaRootDir = join21(runtimeDir, "media");
33020
34401
  const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
33021
34402
  await mediaStore.cleanupExpired().catch((error2) => {
33022
34403
  console.error("[xacpx] media cleanup failed:", error2 instanceof Error ? error2.message : String(error2));
@@ -33025,16 +34406,16 @@ async function prepareChannelMedia(configPath, config4) {
33025
34406
  return { mediaStore, channelDeps: { mediaStore, allowedMediaRoots } };
33026
34407
  }
33027
34408
  function resolveRuntimePaths() {
33028
- const home = process.env.HOME ?? homedir9();
34409
+ const home = process.env.HOME ?? homedir11();
33029
34410
  if (!home) {
33030
34411
  throw new Error("Unable to resolve the current user home directory");
33031
34412
  }
33032
- const configPath = coreEnv("CONFIG") ?? join18(coreHomeDir(home), "config.json");
33033
- const runtimeDir = join18(dirname12(configPath), "runtime");
34413
+ const configPath = coreEnv("CONFIG") ?? join21(coreHomeDir(home), "config.json");
34414
+ const runtimeDir = join21(dirname12(configPath), "runtime");
33034
34415
  return {
33035
34416
  configPath,
33036
- statePath: coreEnv("STATE") ?? join18(coreHomeDir(home), "state.json"),
33037
- perfLogPath: join18(runtimeDir, "perf.log"),
34417
+ statePath: coreEnv("STATE") ?? join21(coreHomeDir(home), "state.json"),
34418
+ perfLogPath: join21(runtimeDir, "perf.log"),
33038
34419
  orchestrationSocketPath: coreEnv("ORCHESTRATION_SOCKET") ?? resolveDaemonOrchestrationSocketPath(runtimeDir)
33039
34420
  };
33040
34421
  }
@@ -33046,13 +34427,13 @@ function resolveBridgeEntryPath() {
33046
34427
  }
33047
34428
  function resolveAppLogPath(configPath) {
33048
34429
  const rootDir = dirname12(configPath);
33049
- const runtimeDir = join18(rootDir, "runtime");
33050
- return join18(runtimeDir, "app.log");
34430
+ const runtimeDir = join21(rootDir, "runtime");
34431
+ return join21(runtimeDir, "app.log");
33051
34432
  }
33052
34433
  function resolvePerfLogPath(configPath) {
33053
34434
  const rootDir = dirname12(configPath);
33054
- const runtimeDir = join18(rootDir, "runtime");
33055
- return join18(runtimeDir, "perf.log");
34435
+ const runtimeDir = join21(rootDir, "runtime");
34436
+ return join21(runtimeDir, "perf.log");
33056
34437
  }
33057
34438
  function resolveOrchestrationSocketPathFromConfigPath(configPath) {
33058
34439
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
@@ -33068,6 +34449,7 @@ var init_main = __esm(async () => {
33068
34449
  init_ensure_config();
33069
34450
  init_load_config();
33070
34451
  init_resolve_acpx_command();
34452
+ init_resolve_agent_command();
33071
34453
  init_console_agent();
33072
34454
  init_app_logger();
33073
34455
  init_daemon_files();
@@ -33095,6 +34477,8 @@ var init_main = __esm(async () => {
33095
34477
  init_inbound();
33096
34478
  init_render_text();
33097
34479
  init_quota_manager();
34480
+ init_control_service();
34481
+ init_agent_catalog();
33098
34482
  init_perf_tracer();
33099
34483
  init_bootstrap();
33100
34484
  init_i18n();
@@ -33148,7 +34532,7 @@ function buildDetails(metadata, version2, verbose) {
33148
34532
  }
33149
34533
  async function defaultRunVersion(command) {
33150
34534
  const spawnSpec = resolveSpawnCommand(command, ["--version"]);
33151
- return await new Promise((resolve3, reject) => {
34535
+ return await new Promise((resolve4, reject) => {
33152
34536
  const child = spawn11(spawnSpec.command, spawnSpec.args, {
33153
34537
  stdio: ["ignore", "pipe", "pipe"]
33154
34538
  });
@@ -33165,7 +34549,7 @@ async function defaultRunVersion(command) {
33165
34549
  if (code === 0) {
33166
34550
  const version2 = stdout2.trim() || stderr.trim();
33167
34551
  if (version2.length > 0) {
33168
- resolve3(version2);
34552
+ resolve4(version2);
33169
34553
  return;
33170
34554
  }
33171
34555
  }
@@ -33287,12 +34671,12 @@ var init_config_check = __esm(async () => {
33287
34671
  });
33288
34672
 
33289
34673
  // src/doctor/checks/daemon-check.ts
33290
- import { readdir as readdir3, readFile as readFile14, rm as rm10 } from "node:fs/promises";
34674
+ import { readdir as readdir4, readFile as readFile15, rm as rm10 } from "node:fs/promises";
33291
34675
  import { fileURLToPath as fileURLToPath6 } from "node:url";
33292
- import { homedir as homedir10 } from "node:os";
33293
- import { join as join19 } from "node:path";
34676
+ import { homedir as homedir12 } from "node:os";
34677
+ import { join as join22 } from "node:path";
33294
34678
  async function checkDaemon(options = {}) {
33295
- const home = options.home ?? process.env.HOME ?? homedir10();
34679
+ const home = options.home ?? process.env.HOME ?? homedir12();
33296
34680
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
33297
34681
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
33298
34682
  home,
@@ -33392,7 +34776,7 @@ async function detectStaleConsumerLockFix(runtimeDir, deps) {
33392
34776
  if (!fileName.endsWith(CONSUMER_LOCK_SUFFIX)) {
33393
34777
  continue;
33394
34778
  }
33395
- const lockPath = join19(runtimeDir, fileName);
34779
+ const lockPath = join22(runtimeDir, fileName);
33396
34780
  const lock2 = await deps.readConsumerLock(lockPath);
33397
34781
  if (lock2 && !deps.isProcessRunning(lock2.pid)) {
33398
34782
  stalePaths.push(lockPath);
@@ -33426,14 +34810,14 @@ async function detectStaleConsumerLockFix(runtimeDir, deps) {
33426
34810
  }
33427
34811
  async function defaultListConsumerLocks(runtimeDir) {
33428
34812
  try {
33429
- return await readdir3(runtimeDir);
34813
+ return await readdir4(runtimeDir);
33430
34814
  } catch {
33431
34815
  return [];
33432
34816
  }
33433
34817
  }
33434
34818
  async function defaultReadConsumerLock(path15) {
33435
34819
  try {
33436
- const raw = await readFile14(path15, "utf8");
34820
+ const raw = await readFile15(path15, "utf8");
33437
34821
  const parsed = JSON.parse(raw);
33438
34822
  return typeof parsed.pid === "number" ? { pid: parsed.pid } : null;
33439
34823
  } catch {
@@ -33456,11 +34840,11 @@ var init_daemon_check = __esm(() => {
33456
34840
  });
33457
34841
 
33458
34842
  // 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";
34843
+ import { stat as stat4, readdir as readdir5 } from "node:fs/promises";
34844
+ import { basename as basename3, join as join23 } from "node:path";
34845
+ import { homedir as homedir13 } from "node:os";
33462
34846
  async function checkLogs(options = {}) {
33463
- const home = options.home ?? process.env.HOME ?? homedir11();
34847
+ const home = options.home ?? process.env.HOME ?? homedir13();
33464
34848
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
33465
34849
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
33466
34850
  home,
@@ -33492,7 +34876,7 @@ async function checkLogs(options = {}) {
33492
34876
  const matched = entries.filter((entry) => isTrackedLogName(entry, tracked));
33493
34877
  const files = [];
33494
34878
  for (const name of matched) {
33495
- const path15 = join20(paths.runtimeDir, name);
34879
+ const path15 = join23(paths.runtimeDir, name);
33496
34880
  try {
33497
34881
  const fileStat = await probe.stat(path15);
33498
34882
  if (fileStat.isDirectory()) {
@@ -33572,8 +34956,8 @@ function formatBytes(bytes) {
33572
34956
  }
33573
34957
  function createLogsFsProbe() {
33574
34958
  return {
33575
- stat: async (path15) => await stat3(path15),
33576
- readdir: async (path15) => await readdir4(path15)
34959
+ stat: async (path15) => await stat4(path15),
34960
+ readdir: async (path15) => await readdir5(path15)
33577
34961
  };
33578
34962
  }
33579
34963
  function formatError6(error2) {
@@ -33642,9 +35026,9 @@ var init_orchestration_health = __esm(() => {
33642
35026
  });
33643
35027
 
33644
35028
  // src/doctor/checks/orchestration-socket-check.ts
33645
- import { homedir as homedir12 } from "node:os";
35029
+ import { homedir as homedir14 } from "node:os";
33646
35030
  async function checkOrchestrationSocket(options = {}) {
33647
- const home = options.home ?? process.env.HOME ?? homedir12();
35031
+ const home = options.home ?? process.env.HOME ?? homedir14();
33648
35032
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
33649
35033
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
33650
35034
  home,
@@ -33817,11 +35201,11 @@ var init_plugin_check = __esm(async () => {
33817
35201
 
33818
35202
  // src/doctor/checks/runtime-check.ts
33819
35203
  import { constants } from "node:fs";
33820
- import { access as access4, stat as stat4 } from "node:fs/promises";
35204
+ import { access as access4, stat as stat5 } from "node:fs/promises";
33821
35205
  import { dirname as dirname13 } from "node:path";
33822
- import { homedir as homedir13 } from "node:os";
35206
+ import { homedir as homedir15 } from "node:os";
33823
35207
  async function checkRuntime(options = {}) {
33824
- const home = options.home ?? process.env.HOME ?? homedir13();
35208
+ const home = options.home ?? process.env.HOME ?? homedir15();
33825
35209
  const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
33826
35210
  const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
33827
35211
  home,
@@ -33915,7 +35299,7 @@ function formatMode(mode) {
33915
35299
  }
33916
35300
  function createRuntimeFsProbe() {
33917
35301
  return {
33918
- stat: async (path15) => await stat4(path15),
35302
+ stat: async (path15) => await stat5(path15),
33919
35303
  access: async (path15, mode) => await access4(path15, mode)
33920
35304
  };
33921
35305
  }
@@ -34247,7 +35631,10 @@ function buildSession(options) {
34247
35631
  return {
34248
35632
  alias: "xacpx-doctor",
34249
35633
  agent: options.agent,
34250
- ...agentConfig.command ? { agentCommand: agentConfig.command } : {},
35634
+ ...(() => {
35635
+ const agentCommand = resolveRuntimeAgentCommand(agentConfig.driver, agentConfig.command, options.config.transport.preferLocalAgents !== false);
35636
+ return agentCommand ? { agentCommand } : {};
35637
+ })(),
34251
35638
  workspace: options.workspace,
34252
35639
  transportSession: `xacpx-doctor-${timestamp}`,
34253
35640
  replyMode: options.config.channel.replyMode,
@@ -34292,6 +35679,7 @@ var SMOKE_PROMPT = "Reply with exactly: ok";
34292
35679
  var init_smoke_check = __esm(async () => {
34293
35680
  init_load_config();
34294
35681
  init_resolve_acpx_command();
35682
+ init_resolve_agent_command();
34295
35683
  init_acpx_bridge_client();
34296
35684
  init_acpx_bridge_transport();
34297
35685
  init_acpx_cli_transport();
@@ -34450,10 +35838,10 @@ var init_render_doctor = __esm(() => {
34450
35838
  });
34451
35839
 
34452
35840
  // src/doctor/doctor.ts
34453
- import { homedir as homedir14 } from "node:os";
34454
- import { join as join21 } from "node:path";
35841
+ import { homedir as homedir16 } from "node:os";
35842
+ import { join as join24 } from "node:path";
34455
35843
  async function runDoctor(options = {}, deps = {}) {
34456
- const home = deps.home ?? process.env.HOME ?? homedir14();
35844
+ const home = deps.home ?? process.env.HOME ?? homedir16();
34457
35845
  const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
34458
35846
  const sharedLoadConfig = createSharedLoadConfig(runtimePaths, deps.loadConfig ?? loadConfig);
34459
35847
  const runners = [
@@ -34606,8 +35994,8 @@ function resolveDoctorRuntimePaths(home, resolver) {
34606
35994
  return resolveRuntimePaths();
34607
35995
  }
34608
35996
  return {
34609
- configPath: join21(coreHomeDir(home), "config.json"),
34610
- statePath: join21(coreHomeDir(home), "state.json")
35997
+ configPath: join24(coreHomeDir(home), "config.json"),
35998
+ statePath: join24(coreHomeDir(home), "state.json")
34611
35999
  };
34612
36000
  }
34613
36001
  function depsUseExplicitRuntimeOverrides() {
@@ -34790,8 +36178,8 @@ var init_doctor2 = __esm(async () => {
34790
36178
  // src/cli.ts
34791
36179
  init_core_home();
34792
36180
  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";
36181
+ import { homedir as homedir17 } from "node:os";
36182
+ import { dirname as dirname14, join as join25, sep as sep2 } from "node:path";
34795
36183
  import { fileURLToPath as fileURLToPath7 } from "node:url";
34796
36184
 
34797
36185
  // src/runtime/migrate-core-home.ts
@@ -47288,7 +48676,7 @@ init_core_home();
47288
48676
  init_daemon_files();
47289
48677
  init_orchestration_ipc();
47290
48678
  import { homedir as homedir2 } from "node:os";
47291
- import { join as join5 } from "node:path";
48679
+ import { join as join6 } from "node:path";
47292
48680
  function resolveDefaultOrchestrationEndpoint(env = process.env, platform = process.platform) {
47293
48681
  const orchestrationSocket = coreEnv("ORCHESTRATION_SOCKET", env);
47294
48682
  if (typeof orchestrationSocket === "string" && orchestrationSocket.trim().length > 0) {
@@ -47296,7 +48684,7 @@ function resolveDefaultOrchestrationEndpoint(env = process.env, platform = proce
47296
48684
  }
47297
48685
  const home = requireHome(env);
47298
48686
  const configOverride = coreEnv("CONFIG", env);
47299
- const configPath = typeof configOverride === "string" && configOverride.trim().length > 0 ? configOverride.trim() : join5(coreHomeDir(home), "config.json");
48687
+ const configPath = typeof configOverride === "string" && configOverride.trim().length > 0 ? configOverride.trim() : join6(coreHomeDir(home), "config.json");
47300
48688
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
47301
48689
  return resolveOrchestrationEndpoint(runtimeDir, platform);
47302
48690
  }
@@ -48833,14 +50221,14 @@ function resolveTemplateChoice(answer, names) {
48833
50221
  init_plugin_home();
48834
50222
  import { spawn as spawn4 } from "node:child_process";
48835
50223
  import { readFile as readFile9 } from "node:fs/promises";
48836
- import { dirname as dirname8, join as join11 } from "node:path";
50224
+ import { dirname as dirname8, join as join12 } from "node:path";
48837
50225
  import { fileURLToPath as fileURLToPath3 } from "node:url";
48838
50226
 
48839
50227
  // src/plugins/package-manager.ts
48840
50228
  init_plugin_home();
48841
50229
  import { spawn as spawn3 } from "node:child_process";
48842
50230
  import { rm as rm4 } from "node:fs/promises";
48843
- import { join as join7 } from "node:path";
50231
+ import { join as join8 } from "node:path";
48844
50232
  function shellSpawnPlan(args) {
48845
50233
  const shell = process.platform === "win32";
48846
50234
  return { shell, args: shell ? args.map((arg) => `"${arg}"`) : args };
@@ -48888,7 +50276,7 @@ async function installPluginPackage(input) {
48888
50276
  const packageManager = input.packageManager ?? await detectPackageManager();
48889
50277
  await normalizePluginHomeManifest(input.pluginHome);
48890
50278
  if (packageManager === "bun") {
48891
- await rm4(join7(input.pluginHome, "bun.lock"), { force: true }).catch(() => {});
50279
+ await rm4(join8(input.pluginHome, "bun.lock"), { force: true }).catch(() => {});
48892
50280
  }
48893
50281
  const spec = input.version ? `${input.packageName}@${input.version}` : input.packageName;
48894
50282
  if (packageManager === "bun") {
@@ -49180,7 +50568,7 @@ async function runInherit(command, args) {
49180
50568
  async function readPackageName() {
49181
50569
  try {
49182
50570
  const here = dirname8(fileURLToPath3(import.meta.url));
49183
- for (const candidate of [join11(here, "..", "package.json"), join11(here, "..", "..", "package.json")]) {
50571
+ for (const candidate of [join12(here, "..", "package.json"), join12(here, "..", "..", "package.json")]) {
49184
50572
  try {
49185
50573
  const parsed = JSON.parse(await readFile9(candidate, "utf8"));
49186
50574
  if (typeof parsed.name === "string" && parsed.name.trim())
@@ -49835,7 +51223,7 @@ async function setChannelAccountEnabled(type, accountId, enabled, rawArgs, deps)
49835
51223
  init_core_home();
49836
51224
  init_plugin_home();
49837
51225
  import { readFile as readFile11 } from "node:fs/promises";
49838
- import { isAbsolute, join as join13, resolve } from "node:path";
51226
+ import { isAbsolute, join as join14, resolve } from "node:path";
49839
51227
  init_plugin_loader();
49840
51228
  init_validate_plugin();
49841
51229
  init_plugin_doctor();
@@ -49866,7 +51254,7 @@ function looksLikePath(spec) {
49866
51254
  }
49867
51255
  async function readDependencyEntries2(pluginHome) {
49868
51256
  try {
49869
- const raw = await readFile11(join13(pluginHome, "package.json"), "utf8");
51257
+ const raw = await readFile11(join14(pluginHome, "package.json"), "utf8");
49870
51258
  const parsed = JSON.parse(raw);
49871
51259
  const out = {};
49872
51260
  for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
@@ -49892,7 +51280,7 @@ async function resolveLocalPluginName(installSpec, pluginHome, namesBeforeInstal
49892
51280
  return name;
49893
51281
  }
49894
51282
  try {
49895
- const raw = await readFile11(join13(installSpec, "package.json"), "utf8");
51283
+ const raw = await readFile11(join14(installSpec, "package.json"), "utf8");
49896
51284
  const parsed = JSON.parse(raw);
49897
51285
  if (typeof parsed.name === "string" && parsed.name.trim())
49898
51286
  return parsed.name.trim();
@@ -51034,7 +52422,7 @@ async function createCliScheduledTaskService() {
51034
52422
  return new ScheduledTaskService(state, stateStore);
51035
52423
  }
51036
52424
  function resolveConfigPathForCurrentEnv() {
51037
- return coreEnv("CONFIG") ?? join22(coreHomeDir(requireHome2()), "config.json");
52425
+ return coreEnv("CONFIG") ?? join25(coreHomeDir(requireHome2()), "config.json");
51038
52426
  }
51039
52427
  function resolveDaemonPathsForCurrentConfig() {
51040
52428
  const configPath = resolveConfigPathForCurrentEnv();
@@ -51093,7 +52481,7 @@ async function defaultRun(options = {}) {
51093
52481
  const firstRunOnboarding = options.firstRunOnboarding ?? decodeFirstRunOnboarding(coreEnv("FIRST_RUN_ONBOARDING"));
51094
52482
  await runConsole2(runtimePaths, {
51095
52483
  buildApp: (paths) => buildApp2(paths, {
51096
- defaultLoggingLevel: resolveCliEntryPath2().includes(`${sep}src${sep}`) ? "debug" : "info",
52484
+ defaultLoggingLevel: resolveCliEntryPath2().includes(`${sep2}src${sep2}`) ? "debug" : "info",
51097
52485
  channel: channelRegistry
51098
52486
  }),
51099
52487
  beforeReady: firstRunOnboarding ? async (runtime) => {
@@ -51104,7 +52492,7 @@ async function defaultRun(options = {}) {
51104
52492
  daemonRuntime,
51105
52493
  ...firstLockCreator ? {
51106
52494
  consumerLockFactory: (runtime) => firstLockCreator.create({
51107
- lockFilePath: `${daemonPaths.runtimeDir}${sep}${firstLockCreator.channel.id}-consumer.lock.json`,
52495
+ lockFilePath: `${daemonPaths.runtimeDir}${sep2}${firstLockCreator.channel.id}-consumer.lock.json`,
51108
52496
  onDiagnostic: async (event, context) => {
51109
52497
  await runtime.logger.info(`${firstLockCreator.channel.id}.consumer_lock.${event}`, `${firstLockCreator.channel.id} consumer lock diagnostic`, context);
51110
52498
  }
@@ -51295,7 +52683,7 @@ async function defaultPromptSecret(message) {
51295
52683
  process.stdout.write(message);
51296
52684
  process.stdin.setRawMode(true);
51297
52685
  process.stdin.resume();
51298
- return await new Promise((resolve3, reject) => {
52686
+ return await new Promise((resolve4, reject) => {
51299
52687
  const chunks = [];
51300
52688
  let inEscape = false;
51301
52689
  const cleanup = () => {
@@ -51326,7 +52714,7 @@ async function defaultPromptSecret(message) {
51326
52714
  if (char === "\r" || char === `
51327
52715
  `) {
51328
52716
  cleanup();
51329
- resolve3(chunks.join(""));
52717
+ resolve4(chunks.join(""));
51330
52718
  return;
51331
52719
  }
51332
52720
  if (char === "" || char === "\b") {
@@ -51381,7 +52769,7 @@ function decodeFirstRunOnboarding(raw) {
51381
52769
  return null;
51382
52770
  }
51383
52771
  function requireHome2() {
51384
- const home = process.env.HOME ?? homedir15();
52772
+ const home = process.env.HOME ?? homedir17();
51385
52773
  if (!home) {
51386
52774
  throw new Error("Unable to resolve the current user home directory");
51387
52775
  }
@@ -51405,7 +52793,7 @@ function safeDaemonLogPaths() {
51405
52793
  const configPath = resolveConfigPathForCurrentEnv();
51406
52794
  const paths = resolveDaemonPathsForCurrentConfig();
51407
52795
  return {
51408
- appLog: join22(dirname14(configPath), "runtime", "app.log"),
52796
+ appLog: join25(dirname14(configPath), "runtime", "app.log"),
51409
52797
  stderrLog: paths.stderrLog
51410
52798
  };
51411
52799
  } catch {