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