ai-project-manage-cli 6.0.92 → 6.0.94
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 +356 -175
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import { AssumptionPlanSource } from "@opc/shared";
|
|
6
5
|
|
|
7
6
|
// src/config.ts
|
|
8
7
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -2217,6 +2216,7 @@ async function runUpdateMessageStatus(options) {
|
|
|
2217
2216
|
|
|
2218
2217
|
// src/commands/connect.ts
|
|
2219
2218
|
import WebSocket from "ws";
|
|
2219
|
+
import { setTimeout as delay2 } from "node:timers/promises";
|
|
2220
2220
|
|
|
2221
2221
|
// src/ws/protocol.ts
|
|
2222
2222
|
function nonEmptyString(v) {
|
|
@@ -3655,6 +3655,7 @@ function forceReleaseConnectLock() {
|
|
|
3655
3655
|
|
|
3656
3656
|
// src/commands/daemon.ts
|
|
3657
3657
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3658
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3658
3659
|
import { createRequire } from "node:module";
|
|
3659
3660
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync13 } from "fs";
|
|
3660
3661
|
import { join as join15 } from "path";
|
|
@@ -3683,10 +3684,12 @@ function buildConnectPm2Ecosystem(options) {
|
|
|
3683
3684
|
args: options.connectArgs,
|
|
3684
3685
|
autorestart: true,
|
|
3685
3686
|
min_uptime: "10s",
|
|
3686
|
-
max_restarts:
|
|
3687
|
+
max_restarts: 0,
|
|
3687
3688
|
restart_delay: 3e3,
|
|
3688
3689
|
exp_backoff_restart_delay: 1e3,
|
|
3689
3690
|
max_memory_restart: "1G",
|
|
3691
|
+
kill_timeout: 5e3,
|
|
3692
|
+
shutdown_with_message: true,
|
|
3690
3693
|
env
|
|
3691
3694
|
}
|
|
3692
3695
|
]
|
|
@@ -3879,10 +3882,44 @@ async function runDaemonStart(options) {
|
|
|
3879
3882
|
console.log("[apm] \u505C\u6B62: apm daemon stop");
|
|
3880
3883
|
}
|
|
3881
3884
|
async function runDaemonStop() {
|
|
3885
|
+
pruneStaleConnectLock();
|
|
3886
|
+
if (!isPm2ConnectOnline()) {
|
|
3887
|
+
killConnectLockProcessIfAlive();
|
|
3888
|
+
forceReleaseConnectLock();
|
|
3889
|
+
console.log(`[apm] ${PM2_APP_NAME} \u672A\u5728\u8FD0\u884C`);
|
|
3890
|
+
return;
|
|
3891
|
+
}
|
|
3882
3892
|
runPm2(["stop", PM2_APP_NAME], { inherit: true });
|
|
3893
|
+
for (let i = 0; i < 10; i += 1) {
|
|
3894
|
+
if (!isPm2ConnectOnline()) break;
|
|
3895
|
+
await delay(500);
|
|
3896
|
+
}
|
|
3897
|
+
if (isPm2ConnectOnline()) {
|
|
3898
|
+
console.log(`[apm] \u4F18\u96C5\u505C\u6B62\u8D85\u65F6\uFF0C\u5F3A\u5236\u79FB\u9664 ${PM2_APP_NAME}\u2026`);
|
|
3899
|
+
runPm2(["delete", PM2_APP_NAME], { inherit: true });
|
|
3900
|
+
}
|
|
3901
|
+
killConnectLockProcessIfAlive();
|
|
3883
3902
|
forceReleaseConnectLock();
|
|
3884
3903
|
console.log(`[apm] ${PM2_APP_NAME} \u5DF2\u505C\u6B62`);
|
|
3885
3904
|
}
|
|
3905
|
+
function killConnectLockProcessIfAlive() {
|
|
3906
|
+
pruneStaleConnectLock();
|
|
3907
|
+
const lock = readConnectLock();
|
|
3908
|
+
if (!lock || !isProcessAlive(lock.pid)) return;
|
|
3909
|
+
try {
|
|
3910
|
+
process.kill(lock.pid, "SIGTERM");
|
|
3911
|
+
} catch {
|
|
3912
|
+
forceReleaseConnectLock();
|
|
3913
|
+
return;
|
|
3914
|
+
}
|
|
3915
|
+
if (isProcessAlive(lock.pid)) {
|
|
3916
|
+
try {
|
|
3917
|
+
process.kill(lock.pid, "SIGKILL");
|
|
3918
|
+
} catch {
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
forceReleaseConnectLock();
|
|
3922
|
+
}
|
|
3886
3923
|
async function runDaemonRestart(options) {
|
|
3887
3924
|
pruneStaleConnectLock();
|
|
3888
3925
|
const lock = readConnectLock();
|
|
@@ -3917,6 +3954,8 @@ async function runDaemonLogs(options) {
|
|
|
3917
3954
|
|
|
3918
3955
|
// src/commands/connect.ts
|
|
3919
3956
|
var HEARTBEAT_MS = 3e4;
|
|
3957
|
+
var RECONNECT_MIN_MS = 1e3;
|
|
3958
|
+
var RECONNECT_MAX_MS = 6e4;
|
|
3920
3959
|
async function updateMessageStatus(cfg, messageId, status) {
|
|
3921
3960
|
const api = createApmApiClient(cfg);
|
|
3922
3961
|
await api.cli.updateMessageStatus({ id: messageId, status });
|
|
@@ -4069,6 +4108,17 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
|
4069
4108
|
}
|
|
4070
4109
|
}
|
|
4071
4110
|
}
|
|
4111
|
+
function interruptibleSleep(ms, signal) {
|
|
4112
|
+
if (signal.aborted) return Promise.resolve();
|
|
4113
|
+
return new Promise((resolve5) => {
|
|
4114
|
+
const timer = setTimeout(resolve5, ms);
|
|
4115
|
+
const onAbort = () => {
|
|
4116
|
+
clearTimeout(timer);
|
|
4117
|
+
resolve5();
|
|
4118
|
+
};
|
|
4119
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4120
|
+
});
|
|
4121
|
+
}
|
|
4072
4122
|
function startHeartbeat(ws, clientMachineId) {
|
|
4073
4123
|
const send = () => {
|
|
4074
4124
|
if (ws.readyState === WebSocket.OPEN) {
|
|
@@ -4084,182 +4134,274 @@ function startHeartbeat(ws, clientMachineId) {
|
|
|
4084
4134
|
const timer = setInterval(send, HEARTBEAT_MS);
|
|
4085
4135
|
return () => clearInterval(timer);
|
|
4086
4136
|
}
|
|
4087
|
-
|
|
4088
|
-
const {
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
const
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
);
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
try {
|
|
4129
|
-
await Promise.race([
|
|
4130
|
-
Promise.all(activeTasks),
|
|
4131
|
-
new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
|
|
4132
|
-
]);
|
|
4133
|
-
} catch {
|
|
4134
|
-
}
|
|
4135
|
-
releaseConnectLock();
|
|
4136
|
-
resolve5();
|
|
4137
|
-
process.exit(code);
|
|
4138
|
-
};
|
|
4139
|
-
ws.on("open", () => {
|
|
4140
|
-
console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
|
|
4141
|
-
stopHeartbeat = startHeartbeat(ws, clientMachineId);
|
|
4142
|
-
});
|
|
4143
|
-
ws.on("message", (data) => {
|
|
4144
|
-
if (shuttingDown) return;
|
|
4145
|
-
const text = Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
|
|
4146
|
-
const parsed = parseAgentWsMessage(text);
|
|
4147
|
-
if (parsed === null) {
|
|
4148
|
-
console.error("[apm] \u6536\u5230\u65E0\u6548 JSON");
|
|
4149
|
-
return;
|
|
4150
|
-
}
|
|
4151
|
-
if (typeof parsed === "object" && parsed !== null && parsed.type === "heartbeat") {
|
|
4152
|
-
return;
|
|
4153
|
-
}
|
|
4154
|
-
const validated = validateAgentWsMessage(parsed, "outbound");
|
|
4155
|
-
if (!validated.ok) {
|
|
4156
|
-
console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
|
|
4157
|
-
return;
|
|
4158
|
-
}
|
|
4159
|
-
if (validated.data.type === "cancel") {
|
|
4160
|
-
const { messageId } = validated.data;
|
|
4161
|
-
pendingCancels.add(messageId);
|
|
4162
|
-
activeRuns.get(messageId)?.abort();
|
|
4163
|
-
return;
|
|
4164
|
-
}
|
|
4165
|
-
if (validated.data.type === "deploy") {
|
|
4166
|
-
const msg2 = validated.data;
|
|
4167
|
-
const perDeployController = new AbortController();
|
|
4168
|
-
const signal2 = AbortSignal.any([
|
|
4169
|
-
shutdownAbort.signal,
|
|
4170
|
-
perDeployController.signal
|
|
4171
|
-
]);
|
|
4172
|
-
const task2 = (async () => {
|
|
4173
|
-
await runSlots.acquire();
|
|
4174
|
-
try {
|
|
4175
|
-
await handleInboundDeploy(cfg, msg2, signal2);
|
|
4176
|
-
} finally {
|
|
4177
|
-
runSlots.release();
|
|
4178
|
-
}
|
|
4179
|
-
})();
|
|
4180
|
-
activeTasks.add(task2);
|
|
4181
|
-
void task2.finally(() => {
|
|
4182
|
-
activeTasks.delete(task2);
|
|
4183
|
-
});
|
|
4184
|
-
return;
|
|
4185
|
-
}
|
|
4186
|
-
if (validated.data.type !== "message") {
|
|
4187
|
-
return;
|
|
4188
|
-
}
|
|
4189
|
-
const msg = validated.data;
|
|
4190
|
-
const perMessageController = new AbortController();
|
|
4191
|
-
activeRuns.set(msg.messageId, perMessageController);
|
|
4192
|
-
if (pendingCancels.has(msg.messageId)) {
|
|
4193
|
-
activeRuns.delete(msg.messageId);
|
|
4194
|
-
pendingCancels.delete(msg.messageId);
|
|
4195
|
-
return;
|
|
4196
|
-
}
|
|
4197
|
-
const signal = AbortSignal.any([
|
|
4137
|
+
function attachWsHandlers(ws, ctx, onOpen) {
|
|
4138
|
+
const {
|
|
4139
|
+
cfg,
|
|
4140
|
+
clientMachineId,
|
|
4141
|
+
shutdownAbort,
|
|
4142
|
+
runSlots,
|
|
4143
|
+
activeTasks,
|
|
4144
|
+
activeRuns,
|
|
4145
|
+
pendingCancels,
|
|
4146
|
+
isShuttingDown
|
|
4147
|
+
} = ctx;
|
|
4148
|
+
ws.on("open", () => {
|
|
4149
|
+
console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
|
|
4150
|
+
onOpen();
|
|
4151
|
+
});
|
|
4152
|
+
ws.on("message", (data) => {
|
|
4153
|
+
if (isShuttingDown()) return;
|
|
4154
|
+
const text = Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
|
|
4155
|
+
const parsed = parseAgentWsMessage(text);
|
|
4156
|
+
if (parsed === null) {
|
|
4157
|
+
console.error("[apm] \u6536\u5230\u65E0\u6548 JSON");
|
|
4158
|
+
return;
|
|
4159
|
+
}
|
|
4160
|
+
if (typeof parsed === "object" && parsed !== null && parsed.type === "heartbeat") {
|
|
4161
|
+
return;
|
|
4162
|
+
}
|
|
4163
|
+
const validated = validateAgentWsMessage(parsed, "outbound");
|
|
4164
|
+
if (!validated.ok) {
|
|
4165
|
+
console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
|
|
4166
|
+
return;
|
|
4167
|
+
}
|
|
4168
|
+
if (validated.data.type === "cancel") {
|
|
4169
|
+
const { messageId } = validated.data;
|
|
4170
|
+
pendingCancels.add(messageId);
|
|
4171
|
+
activeRuns.get(messageId)?.abort();
|
|
4172
|
+
return;
|
|
4173
|
+
}
|
|
4174
|
+
if (validated.data.type === "deploy") {
|
|
4175
|
+
const msg2 = validated.data;
|
|
4176
|
+
const perDeployController = new AbortController();
|
|
4177
|
+
const signal2 = AbortSignal.any([
|
|
4198
4178
|
shutdownAbort.signal,
|
|
4199
|
-
|
|
4179
|
+
perDeployController.signal
|
|
4200
4180
|
]);
|
|
4201
|
-
const
|
|
4202
|
-
shutdownSignal: shutdownAbort.signal,
|
|
4203
|
-
perMessageSignal: perMessageController.signal
|
|
4204
|
-
};
|
|
4205
|
-
const task = (async () => {
|
|
4181
|
+
const task2 = (async () => {
|
|
4206
4182
|
await runSlots.acquire();
|
|
4207
4183
|
try {
|
|
4208
|
-
|
|
4209
|
-
try {
|
|
4210
|
-
await updateMessageStatus(cfg, msg.messageId, "TYPING");
|
|
4211
|
-
} catch (typingErr) {
|
|
4212
|
-
if (isUserCancelled(ctx)) return;
|
|
4213
|
-
console.error(
|
|
4214
|
-
"[apm] \u66F4\u65B0 TYPING \u72B6\u6001\u5931\u8D25:",
|
|
4215
|
-
typingErr instanceof Error ? typingErr.message : typingErr
|
|
4216
|
-
);
|
|
4217
|
-
try {
|
|
4218
|
-
await setMessageError(
|
|
4219
|
-
cfg,
|
|
4220
|
-
msg.messageId,
|
|
4221
|
-
typingErr instanceof Error ? typingErr.message : String(typingErr)
|
|
4222
|
-
);
|
|
4223
|
-
await updateMessageStatus(cfg, msg.messageId, "FAILED");
|
|
4224
|
-
} catch (statusErr) {
|
|
4225
|
-
console.error(
|
|
4226
|
-
"[apm] \u66F4\u65B0 FAILED \u72B6\u6001\u5931\u8D25:",
|
|
4227
|
-
statusErr instanceof Error ? statusErr.message : statusErr
|
|
4228
|
-
);
|
|
4229
|
-
}
|
|
4230
|
-
return;
|
|
4231
|
-
}
|
|
4232
|
-
await handleInboundMessage(cfg, msg, signal, ctx);
|
|
4184
|
+
await handleInboundDeploy(cfg, msg2, signal2);
|
|
4233
4185
|
} finally {
|
|
4234
4186
|
runSlots.release();
|
|
4235
|
-
activeRuns.delete(msg.messageId);
|
|
4236
|
-
pendingCancels.delete(msg.messageId);
|
|
4237
4187
|
}
|
|
4238
4188
|
})();
|
|
4239
|
-
activeTasks.add(
|
|
4240
|
-
void
|
|
4241
|
-
activeTasks.delete(
|
|
4189
|
+
activeTasks.add(task2);
|
|
4190
|
+
void task2.finally(() => {
|
|
4191
|
+
activeTasks.delete(task2);
|
|
4242
4192
|
});
|
|
4193
|
+
return;
|
|
4194
|
+
}
|
|
4195
|
+
if (validated.data.type !== "message") {
|
|
4196
|
+
return;
|
|
4197
|
+
}
|
|
4198
|
+
const msg = validated.data;
|
|
4199
|
+
const perMessageController = new AbortController();
|
|
4200
|
+
activeRuns.set(msg.messageId, perMessageController);
|
|
4201
|
+
if (pendingCancels.has(msg.messageId)) {
|
|
4202
|
+
activeRuns.delete(msg.messageId);
|
|
4203
|
+
pendingCancels.delete(msg.messageId);
|
|
4204
|
+
return;
|
|
4205
|
+
}
|
|
4206
|
+
const signal = AbortSignal.any([
|
|
4207
|
+
shutdownAbort.signal,
|
|
4208
|
+
perMessageController.signal
|
|
4209
|
+
]);
|
|
4210
|
+
const messageCtx = {
|
|
4211
|
+
shutdownSignal: shutdownAbort.signal,
|
|
4212
|
+
perMessageSignal: perMessageController.signal
|
|
4213
|
+
};
|
|
4214
|
+
const task = (async () => {
|
|
4215
|
+
await runSlots.acquire();
|
|
4216
|
+
try {
|
|
4217
|
+
if (signal.aborted || isUserCancelled(messageCtx)) return;
|
|
4218
|
+
try {
|
|
4219
|
+
await updateMessageStatus(cfg, msg.messageId, "TYPING");
|
|
4220
|
+
} catch (typingErr) {
|
|
4221
|
+
if (isUserCancelled(messageCtx)) return;
|
|
4222
|
+
console.error(
|
|
4223
|
+
"[apm] \u66F4\u65B0 TYPING \u72B6\u6001\u5931\u8D25:",
|
|
4224
|
+
typingErr instanceof Error ? typingErr.message : typingErr
|
|
4225
|
+
);
|
|
4226
|
+
try {
|
|
4227
|
+
await setMessageError(
|
|
4228
|
+
cfg,
|
|
4229
|
+
msg.messageId,
|
|
4230
|
+
typingErr instanceof Error ? typingErr.message : String(typingErr)
|
|
4231
|
+
);
|
|
4232
|
+
await updateMessageStatus(cfg, msg.messageId, "FAILED");
|
|
4233
|
+
} catch (statusErr) {
|
|
4234
|
+
console.error(
|
|
4235
|
+
"[apm] \u66F4\u65B0 FAILED \u72B6\u6001\u5931\u8D25:",
|
|
4236
|
+
statusErr instanceof Error ? statusErr.message : statusErr
|
|
4237
|
+
);
|
|
4238
|
+
}
|
|
4239
|
+
return;
|
|
4240
|
+
}
|
|
4241
|
+
await handleInboundMessage(cfg, msg, signal, messageCtx);
|
|
4242
|
+
} finally {
|
|
4243
|
+
runSlots.release();
|
|
4244
|
+
activeRuns.delete(msg.messageId);
|
|
4245
|
+
pendingCancels.delete(msg.messageId);
|
|
4246
|
+
}
|
|
4247
|
+
})();
|
|
4248
|
+
activeTasks.add(task);
|
|
4249
|
+
void task.finally(() => {
|
|
4250
|
+
activeTasks.delete(task);
|
|
4251
|
+
});
|
|
4252
|
+
});
|
|
4253
|
+
}
|
|
4254
|
+
function connectOnce(url, ctx, connectionAbort, onConnected) {
|
|
4255
|
+
return new Promise((resolve5, reject) => {
|
|
4256
|
+
const ws = new WebSocket(url);
|
|
4257
|
+
let stopHeartbeat;
|
|
4258
|
+
let settled = false;
|
|
4259
|
+
const finish = (fn) => {
|
|
4260
|
+
if (settled) return;
|
|
4261
|
+
settled = true;
|
|
4262
|
+
connectionAbort.removeEventListener("abort", onConnectionAbort);
|
|
4263
|
+
stopHeartbeat?.();
|
|
4264
|
+
fn();
|
|
4265
|
+
};
|
|
4266
|
+
const onConnectionAbort = () => {
|
|
4267
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
4268
|
+
ws.terminate();
|
|
4269
|
+
}
|
|
4270
|
+
};
|
|
4271
|
+
connectionAbort.addEventListener("abort", onConnectionAbort);
|
|
4272
|
+
attachWsHandlers(ws, ctx, () => {
|
|
4273
|
+
stopHeartbeat = startHeartbeat(ws, ctx.clientMachineId);
|
|
4274
|
+
onConnected();
|
|
4243
4275
|
});
|
|
4244
4276
|
ws.on("close", (code, reason) => {
|
|
4245
4277
|
console.log(
|
|
4246
4278
|
`[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
|
|
4247
4279
|
);
|
|
4248
|
-
|
|
4280
|
+
if (ctx.isShuttingDown()) {
|
|
4281
|
+
finish(() => reject(new Error("shutdown")));
|
|
4282
|
+
return;
|
|
4283
|
+
}
|
|
4284
|
+
finish(resolve5);
|
|
4249
4285
|
});
|
|
4250
4286
|
ws.on("error", (err) => {
|
|
4251
4287
|
console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
void shutdown();
|
|
4288
|
+
if (ctx.isShuttingDown()) {
|
|
4289
|
+
finish(() => reject(new Error("shutdown")));
|
|
4290
|
+
return;
|
|
4291
|
+
}
|
|
4292
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
4293
|
+
ws.terminate();
|
|
4294
|
+
}
|
|
4260
4295
|
});
|
|
4261
4296
|
});
|
|
4262
4297
|
}
|
|
4298
|
+
async function runConnect(options) {
|
|
4299
|
+
const { didUpdate } = await runUpdate();
|
|
4300
|
+
if (didUpdate) {
|
|
4301
|
+
await handleConnectAfterUpdate(options);
|
|
4302
|
+
}
|
|
4303
|
+
const cfg = await ensureLoggedConfig();
|
|
4304
|
+
if (options.server?.trim()) {
|
|
4305
|
+
cfg.baseUrl = options.server.trim().replace(/\/+$/, "");
|
|
4306
|
+
}
|
|
4307
|
+
const clientMachineId = resolveClientMachineId(cfg);
|
|
4308
|
+
if (!clientMachineId) {
|
|
4309
|
+
console.error("[apm] config \u7F3A\u5C11 clientMachineId\uFF0C\u8BF7\u91CD\u65B0 apm login");
|
|
4310
|
+
process.exit(1);
|
|
4311
|
+
}
|
|
4312
|
+
assertConnectNotRunning();
|
|
4313
|
+
const lockMode = isRunningUnderPm2() ? "pm2" : "foreground";
|
|
4314
|
+
acquireConnectLock(lockMode);
|
|
4315
|
+
process.on("exit", releaseConnectLock);
|
|
4316
|
+
let shuttingDown = false;
|
|
4317
|
+
let shutdownPromise = null;
|
|
4318
|
+
const lifecycleAbort = new AbortController();
|
|
4319
|
+
const shutdownAbort = new AbortController();
|
|
4320
|
+
const runSlots = createRunSlotPool();
|
|
4321
|
+
const activeTasks = /* @__PURE__ */ new Set();
|
|
4322
|
+
const activeRuns = /* @__PURE__ */ new Map();
|
|
4323
|
+
const pendingCancels = /* @__PURE__ */ new Set();
|
|
4324
|
+
let currentConnectionAbort = null;
|
|
4325
|
+
const sessionCtx = {
|
|
4326
|
+
cfg,
|
|
4327
|
+
clientMachineId,
|
|
4328
|
+
shutdownAbort,
|
|
4329
|
+
runSlots,
|
|
4330
|
+
activeTasks,
|
|
4331
|
+
activeRuns,
|
|
4332
|
+
pendingCancels,
|
|
4333
|
+
isShuttingDown: () => shuttingDown
|
|
4334
|
+
};
|
|
4335
|
+
const drainAndExit = async (code = 0) => {
|
|
4336
|
+
const drainMs = isRunningUnderPm2() ? 500 : SHUTDOWN_DRAIN_MS;
|
|
4337
|
+
lifecycleAbort.abort();
|
|
4338
|
+
currentConnectionAbort?.abort();
|
|
4339
|
+
logAbortSignalStats(shutdownAbort.signal, "connect:shutdown-before-abort");
|
|
4340
|
+
shutdownAbort.abort();
|
|
4341
|
+
logAbortSignalStats(shutdownAbort.signal, "connect:shutdown-after-abort");
|
|
4342
|
+
try {
|
|
4343
|
+
await Promise.race([Promise.all(activeTasks), delay2(drainMs)]);
|
|
4344
|
+
} catch {
|
|
4345
|
+
}
|
|
4346
|
+
releaseConnectLock();
|
|
4347
|
+
process.exit(code);
|
|
4348
|
+
};
|
|
4349
|
+
const shutdown = (code = 0) => {
|
|
4350
|
+
if (shuttingDown) return shutdownPromise ?? Promise.resolve();
|
|
4351
|
+
shuttingDown = true;
|
|
4352
|
+
shutdownPromise = drainAndExit(code);
|
|
4353
|
+
return shutdownPromise;
|
|
4354
|
+
};
|
|
4355
|
+
const onStopSignal = (code = 0) => {
|
|
4356
|
+
console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
|
|
4357
|
+
void shutdown(code);
|
|
4358
|
+
};
|
|
4359
|
+
process.on("SIGINT", () => onStopSignal(0));
|
|
4360
|
+
process.on("SIGTERM", () => onStopSignal(0));
|
|
4361
|
+
if (isRunningUnderPm2()) {
|
|
4362
|
+
process.on("message", (msg) => {
|
|
4363
|
+
if (msg === "shutdown") onStopSignal(0);
|
|
4364
|
+
});
|
|
4365
|
+
}
|
|
4366
|
+
let reconnectDelay = RECONNECT_MIN_MS;
|
|
4367
|
+
while (!shuttingDown) {
|
|
4368
|
+
currentConnectionAbort = new AbortController();
|
|
4369
|
+
const connectionSignal = AbortSignal.any([
|
|
4370
|
+
lifecycleAbort.signal,
|
|
4371
|
+
currentConnectionAbort.signal
|
|
4372
|
+
]);
|
|
4373
|
+
const url = buildAgentWsUrl(cfg.baseUrl, resolveApiKey(cfg));
|
|
4374
|
+
console.log(`[apm] \u8FDE\u63A5 ${cfg.baseUrl} \u2026`);
|
|
4375
|
+
try {
|
|
4376
|
+
await connectOnce(url, sessionCtx, connectionSignal, () => {
|
|
4377
|
+
reconnectDelay = RECONNECT_MIN_MS;
|
|
4378
|
+
});
|
|
4379
|
+
if (shuttingDown) break;
|
|
4380
|
+
console.log(`[apm] ${reconnectDelay}ms \u540E\u5C1D\u8BD5\u91CD\u8FDE\u2026`);
|
|
4381
|
+
await interruptibleSleep(reconnectDelay, lifecycleAbort.signal);
|
|
4382
|
+
if (shuttingDown) break;
|
|
4383
|
+
reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);
|
|
4384
|
+
} catch (err) {
|
|
4385
|
+
if (shuttingDown) break;
|
|
4386
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
4387
|
+
if (detail === "shutdown") break;
|
|
4388
|
+
console.error("[apm] \u8FDE\u63A5\u5931\u8D25:", detail);
|
|
4389
|
+
console.log(`[apm] ${reconnectDelay}ms \u540E\u5C1D\u8BD5\u91CD\u8FDE\u2026`);
|
|
4390
|
+
await interruptibleSleep(reconnectDelay, lifecycleAbort.signal);
|
|
4391
|
+
if (shuttingDown) break;
|
|
4392
|
+
reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);
|
|
4393
|
+
} finally {
|
|
4394
|
+
currentConnectionAbort = null;
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
if (!shuttingDown) {
|
|
4398
|
+
void shutdown(0);
|
|
4399
|
+
return;
|
|
4400
|
+
}
|
|
4401
|
+
if (shutdownPromise) {
|
|
4402
|
+
await shutdownPromise;
|
|
4403
|
+
}
|
|
4404
|
+
}
|
|
4263
4405
|
|
|
4264
4406
|
// src/commands/create-pr-errors.ts
|
|
4265
4407
|
import { ApiError as ApiError2 } from "listpage-http";
|
|
@@ -4789,6 +4931,56 @@ async function zipDirectory(distDir, zipPath) {
|
|
|
4789
4931
|
console.error(`\u5DF2\u751F\u6210: ${zipPath} (${sizeMb} MB)`);
|
|
4790
4932
|
return content.length;
|
|
4791
4933
|
}
|
|
4934
|
+
var SFTP_UPLOAD_MAX_ATTEMPTS = 3;
|
|
4935
|
+
var SFTP_FAST_PUT_OPTIONS = {
|
|
4936
|
+
chunkSize: 64 * 1024,
|
|
4937
|
+
concurrency: 4
|
|
4938
|
+
};
|
|
4939
|
+
function buildSftpConnectOptions(settings) {
|
|
4940
|
+
return {
|
|
4941
|
+
host: settings.host,
|
|
4942
|
+
port: settings.port,
|
|
4943
|
+
username: settings.username,
|
|
4944
|
+
password: settings.password,
|
|
4945
|
+
readyTimeout: 3e4,
|
|
4946
|
+
tryKeyboard: true,
|
|
4947
|
+
keepaliveInterval: 1e4,
|
|
4948
|
+
keepaliveCountMax: 3
|
|
4949
|
+
};
|
|
4950
|
+
}
|
|
4951
|
+
async function sleep(ms) {
|
|
4952
|
+
await new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
4953
|
+
}
|
|
4954
|
+
async function uploadZipWithRetry(settings, localZip, remoteZipPath) {
|
|
4955
|
+
let lastError;
|
|
4956
|
+
for (let attempt = 1; attempt <= SFTP_UPLOAD_MAX_ATTEMPTS; attempt++) {
|
|
4957
|
+
const sftp = new SftpClient();
|
|
4958
|
+
try {
|
|
4959
|
+
if (attempt > 1) {
|
|
4960
|
+
console.error(
|
|
4961
|
+
`SFTP \u4E0A\u4F20\u91CD\u8BD5 (${attempt}/${SFTP_UPLOAD_MAX_ATTEMPTS})...`
|
|
4962
|
+
);
|
|
4963
|
+
}
|
|
4964
|
+
await sftp.connect(buildSftpConnectOptions(settings));
|
|
4965
|
+
await ensureRemoteDir(sftp, settings.remotePath);
|
|
4966
|
+
await sftp.fastPut(localZip, remoteZipPath, SFTP_FAST_PUT_OPTIONS);
|
|
4967
|
+
return sftp;
|
|
4968
|
+
} catch (err) {
|
|
4969
|
+
lastError = err;
|
|
4970
|
+
try {
|
|
4971
|
+
await sftp.end();
|
|
4972
|
+
} catch {
|
|
4973
|
+
}
|
|
4974
|
+
if (attempt < SFTP_UPLOAD_MAX_ATTEMPTS) {
|
|
4975
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4976
|
+
const delaySec = attempt * 2;
|
|
4977
|
+
console.error(`SFTP \u4E0A\u4F20\u5931\u8D25 (${message})\uFF0C${delaySec}s \u540E\u91CD\u8BD5...`);
|
|
4978
|
+
await sleep(delaySec * 1e3);
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
throw lastError;
|
|
4983
|
+
}
|
|
4792
4984
|
async function ensureRemoteDir(sftp, dir) {
|
|
4793
4985
|
const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
4794
4986
|
let current = dir.startsWith("/") ? "" : ".";
|
|
@@ -4848,18 +5040,8 @@ async function uploadAndMaybeExtract(settings, localZip, extract) {
|
|
|
4848
5040
|
console.error(
|
|
4849
5041
|
`\u8FDE\u63A5 ${settings.username}@${settings.host}:${settings.port} ...`
|
|
4850
5042
|
);
|
|
4851
|
-
const sftp =
|
|
5043
|
+
const sftp = await uploadZipWithRetry(settings, localZip, remoteZipPath);
|
|
4852
5044
|
try {
|
|
4853
|
-
await sftp.connect({
|
|
4854
|
-
host: settings.host,
|
|
4855
|
-
port: settings.port,
|
|
4856
|
-
username: settings.username,
|
|
4857
|
-
password: settings.password,
|
|
4858
|
-
readyTimeout: 2e4,
|
|
4859
|
-
tryKeyboard: true
|
|
4860
|
-
});
|
|
4861
|
-
await ensureRemoteDir(sftp, settings.remotePath);
|
|
4862
|
-
await sftp.put(localZip, remoteZipPath);
|
|
4863
5045
|
console.error(` \u2713 ${localZip} -> ${remoteZipPath}`);
|
|
4864
5046
|
if (extract) {
|
|
4865
5047
|
const target = settings.remotePath.replace(/\/$/, "");
|
|
@@ -7006,15 +7188,14 @@ function buildProgram() {
|
|
|
7006
7188
|
).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
|
|
7007
7189
|
await runPull(sessionId);
|
|
7008
7190
|
});
|
|
7009
|
-
program.command("dev-gate").description("\u67E5\u8BE2\u672C\u7AEF\u5F00\u53D1\u95E8\u7981\u72B6\u6001\uFF08\u5047\u8BBE\u786E\u8BA4 + \u9700\u6C42\u7406\u89E3\u786E\u8BA4\uFF09").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption(
|
|
7010
|
-
|
|
7011
|
-
"FRONTEND_PLAN \u6216 BACKEND_PLAN"
|
|
7012
|
-
).action(async (sessionId, opts) => {
|
|
7013
|
-
const source = opts.source === "BACKEND_PLAN" ? AssumptionPlanSource.BACKEND_PLAN : AssumptionPlanSource.FRONTEND_PLAN;
|
|
7191
|
+
program.command("dev-gate").description("\u67E5\u8BE2\u672C\u7AEF\u5F00\u53D1\u95E8\u7981\u72B6\u6001\uFF08\u5047\u8BBE\u786E\u8BA4 + \u9700\u6C42\u7406\u89E3\u786E\u8BA4\uFF09").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption("--source <source>", "FRONTEND_PLAN \u6216 BACKEND_PLAN").action(async (sessionId, opts) => {
|
|
7192
|
+
const source = opts.source === "BACKEND_PLAN" ? "BACKEND_PLAN" /* BACKEND_PLAN */ : "FRONTEND_PLAN" /* FRONTEND_PLAN */;
|
|
7014
7193
|
await runDevGate(sessionId, source);
|
|
7015
7194
|
});
|
|
7016
7195
|
program.command("assumptions").description("\uFF08\u5DF2\u5E9F\u5F03\uFF09\u8BF7\u4F7F\u7528 dev-gate").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async () => {
|
|
7017
|
-
console.error(
|
|
7196
|
+
console.error(
|
|
7197
|
+
"[apm] assumptions \u547D\u4EE4\u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u4F7F\u7528: apm dev-gate <sessionId> --source FRONTEND_PLAN|BACKEND_PLAN"
|
|
7198
|
+
);
|
|
7018
7199
|
process.exit(1);
|
|
7019
7200
|
});
|
|
7020
7201
|
program.command("sync-document").description("\u5C06\u672C\u5730 Markdown \u8986\u76D6\u5F0F upsert \u5230\u5E73\u53F0\u4EFB\u52A1\u6587\u6863").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption(
|