ai-project-manage-cli 6.0.74 → 6.0.76

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 +2219 -2287
  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,2448 @@ 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
3269
+ if (signal.aborted) return;
3270
+ const { didInit } = await runStep(
3271
+ "workspace-init",
3272
+ () => ensureWorkspaceInitialized(workdir)
3227
3273
  );
3228
- log("\u6536\u96C6 JAR \u66F4\u65B0...");
3229
- const libUploadEntries = listLibFilesToUpload(
3230
- libDir,
3231
- remoteLibStats,
3232
- projectRoot,
3233
- manifest
3274
+ if (!didInit) {
3275
+ assertApmGitignoredInRepo(workdir);
3276
+ }
3277
+ await runStep(
3278
+ "status-typing",
3279
+ () => updateMessageStatus(cfg, messageId, "TYPING")
3234
3280
  );
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
- );
3281
+ if (shouldRunBranch(msg.sessionId, workdir)) {
3282
+ if (signal.aborted) return;
3283
+ await runStep("branch", () => runBranch(msg.sessionId, { cwd: workdir }));
3284
+ markBranchDone(msg.sessionId, workdir);
3252
3285
  } else {
3253
- log("\u65E0 JAR \u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u66F4\u65B0\u5305\u4E0A\u4F20");
3254
- }
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");
3286
+ console.log(`[apm] step=branch skipped sessionId=${msg.sessionId}`);
3262
3287
  }
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);
3288
+ let pullRan = false;
3289
+ if (shouldRunPull(msg.sessionId, workdir)) {
3290
+ if (signal.aborted) return;
3291
+ await runStep("pull", () => runPull(msg.sessionId, workdir));
3292
+ markPullDone(msg.sessionId, workdir);
3293
+ pullRan = true;
3294
+ } else {
3295
+ console.log(`[apm] step=pull skipped sessionId=${msg.sessionId}`);
3271
3296
  }
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`
3297
+ if (pullRan) {
3298
+ if (signal.aborted) return;
3299
+ await runStep(
3300
+ "commit-pull",
3301
+ () => commitWorkingTreeIfDirty(workdir, "fix: apm pull")
3334
3302
  );
3335
- process.exit(1);
3303
+ } else {
3304
+ console.log(`[apm] step=commit-pull skipped sessionId=${msg.sessionId}`);
3336
3305
  }
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();
3306
+ const cliVersion = readCliVersion();
3307
+ if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
3308
+ if (signal.aborted) return;
3309
+ console.log(
3310
+ `[apm] CLI \u7248\u672C ${cliVersion} \u4E0E\u5DE5\u4F5C\u533A\u8BB0\u5F55\u4E0D\u4E00\u81F4\uFF0C\u6267\u884C update-skills`
3311
+ );
3312
+ await runStep("update-skills", async () => {
3313
+ await syncWorkspaceSkills(cfg, workdir);
3314
+ markSkillsSyncedForCliVersion(workdir, cliVersion);
3315
+ });
3401
3316
  } else {
3402
- signal.addEventListener("abort", onAbort, { once: true });
3317
+ console.log(`[apm] step=update-skills skipped workdir=${workdir}`);
3403
3318
  }
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}`
3319
+ if (signal.aborted) return;
3320
+ if (!pullRan) {
3321
+ await runStep(
3322
+ "sync-project-documents-pull",
3323
+ () => syncRepositoryProjectDocumentsPull(workdir, apmRoot)
3425
3324
  );
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) {
3325
+ }
3326
+ const agentResult = await runStep(
3327
+ "cursor-agent",
3328
+ () => runCursorAgent(
3329
+ cfg,
3330
+ {
3331
+ messageId: msg.messageId,
3332
+ sessionId: msg.sessionId,
3333
+ prompt: msg.content,
3334
+ model: msg.model,
3335
+ apiKey: msg.apiKey,
3336
+ workdir,
3337
+ user: msg.user
3338
+ },
3339
+ { signal }
3340
+ )
3341
+ );
3342
+ await runStep(
3343
+ "ensure-reply",
3344
+ () => ensureMessageHasReply(cfg, msg.sessionId, messageId, {
3345
+ assistantText: agentResult.assistantText,
3346
+ result: agentResult.result,
3347
+ createPlan: agentResult.createPlan
3348
+ })
3349
+ );
3350
+ await runStep(
3351
+ "sync-documents",
3352
+ () => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
3353
+ );
3354
+ await runStep(
3355
+ "sync-project-documents",
3356
+ () => syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot)
3357
+ );
3358
+ await runStep(
3359
+ "commit-files",
3360
+ () => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
3361
+ );
3362
+ await runStep(
3363
+ "status-success",
3364
+ () => updateMessageStatus(cfg, messageId, "SUCCESS")
3365
+ );
3366
+ } catch (err) {
3367
+ if (isUserCancelled(ctx)) {
3368
+ console.log(`[apm] \u6D88\u606F\u5DF2\u7EC8\u6B62 messageId=${messageId}`);
3436
3369
  return;
3437
3370
  }
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) => {
3371
+ console.error(
3372
+ "[apm] \u5904\u7406\u6D88\u606F\u5931\u8D25:",
3373
+ err instanceof Error ? err.message : String(err)
3374
+ );
3375
+ if (err instanceof Error && err.stack) {
3376
+ console.error(err.stack);
3377
+ }
3378
+ try {
3379
+ await setMessageError(
3380
+ cfg,
3381
+ messageId,
3382
+ err instanceof Error ? err.message : String(err)
3383
+ );
3384
+ await updateMessageStatus(cfg, messageId, "FAILED");
3385
+ } catch (statusErr) {
3446
3386
  console.error(
3447
- "[apm] deploy log sync failed:",
3448
- error instanceof Error ? error.message : String(error)
3387
+ "[apm] \u66F4\u65B0 FAILED \u72B6\u6001\u5931\u8D25:",
3388
+ statusErr instanceof Error ? statusErr.message : statusErr
3449
3389
  );
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
3390
  }
3463
- };
3391
+ }
3464
3392
  }
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();
3393
+ function startHeartbeat(ws, clientMachineId) {
3394
+ const send = () => {
3395
+ if (ws.readyState === WebSocket.OPEN) {
3396
+ ws.send(
3397
+ serializeAgentWsMessage({
3398
+ type: "heartbeat",
3399
+ userId: clientMachineId
3400
+ })
3401
+ );
3504
3402
  }
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;
3403
+ };
3404
+ send();
3405
+ const timer = setInterval(send, HEARTBEAT_MS);
3406
+ return () => clearInterval(timer);
3407
+ }
3408
+ function reexecConnect(options) {
3409
+ const args = [process.argv[1], "connect"];
3410
+ const server = options.server?.trim();
3411
+ if (server) {
3412
+ args.push("--server", server);
3518
3413
  }
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();
3414
+ const result = spawnSync2(process.execPath, args, { stdio: "inherit" });
3415
+ if (result.error) {
3416
+ console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
3417
+ process.exit(1);
3553
3418
  }
3419
+ process.exit(result.status ?? 0);
3554
3420
  }
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)`;
3421
+ async function runConnect(options) {
3422
+ const { didUpdate } = await runUpdate();
3423
+ if (didUpdate) {
3424
+ reexecConnect(options);
3569
3425
  }
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
- );
3426
+ const cfg = await ensureLoggedConfig();
3427
+ if (options.server?.trim()) {
3428
+ cfg.baseUrl = options.server.trim().replace(/\/+$/, "");
3591
3429
  }
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") ?? "";
3430
+ const clientMachineId = resolveClientMachineId(cfg);
3431
+ if (!clientMachineId) {
3432
+ console.error("[apm] config \u7F3A\u5C11 clientMachineId\uFF0C\u8BF7\u91CD\u65B0 apm login");
3433
+ process.exit(1);
3434
+ }
3435
+ const url = buildAgentWsUrl(cfg.baseUrl, resolveApiKey(cfg));
3436
+ console.log(`[apm] \u8FDE\u63A5 ${cfg.baseUrl} \u2026`);
3437
+ await new Promise((resolve5, reject) => {
3438
+ const ws = new WebSocket(url);
3439
+ let stopHeartbeat;
3440
+ let shuttingDown = false;
3441
+ const shutdownAbort = new AbortController();
3442
+ const runSlots = createRunSlotPool();
3443
+ const activeTasks = /* @__PURE__ */ new Set();
3444
+ const activeRuns = /* @__PURE__ */ new Map();
3445
+ const pendingCancels = /* @__PURE__ */ new Set();
3446
+ const shutdown = async (code = 0) => {
3447
+ if (shuttingDown) return;
3448
+ shuttingDown = true;
3449
+ logAbortSignalStats(
3450
+ shutdownAbort.signal,
3451
+ "connect:shutdown-before-abort"
3452
+ );
3453
+ shutdownAbort.abort();
3454
+ logAbortSignalStats(shutdownAbort.signal, "connect:shutdown-after-abort");
3455
+ stopHeartbeat?.();
3456
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
3457
+ ws.terminate();
3458
+ }
3459
+ try {
3460
+ await Promise.race([
3461
+ Promise.all(activeTasks),
3462
+ new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
3463
+ ]);
3464
+ } catch {
3465
+ }
3466
+ resolve5();
3467
+ process.exit(code);
3468
+ };
3469
+ ws.on("open", () => {
3470
+ console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
3471
+ stopHeartbeat = startHeartbeat(ws, clientMachineId);
3472
+ });
3473
+ ws.on("message", (data) => {
3474
+ if (shuttingDown) return;
3475
+ const text = Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
3476
+ const parsed = parseAgentWsMessage(text);
3477
+ if (parsed === null) {
3478
+ console.error("[apm] \u6536\u5230\u65E0\u6548 JSON");
3479
+ return;
3480
+ }
3481
+ if (typeof parsed === "object" && parsed !== null && parsed.type === "heartbeat") {
3482
+ return;
3483
+ }
3484
+ const validated = validateAgentWsMessage(parsed, "outbound");
3485
+ if (!validated.ok) {
3486
+ console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
3487
+ return;
3488
+ }
3489
+ if (validated.data.type === "cancel") {
3490
+ const { messageId } = validated.data;
3491
+ pendingCancels.add(messageId);
3492
+ activeRuns.get(messageId)?.abort();
3493
+ return;
3494
+ }
3495
+ if (validated.data.type === "deploy") {
3496
+ const msg2 = validated.data;
3497
+ const perDeployController = new AbortController();
3498
+ const signal2 = AbortSignal.any([
3499
+ shutdownAbort.signal,
3500
+ perDeployController.signal
3501
+ ]);
3502
+ const task2 = (async () => {
3503
+ await runSlots.acquire();
3504
+ try {
3505
+ await handleInboundDeploy(cfg, msg2, signal2);
3506
+ } finally {
3507
+ runSlots.release();
3508
+ }
3509
+ })();
3510
+ activeTasks.add(task2);
3511
+ void task2.finally(() => {
3512
+ activeTasks.delete(task2);
3513
+ });
3514
+ return;
3515
+ }
3516
+ if (validated.data.type !== "message") {
3517
+ return;
3518
+ }
3519
+ const msg = validated.data;
3520
+ const perMessageController = new AbortController();
3521
+ activeRuns.set(msg.messageId, perMessageController);
3522
+ if (pendingCancels.has(msg.messageId)) {
3523
+ activeRuns.delete(msg.messageId);
3524
+ pendingCancels.delete(msg.messageId);
3525
+ return;
3526
+ }
3527
+ const signal = AbortSignal.any([
3528
+ shutdownAbort.signal,
3529
+ perMessageController.signal
3530
+ ]);
3531
+ const ctx = {
3532
+ shutdownSignal: shutdownAbort.signal,
3533
+ perMessageSignal: perMessageController.signal
3534
+ };
3535
+ const task = (async () => {
3536
+ await runSlots.acquire();
3537
+ try {
3538
+ await handleInboundMessage(cfg, msg, signal, ctx);
3539
+ } finally {
3540
+ runSlots.release();
3541
+ activeRuns.delete(msg.messageId);
3542
+ pendingCancels.delete(msg.messageId);
3543
+ }
3544
+ })();
3545
+ activeTasks.add(task);
3546
+ void task.finally(() => {
3547
+ activeTasks.delete(task);
3548
+ });
3549
+ });
3550
+ ws.on("close", (code, reason) => {
3607
3551
  console.log(
3608
- `[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
3609
- ${stack}`
3552
+ `[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
3610
3553
  );
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
- );
3554
+ void shutdown();
3555
+ });
3556
+ ws.on("error", (err) => {
3557
+ console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
3558
+ reject(err);
3559
+ });
3560
+ process.on("SIGINT", () => {
3561
+ console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
3562
+ void shutdown();
3563
+ });
3564
+ process.on("SIGTERM", () => {
3565
+ void shutdown();
3566
+ });
3567
+ });
3617
3568
  }
3618
3569
 
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
- }
3570
+ // src/commands/create-pr.ts
3571
+ async function runCreatePr(options) {
3572
+ const sessionId = options.sessionId.trim();
3573
+ if (!sessionId) {
3574
+ console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
3575
+ process.exit(1);
3640
3576
  }
3641
- if (!text.includes("\n") && text.includes("\\n")) {
3642
- text = text.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
3577
+ const title = options.title.trim();
3578
+ if (!title) {
3579
+ console.error("[apm] \u8BF7\u901A\u8FC7 --title \u6307\u5B9A PR \u6807\u9898");
3580
+ process.exit(1);
3643
3581
  }
3644
- return text.replace(/\r\n/g, "\n").trimEnd() + "\n";
3582
+ const cfg = await ensureLoggedConfig();
3583
+ const api = createApmApiClient(cfg);
3584
+ const workdir = resolveWorkdirPath(options.cwd ?? process.cwd());
3585
+ const pr = await api.cli.createPullRequest({
3586
+ sessionId,
3587
+ workdir,
3588
+ title,
3589
+ content: options.content ?? ""
3590
+ });
3591
+ console.log(`[apm] PR \u5DF2\u5C31\u7EEA #${pr.number} (${pr.state}): ${pr.url}`);
3645
3592
  }
3646
3593
 
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);
3594
+ // src/commands/deploy/deploy.ts
3595
+ import { spawnSync as spawnSync5 } from "node:child_process";
3596
+
3597
+ // src/commands/deploy/internal/apm-config.ts
3598
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
3599
+ import { homedir as homedir2 } from "node:os";
3600
+ import { join as join14, resolve as resolve4 } from "node:path";
3601
+ function loadApmConfig(options) {
3602
+ const p = resolve4(
3603
+ process.cwd(),
3604
+ options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
3605
+ );
3606
+ if (!existsSync13(p)) {
3607
+ console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
3608
+ process.exit(1);
3660
3609
  }
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);
3610
+ try {
3611
+ const raw = readFileSync11(p, "utf8");
3612
+ return JSON.parse(raw);
3613
+ } catch (e) {
3614
+ console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
3615
+ process.exit(1);
3709
3616
  }
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
- }
3617
+ }
3618
+ function req(v, field) {
3619
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3620
+ console.error(`apm.config.json \u4E2D backendDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
3621
+ process.exit(1);
3752
3622
  }
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
- });
3623
+ return v;
3624
+ }
3625
+ function reqTopLevelName(cfg) {
3626
+ const n = (cfg.name ?? "").trim();
3627
+ if (!n) {
3628
+ console.error(
3629
+ "\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"
3630
+ );
3631
+ process.exit(1);
3762
3632
  }
3763
- clearDirty(indices) {
3764
- for (const index of indices) {
3765
- this.dirtyIndices.delete(index);
3766
- }
3633
+ return n;
3634
+ }
3635
+ function reqBackendPositiveInt(v, field) {
3636
+ const n = Number(v);
3637
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
3638
+ console.error(`apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
3639
+ process.exit(1);
3767
3640
  }
3768
- /** 合并所有 assistant 片段,供剧场成员回传等场景使用 */
3769
- getAssistantText() {
3770
- return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
3641
+ return n;
3642
+ }
3643
+ function resolveBackendDeployFromApmConfig(cfg) {
3644
+ const b = cfg.backendDeploy ?? {};
3645
+ const protoRaw = req(b.remoteProtocol, "remoteProtocol").trim().toLowerCase();
3646
+ if (protoRaw !== "http" && protoRaw !== "https") {
3647
+ console.error(
3648
+ "apm.config.json \u4E2D backendDeploy.remoteProtocol \u53EA\u80FD\u4E3A http \u6216 https"
3649
+ );
3650
+ process.exit(1);
3771
3651
  }
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
- }
3652
+ const remoteProtocol = protoRaw;
3653
+ let mappings = [];
3654
+ const rawPorts = b.containerPortsMappings;
3655
+ if (rawPorts !== void 0 && rawPorts !== null) {
3656
+ if (!Array.isArray(rawPorts)) {
3657
+ console.error(
3658
+ "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"
3659
+ );
3660
+ process.exit(1);
3786
3661
  }
3787
- return void 0;
3788
- }
3789
- resolveLogContent() {
3790
- return this.events.map((event) => formatLogEvent(event.type, event)).join("\n");
3662
+ mappings = rawPorts.map((x) => String(x).trim()).filter(Boolean);
3791
3663
  }
3792
- };
3793
- function formatLogEvent(type, event) {
3794
- if (type === "input") {
3795
- return `## \u7528\u6237\u8F93\u5165
3796
-
3797
- ${String(event.content ?? "")}
3798
- `;
3664
+ return {
3665
+ name: reqTopLevelName(cfg),
3666
+ registryHost: req(b.registryHost, "registryHost").trim(),
3667
+ registryNamespace: req(b.registryNamespace, "registryNamespace").trim(),
3668
+ registryUser: req(b.registryUser, "registryUser").trim(),
3669
+ registryPassword: req(b.registryPassword, "registryPassword").trim(),
3670
+ remoteHost: req(b.remoteHost, "remoteHost").trim(),
3671
+ remotePort: reqBackendPositiveInt(b.remotePort, "remotePort"),
3672
+ remoteProtocol,
3673
+ caPath: b.caPath?.trim(),
3674
+ certPath: b.certPath?.trim(),
3675
+ keyPath: b.keyPath?.trim(),
3676
+ envFilePath: typeof b.envFilePath === "string" ? b.envFilePath.trim() : "",
3677
+ containerPortsMappings: mappings,
3678
+ dockerNetwork: b.dockerNetwork?.trim() || void 0
3679
+ };
3680
+ }
3681
+ function reqFe(v, field) {
3682
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3683
+ console.error(`apm.config.json \u4E2D frontendDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
3684
+ process.exit(1);
3799
3685
  }
3800
- if (type === "assistant") {
3801
- return `## \u6A21\u578B\u8F93\u51FA
3802
-
3803
- ${String(event.content ?? "")}
3804
- `;
3686
+ return v;
3687
+ }
3688
+ function resolveFrontendDeployFromApmConfig(cfg) {
3689
+ const f = cfg.frontendDeploy ?? {};
3690
+ const port = Number(f.port);
3691
+ return {
3692
+ endpoint: reqFe(f.endpoint, "endpoint").trim(),
3693
+ port: Number.isFinite(port) && port > 0 ? port : 9e3,
3694
+ useSsl: Boolean(f.useSsl),
3695
+ accessKey: reqFe(f.accessKey, "accessKey").trim(),
3696
+ secretKey: reqFe(f.secretKey, "secretKey").trim(),
3697
+ bucket: reqFe(f.bucket, "bucket").trim()
3698
+ };
3699
+ }
3700
+ function reqWd(v, field) {
3701
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3702
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
3703
+ process.exit(1);
3805
3704
  }
3806
- if (type === "thinking") {
3807
- return `## \u6A21\u578B\u601D\u8003
3808
-
3809
- ${String(event.content ?? "")}
3810
- `;
3705
+ return v;
3706
+ }
3707
+ function reqWisdomPositiveInt(v, field) {
3708
+ const n = Number(v);
3709
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
3710
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
3711
+ process.exit(1);
3811
3712
  }
3812
- if (type === "tool_call") {
3813
- return "````toolcall\n" + JSON.stringify(event, null, 2) + "\n````\n";
3713
+ return n;
3714
+ }
3715
+ function resolveWisdomDeployFromApmConfig(cfg) {
3716
+ const w = cfg.wisdomDeploy ?? {};
3717
+ return {
3718
+ host: reqWd(w.host, "host").trim(),
3719
+ port: reqWisdomPositiveInt(w.port, "port"),
3720
+ username: reqWd(w.username, "username").trim(),
3721
+ password: reqWd(w.password, "password").trim(),
3722
+ remotePath: reqWd(w.remotePath, "remotePath").trim()
3723
+ };
3724
+ }
3725
+ function reqHc(v, field) {
3726
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3727
+ console.error(`apm.config.json \u4E2D healthCheck.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
3728
+ process.exit(1);
3814
3729
  }
3815
- return `## \u672A\u77E5\u4E8B\u4EF6\uFF1A${type}
3816
-
3817
- \`\`\`json
3818
- ${JSON.stringify(event, null, 2)}
3819
- \`\`\``;
3730
+ return v;
3820
3731
  }
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");
3732
+ function reqWb(v, field) {
3733
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3734
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
3735
+ process.exit(1);
3736
+ }
3737
+ return v;
3827
3738
  }
3828
- function readRegistry(path12) {
3829
- if (!existsSync15(path12)) {
3830
- return {};
3739
+ function readEnvVar(env, name) {
3740
+ if (env[name] !== void 0) {
3741
+ return env[name];
3831
3742
  }
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
- }
3743
+ if (process.platform === "win32") {
3744
+ const target = name.toUpperCase();
3745
+ for (const [key, value] of Object.entries(env)) {
3746
+ if (key.toUpperCase() === target) {
3747
+ return value;
3842
3748
  }
3843
- return result;
3844
3749
  }
3845
- } catch {
3846
3750
  }
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];
3751
+ return void 0;
3857
3752
  }
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);
3753
+ function expandWindowsEnvVars(pathStr, env = process.env) {
3754
+ return pathStr.replace(/%([^%]+)%/g, (_, name) => {
3755
+ const value = readEnvVar(env, name);
3756
+ return value ?? `%${name}%`;
3757
+ });
3863
3758
  }
3864
- function clearSessionAgentId(workdir, sessionId, user) {
3865
- const path12 = registryPath(workdir, sessionId);
3866
- const registry = readRegistry(path12);
3867
- if (!(user in registry)) {
3868
- return;
3759
+ function expandUserPath(pathStr, env = process.env) {
3760
+ const home = env.HOME ?? env.USERPROFILE ?? homedir2();
3761
+ const withEnv = expandWindowsEnvVars(pathStr, env);
3762
+ const expanded = withEnv.replace(/^~(?=\/|\\|$)/, home);
3763
+ if (/^[a-zA-Z]:[/\\]/.test(expanded)) {
3764
+ return expanded;
3869
3765
  }
3870
- delete registry[user];
3871
- writeRegistry(path12, registry);
3766
+ return resolve4(expanded);
3872
3767
  }
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);
3768
+ var MAVEN_REPO_ENV_KEYS = [
3769
+ "MAVEN_LOCAL_REPO",
3770
+ "M2_REPO",
3771
+ "MAVEN_REPOSITORY"
3772
+ ];
3773
+ function readMavenLocalRepoFromMavenOpts(mavenOpts, env = process.env) {
3774
+ const match = mavenOpts.match(/-Dmaven\.repo\.local=(?:"([^"]+)"|(\S+))/);
3775
+ const raw = match?.[1]?.trim() || match?.[2]?.trim();
3776
+ return raw ? expandUserPath(raw, env) : null;
3777
+ }
3778
+ function readMavenLocalRepoFromEnv(env = process.env) {
3779
+ for (const key of MAVEN_REPO_ENV_KEYS) {
3780
+ const raw = readEnvVar(env, key)?.trim();
3781
+ if (raw) {
3782
+ return { path: expandUserPath(raw, env), key };
3931
3783
  }
3932
- };
3933
- }
3934
- async function syncCursorMessageLog(cfg, ctx, events) {
3935
- const agentId = ctx.agentId.trim();
3936
- if (!agentId || events.length === 0) {
3937
- return;
3938
3784
  }
3939
- const api = createApmApiClient(cfg);
3940
- await api.cli.upsertCursorMessageLog({
3941
- sessionId: ctx.sessionId,
3942
- messageId: ctx.messageId,
3943
- agentId,
3944
- events
3945
- });
3785
+ const mavenOpts = readEnvVar(env, "MAVEN_OPTS")?.trim();
3786
+ if (mavenOpts) {
3787
+ const path12 = readMavenLocalRepoFromMavenOpts(mavenOpts, env);
3788
+ if (path12) {
3789
+ return { path: path12, key: "MAVEN_OPTS" };
3790
+ }
3791
+ }
3792
+ return null;
3946
3793
  }
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
- }
3794
+ function readMavenLocalRepoFromSettings() {
3795
+ const settingsPath = join14(homedir2(), ".m2", "settings.xml");
3796
+ if (!existsSync13(settingsPath)) {
3797
+ return null;
3798
+ }
3799
+ try {
3800
+ const xml = readFileSync11(settingsPath, "utf8");
3801
+ const match = xml.match(
3802
+ /<localRepository>\s*([^<]+?)\s*<\/localRepository>/
3803
+ );
3804
+ const raw = match?.[1]?.trim();
3805
+ if (!raw) {
3806
+ return null;
3983
3807
  }
3984
- };
3808
+ return expandUserPath(raw);
3809
+ } catch {
3810
+ return null;
3811
+ }
3985
3812
  }
3986
-
3987
- // src/commands/connect/ask-question-tool.ts
3988
- function createAskQuestionMockTool(options) {
3813
+ function resolveMavenLocalRepoWithSource() {
3814
+ const fromEnv = readMavenLocalRepoFromEnv();
3815
+ if (fromEnv) {
3816
+ return {
3817
+ path: fromEnv.path,
3818
+ source: "env",
3819
+ sourceDetail: fromEnv.key
3820
+ };
3821
+ }
3822
+ const fromSettings = readMavenLocalRepoFromSettings();
3823
+ if (fromSettings) {
3824
+ return {
3825
+ path: fromSettings,
3826
+ source: "settings",
3827
+ sourceDetail: "~/.m2/settings.xml"
3828
+ };
3829
+ }
3989
3830
  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
- }
3831
+ path: join14(homedir2(), ".m2", "repository"),
3832
+ source: "default",
3833
+ sourceDetail: "~/.m2/repository"
4034
3834
  };
4035
3835
  }
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) {
3836
+ function resolveWisdomBackendDeployFromApmConfig(cfg) {
3837
+ const projectName = reqTopLevelName(cfg);
3838
+ const w = cfg.wisdomDeploy ?? {};
3839
+ const h = cfg.healthCheck ?? {};
3840
+ const jarPath = reqWb(w.jarPath, "jarPath").trim();
3841
+ const remoteAppDir = posixDirname(jarPath);
3842
+ const healthPort = Number(reqHc(h.port, "port"));
3843
+ const healthTimeout = Number(reqHc(h.timeout, "timeout"));
3844
+ if (!Number.isFinite(healthPort) || !Number.isInteger(healthPort) || healthPort < 1) {
3845
+ console.error("apm.config.json \u4E2D healthCheck.port \u987B\u4E3A\u6B63\u6574\u6570");
3846
+ process.exit(1);
3847
+ }
3848
+ if (!Number.isFinite(healthTimeout) || !Number.isInteger(healthTimeout) || healthTimeout < 1) {
3849
+ console.error("apm.config.json \u4E2D healthCheck.timeout \u987B\u4E3A\u6B63\u6574\u6570");
3850
+ process.exit(1);
3851
+ }
3852
+ const mavenLocalRepo = resolveMavenLocalRepoWithSource();
4043
3853
  return {
4044
- ...createAppendMessageCustomTools(cfg, messageId),
4045
- AskQuestion: createAskQuestionMockTool({
4046
- onInvoke: options?.onAskQuestion
4047
- })
3854
+ projectName,
3855
+ host: reqWd(w.host, "host").trim(),
3856
+ port: reqWisdomPositiveInt(w.port, "port"),
3857
+ username: reqWd(w.username, "username").trim(),
3858
+ password: reqWd(w.password, "password").trim(),
3859
+ remoteVueDistDir: reqWd(w.remotePath, "remotePath").trim(),
3860
+ remoteAppDir,
3861
+ remoteLibDir: `${remoteAppDir}/lib`,
3862
+ startupJar: posixBasename(jarPath),
3863
+ packageName: `${projectName}.jar.zip`,
3864
+ mavenLocalRepo: mavenLocalRepo.path,
3865
+ mavenLocalRepoSource: mavenLocalRepo.source,
3866
+ mavenLocalRepoSourceDetail: mavenLocalRepo.sourceDetail,
3867
+ healthCheckPort: healthPort,
3868
+ healthCheckContext: reqHc(h.context, "context").trim(),
3869
+ healthCheckTimeout: healthTimeout
4048
3870
  };
4049
3871
  }
4050
- function withPlanModeToolHint(prompt, mode) {
4051
- if (mode !== "plan") {
4052
- return prompt;
3872
+ function posixDirname(p) {
3873
+ const normalized = p.replace(/\\/g, "/");
3874
+ const idx = normalized.lastIndexOf("/");
3875
+ if (idx <= 0) {
3876
+ return normalized.startsWith("/") ? "/" : ".";
4053
3877
  }
4054
- return `${prompt.trim()}
4055
-
4056
- ${PLAN_MODE_ASK_QUESTION_HINT}`;
3878
+ return normalized.slice(0, idx);
3879
+ }
3880
+ function posixBasename(p) {
3881
+ const normalized = p.replace(/\\/g, "/");
3882
+ const idx = normalized.lastIndexOf("/");
3883
+ return idx >= 0 ? normalized.slice(idx + 1) : normalized;
4057
3884
  }
4058
3885
 
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}`;
3886
+ // src/commands/deploy/internal/wisdom-auto-deploy.ts
3887
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
3888
+ import path3 from "node:path";
3889
+ import { spawnSync as spawnSync4 } from "node:child_process";
3890
+
3891
+ // src/commands/deploy/internal/wisdom-backend-deploy.ts
3892
+ import {
3893
+ existsSync as existsSync14,
3894
+ mkdirSync as mkdirSync6,
3895
+ readdirSync as readdirSync5,
3896
+ readFileSync as readFileSync12,
3897
+ statSync as statSync5,
3898
+ writeFileSync as writeFileSync12
3899
+ } from "node:fs";
3900
+ import { spawnSync as spawnSync3 } from "node:child_process";
3901
+ import path2 from "node:path";
3902
+ import { Client } from "ssh2";
3903
+ import JSZip2 from "jszip";
3904
+ import SftpClient2 from "ssh2-sftp-client";
3905
+
3906
+ // src/commands/deploy/internal/wisdom-sftp.ts
3907
+ import { readdir, readFile, unlink, writeFile } from "node:fs/promises";
3908
+ import path from "node:path";
3909
+ import JSZip from "jszip";
3910
+ import SftpClient from "ssh2-sftp-client";
3911
+ async function addDirToZip(dir, zipFolder) {
3912
+ const entries = await readdir(dir, { withFileTypes: true });
3913
+ for (const entry of entries) {
3914
+ const fullPath = path.join(dir, entry.name);
3915
+ if (entry.isDirectory()) {
3916
+ const folder = zipFolder.folder(entry.name);
3917
+ if (folder) {
3918
+ await addDirToZip(fullPath, folder);
3919
+ }
3920
+ } else {
3921
+ const content = await readFile(fullPath);
3922
+ zipFolder.file(entry.name, content);
3923
+ }
4083
3924
  }
4084
- return `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
4085
3925
  }
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) {
3926
+ async function zipDirectory(distDir, zipPath) {
3927
+ console.error(`\u538B\u7F29\u76EE\u5F55: ${distDir}`);
3928
+ const zip = new JSZip();
3929
+ await addDirToZip(distDir, zip);
3930
+ const content = await zip.generateAsync({
3931
+ type: "nodebuffer",
3932
+ compression: "DEFLATE",
3933
+ compressionOptions: { level: 6 }
3934
+ });
3935
+ await writeFile(zipPath, content);
3936
+ const sizeMb = (content.length / 1024 / 1024).toFixed(2);
3937
+ console.error(`\u5DF2\u751F\u6210: ${zipPath} (${sizeMb} MB)`);
3938
+ return content.length;
3939
+ }
3940
+ async function ensureRemoteDir(sftp, dir) {
3941
+ const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
3942
+ let current = dir.startsWith("/") ? "" : ".";
3943
+ for (const part of parts) {
3944
+ current = current ? `${current}/${part}` : `/${part}`;
4100
3945
  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
- }
3946
+ await sftp.mkdir(current, true);
3947
+ } catch {
4117
3948
  }
4118
3949
  }
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
3950
  }
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
3951
+ function execCommand(client, command) {
3952
+ return new Promise((resolve5, reject) => {
3953
+ client.exec(command, (err, stream) => {
3954
+ if (err) return reject(err);
3955
+ let stdout = "";
3956
+ let stderr = "";
3957
+ stream.on("close", (code) => {
3958
+ if (code !== 0) {
3959
+ reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
3960
+ return;
3961
+ }
3962
+ resolve5(stdout);
3963
+ }).on("data", (data) => {
3964
+ stdout += data.toString();
3965
+ });
3966
+ stream.stderr.on("data", (data) => {
3967
+ stderr += data.toString();
3968
+ });
3969
+ });
4153
3970
  });
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
- }
3971
+ }
3972
+ function shellSingleQuote(value) {
3973
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
3974
+ }
3975
+ function buildClearRemoteDirExceptZipCommand(target) {
3976
+ const normalized = target.replace(/\\/g, "/").replace(/\/$/, "");
3977
+ const quotedTarget = shellSingleQuote(normalized);
3978
+ const script = [
3979
+ `T=${quotedTarget}`,
3980
+ "S=$(mktemp -d)",
3981
+ 'while IFS= read -r -d "" z; do r="${z#${T}/}"',
3982
+ 'mkdir -p "${S}/$(dirname "$r")"',
3983
+ 'mv "$z" "${S}/${r}"',
3984
+ 'done < <(find "$T" -mindepth 1 -type f -iname "*.zip" -print0)',
3985
+ 'rm -rf "${T}"/*',
3986
+ 'while IFS= read -r -d "" r; do r="${r#./}"',
3987
+ 'mkdir -p "${T}/$(dirname "$r")"',
3988
+ 'mv "${S}/${r}" "${T}/${r}"',
3989
+ 'done < <(cd "$S" 2>/dev/null && find . -type f -print0)',
3990
+ 'rm -rf "$S"'
3991
+ ].join("; ");
3992
+ return `bash -c ${shellSingleQuote(script)}`;
3993
+ }
3994
+ async function uploadAndMaybeExtract(settings, localZip, extract) {
3995
+ const remoteZipPath = `${settings.remotePath.replace(/\/$/, "")}/dist.zip`;
3996
+ console.error(
3997
+ `\u8FDE\u63A5 ${settings.username}@${settings.host}:${settings.port} ...`
4164
3998
  );
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");
3999
+ const sftp = new SftpClient();
4172
4000
  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
- }
4001
+ await sftp.connect({
4002
+ host: settings.host,
4003
+ port: settings.port,
4004
+ username: settings.username,
4005
+ password: settings.password,
4006
+ readyTimeout: 2e4,
4007
+ tryKeyboard: true
4180
4008
  });
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);
4009
+ await ensureRemoteDir(sftp, settings.remotePath);
4010
+ await sftp.put(localZip, remoteZipPath);
4011
+ console.error(` \u2713 ${localZip} -> ${remoteZipPath}`);
4012
+ if (extract) {
4013
+ const target = settings.remotePath.replace(/\/$/, "");
4014
+ const client = sftp.client;
4015
+ const clearCmd = buildClearRemoteDirExceptZipCommand(target);
4016
+ console.error(`\u8FDC\u7A0B\u6E05\u7406\u89E3\u538B\u76EE\u5F55: ${clearCmd}`);
4017
+ await execCommand(client, clearCmd);
4018
+ const unzipCmd = `unzip -o "${remoteZipPath}" -d "${target}"`;
4019
+ console.error(`\u8FDC\u7A0B\u89E3\u538B: ${unzipCmd}`);
4020
+ await execCommand(client, unzipCmd);
4021
+ console.error("\u8FDC\u7A0B\u89E3\u538B\u5B8C\u6210!");
4202
4022
  }
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);
4023
+ console.error(extract ? "\u90E8\u7F72\u5B8C\u6210!" : "\u4E0A\u4F20\u5B8C\u6210!");
4024
+ } finally {
4025
+ await sftp.end();
4026
+ }
4027
+ }
4028
+ async function runWisdomSftpDeploy(params) {
4029
+ const zipPath = path.join(params.localDir, "..", ".deploy-sftp-dist.zip");
4030
+ const resolvedZipPath = path.resolve(zipPath);
4031
+ let zipSizeBytes = 0;
4032
+ try {
4033
+ zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
4034
+ await uploadAndMaybeExtract(
4035
+ params.settings,
4036
+ resolvedZipPath,
4037
+ params.extract
4038
+ );
4039
+ } finally {
4040
+ try {
4041
+ await unlink(resolvedZipPath);
4042
+ console.error(`\u5DF2\u6E05\u7406\u672C\u5730\u4E34\u65F6\u6587\u4EF6: ${resolvedZipPath}`);
4043
+ } catch {
4044
+ }
4045
+ }
4046
+ return {
4047
+ ok: true,
4048
+ localDir: params.localDir,
4049
+ host: params.settings.host,
4050
+ remotePath: params.settings.remotePath,
4051
+ zipSizeBytes,
4052
+ extracted: params.extract
4053
+ };
4054
+ }
4055
+
4056
+ // src/commands/deploy/internal/wisdom-backend-deploy.ts
4057
+ var SPRINGBOOT_JAVA_OPTS = "-XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -Xms512M -Xmx1G";
4058
+ var MAVEN_MODULE = "jeecg-module-system/jeecg-system-start";
4059
+ var MAVEN_PROFILE = "dev";
4060
+ function log(message) {
4061
+ const now = /* @__PURE__ */ new Date();
4062
+ const hh = String(now.getHours()).padStart(2, "0");
4063
+ const mm = String(now.getMinutes()).padStart(2, "0");
4064
+ const ss = String(now.getSeconds()).padStart(2, "0");
4065
+ console.error(`[${hh}:${mm}:${ss}] ${message}`);
4066
+ }
4067
+ function fail(message) {
4068
+ log(`ERROR: ${message}`);
4069
+ process.exit(1);
4070
+ }
4071
+ function expandPath(pathStr) {
4072
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
4073
+ return path2.resolve(pathStr.replace(/^~(?=\/|\\|$)/, home));
4074
+ }
4075
+ function quoteForShell(value) {
4076
+ if (process.platform === "win32") {
4077
+ return `"${value.replace(/"/g, '""')}"`;
4078
+ }
4079
+ return shellSingleQuote(value);
4080
+ }
4081
+ function formatMavenLocalRepoArg(repoPath) {
4082
+ if (process.platform === "win32") {
4083
+ return `-Dmaven.repo.local=${quoteForShell(repoPath)}`;
4084
+ }
4085
+ return `-Dmaven.repo.local=${repoPath}`;
4086
+ }
4087
+ function deployCacheDir() {
4088
+ return path2.join(workspaceApmDir(), "deploy", ".deploy_cache");
4089
+ }
4090
+ function manifestFilePath() {
4091
+ return path2.join(deployCacheDir(), "manifest.json");
4092
+ }
4093
+ function getTargetDir(projectRoot) {
4094
+ return path2.join(projectRoot, MAVEN_MODULE, "target");
4095
+ }
4096
+ function relativeKey(projectRoot, filePath) {
4097
+ return path2.relative(projectRoot, filePath).split(path2.sep).join("/");
4098
+ }
4099
+ function fileSignature(filePath) {
4100
+ const stat2 = statSync5(filePath);
4101
+ return { size: stat2.size, mtime: stat2.mtimeMs / 1e3 };
4102
+ }
4103
+ function loadManifest4() {
4104
+ const manifestPath2 = manifestFilePath();
4105
+ if (!existsSync14(manifestPath2)) {
4106
+ return {};
4107
+ }
4108
+ return JSON.parse(readFileSync12(manifestPath2, "utf8"));
4109
+ }
4110
+ function saveManifest4(manifest) {
4111
+ const dir = deployCacheDir();
4112
+ mkdirSync6(dir, { recursive: true });
4113
+ writeFileSync12(manifestFilePath(), JSON.stringify(manifest, null, 2), "utf8");
4114
+ }
4115
+ function isProjectLibJar(jarName) {
4116
+ return jarName.startsWith("jeecg-");
4117
+ }
4118
+ function shouldUploadLibFile(localPath, remoteAttr, manifest, projectRoot) {
4119
+ if (!remoteAttr) {
4120
+ return [false, "\u8FDC\u7A0B\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7"];
4121
+ }
4122
+ const localSize = statSync5(localPath).size;
4123
+ const remoteSize = remoteAttr.size;
4124
+ if (localSize !== remoteSize) {
4125
+ return [true, `\u5927\u5C0F\u53D8\u5316 ${remoteSize} -> ${localSize}`];
4126
+ }
4127
+ if (isProjectLibJar(path2.basename(localPath)) && manifest) {
4128
+ const key = relativeKey(projectRoot, localPath);
4129
+ const current = fileSignature(localPath);
4130
+ const previous = manifest[key];
4131
+ if (!previous) {
4132
+ return [true, "\u9879\u76EE\u6A21\u5757\u672A\u8BB0\u5F55"];
4215
4133
  }
4216
- if (result.status === "cancelled") {
4217
- throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
4134
+ if (previous.size !== current.size) {
4135
+ return [true, "\u9879\u76EE\u6A21\u5757\u5927\u5C0F\u53D8\u5316"];
4218
4136
  }
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
- }
4137
+ if (previous.mtime < current.mtime) {
4138
+ return [true, "\u9879\u76EE\u6A21\u5757\u91CD\u65B0\u6784\u5EFA"];
4234
4139
  }
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
- );
4140
+ }
4141
+ return [false, "\u5927\u5C0F\u4E00\u81F4\uFF0C\u8DF3\u8FC7"];
4142
+ }
4143
+ function listLibFilesToUpload(localLibDir, remoteStats, projectRoot, manifest = null) {
4144
+ const entries = [];
4145
+ const jarFiles = readdirSync5(localLibDir).filter((name) => name.endsWith(".jar")).sort();
4146
+ for (const jarName of jarFiles) {
4147
+ const jarPath = path2.join(localLibDir, jarName);
4148
+ const remoteAttr = remoteStats.get(jarName);
4149
+ const [shouldUpload, reason] = shouldUploadLibFile(
4150
+ jarPath,
4151
+ remoteAttr,
4152
+ manifest,
4153
+ projectRoot
4154
+ );
4155
+ if (shouldUpload) {
4156
+ entries.push({ path: jarPath, arcname: jarName, reason });
4254
4157
  }
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
4158
  }
4159
+ return entries;
4262
4160
  }
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;
4161
+ function updateManifestEntries(manifest, entries, projectRoot) {
4162
+ for (const entry of entries) {
4163
+ manifest[relativeKey(projectRoot, entry.path)] = fileSignature(entry.path);
4164
+ }
4165
+ return manifest;
4166
+ }
4167
+ async function createUpdatePackage(entries, packageName) {
4168
+ const dir = deployCacheDir();
4169
+ mkdirSync6(dir, { recursive: true });
4170
+ const zipPath = path2.join(dir, packageName);
4171
+ log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${path2.basename(zipPath)}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
4172
+ const zip = new JSZip2();
4173
+ for (const entry of entries) {
4174
+ const content = readFileSync12(entry.path);
4175
+ zip.file(entry.arcname, content);
4176
+ log(` \u6253\u5305: ${entry.arcname} (${entry.reason})`);
4177
+ }
4178
+ const buffer = await zip.generateAsync({
4179
+ type: "nodebuffer",
4180
+ compression: "DEFLATE",
4181
+ compressionOptions: { level: 6 }
4182
+ });
4183
+ writeFileSync12(zipPath, buffer);
4184
+ return zipPath;
4185
+ }
4186
+ function getMvnExecutable() {
4187
+ const isWin = process.platform === "win32";
4188
+ const candidates = isWin ? ["mvn.cmd", "mvn.bat", "mvn"] : ["mvn"];
4189
+ for (const name of candidates) {
4190
+ const result = spawnSync3(isWin ? `where ${name}` : `which ${name}`, {
4191
+ encoding: "utf8",
4192
+ shell: true
4193
+ });
4194
+ if (result.status === 0 && result.stdout.trim()) {
4195
+ return result.stdout.trim().split(/\r?\n/)[0].trim();
4275
4196
  }
4276
4197
  }
4277
- return DEFAULT_REPLY;
4198
+ fail("\u672A\u627E\u5230 mvn \u547D\u4EE4\uFF0C\u8BF7\u786E\u8BA4 Maven \u5DF2\u5B89\u88C5\u5E76\u52A0\u5165 PATH");
4278
4199
  }
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}`);
4200
+ function runMavenBuild(projectRoot, mavenLocalRepo, repoSource) {
4201
+ const mavenRepo = expandPath(mavenLocalRepo);
4202
+ const mvn = getMvnExecutable();
4203
+ const command = [
4204
+ quoteForShell(mvn),
4205
+ "clean",
4206
+ "package",
4207
+ `-P${MAVEN_PROFILE}`,
4208
+ formatMavenLocalRepoArg(mavenRepo),
4209
+ "-DskipTests"
4210
+ ].join(" ");
4211
+ log("\u5F00\u59CB Maven \u6784\u5EFA...");
4212
+ if (repoSource) {
4213
+ log(
4214
+ `Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo} (\u6765\u6E90: ${repoSource.source}/${repoSource.sourceDetail})`
4215
+ );
4216
+ } else {
4217
+ log(`Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo}`);
4218
+ }
4219
+ log(`\u6784\u5EFA\u76EE\u5F55: ${projectRoot}`);
4220
+ const result = spawnSync3(command, {
4221
+ cwd: projectRoot,
4222
+ stdio: "inherit",
4223
+ shell: true,
4224
+ env: process.env
4225
+ });
4226
+ if (result.status !== 0) {
4227
+ fail(`Maven \u6784\u5EFA\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${result.status ?? 1}`);
4285
4228
  }
4286
- return message.content.trim();
4287
4229
  }
4288
- async function ensureMessageHasReply(cfg, sessionId, messageId, fallback) {
4289
- const existing = await fetchMessageContent(cfg, sessionId, messageId);
4290
- if (existing) {
4291
- return;
4230
+ function locateLibDir(projectRoot) {
4231
+ const targetDir = getTargetDir(projectRoot);
4232
+ if (!existsSync14(targetDir)) {
4233
+ fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4292
4234
  }
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
- );
4235
+ const libDir = path2.join(targetDir, "lib");
4236
+ if (!existsSync14(libDir) || !statSync5(libDir).isDirectory()) {
4237
+ fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
4238
+ }
4239
+ const libJars = readdirSync5(libDir).filter((name) => name.endsWith(".jar"));
4240
+ if (libJars.length === 0) {
4241
+ fail(`lib \u76EE\u5F55\u4E0B\u6CA1\u6709\u4F9D\u8D56 JAR: ${libDir}`);
4242
+ }
4243
+ log(`\u5B9A\u4F4D lib \u4EA7\u7269: ${libJars.length} \u4E2A`);
4244
+ return libDir;
4298
4245
  }
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);
4246
+ async function connectSsh(config) {
4247
+ const client = new Client();
4248
+ log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${config.username}@${config.host}:${config.port}`);
4249
+ await new Promise((resolve5, reject) => {
4250
+ client.on("ready", () => resolve5()).on("error", (err) => reject(err)).connect({
4251
+ host: config.host,
4252
+ port: config.port,
4253
+ username: config.username,
4254
+ password: config.password,
4255
+ readyTimeout: 3e4,
4256
+ tryKeyboard: true
4257
+ });
4258
+ });
4259
+ const sftp = new SftpClient2();
4260
+ await sftp.connect({
4261
+ host: config.host,
4262
+ port: config.port,
4263
+ username: config.username,
4264
+ password: config.password,
4265
+ readyTimeout: 3e4,
4266
+ tryKeyboard: true
4267
+ });
4268
+ return { client, sftp };
4306
4269
  }
4307
- function loadManifest4(apmDir) {
4308
- const path12 = toFsPath(manifestPath(apmDir));
4309
- if (!existsSync16(path12)) {
4310
- return null;
4270
+ async function closeSsh(conn) {
4271
+ try {
4272
+ await conn.sftp.end();
4273
+ } catch {
4311
4274
  }
4275
+ conn.client.end();
4276
+ }
4277
+ async function getRemoteFileStats(sftp, remoteDir) {
4278
+ const stats = /* @__PURE__ */ new Map();
4312
4279
  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;
4280
+ const listing = await sftp.list(remoteDir);
4281
+ for (const item of listing) {
4282
+ if (item.name.endsWith(".jar")) {
4283
+ stats.set(item.name, { filename: item.name, size: item.size });
4284
+ }
4318
4285
  }
4319
4286
  } catch {
4320
4287
  }
4321
- return null;
4288
+ return stats;
4322
4289
  }
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"
4290
+ async function uploadUpdatePackage(sftp, zipPath, config) {
4291
+ const remoteDir = config.remoteVueDistDir.replace(/\/$/, "");
4292
+ const remotePath = `${remoteDir}/${path2.basename(zipPath)}`;
4293
+ log(`\u4E0A\u4F20\u66F4\u65B0\u5305 -> ${remotePath}`);
4294
+ try {
4295
+ await sftp.fastPut(zipPath, remotePath);
4296
+ log("\u66F4\u65B0\u5305\u4E0A\u4F20\u6210\u529F");
4297
+ } catch (err) {
4298
+ fail(`\u66F4\u65B0\u5305\u4E0A\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : err}`);
4299
+ }
4300
+ return remotePath;
4301
+ }
4302
+ async function runRemoteCommand(client, command, options) {
4303
+ const check = options?.check ?? true;
4304
+ const stream = options?.stream ?? false;
4305
+ const label = options?.label ?? "\u8FDC\u7A0B\u547D\u4EE4";
4306
+ return new Promise((resolve5, reject) => {
4307
+ client.exec(command, (err, execStream) => {
4308
+ if (err) {
4309
+ reject(err);
4310
+ return;
4311
+ }
4312
+ let out = "";
4313
+ let errText = "";
4314
+ execStream.on("data", (data) => {
4315
+ const text = data.toString();
4316
+ out += text;
4317
+ if (stream) {
4318
+ process.stdout.write(text);
4319
+ }
4320
+ });
4321
+ execStream.stderr.on("data", (data) => {
4322
+ errText += data.toString();
4323
+ });
4324
+ execStream.on("close", (code) => {
4325
+ if (stream && out && !out.endsWith("\n")) {
4326
+ process.stdout.write("\n");
4327
+ }
4328
+ if (check && code !== 0) {
4329
+ const combined = `${out}
4330
+ ${errText}`.trim();
4331
+ fail(
4332
+ `${label}\u5931\u8D25 (exit ${code})` + (combined ? `
4333
+ \u8F93\u51FA: ${combined}` : "")
4334
+ );
4335
+ }
4336
+ resolve5({ exitCode: code, out: out.trim(), err: errText.trim() });
4337
+ });
4338
+ });
4339
+ });
4340
+ }
4341
+ function buildExtractUpdatePackageScript(remoteZipPath, remoteLibDir) {
4342
+ const quotedZip = shellSingleQuote(remoteZipPath);
4343
+ const quotedLib = shellSingleQuote(remoteLibDir);
4344
+ return `
4345
+ set -e
4346
+ TMP=$(mktemp -d)
4347
+ trap 'rm -rf "$TMP"' EXIT
4348
+ unzip -oq ${quotedZip} -d "$TMP"
4349
+ updated=0
4350
+ while IFS= read -r -d '' src; do
4351
+ name=$(basename "$src")
4352
+ dest=${quotedLib}/"$name"
4353
+ if [ -f "$dest" ]; then
4354
+ cp -f "$src" "$dest"
4355
+ echo "\u8986\u76D6: $name"
4356
+ updated=$((updated + 1))
4357
+ else
4358
+ echo "\u8DF3\u8FC7(\u8FDC\u7A0B\u4E0D\u5B58\u5728): $name"
4359
+ fi
4360
+ done < <(find "$TMP" -name '*.jar' -type f -print0)
4361
+ echo "UPDATED_COUNT=$updated"
4362
+ `.trim();
4363
+ }
4364
+ async function extractUpdatePackageOnRemote(client, config, remoteZipPath) {
4365
+ const script = buildExtractUpdatePackageScript(
4366
+ remoteZipPath,
4367
+ config.remoteLibDir
4330
4368
  );
4369
+ const { out } = await runRemoteCommand(client, script, {
4370
+ label: "\u8FDC\u7A0B\u89E3\u538B"
4371
+ });
4372
+ const match = out.match(/UPDATED_COUNT=(\d+)/);
4373
+ if (!match) {
4374
+ fail(`\u8FDC\u7A0B\u89E3\u538B\u5931\u8D25\uFF0C\u672A\u83B7\u53D6\u66F4\u65B0\u6570\u91CF
4375
+ \u8F93\u51FA: ${out || "(\u7A7A)"}`);
4376
+ }
4377
+ const updated = Number.parseInt(match[1], 10);
4378
+ log(`lib \u89E3\u538B\u5B8C\u6210: \u8986\u76D6 ${updated} \u4E2A`);
4379
+ return updated;
4331
4380
  }
4332
- var syncedInSession = /* @__PURE__ */ new Map();
4333
- function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
4334
- const cached = syncedInSession.get(workdir);
4335
- if (cached === currentVersion) {
4336
- return false;
4381
+ function springbootOutputIndicatesSuccess(action, combined) {
4382
+ const lower = combined.toLowerCase();
4383
+ if (action === "health") {
4384
+ return combined.includes("\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7");
4337
4385
  }
4338
- const stored = loadManifest4(workspaceApmDir(workdir));
4339
- if (stored?.cliVersion === currentVersion) {
4340
- syncedInSession.set(workdir, currentVersion);
4341
- return false;
4386
+ if (action === "start" || action === "restart") {
4387
+ return combined.includes("is starting") || lower.includes("is running");
4388
+ }
4389
+ if (action === "stop") {
4390
+ return combined.includes("is stopping") || lower.includes("not running") || lower.includes("please check it");
4391
+ }
4392
+ if (action === "status") {
4393
+ return lower.includes("running") || lower.includes("not running");
4342
4394
  }
4343
4395
  return true;
4344
4396
  }
4345
- function markSkillsSyncedForCliVersion(workdir, cliVersion) {
4346
- saveManifest4(workspaceApmDir(workdir), cliVersion);
4347
- syncedInSession.set(workdir, cliVersion);
4397
+ function buildRemoteStatusScript(remoteAppDir, appName) {
4398
+ const dir = shellSingleQuote(remoteAppDir);
4399
+ const jar = shellSingleQuote(appName);
4400
+ return `
4401
+ set -e
4402
+ cd ${dir}
4403
+ appName=${jar}
4404
+ appIds=$(ps -ef | grep java | grep "$appName" | awk '{print $2}')
4405
+ if [ -z "$appIds" ]; then
4406
+ echo -e "\\033[31m Not running \\033[0m"
4407
+ else
4408
+ echo -e "\\033[32m Running [$appIds] \\033[0m"
4409
+ fi
4410
+ `.trim();
4348
4411
  }
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}`;
4412
+ function buildRemoteRestartScript(remoteAppDir) {
4413
+ const dir = shellSingleQuote(remoteAppDir);
4414
+ const javaOpts = shellSingleQuote(SPRINGBOOT_JAVA_OPTS);
4415
+ return `
4416
+ set -e
4417
+ cd ${dir}
4418
+ releaseApp=$(ls -t | grep '.jar$' | head -n1)
4419
+ lastVersionApp=$(ls -t | grep '.jar$' | head -n2 | tail -n1)
4420
+ appName=$lastVersionApp
4421
+ appIds=$(ps -ef | grep java | grep "$appName" | awk '{print $2}')
4422
+ if [ -z "$appIds" ]; then
4423
+ echo "Maybe $appName not running, please check it..."
4424
+ else
4425
+ echo "The $appName is stopping..."
4426
+ echo "$appIds" | xargs kill
4427
+ fi
4428
+ for i in $(seq 15 -1 1); do
4429
+ echo -n "$i "
4430
+ sleep 1
4431
+ done
4432
+ echo 0
4433
+ if [ ! -d "backup" ]; then
4434
+ mkdir backup
4435
+ fi
4436
+ for i in $(ls | grep '.jar$' | grep -vFx "$releaseApp"); do
4437
+ echo "backup $i"
4438
+ mv "$i" backup/
4439
+ done
4440
+ appName=$releaseApp
4441
+ count=$(ps -ef | grep java | grep "$appName" | wc -l)
4442
+ if [ "$count" != "0" ]; then
4443
+ echo "Maybe $appName is running, please check it..."
4444
+ else
4445
+ echo "The $appName is starting..."
4446
+ nohup java -jar "./$appName" ${javaOpts} > nohup.out 2>&1 &
4447
+ fi
4448
+ `.trim();
4354
4449
  }
4355
- var lastBranchKey = null;
4356
- var lastPullAtByKey = /* @__PURE__ */ new Map();
4357
- function shouldRunBranch(sessionId, workdir) {
4358
- return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
4450
+ function normalizeHealthContext(context) {
4451
+ let normalized = context.trim() || "/";
4452
+ if (!normalized.startsWith("/")) {
4453
+ normalized = `/${normalized}`;
4454
+ }
4455
+ if (!normalized.endsWith("/")) {
4456
+ normalized = `${normalized}/`;
4457
+ }
4458
+ return normalized;
4359
4459
  }
4360
- function markBranchDone(sessionId, workdir) {
4361
- lastBranchKey = sessionWorkdirKey(sessionId, workdir);
4460
+ function buildRemoteHealthScript(port, context, timeoutSecs) {
4461
+ const normalizedContext = normalizeHealthContext(context);
4462
+ const portStr = String(port);
4463
+ const timeoutStr = String(timeoutSecs);
4464
+ return `
4465
+ set -e
4466
+ port=${shellSingleQuote(portStr)}
4467
+ context=${shellSingleQuote(normalizedContext)}
4468
+ timeout=${shellSingleQuote(timeoutStr)}
4469
+ check_url="http://127.0.0.1:${portStr}${normalizedContext}"
4470
+ echo "\u5065\u5EB7\u68C0\u67E5: \${check_url} (\u8D85\u65F6 \${timeout}s)"
4471
+ deadline=$(($(date +%s) + timeout))
4472
+ attempt=0
4473
+ while [ $(date +%s) -lt $deadline ]; do
4474
+ attempt=$((attempt + 1))
4475
+ code=$(curl -s -o /dev/null -w "%{http_code}" "$check_url" 2>/dev/null || echo "000")
4476
+ code=$(echo "$code" | tail -n1 | tr -d '[:space:]')
4477
+ if [ \${#code} -ge 3 ]; then
4478
+ status=\${code:0:3}
4479
+ if echo "$status" | grep -qE '^[0-9]+$' && [ "$status" -ge 200 ] && [ "$status" -lt 500 ]; then
4480
+ echo -e "\\033[32m\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7 (HTTP \${status})\\033[0m"
4481
+ exit 0
4482
+ fi
4483
+ fi
4484
+ remaining=$((deadline - $(date +%s)))
4485
+ if [ $remaining -lt 0 ]; then
4486
+ remaining=0
4487
+ fi
4488
+ echo "\u7B49\u5F85\u670D\u52A1\u542F\u52A8... \u7B2C \${attempt} \u6B21\uFF0C\u5269\u4F59 \${remaining}s"
4489
+ sleep 5
4490
+ done
4491
+ echo -e "\\033[31m\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6 (\${timeout}s): \${check_url}\\033[0m"
4492
+ exit 1
4493
+ `.trim();
4362
4494
  }
4363
- function shouldRunPull(sessionId, workdir) {
4364
- const key = sessionWorkdirKey(sessionId, workdir);
4365
- const last = lastPullAtByKey.get(key);
4366
- if (last == null) {
4367
- return true;
4495
+ async function runRemoteServiceScript(client, script, action) {
4496
+ const { exitCode, out, err } = await runRemoteCommand(client, script, {
4497
+ check: false,
4498
+ label: action === "status" ? "\u8FDC\u7A0B status" : action === "restart" ? "\u8FDC\u7A0B restart" : "\u5065\u5EB7\u68C0\u67E5"
4499
+ });
4500
+ const combined = `${out}
4501
+ ${err}`.trim();
4502
+ const outputOk = springbootOutputIndicatesSuccess(action, combined);
4503
+ if (action === "health") {
4504
+ if (exitCode !== 0 || !outputOk) {
4505
+ fail(`\u5065\u5EB7\u68C0\u67E5\u5931\u8D25
4506
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
4507
+ }
4508
+ return combined;
4509
+ }
4510
+ if (exitCode !== 0 && !outputOk) {
4511
+ fail(`\u8FDC\u7A0B ${action} \u5931\u8D25
4512
+ ${combined}`);
4368
4513
  }
4369
- return Date.now() - last >= PULL_TTL_MS;
4370
- }
4371
- function markPullDone(sessionId, workdir) {
4372
- lastPullAtByKey.set(sessionWorkdirKey(sessionId, workdir), Date.now());
4514
+ if (action === "restart" && !outputOk) {
4515
+ fail(`\u8FDC\u7A0B restart \u672A\u6210\u529F
4516
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
4517
+ }
4518
+ return combined;
4373
4519
  }
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 };
4520
+ function stripAnsi(text) {
4521
+ return text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
4400
4522
  }
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}`);
4523
+ async function getRunningJar(client, config) {
4524
+ const script = buildRemoteStatusScript(
4525
+ config.remoteAppDir,
4526
+ config.startupJar
4527
+ );
4528
+ const combined = await runRemoteServiceScript(client, script, "status");
4529
+ const text = stripAnsi(combined).trim().toLowerCase();
4530
+ if (text.includes("not running")) {
4531
+ return null;
4532
+ }
4533
+ if (text.includes("running")) {
4534
+ return config.startupJar;
4535
+ }
4536
+ return null;
4408
4537
  }
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}`);
4538
+ async function healthCheckService(client, config) {
4539
+ log("\u5065\u5EB7\u68C0\u67E5...");
4540
+ const script = buildRemoteHealthScript(
4541
+ config.healthCheckPort,
4542
+ config.healthCheckContext,
4543
+ config.healthCheckTimeout
4544
+ );
4545
+ await runRemoteServiceScript(client, script, "health");
4413
4546
  }
4414
- var SHUTDOWN_DRAIN_MS = 3e3;
4415
- function isUserCancelled(ctx) {
4416
- return ctx.perMessageSignal.aborted && !ctx.shutdownSignal.aborted;
4547
+ async function restartRemoteService(client, config) {
4548
+ const script = buildRemoteRestartScript(config.remoteAppDir);
4549
+ await runRemoteServiceScript(client, script, "restart");
4417
4550
  }
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
- };
4551
+ async function runWisdomBackendDeploy(options) {
4552
+ const projectRoot = path2.resolve(options.projectRoot ?? process.cwd());
4553
+ const config = options.config;
4554
+ log(`=== \u81EA\u52A8\u90E8\u7F72: ${config.projectName} ===`);
4555
+ log(`\u914D\u7F6E\u6587\u4EF6: ${path2.join(workspaceApmDir(), "apm.config.json")}`);
4556
+ log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
4557
+ runMavenBuild(projectRoot, config.mavenLocalRepo, {
4558
+ source: config.mavenLocalRepoSource,
4559
+ sourceDetail: config.mavenLocalRepoSourceDetail
4560
+ });
4561
+ const libDir = locateLibDir(projectRoot);
4562
+ let manifest = loadManifest4();
4563
+ const conn = await connectSsh(config);
4435
4564
  try {
4436
- if (signal.aborted) return;
4437
- const { didInit } = await runStep(
4438
- "workspace-init",
4439
- () => ensureWorkspaceInitialized(workdir)
4565
+ const remoteLibStats = await getRemoteFileStats(
4566
+ conn.sftp,
4567
+ config.remoteLibDir
4440
4568
  );
4441
- if (!didInit) {
4442
- assertApmGitignoredInRepo(workdir);
4443
- }
4444
- await runStep(
4445
- "status-typing",
4446
- () => updateMessageStatus(cfg, messageId, "TYPING")
4569
+ log("\u6536\u96C6 JAR \u66F4\u65B0...");
4570
+ const libUploadEntries = listLibFilesToUpload(
4571
+ libDir,
4572
+ remoteLibStats,
4573
+ projectRoot,
4574
+ manifest
4447
4575
  );
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")
4576
+ let updated = 0;
4577
+ if (libUploadEntries.length > 0) {
4578
+ const zipPath = await createUpdatePackage(
4579
+ libUploadEntries,
4580
+ config.packageName
4469
4581
  );
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`
4582
+ const remoteZipPath = await uploadUpdatePackage(
4583
+ conn.sftp,
4584
+ zipPath,
4585
+ config
4478
4586
  );
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)
4587
+ log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
4588
+ updated = await extractUpdatePackageOnRemote(
4589
+ conn.client,
4590
+ config,
4591
+ remoteZipPath
4491
4592
  );
4593
+ } else {
4594
+ log("\u65E0 JAR \u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u66F4\u65B0\u5305\u4E0A\u4F20");
4492
4595
  }
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);
4596
+ const runningJar = await getRunningJar(conn.client, config);
4597
+ let needRestart = updated > 0;
4598
+ if (!needRestart && !runningJar) {
4599
+ log("\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u9700\u8981\u542F\u52A8");
4600
+ needRestart = true;
4601
+ } else if (!needRestart) {
4602
+ log("\u6CA1\u6709\u6587\u4EF6\u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u91CD\u542F");
4544
4603
  }
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
- );
4604
+ if (needRestart) {
4605
+ log("\u91CD\u542F\u670D\u52A1...");
4606
+ await restartRemoteService(conn.client, config);
4557
4607
  }
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
- );
4608
+ await healthCheckService(conn.client, config);
4609
+ if (libUploadEntries.length > 0) {
4610
+ manifest = updateManifestEntries(manifest, libUploadEntries, projectRoot);
4611
+ saveManifest4(manifest);
4569
4612
  }
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);
4613
+ } finally {
4614
+ await closeSsh(conn);
4580
4615
  }
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);
4616
+ log("\u90E8\u7F72\u5B8C\u6210");
4617
+ }
4618
+
4619
+ // src/commands/deploy/internal/wisdom-auto-deploy.ts
4620
+ function isWisdomLegacyDeploy(cfg) {
4621
+ const w = cfg.wisdomDeploy;
4622
+ if (!w?.host?.trim() || !w.remotePath?.trim()) {
4623
+ return false;
4585
4624
  }
4586
- process.exit(result.status ?? 0);
4625
+ const hasNewFrontend = Boolean(cfg.frontendDeploy?.endpoint?.trim());
4626
+ const hasNewBackend = Boolean(cfg.backendDeploy?.registryHost?.trim());
4627
+ return !hasNewFrontend && !hasNewBackend;
4587
4628
  }
4588
- async function runConnect(options) {
4589
- const { didUpdate } = await runUpdate();
4590
- if (didUpdate) {
4591
- reexecConnect(options);
4629
+ function detectWisdomProjectType(cwd) {
4630
+ return existsSync15(path3.join(cwd, "package.json")) ? "frontend" : "backend";
4631
+ }
4632
+ function readPackageScripts(cwd) {
4633
+ const pkgPath = path3.join(cwd, "package.json");
4634
+ if (!existsSync15(pkgPath)) {
4635
+ return {};
4592
4636
  }
4593
- const cfg = await ensureLoggedConfig();
4594
- if (options.server?.trim()) {
4595
- cfg.baseUrl = options.server.trim().replace(/\/+$/, "");
4637
+ try {
4638
+ const raw = readFileSync13(pkgPath, "utf8");
4639
+ const parsed = JSON.parse(raw);
4640
+ return parsed.scripts ?? {};
4641
+ } catch {
4642
+ return {};
4596
4643
  }
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);
4644
+ }
4645
+ function resolveFrontendDeployCommand(env, cwd) {
4646
+ const scripts = readPackageScripts(cwd);
4647
+ const deployKey = `deploy:${env}`;
4648
+ if (scripts[deployKey]?.trim()) {
4649
+ return `npm run deploy:${env}`;
4601
4650
  }
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
- });
4651
+ return null;
4735
4652
  }
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);
4653
+ function runShellCommand2(command, cwd) {
4654
+ const result = spawnSync4(command, {
4655
+ cwd,
4656
+ stdio: "inherit",
4657
+ shell: true,
4658
+ env: process.env
4659
+ });
4660
+ if (result.status !== 0) {
4661
+ process.exit(result.status ?? 1);
4743
4662
  }
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);
4663
+ }
4664
+ async function runWisdomAutoDeploy(options) {
4665
+ const cwd = path3.resolve(options.cwd ?? process.cwd());
4666
+ const projectType = detectWisdomProjectType(cwd);
4667
+ console.error(
4668
+ `[apm] \u533B\u52A1\u5B58\u91CF\u9879\u76EE\u81EA\u52A8\u8BC6\u522B: ${projectType === "frontend" ? "\u524D\u7AEF Vue" : "\u540E\u7AEF Java"}`
4669
+ );
4670
+ if (projectType === "frontend") {
4671
+ const deployCmd = resolveFrontendDeployCommand(options.env, cwd);
4672
+ if (!deployCmd) {
4673
+ console.error(
4674
+ `\u524D\u7AEF\u9879\u76EE ${path3.join(cwd, "package.json")} \u4E2D\u672A\u627E\u5230 deploy:${options.env} \u811A\u672C`
4675
+ );
4676
+ process.exit(1);
4677
+ }
4678
+ runShellCommand2(deployCmd, cwd);
4679
+ return { projectType };
4748
4680
  }
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 ?? ""
4681
+ const configPath = options.configPath ?? path3.join(workspaceApmDir(cwd), "apm.config.json");
4682
+ const cfg = loadApmConfig({ configPath });
4683
+ const settings = resolveWisdomBackendDeployFromApmConfig(cfg);
4684
+ console.error(
4685
+ `[apm] \u540E\u7AEF\u90E8\u7F72\u4E0D\u533A\u5206 test/online\uFF0C\u5FFD\u7565\u73AF\u5883\u53C2\u6570: ${options.env}`
4686
+ );
4687
+ await runWisdomBackendDeploy({
4688
+ config: settings,
4689
+ projectRoot: cwd
4757
4690
  });
4758
- console.log(`[apm] PR \u5DF2\u5C31\u7EEA #${pr.number} (${pr.state}): ${pr.url}`);
4691
+ return { projectType };
4759
4692
  }
4760
4693
 
4761
4694
  // src/commands/deploy/deploy.ts
4762
- import { spawnSync as spawnSync5 } from "node:child_process";
4763
4695
  function runShellCommand3(command, cwd) {
4764
4696
  const result = spawnSync5(command, {
4765
4697
  cwd,
@@ -4813,7 +4745,7 @@ import path7 from "node:path";
4813
4745
  import Docker from "dockerode";
4814
4746
 
4815
4747
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
4816
- import { existsSync as existsSync17, readFileSync as readFileSync15 } from "node:fs";
4748
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "node:fs";
4817
4749
  import path4 from "node:path";
4818
4750
  function asOptionalTlsBuffer(value) {
4819
4751
  if (typeof value !== "string") {
@@ -4825,8 +4757,8 @@ function asOptionalTlsBuffer(value) {
4825
4757
  if (normalized === "") {
4826
4758
  return void 0;
4827
4759
  }
4828
- if (existsSync17(normalized)) {
4829
- return readFileSync15(normalized);
4760
+ if (existsSync16(normalized)) {
4761
+ return readFileSync14(normalized);
4830
4762
  }
4831
4763
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
4832
4764
  if (looksLikePath) {
@@ -5036,7 +4968,7 @@ var DockerodeClient = class {
5036
4968
  var createDockerodeClient = (config) => new DockerodeClient(config);
5037
4969
 
5038
4970
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
5039
- import { existsSync as existsSync18, readFileSync as readFileSync16, statSync as statSync6 } from "node:fs";
4971
+ import { existsSync as existsSync17, readFileSync as readFileSync15, statSync as statSync6 } from "node:fs";
5040
4972
  import path5 from "node:path";
5041
4973
  function stripSurroundingQuotes(value) {
5042
4974
  const t = value.trim();
@@ -5053,10 +4985,10 @@ function loadEnvFromFile(envFilePath) {
5053
4985
  return {};
5054
4986
  }
5055
4987
  const targetPath = path5.resolve(envFilePath);
5056
- if (!existsSync18(targetPath) || !statSync6(targetPath).isFile()) {
4988
+ if (!existsSync17(targetPath) || !statSync6(targetPath).isFile()) {
5057
4989
  return {};
5058
4990
  }
5059
- const raw = readFileSync16(targetPath, "utf-8");
4991
+ const raw = readFileSync15(targetPath, "utf-8");
5060
4992
  const result = {};
5061
4993
  for (const line of raw.split(/\r?\n/)) {
5062
4994
  const normalized = line.trim();
@@ -5227,12 +5159,12 @@ function dockerPushImage(params, cwd) {
5227
5159
  }
5228
5160
 
5229
5161
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
5230
- import { existsSync as existsSync19 } from "node:fs";
5162
+ import { existsSync as existsSync18 } from "node:fs";
5231
5163
  import path6 from "node:path";
5232
5164
  function resolveDockerBuildPaths(cwd) {
5233
5165
  const dockerfilePath = path6.join(cwd, "Dockerfile");
5234
5166
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
5235
- if (!existsSync19(dockerfilePath)) {
5167
+ if (!existsSync18(dockerfilePath)) {
5236
5168
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
5237
5169
  }
5238
5170
  Logger.info("\u2713 Dockerfile \u5B58\u5728");