ai-project-manage-cli 6.0.75 → 6.0.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2240 -2288
- 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
|
|
2129
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
2130
2130
|
import WebSocket from "ws";
|
|
2131
2131
|
|
|
2132
2132
|
// src/ws/protocol.ts
|
|
@@ -2250,2516 +2250,2468 @@ function validateAgentWsMessage(value, kind) {
|
|
|
2250
2250
|
|
|
2251
2251
|
// src/commands/connect/deploy-run.ts
|
|
2252
2252
|
import { spawn } from "node:child_process";
|
|
2253
|
-
|
|
2254
|
-
|
|
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
|
|
2290
|
-
|
|
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
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
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
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
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
|
|
2324
|
+
"[apm] deploy log sync failed:",
|
|
2325
|
+
error instanceof Error ? error.message : String(error)
|
|
2323
2326
|
);
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
mappings = rawPorts.map((x) => String(x).trim()).filter(Boolean);
|
|
2327
|
-
}
|
|
2327
|
+
});
|
|
2328
|
+
}, DEPLOY_LOG_SYNC_INTERVAL_MS);
|
|
2328
2329
|
return {
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
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
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
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
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
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
|
|
2372
|
-
|
|
2373
|
-
|
|
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
|
-
|
|
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
|
|
2380
|
-
|
|
2381
|
-
|
|
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
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
if (
|
|
2398
|
-
|
|
2399
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
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 (
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
if (
|
|
2411
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
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
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
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
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
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
|
-
|
|
2464
|
-
const
|
|
2465
|
-
|
|
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
|
-
|
|
2478
|
-
|
|
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
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
}
|
|
2500
|
-
|
|
2501
|
-
|
|
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
|
-
|
|
2513
|
-
|
|
2514
|
-
process.exit(1);
|
|
2622
|
+
resolveLogContent() {
|
|
2623
|
+
return this.events.map((event) => formatLogEvent(event.type, event)).join("\n");
|
|
2515
2624
|
}
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
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
|
-
|
|
2566
|
-
|
|
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
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
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
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
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
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
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
|
|
2632
|
-
|
|
2687
|
+
function loadSessionAgentId(workdir, sessionId, user) {
|
|
2688
|
+
const registry = readRegistry(registryPath(workdir, sessionId));
|
|
2689
|
+
return registry[user];
|
|
2633
2690
|
}
|
|
2634
|
-
function
|
|
2635
|
-
const
|
|
2636
|
-
const
|
|
2637
|
-
|
|
2638
|
-
|
|
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
|
-
|
|
2654
|
-
const
|
|
2655
|
-
|
|
2656
|
-
|
|
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
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
)
|
|
2698
|
-
|
|
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
|
|
2701
|
-
|
|
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
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
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
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
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
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
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
|
-
|
|
2753
|
-
|
|
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
|
-
|
|
2756
|
-
|
|
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
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
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
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
return {};
|
|
2883
|
+
function withPlanModeToolHint(prompt, mode) {
|
|
2884
|
+
if (mode !== "plan") {
|
|
2885
|
+
return prompt;
|
|
2766
2886
|
}
|
|
2767
|
-
return
|
|
2768
|
-
|
|
2769
|
-
|
|
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
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
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
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
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
|
-
|
|
2843
|
-
|
|
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
|
-
|
|
2917
|
+
return `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
|
|
2858
2918
|
}
|
|
2859
|
-
function
|
|
2860
|
-
const
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
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
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
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
|
|
2890
|
-
const
|
|
2891
|
-
|
|
2892
|
-
|
|
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
|
|
2899
|
-
if (
|
|
2900
|
-
|
|
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
|
-
|
|
2903
|
-
|
|
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
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
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
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
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
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
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
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
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
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
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
|
|
3024
|
-
const
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
)
|
|
3028
|
-
|
|
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
|
-
|
|
3037
|
-
log(`lib \u89E3\u538B\u5B8C\u6210: \u8986\u76D6 ${updated} \u4E2A`);
|
|
3038
|
-
return updated;
|
|
3119
|
+
return message.content.trim();
|
|
3039
3120
|
}
|
|
3040
|
-
function
|
|
3041
|
-
const
|
|
3042
|
-
if (
|
|
3043
|
-
return
|
|
3121
|
+
async function ensureMessageHasReply(cfg, sessionId, messageId, fallback) {
|
|
3122
|
+
const existing = await fetchMessageContent(cfg, sessionId, messageId);
|
|
3123
|
+
if (existing) {
|
|
3124
|
+
return;
|
|
3044
3125
|
}
|
|
3045
|
-
|
|
3046
|
-
|
|
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
|
-
|
|
3049
|
-
|
|
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
|
-
|
|
3052
|
-
|
|
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
|
|
3057
|
-
|
|
3058
|
-
|
|
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
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
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
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
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
|
|
3120
|
-
|
|
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
|
-
|
|
3155
|
-
const
|
|
3156
|
-
|
|
3157
|
-
|
|
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
|
|
3202
|
+
return Date.now() - last >= PULL_TTL_MS;
|
|
3178
3203
|
}
|
|
3179
|
-
function
|
|
3180
|
-
|
|
3204
|
+
function markPullDone(sessionId, workdir) {
|
|
3205
|
+
lastPullAtByKey.set(sessionWorkdirKey(sessionId, workdir), Date.now());
|
|
3181
3206
|
}
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
const
|
|
3188
|
-
const
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
return
|
|
3194
|
-
|
|
3195
|
-
|
|
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
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
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
|
|
3207
|
-
const
|
|
3208
|
-
await
|
|
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
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
const
|
|
3221
|
-
|
|
3222
|
-
|
|
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
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
log("\u6536\u96C6 JAR \u66F4\u65B0...");
|
|
3229
|
-
const libUploadEntries = listLibFilesToUpload(
|
|
3230
|
-
libDir,
|
|
3231
|
-
remoteLibStats,
|
|
3232
|
-
projectRoot,
|
|
3233
|
-
manifest
|
|
3269
|
+
if (signal.aborted) return;
|
|
3270
|
+
const { didInit } = await runStep(
|
|
3271
|
+
"workspace-init",
|
|
3272
|
+
() => ensureWorkspaceInitialized(workdir)
|
|
3234
3273
|
);
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
const zipPath = await createUpdatePackage(
|
|
3238
|
-
libUploadEntries,
|
|
3239
|
-
config.packageName
|
|
3240
|
-
);
|
|
3241
|
-
const remoteZipPath = await uploadUpdatePackage(
|
|
3242
|
-
conn.sftp,
|
|
3243
|
-
zipPath,
|
|
3244
|
-
config
|
|
3245
|
-
);
|
|
3246
|
-
log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
|
|
3247
|
-
updated = await extractUpdatePackageOnRemote(
|
|
3248
|
-
conn.client,
|
|
3249
|
-
config,
|
|
3250
|
-
remoteZipPath
|
|
3251
|
-
);
|
|
3252
|
-
} else {
|
|
3253
|
-
log("\u65E0 JAR \u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u66F4\u65B0\u5305\u4E0A\u4F20");
|
|
3274
|
+
if (!didInit) {
|
|
3275
|
+
assertApmGitignoredInRepo(workdir);
|
|
3254
3276
|
}
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
log("\u6CA1\u6709\u6587\u4EF6\u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u91CD\u542F");
|
|
3277
|
+
if (shouldRunBranch(msg.sessionId, workdir)) {
|
|
3278
|
+
if (signal.aborted) return;
|
|
3279
|
+
await runStep("branch", () => runBranch(msg.sessionId, { cwd: workdir }));
|
|
3280
|
+
markBranchDone(msg.sessionId, workdir);
|
|
3281
|
+
} else {
|
|
3282
|
+
console.log(`[apm] step=branch skipped sessionId=${msg.sessionId}`);
|
|
3262
3283
|
}
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3284
|
+
let pullRan = false;
|
|
3285
|
+
if (shouldRunPull(msg.sessionId, workdir)) {
|
|
3286
|
+
if (signal.aborted) return;
|
|
3287
|
+
await runStep("pull", () => runPull(msg.sessionId, workdir));
|
|
3288
|
+
markPullDone(msg.sessionId, workdir);
|
|
3289
|
+
pullRan = true;
|
|
3290
|
+
} else {
|
|
3291
|
+
console.log(`[apm] step=pull skipped sessionId=${msg.sessionId}`);
|
|
3271
3292
|
}
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
// src/commands/deploy/internal/wisdom-auto-deploy.ts
|
|
3279
|
-
function isWisdomLegacyDeploy(cfg) {
|
|
3280
|
-
const w = cfg.wisdomDeploy;
|
|
3281
|
-
if (!w?.host?.trim() || !w.remotePath?.trim()) {
|
|
3282
|
-
return false;
|
|
3283
|
-
}
|
|
3284
|
-
const hasNewFrontend = Boolean(cfg.frontendDeploy?.endpoint?.trim());
|
|
3285
|
-
const hasNewBackend = Boolean(cfg.backendDeploy?.registryHost?.trim());
|
|
3286
|
-
return !hasNewFrontend && !hasNewBackend;
|
|
3287
|
-
}
|
|
3288
|
-
function detectWisdomProjectType(cwd) {
|
|
3289
|
-
return existsSync13(path3.join(cwd, "package.json")) ? "frontend" : "backend";
|
|
3290
|
-
}
|
|
3291
|
-
function readPackageScripts(cwd) {
|
|
3292
|
-
const pkgPath = path3.join(cwd, "package.json");
|
|
3293
|
-
if (!existsSync13(pkgPath)) {
|
|
3294
|
-
return {};
|
|
3295
|
-
}
|
|
3296
|
-
try {
|
|
3297
|
-
const raw = readFileSync11(pkgPath, "utf8");
|
|
3298
|
-
const parsed = JSON.parse(raw);
|
|
3299
|
-
return parsed.scripts ?? {};
|
|
3300
|
-
} catch {
|
|
3301
|
-
return {};
|
|
3302
|
-
}
|
|
3303
|
-
}
|
|
3304
|
-
function resolveFrontendDeployCommand(env, cwd) {
|
|
3305
|
-
const scripts = readPackageScripts(cwd);
|
|
3306
|
-
const deployKey = `deploy:${env}`;
|
|
3307
|
-
if (scripts[deployKey]?.trim()) {
|
|
3308
|
-
return `npm run deploy:${env}`;
|
|
3309
|
-
}
|
|
3310
|
-
return null;
|
|
3311
|
-
}
|
|
3312
|
-
function runShellCommand(command, cwd) {
|
|
3313
|
-
const result = spawnSync3(command, {
|
|
3314
|
-
cwd,
|
|
3315
|
-
stdio: "inherit",
|
|
3316
|
-
shell: true,
|
|
3317
|
-
env: process.env
|
|
3318
|
-
});
|
|
3319
|
-
if (result.status !== 0) {
|
|
3320
|
-
process.exit(result.status ?? 1);
|
|
3321
|
-
}
|
|
3322
|
-
}
|
|
3323
|
-
async function runWisdomAutoDeploy(options) {
|
|
3324
|
-
const cwd = path3.resolve(options.cwd ?? process.cwd());
|
|
3325
|
-
const projectType = detectWisdomProjectType(cwd);
|
|
3326
|
-
console.error(
|
|
3327
|
-
`[apm] \u533B\u52A1\u5B58\u91CF\u9879\u76EE\u81EA\u52A8\u8BC6\u522B: ${projectType === "frontend" ? "\u524D\u7AEF Vue" : "\u540E\u7AEF Java"}`
|
|
3328
|
-
);
|
|
3329
|
-
if (projectType === "frontend") {
|
|
3330
|
-
const deployCmd = resolveFrontendDeployCommand(options.env, cwd);
|
|
3331
|
-
if (!deployCmd) {
|
|
3332
|
-
console.error(
|
|
3333
|
-
`\u524D\u7AEF\u9879\u76EE ${path3.join(cwd, "package.json")} \u4E2D\u672A\u627E\u5230 deploy:${options.env} \u811A\u672C`
|
|
3293
|
+
if (pullRan) {
|
|
3294
|
+
if (signal.aborted) return;
|
|
3295
|
+
await runStep(
|
|
3296
|
+
"commit-pull",
|
|
3297
|
+
() => commitWorkingTreeIfDirty(workdir, "fix: apm pull")
|
|
3334
3298
|
);
|
|
3335
|
-
|
|
3299
|
+
} else {
|
|
3300
|
+
console.log(`[apm] step=commit-pull skipped sessionId=${msg.sessionId}`);
|
|
3336
3301
|
}
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
config: settings,
|
|
3348
|
-
projectRoot: cwd
|
|
3349
|
-
});
|
|
3350
|
-
return { projectType };
|
|
3351
|
-
}
|
|
3352
|
-
|
|
3353
|
-
// src/commands/connect/deploy-run.ts
|
|
3354
|
-
var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
|
|
3355
|
-
function readApmConfig(workdir) {
|
|
3356
|
-
const configPath = join14(workspaceApmDir(workdir), "apm.config.json");
|
|
3357
|
-
if (!existsSync14(configPath)) {
|
|
3358
|
-
return void 0;
|
|
3359
|
-
}
|
|
3360
|
-
try {
|
|
3361
|
-
const raw = readFileSync12(configPath, "utf8");
|
|
3362
|
-
return JSON.parse(raw);
|
|
3363
|
-
} catch {
|
|
3364
|
-
return void 0;
|
|
3365
|
-
}
|
|
3366
|
-
}
|
|
3367
|
-
function readDeployConfig(workdir) {
|
|
3368
|
-
return readApmConfig(workdir)?.deploy;
|
|
3369
|
-
}
|
|
3370
|
-
function resolveDeployCommand(workdir, environment) {
|
|
3371
|
-
const command = readDeployConfig(workdir)?.[environment];
|
|
3372
|
-
if (typeof command === "string" && command.trim()) {
|
|
3373
|
-
return command.trim();
|
|
3374
|
-
}
|
|
3375
|
-
return null;
|
|
3376
|
-
}
|
|
3377
|
-
function missingDeployCommandMessage(environment) {
|
|
3378
|
-
return `deploy.${environment} \u90E8\u7F72\u547D\u4EE4\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u914D\u7F6E`;
|
|
3379
|
-
}
|
|
3380
|
-
function buildDeployLog(stdout, stderr) {
|
|
3381
|
-
return [stdout, stderr].filter(Boolean).join("\n");
|
|
3382
|
-
}
|
|
3383
|
-
function runShellCommand2(command, cwd, signal, onOutput) {
|
|
3384
|
-
return new Promise((resolve5, reject) => {
|
|
3385
|
-
const child = spawn(command, {
|
|
3386
|
-
cwd,
|
|
3387
|
-
shell: true,
|
|
3388
|
-
env: process.env,
|
|
3389
|
-
windowsHide: true
|
|
3390
|
-
});
|
|
3391
|
-
let stdout = "";
|
|
3392
|
-
let stderr = "";
|
|
3393
|
-
const emitLog = () => {
|
|
3394
|
-
onOutput?.(buildDeployLog(stdout, stderr));
|
|
3395
|
-
};
|
|
3396
|
-
const onAbort = () => {
|
|
3397
|
-
child.kill("SIGTERM");
|
|
3398
|
-
};
|
|
3399
|
-
if (signal.aborted) {
|
|
3400
|
-
onAbort();
|
|
3302
|
+
const cliVersion = readCliVersion();
|
|
3303
|
+
if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
|
|
3304
|
+
if (signal.aborted) return;
|
|
3305
|
+
console.log(
|
|
3306
|
+
`[apm] CLI \u7248\u672C ${cliVersion} \u4E0E\u5DE5\u4F5C\u533A\u8BB0\u5F55\u4E0D\u4E00\u81F4\uFF0C\u6267\u884C update-skills`
|
|
3307
|
+
);
|
|
3308
|
+
await runStep("update-skills", async () => {
|
|
3309
|
+
await syncWorkspaceSkills(cfg, workdir);
|
|
3310
|
+
markSkillsSyncedForCliVersion(workdir, cliVersion);
|
|
3311
|
+
});
|
|
3401
3312
|
} else {
|
|
3402
|
-
|
|
3313
|
+
console.log(`[apm] step=update-skills skipped workdir=${workdir}`);
|
|
3403
3314
|
}
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
stderr += String(chunk);
|
|
3410
|
-
emitLog();
|
|
3411
|
-
});
|
|
3412
|
-
child.on("error", (error) => {
|
|
3413
|
-
signal.removeEventListener("abort", onAbort);
|
|
3414
|
-
reject(error);
|
|
3415
|
-
});
|
|
3416
|
-
child.on("close", (code) => {
|
|
3417
|
-
signal.removeEventListener("abort", onAbort);
|
|
3418
|
-
const log2 = buildDeployLog(stdout, stderr);
|
|
3419
|
-
if (code === 0) {
|
|
3420
|
-
resolve5({ log: log2 });
|
|
3421
|
-
return;
|
|
3422
|
-
}
|
|
3423
|
-
const error = new Error(
|
|
3424
|
-
`\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
|
|
3315
|
+
if (signal.aborted) return;
|
|
3316
|
+
if (!pullRan) {
|
|
3317
|
+
await runStep(
|
|
3318
|
+
"sync-project-documents-pull",
|
|
3319
|
+
() => syncRepositoryProjectDocumentsPull(workdir, apmRoot)
|
|
3425
3320
|
);
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3321
|
+
}
|
|
3322
|
+
const agentResult = await runStep(
|
|
3323
|
+
"cursor-agent",
|
|
3324
|
+
() => runCursorAgent(
|
|
3325
|
+
cfg,
|
|
3326
|
+
{
|
|
3327
|
+
messageId: msg.messageId,
|
|
3328
|
+
sessionId: msg.sessionId,
|
|
3329
|
+
prompt: msg.content,
|
|
3330
|
+
model: msg.model,
|
|
3331
|
+
apiKey: msg.apiKey,
|
|
3332
|
+
workdir,
|
|
3333
|
+
user: msg.user
|
|
3334
|
+
},
|
|
3335
|
+
{ signal }
|
|
3336
|
+
)
|
|
3337
|
+
);
|
|
3338
|
+
await runStep(
|
|
3339
|
+
"ensure-reply",
|
|
3340
|
+
() => ensureMessageHasReply(cfg, msg.sessionId, messageId, {
|
|
3341
|
+
assistantText: agentResult.assistantText,
|
|
3342
|
+
result: agentResult.result,
|
|
3343
|
+
createPlan: agentResult.createPlan
|
|
3344
|
+
})
|
|
3345
|
+
);
|
|
3346
|
+
await runStep(
|
|
3347
|
+
"sync-documents",
|
|
3348
|
+
() => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
|
|
3349
|
+
);
|
|
3350
|
+
await runStep(
|
|
3351
|
+
"sync-project-documents",
|
|
3352
|
+
() => syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot)
|
|
3353
|
+
);
|
|
3354
|
+
await runStep(
|
|
3355
|
+
"commit-files",
|
|
3356
|
+
() => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
|
|
3357
|
+
);
|
|
3358
|
+
await runStep(
|
|
3359
|
+
"status-success",
|
|
3360
|
+
() => updateMessageStatus(cfg, messageId, "SUCCESS")
|
|
3361
|
+
);
|
|
3362
|
+
} catch (err) {
|
|
3363
|
+
if (isUserCancelled(ctx)) {
|
|
3364
|
+
console.log(`[apm] \u6D88\u606F\u5DF2\u7EC8\u6B62 messageId=${messageId}`);
|
|
3436
3365
|
return;
|
|
3437
3366
|
}
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3367
|
+
console.error(
|
|
3368
|
+
"[apm] \u5904\u7406\u6D88\u606F\u5931\u8D25:",
|
|
3369
|
+
err instanceof Error ? err.message : String(err)
|
|
3370
|
+
);
|
|
3371
|
+
if (err instanceof Error && err.stack) {
|
|
3372
|
+
console.error(err.stack);
|
|
3373
|
+
}
|
|
3374
|
+
try {
|
|
3375
|
+
await setMessageError(
|
|
3376
|
+
cfg,
|
|
3377
|
+
messageId,
|
|
3378
|
+
err instanceof Error ? err.message : String(err)
|
|
3379
|
+
);
|
|
3380
|
+
await updateMessageStatus(cfg, messageId, "FAILED");
|
|
3381
|
+
} catch (statusErr) {
|
|
3446
3382
|
console.error(
|
|
3447
|
-
"[apm]
|
|
3448
|
-
|
|
3383
|
+
"[apm] \u66F4\u65B0 FAILED \u72B6\u6001\u5931\u8D25:",
|
|
3384
|
+
statusErr instanceof Error ? statusErr.message : statusErr
|
|
3449
3385
|
);
|
|
3450
|
-
});
|
|
3451
|
-
}, DEPLOY_LOG_SYNC_INTERVAL_MS);
|
|
3452
|
-
return {
|
|
3453
|
-
updateLog(log2) {
|
|
3454
|
-
latestLog = log2;
|
|
3455
|
-
},
|
|
3456
|
-
async flush() {
|
|
3457
|
-
clearInterval(timer);
|
|
3458
|
-
await syncIfChanged();
|
|
3459
|
-
},
|
|
3460
|
-
dispose() {
|
|
3461
|
-
clearInterval(timer);
|
|
3462
3386
|
}
|
|
3463
|
-
}
|
|
3387
|
+
}
|
|
3464
3388
|
}
|
|
3465
|
-
|
|
3466
|
-
const
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
const apmConfig = readApmConfig(workdir);
|
|
3475
|
-
if (apmConfig && isWisdomLegacyDeploy(apmConfig)) {
|
|
3476
|
-
console.log(
|
|
3477
|
-
`[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir} (wisdom auto)`
|
|
3478
|
-
);
|
|
3479
|
-
const logSyncer2 = createDeployLogSyncer(api, deploymentRunId);
|
|
3480
|
-
try {
|
|
3481
|
-
await runWisdomAutoDeploy({
|
|
3482
|
-
env: msg.environment,
|
|
3483
|
-
cwd: workdir
|
|
3484
|
-
});
|
|
3485
|
-
await logSyncer2.flush();
|
|
3486
|
-
await api.cli.completeTaskDeployment({
|
|
3487
|
-
id: deploymentRunId,
|
|
3488
|
-
status: "SUCCESS",
|
|
3489
|
-
log: "\u533B\u52A1\u5B58\u91CF\u9879\u76EE\u81EA\u52A8\u90E8\u7F72\u5B8C\u6210"
|
|
3490
|
-
});
|
|
3491
|
-
console.log(`[apm] deploy success id=${deploymentRunId}`);
|
|
3492
|
-
} catch (error) {
|
|
3493
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
3494
|
-
await logSyncer2.flush();
|
|
3495
|
-
await api.cli.completeTaskDeployment({
|
|
3496
|
-
id: deploymentRunId,
|
|
3497
|
-
status: "FAILED",
|
|
3498
|
-
log: detail,
|
|
3499
|
-
error: detail
|
|
3500
|
-
});
|
|
3501
|
-
console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
|
|
3502
|
-
} finally {
|
|
3503
|
-
logSyncer2.dispose();
|
|
3389
|
+
function startHeartbeat(ws, clientMachineId) {
|
|
3390
|
+
const send = () => {
|
|
3391
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
3392
|
+
ws.send(
|
|
3393
|
+
serializeAgentWsMessage({
|
|
3394
|
+
type: "heartbeat",
|
|
3395
|
+
userId: clientMachineId
|
|
3396
|
+
})
|
|
3397
|
+
);
|
|
3504
3398
|
}
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
const
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
error
|
|
3516
|
-
});
|
|
3517
|
-
return;
|
|
3399
|
+
};
|
|
3400
|
+
send();
|
|
3401
|
+
const timer = setInterval(send, HEARTBEAT_MS);
|
|
3402
|
+
return () => clearInterval(timer);
|
|
3403
|
+
}
|
|
3404
|
+
function reexecConnect(options) {
|
|
3405
|
+
const args = [process.argv[1], "connect"];
|
|
3406
|
+
const server = options.server?.trim();
|
|
3407
|
+
if (server) {
|
|
3408
|
+
args.push("--server", server);
|
|
3518
3409
|
}
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
const logSyncer = createDeployLogSyncer(api, deploymentRunId);
|
|
3524
|
-
let latestLog = "";
|
|
3525
|
-
try {
|
|
3526
|
-
const { log: log2 } = await runShellCommand2(command, workdir, signal, (log3) => {
|
|
3527
|
-
latestLog = log3;
|
|
3528
|
-
logSyncer.updateLog(log3);
|
|
3529
|
-
});
|
|
3530
|
-
latestLog = log2;
|
|
3531
|
-
logSyncer.updateLog(log2);
|
|
3532
|
-
await logSyncer.flush();
|
|
3533
|
-
await api.cli.completeTaskDeployment({
|
|
3534
|
-
id: deploymentRunId,
|
|
3535
|
-
status: "SUCCESS",
|
|
3536
|
-
log: log2
|
|
3537
|
-
});
|
|
3538
|
-
console.log(`[apm] deploy success id=${deploymentRunId}`);
|
|
3539
|
-
} catch (error) {
|
|
3540
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
3541
|
-
const log2 = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
|
|
3542
|
-
logSyncer.updateLog(log2);
|
|
3543
|
-
await logSyncer.flush();
|
|
3544
|
-
await api.cli.completeTaskDeployment({
|
|
3545
|
-
id: deploymentRunId,
|
|
3546
|
-
status: "FAILED",
|
|
3547
|
-
log: log2,
|
|
3548
|
-
error: detail
|
|
3549
|
-
});
|
|
3550
|
-
console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
|
|
3551
|
-
} finally {
|
|
3552
|
-
logSyncer.dispose();
|
|
3410
|
+
const result = spawnSync2(process.execPath, args, { stdio: "inherit" });
|
|
3411
|
+
if (result.error) {
|
|
3412
|
+
console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
|
|
3413
|
+
process.exit(1);
|
|
3553
3414
|
}
|
|
3415
|
+
process.exit(result.status ?? 0);
|
|
3554
3416
|
}
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
getMaxListeners,
|
|
3560
|
-
setMaxListeners
|
|
3561
|
-
} from "node:events";
|
|
3562
|
-
function isAbortSignalDebugEnabled() {
|
|
3563
|
-
const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
|
|
3564
|
-
return v === "1" || v === "true" || v === "yes";
|
|
3565
|
-
}
|
|
3566
|
-
function formatAbortSignalStats(signal, label) {
|
|
3567
|
-
if (!signal) {
|
|
3568
|
-
return `[apm:abort-debug] ${label}: (no signal)`;
|
|
3417
|
+
async function runConnect(options) {
|
|
3418
|
+
const { didUpdate } = await runUpdate();
|
|
3419
|
+
if (didUpdate) {
|
|
3420
|
+
reexecConnect(options);
|
|
3569
3421
|
}
|
|
3570
|
-
const
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
}
|
|
3574
|
-
function logAbortSignalStats(signal, label) {
|
|
3575
|
-
if (!isAbortSignalDebugEnabled()) return;
|
|
3576
|
-
console.log(formatAbortSignalStats(signal, label));
|
|
3577
|
-
}
|
|
3578
|
-
var installed = false;
|
|
3579
|
-
function installAbortSignalDebug() {
|
|
3580
|
-
if (!isAbortSignalDebugEnabled() || installed) return;
|
|
3581
|
-
installed = true;
|
|
3582
|
-
const maxFromEnv = Number.parseInt(
|
|
3583
|
-
process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
|
|
3584
|
-
10
|
|
3585
|
-
);
|
|
3586
|
-
if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
|
|
3587
|
-
setMaxListeners(maxFromEnv);
|
|
3588
|
-
console.log(
|
|
3589
|
-
`[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
|
|
3590
|
-
);
|
|
3422
|
+
const cfg = await ensureLoggedConfig();
|
|
3423
|
+
if (options.server?.trim()) {
|
|
3424
|
+
cfg.baseUrl = options.server.trim().replace(/\/+$/, "");
|
|
3591
3425
|
}
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
console.
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
});
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3426
|
+
const clientMachineId = resolveClientMachineId(cfg);
|
|
3427
|
+
if (!clientMachineId) {
|
|
3428
|
+
console.error("[apm] config \u7F3A\u5C11 clientMachineId\uFF0C\u8BF7\u91CD\u65B0 apm login");
|
|
3429
|
+
process.exit(1);
|
|
3430
|
+
}
|
|
3431
|
+
const url = buildAgentWsUrl(cfg.baseUrl, resolveApiKey(cfg));
|
|
3432
|
+
console.log(`[apm] \u8FDE\u63A5 ${cfg.baseUrl} \u2026`);
|
|
3433
|
+
await new Promise((resolve5, reject) => {
|
|
3434
|
+
const ws = new WebSocket(url);
|
|
3435
|
+
let stopHeartbeat;
|
|
3436
|
+
let shuttingDown = false;
|
|
3437
|
+
const shutdownAbort = new AbortController();
|
|
3438
|
+
const runSlots = createRunSlotPool();
|
|
3439
|
+
const activeTasks = /* @__PURE__ */ new Set();
|
|
3440
|
+
const activeRuns = /* @__PURE__ */ new Map();
|
|
3441
|
+
const pendingCancels = /* @__PURE__ */ new Set();
|
|
3442
|
+
const shutdown = async (code = 0) => {
|
|
3443
|
+
if (shuttingDown) return;
|
|
3444
|
+
shuttingDown = true;
|
|
3445
|
+
logAbortSignalStats(
|
|
3446
|
+
shutdownAbort.signal,
|
|
3447
|
+
"connect:shutdown-before-abort"
|
|
3448
|
+
);
|
|
3449
|
+
shutdownAbort.abort();
|
|
3450
|
+
logAbortSignalStats(shutdownAbort.signal, "connect:shutdown-after-abort");
|
|
3451
|
+
stopHeartbeat?.();
|
|
3452
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
3453
|
+
ws.terminate();
|
|
3454
|
+
}
|
|
3455
|
+
try {
|
|
3456
|
+
await Promise.race([
|
|
3457
|
+
Promise.all(activeTasks),
|
|
3458
|
+
new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
|
|
3459
|
+
]);
|
|
3460
|
+
} catch {
|
|
3461
|
+
}
|
|
3462
|
+
resolve5();
|
|
3463
|
+
process.exit(code);
|
|
3464
|
+
};
|
|
3465
|
+
ws.on("open", () => {
|
|
3466
|
+
console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
|
|
3467
|
+
stopHeartbeat = startHeartbeat(ws, clientMachineId);
|
|
3468
|
+
});
|
|
3469
|
+
ws.on("message", (data) => {
|
|
3470
|
+
if (shuttingDown) return;
|
|
3471
|
+
const text = Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
|
|
3472
|
+
const parsed = parseAgentWsMessage(text);
|
|
3473
|
+
if (parsed === null) {
|
|
3474
|
+
console.error("[apm] \u6536\u5230\u65E0\u6548 JSON");
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
if (typeof parsed === "object" && parsed !== null && parsed.type === "heartbeat") {
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3480
|
+
const validated = validateAgentWsMessage(parsed, "outbound");
|
|
3481
|
+
if (!validated.ok) {
|
|
3482
|
+
console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
if (validated.data.type === "cancel") {
|
|
3486
|
+
const { messageId } = validated.data;
|
|
3487
|
+
pendingCancels.add(messageId);
|
|
3488
|
+
activeRuns.get(messageId)?.abort();
|
|
3489
|
+
return;
|
|
3490
|
+
}
|
|
3491
|
+
if (validated.data.type === "deploy") {
|
|
3492
|
+
const msg2 = validated.data;
|
|
3493
|
+
const perDeployController = new AbortController();
|
|
3494
|
+
const signal2 = AbortSignal.any([
|
|
3495
|
+
shutdownAbort.signal,
|
|
3496
|
+
perDeployController.signal
|
|
3497
|
+
]);
|
|
3498
|
+
const task2 = (async () => {
|
|
3499
|
+
await runSlots.acquire();
|
|
3500
|
+
try {
|
|
3501
|
+
await handleInboundDeploy(cfg, msg2, signal2);
|
|
3502
|
+
} finally {
|
|
3503
|
+
runSlots.release();
|
|
3504
|
+
}
|
|
3505
|
+
})();
|
|
3506
|
+
activeTasks.add(task2);
|
|
3507
|
+
void task2.finally(() => {
|
|
3508
|
+
activeTasks.delete(task2);
|
|
3509
|
+
});
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
if (validated.data.type !== "message") {
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
const msg = validated.data;
|
|
3516
|
+
const perMessageController = new AbortController();
|
|
3517
|
+
activeRuns.set(msg.messageId, perMessageController);
|
|
3518
|
+
if (pendingCancels.has(msg.messageId)) {
|
|
3519
|
+
activeRuns.delete(msg.messageId);
|
|
3520
|
+
pendingCancels.delete(msg.messageId);
|
|
3521
|
+
return;
|
|
3522
|
+
}
|
|
3523
|
+
const signal = AbortSignal.any([
|
|
3524
|
+
shutdownAbort.signal,
|
|
3525
|
+
perMessageController.signal
|
|
3526
|
+
]);
|
|
3527
|
+
const ctx = {
|
|
3528
|
+
shutdownSignal: shutdownAbort.signal,
|
|
3529
|
+
perMessageSignal: perMessageController.signal
|
|
3530
|
+
};
|
|
3531
|
+
const task = (async () => {
|
|
3532
|
+
await runSlots.acquire();
|
|
3533
|
+
try {
|
|
3534
|
+
if (signal.aborted || isUserCancelled(ctx)) return;
|
|
3535
|
+
try {
|
|
3536
|
+
await updateMessageStatus(cfg, msg.messageId, "TYPING");
|
|
3537
|
+
} catch (typingErr) {
|
|
3538
|
+
if (isUserCancelled(ctx)) return;
|
|
3539
|
+
console.error(
|
|
3540
|
+
"[apm] \u66F4\u65B0 TYPING \u72B6\u6001\u5931\u8D25:",
|
|
3541
|
+
typingErr instanceof Error ? typingErr.message : typingErr
|
|
3542
|
+
);
|
|
3543
|
+
try {
|
|
3544
|
+
await setMessageError(
|
|
3545
|
+
cfg,
|
|
3546
|
+
msg.messageId,
|
|
3547
|
+
typingErr instanceof Error ? typingErr.message : String(typingErr)
|
|
3548
|
+
);
|
|
3549
|
+
await updateMessageStatus(cfg, msg.messageId, "FAILED");
|
|
3550
|
+
} catch (statusErr) {
|
|
3551
|
+
console.error(
|
|
3552
|
+
"[apm] \u66F4\u65B0 FAILED \u72B6\u6001\u5931\u8D25:",
|
|
3553
|
+
statusErr instanceof Error ? statusErr.message : statusErr
|
|
3554
|
+
);
|
|
3555
|
+
}
|
|
3556
|
+
return;
|
|
3557
|
+
}
|
|
3558
|
+
await handleInboundMessage(cfg, msg, signal, ctx);
|
|
3559
|
+
} finally {
|
|
3560
|
+
runSlots.release();
|
|
3561
|
+
activeRuns.delete(msg.messageId);
|
|
3562
|
+
pendingCancels.delete(msg.messageId);
|
|
3563
|
+
}
|
|
3564
|
+
})();
|
|
3565
|
+
activeTasks.add(task);
|
|
3566
|
+
void task.finally(() => {
|
|
3567
|
+
activeTasks.delete(task);
|
|
3568
|
+
});
|
|
3569
|
+
});
|
|
3570
|
+
ws.on("close", (code, reason) => {
|
|
3607
3571
|
console.log(
|
|
3608
|
-
`[apm
|
|
3609
|
-
${stack}`
|
|
3572
|
+
`[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
|
|
3610
3573
|
);
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3574
|
+
void shutdown();
|
|
3575
|
+
});
|
|
3576
|
+
ws.on("error", (err) => {
|
|
3577
|
+
console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
|
|
3578
|
+
reject(err);
|
|
3579
|
+
});
|
|
3580
|
+
process.on("SIGINT", () => {
|
|
3581
|
+
console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
|
|
3582
|
+
void shutdown();
|
|
3583
|
+
});
|
|
3584
|
+
process.on("SIGTERM", () => {
|
|
3585
|
+
void shutdown();
|
|
3586
|
+
});
|
|
3587
|
+
});
|
|
3617
3588
|
}
|
|
3618
3589
|
|
|
3619
|
-
// src/commands/
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
// src/plan-format.ts
|
|
3627
|
-
function formatPlanMarkdown(raw) {
|
|
3628
|
-
let text = raw.trim();
|
|
3629
|
-
if (!text) {
|
|
3630
|
-
return text;
|
|
3631
|
-
}
|
|
3632
|
-
if (text.startsWith("{") && text.endsWith("}")) {
|
|
3633
|
-
try {
|
|
3634
|
-
const parsed = JSON.parse(text);
|
|
3635
|
-
if (typeof parsed.plan === "string") {
|
|
3636
|
-
return formatPlanMarkdown(parsed.plan);
|
|
3637
|
-
}
|
|
3638
|
-
} catch {
|
|
3639
|
-
}
|
|
3590
|
+
// src/commands/create-pr.ts
|
|
3591
|
+
async function runCreatePr(options) {
|
|
3592
|
+
const sessionId = options.sessionId.trim();
|
|
3593
|
+
if (!sessionId) {
|
|
3594
|
+
console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
3595
|
+
process.exit(1);
|
|
3640
3596
|
}
|
|
3641
|
-
|
|
3642
|
-
|
|
3597
|
+
const title = options.title.trim();
|
|
3598
|
+
if (!title) {
|
|
3599
|
+
console.error("[apm] \u8BF7\u901A\u8FC7 --title \u6307\u5B9A PR \u6807\u9898");
|
|
3600
|
+
process.exit(1);
|
|
3643
3601
|
}
|
|
3644
|
-
|
|
3602
|
+
const cfg = await ensureLoggedConfig();
|
|
3603
|
+
const api = createApmApiClient(cfg);
|
|
3604
|
+
const workdir = resolveWorkdirPath(options.cwd ?? process.cwd());
|
|
3605
|
+
const pr = await api.cli.createPullRequest({
|
|
3606
|
+
sessionId,
|
|
3607
|
+
workdir,
|
|
3608
|
+
title,
|
|
3609
|
+
content: options.content ?? ""
|
|
3610
|
+
});
|
|
3611
|
+
console.log(`[apm] PR \u5DF2\u5C31\u7EEA #${pr.number} (${pr.state}): ${pr.url}`);
|
|
3645
3612
|
}
|
|
3646
3613
|
|
|
3647
|
-
// src/
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3614
|
+
// src/commands/deploy/deploy.ts
|
|
3615
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
3616
|
+
|
|
3617
|
+
// src/commands/deploy/internal/apm-config.ts
|
|
3618
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
|
|
3619
|
+
import { homedir as homedir2 } from "node:os";
|
|
3620
|
+
import { join as join14, resolve as resolve4 } from "node:path";
|
|
3621
|
+
function loadApmConfig(options) {
|
|
3622
|
+
const p = resolve4(
|
|
3623
|
+
process.cwd(),
|
|
3624
|
+
options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
|
|
3625
|
+
);
|
|
3626
|
+
if (!existsSync13(p)) {
|
|
3627
|
+
console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
|
|
3628
|
+
process.exit(1);
|
|
3660
3629
|
}
|
|
3661
|
-
|
|
3662
|
-
const
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
if (formatedEvent.type === "tool_call") {
|
|
3668
|
-
const existingIndex = this.events.findIndex(
|
|
3669
|
-
(e) => e.type === "tool_call" && e.call_id === formatedEvent.call_id
|
|
3670
|
-
);
|
|
3671
|
-
if (existingIndex >= 0) {
|
|
3672
|
-
const existingToolCall = this.events[existingIndex];
|
|
3673
|
-
existingToolCall.args = formatedEvent.args;
|
|
3674
|
-
existingToolCall.result = formatedEvent.result;
|
|
3675
|
-
existingToolCall.status = formatedEvent.status;
|
|
3676
|
-
this.markDirty(existingIndex);
|
|
3677
|
-
return;
|
|
3678
|
-
}
|
|
3679
|
-
this.events.push(formatedEvent);
|
|
3680
|
-
this.markDirty(this.events.length - 1);
|
|
3681
|
-
return;
|
|
3682
|
-
}
|
|
3683
|
-
if (formatedEvent.type === "status") {
|
|
3684
|
-
return;
|
|
3685
|
-
}
|
|
3686
|
-
if (formatedEvent.type === "request") {
|
|
3687
|
-
this.events.push(formatedEvent);
|
|
3688
|
-
this.markDirty(this.events.length - 1);
|
|
3689
|
-
return;
|
|
3690
|
-
}
|
|
3691
|
-
if (latestEvent?.type === formatedEvent.type) {
|
|
3692
|
-
switch (formatedEvent.type) {
|
|
3693
|
-
case "assistant":
|
|
3694
|
-
latestEvent.content += formatedEvent.content;
|
|
3695
|
-
break;
|
|
3696
|
-
case "thinking":
|
|
3697
|
-
latestEvent.content += formatedEvent.content;
|
|
3698
|
-
break;
|
|
3699
|
-
case "task":
|
|
3700
|
-
latestEvent.status = formatedEvent.status;
|
|
3701
|
-
latestEvent.text = formatedEvent.text;
|
|
3702
|
-
break;
|
|
3703
|
-
}
|
|
3704
|
-
this.markDirty(this.events.length - 1);
|
|
3705
|
-
return;
|
|
3706
|
-
}
|
|
3707
|
-
this.events.push(formatedEvent);
|
|
3708
|
-
this.markDirty(this.events.length - 1);
|
|
3630
|
+
try {
|
|
3631
|
+
const raw = readFileSync11(p, "utf8");
|
|
3632
|
+
return JSON.parse(raw);
|
|
3633
|
+
} catch (e) {
|
|
3634
|
+
console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
|
|
3635
|
+
process.exit(1);
|
|
3709
3636
|
}
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
if (block.type === "text" && block.text) {
|
|
3716
|
-
content += block.text;
|
|
3717
|
-
}
|
|
3718
|
-
}
|
|
3719
|
-
return {
|
|
3720
|
-
type: "assistant",
|
|
3721
|
-
content: content || event.content || ""
|
|
3722
|
-
};
|
|
3723
|
-
}
|
|
3724
|
-
case "thinking":
|
|
3725
|
-
return {
|
|
3726
|
-
type: "thinking",
|
|
3727
|
-
content: event.text || event.content || ""
|
|
3728
|
-
};
|
|
3729
|
-
case "tool_call":
|
|
3730
|
-
return {
|
|
3731
|
-
type: "tool_call",
|
|
3732
|
-
args: event.args,
|
|
3733
|
-
result: event.result,
|
|
3734
|
-
status: event.status,
|
|
3735
|
-
call_id: event.call_id,
|
|
3736
|
-
name: event.name
|
|
3737
|
-
};
|
|
3738
|
-
case "task":
|
|
3739
|
-
return {
|
|
3740
|
-
type: "task",
|
|
3741
|
-
status: event.status,
|
|
3742
|
-
text: event.text
|
|
3743
|
-
};
|
|
3744
|
-
case "request":
|
|
3745
|
-
return {
|
|
3746
|
-
...event,
|
|
3747
|
-
type: "request"
|
|
3748
|
-
};
|
|
3749
|
-
case "status":
|
|
3750
|
-
return { type: "status", status: event.status, message: event.message };
|
|
3751
|
-
}
|
|
3637
|
+
}
|
|
3638
|
+
function req(v, field) {
|
|
3639
|
+
if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
|
|
3640
|
+
console.error(`apm.config.json \u4E2D backendDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
3641
|
+
process.exit(1);
|
|
3752
3642
|
}
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3643
|
+
return v;
|
|
3644
|
+
}
|
|
3645
|
+
function reqTopLevelName(cfg) {
|
|
3646
|
+
const n = (cfg.name ?? "").trim();
|
|
3647
|
+
if (!n) {
|
|
3648
|
+
console.error(
|
|
3649
|
+
"\u8BF7\u5728 apm.config.json \u9876\u5C42\u914D\u7F6E name\uFF08\u4E0E\u524D\u7AEF\u5236\u54C1\u524D\u7F00\u3001\u540E\u7AEF\u955C\u50CF\u540D\u5171\u7528\uFF09"
|
|
3650
|
+
);
|
|
3651
|
+
process.exit(1);
|
|
3762
3652
|
}
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3653
|
+
return n;
|
|
3654
|
+
}
|
|
3655
|
+
function reqBackendPositiveInt(v, field) {
|
|
3656
|
+
const n = Number(v);
|
|
3657
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
|
|
3658
|
+
console.error(`apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
|
|
3659
|
+
process.exit(1);
|
|
3767
3660
|
}
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3661
|
+
return n;
|
|
3662
|
+
}
|
|
3663
|
+
function resolveBackendDeployFromApmConfig(cfg) {
|
|
3664
|
+
const b = cfg.backendDeploy ?? {};
|
|
3665
|
+
const protoRaw = req(b.remoteProtocol, "remoteProtocol").trim().toLowerCase();
|
|
3666
|
+
if (protoRaw !== "http" && protoRaw !== "https") {
|
|
3667
|
+
console.error(
|
|
3668
|
+
"apm.config.json \u4E2D backendDeploy.remoteProtocol \u53EA\u80FD\u4E3A http \u6216 https"
|
|
3669
|
+
);
|
|
3670
|
+
process.exit(1);
|
|
3771
3671
|
}
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
}
|
|
3782
|
-
const plan = event.args?.plan;
|
|
3783
|
-
if (typeof plan === "string" && plan.trim()) {
|
|
3784
|
-
return formatPlanMarkdown(plan);
|
|
3785
|
-
}
|
|
3672
|
+
const remoteProtocol = protoRaw;
|
|
3673
|
+
let mappings = [];
|
|
3674
|
+
const rawPorts = b.containerPortsMappings;
|
|
3675
|
+
if (rawPorts !== void 0 && rawPorts !== null) {
|
|
3676
|
+
if (!Array.isArray(rawPorts)) {
|
|
3677
|
+
console.error(
|
|
3678
|
+
"apm.config.json \u4E2D backendDeploy.containerPortsMappings \u987B\u4E3A\u5B57\u7B26\u4E32\u6570\u7EC4\uFF08\u53EF\u4E3A\u7A7A\uFF0C\u7701\u7565\u5219\u4E0D\u52A0\u7AEF\u53E3\u6620\u5C04\uFF09"
|
|
3679
|
+
);
|
|
3680
|
+
process.exit(1);
|
|
3786
3681
|
}
|
|
3787
|
-
|
|
3788
|
-
}
|
|
3789
|
-
resolveLogContent() {
|
|
3790
|
-
return this.events.map((event) => formatLogEvent(event.type, event)).join("\n");
|
|
3682
|
+
mappings = rawPorts.map((x) => String(x).trim()).filter(Boolean);
|
|
3791
3683
|
}
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3684
|
+
return {
|
|
3685
|
+
name: reqTopLevelName(cfg),
|
|
3686
|
+
registryHost: req(b.registryHost, "registryHost").trim(),
|
|
3687
|
+
registryNamespace: req(b.registryNamespace, "registryNamespace").trim(),
|
|
3688
|
+
registryUser: req(b.registryUser, "registryUser").trim(),
|
|
3689
|
+
registryPassword: req(b.registryPassword, "registryPassword").trim(),
|
|
3690
|
+
remoteHost: req(b.remoteHost, "remoteHost").trim(),
|
|
3691
|
+
remotePort: reqBackendPositiveInt(b.remotePort, "remotePort"),
|
|
3692
|
+
remoteProtocol,
|
|
3693
|
+
caPath: b.caPath?.trim(),
|
|
3694
|
+
certPath: b.certPath?.trim(),
|
|
3695
|
+
keyPath: b.keyPath?.trim(),
|
|
3696
|
+
envFilePath: typeof b.envFilePath === "string" ? b.envFilePath.trim() : "",
|
|
3697
|
+
containerPortsMappings: mappings,
|
|
3698
|
+
dockerNetwork: b.dockerNetwork?.trim() || void 0
|
|
3699
|
+
};
|
|
3700
|
+
}
|
|
3701
|
+
function reqFe(v, field) {
|
|
3702
|
+
if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
|
|
3703
|
+
console.error(`apm.config.json \u4E2D frontendDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
3704
|
+
process.exit(1);
|
|
3799
3705
|
}
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3706
|
+
return v;
|
|
3707
|
+
}
|
|
3708
|
+
function resolveFrontendDeployFromApmConfig(cfg) {
|
|
3709
|
+
const f = cfg.frontendDeploy ?? {};
|
|
3710
|
+
const port = Number(f.port);
|
|
3711
|
+
return {
|
|
3712
|
+
endpoint: reqFe(f.endpoint, "endpoint").trim(),
|
|
3713
|
+
port: Number.isFinite(port) && port > 0 ? port : 9e3,
|
|
3714
|
+
useSsl: Boolean(f.useSsl),
|
|
3715
|
+
accessKey: reqFe(f.accessKey, "accessKey").trim(),
|
|
3716
|
+
secretKey: reqFe(f.secretKey, "secretKey").trim(),
|
|
3717
|
+
bucket: reqFe(f.bucket, "bucket").trim()
|
|
3718
|
+
};
|
|
3719
|
+
}
|
|
3720
|
+
function reqWd(v, field) {
|
|
3721
|
+
if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
|
|
3722
|
+
console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
3723
|
+
process.exit(1);
|
|
3805
3724
|
}
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3725
|
+
return v;
|
|
3726
|
+
}
|
|
3727
|
+
function reqWisdomPositiveInt(v, field) {
|
|
3728
|
+
const n = Number(v);
|
|
3729
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
|
|
3730
|
+
console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
|
|
3731
|
+
process.exit(1);
|
|
3811
3732
|
}
|
|
3812
|
-
|
|
3813
|
-
|
|
3733
|
+
return n;
|
|
3734
|
+
}
|
|
3735
|
+
function resolveWisdomDeployFromApmConfig(cfg) {
|
|
3736
|
+
const w = cfg.wisdomDeploy ?? {};
|
|
3737
|
+
return {
|
|
3738
|
+
host: reqWd(w.host, "host").trim(),
|
|
3739
|
+
port: reqWisdomPositiveInt(w.port, "port"),
|
|
3740
|
+
username: reqWd(w.username, "username").trim(),
|
|
3741
|
+
password: reqWd(w.password, "password").trim(),
|
|
3742
|
+
remotePath: reqWd(w.remotePath, "remotePath").trim()
|
|
3743
|
+
};
|
|
3744
|
+
}
|
|
3745
|
+
function reqHc(v, field) {
|
|
3746
|
+
if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
|
|
3747
|
+
console.error(`apm.config.json \u4E2D healthCheck.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
3748
|
+
process.exit(1);
|
|
3814
3749
|
}
|
|
3815
|
-
return
|
|
3816
|
-
|
|
3817
|
-
\`\`\`json
|
|
3818
|
-
${JSON.stringify(event, null, 2)}
|
|
3819
|
-
\`\`\``;
|
|
3750
|
+
return v;
|
|
3820
3751
|
}
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
return
|
|
3752
|
+
function reqWb(v, field) {
|
|
3753
|
+
if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
|
|
3754
|
+
console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
3755
|
+
process.exit(1);
|
|
3756
|
+
}
|
|
3757
|
+
return v;
|
|
3827
3758
|
}
|
|
3828
|
-
function
|
|
3829
|
-
if (
|
|
3830
|
-
return
|
|
3759
|
+
function readEnvVar(env, name) {
|
|
3760
|
+
if (env[name] !== void 0) {
|
|
3761
|
+
return env[name];
|
|
3831
3762
|
}
|
|
3832
|
-
|
|
3833
|
-
const
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
parsed
|
|
3838
|
-
)) {
|
|
3839
|
-
if (typeof value === "string" && value.trim()) {
|
|
3840
|
-
result[key] = value.trim();
|
|
3841
|
-
}
|
|
3763
|
+
if (process.platform === "win32") {
|
|
3764
|
+
const target = name.toUpperCase();
|
|
3765
|
+
for (const [key, value] of Object.entries(env)) {
|
|
3766
|
+
if (key.toUpperCase() === target) {
|
|
3767
|
+
return value;
|
|
3842
3768
|
}
|
|
3843
|
-
return result;
|
|
3844
3769
|
}
|
|
3845
|
-
} catch {
|
|
3846
3770
|
}
|
|
3847
|
-
return
|
|
3848
|
-
}
|
|
3849
|
-
function writeRegistry(path12, registry) {
|
|
3850
|
-
mkdirSync6(dirname4(path12), { recursive: true });
|
|
3851
|
-
writeFileSync11(path12, `${JSON.stringify(registry, null, 2)}
|
|
3852
|
-
`, "utf8");
|
|
3853
|
-
}
|
|
3854
|
-
function loadSessionAgentId(workdir, sessionId, user) {
|
|
3855
|
-
const registry = readRegistry(registryPath(workdir, sessionId));
|
|
3856
|
-
return registry[user];
|
|
3771
|
+
return void 0;
|
|
3857
3772
|
}
|
|
3858
|
-
function
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3773
|
+
function expandWindowsEnvVars(pathStr, env = process.env) {
|
|
3774
|
+
return pathStr.replace(/%([^%]+)%/g, (_, name) => {
|
|
3775
|
+
const value = readEnvVar(env, name);
|
|
3776
|
+
return value ?? `%${name}%`;
|
|
3777
|
+
});
|
|
3863
3778
|
}
|
|
3864
|
-
function
|
|
3865
|
-
const
|
|
3866
|
-
const
|
|
3867
|
-
|
|
3868
|
-
|
|
3779
|
+
function expandUserPath(pathStr, env = process.env) {
|
|
3780
|
+
const home = env.HOME ?? env.USERPROFILE ?? homedir2();
|
|
3781
|
+
const withEnv = expandWindowsEnvVars(pathStr, env);
|
|
3782
|
+
const expanded = withEnv.replace(/^~(?=\/|\\|$)/, home);
|
|
3783
|
+
if (/^[a-zA-Z]:[/\\]/.test(expanded)) {
|
|
3784
|
+
return expanded;
|
|
3869
3785
|
}
|
|
3870
|
-
|
|
3871
|
-
writeRegistry(path12, registry);
|
|
3786
|
+
return resolve4(expanded);
|
|
3872
3787
|
}
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
await syncCursorMessageLog(cfg, ctx, events);
|
|
3889
|
-
session.clearDirty(events.map((event) => event.index));
|
|
3890
|
-
} catch (err) {
|
|
3891
|
-
onError(err);
|
|
3892
|
-
}
|
|
3893
|
-
};
|
|
3894
|
-
const drainDirtyEvents = async (session) => {
|
|
3895
|
-
while (session.getDirtyEvents().length > 0) {
|
|
3896
|
-
await syncDirtyEventsOnce(session);
|
|
3897
|
-
}
|
|
3898
|
-
};
|
|
3899
|
-
const enqueueSync = (session) => {
|
|
3900
|
-
syncChain = syncChain.then(() => drainDirtyEvents(session));
|
|
3901
|
-
};
|
|
3902
|
-
return {
|
|
3903
|
-
schedule(session) {
|
|
3904
|
-
latestSession = session;
|
|
3905
|
-
const now = Date.now();
|
|
3906
|
-
const elapsed = now - lastRunAt;
|
|
3907
|
-
if (elapsed >= CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS) {
|
|
3908
|
-
if (timer) {
|
|
3909
|
-
clearTimeout(timer);
|
|
3910
|
-
timer = void 0;
|
|
3911
|
-
}
|
|
3912
|
-
enqueueSync(session);
|
|
3913
|
-
return;
|
|
3914
|
-
}
|
|
3915
|
-
if (timer) {
|
|
3916
|
-
return;
|
|
3917
|
-
}
|
|
3918
|
-
timer = setTimeout(() => {
|
|
3919
|
-
timer = void 0;
|
|
3920
|
-
enqueueSync(latestSession);
|
|
3921
|
-
}, CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS - elapsed);
|
|
3922
|
-
},
|
|
3923
|
-
async flush(session) {
|
|
3924
|
-
latestSession = session;
|
|
3925
|
-
if (timer) {
|
|
3926
|
-
clearTimeout(timer);
|
|
3927
|
-
timer = void 0;
|
|
3928
|
-
}
|
|
3929
|
-
await syncChain;
|
|
3930
|
-
await drainDirtyEvents(session);
|
|
3788
|
+
var MAVEN_REPO_ENV_KEYS = [
|
|
3789
|
+
"MAVEN_LOCAL_REPO",
|
|
3790
|
+
"M2_REPO",
|
|
3791
|
+
"MAVEN_REPOSITORY"
|
|
3792
|
+
];
|
|
3793
|
+
function readMavenLocalRepoFromMavenOpts(mavenOpts, env = process.env) {
|
|
3794
|
+
const match = mavenOpts.match(/-Dmaven\.repo\.local=(?:"([^"]+)"|(\S+))/);
|
|
3795
|
+
const raw = match?.[1]?.trim() || match?.[2]?.trim();
|
|
3796
|
+
return raw ? expandUserPath(raw, env) : null;
|
|
3797
|
+
}
|
|
3798
|
+
function readMavenLocalRepoFromEnv(env = process.env) {
|
|
3799
|
+
for (const key of MAVEN_REPO_ENV_KEYS) {
|
|
3800
|
+
const raw = readEnvVar(env, key)?.trim();
|
|
3801
|
+
if (raw) {
|
|
3802
|
+
return { path: expandUserPath(raw, env), key };
|
|
3931
3803
|
}
|
|
3932
|
-
};
|
|
3933
|
-
}
|
|
3934
|
-
async function syncCursorMessageLog(cfg, ctx, events) {
|
|
3935
|
-
const agentId = ctx.agentId.trim();
|
|
3936
|
-
if (!agentId || events.length === 0) {
|
|
3937
|
-
return;
|
|
3938
3804
|
}
|
|
3939
|
-
const
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
}
|
|
3805
|
+
const mavenOpts = readEnvVar(env, "MAVEN_OPTS")?.trim();
|
|
3806
|
+
if (mavenOpts) {
|
|
3807
|
+
const path12 = readMavenLocalRepoFromMavenOpts(mavenOpts, env);
|
|
3808
|
+
if (path12) {
|
|
3809
|
+
return { path: path12, key: "MAVEN_OPTS" };
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
return null;
|
|
3946
3813
|
}
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
},
|
|
3961
|
-
required: ["content"]
|
|
3962
|
-
},
|
|
3963
|
-
execute: async (args) => {
|
|
3964
|
-
const content = typeof args.content === "string" ? args.content.trim() : "";
|
|
3965
|
-
if (!content) {
|
|
3966
|
-
return {
|
|
3967
|
-
content: [{ type: "text", text: "content \u4E0D\u80FD\u4E3A\u7A7A" }],
|
|
3968
|
-
isError: true
|
|
3969
|
-
};
|
|
3970
|
-
}
|
|
3971
|
-
try {
|
|
3972
|
-
await appendMessageContent(cfg, messageId, content);
|
|
3973
|
-
console.log(`[apm] append_message \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
|
|
3974
|
-
return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
|
|
3975
|
-
} catch (err) {
|
|
3976
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
3977
|
-
return {
|
|
3978
|
-
content: [{ type: "text", text: `\u8FFD\u52A0\u6D88\u606F\u5931\u8D25: ${detail}` }],
|
|
3979
|
-
isError: true
|
|
3980
|
-
};
|
|
3981
|
-
}
|
|
3982
|
-
}
|
|
3814
|
+
function readMavenLocalRepoFromSettings() {
|
|
3815
|
+
const settingsPath = join14(homedir2(), ".m2", "settings.xml");
|
|
3816
|
+
if (!existsSync13(settingsPath)) {
|
|
3817
|
+
return null;
|
|
3818
|
+
}
|
|
3819
|
+
try {
|
|
3820
|
+
const xml = readFileSync11(settingsPath, "utf8");
|
|
3821
|
+
const match = xml.match(
|
|
3822
|
+
/<localRepository>\s*([^<]+?)\s*<\/localRepository>/
|
|
3823
|
+
);
|
|
3824
|
+
const raw = match?.[1]?.trim();
|
|
3825
|
+
if (!raw) {
|
|
3826
|
+
return null;
|
|
3983
3827
|
}
|
|
3984
|
-
|
|
3828
|
+
return expandUserPath(raw);
|
|
3829
|
+
} catch {
|
|
3830
|
+
return null;
|
|
3831
|
+
}
|
|
3985
3832
|
}
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3833
|
+
function resolveMavenLocalRepoWithSource() {
|
|
3834
|
+
const fromEnv = readMavenLocalRepoFromEnv();
|
|
3835
|
+
if (fromEnv) {
|
|
3836
|
+
return {
|
|
3837
|
+
path: fromEnv.path,
|
|
3838
|
+
source: "env",
|
|
3839
|
+
sourceDetail: fromEnv.key
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3842
|
+
const fromSettings = readMavenLocalRepoFromSettings();
|
|
3843
|
+
if (fromSettings) {
|
|
3844
|
+
return {
|
|
3845
|
+
path: fromSettings,
|
|
3846
|
+
source: "settings",
|
|
3847
|
+
sourceDetail: "~/.m2/settings.xml"
|
|
3848
|
+
};
|
|
3849
|
+
}
|
|
3989
3850
|
return {
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
properties: {
|
|
3994
|
-
title: {
|
|
3995
|
-
type: "string",
|
|
3996
|
-
description: "Optional title for the questions form"
|
|
3997
|
-
},
|
|
3998
|
-
questions: {
|
|
3999
|
-
type: "array",
|
|
4000
|
-
minItems: 1,
|
|
4001
|
-
items: {
|
|
4002
|
-
type: "object",
|
|
4003
|
-
properties: {
|
|
4004
|
-
id: { type: "string" },
|
|
4005
|
-
prompt: { type: "string" },
|
|
4006
|
-
allow_multiple: { type: "boolean" },
|
|
4007
|
-
options: {
|
|
4008
|
-
type: "array",
|
|
4009
|
-
minItems: 2,
|
|
4010
|
-
items: {
|
|
4011
|
-
type: "object",
|
|
4012
|
-
properties: {
|
|
4013
|
-
id: { type: "string" },
|
|
4014
|
-
label: { type: "string" }
|
|
4015
|
-
},
|
|
4016
|
-
required: ["id", "label"]
|
|
4017
|
-
}
|
|
4018
|
-
}
|
|
4019
|
-
},
|
|
4020
|
-
required: ["id", "prompt", "options"]
|
|
4021
|
-
}
|
|
4022
|
-
}
|
|
4023
|
-
},
|
|
4024
|
-
required: ["questions"]
|
|
4025
|
-
},
|
|
4026
|
-
execute: async (args) => {
|
|
4027
|
-
const payload = JSON.stringify(args, null, 2);
|
|
4028
|
-
console.log(`[apm] AskQuestion mock \u8C03\u7528:
|
|
4029
|
-
${payload}`);
|
|
4030
|
-
options?.onInvoke?.(args);
|
|
4031
|
-
return `[mock] AskQuestion \u5DF2\u8BB0\u5F55\uFF08\u672A\u521B\u5EFA\u4EFB\u52A1\u95EE\u9898\u3001\u672A\u7B49\u5F85\u7528\u6237\u56DE\u7B54\uFF09\u3002\u53C2\u6570:
|
|
4032
|
-
${payload}`;
|
|
4033
|
-
}
|
|
3851
|
+
path: join14(homedir2(), ".m2", "repository"),
|
|
3852
|
+
source: "default",
|
|
3853
|
+
sourceDetail: "~/.m2/repository"
|
|
4034
3854
|
};
|
|
4035
3855
|
}
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
3856
|
+
function resolveWisdomBackendDeployFromApmConfig(cfg) {
|
|
3857
|
+
const projectName = reqTopLevelName(cfg);
|
|
3858
|
+
const w = cfg.wisdomDeploy ?? {};
|
|
3859
|
+
const h = cfg.healthCheck ?? {};
|
|
3860
|
+
const jarPath = reqWb(w.jarPath, "jarPath").trim();
|
|
3861
|
+
const remoteAppDir = posixDirname(jarPath);
|
|
3862
|
+
const healthPort = Number(reqHc(h.port, "port"));
|
|
3863
|
+
const healthTimeout = Number(reqHc(h.timeout, "timeout"));
|
|
3864
|
+
if (!Number.isFinite(healthPort) || !Number.isInteger(healthPort) || healthPort < 1) {
|
|
3865
|
+
console.error("apm.config.json \u4E2D healthCheck.port \u987B\u4E3A\u6B63\u6574\u6570");
|
|
3866
|
+
process.exit(1);
|
|
3867
|
+
}
|
|
3868
|
+
if (!Number.isFinite(healthTimeout) || !Number.isInteger(healthTimeout) || healthTimeout < 1) {
|
|
3869
|
+
console.error("apm.config.json \u4E2D healthCheck.timeout \u987B\u4E3A\u6B63\u6574\u6570");
|
|
3870
|
+
process.exit(1);
|
|
3871
|
+
}
|
|
3872
|
+
const mavenLocalRepo = resolveMavenLocalRepoWithSource();
|
|
4043
3873
|
return {
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
3874
|
+
projectName,
|
|
3875
|
+
host: reqWd(w.host, "host").trim(),
|
|
3876
|
+
port: reqWisdomPositiveInt(w.port, "port"),
|
|
3877
|
+
username: reqWd(w.username, "username").trim(),
|
|
3878
|
+
password: reqWd(w.password, "password").trim(),
|
|
3879
|
+
remoteVueDistDir: reqWd(w.remotePath, "remotePath").trim(),
|
|
3880
|
+
remoteAppDir,
|
|
3881
|
+
remoteLibDir: `${remoteAppDir}/lib`,
|
|
3882
|
+
startupJar: posixBasename(jarPath),
|
|
3883
|
+
packageName: `${projectName}.jar.zip`,
|
|
3884
|
+
mavenLocalRepo: mavenLocalRepo.path,
|
|
3885
|
+
mavenLocalRepoSource: mavenLocalRepo.source,
|
|
3886
|
+
mavenLocalRepoSourceDetail: mavenLocalRepo.sourceDetail,
|
|
3887
|
+
healthCheckPort: healthPort,
|
|
3888
|
+
healthCheckContext: reqHc(h.context, "context").trim(),
|
|
3889
|
+
healthCheckTimeout: healthTimeout
|
|
4048
3890
|
};
|
|
4049
3891
|
}
|
|
4050
|
-
function
|
|
4051
|
-
|
|
4052
|
-
|
|
3892
|
+
function posixDirname(p) {
|
|
3893
|
+
const normalized = p.replace(/\\/g, "/");
|
|
3894
|
+
const idx = normalized.lastIndexOf("/");
|
|
3895
|
+
if (idx <= 0) {
|
|
3896
|
+
return normalized.startsWith("/") ? "/" : ".";
|
|
4053
3897
|
}
|
|
4054
|
-
return
|
|
4055
|
-
|
|
4056
|
-
|
|
3898
|
+
return normalized.slice(0, idx);
|
|
3899
|
+
}
|
|
3900
|
+
function posixBasename(p) {
|
|
3901
|
+
const normalized = p.replace(/\\/g, "/");
|
|
3902
|
+
const idx = normalized.lastIndexOf("/");
|
|
3903
|
+
return idx >= 0 ? normalized.slice(idx + 1) : normalized;
|
|
4057
3904
|
}
|
|
4058
3905
|
|
|
4059
|
-
// src/commands/
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
}
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
3906
|
+
// src/commands/deploy/internal/wisdom-auto-deploy.ts
|
|
3907
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
|
|
3908
|
+
import path3 from "node:path";
|
|
3909
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
3910
|
+
|
|
3911
|
+
// src/commands/deploy/internal/wisdom-backend-deploy.ts
|
|
3912
|
+
import {
|
|
3913
|
+
existsSync as existsSync14,
|
|
3914
|
+
mkdirSync as mkdirSync6,
|
|
3915
|
+
readdirSync as readdirSync5,
|
|
3916
|
+
readFileSync as readFileSync12,
|
|
3917
|
+
statSync as statSync5,
|
|
3918
|
+
writeFileSync as writeFileSync12
|
|
3919
|
+
} from "node:fs";
|
|
3920
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
3921
|
+
import path2 from "node:path";
|
|
3922
|
+
import { Client } from "ssh2";
|
|
3923
|
+
import JSZip2 from "jszip";
|
|
3924
|
+
import SftpClient2 from "ssh2-sftp-client";
|
|
3925
|
+
|
|
3926
|
+
// src/commands/deploy/internal/wisdom-sftp.ts
|
|
3927
|
+
import { readdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
3928
|
+
import path from "node:path";
|
|
3929
|
+
import JSZip from "jszip";
|
|
3930
|
+
import SftpClient from "ssh2-sftp-client";
|
|
3931
|
+
async function addDirToZip(dir, zipFolder) {
|
|
3932
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
3933
|
+
for (const entry of entries) {
|
|
3934
|
+
const fullPath = path.join(dir, entry.name);
|
|
3935
|
+
if (entry.isDirectory()) {
|
|
3936
|
+
const folder = zipFolder.folder(entry.name);
|
|
3937
|
+
if (folder) {
|
|
3938
|
+
await addDirToZip(fullPath, folder);
|
|
3939
|
+
}
|
|
3940
|
+
} else {
|
|
3941
|
+
const content = await readFile(fullPath);
|
|
3942
|
+
zipFolder.file(entry.name, content);
|
|
3943
|
+
}
|
|
4083
3944
|
}
|
|
4084
|
-
return `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
|
|
4085
3945
|
}
|
|
4086
|
-
async function
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
}
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
3946
|
+
async function zipDirectory(distDir, zipPath) {
|
|
3947
|
+
console.error(`\u538B\u7F29\u76EE\u5F55: ${distDir}`);
|
|
3948
|
+
const zip = new JSZip();
|
|
3949
|
+
await addDirToZip(distDir, zip);
|
|
3950
|
+
const content = await zip.generateAsync({
|
|
3951
|
+
type: "nodebuffer",
|
|
3952
|
+
compression: "DEFLATE",
|
|
3953
|
+
compressionOptions: { level: 6 }
|
|
3954
|
+
});
|
|
3955
|
+
await writeFile(zipPath, content);
|
|
3956
|
+
const sizeMb = (content.length / 1024 / 1024).toFixed(2);
|
|
3957
|
+
console.error(`\u5DF2\u751F\u6210: ${zipPath} (${sizeMb} MB)`);
|
|
3958
|
+
return content.length;
|
|
3959
|
+
}
|
|
3960
|
+
async function ensureRemoteDir(sftp, dir) {
|
|
3961
|
+
const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
3962
|
+
let current = dir.startsWith("/") ? "" : ".";
|
|
3963
|
+
for (const part of parts) {
|
|
3964
|
+
current = current ? `${current}/${part}` : `/${part}`;
|
|
4100
3965
|
try {
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}${explicitAgentId ? "\uFF08\u53C2\u6570\u6307\u5B9A\uFF09" : ""}`
|
|
4104
|
-
);
|
|
4105
|
-
if (ctx.user) {
|
|
4106
|
-
saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent2.agentId);
|
|
4107
|
-
}
|
|
4108
|
-
return { agent: agent2, resumed: true };
|
|
4109
|
-
} catch (err) {
|
|
4110
|
-
console.warn(
|
|
4111
|
-
`[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
|
|
4112
|
-
err instanceof Error ? err.message : err
|
|
4113
|
-
);
|
|
4114
|
-
if (!explicitAgentId && ctx.user) {
|
|
4115
|
-
clearSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user);
|
|
4116
|
-
}
|
|
3966
|
+
await sftp.mkdir(current, true);
|
|
3967
|
+
} catch {
|
|
4117
3968
|
}
|
|
4118
3969
|
}
|
|
4119
|
-
const agent = await Agent.create(agentOptions);
|
|
4120
|
-
if (ctx.user) {
|
|
4121
|
-
saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent.agentId);
|
|
4122
|
-
}
|
|
4123
|
-
return { agent, resumed: false };
|
|
4124
3970
|
}
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
apiKey,
|
|
4145
|
-
model: ctx.model,
|
|
4146
|
-
cwd: workdir,
|
|
4147
|
-
workdir,
|
|
4148
|
-
sessionId: ctx.sessionId,
|
|
4149
|
-
user: ctx.user,
|
|
4150
|
-
mode: ctx.mode,
|
|
4151
|
-
resumeAgentId: ctx.resumeAgentId,
|
|
4152
|
-
customTools
|
|
3971
|
+
function execCommand(client, command) {
|
|
3972
|
+
return new Promise((resolve5, reject) => {
|
|
3973
|
+
client.exec(command, (err, stream) => {
|
|
3974
|
+
if (err) return reject(err);
|
|
3975
|
+
let stdout = "";
|
|
3976
|
+
let stderr = "";
|
|
3977
|
+
stream.on("close", (code) => {
|
|
3978
|
+
if (code !== 0) {
|
|
3979
|
+
reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
|
|
3980
|
+
return;
|
|
3981
|
+
}
|
|
3982
|
+
resolve5(stdout);
|
|
3983
|
+
}).on("data", (data) => {
|
|
3984
|
+
stdout += data.toString();
|
|
3985
|
+
});
|
|
3986
|
+
stream.stderr.on("data", (data) => {
|
|
3987
|
+
stderr += data.toString();
|
|
3988
|
+
});
|
|
3989
|
+
});
|
|
4153
3990
|
});
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
3991
|
+
}
|
|
3992
|
+
function shellSingleQuote(value) {
|
|
3993
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
3994
|
+
}
|
|
3995
|
+
function buildClearRemoteDirExceptZipCommand(target) {
|
|
3996
|
+
const normalized = target.replace(/\\/g, "/").replace(/\/$/, "");
|
|
3997
|
+
const quotedTarget = shellSingleQuote(normalized);
|
|
3998
|
+
const script = [
|
|
3999
|
+
`T=${quotedTarget}`,
|
|
4000
|
+
"S=$(mktemp -d)",
|
|
4001
|
+
'while IFS= read -r -d "" z; do r="${z#${T}/}"',
|
|
4002
|
+
'mkdir -p "${S}/$(dirname "$r")"',
|
|
4003
|
+
'mv "$z" "${S}/${r}"',
|
|
4004
|
+
'done < <(find "$T" -mindepth 1 -type f -iname "*.zip" -print0)',
|
|
4005
|
+
'rm -rf "${T}"/*',
|
|
4006
|
+
'while IFS= read -r -d "" r; do r="${r#./}"',
|
|
4007
|
+
'mkdir -p "${T}/$(dirname "$r")"',
|
|
4008
|
+
'mv "${S}/${r}" "${T}/${r}"',
|
|
4009
|
+
'done < <(cd "$S" 2>/dev/null && find . -type f -print0)',
|
|
4010
|
+
'rm -rf "$S"'
|
|
4011
|
+
].join("; ");
|
|
4012
|
+
return `bash -c ${shellSingleQuote(script)}`;
|
|
4013
|
+
}
|
|
4014
|
+
async function uploadAndMaybeExtract(settings, localZip, extract) {
|
|
4015
|
+
const remoteZipPath = `${settings.remotePath.replace(/\/$/, "")}/dist.zip`;
|
|
4016
|
+
console.error(
|
|
4017
|
+
`\u8FDE\u63A5 ${settings.username}@${settings.host}:${settings.port} ...`
|
|
4164
4018
|
);
|
|
4165
|
-
|
|
4166
|
-
const abortRun = () => {
|
|
4167
|
-
if (!activeRun?.supports("cancel")) return;
|
|
4168
|
-
void activeRun.cancel().catch(() => void 0);
|
|
4169
|
-
};
|
|
4170
|
-
signal?.addEventListener("abort", abortRun, { once: true });
|
|
4171
|
-
logAbortSignalStats(signal, "runCursorAgent:after-addListener");
|
|
4019
|
+
const sftp = new SftpClient();
|
|
4172
4020
|
try {
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4021
|
+
await sftp.connect({
|
|
4022
|
+
host: settings.host,
|
|
4023
|
+
port: settings.port,
|
|
4024
|
+
username: settings.username,
|
|
4025
|
+
password: settings.password,
|
|
4026
|
+
readyTimeout: 2e4,
|
|
4027
|
+
tryKeyboard: true
|
|
4180
4028
|
});
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
console.
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
console.error(
|
|
4195
|
-
`[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
|
|
4196
|
-
);
|
|
4197
|
-
}
|
|
4198
|
-
}
|
|
4199
|
-
options?.onStreamEvent?.(event);
|
|
4200
|
-
eventSession.addEvent(event);
|
|
4201
|
-
syncRemoteLog.schedule(eventSession);
|
|
4029
|
+
await ensureRemoteDir(sftp, settings.remotePath);
|
|
4030
|
+
await sftp.put(localZip, remoteZipPath);
|
|
4031
|
+
console.error(` \u2713 ${localZip} -> ${remoteZipPath}`);
|
|
4032
|
+
if (extract) {
|
|
4033
|
+
const target = settings.remotePath.replace(/\/$/, "");
|
|
4034
|
+
const client = sftp.client;
|
|
4035
|
+
const clearCmd = buildClearRemoteDirExceptZipCommand(target);
|
|
4036
|
+
console.error(`\u8FDC\u7A0B\u6E05\u7406\u89E3\u538B\u76EE\u5F55: ${clearCmd}`);
|
|
4037
|
+
await execCommand(client, clearCmd);
|
|
4038
|
+
const unzipCmd = `unzip -o "${remoteZipPath}" -d "${target}"`;
|
|
4039
|
+
console.error(`\u8FDC\u7A0B\u89E3\u538B: ${unzipCmd}`);
|
|
4040
|
+
await execCommand(client, unzipCmd);
|
|
4041
|
+
console.error("\u8FDC\u7A0B\u89E3\u538B\u5B8C\u6210!");
|
|
4202
4042
|
}
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4043
|
+
console.error(extract ? "\u90E8\u7F72\u5B8C\u6210!" : "\u4E0A\u4F20\u5B8C\u6210!");
|
|
4044
|
+
} finally {
|
|
4045
|
+
await sftp.end();
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
async function runWisdomSftpDeploy(params) {
|
|
4049
|
+
const zipPath = path.join(params.localDir, "..", ".deploy-sftp-dist.zip");
|
|
4050
|
+
const resolvedZipPath = path.resolve(zipPath);
|
|
4051
|
+
let zipSizeBytes = 0;
|
|
4052
|
+
try {
|
|
4053
|
+
zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
|
|
4054
|
+
await uploadAndMaybeExtract(
|
|
4055
|
+
params.settings,
|
|
4056
|
+
resolvedZipPath,
|
|
4057
|
+
params.extract
|
|
4058
|
+
);
|
|
4059
|
+
} finally {
|
|
4060
|
+
try {
|
|
4061
|
+
await unlink(resolvedZipPath);
|
|
4062
|
+
console.error(`\u5DF2\u6E05\u7406\u672C\u5730\u4E34\u65F6\u6587\u4EF6: ${resolvedZipPath}`);
|
|
4063
|
+
} catch {
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
return {
|
|
4067
|
+
ok: true,
|
|
4068
|
+
localDir: params.localDir,
|
|
4069
|
+
host: params.settings.host,
|
|
4070
|
+
remotePath: params.settings.remotePath,
|
|
4071
|
+
zipSizeBytes,
|
|
4072
|
+
extracted: params.extract
|
|
4073
|
+
};
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
// src/commands/deploy/internal/wisdom-backend-deploy.ts
|
|
4077
|
+
var SPRINGBOOT_JAVA_OPTS = "-XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -Xms512M -Xmx1G";
|
|
4078
|
+
var MAVEN_MODULE = "jeecg-module-system/jeecg-system-start";
|
|
4079
|
+
var MAVEN_PROFILE = "dev";
|
|
4080
|
+
function log(message) {
|
|
4081
|
+
const now = /* @__PURE__ */ new Date();
|
|
4082
|
+
const hh = String(now.getHours()).padStart(2, "0");
|
|
4083
|
+
const mm = String(now.getMinutes()).padStart(2, "0");
|
|
4084
|
+
const ss = String(now.getSeconds()).padStart(2, "0");
|
|
4085
|
+
console.error(`[${hh}:${mm}:${ss}] ${message}`);
|
|
4086
|
+
}
|
|
4087
|
+
function fail(message) {
|
|
4088
|
+
log(`ERROR: ${message}`);
|
|
4089
|
+
process.exit(1);
|
|
4090
|
+
}
|
|
4091
|
+
function expandPath(pathStr) {
|
|
4092
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4093
|
+
return path2.resolve(pathStr.replace(/^~(?=\/|\\|$)/, home));
|
|
4094
|
+
}
|
|
4095
|
+
function quoteForShell(value) {
|
|
4096
|
+
if (process.platform === "win32") {
|
|
4097
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
4098
|
+
}
|
|
4099
|
+
return shellSingleQuote(value);
|
|
4100
|
+
}
|
|
4101
|
+
function formatMavenLocalRepoArg(repoPath) {
|
|
4102
|
+
if (process.platform === "win32") {
|
|
4103
|
+
return `-Dmaven.repo.local=${quoteForShell(repoPath)}`;
|
|
4104
|
+
}
|
|
4105
|
+
return `-Dmaven.repo.local=${repoPath}`;
|
|
4106
|
+
}
|
|
4107
|
+
function deployCacheDir() {
|
|
4108
|
+
return path2.join(workspaceApmDir(), "deploy", ".deploy_cache");
|
|
4109
|
+
}
|
|
4110
|
+
function manifestFilePath() {
|
|
4111
|
+
return path2.join(deployCacheDir(), "manifest.json");
|
|
4112
|
+
}
|
|
4113
|
+
function getTargetDir(projectRoot) {
|
|
4114
|
+
return path2.join(projectRoot, MAVEN_MODULE, "target");
|
|
4115
|
+
}
|
|
4116
|
+
function relativeKey(projectRoot, filePath) {
|
|
4117
|
+
return path2.relative(projectRoot, filePath).split(path2.sep).join("/");
|
|
4118
|
+
}
|
|
4119
|
+
function fileSignature(filePath) {
|
|
4120
|
+
const stat2 = statSync5(filePath);
|
|
4121
|
+
return { size: stat2.size, mtime: stat2.mtimeMs / 1e3 };
|
|
4122
|
+
}
|
|
4123
|
+
function loadManifest4() {
|
|
4124
|
+
const manifestPath2 = manifestFilePath();
|
|
4125
|
+
if (!existsSync14(manifestPath2)) {
|
|
4126
|
+
return {};
|
|
4127
|
+
}
|
|
4128
|
+
return JSON.parse(readFileSync12(manifestPath2, "utf8"));
|
|
4129
|
+
}
|
|
4130
|
+
function saveManifest4(manifest) {
|
|
4131
|
+
const dir = deployCacheDir();
|
|
4132
|
+
mkdirSync6(dir, { recursive: true });
|
|
4133
|
+
writeFileSync12(manifestFilePath(), JSON.stringify(manifest, null, 2), "utf8");
|
|
4134
|
+
}
|
|
4135
|
+
function isProjectLibJar(jarName) {
|
|
4136
|
+
return jarName.startsWith("jeecg-");
|
|
4137
|
+
}
|
|
4138
|
+
function shouldUploadLibFile(localPath, remoteAttr, manifest, projectRoot) {
|
|
4139
|
+
if (!remoteAttr) {
|
|
4140
|
+
return [false, "\u8FDC\u7A0B\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7"];
|
|
4141
|
+
}
|
|
4142
|
+
const localSize = statSync5(localPath).size;
|
|
4143
|
+
const remoteSize = remoteAttr.size;
|
|
4144
|
+
if (localSize !== remoteSize) {
|
|
4145
|
+
return [true, `\u5927\u5C0F\u53D8\u5316 ${remoteSize} -> ${localSize}`];
|
|
4146
|
+
}
|
|
4147
|
+
if (isProjectLibJar(path2.basename(localPath)) && manifest) {
|
|
4148
|
+
const key = relativeKey(projectRoot, localPath);
|
|
4149
|
+
const current = fileSignature(localPath);
|
|
4150
|
+
const previous = manifest[key];
|
|
4151
|
+
if (!previous) {
|
|
4152
|
+
return [true, "\u9879\u76EE\u6A21\u5757\u672A\u8BB0\u5F55"];
|
|
4215
4153
|
}
|
|
4216
|
-
if (
|
|
4217
|
-
|
|
4154
|
+
if (previous.size !== current.size) {
|
|
4155
|
+
return [true, "\u9879\u76EE\u6A21\u5757\u5927\u5C0F\u53D8\u5316"];
|
|
4218
4156
|
}
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
const artifactDocuments = [];
|
|
4222
|
-
for (const artifact of artifacts) {
|
|
4223
|
-
try {
|
|
4224
|
-
const content = (await agent.downloadArtifact(artifact.path)).toString(
|
|
4225
|
-
"utf8"
|
|
4226
|
-
);
|
|
4227
|
-
artifactDocuments.push({ path: artifact.path, content });
|
|
4228
|
-
} catch (err) {
|
|
4229
|
-
console.warn(
|
|
4230
|
-
`[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
|
|
4231
|
-
err instanceof Error ? err.message : err
|
|
4232
|
-
);
|
|
4233
|
-
}
|
|
4157
|
+
if (previous.mtime < current.mtime) {
|
|
4158
|
+
return [true, "\u9879\u76EE\u6A21\u5757\u91CD\u65B0\u6784\u5EFA"];
|
|
4234
4159
|
}
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
`Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
|
|
4253
|
-
);
|
|
4160
|
+
}
|
|
4161
|
+
return [false, "\u5927\u5C0F\u4E00\u81F4\uFF0C\u8DF3\u8FC7"];
|
|
4162
|
+
}
|
|
4163
|
+
function listLibFilesToUpload(localLibDir, remoteStats, projectRoot, manifest = null) {
|
|
4164
|
+
const entries = [];
|
|
4165
|
+
const jarFiles = readdirSync5(localLibDir).filter((name) => name.endsWith(".jar")).sort();
|
|
4166
|
+
for (const jarName of jarFiles) {
|
|
4167
|
+
const jarPath = path2.join(localLibDir, jarName);
|
|
4168
|
+
const remoteAttr = remoteStats.get(jarName);
|
|
4169
|
+
const [shouldUpload, reason] = shouldUploadLibFile(
|
|
4170
|
+
jarPath,
|
|
4171
|
+
remoteAttr,
|
|
4172
|
+
manifest,
|
|
4173
|
+
projectRoot
|
|
4174
|
+
);
|
|
4175
|
+
if (shouldUpload) {
|
|
4176
|
+
entries.push({ path: jarPath, arcname: jarName, reason });
|
|
4254
4177
|
}
|
|
4255
|
-
throw err;
|
|
4256
|
-
} finally {
|
|
4257
|
-
logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
|
|
4258
|
-
signal?.removeEventListener("abort", abortRun);
|
|
4259
|
-
logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
|
|
4260
|
-
await agent[Symbol.asyncDispose]();
|
|
4261
4178
|
}
|
|
4179
|
+
return entries;
|
|
4262
4180
|
}
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4181
|
+
function updateManifestEntries(manifest, entries, projectRoot) {
|
|
4182
|
+
for (const entry of entries) {
|
|
4183
|
+
manifest[relativeKey(projectRoot, entry.path)] = fileSignature(entry.path);
|
|
4184
|
+
}
|
|
4185
|
+
return manifest;
|
|
4186
|
+
}
|
|
4187
|
+
async function createUpdatePackage(entries, packageName) {
|
|
4188
|
+
const dir = deployCacheDir();
|
|
4189
|
+
mkdirSync6(dir, { recursive: true });
|
|
4190
|
+
const zipPath = path2.join(dir, packageName);
|
|
4191
|
+
log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${path2.basename(zipPath)}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
|
|
4192
|
+
const zip = new JSZip2();
|
|
4193
|
+
for (const entry of entries) {
|
|
4194
|
+
const content = readFileSync12(entry.path);
|
|
4195
|
+
zip.file(entry.arcname, content);
|
|
4196
|
+
log(` \u6253\u5305: ${entry.arcname} (${entry.reason})`);
|
|
4197
|
+
}
|
|
4198
|
+
const buffer = await zip.generateAsync({
|
|
4199
|
+
type: "nodebuffer",
|
|
4200
|
+
compression: "DEFLATE",
|
|
4201
|
+
compressionOptions: { level: 6 }
|
|
4202
|
+
});
|
|
4203
|
+
writeFileSync12(zipPath, buffer);
|
|
4204
|
+
return zipPath;
|
|
4205
|
+
}
|
|
4206
|
+
function getMvnExecutable() {
|
|
4207
|
+
const isWin = process.platform === "win32";
|
|
4208
|
+
const candidates = isWin ? ["mvn.cmd", "mvn.bat", "mvn"] : ["mvn"];
|
|
4209
|
+
for (const name of candidates) {
|
|
4210
|
+
const result = spawnSync3(isWin ? `where ${name}` : `which ${name}`, {
|
|
4211
|
+
encoding: "utf8",
|
|
4212
|
+
shell: true
|
|
4213
|
+
});
|
|
4214
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
4215
|
+
return result.stdout.trim().split(/\r?\n/)[0].trim();
|
|
4275
4216
|
}
|
|
4276
4217
|
}
|
|
4277
|
-
|
|
4218
|
+
fail("\u672A\u627E\u5230 mvn \u547D\u4EE4\uFF0C\u8BF7\u786E\u8BA4 Maven \u5DF2\u5B89\u88C5\u5E76\u52A0\u5165 PATH");
|
|
4278
4219
|
}
|
|
4279
|
-
|
|
4280
|
-
const
|
|
4281
|
-
const
|
|
4282
|
-
const
|
|
4283
|
-
|
|
4284
|
-
|
|
4220
|
+
function runMavenBuild(projectRoot, mavenLocalRepo, repoSource) {
|
|
4221
|
+
const mavenRepo = expandPath(mavenLocalRepo);
|
|
4222
|
+
const mvn = getMvnExecutable();
|
|
4223
|
+
const command = [
|
|
4224
|
+
quoteForShell(mvn),
|
|
4225
|
+
"clean",
|
|
4226
|
+
"package",
|
|
4227
|
+
`-P${MAVEN_PROFILE}`,
|
|
4228
|
+
formatMavenLocalRepoArg(mavenRepo),
|
|
4229
|
+
"-DskipTests"
|
|
4230
|
+
].join(" ");
|
|
4231
|
+
log("\u5F00\u59CB Maven \u6784\u5EFA...");
|
|
4232
|
+
if (repoSource) {
|
|
4233
|
+
log(
|
|
4234
|
+
`Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo} (\u6765\u6E90: ${repoSource.source}/${repoSource.sourceDetail})`
|
|
4235
|
+
);
|
|
4236
|
+
} else {
|
|
4237
|
+
log(`Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo}`);
|
|
4238
|
+
}
|
|
4239
|
+
log(`\u6784\u5EFA\u76EE\u5F55: ${projectRoot}`);
|
|
4240
|
+
const result = spawnSync3(command, {
|
|
4241
|
+
cwd: projectRoot,
|
|
4242
|
+
stdio: "inherit",
|
|
4243
|
+
shell: true,
|
|
4244
|
+
env: process.env
|
|
4245
|
+
});
|
|
4246
|
+
if (result.status !== 0) {
|
|
4247
|
+
fail(`Maven \u6784\u5EFA\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${result.status ?? 1}`);
|
|
4285
4248
|
}
|
|
4286
|
-
return message.content.trim();
|
|
4287
4249
|
}
|
|
4288
|
-
|
|
4289
|
-
const
|
|
4290
|
-
if (
|
|
4291
|
-
|
|
4250
|
+
function locateLibDir(projectRoot) {
|
|
4251
|
+
const targetDir = getTargetDir(projectRoot);
|
|
4252
|
+
if (!existsSync14(targetDir)) {
|
|
4253
|
+
fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
|
|
4292
4254
|
}
|
|
4293
|
-
const
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
);
|
|
4255
|
+
const libDir = path2.join(targetDir, "lib");
|
|
4256
|
+
if (!existsSync14(libDir) || !statSync5(libDir).isDirectory()) {
|
|
4257
|
+
fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
|
|
4258
|
+
}
|
|
4259
|
+
const libJars = readdirSync5(libDir).filter((name) => name.endsWith(".jar"));
|
|
4260
|
+
if (libJars.length === 0) {
|
|
4261
|
+
fail(`lib \u76EE\u5F55\u4E0B\u6CA1\u6709\u4F9D\u8D56 JAR: ${libDir}`);
|
|
4262
|
+
}
|
|
4263
|
+
log(`\u5B9A\u4F4D lib \u4EA7\u7269: ${libJars.length} \u4E2A`);
|
|
4264
|
+
return libDir;
|
|
4298
4265
|
}
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4266
|
+
async function connectSsh(config) {
|
|
4267
|
+
const client = new Client();
|
|
4268
|
+
log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${config.username}@${config.host}:${config.port}`);
|
|
4269
|
+
await new Promise((resolve5, reject) => {
|
|
4270
|
+
client.on("ready", () => resolve5()).on("error", (err) => reject(err)).connect({
|
|
4271
|
+
host: config.host,
|
|
4272
|
+
port: config.port,
|
|
4273
|
+
username: config.username,
|
|
4274
|
+
password: config.password,
|
|
4275
|
+
readyTimeout: 3e4,
|
|
4276
|
+
tryKeyboard: true
|
|
4277
|
+
});
|
|
4278
|
+
});
|
|
4279
|
+
const sftp = new SftpClient2();
|
|
4280
|
+
await sftp.connect({
|
|
4281
|
+
host: config.host,
|
|
4282
|
+
port: config.port,
|
|
4283
|
+
username: config.username,
|
|
4284
|
+
password: config.password,
|
|
4285
|
+
readyTimeout: 3e4,
|
|
4286
|
+
tryKeyboard: true
|
|
4287
|
+
});
|
|
4288
|
+
return { client, sftp };
|
|
4306
4289
|
}
|
|
4307
|
-
function
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4290
|
+
async function closeSsh(conn) {
|
|
4291
|
+
try {
|
|
4292
|
+
await conn.sftp.end();
|
|
4293
|
+
} catch {
|
|
4311
4294
|
}
|
|
4295
|
+
conn.client.end();
|
|
4296
|
+
}
|
|
4297
|
+
async function getRemoteFileStats(sftp, remoteDir) {
|
|
4298
|
+
const stats = /* @__PURE__ */ new Map();
|
|
4312
4299
|
try {
|
|
4313
|
-
const
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4300
|
+
const listing = await sftp.list(remoteDir);
|
|
4301
|
+
for (const item of listing) {
|
|
4302
|
+
if (item.name.endsWith(".jar")) {
|
|
4303
|
+
stats.set(item.name, { filename: item.name, size: item.size });
|
|
4304
|
+
}
|
|
4318
4305
|
}
|
|
4319
4306
|
} catch {
|
|
4320
4307
|
}
|
|
4321
|
-
return
|
|
4308
|
+
return stats;
|
|
4322
4309
|
}
|
|
4323
|
-
function
|
|
4324
|
-
const
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
"
|
|
4310
|
+
async function uploadUpdatePackage(sftp, zipPath, config) {
|
|
4311
|
+
const remoteDir = config.remoteVueDistDir.replace(/\/$/, "");
|
|
4312
|
+
const remotePath = `${remoteDir}/${path2.basename(zipPath)}`;
|
|
4313
|
+
log(`\u4E0A\u4F20\u66F4\u65B0\u5305 -> ${remotePath}`);
|
|
4314
|
+
try {
|
|
4315
|
+
await sftp.fastPut(zipPath, remotePath);
|
|
4316
|
+
log("\u66F4\u65B0\u5305\u4E0A\u4F20\u6210\u529F");
|
|
4317
|
+
} catch (err) {
|
|
4318
|
+
fail(`\u66F4\u65B0\u5305\u4E0A\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : err}`);
|
|
4319
|
+
}
|
|
4320
|
+
return remotePath;
|
|
4321
|
+
}
|
|
4322
|
+
async function runRemoteCommand(client, command, options) {
|
|
4323
|
+
const check = options?.check ?? true;
|
|
4324
|
+
const stream = options?.stream ?? false;
|
|
4325
|
+
const label = options?.label ?? "\u8FDC\u7A0B\u547D\u4EE4";
|
|
4326
|
+
return new Promise((resolve5, reject) => {
|
|
4327
|
+
client.exec(command, (err, execStream) => {
|
|
4328
|
+
if (err) {
|
|
4329
|
+
reject(err);
|
|
4330
|
+
return;
|
|
4331
|
+
}
|
|
4332
|
+
let out = "";
|
|
4333
|
+
let errText = "";
|
|
4334
|
+
execStream.on("data", (data) => {
|
|
4335
|
+
const text = data.toString();
|
|
4336
|
+
out += text;
|
|
4337
|
+
if (stream) {
|
|
4338
|
+
process.stdout.write(text);
|
|
4339
|
+
}
|
|
4340
|
+
});
|
|
4341
|
+
execStream.stderr.on("data", (data) => {
|
|
4342
|
+
errText += data.toString();
|
|
4343
|
+
});
|
|
4344
|
+
execStream.on("close", (code) => {
|
|
4345
|
+
if (stream && out && !out.endsWith("\n")) {
|
|
4346
|
+
process.stdout.write("\n");
|
|
4347
|
+
}
|
|
4348
|
+
if (check && code !== 0) {
|
|
4349
|
+
const combined = `${out}
|
|
4350
|
+
${errText}`.trim();
|
|
4351
|
+
fail(
|
|
4352
|
+
`${label}\u5931\u8D25 (exit ${code})` + (combined ? `
|
|
4353
|
+
\u8F93\u51FA: ${combined}` : "")
|
|
4354
|
+
);
|
|
4355
|
+
}
|
|
4356
|
+
resolve5({ exitCode: code, out: out.trim(), err: errText.trim() });
|
|
4357
|
+
});
|
|
4358
|
+
});
|
|
4359
|
+
});
|
|
4360
|
+
}
|
|
4361
|
+
function buildExtractUpdatePackageScript(remoteZipPath, remoteLibDir) {
|
|
4362
|
+
const quotedZip = shellSingleQuote(remoteZipPath);
|
|
4363
|
+
const quotedLib = shellSingleQuote(remoteLibDir);
|
|
4364
|
+
return `
|
|
4365
|
+
set -e
|
|
4366
|
+
TMP=$(mktemp -d)
|
|
4367
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
4368
|
+
unzip -oq ${quotedZip} -d "$TMP"
|
|
4369
|
+
updated=0
|
|
4370
|
+
while IFS= read -r -d '' src; do
|
|
4371
|
+
name=$(basename "$src")
|
|
4372
|
+
dest=${quotedLib}/"$name"
|
|
4373
|
+
if [ -f "$dest" ]; then
|
|
4374
|
+
cp -f "$src" "$dest"
|
|
4375
|
+
echo "\u8986\u76D6: $name"
|
|
4376
|
+
updated=$((updated + 1))
|
|
4377
|
+
else
|
|
4378
|
+
echo "\u8DF3\u8FC7(\u8FDC\u7A0B\u4E0D\u5B58\u5728): $name"
|
|
4379
|
+
fi
|
|
4380
|
+
done < <(find "$TMP" -name '*.jar' -type f -print0)
|
|
4381
|
+
echo "UPDATED_COUNT=$updated"
|
|
4382
|
+
`.trim();
|
|
4383
|
+
}
|
|
4384
|
+
async function extractUpdatePackageOnRemote(client, config, remoteZipPath) {
|
|
4385
|
+
const script = buildExtractUpdatePackageScript(
|
|
4386
|
+
remoteZipPath,
|
|
4387
|
+
config.remoteLibDir
|
|
4330
4388
|
);
|
|
4389
|
+
const { out } = await runRemoteCommand(client, script, {
|
|
4390
|
+
label: "\u8FDC\u7A0B\u89E3\u538B"
|
|
4391
|
+
});
|
|
4392
|
+
const match = out.match(/UPDATED_COUNT=(\d+)/);
|
|
4393
|
+
if (!match) {
|
|
4394
|
+
fail(`\u8FDC\u7A0B\u89E3\u538B\u5931\u8D25\uFF0C\u672A\u83B7\u53D6\u66F4\u65B0\u6570\u91CF
|
|
4395
|
+
\u8F93\u51FA: ${out || "(\u7A7A)"}`);
|
|
4396
|
+
}
|
|
4397
|
+
const updated = Number.parseInt(match[1], 10);
|
|
4398
|
+
log(`lib \u89E3\u538B\u5B8C\u6210: \u8986\u76D6 ${updated} \u4E2A`);
|
|
4399
|
+
return updated;
|
|
4331
4400
|
}
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
return false;
|
|
4401
|
+
function springbootOutputIndicatesSuccess(action, combined) {
|
|
4402
|
+
const lower = combined.toLowerCase();
|
|
4403
|
+
if (action === "health") {
|
|
4404
|
+
return combined.includes("\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7");
|
|
4337
4405
|
}
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4406
|
+
if (action === "start" || action === "restart") {
|
|
4407
|
+
return combined.includes("is starting") || lower.includes("is running");
|
|
4408
|
+
}
|
|
4409
|
+
if (action === "stop") {
|
|
4410
|
+
return combined.includes("is stopping") || lower.includes("not running") || lower.includes("please check it");
|
|
4411
|
+
}
|
|
4412
|
+
if (action === "status") {
|
|
4413
|
+
return lower.includes("running") || lower.includes("not running");
|
|
4342
4414
|
}
|
|
4343
4415
|
return true;
|
|
4344
4416
|
}
|
|
4345
|
-
function
|
|
4346
|
-
|
|
4347
|
-
|
|
4417
|
+
function buildRemoteStatusScript(remoteAppDir, appName) {
|
|
4418
|
+
const dir = shellSingleQuote(remoteAppDir);
|
|
4419
|
+
const jar = shellSingleQuote(appName);
|
|
4420
|
+
return `
|
|
4421
|
+
set -e
|
|
4422
|
+
cd ${dir}
|
|
4423
|
+
appName=${jar}
|
|
4424
|
+
appIds=$(ps -ef | grep java | grep "$appName" | awk '{print $2}')
|
|
4425
|
+
if [ -z "$appIds" ]; then
|
|
4426
|
+
echo -e "\\033[31m Not running \\033[0m"
|
|
4427
|
+
else
|
|
4428
|
+
echo -e "\\033[32m Running [$appIds] \\033[0m"
|
|
4429
|
+
fi
|
|
4430
|
+
`.trim();
|
|
4348
4431
|
}
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4432
|
+
function buildRemoteRestartScript(remoteAppDir) {
|
|
4433
|
+
const dir = shellSingleQuote(remoteAppDir);
|
|
4434
|
+
const javaOpts = shellSingleQuote(SPRINGBOOT_JAVA_OPTS);
|
|
4435
|
+
return `
|
|
4436
|
+
set -e
|
|
4437
|
+
cd ${dir}
|
|
4438
|
+
releaseApp=$(ls -t | grep '.jar$' | head -n1)
|
|
4439
|
+
lastVersionApp=$(ls -t | grep '.jar$' | head -n2 | tail -n1)
|
|
4440
|
+
appName=$lastVersionApp
|
|
4441
|
+
appIds=$(ps -ef | grep java | grep "$appName" | awk '{print $2}')
|
|
4442
|
+
if [ -z "$appIds" ]; then
|
|
4443
|
+
echo "Maybe $appName not running, please check it..."
|
|
4444
|
+
else
|
|
4445
|
+
echo "The $appName is stopping..."
|
|
4446
|
+
echo "$appIds" | xargs kill
|
|
4447
|
+
fi
|
|
4448
|
+
for i in $(seq 15 -1 1); do
|
|
4449
|
+
echo -n "$i "
|
|
4450
|
+
sleep 1
|
|
4451
|
+
done
|
|
4452
|
+
echo 0
|
|
4453
|
+
if [ ! -d "backup" ]; then
|
|
4454
|
+
mkdir backup
|
|
4455
|
+
fi
|
|
4456
|
+
for i in $(ls | grep '.jar$' | grep -vFx "$releaseApp"); do
|
|
4457
|
+
echo "backup $i"
|
|
4458
|
+
mv "$i" backup/
|
|
4459
|
+
done
|
|
4460
|
+
appName=$releaseApp
|
|
4461
|
+
count=$(ps -ef | grep java | grep "$appName" | wc -l)
|
|
4462
|
+
if [ "$count" != "0" ]; then
|
|
4463
|
+
echo "Maybe $appName is running, please check it..."
|
|
4464
|
+
else
|
|
4465
|
+
echo "The $appName is starting..."
|
|
4466
|
+
nohup java -jar "./$appName" ${javaOpts} > nohup.out 2>&1 &
|
|
4467
|
+
fi
|
|
4468
|
+
`.trim();
|
|
4354
4469
|
}
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4470
|
+
function normalizeHealthContext(context) {
|
|
4471
|
+
let normalized = context.trim() || "/";
|
|
4472
|
+
if (!normalized.startsWith("/")) {
|
|
4473
|
+
normalized = `/${normalized}`;
|
|
4474
|
+
}
|
|
4475
|
+
if (!normalized.endsWith("/")) {
|
|
4476
|
+
normalized = `${normalized}/`;
|
|
4477
|
+
}
|
|
4478
|
+
return normalized;
|
|
4359
4479
|
}
|
|
4360
|
-
function
|
|
4361
|
-
|
|
4480
|
+
function buildRemoteHealthScript(port, context, timeoutSecs) {
|
|
4481
|
+
const normalizedContext = normalizeHealthContext(context);
|
|
4482
|
+
const portStr = String(port);
|
|
4483
|
+
const timeoutStr = String(timeoutSecs);
|
|
4484
|
+
return `
|
|
4485
|
+
set -e
|
|
4486
|
+
port=${shellSingleQuote(portStr)}
|
|
4487
|
+
context=${shellSingleQuote(normalizedContext)}
|
|
4488
|
+
timeout=${shellSingleQuote(timeoutStr)}
|
|
4489
|
+
check_url="http://127.0.0.1:${portStr}${normalizedContext}"
|
|
4490
|
+
echo "\u5065\u5EB7\u68C0\u67E5: \${check_url} (\u8D85\u65F6 \${timeout}s)"
|
|
4491
|
+
deadline=$(($(date +%s) + timeout))
|
|
4492
|
+
attempt=0
|
|
4493
|
+
while [ $(date +%s) -lt $deadline ]; do
|
|
4494
|
+
attempt=$((attempt + 1))
|
|
4495
|
+
code=$(curl -s -o /dev/null -w "%{http_code}" "$check_url" 2>/dev/null || echo "000")
|
|
4496
|
+
code=$(echo "$code" | tail -n1 | tr -d '[:space:]')
|
|
4497
|
+
if [ \${#code} -ge 3 ]; then
|
|
4498
|
+
status=\${code:0:3}
|
|
4499
|
+
if echo "$status" | grep -qE '^[0-9]+$' && [ "$status" -ge 200 ] && [ "$status" -lt 500 ]; then
|
|
4500
|
+
echo -e "\\033[32m\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7 (HTTP \${status})\\033[0m"
|
|
4501
|
+
exit 0
|
|
4502
|
+
fi
|
|
4503
|
+
fi
|
|
4504
|
+
remaining=$((deadline - $(date +%s)))
|
|
4505
|
+
if [ $remaining -lt 0 ]; then
|
|
4506
|
+
remaining=0
|
|
4507
|
+
fi
|
|
4508
|
+
echo "\u7B49\u5F85\u670D\u52A1\u542F\u52A8... \u7B2C \${attempt} \u6B21\uFF0C\u5269\u4F59 \${remaining}s"
|
|
4509
|
+
sleep 5
|
|
4510
|
+
done
|
|
4511
|
+
echo -e "\\033[31m\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6 (\${timeout}s): \${check_url}\\033[0m"
|
|
4512
|
+
exit 1
|
|
4513
|
+
`.trim();
|
|
4362
4514
|
}
|
|
4363
|
-
function
|
|
4364
|
-
const
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4515
|
+
async function runRemoteServiceScript(client, script, action) {
|
|
4516
|
+
const { exitCode, out, err } = await runRemoteCommand(client, script, {
|
|
4517
|
+
check: false,
|
|
4518
|
+
label: action === "status" ? "\u8FDC\u7A0B status" : action === "restart" ? "\u8FDC\u7A0B restart" : "\u5065\u5EB7\u68C0\u67E5"
|
|
4519
|
+
});
|
|
4520
|
+
const combined = `${out}
|
|
4521
|
+
${err}`.trim();
|
|
4522
|
+
const outputOk = springbootOutputIndicatesSuccess(action, combined);
|
|
4523
|
+
if (action === "health") {
|
|
4524
|
+
if (exitCode !== 0 || !outputOk) {
|
|
4525
|
+
fail(`\u5065\u5EB7\u68C0\u67E5\u5931\u8D25
|
|
4526
|
+
\u8F93\u51FA: ${combined || "(\u7A7A)"}`);
|
|
4527
|
+
}
|
|
4528
|
+
return combined;
|
|
4368
4529
|
}
|
|
4369
|
-
|
|
4370
|
-
}
|
|
4371
|
-
|
|
4372
|
-
|
|
4530
|
+
if (exitCode !== 0 && !outputOk) {
|
|
4531
|
+
fail(`\u8FDC\u7A0B ${action} \u5931\u8D25
|
|
4532
|
+
${combined}`);
|
|
4533
|
+
}
|
|
4534
|
+
if (action === "restart" && !outputOk) {
|
|
4535
|
+
fail(`\u8FDC\u7A0B restart \u672A\u6210\u529F
|
|
4536
|
+
\u8F93\u51FA: ${combined || "(\u7A7A)"}`);
|
|
4537
|
+
}
|
|
4538
|
+
return combined;
|
|
4373
4539
|
}
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
var DEFAULT_MAX_CONCURRENT = 5;
|
|
4377
|
-
function createRunSlotPool(maxConcurrent = DEFAULT_MAX_CONCURRENT) {
|
|
4378
|
-
let active = 0;
|
|
4379
|
-
const waiters = [];
|
|
4380
|
-
const acquire = () => {
|
|
4381
|
-
if (active < maxConcurrent) {
|
|
4382
|
-
active += 1;
|
|
4383
|
-
return Promise.resolve();
|
|
4384
|
-
}
|
|
4385
|
-
return new Promise((resolve5) => {
|
|
4386
|
-
waiters.push(() => {
|
|
4387
|
-
active += 1;
|
|
4388
|
-
resolve5();
|
|
4389
|
-
});
|
|
4390
|
-
});
|
|
4391
|
-
};
|
|
4392
|
-
const release = () => {
|
|
4393
|
-
active = Math.max(0, active - 1);
|
|
4394
|
-
const next = waiters.shift();
|
|
4395
|
-
if (next) {
|
|
4396
|
-
next();
|
|
4397
|
-
}
|
|
4398
|
-
};
|
|
4399
|
-
return { acquire, release };
|
|
4540
|
+
function stripAnsi(text) {
|
|
4541
|
+
return text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
4400
4542
|
}
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
await
|
|
4407
|
-
|
|
4543
|
+
async function getRunningJar(client, config) {
|
|
4544
|
+
const script = buildRemoteStatusScript(
|
|
4545
|
+
config.remoteAppDir,
|
|
4546
|
+
config.startupJar
|
|
4547
|
+
);
|
|
4548
|
+
const combined = await runRemoteServiceScript(client, script, "status");
|
|
4549
|
+
const text = stripAnsi(combined).trim().toLowerCase();
|
|
4550
|
+
if (text.includes("not running")) {
|
|
4551
|
+
return null;
|
|
4552
|
+
}
|
|
4553
|
+
if (text.includes("running")) {
|
|
4554
|
+
return config.startupJar;
|
|
4555
|
+
}
|
|
4556
|
+
return null;
|
|
4408
4557
|
}
|
|
4409
|
-
async function
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4558
|
+
async function healthCheckService(client, config) {
|
|
4559
|
+
log("\u5065\u5EB7\u68C0\u67E5...");
|
|
4560
|
+
const script = buildRemoteHealthScript(
|
|
4561
|
+
config.healthCheckPort,
|
|
4562
|
+
config.healthCheckContext,
|
|
4563
|
+
config.healthCheckTimeout
|
|
4564
|
+
);
|
|
4565
|
+
await runRemoteServiceScript(client, script, "health");
|
|
4413
4566
|
}
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4567
|
+
async function restartRemoteService(client, config) {
|
|
4568
|
+
const script = buildRemoteRestartScript(config.remoteAppDir);
|
|
4569
|
+
await runRemoteServiceScript(client, script, "restart");
|
|
4417
4570
|
}
|
|
4418
|
-
async function
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
4432
|
-
throw new Error(`[${step}] ${detail}`);
|
|
4433
|
-
}
|
|
4434
|
-
};
|
|
4571
|
+
async function runWisdomBackendDeploy(options) {
|
|
4572
|
+
const projectRoot = path2.resolve(options.projectRoot ?? process.cwd());
|
|
4573
|
+
const config = options.config;
|
|
4574
|
+
log(`=== \u81EA\u52A8\u90E8\u7F72: ${config.projectName} ===`);
|
|
4575
|
+
log(`\u914D\u7F6E\u6587\u4EF6: ${path2.join(workspaceApmDir(), "apm.config.json")}`);
|
|
4576
|
+
log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
|
|
4577
|
+
runMavenBuild(projectRoot, config.mavenLocalRepo, {
|
|
4578
|
+
source: config.mavenLocalRepoSource,
|
|
4579
|
+
sourceDetail: config.mavenLocalRepoSourceDetail
|
|
4580
|
+
});
|
|
4581
|
+
const libDir = locateLibDir(projectRoot);
|
|
4582
|
+
let manifest = loadManifest4();
|
|
4583
|
+
const conn = await connectSsh(config);
|
|
4435
4584
|
try {
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
() => ensureWorkspaceInitialized(workdir)
|
|
4585
|
+
const remoteLibStats = await getRemoteFileStats(
|
|
4586
|
+
conn.sftp,
|
|
4587
|
+
config.remoteLibDir
|
|
4440
4588
|
);
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4589
|
+
log("\u6536\u96C6 JAR \u66F4\u65B0...");
|
|
4590
|
+
const libUploadEntries = listLibFilesToUpload(
|
|
4591
|
+
libDir,
|
|
4592
|
+
remoteLibStats,
|
|
4593
|
+
projectRoot,
|
|
4594
|
+
manifest
|
|
4447
4595
|
);
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
console.log(`[apm] step=branch skipped sessionId=${msg.sessionId}`);
|
|
4454
|
-
}
|
|
4455
|
-
let pullRan = false;
|
|
4456
|
-
if (shouldRunPull(msg.sessionId, workdir)) {
|
|
4457
|
-
if (signal.aborted) return;
|
|
4458
|
-
await runStep("pull", () => runPull(msg.sessionId, workdir));
|
|
4459
|
-
markPullDone(msg.sessionId, workdir);
|
|
4460
|
-
pullRan = true;
|
|
4461
|
-
} else {
|
|
4462
|
-
console.log(`[apm] step=pull skipped sessionId=${msg.sessionId}`);
|
|
4463
|
-
}
|
|
4464
|
-
if (pullRan) {
|
|
4465
|
-
if (signal.aborted) return;
|
|
4466
|
-
await runStep(
|
|
4467
|
-
"commit-pull",
|
|
4468
|
-
() => commitWorkingTreeIfDirty(workdir, "fix: apm pull")
|
|
4596
|
+
let updated = 0;
|
|
4597
|
+
if (libUploadEntries.length > 0) {
|
|
4598
|
+
const zipPath = await createUpdatePackage(
|
|
4599
|
+
libUploadEntries,
|
|
4600
|
+
config.packageName
|
|
4469
4601
|
);
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
|
|
4475
|
-
if (signal.aborted) return;
|
|
4476
|
-
console.log(
|
|
4477
|
-
`[apm] CLI \u7248\u672C ${cliVersion} \u4E0E\u5DE5\u4F5C\u533A\u8BB0\u5F55\u4E0D\u4E00\u81F4\uFF0C\u6267\u884C update-skills`
|
|
4602
|
+
const remoteZipPath = await uploadUpdatePackage(
|
|
4603
|
+
conn.sftp,
|
|
4604
|
+
zipPath,
|
|
4605
|
+
config
|
|
4478
4606
|
);
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
console.log(`[apm] step=update-skills skipped workdir=${workdir}`);
|
|
4485
|
-
}
|
|
4486
|
-
if (signal.aborted) return;
|
|
4487
|
-
if (!pullRan) {
|
|
4488
|
-
await runStep(
|
|
4489
|
-
"sync-project-documents-pull",
|
|
4490
|
-
() => syncRepositoryProjectDocumentsPull(workdir, apmRoot)
|
|
4607
|
+
log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
|
|
4608
|
+
updated = await extractUpdatePackageOnRemote(
|
|
4609
|
+
conn.client,
|
|
4610
|
+
config,
|
|
4611
|
+
remoteZipPath
|
|
4491
4612
|
);
|
|
4613
|
+
} else {
|
|
4614
|
+
log("\u65E0 JAR \u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u66F4\u65B0\u5305\u4E0A\u4F20");
|
|
4492
4615
|
}
|
|
4493
|
-
const
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
prompt: msg.content,
|
|
4501
|
-
model: msg.model,
|
|
4502
|
-
apiKey: msg.apiKey,
|
|
4503
|
-
workdir,
|
|
4504
|
-
user: msg.user
|
|
4505
|
-
},
|
|
4506
|
-
{ signal }
|
|
4507
|
-
)
|
|
4508
|
-
);
|
|
4509
|
-
await runStep(
|
|
4510
|
-
"ensure-reply",
|
|
4511
|
-
() => ensureMessageHasReply(cfg, msg.sessionId, messageId, {
|
|
4512
|
-
assistantText: agentResult.assistantText,
|
|
4513
|
-
result: agentResult.result,
|
|
4514
|
-
createPlan: agentResult.createPlan
|
|
4515
|
-
})
|
|
4516
|
-
);
|
|
4517
|
-
await runStep(
|
|
4518
|
-
"sync-documents",
|
|
4519
|
-
() => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
|
|
4520
|
-
);
|
|
4521
|
-
await runStep(
|
|
4522
|
-
"sync-project-documents",
|
|
4523
|
-
() => syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot)
|
|
4524
|
-
);
|
|
4525
|
-
await runStep(
|
|
4526
|
-
"commit-files",
|
|
4527
|
-
() => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
|
|
4528
|
-
);
|
|
4529
|
-
await runStep(
|
|
4530
|
-
"status-success",
|
|
4531
|
-
() => updateMessageStatus(cfg, messageId, "SUCCESS")
|
|
4532
|
-
);
|
|
4533
|
-
} catch (err) {
|
|
4534
|
-
if (isUserCancelled(ctx)) {
|
|
4535
|
-
console.log(`[apm] \u6D88\u606F\u5DF2\u7EC8\u6B62 messageId=${messageId}`);
|
|
4536
|
-
return;
|
|
4537
|
-
}
|
|
4538
|
-
console.error(
|
|
4539
|
-
"[apm] \u5904\u7406\u6D88\u606F\u5931\u8D25:",
|
|
4540
|
-
err instanceof Error ? err.message : String(err)
|
|
4541
|
-
);
|
|
4542
|
-
if (err instanceof Error && err.stack) {
|
|
4543
|
-
console.error(err.stack);
|
|
4616
|
+
const runningJar = await getRunningJar(conn.client, config);
|
|
4617
|
+
let needRestart = updated > 0;
|
|
4618
|
+
if (!needRestart && !runningJar) {
|
|
4619
|
+
log("\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u9700\u8981\u542F\u52A8");
|
|
4620
|
+
needRestart = true;
|
|
4621
|
+
} else if (!needRestart) {
|
|
4622
|
+
log("\u6CA1\u6709\u6587\u4EF6\u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u91CD\u542F");
|
|
4544
4623
|
}
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
messageId,
|
|
4549
|
-
err instanceof Error ? err.message : String(err)
|
|
4550
|
-
);
|
|
4551
|
-
await updateMessageStatus(cfg, messageId, "FAILED");
|
|
4552
|
-
} catch (statusErr) {
|
|
4553
|
-
console.error(
|
|
4554
|
-
"[apm] \u66F4\u65B0 FAILED \u72B6\u6001\u5931\u8D25:",
|
|
4555
|
-
statusErr instanceof Error ? statusErr.message : statusErr
|
|
4556
|
-
);
|
|
4624
|
+
if (needRestart) {
|
|
4625
|
+
log("\u91CD\u542F\u670D\u52A1...");
|
|
4626
|
+
await restartRemoteService(conn.client, config);
|
|
4557
4627
|
}
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
4563
|
-
ws.send(
|
|
4564
|
-
serializeAgentWsMessage({
|
|
4565
|
-
type: "heartbeat",
|
|
4566
|
-
userId: clientMachineId
|
|
4567
|
-
})
|
|
4568
|
-
);
|
|
4628
|
+
await healthCheckService(conn.client, config);
|
|
4629
|
+
if (libUploadEntries.length > 0) {
|
|
4630
|
+
manifest = updateManifestEntries(manifest, libUploadEntries, projectRoot);
|
|
4631
|
+
saveManifest4(manifest);
|
|
4569
4632
|
}
|
|
4570
|
-
}
|
|
4571
|
-
|
|
4572
|
-
const timer = setInterval(send, HEARTBEAT_MS);
|
|
4573
|
-
return () => clearInterval(timer);
|
|
4574
|
-
}
|
|
4575
|
-
function reexecConnect(options) {
|
|
4576
|
-
const args = [process.argv[1], "connect"];
|
|
4577
|
-
const server = options.server?.trim();
|
|
4578
|
-
if (server) {
|
|
4579
|
-
args.push("--server", server);
|
|
4633
|
+
} finally {
|
|
4634
|
+
await closeSsh(conn);
|
|
4580
4635
|
}
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4636
|
+
log("\u90E8\u7F72\u5B8C\u6210");
|
|
4637
|
+
}
|
|
4638
|
+
|
|
4639
|
+
// src/commands/deploy/internal/wisdom-auto-deploy.ts
|
|
4640
|
+
function isWisdomLegacyDeploy(cfg) {
|
|
4641
|
+
const w = cfg.wisdomDeploy;
|
|
4642
|
+
if (!w?.host?.trim() || !w.remotePath?.trim()) {
|
|
4643
|
+
return false;
|
|
4585
4644
|
}
|
|
4586
|
-
|
|
4645
|
+
const hasNewFrontend = Boolean(cfg.frontendDeploy?.endpoint?.trim());
|
|
4646
|
+
const hasNewBackend = Boolean(cfg.backendDeploy?.registryHost?.trim());
|
|
4647
|
+
return !hasNewFrontend && !hasNewBackend;
|
|
4587
4648
|
}
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4649
|
+
function detectWisdomProjectType(cwd) {
|
|
4650
|
+
return existsSync15(path3.join(cwd, "package.json")) ? "frontend" : "backend";
|
|
4651
|
+
}
|
|
4652
|
+
function readPackageScripts(cwd) {
|
|
4653
|
+
const pkgPath = path3.join(cwd, "package.json");
|
|
4654
|
+
if (!existsSync15(pkgPath)) {
|
|
4655
|
+
return {};
|
|
4592
4656
|
}
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4657
|
+
try {
|
|
4658
|
+
const raw = readFileSync13(pkgPath, "utf8");
|
|
4659
|
+
const parsed = JSON.parse(raw);
|
|
4660
|
+
return parsed.scripts ?? {};
|
|
4661
|
+
} catch {
|
|
4662
|
+
return {};
|
|
4596
4663
|
}
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4664
|
+
}
|
|
4665
|
+
function resolveFrontendDeployCommand(env, cwd) {
|
|
4666
|
+
const scripts = readPackageScripts(cwd);
|
|
4667
|
+
const deployKey = `deploy:${env}`;
|
|
4668
|
+
if (scripts[deployKey]?.trim()) {
|
|
4669
|
+
return `npm run deploy:${env}`;
|
|
4601
4670
|
}
|
|
4602
|
-
|
|
4603
|
-
console.log(`[apm] \u8FDE\u63A5 ${cfg.baseUrl} \u2026`);
|
|
4604
|
-
await new Promise((resolve5, reject) => {
|
|
4605
|
-
const ws = new WebSocket(url);
|
|
4606
|
-
let stopHeartbeat;
|
|
4607
|
-
let shuttingDown = false;
|
|
4608
|
-
const shutdownAbort = new AbortController();
|
|
4609
|
-
const runSlots = createRunSlotPool();
|
|
4610
|
-
const activeTasks = /* @__PURE__ */ new Set();
|
|
4611
|
-
const activeRuns = /* @__PURE__ */ new Map();
|
|
4612
|
-
const pendingCancels = /* @__PURE__ */ new Set();
|
|
4613
|
-
const shutdown = async (code = 0) => {
|
|
4614
|
-
if (shuttingDown) return;
|
|
4615
|
-
shuttingDown = true;
|
|
4616
|
-
logAbortSignalStats(
|
|
4617
|
-
shutdownAbort.signal,
|
|
4618
|
-
"connect:shutdown-before-abort"
|
|
4619
|
-
);
|
|
4620
|
-
shutdownAbort.abort();
|
|
4621
|
-
logAbortSignalStats(shutdownAbort.signal, "connect:shutdown-after-abort");
|
|
4622
|
-
stopHeartbeat?.();
|
|
4623
|
-
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
4624
|
-
ws.terminate();
|
|
4625
|
-
}
|
|
4626
|
-
try {
|
|
4627
|
-
await Promise.race([
|
|
4628
|
-
Promise.all(activeTasks),
|
|
4629
|
-
new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
|
|
4630
|
-
]);
|
|
4631
|
-
} catch {
|
|
4632
|
-
}
|
|
4633
|
-
resolve5();
|
|
4634
|
-
process.exit(code);
|
|
4635
|
-
};
|
|
4636
|
-
ws.on("open", () => {
|
|
4637
|
-
console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
|
|
4638
|
-
stopHeartbeat = startHeartbeat(ws, clientMachineId);
|
|
4639
|
-
});
|
|
4640
|
-
ws.on("message", (data) => {
|
|
4641
|
-
if (shuttingDown) return;
|
|
4642
|
-
const text = Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
|
|
4643
|
-
const parsed = parseAgentWsMessage(text);
|
|
4644
|
-
if (parsed === null) {
|
|
4645
|
-
console.error("[apm] \u6536\u5230\u65E0\u6548 JSON");
|
|
4646
|
-
return;
|
|
4647
|
-
}
|
|
4648
|
-
if (typeof parsed === "object" && parsed !== null && parsed.type === "heartbeat") {
|
|
4649
|
-
return;
|
|
4650
|
-
}
|
|
4651
|
-
const validated = validateAgentWsMessage(parsed, "outbound");
|
|
4652
|
-
if (!validated.ok) {
|
|
4653
|
-
console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
|
|
4654
|
-
return;
|
|
4655
|
-
}
|
|
4656
|
-
if (validated.data.type === "cancel") {
|
|
4657
|
-
const { messageId } = validated.data;
|
|
4658
|
-
pendingCancels.add(messageId);
|
|
4659
|
-
activeRuns.get(messageId)?.abort();
|
|
4660
|
-
return;
|
|
4661
|
-
}
|
|
4662
|
-
if (validated.data.type === "deploy") {
|
|
4663
|
-
const msg2 = validated.data;
|
|
4664
|
-
const perDeployController = new AbortController();
|
|
4665
|
-
const signal2 = AbortSignal.any([
|
|
4666
|
-
shutdownAbort.signal,
|
|
4667
|
-
perDeployController.signal
|
|
4668
|
-
]);
|
|
4669
|
-
const task2 = (async () => {
|
|
4670
|
-
await runSlots.acquire();
|
|
4671
|
-
try {
|
|
4672
|
-
await handleInboundDeploy(cfg, msg2, signal2);
|
|
4673
|
-
} finally {
|
|
4674
|
-
runSlots.release();
|
|
4675
|
-
}
|
|
4676
|
-
})();
|
|
4677
|
-
activeTasks.add(task2);
|
|
4678
|
-
void task2.finally(() => {
|
|
4679
|
-
activeTasks.delete(task2);
|
|
4680
|
-
});
|
|
4681
|
-
return;
|
|
4682
|
-
}
|
|
4683
|
-
if (validated.data.type !== "message") {
|
|
4684
|
-
return;
|
|
4685
|
-
}
|
|
4686
|
-
const msg = validated.data;
|
|
4687
|
-
const perMessageController = new AbortController();
|
|
4688
|
-
activeRuns.set(msg.messageId, perMessageController);
|
|
4689
|
-
if (pendingCancels.has(msg.messageId)) {
|
|
4690
|
-
activeRuns.delete(msg.messageId);
|
|
4691
|
-
pendingCancels.delete(msg.messageId);
|
|
4692
|
-
return;
|
|
4693
|
-
}
|
|
4694
|
-
const signal = AbortSignal.any([
|
|
4695
|
-
shutdownAbort.signal,
|
|
4696
|
-
perMessageController.signal
|
|
4697
|
-
]);
|
|
4698
|
-
const ctx = {
|
|
4699
|
-
shutdownSignal: shutdownAbort.signal,
|
|
4700
|
-
perMessageSignal: perMessageController.signal
|
|
4701
|
-
};
|
|
4702
|
-
const task = (async () => {
|
|
4703
|
-
await runSlots.acquire();
|
|
4704
|
-
try {
|
|
4705
|
-
await handleInboundMessage(cfg, msg, signal, ctx);
|
|
4706
|
-
} finally {
|
|
4707
|
-
runSlots.release();
|
|
4708
|
-
activeRuns.delete(msg.messageId);
|
|
4709
|
-
pendingCancels.delete(msg.messageId);
|
|
4710
|
-
}
|
|
4711
|
-
})();
|
|
4712
|
-
activeTasks.add(task);
|
|
4713
|
-
void task.finally(() => {
|
|
4714
|
-
activeTasks.delete(task);
|
|
4715
|
-
});
|
|
4716
|
-
});
|
|
4717
|
-
ws.on("close", (code, reason) => {
|
|
4718
|
-
console.log(
|
|
4719
|
-
`[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
|
|
4720
|
-
);
|
|
4721
|
-
void shutdown();
|
|
4722
|
-
});
|
|
4723
|
-
ws.on("error", (err) => {
|
|
4724
|
-
console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
|
|
4725
|
-
reject(err);
|
|
4726
|
-
});
|
|
4727
|
-
process.on("SIGINT", () => {
|
|
4728
|
-
console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
|
|
4729
|
-
void shutdown();
|
|
4730
|
-
});
|
|
4731
|
-
process.on("SIGTERM", () => {
|
|
4732
|
-
void shutdown();
|
|
4733
|
-
});
|
|
4734
|
-
});
|
|
4671
|
+
return null;
|
|
4735
4672
|
}
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4673
|
+
function runShellCommand2(command, cwd) {
|
|
4674
|
+
const result = spawnSync4(command, {
|
|
4675
|
+
cwd,
|
|
4676
|
+
stdio: "inherit",
|
|
4677
|
+
shell: true,
|
|
4678
|
+
env: process.env
|
|
4679
|
+
});
|
|
4680
|
+
if (result.status !== 0) {
|
|
4681
|
+
process.exit(result.status ?? 1);
|
|
4743
4682
|
}
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4683
|
+
}
|
|
4684
|
+
async function runWisdomAutoDeploy(options) {
|
|
4685
|
+
const cwd = path3.resolve(options.cwd ?? process.cwd());
|
|
4686
|
+
const projectType = detectWisdomProjectType(cwd);
|
|
4687
|
+
console.error(
|
|
4688
|
+
`[apm] \u533B\u52A1\u5B58\u91CF\u9879\u76EE\u81EA\u52A8\u8BC6\u522B: ${projectType === "frontend" ? "\u524D\u7AEF Vue" : "\u540E\u7AEF Java"}`
|
|
4689
|
+
);
|
|
4690
|
+
if (projectType === "frontend") {
|
|
4691
|
+
const deployCmd = resolveFrontendDeployCommand(options.env, cwd);
|
|
4692
|
+
if (!deployCmd) {
|
|
4693
|
+
console.error(
|
|
4694
|
+
`\u524D\u7AEF\u9879\u76EE ${path3.join(cwd, "package.json")} \u4E2D\u672A\u627E\u5230 deploy:${options.env} \u811A\u672C`
|
|
4695
|
+
);
|
|
4696
|
+
process.exit(1);
|
|
4697
|
+
}
|
|
4698
|
+
runShellCommand2(deployCmd, cwd);
|
|
4699
|
+
return { projectType };
|
|
4748
4700
|
}
|
|
4749
|
-
const
|
|
4750
|
-
const
|
|
4751
|
-
const
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4701
|
+
const configPath = options.configPath ?? path3.join(workspaceApmDir(cwd), "apm.config.json");
|
|
4702
|
+
const cfg = loadApmConfig({ configPath });
|
|
4703
|
+
const settings = resolveWisdomBackendDeployFromApmConfig(cfg);
|
|
4704
|
+
console.error(
|
|
4705
|
+
`[apm] \u540E\u7AEF\u90E8\u7F72\u4E0D\u533A\u5206 test/online\uFF0C\u5FFD\u7565\u73AF\u5883\u53C2\u6570: ${options.env}`
|
|
4706
|
+
);
|
|
4707
|
+
await runWisdomBackendDeploy({
|
|
4708
|
+
config: settings,
|
|
4709
|
+
projectRoot: cwd
|
|
4757
4710
|
});
|
|
4758
|
-
|
|
4711
|
+
return { projectType };
|
|
4759
4712
|
}
|
|
4760
4713
|
|
|
4761
4714
|
// src/commands/deploy/deploy.ts
|
|
4762
|
-
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
4763
4715
|
function runShellCommand3(command, cwd) {
|
|
4764
4716
|
const result = spawnSync5(command, {
|
|
4765
4717
|
cwd,
|
|
@@ -4813,7 +4765,7 @@ import path7 from "node:path";
|
|
|
4813
4765
|
import Docker from "dockerode";
|
|
4814
4766
|
|
|
4815
4767
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
|
|
4816
|
-
import { existsSync as
|
|
4768
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "node:fs";
|
|
4817
4769
|
import path4 from "node:path";
|
|
4818
4770
|
function asOptionalTlsBuffer(value) {
|
|
4819
4771
|
if (typeof value !== "string") {
|
|
@@ -4825,8 +4777,8 @@ function asOptionalTlsBuffer(value) {
|
|
|
4825
4777
|
if (normalized === "") {
|
|
4826
4778
|
return void 0;
|
|
4827
4779
|
}
|
|
4828
|
-
if (
|
|
4829
|
-
return
|
|
4780
|
+
if (existsSync16(normalized)) {
|
|
4781
|
+
return readFileSync14(normalized);
|
|
4830
4782
|
}
|
|
4831
4783
|
const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
|
|
4832
4784
|
if (looksLikePath) {
|
|
@@ -5036,7 +4988,7 @@ var DockerodeClient = class {
|
|
|
5036
4988
|
var createDockerodeClient = (config) => new DockerodeClient(config);
|
|
5037
4989
|
|
|
5038
4990
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
|
|
5039
|
-
import { existsSync as
|
|
4991
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15, statSync as statSync6 } from "node:fs";
|
|
5040
4992
|
import path5 from "node:path";
|
|
5041
4993
|
function stripSurroundingQuotes(value) {
|
|
5042
4994
|
const t = value.trim();
|
|
@@ -5053,10 +5005,10 @@ function loadEnvFromFile(envFilePath) {
|
|
|
5053
5005
|
return {};
|
|
5054
5006
|
}
|
|
5055
5007
|
const targetPath = path5.resolve(envFilePath);
|
|
5056
|
-
if (!
|
|
5008
|
+
if (!existsSync17(targetPath) || !statSync6(targetPath).isFile()) {
|
|
5057
5009
|
return {};
|
|
5058
5010
|
}
|
|
5059
|
-
const raw =
|
|
5011
|
+
const raw = readFileSync15(targetPath, "utf-8");
|
|
5060
5012
|
const result = {};
|
|
5061
5013
|
for (const line of raw.split(/\r?\n/)) {
|
|
5062
5014
|
const normalized = line.trim();
|
|
@@ -5227,12 +5179,12 @@ function dockerPushImage(params, cwd) {
|
|
|
5227
5179
|
}
|
|
5228
5180
|
|
|
5229
5181
|
// src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
|
|
5230
|
-
import { existsSync as
|
|
5182
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
5231
5183
|
import path6 from "node:path";
|
|
5232
5184
|
function resolveDockerBuildPaths(cwd) {
|
|
5233
5185
|
const dockerfilePath = path6.join(cwd, "Dockerfile");
|
|
5234
5186
|
Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
|
|
5235
|
-
if (!
|
|
5187
|
+
if (!existsSync18(dockerfilePath)) {
|
|
5236
5188
|
throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
|
|
5237
5189
|
}
|
|
5238
5190
|
Logger.info("\u2713 Dockerfile \u5B58\u5728");
|