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