weacpx 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -48,18 +48,386 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __promiseAll = (args) => Promise.all(args);
49
49
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
50
50
 
51
+ // src/util/private-file.ts
52
+ import { randomBytes } from "node:crypto";
53
+ import { chmod, mkdir, rename, unlink, writeFile } from "node:fs/promises";
54
+ import { basename, dirname, join } from "node:path";
55
+ async function writePrivateFileAtomic(path, content) {
56
+ const dir = dirname(path);
57
+ await mkdir(dir, { recursive: true });
58
+ const tmpPath = join(dir, `.${basename(path)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
59
+ try {
60
+ await writeFile(tmpPath, content, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
61
+ await chmodPrivate(tmpPath);
62
+ await rename(tmpPath, path);
63
+ await chmodPrivate(path);
64
+ } catch (error) {
65
+ await unlinkIfExists(tmpPath);
66
+ throw error;
67
+ }
68
+ }
69
+ async function chmodPrivate(path) {
70
+ if (process.platform === "win32")
71
+ return;
72
+ await chmod(path, PRIVATE_FILE_MODE).catch(() => {});
73
+ }
74
+ async function unlinkIfExists(path) {
75
+ await unlink(path).catch(() => {});
76
+ }
77
+ var PRIVATE_FILE_MODE = 384;
78
+ var init_private_file = () => {};
79
+
80
+ // src/commands/workspace-path.ts
81
+ import { access } from "node:fs/promises";
82
+ import { homedir } from "node:os";
83
+ import path from "node:path";
84
+ function normalizeWorkspacePath(input) {
85
+ const expanded = expandHome(input);
86
+ if (isWindowsLikePath(expanded)) {
87
+ return path.win32.normalize(expanded).replace(/\\/g, "/");
88
+ }
89
+ return path.posix.normalize(expanded.replace(/\\/g, "/"));
90
+ }
91
+ function basenameForWorkspacePath(input) {
92
+ const normalized = normalizeWorkspacePath(input);
93
+ if (ROOT_PATH_RE.test(normalized)) {
94
+ return normalized;
95
+ }
96
+ const base = path.posix.basename(normalized);
97
+ return base || normalized;
98
+ }
99
+ function sameWorkspacePath(left, right) {
100
+ const normalizedLeft = normalizeWorkspacePath(left);
101
+ const normalizedRight = normalizeWorkspacePath(right);
102
+ if (isWindowsLikePath(normalizedLeft) || isWindowsLikePath(normalizedRight)) {
103
+ return normalizedLeft.toLowerCase() === normalizedRight.toLowerCase();
104
+ }
105
+ return normalizedLeft === normalizedRight;
106
+ }
107
+ function expandHome(input) {
108
+ return input.startsWith("~") ? homedir() + input.slice(1) : input;
109
+ }
110
+ async function pathExists(filePath) {
111
+ try {
112
+ await access(filePath);
113
+ return true;
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+ function isWindowsLikePath(input) {
119
+ return WINDOWS_DRIVE_PATH_RE.test(input) || WINDOWS_UNC_PATH_RE.test(input);
120
+ }
121
+ var WINDOWS_DRIVE_PATH_RE, WINDOWS_UNC_PATH_RE, ROOT_PATH_RE;
122
+ var init_workspace_path = __esm(() => {
123
+ WINDOWS_DRIVE_PATH_RE = /^[a-zA-Z]:[\\/]/;
124
+ WINDOWS_UNC_PATH_RE = /^\\\\/;
125
+ ROOT_PATH_RE = /^(\/|[a-zA-Z]:\/?)$/;
126
+ });
127
+
128
+ // src/config/resolve-agent-command.ts
129
+ function resolveAgentCommand(driver, command) {
130
+ if (!command) {
131
+ return;
132
+ }
133
+ if (driver === "codex" && isLegacyCodexCommand(command)) {
134
+ return;
135
+ }
136
+ return command;
137
+ }
138
+ function isLegacyCodexCommand(command) {
139
+ const normalized = command.trim().replaceAll("\\", "/").toLowerCase();
140
+ 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");
141
+ }
142
+
143
+ // src/config/load-config.ts
144
+ import { readFile } from "node:fs/promises";
145
+ function isRecord(value) {
146
+ return typeof value === "object" && value !== null;
147
+ }
148
+ async function loadConfig(path2, options = {}) {
149
+ const raw = JSON.parse(await readFile(path2, "utf8"));
150
+ return parseConfig(raw, options);
151
+ }
152
+ function parseConfig(raw, options = {}) {
153
+ if (!isRecord(raw)) {
154
+ throw new Error("config must be a JSON object");
155
+ }
156
+ const transport = raw.transport;
157
+ if (!isRecord(transport)) {
158
+ throw new Error("transport must be an object");
159
+ }
160
+ if ("type" in transport && transport.type !== "acpx-cli" && transport.type !== "acpx-bridge") {
161
+ throw new Error("transport.type must be acpx-cli or acpx-bridge");
162
+ }
163
+ if ("sessionInitTimeoutMs" in transport && (typeof transport.sessionInitTimeoutMs !== "number" || !Number.isFinite(transport.sessionInitTimeoutMs) || transport.sessionInitTimeoutMs <= 0)) {
164
+ throw new Error("transport.sessionInitTimeoutMs must be a positive number");
165
+ }
166
+ if ("permissionMode" in transport && transport.permissionMode !== "approve-all" && transport.permissionMode !== "approve-reads" && transport.permissionMode !== "deny-all") {
167
+ throw new Error("transport.permissionMode must be approve-all, approve-reads, or deny-all");
168
+ }
169
+ if ("nonInteractivePermissions" in transport && transport.nonInteractivePermissions !== "deny" && transport.nonInteractivePermissions !== "fail") {
170
+ throw new Error("transport.nonInteractivePermissions must be deny or fail");
171
+ }
172
+ if (!isRecord(raw.agents)) {
173
+ throw new Error("agents must be an object");
174
+ }
175
+ if (!isRecord(raw.workspaces)) {
176
+ throw new Error("workspaces must be an object");
177
+ }
178
+ const logging = raw.logging;
179
+ const wechat = raw.wechat;
180
+ const orchestration = raw.orchestration;
181
+ if (logging !== undefined && !isRecord(logging)) {
182
+ throw new Error("logging must be an object");
183
+ }
184
+ if (wechat !== undefined && !isRecord(wechat)) {
185
+ throw new Error("wechat must be an object");
186
+ }
187
+ if (orchestration !== undefined && !isRecord(orchestration)) {
188
+ throw new Error("orchestration must be an object");
189
+ }
190
+ if (isRecord(logging) && "level" in logging && logging.level !== "error" && logging.level !== "info" && logging.level !== "debug") {
191
+ throw new Error("logging.level must be error, info, or debug");
192
+ }
193
+ for (const field of ["maxSizeBytes", "maxFiles", "retentionDays"]) {
194
+ if (isRecord(logging) && field in logging && (typeof logging[field] !== "number" || !Number.isFinite(logging[field]) || logging[field] <= 0)) {
195
+ throw new Error(`logging.${field} must be a positive number`);
196
+ }
197
+ }
198
+ if (isRecord(wechat) && "replyMode" in wechat && wechat.replyMode !== "stream" && wechat.replyMode !== "final" && wechat.replyMode !== "verbose") {
199
+ throw new Error("wechat.replyMode must be stream, final, or verbose");
200
+ }
201
+ for (const [name, agent] of Object.entries(raw.agents)) {
202
+ if (!isRecord(agent) || typeof agent.driver !== "string" || agent.driver.length === 0) {
203
+ throw new Error(`agent "${name}" must define a non-empty driver`);
204
+ }
205
+ if ("command" in agent && (typeof agent.command !== "string" || agent.command.length === 0)) {
206
+ throw new Error(`agent "${name}" command must be a non-empty string`);
207
+ }
208
+ }
209
+ for (const [name, workspace] of Object.entries(raw.workspaces)) {
210
+ if (!isRecord(workspace) || typeof workspace.cwd !== "string" || workspace.cwd.length === 0) {
211
+ throw new Error(`workspace "${name}" must define a non-empty cwd`);
212
+ }
213
+ if ("allowed_agents" in workspace && (!Array.isArray(workspace.allowed_agents) || workspace.allowed_agents.some((value) => typeof value !== "string"))) {
214
+ throw new Error(`workspace "${name}" allowed_agents must be an array of strings`);
215
+ }
216
+ }
217
+ const rawAgents = raw.agents;
218
+ const agents = {};
219
+ for (const [name, agent] of Object.entries(rawAgents)) {
220
+ const driver = agent.driver;
221
+ const command = typeof agent.command === "string" ? resolveAgentCommand(driver, agent.command) : undefined;
222
+ agents[name] = {
223
+ driver,
224
+ ...command ? { command } : {}
225
+ };
226
+ }
227
+ const rawWorkspaces = raw.workspaces;
228
+ const workspaces = {};
229
+ for (const [name, workspace] of Object.entries(rawWorkspaces)) {
230
+ workspaces[name] = {
231
+ cwd: normalizeWorkspacePath(workspace.cwd),
232
+ ...typeof workspace.description === "string" ? { description: workspace.description } : {}
233
+ };
234
+ }
235
+ const transportType = transport.type === "acpx-cli" || transport.type === "acpx-bridge" ? transport.type : "acpx-bridge";
236
+ const permissionMode = transport.permissionMode === "approve-all" || transport.permissionMode === "approve-reads" || transport.permissionMode === "deny-all" ? transport.permissionMode : DEFAULT_PERMISSION_MODE;
237
+ const nonInteractivePermissions = transport.nonInteractivePermissions === "deny" || transport.nonInteractivePermissions === "fail" ? transport.nonInteractivePermissions : DEFAULT_NON_INTERACTIVE_PERMISSIONS;
238
+ const loggingLevel = logging?.level;
239
+ const resolvedLoggingLevel = loggingLevel === "error" || loggingLevel === "info" || loggingLevel === "debug" ? loggingLevel : options.defaultLoggingLevel ?? DEFAULT_LOGGING_CONFIG.level;
240
+ const replyMode = wechat?.replyMode === "stream" || wechat?.replyMode === "final" || wechat?.replyMode === "verbose" ? wechat.replyMode : DEFAULT_WECHAT_REPLY_MODE;
241
+ const orchestrationConfig = parseOrchestrationConfig(orchestration);
242
+ return {
243
+ transport: {
244
+ ...typeof transport.command === "string" ? { command: transport.command } : {},
245
+ ...typeof transport.sessionInitTimeoutMs === "number" ? { sessionInitTimeoutMs: transport.sessionInitTimeoutMs } : {},
246
+ type: transportType,
247
+ permissionMode,
248
+ nonInteractivePermissions
249
+ },
250
+ logging: {
251
+ level: resolvedLoggingLevel,
252
+ maxSizeBytes: typeof logging?.maxSizeBytes === "number" ? logging.maxSizeBytes : DEFAULT_LOGGING_CONFIG.maxSizeBytes,
253
+ maxFiles: typeof logging?.maxFiles === "number" ? logging.maxFiles : DEFAULT_LOGGING_CONFIG.maxFiles,
254
+ retentionDays: typeof logging?.retentionDays === "number" ? logging.retentionDays : DEFAULT_LOGGING_CONFIG.retentionDays
255
+ },
256
+ wechat: {
257
+ replyMode
258
+ },
259
+ agents,
260
+ workspaces,
261
+ orchestration: orchestrationConfig
262
+ };
263
+ }
264
+ function parseOrchestrationConfig(raw) {
265
+ if (!isRecord(raw)) {
266
+ return {
267
+ ...DEFAULT_ORCHESTRATION_CONFIG
268
+ };
269
+ }
270
+ return {
271
+ maxPendingAgentRequestsPerCoordinator: typeof raw.maxPendingAgentRequestsPerCoordinator === "number" && Number.isFinite(raw.maxPendingAgentRequestsPerCoordinator) && raw.maxPendingAgentRequestsPerCoordinator > 0 ? raw.maxPendingAgentRequestsPerCoordinator : DEFAULT_ORCHESTRATION_CONFIG.maxPendingAgentRequestsPerCoordinator,
272
+ allowWorkerChainedRequests: raw.allowWorkerChainedRequests === true,
273
+ allowedAgentRequestTargets: Array.isArray(raw.allowedAgentRequestTargets) ? raw.allowedAgentRequestTargets.filter((value) => typeof value === "string") : [...DEFAULT_ORCHESTRATION_CONFIG.allowedAgentRequestTargets],
274
+ allowedAgentRequestRoles: Array.isArray(raw.allowedAgentRequestRoles) ? raw.allowedAgentRequestRoles.filter((value) => typeof value === "string") : [...DEFAULT_ORCHESTRATION_CONFIG.allowedAgentRequestRoles],
275
+ progressHeartbeatSeconds: typeof raw.progressHeartbeatSeconds === "number" && Number.isFinite(raw.progressHeartbeatSeconds) ? raw.progressHeartbeatSeconds : DEFAULT_ORCHESTRATION_CONFIG.progressHeartbeatSeconds
276
+ };
277
+ }
278
+ var DEFAULT_LOGGING_CONFIG, DEFAULT_PERMISSION_MODE = "approve-all", DEFAULT_NON_INTERACTIVE_PERMISSIONS = "deny", DEFAULT_WECHAT_REPLY_MODE = "verbose", DEFAULT_ORCHESTRATION_CONFIG;
279
+ var init_load_config = __esm(() => {
280
+ init_workspace_path();
281
+ DEFAULT_LOGGING_CONFIG = {
282
+ level: "info",
283
+ maxSizeBytes: 2 * 1024 * 1024,
284
+ maxFiles: 5,
285
+ retentionDays: 7
286
+ };
287
+ DEFAULT_ORCHESTRATION_CONFIG = {
288
+ maxPendingAgentRequestsPerCoordinator: 3,
289
+ allowWorkerChainedRequests: false,
290
+ allowedAgentRequestTargets: [],
291
+ allowedAgentRequestRoles: [],
292
+ progressHeartbeatSeconds: 300
293
+ };
294
+ });
295
+
296
+ // src/config/config-store.ts
297
+ class ConfigStore {
298
+ path;
299
+ constructor(path2) {
300
+ this.path = path2;
301
+ }
302
+ async load() {
303
+ return await loadConfig(this.path);
304
+ }
305
+ async save(config) {
306
+ await writePrivateFileAtomic(this.path, `${JSON.stringify(config, null, 2)}
307
+ `);
308
+ }
309
+ async upsertWorkspace(name, cwd, description) {
310
+ const config = await this.load();
311
+ const workspace = {
312
+ cwd,
313
+ ...description ? { description } : {}
314
+ };
315
+ config.workspaces[name] = workspace;
316
+ await this.save(config);
317
+ return config;
318
+ }
319
+ async removeWorkspace(name) {
320
+ const config = await this.load();
321
+ delete config.workspaces[name];
322
+ await this.save(config);
323
+ return config;
324
+ }
325
+ async upsertAgent(name, agent) {
326
+ const config = await this.load();
327
+ config.agents[name] = agent;
328
+ await this.save(config);
329
+ return config;
330
+ }
331
+ async removeAgent(name) {
332
+ const config = await this.load();
333
+ delete config.agents[name];
334
+ await this.save(config);
335
+ return config;
336
+ }
337
+ async updateTransport(transport) {
338
+ const config = await this.load();
339
+ config.transport = {
340
+ ...config.transport,
341
+ ...transport
342
+ };
343
+ await this.save(config);
344
+ return config;
345
+ }
346
+ async updateWechat(wechat) {
347
+ const config = await this.load();
348
+ config.wechat = {
349
+ ...config.wechat,
350
+ ...wechat
351
+ };
352
+ await this.save(config);
353
+ return config;
354
+ }
355
+ }
356
+ var init_config_store = __esm(() => {
357
+ init_private_file();
358
+ init_load_config();
359
+ });
360
+
361
+ // src/config/ensure-config.ts
362
+ import { readFile as readFile2 } from "node:fs/promises";
363
+ async function ensureConfigExists(path2) {
364
+ try {
365
+ await loadConfig(path2);
366
+ } catch (error) {
367
+ if (!isMissingFileError(error)) {
368
+ throw error;
369
+ }
370
+ const store = new ConfigStore(path2);
371
+ await store.save(await loadDefaultConfigTemplate());
372
+ }
373
+ }
374
+ async function loadDefaultConfigTemplate() {
375
+ const candidates = [
376
+ new URL("../../config.example.json", import.meta.url),
377
+ new URL("../config.example.json", import.meta.url)
378
+ ];
379
+ let raw;
380
+ let lastError;
381
+ for (const candidate of candidates) {
382
+ try {
383
+ raw = await readFile2(candidate, "utf8");
384
+ break;
385
+ } catch (error) {
386
+ if (!isMissingFileError(error)) {
387
+ throw error;
388
+ }
389
+ lastError = error;
390
+ }
391
+ }
392
+ if (!raw) {
393
+ throw lastError;
394
+ }
395
+ return normalizeDefaultConfigTemplate(JSON.parse(raw));
396
+ }
397
+ function normalizeDefaultConfigTemplate(raw) {
398
+ const template = parseConfig(raw);
399
+ return {
400
+ ...template,
401
+ agents: Object.fromEntries(Object.entries(template.agents).map(([name, agent]) => [
402
+ name,
403
+ {
404
+ driver: agent.driver,
405
+ ...resolveAgentCommand(agent.driver, agent.command) ? { command: resolveAgentCommand(agent.driver, agent.command) } : {}
406
+ }
407
+ ])),
408
+ workspaces: {}
409
+ };
410
+ }
411
+ function isMissingFileError(error) {
412
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
413
+ }
414
+ var init_ensure_config = __esm(() => {
415
+ init_config_store();
416
+ init_load_config();
417
+ });
418
+
51
419
  // src/daemon/daemon-status.ts
52
- import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
53
- import { dirname } from "node:path";
420
+ import { mkdir as mkdir2, readFile as readFile3, rm, writeFile as writeFile2 } from "node:fs/promises";
421
+ import { dirname as dirname2 } from "node:path";
54
422
 
55
423
  class DaemonStatusStore {
56
424
  path;
57
- constructor(path) {
58
- this.path = path;
425
+ constructor(path2) {
426
+ this.path = path2;
59
427
  }
60
428
  async load() {
61
429
  try {
62
- const content = await readFile(this.path, "utf8");
430
+ const content = await readFile3(this.path, "utf8");
63
431
  if (content.trim() === "") {
64
432
  return null;
65
433
  }
@@ -72,8 +440,8 @@ class DaemonStatusStore {
72
440
  }
73
441
  }
74
442
  async save(status) {
75
- await mkdir(dirname(this.path), { recursive: true });
76
- await writeFile(this.path, JSON.stringify(status, null, 2));
443
+ await mkdir2(dirname2(this.path), { recursive: true });
444
+ await writeFile2(this.path, JSON.stringify(status, null, 2));
77
445
  }
78
446
  async clear() {
79
447
  await rm(this.path, { force: true });
@@ -82,8 +450,8 @@ class DaemonStatusStore {
82
450
  var init_daemon_status = () => {};
83
451
 
84
452
  // src/daemon/daemon-controller.ts
85
- import { mkdir as mkdir2, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
86
- import { dirname as dirname2 } from "node:path";
453
+ import { mkdir as mkdir3, readFile as readFile4, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
454
+ import { dirname as dirname3 } from "node:path";
87
455
 
88
456
  class DaemonController {
89
457
  paths;
@@ -157,7 +525,7 @@ class DaemonController {
157
525
  }
158
526
  async loadPid() {
159
527
  try {
160
- const content = await readFile2(this.paths.pidFile, "utf8");
528
+ const content = await readFile4(this.paths.pidFile, "utf8");
161
529
  const pid = Number(content.trim());
162
530
  return Number.isFinite(pid) && pid > 0 ? pid : null;
163
531
  } catch (error) {
@@ -168,8 +536,8 @@ class DaemonController {
168
536
  }
169
537
  }
170
538
  async writePid(pid) {
171
- await mkdir2(dirname2(this.paths.pidFile), { recursive: true });
172
- await writeFile2(this.paths.pidFile, `${pid}
539
+ await mkdir3(dirname3(this.paths.pidFile), { recursive: true });
540
+ await writeFile3(this.paths.pidFile, `${pid}
173
541
  `);
174
542
  }
175
543
  async clearRuntimeFiles() {
@@ -211,7 +579,7 @@ var init_daemon_controller = __esm(() => {
211
579
 
212
580
  // src/process/terminate-process-tree.ts
213
581
  import { spawn } from "node:child_process";
214
- async function terminateProcessTree(pid, platform = process.platform, runCommand = defaultRunProcessCommand, killProcess = (targetPid, signal) => {
582
+ async function terminateProcessTree(pid, options = {}, platform = process.platform, runCommand = defaultRunProcessCommand, killProcess = (targetPid, signal) => {
215
583
  process.kill(targetPid, signal);
216
584
  }, isProcessRunning = defaultIsProcessRunning) {
217
585
  if (pid <= 0) {
@@ -223,7 +591,7 @@ async function terminateProcessTree(pid, platform = process.platform, runCommand
223
591
  } catch {}
224
592
  return;
225
593
  }
226
- const targetPid = pid > 0 ? -pid : pid;
594
+ const targetPid = options.detachedProcessGroup ? -pid : pid;
227
595
  try {
228
596
  killProcess(targetPid, "SIGTERM");
229
597
  } catch {
@@ -258,13 +626,13 @@ async function defaultRunProcessCommand(command, args) {
258
626
  var init_terminate_process_tree = () => {};
259
627
 
260
628
  // src/daemon/create-daemon-controller.ts
261
- import { mkdir as mkdir3, open } from "node:fs/promises";
629
+ import { mkdir as mkdir4, open } from "node:fs/promises";
262
630
  import { spawn as spawn2 } from "node:child_process";
263
631
  function createDaemonController(paths, options) {
264
632
  return new DaemonController(paths, {
265
633
  isProcessRunning: options.isProcessRunning ?? defaultIsProcessRunning2,
266
634
  spawnDetached: async () => {
267
- await mkdir3(paths.runtimeDir, { recursive: true });
635
+ await mkdir4(paths.runtimeDir, { recursive: true });
268
636
  const stdoutHandle = await open(paths.stdoutLog, "a");
269
637
  const stderrHandle = await open(paths.stderrLog, "a");
270
638
  try {
@@ -395,7 +763,7 @@ async function spawnWindowsHiddenProcess(request) {
395
763
  });
396
764
  }
397
765
  async function defaultTerminateProcess(pid) {
398
- await terminateProcessTree(pid);
766
+ await terminateProcessTree(pid, { detachedProcessGroup: true });
399
767
  }
400
768
  var init_create_daemon_controller = __esm(() => {
401
769
  init_daemon_controller();
@@ -404,7 +772,7 @@ var init_create_daemon_controller = __esm(() => {
404
772
 
405
773
  // src/orchestration/orchestration-ipc.ts
406
774
  import { createHash } from "node:crypto";
407
- import { join } from "node:path";
775
+ import { join as join2 } from "node:path";
408
776
  function resolveOrchestrationEndpoint(runtimeDir, platform = process.platform) {
409
777
  if (platform === "win32") {
410
778
  const suffix = createHash("sha256").update(runtimeDir).digest("hex").slice(0, 12);
@@ -415,13 +783,13 @@ function resolveOrchestrationEndpoint(runtimeDir, platform = process.platform) {
415
783
  }
416
784
  return {
417
785
  kind: "unix",
418
- path: join(runtimeDir, "orchestration.sock")
786
+ path: join2(runtimeDir, "orchestration.sock")
419
787
  };
420
788
  }
421
- function createOrchestrationEndpoint(path, platform = process.platform) {
789
+ function createOrchestrationEndpoint(path2, platform = process.platform) {
422
790
  return {
423
- kind: platform === "win32" || path.startsWith("\\\\.\\pipe\\") ? "named-pipe" : "unix",
424
- path
791
+ kind: platform === "win32" || path2.startsWith("\\\\.\\pipe\\") ? "named-pipe" : "unix",
792
+ path: path2
425
793
  };
426
794
  }
427
795
  function encodeOrchestrationRpcRequest(request) {
@@ -435,20 +803,20 @@ function encodeOrchestrationRpcResponse(response) {
435
803
  var init_orchestration_ipc = () => {};
436
804
 
437
805
  // src/daemon/daemon-files.ts
438
- import { dirname as dirname3, join as join2 } from "node:path";
806
+ import { dirname as dirname4, join as join3 } from "node:path";
439
807
  function resolveDaemonPaths(options) {
440
- const runtimeDir = options.runtimeDir ?? join2(options.home, ".weacpx", "runtime");
808
+ const runtimeDir = options.runtimeDir ?? join3(options.home, ".weacpx", "runtime");
441
809
  return {
442
810
  runtimeDir,
443
- pidFile: join2(runtimeDir, "daemon.pid"),
444
- statusFile: join2(runtimeDir, "status.json"),
445
- stdoutLog: join2(runtimeDir, "stdout.log"),
446
- stderrLog: join2(runtimeDir, "stderr.log"),
447
- appLog: join2(runtimeDir, "app.log")
811
+ pidFile: join3(runtimeDir, "daemon.pid"),
812
+ statusFile: join3(runtimeDir, "status.json"),
813
+ stdoutLog: join3(runtimeDir, "stdout.log"),
814
+ stderrLog: join3(runtimeDir, "stderr.log"),
815
+ appLog: join3(runtimeDir, "app.log")
448
816
  };
449
817
  }
450
818
  function resolveRuntimeDirFromConfigPath(configPath) {
451
- return join2(dirname3(configPath), "runtime");
819
+ return join3(dirname4(configPath), "runtime");
452
820
  }
453
821
  function resolveDaemonOrchestrationSocketPath(runtimeDir, platform = process.platform) {
454
822
  return resolveOrchestrationEndpoint(runtimeDir, platform).path;
@@ -3581,8 +3949,8 @@ var require_utils = __commonJS((exports, module) => {
3581
3949
  }
3582
3950
  return ind;
3583
3951
  }
3584
- function removeDotSegments(path) {
3585
- let input = path;
3952
+ function removeDotSegments(path2) {
3953
+ let input = path2;
3586
3954
  const output = [];
3587
3955
  let nextSlash = -1;
3588
3956
  let len = 0;
@@ -3772,8 +4140,8 @@ var require_schemes = __commonJS((exports, module) => {
3772
4140
  wsComponent.secure = undefined;
3773
4141
  }
3774
4142
  if (wsComponent.resourceName) {
3775
- const [path, query] = wsComponent.resourceName.split("?");
3776
- wsComponent.path = path && path !== "/" ? path : undefined;
4143
+ const [path2, query] = wsComponent.resourceName.split("?");
4144
+ wsComponent.path = path2 && path2 !== "/" ? path2 : undefined;
3777
4145
  wsComponent.query = query;
3778
4146
  wsComponent.resourceName = undefined;
3779
4147
  }
@@ -6931,12 +7299,12 @@ var require_dist = __commonJS((exports, module) => {
6931
7299
 
6932
7300
  // src/version.ts
6933
7301
  import fs from "node:fs";
6934
- import path from "node:path";
7302
+ import path2 from "node:path";
6935
7303
  import { fileURLToPath } from "node:url";
6936
7304
  function readVersion() {
6937
7305
  try {
6938
- const dir = path.dirname(fileURLToPath(import.meta.url));
6939
- const pkgPath = path.resolve(dir, "..", "..", "package.json");
7306
+ const dir = path2.dirname(fileURLToPath(import.meta.url));
7307
+ const pkgPath = path2.resolve(dir, "..", "..", "package.json");
6940
7308
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
6941
7309
  return pkg.version ?? "unknown";
6942
7310
  } catch {
@@ -7167,16 +7535,16 @@ var init_quota_errors = __esm(() => {
7167
7535
  });
7168
7536
 
7169
7537
  // src/weixin/monitor/consumer-lock.ts
7170
- import { mkdir as mkdir5, open as open2, readFile as readFile3, rm as rm4 } from "node:fs/promises";
7171
- import { dirname as dirname5, join as join3 } from "node:path";
7172
- import { homedir as homedir2 } from "node:os";
7538
+ import { mkdir as mkdir6, open as open2, readFile as readFile5, rm as rm4 } from "node:fs/promises";
7539
+ import { dirname as dirname6, join as join4 } from "node:path";
7540
+ import { homedir as homedir3 } from "node:os";
7173
7541
  function createWeixinConsumerLock(options = {}) {
7174
- const lockFilePath = options.lockFilePath ?? join3(homedir2(), ".weacpx", "runtime", "weixin-consumer.lock.json");
7542
+ const lockFilePath = options.lockFilePath ?? join4(homedir3(), ".weacpx", "runtime", "weixin-consumer.lock.json");
7175
7543
  const isProcessRunning = options.isProcessRunning ?? defaultIsProcessRunning3;
7176
7544
  const onDiagnostic = options.onDiagnostic;
7177
7545
  return {
7178
7546
  async acquire(meta2) {
7179
- await mkdir5(dirname5(lockFilePath), { recursive: true });
7547
+ await mkdir6(dirname6(lockFilePath), { recursive: true });
7180
7548
  while (true) {
7181
7549
  try {
7182
7550
  const handle = await open2(lockFilePath, "wx");
@@ -7247,9 +7615,9 @@ function createWeixinConsumerLock(options = {}) {
7247
7615
  }
7248
7616
  };
7249
7617
  }
7250
- async function loadLockMetadata(path2) {
7618
+ async function loadLockMetadata(path3) {
7251
7619
  try {
7252
- const raw = await readFile3(path2, "utf8");
7620
+ const raw = await readFile5(path3, "utf8");
7253
7621
  const parsed = JSON.parse(raw);
7254
7622
  if (!parsed || typeof parsed.pid !== "number" || !parsed.mode || !parsed.configPath || !parsed.statePath) {
7255
7623
  return null;
@@ -8308,15 +8676,15 @@ var require_main = __commonJS((exports, module) => {
8308
8676
 
8309
8677
  // src/weixin/storage/state-dir.ts
8310
8678
  import os from "node:os";
8311
- import path2 from "node:path";
8679
+ import path3 from "node:path";
8312
8680
  function resolveStateDir() {
8313
- return process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim() || path2.join(os.homedir(), ".openclaw");
8681
+ return process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim() || path3.join(os.homedir(), ".openclaw");
8314
8682
  }
8315
8683
  var init_state_dir = () => {};
8316
8684
 
8317
8685
  // src/weixin/auth/accounts.ts
8318
8686
  import fs2 from "node:fs";
8319
- import path3 from "node:path";
8687
+ import path4 from "node:path";
8320
8688
  function normalizeAccountId(raw) {
8321
8689
  return raw.trim().toLowerCase().replace(/[@.]/g, "-");
8322
8690
  }
@@ -8330,10 +8698,10 @@ function deriveRawAccountId(normalizedId) {
8330
8698
  return;
8331
8699
  }
8332
8700
  function resolveWeixinStateDir() {
8333
- return path3.join(resolveStateDir(), "openclaw-weixin");
8701
+ return path4.join(resolveStateDir(), "openclaw-weixin");
8334
8702
  }
8335
8703
  function resolveAccountIndexPath() {
8336
- return path3.join(resolveWeixinStateDir(), "accounts.json");
8704
+ return path4.join(resolveWeixinStateDir(), "accounts.json");
8337
8705
  }
8338
8706
  function listIndexedWeixinAccountIds() {
8339
8707
  const filePath = resolveAccountIndexPath();
@@ -8355,13 +8723,13 @@ function registerWeixinAccountId(accountId) {
8355
8723
  fs2.writeFileSync(resolveAccountIndexPath(), JSON.stringify([accountId], null, 2), "utf-8");
8356
8724
  }
8357
8725
  function resolveAccountsDir() {
8358
- return path3.join(resolveWeixinStateDir(), "accounts");
8726
+ return path4.join(resolveWeixinStateDir(), "accounts");
8359
8727
  }
8360
8728
  function resolveAccountPath(accountId) {
8361
- return path3.join(resolveAccountsDir(), `${accountId}.json`);
8729
+ return path4.join(resolveAccountsDir(), `${accountId}.json`);
8362
8730
  }
8363
8731
  function loadLegacyToken() {
8364
- const legacyPath = path3.join(resolveStateDir(), "credentials", "openclaw-weixin", "credentials.json");
8732
+ const legacyPath = path4.join(resolveStateDir(), "credentials", "openclaw-weixin", "credentials.json");
8365
8733
  try {
8366
8734
  if (!fs2.existsSync(legacyPath))
8367
8735
  return;
@@ -8431,7 +8799,7 @@ function resolveConfigPath() {
8431
8799
  const envPath = process.env.OPENCLAW_CONFIG?.trim();
8432
8800
  if (envPath)
8433
8801
  return envPath;
8434
- return path3.join(resolveStateDir(), "openclaw.json");
8802
+ return path4.join(resolveStateDir(), "openclaw.json");
8435
8803
  }
8436
8804
  function loadConfigRouteTag(accountId) {
8437
8805
  try {
@@ -8488,7 +8856,7 @@ var init_accounts = __esm(() => {
8488
8856
  // src/weixin/util/logger.ts
8489
8857
  import fs3 from "node:fs";
8490
8858
  import os2 from "node:os";
8491
- import path4 from "node:path";
8859
+ import path5 from "node:path";
8492
8860
  function resolveMinLevel() {
8493
8861
  const env = process.env.OPENCLAW_LOG_LEVEL?.toUpperCase();
8494
8862
  if (env && env in LEVEL_IDS)
@@ -8507,7 +8875,7 @@ function localDateKey(now) {
8507
8875
  }
8508
8876
  function resolveMainLogPath() {
8509
8877
  const dateKey = localDateKey(new Date);
8510
- return path4.join(MAIN_LOG_DIR, `openclaw-${dateKey}.log`);
8878
+ return path5.join(MAIN_LOG_DIR, `openclaw-${dateKey}.log`);
8511
8879
  }
8512
8880
  function buildLoggerName(accountId) {
8513
8881
  return accountId ? `${SUBSYSTEM}/${accountId}` : SUBSYSTEM;
@@ -8568,7 +8936,7 @@ function createLogger(accountId) {
8568
8936
  }
8569
8937
  var MAIN_LOG_DIR, SUBSYSTEM = "gateway/channels/openclaw-weixin", RUNTIME = "node", RUNTIME_VERSION, HOSTNAME, PARENT_NAMES, LEVEL_IDS, DEFAULT_LOG_LEVEL = "INFO", minLevelId, logDirEnsured = false, logger;
8570
8938
  var init_logger = __esm(() => {
8571
- MAIN_LOG_DIR = path4.join("/tmp", "openclaw");
8939
+ MAIN_LOG_DIR = path5.join("/tmp", "openclaw");
8572
8940
  RUNTIME_VERSION = process.versions.node;
8573
8941
  HOSTNAME = os2.hostname() || "unknown";
8574
8942
  PARENT_NAMES = ["openclaw"];
@@ -8602,6 +8970,49 @@ function redactToken(token, prefixLen = DEFAULT_TOKEN_PREFIX_LEN) {
8602
8970
  function redactBody(body, maxLen = DEFAULT_BODY_MAX_LEN) {
8603
8971
  if (!body)
8604
8972
  return "(empty)";
8973
+ const parsed = parseJson(body);
8974
+ if (parsed !== undefined) {
8975
+ return truncateForBody(JSON.stringify(redactJsonValue(parsed)), maxLen);
8976
+ }
8977
+ return truncateForBody(body, maxLen);
8978
+ }
8979
+ function parseJson(body) {
8980
+ try {
8981
+ return JSON.parse(body);
8982
+ } catch {
8983
+ return;
8984
+ }
8985
+ }
8986
+ function redactJsonValue(value, key) {
8987
+ const normalizedKey = key?.toLowerCase();
8988
+ if (normalizedKey && SECRET_KEYS.has(normalizedKey)) {
8989
+ return redactScalar(value);
8990
+ }
8991
+ if (normalizedKey && CONTENT_KEYS.has(normalizedKey)) {
8992
+ return redactContent(value);
8993
+ }
8994
+ if (Array.isArray(value)) {
8995
+ return value.map((item) => redactJsonValue(item));
8996
+ }
8997
+ if (value && typeof value === "object") {
8998
+ return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
8999
+ entryKey,
9000
+ redactJsonValue(entryValue, entryKey)
9001
+ ]));
9002
+ }
9003
+ return value;
9004
+ }
9005
+ function redactScalar(value) {
9006
+ const len = typeof value === "string" ? value.length : JSON.stringify(value)?.length ?? 0;
9007
+ return `<redacted len=${len}>`;
9008
+ }
9009
+ function redactContent(value) {
9010
+ if (typeof value === "string") {
9011
+ return `<redacted len=${value.length}>`;
9012
+ }
9013
+ return redactJsonValue(value);
9014
+ }
9015
+ function truncateForBody(body, maxLen) {
8605
9016
  if (body.length <= maxLen)
8606
9017
  return body;
8607
9018
  return `${body.slice(0, maxLen)}…(truncated, totalLen=${body.length})`;
@@ -8615,7 +9026,27 @@ function redactUrl(rawUrl) {
8615
9026
  return truncate(rawUrl, 80);
8616
9027
  }
8617
9028
  }
8618
- var DEFAULT_BODY_MAX_LEN = 200, DEFAULT_TOKEN_PREFIX_LEN = 6;
9029
+ var DEFAULT_BODY_MAX_LEN = 200, DEFAULT_TOKEN_PREFIX_LEN = 6, SECRET_KEYS, CONTENT_KEYS;
9030
+ var init_redact = __esm(() => {
9031
+ SECRET_KEYS = new Set([
9032
+ "authorization",
9033
+ "access_token",
9034
+ "aes_key",
9035
+ "aeskey",
9036
+ "context_token",
9037
+ "replycontexttoken",
9038
+ "secret",
9039
+ "signature",
9040
+ "token"
9041
+ ]);
9042
+ CONTENT_KEYS = new Set([
9043
+ "content",
9044
+ "message",
9045
+ "msg",
9046
+ "rawtext",
9047
+ "text"
9048
+ ]);
9049
+ });
8619
9050
 
8620
9051
  // src/weixin/messaging/send-errors.ts
8621
9052
  function formatMessage(input) {
@@ -8900,6 +9331,7 @@ var init_api = __esm(() => {
8900
9331
  init_version();
8901
9332
  init_accounts();
8902
9333
  init_logger();
9334
+ init_redact();
8903
9335
  init_send_errors();
8904
9336
  CHANNEL_VERSION = readVersion();
8905
9337
  });
@@ -9129,6 +9561,7 @@ var ACTIVE_LOGIN_TTL_MS, GET_QRCODE_TIMEOUT_MS = 5000, QR_LONG_POLL_TIMEOUT_MS =
9129
9561
  var init_login_qr = __esm(() => {
9130
9562
  init_api();
9131
9563
  init_logger();
9564
+ init_redact();
9132
9565
  ACTIVE_LOGIN_TTL_MS = 5 * 60000;
9133
9566
  activeLogins = new Map;
9134
9567
  });
@@ -9350,111 +9783,16 @@ var init_types = __esm(() => {
9350
9783
  };
9351
9784
  });
9352
9785
 
9353
- // src/weixin/cdn/aes-ecb.ts
9354
- import { createCipheriv, createDecipheriv } from "node:crypto";
9355
- function encryptAesEcb(plaintext, key) {
9356
- const cipher = createCipheriv("aes-128-ecb", key, null);
9357
- return Buffer.concat([cipher.update(plaintext), cipher.final()]);
9358
- }
9359
- function decryptAesEcb(ciphertext, key) {
9360
- const decipher = createDecipheriv("aes-128-ecb", key, null);
9361
- return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
9362
- }
9363
- function aesEcbPaddedSize(plaintextSize) {
9364
- return Math.ceil((plaintextSize + 1) / 16) * 16;
9365
- }
9366
- var init_aes_ecb = () => {};
9367
-
9368
- // src/weixin/cdn/cdn-url.ts
9369
- function buildCdnDownloadUrl(encryptedQueryParam, cdnBaseUrl) {
9370
- return `${cdnBaseUrl}/download?encrypted_query_param=${encodeURIComponent(encryptedQueryParam)}`;
9371
- }
9372
- function buildCdnUploadUrl(params) {
9373
- return `${params.cdnBaseUrl}/upload?encrypted_query_param=${encodeURIComponent(params.uploadParam)}&filekey=${encodeURIComponent(params.filekey)}`;
9374
- }
9375
-
9376
- // src/weixin/cdn/cdn-upload.ts
9377
- async function uploadBufferToCdn(params) {
9378
- const { buf, uploadFullUrl, uploadParam, filekey, cdnBaseUrl, label, aeskey } = params;
9379
- const ciphertext = encryptAesEcb(buf, aeskey);
9380
- const trimmedFull = uploadFullUrl?.trim();
9381
- let cdnUrl;
9382
- if (trimmedFull) {
9383
- cdnUrl = trimmedFull;
9384
- } else if (uploadParam) {
9385
- cdnUrl = buildCdnUploadUrl({ cdnBaseUrl, uploadParam, filekey });
9386
- } else {
9387
- throw new Error(`${label}: CDN upload URL missing (need upload_full_url or upload_param)`);
9388
- }
9389
- logger.debug(`${label}: CDN POST url=${redactUrl(cdnUrl)} ciphertextSize=${ciphertext.length}`);
9390
- let downloadParam;
9391
- let lastError;
9392
- for (let attempt = 1;attempt <= UPLOAD_MAX_RETRIES; attempt++) {
9393
- try {
9394
- const res = await fetch(cdnUrl, {
9395
- method: "POST",
9396
- headers: { "Content-Type": "application/octet-stream" },
9397
- body: new Uint8Array(ciphertext)
9398
- });
9399
- if (res.status >= 400 && res.status < 500) {
9400
- const errMsg = res.headers.get("x-error-message") ?? await res.text();
9401
- logger.error(`${label}: CDN client error attempt=${attempt} status=${res.status} errMsg=${errMsg}`);
9402
- throw new Error(`CDN upload client error ${res.status}: ${errMsg}`);
9403
- }
9404
- if (res.status !== 200) {
9405
- const errMsg = res.headers.get("x-error-message") ?? `status ${res.status}`;
9406
- logger.error(`${label}: CDN server error attempt=${attempt} status=${res.status} errMsg=${errMsg}`);
9407
- throw new Error(`CDN upload server error: ${errMsg}`);
9408
- }
9409
- downloadParam = res.headers.get("x-encrypted-param") ?? undefined;
9410
- if (!downloadParam) {
9411
- logger.error(`${label}: CDN response missing x-encrypted-param header attempt=${attempt}`);
9412
- throw new Error("CDN upload response missing x-encrypted-param header");
9413
- }
9414
- logger.debug(`${label}: CDN upload success attempt=${attempt}`);
9415
- break;
9416
- } catch (err) {
9417
- lastError = err;
9418
- if (err instanceof Error && err.message.includes("client error"))
9419
- throw err;
9420
- if (attempt < UPLOAD_MAX_RETRIES) {
9421
- logger.error(`${label}: attempt ${attempt} failed, retrying... err=${String(err)}`);
9422
- } else {
9423
- logger.error(`${label}: all ${UPLOAD_MAX_RETRIES} attempts failed err=${String(err)}`);
9424
- }
9425
- }
9426
- }
9427
- if (!downloadParam) {
9428
- throw lastError instanceof Error ? lastError : new Error(`CDN upload failed after ${UPLOAD_MAX_RETRIES} attempts`);
9429
- }
9430
- return { downloadParam };
9431
- }
9432
- var UPLOAD_MAX_RETRIES = 3;
9433
- var init_cdn_upload = __esm(() => {
9434
- init_aes_ecb();
9435
- init_logger();
9436
- });
9437
-
9438
9786
  // src/weixin/media/mime.ts
9439
- import path5 from "node:path";
9787
+ import path6 from "node:path";
9440
9788
  function getMimeFromFilename(filename) {
9441
- const ext = path5.extname(filename).toLowerCase();
9789
+ const ext = path6.extname(filename).toLowerCase();
9442
9790
  return EXTENSION_TO_MIME[ext] ?? "application/octet-stream";
9443
9791
  }
9444
9792
  function getExtensionFromMime(mimeType) {
9445
9793
  const ct = (mimeType.split(";")[0] ?? "").trim().toLowerCase();
9446
9794
  return MIME_TO_EXTENSION[ct] ?? ".bin";
9447
9795
  }
9448
- function getExtensionFromContentTypeOrUrl(contentType, url) {
9449
- if (contentType) {
9450
- const ext2 = getExtensionFromMime(contentType);
9451
- if (ext2 !== ".bin")
9452
- return ext2;
9453
- }
9454
- const ext = path5.extname(new URL(url).pathname).toLowerCase();
9455
- const knownExts = new Set(Object.keys(EXTENSION_TO_MIME));
9456
- return knownExts.has(ext) ? ext : ".bin";
9457
- }
9458
9796
  var EXTENSION_TO_MIME, MIME_TO_EXTENSION;
9459
9797
  var init_mime = __esm(() => {
9460
9798
  EXTENSION_TO_MIME = {
@@ -9509,111 +9847,28 @@ var init_mime = __esm(() => {
9509
9847
  };
9510
9848
  });
9511
9849
 
9512
- // src/weixin/util/random.ts
9513
- import crypto2 from "node:crypto";
9514
- function generateId(prefix) {
9515
- return `${prefix}:${Date.now()}-${crypto2.randomBytes(4).toString("hex")}`;
9516
- }
9517
- function tempFileName(prefix, ext) {
9518
- return `${prefix}-${Date.now()}-${crypto2.randomBytes(4).toString("hex")}${ext}`;
9519
- }
9520
- var init_random = () => {};
9521
-
9522
- // src/weixin/cdn/upload.ts
9523
- import crypto3 from "node:crypto";
9524
- import fs4 from "node:fs/promises";
9525
- import path6 from "node:path";
9526
- async function downloadRemoteImageToTemp(url, destDir) {
9527
- logger.debug(`downloadRemoteImageToTemp: fetching url=${url}`);
9528
- const res = await fetch(url);
9529
- if (!res.ok) {
9530
- const msg = `remote media download failed: ${res.status} ${res.statusText} url=${url}`;
9531
- logger.error(`downloadRemoteImageToTemp: ${msg}`);
9532
- throw new Error(msg);
9533
- }
9534
- const buf = Buffer.from(await res.arrayBuffer());
9535
- logger.debug(`downloadRemoteImageToTemp: downloaded ${buf.length} bytes`);
9536
- await fs4.mkdir(destDir, { recursive: true });
9537
- const ext = getExtensionFromContentTypeOrUrl(res.headers.get("content-type"), url);
9538
- const name = tempFileName("weixin-remote", ext);
9539
- const filePath = path6.join(destDir, name);
9540
- await fs4.writeFile(filePath, buf);
9541
- logger.debug(`downloadRemoteImageToTemp: saved to ${filePath} ext=${ext}`);
9542
- return filePath;
9850
+ // src/weixin/cdn/aes-ecb.ts
9851
+ import { createCipheriv, createDecipheriv } from "node:crypto";
9852
+ function encryptAesEcb(plaintext, key) {
9853
+ const cipher = createCipheriv("aes-128-ecb", key, null);
9854
+ return Buffer.concat([cipher.update(plaintext), cipher.final()]);
9543
9855
  }
9544
- async function uploadMediaToCdn(params) {
9545
- const { filePath, toUserId, opts, cdnBaseUrl, mediaType, label } = params;
9546
- const plaintext = await fs4.readFile(filePath);
9547
- const rawsize = plaintext.length;
9548
- const rawfilemd5 = crypto3.createHash("md5").update(plaintext).digest("hex");
9549
- const filesize = aesEcbPaddedSize(rawsize);
9550
- const filekey = crypto3.randomBytes(16).toString("hex");
9551
- const aeskey = crypto3.randomBytes(16);
9552
- logger.debug(`${label}: file=${filePath} rawsize=${rawsize} filesize=${filesize} md5=${rawfilemd5} filekey=${filekey}`);
9553
- const uploadUrlResp = await getUploadUrl({
9554
- ...opts,
9555
- filekey,
9556
- media_type: mediaType,
9557
- to_user_id: toUserId,
9558
- rawsize,
9559
- rawfilemd5,
9560
- filesize,
9561
- no_need_thumb: true,
9562
- aeskey: aeskey.toString("hex")
9563
- });
9564
- const uploadFullUrl = uploadUrlResp.upload_full_url?.trim();
9565
- const uploadParam = uploadUrlResp.upload_param;
9566
- if (!uploadFullUrl && !uploadParam) {
9567
- logger.error(`${label}: getUploadUrl returned no upload URL (need upload_full_url or upload_param), resp=${JSON.stringify(uploadUrlResp)}`);
9568
- throw new Error(`${label}: getUploadUrl returned no upload URL`);
9569
- }
9570
- const { downloadParam: downloadEncryptedQueryParam } = await uploadBufferToCdn({
9571
- buf: plaintext,
9572
- uploadFullUrl: uploadFullUrl || undefined,
9573
- uploadParam: uploadParam ?? undefined,
9574
- filekey,
9575
- cdnBaseUrl,
9576
- aeskey,
9577
- label: `${label}[orig filekey=${filekey}]`
9578
- });
9579
- return {
9580
- filekey,
9581
- downloadEncryptedQueryParam,
9582
- aeskey: aeskey.toString("hex"),
9583
- fileSize: rawsize,
9584
- fileSizeCiphertext: filesize
9585
- };
9856
+ function decryptAesEcb(ciphertext, key) {
9857
+ const decipher = createDecipheriv("aes-128-ecb", key, null);
9858
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
9586
9859
  }
9587
- async function uploadFileToWeixin(params) {
9588
- return uploadMediaToCdn({
9589
- ...params,
9590
- mediaType: UploadMediaType.IMAGE,
9591
- label: "uploadFileToWeixin"
9592
- });
9860
+ function aesEcbPaddedSize(plaintextSize) {
9861
+ return Math.ceil((plaintextSize + 1) / 16) * 16;
9593
9862
  }
9594
- async function uploadVideoToWeixin(params) {
9595
- return uploadMediaToCdn({
9596
- ...params,
9597
- mediaType: UploadMediaType.VIDEO,
9598
- label: "uploadVideoToWeixin"
9599
- });
9863
+ var init_aes_ecb = () => {};
9864
+
9865
+ // src/weixin/cdn/cdn-url.ts
9866
+ function buildCdnDownloadUrl(encryptedQueryParam, cdnBaseUrl) {
9867
+ return `${cdnBaseUrl}/download?encrypted_query_param=${encodeURIComponent(encryptedQueryParam)}`;
9600
9868
  }
9601
- async function uploadFileAttachmentToWeixin(params) {
9602
- return uploadMediaToCdn({
9603
- ...params,
9604
- mediaType: UploadMediaType.FILE,
9605
- label: "uploadFileAttachmentToWeixin"
9606
- });
9869
+ function buildCdnUploadUrl(params) {
9870
+ return `${params.cdnBaseUrl}/upload?encrypted_query_param=${encodeURIComponent(params.uploadParam)}&filekey=${encodeURIComponent(params.filekey)}`;
9607
9871
  }
9608
- var init_upload = __esm(() => {
9609
- init_api();
9610
- init_aes_ecb();
9611
- init_cdn_upload();
9612
- init_logger();
9613
- init_mime();
9614
- init_random();
9615
- init_types();
9616
- });
9617
9872
 
9618
9873
  // src/weixin/cdn/pic-decrypt.ts
9619
9874
  async function fetchCdnBytes(url, label) {
@@ -9830,6 +10085,13 @@ function buildFinalHeadsUp(input) {
9830
10085
  \uD83D\uDCC4 结果共 ${total} 段,已发 ${sentSoFar} 段。回复 /jx 续看后 ${remaining} 段。`;
9831
10086
  }
9832
10087
 
10088
+ // src/weixin/util/random.ts
10089
+ import crypto2 from "node:crypto";
10090
+ function generateId(prefix) {
10091
+ return `${prefix}:${Date.now()}-${crypto2.randomBytes(4).toString("hex")}`;
10092
+ }
10093
+ var init_random = () => {};
10094
+
9833
10095
  // src/weixin/messaging/inbound.ts
9834
10096
  function contextTokenKey(accountId, userId) {
9835
10097
  return `${accountId}:${userId}`;
@@ -10069,6 +10331,146 @@ var init_error_notice = __esm(() => {
10069
10331
  init_send();
10070
10332
  });
10071
10333
 
10334
+ // src/weixin/cdn/cdn-upload.ts
10335
+ async function uploadBufferToCdn(params) {
10336
+ const { buf, uploadFullUrl, uploadParam, filekey, cdnBaseUrl, label, aeskey } = params;
10337
+ const ciphertext = encryptAesEcb(buf, aeskey);
10338
+ const trimmedFull = uploadFullUrl?.trim();
10339
+ let cdnUrl;
10340
+ if (trimmedFull) {
10341
+ cdnUrl = trimmedFull;
10342
+ } else if (uploadParam) {
10343
+ cdnUrl = buildCdnUploadUrl({ cdnBaseUrl, uploadParam, filekey });
10344
+ } else {
10345
+ throw new Error(`${label}: CDN upload URL missing (need upload_full_url or upload_param)`);
10346
+ }
10347
+ logger.debug(`${label}: CDN POST url=${redactUrl(cdnUrl)} ciphertextSize=${ciphertext.length}`);
10348
+ let downloadParam;
10349
+ let lastError;
10350
+ for (let attempt = 1;attempt <= UPLOAD_MAX_RETRIES; attempt++) {
10351
+ try {
10352
+ const res = await fetch(cdnUrl, {
10353
+ method: "POST",
10354
+ headers: { "Content-Type": "application/octet-stream" },
10355
+ body: new Uint8Array(ciphertext)
10356
+ });
10357
+ if (res.status >= 400 && res.status < 500) {
10358
+ const errMsg = res.headers.get("x-error-message") ?? await res.text();
10359
+ logger.error(`${label}: CDN client error attempt=${attempt} status=${res.status} errMsg=${errMsg}`);
10360
+ throw new Error(`CDN upload client error ${res.status}: ${errMsg}`);
10361
+ }
10362
+ if (res.status !== 200) {
10363
+ const errMsg = res.headers.get("x-error-message") ?? `status ${res.status}`;
10364
+ logger.error(`${label}: CDN server error attempt=${attempt} status=${res.status} errMsg=${errMsg}`);
10365
+ throw new Error(`CDN upload server error: ${errMsg}`);
10366
+ }
10367
+ downloadParam = res.headers.get("x-encrypted-param") ?? undefined;
10368
+ if (!downloadParam) {
10369
+ logger.error(`${label}: CDN response missing x-encrypted-param header attempt=${attempt}`);
10370
+ throw new Error("CDN upload response missing x-encrypted-param header");
10371
+ }
10372
+ logger.debug(`${label}: CDN upload success attempt=${attempt}`);
10373
+ break;
10374
+ } catch (err) {
10375
+ lastError = err;
10376
+ if (err instanceof Error && err.message.includes("client error"))
10377
+ throw err;
10378
+ if (attempt < UPLOAD_MAX_RETRIES) {
10379
+ logger.error(`${label}: attempt ${attempt} failed, retrying... err=${String(err)}`);
10380
+ } else {
10381
+ logger.error(`${label}: all ${UPLOAD_MAX_RETRIES} attempts failed err=${String(err)}`);
10382
+ }
10383
+ }
10384
+ }
10385
+ if (!downloadParam) {
10386
+ throw lastError instanceof Error ? lastError : new Error(`CDN upload failed after ${UPLOAD_MAX_RETRIES} attempts`);
10387
+ }
10388
+ return { downloadParam };
10389
+ }
10390
+ var UPLOAD_MAX_RETRIES = 3;
10391
+ var init_cdn_upload = __esm(() => {
10392
+ init_aes_ecb();
10393
+ init_logger();
10394
+ init_redact();
10395
+ });
10396
+
10397
+ // src/weixin/cdn/upload.ts
10398
+ import crypto3 from "node:crypto";
10399
+ import fs4 from "node:fs/promises";
10400
+ async function uploadMediaToCdn(params) {
10401
+ const { filePath, toUserId, opts, cdnBaseUrl, mediaType, label } = params;
10402
+ const plaintext = await fs4.readFile(filePath);
10403
+ const rawsize = plaintext.length;
10404
+ const rawfilemd5 = crypto3.createHash("md5").update(plaintext).digest("hex");
10405
+ const filesize = aesEcbPaddedSize(rawsize);
10406
+ const filekey = crypto3.randomBytes(16).toString("hex");
10407
+ const aeskey = crypto3.randomBytes(16);
10408
+ logger.debug(`${label}: file=${filePath} rawsize=${rawsize} filesize=${filesize} md5=${rawfilemd5} filekey=${filekey}`);
10409
+ const uploadUrlResp = await getUploadUrl({
10410
+ ...opts,
10411
+ filekey,
10412
+ media_type: mediaType,
10413
+ to_user_id: toUserId,
10414
+ rawsize,
10415
+ rawfilemd5,
10416
+ filesize,
10417
+ no_need_thumb: true,
10418
+ aeskey: aeskey.toString("hex")
10419
+ });
10420
+ const uploadFullUrl = uploadUrlResp.upload_full_url?.trim();
10421
+ const uploadParam = uploadUrlResp.upload_param;
10422
+ if (!uploadFullUrl && !uploadParam) {
10423
+ logger.error(`${label}: getUploadUrl returned no upload URL (need upload_full_url or upload_param), resp=${JSON.stringify(uploadUrlResp)}`);
10424
+ throw new Error(`${label}: getUploadUrl returned no upload URL`);
10425
+ }
10426
+ const { downloadParam: downloadEncryptedQueryParam } = await uploadBufferToCdn({
10427
+ buf: plaintext,
10428
+ uploadFullUrl: uploadFullUrl || undefined,
10429
+ uploadParam: uploadParam ?? undefined,
10430
+ filekey,
10431
+ cdnBaseUrl,
10432
+ aeskey,
10433
+ label: `${label}[orig filekey=${filekey}]`
10434
+ });
10435
+ return {
10436
+ filekey,
10437
+ downloadEncryptedQueryParam,
10438
+ aeskey: aeskey.toString("hex"),
10439
+ fileSize: rawsize,
10440
+ fileSizeCiphertext: filesize
10441
+ };
10442
+ }
10443
+ async function uploadFileToWeixin(params) {
10444
+ return uploadMediaToCdn({
10445
+ ...params,
10446
+ mediaType: UploadMediaType.IMAGE,
10447
+ label: "uploadFileToWeixin"
10448
+ });
10449
+ }
10450
+ async function uploadVideoToWeixin(params) {
10451
+ return uploadMediaToCdn({
10452
+ ...params,
10453
+ mediaType: UploadMediaType.VIDEO,
10454
+ label: "uploadVideoToWeixin"
10455
+ });
10456
+ }
10457
+ async function uploadFileAttachmentToWeixin(params) {
10458
+ return uploadMediaToCdn({
10459
+ ...params,
10460
+ mediaType: UploadMediaType.FILE,
10461
+ label: "uploadFileAttachmentToWeixin"
10462
+ });
10463
+ }
10464
+ var init_upload = __esm(() => {
10465
+ init_api();
10466
+ init_aes_ecb();
10467
+ init_cdn_upload();
10468
+ init_logger();
10469
+ init_mime();
10470
+ init_random();
10471
+ init_types();
10472
+ });
10473
+
10072
10474
  // src/weixin/messaging/send-media.ts
10073
10475
  import path7 from "node:path";
10074
10476
  async function sendWeixinMediaFile(params) {
@@ -10387,6 +10789,35 @@ function hardCutByCodepoint(s, maxBytes) {
10387
10789
  function resolveMediaTempDir(customRoot) {
10388
10790
  return customRoot ?? path9.join(tmpdir(), "weacpx", "media");
10389
10791
  }
10792
+ async function resolveSafeOutboundMediaPath(mediaUrl, mediaTempDir) {
10793
+ if (mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://")) {
10794
+ return null;
10795
+ }
10796
+ const candidate = path9.isAbsolute(mediaUrl) ? mediaUrl : path9.resolve(mediaUrl);
10797
+ const allowedRoots = [mediaTempDir, process.cwd()];
10798
+ const realCandidate = await realpathOrNull(candidate);
10799
+ if (!realCandidate) {
10800
+ return null;
10801
+ }
10802
+ for (const root of allowedRoots) {
10803
+ const realRoot = await realpathOrNull(root);
10804
+ if (realRoot && isPathInside(realCandidate, realRoot)) {
10805
+ return realCandidate;
10806
+ }
10807
+ }
10808
+ return null;
10809
+ }
10810
+ async function realpathOrNull(filePath) {
10811
+ try {
10812
+ return await fs6.realpath(filePath);
10813
+ } catch {
10814
+ return null;
10815
+ }
10816
+ }
10817
+ function isPathInside(candidate, root) {
10818
+ const relative = path9.relative(root, candidate);
10819
+ return relative === "" || !relative.startsWith("..") && !path9.isAbsolute(relative);
10820
+ }
10390
10821
  function createSaveMediaBuffer(mediaTempDir) {
10391
10822
  return async function saveMediaBuffer(buffer, contentType, subdir, _maxBytes, originalFilename) {
10392
10823
  const dir = path9.join(resolveMediaTempDir(mediaTempDir), subdir ?? "");
@@ -10573,12 +11004,11 @@ async function handleWeixinMessageTurn(full, deps) {
10573
11004
  onReplySegment: sendReplySegment
10574
11005
  });
10575
11006
  if (turn.media) {
10576
- let filePath;
10577
11007
  const mediaUrl = turn.media.url;
10578
- if (mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://")) {
10579
- filePath = await downloadRemoteImageToTemp(mediaUrl, path9.join(resolveMediaTempDir(deps.mediaTempDir), "outbound"));
10580
- } else {
10581
- filePath = path9.isAbsolute(mediaUrl) ? mediaUrl : path9.resolve(mediaUrl);
11008
+ const filePath = await resolveSafeOutboundMediaPath(mediaUrl, resolveMediaTempDir(deps.mediaTempDir));
11009
+ if (!filePath) {
11010
+ deps.errLog(`outbound media rejected: url=${mediaUrl}`);
11011
+ return;
10582
11012
  }
10583
11013
  const reservedMedia = deps.reserveFinal ? deps.reserveFinal(to) : true;
10584
11014
  if (!reservedMedia) {
@@ -10685,7 +11115,6 @@ var MAX_FINAL_CHUNK_BYTES = 1800, hasDownloadableMedia = (media) => media?.encry
10685
11115
  var init_handle_weixin_message_turn = __esm(() => {
10686
11116
  init_api();
10687
11117
  init_types();
10688
- init_upload();
10689
11118
  init_media_download();
10690
11119
  init_mime();
10691
11120
  init_inbound();
@@ -11071,8 +11500,8 @@ var init_weixin_sdk = __esm(() => {
11071
11500
  });
11072
11501
 
11073
11502
  // src/logging/app-logger.ts
11074
- import { appendFile, mkdir as mkdir6, readdir, rename, rm as rm5, stat } from "node:fs/promises";
11075
- import { basename, dirname as dirname6, join as join4 } from "node:path";
11503
+ import { appendFile, mkdir as mkdir7, readdir, rename as rename2, rm as rm5, stat } from "node:fs/promises";
11504
+ import { basename as basename2, dirname as dirname7, join as join5 } from "node:path";
11076
11505
  function createNoopAppLogger() {
11077
11506
  return {
11078
11507
  debug: async () => {},
@@ -11112,7 +11541,7 @@ function createAppLogger(options) {
11112
11541
  return;
11113
11542
  }
11114
11543
  const line = formatLogLine(now(), level, event, message, context);
11115
- await mkdir6(dirname6(options.filePath), { recursive: true });
11544
+ await mkdir7(dirname7(options.filePath), { recursive: true });
11116
11545
  await rotateIfNeeded(options.filePath, Buffer.byteLength(line), options.maxSizeBytes, options.maxFiles);
11117
11546
  await appendFile(options.filePath, line, "utf8");
11118
11547
  }
@@ -11122,7 +11551,7 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
11122
11551
  try {
11123
11552
  currentSize = (await stat(filePath)).size;
11124
11553
  } catch (error2) {
11125
- if (!isMissingFileError(error2)) {
11554
+ if (!isMissingFileError2(error2)) {
11126
11555
  throw error2;
11127
11556
  }
11128
11557
  }
@@ -11140,24 +11569,24 @@ async function rotateIfNeeded(filePath, incomingSize, maxSizeBytes, maxFiles) {
11140
11569
  for (let index = maxFiles - 1;index >= 1; index -= 1) {
11141
11570
  const source = `${filePath}.${index}`;
11142
11571
  try {
11143
- await rename(source, `${filePath}.${index + 1}`);
11572
+ await rename2(source, `${filePath}.${index + 1}`);
11144
11573
  } catch (error2) {
11145
- if (!isMissingFileError(error2)) {
11574
+ if (!isMissingFileError2(error2)) {
11146
11575
  throw error2;
11147
11576
  }
11148
11577
  }
11149
11578
  }
11150
- await rename(filePath, `${filePath}.1`);
11579
+ await rename2(filePath, `${filePath}.1`);
11151
11580
  }
11152
11581
  async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
11153
- const parentDir = dirname6(filePath);
11154
- const prefix = `${basename(filePath)}.`;
11582
+ const parentDir = dirname7(filePath);
11583
+ const prefix = `${basename2(filePath)}.`;
11155
11584
  const cutoff = now().getTime() - retentionDays * 24 * 60 * 60 * 1000;
11156
11585
  let files = [];
11157
11586
  try {
11158
11587
  files = await readdir(parentDir);
11159
11588
  } catch (error2) {
11160
- if (isMissingFileError(error2)) {
11589
+ if (isMissingFileError2(error2)) {
11161
11590
  return;
11162
11591
  }
11163
11592
  throw error2;
@@ -11166,7 +11595,7 @@ async function cleanupExpiredRotatedLogs(filePath, retentionDays, now) {
11166
11595
  if (!file.startsWith(prefix) || !/^\d+$/.test(file.slice(prefix.length))) {
11167
11596
  continue;
11168
11597
  }
11169
- const candidate = join4(parentDir, file);
11598
+ const candidate = join5(parentDir, file);
11170
11599
  const details = await stat(candidate);
11171
11600
  if (details.mtime.getTime() < cutoff) {
11172
11601
  await rm5(candidate, { force: true });
@@ -11188,7 +11617,7 @@ function formatValue(value) {
11188
11617
  }
11189
11618
  return JSON.stringify(value);
11190
11619
  }
11191
- function isMissingFileError(error2) {
11620
+ function isMissingFileError2(error2) {
11192
11621
  return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
11193
11622
  }
11194
11623
  var LEVEL_ORDER;
@@ -11201,16 +11630,16 @@ var init_app_logger = __esm(() => {
11201
11630
  });
11202
11631
 
11203
11632
  // src/transport/acpx-session-index.ts
11204
- import { readFile as readFile4 } from "node:fs/promises";
11205
- import { homedir as homedir3 } from "node:os";
11633
+ import { readFile as readFile6 } from "node:fs/promises";
11634
+ import { homedir as homedir4 } from "node:os";
11206
11635
  import { resolve } from "node:path";
11207
11636
  async function resolveSessionAgentCommandFromIndex(session) {
11208
- const home = process.env.HOME ?? homedir3();
11637
+ const home = process.env.HOME ?? homedir4();
11209
11638
  if (!home) {
11210
11639
  return;
11211
11640
  }
11212
11641
  try {
11213
- const raw = await readFile4(resolve(home, ".acpx", "sessions", "index.json"), "utf8");
11642
+ const raw = await readFile6(resolve(home, ".acpx", "sessions", "index.json"), "utf8");
11214
11643
  const parsed = JSON.parse(raw);
11215
11644
  const targetCwd = resolve(session.cwd);
11216
11645
  const match = parsed.entries?.find((entry) => entry.name === session.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
@@ -13222,54 +13651,6 @@ var init_agent_handler = __esm(() => {
13222
13651
  };
13223
13652
  });
13224
13653
 
13225
- // src/commands/workspace-path.ts
13226
- import { access } from "node:fs/promises";
13227
- import { homedir as homedir4 } from "node:os";
13228
- import path11 from "node:path";
13229
- function normalizeWorkspacePath(input) {
13230
- const expanded = expandHome(input);
13231
- if (isWindowsLikePath(expanded)) {
13232
- return path11.win32.normalize(expanded).replace(/\\/g, "/");
13233
- }
13234
- return path11.posix.normalize(expanded.replace(/\\/g, "/"));
13235
- }
13236
- function basenameForWorkspacePath(input) {
13237
- const normalized = normalizeWorkspacePath(input);
13238
- if (ROOT_PATH_RE.test(normalized)) {
13239
- return normalized;
13240
- }
13241
- const base = path11.posix.basename(normalized);
13242
- return base || normalized;
13243
- }
13244
- function sameWorkspacePath(left, right) {
13245
- const normalizedLeft = normalizeWorkspacePath(left);
13246
- const normalizedRight = normalizeWorkspacePath(right);
13247
- if (isWindowsLikePath(normalizedLeft) || isWindowsLikePath(normalizedRight)) {
13248
- return normalizedLeft.toLowerCase() === normalizedRight.toLowerCase();
13249
- }
13250
- return normalizedLeft === normalizedRight;
13251
- }
13252
- function expandHome(input) {
13253
- return input.startsWith("~") ? homedir4() + input.slice(1) : input;
13254
- }
13255
- async function pathExists(filePath) {
13256
- try {
13257
- await access(filePath);
13258
- return true;
13259
- } catch {
13260
- return false;
13261
- }
13262
- }
13263
- function isWindowsLikePath(input) {
13264
- return WINDOWS_DRIVE_PATH_RE.test(input) || WINDOWS_UNC_PATH_RE.test(input);
13265
- }
13266
- var WINDOWS_DRIVE_PATH_RE, WINDOWS_UNC_PATH_RE, ROOT_PATH_RE;
13267
- var init_workspace_path = __esm(() => {
13268
- WINDOWS_DRIVE_PATH_RE = /^[a-zA-Z]:[\\/]/;
13269
- WINDOWS_UNC_PATH_RE = /^\\\\/;
13270
- ROOT_PATH_RE = /^(\/|[a-zA-Z]:\/?)$/;
13271
- });
13272
-
13273
13654
  // src/commands/handlers/workspace-handler.ts
13274
13655
  function handleWorkspaces(context) {
13275
13656
  return { text: context.config ? renderWorkspaces(context.config) : "No config loaded." };
@@ -13667,9 +14048,9 @@ var init_session_recovery_handler = __esm(() => {
13667
14048
  // src/recovery/auto-install-optional-dep.ts
13668
14049
  import { spawn as spawn3 } from "node:child_process";
13669
14050
  import { createWriteStream } from "node:fs";
13670
- import { mkdir as mkdir7 } from "node:fs/promises";
14051
+ import { mkdir as mkdir8 } from "node:fs/promises";
13671
14052
  import { homedir as homedir5 } from "node:os";
13672
- import { join as join5 } from "node:path";
14053
+ import { join as join6 } from "node:path";
13673
14054
  async function autoInstallOptionalDep(pkg, parentPackages, options = {}) {
13674
14055
  const runCli = options.runCli ?? defaultRunCli;
13675
14056
  const openLog = options.openLog ?? defaultLogSink;
@@ -13784,13 +14165,13 @@ ${err.message}`, reason: "spawn" });
13784
14165
  });
13785
14166
  });
13786
14167
  }, defaultLogSink = async () => {
13787
- const dir = join5(homedir5(), ".weacpx", "logs");
13788
- await mkdir7(dir, { recursive: true });
14168
+ const dir = join6(homedir5(), ".weacpx", "logs");
14169
+ await mkdir8(dir, { recursive: true });
13789
14170
  const timestamp = new Date().toISOString().replace(/[:.]/g, "").replace(/-/g, "");
13790
- const path12 = join5(dir, `auto-install-${timestamp}.log`);
13791
- const stream = createWriteStream(path12, { flags: "a" });
14171
+ const path11 = join6(dir, `auto-install-${timestamp}.log`);
14172
+ const stream = createWriteStream(path11, { flags: "a" });
13792
14173
  return {
13793
- path: path12,
14174
+ path: path11,
13794
14175
  append: async (chunk) => {
13795
14176
  await new Promise((resolve2, reject) => stream.write(chunk, (err) => err ? reject(err) : resolve2()));
13796
14177
  },
@@ -13810,9 +14191,10 @@ var init_auto_install_optional_dep = __esm(() => {
13810
14191
 
13811
14192
  // src/recovery/discover-parent-package-paths.ts
13812
14193
  import { spawn as spawn4 } from "node:child_process";
14194
+ import { createRequire as createRequire2 } from "node:module";
13813
14195
  import { access as access2 } from "node:fs/promises";
13814
14196
  import { homedir as homedir6 } from "node:os";
13815
- import { dirname as dirname7, join as join6 } from "node:path";
14197
+ import { dirname as dirname8, join as join7 } from "node:path";
13816
14198
  function deriveParentPackageName(platformPackage) {
13817
14199
  return platformPackage.replace(/-(?:linux|darwin|win32|windows|freebsd|openbsd|sunos|aix)(?:-(?:x64|arm64|ia32|arm|ppc64|s390x))?(?:-(?:baseline|musl|gnu|gnueabihf|musleabihf|msvc))?$/, "");
13818
14200
  }
@@ -13825,7 +14207,7 @@ async function discoverParentPackagePaths(platformPackage, seedPath, deps = {})
13825
14207
  const queryRoot = deps.queryPackageManagerRoot ?? defaultQueryPackageManagerRoot;
13826
14208
  const parentName = deriveParentPackageName(platformPackage);
13827
14209
  const rawCandidates = [];
13828
- const bunGlobalRoot = env.BUN_INSTALL ? join6(env.BUN_INSTALL, "install", "global", "node_modules") : join6(home, ".bun", "install", "global", "node_modules");
14210
+ const bunGlobalRoot = env.BUN_INSTALL ? join7(env.BUN_INSTALL, "install", "global", "node_modules") : join7(home, ".bun", "install", "global", "node_modules");
13829
14211
  const [npmRoot, pnpmRoot, yarnRoot] = await Promise.all([
13830
14212
  queryRoot("npm"),
13831
14213
  queryRoot("pnpm"),
@@ -13848,20 +14230,20 @@ async function discoverParentPackagePaths(platformPackage, seedPath, deps = {})
13848
14230
  if (resolved)
13849
14231
  rawCandidates.push({ path: resolved, manager: classify(resolved) });
13850
14232
  }
13851
- rawCandidates.push({ path: join6(bunGlobalRoot, parentName), manager: "bun" });
14233
+ rawCandidates.push({ path: join7(bunGlobalRoot, parentName), manager: "bun" });
13852
14234
  if (npmRoot)
13853
- rawCandidates.push({ path: join6(npmRoot, parentName), manager: "npm" });
14235
+ rawCandidates.push({ path: join7(npmRoot, parentName), manager: "npm" });
13854
14236
  if (pnpmRoot)
13855
- rawCandidates.push({ path: join6(pnpmRoot, parentName), manager: "pnpm" });
14237
+ rawCandidates.push({ path: join7(pnpmRoot, parentName), manager: "pnpm" });
13856
14238
  if (yarnRoot)
13857
- rawCandidates.push({ path: join6(yarnRoot, parentName), manager: "yarn" });
14239
+ rawCandidates.push({ path: join7(yarnRoot, parentName), manager: "yarn" });
13858
14240
  const seen = new Set;
13859
14241
  const verified = [];
13860
14242
  for (const candidate of rawCandidates) {
13861
14243
  if (seen.has(candidate.path))
13862
14244
  continue;
13863
14245
  seen.add(candidate.path);
13864
- if (await fsExists(join6(candidate.path, "package.json"))) {
14246
+ if (await fsExists(join7(candidate.path, "package.json"))) {
13865
14247
  verified.push(candidate);
13866
14248
  }
13867
14249
  }
@@ -13872,9 +14254,9 @@ function isUnder(child, parent) {
13872
14254
  const p = parent.replace(/[\\/]+$/, "");
13873
14255
  return c === p || c.startsWith(p + "/") || c.startsWith(p + "\\");
13874
14256
  }
13875
- async function defaultFsExists(path12) {
14257
+ async function defaultFsExists(path11) {
13876
14258
  try {
13877
- await access2(path12);
14259
+ await access2(path11);
13878
14260
  return true;
13879
14261
  } catch {
13880
14262
  return false;
@@ -13882,10 +14264,10 @@ async function defaultFsExists(path12) {
13882
14264
  }
13883
14265
  function defaultResolveFromCwd(name, cwd) {
13884
14266
  try {
13885
- const pkgJson = __require.resolve(`${name}/package.json`, {
13886
- paths: [cwd, ...__require.resolve.paths(name) ?? []]
14267
+ const pkgJson = require2.resolve(`${name}/package.json`, {
14268
+ paths: [cwd, ...require2.resolve.paths(name) ?? []]
13887
14269
  });
13888
- return dirname7(pkgJson);
14270
+ return dirname8(pkgJson);
13889
14271
  } catch {
13890
14272
  return null;
13891
14273
  }
@@ -13932,11 +14314,14 @@ async function defaultQueryPackageManagerRoot(tool) {
13932
14314
  const trimmed = stdout2.trim().split(/\r?\n/).pop()?.trim() ?? "";
13933
14315
  if (!trimmed)
13934
14316
  return done(null);
13935
- done(spec.postfix ? join6(trimmed, spec.postfix) : trimmed);
14317
+ done(spec.postfix ? join7(trimmed, spec.postfix) : trimmed);
13936
14318
  });
13937
14319
  });
13938
14320
  }
13939
- var init_discover_parent_package_paths = () => {};
14321
+ var require2;
14322
+ var init_discover_parent_package_paths = __esm(() => {
14323
+ require2 = createRequire2(import.meta.url);
14324
+ });
13940
14325
 
13941
14326
  // src/commands/translate-acpx-note.ts
13942
14327
  function translateAcpxNote(raw) {
@@ -14415,284 +14800,9 @@ var init_command_router = __esm(() => {
14415
14800
  init_session_reset_handler();
14416
14801
  });
14417
14802
 
14418
- // src/config/resolve-agent-command.ts
14419
- function resolveAgentCommand(driver, command) {
14420
- if (!command) {
14421
- return;
14422
- }
14423
- if (driver === "codex" && isLegacyCodexCommand(command)) {
14424
- return;
14425
- }
14426
- return command;
14427
- }
14428
- function isLegacyCodexCommand(command) {
14429
- const normalized = command.trim().replaceAll("\\", "/").toLowerCase();
14430
- 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");
14431
- }
14432
-
14433
- // src/config/load-config.ts
14434
- import { readFile as readFile5 } from "node:fs/promises";
14435
- function isRecord(value) {
14436
- return typeof value === "object" && value !== null;
14437
- }
14438
- async function loadConfig(path12, options = {}) {
14439
- const raw = JSON.parse(await readFile5(path12, "utf8"));
14440
- return parseConfig(raw, options);
14441
- }
14442
- function parseConfig(raw, options = {}) {
14443
- if (!isRecord(raw)) {
14444
- throw new Error("config must be a JSON object");
14445
- }
14446
- const transport = raw.transport;
14447
- if (!isRecord(transport)) {
14448
- throw new Error("transport must be an object");
14449
- }
14450
- if ("type" in transport && transport.type !== "acpx-cli" && transport.type !== "acpx-bridge") {
14451
- throw new Error("transport.type must be acpx-cli or acpx-bridge");
14452
- }
14453
- if ("sessionInitTimeoutMs" in transport && (typeof transport.sessionInitTimeoutMs !== "number" || !Number.isFinite(transport.sessionInitTimeoutMs) || transport.sessionInitTimeoutMs <= 0)) {
14454
- throw new Error("transport.sessionInitTimeoutMs must be a positive number");
14455
- }
14456
- if ("permissionMode" in transport && transport.permissionMode !== "approve-all" && transport.permissionMode !== "approve-reads" && transport.permissionMode !== "deny-all") {
14457
- throw new Error("transport.permissionMode must be approve-all, approve-reads, or deny-all");
14458
- }
14459
- if ("nonInteractivePermissions" in transport && transport.nonInteractivePermissions !== "deny" && transport.nonInteractivePermissions !== "fail") {
14460
- throw new Error("transport.nonInteractivePermissions must be deny or fail");
14461
- }
14462
- if (!isRecord(raw.agents)) {
14463
- throw new Error("agents must be an object");
14464
- }
14465
- if (!isRecord(raw.workspaces)) {
14466
- throw new Error("workspaces must be an object");
14467
- }
14468
- const logging = raw.logging;
14469
- const wechat = raw.wechat;
14470
- const orchestration = raw.orchestration;
14471
- if (logging !== undefined && !isRecord(logging)) {
14472
- throw new Error("logging must be an object");
14473
- }
14474
- if (wechat !== undefined && !isRecord(wechat)) {
14475
- throw new Error("wechat must be an object");
14476
- }
14477
- if (orchestration !== undefined && !isRecord(orchestration)) {
14478
- throw new Error("orchestration must be an object");
14479
- }
14480
- if (isRecord(logging) && "level" in logging && logging.level !== "error" && logging.level !== "info" && logging.level !== "debug") {
14481
- throw new Error("logging.level must be error, info, or debug");
14482
- }
14483
- for (const field of ["maxSizeBytes", "maxFiles", "retentionDays"]) {
14484
- if (isRecord(logging) && field in logging && (typeof logging[field] !== "number" || !Number.isFinite(logging[field]) || logging[field] <= 0)) {
14485
- throw new Error(`logging.${field} must be a positive number`);
14486
- }
14487
- }
14488
- if (isRecord(wechat) && "replyMode" in wechat && wechat.replyMode !== "stream" && wechat.replyMode !== "final" && wechat.replyMode !== "verbose") {
14489
- throw new Error("wechat.replyMode must be stream, final, or verbose");
14490
- }
14491
- for (const [name, agent] of Object.entries(raw.agents)) {
14492
- if (!isRecord(agent) || typeof agent.driver !== "string" || agent.driver.length === 0) {
14493
- throw new Error(`agent "${name}" must define a non-empty driver`);
14494
- }
14495
- if ("command" in agent && (typeof agent.command !== "string" || agent.command.length === 0)) {
14496
- throw new Error(`agent "${name}" command must be a non-empty string`);
14497
- }
14498
- }
14499
- for (const [name, workspace] of Object.entries(raw.workspaces)) {
14500
- if (!isRecord(workspace) || typeof workspace.cwd !== "string" || workspace.cwd.length === 0) {
14501
- throw new Error(`workspace "${name}" must define a non-empty cwd`);
14502
- }
14503
- if ("allowed_agents" in workspace && (!Array.isArray(workspace.allowed_agents) || workspace.allowed_agents.some((value) => typeof value !== "string"))) {
14504
- throw new Error(`workspace "${name}" allowed_agents must be an array of strings`);
14505
- }
14506
- }
14507
- const rawAgents = raw.agents;
14508
- const agents = {};
14509
- for (const [name, agent] of Object.entries(rawAgents)) {
14510
- const driver = agent.driver;
14511
- const command = typeof agent.command === "string" ? resolveAgentCommand(driver, agent.command) : undefined;
14512
- agents[name] = {
14513
- driver,
14514
- ...command ? { command } : {}
14515
- };
14516
- }
14517
- const rawWorkspaces = raw.workspaces;
14518
- const workspaces = {};
14519
- for (const [name, workspace] of Object.entries(rawWorkspaces)) {
14520
- workspaces[name] = {
14521
- cwd: normalizeWorkspacePath(workspace.cwd),
14522
- ...typeof workspace.description === "string" ? { description: workspace.description } : {}
14523
- };
14524
- }
14525
- const transportType = transport.type === "acpx-cli" || transport.type === "acpx-bridge" ? transport.type : "acpx-bridge";
14526
- const permissionMode = transport.permissionMode === "approve-all" || transport.permissionMode === "approve-reads" || transport.permissionMode === "deny-all" ? transport.permissionMode : DEFAULT_PERMISSION_MODE;
14527
- const nonInteractivePermissions = transport.nonInteractivePermissions === "deny" || transport.nonInteractivePermissions === "fail" ? transport.nonInteractivePermissions : DEFAULT_NON_INTERACTIVE_PERMISSIONS;
14528
- const loggingLevel = logging?.level;
14529
- const resolvedLoggingLevel = loggingLevel === "error" || loggingLevel === "info" || loggingLevel === "debug" ? loggingLevel : options.defaultLoggingLevel ?? DEFAULT_LOGGING_CONFIG.level;
14530
- const replyMode = wechat?.replyMode === "stream" || wechat?.replyMode === "final" || wechat?.replyMode === "verbose" ? wechat.replyMode : DEFAULT_WECHAT_REPLY_MODE;
14531
- const orchestrationConfig = parseOrchestrationConfig(orchestration);
14532
- return {
14533
- transport: {
14534
- ...typeof transport.command === "string" ? { command: transport.command } : {},
14535
- ...typeof transport.sessionInitTimeoutMs === "number" ? { sessionInitTimeoutMs: transport.sessionInitTimeoutMs } : {},
14536
- type: transportType,
14537
- permissionMode,
14538
- nonInteractivePermissions
14539
- },
14540
- logging: {
14541
- level: resolvedLoggingLevel,
14542
- maxSizeBytes: typeof logging?.maxSizeBytes === "number" ? logging.maxSizeBytes : DEFAULT_LOGGING_CONFIG.maxSizeBytes,
14543
- maxFiles: typeof logging?.maxFiles === "number" ? logging.maxFiles : DEFAULT_LOGGING_CONFIG.maxFiles,
14544
- retentionDays: typeof logging?.retentionDays === "number" ? logging.retentionDays : DEFAULT_LOGGING_CONFIG.retentionDays
14545
- },
14546
- wechat: {
14547
- replyMode
14548
- },
14549
- agents,
14550
- workspaces,
14551
- orchestration: orchestrationConfig
14552
- };
14553
- }
14554
- function parseOrchestrationConfig(raw) {
14555
- if (!isRecord(raw)) {
14556
- return {
14557
- ...DEFAULT_ORCHESTRATION_CONFIG
14558
- };
14559
- }
14560
- return {
14561
- maxPendingAgentRequestsPerCoordinator: typeof raw.maxPendingAgentRequestsPerCoordinator === "number" && Number.isFinite(raw.maxPendingAgentRequestsPerCoordinator) && raw.maxPendingAgentRequestsPerCoordinator > 0 ? raw.maxPendingAgentRequestsPerCoordinator : DEFAULT_ORCHESTRATION_CONFIG.maxPendingAgentRequestsPerCoordinator,
14562
- allowWorkerChainedRequests: raw.allowWorkerChainedRequests === true,
14563
- allowedAgentRequestTargets: Array.isArray(raw.allowedAgentRequestTargets) ? raw.allowedAgentRequestTargets.filter((value) => typeof value === "string") : [...DEFAULT_ORCHESTRATION_CONFIG.allowedAgentRequestTargets],
14564
- allowedAgentRequestRoles: Array.isArray(raw.allowedAgentRequestRoles) ? raw.allowedAgentRequestRoles.filter((value) => typeof value === "string") : [...DEFAULT_ORCHESTRATION_CONFIG.allowedAgentRequestRoles],
14565
- progressHeartbeatSeconds: typeof raw.progressHeartbeatSeconds === "number" && Number.isFinite(raw.progressHeartbeatSeconds) ? raw.progressHeartbeatSeconds : DEFAULT_ORCHESTRATION_CONFIG.progressHeartbeatSeconds
14566
- };
14567
- }
14568
- var DEFAULT_LOGGING_CONFIG, DEFAULT_PERMISSION_MODE = "approve-all", DEFAULT_NON_INTERACTIVE_PERMISSIONS = "deny", DEFAULT_WECHAT_REPLY_MODE = "verbose", DEFAULT_ORCHESTRATION_CONFIG;
14569
- var init_load_config = __esm(() => {
14570
- init_workspace_path();
14571
- DEFAULT_LOGGING_CONFIG = {
14572
- level: "info",
14573
- maxSizeBytes: 2 * 1024 * 1024,
14574
- maxFiles: 5,
14575
- retentionDays: 7
14576
- };
14577
- DEFAULT_ORCHESTRATION_CONFIG = {
14578
- maxPendingAgentRequestsPerCoordinator: 3,
14579
- allowWorkerChainedRequests: false,
14580
- allowedAgentRequestTargets: [],
14581
- allowedAgentRequestRoles: [],
14582
- progressHeartbeatSeconds: 300
14583
- };
14584
- });
14585
-
14586
- // src/config/config-store.ts
14587
- import { mkdir as mkdir8, writeFile as writeFile5 } from "node:fs/promises";
14588
- import { dirname as dirname8 } from "node:path";
14589
-
14590
- class ConfigStore {
14591
- path;
14592
- constructor(path12) {
14593
- this.path = path12;
14594
- }
14595
- async load() {
14596
- return await loadConfig(this.path);
14597
- }
14598
- async save(config2) {
14599
- await mkdir8(dirname8(this.path), { recursive: true });
14600
- await writeFile5(this.path, `${JSON.stringify(config2, null, 2)}
14601
- `, "utf8");
14602
- }
14603
- async upsertWorkspace(name, cwd, description) {
14604
- const config2 = await this.load();
14605
- const workspace = {
14606
- cwd,
14607
- ...description ? { description } : {}
14608
- };
14609
- config2.workspaces[name] = workspace;
14610
- await this.save(config2);
14611
- return config2;
14612
- }
14613
- async removeWorkspace(name) {
14614
- const config2 = await this.load();
14615
- delete config2.workspaces[name];
14616
- await this.save(config2);
14617
- return config2;
14618
- }
14619
- async upsertAgent(name, agent) {
14620
- const config2 = await this.load();
14621
- config2.agents[name] = agent;
14622
- await this.save(config2);
14623
- return config2;
14624
- }
14625
- async removeAgent(name) {
14626
- const config2 = await this.load();
14627
- delete config2.agents[name];
14628
- await this.save(config2);
14629
- return config2;
14630
- }
14631
- async updateTransport(transport) {
14632
- const config2 = await this.load();
14633
- config2.transport = {
14634
- ...config2.transport,
14635
- ...transport
14636
- };
14637
- await this.save(config2);
14638
- return config2;
14639
- }
14640
- async updateWechat(wechat) {
14641
- const config2 = await this.load();
14642
- config2.wechat = {
14643
- ...config2.wechat,
14644
- ...wechat
14645
- };
14646
- await this.save(config2);
14647
- return config2;
14648
- }
14649
- }
14650
- var init_config_store = __esm(() => {
14651
- init_load_config();
14652
- });
14653
-
14654
- // src/config/ensure-config.ts
14655
- import { readFile as readFile6 } from "node:fs/promises";
14656
- async function ensureConfigExists(path12) {
14657
- try {
14658
- await loadConfig(path12);
14659
- } catch (error2) {
14660
- if (!isMissingFileError2(error2)) {
14661
- throw error2;
14662
- }
14663
- const store = new ConfigStore(path12);
14664
- await store.save(await loadDefaultConfigTemplate());
14665
- }
14666
- }
14667
- async function loadDefaultConfigTemplate() {
14668
- const templatePath = new URL("../../config.example.json", import.meta.url);
14669
- return normalizeDefaultConfigTemplate(JSON.parse(await readFile6(templatePath, "utf8")));
14670
- }
14671
- function normalizeDefaultConfigTemplate(raw) {
14672
- const template = parseConfig(raw);
14673
- return {
14674
- ...template,
14675
- agents: Object.fromEntries(Object.entries(template.agents).map(([name, agent]) => [
14676
- name,
14677
- {
14678
- driver: agent.driver,
14679
- ...resolveAgentCommand(agent.driver, agent.command) ? { command: resolveAgentCommand(agent.driver, agent.command) } : {}
14680
- }
14681
- ])),
14682
- workspaces: {}
14683
- };
14684
- }
14685
- function isMissingFileError2(error2) {
14686
- return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
14687
- }
14688
- var init_ensure_config = __esm(() => {
14689
- init_config_store();
14690
- init_load_config();
14691
- });
14692
-
14693
14803
  // src/config/resolve-acpx-command.ts
14694
14804
  import { readFileSync } from "node:fs";
14695
- import { createRequire as createRequire2 } from "node:module";
14805
+ import { createRequire as createRequire3 } from "node:module";
14696
14806
  import { posix, win32 } from "node:path";
14697
14807
  function resolveAcpxCommand(options = {}) {
14698
14808
  return resolveAcpxCommandMetadata(options).command;
@@ -14706,8 +14816,8 @@ function resolveAcpxCommandMetadata(options = {}) {
14706
14816
  };
14707
14817
  }
14708
14818
  const platform = options.platform ?? process.platform;
14709
- const resolvePackageJson = options.resolvePackageJson ?? ((id) => require2.resolve(id));
14710
- const readPackageJson = options.readPackageJson ?? ((path12) => JSON.parse(readFileSync(path12, "utf8")));
14819
+ const resolvePackageJson = options.resolvePackageJson ?? ((id) => require3.resolve(id));
14820
+ const readPackageJson = options.readPackageJson ?? ((path11) => JSON.parse(readFileSync(path11, "utf8")));
14711
14821
  try {
14712
14822
  const packageJsonPath = resolvePackageJson("acpx/package.json");
14713
14823
  const pkg = readPackageJson(packageJsonPath);
@@ -14728,9 +14838,9 @@ function resolveAcpxCommandMetadata(options = {}) {
14728
14838
  explanation: "transport.command is unset and no bundled acpx was found, so PATH is the fallback."
14729
14839
  };
14730
14840
  }
14731
- var require2;
14841
+ var require3;
14732
14842
  var init_resolve_acpx_command = __esm(() => {
14733
- require2 = createRequire2(import.meta.url);
14843
+ require3 = createRequire3(import.meta.url);
14734
14844
  });
14735
14845
 
14736
14846
  // src/console-agent.ts
@@ -14979,8 +15089,8 @@ class OrchestrationServer {
14979
15089
  if (this.endpoint.kind !== "unix") {
14980
15090
  return;
14981
15091
  }
14982
- const removeFile = this.deps.removeFile ?? (async (path12) => {
14983
- await rm6(path12, { force: true });
15092
+ const removeFile = this.deps.removeFile ?? (async (path11) => {
15093
+ await rm6(path11, { force: true });
14984
15094
  });
14985
15095
  await removeFile(this.endpoint.path);
14986
15096
  }
@@ -15113,9 +15223,9 @@ function requireTaskQuestions(params, key) {
15113
15223
  };
15114
15224
  });
15115
15225
  }
15116
- async function canConnectToEndpoint(path12) {
15226
+ async function canConnectToEndpoint(path11) {
15117
15227
  return await new Promise((resolve2) => {
15118
- const socket = createConnection2(path12);
15228
+ const socket = createConnection2(path11);
15119
15229
  let settled = false;
15120
15230
  const finish = (result) => {
15121
15231
  if (settled) {
@@ -15136,7 +15246,7 @@ async function canConnectToEndpoint(path12) {
15136
15246
  });
15137
15247
  });
15138
15248
  }
15139
- async function listen(server, path12) {
15249
+ async function listen(server, path11) {
15140
15250
  await new Promise((resolve2, reject) => {
15141
15251
  const onError = (error2) => {
15142
15252
  server.off("listening", onListening);
@@ -15148,7 +15258,7 @@ async function listen(server, path12) {
15148
15258
  };
15149
15259
  server.once("error", onError);
15150
15260
  server.once("listening", onListening);
15151
- server.listen(path12);
15261
+ server.listen(path11);
15152
15262
  });
15153
15263
  }
15154
15264
  function isServerNotRunningError(error2) {
@@ -17748,8 +17858,7 @@ function createEmptyState() {
17748
17858
  var init_types2 = () => {};
17749
17859
 
17750
17860
  // src/state/state-store.ts
17751
- import { mkdir as mkdir9, readFile as readFile7, writeFile as writeFile6 } from "node:fs/promises";
17752
- import { dirname as dirname9 } from "node:path";
17861
+ import { readFile as readFile7 } from "node:fs/promises";
17753
17862
  function isRecord2(value) {
17754
17863
  return typeof value === "object" && value !== null && !Array.isArray(value);
17755
17864
  }
@@ -17842,69 +17951,69 @@ function isHumanQuestionPackageRecord(value) {
17842
17951
  const messages = value.messages;
17843
17952
  return isString(value.packageId) && isString(value.coordinatorSession) && (value.status === "active" || value.status === "closed") && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.closedAt) && Array.isArray(initialTaskIds) && initialTaskIds.every(isString) && Array.isArray(openTaskIds) && openTaskIds.every(isString) && Array.isArray(resolvedTaskIds) && resolvedTaskIds.every(isString) && Array.isArray(messages) && messages.every(isHumanQuestionPackageMessageRecord) && isOptionalString(value.awaitingReplyMessageId);
17844
17953
  }
17845
- function parseOrchestrationState(raw, path12) {
17954
+ function parseOrchestrationState(raw, path11) {
17846
17955
  if (raw === undefined) {
17847
17956
  return createEmptyOrchestrationState();
17848
17957
  }
17849
17958
  if (!isRecord2(raw)) {
17850
- throw new Error(`state file "${path12}" must contain an object field "orchestration"`);
17959
+ throw new Error(`state file "${path11}" must contain an object field "orchestration"`);
17851
17960
  }
17852
17961
  const tasks = raw.tasks;
17853
17962
  if (tasks !== undefined && !isRecord2(tasks)) {
17854
- throw new Error(`state file "${path12}" must contain an object field "orchestration.tasks"`);
17963
+ throw new Error(`state file "${path11}" must contain an object field "orchestration.tasks"`);
17855
17964
  }
17856
17965
  const workerBindings = raw.workerBindings;
17857
17966
  if (workerBindings !== undefined && !isRecord2(workerBindings)) {
17858
- throw new Error(`state file "${path12}" must contain an object field "orchestration.workerBindings"`);
17967
+ throw new Error(`state file "${path11}" must contain an object field "orchestration.workerBindings"`);
17859
17968
  }
17860
17969
  const groups = raw.groups;
17861
17970
  if (groups !== undefined && !isRecord2(groups)) {
17862
- throw new Error(`state file "${path12}" must contain an object field "orchestration.groups"`);
17971
+ throw new Error(`state file "${path11}" must contain an object field "orchestration.groups"`);
17863
17972
  }
17864
17973
  const humanQuestionPackages = raw.humanQuestionPackages;
17865
17974
  if (humanQuestionPackages !== undefined && !isRecord2(humanQuestionPackages)) {
17866
- throw new Error(`state file "${path12}" must contain an object field "orchestration.humanQuestionPackages"`);
17975
+ throw new Error(`state file "${path11}" must contain an object field "orchestration.humanQuestionPackages"`);
17867
17976
  }
17868
17977
  const coordinatorQuestionState = raw.coordinatorQuestionState;
17869
17978
  if (coordinatorQuestionState !== undefined && !isRecord2(coordinatorQuestionState)) {
17870
- throw new Error(`state file "${path12}" must contain an object field "orchestration.coordinatorQuestionState"`);
17979
+ throw new Error(`state file "${path11}" must contain an object field "orchestration.coordinatorQuestionState"`);
17871
17980
  }
17872
17981
  const coordinatorRoutes = raw.coordinatorRoutes;
17873
17982
  if (coordinatorRoutes !== undefined && !isRecord2(coordinatorRoutes)) {
17874
- throw new Error(`state file "${path12}" must contain an object field "orchestration.coordinatorRoutes"`);
17983
+ throw new Error(`state file "${path11}" must contain an object field "orchestration.coordinatorRoutes"`);
17875
17984
  }
17876
17985
  const parsedTasks = {};
17877
17986
  for (const [taskId, task] of Object.entries(tasks ?? {})) {
17878
17987
  if (!isTaskRecord(task)) {
17879
- throw new Error(`state file "${path12}" contains an invalid orchestration task at "${taskId}"`);
17988
+ throw new Error(`state file "${path11}" contains an invalid orchestration task at "${taskId}"`);
17880
17989
  }
17881
17990
  parsedTasks[taskId] = task;
17882
17991
  }
17883
17992
  const parsedWorkerBindings = {};
17884
17993
  for (const [workerSession, binding] of Object.entries(workerBindings ?? {})) {
17885
17994
  if (!isWorkerBindingRecord(binding)) {
17886
- throw new Error(`state file "${path12}" contains an invalid orchestration worker binding at "${workerSession}"`);
17995
+ throw new Error(`state file "${path11}" contains an invalid orchestration worker binding at "${workerSession}"`);
17887
17996
  }
17888
17997
  parsedWorkerBindings[workerSession] = binding;
17889
17998
  }
17890
17999
  const parsedGroups = {};
17891
18000
  for (const [groupId, group] of Object.entries(groups ?? {})) {
17892
18001
  if (!isGroupRecord(group)) {
17893
- throw new Error(`state file "${path12}" contains an invalid orchestration group at "${groupId}"`);
18002
+ throw new Error(`state file "${path11}" contains an invalid orchestration group at "${groupId}"`);
17894
18003
  }
17895
18004
  parsedGroups[groupId] = group;
17896
18005
  }
17897
18006
  const parsedHumanQuestionPackages = {};
17898
18007
  for (const [packageId, packageRecord] of Object.entries(humanQuestionPackages ?? {})) {
17899
18008
  if (!isHumanQuestionPackageRecord(packageRecord)) {
17900
- throw new Error(`state file "${path12}" contains an invalid human question package at "${packageId}"`);
18009
+ throw new Error(`state file "${path11}" contains an invalid human question package at "${packageId}"`);
17901
18010
  }
17902
18011
  parsedHumanQuestionPackages[packageId] = packageRecord;
17903
18012
  }
17904
18013
  const parsedCoordinatorQuestionState = {};
17905
18014
  for (const [coordinatorSession, questionState] of Object.entries(coordinatorQuestionState ?? {})) {
17906
18015
  if (!isCoordinatorQuestionStateRecord(questionState)) {
17907
- throw new Error(`state file "${path12}" contains an invalid coordinator question state at "${coordinatorSession}"`);
18016
+ throw new Error(`state file "${path11}" contains an invalid coordinator question state at "${coordinatorSession}"`);
17908
18017
  }
17909
18018
  parsedCoordinatorQuestionState[coordinatorSession] = {
17910
18019
  activePackageId: questionState.activePackageId,
@@ -17914,7 +18023,7 @@ function parseOrchestrationState(raw, path12) {
17914
18023
  const parsedCoordinatorRoutes = {};
17915
18024
  for (const [coordinatorSession, route] of Object.entries(coordinatorRoutes ?? {})) {
17916
18025
  if (!isCoordinatorRouteContextRecord(route)) {
17917
- throw new Error(`state file "${path12}" contains an invalid coordinator route at "${coordinatorSession}"`);
18026
+ throw new Error(`state file "${path11}" contains an invalid coordinator route at "${coordinatorSession}"`);
17918
18027
  }
17919
18028
  parsedCoordinatorRoutes[coordinatorSession] = route;
17920
18029
  }
@@ -17927,30 +18036,62 @@ function parseOrchestrationState(raw, path12) {
17927
18036
  coordinatorRoutes: parsedCoordinatorRoutes
17928
18037
  };
17929
18038
  }
17930
- function parseState(raw, path12) {
18039
+ function isReplyMode(value) {
18040
+ return value === "stream" || value === "final" || value === "verbose";
18041
+ }
18042
+ function isSessionRecord(value) {
18043
+ if (!isRecord2(value)) {
18044
+ return false;
18045
+ }
18046
+ return isString(value.alias) && isString(value.agent) && isString(value.workspace) && isString(value.transport_session) && isOptionalString(value.transport_agent_command) && isOptionalString(value.mode_id) && (value.reply_mode === undefined || isReplyMode(value.reply_mode)) && isString(value.created_at) && isString(value.last_used_at);
18047
+ }
18048
+ function parseSessions(raw, path11) {
18049
+ const sessions = {};
18050
+ for (const [alias, value] of Object.entries(raw)) {
18051
+ if (!isSessionRecord(value)) {
18052
+ throw new Error(`state file "${path11}" contains malformed session record "${alias}"`);
18053
+ }
18054
+ sessions[alias] = value;
18055
+ }
18056
+ return sessions;
18057
+ }
18058
+ function isChatContextRecord(value) {
18059
+ return isRecord2(value) && isString(value.current_session);
18060
+ }
18061
+ function parseChatContexts(raw, path11) {
18062
+ const chatContexts = {};
18063
+ for (const [chatKey, value] of Object.entries(raw)) {
18064
+ if (!isChatContextRecord(value)) {
18065
+ throw new Error(`state file "${path11}" contains malformed chat context record "${chatKey}"`);
18066
+ }
18067
+ chatContexts[chatKey] = value;
18068
+ }
18069
+ return chatContexts;
18070
+ }
18071
+ function parseState(raw, path11) {
17931
18072
  if (!isRecord2(raw)) {
17932
- throw new Error(`state file "${path12}" must contain a JSON object`);
18073
+ throw new Error(`state file "${path11}" must contain a JSON object`);
17933
18074
  }
17934
18075
  const sessions = raw.sessions;
17935
18076
  if (!isRecord2(sessions)) {
17936
- throw new Error(`state file "${path12}" must contain an object field "sessions"`);
18077
+ throw new Error(`state file "${path11}" must contain an object field "sessions"`);
17937
18078
  }
17938
18079
  const chatContexts = raw.chat_contexts;
17939
18080
  if (!isRecord2(chatContexts)) {
17940
- throw new Error(`state file "${path12}" must contain an object field "chat_contexts"`);
18081
+ throw new Error(`state file "${path11}" must contain an object field "chat_contexts"`);
17941
18082
  }
17942
- const orchestration = parseOrchestrationState(raw.orchestration, path12);
18083
+ const orchestration = parseOrchestrationState(raw.orchestration, path11);
17943
18084
  return {
17944
- sessions,
17945
- chat_contexts: chatContexts,
18085
+ sessions: parseSessions(sessions, path11),
18086
+ chat_contexts: parseChatContexts(chatContexts, path11),
17946
18087
  orchestration
17947
18088
  };
17948
18089
  }
17949
18090
 
17950
18091
  class StateStore {
17951
18092
  path;
17952
- constructor(path12) {
17953
- this.path = path12;
18093
+ constructor(path11) {
18094
+ this.path = path11;
17954
18095
  }
17955
18096
  async load() {
17956
18097
  try {
@@ -17975,11 +18116,11 @@ class StateStore {
17975
18116
  }
17976
18117
  }
17977
18118
  async save(state) {
17978
- await mkdir9(dirname9(this.path), { recursive: true });
17979
- await writeFile6(this.path, JSON.stringify(state, null, 2));
18119
+ await writePrivateFileAtomic(this.path, JSON.stringify(state, null, 2));
17980
18120
  }
17981
18121
  }
17982
18122
  var init_state_store = __esm(() => {
18123
+ init_private_file();
17983
18124
  init_types2();
17984
18125
  });
17985
18126
 
@@ -18318,7 +18459,7 @@ async function spawnAcpxBridgeClient(options = {}) {
18318
18459
  await client.request("shutdown", {});
18319
18460
  } finally {
18320
18461
  child.stdin.end();
18321
- await terminateProcessTree(child.pid ?? 0);
18462
+ await terminateProcessTree(child.pid ?? 0, { detachedProcessGroup: false });
18322
18463
  }
18323
18464
  };
18324
18465
  await client.waitUntilReady();
@@ -18781,19 +18922,19 @@ var init_streaming_prompt = __esm(() => {
18781
18922
 
18782
18923
  // src/transport/acpx-cli/node-pty-helper.ts
18783
18924
  import { chmod as chmodFs } from "node:fs/promises";
18784
- import { dirname as dirname10, join as join7 } from "node:path";
18925
+ import { dirname as dirname9, join as join8 } from "node:path";
18785
18926
  function resolveNodePtyHelperPath(packageJsonPath, platform, arch) {
18786
18927
  if (platform === "win32") {
18787
18928
  return null;
18788
18929
  }
18789
- return join7(dirname10(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
18930
+ return join8(dirname9(packageJsonPath), "prebuilds", `${platform}-${arch}`, "spawn-helper");
18790
18931
  }
18791
- async function ensureNodePtyHelperExecutable(helperPath, chmod = chmodFs) {
18932
+ async function ensureNodePtyHelperExecutable(helperPath, chmod2 = chmodFs) {
18792
18933
  if (!helperPath) {
18793
18934
  return;
18794
18935
  }
18795
18936
  try {
18796
- await chmod(helperPath, 493);
18937
+ await chmod2(helperPath, 493);
18797
18938
  } catch (error2) {
18798
18939
  if (error2.code === "ENOENT") {
18799
18940
  return;
@@ -18806,9 +18947,9 @@ var init_node_pty_helper = () => {};
18806
18947
  // src/transport/acpx-queue-owner-launcher.ts
18807
18948
  import { createHash as createHash2 } from "node:crypto";
18808
18949
  import { spawn as spawn6 } from "node:child_process";
18809
- import { readFile as readFile8, unlink } from "node:fs/promises";
18950
+ import { readFile as readFile8, unlink as unlink2 } from "node:fs/promises";
18810
18951
  import { homedir as homedir7 } from "node:os";
18811
- import { join as join8 } from "node:path";
18952
+ import { join as join9 } from "node:path";
18812
18953
  function buildWeacpxMcpServerSpec(input) {
18813
18954
  const { command, args } = splitCommandLine(input.weacpxCommand);
18814
18955
  return {
@@ -18965,12 +19106,12 @@ async function terminateAcpxQueueOwner(sessionId) {
18965
19106
  return;
18966
19107
  }
18967
19108
  if (typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0) {
18968
- await terminateProcessTree(owner.pid);
19109
+ await terminateProcessTree(owner.pid, { detachedProcessGroup: true });
18969
19110
  }
18970
- await unlink(lockPath).catch(() => {});
19111
+ await unlink2(lockPath).catch(() => {});
18971
19112
  }
18972
19113
  function queueLockFilePath(sessionId) {
18973
- return join8(homedir7(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
19114
+ return join9(homedir7(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
18974
19115
  }
18975
19116
  function shortHash(value, length) {
18976
19117
  return createHash2("sha256").update(value).digest("hex").slice(0, length);
@@ -19008,7 +19149,7 @@ function permissionModeToFlag(permissionMode) {
19008
19149
  }
19009
19150
 
19010
19151
  // src/transport/acpx-cli/acpx-cli-transport.ts
19011
- import { createRequire as createRequire3 } from "node:module";
19152
+ import { createRequire as createRequire4 } from "node:module";
19012
19153
  import { spawn as spawn7 } from "node:child_process";
19013
19154
  import { spawn as spawnPty } from "node-pty";
19014
19155
  async function defaultRunner(command, args, options) {
@@ -19017,8 +19158,12 @@ async function defaultRunner(command, args, options) {
19017
19158
  const child = spawn7(spawnSpec.command, spawnSpec.args, { stdio: ["ignore", "pipe", "pipe"] });
19018
19159
  let stdout2 = "";
19019
19160
  let stderr = "";
19161
+ const onAbort = () => {
19162
+ terminateProcessTree(child.pid ?? 0, { detachedProcessGroup: false });
19163
+ };
19164
+ options?.signal?.addEventListener("abort", onAbort, { once: true });
19020
19165
  const timeoutId = options?.timeoutMs ? setTimeout(() => {
19021
- terminateProcessTree(child.pid ?? 0);
19166
+ onAbort();
19022
19167
  reject(new Error(`acpx command timed out after ${options.timeoutMs}ms: ${renderCommandForError(args)}`));
19023
19168
  }, options.timeoutMs) : undefined;
19024
19169
  child.stdout.on("data", (chunk) => {
@@ -19028,11 +19173,13 @@ async function defaultRunner(command, args, options) {
19028
19173
  stderr += String(chunk);
19029
19174
  });
19030
19175
  child.on("error", (error2) => {
19176
+ options?.signal?.removeEventListener("abort", onAbort);
19031
19177
  if (timeoutId)
19032
19178
  clearTimeout(timeoutId);
19033
19179
  reject(error2);
19034
19180
  });
19035
19181
  child.on("close", (code) => {
19182
+ options?.signal?.removeEventListener("abort", onAbort);
19036
19183
  if (timeoutId)
19037
19184
  clearTimeout(timeoutId);
19038
19185
  resolve2({ code: code ?? 1, stdout: stdout2, stderr });
@@ -19040,7 +19187,7 @@ async function defaultRunner(command, args, options) {
19040
19187
  });
19041
19188
  }
19042
19189
  async function defaultPtyRunner(command, args, options) {
19043
- const helperPath = resolveNodePtyHelperPath(require3.resolve("node-pty/package.json"), process.platform, process.arch);
19190
+ const helperPath = resolveNodePtyHelperPath(require4.resolve("node-pty/package.json"), process.platform, process.arch);
19044
19191
  await ensureNodePtyHelperExecutable(helperPath);
19045
19192
  return await new Promise((resolve2, reject) => {
19046
19193
  const spawnSpec = resolveSpawnCommand(command, args);
@@ -19052,14 +19199,19 @@ async function defaultPtyRunner(command, args, options) {
19052
19199
  env: process.env
19053
19200
  });
19054
19201
  let output = "";
19055
- const timeoutId = options?.timeoutMs ? setTimeout(() => {
19202
+ const onAbort = () => {
19056
19203
  child.kill();
19204
+ };
19205
+ options?.signal?.addEventListener("abort", onAbort, { once: true });
19206
+ const timeoutId = options?.timeoutMs ? setTimeout(() => {
19207
+ onAbort();
19057
19208
  reject(new Error(`acpx command timed out after ${options.timeoutMs}ms: ${renderCommandForError(args)}`));
19058
19209
  }, options.timeoutMs) : undefined;
19059
19210
  child.onData((chunk) => {
19060
19211
  output += chunk;
19061
19212
  });
19062
19213
  child.onExit(({ exitCode }) => {
19214
+ options?.signal?.removeEventListener("abort", onAbort);
19063
19215
  if (timeoutId)
19064
19216
  clearTimeout(timeoutId);
19065
19217
  resolve2({ code: exitCode, stdout: output, stderr: "" });
@@ -19217,15 +19369,17 @@ ${baseText}` : "" };
19217
19369
  if (!options?.timeoutMs) {
19218
19370
  return await runner(spawnSpec.command, spawnSpec.args, undefined);
19219
19371
  }
19372
+ const abortController = new AbortController;
19220
19373
  let timeoutId;
19221
19374
  return await Promise.race([
19222
- runner(spawnSpec.command, spawnSpec.args, options).finally(() => {
19375
+ runner(spawnSpec.command, spawnSpec.args, { ...options, signal: abortController.signal }).finally(() => {
19223
19376
  if (timeoutId)
19224
19377
  clearTimeout(timeoutId);
19225
19378
  }),
19226
19379
  new Promise((_, reject) => {
19227
19380
  timeoutId = setTimeout(() => {
19228
19381
  reject(new Error(`acpx command timed out after ${options.timeoutMs}ms: ${renderCommandForError(args)}`));
19382
+ abortController.abort();
19229
19383
  }, options.timeoutMs);
19230
19384
  })
19231
19385
  ]);
@@ -19349,7 +19503,7 @@ function renderCommandForError(args) {
19349
19503
  }
19350
19504
  return rendered.join(" ");
19351
19505
  }
19352
- var require3;
19506
+ var require4;
19353
19507
  var init_acpx_cli_transport = __esm(() => {
19354
19508
  init_spawn_command();
19355
19509
  init_prompt_output();
@@ -19358,7 +19512,7 @@ var init_acpx_cli_transport = __esm(() => {
19358
19512
  init_node_pty_helper();
19359
19513
  init_terminate_process_tree();
19360
19514
  init_acpx_queue_owner_launcher();
19361
- require3 = createRequire3(import.meta.url);
19515
+ require4 = createRequire4(import.meta.url);
19362
19516
  });
19363
19517
 
19364
19518
  // src/weixin/messaging/orchestration-notice-accounts.ts
@@ -19700,7 +19854,7 @@ __export(exports_main, {
19700
19854
  });
19701
19855
  import { randomUUID as randomUUID3 } from "node:crypto";
19702
19856
  import { homedir as homedir8 } from "node:os";
19703
- import { dirname as dirname11, join as join9 } from "node:path";
19857
+ import { dirname as dirname10, join as join10 } from "node:path";
19704
19858
  import { fileURLToPath as fileURLToPath3 } from "node:url";
19705
19859
  function startProgressHeartbeat(orchestration, config2, logger2, quota) {
19706
19860
  const thresholdSeconds = config2.orchestration.progressHeartbeatSeconds;
@@ -20111,7 +20265,7 @@ function resolveRuntimePaths() {
20111
20265
  throw new Error("Unable to resolve the current user home directory");
20112
20266
  }
20113
20267
  const configPath = process.env.WEACPX_CONFIG ?? `${home}/.weacpx/config.json`;
20114
- const runtimeDir = join9(dirname11(configPath), "runtime");
20268
+ const runtimeDir = join10(dirname10(configPath), "runtime");
20115
20269
  return {
20116
20270
  configPath,
20117
20271
  statePath: process.env.WEACPX_STATE ?? `${home}/.weacpx/state.json`,
@@ -20125,9 +20279,9 @@ function resolveBridgeEntryPath() {
20125
20279
  return fileURLToPath3(new URL("./bridge/bridge-main.ts", import.meta.url));
20126
20280
  }
20127
20281
  function resolveAppLogPath(configPath) {
20128
- const rootDir = dirname11(configPath);
20129
- const runtimeDir = join9(rootDir, "runtime");
20130
- return join9(runtimeDir, "app.log");
20282
+ const rootDir = dirname10(configPath);
20283
+ const runtimeDir = join10(rootDir, "runtime");
20284
+ return join10(runtimeDir, "app.log");
20131
20285
  }
20132
20286
  function resolveOrchestrationSocketPathFromConfigPath(configPath) {
20133
20287
  const runtimeDir = resolveRuntimeDirFromConfigPath(configPath);
@@ -20530,7 +20684,7 @@ async function checkOrchestrationHealth(options) {
20530
20684
  // src/doctor/checks/runtime-check.ts
20531
20685
  import { constants } from "node:fs";
20532
20686
  import { access as access3, stat as stat2 } from "node:fs/promises";
20533
- import { dirname as dirname12 } from "node:path";
20687
+ import { dirname as dirname11 } from "node:path";
20534
20688
  import { homedir as homedir10 } from "node:os";
20535
20689
  async function checkRuntime(options = {}) {
20536
20690
  const home = options.home ?? process.env.HOME ?? homedir10();
@@ -20568,107 +20722,107 @@ async function checkRuntime(options = {}) {
20568
20722
  }
20569
20723
  function createRuntimeFsProbe() {
20570
20724
  return {
20571
- stat: async (path12) => await stat2(path12),
20572
- access: async (path12, mode) => await access3(path12, mode)
20725
+ stat: async (path11) => await stat2(path11),
20726
+ access: async (path11, mode) => await access3(path11, mode)
20573
20727
  };
20574
20728
  }
20575
- async function checkDirectoryCreatable(label, path12, probe, platform) {
20729
+ async function checkDirectoryCreatable(label, path11, probe, platform) {
20576
20730
  try {
20577
- const stats = await probe.stat(path12);
20731
+ const stats = await probe.stat(path11);
20578
20732
  if (!stats.isDirectory()) {
20579
20733
  return {
20580
20734
  ok: false,
20581
- detail: `${label}: ${path12} (exists but is not a directory)`
20735
+ detail: `${label}: ${path11} (exists but is not a directory)`
20582
20736
  };
20583
20737
  }
20584
- await probe.access(path12, directoryAccessMode(platform));
20738
+ await probe.access(path11, directoryAccessMode(platform));
20585
20739
  return {
20586
20740
  ok: true,
20587
- detail: `${label}: ${path12} (writable)`
20741
+ detail: `${label}: ${path11} (writable)`
20588
20742
  };
20589
20743
  } catch (error2) {
20590
20744
  if (!isMissingPathError(error2)) {
20591
20745
  return {
20592
20746
  ok: false,
20593
- detail: `${label}: ${path12} (unusable: ${formatError6(error2)})`
20747
+ detail: `${label}: ${path11} (unusable: ${formatError6(error2)})`
20594
20748
  };
20595
20749
  }
20596
- const parentCheck = await checkCreatableAncestorDirectory(path12, probe, platform);
20750
+ const parentCheck = await checkCreatableAncestorDirectory(path11, probe, platform);
20597
20751
  if (!parentCheck.ok) {
20598
20752
  return {
20599
20753
  ok: false,
20600
- detail: `${label}: ${path12} (parent not writable: ${parentCheck.blockingPath})`
20754
+ detail: `${label}: ${path11} (parent not writable: ${parentCheck.blockingPath})`
20601
20755
  };
20602
20756
  }
20603
20757
  return {
20604
20758
  ok: true,
20605
- detail: `${label}: ${path12} (creatable via ${parentCheck.creatableFrom})`
20759
+ detail: `${label}: ${path11} (creatable via ${parentCheck.creatableFrom})`
20606
20760
  };
20607
20761
  }
20608
20762
  }
20609
- async function checkFileCreatable(label, path12, probe, platform) {
20763
+ async function checkFileCreatable(label, path11, probe, platform) {
20610
20764
  try {
20611
- const stats = await probe.stat(path12);
20765
+ const stats = await probe.stat(path11);
20612
20766
  if (stats.isDirectory()) {
20613
20767
  return {
20614
20768
  ok: false,
20615
- detail: `${label}: ${path12} (exists but is a directory)`
20769
+ detail: `${label}: ${path11} (exists but is a directory)`
20616
20770
  };
20617
20771
  }
20618
- await probe.access(path12, constants.W_OK);
20772
+ await probe.access(path11, constants.W_OK);
20619
20773
  return {
20620
20774
  ok: true,
20621
- detail: `${label}: ${path12} (writable)`
20775
+ detail: `${label}: ${path11} (writable)`
20622
20776
  };
20623
20777
  } catch (error2) {
20624
20778
  if (!isMissingPathError(error2)) {
20625
20779
  return {
20626
20780
  ok: false,
20627
- detail: `${label}: ${path12} (unusable: ${formatError6(error2)})`
20781
+ detail: `${label}: ${path11} (unusable: ${formatError6(error2)})`
20628
20782
  };
20629
20783
  }
20630
- const parentCheck = await checkCreatableAncestorDirectory(dirname12(path12), probe, platform);
20784
+ const parentCheck = await checkCreatableAncestorDirectory(dirname11(path11), probe, platform);
20631
20785
  if (!parentCheck.ok) {
20632
20786
  return {
20633
20787
  ok: false,
20634
- detail: `${label}: ${path12} (parent not writable: ${parentCheck.blockingPath})`
20788
+ detail: `${label}: ${path11} (parent not writable: ${parentCheck.blockingPath})`
20635
20789
  };
20636
20790
  }
20637
20791
  return {
20638
20792
  ok: true,
20639
- detail: `${label}: ${path12} (creatable via ${parentCheck.creatableFrom})`
20793
+ detail: `${label}: ${path11} (creatable via ${parentCheck.creatableFrom})`
20640
20794
  };
20641
20795
  }
20642
20796
  }
20643
- async function checkCreatableAncestorDirectory(path12, probe, platform) {
20797
+ async function checkCreatableAncestorDirectory(path11, probe, platform) {
20644
20798
  try {
20645
- const stats = await probe.stat(path12);
20799
+ const stats = await probe.stat(path11);
20646
20800
  if (!stats.isDirectory()) {
20647
20801
  return {
20648
20802
  ok: false,
20649
- creatableFrom: path12,
20650
- blockingPath: path12
20803
+ creatableFrom: path11,
20804
+ blockingPath: path11
20651
20805
  };
20652
20806
  }
20653
- await probe.access(path12, directoryAccessMode(platform));
20807
+ await probe.access(path11, directoryAccessMode(platform));
20654
20808
  return {
20655
20809
  ok: true,
20656
- creatableFrom: path12
20810
+ creatableFrom: path11
20657
20811
  };
20658
20812
  } catch (error2) {
20659
20813
  if (!isMissingPathError(error2)) {
20660
20814
  return {
20661
20815
  ok: false,
20662
- creatableFrom: path12,
20663
- blockingPath: path12
20816
+ creatableFrom: path11,
20817
+ blockingPath: path11
20664
20818
  };
20665
20819
  }
20666
- const parent = dirname12(path12);
20667
- if (parent === path12) {
20820
+ const parent = dirname11(path11);
20821
+ if (parent === path11) {
20668
20822
  return {
20669
20823
  ok: false,
20670
- creatableFrom: path12,
20671
- blockingPath: path12
20824
+ creatableFrom: path11,
20825
+ blockingPath: path11
20672
20826
  };
20673
20827
  }
20674
20828
  const parentCheck = await checkCreatableAncestorDirectory(parent, probe, platform);
@@ -21086,7 +21240,7 @@ var init_render_doctor = __esm(() => {
21086
21240
 
21087
21241
  // src/doctor/doctor.ts
21088
21242
  import { homedir as homedir11 } from "node:os";
21089
- import { join as join10 } from "node:path";
21243
+ import { join as join11 } from "node:path";
21090
21244
  async function runDoctor(options = {}, deps = {}) {
21091
21245
  const home = deps.home ?? process.env.HOME ?? homedir11();
21092
21246
  const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
@@ -21139,8 +21293,8 @@ function resolveDoctorRuntimePaths(home, resolver) {
21139
21293
  return resolveRuntimePaths();
21140
21294
  }
21141
21295
  return {
21142
- configPath: join10(home, ".weacpx", "config.json"),
21143
- statePath: join10(home, ".weacpx", "state.json")
21296
+ configPath: join11(home, ".weacpx", "config.json"),
21297
+ statePath: join11(home, ".weacpx", "state.json")
21144
21298
  };
21145
21299
  }
21146
21300
  function depsUseExplicitRuntimeOverrides() {
@@ -21238,6 +21392,8 @@ var init_doctor2 = __esm(async () => {
21238
21392
  });
21239
21393
 
21240
21394
  // src/cli.ts
21395
+ init_config_store();
21396
+ init_ensure_config();
21241
21397
  init_create_daemon_controller();
21242
21398
  init_daemon_files();
21243
21399
  import { homedir as homedir12 } from "node:os";
@@ -21246,8 +21402,8 @@ import { fileURLToPath as fileURLToPath5 } from "node:url";
21246
21402
 
21247
21403
  // src/daemon/daemon-runtime.ts
21248
21404
  init_daemon_status();
21249
- import { mkdir as mkdir4, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
21250
- import { dirname as dirname4 } from "node:path";
21405
+ import { mkdir as mkdir5, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
21406
+ import { dirname as dirname5 } from "node:path";
21251
21407
 
21252
21408
  class DaemonRuntime {
21253
21409
  paths;
@@ -21273,8 +21429,8 @@ class DaemonRuntime {
21273
21429
  stdout_log: this.paths.stdoutLog,
21274
21430
  stderr_log: this.paths.stderrLog
21275
21431
  };
21276
- await mkdir4(dirname4(this.paths.pidFile), { recursive: true });
21277
- await writeFile3(this.paths.pidFile, `${this.options.pid}
21432
+ await mkdir5(dirname5(this.paths.pidFile), { recursive: true });
21433
+ await writeFile4(this.paths.pidFile, `${this.options.pid}
21278
21434
  `);
21279
21435
  await this.statusStore.save(this.currentStatus);
21280
21436
  }
@@ -21544,10 +21700,10 @@ function mergeDefs(...defs) {
21544
21700
  function cloneDef(schema) {
21545
21701
  return mergeDefs(schema._zod.def);
21546
21702
  }
21547
- function getElementAtPath(obj, path) {
21548
- if (!path)
21703
+ function getElementAtPath(obj, path2) {
21704
+ if (!path2)
21549
21705
  return obj;
21550
- return path.reduce((acc, key) => acc?.[key], obj);
21706
+ return path2.reduce((acc, key) => acc?.[key], obj);
21551
21707
  }
21552
21708
  function promiseAllObject(promisesObj) {
21553
21709
  const keys = Object.keys(promisesObj);
@@ -21928,11 +22084,11 @@ function aborted(x, startIndex = 0) {
21928
22084
  }
21929
22085
  return false;
21930
22086
  }
21931
- function prefixIssues(path, issues) {
22087
+ function prefixIssues(path2, issues) {
21932
22088
  return issues.map((iss) => {
21933
22089
  var _a;
21934
22090
  (_a = iss).path ?? (_a.path = []);
21935
- iss.path.unshift(path);
22091
+ iss.path.unshift(path2);
21936
22092
  return iss;
21937
22093
  });
21938
22094
  }
@@ -27554,8 +27710,8 @@ function getErrorMap() {
27554
27710
  }
27555
27711
  // node_modules/zod/v3/helpers/parseUtil.js
27556
27712
  var makeIssue = (params) => {
27557
- const { data, path, errorMaps, issueData } = params;
27558
- const fullPath = [...path, ...issueData.path || []];
27713
+ const { data, path: path2, errorMaps, issueData } = params;
27714
+ const fullPath = [...path2, ...issueData.path || []];
27559
27715
  const fullIssue = {
27560
27716
  ...issueData,
27561
27717
  path: fullPath
@@ -27667,11 +27823,11 @@ var errorUtil;
27667
27823
 
27668
27824
  // node_modules/zod/v3/types.js
27669
27825
  class ParseInputLazyPath {
27670
- constructor(parent, value, path, key) {
27826
+ constructor(parent, value, path2, key) {
27671
27827
  this._cachedPath = [];
27672
27828
  this.parent = parent;
27673
27829
  this.data = value;
27674
- this._path = path;
27830
+ this._path = path2;
27675
27831
  this._key = key;
27676
27832
  }
27677
27833
  get path() {
@@ -33657,7 +33813,7 @@ init_version();
33657
33813
  // src/mcp/resolve-endpoint.ts
33658
33814
  init_daemon_files();
33659
33815
  init_orchestration_ipc();
33660
- import { homedir } from "node:os";
33816
+ import { homedir as homedir2 } from "node:os";
33661
33817
  function resolveDefaultOrchestrationEndpoint(env = process.env, platform = process.platform) {
33662
33818
  if (typeof env.WEACPX_ORCHESTRATION_SOCKET === "string" && env.WEACPX_ORCHESTRATION_SOCKET.trim().length > 0) {
33663
33819
  return createOrchestrationEndpoint(env.WEACPX_ORCHESTRATION_SOCKET.trim(), platform);
@@ -33668,7 +33824,7 @@ function resolveDefaultOrchestrationEndpoint(env = process.env, platform = proce
33668
33824
  return resolveOrchestrationEndpoint(runtimeDir, platform);
33669
33825
  }
33670
33826
  function requireHome(env) {
33671
- const home = env.HOME ?? homedir();
33827
+ const home = env.HOME ?? homedir2();
33672
33828
  if (!home) {
33673
33829
  throw new Error("Unable to resolve the current user home directory");
33674
33830
  }
@@ -34226,8 +34382,8 @@ function normalizeInputSchemaJson(schema) {
34226
34382
  }
34227
34383
  function formatZodError(error2) {
34228
34384
  return error2.issues.map((issue2) => {
34229
- const path2 = issue2.path.length > 0 ? issue2.path.join(".") : "arguments";
34230
- return `${path2}: ${issue2.message}`;
34385
+ const path3 = issue2.path.length > 0 ? issue2.path.join(".") : "arguments";
34386
+ return `${path3}: ${issue2.message}`;
34231
34387
  }).join("; ");
34232
34388
  }
34233
34389
 
@@ -34264,6 +34420,7 @@ function parseSourceHandle(args, env = process.env) {
34264
34420
  }
34265
34421
 
34266
34422
  // src/cli.ts
34423
+ init_workspace_path();
34267
34424
  init_version();
34268
34425
  init_consumer_lock();
34269
34426
  var HELP_LINES = [
@@ -34276,6 +34433,7 @@ var HELP_LINES = [
34276
34433
  "weacpx stop - 停止服务",
34277
34434
  "weacpx doctor - 运行诊断",
34278
34435
  "weacpx version - 查看版本",
34436
+ "weacpx workspace list|add|rm - 管理本机工作区(别名:ws)",
34279
34437
  "weacpx mcp-stdio --coordinator-session <session> [--source-handle <handle>] - 启动 MCP stdio 服务"
34280
34438
  ];
34281
34439
  async function runCli(args, deps = {}) {
@@ -34313,6 +34471,20 @@ async function runCli(args, deps = {}) {
34313
34471
  }
34314
34472
  return await (deps.doctor ?? defaultDoctor)(parsed.options);
34315
34473
  }
34474
+ case "workspace":
34475
+ case "ws": {
34476
+ const result = await handleWorkspaceCli(args.slice(1), {
34477
+ print,
34478
+ cwd: deps.cwd ?? (() => process.cwd())
34479
+ });
34480
+ if (result === null) {
34481
+ for (const line of HELP_LINES) {
34482
+ print(line);
34483
+ }
34484
+ return 1;
34485
+ }
34486
+ return result;
34487
+ }
34316
34488
  case "mcp-stdio":
34317
34489
  return await (deps.mcpStdio ?? ((subArgs) => defaultMcpStdio(subArgs, { stderr: deps.stderr })))(args.slice(1));
34318
34490
  case "start": {
@@ -34367,6 +34539,83 @@ async function runCli(args, deps = {}) {
34367
34539
  return 1;
34368
34540
  }
34369
34541
  }
34542
+ async function handleWorkspaceCli(args, deps) {
34543
+ const subcommand = args[0];
34544
+ switch (subcommand) {
34545
+ case "list":
34546
+ if (args.length !== 1)
34547
+ return null;
34548
+ return await workspaceList(deps.print);
34549
+ case "add":
34550
+ if (args.length > 2)
34551
+ return null;
34552
+ return await workspaceAdd(args[1], deps);
34553
+ case "rm":
34554
+ if (args.length !== 2 || !args[1])
34555
+ return null;
34556
+ return await workspaceRemove(args[1], deps.print);
34557
+ default:
34558
+ return null;
34559
+ }
34560
+ }
34561
+ async function workspaceList(print) {
34562
+ const store = await createCliConfigStore();
34563
+ const config2 = await store.load();
34564
+ const entries = Object.entries(config2.workspaces);
34565
+ if (entries.length === 0) {
34566
+ print("还没有工作区。");
34567
+ return 0;
34568
+ }
34569
+ print("工作区列表:");
34570
+ for (const [name, workspace] of entries) {
34571
+ print(`- ${name}: ${workspace.cwd}`);
34572
+ }
34573
+ return 0;
34574
+ }
34575
+ async function workspaceAdd(rawName, deps) {
34576
+ const cwd = normalizeWorkspacePath(deps.cwd());
34577
+ const name = rawName === undefined ? basenameForWorkspacePath(cwd) : rawName.trim();
34578
+ if (name.trim().length === 0) {
34579
+ deps.print("工作区名称不能为空。");
34580
+ return 1;
34581
+ }
34582
+ const store = await createCliConfigStore();
34583
+ const config2 = await store.load();
34584
+ const existing = config2.workspaces[name];
34585
+ if (existing) {
34586
+ if (sameWorkspacePath(existing.cwd, cwd)) {
34587
+ deps.print(`工作区「${name}」已存在:${existing.cwd}`);
34588
+ return 0;
34589
+ }
34590
+ deps.print(`工作区「${name}」已存在,但路径不同:${existing.cwd}`);
34591
+ deps.print(`请换一个名称,或先执行:weacpx workspace rm ${name}`);
34592
+ return 1;
34593
+ }
34594
+ await store.upsertWorkspace(name, cwd);
34595
+ deps.print(`工作区「${name}」已保存:${cwd}`);
34596
+ return 0;
34597
+ }
34598
+ async function workspaceRemove(rawName, print) {
34599
+ const name = rawName.trim();
34600
+ if (name.length === 0) {
34601
+ print("工作区名称不能为空。");
34602
+ return 1;
34603
+ }
34604
+ const store = await createCliConfigStore();
34605
+ const config2 = await store.load();
34606
+ if (!config2.workspaces[name]) {
34607
+ print(`没有找到工作区「${name}」。`);
34608
+ return 1;
34609
+ }
34610
+ await store.removeWorkspace(name);
34611
+ print(`工作区「${name}」已删除`);
34612
+ return 0;
34613
+ }
34614
+ async function createCliConfigStore() {
34615
+ const configPath = process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
34616
+ await ensureConfigExists(configPath);
34617
+ return new ConfigStore(configPath);
34618
+ }
34370
34619
  async function defaultLogin() {
34371
34620
  const { main: main4 } = await init_login().then(() => exports_login);
34372
34621
  await main4();