opencode-supertask 0.1.22 → 0.1.23

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/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(timestamp, tz) {
17098
+ constructor(timestamp2, tz) {
17099
17099
  const dateOpts = { zone: tz };
17100
- if (!timestamp) {
17100
+ if (!timestamp2) {
17101
17101
  this.#date = luxon_1.DateTime.local();
17102
- } else if (timestamp instanceof _CronDate) {
17103
- this.#date = timestamp.#date;
17104
- this.#dstStart = timestamp.#dstStart;
17105
- this.#dstEnd = timestamp.#dstEnd;
17106
- } else if (timestamp instanceof Date) {
17107
- this.#date = luxon_1.DateTime.fromJSDate(timestamp, dateOpts);
17108
- } else if (typeof timestamp === "number") {
17109
- this.#date = luxon_1.DateTime.fromMillis(timestamp, dateOpts);
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(timestamp, dateOpts);
17112
- this.#date.isValid || (this.#date = luxon_1.DateTime.fromRFC2822(timestamp, dateOpts));
17113
- this.#date.isValid || (this.#date = luxon_1.DateTime.fromSQL(timestamp, dateOpts));
17114
- this.#date.isValid || (this.#date = luxon_1.DateTime.fromFormat(timestamp, "EEE, d MMM yyyy HH:mm:ss", dateOpts));
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: ${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 existsSync2, readFileSync } from "fs";
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 (!existsSync2(path)) return validateConfig({ configVersion: 2 });
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 existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
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 dirname2, isAbsolute, join as join3 } from "path";
19727
+ import { dirname as dirname3, isAbsolute, join as join3 } from "path";
19322
19728
  import { fileURLToPath as fileURLToPath2 } from "url";
19323
- import { Database as Database3 } from "bun:sqlite";
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 (!existsSync3(override)) throw new Error(`[supertask] Gateway entry not found: ${override}`);
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) => existsSync3(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 (!existsSync3(path)) return null;
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
- mkdirSync2(dirname2(path), { recursive: true });
19364
- writeFileSync(path, version2, "utf-8");
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 = spawnSync("which", [configured], { encoding: "utf8" });
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
- mkdirSync2(dirname2(path), { recursive: true });
19431
- writeFileSync(path, plist, { mode: 384 });
19432
- chmodSync(path, 384);
19836
+ mkdirSync3(dirname3(path), { recursive: true });
19837
+ writeFileSync2(path, plist, { mode: 384 });
19838
+ chmodSync2(path, 384);
19433
19839
  const domain = `gui/${process.getuid()}`;
19434
- spawnSync(launchctlBin(), ["bootout", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`], {
19840
+ spawnSync2(launchctlBin(), ["bootout", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`], {
19435
19841
  stdio: "ignore"
19436
19842
  });
19437
- const loaded = spawnSync(launchctlBin(), ["bootstrap", domain, path], {
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 = spawnSync(pm2Bin(), ["--version"], {
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 = spawnSync(pm2Bin(), args, {
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 (!existsSync3(path)) return false;
19910
+ if (!existsSync4(path)) return false;
19505
19911
  let database = null;
19506
19912
  try {
19507
- database = new Database3(path, { readonly: true });
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 = dirname2(fileURLToPath2(import.meta.url));
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((resolve) => {
20548
+ return new Promise((resolve2) => {
20217
20549
  const timeout = setTimeout(() => {
20218
20550
  this.signalEntry(entry, "SIGKILL");
20219
- resolve();
20551
+ resolve2();
20220
20552
  }, 5e3);
20221
20553
  entry.child.once("close", () => {
20222
20554
  clearTimeout(timeout);
20223
- resolve();
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 existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, renameSync } from "fs";
22926
- import { dirname as dirname3 } from "path";
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 (!existsSync4(configPath)) return {};
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 = dirname3(configPath);
22986
- if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
23317
+ const dir = dirname4(configPath);
23318
+ if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true });
22987
23319
  const tempPath = `${configPath}.${process.pid}.tmp`;
22988
- writeFileSync2(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
22989
- renameSync(tempPath, configPath);
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\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\uFF01'))return;
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 = existsSync4(configPath);
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">\u6E05\u7A7A\u6240\u6709\u4EFB\u52A1\u6570\u636E\uFF08tasks + task_runs + task_templates\uFF09\uFF0C\u4E0D\u53EF\u6062\u590D\u3002</p>
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 { tasks: tasks3, taskRuns: taskRuns4, taskTemplates: taskTemplates4 } = schema_exports;
23561
- await db.delete(taskRuns4);
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
- return c.json({ success: false, error: err instanceof Error ? err.message : String(err) }, 500);
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 existsSync5, readFileSync as readFileSync4, readdirSync } from "fs";
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 (!existsSync5(packageJson) || !existsSync5(gatewayEntry)) return null;
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;
@@ -23801,7 +24136,7 @@ function resolveInstalledPlugin() {
23801
24136
  return plugin;
23802
24137
  }
23803
24138
  const root = cacheRoot();
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)) : [];
24139
+ 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)) : [];
23805
24140
  const installed = packageDirs.map(pluginAt).filter((plugin) => plugin !== null).sort((left, right) => compareVersions(right.version, left.version));
23806
24141
  if (!installed[0]) {
23807
24142
  throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230\u53EF\u8FD0\u884C\u7684 ${PACKAGE_NAME}`);
@@ -23859,6 +24194,7 @@ var {
23859
24194
  // src/cli/index.ts
23860
24195
  init_task_service();
23861
24196
  init_task_template_service();
24197
+ init_database_maintenance_service();
23862
24198
  init_db2();
23863
24199
 
23864
24200
  // src/core/duration.ts
@@ -24107,15 +24443,35 @@ program2.command("template").description("\u7BA1\u7406\u4EFB\u52A1\u8C03\u5EA6\u
24107
24443
  console.log(JSON.stringify({ deleted, id: parseInt(options.id) }));
24108
24444
  }))
24109
24445
  );
24446
+ var databaseCommand = new Command("db").description("\u6570\u636E\u5E93\u68C0\u67E5\u3001\u5907\u4EFD\u3001\u6E05\u7A7A\u4E0E\u6062\u590D");
24447
+ 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 () => {
24448
+ console.log(JSON.stringify(DatabaseMaintenanceService.check(), null, 2));
24449
+ }));
24450
+ 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 () => {
24451
+ console.log(JSON.stringify(DatabaseMaintenanceService.backup(options.output), null, 2));
24452
+ }));
24453
+ 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 () => {
24454
+ if (options.confirm !== "CLEAR") {
24455
+ throw new Error("\u6E05\u7A7A\u6570\u636E\u5E93\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 --confirm CLEAR");
24456
+ }
24457
+ console.log(JSON.stringify(DatabaseMaintenanceService.clear(), null, 2));
24458
+ }));
24459
+ 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 () => {
24460
+ if (options.confirm !== "RESTORE") {
24461
+ throw new Error("\u6062\u590D\u6570\u636E\u5E93\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 --confirm RESTORE");
24462
+ }
24463
+ console.log(JSON.stringify(DatabaseMaintenanceService.restore(options.from), null, 2));
24464
+ }));
24465
+ program2.addCommand(databaseCommand);
24110
24466
  program2.command("init").description("Initialize SuperTask (create config + run migrations)").action(async () => withDb(async () => {
24111
- const { existsSync: existsSync6, mkdirSync: mkdirSync4, writeFileSync: writeFileSync3 } = await import("fs");
24112
- const { dirname: dirname4 } = await import("path");
24467
+ const { existsSync: existsSync7, mkdirSync: mkdirSync5, writeFileSync: writeFileSync4 } = await import("fs");
24468
+ const { dirname: dirname5 } = await import("path");
24113
24469
  const { getConfigPath: getConfigPath2 } = await Promise.resolve().then(() => (init_config(), config_exports));
24114
24470
  const configPath = getConfigPath2();
24115
- if (!existsSync6(configPath)) {
24116
- const dir = dirname4(configPath);
24117
- if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
24118
- writeFileSync3(configPath, JSON.stringify({
24471
+ if (!existsSync7(configPath)) {
24472
+ const dir = dirname5(configPath);
24473
+ if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
24474
+ writeFileSync4(configPath, JSON.stringify({
24119
24475
  configVersion: 2,
24120
24476
  worker: { maxConcurrency: 2 },
24121
24477
  scheduler: { enabled: true }