opencode-supertask 0.1.22 → 0.1.24
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/README.md +30 -5
- package/dist/cli/index.js +548 -147
- package/dist/cli/index.js.map +1 -1
- package/dist/gateway/index.js +449 -108
- package/dist/gateway/index.js.map +1 -1
- package/dist/plugin/supertask.js +47 -24
- package/dist/plugin/supertask.js.map +1 -1
- package/dist/web/index.js +383 -36
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -17095,26 +17095,26 @@ var require_CronDate = __commonJS({
|
|
|
17095
17095
|
* @param {CronDate | Date | number | string} [timestamp] - The timestamp to initialize the CronDate with.
|
|
17096
17096
|
* @param {string} [tz] - The timezone to use for the CronDate.
|
|
17097
17097
|
*/
|
|
17098
|
-
constructor(
|
|
17098
|
+
constructor(timestamp2, tz) {
|
|
17099
17099
|
const dateOpts = { zone: tz };
|
|
17100
|
-
if (!
|
|
17100
|
+
if (!timestamp2) {
|
|
17101
17101
|
this.#date = luxon_1.DateTime.local();
|
|
17102
|
-
} else if (
|
|
17103
|
-
this.#date =
|
|
17104
|
-
this.#dstStart =
|
|
17105
|
-
this.#dstEnd =
|
|
17106
|
-
} else if (
|
|
17107
|
-
this.#date = luxon_1.DateTime.fromJSDate(
|
|
17108
|
-
} else if (typeof
|
|
17109
|
-
this.#date = luxon_1.DateTime.fromMillis(
|
|
17102
|
+
} else if (timestamp2 instanceof _CronDate) {
|
|
17103
|
+
this.#date = timestamp2.#date;
|
|
17104
|
+
this.#dstStart = timestamp2.#dstStart;
|
|
17105
|
+
this.#dstEnd = timestamp2.#dstEnd;
|
|
17106
|
+
} else if (timestamp2 instanceof Date) {
|
|
17107
|
+
this.#date = luxon_1.DateTime.fromJSDate(timestamp2, dateOpts);
|
|
17108
|
+
} else if (typeof timestamp2 === "number") {
|
|
17109
|
+
this.#date = luxon_1.DateTime.fromMillis(timestamp2, dateOpts);
|
|
17110
17110
|
} else {
|
|
17111
|
-
this.#date = luxon_1.DateTime.fromISO(
|
|
17112
|
-
this.#date.isValid || (this.#date = luxon_1.DateTime.fromRFC2822(
|
|
17113
|
-
this.#date.isValid || (this.#date = luxon_1.DateTime.fromSQL(
|
|
17114
|
-
this.#date.isValid || (this.#date = luxon_1.DateTime.fromFormat(
|
|
17111
|
+
this.#date = luxon_1.DateTime.fromISO(timestamp2, dateOpts);
|
|
17112
|
+
this.#date.isValid || (this.#date = luxon_1.DateTime.fromRFC2822(timestamp2, dateOpts));
|
|
17113
|
+
this.#date.isValid || (this.#date = luxon_1.DateTime.fromSQL(timestamp2, dateOpts));
|
|
17114
|
+
this.#date.isValid || (this.#date = luxon_1.DateTime.fromFormat(timestamp2, "EEE, d MMM yyyy HH:mm:ss", dateOpts));
|
|
17115
17115
|
}
|
|
17116
17116
|
if (!this.#date.isValid) {
|
|
17117
|
-
throw new Error(`CronDate: unhandled timestamp: ${
|
|
17117
|
+
throw new Error(`CronDate: unhandled timestamp: ${timestamp2}`);
|
|
17118
17118
|
}
|
|
17119
17119
|
if (tz && tz !== this.#date.zoneName) {
|
|
17120
17120
|
this.#date = this.#date.setZone(tz);
|
|
@@ -19172,6 +19172,412 @@ var init_task_template_service = __esm({
|
|
|
19172
19172
|
}
|
|
19173
19173
|
});
|
|
19174
19174
|
|
|
19175
|
+
// src/core/process-control.ts
|
|
19176
|
+
import { spawnSync } from "child_process";
|
|
19177
|
+
import { basename } from "path";
|
|
19178
|
+
function isSafePid(pid) {
|
|
19179
|
+
return Number.isInteger(pid) && pid > 1 && pid !== process.pid;
|
|
19180
|
+
}
|
|
19181
|
+
function isProcessAlive(pid) {
|
|
19182
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
19183
|
+
try {
|
|
19184
|
+
process.kill(pid, 0);
|
|
19185
|
+
return true;
|
|
19186
|
+
} catch (error) {
|
|
19187
|
+
return error instanceof Error && "code" in error && error.code === "EPERM";
|
|
19188
|
+
}
|
|
19189
|
+
}
|
|
19190
|
+
function inspectUnixProcess(pid) {
|
|
19191
|
+
const result = spawnSync("ps", ["-o", "pgid=", "-o", "command=", "-p", String(pid)], {
|
|
19192
|
+
encoding: "utf8",
|
|
19193
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
19194
|
+
});
|
|
19195
|
+
if (result.status !== 0) return null;
|
|
19196
|
+
const match2 = result.stdout.trim().match(/^(\d+)\s+(.+)$/s);
|
|
19197
|
+
if (!match2) return null;
|
|
19198
|
+
return { processGroupId: Number(match2[1]), command: match2[2] };
|
|
19199
|
+
}
|
|
19200
|
+
function commandMatches(command, expectedExecutable) {
|
|
19201
|
+
const expectedName = basename(expectedExecutable).trim().toLowerCase();
|
|
19202
|
+
if (!expectedName) return false;
|
|
19203
|
+
return command.toLowerCase().includes(expectedName);
|
|
19204
|
+
}
|
|
19205
|
+
function signalSpawnedProcessTree(pid, signal) {
|
|
19206
|
+
if (!isSafePid(pid)) return false;
|
|
19207
|
+
if (process.platform !== "win32") {
|
|
19208
|
+
try {
|
|
19209
|
+
process.kill(-pid, signal);
|
|
19210
|
+
return true;
|
|
19211
|
+
} catch {
|
|
19212
|
+
}
|
|
19213
|
+
}
|
|
19214
|
+
try {
|
|
19215
|
+
process.kill(pid, signal);
|
|
19216
|
+
return true;
|
|
19217
|
+
} catch {
|
|
19218
|
+
return false;
|
|
19219
|
+
}
|
|
19220
|
+
}
|
|
19221
|
+
function signalRecordedProcessTree(pid, signal, expectedExecutable) {
|
|
19222
|
+
if (!isSafePid(pid)) return false;
|
|
19223
|
+
if (process.platform === "win32") {
|
|
19224
|
+
const list = spawnSync("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
|
|
19225
|
+
encoding: "utf8",
|
|
19226
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
19227
|
+
});
|
|
19228
|
+
if (list.status !== 0 || !commandMatches(list.stdout, expectedExecutable)) return false;
|
|
19229
|
+
const args = ["/PID", String(pid), "/T"];
|
|
19230
|
+
if (signal === "SIGKILL") args.push("/F");
|
|
19231
|
+
return spawnSync("taskkill", args, { stdio: "ignore" }).status === 0;
|
|
19232
|
+
}
|
|
19233
|
+
const info = inspectUnixProcess(pid);
|
|
19234
|
+
if (!info || !commandMatches(info.command, expectedExecutable)) return false;
|
|
19235
|
+
try {
|
|
19236
|
+
if (info.processGroupId === pid) process.kill(-pid, signal);
|
|
19237
|
+
else process.kill(pid, signal);
|
|
19238
|
+
return true;
|
|
19239
|
+
} catch {
|
|
19240
|
+
return false;
|
|
19241
|
+
}
|
|
19242
|
+
}
|
|
19243
|
+
var init_process_control = __esm({
|
|
19244
|
+
"src/core/process-control.ts"() {
|
|
19245
|
+
"use strict";
|
|
19246
|
+
}
|
|
19247
|
+
});
|
|
19248
|
+
|
|
19249
|
+
// src/core/services/database-maintenance.service.ts
|
|
19250
|
+
import { Database as Database3 } from "bun:sqlite";
|
|
19251
|
+
import { randomUUID } from "crypto";
|
|
19252
|
+
import {
|
|
19253
|
+
chmodSync,
|
|
19254
|
+
constants,
|
|
19255
|
+
copyFileSync,
|
|
19256
|
+
existsSync as existsSync2,
|
|
19257
|
+
mkdirSync as mkdirSync2,
|
|
19258
|
+
renameSync,
|
|
19259
|
+
statSync,
|
|
19260
|
+
unlinkSync,
|
|
19261
|
+
writeFileSync
|
|
19262
|
+
} from "fs";
|
|
19263
|
+
import { tmpdir } from "os";
|
|
19264
|
+
import { basename as basename2, dirname as dirname2, resolve } from "path";
|
|
19265
|
+
function timestamp() {
|
|
19266
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
19267
|
+
}
|
|
19268
|
+
function normalizedPath(path) {
|
|
19269
|
+
return path === ":memory:" ? path : resolve(path);
|
|
19270
|
+
}
|
|
19271
|
+
function safeUnlink(path) {
|
|
19272
|
+
try {
|
|
19273
|
+
unlinkSync(path);
|
|
19274
|
+
} catch (error) {
|
|
19275
|
+
const code = error instanceof Error && "code" in error ? error.code : void 0;
|
|
19276
|
+
if (code !== "ENOENT") throw error;
|
|
19277
|
+
}
|
|
19278
|
+
}
|
|
19279
|
+
function errorMessage(error) {
|
|
19280
|
+
return error instanceof Error ? error.message : String(error);
|
|
19281
|
+
}
|
|
19282
|
+
var REQUIRED_TABLES, DatabaseMaintenanceConflictError, DatabaseMaintenanceService;
|
|
19283
|
+
var init_database_maintenance_service = __esm({
|
|
19284
|
+
"src/core/services/database-maintenance.service.ts"() {
|
|
19285
|
+
"use strict";
|
|
19286
|
+
init_db2();
|
|
19287
|
+
init_process_control();
|
|
19288
|
+
REQUIRED_TABLES = ["gateway_lock", "tasks", "task_runs", "task_templates"];
|
|
19289
|
+
DatabaseMaintenanceConflictError = class extends Error {
|
|
19290
|
+
constructor(message) {
|
|
19291
|
+
super(message);
|
|
19292
|
+
this.name = "DatabaseMaintenanceConflictError";
|
|
19293
|
+
}
|
|
19294
|
+
};
|
|
19295
|
+
DatabaseMaintenanceService = class {
|
|
19296
|
+
static check() {
|
|
19297
|
+
return this.inspect(getSqlite(), DB_FILE_PATH);
|
|
19298
|
+
}
|
|
19299
|
+
static backup(outputPath) {
|
|
19300
|
+
const sqlite2 = getSqlite();
|
|
19301
|
+
try {
|
|
19302
|
+
sqlite2.query("PRAGMA wal_checkpoint(PASSIVE)").get();
|
|
19303
|
+
} catch {
|
|
19304
|
+
}
|
|
19305
|
+
return this.writeSnapshot(sqlite2, outputPath ?? this.createBackupPath("backup"));
|
|
19306
|
+
}
|
|
19307
|
+
static clear(options = {}) {
|
|
19308
|
+
const sqlite2 = getSqlite();
|
|
19309
|
+
this.assertGatewaySafe(sqlite2, options.allowCurrentGateway ?? false);
|
|
19310
|
+
this.assertNoRunningWork(sqlite2);
|
|
19311
|
+
sqlite2.exec("BEGIN IMMEDIATE");
|
|
19312
|
+
let backup = null;
|
|
19313
|
+
try {
|
|
19314
|
+
this.assertGatewaySafe(sqlite2, options.allowCurrentGateway ?? false);
|
|
19315
|
+
this.assertNoRunningWork(sqlite2);
|
|
19316
|
+
const before = this.readCounts(sqlite2);
|
|
19317
|
+
backup = this.writeSnapshot(sqlite2, this.createBackupPath("pre-clear"));
|
|
19318
|
+
sqlite2.exec("DELETE FROM task_runs");
|
|
19319
|
+
sqlite2.exec("DELETE FROM tasks");
|
|
19320
|
+
sqlite2.exec("DELETE FROM task_templates");
|
|
19321
|
+
sqlite2.exec("COMMIT");
|
|
19322
|
+
return {
|
|
19323
|
+
backupPath: backup.path,
|
|
19324
|
+
deleted: before,
|
|
19325
|
+
check: this.inspect(sqlite2, DB_FILE_PATH)
|
|
19326
|
+
};
|
|
19327
|
+
} catch (error) {
|
|
19328
|
+
if (sqlite2.inTransaction) sqlite2.exec("ROLLBACK");
|
|
19329
|
+
if (error instanceof DatabaseMaintenanceConflictError) throw error;
|
|
19330
|
+
const backupHint = backup ? `\uFF1B\u4E8B\u52A1\u5DF2\u56DE\u6EDA\uFF0C\u5907\u4EFD\u4FDD\u7559\u5728 ${backup.path}` : "";
|
|
19331
|
+
throw new Error(`\u6E05\u7A7A\u6570\u636E\u5E93\u5931\u8D25${backupHint}\uFF1A${errorMessage(error)}`);
|
|
19332
|
+
}
|
|
19333
|
+
}
|
|
19334
|
+
static restore(sourcePath) {
|
|
19335
|
+
if (DB_FILE_PATH === ":memory:") {
|
|
19336
|
+
throw new Error("\u5185\u5B58\u6570\u636E\u5E93\u4E0D\u652F\u6301 restore");
|
|
19337
|
+
}
|
|
19338
|
+
const source = normalizedPath(sourcePath);
|
|
19339
|
+
const livePath = normalizedPath(DB_FILE_PATH);
|
|
19340
|
+
if (source === livePath) throw new Error("\u6062\u590D\u6765\u6E90\u4E0D\u80FD\u662F\u5F53\u524D\u6570\u636E\u5E93\u6587\u4EF6");
|
|
19341
|
+
const current = getSqlite();
|
|
19342
|
+
this.assertGatewaySafe(current, false);
|
|
19343
|
+
this.assertNoRunningWork(current);
|
|
19344
|
+
if (!existsSync2(source) || !statSync(source).isFile()) {
|
|
19345
|
+
throw new Error(`\u5907\u4EFD\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${source}`);
|
|
19346
|
+
}
|
|
19347
|
+
let sourceCheck;
|
|
19348
|
+
try {
|
|
19349
|
+
sourceCheck = this.inspectFile(source);
|
|
19350
|
+
} catch (error) {
|
|
19351
|
+
throw new Error(`\u5907\u4EFD\u6587\u4EF6\u65E0\u6548\uFF1A${errorMessage(error)}`);
|
|
19352
|
+
}
|
|
19353
|
+
if (!sourceCheck.ok) {
|
|
19354
|
+
throw new Error(`\u5907\u4EFD\u6587\u4EF6\u6821\u9A8C\u5931\u8D25\uFF1A${sourceCheck.integrityMessages.join("; ") || "schema/foreign key error"}`);
|
|
19355
|
+
}
|
|
19356
|
+
const stagePath = `${livePath}.restore-${process.pid}-${randomUUID()}.tmp`;
|
|
19357
|
+
const rollbackPath = `${livePath}.rollback-${process.pid}-${randomUUID()}.tmp`;
|
|
19358
|
+
let safetyBackup = null;
|
|
19359
|
+
let liveMoved = false;
|
|
19360
|
+
try {
|
|
19361
|
+
copyFileSync(source, stagePath, constants.COPYFILE_EXCL);
|
|
19362
|
+
chmodSync(stagePath, 384);
|
|
19363
|
+
const staged = new Database3(stagePath);
|
|
19364
|
+
try {
|
|
19365
|
+
const stagedCheck = this.inspect(staged, stagePath);
|
|
19366
|
+
if (!stagedCheck.ok) throw new Error("\u6682\u5B58\u6062\u590D\u6587\u4EF6\u6821\u9A8C\u5931\u8D25");
|
|
19367
|
+
const now = Date.now();
|
|
19368
|
+
staged.query(
|
|
19369
|
+
"INSERT OR REPLACE INTO gateway_lock (id, pid, acquired_at, heartbeat_at, ready_at) VALUES (1, ?, ?, ?, NULL)"
|
|
19370
|
+
).run(process.pid, now, now);
|
|
19371
|
+
} finally {
|
|
19372
|
+
staged.close();
|
|
19373
|
+
}
|
|
19374
|
+
const sqlite2 = getSqlite();
|
|
19375
|
+
sqlite2.exec("BEGIN IMMEDIATE");
|
|
19376
|
+
try {
|
|
19377
|
+
this.assertGatewaySafe(sqlite2, false);
|
|
19378
|
+
this.assertNoRunningWork(sqlite2);
|
|
19379
|
+
safetyBackup = this.writeSnapshot(sqlite2, this.createBackupPath("pre-restore"));
|
|
19380
|
+
const now = Date.now();
|
|
19381
|
+
sqlite2.query(
|
|
19382
|
+
"INSERT OR REPLACE INTO gateway_lock (id, pid, acquired_at, heartbeat_at, ready_at) VALUES (1, ?, ?, ?, NULL)"
|
|
19383
|
+
).run(process.pid, now, now);
|
|
19384
|
+
sqlite2.exec("COMMIT");
|
|
19385
|
+
} catch (error) {
|
|
19386
|
+
if (sqlite2.inTransaction) sqlite2.exec("ROLLBACK");
|
|
19387
|
+
throw error;
|
|
19388
|
+
}
|
|
19389
|
+
const checkpoint = sqlite2.query("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
19390
|
+
if (checkpoint && checkpoint.busy !== 0) {
|
|
19391
|
+
throw new DatabaseMaintenanceConflictError("\u6570\u636E\u5E93\u4ECD\u88AB\u5176\u4ED6\u8FDE\u63A5\u5360\u7528\uFF0C\u65E0\u6CD5\u5B89\u5168\u6062\u590D");
|
|
19392
|
+
}
|
|
19393
|
+
closeDb();
|
|
19394
|
+
safeUnlink(`${livePath}-wal`);
|
|
19395
|
+
safeUnlink(`${livePath}-shm`);
|
|
19396
|
+
renameSync(livePath, rollbackPath);
|
|
19397
|
+
liveMoved = true;
|
|
19398
|
+
renameSync(stagePath, livePath);
|
|
19399
|
+
const restored = getSqlite();
|
|
19400
|
+
restored.exec("BEGIN IMMEDIATE");
|
|
19401
|
+
let recoveredRunningTasks = 0;
|
|
19402
|
+
let closedRunningRuns = 0;
|
|
19403
|
+
try {
|
|
19404
|
+
recoveredRunningTasks = this.scalar(
|
|
19405
|
+
restored,
|
|
19406
|
+
"SELECT COUNT(*) AS count FROM tasks WHERE status = 'running'"
|
|
19407
|
+
);
|
|
19408
|
+
closedRunningRuns = this.scalar(
|
|
19409
|
+
restored,
|
|
19410
|
+
"SELECT COUNT(*) AS count FROM task_runs WHERE status = 'running'"
|
|
19411
|
+
);
|
|
19412
|
+
const finishedAt = Math.floor(Date.now() / 1e3);
|
|
19413
|
+
restored.query(`
|
|
19414
|
+
UPDATE task_runs
|
|
19415
|
+
SET status = 'failed', finished_at = ?,
|
|
19416
|
+
log = CASE
|
|
19417
|
+
WHEN log IS NULL OR log = '' THEN '\u6570\u636E\u5E93\u6062\u590D\u65F6\u5173\u95ED\u9057\u7559\u8FD0\u884C\u8BB0\u5F55'
|
|
19418
|
+
ELSE log || '
|
|
19419
|
+
\u6570\u636E\u5E93\u6062\u590D\u65F6\u5173\u95ED\u9057\u7559\u8FD0\u884C\u8BB0\u5F55'
|
|
19420
|
+
END
|
|
19421
|
+
WHERE status = 'running'
|
|
19422
|
+
`).run(finishedAt);
|
|
19423
|
+
restored.exec(`
|
|
19424
|
+
UPDATE tasks
|
|
19425
|
+
SET status = 'pending', started_at = NULL, finished_at = NULL
|
|
19426
|
+
WHERE status = 'running'
|
|
19427
|
+
`);
|
|
19428
|
+
restored.query("DELETE FROM gateway_lock WHERE pid = ?").run(process.pid);
|
|
19429
|
+
restored.exec("COMMIT");
|
|
19430
|
+
} catch (error) {
|
|
19431
|
+
if (restored.inTransaction) restored.exec("ROLLBACK");
|
|
19432
|
+
throw error;
|
|
19433
|
+
}
|
|
19434
|
+
const check = this.inspect(restored, livePath);
|
|
19435
|
+
if (!check.ok) throw new Error("\u6062\u590D\u540E\u7684\u6570\u636E\u5E93\u672A\u901A\u8FC7\u5B8C\u6574\u6027\u6821\u9A8C");
|
|
19436
|
+
safeUnlink(rollbackPath);
|
|
19437
|
+
return {
|
|
19438
|
+
sourcePath: source,
|
|
19439
|
+
safetyBackupPath: safetyBackup.path,
|
|
19440
|
+
recoveredRunningTasks,
|
|
19441
|
+
closedRunningRuns,
|
|
19442
|
+
check
|
|
19443
|
+
};
|
|
19444
|
+
} catch (error) {
|
|
19445
|
+
closeDb();
|
|
19446
|
+
safeUnlink(`${livePath}-wal`);
|
|
19447
|
+
safeUnlink(`${livePath}-shm`);
|
|
19448
|
+
safeUnlink(stagePath);
|
|
19449
|
+
safeUnlink(`${stagePath}-journal`);
|
|
19450
|
+
safeUnlink(`${stagePath}-wal`);
|
|
19451
|
+
safeUnlink(`${stagePath}-shm`);
|
|
19452
|
+
if (liveMoved && existsSync2(rollbackPath)) {
|
|
19453
|
+
safeUnlink(livePath);
|
|
19454
|
+
renameSync(rollbackPath, livePath);
|
|
19455
|
+
try {
|
|
19456
|
+
const original = getSqlite();
|
|
19457
|
+
original.query("DELETE FROM gateway_lock WHERE pid = ?").run(process.pid);
|
|
19458
|
+
} catch {
|
|
19459
|
+
}
|
|
19460
|
+
} else if (existsSync2(rollbackPath)) {
|
|
19461
|
+
safeUnlink(rollbackPath);
|
|
19462
|
+
} else {
|
|
19463
|
+
try {
|
|
19464
|
+
getSqlite().query("DELETE FROM gateway_lock WHERE pid = ?").run(process.pid);
|
|
19465
|
+
} catch {
|
|
19466
|
+
}
|
|
19467
|
+
}
|
|
19468
|
+
const backupHint = safetyBackup ? `\uFF1B\u5F53\u524D\u5E93\u5B89\u5168\u5907\u4EFD\uFF1A${safetyBackup.path}` : "";
|
|
19469
|
+
throw new Error(`\u6062\u590D\u6570\u636E\u5E93\u5931\u8D25\uFF0C\u5DF2\u5C1D\u8BD5\u56DE\u6EDA${backupHint}\uFF1A${errorMessage(error)}`);
|
|
19470
|
+
}
|
|
19471
|
+
}
|
|
19472
|
+
static inspectFile(path) {
|
|
19473
|
+
let sqlite2;
|
|
19474
|
+
try {
|
|
19475
|
+
sqlite2 = new Database3(path, { readonly: true, strict: true });
|
|
19476
|
+
} catch (error) {
|
|
19477
|
+
throw new Error(`\u65E0\u6CD5\u6253\u5F00\u5907\u4EFD\u6587\u4EF6\uFF1A${errorMessage(error)}`);
|
|
19478
|
+
}
|
|
19479
|
+
try {
|
|
19480
|
+
return this.inspect(sqlite2, path);
|
|
19481
|
+
} finally {
|
|
19482
|
+
sqlite2.close();
|
|
19483
|
+
}
|
|
19484
|
+
}
|
|
19485
|
+
static inspect(sqlite2, path) {
|
|
19486
|
+
const tableRows = sqlite2.query(
|
|
19487
|
+
"SELECT name FROM sqlite_master WHERE type = 'table'"
|
|
19488
|
+
).all();
|
|
19489
|
+
const tableNames = new Set(tableRows.map((row) => row.name));
|
|
19490
|
+
const missingTables = REQUIRED_TABLES.filter((table) => !tableNames.has(table));
|
|
19491
|
+
const integrityRows = sqlite2.query("PRAGMA integrity_check").all();
|
|
19492
|
+
const integrityMessages = integrityRows.map((row) => row.integrity_check);
|
|
19493
|
+
const foreignKeyViolations = sqlite2.query("PRAGMA foreign_key_check").all().length;
|
|
19494
|
+
const journal = sqlite2.query("PRAGMA journal_mode").get();
|
|
19495
|
+
const counts = missingTables.length === 0 ? this.readCounts(sqlite2) : { tasks: 0, taskRuns: 0, taskTemplates: 0 };
|
|
19496
|
+
const runningTasks = missingTables.includes("tasks") ? 0 : this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM tasks WHERE status = 'running'");
|
|
19497
|
+
const runningRuns = missingTables.includes("task_runs") ? 0 : this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM task_runs WHERE status = 'running'");
|
|
19498
|
+
const sizeBytes = path === ":memory:" || !existsSync2(path) ? sqlite2.serialize().byteLength : statSync(path).size;
|
|
19499
|
+
return {
|
|
19500
|
+
ok: integrityMessages.length === 1 && integrityMessages[0] === "ok" && foreignKeyViolations === 0 && missingTables.length === 0,
|
|
19501
|
+
path: normalizedPath(path),
|
|
19502
|
+
sizeBytes,
|
|
19503
|
+
journalMode: journal?.journal_mode ?? "unknown",
|
|
19504
|
+
integrityMessages,
|
|
19505
|
+
foreignKeyViolations,
|
|
19506
|
+
missingTables,
|
|
19507
|
+
counts,
|
|
19508
|
+
runningTasks,
|
|
19509
|
+
runningRuns
|
|
19510
|
+
};
|
|
19511
|
+
}
|
|
19512
|
+
static writeSnapshot(sqlite2, outputPath) {
|
|
19513
|
+
const liveCheck = this.inspect(sqlite2, DB_FILE_PATH);
|
|
19514
|
+
if (!liveCheck.ok) throw new Error("\u5F53\u524D\u6570\u636E\u5E93\u672A\u901A\u8FC7\u5B8C\u6574\u6027\u6821\u9A8C\uFF0C\u5DF2\u62D2\u7EDD\u521B\u5EFA\u5907\u4EFD");
|
|
19515
|
+
const output = normalizedPath(outputPath);
|
|
19516
|
+
if (DB_FILE_PATH !== ":memory:" && output === normalizedPath(DB_FILE_PATH)) {
|
|
19517
|
+
throw new Error("\u5907\u4EFD\u8DEF\u5F84\u4E0D\u80FD\u8986\u76D6\u5F53\u524D\u6570\u636E\u5E93");
|
|
19518
|
+
}
|
|
19519
|
+
if (existsSync2(output)) throw new Error(`\u5907\u4EFD\u6587\u4EF6\u5DF2\u5B58\u5728\uFF1A${output}`);
|
|
19520
|
+
mkdirSync2(dirname2(output), { recursive: true });
|
|
19521
|
+
const temporary = `${output}.tmp-${process.pid}-${randomUUID()}`;
|
|
19522
|
+
try {
|
|
19523
|
+
writeFileSync(temporary, sqlite2.serialize(), { flag: "wx", mode: 384 });
|
|
19524
|
+
const standalone = new Database3(temporary, { readwrite: true, create: false });
|
|
19525
|
+
try {
|
|
19526
|
+
standalone.query("PRAGMA journal_mode = DELETE").get();
|
|
19527
|
+
} finally {
|
|
19528
|
+
standalone.close();
|
|
19529
|
+
}
|
|
19530
|
+
safeUnlink(`${temporary}-wal`);
|
|
19531
|
+
safeUnlink(`${temporary}-shm`);
|
|
19532
|
+
const check = this.inspectFile(temporary);
|
|
19533
|
+
if (!check.ok) throw new Error("\u65B0\u5907\u4EFD\u672A\u901A\u8FC7\u5B8C\u6574\u6027\u6821\u9A8C");
|
|
19534
|
+
renameSync(temporary, output);
|
|
19535
|
+
const finalCheck = { ...check, path: output, sizeBytes: statSync(output).size };
|
|
19536
|
+
return { path: output, sizeBytes: finalCheck.sizeBytes, check: finalCheck };
|
|
19537
|
+
} catch (error) {
|
|
19538
|
+
safeUnlink(temporary);
|
|
19539
|
+
safeUnlink(`${temporary}-wal`);
|
|
19540
|
+
safeUnlink(`${temporary}-shm`);
|
|
19541
|
+
throw error;
|
|
19542
|
+
}
|
|
19543
|
+
}
|
|
19544
|
+
static createBackupPath(kind) {
|
|
19545
|
+
const directory = DB_FILE_PATH === ":memory:" ? tmpdir() : dirname2(normalizedPath(DB_FILE_PATH));
|
|
19546
|
+
const base = DB_FILE_PATH === ":memory:" ? "supertask-memory" : basename2(DB_FILE_PATH, ".db");
|
|
19547
|
+
return resolve(directory, `${base}.${kind}-${timestamp()}-${randomUUID().slice(0, 8)}.db`);
|
|
19548
|
+
}
|
|
19549
|
+
static readCounts(sqlite2) {
|
|
19550
|
+
return {
|
|
19551
|
+
tasks: this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM tasks"),
|
|
19552
|
+
taskRuns: this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM task_runs"),
|
|
19553
|
+
taskTemplates: this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM task_templates")
|
|
19554
|
+
};
|
|
19555
|
+
}
|
|
19556
|
+
static scalar(sqlite2, statement) {
|
|
19557
|
+
const row = sqlite2.query(statement).get();
|
|
19558
|
+
return Number(row?.count ?? 0);
|
|
19559
|
+
}
|
|
19560
|
+
static assertGatewaySafe(sqlite2, allowCurrentGateway) {
|
|
19561
|
+
const lock = sqlite2.query("SELECT pid FROM gateway_lock WHERE id = 1").get();
|
|
19562
|
+
if (!lock || !isProcessAlive(lock.pid)) return;
|
|
19563
|
+
if (allowCurrentGateway && lock.pid === process.pid) return;
|
|
19564
|
+
throw new DatabaseMaintenanceConflictError(
|
|
19565
|
+
`Gateway PID ${lock.pid} \u4ECD\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u6267\u884C pm2 stop supertask-gateway`
|
|
19566
|
+
);
|
|
19567
|
+
}
|
|
19568
|
+
static assertNoRunningWork(sqlite2) {
|
|
19569
|
+
const runningTasks = this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM tasks WHERE status = 'running'");
|
|
19570
|
+
const runningRuns = this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM task_runs WHERE status = 'running'");
|
|
19571
|
+
if (runningTasks > 0 || runningRuns > 0) {
|
|
19572
|
+
throw new DatabaseMaintenanceConflictError(
|
|
19573
|
+
`\u5B58\u5728\u8FD0\u884C\u4E2D\u4EFB\u52A1\uFF08tasks=${runningTasks}, task_runs=${runningRuns}\uFF09\uFF0C\u5DF2\u62D2\u7EDD\u5371\u9669\u64CD\u4F5C`
|
|
19574
|
+
);
|
|
19575
|
+
}
|
|
19576
|
+
}
|
|
19577
|
+
};
|
|
19578
|
+
}
|
|
19579
|
+
});
|
|
19580
|
+
|
|
19175
19581
|
// src/gateway/config.ts
|
|
19176
19582
|
var config_exports = {};
|
|
19177
19583
|
__export(config_exports, {
|
|
@@ -19181,7 +19587,7 @@ __export(config_exports, {
|
|
|
19181
19587
|
loadConfig: () => loadConfig,
|
|
19182
19588
|
validateConfig: () => validateConfig
|
|
19183
19589
|
});
|
|
19184
|
-
import { existsSync as
|
|
19590
|
+
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
19185
19591
|
import { homedir as homedir2 } from "os";
|
|
19186
19592
|
import { join as join2 } from "path";
|
|
19187
19593
|
function getConfigPath() {
|
|
@@ -19261,7 +19667,7 @@ function validateConfig(input) {
|
|
|
19261
19667
|
};
|
|
19262
19668
|
}
|
|
19263
19669
|
function loadConfig(path = getConfigPath()) {
|
|
19264
|
-
if (!
|
|
19670
|
+
if (!existsSync3(path)) return validateConfig({ configVersion: 2 });
|
|
19265
19671
|
try {
|
|
19266
19672
|
return validateConfig(JSON.parse(readFileSync(path, "utf-8")));
|
|
19267
19673
|
} catch (error) {
|
|
@@ -19315,12 +19721,12 @@ __export(pm2_exports, {
|
|
|
19315
19721
|
uninstall: () => uninstall,
|
|
19316
19722
|
upgrade: () => upgrade
|
|
19317
19723
|
});
|
|
19318
|
-
import { execSync, spawnSync } from "child_process";
|
|
19319
|
-
import { chmodSync, existsSync as
|
|
19724
|
+
import { execSync, spawnSync as spawnSync2 } from "child_process";
|
|
19725
|
+
import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
19320
19726
|
import { homedir as homedir3 } from "os";
|
|
19321
|
-
import { dirname as
|
|
19727
|
+
import { dirname as dirname3, isAbsolute, join as join3 } from "path";
|
|
19322
19728
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
19323
|
-
import { Database as
|
|
19729
|
+
import { Database as Database4 } from "bun:sqlite";
|
|
19324
19730
|
function getPackageVersion() {
|
|
19325
19731
|
const envVersion = process.env.npm_package_version;
|
|
19326
19732
|
if (envVersion) return envVersion;
|
|
@@ -19335,14 +19741,14 @@ function getPackageVersion() {
|
|
|
19335
19741
|
function resolveGatewayEntry() {
|
|
19336
19742
|
const override = process.env.SUPERTASK_GATEWAY_ENTRY;
|
|
19337
19743
|
if (override) {
|
|
19338
|
-
if (!
|
|
19744
|
+
if (!existsSync4(override)) throw new Error(`[supertask] Gateway entry not found: ${override}`);
|
|
19339
19745
|
return override;
|
|
19340
19746
|
}
|
|
19341
19747
|
const candidates = [
|
|
19342
19748
|
join3(__dirname, "../gateway/index.js"),
|
|
19343
19749
|
join3(__dirname, "../gateway/index.ts")
|
|
19344
19750
|
];
|
|
19345
|
-
const entry = candidates.find((candidate) =>
|
|
19751
|
+
const entry = candidates.find((candidate) => existsSync4(candidate));
|
|
19346
19752
|
if (!entry) throw new Error(`[supertask] Gateway entry not found. Checked: ${candidates.join(", ")}`);
|
|
19347
19753
|
return entry;
|
|
19348
19754
|
}
|
|
@@ -19352,7 +19758,7 @@ function versionFile() {
|
|
|
19352
19758
|
function getRunningVersion() {
|
|
19353
19759
|
try {
|
|
19354
19760
|
const path = versionFile();
|
|
19355
|
-
if (!
|
|
19761
|
+
if (!existsSync4(path)) return null;
|
|
19356
19762
|
return readFileSync2(path, "utf-8").trim() || null;
|
|
19357
19763
|
} catch {
|
|
19358
19764
|
return null;
|
|
@@ -19360,8 +19766,8 @@ function getRunningVersion() {
|
|
|
19360
19766
|
}
|
|
19361
19767
|
function writeRunningVersion(version2) {
|
|
19362
19768
|
const path = versionFile();
|
|
19363
|
-
|
|
19364
|
-
|
|
19769
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
19770
|
+
writeFileSync2(path, version2, "utf-8");
|
|
19365
19771
|
}
|
|
19366
19772
|
function pm2Bin() {
|
|
19367
19773
|
return process.env.SUPERTASK_PM2_BIN ?? (process.platform === "win32" ? "pm2.cmd" : "pm2");
|
|
@@ -19372,7 +19778,7 @@ function xmlEscape(value) {
|
|
|
19372
19778
|
function resolvePm2Bin() {
|
|
19373
19779
|
const configured = pm2Bin();
|
|
19374
19780
|
if (isAbsolute(configured)) return configured;
|
|
19375
|
-
const result =
|
|
19781
|
+
const result = spawnSync2("which", [configured], { encoding: "utf8" });
|
|
19376
19782
|
const resolved = result.status === 0 ? result.stdout.trim().split("\n")[0] : "";
|
|
19377
19783
|
if (!resolved) throw new Error(`[supertask] \u65E0\u6CD5\u89E3\u6790 pm2 \u53EF\u6267\u884C\u6587\u4EF6: ${configured}`);
|
|
19378
19784
|
return resolved;
|
|
@@ -19427,14 +19833,14 @@ function installMacLaunchAgent() {
|
|
|
19427
19833
|
</dict>
|
|
19428
19834
|
</plist>
|
|
19429
19835
|
`;
|
|
19430
|
-
|
|
19431
|
-
|
|
19432
|
-
|
|
19836
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
19837
|
+
writeFileSync2(path, plist, { mode: 384 });
|
|
19838
|
+
chmodSync2(path, 384);
|
|
19433
19839
|
const domain = `gui/${process.getuid()}`;
|
|
19434
|
-
|
|
19840
|
+
spawnSync2(launchctlBin(), ["bootout", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`], {
|
|
19435
19841
|
stdio: "ignore"
|
|
19436
19842
|
});
|
|
19437
|
-
const loaded =
|
|
19843
|
+
const loaded = spawnSync2(launchctlBin(), ["bootstrap", domain, path], {
|
|
19438
19844
|
encoding: "utf8"
|
|
19439
19845
|
});
|
|
19440
19846
|
if (loaded.status !== 0) {
|
|
@@ -19444,7 +19850,7 @@ function installMacLaunchAgent() {
|
|
|
19444
19850
|
return path;
|
|
19445
19851
|
}
|
|
19446
19852
|
function isPm2Installed() {
|
|
19447
|
-
const result =
|
|
19853
|
+
const result = spawnSync2(pm2Bin(), ["--version"], {
|
|
19448
19854
|
stdio: "ignore",
|
|
19449
19855
|
shell: process.platform === "win32"
|
|
19450
19856
|
});
|
|
@@ -19465,7 +19871,7 @@ function installPm2() {
|
|
|
19465
19871
|
}
|
|
19466
19872
|
}
|
|
19467
19873
|
function pm2Exec(args) {
|
|
19468
|
-
const result =
|
|
19874
|
+
const result = spawnSync2(pm2Bin(), args, {
|
|
19469
19875
|
stdio: ["pipe", "pipe", "pipe"],
|
|
19470
19876
|
encoding: "utf-8",
|
|
19471
19877
|
env: process.env,
|
|
@@ -19501,10 +19907,10 @@ function databasePath() {
|
|
|
19501
19907
|
}
|
|
19502
19908
|
function isGatewayReady(expectedPid) {
|
|
19503
19909
|
const path = databasePath();
|
|
19504
|
-
if (!
|
|
19910
|
+
if (!existsSync4(path)) return false;
|
|
19505
19911
|
let database = null;
|
|
19506
19912
|
try {
|
|
19507
|
-
database = new
|
|
19913
|
+
database = new Database4(path, { readonly: true });
|
|
19508
19914
|
const row = database.query(
|
|
19509
19915
|
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
19510
19916
|
).get();
|
|
@@ -19656,7 +20062,7 @@ var init_pm2 = __esm({
|
|
|
19656
20062
|
"src/daemon/pm2.ts"() {
|
|
19657
20063
|
"use strict";
|
|
19658
20064
|
init_config();
|
|
19659
|
-
__dirname =
|
|
20065
|
+
__dirname = dirname3(fileURLToPath2(import.meta.url));
|
|
19660
20066
|
PROCESS_NAME = "supertask-gateway";
|
|
19661
20067
|
MAC_LAUNCH_AGENT_LABEL = "com.supertask.pm2-resurrect";
|
|
19662
20068
|
GATEWAY_LOCK_STALE_MS = 3e4;
|
|
@@ -19867,80 +20273,6 @@ var init_health = __esm({
|
|
|
19867
20273
|
}
|
|
19868
20274
|
});
|
|
19869
20275
|
|
|
19870
|
-
// src/core/process-control.ts
|
|
19871
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
19872
|
-
import { basename } from "path";
|
|
19873
|
-
function isSafePid(pid) {
|
|
19874
|
-
return Number.isInteger(pid) && pid > 1 && pid !== process.pid;
|
|
19875
|
-
}
|
|
19876
|
-
function isProcessAlive(pid) {
|
|
19877
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
19878
|
-
try {
|
|
19879
|
-
process.kill(pid, 0);
|
|
19880
|
-
return true;
|
|
19881
|
-
} catch (error) {
|
|
19882
|
-
return error instanceof Error && "code" in error && error.code === "EPERM";
|
|
19883
|
-
}
|
|
19884
|
-
}
|
|
19885
|
-
function inspectUnixProcess(pid) {
|
|
19886
|
-
const result = spawnSync2("ps", ["-o", "pgid=", "-o", "command=", "-p", String(pid)], {
|
|
19887
|
-
encoding: "utf8",
|
|
19888
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
19889
|
-
});
|
|
19890
|
-
if (result.status !== 0) return null;
|
|
19891
|
-
const match2 = result.stdout.trim().match(/^(\d+)\s+(.+)$/s);
|
|
19892
|
-
if (!match2) return null;
|
|
19893
|
-
return { processGroupId: Number(match2[1]), command: match2[2] };
|
|
19894
|
-
}
|
|
19895
|
-
function commandMatches(command, expectedExecutable) {
|
|
19896
|
-
const expectedName = basename(expectedExecutable).trim().toLowerCase();
|
|
19897
|
-
if (!expectedName) return false;
|
|
19898
|
-
return command.toLowerCase().includes(expectedName);
|
|
19899
|
-
}
|
|
19900
|
-
function signalSpawnedProcessTree(pid, signal) {
|
|
19901
|
-
if (!isSafePid(pid)) return false;
|
|
19902
|
-
if (process.platform !== "win32") {
|
|
19903
|
-
try {
|
|
19904
|
-
process.kill(-pid, signal);
|
|
19905
|
-
return true;
|
|
19906
|
-
} catch {
|
|
19907
|
-
}
|
|
19908
|
-
}
|
|
19909
|
-
try {
|
|
19910
|
-
process.kill(pid, signal);
|
|
19911
|
-
return true;
|
|
19912
|
-
} catch {
|
|
19913
|
-
return false;
|
|
19914
|
-
}
|
|
19915
|
-
}
|
|
19916
|
-
function signalRecordedProcessTree(pid, signal, expectedExecutable) {
|
|
19917
|
-
if (!isSafePid(pid)) return false;
|
|
19918
|
-
if (process.platform === "win32") {
|
|
19919
|
-
const list = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
|
|
19920
|
-
encoding: "utf8",
|
|
19921
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
19922
|
-
});
|
|
19923
|
-
if (list.status !== 0 || !commandMatches(list.stdout, expectedExecutable)) return false;
|
|
19924
|
-
const args = ["/PID", String(pid), "/T"];
|
|
19925
|
-
if (signal === "SIGKILL") args.push("/F");
|
|
19926
|
-
return spawnSync2("taskkill", args, { stdio: "ignore" }).status === 0;
|
|
19927
|
-
}
|
|
19928
|
-
const info = inspectUnixProcess(pid);
|
|
19929
|
-
if (!info || !commandMatches(info.command, expectedExecutable)) return false;
|
|
19930
|
-
try {
|
|
19931
|
-
if (info.processGroupId === pid) process.kill(-pid, signal);
|
|
19932
|
-
else process.kill(pid, signal);
|
|
19933
|
-
return true;
|
|
19934
|
-
} catch {
|
|
19935
|
-
return false;
|
|
19936
|
-
}
|
|
19937
|
-
}
|
|
19938
|
-
var init_process_control = __esm({
|
|
19939
|
-
"src/core/process-control.ts"() {
|
|
19940
|
-
"use strict";
|
|
19941
|
-
}
|
|
19942
|
-
});
|
|
19943
|
-
|
|
19944
20276
|
// src/worker/index.ts
|
|
19945
20277
|
import { spawn } from "child_process";
|
|
19946
20278
|
var DEFAULT_MAX_OUTPUT_CHARS, FORBIDDEN_AGENT, WorkerEngine;
|
|
@@ -20213,14 +20545,14 @@ ${output}` : ""}`;
|
|
|
20213
20545
|
if (entry.child.exitCode !== null || entry.child.signalCode !== null) {
|
|
20214
20546
|
return Promise.resolve();
|
|
20215
20547
|
}
|
|
20216
|
-
return new Promise((
|
|
20548
|
+
return new Promise((resolve2) => {
|
|
20217
20549
|
const timeout = setTimeout(() => {
|
|
20218
20550
|
this.signalEntry(entry, "SIGKILL");
|
|
20219
|
-
|
|
20551
|
+
resolve2();
|
|
20220
20552
|
}, 5e3);
|
|
20221
20553
|
entry.child.once("close", () => {
|
|
20222
20554
|
clearTimeout(timeout);
|
|
20223
|
-
|
|
20555
|
+
resolve2();
|
|
20224
20556
|
});
|
|
20225
20557
|
this.signalEntry(entry, "SIGTERM");
|
|
20226
20558
|
});
|
|
@@ -22922,8 +23254,8 @@ __export(web_exports, {
|
|
|
22922
23254
|
dashboardApp: () => dashboardApp,
|
|
22923
23255
|
default: () => web_default
|
|
22924
23256
|
});
|
|
22925
|
-
import { existsSync as
|
|
22926
|
-
import { dirname as
|
|
23257
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, renameSync as renameSync2 } from "fs";
|
|
23258
|
+
import { dirname as dirname4 } from "path";
|
|
22927
23259
|
function parsePositiveInteger(value) {
|
|
22928
23260
|
if (!/^\d+$/.test(value)) return null;
|
|
22929
23261
|
const parsed = Number(value);
|
|
@@ -22973,7 +23305,7 @@ function esc(s) {
|
|
|
22973
23305
|
}
|
|
22974
23306
|
function readCurrentConfig() {
|
|
22975
23307
|
const configPath = getConfigPath();
|
|
22976
|
-
if (!
|
|
23308
|
+
if (!existsSync5(configPath)) return {};
|
|
22977
23309
|
try {
|
|
22978
23310
|
return JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
22979
23311
|
} catch {
|
|
@@ -22982,11 +23314,11 @@ function readCurrentConfig() {
|
|
|
22982
23314
|
}
|
|
22983
23315
|
function writeConfig(cfg) {
|
|
22984
23316
|
const configPath = getConfigPath();
|
|
22985
|
-
const dir =
|
|
22986
|
-
if (!
|
|
23317
|
+
const dir = dirname4(configPath);
|
|
23318
|
+
if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true });
|
|
22987
23319
|
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
22988
|
-
|
|
22989
|
-
|
|
23320
|
+
writeFileSync3(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
23321
|
+
renameSync2(tempPath, configPath);
|
|
22990
23322
|
}
|
|
22991
23323
|
function renderLayout(title, activeTab, body) {
|
|
22992
23324
|
return `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>${title} - SuperTask</title>${SHARED_STYLES}
|
|
@@ -23003,12 +23335,12 @@ async function triggerTmpl(id){if(!confirm('\u7ACB\u5373\u89E6\u53D1\u4E00\u6B21
|
|
|
23003
23335
|
function toggleLog(id){const el=document.getElementById('log-'+id);el.style.display=el.style.display==='none'?'block':'none';}
|
|
23004
23336
|
|
|
23005
23337
|
async function clearDatabase(){
|
|
23006
|
-
if(!confirm('\u786E\u5B9A\u6E05\u7A7A\u6240\u6709\u4EFB\u52A1\u6570\u636E\uFF1F\
|
|
23007
|
-
if(!confirm('\u518D\u6B21\u786E\u8BA4\uFF1A\u5C06\u5220\u9664\u6240\u6709\u4EFB\u52A1\u3001\u6267\u884C\u8BB0\u5F55\u548C\u8C03\u5EA6\u6A21\u677F\u3002'))return;
|
|
23338
|
+
if(!confirm('\u786E\u5B9A\u6E05\u7A7A\u6240\u6709\u4EFB\u52A1\u6570\u636E\uFF1F\u7CFB\u7EDF\u4F1A\u5148\u81EA\u52A8\u521B\u5EFA\u5907\u4EFD\u3002'))return;
|
|
23339
|
+
if(!confirm('\u518D\u6B21\u786E\u8BA4\uFF1A\u5C06\u4E8B\u52A1\u6027\u5220\u9664\u6240\u6709\u4EFB\u52A1\u3001\u6267\u884C\u8BB0\u5F55\u548C\u8C03\u5EA6\u6A21\u677F\u3002'))return;
|
|
23008
23340
|
try{
|
|
23009
|
-
const r=await fetch('/api/database/clear',{method:'POST'});
|
|
23341
|
+
const r=await fetch('/api/database/clear',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({confirmation:'CLEAR'})});
|
|
23010
23342
|
const d=await r.json();
|
|
23011
|
-
if(d.success){alert('\u6570\u636E\u5E93\u5DF2\u6E05\u7A7A');location.reload();}
|
|
23343
|
+
if(d.success){alert('\u6570\u636E\u5E93\u5DF2\u6E05\u7A7A\uFF0C\u81EA\u52A8\u5907\u4EFD\uFF1A'+d.backupPath);location.reload();}
|
|
23012
23344
|
else{alert('\u6E05\u7A7A\u5931\u8D25: '+d.error);}
|
|
23013
23345
|
}catch(e){alert('\u6E05\u7A7A\u5931\u8D25: '+e.message);}
|
|
23014
23346
|
}
|
|
@@ -23069,6 +23401,7 @@ var init_web = __esm({
|
|
|
23069
23401
|
init_task_service();
|
|
23070
23402
|
init_task_run_service();
|
|
23071
23403
|
init_task_template_service();
|
|
23404
|
+
init_database_maintenance_service();
|
|
23072
23405
|
init_drizzle_orm();
|
|
23073
23406
|
init_db2();
|
|
23074
23407
|
init_config();
|
|
@@ -23377,7 +23710,7 @@ var init_web = __esm({
|
|
|
23377
23710
|
const stats = await TaskService.stats({});
|
|
23378
23711
|
const runningRuns = await TaskRunService.getAllRunningRuns();
|
|
23379
23712
|
const templates = await TaskTemplateService.list(100);
|
|
23380
|
-
const configExists =
|
|
23713
|
+
const configExists = existsSync5(configPath);
|
|
23381
23714
|
let runRows = "";
|
|
23382
23715
|
if (runningRuns.length > 0) {
|
|
23383
23716
|
for (const run of runningRuns) {
|
|
@@ -23451,7 +23784,7 @@ var init_web = __esm({
|
|
|
23451
23784
|
|
|
23452
23785
|
<div class="card mt16" style="border-color:var(--red)">
|
|
23453
23786
|
<h3 style="margin:0 0 12px;font-size:14px;color:var(--red)">\u5371\u9669\u64CD\u4F5C</h3>
|
|
23454
|
-
<p class="sm mu" style="margin:0 0 12px">\
|
|
23787
|
+
<p class="sm mu" style="margin:0 0 12px">\u81EA\u52A8\u5907\u4EFD\u540E\u4E8B\u52A1\u6027\u6E05\u7A7A tasks\u3001task_runs \u548C task_templates\uFF1B\u5B58\u5728\u8FD0\u884C\u4E2D\u4EFB\u52A1\u65F6\u4F1A\u62D2\u7EDD\u6267\u884C\u3002</p>
|
|
23455
23788
|
<button class="btn btn-danger" style="border-color:var(--red);color:var(--red);padding:6px 16px" onclick="clearDatabase()">\u6E05\u7A7A\u6570\u636E\u5E93</button>
|
|
23456
23789
|
</div>`;
|
|
23457
23790
|
return c.html(renderLayout("\u7CFB\u7EDF\u72B6\u6001", "system", body));
|
|
@@ -23556,14 +23889,16 @@ var init_web = __esm({
|
|
|
23556
23889
|
}
|
|
23557
23890
|
});
|
|
23558
23891
|
app.post("/api/database/clear", async (c) => {
|
|
23892
|
+
const body = await c.req.json().catch(() => ({}));
|
|
23893
|
+
if (body.confirmation !== "CLEAR") {
|
|
23894
|
+
return c.json({ success: false, error: "confirmation must be CLEAR" }, 400);
|
|
23895
|
+
}
|
|
23559
23896
|
try {
|
|
23560
|
-
const
|
|
23561
|
-
|
|
23562
|
-
await db.delete(taskTemplates4);
|
|
23563
|
-
await db.delete(tasks3);
|
|
23564
|
-
return c.json({ success: true });
|
|
23897
|
+
const result = DatabaseMaintenanceService.clear({ allowCurrentGateway: true });
|
|
23898
|
+
return c.json({ success: true, ...result });
|
|
23565
23899
|
} catch (err) {
|
|
23566
|
-
|
|
23900
|
+
const status = err instanceof DatabaseMaintenanceConflictError ? 409 : 500;
|
|
23901
|
+
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) }, status);
|
|
23567
23902
|
}
|
|
23568
23903
|
});
|
|
23569
23904
|
dashboardApp = app;
|
|
@@ -23762,13 +24097,13 @@ __export(update_exports, {
|
|
|
23762
24097
|
resolveInstalledPlugin: () => resolveInstalledPlugin
|
|
23763
24098
|
});
|
|
23764
24099
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
23765
|
-
import { existsSync as
|
|
24100
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, readdirSync } from "fs";
|
|
23766
24101
|
import { homedir as homedir4 } from "os";
|
|
23767
24102
|
import { join as join4 } from "path";
|
|
23768
24103
|
function pluginAt(packageDir) {
|
|
23769
24104
|
const packageJson = join4(packageDir, "package.json");
|
|
23770
24105
|
const gatewayEntry = join4(packageDir, "dist/gateway/index.js");
|
|
23771
|
-
if (!
|
|
24106
|
+
if (!existsSync6(packageJson) || !existsSync6(gatewayEntry)) return null;
|
|
23772
24107
|
try {
|
|
23773
24108
|
const pkg = JSON.parse(readFileSync4(packageJson, "utf8"));
|
|
23774
24109
|
if (pkg.name !== PACKAGE_NAME || typeof pkg.version !== "string") return null;
|
|
@@ -23793,6 +24128,11 @@ function compareVersions(left, right) {
|
|
|
23793
24128
|
function cacheRoot() {
|
|
23794
24129
|
return process.env.SUPERTASK_OPENCODE_CACHE_DIR ?? join4(homedir4(), ".cache/opencode/packages");
|
|
23795
24130
|
}
|
|
24131
|
+
function installedPlugins() {
|
|
24132
|
+
const root = cacheRoot();
|
|
24133
|
+
const packageDirs = existsSync6(root) ? readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && (entry.name === PACKAGE_NAME || entry.name.startsWith(`${PACKAGE_NAME}@`))).map((entry) => join4(root, entry.name, "node_modules", PACKAGE_NAME)) : [];
|
|
24134
|
+
return packageDirs.map(pluginAt).filter((plugin) => plugin !== null);
|
|
24135
|
+
}
|
|
23796
24136
|
function resolveInstalledPlugin() {
|
|
23797
24137
|
const override = process.env.SUPERTASK_PLUGIN_PACKAGE_DIR;
|
|
23798
24138
|
if (override) {
|
|
@@ -23800,9 +24140,7 @@ function resolveInstalledPlugin() {
|
|
|
23800
24140
|
if (!plugin) throw new Error(`[supertask] \u5B89\u88C5\u5305\u65E0\u6548\u6216\u7F3A\u5C11 Gateway \u6784\u5EFA\u4EA7\u7269: ${override}`);
|
|
23801
24141
|
return plugin;
|
|
23802
24142
|
}
|
|
23803
|
-
const
|
|
23804
|
-
const packageDirs = existsSync5(root) ? readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && (entry.name === PACKAGE_NAME || entry.name.startsWith(`${PACKAGE_NAME}@`))).map((entry) => join4(root, entry.name, "node_modules", PACKAGE_NAME)) : [];
|
|
23805
|
-
const installed = packageDirs.map(pluginAt).filter((plugin) => plugin !== null).sort((left, right) => compareVersions(right.version, left.version));
|
|
24143
|
+
const installed = installedPlugins().sort((left, right) => compareVersions(right.version, left.version));
|
|
23806
24144
|
if (!installed[0]) {
|
|
23807
24145
|
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230\u53EF\u8FD0\u884C\u7684 ${PACKAGE_NAME}`);
|
|
23808
24146
|
}
|
|
@@ -23811,10 +24149,52 @@ function resolveInstalledPlugin() {
|
|
|
23811
24149
|
function opencodeBin() {
|
|
23812
24150
|
return process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
23813
24151
|
}
|
|
24152
|
+
function npmBin() {
|
|
24153
|
+
return process.env.SUPERTASK_NPM_BIN ?? "npm";
|
|
24154
|
+
}
|
|
24155
|
+
function latestVersion() {
|
|
24156
|
+
const result = spawnSync3(npmBin(), [
|
|
24157
|
+
"view",
|
|
24158
|
+
PACKAGE_NAME,
|
|
24159
|
+
"dist-tags.latest",
|
|
24160
|
+
"--json"
|
|
24161
|
+
], {
|
|
24162
|
+
encoding: "utf8",
|
|
24163
|
+
env: process.env,
|
|
24164
|
+
timeout: 3e4
|
|
24165
|
+
});
|
|
24166
|
+
const output = `${result.stdout ?? ""}`.trim();
|
|
24167
|
+
if (result.error) {
|
|
24168
|
+
throw new Error(`[supertask] \u67E5\u8BE2 npm latest \u5931\u8D25: ${result.error.message}`);
|
|
24169
|
+
}
|
|
24170
|
+
if (result.status !== 0) {
|
|
24171
|
+
const detail = `${result.stderr ?? ""}`.trim();
|
|
24172
|
+
throw new Error(`[supertask] \u67E5\u8BE2 npm latest \u5931\u8D25: ${detail || `\u9000\u51FA\u7801 ${result.status}`}`);
|
|
24173
|
+
}
|
|
24174
|
+
let version2;
|
|
24175
|
+
try {
|
|
24176
|
+
version2 = JSON.parse(output);
|
|
24177
|
+
} catch {
|
|
24178
|
+
throw new Error(`[supertask] npm latest \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790: ${output || "(empty)"}`);
|
|
24179
|
+
}
|
|
24180
|
+
if (typeof version2 !== "string" || versionParts(version2) === null) {
|
|
24181
|
+
throw new Error(`[supertask] npm latest \u7248\u672C\u65E0\u6548: ${String(version2)}`);
|
|
24182
|
+
}
|
|
24183
|
+
return version2;
|
|
24184
|
+
}
|
|
24185
|
+
function resolveInstalledVersion(expectedVersion) {
|
|
24186
|
+
const override = process.env.SUPERTASK_PLUGIN_PACKAGE_DIR;
|
|
24187
|
+
const installed = override ? [pluginAt(override)].filter((plugin) => plugin !== null) : installedPlugins();
|
|
24188
|
+
const matched = installed.find((plugin) => plugin.version === expectedVersion);
|
|
24189
|
+
if (matched) return matched;
|
|
24190
|
+
const actual = installed.length > 0 ? installed.map((plugin) => plugin.version).join(", ") : "\u672A\u627E\u5230\u53EF\u8FD0\u884C\u7F13\u5B58";
|
|
24191
|
+
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7F13\u5B58\u7248\u672C\u4E0D\u5339\u914D\uFF1A\u671F\u671B ${expectedVersion}\uFF0C\u5B9E\u9645 ${actual}`);
|
|
24192
|
+
}
|
|
23814
24193
|
function installLatestPlugin() {
|
|
24194
|
+
const version2 = latestVersion();
|
|
23815
24195
|
const result = spawnSync3(opencodeBin(), [
|
|
23816
24196
|
"plugin",
|
|
23817
|
-
`${PACKAGE_NAME}
|
|
24197
|
+
`${PACKAGE_NAME}@${version2}`,
|
|
23818
24198
|
"--global",
|
|
23819
24199
|
"--force"
|
|
23820
24200
|
], {
|
|
@@ -23829,7 +24209,7 @@ function installLatestPlugin() {
|
|
|
23829
24209
|
if (result.status !== 0) {
|
|
23830
24210
|
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u66F4\u65B0\u5931\u8D25: ${output || `\u9000\u51FA\u7801 ${result.status}`}`);
|
|
23831
24211
|
}
|
|
23832
|
-
return
|
|
24212
|
+
return resolveInstalledVersion(version2);
|
|
23833
24213
|
}
|
|
23834
24214
|
var PACKAGE_NAME;
|
|
23835
24215
|
var init_update2 = __esm({
|
|
@@ -23859,6 +24239,7 @@ var {
|
|
|
23859
24239
|
// src/cli/index.ts
|
|
23860
24240
|
init_task_service();
|
|
23861
24241
|
init_task_template_service();
|
|
24242
|
+
init_database_maintenance_service();
|
|
23862
24243
|
init_db2();
|
|
23863
24244
|
|
|
23864
24245
|
// src/core/duration.ts
|
|
@@ -24107,15 +24488,35 @@ program2.command("template").description("\u7BA1\u7406\u4EFB\u52A1\u8C03\u5EA6\u
|
|
|
24107
24488
|
console.log(JSON.stringify({ deleted, id: parseInt(options.id) }));
|
|
24108
24489
|
}))
|
|
24109
24490
|
);
|
|
24491
|
+
var databaseCommand = new Command("db").description("\u6570\u636E\u5E93\u68C0\u67E5\u3001\u5907\u4EFD\u3001\u6E05\u7A7A\u4E0E\u6062\u590D");
|
|
24492
|
+
databaseCommand.command("check").description("\u68C0\u67E5\u6570\u636E\u5E93\u5B8C\u6574\u6027\u3001\u5916\u952E\u548C\u4E1A\u52A1\u8868\u7EDF\u8BA1").action(async () => withDb(async () => {
|
|
24493
|
+
console.log(JSON.stringify(DatabaseMaintenanceService.check(), null, 2));
|
|
24494
|
+
}));
|
|
24495
|
+
databaseCommand.command("backup").description("\u521B\u5EFA\u7ECF\u8FC7\u5B8C\u6574\u6027\u6821\u9A8C\u7684\u4E00\u81F4\u6027\u5907\u4EFD").option("-o, --output <path>", "\u5907\u4EFD\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u5199\u5165\u6570\u636E\u5E93\u76EE\u5F55\uFF09").action(async (options) => withDb(async () => {
|
|
24496
|
+
console.log(JSON.stringify(DatabaseMaintenanceService.backup(options.output), null, 2));
|
|
24497
|
+
}));
|
|
24498
|
+
databaseCommand.command("clear").description("\u5907\u4EFD\u540E\u4E8B\u52A1\u6027\u6E05\u7A7A\u4EFB\u52A1\u3001\u6267\u884C\u8BB0\u5F55\u548C\u8C03\u5EA6\u6A21\u677F").option("--confirm <word>", "\u5371\u9669\u64CD\u4F5C\u786E\u8BA4\uFF0C\u5FC5\u987B\u586B\u5199 CLEAR").action(async (options) => withDb(async () => {
|
|
24499
|
+
if (options.confirm !== "CLEAR") {
|
|
24500
|
+
throw new Error("\u6E05\u7A7A\u6570\u636E\u5E93\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 --confirm CLEAR");
|
|
24501
|
+
}
|
|
24502
|
+
console.log(JSON.stringify(DatabaseMaintenanceService.clear(), null, 2));
|
|
24503
|
+
}));
|
|
24504
|
+
databaseCommand.command("restore").description("\u81EA\u52A8\u5907\u4EFD\u5F53\u524D\u5E93\u540E\uFF0C\u4ECE\u6307\u5B9A\u5907\u4EFD\u6062\u590D\u6570\u636E\u5E93").requiredOption("--from <path>", "\u8981\u6062\u590D\u7684 SQLite \u5907\u4EFD\u6587\u4EF6").option("--confirm <word>", "\u5371\u9669\u64CD\u4F5C\u786E\u8BA4\uFF0C\u5FC5\u987B\u586B\u5199 RESTORE").action(async (options) => withDb(async () => {
|
|
24505
|
+
if (options.confirm !== "RESTORE") {
|
|
24506
|
+
throw new Error("\u6062\u590D\u6570\u636E\u5E93\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 --confirm RESTORE");
|
|
24507
|
+
}
|
|
24508
|
+
console.log(JSON.stringify(DatabaseMaintenanceService.restore(options.from), null, 2));
|
|
24509
|
+
}));
|
|
24510
|
+
program2.addCommand(databaseCommand);
|
|
24110
24511
|
program2.command("init").description("Initialize SuperTask (create config + run migrations)").action(async () => withDb(async () => {
|
|
24111
|
-
const { existsSync:
|
|
24112
|
-
const { dirname:
|
|
24512
|
+
const { existsSync: existsSync7, mkdirSync: mkdirSync5, writeFileSync: writeFileSync4 } = await import("fs");
|
|
24513
|
+
const { dirname: dirname5 } = await import("path");
|
|
24113
24514
|
const { getConfigPath: getConfigPath2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
24114
24515
|
const configPath = getConfigPath2();
|
|
24115
|
-
if (!
|
|
24116
|
-
const dir =
|
|
24117
|
-
if (!
|
|
24118
|
-
|
|
24516
|
+
if (!existsSync7(configPath)) {
|
|
24517
|
+
const dir = dirname5(configPath);
|
|
24518
|
+
if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
|
|
24519
|
+
writeFileSync4(configPath, JSON.stringify({
|
|
24119
24520
|
configVersion: 2,
|
|
24120
24521
|
worker: { maxConcurrency: 2 },
|
|
24121
24522
|
scheduler: { enabled: true }
|
|
@@ -24180,7 +24581,7 @@ program2.command("upgrade").description("Update OpenCode plugin cache and restar
|
|
|
24180
24581
|
installed = installLatestPlugin2();
|
|
24181
24582
|
} catch (err) {
|
|
24182
24583
|
console.error(err instanceof Error ? err.message : String(err));
|
|
24183
|
-
console.error("Try manually:
|
|
24584
|
+
console.error("Try manually: query npm dist-tags.latest, then install that exact version with opencode plugin.");
|
|
24184
24585
|
process.exit(1);
|
|
24185
24586
|
}
|
|
24186
24587
|
try {
|