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/web/index.js
CHANGED
|
@@ -7247,26 +7247,26 @@ var require_CronDate = __commonJS({
|
|
|
7247
7247
|
* @param {CronDate | Date | number | string} [timestamp] - The timestamp to initialize the CronDate with.
|
|
7248
7248
|
* @param {string} [tz] - The timezone to use for the CronDate.
|
|
7249
7249
|
*/
|
|
7250
|
-
constructor(
|
|
7250
|
+
constructor(timestamp2, tz) {
|
|
7251
7251
|
const dateOpts = { zone: tz };
|
|
7252
|
-
if (!
|
|
7252
|
+
if (!timestamp2) {
|
|
7253
7253
|
this.#date = luxon_1.DateTime.local();
|
|
7254
|
-
} else if (
|
|
7255
|
-
this.#date =
|
|
7256
|
-
this.#dstStart =
|
|
7257
|
-
this.#dstEnd =
|
|
7258
|
-
} else if (
|
|
7259
|
-
this.#date = luxon_1.DateTime.fromJSDate(
|
|
7260
|
-
} else if (typeof
|
|
7261
|
-
this.#date = luxon_1.DateTime.fromMillis(
|
|
7254
|
+
} else if (timestamp2 instanceof _CronDate) {
|
|
7255
|
+
this.#date = timestamp2.#date;
|
|
7256
|
+
this.#dstStart = timestamp2.#dstStart;
|
|
7257
|
+
this.#dstEnd = timestamp2.#dstEnd;
|
|
7258
|
+
} else if (timestamp2 instanceof Date) {
|
|
7259
|
+
this.#date = luxon_1.DateTime.fromJSDate(timestamp2, dateOpts);
|
|
7260
|
+
} else if (typeof timestamp2 === "number") {
|
|
7261
|
+
this.#date = luxon_1.DateTime.fromMillis(timestamp2, dateOpts);
|
|
7262
7262
|
} else {
|
|
7263
|
-
this.#date = luxon_1.DateTime.fromISO(
|
|
7264
|
-
this.#date.isValid || (this.#date = luxon_1.DateTime.fromRFC2822(
|
|
7265
|
-
this.#date.isValid || (this.#date = luxon_1.DateTime.fromSQL(
|
|
7266
|
-
this.#date.isValid || (this.#date = luxon_1.DateTime.fromFormat(
|
|
7263
|
+
this.#date = luxon_1.DateTime.fromISO(timestamp2, dateOpts);
|
|
7264
|
+
this.#date.isValid || (this.#date = luxon_1.DateTime.fromRFC2822(timestamp2, dateOpts));
|
|
7265
|
+
this.#date.isValid || (this.#date = luxon_1.DateTime.fromSQL(timestamp2, dateOpts));
|
|
7266
|
+
this.#date.isValid || (this.#date = luxon_1.DateTime.fromFormat(timestamp2, "EEE, d MMM yyyy HH:mm:ss", dateOpts));
|
|
7267
7267
|
}
|
|
7268
7268
|
if (!this.#date.isValid) {
|
|
7269
|
-
throw new Error(`CronDate: unhandled timestamp: ${
|
|
7269
|
+
throw new Error(`CronDate: unhandled timestamp: ${timestamp2}`);
|
|
7270
7270
|
}
|
|
7271
7271
|
if (tz && tz !== this.#date.zoneName) {
|
|
7272
7272
|
this.#date = this.#date.setZone(tz);
|
|
@@ -16650,6 +16650,14 @@ function getSqlite() {
|
|
|
16650
16650
|
if (!_sqlite) initDb();
|
|
16651
16651
|
return _sqlite;
|
|
16652
16652
|
}
|
|
16653
|
+
function closeDb() {
|
|
16654
|
+
if (_sqlite) {
|
|
16655
|
+
_sqlite.close();
|
|
16656
|
+
_sqlite = null;
|
|
16657
|
+
_db = null;
|
|
16658
|
+
_migrationRan = false;
|
|
16659
|
+
}
|
|
16660
|
+
}
|
|
16653
16661
|
var db = new Proxy({}, {
|
|
16654
16662
|
get(_, prop) {
|
|
16655
16663
|
const target = getDb();
|
|
@@ -17207,8 +17215,345 @@ var TaskTemplateService = class {
|
|
|
17207
17215
|
}
|
|
17208
17216
|
};
|
|
17209
17217
|
|
|
17218
|
+
// src/core/services/database-maintenance.service.ts
|
|
17219
|
+
import { Database as Database3 } from "bun:sqlite";
|
|
17220
|
+
import { randomUUID } from "crypto";
|
|
17221
|
+
import {
|
|
17222
|
+
chmodSync,
|
|
17223
|
+
constants,
|
|
17224
|
+
copyFileSync,
|
|
17225
|
+
existsSync as existsSync2,
|
|
17226
|
+
mkdirSync as mkdirSync2,
|
|
17227
|
+
renameSync,
|
|
17228
|
+
statSync,
|
|
17229
|
+
unlinkSync,
|
|
17230
|
+
writeFileSync
|
|
17231
|
+
} from "fs";
|
|
17232
|
+
import { tmpdir } from "os";
|
|
17233
|
+
import { basename, dirname as dirname2, resolve } from "path";
|
|
17234
|
+
|
|
17235
|
+
// src/core/process-control.ts
|
|
17236
|
+
function isProcessAlive(pid) {
|
|
17237
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
17238
|
+
try {
|
|
17239
|
+
process.kill(pid, 0);
|
|
17240
|
+
return true;
|
|
17241
|
+
} catch (error) {
|
|
17242
|
+
return error instanceof Error && "code" in error && error.code === "EPERM";
|
|
17243
|
+
}
|
|
17244
|
+
}
|
|
17245
|
+
|
|
17246
|
+
// src/core/services/database-maintenance.service.ts
|
|
17247
|
+
var REQUIRED_TABLES = ["gateway_lock", "tasks", "task_runs", "task_templates"];
|
|
17248
|
+
var DatabaseMaintenanceConflictError = class extends Error {
|
|
17249
|
+
constructor(message) {
|
|
17250
|
+
super(message);
|
|
17251
|
+
this.name = "DatabaseMaintenanceConflictError";
|
|
17252
|
+
}
|
|
17253
|
+
};
|
|
17254
|
+
function timestamp() {
|
|
17255
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
17256
|
+
}
|
|
17257
|
+
function normalizedPath(path) {
|
|
17258
|
+
return path === ":memory:" ? path : resolve(path);
|
|
17259
|
+
}
|
|
17260
|
+
function safeUnlink(path) {
|
|
17261
|
+
try {
|
|
17262
|
+
unlinkSync(path);
|
|
17263
|
+
} catch (error) {
|
|
17264
|
+
const code = error instanceof Error && "code" in error ? error.code : void 0;
|
|
17265
|
+
if (code !== "ENOENT") throw error;
|
|
17266
|
+
}
|
|
17267
|
+
}
|
|
17268
|
+
function errorMessage(error) {
|
|
17269
|
+
return error instanceof Error ? error.message : String(error);
|
|
17270
|
+
}
|
|
17271
|
+
var DatabaseMaintenanceService = class {
|
|
17272
|
+
static check() {
|
|
17273
|
+
return this.inspect(getSqlite(), DB_FILE_PATH);
|
|
17274
|
+
}
|
|
17275
|
+
static backup(outputPath) {
|
|
17276
|
+
const sqlite2 = getSqlite();
|
|
17277
|
+
try {
|
|
17278
|
+
sqlite2.query("PRAGMA wal_checkpoint(PASSIVE)").get();
|
|
17279
|
+
} catch {
|
|
17280
|
+
}
|
|
17281
|
+
return this.writeSnapshot(sqlite2, outputPath ?? this.createBackupPath("backup"));
|
|
17282
|
+
}
|
|
17283
|
+
static clear(options = {}) {
|
|
17284
|
+
const sqlite2 = getSqlite();
|
|
17285
|
+
this.assertGatewaySafe(sqlite2, options.allowCurrentGateway ?? false);
|
|
17286
|
+
this.assertNoRunningWork(sqlite2);
|
|
17287
|
+
sqlite2.exec("BEGIN IMMEDIATE");
|
|
17288
|
+
let backup = null;
|
|
17289
|
+
try {
|
|
17290
|
+
this.assertGatewaySafe(sqlite2, options.allowCurrentGateway ?? false);
|
|
17291
|
+
this.assertNoRunningWork(sqlite2);
|
|
17292
|
+
const before = this.readCounts(sqlite2);
|
|
17293
|
+
backup = this.writeSnapshot(sqlite2, this.createBackupPath("pre-clear"));
|
|
17294
|
+
sqlite2.exec("DELETE FROM task_runs");
|
|
17295
|
+
sqlite2.exec("DELETE FROM tasks");
|
|
17296
|
+
sqlite2.exec("DELETE FROM task_templates");
|
|
17297
|
+
sqlite2.exec("COMMIT");
|
|
17298
|
+
return {
|
|
17299
|
+
backupPath: backup.path,
|
|
17300
|
+
deleted: before,
|
|
17301
|
+
check: this.inspect(sqlite2, DB_FILE_PATH)
|
|
17302
|
+
};
|
|
17303
|
+
} catch (error) {
|
|
17304
|
+
if (sqlite2.inTransaction) sqlite2.exec("ROLLBACK");
|
|
17305
|
+
if (error instanceof DatabaseMaintenanceConflictError) throw error;
|
|
17306
|
+
const backupHint = backup ? `\uFF1B\u4E8B\u52A1\u5DF2\u56DE\u6EDA\uFF0C\u5907\u4EFD\u4FDD\u7559\u5728 ${backup.path}` : "";
|
|
17307
|
+
throw new Error(`\u6E05\u7A7A\u6570\u636E\u5E93\u5931\u8D25${backupHint}\uFF1A${errorMessage(error)}`);
|
|
17308
|
+
}
|
|
17309
|
+
}
|
|
17310
|
+
static restore(sourcePath) {
|
|
17311
|
+
if (DB_FILE_PATH === ":memory:") {
|
|
17312
|
+
throw new Error("\u5185\u5B58\u6570\u636E\u5E93\u4E0D\u652F\u6301 restore");
|
|
17313
|
+
}
|
|
17314
|
+
const source = normalizedPath(sourcePath);
|
|
17315
|
+
const livePath = normalizedPath(DB_FILE_PATH);
|
|
17316
|
+
if (source === livePath) throw new Error("\u6062\u590D\u6765\u6E90\u4E0D\u80FD\u662F\u5F53\u524D\u6570\u636E\u5E93\u6587\u4EF6");
|
|
17317
|
+
const current = getSqlite();
|
|
17318
|
+
this.assertGatewaySafe(current, false);
|
|
17319
|
+
this.assertNoRunningWork(current);
|
|
17320
|
+
if (!existsSync2(source) || !statSync(source).isFile()) {
|
|
17321
|
+
throw new Error(`\u5907\u4EFD\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${source}`);
|
|
17322
|
+
}
|
|
17323
|
+
let sourceCheck;
|
|
17324
|
+
try {
|
|
17325
|
+
sourceCheck = this.inspectFile(source);
|
|
17326
|
+
} catch (error) {
|
|
17327
|
+
throw new Error(`\u5907\u4EFD\u6587\u4EF6\u65E0\u6548\uFF1A${errorMessage(error)}`);
|
|
17328
|
+
}
|
|
17329
|
+
if (!sourceCheck.ok) {
|
|
17330
|
+
throw new Error(`\u5907\u4EFD\u6587\u4EF6\u6821\u9A8C\u5931\u8D25\uFF1A${sourceCheck.integrityMessages.join("; ") || "schema/foreign key error"}`);
|
|
17331
|
+
}
|
|
17332
|
+
const stagePath = `${livePath}.restore-${process.pid}-${randomUUID()}.tmp`;
|
|
17333
|
+
const rollbackPath = `${livePath}.rollback-${process.pid}-${randomUUID()}.tmp`;
|
|
17334
|
+
let safetyBackup = null;
|
|
17335
|
+
let liveMoved = false;
|
|
17336
|
+
try {
|
|
17337
|
+
copyFileSync(source, stagePath, constants.COPYFILE_EXCL);
|
|
17338
|
+
chmodSync(stagePath, 384);
|
|
17339
|
+
const staged = new Database3(stagePath);
|
|
17340
|
+
try {
|
|
17341
|
+
const stagedCheck = this.inspect(staged, stagePath);
|
|
17342
|
+
if (!stagedCheck.ok) throw new Error("\u6682\u5B58\u6062\u590D\u6587\u4EF6\u6821\u9A8C\u5931\u8D25");
|
|
17343
|
+
const now = Date.now();
|
|
17344
|
+
staged.query(
|
|
17345
|
+
"INSERT OR REPLACE INTO gateway_lock (id, pid, acquired_at, heartbeat_at, ready_at) VALUES (1, ?, ?, ?, NULL)"
|
|
17346
|
+
).run(process.pid, now, now);
|
|
17347
|
+
} finally {
|
|
17348
|
+
staged.close();
|
|
17349
|
+
}
|
|
17350
|
+
const sqlite2 = getSqlite();
|
|
17351
|
+
sqlite2.exec("BEGIN IMMEDIATE");
|
|
17352
|
+
try {
|
|
17353
|
+
this.assertGatewaySafe(sqlite2, false);
|
|
17354
|
+
this.assertNoRunningWork(sqlite2);
|
|
17355
|
+
safetyBackup = this.writeSnapshot(sqlite2, this.createBackupPath("pre-restore"));
|
|
17356
|
+
const now = Date.now();
|
|
17357
|
+
sqlite2.query(
|
|
17358
|
+
"INSERT OR REPLACE INTO gateway_lock (id, pid, acquired_at, heartbeat_at, ready_at) VALUES (1, ?, ?, ?, NULL)"
|
|
17359
|
+
).run(process.pid, now, now);
|
|
17360
|
+
sqlite2.exec("COMMIT");
|
|
17361
|
+
} catch (error) {
|
|
17362
|
+
if (sqlite2.inTransaction) sqlite2.exec("ROLLBACK");
|
|
17363
|
+
throw error;
|
|
17364
|
+
}
|
|
17365
|
+
const checkpoint = sqlite2.query("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
17366
|
+
if (checkpoint && checkpoint.busy !== 0) {
|
|
17367
|
+
throw new DatabaseMaintenanceConflictError("\u6570\u636E\u5E93\u4ECD\u88AB\u5176\u4ED6\u8FDE\u63A5\u5360\u7528\uFF0C\u65E0\u6CD5\u5B89\u5168\u6062\u590D");
|
|
17368
|
+
}
|
|
17369
|
+
closeDb();
|
|
17370
|
+
safeUnlink(`${livePath}-wal`);
|
|
17371
|
+
safeUnlink(`${livePath}-shm`);
|
|
17372
|
+
renameSync(livePath, rollbackPath);
|
|
17373
|
+
liveMoved = true;
|
|
17374
|
+
renameSync(stagePath, livePath);
|
|
17375
|
+
const restored = getSqlite();
|
|
17376
|
+
restored.exec("BEGIN IMMEDIATE");
|
|
17377
|
+
let recoveredRunningTasks = 0;
|
|
17378
|
+
let closedRunningRuns = 0;
|
|
17379
|
+
try {
|
|
17380
|
+
recoveredRunningTasks = this.scalar(
|
|
17381
|
+
restored,
|
|
17382
|
+
"SELECT COUNT(*) AS count FROM tasks WHERE status = 'running'"
|
|
17383
|
+
);
|
|
17384
|
+
closedRunningRuns = this.scalar(
|
|
17385
|
+
restored,
|
|
17386
|
+
"SELECT COUNT(*) AS count FROM task_runs WHERE status = 'running'"
|
|
17387
|
+
);
|
|
17388
|
+
const finishedAt = Math.floor(Date.now() / 1e3);
|
|
17389
|
+
restored.query(`
|
|
17390
|
+
UPDATE task_runs
|
|
17391
|
+
SET status = 'failed', finished_at = ?,
|
|
17392
|
+
log = CASE
|
|
17393
|
+
WHEN log IS NULL OR log = '' THEN '\u6570\u636E\u5E93\u6062\u590D\u65F6\u5173\u95ED\u9057\u7559\u8FD0\u884C\u8BB0\u5F55'
|
|
17394
|
+
ELSE log || '
|
|
17395
|
+
\u6570\u636E\u5E93\u6062\u590D\u65F6\u5173\u95ED\u9057\u7559\u8FD0\u884C\u8BB0\u5F55'
|
|
17396
|
+
END
|
|
17397
|
+
WHERE status = 'running'
|
|
17398
|
+
`).run(finishedAt);
|
|
17399
|
+
restored.exec(`
|
|
17400
|
+
UPDATE tasks
|
|
17401
|
+
SET status = 'pending', started_at = NULL, finished_at = NULL
|
|
17402
|
+
WHERE status = 'running'
|
|
17403
|
+
`);
|
|
17404
|
+
restored.query("DELETE FROM gateway_lock WHERE pid = ?").run(process.pid);
|
|
17405
|
+
restored.exec("COMMIT");
|
|
17406
|
+
} catch (error) {
|
|
17407
|
+
if (restored.inTransaction) restored.exec("ROLLBACK");
|
|
17408
|
+
throw error;
|
|
17409
|
+
}
|
|
17410
|
+
const check = this.inspect(restored, livePath);
|
|
17411
|
+
if (!check.ok) throw new Error("\u6062\u590D\u540E\u7684\u6570\u636E\u5E93\u672A\u901A\u8FC7\u5B8C\u6574\u6027\u6821\u9A8C");
|
|
17412
|
+
safeUnlink(rollbackPath);
|
|
17413
|
+
return {
|
|
17414
|
+
sourcePath: source,
|
|
17415
|
+
safetyBackupPath: safetyBackup.path,
|
|
17416
|
+
recoveredRunningTasks,
|
|
17417
|
+
closedRunningRuns,
|
|
17418
|
+
check
|
|
17419
|
+
};
|
|
17420
|
+
} catch (error) {
|
|
17421
|
+
closeDb();
|
|
17422
|
+
safeUnlink(`${livePath}-wal`);
|
|
17423
|
+
safeUnlink(`${livePath}-shm`);
|
|
17424
|
+
safeUnlink(stagePath);
|
|
17425
|
+
safeUnlink(`${stagePath}-journal`);
|
|
17426
|
+
safeUnlink(`${stagePath}-wal`);
|
|
17427
|
+
safeUnlink(`${stagePath}-shm`);
|
|
17428
|
+
if (liveMoved && existsSync2(rollbackPath)) {
|
|
17429
|
+
safeUnlink(livePath);
|
|
17430
|
+
renameSync(rollbackPath, livePath);
|
|
17431
|
+
try {
|
|
17432
|
+
const original = getSqlite();
|
|
17433
|
+
original.query("DELETE FROM gateway_lock WHERE pid = ?").run(process.pid);
|
|
17434
|
+
} catch {
|
|
17435
|
+
}
|
|
17436
|
+
} else if (existsSync2(rollbackPath)) {
|
|
17437
|
+
safeUnlink(rollbackPath);
|
|
17438
|
+
} else {
|
|
17439
|
+
try {
|
|
17440
|
+
getSqlite().query("DELETE FROM gateway_lock WHERE pid = ?").run(process.pid);
|
|
17441
|
+
} catch {
|
|
17442
|
+
}
|
|
17443
|
+
}
|
|
17444
|
+
const backupHint = safetyBackup ? `\uFF1B\u5F53\u524D\u5E93\u5B89\u5168\u5907\u4EFD\uFF1A${safetyBackup.path}` : "";
|
|
17445
|
+
throw new Error(`\u6062\u590D\u6570\u636E\u5E93\u5931\u8D25\uFF0C\u5DF2\u5C1D\u8BD5\u56DE\u6EDA${backupHint}\uFF1A${errorMessage(error)}`);
|
|
17446
|
+
}
|
|
17447
|
+
}
|
|
17448
|
+
static inspectFile(path) {
|
|
17449
|
+
let sqlite2;
|
|
17450
|
+
try {
|
|
17451
|
+
sqlite2 = new Database3(path, { readonly: true, strict: true });
|
|
17452
|
+
} catch (error) {
|
|
17453
|
+
throw new Error(`\u65E0\u6CD5\u6253\u5F00\u5907\u4EFD\u6587\u4EF6\uFF1A${errorMessage(error)}`);
|
|
17454
|
+
}
|
|
17455
|
+
try {
|
|
17456
|
+
return this.inspect(sqlite2, path);
|
|
17457
|
+
} finally {
|
|
17458
|
+
sqlite2.close();
|
|
17459
|
+
}
|
|
17460
|
+
}
|
|
17461
|
+
static inspect(sqlite2, path) {
|
|
17462
|
+
const tableRows = sqlite2.query(
|
|
17463
|
+
"SELECT name FROM sqlite_master WHERE type = 'table'"
|
|
17464
|
+
).all();
|
|
17465
|
+
const tableNames = new Set(tableRows.map((row) => row.name));
|
|
17466
|
+
const missingTables = REQUIRED_TABLES.filter((table) => !tableNames.has(table));
|
|
17467
|
+
const integrityRows = sqlite2.query("PRAGMA integrity_check").all();
|
|
17468
|
+
const integrityMessages = integrityRows.map((row) => row.integrity_check);
|
|
17469
|
+
const foreignKeyViolations = sqlite2.query("PRAGMA foreign_key_check").all().length;
|
|
17470
|
+
const journal = sqlite2.query("PRAGMA journal_mode").get();
|
|
17471
|
+
const counts = missingTables.length === 0 ? this.readCounts(sqlite2) : { tasks: 0, taskRuns: 0, taskTemplates: 0 };
|
|
17472
|
+
const runningTasks = missingTables.includes("tasks") ? 0 : this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM tasks WHERE status = 'running'");
|
|
17473
|
+
const runningRuns = missingTables.includes("task_runs") ? 0 : this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM task_runs WHERE status = 'running'");
|
|
17474
|
+
const sizeBytes = path === ":memory:" || !existsSync2(path) ? sqlite2.serialize().byteLength : statSync(path).size;
|
|
17475
|
+
return {
|
|
17476
|
+
ok: integrityMessages.length === 1 && integrityMessages[0] === "ok" && foreignKeyViolations === 0 && missingTables.length === 0,
|
|
17477
|
+
path: normalizedPath(path),
|
|
17478
|
+
sizeBytes,
|
|
17479
|
+
journalMode: journal?.journal_mode ?? "unknown",
|
|
17480
|
+
integrityMessages,
|
|
17481
|
+
foreignKeyViolations,
|
|
17482
|
+
missingTables,
|
|
17483
|
+
counts,
|
|
17484
|
+
runningTasks,
|
|
17485
|
+
runningRuns
|
|
17486
|
+
};
|
|
17487
|
+
}
|
|
17488
|
+
static writeSnapshot(sqlite2, outputPath) {
|
|
17489
|
+
const liveCheck = this.inspect(sqlite2, DB_FILE_PATH);
|
|
17490
|
+
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");
|
|
17491
|
+
const output = normalizedPath(outputPath);
|
|
17492
|
+
if (DB_FILE_PATH !== ":memory:" && output === normalizedPath(DB_FILE_PATH)) {
|
|
17493
|
+
throw new Error("\u5907\u4EFD\u8DEF\u5F84\u4E0D\u80FD\u8986\u76D6\u5F53\u524D\u6570\u636E\u5E93");
|
|
17494
|
+
}
|
|
17495
|
+
if (existsSync2(output)) throw new Error(`\u5907\u4EFD\u6587\u4EF6\u5DF2\u5B58\u5728\uFF1A${output}`);
|
|
17496
|
+
mkdirSync2(dirname2(output), { recursive: true });
|
|
17497
|
+
const temporary = `${output}.tmp-${process.pid}-${randomUUID()}`;
|
|
17498
|
+
try {
|
|
17499
|
+
writeFileSync(temporary, sqlite2.serialize(), { flag: "wx", mode: 384 });
|
|
17500
|
+
const standalone = new Database3(temporary, { readwrite: true, create: false });
|
|
17501
|
+
try {
|
|
17502
|
+
standalone.query("PRAGMA journal_mode = DELETE").get();
|
|
17503
|
+
} finally {
|
|
17504
|
+
standalone.close();
|
|
17505
|
+
}
|
|
17506
|
+
safeUnlink(`${temporary}-wal`);
|
|
17507
|
+
safeUnlink(`${temporary}-shm`);
|
|
17508
|
+
const check = this.inspectFile(temporary);
|
|
17509
|
+
if (!check.ok) throw new Error("\u65B0\u5907\u4EFD\u672A\u901A\u8FC7\u5B8C\u6574\u6027\u6821\u9A8C");
|
|
17510
|
+
renameSync(temporary, output);
|
|
17511
|
+
const finalCheck = { ...check, path: output, sizeBytes: statSync(output).size };
|
|
17512
|
+
return { path: output, sizeBytes: finalCheck.sizeBytes, check: finalCheck };
|
|
17513
|
+
} catch (error) {
|
|
17514
|
+
safeUnlink(temporary);
|
|
17515
|
+
safeUnlink(`${temporary}-wal`);
|
|
17516
|
+
safeUnlink(`${temporary}-shm`);
|
|
17517
|
+
throw error;
|
|
17518
|
+
}
|
|
17519
|
+
}
|
|
17520
|
+
static createBackupPath(kind) {
|
|
17521
|
+
const directory = DB_FILE_PATH === ":memory:" ? tmpdir() : dirname2(normalizedPath(DB_FILE_PATH));
|
|
17522
|
+
const base = DB_FILE_PATH === ":memory:" ? "supertask-memory" : basename(DB_FILE_PATH, ".db");
|
|
17523
|
+
return resolve(directory, `${base}.${kind}-${timestamp()}-${randomUUID().slice(0, 8)}.db`);
|
|
17524
|
+
}
|
|
17525
|
+
static readCounts(sqlite2) {
|
|
17526
|
+
return {
|
|
17527
|
+
tasks: this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM tasks"),
|
|
17528
|
+
taskRuns: this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM task_runs"),
|
|
17529
|
+
taskTemplates: this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM task_templates")
|
|
17530
|
+
};
|
|
17531
|
+
}
|
|
17532
|
+
static scalar(sqlite2, statement) {
|
|
17533
|
+
const row = sqlite2.query(statement).get();
|
|
17534
|
+
return Number(row?.count ?? 0);
|
|
17535
|
+
}
|
|
17536
|
+
static assertGatewaySafe(sqlite2, allowCurrentGateway) {
|
|
17537
|
+
const lock = sqlite2.query("SELECT pid FROM gateway_lock WHERE id = 1").get();
|
|
17538
|
+
if (!lock || !isProcessAlive(lock.pid)) return;
|
|
17539
|
+
if (allowCurrentGateway && lock.pid === process.pid) return;
|
|
17540
|
+
throw new DatabaseMaintenanceConflictError(
|
|
17541
|
+
`Gateway PID ${lock.pid} \u4ECD\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u6267\u884C pm2 stop supertask-gateway`
|
|
17542
|
+
);
|
|
17543
|
+
}
|
|
17544
|
+
static assertNoRunningWork(sqlite2) {
|
|
17545
|
+
const runningTasks = this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM tasks WHERE status = 'running'");
|
|
17546
|
+
const runningRuns = this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM task_runs WHERE status = 'running'");
|
|
17547
|
+
if (runningTasks > 0 || runningRuns > 0) {
|
|
17548
|
+
throw new DatabaseMaintenanceConflictError(
|
|
17549
|
+
`\u5B58\u5728\u8FD0\u884C\u4E2D\u4EFB\u52A1\uFF08tasks=${runningTasks}, task_runs=${runningRuns}\uFF09\uFF0C\u5DF2\u62D2\u7EDD\u5371\u9669\u64CD\u4F5C`
|
|
17550
|
+
);
|
|
17551
|
+
}
|
|
17552
|
+
}
|
|
17553
|
+
};
|
|
17554
|
+
|
|
17210
17555
|
// src/gateway/config.ts
|
|
17211
|
-
import { existsSync as
|
|
17556
|
+
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
17212
17557
|
import { homedir as homedir2 } from "os";
|
|
17213
17558
|
import { join as join2 } from "path";
|
|
17214
17559
|
var DEFAULT_CONFIG = {
|
|
@@ -17313,7 +17658,7 @@ function validateConfig(input) {
|
|
|
17313
17658
|
};
|
|
17314
17659
|
}
|
|
17315
17660
|
function loadConfig(path = getConfigPath()) {
|
|
17316
|
-
if (!
|
|
17661
|
+
if (!existsSync3(path)) return validateConfig({ configVersion: 2 });
|
|
17317
17662
|
try {
|
|
17318
17663
|
return validateConfig(JSON.parse(readFileSync(path, "utf-8")));
|
|
17319
17664
|
} catch (error) {
|
|
@@ -17323,8 +17668,8 @@ function loadConfig(path = getConfigPath()) {
|
|
|
17323
17668
|
}
|
|
17324
17669
|
|
|
17325
17670
|
// src/web/index.tsx
|
|
17326
|
-
import { existsSync as
|
|
17327
|
-
import { dirname as
|
|
17671
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, renameSync as renameSync2 } from "fs";
|
|
17672
|
+
import { dirname as dirname3 } from "path";
|
|
17328
17673
|
|
|
17329
17674
|
// src/gateway/health.ts
|
|
17330
17675
|
var LOCK_STALE_MS = 3e4;
|
|
@@ -17476,7 +17821,7 @@ function esc(s) {
|
|
|
17476
17821
|
}
|
|
17477
17822
|
function readCurrentConfig() {
|
|
17478
17823
|
const configPath = getConfigPath();
|
|
17479
|
-
if (!
|
|
17824
|
+
if (!existsSync4(configPath)) return {};
|
|
17480
17825
|
try {
|
|
17481
17826
|
return JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
17482
17827
|
} catch {
|
|
@@ -17485,11 +17830,11 @@ function readCurrentConfig() {
|
|
|
17485
17830
|
}
|
|
17486
17831
|
function writeConfig(cfg) {
|
|
17487
17832
|
const configPath = getConfigPath();
|
|
17488
|
-
const dir =
|
|
17489
|
-
if (!
|
|
17833
|
+
const dir = dirname3(configPath);
|
|
17834
|
+
if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
|
|
17490
17835
|
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
17491
|
-
|
|
17492
|
-
|
|
17836
|
+
writeFileSync2(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
17837
|
+
renameSync2(tempPath, configPath);
|
|
17493
17838
|
}
|
|
17494
17839
|
var SHARED_STYLES = html`
|
|
17495
17840
|
<style>
|
|
@@ -17581,12 +17926,12 @@ async function triggerTmpl(id){if(!confirm('\u7ACB\u5373\u89E6\u53D1\u4E00\u6B21
|
|
|
17581
17926
|
function toggleLog(id){const el=document.getElementById('log-'+id);el.style.display=el.style.display==='none'?'block':'none';}
|
|
17582
17927
|
|
|
17583
17928
|
async function clearDatabase(){
|
|
17584
|
-
if(!confirm('\u786E\u5B9A\u6E05\u7A7A\u6240\u6709\u4EFB\u52A1\u6570\u636E\uFF1F\
|
|
17585
|
-
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;
|
|
17929
|
+
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;
|
|
17930
|
+
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;
|
|
17586
17931
|
try{
|
|
17587
|
-
const r=await fetch('/api/database/clear',{method:'POST'});
|
|
17932
|
+
const r=await fetch('/api/database/clear',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({confirmation:'CLEAR'})});
|
|
17588
17933
|
const d=await r.json();
|
|
17589
|
-
if(d.success){alert('\u6570\u636E\u5E93\u5DF2\u6E05\u7A7A');location.reload();}
|
|
17934
|
+
if(d.success){alert('\u6570\u636E\u5E93\u5DF2\u6E05\u7A7A\uFF0C\u81EA\u52A8\u5907\u4EFD\uFF1A'+d.backupPath);location.reload();}
|
|
17590
17935
|
else{alert('\u6E05\u7A7A\u5931\u8D25: '+d.error);}
|
|
17591
17936
|
}catch(e){alert('\u6E05\u7A7A\u5931\u8D25: '+e.message);}
|
|
17592
17937
|
}
|
|
@@ -17827,7 +18172,7 @@ app.get("/system", async (c) => {
|
|
|
17827
18172
|
const stats = await TaskService.stats({});
|
|
17828
18173
|
const runningRuns = await TaskRunService.getAllRunningRuns();
|
|
17829
18174
|
const templates = await TaskTemplateService.list(100);
|
|
17830
|
-
const configExists =
|
|
18175
|
+
const configExists = existsSync4(configPath);
|
|
17831
18176
|
let runRows = "";
|
|
17832
18177
|
if (runningRuns.length > 0) {
|
|
17833
18178
|
for (const run of runningRuns) {
|
|
@@ -17901,7 +18246,7 @@ app.get("/system", async (c) => {
|
|
|
17901
18246
|
|
|
17902
18247
|
<div class="card mt16" style="border-color:var(--red)">
|
|
17903
18248
|
<h3 style="margin:0 0 12px;font-size:14px;color:var(--red)">\u5371\u9669\u64CD\u4F5C</h3>
|
|
17904
|
-
<p class="sm mu" style="margin:0 0 12px">\
|
|
18249
|
+
<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>
|
|
17905
18250
|
<button class="btn btn-danger" style="border-color:var(--red);color:var(--red);padding:6px 16px" onclick="clearDatabase()">\u6E05\u7A7A\u6570\u636E\u5E93</button>
|
|
17906
18251
|
</div>`;
|
|
17907
18252
|
return c.html(renderLayout("\u7CFB\u7EDF\u72B6\u6001", "system", body));
|
|
@@ -18006,14 +18351,16 @@ app.put("/api/config", async (c) => {
|
|
|
18006
18351
|
}
|
|
18007
18352
|
});
|
|
18008
18353
|
app.post("/api/database/clear", async (c) => {
|
|
18354
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18355
|
+
if (body.confirmation !== "CLEAR") {
|
|
18356
|
+
return c.json({ success: false, error: "confirmation must be CLEAR" }, 400);
|
|
18357
|
+
}
|
|
18009
18358
|
try {
|
|
18010
|
-
const
|
|
18011
|
-
|
|
18012
|
-
await db.delete(taskTemplates3);
|
|
18013
|
-
await db.delete(tasks3);
|
|
18014
|
-
return c.json({ success: true });
|
|
18359
|
+
const result = DatabaseMaintenanceService.clear({ allowCurrentGateway: true });
|
|
18360
|
+
return c.json({ success: true, ...result });
|
|
18015
18361
|
} catch (err) {
|
|
18016
|
-
|
|
18362
|
+
const status = err instanceof DatabaseMaintenanceConflictError ? 409 : 500;
|
|
18363
|
+
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) }, status);
|
|
18017
18364
|
}
|
|
18018
18365
|
});
|
|
18019
18366
|
var dashboardApp = app;
|