opencode-supertask 0.1.31 → 0.1.32-rc.2
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 +78 -31
- package/dist/cli/index.js +1547 -513
- package/dist/cli/index.js.map +1 -1
- package/dist/config-CCUuD6Az.d.ts +26 -0
- package/dist/gateway/index.js +1533 -221
- package/dist/gateway/index.js.map +1 -1
- package/dist/plugin/supertask.js +237 -48
- package/dist/plugin/supertask.js.map +1 -1
- package/dist/web/index.d.ts +30 -1
- package/dist/web/index.js +1426 -181
- package/dist/web/index.js.map +1 -1
- package/dist/worker/index.d.ts +1 -24
- package/dist/worker/index.js +173 -23
- package/dist/worker/index.js.map +1 -1
- package/dist/worker/launcher.js +39 -7
- package/dist/worker/launcher.js.map +1 -1
- package/package.json +2 -2
package/dist/web/index.js
CHANGED
|
@@ -9100,8 +9100,8 @@ var require_CronFileParser = __commonJS({
|
|
|
9100
9100
|
* @throws If file cannot be read
|
|
9101
9101
|
*/
|
|
9102
9102
|
static parseFileSync(filePath) {
|
|
9103
|
-
const { readFileSync:
|
|
9104
|
-
const data =
|
|
9103
|
+
const { readFileSync: readFileSync4 } = __require("fs");
|
|
9104
|
+
const data = readFileSync4(filePath, "utf8");
|
|
9105
9105
|
return _CronFileParser.#parseContent(data);
|
|
9106
9106
|
}
|
|
9107
9107
|
/**
|
|
@@ -12224,7 +12224,7 @@ function sql(strings, ...params) {
|
|
|
12224
12224
|
return new SQL([new StringChunk(str)]);
|
|
12225
12225
|
}
|
|
12226
12226
|
sql2.raw = raw2;
|
|
12227
|
-
function
|
|
12227
|
+
function join4(chunks, separator) {
|
|
12228
12228
|
const result = [];
|
|
12229
12229
|
for (const [i, chunk] of chunks.entries()) {
|
|
12230
12230
|
if (i > 0 && separator !== void 0) {
|
|
@@ -12234,7 +12234,7 @@ function sql(strings, ...params) {
|
|
|
12234
12234
|
}
|
|
12235
12235
|
return new SQL(result);
|
|
12236
12236
|
}
|
|
12237
|
-
sql2.join =
|
|
12237
|
+
sql2.join = join4;
|
|
12238
12238
|
function identifier(value) {
|
|
12239
12239
|
return new Name(value);
|
|
12240
12240
|
}
|
|
@@ -13083,8 +13083,8 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
|
|
|
13083
13083
|
}
|
|
13084
13084
|
|
|
13085
13085
|
// src/web/index.tsx
|
|
13086
|
-
import { existsSync as
|
|
13087
|
-
import { dirname as
|
|
13086
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
13087
|
+
import { basename as basename2, dirname as dirname4 } from "path";
|
|
13088
13088
|
|
|
13089
13089
|
// src/core/db/index.ts
|
|
13090
13090
|
import { Database as Database2 } from "bun:sqlite";
|
|
@@ -14571,7 +14571,7 @@ var SQLiteSelectQueryBuilderBase = class extends TypedQueryBuilder {
|
|
|
14571
14571
|
return (table, on) => {
|
|
14572
14572
|
const baseTableName = this.tableName;
|
|
14573
14573
|
const tableName = getTableLikeName(table);
|
|
14574
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
14574
|
+
if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.alias === tableName)) {
|
|
14575
14575
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
14576
14576
|
}
|
|
14577
14577
|
if (!this.isPartialSelect) {
|
|
@@ -15384,7 +15384,7 @@ var SQLiteUpdateBase = class extends QueryPromise {
|
|
|
15384
15384
|
createJoin(joinType) {
|
|
15385
15385
|
return (table, on) => {
|
|
15386
15386
|
const tableName = getTableLikeName(table);
|
|
15387
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
15387
|
+
if (typeof tableName === "string" && this.config.joins.some((join4) => join4.alias === tableName)) {
|
|
15388
15388
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
15389
15389
|
}
|
|
15390
15390
|
if (typeof on === "function") {
|
|
@@ -16490,8 +16490,8 @@ var _sqlite = null;
|
|
|
16490
16490
|
var _db = null;
|
|
16491
16491
|
var _migrationRan = false;
|
|
16492
16492
|
function getMigrationsFolder() {
|
|
16493
|
-
const
|
|
16494
|
-
let dir =
|
|
16493
|
+
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
16494
|
+
let dir = __dirname2;
|
|
16495
16495
|
for (let i = 0; i < 5; i++) {
|
|
16496
16496
|
const candidate = join(dir, "drizzle", "meta", "_journal.json");
|
|
16497
16497
|
if (existsSync(candidate)) {
|
|
@@ -16499,7 +16499,7 @@ function getMigrationsFolder() {
|
|
|
16499
16499
|
}
|
|
16500
16500
|
dir = dirname(dir);
|
|
16501
16501
|
}
|
|
16502
|
-
return join(
|
|
16502
|
+
return join(__dirname2, "../../drizzle");
|
|
16503
16503
|
}
|
|
16504
16504
|
function ensureGatewayLock(sqliteDb) {
|
|
16505
16505
|
sqliteDb.exec(`
|
|
@@ -16582,6 +16582,35 @@ var sqlite = new Proxy({}, {
|
|
|
16582
16582
|
}
|
|
16583
16583
|
});
|
|
16584
16584
|
|
|
16585
|
+
// src/core/duration.ts
|
|
16586
|
+
var DURATION_REGEX = /^(\d+(?:\.\d+)?)\s*(ms|s|sec|seconds?|min|minutes?|m|h|hours?|d|days?|w|weeks?)$/i;
|
|
16587
|
+
var ISO8601_REGEX = /^P(?:([.\d]+)D)?(?:T(?:([.\d]+)H)?(?:([.\d]+)M)?(?:([.\d]+)S)?)?$/i;
|
|
16588
|
+
function parseDuration(input) {
|
|
16589
|
+
const trimmed = input.trim();
|
|
16590
|
+
const simple = DURATION_REGEX.exec(trimmed);
|
|
16591
|
+
if (simple) {
|
|
16592
|
+
const value = parseFloat(simple[1]);
|
|
16593
|
+
const unit = simple[2].toLowerCase();
|
|
16594
|
+
if (unit === "ms") return value;
|
|
16595
|
+
if (unit === "s" || unit === "sec" || unit === "second" || unit === "seconds") return value * 1e3;
|
|
16596
|
+
if (unit === "min" || unit === "minute" || unit === "minutes" || unit === "m") return value * 6e4;
|
|
16597
|
+
if (unit === "h" || unit === "hour" || unit === "hours") return value * 36e5;
|
|
16598
|
+
if (unit === "d" || unit === "day" || unit === "days") return value * 864e5;
|
|
16599
|
+
if (unit === "w" || unit === "week" || unit === "weeks") return value * 6048e5;
|
|
16600
|
+
}
|
|
16601
|
+
const iso = ISO8601_REGEX.exec(trimmed);
|
|
16602
|
+
if (iso) {
|
|
16603
|
+
const days = parseFloat(iso[1] ?? "0");
|
|
16604
|
+
const hours = parseFloat(iso[2] ?? "0");
|
|
16605
|
+
const minutes = parseFloat(iso[3] ?? "0");
|
|
16606
|
+
const seconds = parseFloat(iso[4] ?? "0");
|
|
16607
|
+
return (days * 86400 + hours * 3600 + minutes * 60 + seconds) * 1e3;
|
|
16608
|
+
}
|
|
16609
|
+
const asNumber = Number(trimmed);
|
|
16610
|
+
if (!isNaN(asNumber) && asNumber > 0) return asNumber;
|
|
16611
|
+
return null;
|
|
16612
|
+
}
|
|
16613
|
+
|
|
16585
16614
|
// src/core/services/database-maintenance.service.ts
|
|
16586
16615
|
import { Database as Database3 } from "bun:sqlite";
|
|
16587
16616
|
import { randomUUID } from "crypto";
|
|
@@ -17215,6 +17244,42 @@ function computeBackoff(retryCount, baseMs = 3e4, maxMs = MAX_BACKOFF_MS) {
|
|
|
17215
17244
|
return Math.min(baseMs * Math.pow(2, retryCount - 1), maxMs);
|
|
17216
17245
|
}
|
|
17217
17246
|
|
|
17247
|
+
// src/core/task-working-directory.ts
|
|
17248
|
+
import { statSync as statSync2 } from "fs";
|
|
17249
|
+
import { isAbsolute } from "path";
|
|
17250
|
+
var InvalidTaskWorkingDirectoryError = class extends Error {
|
|
17251
|
+
constructor(message) {
|
|
17252
|
+
super(message);
|
|
17253
|
+
this.name = "InvalidTaskWorkingDirectoryError";
|
|
17254
|
+
}
|
|
17255
|
+
};
|
|
17256
|
+
function validateTaskWorkingDirectory(cwd) {
|
|
17257
|
+
if (cwd == null) return;
|
|
17258
|
+
if (!cwd.trim()) {
|
|
17259
|
+
throw new InvalidTaskWorkingDirectoryError("\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A");
|
|
17260
|
+
}
|
|
17261
|
+
if (!isAbsolute(cwd)) {
|
|
17262
|
+
throw new InvalidTaskWorkingDirectoryError(`\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF1A${cwd}`);
|
|
17263
|
+
}
|
|
17264
|
+
let stat;
|
|
17265
|
+
try {
|
|
17266
|
+
stat = statSync2(cwd);
|
|
17267
|
+
} catch (error) {
|
|
17268
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
17269
|
+
throw new InvalidTaskWorkingDirectoryError(`\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6CD5\u8BBF\u95EE\uFF1A${cwd}\uFF08${detail}\uFF09`);
|
|
17270
|
+
}
|
|
17271
|
+
if (!stat.isDirectory()) {
|
|
17272
|
+
throw new InvalidTaskWorkingDirectoryError(`\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F\u76EE\u5F55\uFF1A${cwd}`);
|
|
17273
|
+
}
|
|
17274
|
+
}
|
|
17275
|
+
|
|
17276
|
+
// src/core/task-batch.ts
|
|
17277
|
+
var TASK_BATCH_TRIM_CHARACTERS = " \n\v\f\r\xA0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF";
|
|
17278
|
+
function normalizeTaskBatchId(batchId) {
|
|
17279
|
+
if (batchId == null) return batchId;
|
|
17280
|
+
return batchId.trim() || null;
|
|
17281
|
+
}
|
|
17282
|
+
|
|
17218
17283
|
// src/core/services/task.service.ts
|
|
17219
17284
|
var { tasks: tasks3, taskRuns: taskRuns3 } = schema_exports;
|
|
17220
17285
|
var cleanupInvocation = 0;
|
|
@@ -17278,34 +17343,75 @@ var TaskService = class {
|
|
|
17278
17343
|
return conditions;
|
|
17279
17344
|
}
|
|
17280
17345
|
static async add(data) {
|
|
17281
|
-
|
|
17346
|
+
const normalizedData = {
|
|
17347
|
+
...data,
|
|
17348
|
+
batchId: normalizeTaskBatchId(data.batchId)
|
|
17349
|
+
};
|
|
17350
|
+
this.validateNewTask(normalizedData);
|
|
17282
17351
|
return db.transaction((tx) => {
|
|
17283
|
-
if (
|
|
17352
|
+
if (normalizedData.dependsOn != null) {
|
|
17284
17353
|
const dependency = tx.select({
|
|
17285
17354
|
id: tasks3.id,
|
|
17286
17355
|
cwd: tasks3.cwd,
|
|
17287
17356
|
status: tasks3.status,
|
|
17288
17357
|
retryCount: tasks3.retryCount,
|
|
17289
17358
|
maxRetries: tasks3.maxRetries
|
|
17290
|
-
}).from(tasks3).where(eq(tasks3.id,
|
|
17359
|
+
}).from(tasks3).where(eq(tasks3.id, normalizedData.dependsOn)).get();
|
|
17291
17360
|
if (!dependency) {
|
|
17292
|
-
throw new Error(`dependsOn \u6307\u5411\u7684\u4EFB\u52A1 #${
|
|
17361
|
+
throw new Error(`dependsOn \u6307\u5411\u7684\u4EFB\u52A1 #${normalizedData.dependsOn} \u4E0D\u5B58\u5728`);
|
|
17293
17362
|
}
|
|
17294
|
-
if ((dependency.cwd ?? null) !== (
|
|
17363
|
+
if ((dependency.cwd ?? null) !== (normalizedData.cwd ?? null)) {
|
|
17295
17364
|
throw new Error("dependsOn \u5FC5\u987B\u6307\u5411\u540C\u4E00 cwd \u7684\u4EFB\u52A1");
|
|
17296
17365
|
}
|
|
17297
17366
|
const dependencyIsRecoverable = dependency.status === "pending" || dependency.status === "running" || dependency.status === "done" || dependency.status === "failed" && (dependency.retryCount ?? 0) <= (dependency.maxRetries ?? 3);
|
|
17298
17367
|
if (!dependencyIsRecoverable) {
|
|
17299
|
-
throw new Error(`dependsOn \u6307\u5411\u7684\u4EFB\u52A1 #${
|
|
17368
|
+
throw new Error(`dependsOn \u6307\u5411\u7684\u4EFB\u52A1 #${normalizedData.dependsOn} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`);
|
|
17300
17369
|
}
|
|
17301
17370
|
}
|
|
17302
|
-
return tx.insert(tasks3).values(
|
|
17371
|
+
return tx.insert(tasks3).values(normalizedData).returning().get();
|
|
17372
|
+
}, { behavior: "immediate" });
|
|
17373
|
+
}
|
|
17374
|
+
static async update(id, data, scope = {}) {
|
|
17375
|
+
if (Object.keys(data).length === 0) throw new Error("\u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A\u8981\u4FEE\u6539\u7684\u5B57\u6BB5");
|
|
17376
|
+
const normalizedData = data.batchId === void 0 ? data : { ...data, batchId: normalizeTaskBatchId(data.batchId) ?? null };
|
|
17377
|
+
return db.transaction((tx) => {
|
|
17378
|
+
const task = tx.select().from(tasks3).where(and(
|
|
17379
|
+
eq(tasks3.id, id),
|
|
17380
|
+
sql`${tasks3.status} IN ('pending', 'failed', 'dead_letter')`,
|
|
17381
|
+
...this.buildScopeWhere(scope)
|
|
17382
|
+
)).get();
|
|
17383
|
+
if (!task) return null;
|
|
17384
|
+
this.validateNewTask({
|
|
17385
|
+
name: normalizedData.name ?? task.name,
|
|
17386
|
+
agent: normalizedData.agent ?? task.agent,
|
|
17387
|
+
model: normalizedData.model ?? task.model,
|
|
17388
|
+
prompt: normalizedData.prompt ?? task.prompt,
|
|
17389
|
+
cwd: task.cwd,
|
|
17390
|
+
category: normalizedData.category ?? task.category,
|
|
17391
|
+
importance: normalizedData.importance ?? task.importance,
|
|
17392
|
+
urgency: normalizedData.urgency ?? task.urgency,
|
|
17393
|
+
batchId: normalizedData.batchId === void 0 ? task.batchId : normalizedData.batchId,
|
|
17394
|
+
maxRetries: normalizedData.maxRetries ?? task.maxRetries,
|
|
17395
|
+
retryBackoffMs: normalizedData.retryBackoffMs ?? task.retryBackoffMs,
|
|
17396
|
+
timeoutMs: normalizedData.timeoutMs === void 0 ? task.timeoutMs : normalizedData.timeoutMs,
|
|
17397
|
+
dependsOn: task.dependsOn
|
|
17398
|
+
});
|
|
17399
|
+
const maxRetries = normalizedData.maxRetries ?? task.maxRetries ?? 3;
|
|
17400
|
+
const exhausted = task.status === "failed" && (task.retryCount ?? 0) > maxRetries;
|
|
17401
|
+
return tx.update(tasks3).set({
|
|
17402
|
+
...normalizedData,
|
|
17403
|
+
...exhausted ? {
|
|
17404
|
+
status: "dead_letter",
|
|
17405
|
+
retryAfter: null
|
|
17406
|
+
} : {}
|
|
17407
|
+
}).where(eq(tasks3.id, id)).returning().get() ?? null;
|
|
17303
17408
|
}, { behavior: "immediate" });
|
|
17304
17409
|
}
|
|
17305
17410
|
static validateNewTask(data) {
|
|
17306
17411
|
if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
17307
17412
|
if (!data.agent.trim()) throw new Error("agent \u4E0D\u80FD\u4E3A\u7A7A");
|
|
17308
17413
|
if (!data.prompt.trim()) throw new Error("prompt \u4E0D\u80FD\u4E3A\u7A7A");
|
|
17414
|
+
validateTaskWorkingDirectory(data.cwd);
|
|
17309
17415
|
this.validateInteger("importance", data.importance, 1, 5);
|
|
17310
17416
|
this.validateInteger("urgency", data.urgency, 1, 5);
|
|
17311
17417
|
this.validateInteger("maxRetries", data.maxRetries, 0, 1e3);
|
|
@@ -17326,12 +17432,14 @@ var TaskService = class {
|
|
|
17326
17432
|
isNull(tasks3.retryAfter),
|
|
17327
17433
|
sql`${tasks3.retryAfter} <= ${nowMs}`
|
|
17328
17434
|
);
|
|
17329
|
-
const
|
|
17435
|
+
const excludedBatchIds = [...new Set((scope.excludedBatchIds ?? []).map((batchId) => normalizeTaskBatchId(batchId)).filter((batchId) => Boolean(batchId)))];
|
|
17436
|
+
const hasExcludedBatches = excludedBatchIds.length > 0;
|
|
17330
17437
|
let batchFilter;
|
|
17331
17438
|
if (hasExcludedBatches) {
|
|
17332
17439
|
batchFilter = or(
|
|
17333
17440
|
isNull(tasks3.batchId),
|
|
17334
|
-
sql
|
|
17441
|
+
sql`trim(${tasks3.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ''`,
|
|
17442
|
+
sql`trim(${tasks3.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) NOT IN ${excludedBatchIds}`
|
|
17335
17443
|
);
|
|
17336
17444
|
}
|
|
17337
17445
|
const statusConditions = or(
|
|
@@ -17365,9 +17473,11 @@ var TaskService = class {
|
|
|
17365
17473
|
),
|
|
17366
17474
|
or(
|
|
17367
17475
|
isNull(tasks3.batchId),
|
|
17476
|
+
sql`trim(${tasks3.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ''`,
|
|
17368
17477
|
sql`NOT EXISTS (
|
|
17369
17478
|
SELECT 1 FROM tasks AS running_batch_task
|
|
17370
|
-
WHERE running_batch_task.batch_id
|
|
17479
|
+
WHERE trim(running_batch_task.batch_id, ${TASK_BATCH_TRIM_CHARACTERS})
|
|
17480
|
+
= trim(${tasks3.batchId}, ${TASK_BATCH_TRIM_CHARACTERS})
|
|
17371
17481
|
AND (
|
|
17372
17482
|
running_batch_task.status = 'running'
|
|
17373
17483
|
OR EXISTS (
|
|
@@ -17391,12 +17501,18 @@ var TaskService = class {
|
|
|
17391
17501
|
).limit(1);
|
|
17392
17502
|
return result[0] ?? null;
|
|
17393
17503
|
}
|
|
17394
|
-
static async countRunning() {
|
|
17504
|
+
static async countRunning(scope = {}) {
|
|
17505
|
+
const scopeConditions = scope.legacyCwd ? [sql`${tasks3.cwd} IS NULL OR trim(${tasks3.cwd}) = ''`] : this.buildScopeWhere(scope);
|
|
17506
|
+
if (scope.batchId !== void 0) {
|
|
17507
|
+
const batchId = normalizeTaskBatchId(scope.batchId);
|
|
17508
|
+
scopeConditions.push(batchId ? sql`trim(${tasks3.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ${batchId}` : sql`0`);
|
|
17509
|
+
}
|
|
17395
17510
|
return db.transaction((tx) => {
|
|
17396
|
-
const runningTasks = tx.select({ count: sql`count(*)` }).from(tasks3).where(eq(tasks3.status, "running")).get();
|
|
17511
|
+
const runningTasks = tx.select({ count: sql`count(*)` }).from(tasks3).where(and(eq(tasks3.status, "running"), ...scopeConditions)).get();
|
|
17397
17512
|
const runsWithoutRunningTask = tx.select({ count: sql`count(DISTINCT ${taskRuns3.taskId})` }).from(taskRuns3).innerJoin(tasks3, eq(tasks3.id, taskRuns3.taskId)).where(and(
|
|
17398
17513
|
eq(taskRuns3.status, "running"),
|
|
17399
|
-
sql`${tasks3.status} <> 'running'
|
|
17514
|
+
sql`${tasks3.status} <> 'running'`,
|
|
17515
|
+
...scopeConditions
|
|
17400
17516
|
)).get();
|
|
17401
17517
|
return Number(runningTasks?.count ?? 0) + Number(runsWithoutRunningTask?.count ?? 0);
|
|
17402
17518
|
}, { behavior: "deferred" });
|
|
@@ -17729,15 +17845,17 @@ var TaskService = class {
|
|
|
17729
17845
|
}).where(and(...conditions, hasViableDependency())).returning().get() ?? null, { behavior: "immediate" });
|
|
17730
17846
|
}
|
|
17731
17847
|
static async retryBatch(batchId, scope = {}) {
|
|
17848
|
+
const normalizedBatchId = normalizeTaskBatchId(batchId);
|
|
17849
|
+
if (!normalizedBatchId) return 0;
|
|
17732
17850
|
const sqlite2 = getSqlite();
|
|
17733
17851
|
const scopeFilter = scope.cwd === void 0 ? "" : "AND candidate.cwd = ?";
|
|
17734
|
-
const parameters = scope.cwd === void 0 ? [
|
|
17852
|
+
const parameters = scope.cwd === void 0 ? [TASK_BATCH_TRIM_CHARACTERS, normalizedBatchId] : [TASK_BATCH_TRIM_CHARACTERS, normalizedBatchId, scope.cwd];
|
|
17735
17853
|
return db.transaction(() => sqlite2.query(`
|
|
17736
17854
|
WITH RECURSIVE
|
|
17737
17855
|
candidate(id, cwd, depends_on) AS MATERIALIZED (
|
|
17738
17856
|
SELECT candidate.id, candidate.cwd, candidate.depends_on
|
|
17739
17857
|
FROM tasks AS candidate
|
|
17740
|
-
WHERE candidate.batch_id = ?
|
|
17858
|
+
WHERE trim(candidate.batch_id, ?) = ?
|
|
17741
17859
|
AND candidate.status IN ('failed', 'dead_letter')
|
|
17742
17860
|
${scopeFilter}
|
|
17743
17861
|
),
|
|
@@ -17791,16 +17909,28 @@ var TaskService = class {
|
|
|
17791
17909
|
static async list(options = {}) {
|
|
17792
17910
|
let query = db.select().from(tasks3).$dynamic();
|
|
17793
17911
|
const conditions = [];
|
|
17794
|
-
if (options.
|
|
17912
|
+
if (options.activeExecution) {
|
|
17913
|
+
conditions.push(or(
|
|
17914
|
+
eq(tasks3.status, "running"),
|
|
17915
|
+
sql`EXISTS (
|
|
17916
|
+
SELECT 1 FROM task_runs AS active_list_run
|
|
17917
|
+
WHERE active_list_run.task_id = ${tasks3.id}
|
|
17918
|
+
AND active_list_run.status = 'running'
|
|
17919
|
+
)`
|
|
17920
|
+
));
|
|
17921
|
+
} else if (options.status) {
|
|
17795
17922
|
conditions.push(eq(tasks3.status, options.status));
|
|
17796
17923
|
}
|
|
17797
|
-
if (options.batchId) {
|
|
17798
|
-
|
|
17924
|
+
if (options.batchId !== void 0) {
|
|
17925
|
+
const batchId = normalizeTaskBatchId(options.batchId);
|
|
17926
|
+
conditions.push(batchId ? sql`trim(${tasks3.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ${batchId}` : sql`0`);
|
|
17799
17927
|
}
|
|
17800
17928
|
if (options.category) {
|
|
17801
17929
|
conditions.push(eq(tasks3.category, options.category));
|
|
17802
17930
|
}
|
|
17803
|
-
if (options.
|
|
17931
|
+
if (options.legacyCwd) {
|
|
17932
|
+
conditions.push(sql`${tasks3.cwd} IS NULL OR trim(${tasks3.cwd}) = ''`);
|
|
17933
|
+
} else if (options.cwd !== void 0) {
|
|
17804
17934
|
conditions.push(eq(tasks3.cwd, options.cwd));
|
|
17805
17935
|
}
|
|
17806
17936
|
if (conditions.length > 0) {
|
|
@@ -17818,9 +17948,12 @@ var TaskService = class {
|
|
|
17818
17948
|
static async stats(options = {}) {
|
|
17819
17949
|
const conditions = [];
|
|
17820
17950
|
if (options.batchId !== void 0) {
|
|
17821
|
-
|
|
17951
|
+
const batchId = normalizeTaskBatchId(options.batchId);
|
|
17952
|
+
conditions.push(batchId ? sql`trim(${tasks3.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ${batchId}` : sql`0`);
|
|
17822
17953
|
}
|
|
17823
|
-
if (options.
|
|
17954
|
+
if (options.legacyCwd) {
|
|
17955
|
+
conditions.push(sql`${tasks3.cwd} IS NULL OR trim(${tasks3.cwd}) = ''`);
|
|
17956
|
+
} else if (options.cwd !== void 0) {
|
|
17824
17957
|
conditions.push(eq(tasks3.cwd, options.cwd));
|
|
17825
17958
|
}
|
|
17826
17959
|
const whereCondition = conditions.length > 0 ? and(...conditions) : void 0;
|
|
@@ -17845,6 +17978,33 @@ var TaskService = class {
|
|
|
17845
17978
|
}
|
|
17846
17979
|
return stats;
|
|
17847
17980
|
}
|
|
17981
|
+
static async projectSummaries(limit = 100) {
|
|
17982
|
+
this.validateInteger("limit", limit, 1, 1e3);
|
|
17983
|
+
const lastCreatedAt = sql`max(${tasks3.createdAt})`;
|
|
17984
|
+
const lastTaskId = sql`max(${tasks3.id})`;
|
|
17985
|
+
const rows = await db.select({
|
|
17986
|
+
cwd: tasks3.cwd,
|
|
17987
|
+
total: sql`count(*)`,
|
|
17988
|
+
pending: sql`sum(CASE WHEN ${tasks3.status} = 'pending' THEN 1 ELSE 0 END)`,
|
|
17989
|
+
running: sql`sum(CASE WHEN ${tasks3.status} = 'running' OR EXISTS (
|
|
17990
|
+
SELECT 1 FROM task_runs AS active_project_run
|
|
17991
|
+
WHERE active_project_run.task_id = ${tasks3.id}
|
|
17992
|
+
AND active_project_run.status = 'running'
|
|
17993
|
+
) THEN 1 ELSE 0 END)`,
|
|
17994
|
+
failed: sql`sum(CASE WHEN ${tasks3.status} IN ('failed', 'dead_letter') THEN 1 ELSE 0 END)`,
|
|
17995
|
+
done: sql`sum(CASE WHEN ${tasks3.status} = 'done' THEN 1 ELSE 0 END)`,
|
|
17996
|
+
lastCreatedAt
|
|
17997
|
+
}).from(tasks3).where(sql`${tasks3.cwd} IS NOT NULL AND trim(${tasks3.cwd}) <> ''`).groupBy(tasks3.cwd).orderBy(desc(lastCreatedAt), desc(lastTaskId)).limit(limit);
|
|
17998
|
+
return rows.flatMap((row) => row.cwd === null ? [] : [{
|
|
17999
|
+
cwd: row.cwd,
|
|
18000
|
+
total: Number(row.total),
|
|
18001
|
+
pending: Number(row.pending),
|
|
18002
|
+
running: Number(row.running),
|
|
18003
|
+
failed: Number(row.failed),
|
|
18004
|
+
done: Number(row.done),
|
|
18005
|
+
lastCreatedAt: row.lastCreatedAt === null ? null : Number(row.lastCreatedAt) * 1e3
|
|
18006
|
+
}]);
|
|
18007
|
+
}
|
|
17848
18008
|
static async delete(id, scope = {}) {
|
|
17849
18009
|
const conditions = [
|
|
17850
18010
|
eq(tasks3.id, id),
|
|
@@ -17997,24 +18157,29 @@ function isValidCronExpr(expr) {
|
|
|
17997
18157
|
var { taskTemplates: taskTemplates2 } = schema_exports;
|
|
17998
18158
|
var TaskTemplateService = class {
|
|
17999
18159
|
static async create(data) {
|
|
18000
|
-
|
|
18160
|
+
const normalizedData = {
|
|
18161
|
+
...data,
|
|
18162
|
+
batchId: normalizeTaskBatchId(data.batchId)
|
|
18163
|
+
};
|
|
18164
|
+
this.validate(normalizedData);
|
|
18001
18165
|
const now = Date.now();
|
|
18002
|
-
const nextRunAt =
|
|
18003
|
-
|
|
18166
|
+
const nextRunAt = normalizedData.nextRunAt ?? this.calculateNextRunAt(
|
|
18167
|
+
normalizedData.scheduleType,
|
|
18004
18168
|
{
|
|
18005
|
-
cronExpr:
|
|
18006
|
-
intervalMs:
|
|
18007
|
-
runAt:
|
|
18169
|
+
cronExpr: normalizedData.cronExpr ?? null,
|
|
18170
|
+
intervalMs: normalizedData.intervalMs ?? null,
|
|
18171
|
+
runAt: normalizedData.runAt ?? null
|
|
18008
18172
|
},
|
|
18009
18173
|
now
|
|
18010
18174
|
);
|
|
18011
|
-
const result = await db.insert(taskTemplates2).values({ ...
|
|
18175
|
+
const result = await db.insert(taskTemplates2).values({ ...normalizedData, nextRunAt, createdAt: now, updatedAt: now }).returning();
|
|
18012
18176
|
return result[0];
|
|
18013
18177
|
}
|
|
18014
18178
|
static validate(data) {
|
|
18015
18179
|
if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
18016
18180
|
if (!data.agent.trim()) throw new Error("agent \u4E0D\u80FD\u4E3A\u7A7A");
|
|
18017
18181
|
if (!data.prompt.trim()) throw new Error("prompt \u4E0D\u80FD\u4E3A\u7A7A");
|
|
18182
|
+
validateTaskWorkingDirectory(data.cwd);
|
|
18018
18183
|
const scheduleType = data.scheduleType;
|
|
18019
18184
|
if (!["cron", "delayed", "recurring"].includes(scheduleType)) {
|
|
18020
18185
|
throw new Error("scheduleType \u5FC5\u987B\u662F cron\u3001delayed \u6216 recurring");
|
|
@@ -18041,13 +18206,40 @@ var TaskTemplateService = class {
|
|
|
18041
18206
|
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
18042
18207
|
}
|
|
18043
18208
|
}
|
|
18044
|
-
static async list(limit = 50) {
|
|
18045
|
-
return await db.select().from(taskTemplates2).orderBy(desc(taskTemplates2.createdAt), desc(taskTemplates2.id)).limit(limit);
|
|
18209
|
+
static async list(limit = 50, offset = 0) {
|
|
18210
|
+
return await db.select().from(taskTemplates2).orderBy(desc(taskTemplates2.createdAt), desc(taskTemplates2.id)).limit(limit).offset(offset);
|
|
18211
|
+
}
|
|
18212
|
+
static async stats() {
|
|
18213
|
+
const result = await db.select({
|
|
18214
|
+
total: sql`count(*)`,
|
|
18215
|
+
enabled: sql`sum(case when ${taskTemplates2.enabled} = 1 then 1 else 0 end)`
|
|
18216
|
+
}).from(taskTemplates2);
|
|
18217
|
+
const total = Number(result[0]?.total ?? 0);
|
|
18218
|
+
const enabled = Number(result[0]?.enabled ?? 0);
|
|
18219
|
+
return { total, enabled, disabled: total - enabled };
|
|
18046
18220
|
}
|
|
18047
18221
|
static async getById(id) {
|
|
18048
18222
|
const result = await db.select().from(taskTemplates2).where(eq(taskTemplates2.id, id));
|
|
18049
18223
|
return result[0] || null;
|
|
18050
18224
|
}
|
|
18225
|
+
static async update(id, data) {
|
|
18226
|
+
const normalizedData = {
|
|
18227
|
+
...data,
|
|
18228
|
+
batchId: normalizeTaskBatchId(data.batchId) ?? null
|
|
18229
|
+
};
|
|
18230
|
+
this.validate(normalizedData);
|
|
18231
|
+
const now = Date.now();
|
|
18232
|
+
const nextRunAt = this.calculateNextRunAt(
|
|
18233
|
+
normalizedData.scheduleType,
|
|
18234
|
+
normalizedData,
|
|
18235
|
+
now
|
|
18236
|
+
);
|
|
18237
|
+
return db.transaction((tx) => {
|
|
18238
|
+
const existing = tx.select({ id: taskTemplates2.id }).from(taskTemplates2).where(eq(taskTemplates2.id, id)).limit(1).get();
|
|
18239
|
+
if (!existing) return null;
|
|
18240
|
+
return tx.update(taskTemplates2).set({ ...normalizedData, nextRunAt, updatedAt: now }).where(eq(taskTemplates2.id, id)).returning().get() ?? null;
|
|
18241
|
+
}, { behavior: "immediate" });
|
|
18242
|
+
}
|
|
18051
18243
|
static async enable(id) {
|
|
18052
18244
|
return db.transaction((tx) => {
|
|
18053
18245
|
const template = tx.select().from(taskTemplates2).where(eq(taskTemplates2.id, id)).limit(1).get();
|
|
@@ -18329,37 +18521,40 @@ function createTaskFromTemplate(templateId, options) {
|
|
|
18329
18521
|
return db.transaction((tx) => {
|
|
18330
18522
|
const tmpl = tx.select().from(taskTemplates3).where(eq(taskTemplates3.id, templateId)).limit(1).get();
|
|
18331
18523
|
if (!tmpl || options.advanceSchedule && !tmpl.enabled) return null;
|
|
18332
|
-
|
|
18333
|
-
|
|
18334
|
-
sql
|
|
18335
|
-
|
|
18336
|
-
|
|
18337
|
-
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
18341
|
-
|
|
18342
|
-
|
|
18343
|
-
eq(schema_exports.tasks.
|
|
18344
|
-
|
|
18345
|
-
|
|
18346
|
-
|
|
18347
|
-
${schema_exports.tasks.status}
|
|
18348
|
-
|
|
18349
|
-
|
|
18350
|
-
|
|
18351
|
-
|
|
18352
|
-
|
|
18353
|
-
|
|
18354
|
-
|
|
18355
|
-
|
|
18356
|
-
|
|
18357
|
-
|
|
18358
|
-
|
|
18359
|
-
|
|
18360
|
-
|
|
18524
|
+
if (options.advanceSchedule && options.expectedNextRunAt !== void 0 && tmpl.nextRunAt !== options.expectedNextRunAt) return null;
|
|
18525
|
+
if (options.advanceSchedule) {
|
|
18526
|
+
const activeTasks = tx.select({ count: sql`count(*)` }).from(schema_exports.tasks).where(and(
|
|
18527
|
+
eq(schema_exports.tasks.templateId, templateId),
|
|
18528
|
+
sql`${schema_exports.tasks.status} IN ('pending', 'running')`
|
|
18529
|
+
)).get();
|
|
18530
|
+
const retryableTasks = tx.select({ count: sql`count(*)` }).from(schema_exports.tasks).where(and(
|
|
18531
|
+
eq(schema_exports.tasks.templateId, templateId),
|
|
18532
|
+
eq(schema_exports.tasks.status, "failed"),
|
|
18533
|
+
sql`${schema_exports.tasks.retryCount} <= ${schema_exports.tasks.maxRetries}`
|
|
18534
|
+
)).get();
|
|
18535
|
+
const detachedRuns = tx.select({ count: sql`count(DISTINCT ${schema_exports.taskRuns.taskId})` }).from(schema_exports.taskRuns).innerJoin(schema_exports.tasks, eq(schema_exports.tasks.id, schema_exports.taskRuns.taskId)).where(and(
|
|
18536
|
+
eq(schema_exports.taskRuns.status, "running"),
|
|
18537
|
+
eq(schema_exports.tasks.templateId, templateId),
|
|
18538
|
+
sql`NOT (
|
|
18539
|
+
${schema_exports.tasks.status} IN ('pending', 'running')
|
|
18540
|
+
OR (
|
|
18541
|
+
${schema_exports.tasks.status} = 'failed'
|
|
18542
|
+
AND ${schema_exports.tasks.retryCount} <= ${schema_exports.tasks.maxRetries}
|
|
18543
|
+
)
|
|
18544
|
+
)`
|
|
18545
|
+
)).get();
|
|
18546
|
+
const activeCount = Number(activeTasks?.count ?? 0) + Number(retryableTasks?.count ?? 0) + Number(detachedRuns?.count ?? 0);
|
|
18547
|
+
if (activeCount >= (tmpl.maxInstances ?? 1)) {
|
|
18548
|
+
if (tmpl.scheduleType !== "delayed") {
|
|
18549
|
+
const nextRunAt2 = TaskTemplateService.calculateNextRunAt(
|
|
18550
|
+
tmpl.scheduleType,
|
|
18551
|
+
tmpl,
|
|
18552
|
+
nowMs
|
|
18553
|
+
);
|
|
18554
|
+
tx.update(taskTemplates3).set({ nextRunAt: nextRunAt2, updatedAt: nowMs }).where(and(eq(taskTemplates3.id, templateId), eq(taskTemplates3.enabled, true))).run();
|
|
18555
|
+
}
|
|
18556
|
+
return null;
|
|
18361
18557
|
}
|
|
18362
|
-
return null;
|
|
18363
18558
|
}
|
|
18364
18559
|
const isDelayed = tmpl.scheduleType === "delayed";
|
|
18365
18560
|
const nextRunAt = isDelayed ? null : TaskTemplateService.calculateNextRunAt(
|
|
@@ -18376,7 +18571,7 @@ function createTaskFromTemplate(templateId, options) {
|
|
|
18376
18571
|
category: tmpl.category ?? "general",
|
|
18377
18572
|
importance: tmpl.importance ?? 3,
|
|
18378
18573
|
urgency: tmpl.urgency ?? 3,
|
|
18379
|
-
batchId: tmpl.batchId,
|
|
18574
|
+
batchId: normalizeTaskBatchId(tmpl.batchId),
|
|
18380
18575
|
maxRetries: tmpl.maxRetries ?? 3,
|
|
18381
18576
|
retryBackoffMs: tmpl.retryBackoffMs ?? 3e4,
|
|
18382
18577
|
timeoutMs: tmpl.timeoutMs,
|
|
@@ -18395,21 +18590,447 @@ function createTaskFromTemplate(templateId, options) {
|
|
|
18395
18590
|
}, { behavior: "immediate" });
|
|
18396
18591
|
}
|
|
18397
18592
|
|
|
18593
|
+
// src/daemon/pm2.ts
|
|
18594
|
+
import { execSync, spawnSync } from "child_process";
|
|
18595
|
+
import {
|
|
18596
|
+
accessSync,
|
|
18597
|
+
chmodSync as chmodSync2,
|
|
18598
|
+
constants,
|
|
18599
|
+
existsSync as existsSync4,
|
|
18600
|
+
mkdirSync as mkdirSync3,
|
|
18601
|
+
readFileSync as readFileSync2,
|
|
18602
|
+
rmSync,
|
|
18603
|
+
statSync as statSync3,
|
|
18604
|
+
writeFileSync as writeFileSync2
|
|
18605
|
+
} from "fs";
|
|
18606
|
+
import { homedir as homedir3, userInfo } from "os";
|
|
18607
|
+
import { delimiter, dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "path";
|
|
18608
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18609
|
+
import { Database as Database5 } from "bun:sqlite";
|
|
18610
|
+
|
|
18611
|
+
// src/daemon/management-lock.ts
|
|
18612
|
+
import { Database as Database4 } from "bun:sqlite";
|
|
18613
|
+
|
|
18614
|
+
// src/daemon/pm2.ts
|
|
18615
|
+
var __dirname = dirname3(fileURLToPath2(import.meta.url));
|
|
18616
|
+
var PROCESS_NAME = "supertask-gateway";
|
|
18617
|
+
var MAC_LAUNCH_AGENT_LABEL = "com.supertask.pm2-resurrect";
|
|
18618
|
+
var GATEWAY_LOCK_STALE_MS = 3e4;
|
|
18619
|
+
function runtimeHome(env) {
|
|
18620
|
+
return resolve2(env.HOME || homedir3());
|
|
18621
|
+
}
|
|
18622
|
+
function runtimePath(value, cwd) {
|
|
18623
|
+
return resolve2(cwd, value);
|
|
18624
|
+
}
|
|
18625
|
+
function versionFile(env = process.env, cwd = process.cwd()) {
|
|
18626
|
+
return env.SUPERTASK_VERSION_FILE ? runtimePath(env.SUPERTASK_VERSION_FILE, cwd) : join3(runtimeHome(env), ".local/share/opencode/supertask-gateway-version");
|
|
18627
|
+
}
|
|
18628
|
+
function getRunningVersion(env = process.env, cwd = process.cwd()) {
|
|
18629
|
+
try {
|
|
18630
|
+
const path = versionFile(env, cwd);
|
|
18631
|
+
if (!existsSync4(path)) return null;
|
|
18632
|
+
return readFileSync2(path, "utf-8").trim() || null;
|
|
18633
|
+
} catch {
|
|
18634
|
+
return null;
|
|
18635
|
+
}
|
|
18636
|
+
}
|
|
18637
|
+
function packageVersionFromGatewayEntry(gatewayEntry) {
|
|
18638
|
+
if (gatewayEntry === null) return null;
|
|
18639
|
+
let directory = dirname3(gatewayEntry);
|
|
18640
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
18641
|
+
const packagePath = join3(directory, "package.json");
|
|
18642
|
+
if (existsSync4(packagePath)) {
|
|
18643
|
+
try {
|
|
18644
|
+
const pkg = JSON.parse(readFileSync2(packagePath, "utf8"));
|
|
18645
|
+
if (pkg.name === "opencode-supertask" && typeof pkg.version === "string") {
|
|
18646
|
+
return pkg.version;
|
|
18647
|
+
}
|
|
18648
|
+
} catch {
|
|
18649
|
+
}
|
|
18650
|
+
}
|
|
18651
|
+
const parent = dirname3(directory);
|
|
18652
|
+
if (parent === directory) break;
|
|
18653
|
+
directory = parent;
|
|
18654
|
+
}
|
|
18655
|
+
return null;
|
|
18656
|
+
}
|
|
18657
|
+
function resolveRuntimeExecutable(command, env, cwd) {
|
|
18658
|
+
if (isAbsolute2(command) || command.includes("/")) return runtimePath(command, cwd);
|
|
18659
|
+
for (const entry of (env.PATH ?? "").split(delimiter)) {
|
|
18660
|
+
if (!entry) continue;
|
|
18661
|
+
const candidate = resolve2(cwd, entry, command);
|
|
18662
|
+
try {
|
|
18663
|
+
accessSync(candidate, constants.X_OK);
|
|
18664
|
+
return candidate;
|
|
18665
|
+
} catch {
|
|
18666
|
+
}
|
|
18667
|
+
}
|
|
18668
|
+
return command;
|
|
18669
|
+
}
|
|
18670
|
+
function runtimeScope(runtime) {
|
|
18671
|
+
const { cwd, env } = runtime;
|
|
18672
|
+
const home = runtimeHome(env);
|
|
18673
|
+
return {
|
|
18674
|
+
cwd: resolve2(cwd),
|
|
18675
|
+
databasePath: env.SUPERTASK_DB_PATH ? runtimePath(env.SUPERTASK_DB_PATH, cwd) : join3(home, ".local/share/opencode/tasks.db"),
|
|
18676
|
+
configPath: env.SUPERTASK_CONFIG_PATH ? runtimePath(env.SUPERTASK_CONFIG_PATH, cwd) : join3(home, ".config/opencode/supertask.json"),
|
|
18677
|
+
opencodePath: resolveRuntimeExecutable(env.SUPERTASK_OPENCODE_BIN ?? "opencode", env, cwd),
|
|
18678
|
+
home,
|
|
18679
|
+
pm2Home: env.PM2_HOME ? runtimePath(env.PM2_HOME, cwd) : join3(home, ".pm2"),
|
|
18680
|
+
managementLockPath: canonicalManagementLockPath(env, cwd)
|
|
18681
|
+
};
|
|
18682
|
+
}
|
|
18683
|
+
function scopesMatch(left, right) {
|
|
18684
|
+
return left.databasePath === right.databasePath && left.configPath === right.configPath && left.opencodePath === right.opencodePath && left.home === right.home && left.pm2Home === right.pm2Home && left.managementLockPath === right.managementLockPath;
|
|
18685
|
+
}
|
|
18686
|
+
function currentScope() {
|
|
18687
|
+
return runtimeScope({ cwd: process.cwd(), env: process.env });
|
|
18688
|
+
}
|
|
18689
|
+
function getGatewayDiagnostic() {
|
|
18690
|
+
const producerScope = currentScope();
|
|
18691
|
+
if (!isPm2Installed()) {
|
|
18692
|
+
return {
|
|
18693
|
+
pm2Installed: false,
|
|
18694
|
+
processFound: false,
|
|
18695
|
+
status: null,
|
|
18696
|
+
pid: null,
|
|
18697
|
+
ready: false,
|
|
18698
|
+
runningVersion: getRunningVersion(),
|
|
18699
|
+
gatewayEntry: null,
|
|
18700
|
+
gatewayPackageVersion: null,
|
|
18701
|
+
logRotationInstalled: false,
|
|
18702
|
+
startupConfigured: process.platform === "darwin" || process.platform === "linux" ? false : null,
|
|
18703
|
+
currentScope: producerScope,
|
|
18704
|
+
gatewayScope: null,
|
|
18705
|
+
scopeMatches: false
|
|
18706
|
+
};
|
|
18707
|
+
}
|
|
18708
|
+
const processes = pm2JsonList();
|
|
18709
|
+
const gateway = processes.find((item) => item.name === PROCESS_NAME);
|
|
18710
|
+
const runtime = gatewayRuntimeFromProcess(gateway);
|
|
18711
|
+
const gatewayEnv = runtime?.env ?? process.env;
|
|
18712
|
+
const managedScope = runtime ? runtimeScope(runtime) : null;
|
|
18713
|
+
const pid = typeof gateway?.pid === "number" ? gateway.pid : null;
|
|
18714
|
+
const readyPath = managedScope?.databasePath ?? databasePath();
|
|
18715
|
+
const lockedVersion = pid == null ? void 0 : gatewayVersionFromLock(pid, readyPath);
|
|
18716
|
+
const gatewayEntry = runtime?.gatewayEntry ?? null;
|
|
18717
|
+
return {
|
|
18718
|
+
pm2Installed: true,
|
|
18719
|
+
processFound: gateway != null,
|
|
18720
|
+
status: gateway?.pm2_env?.status ?? null,
|
|
18721
|
+
pid,
|
|
18722
|
+
ready: pid != null && isGatewayReady(pid, readyPath),
|
|
18723
|
+
runningVersion: lockedVersion === void 0 ? getRunningVersion(gatewayEnv, runtime?.cwd) : lockedVersion,
|
|
18724
|
+
gatewayEntry,
|
|
18725
|
+
gatewayPackageVersion: packageVersionFromGatewayEntry(gatewayEntry),
|
|
18726
|
+
logRotationInstalled: processes.some((item) => item.name === "pm2-logrotate" && item.pm2_env?.status === "online"),
|
|
18727
|
+
startupConfigured: process.platform === "darwin" ? isMacLaunchAgentConfigured(
|
|
18728
|
+
gatewayEnv.PM2_HOME ?? join3(runtimeHome(gatewayEnv), ".pm2"),
|
|
18729
|
+
runtime ?? void 0
|
|
18730
|
+
) : process.platform === "linux" ? isLinuxStartupConfigured(gatewayEnv, runtime?.cwd, runtime ?? void 0) : null,
|
|
18731
|
+
currentScope: producerScope,
|
|
18732
|
+
gatewayScope: managedScope,
|
|
18733
|
+
scopeMatches: managedScope != null && scopesMatch(producerScope, managedScope)
|
|
18734
|
+
};
|
|
18735
|
+
}
|
|
18736
|
+
function pm2Bin(env = process.env) {
|
|
18737
|
+
return env.SUPERTASK_PM2_BIN ?? (process.platform === "win32" ? "pm2.cmd" : "pm2");
|
|
18738
|
+
}
|
|
18739
|
+
function pm2CommandTimeoutMs(env = process.env) {
|
|
18740
|
+
const configured = Number(env.SUPERTASK_PM2_COMMAND_TIMEOUT_MS ?? 15e3);
|
|
18741
|
+
return Number.isFinite(configured) && configured > 0 ? configured : 15e3;
|
|
18742
|
+
}
|
|
18743
|
+
function resolvePm2Bin() {
|
|
18744
|
+
const configured = pm2Bin();
|
|
18745
|
+
if (isAbsolute2(configured)) return configured;
|
|
18746
|
+
const result = spawnSync("which", [configured], { encoding: "utf8" });
|
|
18747
|
+
const resolved = result.status === 0 ? result.stdout.trim().split("\n")[0] : "";
|
|
18748
|
+
if (!resolved) throw new Error(`[supertask] \u65E0\u6CD5\u89E3\u6790 pm2 \u53EF\u6267\u884C\u6587\u4EF6: ${configured}`);
|
|
18749
|
+
return resolved;
|
|
18750
|
+
}
|
|
18751
|
+
function launchAgentPath() {
|
|
18752
|
+
return process.env.SUPERTASK_LAUNCH_AGENT_PATH ?? join3(runtimeHome(process.env), "Library/LaunchAgents", `${MAC_LAUNCH_AGENT_LABEL}.plist`);
|
|
18753
|
+
}
|
|
18754
|
+
function launchctlBin() {
|
|
18755
|
+
return process.env.SUPERTASK_LAUNCHCTL_BIN ?? "launchctl";
|
|
18756
|
+
}
|
|
18757
|
+
function xmlUnescape(value) {
|
|
18758
|
+
return value.replaceAll("'", "'").replaceAll(""", '"').replaceAll(">", ">").replaceAll("<", "<").replaceAll("&", "&");
|
|
18759
|
+
}
|
|
18760
|
+
function plistValue(contents, key) {
|
|
18761
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18762
|
+
const match2 = contents.match(new RegExp(`<key>\\s*${escapedKey}\\s*</key>\\s*<string>([^<]*)</string>`));
|
|
18763
|
+
return match2?.[1] ? xmlUnescape(match2[1]) : null;
|
|
18764
|
+
}
|
|
18765
|
+
function isMacLaunchAgentConfigured(expectedPm2Home, expectedRuntime) {
|
|
18766
|
+
if (typeof process.getuid !== "function") return false;
|
|
18767
|
+
const plistPath = launchAgentPath();
|
|
18768
|
+
if (!existsSync4(plistPath)) return false;
|
|
18769
|
+
try {
|
|
18770
|
+
const contents = readFileSync2(plistPath, "utf8");
|
|
18771
|
+
const argumentsBlock = contents.match(
|
|
18772
|
+
/<key>\s*ProgramArguments\s*<\/key>\s*<array>([\s\S]*?)<\/array>/
|
|
18773
|
+
)?.[1];
|
|
18774
|
+
const programArguments = argumentsBlock ? [...argumentsBlock.matchAll(/<string>([^<]*)<\/string>/g)].map((match2) => xmlUnescape(match2[1])) : [];
|
|
18775
|
+
const bunPath = programArguments[0];
|
|
18776
|
+
const supervisorEntry = programArguments[1];
|
|
18777
|
+
const pm2Path = programArguments[2];
|
|
18778
|
+
const pm2Home = plistValue(contents, "PM2_HOME");
|
|
18779
|
+
const managementLock = plistValue(contents, "SUPERTASK_PM2_MANAGEMENT_LOCK");
|
|
18780
|
+
if (!bunPath || !supervisorEntry || !pm2Path || !pm2Home || !managementLock) return false;
|
|
18781
|
+
if (expectedPm2Home && pm2Home !== expectedPm2Home) return false;
|
|
18782
|
+
const expectedManagementLock = expectedRuntime ? managementLockPath(expectedRuntime.env, expectedRuntime.cwd) : process.env.SUPERTASK_PM2_MANAGEMENT_LOCK ? managementLockPath() : join3(expectedPm2Home ?? currentScope().pm2Home, "supertask-gateway.manage.sqlite");
|
|
18783
|
+
if (managementLock !== expectedManagementLock) return false;
|
|
18784
|
+
accessSync(bunPath, constants.X_OK);
|
|
18785
|
+
accessSync(supervisorEntry, constants.R_OK);
|
|
18786
|
+
accessSync(pm2Path, constants.X_OK);
|
|
18787
|
+
if (spawnSync(bunPath, ["--version"], {
|
|
18788
|
+
stdio: "ignore",
|
|
18789
|
+
timeout: pm2CommandTimeoutMs(),
|
|
18790
|
+
killSignal: "SIGKILL"
|
|
18791
|
+
}).status !== 0) return false;
|
|
18792
|
+
if (spawnSync(pm2Path, ["--version"], {
|
|
18793
|
+
stdio: "ignore",
|
|
18794
|
+
env: { ...process.env, PM2_HOME: pm2Home },
|
|
18795
|
+
timeout: pm2CommandTimeoutMs(),
|
|
18796
|
+
killSignal: "SIGKILL"
|
|
18797
|
+
}).status !== 0) return false;
|
|
18798
|
+
const loaded = spawnSync(
|
|
18799
|
+
launchctlBin(),
|
|
18800
|
+
["print", `gui/${process.getuid()}/${MAC_LAUNCH_AGENT_LABEL}`],
|
|
18801
|
+
{
|
|
18802
|
+
encoding: "utf8",
|
|
18803
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
18804
|
+
timeout: pm2CommandTimeoutMs(),
|
|
18805
|
+
killSignal: "SIGKILL"
|
|
18806
|
+
}
|
|
18807
|
+
);
|
|
18808
|
+
if (loaded.status !== 0) return false;
|
|
18809
|
+
if (!loaded.stdout.includes("state = running")) return false;
|
|
18810
|
+
if (!loaded.stdout.includes(`path = ${plistPath}`)) return false;
|
|
18811
|
+
if (!loaded.stdout.includes(`program = ${bunPath}`)) return false;
|
|
18812
|
+
const dumpPath = join3(pm2Home, "dump.pm2");
|
|
18813
|
+
const dump = JSON.parse(readFileSync2(dumpPath, "utf8"));
|
|
18814
|
+
if (!Array.isArray(dump)) return false;
|
|
18815
|
+
const gateway = dump.find((item) => item.name === PROCESS_NAME);
|
|
18816
|
+
const dumpRuntime = gatewayRuntimeFromProcess(gateway);
|
|
18817
|
+
if (!dumpRuntime || !hasRestorableSavedGatewayRuntime(gateway)) return false;
|
|
18818
|
+
return expectedRuntime === void 0 || dumpRuntime.gatewayEntry === expectedRuntime.gatewayEntry && dumpRuntime.bunPath === expectedRuntime.bunPath && dumpRuntime.cwd === expectedRuntime.cwd && scopesMatch(runtimeScope(dumpRuntime), runtimeScope(expectedRuntime));
|
|
18819
|
+
} catch {
|
|
18820
|
+
return false;
|
|
18821
|
+
}
|
|
18822
|
+
}
|
|
18823
|
+
function systemctlBin(env = process.env) {
|
|
18824
|
+
return env.SUPERTASK_SYSTEMCTL_BIN ?? "systemctl";
|
|
18825
|
+
}
|
|
18826
|
+
function isLinuxStartupConfigured(env = process.env, cwd = process.cwd(), expectedRuntime) {
|
|
18827
|
+
const unit = env.SUPERTASK_PM2_SYSTEMD_UNIT ?? `pm2-${userInfo().username}.service`;
|
|
18828
|
+
const enabled = spawnSync(systemctlBin(env), ["is-enabled", unit], {
|
|
18829
|
+
encoding: "utf8",
|
|
18830
|
+
env,
|
|
18831
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
18832
|
+
timeout: pm2CommandTimeoutMs(env),
|
|
18833
|
+
killSignal: "SIGKILL"
|
|
18834
|
+
});
|
|
18835
|
+
if (enabled.status !== 0 || enabled.stdout.trim() !== "enabled") return false;
|
|
18836
|
+
const contents = spawnSync(systemctlBin(env), ["cat", unit], {
|
|
18837
|
+
encoding: "utf8",
|
|
18838
|
+
env,
|
|
18839
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
18840
|
+
timeout: pm2CommandTimeoutMs(env),
|
|
18841
|
+
killSignal: "SIGKILL"
|
|
18842
|
+
});
|
|
18843
|
+
if (contents.status !== 0) return false;
|
|
18844
|
+
const expectedPm2Home = env.PM2_HOME ?? join3(runtimeHome(env), ".pm2");
|
|
18845
|
+
if (!contents.stdout.includes("resurrect") || !contents.stdout.includes(expectedPm2Home)) {
|
|
18846
|
+
return false;
|
|
18847
|
+
}
|
|
18848
|
+
try {
|
|
18849
|
+
const dump = JSON.parse(readFileSync2(join3(expectedPm2Home, "dump.pm2"), "utf8"));
|
|
18850
|
+
if (!Array.isArray(dump)) return false;
|
|
18851
|
+
const gateway = dump.find((item) => item.name === PROCESS_NAME);
|
|
18852
|
+
const runtime = gatewayRuntimeFromProcess(gateway);
|
|
18853
|
+
if (!runtime || !hasRestorableSavedGatewayRuntime(gateway)) return false;
|
|
18854
|
+
return runtime.cwd === resolve2(cwd) && scopesMatch(runtimeScope(runtime), runtimeScope({ cwd, env })) && (expectedRuntime === void 0 || runtime.gatewayEntry === expectedRuntime.gatewayEntry && runtime.bunPath === expectedRuntime.bunPath);
|
|
18855
|
+
} catch {
|
|
18856
|
+
return false;
|
|
18857
|
+
}
|
|
18858
|
+
}
|
|
18859
|
+
function isPm2Installed(env = process.env) {
|
|
18860
|
+
const result = spawnSync(pm2Bin(env), ["--version"], {
|
|
18861
|
+
stdio: "ignore",
|
|
18862
|
+
env,
|
|
18863
|
+
shell: process.platform === "win32",
|
|
18864
|
+
timeout: pm2CommandTimeoutMs(env),
|
|
18865
|
+
killSignal: "SIGKILL"
|
|
18866
|
+
});
|
|
18867
|
+
return result.status === 0;
|
|
18868
|
+
}
|
|
18869
|
+
function resolveCommand(command) {
|
|
18870
|
+
const lookup = process.platform === "win32" ? "where" : "which";
|
|
18871
|
+
const result = spawnSync(lookup, [command], { encoding: "utf8" });
|
|
18872
|
+
return result.status === 0 ? result.stdout.trim().split("\n")[0] || null : null;
|
|
18873
|
+
}
|
|
18874
|
+
function npmPreferredEnvironment() {
|
|
18875
|
+
if (process.platform === "win32") return null;
|
|
18876
|
+
const npm = resolveCommand("npm");
|
|
18877
|
+
const node = resolveCommand("node");
|
|
18878
|
+
if (!npm || !node) return null;
|
|
18879
|
+
const command = resolvePm2Bin();
|
|
18880
|
+
const path = [.../* @__PURE__ */ new Set([
|
|
18881
|
+
dirname3(command),
|
|
18882
|
+
dirname3(npm),
|
|
18883
|
+
dirname3(node),
|
|
18884
|
+
"/usr/local/bin",
|
|
18885
|
+
"/usr/bin",
|
|
18886
|
+
"/bin"
|
|
18887
|
+
])].join(delimiter);
|
|
18888
|
+
return { command, env: { ...process.env, PATH: path } };
|
|
18889
|
+
}
|
|
18890
|
+
function pm2Exec(args, options = {}) {
|
|
18891
|
+
const npmEnvironment = options.preferNpm ? npmPreferredEnvironment() : null;
|
|
18892
|
+
const effectiveEnv = options.env ?? npmEnvironment?.env ?? process.env;
|
|
18893
|
+
const result = spawnSync(npmEnvironment?.command ?? pm2Bin(effectiveEnv), args, {
|
|
18894
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
18895
|
+
encoding: "utf-8",
|
|
18896
|
+
env: effectiveEnv,
|
|
18897
|
+
shell: process.platform === "win32",
|
|
18898
|
+
timeout: options.timeoutMs ?? pm2CommandTimeoutMs(effectiveEnv),
|
|
18899
|
+
killSignal: "SIGKILL"
|
|
18900
|
+
});
|
|
18901
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
18902
|
+
if (result.error) return { ok: false, output: result.error.message };
|
|
18903
|
+
return { ok: result.status === 0, output };
|
|
18904
|
+
}
|
|
18905
|
+
function requirePm2(args, action, options = {}) {
|
|
18906
|
+
const result = pm2Exec(args, options);
|
|
18907
|
+
if (!result.ok) throw new Error(`[supertask] ${action} failed: ${result.output || "unknown pm2 error"}`);
|
|
18908
|
+
return result.output;
|
|
18909
|
+
}
|
|
18910
|
+
function pm2JsonList(env = process.env) {
|
|
18911
|
+
const output = requirePm2(["jlist"], "pm2 jlist", { env });
|
|
18912
|
+
try {
|
|
18913
|
+
const parsed = JSON.parse(output);
|
|
18914
|
+
if (!Array.isArray(parsed)) throw new Error("result is not an array");
|
|
18915
|
+
return parsed;
|
|
18916
|
+
} catch (error) {
|
|
18917
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
18918
|
+
throw new Error(`[supertask] Invalid pm2 jlist output: ${message}`);
|
|
18919
|
+
}
|
|
18920
|
+
}
|
|
18921
|
+
function gatewayEntryFromProcess(processInfo) {
|
|
18922
|
+
const args = processInfo?.pm2_env?.args ?? processInfo?.args;
|
|
18923
|
+
const candidates = Array.isArray(args) ? [...args].reverse() : typeof args === "string" ? [args] : [];
|
|
18924
|
+
const savedCwd = processInfo?.pm2_env?.pm_cwd ?? processInfo?.pm_cwd;
|
|
18925
|
+
for (const candidate of candidates) {
|
|
18926
|
+
const path = typeof savedCwd === "string" ? runtimePath(candidate, savedCwd) : candidate;
|
|
18927
|
+
if (existsSync4(path)) return resolve2(path);
|
|
18928
|
+
}
|
|
18929
|
+
return null;
|
|
18930
|
+
}
|
|
18931
|
+
function gatewayEnvironmentFromProcess(processInfo) {
|
|
18932
|
+
const saved = processInfo?.pm2_env?.env ?? processInfo?.env;
|
|
18933
|
+
if (!saved) return { ...process.env };
|
|
18934
|
+
const env = {};
|
|
18935
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
18936
|
+
if (typeof value === "string") env[key] = value;
|
|
18937
|
+
}
|
|
18938
|
+
return env;
|
|
18939
|
+
}
|
|
18940
|
+
function gatewayRuntimeFromProcess(processInfo) {
|
|
18941
|
+
const gatewayEntry = gatewayEntryFromProcess(processInfo);
|
|
18942
|
+
if (!gatewayEntry) return null;
|
|
18943
|
+
const savedBunPath = processInfo?.pm2_env?.pm_exec_path ?? processInfo?.pm_exec_path;
|
|
18944
|
+
const savedCwd = processInfo?.pm2_env?.pm_cwd ?? processInfo?.pm_cwd;
|
|
18945
|
+
const savedEnv = gatewayEnvironmentFromProcess(processInfo);
|
|
18946
|
+
if (typeof savedBunPath !== "string" || typeof savedCwd !== "string") return null;
|
|
18947
|
+
try {
|
|
18948
|
+
accessSync(savedBunPath, constants.X_OK);
|
|
18949
|
+
if (!statSync3(savedCwd).isDirectory()) return null;
|
|
18950
|
+
if (spawnSync(savedBunPath, ["--version"], {
|
|
18951
|
+
stdio: "ignore",
|
|
18952
|
+
env: savedEnv,
|
|
18953
|
+
timeout: pm2CommandTimeoutMs(savedEnv),
|
|
18954
|
+
killSignal: "SIGKILL"
|
|
18955
|
+
}).status !== 0) return null;
|
|
18956
|
+
} catch {
|
|
18957
|
+
return null;
|
|
18958
|
+
}
|
|
18959
|
+
const killTimeout = processInfo?.pm2_env?.kill_timeout ?? processInfo?.kill_timeout;
|
|
18960
|
+
return {
|
|
18961
|
+
gatewayEntry,
|
|
18962
|
+
bunPath: savedBunPath,
|
|
18963
|
+
cwd: resolve2(savedCwd),
|
|
18964
|
+
env: savedEnv,
|
|
18965
|
+
killTimeoutMs: Number.isInteger(killTimeout) && killTimeout >= 5e3 ? killTimeout : void 0
|
|
18966
|
+
};
|
|
18967
|
+
}
|
|
18968
|
+
function hasRestorableSavedGatewayRuntime(processInfo) {
|
|
18969
|
+
return gatewayRuntimeFromProcess(processInfo) !== null;
|
|
18970
|
+
}
|
|
18971
|
+
function databasePath(env = process.env, cwd = process.cwd()) {
|
|
18972
|
+
return env.SUPERTASK_DB_PATH ? runtimePath(env.SUPERTASK_DB_PATH, cwd) : join3(runtimeHome(env), ".local/share/opencode/tasks.db");
|
|
18973
|
+
}
|
|
18974
|
+
function isGatewayReady(expectedPid, path = databasePath()) {
|
|
18975
|
+
if (!existsSync4(path)) return false;
|
|
18976
|
+
let database = null;
|
|
18977
|
+
try {
|
|
18978
|
+
database = new Database5(path, { readonly: true });
|
|
18979
|
+
const row = database.query(
|
|
18980
|
+
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
18981
|
+
).get();
|
|
18982
|
+
if (!row || row.ready_at == null) return false;
|
|
18983
|
+
if (expectedPid !== void 0 && row.pid !== expectedPid) return false;
|
|
18984
|
+
const ageMs = Date.now() - row.heartbeat_at;
|
|
18985
|
+
return ageMs >= -5e3 && ageMs < GATEWAY_LOCK_STALE_MS;
|
|
18986
|
+
} catch {
|
|
18987
|
+
return false;
|
|
18988
|
+
} finally {
|
|
18989
|
+
database?.close();
|
|
18990
|
+
}
|
|
18991
|
+
}
|
|
18992
|
+
function gatewayVersionFromLock(expectedPid, path) {
|
|
18993
|
+
if (!existsSync4(path)) return void 0;
|
|
18994
|
+
let database = null;
|
|
18995
|
+
try {
|
|
18996
|
+
database = new Database5(path, { readonly: true });
|
|
18997
|
+
const row = database.query(
|
|
18998
|
+
"SELECT pid, version FROM gateway_lock WHERE id = 1"
|
|
18999
|
+
).get();
|
|
19000
|
+
if (!row || row.pid !== expectedPid) return void 0;
|
|
19001
|
+
return row.version;
|
|
19002
|
+
} catch {
|
|
19003
|
+
return void 0;
|
|
19004
|
+
} finally {
|
|
19005
|
+
database?.close();
|
|
19006
|
+
}
|
|
19007
|
+
}
|
|
19008
|
+
function managementLockPath(env = process.env, cwd = process.cwd()) {
|
|
19009
|
+
const override = env.SUPERTASK_PM2_MANAGEMENT_LOCK;
|
|
19010
|
+
if (override) return runtimePath(override, cwd);
|
|
19011
|
+
return join3(runtimeScope({ env, cwd }).pm2Home, "supertask-gateway.manage.sqlite");
|
|
19012
|
+
}
|
|
19013
|
+
function canonicalManagementLockPath(env = process.env, cwd = process.cwd()) {
|
|
19014
|
+
const home = runtimeHome(env);
|
|
19015
|
+
const pm2Home = env.PM2_HOME ? runtimePath(env.PM2_HOME, cwd) : join3(home, ".pm2");
|
|
19016
|
+
return join3(pm2Home, "supertask-gateway.manage.sqlite");
|
|
19017
|
+
}
|
|
19018
|
+
|
|
18398
19019
|
// src/web/ui.ts
|
|
18399
19020
|
var ZH = {
|
|
18400
19021
|
"app.name": "SuperTask",
|
|
18401
19022
|
"app.dashboard": "\u63A7\u5236\u53F0",
|
|
18402
|
-
"app.tagline": "\u53EF\u9760\u7684\u672C\u5730 Agent \
|
|
19023
|
+
"app.tagline": "\u53EF\u9760\u7684\u672C\u5730 Agent \u4EFB\u52A1\u4E2D\u5FC3",
|
|
18403
19024
|
"app.local": "\u672C\u5730\u8FD0\u884C",
|
|
18404
19025
|
"app.footer": "Local-first \xB7 \u6570\u636E\u7559\u5728\u4F60\u7684\u8BBE\u5907\u4E0A",
|
|
18405
19026
|
"nav.tasks": "\u4EFB\u52A1",
|
|
18406
|
-
"nav.templates": "\
|
|
19027
|
+
"nav.templates": "\u5B9A\u65F6\u4EFB\u52A1",
|
|
18407
19028
|
"nav.runs": "\u6267\u884C\u8BB0\u5F55",
|
|
18408
19029
|
"nav.system": "\u7CFB\u7EDF",
|
|
18409
19030
|
"page.tasks.title": "\u4EFB\u52A1\u961F\u5217",
|
|
18410
19031
|
"page.tasks.description": "\u67E5\u770B\u4F18\u5148\u7EA7\u3001\u6267\u884C\u72B6\u6001\u4E0E\u91CD\u8BD5\u60C5\u51B5\uFF0C\u5FEB\u901F\u5904\u7406\u9700\u8981\u5173\u6CE8\u7684\u4EFB\u52A1\u3002",
|
|
18411
|
-
"page.templates.title": "\
|
|
18412
|
-
"page.templates.description": "\
|
|
19032
|
+
"page.templates.title": "\u5B9A\u65F6\u4EFB\u52A1",
|
|
19033
|
+
"page.templates.description": "\u5728\u7F51\u9875\u521B\u5EFA\u548C\u7F16\u8F91 Cron\u3001\u56FA\u5B9A\u95F4\u9694\u53CA\u4E00\u6B21\u6027\u4EFB\u52A1\uFF0C\u5305\u62EC\u6A21\u578B\u3001Agent \u548C\u63D0\u793A\u8BCD\u3002",
|
|
18413
19034
|
"page.runs.title": "\u6267\u884C\u8BB0\u5F55",
|
|
18414
19035
|
"page.runs.description": "\u8FFD\u8E2A\u6BCF\u6B21 Agent \u6267\u884C\u7684\u72B6\u6001\u3001\u8017\u65F6\u3001\u5FC3\u8DF3\u4E0E\u8F93\u51FA\u3002",
|
|
18415
19036
|
"page.system.title": "\u7CFB\u7EDF\u8BBE\u7F6E",
|
|
@@ -18419,29 +19040,43 @@ var ZH = {
|
|
|
18419
19040
|
"action.retry": "\u91CD\u8BD5",
|
|
18420
19041
|
"action.cancel": "\u53D6\u6D88",
|
|
18421
19042
|
"action.delete": "\u5220\u9664",
|
|
19043
|
+
"action.edit": "\u7F16\u8F91",
|
|
18422
19044
|
"action.enable": "\u542F\u7528",
|
|
18423
19045
|
"action.disable": "\u7981\u7528",
|
|
18424
|
-
"action.trigger": "\u7ACB\u5373\
|
|
19046
|
+
"action.trigger": "\u7ACB\u5373\u8FD0\u884C\u4E00\u6B21",
|
|
19047
|
+
"action.continueSession": "\u7EE7\u7EED\u4F1A\u8BDD",
|
|
18425
19048
|
"action.logs": "\u67E5\u770B\u65E5\u5FD7",
|
|
18426
19049
|
"action.hideLogs": "\u6536\u8D77\u65E5\u5FD7",
|
|
18427
19050
|
"action.save": "\u4FDD\u5B58\u8BBE\u7F6E",
|
|
19051
|
+
"action.saveAndRestart": "\u4FDD\u5B58\u5E76\u91CD\u542F",
|
|
18428
19052
|
"action.copy": "\u590D\u5236 JSON",
|
|
18429
19053
|
"action.close": "\u5173\u95ED",
|
|
18430
19054
|
"action.confirm": "\u786E\u8BA4",
|
|
18431
19055
|
"action.clearDatabase": "\u6E05\u7A7A\u6570\u636E\u5E93",
|
|
19056
|
+
"action.createTask": "\u65B0\u5EFA\u4EFB\u52A1",
|
|
19057
|
+
"action.saveTask": "\u52A0\u5165\u961F\u5217",
|
|
19058
|
+
"action.updateTask": "\u4FDD\u5B58\u4FEE\u6539",
|
|
19059
|
+
"action.createTemplate": "\u65B0\u5EFA\u5B9A\u65F6\u4EFB\u52A1",
|
|
19060
|
+
"action.saveTemplate": "\u4FDD\u5B58\u5B9A\u65F6\u4EFB\u52A1",
|
|
18432
19061
|
"status.pending": "\u5F85\u6267\u884C",
|
|
18433
19062
|
"status.running": "\u8FD0\u884C\u4E2D",
|
|
18434
19063
|
"status.done": "\u5DF2\u5B8C\u6210",
|
|
18435
|
-
"status.failed": "\
|
|
18436
|
-
"status.dead_letter": "\
|
|
19064
|
+
"status.failed": "\u7B49\u5F85\u91CD\u8BD5",
|
|
19065
|
+
"status.dead_letter": "\u5DF2\u505C\u6B62",
|
|
19066
|
+
"status.deadLetterHint": "\u7CFB\u7EDF\u4E0D\u4F1A\u518D\u81EA\u52A8\u8FD0\u884C\u3002\u53EF\u80FD\u662F\u91CD\u8BD5\u6B21\u6570\u5DF2\u7528\u5B8C\uFF0C\u6216\u4F9D\u8D56\u4EFB\u52A1\u65E0\u6CD5\u7EE7\u7EED\uFF1B\u8BF7\u67E5\u770B\u5931\u8D25\u539F\u56E0\uFF0C\u786E\u8BA4\u540E\u624B\u52A8\u91CD\u8BD5\u3002",
|
|
19067
|
+
"status.deadLetterAction": "\u9700\u68C0\u67E5\u539F\u56E0\u540E\u624B\u52A8\u91CD\u8BD5",
|
|
18437
19068
|
"status.cancelled": "\u5DF2\u53D6\u6D88",
|
|
19069
|
+
"status.executionStillActive": "\u6267\u884C\u8FDB\u7A0B\u4ECD\u5728\u9000\u51FA\uFF0C\u6682\u65F6\u5360\u7528\u5E76\u53D1",
|
|
18438
19070
|
"status.unknown": "\u672A\u77E5",
|
|
19071
|
+
"runStatus.running": "\u8FD0\u884C\u4E2D",
|
|
19072
|
+
"runStatus.done": "\u6210\u529F",
|
|
19073
|
+
"runStatus.failed": "\u5931\u8D25",
|
|
18439
19074
|
"stats.total": "\u603B\u4EFB\u52A1",
|
|
18440
19075
|
"stats.pending": "\u5F85\u6267\u884C",
|
|
18441
19076
|
"stats.running": "\u8FD0\u884C\u4E2D",
|
|
18442
19077
|
"stats.done": "\u5DF2\u5B8C\u6210",
|
|
18443
|
-
"stats.failedDead": "\
|
|
18444
|
-
"stats.templates": "\
|
|
19078
|
+
"stats.failedDead": "\u7B49\u5F85\u91CD\u8BD5\u4E0E\u5DF2\u505C\u6B62",
|
|
19079
|
+
"stats.templates": "\u5B9A\u65F6\u4EFB\u52A1\u603B\u6570",
|
|
18445
19080
|
"stats.enabled": "\u5DF2\u542F\u7528",
|
|
18446
19081
|
"stats.disabled": "\u5DF2\u7981\u7528",
|
|
18447
19082
|
"stats.records": "\u6267\u884C\u603B\u6570",
|
|
@@ -18449,7 +19084,7 @@ var ZH = {
|
|
|
18449
19084
|
"stats.pageFailed": "\u672C\u9875\u5931\u8D25",
|
|
18450
19085
|
"stats.pageRunning": "\u672C\u9875\u8FD0\u884C\u4E2D",
|
|
18451
19086
|
"filter.all": "\u5168\u90E8",
|
|
18452
|
-
"filter.searchTasks": "\u641C\u7D22\u5F53\u524D\u9875\u7684\u4EFB\u52A1\u3001Agent \u6216\u63D0\u793A\u8BCD",
|
|
19087
|
+
"filter.searchTasks": "\u641C\u7D22\u5F53\u524D\u9875\u7684\u4EFB\u52A1\u3001\u9879\u76EE\u3001\u6279\u6B21\u3001Agent \u6216\u63D0\u793A\u8BCD",
|
|
18453
19088
|
"filter.noResults": "\u6CA1\u6709\u7B26\u5408\u5F53\u524D\u641C\u7D22\u6761\u4EF6\u7684\u4EFB\u52A1",
|
|
18454
19089
|
"table.id": "ID",
|
|
18455
19090
|
"table.task": "\u4EFB\u52A1",
|
|
@@ -18465,7 +19100,7 @@ var ZH = {
|
|
|
18465
19100
|
"table.nextRun": "\u4E0B\u6B21\u6267\u884C",
|
|
18466
19101
|
"table.run": "Run",
|
|
18467
19102
|
"table.heartbeat": "\u5FC3\u8DF3",
|
|
18468
|
-
"table.session": "
|
|
19103
|
+
"table.session": "\u4F1A\u8BDD",
|
|
18469
19104
|
"table.model": "\u6A21\u578B",
|
|
18470
19105
|
"table.startedAt": "\u542F\u52A8\u65F6\u95F4",
|
|
18471
19106
|
"table.pid": "PID",
|
|
@@ -18473,14 +19108,14 @@ var ZH = {
|
|
|
18473
19108
|
"pagination.next": "\u4E0B\u4E00\u9875",
|
|
18474
19109
|
"pagination.summary": "\u7B2C {page} \u9875\uFF0C\u5171 {pages} \u9875 \xB7 {total} \u6761",
|
|
18475
19110
|
"empty.tasks": "\u961F\u5217\u91CC\u8FD8\u6CA1\u6709\u4EFB\u52A1",
|
|
18476
|
-
"empty.tasksHint": "\u901A\u8FC7 OpenCode \u63D2\u4EF6\
|
|
18477
|
-
"empty.templates": "\u8FD8\u6CA1\u6709\
|
|
18478
|
-
"empty.templatesHint": "\
|
|
19111
|
+
"empty.tasksHint": "\u70B9\u51FB\u201C\u65B0\u5EFA\u4EFB\u52A1\u201D\uFF0C\u6216\u901A\u8FC7 OpenCode \u63D2\u4EF6\u548C supertask add \u521B\u5EFA\u7B2C\u4E00\u4E2A\u4EFB\u52A1\u3002",
|
|
19112
|
+
"empty.templates": "\u8FD8\u6CA1\u6709\u5B9A\u65F6\u4EFB\u52A1",
|
|
19113
|
+
"empty.templatesHint": "\u70B9\u51FB\u53F3\u4E0A\u89D2\u201C\u65B0\u5EFA\u5B9A\u65F6\u4EFB\u52A1\u201D\u5F00\u59CB\u521B\u5EFA\u3002",
|
|
18479
19114
|
"empty.runs": "\u8FD8\u6CA1\u6709\u6267\u884C\u8BB0\u5F55",
|
|
18480
19115
|
"empty.running": "\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1",
|
|
18481
19116
|
"schedule.cron": "Cron",
|
|
18482
|
-
"schedule.recurring": "\
|
|
18483
|
-
"schedule.delayed": "\
|
|
19117
|
+
"schedule.recurring": "\u56FA\u5B9A\u95F4\u9694",
|
|
19118
|
+
"schedule.delayed": "\u4E00\u6B21\u6027",
|
|
18484
19119
|
"schedule.unknown": "\u672A\u77E5",
|
|
18485
19120
|
"schedule.enabled": "\u5DF2\u542F\u7528",
|
|
18486
19121
|
"schedule.disabled": "\u5DF2\u7981\u7528",
|
|
@@ -18489,14 +19124,14 @@ var ZH = {
|
|
|
18489
19124
|
"schedule.hours": "{count} \u5C0F\u65F6",
|
|
18490
19125
|
"schedule.days": "{count} \u5929",
|
|
18491
19126
|
"schedule.overdue": "\u5DF2\u5230\u671F",
|
|
18492
|
-
"system.worker": "
|
|
18493
|
-
"system.scheduler": "
|
|
18494
|
-
"system.watchdog": "
|
|
19127
|
+
"system.worker": "\u4EFB\u52A1\u6267\u884C",
|
|
19128
|
+
"system.scheduler": "\u5B9A\u65F6\u4EFB\u52A1\u670D\u52A1",
|
|
19129
|
+
"system.watchdog": "\u8FD0\u884C\u76D1\u63A7",
|
|
18495
19130
|
"system.maxConcurrency": "\u6700\u5927\u5E76\u53D1",
|
|
18496
19131
|
"system.pollInterval": "\u8F6E\u8BE2\u95F4\u9694",
|
|
18497
19132
|
"system.heartbeatInterval": "\u5FC3\u8DF3\u95F4\u9694",
|
|
18498
19133
|
"system.taskTimeout": "\u4EFB\u52A1\u8D85\u65F6",
|
|
18499
|
-
"system.schedulerEnabled": "\u542F\u7528\
|
|
19134
|
+
"system.schedulerEnabled": "\u542F\u7528\u5B9A\u65F6\u4EFB\u52A1",
|
|
18500
19135
|
"system.checkInterval": "\u68C0\u67E5\u95F4\u9694",
|
|
18501
19136
|
"system.heartbeatTimeout": "\u5FC3\u8DF3\u8D85\u65F6",
|
|
18502
19137
|
"system.cleanupInterval": "\u6E05\u7406\u95F4\u9694",
|
|
@@ -18506,8 +19141,12 @@ var ZH = {
|
|
|
18506
19141
|
"system.minutes": "\u5206\u949F",
|
|
18507
19142
|
"system.hours": "\u5C0F\u65F6",
|
|
18508
19143
|
"system.days": "\u5929",
|
|
18509
|
-
"system.activeTemplates": "\
|
|
18510
|
-
"system.saveHint": "\u4FDD\u5B58\u540E\u9700\u91CD\u542F Gateway \u751F\u6548",
|
|
19144
|
+
"system.activeTemplates": "\u5DF2\u542F\u7528\u5B9A\u65F6\u4EFB\u52A1",
|
|
19145
|
+
"system.saveHint": "\u8BBE\u7F6E\u4FDD\u5B58\u540E\u9700\u8981\u91CD\u542F Gateway \u624D\u80FD\u751F\u6548\u3002",
|
|
19146
|
+
"system.configApplied": "\u9875\u9762\u4E2D\u7684\u8BBE\u7F6E\u6B63\u5728\u751F\u6548\u3002",
|
|
19147
|
+
"system.configPending": "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u5F53\u524D Gateway \u4ECD\u5728\u4F7F\u7528\u4E0A\u6B21\u542F\u52A8\u65F6\u7684\u8BBE\u7F6E\u3002",
|
|
19148
|
+
"system.configForeground": "\u6B64\u9875\u9762\u672A\u8FDE\u63A5\u5230 Gateway \u7684\u8FD0\u884C\u914D\u7F6E\uFF1B\u4FDD\u5B58\u540E\u8BF7\u91CD\u542F Gateway\u3002",
|
|
19149
|
+
"system.configRestartManually": "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u4F46\u5C1A\u672A\u751F\u6548\u3002\u8BF7\u56DE\u5230\u542F\u52A8 Gateway \u7684\u7EC8\u7AEF\uFF0C\u6309 Ctrl-C \u540E\u91CD\u65B0\u8FD0\u884C supertask gateway\u3002",
|
|
18511
19150
|
"system.runningTasks": "\u5F53\u524D\u8FD0\u884C\u4EFB\u52A1\uFF08{running} / {limit} \u5E76\u53D1\uFF09",
|
|
18512
19151
|
"system.taskStats": "\u4EFB\u52A1\u6982\u89C8",
|
|
18513
19152
|
"system.configFile": "\u914D\u7F6E\u6587\u4EF6",
|
|
@@ -18516,7 +19155,46 @@ var ZH = {
|
|
|
18516
19155
|
"system.yes": "\u662F",
|
|
18517
19156
|
"system.noDefault": "\u5426\uFF0C\u5F53\u524D\u4F7F\u7528\u9ED8\u8BA4\u503C",
|
|
18518
19157
|
"system.danger": "\u5371\u9669\u64CD\u4F5C",
|
|
18519
|
-
"system.dangerDescription": "\u7CFB\u7EDF\u4F1A\u5148\u521B\u5EFA\u53EF\u6821\u9A8C\u5907\u4EFD\uFF0C\u518D\u4E8B\u52A1\u6027\u6E05\u7A7A\u4EFB\u52A1\u3001\u6267\u884C\u8BB0\u5F55\u548C\
|
|
19158
|
+
"system.dangerDescription": "\u7CFB\u7EDF\u4F1A\u5148\u521B\u5EFA\u53EF\u6821\u9A8C\u5907\u4EFD\uFF0C\u518D\u4E8B\u52A1\u6027\u6E05\u7A7A\u4EFB\u52A1\u3001\u6267\u884C\u8BB0\u5F55\u548C\u5B9A\u65F6\u4EFB\u52A1\uFF1B\u5B58\u5728\u8FD0\u884C\u4EFB\u52A1\u65F6\u4F1A\u62D2\u7EDD\u64CD\u4F5C\u3002",
|
|
19159
|
+
"template.createTitle": "\u65B0\u5EFA\u5B9A\u65F6\u4EFB\u52A1",
|
|
19160
|
+
"template.editTitle": "\u7F16\u8F91\u5B9A\u65F6\u4EFB\u52A1",
|
|
19161
|
+
"template.formSubtitle": "\u8BBE\u7F6E\u4F55\u65F6\u8FD0\u884C\uFF0C\u4EE5\u53CA OpenCode \u8981\u4F7F\u7528\u7684\u9879\u76EE\u3001\u6A21\u578B\u548C\u63D0\u793A\u8BCD\u3002",
|
|
19162
|
+
"template.name": "\u540D\u79F0",
|
|
19163
|
+
"template.cwd": "\u9879\u76EE\u76EE\u5F55",
|
|
19164
|
+
"template.cwdHint": "OpenCode \u4F1A\u5728\u8FD9\u4E2A\u76EE\u5F55\u4E2D\u6267\u884C\u4EFB\u52A1\u3002",
|
|
19165
|
+
"template.agent": "Agent",
|
|
19166
|
+
"template.model": "\u6A21\u578B",
|
|
19167
|
+
"template.prompt": "\u63D0\u793A\u8BCD",
|
|
19168
|
+
"template.scheduleType": "\u6267\u884C\u65B9\u5F0F",
|
|
19169
|
+
"template.cronExpr": "Cron \u8868\u8FBE\u5F0F",
|
|
19170
|
+
"template.cronHint": "\u4F8B\u5982\uFF1A0 9 * * *\uFF08\u6BCF\u5929 09:00\uFF09",
|
|
19171
|
+
"template.interval": "\u6267\u884C\u95F4\u9694",
|
|
19172
|
+
"template.runAt": "\u6267\u884C\u65F6\u95F4",
|
|
19173
|
+
"template.durationHint": "\u652F\u6301 30s\u30015min\u30011h\u30012d",
|
|
19174
|
+
"template.advanced": "\u66F4\u591A\u6267\u884C\u8BBE\u7F6E",
|
|
19175
|
+
"template.category": "\u5206\u7C7B",
|
|
19176
|
+
"template.batchId": "\u6279\u6B21 ID",
|
|
19177
|
+
"template.importance": "\u91CD\u8981\u7A0B\u5EA6\uFF081-5\uFF09",
|
|
19178
|
+
"template.urgency": "\u7D27\u6025\u7A0B\u5EA6\uFF081-5\uFF09",
|
|
19179
|
+
"template.maxInstances": "\u81EA\u52A8\u8C03\u5EA6\u4E0A\u9650",
|
|
19180
|
+
"template.maxInstancesHint": "\u4EC5\u9650\u5236\u81EA\u52A8\u8C03\u5EA6\uFF1B\u624B\u52A8\u201C\u7ACB\u5373\u8FD0\u884C\u4E00\u6B21\u201D\u59CB\u7EC8\u52A0\u5165\u961F\u5217\u3002\u6D3B\u8DC3\u4EFB\u52A1\u5305\u542B\u5F85\u6267\u884C\u3001\u8FD0\u884C\u4E2D\u548C\u7B49\u5F85\u91CD\u8BD5\u3002",
|
|
19181
|
+
"template.maxRetries": "\u5931\u8D25\u91CD\u8BD5\u6B21\u6570",
|
|
19182
|
+
"template.retryBackoff": "\u91CD\u8BD5\u7B49\u5F85",
|
|
19183
|
+
"template.timeout": "\u5355\u6B21\u8D85\u65F6",
|
|
19184
|
+
"template.optional": "\u53EF\u9009",
|
|
19185
|
+
"template.futureOnly": "\u4FDD\u5B58\u540E\u7684\u8BBE\u7F6E\u7528\u4E8E\u4EE5\u540E\u751F\u6210\u7684\u4EFB\u52A1\uFF1B\u7F16\u8F91\u4E0D\u4F1A\u6539\u53D8\u5DF2\u7ECF\u8FDB\u5165\u961F\u5217\u6216\u6B63\u5728\u6267\u884C\u7684\u4EFB\u52A1\u3002",
|
|
19186
|
+
"projects.title": "\u9879\u76EE\u5206\u7EC4",
|
|
19187
|
+
"projects.description": "\u9879\u76EE\u76EE\u5F55\u662F\u9694\u79BB\u952E\uFF1B\u5148\u770B\u6BCF\u4E2A\u9879\u76EE\u6709\u6CA1\u6709\u8FD0\u884C\u4E2D\u6216\u6392\u961F\u4EFB\u52A1\uFF0C\u518D\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\u6DFB\u52A0\u3002",
|
|
19188
|
+
"projects.all": "\u5168\u90E8\u9879\u76EE",
|
|
19189
|
+
"projects.legacy": "\u672A\u5206\u7EC4",
|
|
19190
|
+
"projects.legacyHint": "\u65E7\u7248\u672C\u4E2D\u6CA1\u6709\u9879\u76EE\u76EE\u5F55\u7684\u4EFB\u52A1\uFF1B\u53EF\u67E5\u770B\u3001\u53D6\u6D88\u6216\u5220\u9664\uFF0C\u9700\u8981\u4FEE\u6539\u65F6\u8BF7\u5728\u6B63\u786E\u9879\u76EE\u76EE\u5F55\u4E0B\u91CD\u65B0\u521B\u5EFA\u3002",
|
|
19191
|
+
"projects.counts": "\u8FD0\u884C {running} \xB7 \u6392\u961F {pending} \xB7 \u5F02\u5E38 {failed}",
|
|
19192
|
+
"task.createTitle": "\u65B0\u5EFA\u4EFB\u52A1",
|
|
19193
|
+
"task.editTitle": "\u7F16\u8F91\u4EFB\u52A1",
|
|
19194
|
+
"task.formSubtitle": "\u4EFB\u52A1\u4F1A\u7ACB\u5373\u52A0\u5165\u6301\u4E45\u961F\u5217\uFF1B\u5E76\u53D1\u5DF2\u6EE1\u65F6\u4F1A\u81EA\u52A8\u7B49\u5F85\u3002",
|
|
19195
|
+
"task.batchHint": "\u76F8\u540C\u975E\u7A7A\u6279\u6B21 ID \u7684\u4EFB\u52A1\u4E25\u683C\u4E32\u884C\uFF1B\u7559\u7A7A\u5219\u4E0D\u53D7\u6279\u6B21\u4E32\u884C\u9650\u5236\uFF0C\u4F46\u4ECD\u53D7\u5168\u5C40\u5E76\u53D1\u548C\u4F9D\u8D56\u7EA6\u675F\u3002",
|
|
19196
|
+
"task.projectExisting": "\u6B64\u9879\u76EE\u73B0\u6709 {total} \u4E2A\u4EFB\u52A1\uFF1A\u8FD0\u884C {running}\uFF0C\u6392\u961F {pending}\uFF0C\u5F02\u5E38 {failed}\u3002",
|
|
19197
|
+
"task.projectNew": "\u8FD9\u662F\u4E00\u4E2A\u65B0\u9879\u76EE\u5206\u7EC4\uFF1B\u521B\u5EFA\u540E\u4F1A\u51FA\u73B0\u5728\u9879\u76EE\u5217\u8868\u4E2D\u3002",
|
|
18520
19198
|
"theme.label": "\u4E3B\u9898",
|
|
18521
19199
|
"theme.system": "\u8DDF\u968F\u7CFB\u7EDF",
|
|
18522
19200
|
"theme.light": "\u6D45\u8272",
|
|
@@ -18531,21 +19209,31 @@ var ZH = {
|
|
|
18531
19209
|
"dialog.retryTaskBody": "\u4EFB\u52A1\u5C06\u56DE\u5230\u5F85\u6267\u884C\u72B6\u6001\uFF0C\u5E76\u91CD\u7F6E\u81EA\u52A8\u91CD\u8BD5\u9884\u7B97\u3002",
|
|
18532
19210
|
"dialog.deleteTask": "\u5220\u9664\u4EFB\u52A1 #{id}\uFF1F",
|
|
18533
19211
|
"dialog.deleteTaskBody": "\u4EFB\u52A1\u53CA\u5173\u8054\u6267\u884C\u8BB0\u5F55\u5C06\u6C38\u4E45\u5220\u9664\uFF0C\u6B64\u64CD\u4F5C\u65E0\u6CD5\u64A4\u9500\u3002",
|
|
18534
|
-
"dialog.disableTemplate": "\u7981\u7528\u8FD9\u4E2A\
|
|
18535
|
-
"dialog.disableTemplateBody": "\
|
|
18536
|
-
"dialog.deleteTemplate": "\u5220\u9664\u8FD9\u4E2A\
|
|
18537
|
-
"dialog.deleteTemplateBody": "\
|
|
18538
|
-
"dialog.triggerTemplate": "\u7ACB\u5373\
|
|
18539
|
-
"dialog.triggerTemplateBody": "\u7CFB\u7EDF\u4F1A\u6309\u5F53\u524D\
|
|
19212
|
+
"dialog.disableTemplate": "\u7981\u7528\u8FD9\u4E2A\u5B9A\u65F6\u4EFB\u52A1\uFF1F",
|
|
19213
|
+
"dialog.disableTemplateBody": "\u5B83\u5C06\u505C\u6B62\u81EA\u52A8\u521B\u5EFA\u65B0\u4EFB\u52A1\uFF0C\u5DF2\u6709\u4EFB\u52A1\u4E0D\u53D7\u5F71\u54CD\u3002",
|
|
19214
|
+
"dialog.deleteTemplate": "\u5220\u9664\u8FD9\u4E2A\u5B9A\u65F6\u4EFB\u52A1\uFF1F",
|
|
19215
|
+
"dialog.deleteTemplateBody": "\u5B9A\u65F6\u4EFB\u52A1\u914D\u7F6E\u5C06\u6C38\u4E45\u5220\u9664\uFF0C\u6B64\u64CD\u4F5C\u65E0\u6CD5\u64A4\u9500\u3002",
|
|
19216
|
+
"dialog.triggerTemplate": "\u7ACB\u5373\u8FD0\u884C\u4E00\u6B21\uFF1F",
|
|
19217
|
+
"dialog.triggerTemplateBody": "\u7CFB\u7EDF\u4F1A\u6309\u5F53\u524D\u8BBE\u7F6E\u7ACB\u5373\u521B\u5EFA\u4E00\u4E2A\u4EFB\u52A1\u5E76\u52A0\u5165\u961F\u5217\uFF1B\u82E5\u5168\u5C40\u5E76\u53D1\u5DF2\u6EE1\uFF0C\u4EFB\u52A1\u4F1A\u7B49\u5F85\u6267\u884C\u3002",
|
|
18540
19218
|
"dialog.clearTitle": "\u786E\u8BA4\u6E05\u7A7A\u6570\u636E\u5E93",
|
|
18541
|
-
"dialog.clearBody": "\u8FD9\u4F1A\u5220\u9664\u5168\u90E8\u4EFB\u52A1\u3001\u6267\u884C\u8BB0\u5F55\u548C\
|
|
19219
|
+
"dialog.clearBody": "\u8FD9\u4F1A\u5220\u9664\u5168\u90E8\u4EFB\u52A1\u3001\u6267\u884C\u8BB0\u5F55\u548C\u5B9A\u65F6\u4EFB\u52A1\u3002\u7CFB\u7EDF\u4F1A\u5148\u81EA\u52A8\u5907\u4EFD\u3002",
|
|
18542
19220
|
"dialog.clearInstruction": "\u8F93\u5165 CLEAR \u4EE5\u786E\u8BA4",
|
|
19221
|
+
"dialog.restartGateway": "\u91CD\u542F\u5E76\u5E94\u7528\u8BBE\u7F6E\uFF1F",
|
|
19222
|
+
"dialog.restartGatewayBody": "Gateway \u4F1A\u77ED\u6682\u79BB\u7EBF\uFF0C\u901A\u5E38\u6570\u79D2\u540E\u7531 PM2 \u81EA\u52A8\u6062\u590D\u3002",
|
|
19223
|
+
"dialog.restartGatewayRunningBody": "\u5F53\u524D\u6709 {count} \u4E2A\u4EFB\u52A1\u5728\u8FD0\u884C\u3002\u7CFB\u7EDF\u4F1A\u5148\u7B49\u5F85\u5B83\u4EEC\u5B8C\u6210\uFF1B\u8D85\u65F6\u540E\uFF0C\u672A\u5B8C\u6210\u4EFB\u52A1\u4F1A\u88AB\u5B89\u5168\u505C\u6B62\u5E76\u653E\u56DE\u961F\u5217\uFF0C\u7136\u540E Gateway \u7531 PM2 \u81EA\u52A8\u6062\u590D\u3002",
|
|
18543
19224
|
"feedback.retryFailed": "\u91CD\u8BD5\u5931\u8D25",
|
|
18544
19225
|
"feedback.cancelFailed": "\u53D6\u6D88\u5931\u8D25",
|
|
18545
19226
|
"feedback.deleteFailed": "\u5220\u9664\u5931\u8D25",
|
|
18546
19227
|
"feedback.requestFailed": "\u8BF7\u6C42\u5931\u8D25",
|
|
18547
19228
|
"feedback.triggered": "\u5DF2\u521B\u5EFA\u4EFB\u52A1 #{id}",
|
|
18548
|
-
"feedback.
|
|
19229
|
+
"feedback.taskCreated": "\u4EFB\u52A1 #{id} \u5DF2\u52A0\u5165\u961F\u5217",
|
|
19230
|
+
"feedback.taskUpdated": "\u4EFB\u52A1 #{id} \u5DF2\u66F4\u65B0",
|
|
19231
|
+
"feedback.templateCreated": "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u521B\u5EFA",
|
|
19232
|
+
"feedback.templateUpdated": "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u66F4\u65B0",
|
|
19233
|
+
"feedback.configSaved": "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58",
|
|
19234
|
+
"feedback.sessionCommandCopied": "\u4F1A\u8BDD\u547D\u4EE4\u5DF2\u590D\u5236\uFF0C\u53EF\u76F4\u63A5\u7C98\u8D34\u5230\u7EC8\u7AEF\u8FD0\u884C",
|
|
19235
|
+
"feedback.restarting": "Gateway \u6B63\u5728\u91CD\u542F\uFF0C\u8BF7\u7A0D\u5019\u2026",
|
|
19236
|
+
"feedback.restartTimeout": "Gateway \u5C1A\u672A\u6062\u590D\uFF0C\u8BF7\u7A0D\u540E\u5237\u65B0\u6216\u68C0\u67E5 PM2 \u72B6\u6001",
|
|
18549
19237
|
"feedback.databaseCleared": "\u6570\u636E\u5E93\u5DF2\u6E05\u7A7A\uFF0C\u5907\u4EFD\u4F4D\u4E8E\uFF1A{path}",
|
|
18550
19238
|
"feedback.copyFailed": "\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u9009\u62E9\u5185\u5BB9",
|
|
18551
19239
|
"a11y.skip": "\u8DF3\u5230\u4E3B\u8981\u5185\u5BB9",
|
|
@@ -18558,13 +19246,13 @@ var EN = {
|
|
|
18558
19246
|
"app.local": "Running locally",
|
|
18559
19247
|
"app.footer": "Local-first \xB7 Your data stays on this device",
|
|
18560
19248
|
"nav.tasks": "Tasks",
|
|
18561
|
-
"nav.templates": "
|
|
19249
|
+
"nav.templates": "Scheduled tasks",
|
|
18562
19250
|
"nav.runs": "Runs",
|
|
18563
19251
|
"nav.system": "System",
|
|
18564
19252
|
"page.tasks.title": "Task queue",
|
|
18565
19253
|
"page.tasks.description": "Track priority, execution state, and retries, then act on tasks that need attention.",
|
|
18566
|
-
"page.templates.title": "
|
|
18567
|
-
"page.templates.description": "
|
|
19254
|
+
"page.templates.title": "Scheduled tasks",
|
|
19255
|
+
"page.templates.description": "Create and edit cron, fixed-interval, and one-time tasks, including their model, agent, and prompt.",
|
|
18568
19256
|
"page.runs.title": "Execution history",
|
|
18569
19257
|
"page.runs.description": "Inspect the status, duration, heartbeat, and output of every agent run.",
|
|
18570
19258
|
"page.system.title": "System settings",
|
|
@@ -18574,29 +19262,43 @@ var EN = {
|
|
|
18574
19262
|
"action.retry": "Retry",
|
|
18575
19263
|
"action.cancel": "Cancel",
|
|
18576
19264
|
"action.delete": "Delete",
|
|
19265
|
+
"action.edit": "Edit",
|
|
18577
19266
|
"action.enable": "Enable",
|
|
18578
19267
|
"action.disable": "Disable",
|
|
18579
19268
|
"action.trigger": "Run now",
|
|
19269
|
+
"action.continueSession": "Continue session",
|
|
18580
19270
|
"action.logs": "View log",
|
|
18581
19271
|
"action.hideLogs": "Hide log",
|
|
18582
19272
|
"action.save": "Save settings",
|
|
19273
|
+
"action.saveAndRestart": "Save and restart",
|
|
18583
19274
|
"action.copy": "Copy JSON",
|
|
18584
19275
|
"action.close": "Close",
|
|
18585
19276
|
"action.confirm": "Confirm",
|
|
18586
19277
|
"action.clearDatabase": "Clear database",
|
|
19278
|
+
"action.createTask": "New task",
|
|
19279
|
+
"action.saveTask": "Add to queue",
|
|
19280
|
+
"action.updateTask": "Save changes",
|
|
19281
|
+
"action.createTemplate": "New scheduled task",
|
|
19282
|
+
"action.saveTemplate": "Save scheduled task",
|
|
18587
19283
|
"status.pending": "Pending",
|
|
18588
19284
|
"status.running": "Running",
|
|
18589
19285
|
"status.done": "Done",
|
|
18590
|
-
"status.failed": "
|
|
18591
|
-
"status.dead_letter": "
|
|
19286
|
+
"status.failed": "Waiting to retry",
|
|
19287
|
+
"status.dead_letter": "Stopped",
|
|
19288
|
+
"status.deadLetterHint": "The system will not run this task automatically again. Its retries may be exhausted or a dependency cannot continue; inspect the error, then retry manually if appropriate.",
|
|
19289
|
+
"status.deadLetterAction": "Inspect the error, then retry manually",
|
|
18592
19290
|
"status.cancelled": "Cancelled",
|
|
19291
|
+
"status.executionStillActive": "The execution process is still stopping and temporarily occupies a slot",
|
|
18593
19292
|
"status.unknown": "Unknown",
|
|
19293
|
+
"runStatus.running": "Running",
|
|
19294
|
+
"runStatus.done": "Succeeded",
|
|
19295
|
+
"runStatus.failed": "Failed",
|
|
18594
19296
|
"stats.total": "Total tasks",
|
|
18595
19297
|
"stats.pending": "Pending",
|
|
18596
19298
|
"stats.running": "Running",
|
|
18597
19299
|
"stats.done": "Completed",
|
|
18598
|
-
"stats.failedDead": "
|
|
18599
|
-
"stats.templates": "
|
|
19300
|
+
"stats.failedDead": "Waiting to retry & stopped",
|
|
19301
|
+
"stats.templates": "Scheduled tasks",
|
|
18600
19302
|
"stats.enabled": "Enabled",
|
|
18601
19303
|
"stats.disabled": "Disabled",
|
|
18602
19304
|
"stats.records": "Total runs",
|
|
@@ -18604,7 +19306,7 @@ var EN = {
|
|
|
18604
19306
|
"stats.pageFailed": "Failed here",
|
|
18605
19307
|
"stats.pageRunning": "Running here",
|
|
18606
19308
|
"filter.all": "All",
|
|
18607
|
-
"filter.searchTasks": "Search tasks, agents, or prompts on this page",
|
|
19309
|
+
"filter.searchTasks": "Search tasks, projects, batches, agents, or prompts on this page",
|
|
18608
19310
|
"filter.noResults": "No tasks match this search",
|
|
18609
19311
|
"table.id": "ID",
|
|
18610
19312
|
"table.task": "Task",
|
|
@@ -18628,14 +19330,14 @@ var EN = {
|
|
|
18628
19330
|
"pagination.next": "Next",
|
|
18629
19331
|
"pagination.summary": "Page {page} of {pages} \xB7 {total} items",
|
|
18630
19332
|
"empty.tasks": "Your queue is empty",
|
|
18631
|
-
"empty.tasksHint": "
|
|
18632
|
-
"empty.templates": "No
|
|
18633
|
-
"empty.templatesHint": "
|
|
19333
|
+
"empty.tasksHint": "Select \u201CNew task\u201D, or use the OpenCode plugin or supertask add.",
|
|
19334
|
+
"empty.templates": "No scheduled tasks yet",
|
|
19335
|
+
"empty.templatesHint": "Select \u201CNew scheduled task\u201D to create one.",
|
|
18634
19336
|
"empty.runs": "No execution history yet",
|
|
18635
19337
|
"empty.running": "No tasks are running right now",
|
|
18636
19338
|
"schedule.cron": "Cron",
|
|
18637
|
-
"schedule.recurring": "
|
|
18638
|
-
"schedule.delayed": "
|
|
19339
|
+
"schedule.recurring": "Fixed interval",
|
|
19340
|
+
"schedule.delayed": "One-time",
|
|
18639
19341
|
"schedule.unknown": "Unknown",
|
|
18640
19342
|
"schedule.enabled": "Enabled",
|
|
18641
19343
|
"schedule.disabled": "Disabled",
|
|
@@ -18644,14 +19346,14 @@ var EN = {
|
|
|
18644
19346
|
"schedule.hours": "{count} hr",
|
|
18645
19347
|
"schedule.days": "{count} days",
|
|
18646
19348
|
"schedule.overdue": "Overdue",
|
|
18647
|
-
"system.worker": "
|
|
18648
|
-
"system.scheduler": "
|
|
18649
|
-
"system.watchdog": "
|
|
19349
|
+
"system.worker": "Task execution",
|
|
19350
|
+
"system.scheduler": "Scheduled task service",
|
|
19351
|
+
"system.watchdog": "Runtime monitor",
|
|
18650
19352
|
"system.maxConcurrency": "Max concurrency",
|
|
18651
19353
|
"system.pollInterval": "Poll interval",
|
|
18652
19354
|
"system.heartbeatInterval": "Heartbeat interval",
|
|
18653
19355
|
"system.taskTimeout": "Task timeout",
|
|
18654
|
-
"system.schedulerEnabled": "Enable
|
|
19356
|
+
"system.schedulerEnabled": "Enable scheduled tasks",
|
|
18655
19357
|
"system.checkInterval": "Check interval",
|
|
18656
19358
|
"system.heartbeatTimeout": "Heartbeat timeout",
|
|
18657
19359
|
"system.cleanupInterval": "Cleanup interval",
|
|
@@ -18661,8 +19363,12 @@ var EN = {
|
|
|
18661
19363
|
"system.minutes": "minutes",
|
|
18662
19364
|
"system.hours": "hours",
|
|
18663
19365
|
"system.days": "days",
|
|
18664
|
-
"system.activeTemplates": "
|
|
18665
|
-
"system.saveHint": "Restart Gateway to apply saved settings",
|
|
19366
|
+
"system.activeTemplates": "Enabled scheduled tasks",
|
|
19367
|
+
"system.saveHint": "Restart Gateway to apply saved settings.",
|
|
19368
|
+
"system.configApplied": "The settings shown here are active.",
|
|
19369
|
+
"system.configPending": "Settings are saved, but this Gateway is still using the values from its last start.",
|
|
19370
|
+
"system.configForeground": "This page is not connected to Gateway runtime settings. Restart Gateway after saving.",
|
|
19371
|
+
"system.configRestartManually": "Settings are saved but not active. Return to the terminal that launched Gateway, press Ctrl-C, then run supertask gateway again.",
|
|
18666
19372
|
"system.runningTasks": "Active tasks ({running} / {limit} concurrent)",
|
|
18667
19373
|
"system.taskStats": "Task overview",
|
|
18668
19374
|
"system.configFile": "Configuration file",
|
|
@@ -18672,6 +19378,45 @@ var EN = {
|
|
|
18672
19378
|
"system.noDefault": "No, using defaults",
|
|
18673
19379
|
"system.danger": "Danger zone",
|
|
18674
19380
|
"system.dangerDescription": "A verified backup is created first, then tasks, runs, and templates are cleared transactionally. Active work blocks this operation.",
|
|
19381
|
+
"template.createTitle": "New scheduled task",
|
|
19382
|
+
"template.editTitle": "Edit scheduled task",
|
|
19383
|
+
"template.formSubtitle": "Choose when to run and which project, model, and prompt OpenCode should use.",
|
|
19384
|
+
"template.name": "Name",
|
|
19385
|
+
"template.cwd": "Project directory",
|
|
19386
|
+
"template.cwdHint": "OpenCode runs the task in this directory.",
|
|
19387
|
+
"template.agent": "Agent",
|
|
19388
|
+
"template.model": "Model",
|
|
19389
|
+
"template.prompt": "Prompt",
|
|
19390
|
+
"template.scheduleType": "Schedule",
|
|
19391
|
+
"template.cronExpr": "Cron expression",
|
|
19392
|
+
"template.cronHint": "Example: 0 9 * * * (daily at 09:00)",
|
|
19393
|
+
"template.interval": "Interval",
|
|
19394
|
+
"template.runAt": "Run at",
|
|
19395
|
+
"template.durationHint": "Supports 30s, 5min, 1h, 2d",
|
|
19396
|
+
"template.advanced": "More execution settings",
|
|
19397
|
+
"template.category": "Category",
|
|
19398
|
+
"template.batchId": "Batch ID",
|
|
19399
|
+
"template.importance": "Importance (1-5)",
|
|
19400
|
+
"template.urgency": "Urgency (1-5)",
|
|
19401
|
+
"template.maxInstances": "Automatic scheduling limit",
|
|
19402
|
+
"template.maxInstancesHint": "Limits automatic scheduling only. \u201CRun now\u201D always queues a task. Active instances include pending, running, and retry-waiting tasks.",
|
|
19403
|
+
"template.maxRetries": "Failure retries",
|
|
19404
|
+
"template.retryBackoff": "Retry delay",
|
|
19405
|
+
"template.timeout": "Run timeout",
|
|
19406
|
+
"template.optional": "Optional",
|
|
19407
|
+
"template.futureOnly": "Saved settings apply to tasks created in the future. Editing does not change queued or running tasks.",
|
|
19408
|
+
"projects.title": "Projects",
|
|
19409
|
+
"projects.description": "The project directory is the isolation key. Check active and queued work before adding more.",
|
|
19410
|
+
"projects.all": "All projects",
|
|
19411
|
+
"projects.legacy": "Ungrouped",
|
|
19412
|
+
"projects.legacyHint": "Legacy tasks without a project directory. View, cancel, or delete them; recreate under the correct project to make changes.",
|
|
19413
|
+
"projects.counts": "Running {running} \xB7 queued {pending} \xB7 issues {failed}",
|
|
19414
|
+
"task.createTitle": "New task",
|
|
19415
|
+
"task.editTitle": "Edit task",
|
|
19416
|
+
"task.formSubtitle": "The task enters the durable queue immediately and waits automatically when concurrency is full.",
|
|
19417
|
+
"task.batchHint": "Tasks with the same non-empty batch ID run serially. Blank removes the batch constraint; global concurrency and dependencies still apply.",
|
|
19418
|
+
"task.projectExisting": "This project has {total} tasks: {running} running, {pending} queued, and {failed} with issues.",
|
|
19419
|
+
"task.projectNew": "This is a new project group. It appears in the project list after creation.",
|
|
18675
19420
|
"theme.label": "Theme",
|
|
18676
19421
|
"theme.system": "System",
|
|
18677
19422
|
"theme.light": "Light",
|
|
@@ -18689,18 +19434,28 @@ var EN = {
|
|
|
18689
19434
|
"dialog.disableTemplate": "Disable this schedule?",
|
|
18690
19435
|
"dialog.disableTemplateBody": "It will stop creating new tasks automatically. Existing tasks are unchanged.",
|
|
18691
19436
|
"dialog.deleteTemplate": "Delete this schedule?",
|
|
18692
|
-
"dialog.deleteTemplateBody": "This
|
|
19437
|
+
"dialog.deleteTemplateBody": "This scheduled task configuration will be permanently deleted.",
|
|
18693
19438
|
"dialog.triggerTemplate": "Run this schedule now?",
|
|
18694
|
-
"dialog.triggerTemplateBody": "A
|
|
19439
|
+
"dialog.triggerTemplateBody": "A task is queued immediately using the current settings. If global concurrency is full, it waits to run.",
|
|
18695
19440
|
"dialog.clearTitle": "Confirm database clear",
|
|
18696
|
-
"dialog.clearBody": "This deletes every task, run, and
|
|
19441
|
+
"dialog.clearBody": "This deletes every task, run, and scheduled task after creating a backup.",
|
|
18697
19442
|
"dialog.clearInstruction": "Type CLEAR to confirm",
|
|
19443
|
+
"dialog.restartGateway": "Restart and apply settings?",
|
|
19444
|
+
"dialog.restartGatewayBody": "Gateway will be briefly unavailable and should recover through PM2 within a few seconds.",
|
|
19445
|
+
"dialog.restartGatewayRunningBody": "{count} tasks are running. The system waits for them first; after the grace period, unfinished tasks are stopped safely and returned to the queue before PM2 restores Gateway.",
|
|
18698
19446
|
"feedback.retryFailed": "Retry failed",
|
|
18699
19447
|
"feedback.cancelFailed": "Cancellation failed",
|
|
18700
19448
|
"feedback.deleteFailed": "Delete failed",
|
|
18701
19449
|
"feedback.requestFailed": "Request failed",
|
|
18702
19450
|
"feedback.triggered": "Task #{id} created",
|
|
18703
|
-
"feedback.
|
|
19451
|
+
"feedback.taskCreated": "Task #{id} added to the queue",
|
|
19452
|
+
"feedback.taskUpdated": "Task #{id} updated",
|
|
19453
|
+
"feedback.templateCreated": "Scheduled task created",
|
|
19454
|
+
"feedback.templateUpdated": "Scheduled task updated",
|
|
19455
|
+
"feedback.configSaved": "Settings saved",
|
|
19456
|
+
"feedback.sessionCommandCopied": "Session command copied. Paste it into a terminal to continue.",
|
|
19457
|
+
"feedback.restarting": "Gateway is restarting\u2026",
|
|
19458
|
+
"feedback.restartTimeout": "Gateway has not recovered yet. Refresh later or check PM2 status.",
|
|
18704
19459
|
"feedback.databaseCleared": "Database cleared. Backup: {path}",
|
|
18705
19460
|
"feedback.copyFailed": "Copy failed. Select the content manually.",
|
|
18706
19461
|
"a11y.skip": "Skip to main content",
|
|
@@ -18714,6 +19469,14 @@ function statusText(locale, status) {
|
|
|
18714
19469
|
const key = `status.${status}`;
|
|
18715
19470
|
return key in ZH ? t(locale, key) : t(locale, "status.unknown");
|
|
18716
19471
|
}
|
|
19472
|
+
function runStatusText(locale, status) {
|
|
19473
|
+
const key = `runStatus.${status}`;
|
|
19474
|
+
return key in ZH ? t(locale, key) : t(locale, "status.unknown");
|
|
19475
|
+
}
|
|
19476
|
+
function resolveEditedRunAt(originalEpoch, originalLocal, currentLocal) {
|
|
19477
|
+
if (originalEpoch !== null && originalLocal === currentLocal) return originalEpoch;
|
|
19478
|
+
return new Date(currentLocal).getTime();
|
|
19479
|
+
}
|
|
18717
19480
|
function formatRelative(timestamp2, locale) {
|
|
18718
19481
|
if (!timestamp2) return "\u2014";
|
|
18719
19482
|
const deltaMs = timestamp2 - Date.now();
|
|
@@ -18793,8 +19556,8 @@ var STYLES = `
|
|
|
18793
19556
|
radial-gradient(circle at 10% -10%,var(--bg-glow),transparent 34rem),var(--bg);
|
|
18794
19557
|
font-family:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
|
18795
19558
|
font-size:14px; line-height:1.5; -webkit-font-smoothing:antialiased; }
|
|
18796
|
-
button,input,select { font:inherit; }
|
|
18797
|
-
button,a,select,input { -webkit-tap-highlight-color:transparent; }
|
|
19559
|
+
button,input,select,textarea { font:inherit; }
|
|
19560
|
+
button,a,select,input,textarea { -webkit-tap-highlight-color:transparent; }
|
|
18798
19561
|
a { color:inherit; }
|
|
18799
19562
|
code,.mono,.m { font-family:"SFMono-Regular",Consolas,"Liberation Mono",monospace; }
|
|
18800
19563
|
.skip-link { position:fixed; left:16px; top:-60px; z-index:200; padding:10px 14px; border-radius:8px;
|
|
@@ -18875,6 +19638,16 @@ var STYLES = `
|
|
|
18875
19638
|
.panel-head { min-height:52px; display:flex; align-items:center; justify-content:space-between; gap:12px; padding:0 18px;
|
|
18876
19639
|
border-bottom:1px solid var(--border); }
|
|
18877
19640
|
.panel-head h2,.panel-head h3 { margin:0; font-size:14px; letter-spacing:-.01em; }
|
|
19641
|
+
.panel-head p { margin:3px 0 0; color:var(--text-2); font-size:11px; }
|
|
19642
|
+
.project-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:10px; padding:14px; }
|
|
19643
|
+
.project-card { min-width:0; padding:12px; border:1px solid var(--border); border-radius:10px; color:var(--text); background:var(--surface-2); text-decoration:none; }
|
|
19644
|
+
.project-card:hover { border-color:var(--border-strong); background:var(--surface-3); }
|
|
19645
|
+
.project-card.active { border-color:color-mix(in srgb,var(--primary) 45%,var(--border)); background:var(--primary-soft); }
|
|
19646
|
+
.project-card-head { display:flex; align-items:center; justify-content:space-between; gap:10px; }
|
|
19647
|
+
.project-card-head strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
19648
|
+
.project-card-head span { color:var(--text-3); font-size:11px; }
|
|
19649
|
+
.project-path { margin-top:4px; overflow:hidden; color:var(--text-3); font-family:"SFMono-Regular",Consolas,monospace; font-size:10px; text-overflow:ellipsis; white-space:nowrap; }
|
|
19650
|
+
.project-counts { margin-top:8px; color:var(--text-2); font-size:11px; }
|
|
18878
19651
|
.table-wrap { width:100%; overflow-x:auto; }
|
|
18879
19652
|
table { width:100%; border-collapse:separate; border-spacing:0; font-size:13px; }
|
|
18880
19653
|
th { height:42px; padding:0 13px; color:var(--text-3); background:var(--surface-2); border-bottom:1px solid var(--border);
|
|
@@ -18885,6 +19658,7 @@ var STYLES = `
|
|
|
18885
19658
|
tbody tr:hover { background:color-mix(in srgb,var(--primary-soft) 30%,transparent); }
|
|
18886
19659
|
.task-name { font-weight:680; color:var(--text); }
|
|
18887
19660
|
.task-prompt { max-width:520px; margin-top:3px; color:var(--text-2); font-size:12px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
19661
|
+
.task-context { margin-top:6px; }
|
|
18888
19662
|
.muted,.mu { color:var(--text-2); }
|
|
18889
19663
|
.faint { color:var(--text-3); }
|
|
18890
19664
|
.small,.sm { font-size:12px; }
|
|
@@ -18968,6 +19742,18 @@ var STYLES = `
|
|
|
18968
19742
|
.dialog-head p { margin:3px 0 0; color:var(--text-2); font-size:11px; }
|
|
18969
19743
|
.dialog-body { max-height:70vh; overflow:auto; padding:18px; }
|
|
18970
19744
|
.dialog-actions { display:flex; align-items:center; justify-content:flex-end; gap:8px; padding:14px 18px; border-top:1px solid var(--border); }
|
|
19745
|
+
.template-dialog { width:min(880px,calc(100% - 32px)); }
|
|
19746
|
+
.template-form-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:15px; }
|
|
19747
|
+
.form-field { display:flex; min-width:0; flex-direction:column; gap:6px; color:var(--text-2); font-size:12px; font-weight:650; }
|
|
19748
|
+
.form-field-wide { grid-column:1 / -1; }
|
|
19749
|
+
.form-field input,.form-field select,.form-field textarea { width:100%; min-height:39px; padding:8px 10px; border:1px solid var(--border); border-radius:9px;
|
|
19750
|
+
outline:none; color:var(--text); background:var(--surface-2); font-weight:450; }
|
|
19751
|
+
.form-field textarea { resize:vertical; line-height:1.5; }
|
|
19752
|
+
.form-field input:focus,.form-field select:focus,.form-field textarea:focus { border-color:var(--primary); box-shadow:var(--focus); background:var(--surface); }
|
|
19753
|
+
.form-field small { color:var(--text-3); font-size:10px; font-weight:450; }
|
|
19754
|
+
.advanced-fields { margin-top:18px; padding-top:14px; border-top:1px solid var(--border); }
|
|
19755
|
+
.advanced-fields summary { margin-bottom:14px; color:var(--text-2); cursor:pointer; font-size:12px; font-weight:700; }
|
|
19756
|
+
.form-note { margin:16px 0 0; color:var(--text-3); font-size:11px; }
|
|
18971
19757
|
.json-view { min-height:160px; margin:0; padding:15px; overflow:auto; border:1px solid var(--border); border-radius:10px; color:var(--text-2);
|
|
18972
19758
|
background:var(--surface-2); font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
|
|
18973
19759
|
.confirm-copy { color:var(--text-2); margin:0; }
|
|
@@ -19033,6 +19819,9 @@ var STYLES = `
|
|
|
19033
19819
|
.responsive-table td[data-primary]::before { display:none; }
|
|
19034
19820
|
.responsive-table .task-prompt { max-width:100%; }
|
|
19035
19821
|
.responsive-table .actions { justify-content:flex-start; }
|
|
19822
|
+
.project-grid { grid-template-columns:1fr; }
|
|
19823
|
+
.template-form-grid { grid-template-columns:1fr; }
|
|
19824
|
+
.form-field-wide { grid-column:auto; }
|
|
19036
19825
|
}
|
|
19037
19826
|
@media (max-width:520px) {
|
|
19038
19827
|
.stats-grid,.stats-grid.three { grid-template-columns:1fr 1fr; }
|
|
@@ -19079,13 +19868,31 @@ function clientMessages(locale) {
|
|
|
19079
19868
|
"dialog.clearTitle",
|
|
19080
19869
|
"dialog.clearBody",
|
|
19081
19870
|
"dialog.clearInstruction",
|
|
19871
|
+
"dialog.restartGateway",
|
|
19872
|
+
"dialog.restartGatewayBody",
|
|
19873
|
+
"dialog.restartGatewayRunningBody",
|
|
19082
19874
|
"feedback.retryFailed",
|
|
19083
19875
|
"feedback.cancelFailed",
|
|
19084
19876
|
"feedback.deleteFailed",
|
|
19085
19877
|
"feedback.requestFailed",
|
|
19086
19878
|
"feedback.triggered",
|
|
19879
|
+
"feedback.taskCreated",
|
|
19880
|
+
"feedback.taskUpdated",
|
|
19881
|
+
"task.projectExisting",
|
|
19882
|
+
"task.projectNew",
|
|
19883
|
+
"task.createTitle",
|
|
19884
|
+
"task.editTitle",
|
|
19885
|
+
"action.saveTask",
|
|
19886
|
+
"action.updateTask",
|
|
19087
19887
|
"feedback.configSaved",
|
|
19088
19888
|
"feedback.databaseCleared",
|
|
19889
|
+
"feedback.templateCreated",
|
|
19890
|
+
"feedback.templateUpdated",
|
|
19891
|
+
"feedback.sessionCommandCopied",
|
|
19892
|
+
"feedback.restarting",
|
|
19893
|
+
"feedback.restartTimeout",
|
|
19894
|
+
"template.createTitle",
|
|
19895
|
+
"template.editTitle",
|
|
19089
19896
|
"filter.noResults"
|
|
19090
19897
|
];
|
|
19091
19898
|
return Object.fromEntries(keys.map((key) => [key, t(locale, key)]));
|
|
@@ -19179,6 +19986,7 @@ function renderLayout(options) {
|
|
|
19179
19986
|
themeMedia.addEventListener?.('change',()=>{if((localStorage.getItem('supertask-theme')||'system')==='system')applyTheme('system');});
|
|
19180
19987
|
applyTheme(localStorage.getItem('supertask-theme')||'system');
|
|
19181
19988
|
requestAnimationFrame(()=>document.documentElement.classList.add('ui-ready'));
|
|
19989
|
+
${resolveEditedRunAt.toString()}
|
|
19182
19990
|
function refreshPage(button){button.classList.add('refreshing');button.setAttribute('aria-label','${t(locale, "a11y.refreshing")}');location.reload();}
|
|
19183
19991
|
function showToast(message,type='ok'){const region=document.getElementById('toast-region');const node=document.createElement('div');node.className='toast '+type;node.innerHTML=(type==='error'?'${icon("alert")}':'${icon("check")}')+'<span></span>';node.querySelector('span').textContent=message;region.appendChild(node);setTimeout(()=>{node.classList.add('leaving');setTimeout(()=>node.remove(),220)},3600);}
|
|
19184
19992
|
async function readJson(response){const data=await response.json().catch(()=>({}));if(!response.ok)throw new Error(data.error||text('feedback.requestFailed'));return data;}
|
|
@@ -19190,6 +19998,22 @@ function renderLayout(options) {
|
|
|
19190
19998
|
async function showRecord(url){try{const data=await readJson(await fetch(url));document.getElementById('detail-content').textContent=JSON.stringify(data,null,2);document.getElementById('detail-dialog').showModal()}catch(error){showToast(error.message,'error')}}
|
|
19191
19999
|
const showDetail=id=>showRecord('/api/tasks/'+id);const showRunDetail=id=>showRecord('/api/runs/'+id);const showTemplateDetail=id=>showRecord('/api/templates/'+id);
|
|
19192
20000
|
async function copyDetails(){try{await navigator.clipboard.writeText(document.getElementById('detail-content').textContent);showToast(text('details.copySuccess'))}catch{showToast(text('feedback.copyFailed'),'error')}}
|
|
20001
|
+
async function copySessionCommand(id){try{const data=await readJson(await fetch('/api/runs/'+id+'/session-command'));await navigator.clipboard.writeText(data.command);showToast(text('feedback.sessionCommandCopied'))}catch(error){showToast(error.message||text('feedback.copyFailed'),'error')}}
|
|
20002
|
+
function taskField(name){return document.getElementById('task-'+name)}
|
|
20003
|
+
function taskProjects(){const node=document.getElementById('task-project-data');if(!node)return {};try{return JSON.parse(node.textContent||'{}')}catch{return {}}}
|
|
20004
|
+
function updateTaskProjectStatus(){const node=taskField('project-status');if(!node)return;const cwd=taskField('cwd').value.trim();if(!cwd){node.textContent='';return}const project=taskProjects()[cwd];node.textContent=project?text('task.projectExisting',project):text('task.projectNew')}
|
|
20005
|
+
function openTaskCreator(){const form=document.getElementById('task-form');form.reset();taskField('id').value='';taskField('cwd').readOnly=false;taskField('cwd').value=form.dataset.defaultCwd||'';taskField('dialog-title').textContent=text('task.createTitle');taskField('save').textContent=text('action.saveTask');updateTaskProjectStatus();document.getElementById('task-dialog').showModal();setTimeout(()=>taskField('name').focus(),50)}
|
|
20006
|
+
async function openTaskEditor(id){try{const data=await readJson(await fetch('/api/tasks/'+id));taskField('id').value=String(id);taskField('name').value=data.name||'';taskField('cwd').value=data.cwd||'';taskField('cwd').readOnly=true;taskField('agent').value=data.agent||'';taskField('model').value=data.model||'default';taskField('prompt').value=data.prompt||'';taskField('category').value=data.category||'general';taskField('batch').value=data.batchId||'';taskField('importance').value=String(data.importance??3);taskField('urgency').value=String(data.urgency??3);taskField('max-retries').value=String(data.maxRetries??3);taskField('retry-backoff').value=durationInput(data.retryBackoffMs??30000);taskField('timeout').value=durationInput(data.timeoutMs);taskField('dialog-title').textContent=text('task.editTitle');taskField('save').textContent=text('action.updateTask');updateTaskProjectStatus();document.getElementById('task-dialog').showModal();setTimeout(()=>taskField('name').focus(),50)}catch(error){showToast(error.message,'error')}}
|
|
20007
|
+
async function saveTask(event){event.preventDefault();const form=document.getElementById('task-form');if(!form.reportValidity())return;const id=taskField('id').value;const body={name:taskField('name').value,cwd:taskField('cwd').value,agent:taskField('agent').value,model:taskField('model').value,prompt:taskField('prompt').value,category:taskField('category').value,batchId:taskField('batch').value,importance:Number(taskField('importance').value),urgency:Number(taskField('urgency').value),maxRetries:Number(taskField('max-retries').value),retryBackoff:taskField('retry-backoff').value,timeout:taskField('timeout').value};const button=taskField('save');button.disabled=true;try{const data=await readJson(await fetch(id?'/api/tasks/'+id:'/api/tasks',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}));showToast(text(id?'feedback.taskUpdated':'feedback.taskCreated',{id:data.task.id}));document.getElementById('task-dialog').close();setTimeout(()=>location.assign(id?location.href:'/?cwd='+encodeURIComponent(data.task.cwd||'')),450)}catch(error){showToast(error.message,'error')}finally{button.disabled=false}}
|
|
20008
|
+
function templateField(name){return document.getElementById('template-'+name)}
|
|
20009
|
+
function durationInput(milliseconds){if(milliseconds==null)return '';if(milliseconds===0)return '0';const units=[['d',86400000],['h',3600000],['min',60000],['s',1000],['ms',1]];for(const [unit,factor] of units){if(milliseconds%factor===0)return String(milliseconds/factor)+unit}return String(milliseconds)+'ms'}
|
|
20010
|
+
function localDateTime(milliseconds){const date=new Date(milliseconds);const local=new Date(milliseconds-date.getTimezoneOffset()*60000);return local.toISOString().slice(0,23)}
|
|
20011
|
+
function updateTemplateScheduleFields(){const type=templateField('schedule-type').value;const fields={cron:templateField('cron-field'),recurring:templateField('interval-field'),delayed:templateField('run-at-field')};for(const [name,node] of Object.entries(fields)){node.hidden=name!==type;node.querySelector('input').required=name===type}}
|
|
20012
|
+
function setOriginalRunAt(epoch){const input=templateField('run-at');const local=epoch?localDateTime(epoch):'';input.value=local;input.dataset.originalEpoch=epoch?String(epoch):'';input.dataset.originalLocal=local}
|
|
20013
|
+
function selectedRunAt(){const input=templateField('run-at');return resolveEditedRunAt(input.dataset.originalEpoch?Number(input.dataset.originalEpoch):null,input.dataset.originalLocal||'',input.value)}
|
|
20014
|
+
function openTemplateCreator(){const form=document.getElementById('template-form');form.reset();templateField('id').value='';templateField('dialog-title').textContent=text('template.createTitle');setOriginalRunAt(null);templateField('run-at').value=localDateTime(Date.now()+3600000);updateTemplateScheduleFields();document.getElementById('template-dialog').showModal();setTimeout(()=>templateField('name').focus(),50)}
|
|
20015
|
+
async function openTemplateEditor(id){try{const data=await readJson(await fetch('/api/templates/'+id));templateField('id').value=String(id);templateField('dialog-title').textContent=text('template.editTitle');templateField('name').value=data.name||'';templateField('cwd').value=data.cwd||'';templateField('agent').value=data.agent||'';templateField('model').value=data.model||'default';templateField('prompt').value=data.prompt||'';templateField('schedule-type').value=data.scheduleType;templateField('cron').value=data.cronExpr||'';templateField('interval').value=durationInput(data.intervalMs);setOriginalRunAt(data.runAt||null);if(!data.runAt)templateField('run-at').value=localDateTime(Date.now()+3600000);templateField('category').value=data.category||'general';templateField('batch').value=data.batchId||'';templateField('importance').value=String(data.importance??3);templateField('urgency').value=String(data.urgency??3);templateField('max-instances').value=String(data.maxInstances??1);templateField('max-retries').value=String(data.maxRetries??3);templateField('retry-backoff').value=durationInput(data.retryBackoffMs??30000);templateField('timeout').value=durationInput(data.timeoutMs);updateTemplateScheduleFields();document.getElementById('template-dialog').showModal();setTimeout(()=>templateField('name').focus(),50)}catch(error){showToast(error.message,'error')}}
|
|
20016
|
+
async function saveTemplate(event){event.preventDefault();const form=document.getElementById('template-form');if(!form.reportValidity())return;const id=templateField('id').value;const type=templateField('schedule-type').value;const body={name:templateField('name').value,cwd:templateField('cwd').value,agent:templateField('agent').value,model:templateField('model').value,prompt:templateField('prompt').value,scheduleType:type,cronExpr:templateField('cron').value,interval:templateField('interval').value,runAt:type==='delayed'?selectedRunAt():null,category:templateField('category').value,batchId:templateField('batch').value,importance:Number(templateField('importance').value),urgency:Number(templateField('urgency').value),maxInstances:Number(templateField('max-instances').value),maxRetries:Number(templateField('max-retries').value),retryBackoff:templateField('retry-backoff').value,timeout:templateField('timeout').value};const button=templateField('save');button.disabled=true;try{await readJson(await fetch(id?'/api/templates/'+id:'/api/templates',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}));showToast(text(id?'feedback.templateUpdated':'feedback.templateCreated'));document.getElementById('template-dialog').close();setTimeout(()=>location.assign(id?location.href:'/templates'),450)}catch(error){showToast(error.message,'error')}finally{button.disabled=false}}
|
|
19193
20017
|
async function enableTmpl(id){try{await readJson(await fetch('/api/templates/'+id+'/enable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
|
|
19194
20018
|
async function disableTmpl(id){if(!await ask(text('dialog.disableTemplate'),text('dialog.disableTemplateBody')))return;try{await readJson(await fetch('/api/templates/'+id+'/disable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
|
|
19195
20019
|
async function deleteTmpl(id){if(!await ask(text('dialog.deleteTemplate'),text('dialog.deleteTemplateBody'),true))return;try{await readJson(await fetch('/api/templates/'+id,{method:'DELETE'}));location.reload()}catch(error){showToast(error.message,'error')}}
|
|
@@ -19197,7 +20021,9 @@ function renderLayout(options) {
|
|
|
19197
20021
|
function toggleLog(id,button){const panel=document.getElementById('log-'+id);const hidden=!panel.hidden;panel.hidden=hidden;button.setAttribute('aria-expanded',String(!hidden));button.textContent=text(hidden?'action.logs':'action.hideLogs');}
|
|
19198
20022
|
function filterTasks(value){const query=value.trim().toLocaleLowerCase();let visible=0;document.querySelectorAll('[data-task-row]').forEach(row=>{const match=!query||row.dataset.search.toLocaleLowerCase().includes(query);row.hidden=!match;if(match)visible++});const empty=document.getElementById('search-empty');if(empty)empty.hidden=visible!==0;}
|
|
19199
20023
|
async function clearDatabase(){if(!await askDanger())return;try{const data=await readJson(await fetch('/api/database/clear',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({confirmation:'CLEAR'})}));showToast(text('feedback.databaseCleared',{path:data.backupPath}));setTimeout(()=>location.reload(),1000)}catch(error){showToast(error.message,'error')}}
|
|
19200
|
-
async function
|
|
20024
|
+
async function confirmGatewayRestart(runningCount=0){const body=runningCount>0?text('dialog.restartGatewayRunningBody',{count:runningCount}):text('dialog.restartGatewayBody');return await ask(text('dialog.restartGateway'),body)}
|
|
20025
|
+
async function restartGateway(){try{const data=await readJson(await fetch('/api/gateway/restart',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({confirmation:'RESTART'})}));showToast(text('feedback.restarting'));for(let attempt=0;attempt<120;attempt++){await new Promise(resolve=>setTimeout(resolve,500));try{const status=await readJson(await fetch('/api/gateway/status',{cache:'no-store'}));if(status.pid!==data.previousPid&&status.managed&&status.ready&&!status.restartRequired){location.reload();return true}}catch{}}showToast(text('feedback.restartTimeout'),'error');return false}catch(error){showToast(error.message,'error');return false}}
|
|
20026
|
+
async function saveConfig(restartAfterSave=false,runningCount=0){if(restartAfterSave&&!await confirmGatewayRestart(runningCount))return;const form=document.getElementById('config-form');const data={worker:{maxConcurrency:Number(form.mc.value),pollIntervalMs:Number(form.pi.value),heartbeatIntervalMs:Number(form.hi.value)*1000,taskTimeoutMs:Number(form.to.value)*60000},scheduler:{enabled:form.se.checked,checkIntervalMs:Number(form.si.value)},watchdog:{heartbeatTimeoutMs:Number(form.wt.value)*1000,checkIntervalMs:Number(form.wci.value)*1000,cleanupIntervalMs:Number(form.wcl.value)*3600000,retentionDays:Number(form.rd.value)}};try{await readJson(await fetch('/api/config',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)}));showToast(text('feedback.configSaved'));if(restartAfterSave){const restarted=await restartGateway();if(!restarted)setTimeout(()=>location.reload(),500)}else{setTimeout(()=>location.reload(),500)}}catch(error){showToast(error.message,'error')}}
|
|
19201
20027
|
</script>
|
|
19202
20028
|
</body>
|
|
19203
20029
|
</html>`;
|
|
@@ -19205,6 +20031,7 @@ function renderLayout(options) {
|
|
|
19205
20031
|
|
|
19206
20032
|
// src/web/index.tsx
|
|
19207
20033
|
var app = new Hono2();
|
|
20034
|
+
var LEGACY_PROJECT_FILTER = "__supertask_legacy__";
|
|
19208
20035
|
var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
19209
20036
|
"pending",
|
|
19210
20037
|
"running",
|
|
@@ -19213,6 +20040,41 @@ var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
|
19213
20040
|
"dead_letter",
|
|
19214
20041
|
"cancelled"
|
|
19215
20042
|
]);
|
|
20043
|
+
var SESSION_ID_PATTERN = /^ses_[A-Za-z0-9_]+$/;
|
|
20044
|
+
var runtimeConfig = null;
|
|
20045
|
+
var restartScheduled = false;
|
|
20046
|
+
function setDashboardRuntimeConfig(config) {
|
|
20047
|
+
runtimeConfig = config === null ? null : structuredClone(config);
|
|
20048
|
+
}
|
|
20049
|
+
function isValidSessionId(value) {
|
|
20050
|
+
return typeof value === "string" && SESSION_ID_PATTERN.test(value);
|
|
20051
|
+
}
|
|
20052
|
+
function maskSessionId(value) {
|
|
20053
|
+
return isValidSessionId(value) ? `${value.slice(4, 7)}***${value.slice(-3)}` : "\u2014";
|
|
20054
|
+
}
|
|
20055
|
+
function sessionCommand(value) {
|
|
20056
|
+
if (!isValidSessionId(value)) throw new Error("session unavailable");
|
|
20057
|
+
return `opencode --session ${value}`;
|
|
20058
|
+
}
|
|
20059
|
+
function configsEqual(left, right) {
|
|
20060
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
20061
|
+
}
|
|
20062
|
+
function resolveDashboardConfigState(runtimeAvailable, restartRequired, managedRestart) {
|
|
20063
|
+
if (!runtimeAvailable) return "foreground";
|
|
20064
|
+
if (!restartRequired) return "applied";
|
|
20065
|
+
return managedRestart ? "pending" : "manual";
|
|
20066
|
+
}
|
|
20067
|
+
function isSafeDashboardRestartTarget(diagnostic, currentPid) {
|
|
20068
|
+
return diagnostic.pm2Installed && diagnostic.processFound && diagnostic.status === "online" && diagnostic.pid === currentPid && diagnostic.ready && diagnostic.scopeMatches;
|
|
20069
|
+
}
|
|
20070
|
+
function canRestartCurrentGateway() {
|
|
20071
|
+
if (runtimeConfig === null) return false;
|
|
20072
|
+
try {
|
|
20073
|
+
return isSafeDashboardRestartTarget(getGatewayDiagnostic(), process.pid);
|
|
20074
|
+
} catch {
|
|
20075
|
+
return false;
|
|
20076
|
+
}
|
|
20077
|
+
}
|
|
19216
20078
|
function parsePositiveInteger(value) {
|
|
19217
20079
|
if (!/^\d+$/.test(value)) return null;
|
|
19218
20080
|
const parsed = Number(value);
|
|
@@ -19221,6 +20083,134 @@ function parsePositiveInteger(value) {
|
|
|
19221
20083
|
function parseTaskStatus(value) {
|
|
19222
20084
|
return TASK_STATUSES.has(value) ? value : null;
|
|
19223
20085
|
}
|
|
20086
|
+
function parseTaskPayload(value) {
|
|
20087
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
20088
|
+
throw new Error("\u8BF7\u6C42\u5185\u5BB9\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
20089
|
+
}
|
|
20090
|
+
const input = value;
|
|
20091
|
+
const requiredString = (name) => {
|
|
20092
|
+
const field = input[name];
|
|
20093
|
+
if (typeof field !== "string" || !field.trim()) throw new Error(`${name} \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
20094
|
+
return field.trim();
|
|
20095
|
+
};
|
|
20096
|
+
const optionalString = (name, fallback = null) => {
|
|
20097
|
+
const field = input[name];
|
|
20098
|
+
if (field === void 0 || field === null || field === "") return fallback;
|
|
20099
|
+
if (typeof field !== "string") throw new Error(`${name} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
|
|
20100
|
+
return field.trim() || fallback;
|
|
20101
|
+
};
|
|
20102
|
+
const integer2 = (name, fallback) => {
|
|
20103
|
+
const field = input[name];
|
|
20104
|
+
if (field === void 0 || field === null || field === "") return fallback;
|
|
20105
|
+
if (typeof field !== "number" || !Number.isSafeInteger(field)) {
|
|
20106
|
+
throw new Error(`${name} \u5FC5\u987B\u662F\u6574\u6570`);
|
|
20107
|
+
}
|
|
20108
|
+
return field;
|
|
20109
|
+
};
|
|
20110
|
+
const duration = (name, fallback, allowZero = false) => {
|
|
20111
|
+
const raw2 = optionalString(name, fallback);
|
|
20112
|
+
if (raw2 === null) return null;
|
|
20113
|
+
if (allowZero && raw2 === "0") return 0;
|
|
20114
|
+
if (Number.isFinite(Number(raw2))) {
|
|
20115
|
+
throw new Error(`${name} \u5FC5\u987B\u5E26\u65F6\u95F4\u5355\u4F4D\uFF0C\u8BF7\u4F7F\u7528 30s\u30015min\u30011h \u6216 2d`);
|
|
20116
|
+
}
|
|
20117
|
+
const milliseconds = parseDuration(raw2);
|
|
20118
|
+
if (milliseconds === null || !Number.isSafeInteger(milliseconds)) {
|
|
20119
|
+
throw new Error(`${name} \u683C\u5F0F\u65E0\u6548\uFF0C\u8BF7\u4F7F\u7528 30s\u30015min\u30011h \u6216 2d`);
|
|
20120
|
+
}
|
|
20121
|
+
return milliseconds;
|
|
20122
|
+
};
|
|
20123
|
+
return {
|
|
20124
|
+
name: requiredString("name"),
|
|
20125
|
+
cwd: requiredString("cwd"),
|
|
20126
|
+
agent: requiredString("agent"),
|
|
20127
|
+
model: optionalString("model", "default"),
|
|
20128
|
+
prompt: requiredString("prompt"),
|
|
20129
|
+
category: optionalString("category", "general"),
|
|
20130
|
+
batchId: optionalString("batchId"),
|
|
20131
|
+
importance: integer2("importance", 3),
|
|
20132
|
+
urgency: integer2("urgency", 3),
|
|
20133
|
+
maxRetries: integer2("maxRetries", 3),
|
|
20134
|
+
retryBackoffMs: duration("retryBackoff", "30s", true),
|
|
20135
|
+
timeoutMs: duration("timeout", null)
|
|
20136
|
+
};
|
|
20137
|
+
}
|
|
20138
|
+
function editableTaskPayload(input) {
|
|
20139
|
+
return {
|
|
20140
|
+
name: input.name,
|
|
20141
|
+
agent: input.agent,
|
|
20142
|
+
model: input.model ?? "default",
|
|
20143
|
+
prompt: input.prompt,
|
|
20144
|
+
category: input.category ?? "general",
|
|
20145
|
+
batchId: input.batchId ?? null,
|
|
20146
|
+
importance: input.importance ?? 3,
|
|
20147
|
+
urgency: input.urgency ?? 3,
|
|
20148
|
+
maxRetries: input.maxRetries ?? 3,
|
|
20149
|
+
retryBackoffMs: input.retryBackoffMs ?? 3e4,
|
|
20150
|
+
timeoutMs: input.timeoutMs ?? null
|
|
20151
|
+
};
|
|
20152
|
+
}
|
|
20153
|
+
function parseTemplatePayload(value) {
|
|
20154
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
20155
|
+
throw new Error("\u8BF7\u6C42\u5185\u5BB9\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
20156
|
+
}
|
|
20157
|
+
const input = value;
|
|
20158
|
+
const requiredString = (name) => {
|
|
20159
|
+
const field = input[name];
|
|
20160
|
+
if (typeof field !== "string" || !field.trim()) throw new Error(`${name} \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
20161
|
+
return field.trim();
|
|
20162
|
+
};
|
|
20163
|
+
const optionalString = (name, fallback = null) => {
|
|
20164
|
+
const field = input[name];
|
|
20165
|
+
if (field === void 0 || field === null || field === "") return fallback;
|
|
20166
|
+
if (typeof field !== "string") throw new Error(`${name} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
|
|
20167
|
+
return field.trim() || fallback;
|
|
20168
|
+
};
|
|
20169
|
+
const integer2 = (name, fallback) => {
|
|
20170
|
+
const field = input[name];
|
|
20171
|
+
if (field === void 0 || field === null || field === "") return fallback;
|
|
20172
|
+
if (typeof field !== "number" || !Number.isSafeInteger(field)) {
|
|
20173
|
+
throw new Error(`${name} \u5FC5\u987B\u662F\u6574\u6570`);
|
|
20174
|
+
}
|
|
20175
|
+
return field;
|
|
20176
|
+
};
|
|
20177
|
+
const duration = (name, fallback, allowZero = false) => {
|
|
20178
|
+
const raw2 = optionalString(name, fallback);
|
|
20179
|
+
if (raw2 === null) return null;
|
|
20180
|
+
if (allowZero && raw2 === "0") return 0;
|
|
20181
|
+
if (Number.isFinite(Number(raw2))) {
|
|
20182
|
+
throw new Error(`${name} \u5FC5\u987B\u5E26\u65F6\u95F4\u5355\u4F4D\uFF0C\u8BF7\u4F7F\u7528 30s\u30015min\u30011h \u6216 2d`);
|
|
20183
|
+
}
|
|
20184
|
+
const milliseconds = parseDuration(raw2);
|
|
20185
|
+
if (milliseconds === null || !Number.isSafeInteger(milliseconds)) {
|
|
20186
|
+
throw new Error(`${name} \u683C\u5F0F\u65E0\u6548\uFF0C\u8BF7\u4F7F\u7528 30s\u30015min\u30011h \u6216 2d`);
|
|
20187
|
+
}
|
|
20188
|
+
return milliseconds;
|
|
20189
|
+
};
|
|
20190
|
+
const scheduleType = requiredString("scheduleType");
|
|
20191
|
+
if (!["cron", "delayed", "recurring"].includes(scheduleType)) {
|
|
20192
|
+
throw new Error("scheduleType \u5FC5\u987B\u662F cron\u3001delayed \u6216 recurring");
|
|
20193
|
+
}
|
|
20194
|
+
return {
|
|
20195
|
+
name: requiredString("name"),
|
|
20196
|
+
agent: requiredString("agent"),
|
|
20197
|
+
model: optionalString("model", "default"),
|
|
20198
|
+
prompt: requiredString("prompt"),
|
|
20199
|
+
cwd: requiredString("cwd"),
|
|
20200
|
+
category: optionalString("category", "general"),
|
|
20201
|
+
importance: integer2("importance", 3),
|
|
20202
|
+
urgency: integer2("urgency", 3),
|
|
20203
|
+
batchId: optionalString("batchId"),
|
|
20204
|
+
scheduleType,
|
|
20205
|
+
cronExpr: scheduleType === "cron" ? requiredString("cronExpr") : null,
|
|
20206
|
+
intervalMs: scheduleType === "recurring" ? duration("interval", null) : null,
|
|
20207
|
+
runAt: scheduleType === "delayed" ? integer2("runAt", null) : null,
|
|
20208
|
+
maxInstances: integer2("maxInstances", 1),
|
|
20209
|
+
maxRetries: integer2("maxRetries", 3),
|
|
20210
|
+
retryBackoffMs: duration("retryBackoff", "30s", true),
|
|
20211
|
+
timeoutMs: duration("timeout", null)
|
|
20212
|
+
};
|
|
20213
|
+
}
|
|
19224
20214
|
function safeStatus(value) {
|
|
19225
20215
|
return value && TASK_STATUSES.has(value) ? value : "unknown";
|
|
19226
20216
|
}
|
|
@@ -19286,19 +20276,19 @@ function esc(value) {
|
|
|
19286
20276
|
}
|
|
19287
20277
|
function readCurrentConfig() {
|
|
19288
20278
|
const configPath = getConfigPath();
|
|
19289
|
-
if (!
|
|
20279
|
+
if (!existsSync5(configPath)) return {};
|
|
19290
20280
|
try {
|
|
19291
|
-
return JSON.parse(
|
|
20281
|
+
return JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
19292
20282
|
} catch {
|
|
19293
20283
|
return {};
|
|
19294
20284
|
}
|
|
19295
20285
|
}
|
|
19296
20286
|
function writeConfig(cfg) {
|
|
19297
20287
|
const configPath = getConfigPath();
|
|
19298
|
-
const dir =
|
|
19299
|
-
if (!
|
|
20288
|
+
const dir = dirname4(configPath);
|
|
20289
|
+
if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true });
|
|
19300
20290
|
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
19301
|
-
|
|
20291
|
+
writeFileSync3(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
19302
20292
|
renameSync2(tempPath, configPath);
|
|
19303
20293
|
}
|
|
19304
20294
|
function statCard(value, label, tone, cardIcon, delay = "") {
|
|
@@ -19336,24 +20326,52 @@ app.get("/", async (c) => {
|
|
|
19336
20326
|
const page = parsePositiveInteger(c.req.query("page") || "1");
|
|
19337
20327
|
if (page === null) return c.text("invalid page", 400);
|
|
19338
20328
|
const statusFilter = c.req.query("status") || "";
|
|
20329
|
+
const requestedCwd = c.req.query("cwd") || "";
|
|
20330
|
+
const legacyProjectFilter = requestedCwd === LEGACY_PROJECT_FILTER;
|
|
20331
|
+
const cwdFilter = legacyProjectFilter ? "" : requestedCwd;
|
|
20332
|
+
const projectFilter = legacyProjectFilter ? LEGACY_PROJECT_FILTER : cwdFilter;
|
|
19339
20333
|
const parsedStatus = statusFilter ? parseTaskStatus(statusFilter) : null;
|
|
19340
20334
|
if (statusFilter && !parsedStatus) return c.text("invalid status", 400);
|
|
19341
20335
|
const limit = 50;
|
|
19342
20336
|
const offset = (page - 1) * limit;
|
|
19343
|
-
const [tasks4, statsData] = await Promise.all([
|
|
19344
|
-
TaskService.list({
|
|
19345
|
-
|
|
20337
|
+
const [tasks4, statsData, globalStats, projects, globalRunning, legacyStats, legacyRunning] = await Promise.all([
|
|
20338
|
+
TaskService.list({
|
|
20339
|
+
limit,
|
|
20340
|
+
offset,
|
|
20341
|
+
...parsedStatus === "running" ? { activeExecution: true } : parsedStatus ? { status: parsedStatus } : {},
|
|
20342
|
+
...legacyProjectFilter ? { legacyCwd: true } : cwdFilter ? { cwd: cwdFilter } : {}
|
|
20343
|
+
}),
|
|
20344
|
+
TaskService.stats(legacyProjectFilter ? { legacyCwd: true } : cwdFilter ? { cwd: cwdFilter } : {}),
|
|
20345
|
+
TaskService.stats(),
|
|
20346
|
+
TaskService.projectSummaries(1e3),
|
|
20347
|
+
TaskService.countRunning(),
|
|
20348
|
+
TaskService.stats({ legacyCwd: true }),
|
|
20349
|
+
TaskService.countRunning({ legacyCwd: true })
|
|
19346
20350
|
]);
|
|
19347
20351
|
const latestRuns = await TaskRunService.getLatestByTaskIds(tasks4.map((task) => task.id));
|
|
20352
|
+
const selectedRunning = legacyProjectFilter ? legacyRunning : cwdFilter ? projects.find((project) => project.cwd === cwdFilter)?.running ?? 0 : globalRunning;
|
|
19348
20353
|
const counts = {
|
|
19349
20354
|
pending: statsData.pending || 0,
|
|
19350
|
-
running:
|
|
20355
|
+
running: selectedRunning,
|
|
19351
20356
|
done: statsData.done || 0,
|
|
19352
20357
|
failed: (statsData.failed || 0) + (statsData.dead_letter || 0),
|
|
19353
20358
|
total: statsData.total || 0
|
|
19354
20359
|
};
|
|
19355
|
-
const filteredTotal = parsedStatus ? Number(statsData[parsedStatus] ?? 0) : counts.total;
|
|
20360
|
+
const filteredTotal = parsedStatus === "running" ? selectedRunning : parsedStatus ? Number(statsData[parsedStatus] ?? 0) : counts.total;
|
|
19356
20361
|
const totalPages = Math.max(1, Math.ceil(filteredTotal / limit));
|
|
20362
|
+
if (page > totalPages) {
|
|
20363
|
+
const params = new URLSearchParams({ page: String(totalPages) });
|
|
20364
|
+
if (statusFilter) params.set("status", statusFilter);
|
|
20365
|
+
if (projectFilter) params.set("cwd", projectFilter);
|
|
20366
|
+
return c.redirect(`/?${params.toString()}`);
|
|
20367
|
+
}
|
|
20368
|
+
const taskListUrl = (status, cwd) => {
|
|
20369
|
+
const params = new URLSearchParams();
|
|
20370
|
+
if (status) params.set("status", status);
|
|
20371
|
+
if (cwd) params.set("cwd", cwd);
|
|
20372
|
+
const query = params.toString();
|
|
20373
|
+
return query ? `/?${query}` : "/";
|
|
20374
|
+
};
|
|
19357
20375
|
const filterItems = [
|
|
19358
20376
|
{ status: "", label: t(locale, "filter.all") },
|
|
19359
20377
|
{ status: "pending", label: statusText(locale, "pending") },
|
|
@@ -19364,22 +20382,27 @@ app.get("/", async (c) => {
|
|
|
19364
20382
|
{ status: "cancelled", label: statusText(locale, "cancelled") }
|
|
19365
20383
|
];
|
|
19366
20384
|
const filters = filterItems.map(({ status, label }) => {
|
|
19367
|
-
const href = status
|
|
19368
|
-
return `<a href="${href}" class="filter-chip ${statusFilter === status ? "active" : ""}">${label}</a>`;
|
|
20385
|
+
const href = taskListUrl(status, projectFilter);
|
|
20386
|
+
return `<a href="${esc(href)}" class="filter-chip ${statusFilter === status ? "active" : ""}">${label}</a>`;
|
|
19369
20387
|
}).join("");
|
|
19370
20388
|
const rows = tasks4.map((task) => {
|
|
19371
20389
|
const status = safeStatus(task.status);
|
|
19372
|
-
const
|
|
19373
|
-
const
|
|
20390
|
+
const latestRun = latestRuns.get(task.id);
|
|
20391
|
+
const executionActive = latestRun?.status === "running";
|
|
20392
|
+
const batchId = task.batchId?.trim() || null;
|
|
20393
|
+
const searchable = esc(`${task.name} ${task.agent} ${task.prompt} ${task.cwd ?? ""} ${task.batchId ?? ""} ${task.category ?? ""}`);
|
|
19374
20394
|
return `<tr data-task-row data-search="${searchable}">
|
|
19375
20395
|
<td class="faint" data-label="${t(locale, "table.id")}">#${task.id}</td>
|
|
19376
|
-
<td data-primary data-label="${t(locale, "table.task")}"><div class="task-name">${esc(task.name)}</div><div class="task-prompt" title="${esc(task.prompt)}">${esc(task.prompt.substring(0, 160))}</div
|
|
20396
|
+
<td data-primary data-label="${t(locale, "table.task")}"><div class="task-name">${esc(task.name)}</div><div class="task-prompt" title="${esc(task.prompt)}">${esc(task.prompt.substring(0, 160))}</div>
|
|
20397
|
+
<div class="actions task-context"><span class="tag" title="${esc(task.cwd ?? "")}">${esc(task.cwd ? basename2(task.cwd) || task.cwd : t(locale, "projects.legacy"))}</span>${batchId ? `<span class="tag" title="${esc(t(locale, "template.batchId"))}">${esc(batchId)}</span>` : ""}</div></td>
|
|
19377
20398
|
<td data-label="${t(locale, "table.agent")}"><span class="tag">${esc(task.agent)}</span></td>
|
|
19378
|
-
<td data-label="${t(locale, "table.status")}"><span class="badge b-${status}">${statusText(locale, status)}</span
|
|
19379
|
-
<td data-label="${t(locale, "table.duration")}" class="small ${task.status === "running" ? "" : "muted"}">${formatDuration(task.startedAt, task.finishedAt)}</td>
|
|
20399
|
+
<td data-label="${t(locale, "table.status")}"><span class="badge b-${status}" ${status === "dead_letter" ? `title="${esc(t(locale, "status.deadLetterHint"))}"` : ""}>${statusText(locale, status)}</span>${status === "dead_letter" ? `<div class="muted small" style="margin-top:5px">${t(locale, "status.deadLetterAction")}</div>` : ""}${executionActive && status !== "running" ? `<div class="muted small" style="margin-top:5px">${t(locale, "status.executionStillActive")}</div>` : ""}</td>
|
|
20400
|
+
<td data-label="${t(locale, "table.duration")}" class="small ${executionActive || task.status === "running" ? "" : "muted"}">${formatDuration(task.startedAt, task.finishedAt)}</td>
|
|
19380
20401
|
<td data-label="${t(locale, "table.retries")}" class="muted small">${(task.retryCount ?? 0) > 0 ? task.retryCount : "\u2014"}</td>
|
|
19381
20402
|
<td data-label="${t(locale, "table.actions")}"><div class="actions">
|
|
20403
|
+
${task.cwd?.trim() && ["pending", "failed", "dead_letter"].includes(task.status ?? "") ? `<button type="button" class="btn" onclick="openTaskEditor(${task.id})">${t(locale, "action.edit")}</button>` : ""}
|
|
19382
20404
|
<button type="button" class="btn" onclick="showDetail(${task.id})">${t(locale, "action.details")}</button>
|
|
20405
|
+
${isValidSessionId(latestRun?.sessionId) ? `<button type="button" class="btn" onclick="copySessionCommand(${latestRun.id})">${icon("copy")}${t(locale, "action.continueSession")}</button>` : ""}
|
|
19383
20406
|
${task.status === "failed" || task.status === "dead_letter" ? `<button type="button" class="btn btn-warning" onclick="retryTask(${task.id})">${t(locale, "action.retry")}</button>` : ""}
|
|
19384
20407
|
${["pending", "running", "failed"].includes(task.status ?? "") ? `<button type="button" class="btn btn-warning" onclick="cancelTask(${task.id})">${t(locale, "action.cancel")}</button>` : ""}
|
|
19385
20408
|
${task.status === "running" || executionActive ? "" : `<button type="button" class="btn btn-danger" onclick="deleteTask(${task.id})">${t(locale, "action.delete")}</button>`}
|
|
@@ -19391,7 +20414,32 @@ app.get("/", async (c) => {
|
|
|
19391
20414
|
<tbody>${rows}</tbody>
|
|
19392
20415
|
</table></div>
|
|
19393
20416
|
<div id="search-empty" hidden>${emptyState(t(locale, "filter.noResults"), "")}</div>`;
|
|
19394
|
-
const
|
|
20417
|
+
const paginationParts = [];
|
|
20418
|
+
if (statusFilter) paginationParts.push(`status=${encodeURIComponent(statusFilter)}`);
|
|
20419
|
+
if (projectFilter) paginationParts.push(`cwd=${encodeURIComponent(projectFilter)}`);
|
|
20420
|
+
const suffix = paginationParts.length > 0 ? `&${paginationParts.join("&")}` : "";
|
|
20421
|
+
const projectCards = [
|
|
20422
|
+
`<a class="project-card ${projectFilter ? "" : "active"}" href="${esc(taskListUrl(statusFilter, ""))}">
|
|
20423
|
+
<div class="project-card-head"><strong>${t(locale, "projects.all")}</strong><span>${globalStats.total || 0}</span></div>
|
|
20424
|
+
<div class="project-counts">${t(locale, "projects.counts", { running: globalRunning, pending: globalStats.pending || 0, failed: (globalStats.failed || 0) + (globalStats.dead_letter || 0) })}</div>
|
|
20425
|
+
</a>`,
|
|
20426
|
+
...legacyStats.total > 0 ? [`<a class="project-card ${legacyProjectFilter ? "active" : ""}" href="${esc(taskListUrl(statusFilter, LEGACY_PROJECT_FILTER))}">
|
|
20427
|
+
<div class="project-card-head"><strong>${t(locale, "projects.legacy")}</strong><span>${legacyStats.total}</span></div>
|
|
20428
|
+
<div class="project-path">${t(locale, "projects.legacyHint")}</div>
|
|
20429
|
+
<div class="project-counts">${t(locale, "projects.counts", { running: legacyRunning, pending: legacyStats.pending || 0, failed: (legacyStats.failed || 0) + (legacyStats.dead_letter || 0) })}</div>
|
|
20430
|
+
</a>`] : [],
|
|
20431
|
+
...projects.map((project) => `<a class="project-card ${cwdFilter === project.cwd ? "active" : ""}" href="${esc(taskListUrl(statusFilter, project.cwd))}" title="${esc(project.cwd)}">
|
|
20432
|
+
<div class="project-card-head"><strong>${esc(basename2(project.cwd) || project.cwd)}</strong><span>${project.total}</span></div>
|
|
20433
|
+
<div class="project-path">${esc(project.cwd)}</div>
|
|
20434
|
+
<div class="project-counts">${t(locale, "projects.counts", { running: project.running, pending: project.pending, failed: project.failed })}</div>
|
|
20435
|
+
</a>`)
|
|
20436
|
+
].join("");
|
|
20437
|
+
const projectData = JSON.stringify(Object.fromEntries(projects.map((project) => [project.cwd, {
|
|
20438
|
+
total: project.total,
|
|
20439
|
+
pending: project.pending,
|
|
20440
|
+
running: project.running,
|
|
20441
|
+
failed: project.failed
|
|
20442
|
+
}]))).replace(/</g, "\\u003c");
|
|
19395
20443
|
const body = `
|
|
19396
20444
|
<div class="stats-grid">
|
|
19397
20445
|
${statCard(counts.pending, t(locale, "stats.pending"), "tone-neutral", icon("clock"))}
|
|
@@ -19399,19 +20447,61 @@ app.get("/", async (c) => {
|
|
|
19399
20447
|
${statCard(counts.done, t(locale, "stats.done"), "tone-green", icon("check"), "reveal-delay-1")}
|
|
19400
20448
|
${statCard(counts.failed, t(locale, "stats.failedDead"), "tone-red", icon("alert"), "reveal-delay-2")}
|
|
19401
20449
|
</div>
|
|
20450
|
+
<section class="panel project-panel reveal reveal-delay-1">
|
|
20451
|
+
<div class="panel-head"><div><h2>${t(locale, "projects.title")}</h2><p>${t(locale, "projects.description")}</p></div><button type="button" class="btn btn-primary" onclick="openTaskCreator()">${t(locale, "action.createTask")}</button></div>
|
|
20452
|
+
<div class="project-grid">${projectCards}</div>
|
|
20453
|
+
</section>
|
|
19402
20454
|
<div class="toolbar reveal reveal-delay-1">
|
|
19403
20455
|
<div class="filters">${filters}</div>
|
|
19404
20456
|
<label class="search-box">${icon("search")}<input type="search" oninput="filterTasks(this.value)" placeholder="${t(locale, "filter.searchTasks")}" aria-label="${t(locale, "filter.searchTasks")}"></label>
|
|
19405
20457
|
</div>
|
|
19406
20458
|
<section class="panel reveal reveal-delay-2">${table}</section>
|
|
19407
|
-
${pagination(locale, "/", page, totalPages, filteredTotal, suffix)}
|
|
20459
|
+
${pagination(locale, "/", page, totalPages, filteredTotal, suffix)}
|
|
20460
|
+
<script type="application/json" id="task-project-data">${projectData}</script>
|
|
20461
|
+
<dialog id="task-dialog" class="template-dialog">
|
|
20462
|
+
<form id="task-form" data-default-cwd="${esc(cwdFilter)}" onsubmit="saveTask(event)">
|
|
20463
|
+
<input id="task-id" type="hidden">
|
|
20464
|
+
<div class="dialog-head"><div><h2 id="task-dialog-title">${t(locale, "task.createTitle")}</h2><p>${t(locale, "task.formSubtitle")}</p></div><button type="button" class="icon-button" onclick="document.getElementById('task-dialog').close()" aria-label="${t(locale, "action.close")}">${icon("close")}</button></div>
|
|
20465
|
+
<div class="dialog-body">
|
|
20466
|
+
<div class="template-form-grid">
|
|
20467
|
+
<label class="form-field"><span>${t(locale, "template.name")}</span><input id="task-name" required maxlength="200" autocomplete="off"></label>
|
|
20468
|
+
<label class="form-field"><span>${t(locale, "template.cwd")}</span><input id="task-cwd" required autocomplete="off" list="task-cwd-options" placeholder="/path/to/project" oninput="updateTaskProjectStatus()"><small>${t(locale, "template.cwdHint")}</small></label>
|
|
20469
|
+
<datalist id="task-cwd-options">${projects.map((project) => `<option value="${esc(project.cwd)}"></option>`).join("")}</datalist>
|
|
20470
|
+
<label class="form-field"><span>${t(locale, "template.agent")}</span><input id="task-agent" required autocomplete="off" value="build" placeholder="build"></label>
|
|
20471
|
+
<label class="form-field"><span>${t(locale, "template.model")}</span><input id="task-model" required autocomplete="off" value="default" placeholder="default"></label>
|
|
20472
|
+
<label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="task-prompt" rows="6" required></textarea></label>
|
|
20473
|
+
</div>
|
|
20474
|
+
<p id="task-project-status" class="form-note"></p>
|
|
20475
|
+
<details class="advanced-fields">
|
|
20476
|
+
<summary>${t(locale, "template.advanced")}</summary>
|
|
20477
|
+
<div class="template-form-grid">
|
|
20478
|
+
<label class="form-field"><span>${t(locale, "template.category")}</span><input id="task-category" autocomplete="off" value="general"></label>
|
|
20479
|
+
<label class="form-field"><span>${t(locale, "template.batchId")}</span><input id="task-batch" autocomplete="off"><small>${t(locale, "task.batchHint")}</small></label>
|
|
20480
|
+
<label class="form-field"><span>${t(locale, "template.importance")}</span><input id="task-importance" type="number" min="1" max="5" step="1" value="3" required></label>
|
|
20481
|
+
<label class="form-field"><span>${t(locale, "template.urgency")}</span><input id="task-urgency" type="number" min="1" max="5" step="1" value="3" required></label>
|
|
20482
|
+
<label class="form-field"><span>${t(locale, "template.maxRetries")}</span><input id="task-max-retries" type="number" min="0" max="1000" step="1" value="3" required></label>
|
|
20483
|
+
<label class="form-field"><span>${t(locale, "template.retryBackoff")}</span><input id="task-retry-backoff" autocomplete="off" value="30s"><small>${t(locale, "template.durationHint")}</small></label>
|
|
20484
|
+
<label class="form-field"><span>${t(locale, "template.timeout")}</span><input id="task-timeout" autocomplete="off" placeholder="30min"><small>${t(locale, "template.optional")}</small></label>
|
|
20485
|
+
</div>
|
|
20486
|
+
</details>
|
|
20487
|
+
</div>
|
|
20488
|
+
<div class="dialog-actions"><button type="button" class="btn" onclick="document.getElementById('task-dialog').close()">${t(locale, "action.cancel")}</button><button id="task-save" type="submit" class="btn btn-primary">${t(locale, "action.saveTask")}</button></div>
|
|
20489
|
+
</form>
|
|
20490
|
+
</dialog>`;
|
|
19408
20491
|
return c.html(renderLayout({ locale, activeTab: "tasks", body }));
|
|
19409
20492
|
});
|
|
19410
20493
|
app.get("/templates", async (c) => {
|
|
19411
20494
|
const locale = resolveLocale(c);
|
|
19412
|
-
const
|
|
19413
|
-
|
|
19414
|
-
const
|
|
20495
|
+
const page = parsePositiveInteger(c.req.query("page") || "1");
|
|
20496
|
+
if (page === null) return c.text("invalid page", 400);
|
|
20497
|
+
const limit = 50;
|
|
20498
|
+
const offset = (page - 1) * limit;
|
|
20499
|
+
const [templates, templateStats] = await Promise.all([
|
|
20500
|
+
TaskTemplateService.list(limit, offset),
|
|
20501
|
+
TaskTemplateService.stats()
|
|
20502
|
+
]);
|
|
20503
|
+
const totalPages = Math.max(1, Math.ceil(templateStats.total / limit));
|
|
20504
|
+
if (page > totalPages) return c.redirect(`/templates?page=${totalPages}`);
|
|
19415
20505
|
const rows = templates.map((template) => {
|
|
19416
20506
|
const scheduleType = ["cron", "recurring", "delayed"].includes(template.scheduleType) ? template.scheduleType : "unknown";
|
|
19417
20507
|
const typeLabel = scheduleType === "cron" ? t(locale, "schedule.cron") : scheduleType === "recurring" ? t(locale, "schedule.recurring") : scheduleType === "delayed" ? t(locale, "schedule.delayed") : t(locale, "schedule.unknown");
|
|
@@ -19429,21 +20519,56 @@ app.get("/templates", async (c) => {
|
|
|
19429
20519
|
<td data-label="${t(locale, "table.status")}"><span class="badge ${template.enabled ? "b-done" : "b-cancelled"}">${t(locale, template.enabled ? "schedule.enabled" : "schedule.disabled")}</span></td>
|
|
19430
20520
|
<td data-label="${t(locale, "table.lastRun")}" class="small muted">${formatRelative(template.lastRunAt, locale)}</td>
|
|
19431
20521
|
<td data-label="${t(locale, "table.nextRun")}" class="small">${formatFuture(template.nextRunAt, locale)}</td>
|
|
19432
|
-
<td data-label="${t(locale, "table.actions")}"><div class="actions"><button type="button" class="btn" onclick="showTemplateDetail(${template.id})">${t(locale, "action.details")}</button>
|
|
20522
|
+
<td data-label="${t(locale, "table.actions")}"><div class="actions"><button type="button" class="btn" onclick="openTemplateEditor(${template.id})">${t(locale, "action.edit")}</button><button type="button" class="btn" onclick="showTemplateDetail(${template.id})">${t(locale, "action.details")}</button>
|
|
19433
20523
|
<button type="button" class="btn btn-primary" onclick="triggerTmpl(${template.id})">${t(locale, "action.trigger")}</button>${toggle}
|
|
19434
20524
|
<button type="button" class="btn btn-danger" onclick="deleteTmpl(${template.id})">${t(locale, "action.delete")}</button></div></td>
|
|
19435
20525
|
</tr>`;
|
|
19436
20526
|
}).join("");
|
|
19437
20527
|
const body = `
|
|
19438
20528
|
<div class="stats-grid three">
|
|
19439
|
-
${statCard(
|
|
19440
|
-
${statCard(enabled, t(locale, "stats.enabled"), "tone-green", icon("check"), "reveal-delay-1")}
|
|
19441
|
-
${statCard(disabled, t(locale, "stats.disabled"), "tone-neutral", icon("clock"), "reveal-delay-2")}
|
|
20529
|
+
${statCard(templateStats.total, t(locale, "stats.templates"), "tone-purple", icon("templates"))}
|
|
20530
|
+
${statCard(templateStats.enabled, t(locale, "stats.enabled"), "tone-green", icon("check"), "reveal-delay-1")}
|
|
20531
|
+
${statCard(templateStats.disabled, t(locale, "stats.disabled"), "tone-neutral", icon("clock"), "reveal-delay-2")}
|
|
19442
20532
|
</div>
|
|
19443
20533
|
<section class="panel reveal reveal-delay-2">
|
|
19444
|
-
<div class="panel-head"><h2>${t(locale, "page.templates.title")}</h2></div>
|
|
19445
|
-
${templates.length === 0 ? emptyState(t(locale, "empty.templates"), t(locale, "empty.templatesHint")
|
|
19446
|
-
</section
|
|
20534
|
+
<div class="panel-head"><h2>${t(locale, "page.templates.title")}</h2><button type="button" class="btn btn-primary" onclick="openTemplateCreator()">${t(locale, "action.createTemplate")}</button></div>
|
|
20535
|
+
${templates.length === 0 ? emptyState(t(locale, "empty.templates"), t(locale, "empty.templatesHint")) : `<div class="table-wrap"><table class="responsive-table"><thead><tr><th>ID</th><th>${t(locale, "table.name")}</th><th>${t(locale, "table.type")}</th><th>${t(locale, "table.rule")}</th><th>${t(locale, "table.status")}</th><th>${t(locale, "table.lastRun")}</th><th>${t(locale, "table.nextRun")}</th><th>${t(locale, "table.actions")}</th></tr></thead><tbody>${rows}</tbody></table></div>`}
|
|
20536
|
+
</section>
|
|
20537
|
+
<dialog id="template-dialog" class="template-dialog">
|
|
20538
|
+
<form id="template-form" onsubmit="saveTemplate(event)">
|
|
20539
|
+
<input id="template-id" type="hidden">
|
|
20540
|
+
<div class="dialog-head"><div><h2 id="template-dialog-title">${t(locale, "template.createTitle")}</h2><p>${t(locale, "template.formSubtitle")}</p></div><button type="button" class="icon-button" onclick="document.getElementById('template-dialog').close()" aria-label="${t(locale, "action.close")}">${icon("close")}</button></div>
|
|
20541
|
+
<div class="dialog-body">
|
|
20542
|
+
<div class="template-form-grid">
|
|
20543
|
+
<label class="form-field"><span>${t(locale, "template.name")}</span><input id="template-name" required maxlength="200" autocomplete="off"></label>
|
|
20544
|
+
<label class="form-field"><span>${t(locale, "template.cwd")}</span><input id="template-cwd" required autocomplete="off" placeholder="/path/to/project"><small>${t(locale, "template.cwdHint")}</small></label>
|
|
20545
|
+
<label class="form-field"><span>${t(locale, "template.agent")}</span><input id="template-agent" required autocomplete="off" placeholder="build"></label>
|
|
20546
|
+
<label class="form-field"><span>${t(locale, "template.model")}</span><input id="template-model" required autocomplete="off" value="default" placeholder="default"></label>
|
|
20547
|
+
<label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="template-prompt" rows="6" required></textarea></label>
|
|
20548
|
+
<label class="form-field"><span>${t(locale, "template.scheduleType")}</span><select id="template-schedule-type" onchange="updateTemplateScheduleFields()"><option value="recurring">${t(locale, "schedule.recurring")}</option><option value="delayed">${t(locale, "schedule.delayed")}</option><option value="cron">${t(locale, "schedule.cron")}</option></select></label>
|
|
20549
|
+
<label id="template-cron-field" class="form-field" hidden><span>${t(locale, "template.cronExpr")}</span><input id="template-cron" autocomplete="off" placeholder="0 9 * * *"><small>${t(locale, "template.cronHint")}</small></label>
|
|
20550
|
+
<label id="template-interval-field" class="form-field"><span>${t(locale, "template.interval")}</span><input id="template-interval" autocomplete="off" value="1h" placeholder="30s / 5min / 1h"><small>${t(locale, "template.durationHint")}</small></label>
|
|
20551
|
+
<label id="template-run-at-field" class="form-field" hidden><span>${t(locale, "template.runAt")}</span><input id="template-run-at" type="datetime-local" step="0.001"></label>
|
|
20552
|
+
</div>
|
|
20553
|
+
<details class="advanced-fields">
|
|
20554
|
+
<summary>${t(locale, "template.advanced")}</summary>
|
|
20555
|
+
<div class="template-form-grid">
|
|
20556
|
+
<label class="form-field"><span>${t(locale, "template.category")}</span><input id="template-category" autocomplete="off" value="general"></label>
|
|
20557
|
+
<label class="form-field"><span>${t(locale, "template.batchId")}</span><input id="template-batch" autocomplete="off"><small>${t(locale, "template.optional")}</small></label>
|
|
20558
|
+
<label class="form-field"><span>${t(locale, "template.importance")}</span><input id="template-importance" type="number" min="1" max="5" step="1" value="3" required></label>
|
|
20559
|
+
<label class="form-field"><span>${t(locale, "template.urgency")}</span><input id="template-urgency" type="number" min="1" max="5" step="1" value="3" required></label>
|
|
20560
|
+
<label class="form-field"><span>${t(locale, "template.maxInstances")}</span><input id="template-max-instances" type="number" min="1" max="1000" step="1" value="1" required><small>${t(locale, "template.maxInstancesHint")}</small></label>
|
|
20561
|
+
<label class="form-field"><span>${t(locale, "template.maxRetries")}</span><input id="template-max-retries" type="number" min="0" max="1000" step="1" value="3" required></label>
|
|
20562
|
+
<label class="form-field"><span>${t(locale, "template.retryBackoff")}</span><input id="template-retry-backoff" autocomplete="off" value="30s"><small>${t(locale, "template.durationHint")}</small></label>
|
|
20563
|
+
<label class="form-field"><span>${t(locale, "template.timeout")}</span><input id="template-timeout" autocomplete="off" placeholder="30min"><small>${t(locale, "template.optional")}</small></label>
|
|
20564
|
+
</div>
|
|
20565
|
+
</details>
|
|
20566
|
+
<p class="form-note">${t(locale, "template.futureOnly")}</p>
|
|
20567
|
+
</div>
|
|
20568
|
+
<div class="dialog-actions"><button type="button" class="btn" onclick="document.getElementById('template-dialog').close()">${t(locale, "action.cancel")}</button><button id="template-save" type="submit" class="btn btn-primary">${t(locale, "action.saveTemplate")}</button></div>
|
|
20569
|
+
</form>
|
|
20570
|
+
</dialog>
|
|
20571
|
+
${pagination(locale, "/templates", page, totalPages, templateStats.total)}`;
|
|
19447
20572
|
return c.html(renderLayout({ locale, activeTab: "templates", body }));
|
|
19448
20573
|
});
|
|
19449
20574
|
app.get("/runs", async (c) => {
|
|
@@ -19471,9 +20596,11 @@ app.get("/runs", async (c) => {
|
|
|
19471
20596
|
const totalResult = await db.select({ count: sql`count(*)` }).from(taskRuns4);
|
|
19472
20597
|
const total = Number(totalResult[0]?.count ?? 0);
|
|
19473
20598
|
const totalPages = Math.max(1, Math.ceil(total / limit));
|
|
20599
|
+
if (page > totalPages) return c.redirect(`/runs?page=${totalPages}`);
|
|
19474
20600
|
const logs = [];
|
|
19475
20601
|
const rows = runs.map((run) => {
|
|
19476
20602
|
const status = safeStatus(run.status);
|
|
20603
|
+
const resumable = isValidSessionId(run.sessionId);
|
|
19477
20604
|
if (run.log) {
|
|
19478
20605
|
logs.push(`<section id="log-${run.id}" class="panel log-panel" hidden><div class="panel-head"><h3>Run #${run.id} \xB7 ${esc(run.taskName)}</h3></div><div class="log-box">${esc(run.log)}</div></section>`);
|
|
19479
20606
|
}
|
|
@@ -19481,10 +20608,12 @@ app.get("/runs", async (c) => {
|
|
|
19481
20608
|
<td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td>
|
|
19482
20609
|
<td data-primary data-label="${t(locale, "table.task")}"><div class="task-name">${esc(run.taskName)} <span class="faint">#${run.taskId}</span></div>${run.model ? `<div style="margin-top:4px"><span class="tag">${esc(run.model)}</span></div>` : ""}</td>
|
|
19483
20610
|
<td data-label="${t(locale, "table.agent")}"><span class="tag">${esc(run.taskAgent)}</span></td>
|
|
19484
|
-
<td data-label="${t(locale, "table.
|
|
20611
|
+
<td data-label="${t(locale, "table.session")}" class="m small">${esc(maskSessionId(run.sessionId))}</td>
|
|
20612
|
+
<td data-label="${t(locale, "table.status")}"><span class="badge b-${status}">${runStatusText(locale, run.status ?? "unknown")}</span></td>
|
|
19485
20613
|
<td data-label="${t(locale, "table.duration")}" class="small">${formatDuration(run.startedAt, run.finishedAt)}</td>
|
|
19486
20614
|
<td data-label="${t(locale, "table.heartbeat")}" class="small muted">${formatRelative(run.heartbeatAt, locale)}</td>
|
|
19487
20615
|
<td data-label="${t(locale, "table.actions")}"><div class="actions"><button type="button" class="btn" onclick="showRunDetail(${run.id})">${t(locale, "action.details")}</button>
|
|
20616
|
+
${resumable ? `<button type="button" class="btn" onclick="copySessionCommand(${run.id})">${icon("copy")}${t(locale, "action.continueSession")}</button>` : ""}
|
|
19488
20617
|
${run.log ? `<button type="button" class="btn" aria-expanded="false" onclick="toggleLog(${run.id},this)">${t(locale, "action.logs")}</button>` : ""}</div></td>
|
|
19489
20618
|
</tr>`;
|
|
19490
20619
|
}).join("");
|
|
@@ -19495,22 +20624,37 @@ app.get("/runs", async (c) => {
|
|
|
19495
20624
|
${statCard(runs.filter((run) => run.status === "failed").length, t(locale, "stats.pageFailed"), "tone-red", icon("alert"), "reveal-delay-1")}
|
|
19496
20625
|
${statCard(runs.filter((run) => run.status === "running").length, t(locale, "stats.pageRunning"), "tone-blue", icon("activity"), "reveal-delay-2")}
|
|
19497
20626
|
</div>
|
|
19498
|
-
<section class="panel reveal reveal-delay-2">${runs.length === 0 ? emptyState(t(locale, "empty.runs"), "") : `<div class="table-wrap"><table class="responsive-table"><thead><tr><th>${t(locale, "table.run")}</th><th>${t(locale, "table.task")}</th><th>${t(locale, "table.agent")}</th><th>${t(locale, "table.status")}</th><th>${t(locale, "table.duration")}</th><th>${t(locale, "table.heartbeat")}</th><th>${t(locale, "table.actions")}</th></tr></thead><tbody>${rows}</tbody></table></div>`}</section>
|
|
20627
|
+
<section class="panel reveal reveal-delay-2">${runs.length === 0 ? emptyState(t(locale, "empty.runs"), "") : `<div class="table-wrap"><table class="responsive-table"><thead><tr><th>${t(locale, "table.run")}</th><th>${t(locale, "table.task")}</th><th>${t(locale, "table.agent")}</th><th>${t(locale, "table.session")}</th><th>${t(locale, "table.status")}</th><th>${t(locale, "table.duration")}</th><th>${t(locale, "table.heartbeat")}</th><th>${t(locale, "table.actions")}</th></tr></thead><tbody>${rows}</tbody></table></div>`}</section>
|
|
19499
20628
|
${logs.join("")}${pagination(locale, "/runs", page, totalPages, total)}`;
|
|
19500
20629
|
return c.html(renderLayout({ locale, activeTab: "runs", body }));
|
|
19501
20630
|
});
|
|
19502
20631
|
app.get("/system", async (c) => {
|
|
19503
20632
|
const locale = resolveLocale(c);
|
|
19504
20633
|
const config = loadConfig();
|
|
20634
|
+
const activeConfig = runtimeConfig ?? config;
|
|
20635
|
+
const restartRequired = runtimeConfig !== null && !configsEqual(config, runtimeConfig);
|
|
20636
|
+
const managedRestart = canRestartCurrentGateway();
|
|
20637
|
+
const configState = resolveDashboardConfigState(
|
|
20638
|
+
runtimeConfig !== null,
|
|
20639
|
+
restartRequired,
|
|
20640
|
+
managedRestart
|
|
20641
|
+
);
|
|
20642
|
+
const configStateKey = {
|
|
20643
|
+
foreground: "system.configForeground",
|
|
20644
|
+
applied: "system.configApplied",
|
|
20645
|
+
pending: "system.configPending",
|
|
20646
|
+
manual: "system.configRestartManually"
|
|
20647
|
+
}[configState];
|
|
20648
|
+
const configStateText = t(locale, configStateKey);
|
|
19505
20649
|
const configPath = getConfigPath();
|
|
19506
|
-
const [stats, runningRuns,
|
|
20650
|
+
const [stats, runningRuns, templateStats] = await Promise.all([
|
|
19507
20651
|
TaskService.stats({}),
|
|
19508
20652
|
TaskRunService.getAllRunningRuns(),
|
|
19509
|
-
TaskTemplateService.
|
|
20653
|
+
TaskTemplateService.stats()
|
|
19510
20654
|
]);
|
|
19511
|
-
const configExists =
|
|
20655
|
+
const configExists = existsSync5(configPath);
|
|
19512
20656
|
const runRows = runningRuns.map((run) => {
|
|
19513
|
-
const session =
|
|
20657
|
+
const session = maskSessionId(run.sessionId);
|
|
19514
20658
|
return `<tr><td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td><td data-primary data-label="${t(locale, "table.task")}">#${run.taskId}</td><td data-label="${t(locale, "table.session")}" class="m small">${esc(session)}</td>
|
|
19515
20659
|
<td data-label="${t(locale, "table.model")}" class="small">${esc(run.model) || "\u2014"}</td><td data-label="${t(locale, "table.startedAt")}" class="small">${formatDateTime(run.startedAt, locale)}</td>
|
|
19516
20660
|
<td data-label="${t(locale, "table.heartbeat")}" class="small muted">${formatRelative(run.heartbeatAt, locale)}</td><td data-label="${t(locale, "table.pid")}" class="m small">W:${run.workerPid ?? "\u2014"} C:${run.childPid ?? "\u2014"}</td>
|
|
@@ -19526,10 +20670,10 @@ app.get("/system", async (c) => {
|
|
|
19526
20670
|
<div class="field"><label for="hi">${t(locale, "system.heartbeatInterval")}</label>${unitInput("hi", config.worker.heartbeatIntervalMs / 1e3, 5, t(locale, "system.seconds"))}</div>
|
|
19527
20671
|
<div class="field"><label for="to">${t(locale, "system.taskTimeout")}</label>${unitInput("to", config.worker.taskTimeoutMs / 6e4, 1, t(locale, "system.minutes"))}</div>
|
|
19528
20672
|
</section>
|
|
19529
|
-
<section class="card settings-card reveal-delay-1"><h2 class="settings-title"><span>${icon("templates")}${t(locale, "system.scheduler")}</span><span class="badge ${
|
|
20673
|
+
<section class="card settings-card reveal-delay-1"><h2 class="settings-title"><span>${icon("templates")}${t(locale, "system.scheduler")}</span><span class="badge ${activeConfig.scheduler.enabled ? "b-done" : "b-cancelled"}">${t(locale, activeConfig.scheduler.enabled ? "schedule.enabled" : "schedule.disabled")}</span></h2>
|
|
19530
20674
|
<div class="switch-field"><label for="se">${t(locale, "system.schedulerEnabled")}</label><label class="switch"><input id="se" type="checkbox" name="se" ${config.scheduler.enabled ? "checked" : ""}><span></span></label></div>
|
|
19531
20675
|
<div class="field"><label for="si">${t(locale, "system.checkInterval")}</label>${unitInput("si", config.scheduler.checkIntervalMs, 100, t(locale, "system.milliseconds"))}</div>
|
|
19532
|
-
<div class="info-row"><span class="info-key">${t(locale, "system.activeTemplates")}</span><span class="info-value">${
|
|
20676
|
+
<div class="info-row"><span class="info-key">${t(locale, "system.activeTemplates")}</span><span class="info-value">${templateStats.enabled} / ${templateStats.total}</span></div>
|
|
19533
20677
|
</section>
|
|
19534
20678
|
<section class="card settings-card reveal-delay-2"><h2 class="settings-title"><span>${icon("system")}${t(locale, "system.watchdog")}</span></h2>
|
|
19535
20679
|
<div class="field"><label for="wt">${t(locale, "system.heartbeatTimeout")}</label>${unitInput("wt", config.watchdog.heartbeatTimeoutMs / 1e3, 10, t(locale, "system.seconds"))}</div>
|
|
@@ -19538,10 +20682,10 @@ app.get("/system", async (c) => {
|
|
|
19538
20682
|
<div class="field"><label for="rd">${t(locale, "system.retentionDays")}</label>${unitInput("rd", config.watchdog.retentionDays, 1, t(locale, "system.days"))}</div>
|
|
19539
20683
|
</section>
|
|
19540
20684
|
</div>
|
|
19541
|
-
<div class="save-row"><span class="muted small">${t(locale, "
|
|
20685
|
+
<div class="save-row"><span class="muted small">${configStateText}</span><div class="actions"><button type="submit" class="btn">${t(locale, "action.save")}</button>${managedRestart ? `<button type="button" class="btn btn-primary" onclick="saveConfig(true,${runningRuns.length})">${t(locale, "action.saveAndRestart")}</button>` : ""}</div></div>
|
|
19542
20686
|
</form>
|
|
19543
20687
|
<section class="panel reveal">
|
|
19544
|
-
<div class="panel-head"><h2>${t(locale, "system.runningTasks", { running: runningRuns.length, limit:
|
|
20688
|
+
<div class="panel-head"><h2>${t(locale, "system.runningTasks", { running: runningRuns.length, limit: activeConfig.worker.maxConcurrency })}</h2></div>
|
|
19545
20689
|
${runningRuns.length === 0 ? emptyState(t(locale, "empty.running"), "") : `<div class="table-wrap"><table class="responsive-table"><thead><tr><th>${t(locale, "table.run")}</th><th>${t(locale, "table.task")}</th><th>${t(locale, "table.session")}</th><th>${t(locale, "table.model")}</th><th>${t(locale, "table.startedAt")}</th><th>${t(locale, "table.heartbeat")}</th><th>${t(locale, "table.pid")}</th><th>${t(locale, "table.duration")}</th></tr></thead><tbody>${runRows}</tbody></table></div>`}
|
|
19546
20690
|
</section>
|
|
19547
20691
|
<section class="panel reveal reveal-delay-1"><div class="panel-head"><h2>${t(locale, "system.taskStats")}</h2></div><div class="overview-grid">
|
|
@@ -19558,6 +20702,30 @@ app.get("/system", async (c) => {
|
|
|
19558
20702
|
<button type="button" class="btn btn-danger" onclick="clearDatabase()">${icon("database")}${t(locale, "action.clearDatabase")}</button></section>`;
|
|
19559
20703
|
return c.html(renderLayout({ locale, activeTab: "system", body }));
|
|
19560
20704
|
});
|
|
20705
|
+
app.post("/api/tasks", async (c) => {
|
|
20706
|
+
try {
|
|
20707
|
+
const task = await TaskService.add(parseTaskPayload(await c.req.json()));
|
|
20708
|
+
return c.json({ success: true, task }, 201);
|
|
20709
|
+
} catch (error) {
|
|
20710
|
+
return c.json({
|
|
20711
|
+
error: error instanceof Error ? error.message : String(error)
|
|
20712
|
+
}, 400);
|
|
20713
|
+
}
|
|
20714
|
+
});
|
|
20715
|
+
app.put("/api/tasks/:id", async (c) => {
|
|
20716
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
20717
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
20718
|
+
try {
|
|
20719
|
+
const input = parseTaskPayload(await c.req.json());
|
|
20720
|
+
const task = await TaskService.update(id, editableTaskPayload(input));
|
|
20721
|
+
if (task) return c.json({ success: true, task });
|
|
20722
|
+
return await TaskService.getById(id) ? c.json({ error: "task status does not allow editing" }, 409) : c.json({ error: "not found" }, 404);
|
|
20723
|
+
} catch (error) {
|
|
20724
|
+
return c.json({
|
|
20725
|
+
error: error instanceof Error ? error.message : String(error)
|
|
20726
|
+
}, 400);
|
|
20727
|
+
}
|
|
20728
|
+
});
|
|
19561
20729
|
app.get("/api/tasks/:id", async (c) => {
|
|
19562
20730
|
const id = parsePositiveInteger(c.req.param("id"));
|
|
19563
20731
|
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
@@ -19573,6 +20741,24 @@ app.get("/api/runs/:id", async (c) => {
|
|
|
19573
20741
|
if (!run) return c.json({ error: "not found" }, 404);
|
|
19574
20742
|
return c.json(run);
|
|
19575
20743
|
});
|
|
20744
|
+
app.get("/api/runs/:id/session-command", async (c) => {
|
|
20745
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
20746
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
20747
|
+
const run = await TaskRunService.getById(id);
|
|
20748
|
+
if (!run) return c.json({ error: "not found" }, 404);
|
|
20749
|
+
if (!isValidSessionId(run.sessionId)) return c.json({ error: "session unavailable" }, 409);
|
|
20750
|
+
return c.json({ command: sessionCommand(run.sessionId) });
|
|
20751
|
+
});
|
|
20752
|
+
app.get("/api/gateway/status", (c) => {
|
|
20753
|
+
const savedConfig = loadConfig();
|
|
20754
|
+
const managed = canRestartCurrentGateway();
|
|
20755
|
+
return c.json({
|
|
20756
|
+
pid: process.pid,
|
|
20757
|
+
managed,
|
|
20758
|
+
ready: managed && getGatewayHealth().status === "ok",
|
|
20759
|
+
restartRequired: runtimeConfig === null || !configsEqual(savedConfig, runtimeConfig)
|
|
20760
|
+
});
|
|
20761
|
+
});
|
|
19576
20762
|
app.get("/api/templates/:id", async (c) => {
|
|
19577
20763
|
const id = parsePositiveInteger(c.req.param("id"));
|
|
19578
20764
|
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
@@ -19580,6 +20766,30 @@ app.get("/api/templates/:id", async (c) => {
|
|
|
19580
20766
|
if (!template) return c.json({ error: "not found" }, 404);
|
|
19581
20767
|
return c.json(template);
|
|
19582
20768
|
});
|
|
20769
|
+
app.post("/api/templates", async (c) => {
|
|
20770
|
+
try {
|
|
20771
|
+
const input = parseTemplatePayload(await c.req.json());
|
|
20772
|
+
const template = await TaskTemplateService.create(input);
|
|
20773
|
+
return c.json({ success: true, template }, 201);
|
|
20774
|
+
} catch (error) {
|
|
20775
|
+
return c.json({
|
|
20776
|
+
error: error instanceof Error ? error.message : String(error)
|
|
20777
|
+
}, 400);
|
|
20778
|
+
}
|
|
20779
|
+
});
|
|
20780
|
+
app.put("/api/templates/:id", async (c) => {
|
|
20781
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
20782
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
20783
|
+
try {
|
|
20784
|
+
const input = parseTemplatePayload(await c.req.json());
|
|
20785
|
+
const template = await TaskTemplateService.update(id, input);
|
|
20786
|
+
return template ? c.json({ success: true, template }) : c.json({ error: "not found" }, 404);
|
|
20787
|
+
} catch (error) {
|
|
20788
|
+
return c.json({
|
|
20789
|
+
error: error instanceof Error ? error.message : String(error)
|
|
20790
|
+
}, 400);
|
|
20791
|
+
}
|
|
20792
|
+
});
|
|
19583
20793
|
app.post("/api/tasks/:id/retry", async (c) => {
|
|
19584
20794
|
const id = parsePositiveInteger(c.req.param("id"));
|
|
19585
20795
|
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
@@ -19628,22 +20838,31 @@ app.delete("/api/templates/:id", async (c) => {
|
|
|
19628
20838
|
app.post("/api/templates/:id/trigger", async (c) => {
|
|
19629
20839
|
const id = parsePositiveInteger(c.req.param("id"));
|
|
19630
20840
|
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
19631
|
-
const template = await TaskTemplateService.getById(id);
|
|
19632
|
-
if (!template) return c.json({ error: "not found" }, 404);
|
|
19633
20841
|
const task = await triggerTaskFromTemplate(id);
|
|
19634
|
-
|
|
19635
|
-
return c.json({ success: true, taskId: task.id });
|
|
20842
|
+
return task ? c.json({ success: true, taskId: task.id }) : c.json({ error: "not found" }, 404);
|
|
19636
20843
|
});
|
|
19637
20844
|
app.put("/api/config", async (c) => {
|
|
19638
20845
|
try {
|
|
19639
|
-
const
|
|
20846
|
+
const rawBody = await c.req.json();
|
|
20847
|
+
if (!rawBody || typeof rawBody !== "object" || Array.isArray(rawBody)) {
|
|
20848
|
+
throw new Error("\u8BF7\u6C42\u5185\u5BB9\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
20849
|
+
}
|
|
20850
|
+
const body = rawBody;
|
|
20851
|
+
const section = (name) => {
|
|
20852
|
+
const value = body[name];
|
|
20853
|
+
if (value === void 0) return {};
|
|
20854
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
20855
|
+
throw new Error(`${name} \u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
20856
|
+
}
|
|
20857
|
+
return value;
|
|
20858
|
+
};
|
|
19640
20859
|
const current = readCurrentConfig();
|
|
19641
20860
|
const currentWorker = current.worker ?? {};
|
|
19642
20861
|
const currentScheduler = current.scheduler ?? {};
|
|
19643
20862
|
const currentWatchdog = current.watchdog ?? {};
|
|
19644
|
-
const bodyWorker =
|
|
19645
|
-
const bodyScheduler =
|
|
19646
|
-
const bodyWatchdog =
|
|
20863
|
+
const bodyWorker = section("worker");
|
|
20864
|
+
const bodyScheduler = section("scheduler");
|
|
20865
|
+
const bodyWatchdog = section("watchdog");
|
|
19647
20866
|
const merged = {
|
|
19648
20867
|
...current,
|
|
19649
20868
|
...body,
|
|
@@ -19652,8 +20871,13 @@ app.put("/api/config", async (c) => {
|
|
|
19652
20871
|
scheduler: { ...currentScheduler, ...bodyScheduler },
|
|
19653
20872
|
watchdog: { ...currentWatchdog, ...bodyWatchdog }
|
|
19654
20873
|
};
|
|
19655
|
-
|
|
19656
|
-
|
|
20874
|
+
const savedConfig = validateConfig(merged);
|
|
20875
|
+
writeConfig(savedConfig);
|
|
20876
|
+
return c.json({
|
|
20877
|
+
success: true,
|
|
20878
|
+
restartRequired: runtimeConfig === null || !configsEqual(savedConfig, runtimeConfig),
|
|
20879
|
+
managed: canRestartCurrentGateway()
|
|
20880
|
+
});
|
|
19657
20881
|
} catch (error) {
|
|
19658
20882
|
return c.json({
|
|
19659
20883
|
success: false,
|
|
@@ -19661,9 +20885,27 @@ app.put("/api/config", async (c) => {
|
|
|
19661
20885
|
}, 400);
|
|
19662
20886
|
}
|
|
19663
20887
|
});
|
|
20888
|
+
app.post("/api/gateway/restart", async (c) => {
|
|
20889
|
+
const rawBody = await c.req.json().catch(() => null);
|
|
20890
|
+
const confirmation = rawBody && typeof rawBody === "object" && !Array.isArray(rawBody) ? rawBody.confirmation : void 0;
|
|
20891
|
+
if (confirmation !== "RESTART") {
|
|
20892
|
+
return c.json({ error: "confirmation must be RESTART" }, 400);
|
|
20893
|
+
}
|
|
20894
|
+
if (restartScheduled) return c.json({ error: "restart already scheduled" }, 409);
|
|
20895
|
+
if (!canRestartCurrentGateway()) {
|
|
20896
|
+
return c.json({ error: "\u5F53\u524D Gateway \u4E0D\u662F\u7531\u5339\u914D\u8FD0\u884C\u4F5C\u7528\u57DF\u7684 PM2 \u8FDB\u7A0B\u6258\u7BA1\uFF0C\u65E0\u6CD5\u4ECE\u7F51\u9875\u5B89\u5168\u91CD\u542F" }, 409);
|
|
20897
|
+
}
|
|
20898
|
+
restartScheduled = true;
|
|
20899
|
+
const previousPid = process.pid;
|
|
20900
|
+
setTimeout(() => {
|
|
20901
|
+
process.kill(previousPid, "SIGTERM");
|
|
20902
|
+
}, 500);
|
|
20903
|
+
return c.json({ success: true, previousPid }, 202);
|
|
20904
|
+
});
|
|
19664
20905
|
app.post("/api/database/clear", async (c) => {
|
|
19665
|
-
const
|
|
19666
|
-
|
|
20906
|
+
const rawBody = await c.req.json().catch(() => null);
|
|
20907
|
+
const confirmation = rawBody && typeof rawBody === "object" && !Array.isArray(rawBody) ? rawBody.confirmation : void 0;
|
|
20908
|
+
if (confirmation !== "CLEAR") {
|
|
19667
20909
|
return c.json({ success: false, error: "confirmation must be CLEAR" }, 400);
|
|
19668
20910
|
}
|
|
19669
20911
|
try {
|
|
@@ -19685,6 +20927,9 @@ var web_default = {
|
|
|
19685
20927
|
};
|
|
19686
20928
|
export {
|
|
19687
20929
|
dashboardApp,
|
|
19688
|
-
web_default as default
|
|
20930
|
+
web_default as default,
|
|
20931
|
+
isSafeDashboardRestartTarget,
|
|
20932
|
+
resolveDashboardConfigState,
|
|
20933
|
+
setDashboardRuntimeConfig
|
|
19689
20934
|
};
|
|
19690
20935
|
//# sourceMappingURL=index.js.map
|