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/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(timestamp, tz) {
7250
+ constructor(timestamp2, tz) {
7251
7251
  const dateOpts = { zone: tz };
7252
- if (!timestamp) {
7252
+ if (!timestamp2) {
7253
7253
  this.#date = luxon_1.DateTime.local();
7254
- } else if (timestamp instanceof _CronDate) {
7255
- this.#date = timestamp.#date;
7256
- this.#dstStart = timestamp.#dstStart;
7257
- this.#dstEnd = timestamp.#dstEnd;
7258
- } else if (timestamp instanceof Date) {
7259
- this.#date = luxon_1.DateTime.fromJSDate(timestamp, dateOpts);
7260
- } else if (typeof timestamp === "number") {
7261
- this.#date = luxon_1.DateTime.fromMillis(timestamp, dateOpts);
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(timestamp, dateOpts);
7264
- this.#date.isValid || (this.#date = luxon_1.DateTime.fromRFC2822(timestamp, dateOpts));
7265
- this.#date.isValid || (this.#date = luxon_1.DateTime.fromSQL(timestamp, dateOpts));
7266
- this.#date.isValid || (this.#date = luxon_1.DateTime.fromFormat(timestamp, "EEE, d MMM yyyy HH:mm:ss", dateOpts));
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: ${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 existsSync2, readFileSync } from "fs";
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 (!existsSync2(path)) return validateConfig({ configVersion: 2 });
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 existsSync3, readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2, renameSync } from "fs";
17327
- import { dirname as dirname2 } from "path";
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 (!existsSync3(configPath)) return {};
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 = dirname2(configPath);
17489
- if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
17833
+ const dir = dirname3(configPath);
17834
+ if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
17490
17835
  const tempPath = `${configPath}.${process.pid}.tmp`;
17491
- writeFileSync(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
17492
- renameSync(tempPath, configPath);
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\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\uFF01'))return;
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 = existsSync3(configPath);
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">\u6E05\u7A7A\u6240\u6709\u4EFB\u52A1\u6570\u636E\uFF08tasks + task_runs + task_templates\uFF09\uFF0C\u4E0D\u53EF\u6062\u590D\u3002</p>
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 { tasks: tasks3, taskRuns: taskRuns4, taskTemplates: taskTemplates3 } = schema_exports;
18011
- await db.delete(taskRuns4);
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
- return c.json({ success: false, error: err instanceof Error ? err.message : String(err) }, 500);
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;