ai-project-manage-cli 6.0.75 → 6.0.77

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.
Files changed (2) hide show
  1. package/dist/index.js +2240 -2288
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2126,7 +2126,7 @@ async function runUpdateMessageStatus(options) {
2126
2126
  }
2127
2127
 
2128
2128
  // src/commands/connect.ts
2129
- import { spawnSync as spawnSync4 } from "child_process";
2129
+ import { spawnSync as spawnSync2 } from "child_process";
2130
2130
  import WebSocket from "ws";
2131
2131
 
2132
2132
  // src/ws/protocol.ts
@@ -2250,2516 +2250,2468 @@ function validateAgentWsMessage(value, kind) {
2250
2250
 
2251
2251
  // src/commands/connect/deploy-run.ts
2252
2252
  import { spawn } from "node:child_process";
2253
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "node:fs";
2254
- import { join as join14 } from "node:path";
2255
-
2256
- // src/commands/deploy/internal/wisdom-auto-deploy.ts
2257
- import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
2258
- import path3 from "node:path";
2259
- import { spawnSync as spawnSync3 } from "node:child_process";
2260
-
2261
- // src/commands/deploy/internal/apm-config.ts
2262
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
2263
- import { homedir as homedir2 } from "node:os";
2264
- import { join as join13, resolve as resolve3 } from "node:path";
2265
- function loadApmConfig(options) {
2266
- const p = resolve3(
2267
- process.cwd(),
2268
- options?.configPath ?? resolve3(workspaceApmDir(), "apm.config.json")
2269
- );
2270
- if (!existsSync11(p)) {
2271
- console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
2272
- process.exit(1);
2273
- }
2274
- try {
2275
- const raw = readFileSync9(p, "utf8");
2276
- return JSON.parse(raw);
2277
- } catch (e) {
2278
- console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
2279
- process.exit(1);
2280
- }
2281
- }
2282
- function req(v, field) {
2283
- if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2284
- console.error(`apm.config.json \u4E2D backendDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2285
- process.exit(1);
2286
- }
2287
- return v;
2253
+ var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
2254
+ function resolveDeployCommand(environment) {
2255
+ return `apm deploy ${environment}`;
2288
2256
  }
2289
- function reqTopLevelName(cfg) {
2290
- const n = (cfg.name ?? "").trim();
2291
- if (!n) {
2292
- console.error(
2293
- "\u8BF7\u5728 apm.config.json \u9876\u5C42\u914D\u7F6E name\uFF08\u4E0E\u524D\u7AEF\u5236\u54C1\u524D\u7F00\u3001\u540E\u7AEF\u955C\u50CF\u540D\u5171\u7528\uFF09"
2294
- );
2295
- process.exit(1);
2296
- }
2297
- return n;
2257
+ function buildDeployLog(stdout, stderr) {
2258
+ return [stdout, stderr].filter(Boolean).join("\n");
2298
2259
  }
2299
- function reqBackendPositiveInt(v, field) {
2300
- const n = Number(v);
2301
- if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
2302
- console.error(`apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
2303
- process.exit(1);
2304
- }
2305
- return n;
2260
+ function runShellCommand(command, cwd, signal, onOutput) {
2261
+ return new Promise((resolve5, reject) => {
2262
+ const child = spawn(command, {
2263
+ cwd,
2264
+ shell: true,
2265
+ env: process.env,
2266
+ windowsHide: true
2267
+ });
2268
+ let stdout = "";
2269
+ let stderr = "";
2270
+ const emitLog = () => {
2271
+ onOutput?.(buildDeployLog(stdout, stderr));
2272
+ };
2273
+ const onAbort = () => {
2274
+ child.kill("SIGTERM");
2275
+ };
2276
+ if (signal.aborted) {
2277
+ onAbort();
2278
+ } else {
2279
+ signal.addEventListener("abort", onAbort, { once: true });
2280
+ }
2281
+ child.stdout.on("data", (chunk) => {
2282
+ stdout += String(chunk);
2283
+ emitLog();
2284
+ });
2285
+ child.stderr.on("data", (chunk) => {
2286
+ stderr += String(chunk);
2287
+ emitLog();
2288
+ });
2289
+ child.on("error", (error) => {
2290
+ signal.removeEventListener("abort", onAbort);
2291
+ reject(error);
2292
+ });
2293
+ child.on("close", (code) => {
2294
+ signal.removeEventListener("abort", onAbort);
2295
+ const log2 = buildDeployLog(stdout, stderr);
2296
+ if (code === 0) {
2297
+ resolve5({ log: log2 });
2298
+ return;
2299
+ }
2300
+ const error = new Error(
2301
+ `\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
2302
+ );
2303
+ error.log = log2;
2304
+ reject(error);
2305
+ });
2306
+ });
2306
2307
  }
2307
- function resolveBackendDeployFromApmConfig(cfg) {
2308
- const b = cfg.backendDeploy ?? {};
2309
- const protoRaw = req(b.remoteProtocol, "remoteProtocol").trim().toLowerCase();
2310
- if (protoRaw !== "http" && protoRaw !== "https") {
2311
- console.error(
2312
- "apm.config.json \u4E2D backendDeploy.remoteProtocol \u53EA\u80FD\u4E3A http \u6216 https"
2313
- );
2314
- process.exit(1);
2315
- }
2316
- const remoteProtocol = protoRaw;
2317
- let mappings = [];
2318
- const rawPorts = b.containerPortsMappings;
2319
- if (rawPorts !== void 0 && rawPorts !== null) {
2320
- if (!Array.isArray(rawPorts)) {
2308
+ function createDeployLogSyncer(api, deploymentRunId) {
2309
+ let lastSyncedLog = "";
2310
+ let latestLog = "";
2311
+ const syncIfChanged = async () => {
2312
+ if (!latestLog || latestLog === lastSyncedLog) {
2313
+ return;
2314
+ }
2315
+ await api.cli.syncTaskDeploymentLog({
2316
+ id: deploymentRunId,
2317
+ log: latestLog
2318
+ });
2319
+ lastSyncedLog = latestLog;
2320
+ };
2321
+ const timer = setInterval(() => {
2322
+ void syncIfChanged().catch((error) => {
2321
2323
  console.error(
2322
- "apm.config.json \u4E2D backendDeploy.containerPortsMappings \u987B\u4E3A\u5B57\u7B26\u4E32\u6570\u7EC4\uFF08\u53EF\u4E3A\u7A7A\uFF0C\u7701\u7565\u5219\u4E0D\u52A0\u7AEF\u53E3\u6620\u5C04\uFF09"
2324
+ "[apm] deploy log sync failed:",
2325
+ error instanceof Error ? error.message : String(error)
2323
2326
  );
2324
- process.exit(1);
2325
- }
2326
- mappings = rawPorts.map((x) => String(x).trim()).filter(Boolean);
2327
- }
2327
+ });
2328
+ }, DEPLOY_LOG_SYNC_INTERVAL_MS);
2328
2329
  return {
2329
- name: reqTopLevelName(cfg),
2330
- registryHost: req(b.registryHost, "registryHost").trim(),
2331
- registryNamespace: req(b.registryNamespace, "registryNamespace").trim(),
2332
- registryUser: req(b.registryUser, "registryUser").trim(),
2333
- registryPassword: req(b.registryPassword, "registryPassword").trim(),
2334
- remoteHost: req(b.remoteHost, "remoteHost").trim(),
2335
- remotePort: reqBackendPositiveInt(b.remotePort, "remotePort"),
2336
- remoteProtocol,
2337
- caPath: b.caPath?.trim(),
2338
- certPath: b.certPath?.trim(),
2339
- keyPath: b.keyPath?.trim(),
2340
- envFilePath: typeof b.envFilePath === "string" ? b.envFilePath.trim() : "",
2341
- containerPortsMappings: mappings,
2342
- dockerNetwork: b.dockerNetwork?.trim() || void 0
2330
+ updateLog(log2) {
2331
+ latestLog = log2;
2332
+ },
2333
+ async flush() {
2334
+ clearInterval(timer);
2335
+ await syncIfChanged();
2336
+ },
2337
+ dispose() {
2338
+ clearInterval(timer);
2339
+ }
2343
2340
  };
2344
2341
  }
2345
- function reqFe(v, field) {
2346
- if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2347
- console.error(`apm.config.json \u4E2D frontendDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2348
- process.exit(1);
2342
+ async function handleInboundDeploy(cfg, msg, signal) {
2343
+ const api = createApmApiClient(cfg);
2344
+ const deploymentRunId = msg.deploymentRunId;
2345
+ if (signal.aborted) return;
2346
+ await api.cli.updateTaskDeploymentStatus({
2347
+ id: deploymentRunId,
2348
+ status: "DEPLOYING"
2349
+ });
2350
+ const workdir = requireRemoteWorkdir(msg.workdir);
2351
+ const command = resolveDeployCommand(msg.environment);
2352
+ console.log(
2353
+ `[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir}`
2354
+ );
2355
+ console.log(`[apm] deploy command: ${command}`);
2356
+ const logSyncer = createDeployLogSyncer(api, deploymentRunId);
2357
+ let latestLog = "";
2358
+ try {
2359
+ const { log: log2 } = await runShellCommand(command, workdir, signal, (log3) => {
2360
+ latestLog = log3;
2361
+ logSyncer.updateLog(log3);
2362
+ });
2363
+ latestLog = log2;
2364
+ logSyncer.updateLog(log2);
2365
+ await logSyncer.flush();
2366
+ await api.cli.completeTaskDeployment({
2367
+ id: deploymentRunId,
2368
+ status: "SUCCESS",
2369
+ log: log2
2370
+ });
2371
+ console.log(`[apm] deploy success id=${deploymentRunId}`);
2372
+ } catch (error) {
2373
+ const detail = error instanceof Error ? error.message : String(error);
2374
+ const log2 = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
2375
+ logSyncer.updateLog(log2);
2376
+ await logSyncer.flush();
2377
+ await api.cli.completeTaskDeployment({
2378
+ id: deploymentRunId,
2379
+ status: "FAILED",
2380
+ log: log2,
2381
+ error: detail
2382
+ });
2383
+ console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
2384
+ } finally {
2385
+ logSyncer.dispose();
2349
2386
  }
2350
- return v;
2351
- }
2352
- function resolveFrontendDeployFromApmConfig(cfg) {
2353
- const f = cfg.frontendDeploy ?? {};
2354
- const port = Number(f.port);
2355
- return {
2356
- endpoint: reqFe(f.endpoint, "endpoint").trim(),
2357
- port: Number.isFinite(port) && port > 0 ? port : 9e3,
2358
- useSsl: Boolean(f.useSsl),
2359
- accessKey: reqFe(f.accessKey, "accessKey").trim(),
2360
- secretKey: reqFe(f.secretKey, "secretKey").trim(),
2361
- bucket: reqFe(f.bucket, "bucket").trim()
2362
- };
2363
2387
  }
2364
- function reqWd(v, field) {
2365
- if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2366
- console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2367
- process.exit(1);
2368
- }
2369
- return v;
2388
+
2389
+ // src/commands/connect/abort-signal-debug.ts
2390
+ import {
2391
+ getEventListeners,
2392
+ getMaxListeners,
2393
+ setMaxListeners
2394
+ } from "node:events";
2395
+ function isAbortSignalDebugEnabled() {
2396
+ const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
2397
+ return v === "1" || v === "true" || v === "yes";
2370
2398
  }
2371
- function reqWisdomPositiveInt(v, field) {
2372
- const n = Number(v);
2373
- if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
2374
- console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
2375
- process.exit(1);
2399
+ function formatAbortSignalStats(signal, label) {
2400
+ if (!signal) {
2401
+ return `[apm:abort-debug] ${label}: (no signal)`;
2376
2402
  }
2377
- return n;
2403
+ const listeners = getEventListeners(signal, "abort");
2404
+ const max = getMaxListeners(signal);
2405
+ return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
2378
2406
  }
2379
- function resolveWisdomDeployFromApmConfig(cfg) {
2380
- const w = cfg.wisdomDeploy ?? {};
2381
- return {
2382
- host: reqWd(w.host, "host").trim(),
2383
- port: reqWisdomPositiveInt(w.port, "port"),
2384
- username: reqWd(w.username, "username").trim(),
2385
- password: reqWd(w.password, "password").trim(),
2386
- remotePath: reqWd(w.remotePath, "remotePath").trim()
2387
- };
2407
+ function logAbortSignalStats(signal, label) {
2408
+ if (!isAbortSignalDebugEnabled()) return;
2409
+ console.log(formatAbortSignalStats(signal, label));
2388
2410
  }
2389
- function reqHc(v, field) {
2390
- if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2391
- console.error(`apm.config.json \u4E2D healthCheck.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2392
- process.exit(1);
2393
- }
2394
- return v;
2395
- }
2396
- function reqWb(v, field) {
2397
- if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2398
- console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2399
- process.exit(1);
2411
+ var installed = false;
2412
+ function installAbortSignalDebug() {
2413
+ if (!isAbortSignalDebugEnabled() || installed) return;
2414
+ installed = true;
2415
+ const maxFromEnv = Number.parseInt(
2416
+ process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
2417
+ 10
2418
+ );
2419
+ if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
2420
+ setMaxListeners(maxFromEnv);
2421
+ console.log(
2422
+ `[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
2423
+ );
2400
2424
  }
2401
- return v;
2425
+ process.on("warning", (warning) => {
2426
+ if (warning.name !== "MaxListenersExceededWarning") return;
2427
+ console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
2428
+ if (warning.stack) {
2429
+ console.warn(warning.stack);
2430
+ }
2431
+ });
2432
+ const proto = AbortSignal.prototype;
2433
+ const original = proto.addEventListener;
2434
+ proto.addEventListener = function(type, listener, options) {
2435
+ if (type === "abort") {
2436
+ const sig = this;
2437
+ const before = getEventListeners(sig, "abort").length;
2438
+ const max = getMaxListeners(sig);
2439
+ const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
2440
+ console.log(
2441
+ `[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
2442
+ ${stack}`
2443
+ );
2444
+ }
2445
+ return original.call(this, type, listener, options);
2446
+ };
2447
+ console.log(
2448
+ "[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
2449
+ );
2402
2450
  }
2403
- function readEnvVar(env, name) {
2404
- if (env[name] !== void 0) {
2405
- return env[name];
2451
+
2452
+ // src/commands/connect/cursor-agent.ts
2453
+ import {
2454
+ Agent,
2455
+ CursorAgentError
2456
+ } from "@cursor/sdk";
2457
+ import { setMaxListeners as setMaxListeners2 } from "node:events";
2458
+
2459
+ // src/plan-format.ts
2460
+ function formatPlanMarkdown(raw) {
2461
+ let text = raw.trim();
2462
+ if (!text) {
2463
+ return text;
2406
2464
  }
2407
- if (process.platform === "win32") {
2408
- const target = name.toUpperCase();
2409
- for (const [key, value] of Object.entries(env)) {
2410
- if (key.toUpperCase() === target) {
2411
- return value;
2465
+ if (text.startsWith("{") && text.endsWith("}")) {
2466
+ try {
2467
+ const parsed = JSON.parse(text);
2468
+ if (typeof parsed.plan === "string") {
2469
+ return formatPlanMarkdown(parsed.plan);
2412
2470
  }
2471
+ } catch {
2413
2472
  }
2414
2473
  }
2415
- return void 0;
2416
- }
2417
- function expandWindowsEnvVars(pathStr, env = process.env) {
2418
- return pathStr.replace(/%([^%]+)%/g, (_, name) => {
2419
- const value = readEnvVar(env, name);
2420
- return value ?? `%${name}%`;
2421
- });
2422
- }
2423
- function expandUserPath(pathStr, env = process.env) {
2424
- const home = env.HOME ?? env.USERPROFILE ?? homedir2();
2425
- const withEnv = expandWindowsEnvVars(pathStr, env);
2426
- const expanded = withEnv.replace(/^~(?=\/|\\|$)/, home);
2427
- if (/^[a-zA-Z]:[/\\]/.test(expanded)) {
2428
- return expanded;
2474
+ if (!text.includes("\n") && text.includes("\\n")) {
2475
+ text = text.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
2429
2476
  }
2430
- return resolve3(expanded);
2431
- }
2432
- var MAVEN_REPO_ENV_KEYS = [
2433
- "MAVEN_LOCAL_REPO",
2434
- "M2_REPO",
2435
- "MAVEN_REPOSITORY"
2436
- ];
2437
- function readMavenLocalRepoFromMavenOpts(mavenOpts, env = process.env) {
2438
- const match = mavenOpts.match(/-Dmaven\.repo\.local=(?:"([^"]+)"|(\S+))/);
2439
- const raw = match?.[1]?.trim() || match?.[2]?.trim();
2440
- return raw ? expandUserPath(raw, env) : null;
2477
+ return text.replace(/\r\n/g, "\n").trimEnd() + "\n";
2441
2478
  }
2442
- function readMavenLocalRepoFromEnv(env = process.env) {
2443
- for (const key of MAVEN_REPO_ENV_KEYS) {
2444
- const raw = readEnvVar(env, key)?.trim();
2445
- if (raw) {
2446
- return { path: expandUserPath(raw, env), key };
2479
+
2480
+ // src/session-utils.ts
2481
+ var EventSession = class {
2482
+ events = [];
2483
+ dirtyIndices = /* @__PURE__ */ new Set();
2484
+ constructor(prompt) {
2485
+ this.events.push({
2486
+ type: "input",
2487
+ content: prompt
2488
+ });
2489
+ this.markDirty(0);
2490
+ }
2491
+ markDirty(index) {
2492
+ this.dirtyIndices.add(index);
2493
+ }
2494
+ addEvent(event) {
2495
+ const latestEvent = this.events[this.events.length - 1];
2496
+ const formatedEvent = this.formatEvent(event);
2497
+ if (!formatedEvent) {
2498
+ return;
2499
+ }
2500
+ if (formatedEvent.type === "tool_call") {
2501
+ const existingIndex = this.events.findIndex(
2502
+ (e) => e.type === "tool_call" && e.call_id === formatedEvent.call_id
2503
+ );
2504
+ if (existingIndex >= 0) {
2505
+ const existingToolCall = this.events[existingIndex];
2506
+ existingToolCall.args = formatedEvent.args;
2507
+ existingToolCall.result = formatedEvent.result;
2508
+ existingToolCall.status = formatedEvent.status;
2509
+ this.markDirty(existingIndex);
2510
+ return;
2511
+ }
2512
+ this.events.push(formatedEvent);
2513
+ this.markDirty(this.events.length - 1);
2514
+ return;
2515
+ }
2516
+ if (formatedEvent.type === "status") {
2517
+ return;
2518
+ }
2519
+ if (formatedEvent.type === "request") {
2520
+ this.events.push(formatedEvent);
2521
+ this.markDirty(this.events.length - 1);
2522
+ return;
2523
+ }
2524
+ if (latestEvent?.type === formatedEvent.type) {
2525
+ switch (formatedEvent.type) {
2526
+ case "assistant":
2527
+ latestEvent.content += formatedEvent.content;
2528
+ break;
2529
+ case "thinking":
2530
+ latestEvent.content += formatedEvent.content;
2531
+ break;
2532
+ case "task":
2533
+ latestEvent.status = formatedEvent.status;
2534
+ latestEvent.text = formatedEvent.text;
2535
+ break;
2536
+ }
2537
+ this.markDirty(this.events.length - 1);
2538
+ return;
2447
2539
  }
2540
+ this.events.push(formatedEvent);
2541
+ this.markDirty(this.events.length - 1);
2448
2542
  }
2449
- const mavenOpts = readEnvVar(env, "MAVEN_OPTS")?.trim();
2450
- if (mavenOpts) {
2451
- const path12 = readMavenLocalRepoFromMavenOpts(mavenOpts, env);
2452
- if (path12) {
2453
- return { path: path12, key: "MAVEN_OPTS" };
2543
+ formatEvent(event) {
2544
+ switch (event.type) {
2545
+ case "assistant": {
2546
+ let content = "";
2547
+ for (const block of event.message.content) {
2548
+ if (block.type === "text" && block.text) {
2549
+ content += block.text;
2550
+ }
2551
+ }
2552
+ return {
2553
+ type: "assistant",
2554
+ content: content || event.content || ""
2555
+ };
2556
+ }
2557
+ case "thinking":
2558
+ return {
2559
+ type: "thinking",
2560
+ content: event.text || event.content || ""
2561
+ };
2562
+ case "tool_call":
2563
+ return {
2564
+ type: "tool_call",
2565
+ args: event.args,
2566
+ result: event.result,
2567
+ status: event.status,
2568
+ call_id: event.call_id,
2569
+ name: event.name
2570
+ };
2571
+ case "task":
2572
+ return {
2573
+ type: "task",
2574
+ status: event.status,
2575
+ text: event.text
2576
+ };
2577
+ case "request":
2578
+ return {
2579
+ ...event,
2580
+ type: "request"
2581
+ };
2582
+ case "status":
2583
+ return { type: "status", status: event.status, message: event.message };
2454
2584
  }
2455
2585
  }
2456
- return null;
2457
- }
2458
- function readMavenLocalRepoFromSettings() {
2459
- const settingsPath = join13(homedir2(), ".m2", "settings.xml");
2460
- if (!existsSync11(settingsPath)) {
2461
- return null;
2586
+ getDirtyEvents() {
2587
+ return [...this.dirtyIndices].sort((a, b) => a - b).map((index) => {
2588
+ const event = this.events[index];
2589
+ return {
2590
+ index,
2591
+ type: event.type,
2592
+ data: JSON.stringify(event)
2593
+ };
2594
+ });
2462
2595
  }
2463
- try {
2464
- const xml = readFileSync9(settingsPath, "utf8");
2465
- const match = xml.match(
2466
- /<localRepository>\s*([^<]+?)\s*<\/localRepository>/
2467
- );
2468
- const raw = match?.[1]?.trim();
2469
- if (!raw) {
2470
- return null;
2596
+ clearDirty(indices) {
2597
+ for (const index of indices) {
2598
+ this.dirtyIndices.delete(index);
2471
2599
  }
2472
- return expandUserPath(raw);
2473
- } catch {
2474
- return null;
2475
2600
  }
2476
- }
2477
- function resolveMavenLocalRepoWithSource() {
2478
- const fromEnv = readMavenLocalRepoFromEnv();
2479
- if (fromEnv) {
2480
- return {
2481
- path: fromEnv.path,
2482
- source: "env",
2483
- sourceDetail: fromEnv.key
2484
- };
2601
+ /** 合并所有 assistant 片段,供剧场成员回传等场景使用 */
2602
+ getAssistantText() {
2603
+ return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
2485
2604
  }
2486
- const fromSettings = readMavenLocalRepoFromSettings();
2487
- if (fromSettings) {
2488
- return {
2489
- path: fromSettings,
2490
- source: "settings",
2491
- sourceDetail: "~/.m2/settings.xml"
2492
- };
2493
- }
2494
- return {
2495
- path: join13(homedir2(), ".m2", "repository"),
2496
- source: "default",
2497
- sourceDetail: "~/.m2/repository"
2498
- };
2499
- }
2500
- function resolveWisdomBackendDeployFromApmConfig(cfg) {
2501
- const projectName = reqTopLevelName(cfg);
2502
- const w = cfg.wisdomDeploy ?? {};
2503
- const h = cfg.healthCheck ?? {};
2504
- const jarPath = reqWb(w.jarPath, "jarPath").trim();
2505
- const remoteAppDir = posixDirname(jarPath);
2506
- const healthPort = Number(reqHc(h.port, "port"));
2507
- const healthTimeout = Number(reqHc(h.timeout, "timeout"));
2508
- if (!Number.isFinite(healthPort) || !Number.isInteger(healthPort) || healthPort < 1) {
2509
- console.error("apm.config.json \u4E2D healthCheck.port \u987B\u4E3A\u6B63\u6574\u6570");
2510
- process.exit(1);
2605
+ /** plan 模式下 createPlan 工具 completed 时的 plan 字段(取最后一次) */
2606
+ getCreatePlanContent() {
2607
+ for (let i = this.events.length - 1; i >= 0; i--) {
2608
+ const event = this.events[i];
2609
+ if (event.type !== "tool_call") {
2610
+ continue;
2611
+ }
2612
+ if (event.name !== "createPlan" || event.status !== "completed") {
2613
+ continue;
2614
+ }
2615
+ const plan = event.args?.plan;
2616
+ if (typeof plan === "string" && plan.trim()) {
2617
+ return formatPlanMarkdown(plan);
2618
+ }
2619
+ }
2620
+ return void 0;
2511
2621
  }
2512
- if (!Number.isFinite(healthTimeout) || !Number.isInteger(healthTimeout) || healthTimeout < 1) {
2513
- console.error("apm.config.json \u4E2D healthCheck.timeout \u987B\u4E3A\u6B63\u6574\u6570");
2514
- process.exit(1);
2622
+ resolveLogContent() {
2623
+ return this.events.map((event) => formatLogEvent(event.type, event)).join("\n");
2515
2624
  }
2516
- const mavenLocalRepo = resolveMavenLocalRepoWithSource();
2517
- return {
2518
- projectName,
2519
- host: reqWd(w.host, "host").trim(),
2520
- port: reqWisdomPositiveInt(w.port, "port"),
2521
- username: reqWd(w.username, "username").trim(),
2522
- password: reqWd(w.password, "password").trim(),
2523
- remoteVueDistDir: reqWd(w.remotePath, "remotePath").trim(),
2524
- remoteAppDir,
2525
- remoteLibDir: `${remoteAppDir}/lib`,
2526
- startupJar: posixBasename(jarPath),
2527
- packageName: `${projectName}.jar.zip`,
2528
- mavenLocalRepo: mavenLocalRepo.path,
2529
- mavenLocalRepoSource: mavenLocalRepo.source,
2530
- mavenLocalRepoSourceDetail: mavenLocalRepo.sourceDetail,
2531
- healthCheckPort: healthPort,
2532
- healthCheckContext: reqHc(h.context, "context").trim(),
2533
- healthCheckTimeout: healthTimeout
2534
- };
2535
- }
2536
- function posixDirname(p) {
2537
- const normalized = p.replace(/\\/g, "/");
2538
- const idx = normalized.lastIndexOf("/");
2539
- if (idx <= 0) {
2540
- return normalized.startsWith("/") ? "/" : ".";
2625
+ };
2626
+ function formatLogEvent(type, event) {
2627
+ if (type === "input") {
2628
+ return `## \u7528\u6237\u8F93\u5165
2629
+
2630
+ ${String(event.content ?? "")}
2631
+ `;
2541
2632
  }
2542
- return normalized.slice(0, idx);
2543
- }
2544
- function posixBasename(p) {
2545
- const normalized = p.replace(/\\/g, "/");
2546
- const idx = normalized.lastIndexOf("/");
2547
- return idx >= 0 ? normalized.slice(idx + 1) : normalized;
2548
- }
2633
+ if (type === "assistant") {
2634
+ return `## \u6A21\u578B\u8F93\u51FA
2549
2635
 
2550
- // src/commands/deploy/internal/wisdom-backend-deploy.ts
2551
- import {
2552
- existsSync as existsSync12,
2553
- mkdirSync as mkdirSync5,
2554
- readdirSync as readdirSync5,
2555
- readFileSync as readFileSync10,
2556
- statSync as statSync5,
2557
- writeFileSync as writeFileSync10
2558
- } from "node:fs";
2559
- import { spawnSync as spawnSync2 } from "node:child_process";
2560
- import path2 from "node:path";
2561
- import { Client } from "ssh2";
2562
- import JSZip2 from "jszip";
2563
- import SftpClient2 from "ssh2-sftp-client";
2636
+ ${String(event.content ?? "")}
2637
+ `;
2638
+ }
2639
+ if (type === "thinking") {
2640
+ return `## \u6A21\u578B\u601D\u8003
2564
2641
 
2565
- // src/commands/deploy/internal/wisdom-sftp.ts
2566
- import { readdir, readFile, unlink, writeFile } from "node:fs/promises";
2567
- import path from "node:path";
2568
- import JSZip from "jszip";
2569
- import SftpClient from "ssh2-sftp-client";
2570
- async function addDirToZip(dir, zipFolder) {
2571
- const entries = await readdir(dir, { withFileTypes: true });
2572
- for (const entry of entries) {
2573
- const fullPath = path.join(dir, entry.name);
2574
- if (entry.isDirectory()) {
2575
- const folder = zipFolder.folder(entry.name);
2576
- if (folder) {
2577
- await addDirToZip(fullPath, folder);
2578
- }
2579
- } else {
2580
- const content = await readFile(fullPath);
2581
- zipFolder.file(entry.name, content);
2582
- }
2642
+ ${String(event.content ?? "")}
2643
+ `;
2583
2644
  }
2645
+ if (type === "tool_call") {
2646
+ return "````toolcall\n" + JSON.stringify(event, null, 2) + "\n````\n";
2647
+ }
2648
+ return `## \u672A\u77E5\u4E8B\u4EF6\uFF1A${type}
2649
+
2650
+ \`\`\`json
2651
+ ${JSON.stringify(event, null, 2)}
2652
+ \`\`\``;
2584
2653
  }
2585
- async function zipDirectory(distDir, zipPath) {
2586
- console.error(`\u538B\u7F29\u76EE\u5F55: ${distDir}`);
2587
- const zip = new JSZip();
2588
- await addDirToZip(distDir, zip);
2589
- const content = await zip.generateAsync({
2590
- type: "nodebuffer",
2591
- compression: "DEFLATE",
2592
- compressionOptions: { level: 6 }
2593
- });
2594
- await writeFile(zipPath, content);
2595
- const sizeMb = (content.length / 1024 / 1024).toFixed(2);
2596
- console.error(`\u5DF2\u751F\u6210: ${zipPath} (${sizeMb} MB)`);
2597
- return content.length;
2654
+
2655
+ // src/commands/connect/agent-session-registry.ts
2656
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "node:fs";
2657
+ import { dirname as dirname4, resolve as resolve3 } from "node:path";
2658
+ function registryPath(workdir, sessionId) {
2659
+ return resolve3(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
2598
2660
  }
2599
- async function ensureRemoteDir(sftp, dir) {
2600
- const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
2601
- let current = dir.startsWith("/") ? "" : ".";
2602
- for (const part of parts) {
2603
- current = current ? `${current}/${part}` : `/${part}`;
2604
- try {
2605
- await sftp.mkdir(current, true);
2606
- } catch {
2661
+ function readRegistry(path12) {
2662
+ if (!existsSync11(path12)) {
2663
+ return {};
2664
+ }
2665
+ try {
2666
+ const parsed = JSON.parse(readFileSync9(path12, "utf8"));
2667
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2668
+ const result = {};
2669
+ for (const [key, value] of Object.entries(
2670
+ parsed
2671
+ )) {
2672
+ if (typeof value === "string" && value.trim()) {
2673
+ result[key] = value.trim();
2674
+ }
2675
+ }
2676
+ return result;
2607
2677
  }
2678
+ } catch {
2608
2679
  }
2680
+ return {};
2609
2681
  }
2610
- function execCommand(client, command) {
2611
- return new Promise((resolve5, reject) => {
2612
- client.exec(command, (err, stream) => {
2613
- if (err) return reject(err);
2614
- let stdout = "";
2615
- let stderr = "";
2616
- stream.on("close", (code) => {
2617
- if (code !== 0) {
2618
- reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
2619
- return;
2620
- }
2621
- resolve5(stdout);
2622
- }).on("data", (data) => {
2623
- stdout += data.toString();
2624
- });
2625
- stream.stderr.on("data", (data) => {
2626
- stderr += data.toString();
2627
- });
2628
- });
2629
- });
2682
+ function writeRegistry(path12, registry) {
2683
+ mkdirSync5(dirname4(path12), { recursive: true });
2684
+ writeFileSync10(path12, `${JSON.stringify(registry, null, 2)}
2685
+ `, "utf8");
2630
2686
  }
2631
- function shellSingleQuote(value) {
2632
- return `'${value.replace(/'/g, `'"'"'`)}'`;
2687
+ function loadSessionAgentId(workdir, sessionId, user) {
2688
+ const registry = readRegistry(registryPath(workdir, sessionId));
2689
+ return registry[user];
2633
2690
  }
2634
- function buildClearRemoteDirExceptZipCommand(target) {
2635
- const normalized = target.replace(/\\/g, "/").replace(/\/$/, "");
2636
- const quotedTarget = shellSingleQuote(normalized);
2637
- const script = [
2638
- `T=${quotedTarget}`,
2639
- "S=$(mktemp -d)",
2640
- 'while IFS= read -r -d "" z; do r="${z#${T}/}"',
2641
- 'mkdir -p "${S}/$(dirname "$r")"',
2642
- 'mv "$z" "${S}/${r}"',
2643
- 'done < <(find "$T" -mindepth 1 -type f -iname "*.zip" -print0)',
2644
- 'rm -rf "${T}"/*',
2645
- 'while IFS= read -r -d "" r; do r="${r#./}"',
2646
- 'mkdir -p "${T}/$(dirname "$r")"',
2647
- 'mv "${S}/${r}" "${T}/${r}"',
2648
- 'done < <(cd "$S" 2>/dev/null && find . -type f -print0)',
2649
- 'rm -rf "$S"'
2650
- ].join("; ");
2651
- return `bash -c ${shellSingleQuote(script)}`;
2691
+ function saveSessionAgentId(workdir, sessionId, user, agentId) {
2692
+ const path12 = registryPath(workdir, sessionId);
2693
+ const registry = readRegistry(path12);
2694
+ registry[user] = agentId;
2695
+ writeRegistry(path12, registry);
2652
2696
  }
2653
- async function uploadAndMaybeExtract(settings, localZip, extract) {
2654
- const remoteZipPath = `${settings.remotePath.replace(/\/$/, "")}/dist.zip`;
2655
- console.error(
2656
- `\u8FDE\u63A5 ${settings.username}@${settings.host}:${settings.port} ...`
2657
- );
2658
- const sftp = new SftpClient();
2659
- try {
2660
- await sftp.connect({
2661
- host: settings.host,
2662
- port: settings.port,
2663
- username: settings.username,
2664
- password: settings.password,
2665
- readyTimeout: 2e4,
2666
- tryKeyboard: true
2667
- });
2668
- await ensureRemoteDir(sftp, settings.remotePath);
2669
- await sftp.put(localZip, remoteZipPath);
2670
- console.error(` \u2713 ${localZip} -> ${remoteZipPath}`);
2671
- if (extract) {
2672
- const target = settings.remotePath.replace(/\/$/, "");
2673
- const client = sftp.client;
2674
- const clearCmd = buildClearRemoteDirExceptZipCommand(target);
2675
- console.error(`\u8FDC\u7A0B\u6E05\u7406\u89E3\u538B\u76EE\u5F55: ${clearCmd}`);
2676
- await execCommand(client, clearCmd);
2677
- const unzipCmd = `unzip -o "${remoteZipPath}" -d "${target}"`;
2678
- console.error(`\u8FDC\u7A0B\u89E3\u538B: ${unzipCmd}`);
2679
- await execCommand(client, unzipCmd);
2680
- console.error("\u8FDC\u7A0B\u89E3\u538B\u5B8C\u6210!");
2681
- }
2682
- console.error(extract ? "\u90E8\u7F72\u5B8C\u6210!" : "\u4E0A\u4F20\u5B8C\u6210!");
2683
- } finally {
2684
- await sftp.end();
2697
+ function clearSessionAgentId(workdir, sessionId, user) {
2698
+ const path12 = registryPath(workdir, sessionId);
2699
+ const registry = readRegistry(path12);
2700
+ if (!(user in registry)) {
2701
+ return;
2685
2702
  }
2703
+ delete registry[user];
2704
+ writeRegistry(path12, registry);
2686
2705
  }
2687
- async function runWisdomSftpDeploy(params) {
2688
- const zipPath = path.join(params.localDir, "..", ".deploy-sftp-dist.zip");
2689
- const resolvedZipPath = path.resolve(zipPath);
2690
- let zipSizeBytes = 0;
2691
- try {
2692
- zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
2693
- await uploadAndMaybeExtract(
2694
- params.settings,
2695
- resolvedZipPath,
2696
- params.extract
2697
- );
2698
- } finally {
2706
+
2707
+ // src/commands/connect/cursor-message-log.ts
2708
+ var CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS = 2e3;
2709
+ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2710
+ let lastRunAt = 0;
2711
+ let timer;
2712
+ let latestSession;
2713
+ let syncChain = Promise.resolve();
2714
+ const syncDirtyEventsOnce = async (session) => {
2715
+ const events = session.getDirtyEvents();
2716
+ if (events.length === 0) {
2717
+ return;
2718
+ }
2719
+ lastRunAt = Date.now();
2699
2720
  try {
2700
- await unlink(resolvedZipPath);
2701
- console.error(`\u5DF2\u6E05\u7406\u672C\u5730\u4E34\u65F6\u6587\u4EF6: ${resolvedZipPath}`);
2702
- } catch {
2721
+ await syncCursorMessageLog(cfg, ctx, events);
2722
+ session.clearDirty(events.map((event) => event.index));
2723
+ } catch (err) {
2724
+ onError(err);
2703
2725
  }
2704
- }
2726
+ };
2727
+ const drainDirtyEvents = async (session) => {
2728
+ while (session.getDirtyEvents().length > 0) {
2729
+ await syncDirtyEventsOnce(session);
2730
+ }
2731
+ };
2732
+ const enqueueSync = (session) => {
2733
+ syncChain = syncChain.then(() => drainDirtyEvents(session));
2734
+ };
2705
2735
  return {
2706
- ok: true,
2707
- localDir: params.localDir,
2708
- host: params.settings.host,
2709
- remotePath: params.settings.remotePath,
2710
- zipSizeBytes,
2711
- extracted: params.extract
2736
+ schedule(session) {
2737
+ latestSession = session;
2738
+ const now = Date.now();
2739
+ const elapsed = now - lastRunAt;
2740
+ if (elapsed >= CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS) {
2741
+ if (timer) {
2742
+ clearTimeout(timer);
2743
+ timer = void 0;
2744
+ }
2745
+ enqueueSync(session);
2746
+ return;
2747
+ }
2748
+ if (timer) {
2749
+ return;
2750
+ }
2751
+ timer = setTimeout(() => {
2752
+ timer = void 0;
2753
+ enqueueSync(latestSession);
2754
+ }, CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS - elapsed);
2755
+ },
2756
+ async flush(session) {
2757
+ latestSession = session;
2758
+ if (timer) {
2759
+ clearTimeout(timer);
2760
+ timer = void 0;
2761
+ }
2762
+ await syncChain;
2763
+ await drainDirtyEvents(session);
2764
+ }
2712
2765
  };
2713
2766
  }
2714
-
2715
- // src/commands/deploy/internal/wisdom-backend-deploy.ts
2716
- var SPRINGBOOT_JAVA_OPTS = "-XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -Xms512M -Xmx1G";
2717
- var MAVEN_MODULE = "jeecg-module-system/jeecg-system-start";
2718
- var MAVEN_PROFILE = "dev";
2719
- function log(message) {
2720
- const now = /* @__PURE__ */ new Date();
2721
- const hh = String(now.getHours()).padStart(2, "0");
2722
- const mm = String(now.getMinutes()).padStart(2, "0");
2723
- const ss = String(now.getSeconds()).padStart(2, "0");
2724
- console.error(`[${hh}:${mm}:${ss}] ${message}`);
2725
- }
2726
- function fail(message) {
2727
- log(`ERROR: ${message}`);
2728
- process.exit(1);
2729
- }
2730
- function expandPath(pathStr) {
2731
- const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
2732
- return path2.resolve(pathStr.replace(/^~(?=\/|\\|$)/, home));
2733
- }
2734
- function quoteForShell(value) {
2735
- if (process.platform === "win32") {
2736
- return `"${value.replace(/"/g, '""')}"`;
2737
- }
2738
- return shellSingleQuote(value);
2739
- }
2740
- function formatMavenLocalRepoArg(repoPath) {
2741
- if (process.platform === "win32") {
2742
- return `-Dmaven.repo.local=${quoteForShell(repoPath)}`;
2767
+ async function syncCursorMessageLog(cfg, ctx, events) {
2768
+ const agentId = ctx.agentId.trim();
2769
+ if (!agentId || events.length === 0) {
2770
+ return;
2743
2771
  }
2744
- return `-Dmaven.repo.local=${repoPath}`;
2745
- }
2746
- function deployCacheDir() {
2747
- return path2.join(workspaceApmDir(), "deploy", ".deploy_cache");
2748
- }
2749
- function manifestFilePath() {
2750
- return path2.join(deployCacheDir(), "manifest.json");
2772
+ const api = createApmApiClient(cfg);
2773
+ await api.cli.upsertCursorMessageLog({
2774
+ sessionId: ctx.sessionId,
2775
+ messageId: ctx.messageId,
2776
+ agentId,
2777
+ events
2778
+ });
2751
2779
  }
2752
- function getTargetDir(projectRoot) {
2753
- return path2.join(projectRoot, MAVEN_MODULE, "target");
2780
+
2781
+ // src/commands/connect/append-message-tool.ts
2782
+ function createAppendMessageCustomTools(cfg, messageId) {
2783
+ return {
2784
+ append_message: {
2785
+ description: "\u5411\u5F53\u524D\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u56DE\u590D\u5185\u5BB9\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8865\u5145\u8FDB\u5C55\uFF1B\u88AB @ \u65F6\u6536\u5230\u540E\u5E94\u5148\u7B80\u77ED\u786E\u8BA4\u518D\u6267\u884C\u4EFB\u52A1\u3002",
2786
+ inputSchema: {
2787
+ type: "object",
2788
+ properties: {
2789
+ content: {
2790
+ type: "string",
2791
+ description: "\u8981\u53D1\u9001\u5230\u7FA4\u91CC\u7684\u56DE\u590D\u5185\u5BB9"
2792
+ }
2793
+ },
2794
+ required: ["content"]
2795
+ },
2796
+ execute: async (args) => {
2797
+ const content = typeof args.content === "string" ? args.content.trim() : "";
2798
+ if (!content) {
2799
+ return {
2800
+ content: [{ type: "text", text: "content \u4E0D\u80FD\u4E3A\u7A7A" }],
2801
+ isError: true
2802
+ };
2803
+ }
2804
+ try {
2805
+ await appendMessageContent(cfg, messageId, content);
2806
+ console.log(`[apm] append_message \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
2807
+ return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
2808
+ } catch (err) {
2809
+ const detail = err instanceof Error ? err.message : String(err);
2810
+ return {
2811
+ content: [{ type: "text", text: `\u8FFD\u52A0\u6D88\u606F\u5931\u8D25: ${detail}` }],
2812
+ isError: true
2813
+ };
2814
+ }
2815
+ }
2816
+ }
2817
+ };
2754
2818
  }
2755
- function relativeKey(projectRoot, filePath) {
2756
- return path2.relative(projectRoot, filePath).split(path2.sep).join("/");
2819
+
2820
+ // src/commands/connect/ask-question-tool.ts
2821
+ function createAskQuestionMockTool(options) {
2822
+ return {
2823
+ description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
2824
+ inputSchema: {
2825
+ type: "object",
2826
+ properties: {
2827
+ title: {
2828
+ type: "string",
2829
+ description: "Optional title for the questions form"
2830
+ },
2831
+ questions: {
2832
+ type: "array",
2833
+ minItems: 1,
2834
+ items: {
2835
+ type: "object",
2836
+ properties: {
2837
+ id: { type: "string" },
2838
+ prompt: { type: "string" },
2839
+ allow_multiple: { type: "boolean" },
2840
+ options: {
2841
+ type: "array",
2842
+ minItems: 2,
2843
+ items: {
2844
+ type: "object",
2845
+ properties: {
2846
+ id: { type: "string" },
2847
+ label: { type: "string" }
2848
+ },
2849
+ required: ["id", "label"]
2850
+ }
2851
+ }
2852
+ },
2853
+ required: ["id", "prompt", "options"]
2854
+ }
2855
+ }
2856
+ },
2857
+ required: ["questions"]
2858
+ },
2859
+ execute: async (args) => {
2860
+ const payload = JSON.stringify(args, null, 2);
2861
+ console.log(`[apm] AskQuestion mock \u8C03\u7528:
2862
+ ${payload}`);
2863
+ options?.onInvoke?.(args);
2864
+ return `[mock] AskQuestion \u5DF2\u8BB0\u5F55\uFF08\u672A\u521B\u5EFA\u4EFB\u52A1\u95EE\u9898\u3001\u672A\u7B49\u5F85\u7528\u6237\u56DE\u7B54\uFF09\u3002\u53C2\u6570:
2865
+ ${payload}`;
2866
+ }
2867
+ };
2757
2868
  }
2758
- function fileSignature(filePath) {
2759
- const stat2 = statSync5(filePath);
2760
- return { size: stat2.size, mtime: stat2.mtimeMs / 1e3 };
2869
+
2870
+ // src/commands/connect/cursor-custom-tools.ts
2871
+ var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
2872
+ AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
2873
+ \u9700\u8981\u5411\u7528\u6237\u786E\u8BA4\u65F6\uFF0C\u8BF7\u8C03\u7528 AskQuestion\uFF08\u7ECF CallMcpTool / custom-user-tools\uFF09\uFF0C\u4E0D\u8981\u5047\u8BBE IDE \u5185\u7F6E AskQuestion \u4E0D\u53EF\u7528\u3002
2874
+ \u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
2875
+ function createCursorCustomTools(cfg, messageId, options) {
2876
+ return {
2877
+ ...createAppendMessageCustomTools(cfg, messageId),
2878
+ AskQuestion: createAskQuestionMockTool({
2879
+ onInvoke: options?.onAskQuestion
2880
+ })
2881
+ };
2761
2882
  }
2762
- function loadManifest3() {
2763
- const manifestPath2 = manifestFilePath();
2764
- if (!existsSync12(manifestPath2)) {
2765
- return {};
2883
+ function withPlanModeToolHint(prompt, mode) {
2884
+ if (mode !== "plan") {
2885
+ return prompt;
2766
2886
  }
2767
- return JSON.parse(readFileSync10(manifestPath2, "utf8"));
2768
- }
2769
- function saveManifest3(manifest) {
2770
- const dir = deployCacheDir();
2771
- mkdirSync5(dir, { recursive: true });
2772
- writeFileSync10(manifestFilePath(), JSON.stringify(manifest, null, 2), "utf8");
2773
- }
2774
- function isProjectLibJar(jarName) {
2775
- return jarName.startsWith("jeecg-");
2887
+ return `${prompt.trim()}
2888
+
2889
+ ${PLAN_MODE_ASK_QUESTION_HINT}`;
2776
2890
  }
2777
- function shouldUploadLibFile(localPath, remoteAttr, manifest, projectRoot) {
2778
- if (!remoteAttr) {
2779
- return [false, "\u8FDC\u7A0B\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7"];
2891
+
2892
+ // src/commands/connect/cursor-agent.ts
2893
+ setMaxListeners2(50);
2894
+ installAbortSignalDebug();
2895
+ var noopRemoteLogSync = {
2896
+ schedule(_session) {
2897
+ },
2898
+ async flush(_session) {
2780
2899
  }
2781
- const localSize = statSync5(localPath).size;
2782
- const remoteSize = remoteAttr.size;
2783
- if (localSize !== remoteSize) {
2784
- return [true, `\u5927\u5C0F\u53D8\u5316 ${remoteSize} -> ${localSize}`];
2785
- }
2786
- if (isProjectLibJar(path2.basename(localPath)) && manifest) {
2787
- const key = relativeKey(projectRoot, localPath);
2788
- const current = fileSignature(localPath);
2789
- const previous = manifest[key];
2790
- if (!previous) {
2791
- return [true, "\u9879\u76EE\u6A21\u5757\u672A\u8BB0\u5F55"];
2792
- }
2793
- if (previous.size !== current.size) {
2794
- return [true, "\u9879\u76EE\u6A21\u5757\u5927\u5C0F\u53D8\u5316"];
2795
- }
2796
- if (previous.mtime < current.mtime) {
2797
- return [true, "\u9879\u76EE\u6A21\u5757\u91CD\u65B0\u6784\u5EFA"];
2798
- }
2799
- }
2800
- return [false, "\u5927\u5C0F\u4E00\u81F4\uFF0C\u8DF3\u8FC7"];
2801
- }
2802
- function listLibFilesToUpload(localLibDir, remoteStats, projectRoot, manifest = null) {
2803
- const entries = [];
2804
- const jarFiles = readdirSync5(localLibDir).filter((name) => name.endsWith(".jar")).sort();
2805
- for (const jarName of jarFiles) {
2806
- const jarPath = path2.join(localLibDir, jarName);
2807
- const remoteAttr = remoteStats.get(jarName);
2808
- const [shouldUpload, reason] = shouldUploadLibFile(
2809
- jarPath,
2810
- remoteAttr,
2811
- manifest,
2812
- projectRoot
2813
- );
2814
- if (shouldUpload) {
2815
- entries.push({ path: jarPath, arcname: jarName, reason });
2816
- }
2817
- }
2818
- return entries;
2819
- }
2820
- function updateManifestEntries(manifest, entries, projectRoot) {
2821
- for (const entry of entries) {
2822
- manifest[relativeKey(projectRoot, entry.path)] = fileSignature(entry.path);
2823
- }
2824
- return manifest;
2825
- }
2826
- async function createUpdatePackage(entries, packageName) {
2827
- const dir = deployCacheDir();
2828
- mkdirSync5(dir, { recursive: true });
2829
- const zipPath = path2.join(dir, packageName);
2830
- log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${path2.basename(zipPath)}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
2831
- const zip = new JSZip2();
2832
- for (const entry of entries) {
2833
- const content = readFileSync10(entry.path);
2834
- zip.file(entry.arcname, content);
2835
- log(` \u6253\u5305: ${entry.arcname} (${entry.reason})`);
2836
- }
2837
- const buffer = await zip.generateAsync({
2838
- type: "nodebuffer",
2839
- compression: "DEFLATE",
2840
- compressionOptions: { level: 6 }
2900
+ };
2901
+ var logCtx = (ctx, agentId) => ({
2902
+ sessionId: ctx.sessionId,
2903
+ messageId: ctx.messageId,
2904
+ agentId
2905
+ });
2906
+ function formatCursorRunFailure(runId, options) {
2907
+ const details = [
2908
+ options?.statusError?.trim(),
2909
+ options?.resultText?.trim()
2910
+ ].filter((value, index, arr) => {
2911
+ if (!value) return false;
2912
+ return arr.indexOf(value) === index;
2841
2913
  });
2842
- writeFileSync10(zipPath, buffer);
2843
- return zipPath;
2844
- }
2845
- function getMvnExecutable() {
2846
- const isWin = process.platform === "win32";
2847
- const candidates = isWin ? ["mvn.cmd", "mvn.bat", "mvn"] : ["mvn"];
2848
- for (const name of candidates) {
2849
- const result = spawnSync2(isWin ? `where ${name}` : `which ${name}`, {
2850
- encoding: "utf8",
2851
- shell: true
2852
- });
2853
- if (result.status === 0 && result.stdout.trim()) {
2854
- return result.stdout.trim().split(/\r?\n/)[0].trim();
2855
- }
2914
+ if (details.length === 0) {
2915
+ return `Cursor run \u5931\u8D25: ${runId}`;
2856
2916
  }
2857
- fail("\u672A\u627E\u5230 mvn \u547D\u4EE4\uFF0C\u8BF7\u786E\u8BA4 Maven \u5DF2\u5B89\u88C5\u5E76\u52A0\u5165 PATH");
2917
+ return `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
2858
2918
  }
2859
- function runMavenBuild(projectRoot, mavenLocalRepo, repoSource) {
2860
- const mavenRepo = expandPath(mavenLocalRepo);
2861
- const mvn = getMvnExecutable();
2862
- const command = [
2863
- quoteForShell(mvn),
2864
- "clean",
2865
- "package",
2866
- `-P${MAVEN_PROFILE}`,
2867
- formatMavenLocalRepoArg(mavenRepo),
2868
- "-DskipTests"
2869
- ].join(" ");
2870
- log("\u5F00\u59CB Maven \u6784\u5EFA...");
2871
- if (repoSource) {
2872
- log(
2873
- `Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo} (\u6765\u6E90: ${repoSource.source}/${repoSource.sourceDetail})`
2874
- );
2875
- } else {
2876
- log(`Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo}`);
2919
+ async function obtainAgent(ctx) {
2920
+ const agentOptions = {
2921
+ apiKey: ctx.apiKey,
2922
+ model: { id: ctx.model || "default" },
2923
+ local: {
2924
+ cwd: ctx.cwd,
2925
+ ...ctx.customTools ? { customTools: ctx.customTools } : {}
2926
+ },
2927
+ ...ctx.mode ? { mode: ctx.mode } : {}
2928
+ // mcpServers: createPlaywrightMcpServers(),
2929
+ };
2930
+ const explicitAgentId = ctx.resumeAgentId?.trim();
2931
+ const savedAgentId = explicitAgentId || (ctx.user ? loadSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user) : void 0);
2932
+ if (savedAgentId) {
2933
+ try {
2934
+ const agent2 = await Agent.resume(savedAgentId, agentOptions);
2935
+ console.log(
2936
+ `[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}${explicitAgentId ? "\uFF08\u53C2\u6570\u6307\u5B9A\uFF09" : ""}`
2937
+ );
2938
+ if (ctx.user) {
2939
+ saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent2.agentId);
2940
+ }
2941
+ return { agent: agent2, resumed: true };
2942
+ } catch (err) {
2943
+ console.warn(
2944
+ `[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
2945
+ err instanceof Error ? err.message : err
2946
+ );
2947
+ if (!explicitAgentId && ctx.user) {
2948
+ clearSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user);
2949
+ }
2950
+ }
2877
2951
  }
2878
- log(`\u6784\u5EFA\u76EE\u5F55: ${projectRoot}`);
2879
- const result = spawnSync2(command, {
2880
- cwd: projectRoot,
2881
- stdio: "inherit",
2882
- shell: true,
2883
- env: process.env
2884
- });
2885
- if (result.status !== 0) {
2886
- fail(`Maven \u6784\u5EFA\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${result.status ?? 1}`);
2952
+ const agent = await Agent.create(agentOptions);
2953
+ if (ctx.user) {
2954
+ saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent.agentId);
2887
2955
  }
2956
+ return { agent, resumed: false };
2888
2957
  }
2889
- function locateLibDir(projectRoot) {
2890
- const targetDir = getTargetDir(projectRoot);
2891
- if (!existsSync12(targetDir)) {
2892
- fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
2893
- }
2894
- const libDir = path2.join(targetDir, "lib");
2895
- if (!existsSync12(libDir) || !statSync5(libDir).isDirectory()) {
2896
- fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
2958
+ async function runCursorAgent(cfg, ctx, options) {
2959
+ const signal = options?.signal;
2960
+ logAbortSignalStats(signal, "runCursorAgent:start");
2961
+ if (signal?.aborted) {
2962
+ throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
2897
2963
  }
2898
- const libJars = readdirSync5(libDir).filter((name) => name.endsWith(".jar"));
2899
- if (libJars.length === 0) {
2900
- fail(`lib \u76EE\u5F55\u4E0B\u6CA1\u6709\u4F9D\u8D56 JAR: ${libDir}`);
2964
+ const apiKey = ctx.apiKey.trim();
2965
+ if (!apiKey) {
2966
+ throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
2901
2967
  }
2902
- log(`\u5B9A\u4F4D lib \u4EA7\u7269: ${libJars.length} \u4E2A`);
2903
- return libDir;
2904
- }
2905
- async function connectSsh(config) {
2906
- const client = new Client();
2907
- log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${config.username}@${config.host}:${config.port}`);
2908
- await new Promise((resolve5, reject) => {
2909
- client.on("ready", () => resolve5()).on("error", (err) => reject(err)).connect({
2910
- host: config.host,
2911
- port: config.port,
2912
- username: config.username,
2913
- password: config.password,
2914
- readyTimeout: 3e4,
2915
- tryKeyboard: true
2916
- });
2968
+ const workdir = resolveWorkdirPath(ctx.workdir);
2969
+ const customTools = createCursorCustomTools(cfg, ctx.messageId, {
2970
+ onAskQuestion: options?.onAskQuestion
2917
2971
  });
2918
- const sftp = new SftpClient2();
2919
- await sftp.connect({
2920
- host: config.host,
2921
- port: config.port,
2922
- username: config.username,
2923
- password: config.password,
2924
- readyTimeout: 3e4,
2925
- tryKeyboard: true
2972
+ const prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
2973
+ console.log(
2974
+ `[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} sessionId=${ctx.sessionId} cwd=${workdir}`
2975
+ );
2976
+ const { agent, resumed } = await obtainAgent({
2977
+ apiKey,
2978
+ model: ctx.model,
2979
+ cwd: workdir,
2980
+ workdir,
2981
+ sessionId: ctx.sessionId,
2982
+ user: ctx.user,
2983
+ mode: ctx.mode,
2984
+ resumeAgentId: ctx.resumeAgentId,
2985
+ customTools
2926
2986
  });
2927
- return { client, sftp };
2928
- }
2929
- async function closeSsh(conn) {
2930
- try {
2931
- await conn.sftp.end();
2932
- } catch {
2933
- }
2934
- conn.client.end();
2935
- }
2936
- async function getRemoteFileStats(sftp, remoteDir) {
2937
- const stats = /* @__PURE__ */ new Map();
2938
- try {
2939
- const listing = await sftp.list(remoteDir);
2940
- for (const item of listing) {
2941
- if (item.name.endsWith(".jar")) {
2942
- stats.set(item.name, { filename: item.name, size: item.size });
2943
- }
2987
+ const eventSession = new EventSession(prompt);
2988
+ const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorMessageLogSync(
2989
+ cfg,
2990
+ logCtx(ctx, agent.agentId),
2991
+ (err) => {
2992
+ console.warn(
2993
+ "[apm] \u540C\u6B65 Cursor \u6D88\u606F\u65E5\u5FD7\u5931\u8D25:",
2994
+ err instanceof Error ? err.message : err
2995
+ );
2944
2996
  }
2945
- } catch {
2946
- }
2947
- return stats;
2948
- }
2949
- async function uploadUpdatePackage(sftp, zipPath, config) {
2950
- const remoteDir = config.remoteVueDistDir.replace(/\/$/, "");
2951
- const remotePath = `${remoteDir}/${path2.basename(zipPath)}`;
2952
- log(`\u4E0A\u4F20\u66F4\u65B0\u5305 -> ${remotePath}`);
2997
+ );
2998
+ let activeRun;
2999
+ const abortRun = () => {
3000
+ if (!activeRun?.supports("cancel")) return;
3001
+ void activeRun.cancel().catch(() => void 0);
3002
+ };
3003
+ signal?.addEventListener("abort", abortRun, { once: true });
3004
+ logAbortSignalStats(signal, "runCursorAgent:after-addListener");
2953
3005
  try {
2954
- await sftp.fastPut(zipPath, remotePath);
2955
- log("\u66F4\u65B0\u5305\u4E0A\u4F20\u6210\u529F");
2956
- } catch (err) {
2957
- fail(`\u66F4\u65B0\u5305\u4E0A\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : err}`);
2958
- }
2959
- return remotePath;
2960
- }
2961
- async function runRemoteCommand(client, command, options) {
2962
- const check = options?.check ?? true;
2963
- const stream = options?.stream ?? false;
2964
- const label = options?.label ?? "\u8FDC\u7A0B\u547D\u4EE4";
2965
- return new Promise((resolve5, reject) => {
2966
- client.exec(command, (err, execStream) => {
2967
- if (err) {
2968
- reject(err);
2969
- return;
3006
+ const run = await agent.send(prompt, {
3007
+ ...ctx.mode ? { mode: ctx.mode } : {},
3008
+ // mcpServers: createPlaywrightMcpServers(),
3009
+ local: {
3010
+ ...options?.forceSend ? { force: true } : {},
3011
+ customTools
2970
3012
  }
2971
- let out = "";
2972
- let errText = "";
2973
- execStream.on("data", (data) => {
2974
- const text = data.toString();
2975
- out += text;
2976
- if (stream) {
2977
- process.stdout.write(text);
2978
- }
2979
- });
2980
- execStream.stderr.on("data", (data) => {
2981
- errText += data.toString();
2982
- });
2983
- execStream.on("close", (code) => {
2984
- if (stream && out && !out.endsWith("\n")) {
2985
- process.stdout.write("\n");
2986
- }
2987
- if (check && code !== 0) {
2988
- const combined = `${out}
2989
- ${errText}`.trim();
2990
- fail(
2991
- `${label}\u5931\u8D25 (exit ${code})` + (combined ? `
2992
- \u8F93\u51FA: ${combined}` : "")
3013
+ });
3014
+ activeRun = run;
3015
+ logAbortSignalStats(signal, "runCursorAgent:after-send");
3016
+ console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
3017
+ let lastRunErrorStatus;
3018
+ for await (const event of run.stream()) {
3019
+ if (signal?.aborted) {
3020
+ abortRun();
3021
+ throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
3022
+ }
3023
+ if (event.type === "status" && event.status === "ERROR") {
3024
+ const message = event.message?.trim();
3025
+ if (message) {
3026
+ lastRunErrorStatus = message;
3027
+ console.error(
3028
+ `[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
2993
3029
  );
2994
3030
  }
2995
- resolve5({ exitCode: code, out: out.trim(), err: errText.trim() });
3031
+ }
3032
+ options?.onStreamEvent?.(event);
3033
+ eventSession.addEvent(event);
3034
+ syncRemoteLog.schedule(eventSession);
3035
+ }
3036
+ await syncRemoteLog.flush(eventSession);
3037
+ const result = await run.wait();
3038
+ if (result.status === "error") {
3039
+ const failureMessage = formatCursorRunFailure(result.id, {
3040
+ statusError: lastRunErrorStatus,
3041
+ resultText: result.result
2996
3042
  });
2997
- });
2998
- });
3043
+ console.error(`[apm] ${failureMessage}`);
3044
+ if (resumed) {
3045
+ clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
3046
+ }
3047
+ throw new Error(failureMessage);
3048
+ }
3049
+ if (result.status === "cancelled") {
3050
+ throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
3051
+ }
3052
+ console.log(`[apm] Cursor Agent \u5B8C\u6210 messageId=${ctx.messageId}`);
3053
+ const artifacts = await agent.listArtifacts().catch(() => []);
3054
+ const artifactDocuments = [];
3055
+ for (const artifact of artifacts) {
3056
+ try {
3057
+ const content = (await agent.downloadArtifact(artifact.path)).toString(
3058
+ "utf8"
3059
+ );
3060
+ artifactDocuments.push({ path: artifact.path, content });
3061
+ } catch (err) {
3062
+ console.warn(
3063
+ `[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
3064
+ err instanceof Error ? err.message : err
3065
+ );
3066
+ }
3067
+ }
3068
+ return {
3069
+ runId: result.id,
3070
+ agentId: agent.agentId,
3071
+ status: result.status,
3072
+ result: result.result,
3073
+ durationMs: result.durationMs,
3074
+ assistantText: eventSession.getAssistantText(),
3075
+ createPlan: eventSession.getCreatePlanContent(),
3076
+ artifacts,
3077
+ artifactDocuments
3078
+ };
3079
+ } catch (err) {
3080
+ if (err instanceof CursorAgentError) {
3081
+ if (resumed) {
3082
+ clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
3083
+ }
3084
+ throw new Error(
3085
+ `Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
3086
+ );
3087
+ }
3088
+ throw err;
3089
+ } finally {
3090
+ logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
3091
+ signal?.removeEventListener("abort", abortRun);
3092
+ logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
3093
+ await agent[Symbol.asyncDispose]();
3094
+ }
2999
3095
  }
3000
- function buildExtractUpdatePackageScript(remoteZipPath, remoteLibDir) {
3001
- const quotedZip = shellSingleQuote(remoteZipPath);
3002
- const quotedLib = shellSingleQuote(remoteLibDir);
3003
- return `
3004
- set -e
3005
- TMP=$(mktemp -d)
3006
- trap 'rm -rf "$TMP"' EXIT
3007
- unzip -oq ${quotedZip} -d "$TMP"
3008
- updated=0
3009
- while IFS= read -r -d '' src; do
3010
- name=$(basename "$src")
3011
- dest=${quotedLib}/"$name"
3012
- if [ -f "$dest" ]; then
3013
- cp -f "$src" "$dest"
3014
- echo "\u8986\u76D6: $name"
3015
- updated=$((updated + 1))
3016
- else
3017
- echo "\u8DF3\u8FC7(\u8FDC\u7A0B\u4E0D\u5B58\u5728): $name"
3018
- fi
3019
- done < <(find "$TMP" -name '*.jar' -type f -print0)
3020
- echo "UPDATED_COUNT=$updated"
3021
- `.trim();
3096
+
3097
+ // src/commands/connect/ensure-message-reply.ts
3098
+ var DEFAULT_REPLY = "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002";
3099
+ function resolveMessageReplyFallback(fallback) {
3100
+ for (const candidate of [
3101
+ fallback.assistantText,
3102
+ fallback.result,
3103
+ fallback.createPlan
3104
+ ]) {
3105
+ const trimmed = candidate?.trim();
3106
+ if (trimmed) {
3107
+ return trimmed;
3108
+ }
3109
+ }
3110
+ return DEFAULT_REPLY;
3022
3111
  }
3023
- async function extractUpdatePackageOnRemote(client, config, remoteZipPath) {
3024
- const script = buildExtractUpdatePackageScript(
3025
- remoteZipPath,
3026
- config.remoteLibDir
3027
- );
3028
- const { out } = await runRemoteCommand(client, script, {
3029
- label: "\u8FDC\u7A0B\u89E3\u538B"
3030
- });
3031
- const match = out.match(/UPDATED_COUNT=(\d+)/);
3032
- if (!match) {
3033
- fail(`\u8FDC\u7A0B\u89E3\u538B\u5931\u8D25\uFF0C\u672A\u83B7\u53D6\u66F4\u65B0\u6570\u91CF
3034
- \u8F93\u51FA: ${out || "(\u7A7A)"}`);
3112
+ async function fetchMessageContent(cfg, sessionId, messageId) {
3113
+ const api = createApmApiClient(cfg);
3114
+ const messages = await api.cli.listSessionMessages({ sessionId });
3115
+ const message = messages.find((item) => item.id === messageId);
3116
+ if (!message) {
3117
+ throw new Error(`\u6D88\u606F\u4E0D\u5B58\u5728: ${messageId}`);
3035
3118
  }
3036
- const updated = Number.parseInt(match[1], 10);
3037
- log(`lib \u89E3\u538B\u5B8C\u6210: \u8986\u76D6 ${updated} \u4E2A`);
3038
- return updated;
3119
+ return message.content.trim();
3039
3120
  }
3040
- function springbootOutputIndicatesSuccess(action, combined) {
3041
- const lower = combined.toLowerCase();
3042
- if (action === "health") {
3043
- return combined.includes("\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7");
3121
+ async function ensureMessageHasReply(cfg, sessionId, messageId, fallback) {
3122
+ const existing = await fetchMessageContent(cfg, sessionId, messageId);
3123
+ if (existing) {
3124
+ return;
3044
3125
  }
3045
- if (action === "start" || action === "restart") {
3046
- return combined.includes("is starting") || lower.includes("is running");
3126
+ const content = resolveMessageReplyFallback(fallback);
3127
+ await appendMessageContent(cfg, messageId, content);
3128
+ console.log(
3129
+ `[apm] \u6D88\u606F\u65E0\u56DE\u590D\u5185\u5BB9\uFF0C\u5DF2\u81EA\u52A8\u8FFD\u52A0: messageId=${messageId} len=${content.length}`
3130
+ );
3131
+ }
3132
+
3133
+ // src/commands/connect/cli-version-sync.ts
3134
+ import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync11 } from "fs";
3135
+ import { join as join13 } from "path";
3136
+ var CLI_VERSION_FILE = ".cli-version.json";
3137
+ function manifestPath(apmDir) {
3138
+ return join13(apmDir, CLI_VERSION_FILE);
3139
+ }
3140
+ function loadManifest3(apmDir) {
3141
+ const path12 = toFsPath(manifestPath(apmDir));
3142
+ if (!existsSync12(path12)) {
3143
+ return null;
3047
3144
  }
3048
- if (action === "stop") {
3049
- return combined.includes("is stopping") || lower.includes("not running") || lower.includes("please check it");
3145
+ try {
3146
+ const parsed = JSON.parse(
3147
+ readFileSync10(path12, "utf8")
3148
+ );
3149
+ if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
3150
+ return parsed;
3151
+ }
3152
+ } catch {
3050
3153
  }
3051
- if (action === "status") {
3052
- return lower.includes("running") || lower.includes("not running");
3154
+ return null;
3155
+ }
3156
+ function saveManifest3(apmDir, cliVersion) {
3157
+ const manifest = { version: 1, cliVersion };
3158
+ writeFileSync11(
3159
+ toFsPath(manifestPath(apmDir)),
3160
+ `${JSON.stringify(manifest, null, 2)}
3161
+ `,
3162
+ "utf8"
3163
+ );
3164
+ }
3165
+ var syncedInSession = /* @__PURE__ */ new Map();
3166
+ function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
3167
+ const cached = syncedInSession.get(workdir);
3168
+ if (cached === currentVersion) {
3169
+ return false;
3170
+ }
3171
+ const stored = loadManifest3(workspaceApmDir(workdir));
3172
+ if (stored?.cliVersion === currentVersion) {
3173
+ syncedInSession.set(workdir, currentVersion);
3174
+ return false;
3053
3175
  }
3054
3176
  return true;
3055
3177
  }
3056
- function buildRemoteStatusScript(remoteAppDir, appName) {
3057
- const dir = shellSingleQuote(remoteAppDir);
3058
- const jar = shellSingleQuote(appName);
3059
- return `
3060
- set -e
3061
- cd ${dir}
3062
- appName=${jar}
3063
- appIds=$(ps -ef | grep java | grep "$appName" | awk '{print $2}')
3064
- if [ -z "$appIds" ]; then
3065
- echo -e "\\033[31m Not running \\033[0m"
3066
- else
3067
- echo -e "\\033[32m Running [$appIds] \\033[0m"
3068
- fi
3069
- `.trim();
3178
+ function markSkillsSyncedForCliVersion(workdir, cliVersion) {
3179
+ saveManifest3(workspaceApmDir(workdir), cliVersion);
3180
+ syncedInSession.set(workdir, cliVersion);
3070
3181
  }
3071
- function buildRemoteRestartScript(remoteAppDir) {
3072
- const dir = shellSingleQuote(remoteAppDir);
3073
- const javaOpts = shellSingleQuote(SPRINGBOOT_JAVA_OPTS);
3074
- return `
3075
- set -e
3076
- cd ${dir}
3077
- releaseApp=$(ls -t | grep '.jar$' | head -n1)
3078
- lastVersionApp=$(ls -t | grep '.jar$' | head -n2 | tail -n1)
3079
- appName=$lastVersionApp
3080
- appIds=$(ps -ef | grep java | grep "$appName" | awk '{print $2}')
3081
- if [ -z "$appIds" ]; then
3082
- echo "Maybe $appName not running, please check it..."
3083
- else
3084
- echo "The $appName is stopping..."
3085
- echo "$appIds" | xargs kill
3086
- fi
3087
- for i in $(seq 15 -1 1); do
3088
- echo -n "$i "
3089
- sleep 1
3090
- done
3091
- echo 0
3092
- if [ ! -d "backup" ]; then
3093
- mkdir backup
3094
- fi
3095
- for i in $(ls | grep '.jar$' | grep -vFx "$releaseApp"); do
3096
- echo "backup $i"
3097
- mv "$i" backup/
3098
- done
3099
- appName=$releaseApp
3100
- count=$(ps -ef | grep java | grep "$appName" | wc -l)
3101
- if [ "$count" != "0" ]; then
3102
- echo "Maybe $appName is running, please check it..."
3103
- else
3104
- echo "The $appName is starting..."
3105
- nohup java -jar "./$appName" ${javaOpts} > nohup.out 2>&1 &
3106
- fi
3107
- `.trim();
3182
+
3183
+ // src/commands/connect/pre-step-cache.ts
3184
+ var PULL_TTL_MS = 3e4;
3185
+ function sessionWorkdirKey(sessionId, workdir) {
3186
+ return `${sessionId}\0${workdir}`;
3108
3187
  }
3109
- function normalizeHealthContext(context) {
3110
- let normalized = context.trim() || "/";
3111
- if (!normalized.startsWith("/")) {
3112
- normalized = `/${normalized}`;
3113
- }
3114
- if (!normalized.endsWith("/")) {
3115
- normalized = `${normalized}/`;
3116
- }
3117
- return normalized;
3188
+ var lastBranchKey = null;
3189
+ var lastPullAtByKey = /* @__PURE__ */ new Map();
3190
+ function shouldRunBranch(sessionId, workdir) {
3191
+ return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
3118
3192
  }
3119
- function buildRemoteHealthScript(port, context, timeoutSecs) {
3120
- const normalizedContext = normalizeHealthContext(context);
3121
- const portStr = String(port);
3122
- const timeoutStr = String(timeoutSecs);
3123
- return `
3124
- set -e
3125
- port=${shellSingleQuote(portStr)}
3126
- context=${shellSingleQuote(normalizedContext)}
3127
- timeout=${shellSingleQuote(timeoutStr)}
3128
- check_url="http://127.0.0.1:${portStr}${normalizedContext}"
3129
- echo "\u5065\u5EB7\u68C0\u67E5: \${check_url} (\u8D85\u65F6 \${timeout}s)"
3130
- deadline=$(($(date +%s) + timeout))
3131
- attempt=0
3132
- while [ $(date +%s) -lt $deadline ]; do
3133
- attempt=$((attempt + 1))
3134
- code=$(curl -s -o /dev/null -w "%{http_code}" "$check_url" 2>/dev/null || echo "000")
3135
- code=$(echo "$code" | tail -n1 | tr -d '[:space:]')
3136
- if [ \${#code} -ge 3 ]; then
3137
- status=\${code:0:3}
3138
- if echo "$status" | grep -qE '^[0-9]+$' && [ "$status" -ge 200 ] && [ "$status" -lt 500 ]; then
3139
- echo -e "\\033[32m\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7 (HTTP \${status})\\033[0m"
3140
- exit 0
3141
- fi
3142
- fi
3143
- remaining=$((deadline - $(date +%s)))
3144
- if [ $remaining -lt 0 ]; then
3145
- remaining=0
3146
- fi
3147
- echo "\u7B49\u5F85\u670D\u52A1\u542F\u52A8... \u7B2C \${attempt} \u6B21\uFF0C\u5269\u4F59 \${remaining}s"
3148
- sleep 5
3149
- done
3150
- echo -e "\\033[31m\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6 (\${timeout}s): \${check_url}\\033[0m"
3151
- exit 1
3152
- `.trim();
3193
+ function markBranchDone(sessionId, workdir) {
3194
+ lastBranchKey = sessionWorkdirKey(sessionId, workdir);
3153
3195
  }
3154
- async function runRemoteServiceScript(client, script, action) {
3155
- const { exitCode, out, err } = await runRemoteCommand(client, script, {
3156
- check: false,
3157
- label: action === "status" ? "\u8FDC\u7A0B status" : action === "restart" ? "\u8FDC\u7A0B restart" : "\u5065\u5EB7\u68C0\u67E5"
3158
- });
3159
- const combined = `${out}
3160
- ${err}`.trim();
3161
- const outputOk = springbootOutputIndicatesSuccess(action, combined);
3162
- if (action === "health") {
3163
- if (exitCode !== 0 || !outputOk) {
3164
- fail(`\u5065\u5EB7\u68C0\u67E5\u5931\u8D25
3165
- \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
3166
- }
3167
- return combined;
3168
- }
3169
- if (exitCode !== 0 && !outputOk) {
3170
- fail(`\u8FDC\u7A0B ${action} \u5931\u8D25
3171
- ${combined}`);
3172
- }
3173
- if (action === "restart" && !outputOk) {
3174
- fail(`\u8FDC\u7A0B restart \u672A\u6210\u529F
3175
- \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
3196
+ function shouldRunPull(sessionId, workdir) {
3197
+ const key = sessionWorkdirKey(sessionId, workdir);
3198
+ const last = lastPullAtByKey.get(key);
3199
+ if (last == null) {
3200
+ return true;
3176
3201
  }
3177
- return combined;
3202
+ return Date.now() - last >= PULL_TTL_MS;
3178
3203
  }
3179
- function stripAnsi(text) {
3180
- return text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
3204
+ function markPullDone(sessionId, workdir) {
3205
+ lastPullAtByKey.set(sessionWorkdirKey(sessionId, workdir), Date.now());
3181
3206
  }
3182
- async function getRunningJar(client, config) {
3183
- const script = buildRemoteStatusScript(
3184
- config.remoteAppDir,
3185
- config.startupJar
3186
- );
3187
- const combined = await runRemoteServiceScript(client, script, "status");
3188
- const text = stripAnsi(combined).trim().toLowerCase();
3189
- if (text.includes("not running")) {
3190
- return null;
3191
- }
3192
- if (text.includes("running")) {
3193
- return config.startupJar;
3194
- }
3195
- return null;
3207
+
3208
+ // src/commands/connect/run-slot-pool.ts
3209
+ var DEFAULT_MAX_CONCURRENT = 5;
3210
+ function createRunSlotPool(maxConcurrent = DEFAULT_MAX_CONCURRENT) {
3211
+ let active = 0;
3212
+ const waiters = [];
3213
+ const acquire = () => {
3214
+ if (active < maxConcurrent) {
3215
+ active += 1;
3216
+ return Promise.resolve();
3217
+ }
3218
+ return new Promise((resolve5) => {
3219
+ waiters.push(() => {
3220
+ active += 1;
3221
+ resolve5();
3222
+ });
3223
+ });
3224
+ };
3225
+ const release = () => {
3226
+ active = Math.max(0, active - 1);
3227
+ const next = waiters.shift();
3228
+ if (next) {
3229
+ next();
3230
+ }
3231
+ };
3232
+ return { acquire, release };
3196
3233
  }
3197
- async function healthCheckService(client, config) {
3198
- log("\u5065\u5EB7\u68C0\u67E5...");
3199
- const script = buildRemoteHealthScript(
3200
- config.healthCheckPort,
3201
- config.healthCheckContext,
3202
- config.healthCheckTimeout
3203
- );
3204
- await runRemoteServiceScript(client, script, "health");
3234
+
3235
+ // src/commands/connect.ts
3236
+ var HEARTBEAT_MS = 3e4;
3237
+ async function updateMessageStatus(cfg, messageId, status) {
3238
+ const api = createApmApiClient(cfg);
3239
+ await api.cli.updateMessageStatus({ id: messageId, status });
3240
+ console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
3205
3241
  }
3206
- async function restartRemoteService(client, config) {
3207
- const script = buildRemoteRestartScript(config.remoteAppDir);
3208
- await runRemoteServiceScript(client, script, "restart");
3242
+ async function setMessageError(cfg, messageId, error) {
3243
+ const api = createApmApiClient(cfg);
3244
+ await api.cli.setMessageError({ id: messageId, error });
3245
+ console.log(`[apm] \u5DF2\u8BBE\u7F6E\u6D88\u606F\u9519\u8BEF: ${messageId}`);
3209
3246
  }
3210
- async function runWisdomBackendDeploy(options) {
3211
- const projectRoot = path2.resolve(options.projectRoot ?? process.cwd());
3212
- const config = options.config;
3213
- log(`=== \u81EA\u52A8\u90E8\u7F72: ${config.projectName} ===`);
3214
- log(`\u914D\u7F6E\u6587\u4EF6: ${path2.join(workspaceApmDir(), "apm.config.json")}`);
3215
- log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
3216
- runMavenBuild(projectRoot, config.mavenLocalRepo, {
3217
- source: config.mavenLocalRepoSource,
3218
- sourceDetail: config.mavenLocalRepoSourceDetail
3219
- });
3220
- const libDir = locateLibDir(projectRoot);
3221
- let manifest = loadManifest3();
3222
- const conn = await connectSsh(config);
3247
+ var SHUTDOWN_DRAIN_MS = 3e3;
3248
+ function isUserCancelled(ctx) {
3249
+ return ctx.perMessageSignal.aborted && !ctx.shutdownSignal.aborted;
3250
+ }
3251
+ async function handleInboundMessage(cfg, msg, signal, ctx) {
3252
+ if (isUserCancelled(ctx)) return;
3253
+ if (signal.aborted) return;
3254
+ const messageId = msg.messageId;
3255
+ const workdir = requireRemoteWorkdir(msg.workdir);
3256
+ const apmRoot = workspaceApmDir(workdir);
3257
+ const runStep = async (step, fn) => {
3258
+ const startedAt = Date.now();
3259
+ try {
3260
+ const result = await fn();
3261
+ console.log(`[apm] step=${step} elapsed=${Date.now() - startedAt}ms`);
3262
+ return result;
3263
+ } catch (err) {
3264
+ const detail = err instanceof Error ? err.message : String(err);
3265
+ throw new Error(`[${step}] ${detail}`);
3266
+ }
3267
+ };
3223
3268
  try {
3224
- const remoteLibStats = await getRemoteFileStats(
3225
- conn.sftp,
3226
- config.remoteLibDir
3227
- );
3228
- log("\u6536\u96C6 JAR \u66F4\u65B0...");
3229
- const libUploadEntries = listLibFilesToUpload(
3230
- libDir,
3231
- remoteLibStats,
3232
- projectRoot,
3233
- manifest
3269
+ if (signal.aborted) return;
3270
+ const { didInit } = await runStep(
3271
+ "workspace-init",
3272
+ () => ensureWorkspaceInitialized(workdir)
3234
3273
  );
3235
- let updated = 0;
3236
- if (libUploadEntries.length > 0) {
3237
- const zipPath = await createUpdatePackage(
3238
- libUploadEntries,
3239
- config.packageName
3240
- );
3241
- const remoteZipPath = await uploadUpdatePackage(
3242
- conn.sftp,
3243
- zipPath,
3244
- config
3245
- );
3246
- log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
3247
- updated = await extractUpdatePackageOnRemote(
3248
- conn.client,
3249
- config,
3250
- remoteZipPath
3251
- );
3252
- } else {
3253
- log("\u65E0 JAR \u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u66F4\u65B0\u5305\u4E0A\u4F20");
3274
+ if (!didInit) {
3275
+ assertApmGitignoredInRepo(workdir);
3254
3276
  }
3255
- const runningJar = await getRunningJar(conn.client, config);
3256
- let needRestart = updated > 0;
3257
- if (!needRestart && !runningJar) {
3258
- log("\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u9700\u8981\u542F\u52A8");
3259
- needRestart = true;
3260
- } else if (!needRestart) {
3261
- log("\u6CA1\u6709\u6587\u4EF6\u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u91CD\u542F");
3277
+ if (shouldRunBranch(msg.sessionId, workdir)) {
3278
+ if (signal.aborted) return;
3279
+ await runStep("branch", () => runBranch(msg.sessionId, { cwd: workdir }));
3280
+ markBranchDone(msg.sessionId, workdir);
3281
+ } else {
3282
+ console.log(`[apm] step=branch skipped sessionId=${msg.sessionId}`);
3262
3283
  }
3263
- if (needRestart) {
3264
- log("\u91CD\u542F\u670D\u52A1...");
3265
- await restartRemoteService(conn.client, config);
3266
- }
3267
- await healthCheckService(conn.client, config);
3268
- if (libUploadEntries.length > 0) {
3269
- manifest = updateManifestEntries(manifest, libUploadEntries, projectRoot);
3270
- saveManifest3(manifest);
3284
+ let pullRan = false;
3285
+ if (shouldRunPull(msg.sessionId, workdir)) {
3286
+ if (signal.aborted) return;
3287
+ await runStep("pull", () => runPull(msg.sessionId, workdir));
3288
+ markPullDone(msg.sessionId, workdir);
3289
+ pullRan = true;
3290
+ } else {
3291
+ console.log(`[apm] step=pull skipped sessionId=${msg.sessionId}`);
3271
3292
  }
3272
- } finally {
3273
- await closeSsh(conn);
3274
- }
3275
- log("\u90E8\u7F72\u5B8C\u6210");
3276
- }
3277
-
3278
- // src/commands/deploy/internal/wisdom-auto-deploy.ts
3279
- function isWisdomLegacyDeploy(cfg) {
3280
- const w = cfg.wisdomDeploy;
3281
- if (!w?.host?.trim() || !w.remotePath?.trim()) {
3282
- return false;
3283
- }
3284
- const hasNewFrontend = Boolean(cfg.frontendDeploy?.endpoint?.trim());
3285
- const hasNewBackend = Boolean(cfg.backendDeploy?.registryHost?.trim());
3286
- return !hasNewFrontend && !hasNewBackend;
3287
- }
3288
- function detectWisdomProjectType(cwd) {
3289
- return existsSync13(path3.join(cwd, "package.json")) ? "frontend" : "backend";
3290
- }
3291
- function readPackageScripts(cwd) {
3292
- const pkgPath = path3.join(cwd, "package.json");
3293
- if (!existsSync13(pkgPath)) {
3294
- return {};
3295
- }
3296
- try {
3297
- const raw = readFileSync11(pkgPath, "utf8");
3298
- const parsed = JSON.parse(raw);
3299
- return parsed.scripts ?? {};
3300
- } catch {
3301
- return {};
3302
- }
3303
- }
3304
- function resolveFrontendDeployCommand(env, cwd) {
3305
- const scripts = readPackageScripts(cwd);
3306
- const deployKey = `deploy:${env}`;
3307
- if (scripts[deployKey]?.trim()) {
3308
- return `npm run deploy:${env}`;
3309
- }
3310
- return null;
3311
- }
3312
- function runShellCommand(command, cwd) {
3313
- const result = spawnSync3(command, {
3314
- cwd,
3315
- stdio: "inherit",
3316
- shell: true,
3317
- env: process.env
3318
- });
3319
- if (result.status !== 0) {
3320
- process.exit(result.status ?? 1);
3321
- }
3322
- }
3323
- async function runWisdomAutoDeploy(options) {
3324
- const cwd = path3.resolve(options.cwd ?? process.cwd());
3325
- const projectType = detectWisdomProjectType(cwd);
3326
- console.error(
3327
- `[apm] \u533B\u52A1\u5B58\u91CF\u9879\u76EE\u81EA\u52A8\u8BC6\u522B: ${projectType === "frontend" ? "\u524D\u7AEF Vue" : "\u540E\u7AEF Java"}`
3328
- );
3329
- if (projectType === "frontend") {
3330
- const deployCmd = resolveFrontendDeployCommand(options.env, cwd);
3331
- if (!deployCmd) {
3332
- console.error(
3333
- `\u524D\u7AEF\u9879\u76EE ${path3.join(cwd, "package.json")} \u4E2D\u672A\u627E\u5230 deploy:${options.env} \u811A\u672C`
3293
+ if (pullRan) {
3294
+ if (signal.aborted) return;
3295
+ await runStep(
3296
+ "commit-pull",
3297
+ () => commitWorkingTreeIfDirty(workdir, "fix: apm pull")
3334
3298
  );
3335
- process.exit(1);
3299
+ } else {
3300
+ console.log(`[apm] step=commit-pull skipped sessionId=${msg.sessionId}`);
3336
3301
  }
3337
- runShellCommand(deployCmd, cwd);
3338
- return { projectType };
3339
- }
3340
- const configPath = options.configPath ?? path3.join(workspaceApmDir(cwd), "apm.config.json");
3341
- const cfg = loadApmConfig({ configPath });
3342
- const settings = resolveWisdomBackendDeployFromApmConfig(cfg);
3343
- console.error(
3344
- `[apm] \u540E\u7AEF\u90E8\u7F72\u4E0D\u533A\u5206 test/online\uFF0C\u5FFD\u7565\u73AF\u5883\u53C2\u6570: ${options.env}`
3345
- );
3346
- await runWisdomBackendDeploy({
3347
- config: settings,
3348
- projectRoot: cwd
3349
- });
3350
- return { projectType };
3351
- }
3352
-
3353
- // src/commands/connect/deploy-run.ts
3354
- var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
3355
- function readApmConfig(workdir) {
3356
- const configPath = join14(workspaceApmDir(workdir), "apm.config.json");
3357
- if (!existsSync14(configPath)) {
3358
- return void 0;
3359
- }
3360
- try {
3361
- const raw = readFileSync12(configPath, "utf8");
3362
- return JSON.parse(raw);
3363
- } catch {
3364
- return void 0;
3365
- }
3366
- }
3367
- function readDeployConfig(workdir) {
3368
- return readApmConfig(workdir)?.deploy;
3369
- }
3370
- function resolveDeployCommand(workdir, environment) {
3371
- const command = readDeployConfig(workdir)?.[environment];
3372
- if (typeof command === "string" && command.trim()) {
3373
- return command.trim();
3374
- }
3375
- return null;
3376
- }
3377
- function missingDeployCommandMessage(environment) {
3378
- return `deploy.${environment} \u90E8\u7F72\u547D\u4EE4\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u914D\u7F6E`;
3379
- }
3380
- function buildDeployLog(stdout, stderr) {
3381
- return [stdout, stderr].filter(Boolean).join("\n");
3382
- }
3383
- function runShellCommand2(command, cwd, signal, onOutput) {
3384
- return new Promise((resolve5, reject) => {
3385
- const child = spawn(command, {
3386
- cwd,
3387
- shell: true,
3388
- env: process.env,
3389
- windowsHide: true
3390
- });
3391
- let stdout = "";
3392
- let stderr = "";
3393
- const emitLog = () => {
3394
- onOutput?.(buildDeployLog(stdout, stderr));
3395
- };
3396
- const onAbort = () => {
3397
- child.kill("SIGTERM");
3398
- };
3399
- if (signal.aborted) {
3400
- onAbort();
3302
+ const cliVersion = readCliVersion();
3303
+ if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
3304
+ if (signal.aborted) return;
3305
+ console.log(
3306
+ `[apm] CLI \u7248\u672C ${cliVersion} \u4E0E\u5DE5\u4F5C\u533A\u8BB0\u5F55\u4E0D\u4E00\u81F4\uFF0C\u6267\u884C update-skills`
3307
+ );
3308
+ await runStep("update-skills", async () => {
3309
+ await syncWorkspaceSkills(cfg, workdir);
3310
+ markSkillsSyncedForCliVersion(workdir, cliVersion);
3311
+ });
3401
3312
  } else {
3402
- signal.addEventListener("abort", onAbort, { once: true });
3313
+ console.log(`[apm] step=update-skills skipped workdir=${workdir}`);
3403
3314
  }
3404
- child.stdout.on("data", (chunk) => {
3405
- stdout += String(chunk);
3406
- emitLog();
3407
- });
3408
- child.stderr.on("data", (chunk) => {
3409
- stderr += String(chunk);
3410
- emitLog();
3411
- });
3412
- child.on("error", (error) => {
3413
- signal.removeEventListener("abort", onAbort);
3414
- reject(error);
3415
- });
3416
- child.on("close", (code) => {
3417
- signal.removeEventListener("abort", onAbort);
3418
- const log2 = buildDeployLog(stdout, stderr);
3419
- if (code === 0) {
3420
- resolve5({ log: log2 });
3421
- return;
3422
- }
3423
- const error = new Error(
3424
- `\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
3315
+ if (signal.aborted) return;
3316
+ if (!pullRan) {
3317
+ await runStep(
3318
+ "sync-project-documents-pull",
3319
+ () => syncRepositoryProjectDocumentsPull(workdir, apmRoot)
3425
3320
  );
3426
- error.log = log2;
3427
- reject(error);
3428
- });
3429
- });
3430
- }
3431
- function createDeployLogSyncer(api, deploymentRunId) {
3432
- let lastSyncedLog = "";
3433
- let latestLog = "";
3434
- const syncIfChanged = async () => {
3435
- if (!latestLog || latestLog === lastSyncedLog) {
3321
+ }
3322
+ const agentResult = await runStep(
3323
+ "cursor-agent",
3324
+ () => runCursorAgent(
3325
+ cfg,
3326
+ {
3327
+ messageId: msg.messageId,
3328
+ sessionId: msg.sessionId,
3329
+ prompt: msg.content,
3330
+ model: msg.model,
3331
+ apiKey: msg.apiKey,
3332
+ workdir,
3333
+ user: msg.user
3334
+ },
3335
+ { signal }
3336
+ )
3337
+ );
3338
+ await runStep(
3339
+ "ensure-reply",
3340
+ () => ensureMessageHasReply(cfg, msg.sessionId, messageId, {
3341
+ assistantText: agentResult.assistantText,
3342
+ result: agentResult.result,
3343
+ createPlan: agentResult.createPlan
3344
+ })
3345
+ );
3346
+ await runStep(
3347
+ "sync-documents",
3348
+ () => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
3349
+ );
3350
+ await runStep(
3351
+ "sync-project-documents",
3352
+ () => syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot)
3353
+ );
3354
+ await runStep(
3355
+ "commit-files",
3356
+ () => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
3357
+ );
3358
+ await runStep(
3359
+ "status-success",
3360
+ () => updateMessageStatus(cfg, messageId, "SUCCESS")
3361
+ );
3362
+ } catch (err) {
3363
+ if (isUserCancelled(ctx)) {
3364
+ console.log(`[apm] \u6D88\u606F\u5DF2\u7EC8\u6B62 messageId=${messageId}`);
3436
3365
  return;
3437
3366
  }
3438
- await api.cli.syncTaskDeploymentLog({
3439
- id: deploymentRunId,
3440
- log: latestLog
3441
- });
3442
- lastSyncedLog = latestLog;
3443
- };
3444
- const timer = setInterval(() => {
3445
- void syncIfChanged().catch((error) => {
3367
+ console.error(
3368
+ "[apm] \u5904\u7406\u6D88\u606F\u5931\u8D25:",
3369
+ err instanceof Error ? err.message : String(err)
3370
+ );
3371
+ if (err instanceof Error && err.stack) {
3372
+ console.error(err.stack);
3373
+ }
3374
+ try {
3375
+ await setMessageError(
3376
+ cfg,
3377
+ messageId,
3378
+ err instanceof Error ? err.message : String(err)
3379
+ );
3380
+ await updateMessageStatus(cfg, messageId, "FAILED");
3381
+ } catch (statusErr) {
3446
3382
  console.error(
3447
- "[apm] deploy log sync failed:",
3448
- error instanceof Error ? error.message : String(error)
3383
+ "[apm] \u66F4\u65B0 FAILED \u72B6\u6001\u5931\u8D25:",
3384
+ statusErr instanceof Error ? statusErr.message : statusErr
3449
3385
  );
3450
- });
3451
- }, DEPLOY_LOG_SYNC_INTERVAL_MS);
3452
- return {
3453
- updateLog(log2) {
3454
- latestLog = log2;
3455
- },
3456
- async flush() {
3457
- clearInterval(timer);
3458
- await syncIfChanged();
3459
- },
3460
- dispose() {
3461
- clearInterval(timer);
3462
3386
  }
3463
- };
3387
+ }
3464
3388
  }
3465
- async function handleInboundDeploy(cfg, msg, signal) {
3466
- const api = createApmApiClient(cfg);
3467
- const deploymentRunId = msg.deploymentRunId;
3468
- if (signal.aborted) return;
3469
- await api.cli.updateTaskDeploymentStatus({
3470
- id: deploymentRunId,
3471
- status: "DEPLOYING"
3472
- });
3473
- const workdir = requireRemoteWorkdir(msg.workdir);
3474
- const apmConfig = readApmConfig(workdir);
3475
- if (apmConfig && isWisdomLegacyDeploy(apmConfig)) {
3476
- console.log(
3477
- `[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir} (wisdom auto)`
3478
- );
3479
- const logSyncer2 = createDeployLogSyncer(api, deploymentRunId);
3480
- try {
3481
- await runWisdomAutoDeploy({
3482
- env: msg.environment,
3483
- cwd: workdir
3484
- });
3485
- await logSyncer2.flush();
3486
- await api.cli.completeTaskDeployment({
3487
- id: deploymentRunId,
3488
- status: "SUCCESS",
3489
- log: "\u533B\u52A1\u5B58\u91CF\u9879\u76EE\u81EA\u52A8\u90E8\u7F72\u5B8C\u6210"
3490
- });
3491
- console.log(`[apm] deploy success id=${deploymentRunId}`);
3492
- } catch (error) {
3493
- const detail = error instanceof Error ? error.message : String(error);
3494
- await logSyncer2.flush();
3495
- await api.cli.completeTaskDeployment({
3496
- id: deploymentRunId,
3497
- status: "FAILED",
3498
- log: detail,
3499
- error: detail
3500
- });
3501
- console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
3502
- } finally {
3503
- logSyncer2.dispose();
3389
+ function startHeartbeat(ws, clientMachineId) {
3390
+ const send = () => {
3391
+ if (ws.readyState === WebSocket.OPEN) {
3392
+ ws.send(
3393
+ serializeAgentWsMessage({
3394
+ type: "heartbeat",
3395
+ userId: clientMachineId
3396
+ })
3397
+ );
3504
3398
  }
3505
- return;
3506
- }
3507
- const command = resolveDeployCommand(workdir, msg.environment);
3508
- if (!command) {
3509
- const error = missingDeployCommandMessage(msg.environment);
3510
- console.error(`[apm] ${error}`);
3511
- await api.cli.completeTaskDeployment({
3512
- id: deploymentRunId,
3513
- status: "FAILED",
3514
- log: error,
3515
- error
3516
- });
3517
- return;
3399
+ };
3400
+ send();
3401
+ const timer = setInterval(send, HEARTBEAT_MS);
3402
+ return () => clearInterval(timer);
3403
+ }
3404
+ function reexecConnect(options) {
3405
+ const args = [process.argv[1], "connect"];
3406
+ const server = options.server?.trim();
3407
+ if (server) {
3408
+ args.push("--server", server);
3518
3409
  }
3519
- console.log(
3520
- `[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir}`
3521
- );
3522
- console.log(`[apm] deploy command: ${command}`);
3523
- const logSyncer = createDeployLogSyncer(api, deploymentRunId);
3524
- let latestLog = "";
3525
- try {
3526
- const { log: log2 } = await runShellCommand2(command, workdir, signal, (log3) => {
3527
- latestLog = log3;
3528
- logSyncer.updateLog(log3);
3529
- });
3530
- latestLog = log2;
3531
- logSyncer.updateLog(log2);
3532
- await logSyncer.flush();
3533
- await api.cli.completeTaskDeployment({
3534
- id: deploymentRunId,
3535
- status: "SUCCESS",
3536
- log: log2
3537
- });
3538
- console.log(`[apm] deploy success id=${deploymentRunId}`);
3539
- } catch (error) {
3540
- const detail = error instanceof Error ? error.message : String(error);
3541
- const log2 = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
3542
- logSyncer.updateLog(log2);
3543
- await logSyncer.flush();
3544
- await api.cli.completeTaskDeployment({
3545
- id: deploymentRunId,
3546
- status: "FAILED",
3547
- log: log2,
3548
- error: detail
3549
- });
3550
- console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
3551
- } finally {
3552
- logSyncer.dispose();
3410
+ const result = spawnSync2(process.execPath, args, { stdio: "inherit" });
3411
+ if (result.error) {
3412
+ console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
3413
+ process.exit(1);
3553
3414
  }
3415
+ process.exit(result.status ?? 0);
3554
3416
  }
3555
-
3556
- // src/commands/connect/abort-signal-debug.ts
3557
- import {
3558
- getEventListeners,
3559
- getMaxListeners,
3560
- setMaxListeners
3561
- } from "node:events";
3562
- function isAbortSignalDebugEnabled() {
3563
- const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
3564
- return v === "1" || v === "true" || v === "yes";
3565
- }
3566
- function formatAbortSignalStats(signal, label) {
3567
- if (!signal) {
3568
- return `[apm:abort-debug] ${label}: (no signal)`;
3417
+ async function runConnect(options) {
3418
+ const { didUpdate } = await runUpdate();
3419
+ if (didUpdate) {
3420
+ reexecConnect(options);
3569
3421
  }
3570
- const listeners = getEventListeners(signal, "abort");
3571
- const max = getMaxListeners(signal);
3572
- return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
3573
- }
3574
- function logAbortSignalStats(signal, label) {
3575
- if (!isAbortSignalDebugEnabled()) return;
3576
- console.log(formatAbortSignalStats(signal, label));
3577
- }
3578
- var installed = false;
3579
- function installAbortSignalDebug() {
3580
- if (!isAbortSignalDebugEnabled() || installed) return;
3581
- installed = true;
3582
- const maxFromEnv = Number.parseInt(
3583
- process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
3584
- 10
3585
- );
3586
- if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
3587
- setMaxListeners(maxFromEnv);
3588
- console.log(
3589
- `[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
3590
- );
3422
+ const cfg = await ensureLoggedConfig();
3423
+ if (options.server?.trim()) {
3424
+ cfg.baseUrl = options.server.trim().replace(/\/+$/, "");
3591
3425
  }
3592
- process.on("warning", (warning) => {
3593
- if (warning.name !== "MaxListenersExceededWarning") return;
3594
- console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
3595
- if (warning.stack) {
3596
- console.warn(warning.stack);
3597
- }
3598
- });
3599
- const proto = AbortSignal.prototype;
3600
- const original = proto.addEventListener;
3601
- proto.addEventListener = function(type, listener, options) {
3602
- if (type === "abort") {
3603
- const sig = this;
3604
- const before = getEventListeners(sig, "abort").length;
3605
- const max = getMaxListeners(sig);
3606
- const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
3426
+ const clientMachineId = resolveClientMachineId(cfg);
3427
+ if (!clientMachineId) {
3428
+ console.error("[apm] config \u7F3A\u5C11 clientMachineId\uFF0C\u8BF7\u91CD\u65B0 apm login");
3429
+ process.exit(1);
3430
+ }
3431
+ const url = buildAgentWsUrl(cfg.baseUrl, resolveApiKey(cfg));
3432
+ console.log(`[apm] \u8FDE\u63A5 ${cfg.baseUrl} \u2026`);
3433
+ await new Promise((resolve5, reject) => {
3434
+ const ws = new WebSocket(url);
3435
+ let stopHeartbeat;
3436
+ let shuttingDown = false;
3437
+ const shutdownAbort = new AbortController();
3438
+ const runSlots = createRunSlotPool();
3439
+ const activeTasks = /* @__PURE__ */ new Set();
3440
+ const activeRuns = /* @__PURE__ */ new Map();
3441
+ const pendingCancels = /* @__PURE__ */ new Set();
3442
+ const shutdown = async (code = 0) => {
3443
+ if (shuttingDown) return;
3444
+ shuttingDown = true;
3445
+ logAbortSignalStats(
3446
+ shutdownAbort.signal,
3447
+ "connect:shutdown-before-abort"
3448
+ );
3449
+ shutdownAbort.abort();
3450
+ logAbortSignalStats(shutdownAbort.signal, "connect:shutdown-after-abort");
3451
+ stopHeartbeat?.();
3452
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
3453
+ ws.terminate();
3454
+ }
3455
+ try {
3456
+ await Promise.race([
3457
+ Promise.all(activeTasks),
3458
+ new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
3459
+ ]);
3460
+ } catch {
3461
+ }
3462
+ resolve5();
3463
+ process.exit(code);
3464
+ };
3465
+ ws.on("open", () => {
3466
+ console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
3467
+ stopHeartbeat = startHeartbeat(ws, clientMachineId);
3468
+ });
3469
+ ws.on("message", (data) => {
3470
+ if (shuttingDown) return;
3471
+ const text = Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
3472
+ const parsed = parseAgentWsMessage(text);
3473
+ if (parsed === null) {
3474
+ console.error("[apm] \u6536\u5230\u65E0\u6548 JSON");
3475
+ return;
3476
+ }
3477
+ if (typeof parsed === "object" && parsed !== null && parsed.type === "heartbeat") {
3478
+ return;
3479
+ }
3480
+ const validated = validateAgentWsMessage(parsed, "outbound");
3481
+ if (!validated.ok) {
3482
+ console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
3483
+ return;
3484
+ }
3485
+ if (validated.data.type === "cancel") {
3486
+ const { messageId } = validated.data;
3487
+ pendingCancels.add(messageId);
3488
+ activeRuns.get(messageId)?.abort();
3489
+ return;
3490
+ }
3491
+ if (validated.data.type === "deploy") {
3492
+ const msg2 = validated.data;
3493
+ const perDeployController = new AbortController();
3494
+ const signal2 = AbortSignal.any([
3495
+ shutdownAbort.signal,
3496
+ perDeployController.signal
3497
+ ]);
3498
+ const task2 = (async () => {
3499
+ await runSlots.acquire();
3500
+ try {
3501
+ await handleInboundDeploy(cfg, msg2, signal2);
3502
+ } finally {
3503
+ runSlots.release();
3504
+ }
3505
+ })();
3506
+ activeTasks.add(task2);
3507
+ void task2.finally(() => {
3508
+ activeTasks.delete(task2);
3509
+ });
3510
+ return;
3511
+ }
3512
+ if (validated.data.type !== "message") {
3513
+ return;
3514
+ }
3515
+ const msg = validated.data;
3516
+ const perMessageController = new AbortController();
3517
+ activeRuns.set(msg.messageId, perMessageController);
3518
+ if (pendingCancels.has(msg.messageId)) {
3519
+ activeRuns.delete(msg.messageId);
3520
+ pendingCancels.delete(msg.messageId);
3521
+ return;
3522
+ }
3523
+ const signal = AbortSignal.any([
3524
+ shutdownAbort.signal,
3525
+ perMessageController.signal
3526
+ ]);
3527
+ const ctx = {
3528
+ shutdownSignal: shutdownAbort.signal,
3529
+ perMessageSignal: perMessageController.signal
3530
+ };
3531
+ const task = (async () => {
3532
+ await runSlots.acquire();
3533
+ try {
3534
+ if (signal.aborted || isUserCancelled(ctx)) return;
3535
+ try {
3536
+ await updateMessageStatus(cfg, msg.messageId, "TYPING");
3537
+ } catch (typingErr) {
3538
+ if (isUserCancelled(ctx)) return;
3539
+ console.error(
3540
+ "[apm] \u66F4\u65B0 TYPING \u72B6\u6001\u5931\u8D25:",
3541
+ typingErr instanceof Error ? typingErr.message : typingErr
3542
+ );
3543
+ try {
3544
+ await setMessageError(
3545
+ cfg,
3546
+ msg.messageId,
3547
+ typingErr instanceof Error ? typingErr.message : String(typingErr)
3548
+ );
3549
+ await updateMessageStatus(cfg, msg.messageId, "FAILED");
3550
+ } catch (statusErr) {
3551
+ console.error(
3552
+ "[apm] \u66F4\u65B0 FAILED \u72B6\u6001\u5931\u8D25:",
3553
+ statusErr instanceof Error ? statusErr.message : statusErr
3554
+ );
3555
+ }
3556
+ return;
3557
+ }
3558
+ await handleInboundMessage(cfg, msg, signal, ctx);
3559
+ } finally {
3560
+ runSlots.release();
3561
+ activeRuns.delete(msg.messageId);
3562
+ pendingCancels.delete(msg.messageId);
3563
+ }
3564
+ })();
3565
+ activeTasks.add(task);
3566
+ void task.finally(() => {
3567
+ activeTasks.delete(task);
3568
+ });
3569
+ });
3570
+ ws.on("close", (code, reason) => {
3607
3571
  console.log(
3608
- `[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
3609
- ${stack}`
3572
+ `[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
3610
3573
  );
3611
- }
3612
- return original.call(this, type, listener, options);
3613
- };
3614
- console.log(
3615
- "[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
3616
- );
3574
+ void shutdown();
3575
+ });
3576
+ ws.on("error", (err) => {
3577
+ console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
3578
+ reject(err);
3579
+ });
3580
+ process.on("SIGINT", () => {
3581
+ console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
3582
+ void shutdown();
3583
+ });
3584
+ process.on("SIGTERM", () => {
3585
+ void shutdown();
3586
+ });
3587
+ });
3617
3588
  }
3618
3589
 
3619
- // src/commands/connect/cursor-agent.ts
3620
- import {
3621
- Agent,
3622
- CursorAgentError
3623
- } from "@cursor/sdk";
3624
- import { setMaxListeners as setMaxListeners2 } from "node:events";
3625
-
3626
- // src/plan-format.ts
3627
- function formatPlanMarkdown(raw) {
3628
- let text = raw.trim();
3629
- if (!text) {
3630
- return text;
3631
- }
3632
- if (text.startsWith("{") && text.endsWith("}")) {
3633
- try {
3634
- const parsed = JSON.parse(text);
3635
- if (typeof parsed.plan === "string") {
3636
- return formatPlanMarkdown(parsed.plan);
3637
- }
3638
- } catch {
3639
- }
3590
+ // src/commands/create-pr.ts
3591
+ async function runCreatePr(options) {
3592
+ const sessionId = options.sessionId.trim();
3593
+ if (!sessionId) {
3594
+ console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
3595
+ process.exit(1);
3640
3596
  }
3641
- if (!text.includes("\n") && text.includes("\\n")) {
3642
- text = text.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
3597
+ const title = options.title.trim();
3598
+ if (!title) {
3599
+ console.error("[apm] \u8BF7\u901A\u8FC7 --title \u6307\u5B9A PR \u6807\u9898");
3600
+ process.exit(1);
3643
3601
  }
3644
- return text.replace(/\r\n/g, "\n").trimEnd() + "\n";
3602
+ const cfg = await ensureLoggedConfig();
3603
+ const api = createApmApiClient(cfg);
3604
+ const workdir = resolveWorkdirPath(options.cwd ?? process.cwd());
3605
+ const pr = await api.cli.createPullRequest({
3606
+ sessionId,
3607
+ workdir,
3608
+ title,
3609
+ content: options.content ?? ""
3610
+ });
3611
+ console.log(`[apm] PR \u5DF2\u5C31\u7EEA #${pr.number} (${pr.state}): ${pr.url}`);
3645
3612
  }
3646
3613
 
3647
- // src/session-utils.ts
3648
- var EventSession = class {
3649
- events = [];
3650
- dirtyIndices = /* @__PURE__ */ new Set();
3651
- constructor(prompt) {
3652
- this.events.push({
3653
- type: "input",
3654
- content: prompt
3655
- });
3656
- this.markDirty(0);
3657
- }
3658
- markDirty(index) {
3659
- this.dirtyIndices.add(index);
3614
+ // src/commands/deploy/deploy.ts
3615
+ import { spawnSync as spawnSync5 } from "node:child_process";
3616
+
3617
+ // src/commands/deploy/internal/apm-config.ts
3618
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
3619
+ import { homedir as homedir2 } from "node:os";
3620
+ import { join as join14, resolve as resolve4 } from "node:path";
3621
+ function loadApmConfig(options) {
3622
+ const p = resolve4(
3623
+ process.cwd(),
3624
+ options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
3625
+ );
3626
+ if (!existsSync13(p)) {
3627
+ console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
3628
+ process.exit(1);
3660
3629
  }
3661
- addEvent(event) {
3662
- const latestEvent = this.events[this.events.length - 1];
3663
- const formatedEvent = this.formatEvent(event);
3664
- if (!formatedEvent) {
3665
- return;
3666
- }
3667
- if (formatedEvent.type === "tool_call") {
3668
- const existingIndex = this.events.findIndex(
3669
- (e) => e.type === "tool_call" && e.call_id === formatedEvent.call_id
3670
- );
3671
- if (existingIndex >= 0) {
3672
- const existingToolCall = this.events[existingIndex];
3673
- existingToolCall.args = formatedEvent.args;
3674
- existingToolCall.result = formatedEvent.result;
3675
- existingToolCall.status = formatedEvent.status;
3676
- this.markDirty(existingIndex);
3677
- return;
3678
- }
3679
- this.events.push(formatedEvent);
3680
- this.markDirty(this.events.length - 1);
3681
- return;
3682
- }
3683
- if (formatedEvent.type === "status") {
3684
- return;
3685
- }
3686
- if (formatedEvent.type === "request") {
3687
- this.events.push(formatedEvent);
3688
- this.markDirty(this.events.length - 1);
3689
- return;
3690
- }
3691
- if (latestEvent?.type === formatedEvent.type) {
3692
- switch (formatedEvent.type) {
3693
- case "assistant":
3694
- latestEvent.content += formatedEvent.content;
3695
- break;
3696
- case "thinking":
3697
- latestEvent.content += formatedEvent.content;
3698
- break;
3699
- case "task":
3700
- latestEvent.status = formatedEvent.status;
3701
- latestEvent.text = formatedEvent.text;
3702
- break;
3703
- }
3704
- this.markDirty(this.events.length - 1);
3705
- return;
3706
- }
3707
- this.events.push(formatedEvent);
3708
- this.markDirty(this.events.length - 1);
3630
+ try {
3631
+ const raw = readFileSync11(p, "utf8");
3632
+ return JSON.parse(raw);
3633
+ } catch (e) {
3634
+ console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
3635
+ process.exit(1);
3709
3636
  }
3710
- formatEvent(event) {
3711
- switch (event.type) {
3712
- case "assistant": {
3713
- let content = "";
3714
- for (const block of event.message.content) {
3715
- if (block.type === "text" && block.text) {
3716
- content += block.text;
3717
- }
3718
- }
3719
- return {
3720
- type: "assistant",
3721
- content: content || event.content || ""
3722
- };
3723
- }
3724
- case "thinking":
3725
- return {
3726
- type: "thinking",
3727
- content: event.text || event.content || ""
3728
- };
3729
- case "tool_call":
3730
- return {
3731
- type: "tool_call",
3732
- args: event.args,
3733
- result: event.result,
3734
- status: event.status,
3735
- call_id: event.call_id,
3736
- name: event.name
3737
- };
3738
- case "task":
3739
- return {
3740
- type: "task",
3741
- status: event.status,
3742
- text: event.text
3743
- };
3744
- case "request":
3745
- return {
3746
- ...event,
3747
- type: "request"
3748
- };
3749
- case "status":
3750
- return { type: "status", status: event.status, message: event.message };
3751
- }
3637
+ }
3638
+ function req(v, field) {
3639
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3640
+ console.error(`apm.config.json \u4E2D backendDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
3641
+ process.exit(1);
3752
3642
  }
3753
- getDirtyEvents() {
3754
- return [...this.dirtyIndices].sort((a, b) => a - b).map((index) => {
3755
- const event = this.events[index];
3756
- return {
3757
- index,
3758
- type: event.type,
3759
- data: JSON.stringify(event)
3760
- };
3761
- });
3643
+ return v;
3644
+ }
3645
+ function reqTopLevelName(cfg) {
3646
+ const n = (cfg.name ?? "").trim();
3647
+ if (!n) {
3648
+ console.error(
3649
+ "\u8BF7\u5728 apm.config.json \u9876\u5C42\u914D\u7F6E name\uFF08\u4E0E\u524D\u7AEF\u5236\u54C1\u524D\u7F00\u3001\u540E\u7AEF\u955C\u50CF\u540D\u5171\u7528\uFF09"
3650
+ );
3651
+ process.exit(1);
3762
3652
  }
3763
- clearDirty(indices) {
3764
- for (const index of indices) {
3765
- this.dirtyIndices.delete(index);
3766
- }
3653
+ return n;
3654
+ }
3655
+ function reqBackendPositiveInt(v, field) {
3656
+ const n = Number(v);
3657
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
3658
+ console.error(`apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
3659
+ process.exit(1);
3767
3660
  }
3768
- /** 合并所有 assistant 片段,供剧场成员回传等场景使用 */
3769
- getAssistantText() {
3770
- return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
3661
+ return n;
3662
+ }
3663
+ function resolveBackendDeployFromApmConfig(cfg) {
3664
+ const b = cfg.backendDeploy ?? {};
3665
+ const protoRaw = req(b.remoteProtocol, "remoteProtocol").trim().toLowerCase();
3666
+ if (protoRaw !== "http" && protoRaw !== "https") {
3667
+ console.error(
3668
+ "apm.config.json \u4E2D backendDeploy.remoteProtocol \u53EA\u80FD\u4E3A http \u6216 https"
3669
+ );
3670
+ process.exit(1);
3771
3671
  }
3772
- /** plan 模式下 createPlan 工具 completed 时的 plan 字段(取最后一次) */
3773
- getCreatePlanContent() {
3774
- for (let i = this.events.length - 1; i >= 0; i--) {
3775
- const event = this.events[i];
3776
- if (event.type !== "tool_call") {
3777
- continue;
3778
- }
3779
- if (event.name !== "createPlan" || event.status !== "completed") {
3780
- continue;
3781
- }
3782
- const plan = event.args?.plan;
3783
- if (typeof plan === "string" && plan.trim()) {
3784
- return formatPlanMarkdown(plan);
3785
- }
3672
+ const remoteProtocol = protoRaw;
3673
+ let mappings = [];
3674
+ const rawPorts = b.containerPortsMappings;
3675
+ if (rawPorts !== void 0 && rawPorts !== null) {
3676
+ if (!Array.isArray(rawPorts)) {
3677
+ console.error(
3678
+ "apm.config.json \u4E2D backendDeploy.containerPortsMappings \u987B\u4E3A\u5B57\u7B26\u4E32\u6570\u7EC4\uFF08\u53EF\u4E3A\u7A7A\uFF0C\u7701\u7565\u5219\u4E0D\u52A0\u7AEF\u53E3\u6620\u5C04\uFF09"
3679
+ );
3680
+ process.exit(1);
3786
3681
  }
3787
- return void 0;
3788
- }
3789
- resolveLogContent() {
3790
- return this.events.map((event) => formatLogEvent(event.type, event)).join("\n");
3682
+ mappings = rawPorts.map((x) => String(x).trim()).filter(Boolean);
3791
3683
  }
3792
- };
3793
- function formatLogEvent(type, event) {
3794
- if (type === "input") {
3795
- return `## \u7528\u6237\u8F93\u5165
3796
-
3797
- ${String(event.content ?? "")}
3798
- `;
3684
+ return {
3685
+ name: reqTopLevelName(cfg),
3686
+ registryHost: req(b.registryHost, "registryHost").trim(),
3687
+ registryNamespace: req(b.registryNamespace, "registryNamespace").trim(),
3688
+ registryUser: req(b.registryUser, "registryUser").trim(),
3689
+ registryPassword: req(b.registryPassword, "registryPassword").trim(),
3690
+ remoteHost: req(b.remoteHost, "remoteHost").trim(),
3691
+ remotePort: reqBackendPositiveInt(b.remotePort, "remotePort"),
3692
+ remoteProtocol,
3693
+ caPath: b.caPath?.trim(),
3694
+ certPath: b.certPath?.trim(),
3695
+ keyPath: b.keyPath?.trim(),
3696
+ envFilePath: typeof b.envFilePath === "string" ? b.envFilePath.trim() : "",
3697
+ containerPortsMappings: mappings,
3698
+ dockerNetwork: b.dockerNetwork?.trim() || void 0
3699
+ };
3700
+ }
3701
+ function reqFe(v, field) {
3702
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3703
+ console.error(`apm.config.json \u4E2D frontendDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
3704
+ process.exit(1);
3799
3705
  }
3800
- if (type === "assistant") {
3801
- return `## \u6A21\u578B\u8F93\u51FA
3802
-
3803
- ${String(event.content ?? "")}
3804
- `;
3706
+ return v;
3707
+ }
3708
+ function resolveFrontendDeployFromApmConfig(cfg) {
3709
+ const f = cfg.frontendDeploy ?? {};
3710
+ const port = Number(f.port);
3711
+ return {
3712
+ endpoint: reqFe(f.endpoint, "endpoint").trim(),
3713
+ port: Number.isFinite(port) && port > 0 ? port : 9e3,
3714
+ useSsl: Boolean(f.useSsl),
3715
+ accessKey: reqFe(f.accessKey, "accessKey").trim(),
3716
+ secretKey: reqFe(f.secretKey, "secretKey").trim(),
3717
+ bucket: reqFe(f.bucket, "bucket").trim()
3718
+ };
3719
+ }
3720
+ function reqWd(v, field) {
3721
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3722
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
3723
+ process.exit(1);
3805
3724
  }
3806
- if (type === "thinking") {
3807
- return `## \u6A21\u578B\u601D\u8003
3808
-
3809
- ${String(event.content ?? "")}
3810
- `;
3725
+ return v;
3726
+ }
3727
+ function reqWisdomPositiveInt(v, field) {
3728
+ const n = Number(v);
3729
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
3730
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
3731
+ process.exit(1);
3811
3732
  }
3812
- if (type === "tool_call") {
3813
- return "````toolcall\n" + JSON.stringify(event, null, 2) + "\n````\n";
3733
+ return n;
3734
+ }
3735
+ function resolveWisdomDeployFromApmConfig(cfg) {
3736
+ const w = cfg.wisdomDeploy ?? {};
3737
+ return {
3738
+ host: reqWd(w.host, "host").trim(),
3739
+ port: reqWisdomPositiveInt(w.port, "port"),
3740
+ username: reqWd(w.username, "username").trim(),
3741
+ password: reqWd(w.password, "password").trim(),
3742
+ remotePath: reqWd(w.remotePath, "remotePath").trim()
3743
+ };
3744
+ }
3745
+ function reqHc(v, field) {
3746
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3747
+ console.error(`apm.config.json \u4E2D healthCheck.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
3748
+ process.exit(1);
3814
3749
  }
3815
- return `## \u672A\u77E5\u4E8B\u4EF6\uFF1A${type}
3816
-
3817
- \`\`\`json
3818
- ${JSON.stringify(event, null, 2)}
3819
- \`\`\``;
3750
+ return v;
3820
3751
  }
3821
-
3822
- // src/commands/connect/agent-session-registry.ts
3823
- import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "node:fs";
3824
- import { dirname as dirname4, resolve as resolve4 } from "node:path";
3825
- function registryPath(workdir, sessionId) {
3826
- return resolve4(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
3752
+ function reqWb(v, field) {
3753
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3754
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
3755
+ process.exit(1);
3756
+ }
3757
+ return v;
3827
3758
  }
3828
- function readRegistry(path12) {
3829
- if (!existsSync15(path12)) {
3830
- return {};
3759
+ function readEnvVar(env, name) {
3760
+ if (env[name] !== void 0) {
3761
+ return env[name];
3831
3762
  }
3832
- try {
3833
- const parsed = JSON.parse(readFileSync13(path12, "utf8"));
3834
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3835
- const result = {};
3836
- for (const [key, value] of Object.entries(
3837
- parsed
3838
- )) {
3839
- if (typeof value === "string" && value.trim()) {
3840
- result[key] = value.trim();
3841
- }
3763
+ if (process.platform === "win32") {
3764
+ const target = name.toUpperCase();
3765
+ for (const [key, value] of Object.entries(env)) {
3766
+ if (key.toUpperCase() === target) {
3767
+ return value;
3842
3768
  }
3843
- return result;
3844
3769
  }
3845
- } catch {
3846
3770
  }
3847
- return {};
3848
- }
3849
- function writeRegistry(path12, registry) {
3850
- mkdirSync6(dirname4(path12), { recursive: true });
3851
- writeFileSync11(path12, `${JSON.stringify(registry, null, 2)}
3852
- `, "utf8");
3853
- }
3854
- function loadSessionAgentId(workdir, sessionId, user) {
3855
- const registry = readRegistry(registryPath(workdir, sessionId));
3856
- return registry[user];
3771
+ return void 0;
3857
3772
  }
3858
- function saveSessionAgentId(workdir, sessionId, user, agentId) {
3859
- const path12 = registryPath(workdir, sessionId);
3860
- const registry = readRegistry(path12);
3861
- registry[user] = agentId;
3862
- writeRegistry(path12, registry);
3773
+ function expandWindowsEnvVars(pathStr, env = process.env) {
3774
+ return pathStr.replace(/%([^%]+)%/g, (_, name) => {
3775
+ const value = readEnvVar(env, name);
3776
+ return value ?? `%${name}%`;
3777
+ });
3863
3778
  }
3864
- function clearSessionAgentId(workdir, sessionId, user) {
3865
- const path12 = registryPath(workdir, sessionId);
3866
- const registry = readRegistry(path12);
3867
- if (!(user in registry)) {
3868
- return;
3779
+ function expandUserPath(pathStr, env = process.env) {
3780
+ const home = env.HOME ?? env.USERPROFILE ?? homedir2();
3781
+ const withEnv = expandWindowsEnvVars(pathStr, env);
3782
+ const expanded = withEnv.replace(/^~(?=\/|\\|$)/, home);
3783
+ if (/^[a-zA-Z]:[/\\]/.test(expanded)) {
3784
+ return expanded;
3869
3785
  }
3870
- delete registry[user];
3871
- writeRegistry(path12, registry);
3786
+ return resolve4(expanded);
3872
3787
  }
3873
-
3874
- // src/commands/connect/cursor-message-log.ts
3875
- var CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS = 2e3;
3876
- function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
3877
- let lastRunAt = 0;
3878
- let timer;
3879
- let latestSession;
3880
- let syncChain = Promise.resolve();
3881
- const syncDirtyEventsOnce = async (session) => {
3882
- const events = session.getDirtyEvents();
3883
- if (events.length === 0) {
3884
- return;
3885
- }
3886
- lastRunAt = Date.now();
3887
- try {
3888
- await syncCursorMessageLog(cfg, ctx, events);
3889
- session.clearDirty(events.map((event) => event.index));
3890
- } catch (err) {
3891
- onError(err);
3892
- }
3893
- };
3894
- const drainDirtyEvents = async (session) => {
3895
- while (session.getDirtyEvents().length > 0) {
3896
- await syncDirtyEventsOnce(session);
3897
- }
3898
- };
3899
- const enqueueSync = (session) => {
3900
- syncChain = syncChain.then(() => drainDirtyEvents(session));
3901
- };
3902
- return {
3903
- schedule(session) {
3904
- latestSession = session;
3905
- const now = Date.now();
3906
- const elapsed = now - lastRunAt;
3907
- if (elapsed >= CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS) {
3908
- if (timer) {
3909
- clearTimeout(timer);
3910
- timer = void 0;
3911
- }
3912
- enqueueSync(session);
3913
- return;
3914
- }
3915
- if (timer) {
3916
- return;
3917
- }
3918
- timer = setTimeout(() => {
3919
- timer = void 0;
3920
- enqueueSync(latestSession);
3921
- }, CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS - elapsed);
3922
- },
3923
- async flush(session) {
3924
- latestSession = session;
3925
- if (timer) {
3926
- clearTimeout(timer);
3927
- timer = void 0;
3928
- }
3929
- await syncChain;
3930
- await drainDirtyEvents(session);
3788
+ var MAVEN_REPO_ENV_KEYS = [
3789
+ "MAVEN_LOCAL_REPO",
3790
+ "M2_REPO",
3791
+ "MAVEN_REPOSITORY"
3792
+ ];
3793
+ function readMavenLocalRepoFromMavenOpts(mavenOpts, env = process.env) {
3794
+ const match = mavenOpts.match(/-Dmaven\.repo\.local=(?:"([^"]+)"|(\S+))/);
3795
+ const raw = match?.[1]?.trim() || match?.[2]?.trim();
3796
+ return raw ? expandUserPath(raw, env) : null;
3797
+ }
3798
+ function readMavenLocalRepoFromEnv(env = process.env) {
3799
+ for (const key of MAVEN_REPO_ENV_KEYS) {
3800
+ const raw = readEnvVar(env, key)?.trim();
3801
+ if (raw) {
3802
+ return { path: expandUserPath(raw, env), key };
3931
3803
  }
3932
- };
3933
- }
3934
- async function syncCursorMessageLog(cfg, ctx, events) {
3935
- const agentId = ctx.agentId.trim();
3936
- if (!agentId || events.length === 0) {
3937
- return;
3938
3804
  }
3939
- const api = createApmApiClient(cfg);
3940
- await api.cli.upsertCursorMessageLog({
3941
- sessionId: ctx.sessionId,
3942
- messageId: ctx.messageId,
3943
- agentId,
3944
- events
3945
- });
3805
+ const mavenOpts = readEnvVar(env, "MAVEN_OPTS")?.trim();
3806
+ if (mavenOpts) {
3807
+ const path12 = readMavenLocalRepoFromMavenOpts(mavenOpts, env);
3808
+ if (path12) {
3809
+ return { path: path12, key: "MAVEN_OPTS" };
3810
+ }
3811
+ }
3812
+ return null;
3946
3813
  }
3947
-
3948
- // src/commands/connect/append-message-tool.ts
3949
- function createAppendMessageCustomTools(cfg, messageId) {
3950
- return {
3951
- append_message: {
3952
- description: "\u5411\u5F53\u524D\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u56DE\u590D\u5185\u5BB9\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8865\u5145\u8FDB\u5C55\uFF1B\u88AB @ \u65F6\u6536\u5230\u540E\u5E94\u5148\u7B80\u77ED\u786E\u8BA4\u518D\u6267\u884C\u4EFB\u52A1\u3002",
3953
- inputSchema: {
3954
- type: "object",
3955
- properties: {
3956
- content: {
3957
- type: "string",
3958
- description: "\u8981\u53D1\u9001\u5230\u7FA4\u91CC\u7684\u56DE\u590D\u5185\u5BB9"
3959
- }
3960
- },
3961
- required: ["content"]
3962
- },
3963
- execute: async (args) => {
3964
- const content = typeof args.content === "string" ? args.content.trim() : "";
3965
- if (!content) {
3966
- return {
3967
- content: [{ type: "text", text: "content \u4E0D\u80FD\u4E3A\u7A7A" }],
3968
- isError: true
3969
- };
3970
- }
3971
- try {
3972
- await appendMessageContent(cfg, messageId, content);
3973
- console.log(`[apm] append_message \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
3974
- return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
3975
- } catch (err) {
3976
- const detail = err instanceof Error ? err.message : String(err);
3977
- return {
3978
- content: [{ type: "text", text: `\u8FFD\u52A0\u6D88\u606F\u5931\u8D25: ${detail}` }],
3979
- isError: true
3980
- };
3981
- }
3982
- }
3814
+ function readMavenLocalRepoFromSettings() {
3815
+ const settingsPath = join14(homedir2(), ".m2", "settings.xml");
3816
+ if (!existsSync13(settingsPath)) {
3817
+ return null;
3818
+ }
3819
+ try {
3820
+ const xml = readFileSync11(settingsPath, "utf8");
3821
+ const match = xml.match(
3822
+ /<localRepository>\s*([^<]+?)\s*<\/localRepository>/
3823
+ );
3824
+ const raw = match?.[1]?.trim();
3825
+ if (!raw) {
3826
+ return null;
3983
3827
  }
3984
- };
3828
+ return expandUserPath(raw);
3829
+ } catch {
3830
+ return null;
3831
+ }
3985
3832
  }
3986
-
3987
- // src/commands/connect/ask-question-tool.ts
3988
- function createAskQuestionMockTool(options) {
3833
+ function resolveMavenLocalRepoWithSource() {
3834
+ const fromEnv = readMavenLocalRepoFromEnv();
3835
+ if (fromEnv) {
3836
+ return {
3837
+ path: fromEnv.path,
3838
+ source: "env",
3839
+ sourceDetail: fromEnv.key
3840
+ };
3841
+ }
3842
+ const fromSettings = readMavenLocalRepoFromSettings();
3843
+ if (fromSettings) {
3844
+ return {
3845
+ path: fromSettings,
3846
+ source: "settings",
3847
+ sourceDetail: "~/.m2/settings.xml"
3848
+ };
3849
+ }
3989
3850
  return {
3990
- description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
3991
- inputSchema: {
3992
- type: "object",
3993
- properties: {
3994
- title: {
3995
- type: "string",
3996
- description: "Optional title for the questions form"
3997
- },
3998
- questions: {
3999
- type: "array",
4000
- minItems: 1,
4001
- items: {
4002
- type: "object",
4003
- properties: {
4004
- id: { type: "string" },
4005
- prompt: { type: "string" },
4006
- allow_multiple: { type: "boolean" },
4007
- options: {
4008
- type: "array",
4009
- minItems: 2,
4010
- items: {
4011
- type: "object",
4012
- properties: {
4013
- id: { type: "string" },
4014
- label: { type: "string" }
4015
- },
4016
- required: ["id", "label"]
4017
- }
4018
- }
4019
- },
4020
- required: ["id", "prompt", "options"]
4021
- }
4022
- }
4023
- },
4024
- required: ["questions"]
4025
- },
4026
- execute: async (args) => {
4027
- const payload = JSON.stringify(args, null, 2);
4028
- console.log(`[apm] AskQuestion mock \u8C03\u7528:
4029
- ${payload}`);
4030
- options?.onInvoke?.(args);
4031
- return `[mock] AskQuestion \u5DF2\u8BB0\u5F55\uFF08\u672A\u521B\u5EFA\u4EFB\u52A1\u95EE\u9898\u3001\u672A\u7B49\u5F85\u7528\u6237\u56DE\u7B54\uFF09\u3002\u53C2\u6570:
4032
- ${payload}`;
4033
- }
3851
+ path: join14(homedir2(), ".m2", "repository"),
3852
+ source: "default",
3853
+ sourceDetail: "~/.m2/repository"
4034
3854
  };
4035
3855
  }
4036
-
4037
- // src/commands/connect/cursor-custom-tools.ts
4038
- var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
4039
- AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
4040
- \u9700\u8981\u5411\u7528\u6237\u786E\u8BA4\u65F6\uFF0C\u8BF7\u8C03\u7528 AskQuestion\uFF08\u7ECF CallMcpTool / custom-user-tools\uFF09\uFF0C\u4E0D\u8981\u5047\u8BBE IDE \u5185\u7F6E AskQuestion \u4E0D\u53EF\u7528\u3002
4041
- \u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
4042
- function createCursorCustomTools(cfg, messageId, options) {
3856
+ function resolveWisdomBackendDeployFromApmConfig(cfg) {
3857
+ const projectName = reqTopLevelName(cfg);
3858
+ const w = cfg.wisdomDeploy ?? {};
3859
+ const h = cfg.healthCheck ?? {};
3860
+ const jarPath = reqWb(w.jarPath, "jarPath").trim();
3861
+ const remoteAppDir = posixDirname(jarPath);
3862
+ const healthPort = Number(reqHc(h.port, "port"));
3863
+ const healthTimeout = Number(reqHc(h.timeout, "timeout"));
3864
+ if (!Number.isFinite(healthPort) || !Number.isInteger(healthPort) || healthPort < 1) {
3865
+ console.error("apm.config.json \u4E2D healthCheck.port \u987B\u4E3A\u6B63\u6574\u6570");
3866
+ process.exit(1);
3867
+ }
3868
+ if (!Number.isFinite(healthTimeout) || !Number.isInteger(healthTimeout) || healthTimeout < 1) {
3869
+ console.error("apm.config.json \u4E2D healthCheck.timeout \u987B\u4E3A\u6B63\u6574\u6570");
3870
+ process.exit(1);
3871
+ }
3872
+ const mavenLocalRepo = resolveMavenLocalRepoWithSource();
4043
3873
  return {
4044
- ...createAppendMessageCustomTools(cfg, messageId),
4045
- AskQuestion: createAskQuestionMockTool({
4046
- onInvoke: options?.onAskQuestion
4047
- })
3874
+ projectName,
3875
+ host: reqWd(w.host, "host").trim(),
3876
+ port: reqWisdomPositiveInt(w.port, "port"),
3877
+ username: reqWd(w.username, "username").trim(),
3878
+ password: reqWd(w.password, "password").trim(),
3879
+ remoteVueDistDir: reqWd(w.remotePath, "remotePath").trim(),
3880
+ remoteAppDir,
3881
+ remoteLibDir: `${remoteAppDir}/lib`,
3882
+ startupJar: posixBasename(jarPath),
3883
+ packageName: `${projectName}.jar.zip`,
3884
+ mavenLocalRepo: mavenLocalRepo.path,
3885
+ mavenLocalRepoSource: mavenLocalRepo.source,
3886
+ mavenLocalRepoSourceDetail: mavenLocalRepo.sourceDetail,
3887
+ healthCheckPort: healthPort,
3888
+ healthCheckContext: reqHc(h.context, "context").trim(),
3889
+ healthCheckTimeout: healthTimeout
4048
3890
  };
4049
3891
  }
4050
- function withPlanModeToolHint(prompt, mode) {
4051
- if (mode !== "plan") {
4052
- return prompt;
3892
+ function posixDirname(p) {
3893
+ const normalized = p.replace(/\\/g, "/");
3894
+ const idx = normalized.lastIndexOf("/");
3895
+ if (idx <= 0) {
3896
+ return normalized.startsWith("/") ? "/" : ".";
4053
3897
  }
4054
- return `${prompt.trim()}
4055
-
4056
- ${PLAN_MODE_ASK_QUESTION_HINT}`;
3898
+ return normalized.slice(0, idx);
3899
+ }
3900
+ function posixBasename(p) {
3901
+ const normalized = p.replace(/\\/g, "/");
3902
+ const idx = normalized.lastIndexOf("/");
3903
+ return idx >= 0 ? normalized.slice(idx + 1) : normalized;
4057
3904
  }
4058
3905
 
4059
- // src/commands/connect/cursor-agent.ts
4060
- setMaxListeners2(50);
4061
- installAbortSignalDebug();
4062
- var noopRemoteLogSync = {
4063
- schedule(_session) {
4064
- },
4065
- async flush(_session) {
4066
- }
4067
- };
4068
- var logCtx = (ctx, agentId) => ({
4069
- sessionId: ctx.sessionId,
4070
- messageId: ctx.messageId,
4071
- agentId
4072
- });
4073
- function formatCursorRunFailure(runId, options) {
4074
- const details = [
4075
- options?.statusError?.trim(),
4076
- options?.resultText?.trim()
4077
- ].filter((value, index, arr) => {
4078
- if (!value) return false;
4079
- return arr.indexOf(value) === index;
4080
- });
4081
- if (details.length === 0) {
4082
- return `Cursor run \u5931\u8D25: ${runId}`;
3906
+ // src/commands/deploy/internal/wisdom-auto-deploy.ts
3907
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
3908
+ import path3 from "node:path";
3909
+ import { spawnSync as spawnSync4 } from "node:child_process";
3910
+
3911
+ // src/commands/deploy/internal/wisdom-backend-deploy.ts
3912
+ import {
3913
+ existsSync as existsSync14,
3914
+ mkdirSync as mkdirSync6,
3915
+ readdirSync as readdirSync5,
3916
+ readFileSync as readFileSync12,
3917
+ statSync as statSync5,
3918
+ writeFileSync as writeFileSync12
3919
+ } from "node:fs";
3920
+ import { spawnSync as spawnSync3 } from "node:child_process";
3921
+ import path2 from "node:path";
3922
+ import { Client } from "ssh2";
3923
+ import JSZip2 from "jszip";
3924
+ import SftpClient2 from "ssh2-sftp-client";
3925
+
3926
+ // src/commands/deploy/internal/wisdom-sftp.ts
3927
+ import { readdir, readFile, unlink, writeFile } from "node:fs/promises";
3928
+ import path from "node:path";
3929
+ import JSZip from "jszip";
3930
+ import SftpClient from "ssh2-sftp-client";
3931
+ async function addDirToZip(dir, zipFolder) {
3932
+ const entries = await readdir(dir, { withFileTypes: true });
3933
+ for (const entry of entries) {
3934
+ const fullPath = path.join(dir, entry.name);
3935
+ if (entry.isDirectory()) {
3936
+ const folder = zipFolder.folder(entry.name);
3937
+ if (folder) {
3938
+ await addDirToZip(fullPath, folder);
3939
+ }
3940
+ } else {
3941
+ const content = await readFile(fullPath);
3942
+ zipFolder.file(entry.name, content);
3943
+ }
4083
3944
  }
4084
- return `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
4085
3945
  }
4086
- async function obtainAgent(ctx) {
4087
- const agentOptions = {
4088
- apiKey: ctx.apiKey,
4089
- model: { id: ctx.model || "default" },
4090
- local: {
4091
- cwd: ctx.cwd,
4092
- ...ctx.customTools ? { customTools: ctx.customTools } : {}
4093
- },
4094
- ...ctx.mode ? { mode: ctx.mode } : {}
4095
- // mcpServers: createPlaywrightMcpServers(),
4096
- };
4097
- const explicitAgentId = ctx.resumeAgentId?.trim();
4098
- const savedAgentId = explicitAgentId || (ctx.user ? loadSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user) : void 0);
4099
- if (savedAgentId) {
3946
+ async function zipDirectory(distDir, zipPath) {
3947
+ console.error(`\u538B\u7F29\u76EE\u5F55: ${distDir}`);
3948
+ const zip = new JSZip();
3949
+ await addDirToZip(distDir, zip);
3950
+ const content = await zip.generateAsync({
3951
+ type: "nodebuffer",
3952
+ compression: "DEFLATE",
3953
+ compressionOptions: { level: 6 }
3954
+ });
3955
+ await writeFile(zipPath, content);
3956
+ const sizeMb = (content.length / 1024 / 1024).toFixed(2);
3957
+ console.error(`\u5DF2\u751F\u6210: ${zipPath} (${sizeMb} MB)`);
3958
+ return content.length;
3959
+ }
3960
+ async function ensureRemoteDir(sftp, dir) {
3961
+ const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
3962
+ let current = dir.startsWith("/") ? "" : ".";
3963
+ for (const part of parts) {
3964
+ current = current ? `${current}/${part}` : `/${part}`;
4100
3965
  try {
4101
- const agent2 = await Agent.resume(savedAgentId, agentOptions);
4102
- console.log(
4103
- `[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}${explicitAgentId ? "\uFF08\u53C2\u6570\u6307\u5B9A\uFF09" : ""}`
4104
- );
4105
- if (ctx.user) {
4106
- saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent2.agentId);
4107
- }
4108
- return { agent: agent2, resumed: true };
4109
- } catch (err) {
4110
- console.warn(
4111
- `[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
4112
- err instanceof Error ? err.message : err
4113
- );
4114
- if (!explicitAgentId && ctx.user) {
4115
- clearSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user);
4116
- }
3966
+ await sftp.mkdir(current, true);
3967
+ } catch {
4117
3968
  }
4118
3969
  }
4119
- const agent = await Agent.create(agentOptions);
4120
- if (ctx.user) {
4121
- saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent.agentId);
4122
- }
4123
- return { agent, resumed: false };
4124
3970
  }
4125
- async function runCursorAgent(cfg, ctx, options) {
4126
- const signal = options?.signal;
4127
- logAbortSignalStats(signal, "runCursorAgent:start");
4128
- if (signal?.aborted) {
4129
- throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
4130
- }
4131
- const apiKey = ctx.apiKey.trim();
4132
- if (!apiKey) {
4133
- throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
4134
- }
4135
- const workdir = resolveWorkdirPath(ctx.workdir);
4136
- const customTools = createCursorCustomTools(cfg, ctx.messageId, {
4137
- onAskQuestion: options?.onAskQuestion
4138
- });
4139
- const prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
4140
- console.log(
4141
- `[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} sessionId=${ctx.sessionId} cwd=${workdir}`
4142
- );
4143
- const { agent, resumed } = await obtainAgent({
4144
- apiKey,
4145
- model: ctx.model,
4146
- cwd: workdir,
4147
- workdir,
4148
- sessionId: ctx.sessionId,
4149
- user: ctx.user,
4150
- mode: ctx.mode,
4151
- resumeAgentId: ctx.resumeAgentId,
4152
- customTools
3971
+ function execCommand(client, command) {
3972
+ return new Promise((resolve5, reject) => {
3973
+ client.exec(command, (err, stream) => {
3974
+ if (err) return reject(err);
3975
+ let stdout = "";
3976
+ let stderr = "";
3977
+ stream.on("close", (code) => {
3978
+ if (code !== 0) {
3979
+ reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
3980
+ return;
3981
+ }
3982
+ resolve5(stdout);
3983
+ }).on("data", (data) => {
3984
+ stdout += data.toString();
3985
+ });
3986
+ stream.stderr.on("data", (data) => {
3987
+ stderr += data.toString();
3988
+ });
3989
+ });
4153
3990
  });
4154
- const eventSession = new EventSession(prompt);
4155
- const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorMessageLogSync(
4156
- cfg,
4157
- logCtx(ctx, agent.agentId),
4158
- (err) => {
4159
- console.warn(
4160
- "[apm] \u540C\u6B65 Cursor \u6D88\u606F\u65E5\u5FD7\u5931\u8D25:",
4161
- err instanceof Error ? err.message : err
4162
- );
4163
- }
3991
+ }
3992
+ function shellSingleQuote(value) {
3993
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
3994
+ }
3995
+ function buildClearRemoteDirExceptZipCommand(target) {
3996
+ const normalized = target.replace(/\\/g, "/").replace(/\/$/, "");
3997
+ const quotedTarget = shellSingleQuote(normalized);
3998
+ const script = [
3999
+ `T=${quotedTarget}`,
4000
+ "S=$(mktemp -d)",
4001
+ 'while IFS= read -r -d "" z; do r="${z#${T}/}"',
4002
+ 'mkdir -p "${S}/$(dirname "$r")"',
4003
+ 'mv "$z" "${S}/${r}"',
4004
+ 'done < <(find "$T" -mindepth 1 -type f -iname "*.zip" -print0)',
4005
+ 'rm -rf "${T}"/*',
4006
+ 'while IFS= read -r -d "" r; do r="${r#./}"',
4007
+ 'mkdir -p "${T}/$(dirname "$r")"',
4008
+ 'mv "${S}/${r}" "${T}/${r}"',
4009
+ 'done < <(cd "$S" 2>/dev/null && find . -type f -print0)',
4010
+ 'rm -rf "$S"'
4011
+ ].join("; ");
4012
+ return `bash -c ${shellSingleQuote(script)}`;
4013
+ }
4014
+ async function uploadAndMaybeExtract(settings, localZip, extract) {
4015
+ const remoteZipPath = `${settings.remotePath.replace(/\/$/, "")}/dist.zip`;
4016
+ console.error(
4017
+ `\u8FDE\u63A5 ${settings.username}@${settings.host}:${settings.port} ...`
4164
4018
  );
4165
- let activeRun;
4166
- const abortRun = () => {
4167
- if (!activeRun?.supports("cancel")) return;
4168
- void activeRun.cancel().catch(() => void 0);
4169
- };
4170
- signal?.addEventListener("abort", abortRun, { once: true });
4171
- logAbortSignalStats(signal, "runCursorAgent:after-addListener");
4019
+ const sftp = new SftpClient();
4172
4020
  try {
4173
- const run = await agent.send(prompt, {
4174
- ...ctx.mode ? { mode: ctx.mode } : {},
4175
- // mcpServers: createPlaywrightMcpServers(),
4176
- local: {
4177
- ...options?.forceSend ? { force: true } : {},
4178
- customTools
4179
- }
4021
+ await sftp.connect({
4022
+ host: settings.host,
4023
+ port: settings.port,
4024
+ username: settings.username,
4025
+ password: settings.password,
4026
+ readyTimeout: 2e4,
4027
+ tryKeyboard: true
4180
4028
  });
4181
- activeRun = run;
4182
- logAbortSignalStats(signal, "runCursorAgent:after-send");
4183
- console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
4184
- let lastRunErrorStatus;
4185
- for await (const event of run.stream()) {
4186
- if (signal?.aborted) {
4187
- abortRun();
4188
- throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
4189
- }
4190
- if (event.type === "status" && event.status === "ERROR") {
4191
- const message = event.message?.trim();
4192
- if (message) {
4193
- lastRunErrorStatus = message;
4194
- console.error(
4195
- `[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
4196
- );
4197
- }
4198
- }
4199
- options?.onStreamEvent?.(event);
4200
- eventSession.addEvent(event);
4201
- syncRemoteLog.schedule(eventSession);
4029
+ await ensureRemoteDir(sftp, settings.remotePath);
4030
+ await sftp.put(localZip, remoteZipPath);
4031
+ console.error(` \u2713 ${localZip} -> ${remoteZipPath}`);
4032
+ if (extract) {
4033
+ const target = settings.remotePath.replace(/\/$/, "");
4034
+ const client = sftp.client;
4035
+ const clearCmd = buildClearRemoteDirExceptZipCommand(target);
4036
+ console.error(`\u8FDC\u7A0B\u6E05\u7406\u89E3\u538B\u76EE\u5F55: ${clearCmd}`);
4037
+ await execCommand(client, clearCmd);
4038
+ const unzipCmd = `unzip -o "${remoteZipPath}" -d "${target}"`;
4039
+ console.error(`\u8FDC\u7A0B\u89E3\u538B: ${unzipCmd}`);
4040
+ await execCommand(client, unzipCmd);
4041
+ console.error("\u8FDC\u7A0B\u89E3\u538B\u5B8C\u6210!");
4202
4042
  }
4203
- await syncRemoteLog.flush(eventSession);
4204
- const result = await run.wait();
4205
- if (result.status === "error") {
4206
- const failureMessage = formatCursorRunFailure(result.id, {
4207
- statusError: lastRunErrorStatus,
4208
- resultText: result.result
4209
- });
4210
- console.error(`[apm] ${failureMessage}`);
4211
- if (resumed) {
4212
- clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
4213
- }
4214
- throw new Error(failureMessage);
4043
+ console.error(extract ? "\u90E8\u7F72\u5B8C\u6210!" : "\u4E0A\u4F20\u5B8C\u6210!");
4044
+ } finally {
4045
+ await sftp.end();
4046
+ }
4047
+ }
4048
+ async function runWisdomSftpDeploy(params) {
4049
+ const zipPath = path.join(params.localDir, "..", ".deploy-sftp-dist.zip");
4050
+ const resolvedZipPath = path.resolve(zipPath);
4051
+ let zipSizeBytes = 0;
4052
+ try {
4053
+ zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
4054
+ await uploadAndMaybeExtract(
4055
+ params.settings,
4056
+ resolvedZipPath,
4057
+ params.extract
4058
+ );
4059
+ } finally {
4060
+ try {
4061
+ await unlink(resolvedZipPath);
4062
+ console.error(`\u5DF2\u6E05\u7406\u672C\u5730\u4E34\u65F6\u6587\u4EF6: ${resolvedZipPath}`);
4063
+ } catch {
4064
+ }
4065
+ }
4066
+ return {
4067
+ ok: true,
4068
+ localDir: params.localDir,
4069
+ host: params.settings.host,
4070
+ remotePath: params.settings.remotePath,
4071
+ zipSizeBytes,
4072
+ extracted: params.extract
4073
+ };
4074
+ }
4075
+
4076
+ // src/commands/deploy/internal/wisdom-backend-deploy.ts
4077
+ var SPRINGBOOT_JAVA_OPTS = "-XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -Xms512M -Xmx1G";
4078
+ var MAVEN_MODULE = "jeecg-module-system/jeecg-system-start";
4079
+ var MAVEN_PROFILE = "dev";
4080
+ function log(message) {
4081
+ const now = /* @__PURE__ */ new Date();
4082
+ const hh = String(now.getHours()).padStart(2, "0");
4083
+ const mm = String(now.getMinutes()).padStart(2, "0");
4084
+ const ss = String(now.getSeconds()).padStart(2, "0");
4085
+ console.error(`[${hh}:${mm}:${ss}] ${message}`);
4086
+ }
4087
+ function fail(message) {
4088
+ log(`ERROR: ${message}`);
4089
+ process.exit(1);
4090
+ }
4091
+ function expandPath(pathStr) {
4092
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
4093
+ return path2.resolve(pathStr.replace(/^~(?=\/|\\|$)/, home));
4094
+ }
4095
+ function quoteForShell(value) {
4096
+ if (process.platform === "win32") {
4097
+ return `"${value.replace(/"/g, '""')}"`;
4098
+ }
4099
+ return shellSingleQuote(value);
4100
+ }
4101
+ function formatMavenLocalRepoArg(repoPath) {
4102
+ if (process.platform === "win32") {
4103
+ return `-Dmaven.repo.local=${quoteForShell(repoPath)}`;
4104
+ }
4105
+ return `-Dmaven.repo.local=${repoPath}`;
4106
+ }
4107
+ function deployCacheDir() {
4108
+ return path2.join(workspaceApmDir(), "deploy", ".deploy_cache");
4109
+ }
4110
+ function manifestFilePath() {
4111
+ return path2.join(deployCacheDir(), "manifest.json");
4112
+ }
4113
+ function getTargetDir(projectRoot) {
4114
+ return path2.join(projectRoot, MAVEN_MODULE, "target");
4115
+ }
4116
+ function relativeKey(projectRoot, filePath) {
4117
+ return path2.relative(projectRoot, filePath).split(path2.sep).join("/");
4118
+ }
4119
+ function fileSignature(filePath) {
4120
+ const stat2 = statSync5(filePath);
4121
+ return { size: stat2.size, mtime: stat2.mtimeMs / 1e3 };
4122
+ }
4123
+ function loadManifest4() {
4124
+ const manifestPath2 = manifestFilePath();
4125
+ if (!existsSync14(manifestPath2)) {
4126
+ return {};
4127
+ }
4128
+ return JSON.parse(readFileSync12(manifestPath2, "utf8"));
4129
+ }
4130
+ function saveManifest4(manifest) {
4131
+ const dir = deployCacheDir();
4132
+ mkdirSync6(dir, { recursive: true });
4133
+ writeFileSync12(manifestFilePath(), JSON.stringify(manifest, null, 2), "utf8");
4134
+ }
4135
+ function isProjectLibJar(jarName) {
4136
+ return jarName.startsWith("jeecg-");
4137
+ }
4138
+ function shouldUploadLibFile(localPath, remoteAttr, manifest, projectRoot) {
4139
+ if (!remoteAttr) {
4140
+ return [false, "\u8FDC\u7A0B\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7"];
4141
+ }
4142
+ const localSize = statSync5(localPath).size;
4143
+ const remoteSize = remoteAttr.size;
4144
+ if (localSize !== remoteSize) {
4145
+ return [true, `\u5927\u5C0F\u53D8\u5316 ${remoteSize} -> ${localSize}`];
4146
+ }
4147
+ if (isProjectLibJar(path2.basename(localPath)) && manifest) {
4148
+ const key = relativeKey(projectRoot, localPath);
4149
+ const current = fileSignature(localPath);
4150
+ const previous = manifest[key];
4151
+ if (!previous) {
4152
+ return [true, "\u9879\u76EE\u6A21\u5757\u672A\u8BB0\u5F55"];
4215
4153
  }
4216
- if (result.status === "cancelled") {
4217
- throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
4154
+ if (previous.size !== current.size) {
4155
+ return [true, "\u9879\u76EE\u6A21\u5757\u5927\u5C0F\u53D8\u5316"];
4218
4156
  }
4219
- console.log(`[apm] Cursor Agent \u5B8C\u6210 messageId=${ctx.messageId}`);
4220
- const artifacts = await agent.listArtifacts().catch(() => []);
4221
- const artifactDocuments = [];
4222
- for (const artifact of artifacts) {
4223
- try {
4224
- const content = (await agent.downloadArtifact(artifact.path)).toString(
4225
- "utf8"
4226
- );
4227
- artifactDocuments.push({ path: artifact.path, content });
4228
- } catch (err) {
4229
- console.warn(
4230
- `[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
4231
- err instanceof Error ? err.message : err
4232
- );
4233
- }
4157
+ if (previous.mtime < current.mtime) {
4158
+ return [true, "\u9879\u76EE\u6A21\u5757\u91CD\u65B0\u6784\u5EFA"];
4234
4159
  }
4235
- return {
4236
- runId: result.id,
4237
- agentId: agent.agentId,
4238
- status: result.status,
4239
- result: result.result,
4240
- durationMs: result.durationMs,
4241
- assistantText: eventSession.getAssistantText(),
4242
- createPlan: eventSession.getCreatePlanContent(),
4243
- artifacts,
4244
- artifactDocuments
4245
- };
4246
- } catch (err) {
4247
- if (err instanceof CursorAgentError) {
4248
- if (resumed) {
4249
- clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
4250
- }
4251
- throw new Error(
4252
- `Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
4253
- );
4160
+ }
4161
+ return [false, "\u5927\u5C0F\u4E00\u81F4\uFF0C\u8DF3\u8FC7"];
4162
+ }
4163
+ function listLibFilesToUpload(localLibDir, remoteStats, projectRoot, manifest = null) {
4164
+ const entries = [];
4165
+ const jarFiles = readdirSync5(localLibDir).filter((name) => name.endsWith(".jar")).sort();
4166
+ for (const jarName of jarFiles) {
4167
+ const jarPath = path2.join(localLibDir, jarName);
4168
+ const remoteAttr = remoteStats.get(jarName);
4169
+ const [shouldUpload, reason] = shouldUploadLibFile(
4170
+ jarPath,
4171
+ remoteAttr,
4172
+ manifest,
4173
+ projectRoot
4174
+ );
4175
+ if (shouldUpload) {
4176
+ entries.push({ path: jarPath, arcname: jarName, reason });
4254
4177
  }
4255
- throw err;
4256
- } finally {
4257
- logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
4258
- signal?.removeEventListener("abort", abortRun);
4259
- logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
4260
- await agent[Symbol.asyncDispose]();
4261
4178
  }
4179
+ return entries;
4262
4180
  }
4263
-
4264
- // src/commands/connect/ensure-message-reply.ts
4265
- var DEFAULT_REPLY = "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u3002";
4266
- function resolveMessageReplyFallback(fallback) {
4267
- for (const candidate of [
4268
- fallback.assistantText,
4269
- fallback.result,
4270
- fallback.createPlan
4271
- ]) {
4272
- const trimmed = candidate?.trim();
4273
- if (trimmed) {
4274
- return trimmed;
4181
+ function updateManifestEntries(manifest, entries, projectRoot) {
4182
+ for (const entry of entries) {
4183
+ manifest[relativeKey(projectRoot, entry.path)] = fileSignature(entry.path);
4184
+ }
4185
+ return manifest;
4186
+ }
4187
+ async function createUpdatePackage(entries, packageName) {
4188
+ const dir = deployCacheDir();
4189
+ mkdirSync6(dir, { recursive: true });
4190
+ const zipPath = path2.join(dir, packageName);
4191
+ log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${path2.basename(zipPath)}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
4192
+ const zip = new JSZip2();
4193
+ for (const entry of entries) {
4194
+ const content = readFileSync12(entry.path);
4195
+ zip.file(entry.arcname, content);
4196
+ log(` \u6253\u5305: ${entry.arcname} (${entry.reason})`);
4197
+ }
4198
+ const buffer = await zip.generateAsync({
4199
+ type: "nodebuffer",
4200
+ compression: "DEFLATE",
4201
+ compressionOptions: { level: 6 }
4202
+ });
4203
+ writeFileSync12(zipPath, buffer);
4204
+ return zipPath;
4205
+ }
4206
+ function getMvnExecutable() {
4207
+ const isWin = process.platform === "win32";
4208
+ const candidates = isWin ? ["mvn.cmd", "mvn.bat", "mvn"] : ["mvn"];
4209
+ for (const name of candidates) {
4210
+ const result = spawnSync3(isWin ? `where ${name}` : `which ${name}`, {
4211
+ encoding: "utf8",
4212
+ shell: true
4213
+ });
4214
+ if (result.status === 0 && result.stdout.trim()) {
4215
+ return result.stdout.trim().split(/\r?\n/)[0].trim();
4275
4216
  }
4276
4217
  }
4277
- return DEFAULT_REPLY;
4218
+ fail("\u672A\u627E\u5230 mvn \u547D\u4EE4\uFF0C\u8BF7\u786E\u8BA4 Maven \u5DF2\u5B89\u88C5\u5E76\u52A0\u5165 PATH");
4278
4219
  }
4279
- async function fetchMessageContent(cfg, sessionId, messageId) {
4280
- const api = createApmApiClient(cfg);
4281
- const messages = await api.cli.listSessionMessages({ sessionId });
4282
- const message = messages.find((item) => item.id === messageId);
4283
- if (!message) {
4284
- throw new Error(`\u6D88\u606F\u4E0D\u5B58\u5728: ${messageId}`);
4220
+ function runMavenBuild(projectRoot, mavenLocalRepo, repoSource) {
4221
+ const mavenRepo = expandPath(mavenLocalRepo);
4222
+ const mvn = getMvnExecutable();
4223
+ const command = [
4224
+ quoteForShell(mvn),
4225
+ "clean",
4226
+ "package",
4227
+ `-P${MAVEN_PROFILE}`,
4228
+ formatMavenLocalRepoArg(mavenRepo),
4229
+ "-DskipTests"
4230
+ ].join(" ");
4231
+ log("\u5F00\u59CB Maven \u6784\u5EFA...");
4232
+ if (repoSource) {
4233
+ log(
4234
+ `Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo} (\u6765\u6E90: ${repoSource.source}/${repoSource.sourceDetail})`
4235
+ );
4236
+ } else {
4237
+ log(`Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo}`);
4238
+ }
4239
+ log(`\u6784\u5EFA\u76EE\u5F55: ${projectRoot}`);
4240
+ const result = spawnSync3(command, {
4241
+ cwd: projectRoot,
4242
+ stdio: "inherit",
4243
+ shell: true,
4244
+ env: process.env
4245
+ });
4246
+ if (result.status !== 0) {
4247
+ fail(`Maven \u6784\u5EFA\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${result.status ?? 1}`);
4285
4248
  }
4286
- return message.content.trim();
4287
4249
  }
4288
- async function ensureMessageHasReply(cfg, sessionId, messageId, fallback) {
4289
- const existing = await fetchMessageContent(cfg, sessionId, messageId);
4290
- if (existing) {
4291
- return;
4250
+ function locateLibDir(projectRoot) {
4251
+ const targetDir = getTargetDir(projectRoot);
4252
+ if (!existsSync14(targetDir)) {
4253
+ fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4292
4254
  }
4293
- const content = resolveMessageReplyFallback(fallback);
4294
- await appendMessageContent(cfg, messageId, content);
4295
- console.log(
4296
- `[apm] \u6D88\u606F\u65E0\u56DE\u590D\u5185\u5BB9\uFF0C\u5DF2\u81EA\u52A8\u8FFD\u52A0: messageId=${messageId} len=${content.length}`
4297
- );
4255
+ const libDir = path2.join(targetDir, "lib");
4256
+ if (!existsSync14(libDir) || !statSync5(libDir).isDirectory()) {
4257
+ fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
4258
+ }
4259
+ const libJars = readdirSync5(libDir).filter((name) => name.endsWith(".jar"));
4260
+ if (libJars.length === 0) {
4261
+ fail(`lib \u76EE\u5F55\u4E0B\u6CA1\u6709\u4F9D\u8D56 JAR: ${libDir}`);
4262
+ }
4263
+ log(`\u5B9A\u4F4D lib \u4EA7\u7269: ${libJars.length} \u4E2A`);
4264
+ return libDir;
4298
4265
  }
4299
-
4300
- // src/commands/connect/cli-version-sync.ts
4301
- import { existsSync as existsSync16, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "fs";
4302
- import { join as join15 } from "path";
4303
- var CLI_VERSION_FILE = ".cli-version.json";
4304
- function manifestPath(apmDir) {
4305
- return join15(apmDir, CLI_VERSION_FILE);
4266
+ async function connectSsh(config) {
4267
+ const client = new Client();
4268
+ log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${config.username}@${config.host}:${config.port}`);
4269
+ await new Promise((resolve5, reject) => {
4270
+ client.on("ready", () => resolve5()).on("error", (err) => reject(err)).connect({
4271
+ host: config.host,
4272
+ port: config.port,
4273
+ username: config.username,
4274
+ password: config.password,
4275
+ readyTimeout: 3e4,
4276
+ tryKeyboard: true
4277
+ });
4278
+ });
4279
+ const sftp = new SftpClient2();
4280
+ await sftp.connect({
4281
+ host: config.host,
4282
+ port: config.port,
4283
+ username: config.username,
4284
+ password: config.password,
4285
+ readyTimeout: 3e4,
4286
+ tryKeyboard: true
4287
+ });
4288
+ return { client, sftp };
4306
4289
  }
4307
- function loadManifest4(apmDir) {
4308
- const path12 = toFsPath(manifestPath(apmDir));
4309
- if (!existsSync16(path12)) {
4310
- return null;
4290
+ async function closeSsh(conn) {
4291
+ try {
4292
+ await conn.sftp.end();
4293
+ } catch {
4311
4294
  }
4295
+ conn.client.end();
4296
+ }
4297
+ async function getRemoteFileStats(sftp, remoteDir) {
4298
+ const stats = /* @__PURE__ */ new Map();
4312
4299
  try {
4313
- const parsed = JSON.parse(
4314
- readFileSync14(path12, "utf8")
4315
- );
4316
- if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
4317
- return parsed;
4300
+ const listing = await sftp.list(remoteDir);
4301
+ for (const item of listing) {
4302
+ if (item.name.endsWith(".jar")) {
4303
+ stats.set(item.name, { filename: item.name, size: item.size });
4304
+ }
4318
4305
  }
4319
4306
  } catch {
4320
4307
  }
4321
- return null;
4308
+ return stats;
4322
4309
  }
4323
- function saveManifest4(apmDir, cliVersion) {
4324
- const manifest = { version: 1, cliVersion };
4325
- writeFileSync12(
4326
- toFsPath(manifestPath(apmDir)),
4327
- `${JSON.stringify(manifest, null, 2)}
4328
- `,
4329
- "utf8"
4310
+ async function uploadUpdatePackage(sftp, zipPath, config) {
4311
+ const remoteDir = config.remoteVueDistDir.replace(/\/$/, "");
4312
+ const remotePath = `${remoteDir}/${path2.basename(zipPath)}`;
4313
+ log(`\u4E0A\u4F20\u66F4\u65B0\u5305 -> ${remotePath}`);
4314
+ try {
4315
+ await sftp.fastPut(zipPath, remotePath);
4316
+ log("\u66F4\u65B0\u5305\u4E0A\u4F20\u6210\u529F");
4317
+ } catch (err) {
4318
+ fail(`\u66F4\u65B0\u5305\u4E0A\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : err}`);
4319
+ }
4320
+ return remotePath;
4321
+ }
4322
+ async function runRemoteCommand(client, command, options) {
4323
+ const check = options?.check ?? true;
4324
+ const stream = options?.stream ?? false;
4325
+ const label = options?.label ?? "\u8FDC\u7A0B\u547D\u4EE4";
4326
+ return new Promise((resolve5, reject) => {
4327
+ client.exec(command, (err, execStream) => {
4328
+ if (err) {
4329
+ reject(err);
4330
+ return;
4331
+ }
4332
+ let out = "";
4333
+ let errText = "";
4334
+ execStream.on("data", (data) => {
4335
+ const text = data.toString();
4336
+ out += text;
4337
+ if (stream) {
4338
+ process.stdout.write(text);
4339
+ }
4340
+ });
4341
+ execStream.stderr.on("data", (data) => {
4342
+ errText += data.toString();
4343
+ });
4344
+ execStream.on("close", (code) => {
4345
+ if (stream && out && !out.endsWith("\n")) {
4346
+ process.stdout.write("\n");
4347
+ }
4348
+ if (check && code !== 0) {
4349
+ const combined = `${out}
4350
+ ${errText}`.trim();
4351
+ fail(
4352
+ `${label}\u5931\u8D25 (exit ${code})` + (combined ? `
4353
+ \u8F93\u51FA: ${combined}` : "")
4354
+ );
4355
+ }
4356
+ resolve5({ exitCode: code, out: out.trim(), err: errText.trim() });
4357
+ });
4358
+ });
4359
+ });
4360
+ }
4361
+ function buildExtractUpdatePackageScript(remoteZipPath, remoteLibDir) {
4362
+ const quotedZip = shellSingleQuote(remoteZipPath);
4363
+ const quotedLib = shellSingleQuote(remoteLibDir);
4364
+ return `
4365
+ set -e
4366
+ TMP=$(mktemp -d)
4367
+ trap 'rm -rf "$TMP"' EXIT
4368
+ unzip -oq ${quotedZip} -d "$TMP"
4369
+ updated=0
4370
+ while IFS= read -r -d '' src; do
4371
+ name=$(basename "$src")
4372
+ dest=${quotedLib}/"$name"
4373
+ if [ -f "$dest" ]; then
4374
+ cp -f "$src" "$dest"
4375
+ echo "\u8986\u76D6: $name"
4376
+ updated=$((updated + 1))
4377
+ else
4378
+ echo "\u8DF3\u8FC7(\u8FDC\u7A0B\u4E0D\u5B58\u5728): $name"
4379
+ fi
4380
+ done < <(find "$TMP" -name '*.jar' -type f -print0)
4381
+ echo "UPDATED_COUNT=$updated"
4382
+ `.trim();
4383
+ }
4384
+ async function extractUpdatePackageOnRemote(client, config, remoteZipPath) {
4385
+ const script = buildExtractUpdatePackageScript(
4386
+ remoteZipPath,
4387
+ config.remoteLibDir
4330
4388
  );
4389
+ const { out } = await runRemoteCommand(client, script, {
4390
+ label: "\u8FDC\u7A0B\u89E3\u538B"
4391
+ });
4392
+ const match = out.match(/UPDATED_COUNT=(\d+)/);
4393
+ if (!match) {
4394
+ fail(`\u8FDC\u7A0B\u89E3\u538B\u5931\u8D25\uFF0C\u672A\u83B7\u53D6\u66F4\u65B0\u6570\u91CF
4395
+ \u8F93\u51FA: ${out || "(\u7A7A)"}`);
4396
+ }
4397
+ const updated = Number.parseInt(match[1], 10);
4398
+ log(`lib \u89E3\u538B\u5B8C\u6210: \u8986\u76D6 ${updated} \u4E2A`);
4399
+ return updated;
4331
4400
  }
4332
- var syncedInSession = /* @__PURE__ */ new Map();
4333
- function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
4334
- const cached = syncedInSession.get(workdir);
4335
- if (cached === currentVersion) {
4336
- return false;
4401
+ function springbootOutputIndicatesSuccess(action, combined) {
4402
+ const lower = combined.toLowerCase();
4403
+ if (action === "health") {
4404
+ return combined.includes("\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7");
4337
4405
  }
4338
- const stored = loadManifest4(workspaceApmDir(workdir));
4339
- if (stored?.cliVersion === currentVersion) {
4340
- syncedInSession.set(workdir, currentVersion);
4341
- return false;
4406
+ if (action === "start" || action === "restart") {
4407
+ return combined.includes("is starting") || lower.includes("is running");
4408
+ }
4409
+ if (action === "stop") {
4410
+ return combined.includes("is stopping") || lower.includes("not running") || lower.includes("please check it");
4411
+ }
4412
+ if (action === "status") {
4413
+ return lower.includes("running") || lower.includes("not running");
4342
4414
  }
4343
4415
  return true;
4344
4416
  }
4345
- function markSkillsSyncedForCliVersion(workdir, cliVersion) {
4346
- saveManifest4(workspaceApmDir(workdir), cliVersion);
4347
- syncedInSession.set(workdir, cliVersion);
4417
+ function buildRemoteStatusScript(remoteAppDir, appName) {
4418
+ const dir = shellSingleQuote(remoteAppDir);
4419
+ const jar = shellSingleQuote(appName);
4420
+ return `
4421
+ set -e
4422
+ cd ${dir}
4423
+ appName=${jar}
4424
+ appIds=$(ps -ef | grep java | grep "$appName" | awk '{print $2}')
4425
+ if [ -z "$appIds" ]; then
4426
+ echo -e "\\033[31m Not running \\033[0m"
4427
+ else
4428
+ echo -e "\\033[32m Running [$appIds] \\033[0m"
4429
+ fi
4430
+ `.trim();
4348
4431
  }
4349
-
4350
- // src/commands/connect/pre-step-cache.ts
4351
- var PULL_TTL_MS = 3e4;
4352
- function sessionWorkdirKey(sessionId, workdir) {
4353
- return `${sessionId}\0${workdir}`;
4432
+ function buildRemoteRestartScript(remoteAppDir) {
4433
+ const dir = shellSingleQuote(remoteAppDir);
4434
+ const javaOpts = shellSingleQuote(SPRINGBOOT_JAVA_OPTS);
4435
+ return `
4436
+ set -e
4437
+ cd ${dir}
4438
+ releaseApp=$(ls -t | grep '.jar$' | head -n1)
4439
+ lastVersionApp=$(ls -t | grep '.jar$' | head -n2 | tail -n1)
4440
+ appName=$lastVersionApp
4441
+ appIds=$(ps -ef | grep java | grep "$appName" | awk '{print $2}')
4442
+ if [ -z "$appIds" ]; then
4443
+ echo "Maybe $appName not running, please check it..."
4444
+ else
4445
+ echo "The $appName is stopping..."
4446
+ echo "$appIds" | xargs kill
4447
+ fi
4448
+ for i in $(seq 15 -1 1); do
4449
+ echo -n "$i "
4450
+ sleep 1
4451
+ done
4452
+ echo 0
4453
+ if [ ! -d "backup" ]; then
4454
+ mkdir backup
4455
+ fi
4456
+ for i in $(ls | grep '.jar$' | grep -vFx "$releaseApp"); do
4457
+ echo "backup $i"
4458
+ mv "$i" backup/
4459
+ done
4460
+ appName=$releaseApp
4461
+ count=$(ps -ef | grep java | grep "$appName" | wc -l)
4462
+ if [ "$count" != "0" ]; then
4463
+ echo "Maybe $appName is running, please check it..."
4464
+ else
4465
+ echo "The $appName is starting..."
4466
+ nohup java -jar "./$appName" ${javaOpts} > nohup.out 2>&1 &
4467
+ fi
4468
+ `.trim();
4354
4469
  }
4355
- var lastBranchKey = null;
4356
- var lastPullAtByKey = /* @__PURE__ */ new Map();
4357
- function shouldRunBranch(sessionId, workdir) {
4358
- return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
4470
+ function normalizeHealthContext(context) {
4471
+ let normalized = context.trim() || "/";
4472
+ if (!normalized.startsWith("/")) {
4473
+ normalized = `/${normalized}`;
4474
+ }
4475
+ if (!normalized.endsWith("/")) {
4476
+ normalized = `${normalized}/`;
4477
+ }
4478
+ return normalized;
4359
4479
  }
4360
- function markBranchDone(sessionId, workdir) {
4361
- lastBranchKey = sessionWorkdirKey(sessionId, workdir);
4480
+ function buildRemoteHealthScript(port, context, timeoutSecs) {
4481
+ const normalizedContext = normalizeHealthContext(context);
4482
+ const portStr = String(port);
4483
+ const timeoutStr = String(timeoutSecs);
4484
+ return `
4485
+ set -e
4486
+ port=${shellSingleQuote(portStr)}
4487
+ context=${shellSingleQuote(normalizedContext)}
4488
+ timeout=${shellSingleQuote(timeoutStr)}
4489
+ check_url="http://127.0.0.1:${portStr}${normalizedContext}"
4490
+ echo "\u5065\u5EB7\u68C0\u67E5: \${check_url} (\u8D85\u65F6 \${timeout}s)"
4491
+ deadline=$(($(date +%s) + timeout))
4492
+ attempt=0
4493
+ while [ $(date +%s) -lt $deadline ]; do
4494
+ attempt=$((attempt + 1))
4495
+ code=$(curl -s -o /dev/null -w "%{http_code}" "$check_url" 2>/dev/null || echo "000")
4496
+ code=$(echo "$code" | tail -n1 | tr -d '[:space:]')
4497
+ if [ \${#code} -ge 3 ]; then
4498
+ status=\${code:0:3}
4499
+ if echo "$status" | grep -qE '^[0-9]+$' && [ "$status" -ge 200 ] && [ "$status" -lt 500 ]; then
4500
+ echo -e "\\033[32m\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7 (HTTP \${status})\\033[0m"
4501
+ exit 0
4502
+ fi
4503
+ fi
4504
+ remaining=$((deadline - $(date +%s)))
4505
+ if [ $remaining -lt 0 ]; then
4506
+ remaining=0
4507
+ fi
4508
+ echo "\u7B49\u5F85\u670D\u52A1\u542F\u52A8... \u7B2C \${attempt} \u6B21\uFF0C\u5269\u4F59 \${remaining}s"
4509
+ sleep 5
4510
+ done
4511
+ echo -e "\\033[31m\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6 (\${timeout}s): \${check_url}\\033[0m"
4512
+ exit 1
4513
+ `.trim();
4362
4514
  }
4363
- function shouldRunPull(sessionId, workdir) {
4364
- const key = sessionWorkdirKey(sessionId, workdir);
4365
- const last = lastPullAtByKey.get(key);
4366
- if (last == null) {
4367
- return true;
4515
+ async function runRemoteServiceScript(client, script, action) {
4516
+ const { exitCode, out, err } = await runRemoteCommand(client, script, {
4517
+ check: false,
4518
+ label: action === "status" ? "\u8FDC\u7A0B status" : action === "restart" ? "\u8FDC\u7A0B restart" : "\u5065\u5EB7\u68C0\u67E5"
4519
+ });
4520
+ const combined = `${out}
4521
+ ${err}`.trim();
4522
+ const outputOk = springbootOutputIndicatesSuccess(action, combined);
4523
+ if (action === "health") {
4524
+ if (exitCode !== 0 || !outputOk) {
4525
+ fail(`\u5065\u5EB7\u68C0\u67E5\u5931\u8D25
4526
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
4527
+ }
4528
+ return combined;
4368
4529
  }
4369
- return Date.now() - last >= PULL_TTL_MS;
4370
- }
4371
- function markPullDone(sessionId, workdir) {
4372
- lastPullAtByKey.set(sessionWorkdirKey(sessionId, workdir), Date.now());
4530
+ if (exitCode !== 0 && !outputOk) {
4531
+ fail(`\u8FDC\u7A0B ${action} \u5931\u8D25
4532
+ ${combined}`);
4533
+ }
4534
+ if (action === "restart" && !outputOk) {
4535
+ fail(`\u8FDC\u7A0B restart \u672A\u6210\u529F
4536
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
4537
+ }
4538
+ return combined;
4373
4539
  }
4374
-
4375
- // src/commands/connect/run-slot-pool.ts
4376
- var DEFAULT_MAX_CONCURRENT = 5;
4377
- function createRunSlotPool(maxConcurrent = DEFAULT_MAX_CONCURRENT) {
4378
- let active = 0;
4379
- const waiters = [];
4380
- const acquire = () => {
4381
- if (active < maxConcurrent) {
4382
- active += 1;
4383
- return Promise.resolve();
4384
- }
4385
- return new Promise((resolve5) => {
4386
- waiters.push(() => {
4387
- active += 1;
4388
- resolve5();
4389
- });
4390
- });
4391
- };
4392
- const release = () => {
4393
- active = Math.max(0, active - 1);
4394
- const next = waiters.shift();
4395
- if (next) {
4396
- next();
4397
- }
4398
- };
4399
- return { acquire, release };
4540
+ function stripAnsi(text) {
4541
+ return text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
4400
4542
  }
4401
-
4402
- // src/commands/connect.ts
4403
- var HEARTBEAT_MS = 3e4;
4404
- async function updateMessageStatus(cfg, messageId, status) {
4405
- const api = createApmApiClient(cfg);
4406
- await api.cli.updateMessageStatus({ id: messageId, status });
4407
- console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
4543
+ async function getRunningJar(client, config) {
4544
+ const script = buildRemoteStatusScript(
4545
+ config.remoteAppDir,
4546
+ config.startupJar
4547
+ );
4548
+ const combined = await runRemoteServiceScript(client, script, "status");
4549
+ const text = stripAnsi(combined).trim().toLowerCase();
4550
+ if (text.includes("not running")) {
4551
+ return null;
4552
+ }
4553
+ if (text.includes("running")) {
4554
+ return config.startupJar;
4555
+ }
4556
+ return null;
4408
4557
  }
4409
- async function setMessageError(cfg, messageId, error) {
4410
- const api = createApmApiClient(cfg);
4411
- await api.cli.setMessageError({ id: messageId, error });
4412
- console.log(`[apm] \u5DF2\u8BBE\u7F6E\u6D88\u606F\u9519\u8BEF: ${messageId}`);
4558
+ async function healthCheckService(client, config) {
4559
+ log("\u5065\u5EB7\u68C0\u67E5...");
4560
+ const script = buildRemoteHealthScript(
4561
+ config.healthCheckPort,
4562
+ config.healthCheckContext,
4563
+ config.healthCheckTimeout
4564
+ );
4565
+ await runRemoteServiceScript(client, script, "health");
4413
4566
  }
4414
- var SHUTDOWN_DRAIN_MS = 3e3;
4415
- function isUserCancelled(ctx) {
4416
- return ctx.perMessageSignal.aborted && !ctx.shutdownSignal.aborted;
4567
+ async function restartRemoteService(client, config) {
4568
+ const script = buildRemoteRestartScript(config.remoteAppDir);
4569
+ await runRemoteServiceScript(client, script, "restart");
4417
4570
  }
4418
- async function handleInboundMessage(cfg, msg, signal, ctx) {
4419
- if (isUserCancelled(ctx)) return;
4420
- if (signal.aborted) return;
4421
- const messageId = msg.messageId;
4422
- const workdir = requireRemoteWorkdir(msg.workdir);
4423
- const apmRoot = workspaceApmDir(workdir);
4424
- const runStep = async (step, fn) => {
4425
- const startedAt = Date.now();
4426
- try {
4427
- const result = await fn();
4428
- console.log(`[apm] step=${step} elapsed=${Date.now() - startedAt}ms`);
4429
- return result;
4430
- } catch (err) {
4431
- const detail = err instanceof Error ? err.message : String(err);
4432
- throw new Error(`[${step}] ${detail}`);
4433
- }
4434
- };
4571
+ async function runWisdomBackendDeploy(options) {
4572
+ const projectRoot = path2.resolve(options.projectRoot ?? process.cwd());
4573
+ const config = options.config;
4574
+ log(`=== \u81EA\u52A8\u90E8\u7F72: ${config.projectName} ===`);
4575
+ log(`\u914D\u7F6E\u6587\u4EF6: ${path2.join(workspaceApmDir(), "apm.config.json")}`);
4576
+ log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
4577
+ runMavenBuild(projectRoot, config.mavenLocalRepo, {
4578
+ source: config.mavenLocalRepoSource,
4579
+ sourceDetail: config.mavenLocalRepoSourceDetail
4580
+ });
4581
+ const libDir = locateLibDir(projectRoot);
4582
+ let manifest = loadManifest4();
4583
+ const conn = await connectSsh(config);
4435
4584
  try {
4436
- if (signal.aborted) return;
4437
- const { didInit } = await runStep(
4438
- "workspace-init",
4439
- () => ensureWorkspaceInitialized(workdir)
4585
+ const remoteLibStats = await getRemoteFileStats(
4586
+ conn.sftp,
4587
+ config.remoteLibDir
4440
4588
  );
4441
- if (!didInit) {
4442
- assertApmGitignoredInRepo(workdir);
4443
- }
4444
- await runStep(
4445
- "status-typing",
4446
- () => updateMessageStatus(cfg, messageId, "TYPING")
4589
+ log("\u6536\u96C6 JAR \u66F4\u65B0...");
4590
+ const libUploadEntries = listLibFilesToUpload(
4591
+ libDir,
4592
+ remoteLibStats,
4593
+ projectRoot,
4594
+ manifest
4447
4595
  );
4448
- if (shouldRunBranch(msg.sessionId, workdir)) {
4449
- if (signal.aborted) return;
4450
- await runStep("branch", () => runBranch(msg.sessionId, { cwd: workdir }));
4451
- markBranchDone(msg.sessionId, workdir);
4452
- } else {
4453
- console.log(`[apm] step=branch skipped sessionId=${msg.sessionId}`);
4454
- }
4455
- let pullRan = false;
4456
- if (shouldRunPull(msg.sessionId, workdir)) {
4457
- if (signal.aborted) return;
4458
- await runStep("pull", () => runPull(msg.sessionId, workdir));
4459
- markPullDone(msg.sessionId, workdir);
4460
- pullRan = true;
4461
- } else {
4462
- console.log(`[apm] step=pull skipped sessionId=${msg.sessionId}`);
4463
- }
4464
- if (pullRan) {
4465
- if (signal.aborted) return;
4466
- await runStep(
4467
- "commit-pull",
4468
- () => commitWorkingTreeIfDirty(workdir, "fix: apm pull")
4596
+ let updated = 0;
4597
+ if (libUploadEntries.length > 0) {
4598
+ const zipPath = await createUpdatePackage(
4599
+ libUploadEntries,
4600
+ config.packageName
4469
4601
  );
4470
- } else {
4471
- console.log(`[apm] step=commit-pull skipped sessionId=${msg.sessionId}`);
4472
- }
4473
- const cliVersion = readCliVersion();
4474
- if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
4475
- if (signal.aborted) return;
4476
- console.log(
4477
- `[apm] CLI \u7248\u672C ${cliVersion} \u4E0E\u5DE5\u4F5C\u533A\u8BB0\u5F55\u4E0D\u4E00\u81F4\uFF0C\u6267\u884C update-skills`
4602
+ const remoteZipPath = await uploadUpdatePackage(
4603
+ conn.sftp,
4604
+ zipPath,
4605
+ config
4478
4606
  );
4479
- await runStep("update-skills", async () => {
4480
- await syncWorkspaceSkills(cfg, workdir);
4481
- markSkillsSyncedForCliVersion(workdir, cliVersion);
4482
- });
4483
- } else {
4484
- console.log(`[apm] step=update-skills skipped workdir=${workdir}`);
4485
- }
4486
- if (signal.aborted) return;
4487
- if (!pullRan) {
4488
- await runStep(
4489
- "sync-project-documents-pull",
4490
- () => syncRepositoryProjectDocumentsPull(workdir, apmRoot)
4607
+ log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
4608
+ updated = await extractUpdatePackageOnRemote(
4609
+ conn.client,
4610
+ config,
4611
+ remoteZipPath
4491
4612
  );
4613
+ } else {
4614
+ log("\u65E0 JAR \u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u66F4\u65B0\u5305\u4E0A\u4F20");
4492
4615
  }
4493
- const agentResult = await runStep(
4494
- "cursor-agent",
4495
- () => runCursorAgent(
4496
- cfg,
4497
- {
4498
- messageId: msg.messageId,
4499
- sessionId: msg.sessionId,
4500
- prompt: msg.content,
4501
- model: msg.model,
4502
- apiKey: msg.apiKey,
4503
- workdir,
4504
- user: msg.user
4505
- },
4506
- { signal }
4507
- )
4508
- );
4509
- await runStep(
4510
- "ensure-reply",
4511
- () => ensureMessageHasReply(cfg, msg.sessionId, messageId, {
4512
- assistantText: agentResult.assistantText,
4513
- result: agentResult.result,
4514
- createPlan: agentResult.createPlan
4515
- })
4516
- );
4517
- await runStep(
4518
- "sync-documents",
4519
- () => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
4520
- );
4521
- await runStep(
4522
- "sync-project-documents",
4523
- () => syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot)
4524
- );
4525
- await runStep(
4526
- "commit-files",
4527
- () => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
4528
- );
4529
- await runStep(
4530
- "status-success",
4531
- () => updateMessageStatus(cfg, messageId, "SUCCESS")
4532
- );
4533
- } catch (err) {
4534
- if (isUserCancelled(ctx)) {
4535
- console.log(`[apm] \u6D88\u606F\u5DF2\u7EC8\u6B62 messageId=${messageId}`);
4536
- return;
4537
- }
4538
- console.error(
4539
- "[apm] \u5904\u7406\u6D88\u606F\u5931\u8D25:",
4540
- err instanceof Error ? err.message : String(err)
4541
- );
4542
- if (err instanceof Error && err.stack) {
4543
- console.error(err.stack);
4616
+ const runningJar = await getRunningJar(conn.client, config);
4617
+ let needRestart = updated > 0;
4618
+ if (!needRestart && !runningJar) {
4619
+ log("\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u9700\u8981\u542F\u52A8");
4620
+ needRestart = true;
4621
+ } else if (!needRestart) {
4622
+ log("\u6CA1\u6709\u6587\u4EF6\u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u91CD\u542F");
4544
4623
  }
4545
- try {
4546
- await setMessageError(
4547
- cfg,
4548
- messageId,
4549
- err instanceof Error ? err.message : String(err)
4550
- );
4551
- await updateMessageStatus(cfg, messageId, "FAILED");
4552
- } catch (statusErr) {
4553
- console.error(
4554
- "[apm] \u66F4\u65B0 FAILED \u72B6\u6001\u5931\u8D25:",
4555
- statusErr instanceof Error ? statusErr.message : statusErr
4556
- );
4624
+ if (needRestart) {
4625
+ log("\u91CD\u542F\u670D\u52A1...");
4626
+ await restartRemoteService(conn.client, config);
4557
4627
  }
4558
- }
4559
- }
4560
- function startHeartbeat(ws, clientMachineId) {
4561
- const send = () => {
4562
- if (ws.readyState === WebSocket.OPEN) {
4563
- ws.send(
4564
- serializeAgentWsMessage({
4565
- type: "heartbeat",
4566
- userId: clientMachineId
4567
- })
4568
- );
4628
+ await healthCheckService(conn.client, config);
4629
+ if (libUploadEntries.length > 0) {
4630
+ manifest = updateManifestEntries(manifest, libUploadEntries, projectRoot);
4631
+ saveManifest4(manifest);
4569
4632
  }
4570
- };
4571
- send();
4572
- const timer = setInterval(send, HEARTBEAT_MS);
4573
- return () => clearInterval(timer);
4574
- }
4575
- function reexecConnect(options) {
4576
- const args = [process.argv[1], "connect"];
4577
- const server = options.server?.trim();
4578
- if (server) {
4579
- args.push("--server", server);
4633
+ } finally {
4634
+ await closeSsh(conn);
4580
4635
  }
4581
- const result = spawnSync4(process.execPath, args, { stdio: "inherit" });
4582
- if (result.error) {
4583
- console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
4584
- process.exit(1);
4636
+ log("\u90E8\u7F72\u5B8C\u6210");
4637
+ }
4638
+
4639
+ // src/commands/deploy/internal/wisdom-auto-deploy.ts
4640
+ function isWisdomLegacyDeploy(cfg) {
4641
+ const w = cfg.wisdomDeploy;
4642
+ if (!w?.host?.trim() || !w.remotePath?.trim()) {
4643
+ return false;
4585
4644
  }
4586
- process.exit(result.status ?? 0);
4645
+ const hasNewFrontend = Boolean(cfg.frontendDeploy?.endpoint?.trim());
4646
+ const hasNewBackend = Boolean(cfg.backendDeploy?.registryHost?.trim());
4647
+ return !hasNewFrontend && !hasNewBackend;
4587
4648
  }
4588
- async function runConnect(options) {
4589
- const { didUpdate } = await runUpdate();
4590
- if (didUpdate) {
4591
- reexecConnect(options);
4649
+ function detectWisdomProjectType(cwd) {
4650
+ return existsSync15(path3.join(cwd, "package.json")) ? "frontend" : "backend";
4651
+ }
4652
+ function readPackageScripts(cwd) {
4653
+ const pkgPath = path3.join(cwd, "package.json");
4654
+ if (!existsSync15(pkgPath)) {
4655
+ return {};
4592
4656
  }
4593
- const cfg = await ensureLoggedConfig();
4594
- if (options.server?.trim()) {
4595
- cfg.baseUrl = options.server.trim().replace(/\/+$/, "");
4657
+ try {
4658
+ const raw = readFileSync13(pkgPath, "utf8");
4659
+ const parsed = JSON.parse(raw);
4660
+ return parsed.scripts ?? {};
4661
+ } catch {
4662
+ return {};
4596
4663
  }
4597
- const clientMachineId = resolveClientMachineId(cfg);
4598
- if (!clientMachineId) {
4599
- console.error("[apm] config \u7F3A\u5C11 clientMachineId\uFF0C\u8BF7\u91CD\u65B0 apm login");
4600
- process.exit(1);
4664
+ }
4665
+ function resolveFrontendDeployCommand(env, cwd) {
4666
+ const scripts = readPackageScripts(cwd);
4667
+ const deployKey = `deploy:${env}`;
4668
+ if (scripts[deployKey]?.trim()) {
4669
+ return `npm run deploy:${env}`;
4601
4670
  }
4602
- const url = buildAgentWsUrl(cfg.baseUrl, resolveApiKey(cfg));
4603
- console.log(`[apm] \u8FDE\u63A5 ${cfg.baseUrl} \u2026`);
4604
- await new Promise((resolve5, reject) => {
4605
- const ws = new WebSocket(url);
4606
- let stopHeartbeat;
4607
- let shuttingDown = false;
4608
- const shutdownAbort = new AbortController();
4609
- const runSlots = createRunSlotPool();
4610
- const activeTasks = /* @__PURE__ */ new Set();
4611
- const activeRuns = /* @__PURE__ */ new Map();
4612
- const pendingCancels = /* @__PURE__ */ new Set();
4613
- const shutdown = async (code = 0) => {
4614
- if (shuttingDown) return;
4615
- shuttingDown = true;
4616
- logAbortSignalStats(
4617
- shutdownAbort.signal,
4618
- "connect:shutdown-before-abort"
4619
- );
4620
- shutdownAbort.abort();
4621
- logAbortSignalStats(shutdownAbort.signal, "connect:shutdown-after-abort");
4622
- stopHeartbeat?.();
4623
- if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
4624
- ws.terminate();
4625
- }
4626
- try {
4627
- await Promise.race([
4628
- Promise.all(activeTasks),
4629
- new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
4630
- ]);
4631
- } catch {
4632
- }
4633
- resolve5();
4634
- process.exit(code);
4635
- };
4636
- ws.on("open", () => {
4637
- console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
4638
- stopHeartbeat = startHeartbeat(ws, clientMachineId);
4639
- });
4640
- ws.on("message", (data) => {
4641
- if (shuttingDown) return;
4642
- const text = Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
4643
- const parsed = parseAgentWsMessage(text);
4644
- if (parsed === null) {
4645
- console.error("[apm] \u6536\u5230\u65E0\u6548 JSON");
4646
- return;
4647
- }
4648
- if (typeof parsed === "object" && parsed !== null && parsed.type === "heartbeat") {
4649
- return;
4650
- }
4651
- const validated = validateAgentWsMessage(parsed, "outbound");
4652
- if (!validated.ok) {
4653
- console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
4654
- return;
4655
- }
4656
- if (validated.data.type === "cancel") {
4657
- const { messageId } = validated.data;
4658
- pendingCancels.add(messageId);
4659
- activeRuns.get(messageId)?.abort();
4660
- return;
4661
- }
4662
- if (validated.data.type === "deploy") {
4663
- const msg2 = validated.data;
4664
- const perDeployController = new AbortController();
4665
- const signal2 = AbortSignal.any([
4666
- shutdownAbort.signal,
4667
- perDeployController.signal
4668
- ]);
4669
- const task2 = (async () => {
4670
- await runSlots.acquire();
4671
- try {
4672
- await handleInboundDeploy(cfg, msg2, signal2);
4673
- } finally {
4674
- runSlots.release();
4675
- }
4676
- })();
4677
- activeTasks.add(task2);
4678
- void task2.finally(() => {
4679
- activeTasks.delete(task2);
4680
- });
4681
- return;
4682
- }
4683
- if (validated.data.type !== "message") {
4684
- return;
4685
- }
4686
- const msg = validated.data;
4687
- const perMessageController = new AbortController();
4688
- activeRuns.set(msg.messageId, perMessageController);
4689
- if (pendingCancels.has(msg.messageId)) {
4690
- activeRuns.delete(msg.messageId);
4691
- pendingCancels.delete(msg.messageId);
4692
- return;
4693
- }
4694
- const signal = AbortSignal.any([
4695
- shutdownAbort.signal,
4696
- perMessageController.signal
4697
- ]);
4698
- const ctx = {
4699
- shutdownSignal: shutdownAbort.signal,
4700
- perMessageSignal: perMessageController.signal
4701
- };
4702
- const task = (async () => {
4703
- await runSlots.acquire();
4704
- try {
4705
- await handleInboundMessage(cfg, msg, signal, ctx);
4706
- } finally {
4707
- runSlots.release();
4708
- activeRuns.delete(msg.messageId);
4709
- pendingCancels.delete(msg.messageId);
4710
- }
4711
- })();
4712
- activeTasks.add(task);
4713
- void task.finally(() => {
4714
- activeTasks.delete(task);
4715
- });
4716
- });
4717
- ws.on("close", (code, reason) => {
4718
- console.log(
4719
- `[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
4720
- );
4721
- void shutdown();
4722
- });
4723
- ws.on("error", (err) => {
4724
- console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
4725
- reject(err);
4726
- });
4727
- process.on("SIGINT", () => {
4728
- console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
4729
- void shutdown();
4730
- });
4731
- process.on("SIGTERM", () => {
4732
- void shutdown();
4733
- });
4734
- });
4671
+ return null;
4735
4672
  }
4736
-
4737
- // src/commands/create-pr.ts
4738
- async function runCreatePr(options) {
4739
- const sessionId = options.sessionId.trim();
4740
- if (!sessionId) {
4741
- console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
4742
- process.exit(1);
4673
+ function runShellCommand2(command, cwd) {
4674
+ const result = spawnSync4(command, {
4675
+ cwd,
4676
+ stdio: "inherit",
4677
+ shell: true,
4678
+ env: process.env
4679
+ });
4680
+ if (result.status !== 0) {
4681
+ process.exit(result.status ?? 1);
4743
4682
  }
4744
- const title = options.title.trim();
4745
- if (!title) {
4746
- console.error("[apm] \u8BF7\u901A\u8FC7 --title \u6307\u5B9A PR \u6807\u9898");
4747
- process.exit(1);
4683
+ }
4684
+ async function runWisdomAutoDeploy(options) {
4685
+ const cwd = path3.resolve(options.cwd ?? process.cwd());
4686
+ const projectType = detectWisdomProjectType(cwd);
4687
+ console.error(
4688
+ `[apm] \u533B\u52A1\u5B58\u91CF\u9879\u76EE\u81EA\u52A8\u8BC6\u522B: ${projectType === "frontend" ? "\u524D\u7AEF Vue" : "\u540E\u7AEF Java"}`
4689
+ );
4690
+ if (projectType === "frontend") {
4691
+ const deployCmd = resolveFrontendDeployCommand(options.env, cwd);
4692
+ if (!deployCmd) {
4693
+ console.error(
4694
+ `\u524D\u7AEF\u9879\u76EE ${path3.join(cwd, "package.json")} \u4E2D\u672A\u627E\u5230 deploy:${options.env} \u811A\u672C`
4695
+ );
4696
+ process.exit(1);
4697
+ }
4698
+ runShellCommand2(deployCmd, cwd);
4699
+ return { projectType };
4748
4700
  }
4749
- const cfg = await ensureLoggedConfig();
4750
- const api = createApmApiClient(cfg);
4751
- const workdir = resolveWorkdirPath(options.cwd ?? process.cwd());
4752
- const pr = await api.cli.createPullRequest({
4753
- sessionId,
4754
- workdir,
4755
- title,
4756
- content: options.content ?? ""
4701
+ const configPath = options.configPath ?? path3.join(workspaceApmDir(cwd), "apm.config.json");
4702
+ const cfg = loadApmConfig({ configPath });
4703
+ const settings = resolveWisdomBackendDeployFromApmConfig(cfg);
4704
+ console.error(
4705
+ `[apm] \u540E\u7AEF\u90E8\u7F72\u4E0D\u533A\u5206 test/online\uFF0C\u5FFD\u7565\u73AF\u5883\u53C2\u6570: ${options.env}`
4706
+ );
4707
+ await runWisdomBackendDeploy({
4708
+ config: settings,
4709
+ projectRoot: cwd
4757
4710
  });
4758
- console.log(`[apm] PR \u5DF2\u5C31\u7EEA #${pr.number} (${pr.state}): ${pr.url}`);
4711
+ return { projectType };
4759
4712
  }
4760
4713
 
4761
4714
  // src/commands/deploy/deploy.ts
4762
- import { spawnSync as spawnSync5 } from "node:child_process";
4763
4715
  function runShellCommand3(command, cwd) {
4764
4716
  const result = spawnSync5(command, {
4765
4717
  cwd,
@@ -4813,7 +4765,7 @@ import path7 from "node:path";
4813
4765
  import Docker from "dockerode";
4814
4766
 
4815
4767
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
4816
- import { existsSync as existsSync17, readFileSync as readFileSync15 } from "node:fs";
4768
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "node:fs";
4817
4769
  import path4 from "node:path";
4818
4770
  function asOptionalTlsBuffer(value) {
4819
4771
  if (typeof value !== "string") {
@@ -4825,8 +4777,8 @@ function asOptionalTlsBuffer(value) {
4825
4777
  if (normalized === "") {
4826
4778
  return void 0;
4827
4779
  }
4828
- if (existsSync17(normalized)) {
4829
- return readFileSync15(normalized);
4780
+ if (existsSync16(normalized)) {
4781
+ return readFileSync14(normalized);
4830
4782
  }
4831
4783
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
4832
4784
  if (looksLikePath) {
@@ -5036,7 +4988,7 @@ var DockerodeClient = class {
5036
4988
  var createDockerodeClient = (config) => new DockerodeClient(config);
5037
4989
 
5038
4990
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
5039
- import { existsSync as existsSync18, readFileSync as readFileSync16, statSync as statSync6 } from "node:fs";
4991
+ import { existsSync as existsSync17, readFileSync as readFileSync15, statSync as statSync6 } from "node:fs";
5040
4992
  import path5 from "node:path";
5041
4993
  function stripSurroundingQuotes(value) {
5042
4994
  const t = value.trim();
@@ -5053,10 +5005,10 @@ function loadEnvFromFile(envFilePath) {
5053
5005
  return {};
5054
5006
  }
5055
5007
  const targetPath = path5.resolve(envFilePath);
5056
- if (!existsSync18(targetPath) || !statSync6(targetPath).isFile()) {
5008
+ if (!existsSync17(targetPath) || !statSync6(targetPath).isFile()) {
5057
5009
  return {};
5058
5010
  }
5059
- const raw = readFileSync16(targetPath, "utf-8");
5011
+ const raw = readFileSync15(targetPath, "utf-8");
5060
5012
  const result = {};
5061
5013
  for (const line of raw.split(/\r?\n/)) {
5062
5014
  const normalized = line.trim();
@@ -5227,12 +5179,12 @@ function dockerPushImage(params, cwd) {
5227
5179
  }
5228
5180
 
5229
5181
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
5230
- import { existsSync as existsSync19 } from "node:fs";
5182
+ import { existsSync as existsSync18 } from "node:fs";
5231
5183
  import path6 from "node:path";
5232
5184
  function resolveDockerBuildPaths(cwd) {
5233
5185
  const dockerfilePath = path6.join(cwd, "Dockerfile");
5234
5186
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
5235
- if (!existsSync19(dockerfilePath)) {
5187
+ if (!existsSync18(dockerfilePath)) {
5236
5188
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
5237
5189
  }
5238
5190
  Logger.info("\u2713 Dockerfile \u5B58\u5728");