opencode-supertask 0.1.31 → 0.1.32-rc.1
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 +1560 -538
- 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/cli/index.js
CHANGED
|
@@ -9583,6 +9583,54 @@ var init_backoff = __esm({
|
|
|
9583
9583
|
}
|
|
9584
9584
|
});
|
|
9585
9585
|
|
|
9586
|
+
// src/core/task-working-directory.ts
|
|
9587
|
+
import { statSync } from "fs";
|
|
9588
|
+
import { isAbsolute } from "path";
|
|
9589
|
+
function validateTaskWorkingDirectory(cwd) {
|
|
9590
|
+
if (cwd == null) return;
|
|
9591
|
+
if (!cwd.trim()) {
|
|
9592
|
+
throw new InvalidTaskWorkingDirectoryError("\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A");
|
|
9593
|
+
}
|
|
9594
|
+
if (!isAbsolute(cwd)) {
|
|
9595
|
+
throw new InvalidTaskWorkingDirectoryError(`\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF1A${cwd}`);
|
|
9596
|
+
}
|
|
9597
|
+
let stat;
|
|
9598
|
+
try {
|
|
9599
|
+
stat = statSync(cwd);
|
|
9600
|
+
} catch (error) {
|
|
9601
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
9602
|
+
throw new InvalidTaskWorkingDirectoryError(`\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6CD5\u8BBF\u95EE\uFF1A${cwd}\uFF08${detail}\uFF09`);
|
|
9603
|
+
}
|
|
9604
|
+
if (!stat.isDirectory()) {
|
|
9605
|
+
throw new InvalidTaskWorkingDirectoryError(`\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F\u76EE\u5F55\uFF1A${cwd}`);
|
|
9606
|
+
}
|
|
9607
|
+
}
|
|
9608
|
+
var InvalidTaskWorkingDirectoryError;
|
|
9609
|
+
var init_task_working_directory = __esm({
|
|
9610
|
+
"src/core/task-working-directory.ts"() {
|
|
9611
|
+
"use strict";
|
|
9612
|
+
InvalidTaskWorkingDirectoryError = class extends Error {
|
|
9613
|
+
constructor(message) {
|
|
9614
|
+
super(message);
|
|
9615
|
+
this.name = "InvalidTaskWorkingDirectoryError";
|
|
9616
|
+
}
|
|
9617
|
+
};
|
|
9618
|
+
}
|
|
9619
|
+
});
|
|
9620
|
+
|
|
9621
|
+
// src/core/task-batch.ts
|
|
9622
|
+
function normalizeTaskBatchId(batchId) {
|
|
9623
|
+
if (batchId == null) return batchId;
|
|
9624
|
+
return batchId.trim() || null;
|
|
9625
|
+
}
|
|
9626
|
+
var TASK_BATCH_TRIM_CHARACTERS;
|
|
9627
|
+
var init_task_batch = __esm({
|
|
9628
|
+
"src/core/task-batch.ts"() {
|
|
9629
|
+
"use strict";
|
|
9630
|
+
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";
|
|
9631
|
+
}
|
|
9632
|
+
});
|
|
9633
|
+
|
|
9586
9634
|
// src/core/services/task.service.ts
|
|
9587
9635
|
function hasNoExecutableDependents() {
|
|
9588
9636
|
return sql`NOT EXISTS (
|
|
@@ -9636,6 +9684,8 @@ var init_task_service = __esm({
|
|
|
9636
9684
|
init_db2();
|
|
9637
9685
|
init_drizzle_orm();
|
|
9638
9686
|
init_backoff();
|
|
9687
|
+
init_task_working_directory();
|
|
9688
|
+
init_task_batch();
|
|
9639
9689
|
({ tasks: tasks2, taskRuns: taskRuns2 } = schema_exports);
|
|
9640
9690
|
cleanupInvocation = 0;
|
|
9641
9691
|
TaskDeletionConflictError = class extends Error {
|
|
@@ -9653,34 +9703,75 @@ var init_task_service = __esm({
|
|
|
9653
9703
|
return conditions;
|
|
9654
9704
|
}
|
|
9655
9705
|
static async add(data) {
|
|
9656
|
-
|
|
9706
|
+
const normalizedData = {
|
|
9707
|
+
...data,
|
|
9708
|
+
batchId: normalizeTaskBatchId(data.batchId)
|
|
9709
|
+
};
|
|
9710
|
+
this.validateNewTask(normalizedData);
|
|
9657
9711
|
return db.transaction((tx) => {
|
|
9658
|
-
if (
|
|
9712
|
+
if (normalizedData.dependsOn != null) {
|
|
9659
9713
|
const dependency = tx.select({
|
|
9660
9714
|
id: tasks2.id,
|
|
9661
9715
|
cwd: tasks2.cwd,
|
|
9662
9716
|
status: tasks2.status,
|
|
9663
9717
|
retryCount: tasks2.retryCount,
|
|
9664
9718
|
maxRetries: tasks2.maxRetries
|
|
9665
|
-
}).from(tasks2).where(eq(tasks2.id,
|
|
9719
|
+
}).from(tasks2).where(eq(tasks2.id, normalizedData.dependsOn)).get();
|
|
9666
9720
|
if (!dependency) {
|
|
9667
|
-
throw new Error(`dependsOn \u6307\u5411\u7684\u4EFB\u52A1 #${
|
|
9721
|
+
throw new Error(`dependsOn \u6307\u5411\u7684\u4EFB\u52A1 #${normalizedData.dependsOn} \u4E0D\u5B58\u5728`);
|
|
9668
9722
|
}
|
|
9669
|
-
if ((dependency.cwd ?? null) !== (
|
|
9723
|
+
if ((dependency.cwd ?? null) !== (normalizedData.cwd ?? null)) {
|
|
9670
9724
|
throw new Error("dependsOn \u5FC5\u987B\u6307\u5411\u540C\u4E00 cwd \u7684\u4EFB\u52A1");
|
|
9671
9725
|
}
|
|
9672
9726
|
const dependencyIsRecoverable = dependency.status === "pending" || dependency.status === "running" || dependency.status === "done" || dependency.status === "failed" && (dependency.retryCount ?? 0) <= (dependency.maxRetries ?? 3);
|
|
9673
9727
|
if (!dependencyIsRecoverable) {
|
|
9674
|
-
throw new Error(`dependsOn \u6307\u5411\u7684\u4EFB\u52A1 #${
|
|
9728
|
+
throw new Error(`dependsOn \u6307\u5411\u7684\u4EFB\u52A1 #${normalizedData.dependsOn} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`);
|
|
9675
9729
|
}
|
|
9676
9730
|
}
|
|
9677
|
-
return tx.insert(tasks2).values(
|
|
9731
|
+
return tx.insert(tasks2).values(normalizedData).returning().get();
|
|
9732
|
+
}, { behavior: "immediate" });
|
|
9733
|
+
}
|
|
9734
|
+
static async update(id, data, scope = {}) {
|
|
9735
|
+
if (Object.keys(data).length === 0) throw new Error("\u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A\u8981\u4FEE\u6539\u7684\u5B57\u6BB5");
|
|
9736
|
+
const normalizedData = data.batchId === void 0 ? data : { ...data, batchId: normalizeTaskBatchId(data.batchId) ?? null };
|
|
9737
|
+
return db.transaction((tx) => {
|
|
9738
|
+
const task = tx.select().from(tasks2).where(and(
|
|
9739
|
+
eq(tasks2.id, id),
|
|
9740
|
+
sql`${tasks2.status} IN ('pending', 'failed', 'dead_letter')`,
|
|
9741
|
+
...this.buildScopeWhere(scope)
|
|
9742
|
+
)).get();
|
|
9743
|
+
if (!task) return null;
|
|
9744
|
+
this.validateNewTask({
|
|
9745
|
+
name: normalizedData.name ?? task.name,
|
|
9746
|
+
agent: normalizedData.agent ?? task.agent,
|
|
9747
|
+
model: normalizedData.model ?? task.model,
|
|
9748
|
+
prompt: normalizedData.prompt ?? task.prompt,
|
|
9749
|
+
cwd: task.cwd,
|
|
9750
|
+
category: normalizedData.category ?? task.category,
|
|
9751
|
+
importance: normalizedData.importance ?? task.importance,
|
|
9752
|
+
urgency: normalizedData.urgency ?? task.urgency,
|
|
9753
|
+
batchId: normalizedData.batchId === void 0 ? task.batchId : normalizedData.batchId,
|
|
9754
|
+
maxRetries: normalizedData.maxRetries ?? task.maxRetries,
|
|
9755
|
+
retryBackoffMs: normalizedData.retryBackoffMs ?? task.retryBackoffMs,
|
|
9756
|
+
timeoutMs: normalizedData.timeoutMs === void 0 ? task.timeoutMs : normalizedData.timeoutMs,
|
|
9757
|
+
dependsOn: task.dependsOn
|
|
9758
|
+
});
|
|
9759
|
+
const maxRetries = normalizedData.maxRetries ?? task.maxRetries ?? 3;
|
|
9760
|
+
const exhausted = task.status === "failed" && (task.retryCount ?? 0) > maxRetries;
|
|
9761
|
+
return tx.update(tasks2).set({
|
|
9762
|
+
...normalizedData,
|
|
9763
|
+
...exhausted ? {
|
|
9764
|
+
status: "dead_letter",
|
|
9765
|
+
retryAfter: null
|
|
9766
|
+
} : {}
|
|
9767
|
+
}).where(eq(tasks2.id, id)).returning().get() ?? null;
|
|
9678
9768
|
}, { behavior: "immediate" });
|
|
9679
9769
|
}
|
|
9680
9770
|
static validateNewTask(data) {
|
|
9681
9771
|
if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
9682
9772
|
if (!data.agent.trim()) throw new Error("agent \u4E0D\u80FD\u4E3A\u7A7A");
|
|
9683
9773
|
if (!data.prompt.trim()) throw new Error("prompt \u4E0D\u80FD\u4E3A\u7A7A");
|
|
9774
|
+
validateTaskWorkingDirectory(data.cwd);
|
|
9684
9775
|
this.validateInteger("importance", data.importance, 1, 5);
|
|
9685
9776
|
this.validateInteger("urgency", data.urgency, 1, 5);
|
|
9686
9777
|
this.validateInteger("maxRetries", data.maxRetries, 0, 1e3);
|
|
@@ -9701,12 +9792,14 @@ var init_task_service = __esm({
|
|
|
9701
9792
|
isNull(tasks2.retryAfter),
|
|
9702
9793
|
sql`${tasks2.retryAfter} <= ${nowMs}`
|
|
9703
9794
|
);
|
|
9704
|
-
const
|
|
9795
|
+
const excludedBatchIds = [...new Set((scope.excludedBatchIds ?? []).map((batchId) => normalizeTaskBatchId(batchId)).filter((batchId) => Boolean(batchId)))];
|
|
9796
|
+
const hasExcludedBatches = excludedBatchIds.length > 0;
|
|
9705
9797
|
let batchFilter;
|
|
9706
9798
|
if (hasExcludedBatches) {
|
|
9707
9799
|
batchFilter = or(
|
|
9708
9800
|
isNull(tasks2.batchId),
|
|
9709
|
-
sql
|
|
9801
|
+
sql`trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ''`,
|
|
9802
|
+
sql`trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) NOT IN ${excludedBatchIds}`
|
|
9710
9803
|
);
|
|
9711
9804
|
}
|
|
9712
9805
|
const statusConditions = or(
|
|
@@ -9740,9 +9833,11 @@ var init_task_service = __esm({
|
|
|
9740
9833
|
),
|
|
9741
9834
|
or(
|
|
9742
9835
|
isNull(tasks2.batchId),
|
|
9836
|
+
sql`trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ''`,
|
|
9743
9837
|
sql`NOT EXISTS (
|
|
9744
9838
|
SELECT 1 FROM tasks AS running_batch_task
|
|
9745
|
-
WHERE running_batch_task.batch_id
|
|
9839
|
+
WHERE trim(running_batch_task.batch_id, ${TASK_BATCH_TRIM_CHARACTERS})
|
|
9840
|
+
= trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS})
|
|
9746
9841
|
AND (
|
|
9747
9842
|
running_batch_task.status = 'running'
|
|
9748
9843
|
OR EXISTS (
|
|
@@ -9766,12 +9861,18 @@ var init_task_service = __esm({
|
|
|
9766
9861
|
).limit(1);
|
|
9767
9862
|
return result[0] ?? null;
|
|
9768
9863
|
}
|
|
9769
|
-
static async countRunning() {
|
|
9864
|
+
static async countRunning(scope = {}) {
|
|
9865
|
+
const scopeConditions = scope.legacyCwd ? [sql`${tasks2.cwd} IS NULL OR trim(${tasks2.cwd}) = ''`] : this.buildScopeWhere(scope);
|
|
9866
|
+
if (scope.batchId !== void 0) {
|
|
9867
|
+
const batchId = normalizeTaskBatchId(scope.batchId);
|
|
9868
|
+
scopeConditions.push(batchId ? sql`trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ${batchId}` : sql`0`);
|
|
9869
|
+
}
|
|
9770
9870
|
return db.transaction((tx) => {
|
|
9771
|
-
const runningTasks = tx.select({ count: sql`count(*)` }).from(tasks2).where(eq(tasks2.status, "running")).get();
|
|
9871
|
+
const runningTasks = tx.select({ count: sql`count(*)` }).from(tasks2).where(and(eq(tasks2.status, "running"), ...scopeConditions)).get();
|
|
9772
9872
|
const runsWithoutRunningTask = tx.select({ count: sql`count(DISTINCT ${taskRuns2.taskId})` }).from(taskRuns2).innerJoin(tasks2, eq(tasks2.id, taskRuns2.taskId)).where(and(
|
|
9773
9873
|
eq(taskRuns2.status, "running"),
|
|
9774
|
-
sql`${tasks2.status} <> 'running'
|
|
9874
|
+
sql`${tasks2.status} <> 'running'`,
|
|
9875
|
+
...scopeConditions
|
|
9775
9876
|
)).get();
|
|
9776
9877
|
return Number(runningTasks?.count ?? 0) + Number(runsWithoutRunningTask?.count ?? 0);
|
|
9777
9878
|
}, { behavior: "deferred" });
|
|
@@ -10104,15 +10205,17 @@ var init_task_service = __esm({
|
|
|
10104
10205
|
}).where(and(...conditions, hasViableDependency())).returning().get() ?? null, { behavior: "immediate" });
|
|
10105
10206
|
}
|
|
10106
10207
|
static async retryBatch(batchId, scope = {}) {
|
|
10208
|
+
const normalizedBatchId = normalizeTaskBatchId(batchId);
|
|
10209
|
+
if (!normalizedBatchId) return 0;
|
|
10107
10210
|
const sqlite2 = getSqlite();
|
|
10108
10211
|
const scopeFilter = scope.cwd === void 0 ? "" : "AND candidate.cwd = ?";
|
|
10109
|
-
const parameters = scope.cwd === void 0 ? [
|
|
10212
|
+
const parameters = scope.cwd === void 0 ? [TASK_BATCH_TRIM_CHARACTERS, normalizedBatchId] : [TASK_BATCH_TRIM_CHARACTERS, normalizedBatchId, scope.cwd];
|
|
10110
10213
|
return db.transaction(() => sqlite2.query(`
|
|
10111
10214
|
WITH RECURSIVE
|
|
10112
10215
|
candidate(id, cwd, depends_on) AS MATERIALIZED (
|
|
10113
10216
|
SELECT candidate.id, candidate.cwd, candidate.depends_on
|
|
10114
10217
|
FROM tasks AS candidate
|
|
10115
|
-
WHERE candidate.batch_id = ?
|
|
10218
|
+
WHERE trim(candidate.batch_id, ?) = ?
|
|
10116
10219
|
AND candidate.status IN ('failed', 'dead_letter')
|
|
10117
10220
|
${scopeFilter}
|
|
10118
10221
|
),
|
|
@@ -10166,16 +10269,28 @@ var init_task_service = __esm({
|
|
|
10166
10269
|
static async list(options = {}) {
|
|
10167
10270
|
let query = db.select().from(tasks2).$dynamic();
|
|
10168
10271
|
const conditions = [];
|
|
10169
|
-
if (options.
|
|
10272
|
+
if (options.activeExecution) {
|
|
10273
|
+
conditions.push(or(
|
|
10274
|
+
eq(tasks2.status, "running"),
|
|
10275
|
+
sql`EXISTS (
|
|
10276
|
+
SELECT 1 FROM task_runs AS active_list_run
|
|
10277
|
+
WHERE active_list_run.task_id = ${tasks2.id}
|
|
10278
|
+
AND active_list_run.status = 'running'
|
|
10279
|
+
)`
|
|
10280
|
+
));
|
|
10281
|
+
} else if (options.status) {
|
|
10170
10282
|
conditions.push(eq(tasks2.status, options.status));
|
|
10171
10283
|
}
|
|
10172
|
-
if (options.batchId) {
|
|
10173
|
-
|
|
10284
|
+
if (options.batchId !== void 0) {
|
|
10285
|
+
const batchId = normalizeTaskBatchId(options.batchId);
|
|
10286
|
+
conditions.push(batchId ? sql`trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ${batchId}` : sql`0`);
|
|
10174
10287
|
}
|
|
10175
10288
|
if (options.category) {
|
|
10176
10289
|
conditions.push(eq(tasks2.category, options.category));
|
|
10177
10290
|
}
|
|
10178
|
-
if (options.
|
|
10291
|
+
if (options.legacyCwd) {
|
|
10292
|
+
conditions.push(sql`${tasks2.cwd} IS NULL OR trim(${tasks2.cwd}) = ''`);
|
|
10293
|
+
} else if (options.cwd !== void 0) {
|
|
10179
10294
|
conditions.push(eq(tasks2.cwd, options.cwd));
|
|
10180
10295
|
}
|
|
10181
10296
|
if (conditions.length > 0) {
|
|
@@ -10193,9 +10308,12 @@ var init_task_service = __esm({
|
|
|
10193
10308
|
static async stats(options = {}) {
|
|
10194
10309
|
const conditions = [];
|
|
10195
10310
|
if (options.batchId !== void 0) {
|
|
10196
|
-
|
|
10311
|
+
const batchId = normalizeTaskBatchId(options.batchId);
|
|
10312
|
+
conditions.push(batchId ? sql`trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ${batchId}` : sql`0`);
|
|
10197
10313
|
}
|
|
10198
|
-
if (options.
|
|
10314
|
+
if (options.legacyCwd) {
|
|
10315
|
+
conditions.push(sql`${tasks2.cwd} IS NULL OR trim(${tasks2.cwd}) = ''`);
|
|
10316
|
+
} else if (options.cwd !== void 0) {
|
|
10199
10317
|
conditions.push(eq(tasks2.cwd, options.cwd));
|
|
10200
10318
|
}
|
|
10201
10319
|
const whereCondition = conditions.length > 0 ? and(...conditions) : void 0;
|
|
@@ -10220,6 +10338,33 @@ var init_task_service = __esm({
|
|
|
10220
10338
|
}
|
|
10221
10339
|
return stats;
|
|
10222
10340
|
}
|
|
10341
|
+
static async projectSummaries(limit = 100) {
|
|
10342
|
+
this.validateInteger("limit", limit, 1, 1e3);
|
|
10343
|
+
const lastCreatedAt = sql`max(${tasks2.createdAt})`;
|
|
10344
|
+
const lastTaskId = sql`max(${tasks2.id})`;
|
|
10345
|
+
const rows = await db.select({
|
|
10346
|
+
cwd: tasks2.cwd,
|
|
10347
|
+
total: sql`count(*)`,
|
|
10348
|
+
pending: sql`sum(CASE WHEN ${tasks2.status} = 'pending' THEN 1 ELSE 0 END)`,
|
|
10349
|
+
running: sql`sum(CASE WHEN ${tasks2.status} = 'running' OR EXISTS (
|
|
10350
|
+
SELECT 1 FROM task_runs AS active_project_run
|
|
10351
|
+
WHERE active_project_run.task_id = ${tasks2.id}
|
|
10352
|
+
AND active_project_run.status = 'running'
|
|
10353
|
+
) THEN 1 ELSE 0 END)`,
|
|
10354
|
+
failed: sql`sum(CASE WHEN ${tasks2.status} IN ('failed', 'dead_letter') THEN 1 ELSE 0 END)`,
|
|
10355
|
+
done: sql`sum(CASE WHEN ${tasks2.status} = 'done' THEN 1 ELSE 0 END)`,
|
|
10356
|
+
lastCreatedAt
|
|
10357
|
+
}).from(tasks2).where(sql`${tasks2.cwd} IS NOT NULL AND trim(${tasks2.cwd}) <> ''`).groupBy(tasks2.cwd).orderBy(desc(lastCreatedAt), desc(lastTaskId)).limit(limit);
|
|
10358
|
+
return rows.flatMap((row) => row.cwd === null ? [] : [{
|
|
10359
|
+
cwd: row.cwd,
|
|
10360
|
+
total: Number(row.total),
|
|
10361
|
+
pending: Number(row.pending),
|
|
10362
|
+
running: Number(row.running),
|
|
10363
|
+
failed: Number(row.failed),
|
|
10364
|
+
done: Number(row.done),
|
|
10365
|
+
lastCreatedAt: row.lastCreatedAt === null ? null : Number(row.lastCreatedAt) * 1e3
|
|
10366
|
+
}]);
|
|
10367
|
+
}
|
|
10223
10368
|
static async delete(id, scope = {}) {
|
|
10224
10369
|
const conditions = [
|
|
10225
10370
|
eq(tasks2.id, id),
|
|
@@ -10353,12 +10498,15 @@ var init_task_service = __esm({
|
|
|
10353
10498
|
function isLaunchIdentity(value) {
|
|
10354
10499
|
return value != null && /^gateway-[1-9]\d*:launch:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
10355
10500
|
}
|
|
10501
|
+
function drainProofAckForIdentity(launchIdentity) {
|
|
10502
|
+
return { type: DRAIN_PROOF_ACK_MESSAGE_TYPE, identity: launchIdentity };
|
|
10503
|
+
}
|
|
10356
10504
|
function isMatchingDrainProof(message, launchIdentity) {
|
|
10357
10505
|
if (typeof message !== "object" || message == null) return false;
|
|
10358
10506
|
const candidate = message;
|
|
10359
10507
|
return candidate.type === DRAIN_PROOF_MESSAGE_TYPE && candidate.identity === launchIdentity;
|
|
10360
10508
|
}
|
|
10361
|
-
var LEGACY_GUARDIAN_LAUNCH_PROTOCOL, TOKEN_GUARDIAN_LAUNCH_PROTOCOL, LAUNCH_IDENTITY_ARGUMENT, DRAIN_PROOF_MESSAGE_TYPE, MANAGED_RUN_ENV, MANAGED_RUN_ENV_VALUE;
|
|
10509
|
+
var LEGACY_GUARDIAN_LAUNCH_PROTOCOL, TOKEN_GUARDIAN_LAUNCH_PROTOCOL, LAUNCH_IDENTITY_ARGUMENT, DRAIN_PROOF_MESSAGE_TYPE, DRAIN_PROOF_ACK_MESSAGE_TYPE, MANAGED_RUN_ENV, MANAGED_RUN_ENV_VALUE;
|
|
10362
10510
|
var init_launch_protocol = __esm({
|
|
10363
10511
|
"src/core/launch-protocol.ts"() {
|
|
10364
10512
|
"use strict";
|
|
@@ -10366,6 +10514,7 @@ var init_launch_protocol = __esm({
|
|
|
10366
10514
|
TOKEN_GUARDIAN_LAUNCH_PROTOCOL = "gated-v3-token-guardian";
|
|
10367
10515
|
LAUNCH_IDENTITY_ARGUMENT = "--supertask-launch-identity";
|
|
10368
10516
|
DRAIN_PROOF_MESSAGE_TYPE = "supertask-drained";
|
|
10517
|
+
DRAIN_PROOF_ACK_MESSAGE_TYPE = "supertask-drained-ack";
|
|
10369
10518
|
MANAGED_RUN_ENV = "SUPERTASK_MANAGED_RUN";
|
|
10370
10519
|
MANAGED_RUN_ENV_VALUE = "1";
|
|
10371
10520
|
}
|
|
@@ -19955,27 +20104,34 @@ var init_task_template_service = __esm({
|
|
|
19955
20104
|
init_db2();
|
|
19956
20105
|
init_drizzle_orm();
|
|
19957
20106
|
init_cron_parser();
|
|
20107
|
+
init_task_working_directory();
|
|
20108
|
+
init_task_batch();
|
|
19958
20109
|
({ taskTemplates: taskTemplates2 } = schema_exports);
|
|
19959
20110
|
TaskTemplateService = class {
|
|
19960
20111
|
static async create(data) {
|
|
19961
|
-
|
|
20112
|
+
const normalizedData = {
|
|
20113
|
+
...data,
|
|
20114
|
+
batchId: normalizeTaskBatchId(data.batchId)
|
|
20115
|
+
};
|
|
20116
|
+
this.validate(normalizedData);
|
|
19962
20117
|
const now = Date.now();
|
|
19963
|
-
const nextRunAt =
|
|
19964
|
-
|
|
20118
|
+
const nextRunAt = normalizedData.nextRunAt ?? this.calculateNextRunAt(
|
|
20119
|
+
normalizedData.scheduleType,
|
|
19965
20120
|
{
|
|
19966
|
-
cronExpr:
|
|
19967
|
-
intervalMs:
|
|
19968
|
-
runAt:
|
|
20121
|
+
cronExpr: normalizedData.cronExpr ?? null,
|
|
20122
|
+
intervalMs: normalizedData.intervalMs ?? null,
|
|
20123
|
+
runAt: normalizedData.runAt ?? null
|
|
19969
20124
|
},
|
|
19970
20125
|
now
|
|
19971
20126
|
);
|
|
19972
|
-
const result = await db.insert(taskTemplates2).values({ ...
|
|
20127
|
+
const result = await db.insert(taskTemplates2).values({ ...normalizedData, nextRunAt, createdAt: now, updatedAt: now }).returning();
|
|
19973
20128
|
return result[0];
|
|
19974
20129
|
}
|
|
19975
20130
|
static validate(data) {
|
|
19976
20131
|
if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
19977
20132
|
if (!data.agent.trim()) throw new Error("agent \u4E0D\u80FD\u4E3A\u7A7A");
|
|
19978
20133
|
if (!data.prompt.trim()) throw new Error("prompt \u4E0D\u80FD\u4E3A\u7A7A");
|
|
20134
|
+
validateTaskWorkingDirectory(data.cwd);
|
|
19979
20135
|
const scheduleType = data.scheduleType;
|
|
19980
20136
|
if (!["cron", "delayed", "recurring"].includes(scheduleType)) {
|
|
19981
20137
|
throw new Error("scheduleType \u5FC5\u987B\u662F cron\u3001delayed \u6216 recurring");
|
|
@@ -20002,13 +20158,40 @@ var init_task_template_service = __esm({
|
|
|
20002
20158
|
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
20003
20159
|
}
|
|
20004
20160
|
}
|
|
20005
|
-
static async list(limit = 50) {
|
|
20006
|
-
return await db.select().from(taskTemplates2).orderBy(desc(taskTemplates2.createdAt), desc(taskTemplates2.id)).limit(limit);
|
|
20161
|
+
static async list(limit = 50, offset = 0) {
|
|
20162
|
+
return await db.select().from(taskTemplates2).orderBy(desc(taskTemplates2.createdAt), desc(taskTemplates2.id)).limit(limit).offset(offset);
|
|
20163
|
+
}
|
|
20164
|
+
static async stats() {
|
|
20165
|
+
const result = await db.select({
|
|
20166
|
+
total: sql`count(*)`,
|
|
20167
|
+
enabled: sql`sum(case when ${taskTemplates2.enabled} = 1 then 1 else 0 end)`
|
|
20168
|
+
}).from(taskTemplates2);
|
|
20169
|
+
const total = Number(result[0]?.total ?? 0);
|
|
20170
|
+
const enabled = Number(result[0]?.enabled ?? 0);
|
|
20171
|
+
return { total, enabled, disabled: total - enabled };
|
|
20007
20172
|
}
|
|
20008
20173
|
static async getById(id) {
|
|
20009
20174
|
const result = await db.select().from(taskTemplates2).where(eq(taskTemplates2.id, id));
|
|
20010
20175
|
return result[0] || null;
|
|
20011
20176
|
}
|
|
20177
|
+
static async update(id, data) {
|
|
20178
|
+
const normalizedData = {
|
|
20179
|
+
...data,
|
|
20180
|
+
batchId: normalizeTaskBatchId(data.batchId) ?? null
|
|
20181
|
+
};
|
|
20182
|
+
this.validate(normalizedData);
|
|
20183
|
+
const now = Date.now();
|
|
20184
|
+
const nextRunAt = this.calculateNextRunAt(
|
|
20185
|
+
normalizedData.scheduleType,
|
|
20186
|
+
normalizedData,
|
|
20187
|
+
now
|
|
20188
|
+
);
|
|
20189
|
+
return db.transaction((tx) => {
|
|
20190
|
+
const existing = tx.select({ id: taskTemplates2.id }).from(taskTemplates2).where(eq(taskTemplates2.id, id)).limit(1).get();
|
|
20191
|
+
if (!existing) return null;
|
|
20192
|
+
return tx.update(taskTemplates2).set({ ...normalizedData, nextRunAt, updatedAt: now }).where(eq(taskTemplates2.id, id)).returning().get() ?? null;
|
|
20193
|
+
}, { behavior: "immediate" });
|
|
20194
|
+
}
|
|
20012
20195
|
static async enable(id) {
|
|
20013
20196
|
return db.transaction((tx) => {
|
|
20014
20197
|
const template = tx.select().from(taskTemplates2).where(eq(taskTemplates2.id, id)).limit(1).get();
|
|
@@ -20102,7 +20285,7 @@ import {
|
|
|
20102
20285
|
existsSync as existsSync2,
|
|
20103
20286
|
mkdirSync as mkdirSync2,
|
|
20104
20287
|
renameSync,
|
|
20105
|
-
statSync,
|
|
20288
|
+
statSync as statSync2,
|
|
20106
20289
|
unlinkSync,
|
|
20107
20290
|
writeFileSync
|
|
20108
20291
|
} from "fs";
|
|
@@ -20204,9 +20387,9 @@ var init_database_maintenance_service = __esm({
|
|
|
20204
20387
|
if (!existsSync2(source)) {
|
|
20205
20388
|
throw new Error(`\u5907\u4EFD\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${source}`);
|
|
20206
20389
|
}
|
|
20207
|
-
const sourceStat =
|
|
20390
|
+
const sourceStat = statSync2(source);
|
|
20208
20391
|
if (!sourceStat.isFile()) throw new Error(`\u5907\u4EFD\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6\uFF1A${source}`);
|
|
20209
|
-
const liveStat =
|
|
20392
|
+
const liveStat = statSync2(livePath);
|
|
20210
20393
|
if (sourceStat.dev === liveStat.dev && sourceStat.ino === liveStat.ino) {
|
|
20211
20394
|
throw new Error("\u6062\u590D\u6765\u6E90\u4E0D\u80FD\u662F\u5F53\u524D\u6570\u636E\u5E93\u7684\u7B26\u53F7\u94FE\u63A5\u6216\u786C\u94FE\u63A5\u522B\u540D");
|
|
20212
20395
|
}
|
|
@@ -20447,7 +20630,7 @@ var init_database_maintenance_service = __esm({
|
|
|
20447
20630
|
const counts = missingTables.length === 0 ? this.readCounts(sqlite2) : { tasks: 0, taskRuns: 0, taskTemplates: 0 };
|
|
20448
20631
|
const runningTasks = missingTables.includes("tasks") ? 0 : this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM tasks WHERE status = 'running'");
|
|
20449
20632
|
const runningRuns = missingTables.includes("task_runs") ? 0 : this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM task_runs WHERE status = 'running'");
|
|
20450
|
-
const sizeBytes = path === ":memory:" || !existsSync2(path) ? sqlite2.serialize().byteLength :
|
|
20633
|
+
const sizeBytes = path === ":memory:" || !existsSync2(path) ? sqlite2.serialize().byteLength : statSync2(path).size;
|
|
20451
20634
|
return {
|
|
20452
20635
|
ok: integrityMessages.length === 1 && integrityMessages[0] === "ok" && foreignKeyViolations === 0 && missingTables.length === 0,
|
|
20453
20636
|
path: normalizedPath(path),
|
|
@@ -20484,7 +20667,7 @@ var init_database_maintenance_service = __esm({
|
|
|
20484
20667
|
const check = this.inspectFile(temporary);
|
|
20485
20668
|
if (!check.ok) throw new Error("\u65B0\u5907\u4EFD\u672A\u901A\u8FC7\u5B8C\u6574\u6027\u6821\u9A8C");
|
|
20486
20669
|
renameSync(temporary, output);
|
|
20487
|
-
const finalCheck = { ...check, path: output, sizeBytes:
|
|
20670
|
+
const finalCheck = { ...check, path: output, sizeBytes: statSync2(output).size };
|
|
20488
20671
|
return { path: output, sizeBytes: finalCheck.sizeBytes, check: finalCheck };
|
|
20489
20672
|
} catch (error) {
|
|
20490
20673
|
safeUnlink(temporary);
|
|
@@ -20530,6 +20713,41 @@ var init_database_maintenance_service = __esm({
|
|
|
20530
20713
|
}
|
|
20531
20714
|
});
|
|
20532
20715
|
|
|
20716
|
+
// src/core/duration.ts
|
|
20717
|
+
function parseDuration(input) {
|
|
20718
|
+
const trimmed = input.trim();
|
|
20719
|
+
const simple = DURATION_REGEX.exec(trimmed);
|
|
20720
|
+
if (simple) {
|
|
20721
|
+
const value = parseFloat(simple[1]);
|
|
20722
|
+
const unit = simple[2].toLowerCase();
|
|
20723
|
+
if (unit === "ms") return value;
|
|
20724
|
+
if (unit === "s" || unit === "sec" || unit === "second" || unit === "seconds") return value * 1e3;
|
|
20725
|
+
if (unit === "min" || unit === "minute" || unit === "minutes" || unit === "m") return value * 6e4;
|
|
20726
|
+
if (unit === "h" || unit === "hour" || unit === "hours") return value * 36e5;
|
|
20727
|
+
if (unit === "d" || unit === "day" || unit === "days") return value * 864e5;
|
|
20728
|
+
if (unit === "w" || unit === "week" || unit === "weeks") return value * 6048e5;
|
|
20729
|
+
}
|
|
20730
|
+
const iso = ISO8601_REGEX.exec(trimmed);
|
|
20731
|
+
if (iso) {
|
|
20732
|
+
const days = parseFloat(iso[1] ?? "0");
|
|
20733
|
+
const hours = parseFloat(iso[2] ?? "0");
|
|
20734
|
+
const minutes = parseFloat(iso[3] ?? "0");
|
|
20735
|
+
const seconds = parseFloat(iso[4] ?? "0");
|
|
20736
|
+
return (days * 86400 + hours * 3600 + minutes * 60 + seconds) * 1e3;
|
|
20737
|
+
}
|
|
20738
|
+
const asNumber = Number(trimmed);
|
|
20739
|
+
if (!isNaN(asNumber) && asNumber > 0) return asNumber;
|
|
20740
|
+
return null;
|
|
20741
|
+
}
|
|
20742
|
+
var DURATION_REGEX, ISO8601_REGEX;
|
|
20743
|
+
var init_duration = __esm({
|
|
20744
|
+
"src/core/duration.ts"() {
|
|
20745
|
+
"use strict";
|
|
20746
|
+
DURATION_REGEX = /^(\d+(?:\.\d+)?)\s*(ms|s|sec|seconds?|min|minutes?|m|h|hours?|d|days?|w|weeks?)$/i;
|
|
20747
|
+
ISO8601_REGEX = /^P(?:([.\d]+)D)?(?:T(?:([.\d]+)H)?(?:([.\d]+)M)?(?:([.\d]+)S)?)?$/i;
|
|
20748
|
+
}
|
|
20749
|
+
});
|
|
20750
|
+
|
|
20533
20751
|
// src/gateway/config.ts
|
|
20534
20752
|
var config_exports = {};
|
|
20535
20753
|
__export(config_exports, {
|
|
@@ -20765,11 +20983,11 @@ import {
|
|
|
20765
20983
|
mkdirSync as mkdirSync4,
|
|
20766
20984
|
readFileSync as readFileSync3,
|
|
20767
20985
|
rmSync,
|
|
20768
|
-
statSync as
|
|
20986
|
+
statSync as statSync3,
|
|
20769
20987
|
writeFileSync as writeFileSync2
|
|
20770
20988
|
} from "fs";
|
|
20771
20989
|
import { homedir as homedir3, userInfo } from "os";
|
|
20772
|
-
import { delimiter, dirname as dirname6, isAbsolute, join as join4, resolve as resolve3 } from "path";
|
|
20990
|
+
import { delimiter, dirname as dirname6, isAbsolute as isAbsolute2, join as join4, resolve as resolve3 } from "path";
|
|
20773
20991
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
20774
20992
|
import { Database as Database5 } from "bun:sqlite";
|
|
20775
20993
|
function resolveGatewayEntry() {
|
|
@@ -20804,8 +21022,28 @@ function getRunningVersion(env = process.env, cwd = process.cwd()) {
|
|
|
20804
21022
|
return null;
|
|
20805
21023
|
}
|
|
20806
21024
|
}
|
|
21025
|
+
function packageVersionFromGatewayEntry(gatewayEntry) {
|
|
21026
|
+
if (gatewayEntry === null) return null;
|
|
21027
|
+
let directory = dirname6(gatewayEntry);
|
|
21028
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
21029
|
+
const packagePath = join4(directory, "package.json");
|
|
21030
|
+
if (existsSync5(packagePath)) {
|
|
21031
|
+
try {
|
|
21032
|
+
const pkg = JSON.parse(readFileSync3(packagePath, "utf8"));
|
|
21033
|
+
if (pkg.name === "opencode-supertask" && typeof pkg.version === "string") {
|
|
21034
|
+
return pkg.version;
|
|
21035
|
+
}
|
|
21036
|
+
} catch {
|
|
21037
|
+
}
|
|
21038
|
+
}
|
|
21039
|
+
const parent = dirname6(directory);
|
|
21040
|
+
if (parent === directory) break;
|
|
21041
|
+
directory = parent;
|
|
21042
|
+
}
|
|
21043
|
+
return null;
|
|
21044
|
+
}
|
|
20807
21045
|
function resolveRuntimeExecutable(command, env, cwd) {
|
|
20808
|
-
if (
|
|
21046
|
+
if (isAbsolute2(command) || command.includes("/")) return runtimePath(command, cwd);
|
|
20809
21047
|
for (const entry of (env.PATH ?? "").split(delimiter)) {
|
|
20810
21048
|
if (!entry) continue;
|
|
20811
21049
|
const candidate = resolve3(cwd, entry, command);
|
|
@@ -20846,6 +21084,8 @@ function getGatewayDiagnostic() {
|
|
|
20846
21084
|
pid: null,
|
|
20847
21085
|
ready: false,
|
|
20848
21086
|
runningVersion: getRunningVersion(),
|
|
21087
|
+
gatewayEntry: null,
|
|
21088
|
+
gatewayPackageVersion: null,
|
|
20849
21089
|
logRotationInstalled: false,
|
|
20850
21090
|
startupConfigured: process.platform === "darwin" || process.platform === "linux" ? false : null,
|
|
20851
21091
|
currentScope: producerScope,
|
|
@@ -20861,6 +21101,7 @@ function getGatewayDiagnostic() {
|
|
|
20861
21101
|
const pid = typeof gateway?.pid === "number" ? gateway.pid : null;
|
|
20862
21102
|
const readyPath = managedScope?.databasePath ?? databasePath();
|
|
20863
21103
|
const lockedVersion = pid == null ? void 0 : gatewayVersionFromLock(pid, readyPath);
|
|
21104
|
+
const gatewayEntry = runtime?.gatewayEntry ?? null;
|
|
20864
21105
|
return {
|
|
20865
21106
|
pm2Installed: true,
|
|
20866
21107
|
processFound: gateway != null,
|
|
@@ -20868,6 +21109,8 @@ function getGatewayDiagnostic() {
|
|
|
20868
21109
|
pid,
|
|
20869
21110
|
ready: pid != null && isGatewayReady(pid, readyPath),
|
|
20870
21111
|
runningVersion: lockedVersion === void 0 ? getRunningVersion(gatewayEnv, runtime?.cwd) : lockedVersion,
|
|
21112
|
+
gatewayEntry,
|
|
21113
|
+
gatewayPackageVersion: packageVersionFromGatewayEntry(gatewayEntry),
|
|
20871
21114
|
logRotationInstalled: processes.some((item) => item.name === "pm2-logrotate" && item.pm2_env?.status === "online"),
|
|
20872
21115
|
startupConfigured: process.platform === "darwin" ? isMacLaunchAgentConfigured(
|
|
20873
21116
|
gatewayEnv.PM2_HOME ?? join4(runtimeHome(gatewayEnv), ".pm2"),
|
|
@@ -20895,7 +21138,7 @@ function xmlEscape(value) {
|
|
|
20895
21138
|
}
|
|
20896
21139
|
function resolvePm2Bin() {
|
|
20897
21140
|
const configured = pm2Bin();
|
|
20898
|
-
if (
|
|
21141
|
+
if (isAbsolute2(configured)) return configured;
|
|
20899
21142
|
const result = spawnSync2("which", [configured], { encoding: "utf8" });
|
|
20900
21143
|
const resolved = result.status === 0 ? result.stdout.trim().split("\n")[0] : "";
|
|
20901
21144
|
if (!resolved) throw new Error(`[supertask] \u65E0\u6CD5\u89E3\u6790 pm2 \u53EF\u6267\u884C\u6587\u4EF6: ${configured}`);
|
|
@@ -21257,7 +21500,7 @@ function gatewayRuntimeFromProcess(processInfo) {
|
|
|
21257
21500
|
if (typeof savedBunPath !== "string" || typeof savedCwd !== "string") return null;
|
|
21258
21501
|
try {
|
|
21259
21502
|
accessSync(savedBunPath, constants.X_OK);
|
|
21260
|
-
if (!
|
|
21503
|
+
if (!statSync3(savedCwd).isDirectory()) return null;
|
|
21261
21504
|
if (spawnSync2(savedBunPath, ["--version"], {
|
|
21262
21505
|
stdio: "ignore",
|
|
21263
21506
|
env: savedEnv,
|
|
@@ -21464,7 +21707,7 @@ function pm2StartGateway(runtime = currentGatewayRuntime()) {
|
|
|
21464
21707
|
try {
|
|
21465
21708
|
accessSync(runtime.bunPath, constants.X_OK);
|
|
21466
21709
|
accessSync(runtime.gatewayEntry, constants.R_OK);
|
|
21467
|
-
if (!
|
|
21710
|
+
if (!statSync3(runtime.cwd).isDirectory()) throw new Error("cwd is not a directory");
|
|
21468
21711
|
} catch (error) {
|
|
21469
21712
|
throw new Error(`[supertask] Gateway \u76EE\u6807\u8FD0\u884C\u65F6\u4E0D\u53EF\u7528: ${error instanceof Error ? error.message : String(error)}`);
|
|
21470
21713
|
}
|
|
@@ -21854,148 +22097,419 @@ var init_pm2 = __esm({
|
|
|
21854
22097
|
}
|
|
21855
22098
|
});
|
|
21856
22099
|
|
|
21857
|
-
// src/
|
|
21858
|
-
function
|
|
21859
|
-
const
|
|
21860
|
-
|
|
21861
|
-
|
|
21862
|
-
|
|
21863
|
-
|
|
21864
|
-
|
|
21865
|
-
scheduler: now,
|
|
21866
|
-
watchdog: now,
|
|
21867
|
-
watchdogCleanup: now
|
|
21868
|
-
},
|
|
21869
|
-
lastSuccessAt: {
|
|
21870
|
-
worker: now,
|
|
21871
|
-
scheduler: now,
|
|
21872
|
-
watchdog: now,
|
|
21873
|
-
watchdogCleanup: now
|
|
21874
|
-
},
|
|
21875
|
-
consecutiveFailures: {
|
|
21876
|
-
worker: 0,
|
|
21877
|
-
scheduler: 0,
|
|
21878
|
-
watchdog: 0,
|
|
21879
|
-
watchdogCleanup: 0
|
|
21880
|
-
},
|
|
21881
|
-
lastError: {
|
|
21882
|
-
worker: null,
|
|
21883
|
-
scheduler: null,
|
|
21884
|
-
watchdog: null,
|
|
21885
|
-
watchdogCleanup: null
|
|
21886
|
-
}
|
|
21887
|
-
};
|
|
21888
|
-
}
|
|
21889
|
-
function markGatewayActivity(component) {
|
|
21890
|
-
if (state) state.lastActivityAt[component] = Date.now();
|
|
21891
|
-
}
|
|
21892
|
-
function markGatewaySuccess(component) {
|
|
21893
|
-
if (!state) return;
|
|
21894
|
-
const now = Date.now();
|
|
21895
|
-
state.lastActivityAt[component] = now;
|
|
21896
|
-
state.lastSuccessAt[component] = now;
|
|
21897
|
-
state.consecutiveFailures[component] = 0;
|
|
21898
|
-
}
|
|
21899
|
-
function markGatewayFailure(component, error) {
|
|
21900
|
-
if (!state) return;
|
|
21901
|
-
const now = Date.now();
|
|
21902
|
-
state.lastActivityAt[component] = now;
|
|
21903
|
-
state.consecutiveFailures[component] += 1;
|
|
21904
|
-
state.lastError[component] = {
|
|
21905
|
-
at: now,
|
|
21906
|
-
message: error instanceof Error ? error.message : String(error)
|
|
21907
|
-
};
|
|
21908
|
-
}
|
|
21909
|
-
function resetGatewayHealth() {
|
|
21910
|
-
state = null;
|
|
21911
|
-
}
|
|
21912
|
-
function componentStatus(component, enabled, maxAgeMs, now) {
|
|
21913
|
-
const lastActivityAt = state?.lastActivityAt[component] ?? null;
|
|
21914
|
-
const ageMs = lastActivityAt == null ? null : Math.max(0, now - lastActivityAt);
|
|
21915
|
-
const consecutiveFailures = state?.consecutiveFailures[component] ?? 0;
|
|
22100
|
+
// src/core/semver.ts
|
|
22101
|
+
function parseSemanticVersion(version2) {
|
|
22102
|
+
const match2 = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(version2);
|
|
22103
|
+
if (!match2) return null;
|
|
22104
|
+
const prerelease = match2[4]?.split(".") ?? [];
|
|
22105
|
+
if (prerelease.some((identifier) => /^\d+$/.test(identifier) && identifier.length > 1 && identifier.startsWith("0"))) {
|
|
22106
|
+
return null;
|
|
22107
|
+
}
|
|
21916
22108
|
return {
|
|
21917
|
-
|
|
21918
|
-
|
|
21919
|
-
|
|
21920
|
-
|
|
21921
|
-
consecutiveFailures,
|
|
21922
|
-
lastError: state?.lastError[component] ?? null,
|
|
21923
|
-
ageMs,
|
|
21924
|
-
maxAgeMs
|
|
22109
|
+
major: Number(match2[1]),
|
|
22110
|
+
minor: Number(match2[2]),
|
|
22111
|
+
patch: Number(match2[3]),
|
|
22112
|
+
prerelease
|
|
21925
22113
|
};
|
|
21926
22114
|
}
|
|
21927
|
-
function
|
|
21928
|
-
const
|
|
21929
|
-
|
|
21930
|
-
|
|
21931
|
-
|
|
21932
|
-
|
|
21933
|
-
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
)
|
|
21940
|
-
|
|
21941
|
-
|
|
21942
|
-
|
|
21943
|
-
|
|
21944
|
-
now
|
|
21945
|
-
);
|
|
21946
|
-
const watchdogCleanup = componentStatus(
|
|
21947
|
-
"watchdogCleanup",
|
|
21948
|
-
true,
|
|
21949
|
-
Math.max((state?.config.watchdogCleanupIntervalMs ?? 864e5) * 2, 6e4),
|
|
21950
|
-
now
|
|
21951
|
-
);
|
|
21952
|
-
let lock = { pid: null, heartbeatAt: null, readyAt: null, ageMs: null, healthy: false };
|
|
21953
|
-
try {
|
|
21954
|
-
const row = sqlite.prepare(
|
|
21955
|
-
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
21956
|
-
).get();
|
|
21957
|
-
if (row) {
|
|
21958
|
-
const ageMs = Math.max(0, now - row.heartbeat_at);
|
|
21959
|
-
lock = {
|
|
21960
|
-
pid: row.pid,
|
|
21961
|
-
heartbeatAt: row.heartbeat_at,
|
|
21962
|
-
readyAt: row.ready_at,
|
|
21963
|
-
ageMs,
|
|
21964
|
-
healthy: row.pid === process.pid && row.ready_at != null && ageMs < LOCK_STALE_MS
|
|
21965
|
-
};
|
|
22115
|
+
function compareSemanticVersions(left, right) {
|
|
22116
|
+
const a = parseSemanticVersion(left);
|
|
22117
|
+
const b = parseSemanticVersion(right);
|
|
22118
|
+
if (!a || !b) return null;
|
|
22119
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
22120
|
+
if (a[key] !== b[key]) return a[key] - b[key];
|
|
22121
|
+
}
|
|
22122
|
+
if (a.prerelease.length === 0 || b.prerelease.length === 0) {
|
|
22123
|
+
if (a.prerelease.length === b.prerelease.length) return 0;
|
|
22124
|
+
return a.prerelease.length === 0 ? 1 : -1;
|
|
22125
|
+
}
|
|
22126
|
+
const identifiers = Math.max(a.prerelease.length, b.prerelease.length);
|
|
22127
|
+
for (let index2 = 0; index2 < identifiers; index2 += 1) {
|
|
22128
|
+
const leftIdentifier = a.prerelease[index2];
|
|
22129
|
+
const rightIdentifier = b.prerelease[index2];
|
|
22130
|
+
if (leftIdentifier === void 0 || rightIdentifier === void 0) {
|
|
22131
|
+
return leftIdentifier === rightIdentifier ? 0 : leftIdentifier === void 0 ? -1 : 1;
|
|
21966
22132
|
}
|
|
21967
|
-
|
|
22133
|
+
if (leftIdentifier === rightIdentifier) continue;
|
|
22134
|
+
const leftNumeric = /^\d+$/.test(leftIdentifier);
|
|
22135
|
+
const rightNumeric = /^\d+$/.test(rightIdentifier);
|
|
22136
|
+
if (leftNumeric && rightNumeric) return Number(leftIdentifier) - Number(rightIdentifier);
|
|
22137
|
+
if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1;
|
|
22138
|
+
return leftIdentifier < rightIdentifier ? -1 : 1;
|
|
21968
22139
|
}
|
|
21969
|
-
|
|
21970
|
-
return {
|
|
21971
|
-
status: healthy ? "ok" : "degraded",
|
|
21972
|
-
pid: process.pid,
|
|
21973
|
-
uptimeMs: state ? Math.max(0, now - state.startedAt) : 0,
|
|
21974
|
-
lock,
|
|
21975
|
-
components: { worker, scheduler, watchdog, watchdogCleanup }
|
|
21976
|
-
};
|
|
22140
|
+
return 0;
|
|
21977
22141
|
}
|
|
21978
|
-
|
|
21979
|
-
|
|
21980
|
-
|
|
22142
|
+
function isSemanticVersion(version2) {
|
|
22143
|
+
return parseSemanticVersion(version2) !== null;
|
|
22144
|
+
}
|
|
22145
|
+
var init_semver = __esm({
|
|
22146
|
+
"src/core/semver.ts"() {
|
|
21981
22147
|
"use strict";
|
|
21982
|
-
init_db2();
|
|
21983
|
-
LOCK_STALE_MS = 3e4;
|
|
21984
|
-
state = null;
|
|
21985
22148
|
}
|
|
21986
22149
|
});
|
|
21987
22150
|
|
|
21988
|
-
// src/
|
|
21989
|
-
|
|
21990
|
-
|
|
21991
|
-
|
|
21992
|
-
|
|
21993
|
-
|
|
21994
|
-
|
|
21995
|
-
|
|
21996
|
-
|
|
21997
|
-
|
|
21998
|
-
|
|
22151
|
+
// src/daemon/update.ts
|
|
22152
|
+
var update_exports = {};
|
|
22153
|
+
__export(update_exports, {
|
|
22154
|
+
getOpenCodePluginDiagnostic: () => getOpenCodePluginDiagnostic,
|
|
22155
|
+
installLatestPlugin: () => installLatestPlugin,
|
|
22156
|
+
installPluginVersion: () => installPluginVersion,
|
|
22157
|
+
resolveConfiguredPluginSpec: () => resolveConfiguredPluginSpec,
|
|
22158
|
+
resolveInstalledPlugin: () => resolveInstalledPlugin,
|
|
22159
|
+
resolveInstalledPluginVersion: () => resolveInstalledPluginVersion
|
|
22160
|
+
});
|
|
22161
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
22162
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, readdirSync } from "fs";
|
|
22163
|
+
import { homedir as homedir4 } from "os";
|
|
22164
|
+
import { join as join5 } from "path";
|
|
22165
|
+
function pluginAt(packageDir) {
|
|
22166
|
+
const packageJson = join5(packageDir, "package.json");
|
|
22167
|
+
const gatewayEntry = join5(packageDir, "dist/gateway/index.js");
|
|
22168
|
+
if (!existsSync6(packageJson) || !existsSync6(gatewayEntry)) return null;
|
|
22169
|
+
try {
|
|
22170
|
+
const pkg = JSON.parse(readFileSync4(packageJson, "utf8"));
|
|
22171
|
+
if (pkg.name !== PACKAGE_NAME || typeof pkg.version !== "string") return null;
|
|
22172
|
+
return { packageDir, gatewayEntry, version: pkg.version };
|
|
22173
|
+
} catch {
|
|
22174
|
+
return null;
|
|
22175
|
+
}
|
|
22176
|
+
}
|
|
22177
|
+
function compareVersions(left, right) {
|
|
22178
|
+
return compareSemanticVersions(left, right) ?? left.localeCompare(right);
|
|
22179
|
+
}
|
|
22180
|
+
function cacheRoot() {
|
|
22181
|
+
return process.env.SUPERTASK_OPENCODE_CACHE_DIR ?? join5(homedir4(), ".cache/opencode/packages");
|
|
22182
|
+
}
|
|
22183
|
+
function installedPlugins() {
|
|
22184
|
+
const root = cacheRoot();
|
|
22185
|
+
const packageDirs = existsSync6(root) ? readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && (entry.name === PACKAGE_NAME || entry.name.startsWith(`${PACKAGE_NAME}@`))).map((entry) => join5(root, entry.name, "node_modules", PACKAGE_NAME)) : [];
|
|
22186
|
+
return packageDirs.map(pluginAt).filter((plugin) => plugin !== null);
|
|
22187
|
+
}
|
|
22188
|
+
function resolveInstalledPlugin() {
|
|
22189
|
+
const override = process.env.SUPERTASK_PLUGIN_PACKAGE_DIR;
|
|
22190
|
+
if (override) {
|
|
22191
|
+
const plugin = pluginAt(override);
|
|
22192
|
+
if (!plugin) throw new Error(`[supertask] \u5B89\u88C5\u5305\u65E0\u6548\u6216\u7F3A\u5C11 Gateway \u6784\u5EFA\u4EA7\u7269: ${override}`);
|
|
22193
|
+
return plugin;
|
|
22194
|
+
}
|
|
22195
|
+
const installed = installedPlugins().sort((left, right) => compareVersions(right.version, left.version));
|
|
22196
|
+
if (!installed[0]) {
|
|
22197
|
+
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230\u53EF\u8FD0\u884C\u7684 ${PACKAGE_NAME}`);
|
|
22198
|
+
}
|
|
22199
|
+
return installed[0];
|
|
22200
|
+
}
|
|
22201
|
+
function opencodeBin() {
|
|
22202
|
+
return process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
22203
|
+
}
|
|
22204
|
+
function npmBin() {
|
|
22205
|
+
return process.env.SUPERTASK_NPM_BIN ?? "npm";
|
|
22206
|
+
}
|
|
22207
|
+
function latestVersion() {
|
|
22208
|
+
const result = spawnSync3(npmBin(), [
|
|
22209
|
+
"view",
|
|
22210
|
+
PACKAGE_NAME,
|
|
22211
|
+
"dist-tags.latest",
|
|
22212
|
+
"--json"
|
|
22213
|
+
], {
|
|
22214
|
+
encoding: "utf8",
|
|
22215
|
+
env: process.env,
|
|
22216
|
+
timeout: 3e4
|
|
22217
|
+
});
|
|
22218
|
+
const output = `${result.stdout ?? ""}`.trim();
|
|
22219
|
+
if (result.error) {
|
|
22220
|
+
throw new Error(`[supertask] \u67E5\u8BE2 npm latest \u5931\u8D25: ${result.error.message}`);
|
|
22221
|
+
}
|
|
22222
|
+
if (result.status !== 0) {
|
|
22223
|
+
const detail = `${result.stderr ?? ""}`.trim();
|
|
22224
|
+
throw new Error(`[supertask] \u67E5\u8BE2 npm latest \u5931\u8D25: ${detail || `\u9000\u51FA\u7801 ${result.status}`}`);
|
|
22225
|
+
}
|
|
22226
|
+
let response;
|
|
22227
|
+
try {
|
|
22228
|
+
response = JSON.parse(output);
|
|
22229
|
+
} catch {
|
|
22230
|
+
throw new Error(`[supertask] npm latest \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790: ${output || "(empty)"}`);
|
|
22231
|
+
}
|
|
22232
|
+
const versions = typeof response === "string" ? [response] : Array.isArray(response) && response.every((value) => typeof value === "string") ? response : [];
|
|
22233
|
+
const uniqueVersions = [...new Set(versions)];
|
|
22234
|
+
if (uniqueVersions.length !== 1 || !isSemanticVersion(uniqueVersions[0])) {
|
|
22235
|
+
throw new Error(`[supertask] npm latest \u7248\u672C\u65E0\u6548: ${String(response)}`);
|
|
22236
|
+
}
|
|
22237
|
+
return uniqueVersions[0];
|
|
22238
|
+
}
|
|
22239
|
+
function resolveInstalledPluginVersion(expectedVersion) {
|
|
22240
|
+
const override = process.env.SUPERTASK_PLUGIN_PACKAGE_DIR;
|
|
22241
|
+
const installed = override ? [pluginAt(override)].filter((plugin) => plugin !== null) : installedPlugins();
|
|
22242
|
+
const matched = installed.find((plugin) => plugin.version === expectedVersion);
|
|
22243
|
+
if (matched) return matched;
|
|
22244
|
+
const actual = installed.length > 0 ? installed.map((plugin) => plugin.version).join(", ") : "\u672A\u627E\u5230\u53EF\u8FD0\u884C\u7F13\u5B58";
|
|
22245
|
+
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7F13\u5B58\u7248\u672C\u4E0D\u5339\u914D\uFF1A\u671F\u671B ${expectedVersion}\uFF0C\u5B9E\u9645 ${actual}`);
|
|
22246
|
+
}
|
|
22247
|
+
function resolveConfiguredPluginSpec(value) {
|
|
22248
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
22249
|
+
throw new Error("[supertask] OpenCode \u6700\u7EC8\u914D\u7F6E\u4E0D\u662F\u5BF9\u8C61");
|
|
22250
|
+
}
|
|
22251
|
+
const plugins = value.plugin;
|
|
22252
|
+
if (!Array.isArray(plugins)) {
|
|
22253
|
+
throw new Error(`[supertask] OpenCode \u6700\u7EC8\u914D\u7F6E\u672A\u542F\u7528 ${PACKAGE_NAME}`);
|
|
22254
|
+
}
|
|
22255
|
+
const matches = plugins.flatMap((entry) => {
|
|
22256
|
+
if (typeof entry === "string") return [entry];
|
|
22257
|
+
if (Array.isArray(entry) && typeof entry[0] === "string") return [entry[0]];
|
|
22258
|
+
return [];
|
|
22259
|
+
}).filter((spec2) => spec2 === PACKAGE_NAME || spec2.startsWith(`${PACKAGE_NAME}@`));
|
|
22260
|
+
if (matches.length === 0) {
|
|
22261
|
+
throw new Error(`[supertask] OpenCode \u6700\u7EC8\u914D\u7F6E\u672A\u542F\u7528 ${PACKAGE_NAME}`);
|
|
22262
|
+
}
|
|
22263
|
+
if (matches.length !== 1) {
|
|
22264
|
+
throw new Error(`[supertask] OpenCode \u6700\u7EC8\u914D\u7F6E\u5305\u542B\u591A\u4E2A ${PACKAGE_NAME} \u58F0\u660E: ${matches.join(", ")}`);
|
|
22265
|
+
}
|
|
22266
|
+
const spec = matches[0];
|
|
22267
|
+
const version2 = spec.startsWith(`${PACKAGE_NAME}@`) ? spec.slice(PACKAGE_NAME.length + 1) : null;
|
|
22268
|
+
return {
|
|
22269
|
+
spec,
|
|
22270
|
+
version: version2 !== null && isSemanticVersion(version2) ? version2 : null,
|
|
22271
|
+
exact: version2 !== null && isSemanticVersion(version2)
|
|
22272
|
+
};
|
|
22273
|
+
}
|
|
22274
|
+
function getOpenCodePluginDiagnostic() {
|
|
22275
|
+
const result = spawnSync3(opencodeBin(), ["debug", "config", "--pure"], {
|
|
22276
|
+
encoding: "utf8",
|
|
22277
|
+
env: process.env,
|
|
22278
|
+
timeout: 3e4
|
|
22279
|
+
});
|
|
22280
|
+
const failed = (message) => ({
|
|
22281
|
+
ok: false,
|
|
22282
|
+
spec: "",
|
|
22283
|
+
version: null,
|
|
22284
|
+
exact: false,
|
|
22285
|
+
cachedVersion: null,
|
|
22286
|
+
packageDir: null,
|
|
22287
|
+
error: message
|
|
22288
|
+
});
|
|
22289
|
+
if (result.error) {
|
|
22290
|
+
return failed(`[supertask] \u65E0\u6CD5\u8BFB\u53D6 OpenCode \u6700\u7EC8\u914D\u7F6E: ${result.error.message}`);
|
|
22291
|
+
}
|
|
22292
|
+
if (result.status !== 0) {
|
|
22293
|
+
const detail = `${result.stderr ?? ""}`.trim();
|
|
22294
|
+
return failed(`[supertask] \u65E0\u6CD5\u8BFB\u53D6 OpenCode \u6700\u7EC8\u914D\u7F6E: ${detail || `\u9000\u51FA\u7801 ${result.status}`}`);
|
|
22295
|
+
}
|
|
22296
|
+
let config;
|
|
22297
|
+
try {
|
|
22298
|
+
config = JSON.parse(`${result.stdout ?? ""}`);
|
|
22299
|
+
} catch {
|
|
22300
|
+
return failed("[supertask] OpenCode \u6700\u7EC8\u914D\u7F6E\u4E0D\u662F\u6709\u6548 JSON");
|
|
22301
|
+
}
|
|
22302
|
+
let configured;
|
|
22303
|
+
try {
|
|
22304
|
+
configured = resolveConfiguredPluginSpec(config);
|
|
22305
|
+
} catch (error) {
|
|
22306
|
+
return failed(error instanceof Error ? error.message : String(error));
|
|
22307
|
+
}
|
|
22308
|
+
if (!configured.exact || configured.version === null) {
|
|
22309
|
+
return {
|
|
22310
|
+
ok: false,
|
|
22311
|
+
...configured,
|
|
22312
|
+
cachedVersion: null,
|
|
22313
|
+
packageDir: null,
|
|
22314
|
+
error: `[supertask] OpenCode \u63D2\u4EF6\u5FC5\u987B\u56FA\u5B9A\u7CBE\u786E\u7248\u672C\uFF0C\u4E0D\u80FD\u4F7F\u7528 ${configured.spec}`
|
|
22315
|
+
};
|
|
22316
|
+
}
|
|
22317
|
+
try {
|
|
22318
|
+
const installed = resolveInstalledPluginVersion(configured.version);
|
|
22319
|
+
return {
|
|
22320
|
+
ok: true,
|
|
22321
|
+
...configured,
|
|
22322
|
+
cachedVersion: installed.version,
|
|
22323
|
+
packageDir: installed.packageDir,
|
|
22324
|
+
error: null
|
|
22325
|
+
};
|
|
22326
|
+
} catch (error) {
|
|
22327
|
+
return {
|
|
22328
|
+
ok: false,
|
|
22329
|
+
...configured,
|
|
22330
|
+
cachedVersion: null,
|
|
22331
|
+
packageDir: null,
|
|
22332
|
+
error: error instanceof Error ? error.message : String(error)
|
|
22333
|
+
};
|
|
22334
|
+
}
|
|
22335
|
+
}
|
|
22336
|
+
function installPluginVersion(version2) {
|
|
22337
|
+
if (!isSemanticVersion(version2)) {
|
|
22338
|
+
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7248\u672C\u65E0\u6548: ${version2}`);
|
|
22339
|
+
}
|
|
22340
|
+
const result = spawnSync3(opencodeBin(), [
|
|
22341
|
+
"plugin",
|
|
22342
|
+
`${PACKAGE_NAME}@${version2}`,
|
|
22343
|
+
"--global",
|
|
22344
|
+
"--force"
|
|
22345
|
+
], {
|
|
22346
|
+
encoding: "utf8",
|
|
22347
|
+
env: process.env,
|
|
22348
|
+
timeout: 12e4
|
|
22349
|
+
});
|
|
22350
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
22351
|
+
if (result.error) {
|
|
22352
|
+
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u66F4\u65B0\u5931\u8D25: ${result.error.message}`);
|
|
22353
|
+
}
|
|
22354
|
+
if (result.status !== 0) {
|
|
22355
|
+
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u66F4\u65B0\u5931\u8D25: ${output || `\u9000\u51FA\u7801 ${result.status}`}`);
|
|
22356
|
+
}
|
|
22357
|
+
return resolveInstalledPluginVersion(version2);
|
|
22358
|
+
}
|
|
22359
|
+
function installLatestPlugin() {
|
|
22360
|
+
return installPluginVersion(latestVersion());
|
|
22361
|
+
}
|
|
22362
|
+
var PACKAGE_NAME;
|
|
22363
|
+
var init_update2 = __esm({
|
|
22364
|
+
"src/daemon/update.ts"() {
|
|
22365
|
+
"use strict";
|
|
22366
|
+
init_semver();
|
|
22367
|
+
PACKAGE_NAME = "opencode-supertask";
|
|
22368
|
+
}
|
|
22369
|
+
});
|
|
22370
|
+
|
|
22371
|
+
// src/gateway/health.ts
|
|
22372
|
+
function initializeGatewayHealth(config) {
|
|
22373
|
+
const now = Date.now();
|
|
22374
|
+
state = {
|
|
22375
|
+
startedAt: now,
|
|
22376
|
+
config,
|
|
22377
|
+
lastActivityAt: {
|
|
22378
|
+
worker: now,
|
|
22379
|
+
scheduler: now,
|
|
22380
|
+
watchdog: now,
|
|
22381
|
+
watchdogCleanup: now
|
|
22382
|
+
},
|
|
22383
|
+
lastSuccessAt: {
|
|
22384
|
+
worker: now,
|
|
22385
|
+
scheduler: now,
|
|
22386
|
+
watchdog: now,
|
|
22387
|
+
watchdogCleanup: now
|
|
22388
|
+
},
|
|
22389
|
+
consecutiveFailures: {
|
|
22390
|
+
worker: 0,
|
|
22391
|
+
scheduler: 0,
|
|
22392
|
+
watchdog: 0,
|
|
22393
|
+
watchdogCleanup: 0
|
|
22394
|
+
},
|
|
22395
|
+
lastError: {
|
|
22396
|
+
worker: null,
|
|
22397
|
+
scheduler: null,
|
|
22398
|
+
watchdog: null,
|
|
22399
|
+
watchdogCleanup: null
|
|
22400
|
+
}
|
|
22401
|
+
};
|
|
22402
|
+
}
|
|
22403
|
+
function markGatewayActivity(component) {
|
|
22404
|
+
if (state) state.lastActivityAt[component] = Date.now();
|
|
22405
|
+
}
|
|
22406
|
+
function markGatewaySuccess(component) {
|
|
22407
|
+
if (!state) return;
|
|
22408
|
+
const now = Date.now();
|
|
22409
|
+
state.lastActivityAt[component] = now;
|
|
22410
|
+
state.lastSuccessAt[component] = now;
|
|
22411
|
+
state.consecutiveFailures[component] = 0;
|
|
22412
|
+
}
|
|
22413
|
+
function markGatewayFailure(component, error) {
|
|
22414
|
+
if (!state) return;
|
|
22415
|
+
const now = Date.now();
|
|
22416
|
+
state.lastActivityAt[component] = now;
|
|
22417
|
+
state.consecutiveFailures[component] += 1;
|
|
22418
|
+
state.lastError[component] = {
|
|
22419
|
+
at: now,
|
|
22420
|
+
message: error instanceof Error ? error.message : String(error)
|
|
22421
|
+
};
|
|
22422
|
+
}
|
|
22423
|
+
function resetGatewayHealth() {
|
|
22424
|
+
state = null;
|
|
22425
|
+
}
|
|
22426
|
+
function componentStatus(component, enabled, maxAgeMs, now) {
|
|
22427
|
+
const lastActivityAt = state?.lastActivityAt[component] ?? null;
|
|
22428
|
+
const ageMs = lastActivityAt == null ? null : Math.max(0, now - lastActivityAt);
|
|
22429
|
+
const consecutiveFailures = state?.consecutiveFailures[component] ?? 0;
|
|
22430
|
+
return {
|
|
22431
|
+
enabled,
|
|
22432
|
+
healthy: !enabled || ageMs != null && ageMs <= maxAgeMs && consecutiveFailures === 0,
|
|
22433
|
+
lastActivityAt,
|
|
22434
|
+
lastSuccessAt: state?.lastSuccessAt[component] ?? null,
|
|
22435
|
+
consecutiveFailures,
|
|
22436
|
+
lastError: state?.lastError[component] ?? null,
|
|
22437
|
+
ageMs,
|
|
22438
|
+
maxAgeMs
|
|
22439
|
+
};
|
|
22440
|
+
}
|
|
22441
|
+
function getGatewayHealth(now = Date.now()) {
|
|
22442
|
+
const worker = componentStatus(
|
|
22443
|
+
"worker",
|
|
22444
|
+
true,
|
|
22445
|
+
Math.max((state?.config.workerPollIntervalMs ?? 1e3) * 5, 5e3),
|
|
22446
|
+
now
|
|
22447
|
+
);
|
|
22448
|
+
const scheduler = componentStatus(
|
|
22449
|
+
"scheduler",
|
|
22450
|
+
state?.config.schedulerEnabled ?? false,
|
|
22451
|
+
Math.max((state?.config.schedulerCheckIntervalMs ?? 1e3) * 5, 5e3),
|
|
22452
|
+
now
|
|
22453
|
+
);
|
|
22454
|
+
const watchdog = componentStatus(
|
|
22455
|
+
"watchdog",
|
|
22456
|
+
true,
|
|
22457
|
+
Math.max((state?.config.watchdogCheckIntervalMs ?? 6e4) * 3, 5e3),
|
|
22458
|
+
now
|
|
22459
|
+
);
|
|
22460
|
+
const watchdogCleanup = componentStatus(
|
|
22461
|
+
"watchdogCleanup",
|
|
22462
|
+
true,
|
|
22463
|
+
Math.max((state?.config.watchdogCleanupIntervalMs ?? 864e5) * 2, 6e4),
|
|
22464
|
+
now
|
|
22465
|
+
);
|
|
22466
|
+
let lock = { pid: null, heartbeatAt: null, readyAt: null, ageMs: null, healthy: false };
|
|
22467
|
+
try {
|
|
22468
|
+
const row = sqlite.prepare(
|
|
22469
|
+
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
22470
|
+
).get();
|
|
22471
|
+
if (row) {
|
|
22472
|
+
const ageMs = Math.max(0, now - row.heartbeat_at);
|
|
22473
|
+
lock = {
|
|
22474
|
+
pid: row.pid,
|
|
22475
|
+
heartbeatAt: row.heartbeat_at,
|
|
22476
|
+
readyAt: row.ready_at,
|
|
22477
|
+
ageMs,
|
|
22478
|
+
healthy: row.pid === process.pid && row.ready_at != null && ageMs < LOCK_STALE_MS
|
|
22479
|
+
};
|
|
22480
|
+
}
|
|
22481
|
+
} catch {
|
|
22482
|
+
}
|
|
22483
|
+
const healthy = state != null && lock.healthy && worker.healthy && scheduler.healthy && watchdog.healthy && watchdogCleanup.healthy;
|
|
22484
|
+
return {
|
|
22485
|
+
status: healthy ? "ok" : "degraded",
|
|
22486
|
+
pid: process.pid,
|
|
22487
|
+
uptimeMs: state ? Math.max(0, now - state.startedAt) : 0,
|
|
22488
|
+
lock,
|
|
22489
|
+
components: { worker, scheduler, watchdog, watchdogCleanup }
|
|
22490
|
+
};
|
|
22491
|
+
}
|
|
22492
|
+
var LOCK_STALE_MS, state;
|
|
22493
|
+
var init_health = __esm({
|
|
22494
|
+
"src/gateway/health.ts"() {
|
|
22495
|
+
"use strict";
|
|
22496
|
+
init_db2();
|
|
22497
|
+
LOCK_STALE_MS = 3e4;
|
|
22498
|
+
state = null;
|
|
22499
|
+
}
|
|
22500
|
+
});
|
|
22501
|
+
|
|
22502
|
+
// src/worker/index.ts
|
|
22503
|
+
import { spawn } from "child_process";
|
|
22504
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
22505
|
+
import { existsSync as existsSync7 } from "fs";
|
|
22506
|
+
import { dirname as dirname7, join as join6 } from "path";
|
|
22507
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
22508
|
+
function assertWorkerProcessIsolationSupported(platform = process.platform) {
|
|
22509
|
+
if (platform === "win32") {
|
|
22510
|
+
throw new Error(
|
|
22511
|
+
"Windows Worker \u5DF2\u5B89\u5168\u7981\u7528\uFF1A\u5F53\u524D\u8FD0\u884C\u65F6\u65E0\u6CD5\u7528 Job Object \u8BC1\u660E\u6574\u4E2A OpenCode \u8FDB\u7A0B\u6811\u5DF2\u9000\u51FA"
|
|
22512
|
+
);
|
|
21999
22513
|
}
|
|
22000
22514
|
}
|
|
22001
22515
|
var DEFAULT_MAX_OUTPUT_CHARS, FORBIDDEN_AGENT, WorkerEngine;
|
|
@@ -22007,6 +22521,8 @@ var init_worker = __esm({
|
|
|
22007
22521
|
init_health();
|
|
22008
22522
|
init_process_control();
|
|
22009
22523
|
init_launch_protocol();
|
|
22524
|
+
init_task_working_directory();
|
|
22525
|
+
init_task_batch();
|
|
22010
22526
|
DEFAULT_MAX_OUTPUT_CHARS = 64 * 1024;
|
|
22011
22527
|
FORBIDDEN_AGENT = "supertask-runner";
|
|
22012
22528
|
WorkerEngine = class {
|
|
@@ -22112,7 +22628,8 @@ var init_worker = __esm({
|
|
|
22112
22628
|
if (!task) break;
|
|
22113
22629
|
if (this.stopped) break;
|
|
22114
22630
|
if (!await TaskService.start(task.id)) continue;
|
|
22115
|
-
|
|
22631
|
+
const batchId = normalizeTaskBatchId(task.batchId);
|
|
22632
|
+
if (batchId) this.activeBatchIds.add(batchId);
|
|
22116
22633
|
if (this.stopped) {
|
|
22117
22634
|
await TaskService.resetRunningToPending([task.id]);
|
|
22118
22635
|
this.releaseBatch(task);
|
|
@@ -22143,6 +22660,14 @@ var init_worker = __esm({
|
|
|
22143
22660
|
this.releaseBatch(task);
|
|
22144
22661
|
continue;
|
|
22145
22662
|
}
|
|
22663
|
+
try {
|
|
22664
|
+
validateTaskWorkingDirectory(task.cwd);
|
|
22665
|
+
} catch (error) {
|
|
22666
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22667
|
+
await TaskService.failRun(task.id, run.id, message, { setDeadLetter: true });
|
|
22668
|
+
this.releaseBatch(task);
|
|
22669
|
+
continue;
|
|
22670
|
+
}
|
|
22146
22671
|
await this.spawnTask(task, run.id, launchIdentity);
|
|
22147
22672
|
} catch (err) {
|
|
22148
22673
|
this.releaseBatch(task);
|
|
@@ -22169,10 +22694,10 @@ var init_worker = __esm({
|
|
|
22169
22694
|
const extension = import.meta.url.endsWith(".ts") ? "ts" : "js";
|
|
22170
22695
|
const moduleDir = dirname7(fileURLToPath5(import.meta.url));
|
|
22171
22696
|
const candidates = [
|
|
22172
|
-
|
|
22173
|
-
|
|
22697
|
+
join6(moduleDir, `launcher.${extension}`),
|
|
22698
|
+
join6(moduleDir, `../worker/launcher.${extension}`)
|
|
22174
22699
|
];
|
|
22175
|
-
const entry = candidates.find((candidate) =>
|
|
22700
|
+
const entry = candidates.find((candidate) => existsSync7(candidate));
|
|
22176
22701
|
if (!entry) throw new Error(`Worker launcher \u4E0D\u5B58\u5728\uFF1A${candidates.join(", ")}`);
|
|
22177
22702
|
return entry;
|
|
22178
22703
|
}
|
|
@@ -22228,6 +22753,11 @@ var init_worker = __esm({
|
|
|
22228
22753
|
child.on("message", (message) => {
|
|
22229
22754
|
if (isMatchingDrainProof(message, entry.launchIdentity)) {
|
|
22230
22755
|
entry.guardianDrained = true;
|
|
22756
|
+
try {
|
|
22757
|
+
child.send(drainProofAckForIdentity(entry.launchIdentity));
|
|
22758
|
+
} catch (error) {
|
|
22759
|
+
this.logError("drain proof acknowledgment failed", error, task.id);
|
|
22760
|
+
}
|
|
22231
22761
|
}
|
|
22232
22762
|
});
|
|
22233
22763
|
let spawnError = null;
|
|
@@ -22507,7 +23037,8 @@ ${output}` : ""}` : output;
|
|
|
22507
23037
|
);
|
|
22508
23038
|
}
|
|
22509
23039
|
releaseBatch(task) {
|
|
22510
|
-
|
|
23040
|
+
const batchId = normalizeTaskBatchId(task.batchId);
|
|
23041
|
+
if (batchId) this.activeBatchIds.delete(batchId);
|
|
22511
23042
|
}
|
|
22512
23043
|
resolveModel(taskModel) {
|
|
22513
23044
|
if (!taskModel || taskModel === "default") return null;
|
|
@@ -22828,8 +23359,11 @@ var init_watchdog = __esm({
|
|
|
22828
23359
|
});
|
|
22829
23360
|
|
|
22830
23361
|
// src/gateway/scheduler/job-templates.ts
|
|
22831
|
-
async function cloneTaskFromTemplate(templateId) {
|
|
22832
|
-
return createTaskFromTemplate(templateId, {
|
|
23362
|
+
async function cloneTaskFromTemplate(templateId, expectedNextRunAt) {
|
|
23363
|
+
return createTaskFromTemplate(templateId, {
|
|
23364
|
+
advanceSchedule: true,
|
|
23365
|
+
expectedNextRunAt
|
|
23366
|
+
});
|
|
22833
23367
|
}
|
|
22834
23368
|
async function triggerTaskFromTemplate(templateId) {
|
|
22835
23369
|
return createTaskFromTemplate(templateId, {
|
|
@@ -22842,37 +23376,40 @@ function createTaskFromTemplate(templateId, options) {
|
|
|
22842
23376
|
return db.transaction((tx) => {
|
|
22843
23377
|
const tmpl = tx.select().from(taskTemplates3).where(eq(taskTemplates3.id, templateId)).limit(1).get();
|
|
22844
23378
|
if (!tmpl || options.advanceSchedule && !tmpl.enabled) return null;
|
|
22845
|
-
|
|
22846
|
-
|
|
22847
|
-
sql
|
|
22848
|
-
|
|
22849
|
-
|
|
22850
|
-
|
|
22851
|
-
|
|
22852
|
-
|
|
22853
|
-
|
|
22854
|
-
|
|
22855
|
-
|
|
22856
|
-
eq(schema_exports.tasks.
|
|
22857
|
-
|
|
22858
|
-
|
|
22859
|
-
|
|
22860
|
-
${schema_exports.tasks.status}
|
|
22861
|
-
|
|
22862
|
-
|
|
22863
|
-
|
|
22864
|
-
|
|
22865
|
-
|
|
22866
|
-
|
|
22867
|
-
|
|
22868
|
-
|
|
22869
|
-
|
|
22870
|
-
|
|
22871
|
-
|
|
22872
|
-
|
|
22873
|
-
|
|
23379
|
+
if (options.advanceSchedule && options.expectedNextRunAt !== void 0 && tmpl.nextRunAt !== options.expectedNextRunAt) return null;
|
|
23380
|
+
if (options.advanceSchedule) {
|
|
23381
|
+
const activeTasks = tx.select({ count: sql`count(*)` }).from(schema_exports.tasks).where(and(
|
|
23382
|
+
eq(schema_exports.tasks.templateId, templateId),
|
|
23383
|
+
sql`${schema_exports.tasks.status} IN ('pending', 'running')`
|
|
23384
|
+
)).get();
|
|
23385
|
+
const retryableTasks = tx.select({ count: sql`count(*)` }).from(schema_exports.tasks).where(and(
|
|
23386
|
+
eq(schema_exports.tasks.templateId, templateId),
|
|
23387
|
+
eq(schema_exports.tasks.status, "failed"),
|
|
23388
|
+
sql`${schema_exports.tasks.retryCount} <= ${schema_exports.tasks.maxRetries}`
|
|
23389
|
+
)).get();
|
|
23390
|
+
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(
|
|
23391
|
+
eq(schema_exports.taskRuns.status, "running"),
|
|
23392
|
+
eq(schema_exports.tasks.templateId, templateId),
|
|
23393
|
+
sql`NOT (
|
|
23394
|
+
${schema_exports.tasks.status} IN ('pending', 'running')
|
|
23395
|
+
OR (
|
|
23396
|
+
${schema_exports.tasks.status} = 'failed'
|
|
23397
|
+
AND ${schema_exports.tasks.retryCount} <= ${schema_exports.tasks.maxRetries}
|
|
23398
|
+
)
|
|
23399
|
+
)`
|
|
23400
|
+
)).get();
|
|
23401
|
+
const activeCount = Number(activeTasks?.count ?? 0) + Number(retryableTasks?.count ?? 0) + Number(detachedRuns?.count ?? 0);
|
|
23402
|
+
if (activeCount >= (tmpl.maxInstances ?? 1)) {
|
|
23403
|
+
if (tmpl.scheduleType !== "delayed") {
|
|
23404
|
+
const nextRunAt2 = TaskTemplateService.calculateNextRunAt(
|
|
23405
|
+
tmpl.scheduleType,
|
|
23406
|
+
tmpl,
|
|
23407
|
+
nowMs
|
|
23408
|
+
);
|
|
23409
|
+
tx.update(taskTemplates3).set({ nextRunAt: nextRunAt2, updatedAt: nowMs }).where(and(eq(taskTemplates3.id, templateId), eq(taskTemplates3.enabled, true))).run();
|
|
23410
|
+
}
|
|
23411
|
+
return null;
|
|
22874
23412
|
}
|
|
22875
|
-
return null;
|
|
22876
23413
|
}
|
|
22877
23414
|
const isDelayed = tmpl.scheduleType === "delayed";
|
|
22878
23415
|
const nextRunAt = isDelayed ? null : TaskTemplateService.calculateNextRunAt(
|
|
@@ -22889,7 +23426,7 @@ function createTaskFromTemplate(templateId, options) {
|
|
|
22889
23426
|
category: tmpl.category ?? "general",
|
|
22890
23427
|
importance: tmpl.importance ?? 3,
|
|
22891
23428
|
urgency: tmpl.urgency ?? 3,
|
|
22892
|
-
batchId: tmpl.batchId,
|
|
23429
|
+
batchId: normalizeTaskBatchId(tmpl.batchId),
|
|
22893
23430
|
maxRetries: tmpl.maxRetries ?? 3,
|
|
22894
23431
|
retryBackoffMs: tmpl.retryBackoffMs ?? 3e4,
|
|
22895
23432
|
timeoutMs: tmpl.timeoutMs,
|
|
@@ -22950,6 +23487,7 @@ var init_job_templates = __esm({
|
|
|
22950
23487
|
init_db2();
|
|
22951
23488
|
init_drizzle_orm();
|
|
22952
23489
|
init_task_template_service();
|
|
23490
|
+
init_task_batch();
|
|
22953
23491
|
({ taskTemplates: taskTemplates3 } = schema_exports);
|
|
22954
23492
|
DUE_TEMPLATE_BATCH_SIZE = 100;
|
|
22955
23493
|
}
|
|
@@ -22999,7 +23537,7 @@ var init_scheduler = __esm({
|
|
|
22999
23537
|
const lastTemplate = dueTemplates.at(-1);
|
|
23000
23538
|
for (const tmpl of dueTemplates) {
|
|
23001
23539
|
try {
|
|
23002
|
-
const task = await cloneTaskFromTemplate(tmpl.id);
|
|
23540
|
+
const task = await cloneTaskFromTemplate(tmpl.id, tmpl.nextRunAt);
|
|
23003
23541
|
if (task) {
|
|
23004
23542
|
console.log(JSON.stringify({
|
|
23005
23543
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -25287,6 +25825,14 @@ function statusText(locale, status) {
|
|
|
25287
25825
|
const key = `status.${status}`;
|
|
25288
25826
|
return key in ZH ? t(locale, key) : t(locale, "status.unknown");
|
|
25289
25827
|
}
|
|
25828
|
+
function runStatusText(locale, status) {
|
|
25829
|
+
const key = `runStatus.${status}`;
|
|
25830
|
+
return key in ZH ? t(locale, key) : t(locale, "status.unknown");
|
|
25831
|
+
}
|
|
25832
|
+
function resolveEditedRunAt(originalEpoch, originalLocal, currentLocal) {
|
|
25833
|
+
if (originalEpoch !== null && originalLocal === currentLocal) return originalEpoch;
|
|
25834
|
+
return new Date(currentLocal).getTime();
|
|
25835
|
+
}
|
|
25290
25836
|
function formatRelative(timestamp2, locale) {
|
|
25291
25837
|
if (!timestamp2) return "\u2014";
|
|
25292
25838
|
const deltaMs = timestamp2 - Date.now();
|
|
@@ -25363,13 +25909,31 @@ function clientMessages(locale) {
|
|
|
25363
25909
|
"dialog.clearTitle",
|
|
25364
25910
|
"dialog.clearBody",
|
|
25365
25911
|
"dialog.clearInstruction",
|
|
25912
|
+
"dialog.restartGateway",
|
|
25913
|
+
"dialog.restartGatewayBody",
|
|
25914
|
+
"dialog.restartGatewayRunningBody",
|
|
25366
25915
|
"feedback.retryFailed",
|
|
25367
25916
|
"feedback.cancelFailed",
|
|
25368
25917
|
"feedback.deleteFailed",
|
|
25369
25918
|
"feedback.requestFailed",
|
|
25370
25919
|
"feedback.triggered",
|
|
25920
|
+
"feedback.taskCreated",
|
|
25921
|
+
"feedback.taskUpdated",
|
|
25922
|
+
"task.projectExisting",
|
|
25923
|
+
"task.projectNew",
|
|
25924
|
+
"task.createTitle",
|
|
25925
|
+
"task.editTitle",
|
|
25926
|
+
"action.saveTask",
|
|
25927
|
+
"action.updateTask",
|
|
25371
25928
|
"feedback.configSaved",
|
|
25372
25929
|
"feedback.databaseCleared",
|
|
25930
|
+
"feedback.templateCreated",
|
|
25931
|
+
"feedback.templateUpdated",
|
|
25932
|
+
"feedback.sessionCommandCopied",
|
|
25933
|
+
"feedback.restarting",
|
|
25934
|
+
"feedback.restartTimeout",
|
|
25935
|
+
"template.createTitle",
|
|
25936
|
+
"template.editTitle",
|
|
25373
25937
|
"filter.noResults"
|
|
25374
25938
|
];
|
|
25375
25939
|
return Object.fromEntries(keys.map((key) => [key, t(locale, key)]));
|
|
@@ -25463,6 +26027,7 @@ function renderLayout(options) {
|
|
|
25463
26027
|
themeMedia.addEventListener?.('change',()=>{if((localStorage.getItem('supertask-theme')||'system')==='system')applyTheme('system');});
|
|
25464
26028
|
applyTheme(localStorage.getItem('supertask-theme')||'system');
|
|
25465
26029
|
requestAnimationFrame(()=>document.documentElement.classList.add('ui-ready'));
|
|
26030
|
+
${resolveEditedRunAt.toString()}
|
|
25466
26031
|
function refreshPage(button){button.classList.add('refreshing');button.setAttribute('aria-label','${t(locale, "a11y.refreshing")}');location.reload();}
|
|
25467
26032
|
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);}
|
|
25468
26033
|
async function readJson(response){const data=await response.json().catch(()=>({}));if(!response.ok)throw new Error(data.error||text('feedback.requestFailed'));return data;}
|
|
@@ -25474,6 +26039,22 @@ function renderLayout(options) {
|
|
|
25474
26039
|
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')}}
|
|
25475
26040
|
const showDetail=id=>showRecord('/api/tasks/'+id);const showRunDetail=id=>showRecord('/api/runs/'+id);const showTemplateDetail=id=>showRecord('/api/templates/'+id);
|
|
25476
26041
|
async function copyDetails(){try{await navigator.clipboard.writeText(document.getElementById('detail-content').textContent);showToast(text('details.copySuccess'))}catch{showToast(text('feedback.copyFailed'),'error')}}
|
|
26042
|
+
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')}}
|
|
26043
|
+
function taskField(name){return document.getElementById('task-'+name)}
|
|
26044
|
+
function taskProjects(){const node=document.getElementById('task-project-data');if(!node)return {};try{return JSON.parse(node.textContent||'{}')}catch{return {}}}
|
|
26045
|
+
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')}
|
|
26046
|
+
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)}
|
|
26047
|
+
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')}}
|
|
26048
|
+
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}}
|
|
26049
|
+
function templateField(name){return document.getElementById('template-'+name)}
|
|
26050
|
+
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'}
|
|
26051
|
+
function localDateTime(milliseconds){const date=new Date(milliseconds);const local=new Date(milliseconds-date.getTimezoneOffset()*60000);return local.toISOString().slice(0,23)}
|
|
26052
|
+
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}}
|
|
26053
|
+
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}
|
|
26054
|
+
function selectedRunAt(){const input=templateField('run-at');return resolveEditedRunAt(input.dataset.originalEpoch?Number(input.dataset.originalEpoch):null,input.dataset.originalLocal||'',input.value)}
|
|
26055
|
+
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)}
|
|
26056
|
+
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')}}
|
|
26057
|
+
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}}
|
|
25477
26058
|
async function enableTmpl(id){try{await readJson(await fetch('/api/templates/'+id+'/enable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
|
|
25478
26059
|
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')}}
|
|
25479
26060
|
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')}}
|
|
@@ -25481,7 +26062,9 @@ function renderLayout(options) {
|
|
|
25481
26062
|
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');}
|
|
25482
26063
|
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;}
|
|
25483
26064
|
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')}}
|
|
25484
|
-
async function
|
|
26065
|
+
async function confirmGatewayRestart(runningCount=0){const body=runningCount>0?text('dialog.restartGatewayRunningBody',{count:runningCount}):text('dialog.restartGatewayBody');return await ask(text('dialog.restartGateway'),body)}
|
|
26066
|
+
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}}
|
|
26067
|
+
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')}}
|
|
25485
26068
|
</script>
|
|
25486
26069
|
</body>
|
|
25487
26070
|
</html>`;
|
|
@@ -25493,17 +26076,17 @@ var init_ui = __esm({
|
|
|
25493
26076
|
ZH = {
|
|
25494
26077
|
"app.name": "SuperTask",
|
|
25495
26078
|
"app.dashboard": "\u63A7\u5236\u53F0",
|
|
25496
|
-
"app.tagline": "\u53EF\u9760\u7684\u672C\u5730 Agent \
|
|
26079
|
+
"app.tagline": "\u53EF\u9760\u7684\u672C\u5730 Agent \u4EFB\u52A1\u4E2D\u5FC3",
|
|
25497
26080
|
"app.local": "\u672C\u5730\u8FD0\u884C",
|
|
25498
26081
|
"app.footer": "Local-first \xB7 \u6570\u636E\u7559\u5728\u4F60\u7684\u8BBE\u5907\u4E0A",
|
|
25499
26082
|
"nav.tasks": "\u4EFB\u52A1",
|
|
25500
|
-
"nav.templates": "\
|
|
26083
|
+
"nav.templates": "\u5B9A\u65F6\u4EFB\u52A1",
|
|
25501
26084
|
"nav.runs": "\u6267\u884C\u8BB0\u5F55",
|
|
25502
26085
|
"nav.system": "\u7CFB\u7EDF",
|
|
25503
26086
|
"page.tasks.title": "\u4EFB\u52A1\u961F\u5217",
|
|
25504
26087
|
"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",
|
|
25505
|
-
"page.templates.title": "\
|
|
25506
|
-
"page.templates.description": "\
|
|
26088
|
+
"page.templates.title": "\u5B9A\u65F6\u4EFB\u52A1",
|
|
26089
|
+
"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",
|
|
25507
26090
|
"page.runs.title": "\u6267\u884C\u8BB0\u5F55",
|
|
25508
26091
|
"page.runs.description": "\u8FFD\u8E2A\u6BCF\u6B21 Agent \u6267\u884C\u7684\u72B6\u6001\u3001\u8017\u65F6\u3001\u5FC3\u8DF3\u4E0E\u8F93\u51FA\u3002",
|
|
25509
26092
|
"page.system.title": "\u7CFB\u7EDF\u8BBE\u7F6E",
|
|
@@ -25513,29 +26096,43 @@ var init_ui = __esm({
|
|
|
25513
26096
|
"action.retry": "\u91CD\u8BD5",
|
|
25514
26097
|
"action.cancel": "\u53D6\u6D88",
|
|
25515
26098
|
"action.delete": "\u5220\u9664",
|
|
26099
|
+
"action.edit": "\u7F16\u8F91",
|
|
25516
26100
|
"action.enable": "\u542F\u7528",
|
|
25517
26101
|
"action.disable": "\u7981\u7528",
|
|
25518
|
-
"action.trigger": "\u7ACB\u5373\
|
|
26102
|
+
"action.trigger": "\u7ACB\u5373\u8FD0\u884C\u4E00\u6B21",
|
|
26103
|
+
"action.continueSession": "\u7EE7\u7EED\u4F1A\u8BDD",
|
|
25519
26104
|
"action.logs": "\u67E5\u770B\u65E5\u5FD7",
|
|
25520
26105
|
"action.hideLogs": "\u6536\u8D77\u65E5\u5FD7",
|
|
25521
26106
|
"action.save": "\u4FDD\u5B58\u8BBE\u7F6E",
|
|
26107
|
+
"action.saveAndRestart": "\u4FDD\u5B58\u5E76\u91CD\u542F",
|
|
25522
26108
|
"action.copy": "\u590D\u5236 JSON",
|
|
25523
26109
|
"action.close": "\u5173\u95ED",
|
|
25524
26110
|
"action.confirm": "\u786E\u8BA4",
|
|
25525
26111
|
"action.clearDatabase": "\u6E05\u7A7A\u6570\u636E\u5E93",
|
|
26112
|
+
"action.createTask": "\u65B0\u5EFA\u4EFB\u52A1",
|
|
26113
|
+
"action.saveTask": "\u52A0\u5165\u961F\u5217",
|
|
26114
|
+
"action.updateTask": "\u4FDD\u5B58\u4FEE\u6539",
|
|
26115
|
+
"action.createTemplate": "\u65B0\u5EFA\u5B9A\u65F6\u4EFB\u52A1",
|
|
26116
|
+
"action.saveTemplate": "\u4FDD\u5B58\u5B9A\u65F6\u4EFB\u52A1",
|
|
25526
26117
|
"status.pending": "\u5F85\u6267\u884C",
|
|
25527
26118
|
"status.running": "\u8FD0\u884C\u4E2D",
|
|
25528
26119
|
"status.done": "\u5DF2\u5B8C\u6210",
|
|
25529
|
-
"status.failed": "\
|
|
25530
|
-
"status.dead_letter": "\
|
|
26120
|
+
"status.failed": "\u7B49\u5F85\u91CD\u8BD5",
|
|
26121
|
+
"status.dead_letter": "\u5DF2\u505C\u6B62",
|
|
26122
|
+
"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",
|
|
26123
|
+
"status.deadLetterAction": "\u9700\u68C0\u67E5\u539F\u56E0\u540E\u624B\u52A8\u91CD\u8BD5",
|
|
25531
26124
|
"status.cancelled": "\u5DF2\u53D6\u6D88",
|
|
26125
|
+
"status.executionStillActive": "\u6267\u884C\u8FDB\u7A0B\u4ECD\u5728\u9000\u51FA\uFF0C\u6682\u65F6\u5360\u7528\u5E76\u53D1",
|
|
25532
26126
|
"status.unknown": "\u672A\u77E5",
|
|
26127
|
+
"runStatus.running": "\u8FD0\u884C\u4E2D",
|
|
26128
|
+
"runStatus.done": "\u6210\u529F",
|
|
26129
|
+
"runStatus.failed": "\u5931\u8D25",
|
|
25533
26130
|
"stats.total": "\u603B\u4EFB\u52A1",
|
|
25534
26131
|
"stats.pending": "\u5F85\u6267\u884C",
|
|
25535
26132
|
"stats.running": "\u8FD0\u884C\u4E2D",
|
|
25536
26133
|
"stats.done": "\u5DF2\u5B8C\u6210",
|
|
25537
|
-
"stats.failedDead": "\
|
|
25538
|
-
"stats.templates": "\
|
|
26134
|
+
"stats.failedDead": "\u7B49\u5F85\u91CD\u8BD5\u4E0E\u5DF2\u505C\u6B62",
|
|
26135
|
+
"stats.templates": "\u5B9A\u65F6\u4EFB\u52A1\u603B\u6570",
|
|
25539
26136
|
"stats.enabled": "\u5DF2\u542F\u7528",
|
|
25540
26137
|
"stats.disabled": "\u5DF2\u7981\u7528",
|
|
25541
26138
|
"stats.records": "\u6267\u884C\u603B\u6570",
|
|
@@ -25543,7 +26140,7 @@ var init_ui = __esm({
|
|
|
25543
26140
|
"stats.pageFailed": "\u672C\u9875\u5931\u8D25",
|
|
25544
26141
|
"stats.pageRunning": "\u672C\u9875\u8FD0\u884C\u4E2D",
|
|
25545
26142
|
"filter.all": "\u5168\u90E8",
|
|
25546
|
-
"filter.searchTasks": "\u641C\u7D22\u5F53\u524D\u9875\u7684\u4EFB\u52A1\u3001Agent \u6216\u63D0\u793A\u8BCD",
|
|
26143
|
+
"filter.searchTasks": "\u641C\u7D22\u5F53\u524D\u9875\u7684\u4EFB\u52A1\u3001\u9879\u76EE\u3001\u6279\u6B21\u3001Agent \u6216\u63D0\u793A\u8BCD",
|
|
25547
26144
|
"filter.noResults": "\u6CA1\u6709\u7B26\u5408\u5F53\u524D\u641C\u7D22\u6761\u4EF6\u7684\u4EFB\u52A1",
|
|
25548
26145
|
"table.id": "ID",
|
|
25549
26146
|
"table.task": "\u4EFB\u52A1",
|
|
@@ -25559,7 +26156,7 @@ var init_ui = __esm({
|
|
|
25559
26156
|
"table.nextRun": "\u4E0B\u6B21\u6267\u884C",
|
|
25560
26157
|
"table.run": "Run",
|
|
25561
26158
|
"table.heartbeat": "\u5FC3\u8DF3",
|
|
25562
|
-
"table.session": "
|
|
26159
|
+
"table.session": "\u4F1A\u8BDD",
|
|
25563
26160
|
"table.model": "\u6A21\u578B",
|
|
25564
26161
|
"table.startedAt": "\u542F\u52A8\u65F6\u95F4",
|
|
25565
26162
|
"table.pid": "PID",
|
|
@@ -25567,14 +26164,14 @@ var init_ui = __esm({
|
|
|
25567
26164
|
"pagination.next": "\u4E0B\u4E00\u9875",
|
|
25568
26165
|
"pagination.summary": "\u7B2C {page} \u9875\uFF0C\u5171 {pages} \u9875 \xB7 {total} \u6761",
|
|
25569
26166
|
"empty.tasks": "\u961F\u5217\u91CC\u8FD8\u6CA1\u6709\u4EFB\u52A1",
|
|
25570
|
-
"empty.tasksHint": "\u901A\u8FC7 OpenCode \u63D2\u4EF6\
|
|
25571
|
-
"empty.templates": "\u8FD8\u6CA1\u6709\
|
|
25572
|
-
"empty.templatesHint": "\
|
|
26167
|
+
"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",
|
|
26168
|
+
"empty.templates": "\u8FD8\u6CA1\u6709\u5B9A\u65F6\u4EFB\u52A1",
|
|
26169
|
+
"empty.templatesHint": "\u70B9\u51FB\u53F3\u4E0A\u89D2\u201C\u65B0\u5EFA\u5B9A\u65F6\u4EFB\u52A1\u201D\u5F00\u59CB\u521B\u5EFA\u3002",
|
|
25573
26170
|
"empty.runs": "\u8FD8\u6CA1\u6709\u6267\u884C\u8BB0\u5F55",
|
|
25574
26171
|
"empty.running": "\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1",
|
|
25575
26172
|
"schedule.cron": "Cron",
|
|
25576
|
-
"schedule.recurring": "\
|
|
25577
|
-
"schedule.delayed": "\
|
|
26173
|
+
"schedule.recurring": "\u56FA\u5B9A\u95F4\u9694",
|
|
26174
|
+
"schedule.delayed": "\u4E00\u6B21\u6027",
|
|
25578
26175
|
"schedule.unknown": "\u672A\u77E5",
|
|
25579
26176
|
"schedule.enabled": "\u5DF2\u542F\u7528",
|
|
25580
26177
|
"schedule.disabled": "\u5DF2\u7981\u7528",
|
|
@@ -25583,14 +26180,14 @@ var init_ui = __esm({
|
|
|
25583
26180
|
"schedule.hours": "{count} \u5C0F\u65F6",
|
|
25584
26181
|
"schedule.days": "{count} \u5929",
|
|
25585
26182
|
"schedule.overdue": "\u5DF2\u5230\u671F",
|
|
25586
|
-
"system.worker": "
|
|
25587
|
-
"system.scheduler": "
|
|
25588
|
-
"system.watchdog": "
|
|
26183
|
+
"system.worker": "\u4EFB\u52A1\u6267\u884C",
|
|
26184
|
+
"system.scheduler": "\u5B9A\u65F6\u4EFB\u52A1\u670D\u52A1",
|
|
26185
|
+
"system.watchdog": "\u8FD0\u884C\u76D1\u63A7",
|
|
25589
26186
|
"system.maxConcurrency": "\u6700\u5927\u5E76\u53D1",
|
|
25590
26187
|
"system.pollInterval": "\u8F6E\u8BE2\u95F4\u9694",
|
|
25591
26188
|
"system.heartbeatInterval": "\u5FC3\u8DF3\u95F4\u9694",
|
|
25592
26189
|
"system.taskTimeout": "\u4EFB\u52A1\u8D85\u65F6",
|
|
25593
|
-
"system.schedulerEnabled": "\u542F\u7528\
|
|
26190
|
+
"system.schedulerEnabled": "\u542F\u7528\u5B9A\u65F6\u4EFB\u52A1",
|
|
25594
26191
|
"system.checkInterval": "\u68C0\u67E5\u95F4\u9694",
|
|
25595
26192
|
"system.heartbeatTimeout": "\u5FC3\u8DF3\u8D85\u65F6",
|
|
25596
26193
|
"system.cleanupInterval": "\u6E05\u7406\u95F4\u9694",
|
|
@@ -25600,8 +26197,12 @@ var init_ui = __esm({
|
|
|
25600
26197
|
"system.minutes": "\u5206\u949F",
|
|
25601
26198
|
"system.hours": "\u5C0F\u65F6",
|
|
25602
26199
|
"system.days": "\u5929",
|
|
25603
|
-
"system.activeTemplates": "\
|
|
25604
|
-
"system.saveHint": "\u4FDD\u5B58\u540E\u9700\u91CD\u542F Gateway \u751F\u6548",
|
|
26200
|
+
"system.activeTemplates": "\u5DF2\u542F\u7528\u5B9A\u65F6\u4EFB\u52A1",
|
|
26201
|
+
"system.saveHint": "\u8BBE\u7F6E\u4FDD\u5B58\u540E\u9700\u8981\u91CD\u542F Gateway \u624D\u80FD\u751F\u6548\u3002",
|
|
26202
|
+
"system.configApplied": "\u9875\u9762\u4E2D\u7684\u8BBE\u7F6E\u6B63\u5728\u751F\u6548\u3002",
|
|
26203
|
+
"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",
|
|
26204
|
+
"system.configForeground": "\u6B64\u9875\u9762\u672A\u8FDE\u63A5\u5230 Gateway \u7684\u8FD0\u884C\u914D\u7F6E\uFF1B\u4FDD\u5B58\u540E\u8BF7\u91CD\u542F Gateway\u3002",
|
|
26205
|
+
"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",
|
|
25605
26206
|
"system.runningTasks": "\u5F53\u524D\u8FD0\u884C\u4EFB\u52A1\uFF08{running} / {limit} \u5E76\u53D1\uFF09",
|
|
25606
26207
|
"system.taskStats": "\u4EFB\u52A1\u6982\u89C8",
|
|
25607
26208
|
"system.configFile": "\u914D\u7F6E\u6587\u4EF6",
|
|
@@ -25610,7 +26211,46 @@ var init_ui = __esm({
|
|
|
25610
26211
|
"system.yes": "\u662F",
|
|
25611
26212
|
"system.noDefault": "\u5426\uFF0C\u5F53\u524D\u4F7F\u7528\u9ED8\u8BA4\u503C",
|
|
25612
26213
|
"system.danger": "\u5371\u9669\u64CD\u4F5C",
|
|
25613
|
-
"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\
|
|
26214
|
+
"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",
|
|
26215
|
+
"template.createTitle": "\u65B0\u5EFA\u5B9A\u65F6\u4EFB\u52A1",
|
|
26216
|
+
"template.editTitle": "\u7F16\u8F91\u5B9A\u65F6\u4EFB\u52A1",
|
|
26217
|
+
"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",
|
|
26218
|
+
"template.name": "\u540D\u79F0",
|
|
26219
|
+
"template.cwd": "\u9879\u76EE\u76EE\u5F55",
|
|
26220
|
+
"template.cwdHint": "OpenCode \u4F1A\u5728\u8FD9\u4E2A\u76EE\u5F55\u4E2D\u6267\u884C\u4EFB\u52A1\u3002",
|
|
26221
|
+
"template.agent": "Agent",
|
|
26222
|
+
"template.model": "\u6A21\u578B",
|
|
26223
|
+
"template.prompt": "\u63D0\u793A\u8BCD",
|
|
26224
|
+
"template.scheduleType": "\u6267\u884C\u65B9\u5F0F",
|
|
26225
|
+
"template.cronExpr": "Cron \u8868\u8FBE\u5F0F",
|
|
26226
|
+
"template.cronHint": "\u4F8B\u5982\uFF1A0 9 * * *\uFF08\u6BCF\u5929 09:00\uFF09",
|
|
26227
|
+
"template.interval": "\u6267\u884C\u95F4\u9694",
|
|
26228
|
+
"template.runAt": "\u6267\u884C\u65F6\u95F4",
|
|
26229
|
+
"template.durationHint": "\u652F\u6301 30s\u30015min\u30011h\u30012d",
|
|
26230
|
+
"template.advanced": "\u66F4\u591A\u6267\u884C\u8BBE\u7F6E",
|
|
26231
|
+
"template.category": "\u5206\u7C7B",
|
|
26232
|
+
"template.batchId": "\u6279\u6B21 ID",
|
|
26233
|
+
"template.importance": "\u91CD\u8981\u7A0B\u5EA6\uFF081-5\uFF09",
|
|
26234
|
+
"template.urgency": "\u7D27\u6025\u7A0B\u5EA6\uFF081-5\uFF09",
|
|
26235
|
+
"template.maxInstances": "\u81EA\u52A8\u8C03\u5EA6\u4E0A\u9650",
|
|
26236
|
+
"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",
|
|
26237
|
+
"template.maxRetries": "\u5931\u8D25\u91CD\u8BD5\u6B21\u6570",
|
|
26238
|
+
"template.retryBackoff": "\u91CD\u8BD5\u7B49\u5F85",
|
|
26239
|
+
"template.timeout": "\u5355\u6B21\u8D85\u65F6",
|
|
26240
|
+
"template.optional": "\u53EF\u9009",
|
|
26241
|
+
"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",
|
|
26242
|
+
"projects.title": "\u9879\u76EE\u5206\u7EC4",
|
|
26243
|
+
"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",
|
|
26244
|
+
"projects.all": "\u5168\u90E8\u9879\u76EE",
|
|
26245
|
+
"projects.legacy": "\u672A\u5206\u7EC4",
|
|
26246
|
+
"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",
|
|
26247
|
+
"projects.counts": "\u8FD0\u884C {running} \xB7 \u6392\u961F {pending} \xB7 \u5F02\u5E38 {failed}",
|
|
26248
|
+
"task.createTitle": "\u65B0\u5EFA\u4EFB\u52A1",
|
|
26249
|
+
"task.editTitle": "\u7F16\u8F91\u4EFB\u52A1",
|
|
26250
|
+
"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",
|
|
26251
|
+
"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",
|
|
26252
|
+
"task.projectExisting": "\u6B64\u9879\u76EE\u73B0\u6709 {total} \u4E2A\u4EFB\u52A1\uFF1A\u8FD0\u884C {running}\uFF0C\u6392\u961F {pending}\uFF0C\u5F02\u5E38 {failed}\u3002",
|
|
26253
|
+
"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",
|
|
25614
26254
|
"theme.label": "\u4E3B\u9898",
|
|
25615
26255
|
"theme.system": "\u8DDF\u968F\u7CFB\u7EDF",
|
|
25616
26256
|
"theme.light": "\u6D45\u8272",
|
|
@@ -25625,21 +26265,31 @@ var init_ui = __esm({
|
|
|
25625
26265
|
"dialog.retryTaskBody": "\u4EFB\u52A1\u5C06\u56DE\u5230\u5F85\u6267\u884C\u72B6\u6001\uFF0C\u5E76\u91CD\u7F6E\u81EA\u52A8\u91CD\u8BD5\u9884\u7B97\u3002",
|
|
25626
26266
|
"dialog.deleteTask": "\u5220\u9664\u4EFB\u52A1 #{id}\uFF1F",
|
|
25627
26267
|
"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",
|
|
25628
|
-
"dialog.disableTemplate": "\u7981\u7528\u8FD9\u4E2A\
|
|
25629
|
-
"dialog.disableTemplateBody": "\
|
|
25630
|
-
"dialog.deleteTemplate": "\u5220\u9664\u8FD9\u4E2A\
|
|
25631
|
-
"dialog.deleteTemplateBody": "\
|
|
25632
|
-
"dialog.triggerTemplate": "\u7ACB\u5373\
|
|
25633
|
-
"dialog.triggerTemplateBody": "\u7CFB\u7EDF\u4F1A\u6309\u5F53\u524D\
|
|
26268
|
+
"dialog.disableTemplate": "\u7981\u7528\u8FD9\u4E2A\u5B9A\u65F6\u4EFB\u52A1\uFF1F",
|
|
26269
|
+
"dialog.disableTemplateBody": "\u5B83\u5C06\u505C\u6B62\u81EA\u52A8\u521B\u5EFA\u65B0\u4EFB\u52A1\uFF0C\u5DF2\u6709\u4EFB\u52A1\u4E0D\u53D7\u5F71\u54CD\u3002",
|
|
26270
|
+
"dialog.deleteTemplate": "\u5220\u9664\u8FD9\u4E2A\u5B9A\u65F6\u4EFB\u52A1\uFF1F",
|
|
26271
|
+
"dialog.deleteTemplateBody": "\u5B9A\u65F6\u4EFB\u52A1\u914D\u7F6E\u5C06\u6C38\u4E45\u5220\u9664\uFF0C\u6B64\u64CD\u4F5C\u65E0\u6CD5\u64A4\u9500\u3002",
|
|
26272
|
+
"dialog.triggerTemplate": "\u7ACB\u5373\u8FD0\u884C\u4E00\u6B21\uFF1F",
|
|
26273
|
+
"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",
|
|
25634
26274
|
"dialog.clearTitle": "\u786E\u8BA4\u6E05\u7A7A\u6570\u636E\u5E93",
|
|
25635
|
-
"dialog.clearBody": "\u8FD9\u4F1A\u5220\u9664\u5168\u90E8\u4EFB\u52A1\u3001\u6267\u884C\u8BB0\u5F55\u548C\
|
|
26275
|
+
"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",
|
|
25636
26276
|
"dialog.clearInstruction": "\u8F93\u5165 CLEAR \u4EE5\u786E\u8BA4",
|
|
26277
|
+
"dialog.restartGateway": "\u91CD\u542F\u5E76\u5E94\u7528\u8BBE\u7F6E\uFF1F",
|
|
26278
|
+
"dialog.restartGatewayBody": "Gateway \u4F1A\u77ED\u6682\u79BB\u7EBF\uFF0C\u901A\u5E38\u6570\u79D2\u540E\u7531 PM2 \u81EA\u52A8\u6062\u590D\u3002",
|
|
26279
|
+
"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",
|
|
25637
26280
|
"feedback.retryFailed": "\u91CD\u8BD5\u5931\u8D25",
|
|
25638
26281
|
"feedback.cancelFailed": "\u53D6\u6D88\u5931\u8D25",
|
|
25639
26282
|
"feedback.deleteFailed": "\u5220\u9664\u5931\u8D25",
|
|
25640
26283
|
"feedback.requestFailed": "\u8BF7\u6C42\u5931\u8D25",
|
|
25641
26284
|
"feedback.triggered": "\u5DF2\u521B\u5EFA\u4EFB\u52A1 #{id}",
|
|
25642
|
-
"feedback.
|
|
26285
|
+
"feedback.taskCreated": "\u4EFB\u52A1 #{id} \u5DF2\u52A0\u5165\u961F\u5217",
|
|
26286
|
+
"feedback.taskUpdated": "\u4EFB\u52A1 #{id} \u5DF2\u66F4\u65B0",
|
|
26287
|
+
"feedback.templateCreated": "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u521B\u5EFA",
|
|
26288
|
+
"feedback.templateUpdated": "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u66F4\u65B0",
|
|
26289
|
+
"feedback.configSaved": "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58",
|
|
26290
|
+
"feedback.sessionCommandCopied": "\u4F1A\u8BDD\u547D\u4EE4\u5DF2\u590D\u5236\uFF0C\u53EF\u76F4\u63A5\u7C98\u8D34\u5230\u7EC8\u7AEF\u8FD0\u884C",
|
|
26291
|
+
"feedback.restarting": "Gateway \u6B63\u5728\u91CD\u542F\uFF0C\u8BF7\u7A0D\u5019\u2026",
|
|
26292
|
+
"feedback.restartTimeout": "Gateway \u5C1A\u672A\u6062\u590D\uFF0C\u8BF7\u7A0D\u540E\u5237\u65B0\u6216\u68C0\u67E5 PM2 \u72B6\u6001",
|
|
25643
26293
|
"feedback.databaseCleared": "\u6570\u636E\u5E93\u5DF2\u6E05\u7A7A\uFF0C\u5907\u4EFD\u4F4D\u4E8E\uFF1A{path}",
|
|
25644
26294
|
"feedback.copyFailed": "\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u9009\u62E9\u5185\u5BB9",
|
|
25645
26295
|
"a11y.skip": "\u8DF3\u5230\u4E3B\u8981\u5185\u5BB9",
|
|
@@ -25652,13 +26302,13 @@ var init_ui = __esm({
|
|
|
25652
26302
|
"app.local": "Running locally",
|
|
25653
26303
|
"app.footer": "Local-first \xB7 Your data stays on this device",
|
|
25654
26304
|
"nav.tasks": "Tasks",
|
|
25655
|
-
"nav.templates": "
|
|
26305
|
+
"nav.templates": "Scheduled tasks",
|
|
25656
26306
|
"nav.runs": "Runs",
|
|
25657
26307
|
"nav.system": "System",
|
|
25658
26308
|
"page.tasks.title": "Task queue",
|
|
25659
26309
|
"page.tasks.description": "Track priority, execution state, and retries, then act on tasks that need attention.",
|
|
25660
|
-
"page.templates.title": "
|
|
25661
|
-
"page.templates.description": "
|
|
26310
|
+
"page.templates.title": "Scheduled tasks",
|
|
26311
|
+
"page.templates.description": "Create and edit cron, fixed-interval, and one-time tasks, including their model, agent, and prompt.",
|
|
25662
26312
|
"page.runs.title": "Execution history",
|
|
25663
26313
|
"page.runs.description": "Inspect the status, duration, heartbeat, and output of every agent run.",
|
|
25664
26314
|
"page.system.title": "System settings",
|
|
@@ -25668,29 +26318,43 @@ var init_ui = __esm({
|
|
|
25668
26318
|
"action.retry": "Retry",
|
|
25669
26319
|
"action.cancel": "Cancel",
|
|
25670
26320
|
"action.delete": "Delete",
|
|
26321
|
+
"action.edit": "Edit",
|
|
25671
26322
|
"action.enable": "Enable",
|
|
25672
26323
|
"action.disable": "Disable",
|
|
25673
26324
|
"action.trigger": "Run now",
|
|
26325
|
+
"action.continueSession": "Continue session",
|
|
25674
26326
|
"action.logs": "View log",
|
|
25675
26327
|
"action.hideLogs": "Hide log",
|
|
25676
26328
|
"action.save": "Save settings",
|
|
26329
|
+
"action.saveAndRestart": "Save and restart",
|
|
25677
26330
|
"action.copy": "Copy JSON",
|
|
25678
26331
|
"action.close": "Close",
|
|
25679
26332
|
"action.confirm": "Confirm",
|
|
25680
26333
|
"action.clearDatabase": "Clear database",
|
|
26334
|
+
"action.createTask": "New task",
|
|
26335
|
+
"action.saveTask": "Add to queue",
|
|
26336
|
+
"action.updateTask": "Save changes",
|
|
26337
|
+
"action.createTemplate": "New scheduled task",
|
|
26338
|
+
"action.saveTemplate": "Save scheduled task",
|
|
25681
26339
|
"status.pending": "Pending",
|
|
25682
26340
|
"status.running": "Running",
|
|
25683
26341
|
"status.done": "Done",
|
|
25684
|
-
"status.failed": "
|
|
25685
|
-
"status.dead_letter": "
|
|
26342
|
+
"status.failed": "Waiting to retry",
|
|
26343
|
+
"status.dead_letter": "Stopped",
|
|
26344
|
+
"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.",
|
|
26345
|
+
"status.deadLetterAction": "Inspect the error, then retry manually",
|
|
25686
26346
|
"status.cancelled": "Cancelled",
|
|
26347
|
+
"status.executionStillActive": "The execution process is still stopping and temporarily occupies a slot",
|
|
25687
26348
|
"status.unknown": "Unknown",
|
|
26349
|
+
"runStatus.running": "Running",
|
|
26350
|
+
"runStatus.done": "Succeeded",
|
|
26351
|
+
"runStatus.failed": "Failed",
|
|
25688
26352
|
"stats.total": "Total tasks",
|
|
25689
26353
|
"stats.pending": "Pending",
|
|
25690
26354
|
"stats.running": "Running",
|
|
25691
26355
|
"stats.done": "Completed",
|
|
25692
|
-
"stats.failedDead": "
|
|
25693
|
-
"stats.templates": "
|
|
26356
|
+
"stats.failedDead": "Waiting to retry & stopped",
|
|
26357
|
+
"stats.templates": "Scheduled tasks",
|
|
25694
26358
|
"stats.enabled": "Enabled",
|
|
25695
26359
|
"stats.disabled": "Disabled",
|
|
25696
26360
|
"stats.records": "Total runs",
|
|
@@ -25698,7 +26362,7 @@ var init_ui = __esm({
|
|
|
25698
26362
|
"stats.pageFailed": "Failed here",
|
|
25699
26363
|
"stats.pageRunning": "Running here",
|
|
25700
26364
|
"filter.all": "All",
|
|
25701
|
-
"filter.searchTasks": "Search tasks, agents, or prompts on this page",
|
|
26365
|
+
"filter.searchTasks": "Search tasks, projects, batches, agents, or prompts on this page",
|
|
25702
26366
|
"filter.noResults": "No tasks match this search",
|
|
25703
26367
|
"table.id": "ID",
|
|
25704
26368
|
"table.task": "Task",
|
|
@@ -25722,14 +26386,14 @@ var init_ui = __esm({
|
|
|
25722
26386
|
"pagination.next": "Next",
|
|
25723
26387
|
"pagination.summary": "Page {page} of {pages} \xB7 {total} items",
|
|
25724
26388
|
"empty.tasks": "Your queue is empty",
|
|
25725
|
-
"empty.tasksHint": "
|
|
25726
|
-
"empty.templates": "No
|
|
25727
|
-
"empty.templatesHint": "
|
|
26389
|
+
"empty.tasksHint": "Select \u201CNew task\u201D, or use the OpenCode plugin or supertask add.",
|
|
26390
|
+
"empty.templates": "No scheduled tasks yet",
|
|
26391
|
+
"empty.templatesHint": "Select \u201CNew scheduled task\u201D to create one.",
|
|
25728
26392
|
"empty.runs": "No execution history yet",
|
|
25729
26393
|
"empty.running": "No tasks are running right now",
|
|
25730
26394
|
"schedule.cron": "Cron",
|
|
25731
|
-
"schedule.recurring": "
|
|
25732
|
-
"schedule.delayed": "
|
|
26395
|
+
"schedule.recurring": "Fixed interval",
|
|
26396
|
+
"schedule.delayed": "One-time",
|
|
25733
26397
|
"schedule.unknown": "Unknown",
|
|
25734
26398
|
"schedule.enabled": "Enabled",
|
|
25735
26399
|
"schedule.disabled": "Disabled",
|
|
@@ -25738,14 +26402,14 @@ var init_ui = __esm({
|
|
|
25738
26402
|
"schedule.hours": "{count} hr",
|
|
25739
26403
|
"schedule.days": "{count} days",
|
|
25740
26404
|
"schedule.overdue": "Overdue",
|
|
25741
|
-
"system.worker": "
|
|
25742
|
-
"system.scheduler": "
|
|
25743
|
-
"system.watchdog": "
|
|
26405
|
+
"system.worker": "Task execution",
|
|
26406
|
+
"system.scheduler": "Scheduled task service",
|
|
26407
|
+
"system.watchdog": "Runtime monitor",
|
|
25744
26408
|
"system.maxConcurrency": "Max concurrency",
|
|
25745
26409
|
"system.pollInterval": "Poll interval",
|
|
25746
26410
|
"system.heartbeatInterval": "Heartbeat interval",
|
|
25747
26411
|
"system.taskTimeout": "Task timeout",
|
|
25748
|
-
"system.schedulerEnabled": "Enable
|
|
26412
|
+
"system.schedulerEnabled": "Enable scheduled tasks",
|
|
25749
26413
|
"system.checkInterval": "Check interval",
|
|
25750
26414
|
"system.heartbeatTimeout": "Heartbeat timeout",
|
|
25751
26415
|
"system.cleanupInterval": "Cleanup interval",
|
|
@@ -25755,8 +26419,12 @@ var init_ui = __esm({
|
|
|
25755
26419
|
"system.minutes": "minutes",
|
|
25756
26420
|
"system.hours": "hours",
|
|
25757
26421
|
"system.days": "days",
|
|
25758
|
-
"system.activeTemplates": "
|
|
25759
|
-
"system.saveHint": "Restart Gateway to apply saved settings",
|
|
26422
|
+
"system.activeTemplates": "Enabled scheduled tasks",
|
|
26423
|
+
"system.saveHint": "Restart Gateway to apply saved settings.",
|
|
26424
|
+
"system.configApplied": "The settings shown here are active.",
|
|
26425
|
+
"system.configPending": "Settings are saved, but this Gateway is still using the values from its last start.",
|
|
26426
|
+
"system.configForeground": "This page is not connected to Gateway runtime settings. Restart Gateway after saving.",
|
|
26427
|
+
"system.configRestartManually": "Settings are saved but not active. Return to the terminal that launched Gateway, press Ctrl-C, then run supertask gateway again.",
|
|
25760
26428
|
"system.runningTasks": "Active tasks ({running} / {limit} concurrent)",
|
|
25761
26429
|
"system.taskStats": "Task overview",
|
|
25762
26430
|
"system.configFile": "Configuration file",
|
|
@@ -25766,6 +26434,45 @@ var init_ui = __esm({
|
|
|
25766
26434
|
"system.noDefault": "No, using defaults",
|
|
25767
26435
|
"system.danger": "Danger zone",
|
|
25768
26436
|
"system.dangerDescription": "A verified backup is created first, then tasks, runs, and templates are cleared transactionally. Active work blocks this operation.",
|
|
26437
|
+
"template.createTitle": "New scheduled task",
|
|
26438
|
+
"template.editTitle": "Edit scheduled task",
|
|
26439
|
+
"template.formSubtitle": "Choose when to run and which project, model, and prompt OpenCode should use.",
|
|
26440
|
+
"template.name": "Name",
|
|
26441
|
+
"template.cwd": "Project directory",
|
|
26442
|
+
"template.cwdHint": "OpenCode runs the task in this directory.",
|
|
26443
|
+
"template.agent": "Agent",
|
|
26444
|
+
"template.model": "Model",
|
|
26445
|
+
"template.prompt": "Prompt",
|
|
26446
|
+
"template.scheduleType": "Schedule",
|
|
26447
|
+
"template.cronExpr": "Cron expression",
|
|
26448
|
+
"template.cronHint": "Example: 0 9 * * * (daily at 09:00)",
|
|
26449
|
+
"template.interval": "Interval",
|
|
26450
|
+
"template.runAt": "Run at",
|
|
26451
|
+
"template.durationHint": "Supports 30s, 5min, 1h, 2d",
|
|
26452
|
+
"template.advanced": "More execution settings",
|
|
26453
|
+
"template.category": "Category",
|
|
26454
|
+
"template.batchId": "Batch ID",
|
|
26455
|
+
"template.importance": "Importance (1-5)",
|
|
26456
|
+
"template.urgency": "Urgency (1-5)",
|
|
26457
|
+
"template.maxInstances": "Automatic scheduling limit",
|
|
26458
|
+
"template.maxInstancesHint": "Limits automatic scheduling only. \u201CRun now\u201D always queues a task. Active instances include pending, running, and retry-waiting tasks.",
|
|
26459
|
+
"template.maxRetries": "Failure retries",
|
|
26460
|
+
"template.retryBackoff": "Retry delay",
|
|
26461
|
+
"template.timeout": "Run timeout",
|
|
26462
|
+
"template.optional": "Optional",
|
|
26463
|
+
"template.futureOnly": "Saved settings apply to tasks created in the future. Editing does not change queued or running tasks.",
|
|
26464
|
+
"projects.title": "Projects",
|
|
26465
|
+
"projects.description": "The project directory is the isolation key. Check active and queued work before adding more.",
|
|
26466
|
+
"projects.all": "All projects",
|
|
26467
|
+
"projects.legacy": "Ungrouped",
|
|
26468
|
+
"projects.legacyHint": "Legacy tasks without a project directory. View, cancel, or delete them; recreate under the correct project to make changes.",
|
|
26469
|
+
"projects.counts": "Running {running} \xB7 queued {pending} \xB7 issues {failed}",
|
|
26470
|
+
"task.createTitle": "New task",
|
|
26471
|
+
"task.editTitle": "Edit task",
|
|
26472
|
+
"task.formSubtitle": "The task enters the durable queue immediately and waits automatically when concurrency is full.",
|
|
26473
|
+
"task.batchHint": "Tasks with the same non-empty batch ID run serially. Blank removes the batch constraint; global concurrency and dependencies still apply.",
|
|
26474
|
+
"task.projectExisting": "This project has {total} tasks: {running} running, {pending} queued, and {failed} with issues.",
|
|
26475
|
+
"task.projectNew": "This is a new project group. It appears in the project list after creation.",
|
|
25769
26476
|
"theme.label": "Theme",
|
|
25770
26477
|
"theme.system": "System",
|
|
25771
26478
|
"theme.light": "Light",
|
|
@@ -25783,18 +26490,28 @@ var init_ui = __esm({
|
|
|
25783
26490
|
"dialog.disableTemplate": "Disable this schedule?",
|
|
25784
26491
|
"dialog.disableTemplateBody": "It will stop creating new tasks automatically. Existing tasks are unchanged.",
|
|
25785
26492
|
"dialog.deleteTemplate": "Delete this schedule?",
|
|
25786
|
-
"dialog.deleteTemplateBody": "This
|
|
26493
|
+
"dialog.deleteTemplateBody": "This scheduled task configuration will be permanently deleted.",
|
|
25787
26494
|
"dialog.triggerTemplate": "Run this schedule now?",
|
|
25788
|
-
"dialog.triggerTemplateBody": "A
|
|
26495
|
+
"dialog.triggerTemplateBody": "A task is queued immediately using the current settings. If global concurrency is full, it waits to run.",
|
|
25789
26496
|
"dialog.clearTitle": "Confirm database clear",
|
|
25790
|
-
"dialog.clearBody": "This deletes every task, run, and
|
|
26497
|
+
"dialog.clearBody": "This deletes every task, run, and scheduled task after creating a backup.",
|
|
25791
26498
|
"dialog.clearInstruction": "Type CLEAR to confirm",
|
|
26499
|
+
"dialog.restartGateway": "Restart and apply settings?",
|
|
26500
|
+
"dialog.restartGatewayBody": "Gateway will be briefly unavailable and should recover through PM2 within a few seconds.",
|
|
26501
|
+
"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.",
|
|
25792
26502
|
"feedback.retryFailed": "Retry failed",
|
|
25793
26503
|
"feedback.cancelFailed": "Cancellation failed",
|
|
25794
26504
|
"feedback.deleteFailed": "Delete failed",
|
|
25795
26505
|
"feedback.requestFailed": "Request failed",
|
|
25796
26506
|
"feedback.triggered": "Task #{id} created",
|
|
25797
|
-
"feedback.
|
|
26507
|
+
"feedback.taskCreated": "Task #{id} added to the queue",
|
|
26508
|
+
"feedback.taskUpdated": "Task #{id} updated",
|
|
26509
|
+
"feedback.templateCreated": "Scheduled task created",
|
|
26510
|
+
"feedback.templateUpdated": "Scheduled task updated",
|
|
26511
|
+
"feedback.configSaved": "Settings saved",
|
|
26512
|
+
"feedback.sessionCommandCopied": "Session command copied. Paste it into a terminal to continue.",
|
|
26513
|
+
"feedback.restarting": "Gateway is restarting\u2026",
|
|
26514
|
+
"feedback.restartTimeout": "Gateway has not recovered yet. Refresh later or check PM2 status.",
|
|
25798
26515
|
"feedback.databaseCleared": "Database cleared. Backup: {path}",
|
|
25799
26516
|
"feedback.copyFailed": "Copy failed. Select the content manually.",
|
|
25800
26517
|
"a11y.skip": "Skip to main content",
|
|
@@ -25829,8 +26546,8 @@ var init_ui = __esm({
|
|
|
25829
26546
|
radial-gradient(circle at 10% -10%,var(--bg-glow),transparent 34rem),var(--bg);
|
|
25830
26547
|
font-family:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
|
25831
26548
|
font-size:14px; line-height:1.5; -webkit-font-smoothing:antialiased; }
|
|
25832
|
-
button,input,select { font:inherit; }
|
|
25833
|
-
button,a,select,input { -webkit-tap-highlight-color:transparent; }
|
|
26549
|
+
button,input,select,textarea { font:inherit; }
|
|
26550
|
+
button,a,select,input,textarea { -webkit-tap-highlight-color:transparent; }
|
|
25834
26551
|
a { color:inherit; }
|
|
25835
26552
|
code,.mono,.m { font-family:"SFMono-Regular",Consolas,"Liberation Mono",monospace; }
|
|
25836
26553
|
.skip-link { position:fixed; left:16px; top:-60px; z-index:200; padding:10px 14px; border-radius:8px;
|
|
@@ -25911,6 +26628,16 @@ var init_ui = __esm({
|
|
|
25911
26628
|
.panel-head { min-height:52px; display:flex; align-items:center; justify-content:space-between; gap:12px; padding:0 18px;
|
|
25912
26629
|
border-bottom:1px solid var(--border); }
|
|
25913
26630
|
.panel-head h2,.panel-head h3 { margin:0; font-size:14px; letter-spacing:-.01em; }
|
|
26631
|
+
.panel-head p { margin:3px 0 0; color:var(--text-2); font-size:11px; }
|
|
26632
|
+
.project-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:10px; padding:14px; }
|
|
26633
|
+
.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; }
|
|
26634
|
+
.project-card:hover { border-color:var(--border-strong); background:var(--surface-3); }
|
|
26635
|
+
.project-card.active { border-color:color-mix(in srgb,var(--primary) 45%,var(--border)); background:var(--primary-soft); }
|
|
26636
|
+
.project-card-head { display:flex; align-items:center; justify-content:space-between; gap:10px; }
|
|
26637
|
+
.project-card-head strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
26638
|
+
.project-card-head span { color:var(--text-3); font-size:11px; }
|
|
26639
|
+
.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; }
|
|
26640
|
+
.project-counts { margin-top:8px; color:var(--text-2); font-size:11px; }
|
|
25914
26641
|
.table-wrap { width:100%; overflow-x:auto; }
|
|
25915
26642
|
table { width:100%; border-collapse:separate; border-spacing:0; font-size:13px; }
|
|
25916
26643
|
th { height:42px; padding:0 13px; color:var(--text-3); background:var(--surface-2); border-bottom:1px solid var(--border);
|
|
@@ -25921,6 +26648,7 @@ var init_ui = __esm({
|
|
|
25921
26648
|
tbody tr:hover { background:color-mix(in srgb,var(--primary-soft) 30%,transparent); }
|
|
25922
26649
|
.task-name { font-weight:680; color:var(--text); }
|
|
25923
26650
|
.task-prompt { max-width:520px; margin-top:3px; color:var(--text-2); font-size:12px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
26651
|
+
.task-context { margin-top:6px; }
|
|
25924
26652
|
.muted,.mu { color:var(--text-2); }
|
|
25925
26653
|
.faint { color:var(--text-3); }
|
|
25926
26654
|
.small,.sm { font-size:12px; }
|
|
@@ -26004,6 +26732,18 @@ var init_ui = __esm({
|
|
|
26004
26732
|
.dialog-head p { margin:3px 0 0; color:var(--text-2); font-size:11px; }
|
|
26005
26733
|
.dialog-body { max-height:70vh; overflow:auto; padding:18px; }
|
|
26006
26734
|
.dialog-actions { display:flex; align-items:center; justify-content:flex-end; gap:8px; padding:14px 18px; border-top:1px solid var(--border); }
|
|
26735
|
+
.template-dialog { width:min(880px,calc(100% - 32px)); }
|
|
26736
|
+
.template-form-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:15px; }
|
|
26737
|
+
.form-field { display:flex; min-width:0; flex-direction:column; gap:6px; color:var(--text-2); font-size:12px; font-weight:650; }
|
|
26738
|
+
.form-field-wide { grid-column:1 / -1; }
|
|
26739
|
+
.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;
|
|
26740
|
+
outline:none; color:var(--text); background:var(--surface-2); font-weight:450; }
|
|
26741
|
+
.form-field textarea { resize:vertical; line-height:1.5; }
|
|
26742
|
+
.form-field input:focus,.form-field select:focus,.form-field textarea:focus { border-color:var(--primary); box-shadow:var(--focus); background:var(--surface); }
|
|
26743
|
+
.form-field small { color:var(--text-3); font-size:10px; font-weight:450; }
|
|
26744
|
+
.advanced-fields { margin-top:18px; padding-top:14px; border-top:1px solid var(--border); }
|
|
26745
|
+
.advanced-fields summary { margin-bottom:14px; color:var(--text-2); cursor:pointer; font-size:12px; font-weight:700; }
|
|
26746
|
+
.form-note { margin:16px 0 0; color:var(--text-3); font-size:11px; }
|
|
26007
26747
|
.json-view { min-height:160px; margin:0; padding:15px; overflow:auto; border:1px solid var(--border); border-radius:10px; color:var(--text-2);
|
|
26008
26748
|
background:var(--surface-2); font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
|
|
26009
26749
|
.confirm-copy { color:var(--text-2); margin:0; }
|
|
@@ -26069,6 +26809,9 @@ var init_ui = __esm({
|
|
|
26069
26809
|
.responsive-table td[data-primary]::before { display:none; }
|
|
26070
26810
|
.responsive-table .task-prompt { max-width:100%; }
|
|
26071
26811
|
.responsive-table .actions { justify-content:flex-start; }
|
|
26812
|
+
.project-grid { grid-template-columns:1fr; }
|
|
26813
|
+
.template-form-grid { grid-template-columns:1fr; }
|
|
26814
|
+
.form-field-wide { grid-column:auto; }
|
|
26072
26815
|
}
|
|
26073
26816
|
@media (max-width:520px) {
|
|
26074
26817
|
.stats-grid,.stats-grid.three { grid-template-columns:1fr 1fr; }
|
|
@@ -26096,10 +26839,45 @@ var init_ui = __esm({
|
|
|
26096
26839
|
var web_exports = {};
|
|
26097
26840
|
__export(web_exports, {
|
|
26098
26841
|
dashboardApp: () => dashboardApp,
|
|
26099
|
-
default: () => web_default
|
|
26842
|
+
default: () => web_default,
|
|
26843
|
+
isSafeDashboardRestartTarget: () => isSafeDashboardRestartTarget,
|
|
26844
|
+
resolveDashboardConfigState: () => resolveDashboardConfigState,
|
|
26845
|
+
setDashboardRuntimeConfig: () => setDashboardRuntimeConfig
|
|
26100
26846
|
});
|
|
26101
|
-
import { existsSync as
|
|
26102
|
-
import { dirname as dirname8 } from "path";
|
|
26847
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
26848
|
+
import { basename as basename3, dirname as dirname8 } from "path";
|
|
26849
|
+
function setDashboardRuntimeConfig(config) {
|
|
26850
|
+
runtimeConfig = config === null ? null : structuredClone(config);
|
|
26851
|
+
}
|
|
26852
|
+
function isValidSessionId(value) {
|
|
26853
|
+
return typeof value === "string" && SESSION_ID_PATTERN.test(value);
|
|
26854
|
+
}
|
|
26855
|
+
function maskSessionId(value) {
|
|
26856
|
+
return isValidSessionId(value) ? `${value.slice(4, 7)}***${value.slice(-3)}` : "\u2014";
|
|
26857
|
+
}
|
|
26858
|
+
function sessionCommand(value) {
|
|
26859
|
+
if (!isValidSessionId(value)) throw new Error("session unavailable");
|
|
26860
|
+
return `opencode --session ${value}`;
|
|
26861
|
+
}
|
|
26862
|
+
function configsEqual(left, right) {
|
|
26863
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
26864
|
+
}
|
|
26865
|
+
function resolveDashboardConfigState(runtimeAvailable, restartRequired, managedRestart) {
|
|
26866
|
+
if (!runtimeAvailable) return "foreground";
|
|
26867
|
+
if (!restartRequired) return "applied";
|
|
26868
|
+
return managedRestart ? "pending" : "manual";
|
|
26869
|
+
}
|
|
26870
|
+
function isSafeDashboardRestartTarget(diagnostic, currentPid) {
|
|
26871
|
+
return diagnostic.pm2Installed && diagnostic.processFound && diagnostic.status === "online" && diagnostic.pid === currentPid && diagnostic.ready && diagnostic.scopeMatches;
|
|
26872
|
+
}
|
|
26873
|
+
function canRestartCurrentGateway() {
|
|
26874
|
+
if (runtimeConfig === null) return false;
|
|
26875
|
+
try {
|
|
26876
|
+
return isSafeDashboardRestartTarget(getGatewayDiagnostic(), process.pid);
|
|
26877
|
+
} catch {
|
|
26878
|
+
return false;
|
|
26879
|
+
}
|
|
26880
|
+
}
|
|
26103
26881
|
function parsePositiveInteger2(value) {
|
|
26104
26882
|
if (!/^\d+$/.test(value)) return null;
|
|
26105
26883
|
const parsed = Number(value);
|
|
@@ -26108,6 +26886,134 @@ function parsePositiveInteger2(value) {
|
|
|
26108
26886
|
function parseTaskStatus2(value) {
|
|
26109
26887
|
return TASK_STATUSES2.has(value) ? value : null;
|
|
26110
26888
|
}
|
|
26889
|
+
function parseTaskPayload(value) {
|
|
26890
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
26891
|
+
throw new Error("\u8BF7\u6C42\u5185\u5BB9\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
26892
|
+
}
|
|
26893
|
+
const input = value;
|
|
26894
|
+
const requiredString = (name) => {
|
|
26895
|
+
const field = input[name];
|
|
26896
|
+
if (typeof field !== "string" || !field.trim()) throw new Error(`${name} \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
26897
|
+
return field.trim();
|
|
26898
|
+
};
|
|
26899
|
+
const optionalString = (name, fallback = null) => {
|
|
26900
|
+
const field = input[name];
|
|
26901
|
+
if (field === void 0 || field === null || field === "") return fallback;
|
|
26902
|
+
if (typeof field !== "string") throw new Error(`${name} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
|
|
26903
|
+
return field.trim() || fallback;
|
|
26904
|
+
};
|
|
26905
|
+
const integer2 = (name, fallback) => {
|
|
26906
|
+
const field = input[name];
|
|
26907
|
+
if (field === void 0 || field === null || field === "") return fallback;
|
|
26908
|
+
if (typeof field !== "number" || !Number.isSafeInteger(field)) {
|
|
26909
|
+
throw new Error(`${name} \u5FC5\u987B\u662F\u6574\u6570`);
|
|
26910
|
+
}
|
|
26911
|
+
return field;
|
|
26912
|
+
};
|
|
26913
|
+
const duration = (name, fallback, allowZero = false) => {
|
|
26914
|
+
const raw2 = optionalString(name, fallback);
|
|
26915
|
+
if (raw2 === null) return null;
|
|
26916
|
+
if (allowZero && raw2 === "0") return 0;
|
|
26917
|
+
if (Number.isFinite(Number(raw2))) {
|
|
26918
|
+
throw new Error(`${name} \u5FC5\u987B\u5E26\u65F6\u95F4\u5355\u4F4D\uFF0C\u8BF7\u4F7F\u7528 30s\u30015min\u30011h \u6216 2d`);
|
|
26919
|
+
}
|
|
26920
|
+
const milliseconds = parseDuration(raw2);
|
|
26921
|
+
if (milliseconds === null || !Number.isSafeInteger(milliseconds)) {
|
|
26922
|
+
throw new Error(`${name} \u683C\u5F0F\u65E0\u6548\uFF0C\u8BF7\u4F7F\u7528 30s\u30015min\u30011h \u6216 2d`);
|
|
26923
|
+
}
|
|
26924
|
+
return milliseconds;
|
|
26925
|
+
};
|
|
26926
|
+
return {
|
|
26927
|
+
name: requiredString("name"),
|
|
26928
|
+
cwd: requiredString("cwd"),
|
|
26929
|
+
agent: requiredString("agent"),
|
|
26930
|
+
model: optionalString("model", "default"),
|
|
26931
|
+
prompt: requiredString("prompt"),
|
|
26932
|
+
category: optionalString("category", "general"),
|
|
26933
|
+
batchId: optionalString("batchId"),
|
|
26934
|
+
importance: integer2("importance", 3),
|
|
26935
|
+
urgency: integer2("urgency", 3),
|
|
26936
|
+
maxRetries: integer2("maxRetries", 3),
|
|
26937
|
+
retryBackoffMs: duration("retryBackoff", "30s", true),
|
|
26938
|
+
timeoutMs: duration("timeout", null)
|
|
26939
|
+
};
|
|
26940
|
+
}
|
|
26941
|
+
function editableTaskPayload(input) {
|
|
26942
|
+
return {
|
|
26943
|
+
name: input.name,
|
|
26944
|
+
agent: input.agent,
|
|
26945
|
+
model: input.model ?? "default",
|
|
26946
|
+
prompt: input.prompt,
|
|
26947
|
+
category: input.category ?? "general",
|
|
26948
|
+
batchId: input.batchId ?? null,
|
|
26949
|
+
importance: input.importance ?? 3,
|
|
26950
|
+
urgency: input.urgency ?? 3,
|
|
26951
|
+
maxRetries: input.maxRetries ?? 3,
|
|
26952
|
+
retryBackoffMs: input.retryBackoffMs ?? 3e4,
|
|
26953
|
+
timeoutMs: input.timeoutMs ?? null
|
|
26954
|
+
};
|
|
26955
|
+
}
|
|
26956
|
+
function parseTemplatePayload(value) {
|
|
26957
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
26958
|
+
throw new Error("\u8BF7\u6C42\u5185\u5BB9\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
26959
|
+
}
|
|
26960
|
+
const input = value;
|
|
26961
|
+
const requiredString = (name) => {
|
|
26962
|
+
const field = input[name];
|
|
26963
|
+
if (typeof field !== "string" || !field.trim()) throw new Error(`${name} \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
26964
|
+
return field.trim();
|
|
26965
|
+
};
|
|
26966
|
+
const optionalString = (name, fallback = null) => {
|
|
26967
|
+
const field = input[name];
|
|
26968
|
+
if (field === void 0 || field === null || field === "") return fallback;
|
|
26969
|
+
if (typeof field !== "string") throw new Error(`${name} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
|
|
26970
|
+
return field.trim() || fallback;
|
|
26971
|
+
};
|
|
26972
|
+
const integer2 = (name, fallback) => {
|
|
26973
|
+
const field = input[name];
|
|
26974
|
+
if (field === void 0 || field === null || field === "") return fallback;
|
|
26975
|
+
if (typeof field !== "number" || !Number.isSafeInteger(field)) {
|
|
26976
|
+
throw new Error(`${name} \u5FC5\u987B\u662F\u6574\u6570`);
|
|
26977
|
+
}
|
|
26978
|
+
return field;
|
|
26979
|
+
};
|
|
26980
|
+
const duration = (name, fallback, allowZero = false) => {
|
|
26981
|
+
const raw2 = optionalString(name, fallback);
|
|
26982
|
+
if (raw2 === null) return null;
|
|
26983
|
+
if (allowZero && raw2 === "0") return 0;
|
|
26984
|
+
if (Number.isFinite(Number(raw2))) {
|
|
26985
|
+
throw new Error(`${name} \u5FC5\u987B\u5E26\u65F6\u95F4\u5355\u4F4D\uFF0C\u8BF7\u4F7F\u7528 30s\u30015min\u30011h \u6216 2d`);
|
|
26986
|
+
}
|
|
26987
|
+
const milliseconds = parseDuration(raw2);
|
|
26988
|
+
if (milliseconds === null || !Number.isSafeInteger(milliseconds)) {
|
|
26989
|
+
throw new Error(`${name} \u683C\u5F0F\u65E0\u6548\uFF0C\u8BF7\u4F7F\u7528 30s\u30015min\u30011h \u6216 2d`);
|
|
26990
|
+
}
|
|
26991
|
+
return milliseconds;
|
|
26992
|
+
};
|
|
26993
|
+
const scheduleType = requiredString("scheduleType");
|
|
26994
|
+
if (!["cron", "delayed", "recurring"].includes(scheduleType)) {
|
|
26995
|
+
throw new Error("scheduleType \u5FC5\u987B\u662F cron\u3001delayed \u6216 recurring");
|
|
26996
|
+
}
|
|
26997
|
+
return {
|
|
26998
|
+
name: requiredString("name"),
|
|
26999
|
+
agent: requiredString("agent"),
|
|
27000
|
+
model: optionalString("model", "default"),
|
|
27001
|
+
prompt: requiredString("prompt"),
|
|
27002
|
+
cwd: requiredString("cwd"),
|
|
27003
|
+
category: optionalString("category", "general"),
|
|
27004
|
+
importance: integer2("importance", 3),
|
|
27005
|
+
urgency: integer2("urgency", 3),
|
|
27006
|
+
batchId: optionalString("batchId"),
|
|
27007
|
+
scheduleType,
|
|
27008
|
+
cronExpr: scheduleType === "cron" ? requiredString("cronExpr") : null,
|
|
27009
|
+
intervalMs: scheduleType === "recurring" ? duration("interval", null) : null,
|
|
27010
|
+
runAt: scheduleType === "delayed" ? integer2("runAt", null) : null,
|
|
27011
|
+
maxInstances: integer2("maxInstances", 1),
|
|
27012
|
+
maxRetries: integer2("maxRetries", 3),
|
|
27013
|
+
retryBackoffMs: duration("retryBackoff", "30s", true),
|
|
27014
|
+
timeoutMs: duration("timeout", null)
|
|
27015
|
+
};
|
|
27016
|
+
}
|
|
26111
27017
|
function safeStatus(value) {
|
|
26112
27018
|
return value && TASK_STATUSES2.has(value) ? value : "unknown";
|
|
26113
27019
|
}
|
|
@@ -26142,9 +27048,9 @@ function esc(value) {
|
|
|
26142
27048
|
}
|
|
26143
27049
|
function readCurrentConfig() {
|
|
26144
27050
|
const configPath = getConfigPath();
|
|
26145
|
-
if (!
|
|
27051
|
+
if (!existsSync8(configPath)) return {};
|
|
26146
27052
|
try {
|
|
26147
|
-
return JSON.parse(
|
|
27053
|
+
return JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
26148
27054
|
} catch {
|
|
26149
27055
|
return {};
|
|
26150
27056
|
}
|
|
@@ -26152,7 +27058,7 @@ function readCurrentConfig() {
|
|
|
26152
27058
|
function writeConfig(cfg) {
|
|
26153
27059
|
const configPath = getConfigPath();
|
|
26154
27060
|
const dir = dirname8(configPath);
|
|
26155
|
-
if (!
|
|
27061
|
+
if (!existsSync8(dir)) mkdirSync5(dir, { recursive: true });
|
|
26156
27062
|
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
26157
27063
|
writeFileSync3(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
26158
27064
|
renameSync2(tempPath, configPath);
|
|
@@ -26187,13 +27093,14 @@ function pagination(locale, basePath, page, pages, total, suffix = "") {
|
|
|
26187
27093
|
const next = page < pages ? `<a class="btn" href="${basePath}?page=${page + 1}${suffix}">${t(locale, "pagination.next")}${icon("chevronRight")}</a>` : "";
|
|
26188
27094
|
return `<div class="pagination">${previous}<span class="summary">${t(locale, "pagination.summary", { page, pages, total })}</span>${next}</div>`;
|
|
26189
27095
|
}
|
|
26190
|
-
var app, TASK_STATUSES2, dashboardApp, web_default;
|
|
27096
|
+
var app, LEGACY_PROJECT_FILTER, TASK_STATUSES2, SESSION_ID_PATTERN, runtimeConfig, restartScheduled, dashboardApp, web_default;
|
|
26191
27097
|
var init_web = __esm({
|
|
26192
27098
|
"src/web/index.tsx"() {
|
|
26193
27099
|
"use strict";
|
|
26194
27100
|
init_dist();
|
|
26195
27101
|
init_drizzle_orm();
|
|
26196
27102
|
init_db2();
|
|
27103
|
+
init_duration();
|
|
26197
27104
|
init_database_maintenance_service();
|
|
26198
27105
|
init_task_run_service();
|
|
26199
27106
|
init_task_service();
|
|
@@ -26201,8 +27108,10 @@ var init_web = __esm({
|
|
|
26201
27108
|
init_config();
|
|
26202
27109
|
init_health();
|
|
26203
27110
|
init_job_templates();
|
|
27111
|
+
init_pm2();
|
|
26204
27112
|
init_ui();
|
|
26205
27113
|
app = new Hono2();
|
|
27114
|
+
LEGACY_PROJECT_FILTER = "__supertask_legacy__";
|
|
26206
27115
|
TASK_STATUSES2 = /* @__PURE__ */ new Set([
|
|
26207
27116
|
"pending",
|
|
26208
27117
|
"running",
|
|
@@ -26211,6 +27120,9 @@ var init_web = __esm({
|
|
|
26211
27120
|
"dead_letter",
|
|
26212
27121
|
"cancelled"
|
|
26213
27122
|
]);
|
|
27123
|
+
SESSION_ID_PATTERN = /^ses_[A-Za-z0-9_]+$/;
|
|
27124
|
+
runtimeConfig = null;
|
|
27125
|
+
restartScheduled = false;
|
|
26214
27126
|
app.use("*", async (c, next) => {
|
|
26215
27127
|
await next();
|
|
26216
27128
|
c.header("X-Content-Type-Options", "nosniff");
|
|
@@ -26247,24 +27159,52 @@ var init_web = __esm({
|
|
|
26247
27159
|
const page = parsePositiveInteger2(c.req.query("page") || "1");
|
|
26248
27160
|
if (page === null) return c.text("invalid page", 400);
|
|
26249
27161
|
const statusFilter = c.req.query("status") || "";
|
|
27162
|
+
const requestedCwd = c.req.query("cwd") || "";
|
|
27163
|
+
const legacyProjectFilter = requestedCwd === LEGACY_PROJECT_FILTER;
|
|
27164
|
+
const cwdFilter = legacyProjectFilter ? "" : requestedCwd;
|
|
27165
|
+
const projectFilter = legacyProjectFilter ? LEGACY_PROJECT_FILTER : cwdFilter;
|
|
26250
27166
|
const parsedStatus = statusFilter ? parseTaskStatus2(statusFilter) : null;
|
|
26251
27167
|
if (statusFilter && !parsedStatus) return c.text("invalid status", 400);
|
|
26252
27168
|
const limit = 50;
|
|
26253
27169
|
const offset = (page - 1) * limit;
|
|
26254
|
-
const [tasks4, statsData] = await Promise.all([
|
|
26255
|
-
TaskService.list({
|
|
26256
|
-
|
|
27170
|
+
const [tasks4, statsData, globalStats, projects, globalRunning, legacyStats, legacyRunning] = await Promise.all([
|
|
27171
|
+
TaskService.list({
|
|
27172
|
+
limit,
|
|
27173
|
+
offset,
|
|
27174
|
+
...parsedStatus === "running" ? { activeExecution: true } : parsedStatus ? { status: parsedStatus } : {},
|
|
27175
|
+
...legacyProjectFilter ? { legacyCwd: true } : cwdFilter ? { cwd: cwdFilter } : {}
|
|
27176
|
+
}),
|
|
27177
|
+
TaskService.stats(legacyProjectFilter ? { legacyCwd: true } : cwdFilter ? { cwd: cwdFilter } : {}),
|
|
27178
|
+
TaskService.stats(),
|
|
27179
|
+
TaskService.projectSummaries(1e3),
|
|
27180
|
+
TaskService.countRunning(),
|
|
27181
|
+
TaskService.stats({ legacyCwd: true }),
|
|
27182
|
+
TaskService.countRunning({ legacyCwd: true })
|
|
26257
27183
|
]);
|
|
26258
27184
|
const latestRuns = await TaskRunService.getLatestByTaskIds(tasks4.map((task) => task.id));
|
|
27185
|
+
const selectedRunning = legacyProjectFilter ? legacyRunning : cwdFilter ? projects.find((project) => project.cwd === cwdFilter)?.running ?? 0 : globalRunning;
|
|
26259
27186
|
const counts = {
|
|
26260
27187
|
pending: statsData.pending || 0,
|
|
26261
|
-
running:
|
|
27188
|
+
running: selectedRunning,
|
|
26262
27189
|
done: statsData.done || 0,
|
|
26263
27190
|
failed: (statsData.failed || 0) + (statsData.dead_letter || 0),
|
|
26264
27191
|
total: statsData.total || 0
|
|
26265
27192
|
};
|
|
26266
|
-
const filteredTotal = parsedStatus ? Number(statsData[parsedStatus] ?? 0) : counts.total;
|
|
27193
|
+
const filteredTotal = parsedStatus === "running" ? selectedRunning : parsedStatus ? Number(statsData[parsedStatus] ?? 0) : counts.total;
|
|
26267
27194
|
const totalPages = Math.max(1, Math.ceil(filteredTotal / limit));
|
|
27195
|
+
if (page > totalPages) {
|
|
27196
|
+
const params = new URLSearchParams({ page: String(totalPages) });
|
|
27197
|
+
if (statusFilter) params.set("status", statusFilter);
|
|
27198
|
+
if (projectFilter) params.set("cwd", projectFilter);
|
|
27199
|
+
return c.redirect(`/?${params.toString()}`);
|
|
27200
|
+
}
|
|
27201
|
+
const taskListUrl = (status, cwd) => {
|
|
27202
|
+
const params = new URLSearchParams();
|
|
27203
|
+
if (status) params.set("status", status);
|
|
27204
|
+
if (cwd) params.set("cwd", cwd);
|
|
27205
|
+
const query = params.toString();
|
|
27206
|
+
return query ? `/?${query}` : "/";
|
|
27207
|
+
};
|
|
26268
27208
|
const filterItems = [
|
|
26269
27209
|
{ status: "", label: t(locale, "filter.all") },
|
|
26270
27210
|
{ status: "pending", label: statusText(locale, "pending") },
|
|
@@ -26275,22 +27215,27 @@ var init_web = __esm({
|
|
|
26275
27215
|
{ status: "cancelled", label: statusText(locale, "cancelled") }
|
|
26276
27216
|
];
|
|
26277
27217
|
const filters = filterItems.map(({ status, label }) => {
|
|
26278
|
-
const href = status
|
|
26279
|
-
return `<a href="${href}" class="filter-chip ${statusFilter === status ? "active" : ""}">${label}</a>`;
|
|
27218
|
+
const href = taskListUrl(status, projectFilter);
|
|
27219
|
+
return `<a href="${esc(href)}" class="filter-chip ${statusFilter === status ? "active" : ""}">${label}</a>`;
|
|
26280
27220
|
}).join("");
|
|
26281
27221
|
const rows = tasks4.map((task) => {
|
|
26282
27222
|
const status = safeStatus(task.status);
|
|
26283
|
-
const
|
|
26284
|
-
const
|
|
27223
|
+
const latestRun = latestRuns.get(task.id);
|
|
27224
|
+
const executionActive = latestRun?.status === "running";
|
|
27225
|
+
const batchId = task.batchId?.trim() || null;
|
|
27226
|
+
const searchable = esc(`${task.name} ${task.agent} ${task.prompt} ${task.cwd ?? ""} ${task.batchId ?? ""} ${task.category ?? ""}`);
|
|
26285
27227
|
return `<tr data-task-row data-search="${searchable}">
|
|
26286
27228
|
<td class="faint" data-label="${t(locale, "table.id")}">#${task.id}</td>
|
|
26287
|
-
<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
|
|
27229
|
+
<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>
|
|
27230
|
+
<div class="actions task-context"><span class="tag" title="${esc(task.cwd ?? "")}">${esc(task.cwd ? basename3(task.cwd) || task.cwd : t(locale, "projects.legacy"))}</span>${batchId ? `<span class="tag" title="${esc(t(locale, "template.batchId"))}">${esc(batchId)}</span>` : ""}</div></td>
|
|
26288
27231
|
<td data-label="${t(locale, "table.agent")}"><span class="tag">${esc(task.agent)}</span></td>
|
|
26289
|
-
<td data-label="${t(locale, "table.status")}"><span class="badge b-${status}">${statusText(locale, status)}</span
|
|
26290
|
-
<td data-label="${t(locale, "table.duration")}" class="small ${task.status === "running" ? "" : "muted"}">${formatDuration(task.startedAt, task.finishedAt)}</td>
|
|
27232
|
+
<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>
|
|
27233
|
+
<td data-label="${t(locale, "table.duration")}" class="small ${executionActive || task.status === "running" ? "" : "muted"}">${formatDuration(task.startedAt, task.finishedAt)}</td>
|
|
26291
27234
|
<td data-label="${t(locale, "table.retries")}" class="muted small">${(task.retryCount ?? 0) > 0 ? task.retryCount : "\u2014"}</td>
|
|
26292
27235
|
<td data-label="${t(locale, "table.actions")}"><div class="actions">
|
|
27236
|
+
${task.cwd?.trim() && ["pending", "failed", "dead_letter"].includes(task.status ?? "") ? `<button type="button" class="btn" onclick="openTaskEditor(${task.id})">${t(locale, "action.edit")}</button>` : ""}
|
|
26293
27237
|
<button type="button" class="btn" onclick="showDetail(${task.id})">${t(locale, "action.details")}</button>
|
|
27238
|
+
${isValidSessionId(latestRun?.sessionId) ? `<button type="button" class="btn" onclick="copySessionCommand(${latestRun.id})">${icon("copy")}${t(locale, "action.continueSession")}</button>` : ""}
|
|
26294
27239
|
${task.status === "failed" || task.status === "dead_letter" ? `<button type="button" class="btn btn-warning" onclick="retryTask(${task.id})">${t(locale, "action.retry")}</button>` : ""}
|
|
26295
27240
|
${["pending", "running", "failed"].includes(task.status ?? "") ? `<button type="button" class="btn btn-warning" onclick="cancelTask(${task.id})">${t(locale, "action.cancel")}</button>` : ""}
|
|
26296
27241
|
${task.status === "running" || executionActive ? "" : `<button type="button" class="btn btn-danger" onclick="deleteTask(${task.id})">${t(locale, "action.delete")}</button>`}
|
|
@@ -26302,7 +27247,32 @@ var init_web = __esm({
|
|
|
26302
27247
|
<tbody>${rows}</tbody>
|
|
26303
27248
|
</table></div>
|
|
26304
27249
|
<div id="search-empty" hidden>${emptyState(t(locale, "filter.noResults"), "")}</div>`;
|
|
26305
|
-
const
|
|
27250
|
+
const paginationParts = [];
|
|
27251
|
+
if (statusFilter) paginationParts.push(`status=${encodeURIComponent(statusFilter)}`);
|
|
27252
|
+
if (projectFilter) paginationParts.push(`cwd=${encodeURIComponent(projectFilter)}`);
|
|
27253
|
+
const suffix = paginationParts.length > 0 ? `&${paginationParts.join("&")}` : "";
|
|
27254
|
+
const projectCards = [
|
|
27255
|
+
`<a class="project-card ${projectFilter ? "" : "active"}" href="${esc(taskListUrl(statusFilter, ""))}">
|
|
27256
|
+
<div class="project-card-head"><strong>${t(locale, "projects.all")}</strong><span>${globalStats.total || 0}</span></div>
|
|
27257
|
+
<div class="project-counts">${t(locale, "projects.counts", { running: globalRunning, pending: globalStats.pending || 0, failed: (globalStats.failed || 0) + (globalStats.dead_letter || 0) })}</div>
|
|
27258
|
+
</a>`,
|
|
27259
|
+
...legacyStats.total > 0 ? [`<a class="project-card ${legacyProjectFilter ? "active" : ""}" href="${esc(taskListUrl(statusFilter, LEGACY_PROJECT_FILTER))}">
|
|
27260
|
+
<div class="project-card-head"><strong>${t(locale, "projects.legacy")}</strong><span>${legacyStats.total}</span></div>
|
|
27261
|
+
<div class="project-path">${t(locale, "projects.legacyHint")}</div>
|
|
27262
|
+
<div class="project-counts">${t(locale, "projects.counts", { running: legacyRunning, pending: legacyStats.pending || 0, failed: (legacyStats.failed || 0) + (legacyStats.dead_letter || 0) })}</div>
|
|
27263
|
+
</a>`] : [],
|
|
27264
|
+
...projects.map((project) => `<a class="project-card ${cwdFilter === project.cwd ? "active" : ""}" href="${esc(taskListUrl(statusFilter, project.cwd))}" title="${esc(project.cwd)}">
|
|
27265
|
+
<div class="project-card-head"><strong>${esc(basename3(project.cwd) || project.cwd)}</strong><span>${project.total}</span></div>
|
|
27266
|
+
<div class="project-path">${esc(project.cwd)}</div>
|
|
27267
|
+
<div class="project-counts">${t(locale, "projects.counts", { running: project.running, pending: project.pending, failed: project.failed })}</div>
|
|
27268
|
+
</a>`)
|
|
27269
|
+
].join("");
|
|
27270
|
+
const projectData = JSON.stringify(Object.fromEntries(projects.map((project) => [project.cwd, {
|
|
27271
|
+
total: project.total,
|
|
27272
|
+
pending: project.pending,
|
|
27273
|
+
running: project.running,
|
|
27274
|
+
failed: project.failed
|
|
27275
|
+
}]))).replace(/</g, "\\u003c");
|
|
26306
27276
|
const body = `
|
|
26307
27277
|
<div class="stats-grid">
|
|
26308
27278
|
${statCard(counts.pending, t(locale, "stats.pending"), "tone-neutral", icon("clock"))}
|
|
@@ -26310,19 +27280,61 @@ var init_web = __esm({
|
|
|
26310
27280
|
${statCard(counts.done, t(locale, "stats.done"), "tone-green", icon("check"), "reveal-delay-1")}
|
|
26311
27281
|
${statCard(counts.failed, t(locale, "stats.failedDead"), "tone-red", icon("alert"), "reveal-delay-2")}
|
|
26312
27282
|
</div>
|
|
27283
|
+
<section class="panel project-panel reveal reveal-delay-1">
|
|
27284
|
+
<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>
|
|
27285
|
+
<div class="project-grid">${projectCards}</div>
|
|
27286
|
+
</section>
|
|
26313
27287
|
<div class="toolbar reveal reveal-delay-1">
|
|
26314
27288
|
<div class="filters">${filters}</div>
|
|
26315
27289
|
<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>
|
|
26316
27290
|
</div>
|
|
26317
27291
|
<section class="panel reveal reveal-delay-2">${table}</section>
|
|
26318
|
-
${pagination(locale, "/", page, totalPages, filteredTotal, suffix)}
|
|
27292
|
+
${pagination(locale, "/", page, totalPages, filteredTotal, suffix)}
|
|
27293
|
+
<script type="application/json" id="task-project-data">${projectData}</script>
|
|
27294
|
+
<dialog id="task-dialog" class="template-dialog">
|
|
27295
|
+
<form id="task-form" data-default-cwd="${esc(cwdFilter)}" onsubmit="saveTask(event)">
|
|
27296
|
+
<input id="task-id" type="hidden">
|
|
27297
|
+
<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>
|
|
27298
|
+
<div class="dialog-body">
|
|
27299
|
+
<div class="template-form-grid">
|
|
27300
|
+
<label class="form-field"><span>${t(locale, "template.name")}</span><input id="task-name" required maxlength="200" autocomplete="off"></label>
|
|
27301
|
+
<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>
|
|
27302
|
+
<datalist id="task-cwd-options">${projects.map((project) => `<option value="${esc(project.cwd)}"></option>`).join("")}</datalist>
|
|
27303
|
+
<label class="form-field"><span>${t(locale, "template.agent")}</span><input id="task-agent" required autocomplete="off" value="build" placeholder="build"></label>
|
|
27304
|
+
<label class="form-field"><span>${t(locale, "template.model")}</span><input id="task-model" required autocomplete="off" value="default" placeholder="default"></label>
|
|
27305
|
+
<label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="task-prompt" rows="6" required></textarea></label>
|
|
27306
|
+
</div>
|
|
27307
|
+
<p id="task-project-status" class="form-note"></p>
|
|
27308
|
+
<details class="advanced-fields">
|
|
27309
|
+
<summary>${t(locale, "template.advanced")}</summary>
|
|
27310
|
+
<div class="template-form-grid">
|
|
27311
|
+
<label class="form-field"><span>${t(locale, "template.category")}</span><input id="task-category" autocomplete="off" value="general"></label>
|
|
27312
|
+
<label class="form-field"><span>${t(locale, "template.batchId")}</span><input id="task-batch" autocomplete="off"><small>${t(locale, "task.batchHint")}</small></label>
|
|
27313
|
+
<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>
|
|
27314
|
+
<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>
|
|
27315
|
+
<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>
|
|
27316
|
+
<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>
|
|
27317
|
+
<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>
|
|
27318
|
+
</div>
|
|
27319
|
+
</details>
|
|
27320
|
+
</div>
|
|
27321
|
+
<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>
|
|
27322
|
+
</form>
|
|
27323
|
+
</dialog>`;
|
|
26319
27324
|
return c.html(renderLayout({ locale, activeTab: "tasks", body }));
|
|
26320
27325
|
});
|
|
26321
27326
|
app.get("/templates", async (c) => {
|
|
26322
27327
|
const locale = resolveLocale(c);
|
|
26323
|
-
const
|
|
26324
|
-
|
|
26325
|
-
const
|
|
27328
|
+
const page = parsePositiveInteger2(c.req.query("page") || "1");
|
|
27329
|
+
if (page === null) return c.text("invalid page", 400);
|
|
27330
|
+
const limit = 50;
|
|
27331
|
+
const offset = (page - 1) * limit;
|
|
27332
|
+
const [templates, templateStats] = await Promise.all([
|
|
27333
|
+
TaskTemplateService.list(limit, offset),
|
|
27334
|
+
TaskTemplateService.stats()
|
|
27335
|
+
]);
|
|
27336
|
+
const totalPages = Math.max(1, Math.ceil(templateStats.total / limit));
|
|
27337
|
+
if (page > totalPages) return c.redirect(`/templates?page=${totalPages}`);
|
|
26326
27338
|
const rows = templates.map((template) => {
|
|
26327
27339
|
const scheduleType = ["cron", "recurring", "delayed"].includes(template.scheduleType) ? template.scheduleType : "unknown";
|
|
26328
27340
|
const typeLabel = scheduleType === "cron" ? t(locale, "schedule.cron") : scheduleType === "recurring" ? t(locale, "schedule.recurring") : scheduleType === "delayed" ? t(locale, "schedule.delayed") : t(locale, "schedule.unknown");
|
|
@@ -26340,21 +27352,56 @@ var init_web = __esm({
|
|
|
26340
27352
|
<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>
|
|
26341
27353
|
<td data-label="${t(locale, "table.lastRun")}" class="small muted">${formatRelative(template.lastRunAt, locale)}</td>
|
|
26342
27354
|
<td data-label="${t(locale, "table.nextRun")}" class="small">${formatFuture(template.nextRunAt, locale)}</td>
|
|
26343
|
-
<td data-label="${t(locale, "table.actions")}"><div class="actions"><button type="button" class="btn" onclick="showTemplateDetail(${template.id})">${t(locale, "action.details")}</button>
|
|
27355
|
+
<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>
|
|
26344
27356
|
<button type="button" class="btn btn-primary" onclick="triggerTmpl(${template.id})">${t(locale, "action.trigger")}</button>${toggle}
|
|
26345
27357
|
<button type="button" class="btn btn-danger" onclick="deleteTmpl(${template.id})">${t(locale, "action.delete")}</button></div></td>
|
|
26346
27358
|
</tr>`;
|
|
26347
27359
|
}).join("");
|
|
26348
27360
|
const body = `
|
|
26349
27361
|
<div class="stats-grid three">
|
|
26350
|
-
${statCard(
|
|
26351
|
-
${statCard(enabled, t(locale, "stats.enabled"), "tone-green", icon("check"), "reveal-delay-1")}
|
|
26352
|
-
${statCard(disabled, t(locale, "stats.disabled"), "tone-neutral", icon("clock"), "reveal-delay-2")}
|
|
27362
|
+
${statCard(templateStats.total, t(locale, "stats.templates"), "tone-purple", icon("templates"))}
|
|
27363
|
+
${statCard(templateStats.enabled, t(locale, "stats.enabled"), "tone-green", icon("check"), "reveal-delay-1")}
|
|
27364
|
+
${statCard(templateStats.disabled, t(locale, "stats.disabled"), "tone-neutral", icon("clock"), "reveal-delay-2")}
|
|
26353
27365
|
</div>
|
|
26354
27366
|
<section class="panel reveal reveal-delay-2">
|
|
26355
|
-
<div class="panel-head"><h2>${t(locale, "page.templates.title")}</h2></div>
|
|
26356
|
-
${templates.length === 0 ? emptyState(t(locale, "empty.templates"), t(locale, "empty.templatesHint")
|
|
26357
|
-
</section
|
|
27367
|
+
<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>
|
|
27368
|
+
${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>`}
|
|
27369
|
+
</section>
|
|
27370
|
+
<dialog id="template-dialog" class="template-dialog">
|
|
27371
|
+
<form id="template-form" onsubmit="saveTemplate(event)">
|
|
27372
|
+
<input id="template-id" type="hidden">
|
|
27373
|
+
<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>
|
|
27374
|
+
<div class="dialog-body">
|
|
27375
|
+
<div class="template-form-grid">
|
|
27376
|
+
<label class="form-field"><span>${t(locale, "template.name")}</span><input id="template-name" required maxlength="200" autocomplete="off"></label>
|
|
27377
|
+
<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>
|
|
27378
|
+
<label class="form-field"><span>${t(locale, "template.agent")}</span><input id="template-agent" required autocomplete="off" placeholder="build"></label>
|
|
27379
|
+
<label class="form-field"><span>${t(locale, "template.model")}</span><input id="template-model" required autocomplete="off" value="default" placeholder="default"></label>
|
|
27380
|
+
<label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="template-prompt" rows="6" required></textarea></label>
|
|
27381
|
+
<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>
|
|
27382
|
+
<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>
|
|
27383
|
+
<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>
|
|
27384
|
+
<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>
|
|
27385
|
+
</div>
|
|
27386
|
+
<details class="advanced-fields">
|
|
27387
|
+
<summary>${t(locale, "template.advanced")}</summary>
|
|
27388
|
+
<div class="template-form-grid">
|
|
27389
|
+
<label class="form-field"><span>${t(locale, "template.category")}</span><input id="template-category" autocomplete="off" value="general"></label>
|
|
27390
|
+
<label class="form-field"><span>${t(locale, "template.batchId")}</span><input id="template-batch" autocomplete="off"><small>${t(locale, "template.optional")}</small></label>
|
|
27391
|
+
<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>
|
|
27392
|
+
<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>
|
|
27393
|
+
<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>
|
|
27394
|
+
<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>
|
|
27395
|
+
<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>
|
|
27396
|
+
<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>
|
|
27397
|
+
</div>
|
|
27398
|
+
</details>
|
|
27399
|
+
<p class="form-note">${t(locale, "template.futureOnly")}</p>
|
|
27400
|
+
</div>
|
|
27401
|
+
<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>
|
|
27402
|
+
</form>
|
|
27403
|
+
</dialog>
|
|
27404
|
+
${pagination(locale, "/templates", page, totalPages, templateStats.total)}`;
|
|
26358
27405
|
return c.html(renderLayout({ locale, activeTab: "templates", body }));
|
|
26359
27406
|
});
|
|
26360
27407
|
app.get("/runs", async (c) => {
|
|
@@ -26382,9 +27429,11 @@ var init_web = __esm({
|
|
|
26382
27429
|
const totalResult = await db.select({ count: sql`count(*)` }).from(taskRuns4);
|
|
26383
27430
|
const total = Number(totalResult[0]?.count ?? 0);
|
|
26384
27431
|
const totalPages = Math.max(1, Math.ceil(total / limit));
|
|
27432
|
+
if (page > totalPages) return c.redirect(`/runs?page=${totalPages}`);
|
|
26385
27433
|
const logs = [];
|
|
26386
27434
|
const rows = runs.map((run) => {
|
|
26387
27435
|
const status = safeStatus(run.status);
|
|
27436
|
+
const resumable = isValidSessionId(run.sessionId);
|
|
26388
27437
|
if (run.log) {
|
|
26389
27438
|
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>`);
|
|
26390
27439
|
}
|
|
@@ -26392,10 +27441,12 @@ var init_web = __esm({
|
|
|
26392
27441
|
<td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td>
|
|
26393
27442
|
<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>
|
|
26394
27443
|
<td data-label="${t(locale, "table.agent")}"><span class="tag">${esc(run.taskAgent)}</span></td>
|
|
26395
|
-
<td data-label="${t(locale, "table.
|
|
27444
|
+
<td data-label="${t(locale, "table.session")}" class="m small">${esc(maskSessionId(run.sessionId))}</td>
|
|
27445
|
+
<td data-label="${t(locale, "table.status")}"><span class="badge b-${status}">${runStatusText(locale, run.status ?? "unknown")}</span></td>
|
|
26396
27446
|
<td data-label="${t(locale, "table.duration")}" class="small">${formatDuration(run.startedAt, run.finishedAt)}</td>
|
|
26397
27447
|
<td data-label="${t(locale, "table.heartbeat")}" class="small muted">${formatRelative(run.heartbeatAt, locale)}</td>
|
|
26398
27448
|
<td data-label="${t(locale, "table.actions")}"><div class="actions"><button type="button" class="btn" onclick="showRunDetail(${run.id})">${t(locale, "action.details")}</button>
|
|
27449
|
+
${resumable ? `<button type="button" class="btn" onclick="copySessionCommand(${run.id})">${icon("copy")}${t(locale, "action.continueSession")}</button>` : ""}
|
|
26399
27450
|
${run.log ? `<button type="button" class="btn" aria-expanded="false" onclick="toggleLog(${run.id},this)">${t(locale, "action.logs")}</button>` : ""}</div></td>
|
|
26400
27451
|
</tr>`;
|
|
26401
27452
|
}).join("");
|
|
@@ -26406,22 +27457,37 @@ var init_web = __esm({
|
|
|
26406
27457
|
${statCard(runs.filter((run) => run.status === "failed").length, t(locale, "stats.pageFailed"), "tone-red", icon("alert"), "reveal-delay-1")}
|
|
26407
27458
|
${statCard(runs.filter((run) => run.status === "running").length, t(locale, "stats.pageRunning"), "tone-blue", icon("activity"), "reveal-delay-2")}
|
|
26408
27459
|
</div>
|
|
26409
|
-
<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>
|
|
27460
|
+
<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>
|
|
26410
27461
|
${logs.join("")}${pagination(locale, "/runs", page, totalPages, total)}`;
|
|
26411
27462
|
return c.html(renderLayout({ locale, activeTab: "runs", body }));
|
|
26412
27463
|
});
|
|
26413
27464
|
app.get("/system", async (c) => {
|
|
26414
27465
|
const locale = resolveLocale(c);
|
|
26415
27466
|
const config = loadConfig();
|
|
27467
|
+
const activeConfig = runtimeConfig ?? config;
|
|
27468
|
+
const restartRequired = runtimeConfig !== null && !configsEqual(config, runtimeConfig);
|
|
27469
|
+
const managedRestart = canRestartCurrentGateway();
|
|
27470
|
+
const configState = resolveDashboardConfigState(
|
|
27471
|
+
runtimeConfig !== null,
|
|
27472
|
+
restartRequired,
|
|
27473
|
+
managedRestart
|
|
27474
|
+
);
|
|
27475
|
+
const configStateKey = {
|
|
27476
|
+
foreground: "system.configForeground",
|
|
27477
|
+
applied: "system.configApplied",
|
|
27478
|
+
pending: "system.configPending",
|
|
27479
|
+
manual: "system.configRestartManually"
|
|
27480
|
+
}[configState];
|
|
27481
|
+
const configStateText = t(locale, configStateKey);
|
|
26416
27482
|
const configPath = getConfigPath();
|
|
26417
|
-
const [stats, runningRuns,
|
|
27483
|
+
const [stats, runningRuns, templateStats] = await Promise.all([
|
|
26418
27484
|
TaskService.stats({}),
|
|
26419
27485
|
TaskRunService.getAllRunningRuns(),
|
|
26420
|
-
TaskTemplateService.
|
|
27486
|
+
TaskTemplateService.stats()
|
|
26421
27487
|
]);
|
|
26422
|
-
const configExists =
|
|
27488
|
+
const configExists = existsSync8(configPath);
|
|
26423
27489
|
const runRows = runningRuns.map((run) => {
|
|
26424
|
-
const session =
|
|
27490
|
+
const session = maskSessionId(run.sessionId);
|
|
26425
27491
|
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>
|
|
26426
27492
|
<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>
|
|
26427
27493
|
<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>
|
|
@@ -26437,10 +27503,10 @@ var init_web = __esm({
|
|
|
26437
27503
|
<div class="field"><label for="hi">${t(locale, "system.heartbeatInterval")}</label>${unitInput("hi", config.worker.heartbeatIntervalMs / 1e3, 5, t(locale, "system.seconds"))}</div>
|
|
26438
27504
|
<div class="field"><label for="to">${t(locale, "system.taskTimeout")}</label>${unitInput("to", config.worker.taskTimeoutMs / 6e4, 1, t(locale, "system.minutes"))}</div>
|
|
26439
27505
|
</section>
|
|
26440
|
-
<section class="card settings-card reveal-delay-1"><h2 class="settings-title"><span>${icon("templates")}${t(locale, "system.scheduler")}</span><span class="badge ${
|
|
27506
|
+
<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>
|
|
26441
27507
|
<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>
|
|
26442
27508
|
<div class="field"><label for="si">${t(locale, "system.checkInterval")}</label>${unitInput("si", config.scheduler.checkIntervalMs, 100, t(locale, "system.milliseconds"))}</div>
|
|
26443
|
-
<div class="info-row"><span class="info-key">${t(locale, "system.activeTemplates")}</span><span class="info-value">${
|
|
27509
|
+
<div class="info-row"><span class="info-key">${t(locale, "system.activeTemplates")}</span><span class="info-value">${templateStats.enabled} / ${templateStats.total}</span></div>
|
|
26444
27510
|
</section>
|
|
26445
27511
|
<section class="card settings-card reveal-delay-2"><h2 class="settings-title"><span>${icon("system")}${t(locale, "system.watchdog")}</span></h2>
|
|
26446
27512
|
<div class="field"><label for="wt">${t(locale, "system.heartbeatTimeout")}</label>${unitInput("wt", config.watchdog.heartbeatTimeoutMs / 1e3, 10, t(locale, "system.seconds"))}</div>
|
|
@@ -26449,10 +27515,10 @@ var init_web = __esm({
|
|
|
26449
27515
|
<div class="field"><label for="rd">${t(locale, "system.retentionDays")}</label>${unitInput("rd", config.watchdog.retentionDays, 1, t(locale, "system.days"))}</div>
|
|
26450
27516
|
</section>
|
|
26451
27517
|
</div>
|
|
26452
|
-
<div class="save-row"><span class="muted small">${t(locale, "
|
|
27518
|
+
<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>
|
|
26453
27519
|
</form>
|
|
26454
27520
|
<section class="panel reveal">
|
|
26455
|
-
<div class="panel-head"><h2>${t(locale, "system.runningTasks", { running: runningRuns.length, limit:
|
|
27521
|
+
<div class="panel-head"><h2>${t(locale, "system.runningTasks", { running: runningRuns.length, limit: activeConfig.worker.maxConcurrency })}</h2></div>
|
|
26456
27522
|
${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>`}
|
|
26457
27523
|
</section>
|
|
26458
27524
|
<section class="panel reveal reveal-delay-1"><div class="panel-head"><h2>${t(locale, "system.taskStats")}</h2></div><div class="overview-grid">
|
|
@@ -26469,6 +27535,30 @@ var init_web = __esm({
|
|
|
26469
27535
|
<button type="button" class="btn btn-danger" onclick="clearDatabase()">${icon("database")}${t(locale, "action.clearDatabase")}</button></section>`;
|
|
26470
27536
|
return c.html(renderLayout({ locale, activeTab: "system", body }));
|
|
26471
27537
|
});
|
|
27538
|
+
app.post("/api/tasks", async (c) => {
|
|
27539
|
+
try {
|
|
27540
|
+
const task = await TaskService.add(parseTaskPayload(await c.req.json()));
|
|
27541
|
+
return c.json({ success: true, task }, 201);
|
|
27542
|
+
} catch (error) {
|
|
27543
|
+
return c.json({
|
|
27544
|
+
error: error instanceof Error ? error.message : String(error)
|
|
27545
|
+
}, 400);
|
|
27546
|
+
}
|
|
27547
|
+
});
|
|
27548
|
+
app.put("/api/tasks/:id", async (c) => {
|
|
27549
|
+
const id = parsePositiveInteger2(c.req.param("id"));
|
|
27550
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
27551
|
+
try {
|
|
27552
|
+
const input = parseTaskPayload(await c.req.json());
|
|
27553
|
+
const task = await TaskService.update(id, editableTaskPayload(input));
|
|
27554
|
+
if (task) return c.json({ success: true, task });
|
|
27555
|
+
return await TaskService.getById(id) ? c.json({ error: "task status does not allow editing" }, 409) : c.json({ error: "not found" }, 404);
|
|
27556
|
+
} catch (error) {
|
|
27557
|
+
return c.json({
|
|
27558
|
+
error: error instanceof Error ? error.message : String(error)
|
|
27559
|
+
}, 400);
|
|
27560
|
+
}
|
|
27561
|
+
});
|
|
26472
27562
|
app.get("/api/tasks/:id", async (c) => {
|
|
26473
27563
|
const id = parsePositiveInteger2(c.req.param("id"));
|
|
26474
27564
|
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
@@ -26484,6 +27574,24 @@ var init_web = __esm({
|
|
|
26484
27574
|
if (!run) return c.json({ error: "not found" }, 404);
|
|
26485
27575
|
return c.json(run);
|
|
26486
27576
|
});
|
|
27577
|
+
app.get("/api/runs/:id/session-command", async (c) => {
|
|
27578
|
+
const id = parsePositiveInteger2(c.req.param("id"));
|
|
27579
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
27580
|
+
const run = await TaskRunService.getById(id);
|
|
27581
|
+
if (!run) return c.json({ error: "not found" }, 404);
|
|
27582
|
+
if (!isValidSessionId(run.sessionId)) return c.json({ error: "session unavailable" }, 409);
|
|
27583
|
+
return c.json({ command: sessionCommand(run.sessionId) });
|
|
27584
|
+
});
|
|
27585
|
+
app.get("/api/gateway/status", (c) => {
|
|
27586
|
+
const savedConfig = loadConfig();
|
|
27587
|
+
const managed = canRestartCurrentGateway();
|
|
27588
|
+
return c.json({
|
|
27589
|
+
pid: process.pid,
|
|
27590
|
+
managed,
|
|
27591
|
+
ready: managed && getGatewayHealth().status === "ok",
|
|
27592
|
+
restartRequired: runtimeConfig === null || !configsEqual(savedConfig, runtimeConfig)
|
|
27593
|
+
});
|
|
27594
|
+
});
|
|
26487
27595
|
app.get("/api/templates/:id", async (c) => {
|
|
26488
27596
|
const id = parsePositiveInteger2(c.req.param("id"));
|
|
26489
27597
|
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
@@ -26491,6 +27599,30 @@ var init_web = __esm({
|
|
|
26491
27599
|
if (!template) return c.json({ error: "not found" }, 404);
|
|
26492
27600
|
return c.json(template);
|
|
26493
27601
|
});
|
|
27602
|
+
app.post("/api/templates", async (c) => {
|
|
27603
|
+
try {
|
|
27604
|
+
const input = parseTemplatePayload(await c.req.json());
|
|
27605
|
+
const template = await TaskTemplateService.create(input);
|
|
27606
|
+
return c.json({ success: true, template }, 201);
|
|
27607
|
+
} catch (error) {
|
|
27608
|
+
return c.json({
|
|
27609
|
+
error: error instanceof Error ? error.message : String(error)
|
|
27610
|
+
}, 400);
|
|
27611
|
+
}
|
|
27612
|
+
});
|
|
27613
|
+
app.put("/api/templates/:id", async (c) => {
|
|
27614
|
+
const id = parsePositiveInteger2(c.req.param("id"));
|
|
27615
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
27616
|
+
try {
|
|
27617
|
+
const input = parseTemplatePayload(await c.req.json());
|
|
27618
|
+
const template = await TaskTemplateService.update(id, input);
|
|
27619
|
+
return template ? c.json({ success: true, template }) : c.json({ error: "not found" }, 404);
|
|
27620
|
+
} catch (error) {
|
|
27621
|
+
return c.json({
|
|
27622
|
+
error: error instanceof Error ? error.message : String(error)
|
|
27623
|
+
}, 400);
|
|
27624
|
+
}
|
|
27625
|
+
});
|
|
26494
27626
|
app.post("/api/tasks/:id/retry", async (c) => {
|
|
26495
27627
|
const id = parsePositiveInteger2(c.req.param("id"));
|
|
26496
27628
|
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
@@ -26539,22 +27671,31 @@ var init_web = __esm({
|
|
|
26539
27671
|
app.post("/api/templates/:id/trigger", async (c) => {
|
|
26540
27672
|
const id = parsePositiveInteger2(c.req.param("id"));
|
|
26541
27673
|
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
26542
|
-
const template = await TaskTemplateService.getById(id);
|
|
26543
|
-
if (!template) return c.json({ error: "not found" }, 404);
|
|
26544
27674
|
const task = await triggerTaskFromTemplate(id);
|
|
26545
|
-
|
|
26546
|
-
return c.json({ success: true, taskId: task.id });
|
|
27675
|
+
return task ? c.json({ success: true, taskId: task.id }) : c.json({ error: "not found" }, 404);
|
|
26547
27676
|
});
|
|
26548
27677
|
app.put("/api/config", async (c) => {
|
|
26549
27678
|
try {
|
|
26550
|
-
const
|
|
27679
|
+
const rawBody = await c.req.json();
|
|
27680
|
+
if (!rawBody || typeof rawBody !== "object" || Array.isArray(rawBody)) {
|
|
27681
|
+
throw new Error("\u8BF7\u6C42\u5185\u5BB9\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
27682
|
+
}
|
|
27683
|
+
const body = rawBody;
|
|
27684
|
+
const section = (name) => {
|
|
27685
|
+
const value = body[name];
|
|
27686
|
+
if (value === void 0) return {};
|
|
27687
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
27688
|
+
throw new Error(`${name} \u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
27689
|
+
}
|
|
27690
|
+
return value;
|
|
27691
|
+
};
|
|
26551
27692
|
const current = readCurrentConfig();
|
|
26552
27693
|
const currentWorker = current.worker ?? {};
|
|
26553
27694
|
const currentScheduler = current.scheduler ?? {};
|
|
26554
27695
|
const currentWatchdog = current.watchdog ?? {};
|
|
26555
|
-
const bodyWorker =
|
|
26556
|
-
const bodyScheduler =
|
|
26557
|
-
const bodyWatchdog =
|
|
27696
|
+
const bodyWorker = section("worker");
|
|
27697
|
+
const bodyScheduler = section("scheduler");
|
|
27698
|
+
const bodyWatchdog = section("watchdog");
|
|
26558
27699
|
const merged = {
|
|
26559
27700
|
...current,
|
|
26560
27701
|
...body,
|
|
@@ -26563,8 +27704,13 @@ var init_web = __esm({
|
|
|
26563
27704
|
scheduler: { ...currentScheduler, ...bodyScheduler },
|
|
26564
27705
|
watchdog: { ...currentWatchdog, ...bodyWatchdog }
|
|
26565
27706
|
};
|
|
26566
|
-
|
|
26567
|
-
|
|
27707
|
+
const savedConfig = validateConfig(merged);
|
|
27708
|
+
writeConfig(savedConfig);
|
|
27709
|
+
return c.json({
|
|
27710
|
+
success: true,
|
|
27711
|
+
restartRequired: runtimeConfig === null || !configsEqual(savedConfig, runtimeConfig),
|
|
27712
|
+
managed: canRestartCurrentGateway()
|
|
27713
|
+
});
|
|
26568
27714
|
} catch (error) {
|
|
26569
27715
|
return c.json({
|
|
26570
27716
|
success: false,
|
|
@@ -26572,9 +27718,27 @@ var init_web = __esm({
|
|
|
26572
27718
|
}, 400);
|
|
26573
27719
|
}
|
|
26574
27720
|
});
|
|
27721
|
+
app.post("/api/gateway/restart", async (c) => {
|
|
27722
|
+
const rawBody = await c.req.json().catch(() => null);
|
|
27723
|
+
const confirmation = rawBody && typeof rawBody === "object" && !Array.isArray(rawBody) ? rawBody.confirmation : void 0;
|
|
27724
|
+
if (confirmation !== "RESTART") {
|
|
27725
|
+
return c.json({ error: "confirmation must be RESTART" }, 400);
|
|
27726
|
+
}
|
|
27727
|
+
if (restartScheduled) return c.json({ error: "restart already scheduled" }, 409);
|
|
27728
|
+
if (!canRestartCurrentGateway()) {
|
|
27729
|
+
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);
|
|
27730
|
+
}
|
|
27731
|
+
restartScheduled = true;
|
|
27732
|
+
const previousPid = process.pid;
|
|
27733
|
+
setTimeout(() => {
|
|
27734
|
+
process.kill(previousPid, "SIGTERM");
|
|
27735
|
+
}, 500);
|
|
27736
|
+
return c.json({ success: true, previousPid }, 202);
|
|
27737
|
+
});
|
|
26575
27738
|
app.post("/api/database/clear", async (c) => {
|
|
26576
|
-
const
|
|
26577
|
-
|
|
27739
|
+
const rawBody = await c.req.json().catch(() => null);
|
|
27740
|
+
const confirmation = rawBody && typeof rawBody === "object" && !Array.isArray(rawBody) ? rawBody.confirmation : void 0;
|
|
27741
|
+
if (confirmation !== "CLEAR") {
|
|
26578
27742
|
return c.json({ success: false, error: "confirmation must be CLEAR" }, 400);
|
|
26579
27743
|
}
|
|
26580
27744
|
try {
|
|
@@ -26824,8 +27988,9 @@ async function main() {
|
|
|
26824
27988
|
await scheduler.start();
|
|
26825
27989
|
if (shuttingDown) return;
|
|
26826
27990
|
if (cfg.dashboard.enabled) {
|
|
26827
|
-
const { dashboardApp: dashboardApp2 } = await Promise.resolve().then(() => (init_web(), web_exports));
|
|
27991
|
+
const { dashboardApp: dashboardApp2, setDashboardRuntimeConfig: setDashboardRuntimeConfig2 } = await Promise.resolve().then(() => (init_web(), web_exports));
|
|
26828
27992
|
if (shuttingDown) return;
|
|
27993
|
+
setDashboardRuntimeConfig2(cfg);
|
|
26829
27994
|
dashboardServer = Bun.serve({
|
|
26830
27995
|
hostname: "127.0.0.1",
|
|
26831
27996
|
port: cfg.dashboard.port,
|
|
@@ -26879,185 +28044,6 @@ var init_gateway = __esm({
|
|
|
26879
28044
|
}
|
|
26880
28045
|
});
|
|
26881
28046
|
|
|
26882
|
-
// src/core/semver.ts
|
|
26883
|
-
function parseSemanticVersion(version2) {
|
|
26884
|
-
const match2 = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(version2);
|
|
26885
|
-
if (!match2) return null;
|
|
26886
|
-
const prerelease = match2[4]?.split(".") ?? [];
|
|
26887
|
-
if (prerelease.some((identifier) => /^\d+$/.test(identifier) && identifier.length > 1 && identifier.startsWith("0"))) {
|
|
26888
|
-
return null;
|
|
26889
|
-
}
|
|
26890
|
-
return {
|
|
26891
|
-
major: Number(match2[1]),
|
|
26892
|
-
minor: Number(match2[2]),
|
|
26893
|
-
patch: Number(match2[3]),
|
|
26894
|
-
prerelease
|
|
26895
|
-
};
|
|
26896
|
-
}
|
|
26897
|
-
function compareSemanticVersions(left, right) {
|
|
26898
|
-
const a = parseSemanticVersion(left);
|
|
26899
|
-
const b = parseSemanticVersion(right);
|
|
26900
|
-
if (!a || !b) return null;
|
|
26901
|
-
for (const key of ["major", "minor", "patch"]) {
|
|
26902
|
-
if (a[key] !== b[key]) return a[key] - b[key];
|
|
26903
|
-
}
|
|
26904
|
-
if (a.prerelease.length === 0 || b.prerelease.length === 0) {
|
|
26905
|
-
if (a.prerelease.length === b.prerelease.length) return 0;
|
|
26906
|
-
return a.prerelease.length === 0 ? 1 : -1;
|
|
26907
|
-
}
|
|
26908
|
-
const identifiers = Math.max(a.prerelease.length, b.prerelease.length);
|
|
26909
|
-
for (let index2 = 0; index2 < identifiers; index2 += 1) {
|
|
26910
|
-
const leftIdentifier = a.prerelease[index2];
|
|
26911
|
-
const rightIdentifier = b.prerelease[index2];
|
|
26912
|
-
if (leftIdentifier === void 0 || rightIdentifier === void 0) {
|
|
26913
|
-
return leftIdentifier === rightIdentifier ? 0 : leftIdentifier === void 0 ? -1 : 1;
|
|
26914
|
-
}
|
|
26915
|
-
if (leftIdentifier === rightIdentifier) continue;
|
|
26916
|
-
const leftNumeric = /^\d+$/.test(leftIdentifier);
|
|
26917
|
-
const rightNumeric = /^\d+$/.test(rightIdentifier);
|
|
26918
|
-
if (leftNumeric && rightNumeric) return Number(leftIdentifier) - Number(rightIdentifier);
|
|
26919
|
-
if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1;
|
|
26920
|
-
return leftIdentifier < rightIdentifier ? -1 : 1;
|
|
26921
|
-
}
|
|
26922
|
-
return 0;
|
|
26923
|
-
}
|
|
26924
|
-
function isSemanticVersion(version2) {
|
|
26925
|
-
return parseSemanticVersion(version2) !== null;
|
|
26926
|
-
}
|
|
26927
|
-
var init_semver = __esm({
|
|
26928
|
-
"src/core/semver.ts"() {
|
|
26929
|
-
"use strict";
|
|
26930
|
-
}
|
|
26931
|
-
});
|
|
26932
|
-
|
|
26933
|
-
// src/daemon/update.ts
|
|
26934
|
-
var update_exports = {};
|
|
26935
|
-
__export(update_exports, {
|
|
26936
|
-
installLatestPlugin: () => installLatestPlugin,
|
|
26937
|
-
installPluginVersion: () => installPluginVersion,
|
|
26938
|
-
resolveInstalledPlugin: () => resolveInstalledPlugin
|
|
26939
|
-
});
|
|
26940
|
-
import { spawnSync as spawnSync3 } from "child_process";
|
|
26941
|
-
import { existsSync as existsSync8, readFileSync as readFileSync5, readdirSync } from "fs";
|
|
26942
|
-
import { homedir as homedir4 } from "os";
|
|
26943
|
-
import { join as join6 } from "path";
|
|
26944
|
-
function pluginAt(packageDir) {
|
|
26945
|
-
const packageJson = join6(packageDir, "package.json");
|
|
26946
|
-
const gatewayEntry = join6(packageDir, "dist/gateway/index.js");
|
|
26947
|
-
if (!existsSync8(packageJson) || !existsSync8(gatewayEntry)) return null;
|
|
26948
|
-
try {
|
|
26949
|
-
const pkg = JSON.parse(readFileSync5(packageJson, "utf8"));
|
|
26950
|
-
if (pkg.name !== PACKAGE_NAME || typeof pkg.version !== "string") return null;
|
|
26951
|
-
return { packageDir, gatewayEntry, version: pkg.version };
|
|
26952
|
-
} catch {
|
|
26953
|
-
return null;
|
|
26954
|
-
}
|
|
26955
|
-
}
|
|
26956
|
-
function compareVersions(left, right) {
|
|
26957
|
-
return compareSemanticVersions(left, right) ?? left.localeCompare(right);
|
|
26958
|
-
}
|
|
26959
|
-
function cacheRoot() {
|
|
26960
|
-
return process.env.SUPERTASK_OPENCODE_CACHE_DIR ?? join6(homedir4(), ".cache/opencode/packages");
|
|
26961
|
-
}
|
|
26962
|
-
function installedPlugins() {
|
|
26963
|
-
const root = cacheRoot();
|
|
26964
|
-
const packageDirs = existsSync8(root) ? readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && (entry.name === PACKAGE_NAME || entry.name.startsWith(`${PACKAGE_NAME}@`))).map((entry) => join6(root, entry.name, "node_modules", PACKAGE_NAME)) : [];
|
|
26965
|
-
return packageDirs.map(pluginAt).filter((plugin) => plugin !== null);
|
|
26966
|
-
}
|
|
26967
|
-
function resolveInstalledPlugin() {
|
|
26968
|
-
const override = process.env.SUPERTASK_PLUGIN_PACKAGE_DIR;
|
|
26969
|
-
if (override) {
|
|
26970
|
-
const plugin = pluginAt(override);
|
|
26971
|
-
if (!plugin) throw new Error(`[supertask] \u5B89\u88C5\u5305\u65E0\u6548\u6216\u7F3A\u5C11 Gateway \u6784\u5EFA\u4EA7\u7269: ${override}`);
|
|
26972
|
-
return plugin;
|
|
26973
|
-
}
|
|
26974
|
-
const installed = installedPlugins().sort((left, right) => compareVersions(right.version, left.version));
|
|
26975
|
-
if (!installed[0]) {
|
|
26976
|
-
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230\u53EF\u8FD0\u884C\u7684 ${PACKAGE_NAME}`);
|
|
26977
|
-
}
|
|
26978
|
-
return installed[0];
|
|
26979
|
-
}
|
|
26980
|
-
function opencodeBin() {
|
|
26981
|
-
return process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
26982
|
-
}
|
|
26983
|
-
function npmBin() {
|
|
26984
|
-
return process.env.SUPERTASK_NPM_BIN ?? "npm";
|
|
26985
|
-
}
|
|
26986
|
-
function latestVersion() {
|
|
26987
|
-
const result = spawnSync3(npmBin(), [
|
|
26988
|
-
"view",
|
|
26989
|
-
PACKAGE_NAME,
|
|
26990
|
-
"dist-tags.latest",
|
|
26991
|
-
"--json"
|
|
26992
|
-
], {
|
|
26993
|
-
encoding: "utf8",
|
|
26994
|
-
env: process.env,
|
|
26995
|
-
timeout: 3e4
|
|
26996
|
-
});
|
|
26997
|
-
const output = `${result.stdout ?? ""}`.trim();
|
|
26998
|
-
if (result.error) {
|
|
26999
|
-
throw new Error(`[supertask] \u67E5\u8BE2 npm latest \u5931\u8D25: ${result.error.message}`);
|
|
27000
|
-
}
|
|
27001
|
-
if (result.status !== 0) {
|
|
27002
|
-
const detail = `${result.stderr ?? ""}`.trim();
|
|
27003
|
-
throw new Error(`[supertask] \u67E5\u8BE2 npm latest \u5931\u8D25: ${detail || `\u9000\u51FA\u7801 ${result.status}`}`);
|
|
27004
|
-
}
|
|
27005
|
-
let response;
|
|
27006
|
-
try {
|
|
27007
|
-
response = JSON.parse(output);
|
|
27008
|
-
} catch {
|
|
27009
|
-
throw new Error(`[supertask] npm latest \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790: ${output || "(empty)"}`);
|
|
27010
|
-
}
|
|
27011
|
-
const versions = typeof response === "string" ? [response] : Array.isArray(response) && response.every((value) => typeof value === "string") ? response : [];
|
|
27012
|
-
const uniqueVersions = [...new Set(versions)];
|
|
27013
|
-
if (uniqueVersions.length !== 1 || !isSemanticVersion(uniqueVersions[0])) {
|
|
27014
|
-
throw new Error(`[supertask] npm latest \u7248\u672C\u65E0\u6548: ${String(response)}`);
|
|
27015
|
-
}
|
|
27016
|
-
return uniqueVersions[0];
|
|
27017
|
-
}
|
|
27018
|
-
function resolveInstalledVersion(expectedVersion) {
|
|
27019
|
-
const override = process.env.SUPERTASK_PLUGIN_PACKAGE_DIR;
|
|
27020
|
-
const installed = override ? [pluginAt(override)].filter((plugin) => plugin !== null) : installedPlugins();
|
|
27021
|
-
const matched = installed.find((plugin) => plugin.version === expectedVersion);
|
|
27022
|
-
if (matched) return matched;
|
|
27023
|
-
const actual = installed.length > 0 ? installed.map((plugin) => plugin.version).join(", ") : "\u672A\u627E\u5230\u53EF\u8FD0\u884C\u7F13\u5B58";
|
|
27024
|
-
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7F13\u5B58\u7248\u672C\u4E0D\u5339\u914D\uFF1A\u671F\u671B ${expectedVersion}\uFF0C\u5B9E\u9645 ${actual}`);
|
|
27025
|
-
}
|
|
27026
|
-
function installPluginVersion(version2) {
|
|
27027
|
-
if (!isSemanticVersion(version2)) {
|
|
27028
|
-
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7248\u672C\u65E0\u6548: ${version2}`);
|
|
27029
|
-
}
|
|
27030
|
-
const result = spawnSync3(opencodeBin(), [
|
|
27031
|
-
"plugin",
|
|
27032
|
-
`${PACKAGE_NAME}@${version2}`,
|
|
27033
|
-
"--global",
|
|
27034
|
-
"--force"
|
|
27035
|
-
], {
|
|
27036
|
-
encoding: "utf8",
|
|
27037
|
-
env: process.env,
|
|
27038
|
-
timeout: 12e4
|
|
27039
|
-
});
|
|
27040
|
-
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
27041
|
-
if (result.error) {
|
|
27042
|
-
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u66F4\u65B0\u5931\u8D25: ${result.error.message}`);
|
|
27043
|
-
}
|
|
27044
|
-
if (result.status !== 0) {
|
|
27045
|
-
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u66F4\u65B0\u5931\u8D25: ${output || `\u9000\u51FA\u7801 ${result.status}`}`);
|
|
27046
|
-
}
|
|
27047
|
-
return resolveInstalledVersion(version2);
|
|
27048
|
-
}
|
|
27049
|
-
function installLatestPlugin() {
|
|
27050
|
-
return installPluginVersion(latestVersion());
|
|
27051
|
-
}
|
|
27052
|
-
var PACKAGE_NAME;
|
|
27053
|
-
var init_update2 = __esm({
|
|
27054
|
-
"src/daemon/update.ts"() {
|
|
27055
|
-
"use strict";
|
|
27056
|
-
init_semver();
|
|
27057
|
-
PACKAGE_NAME = "opencode-supertask";
|
|
27058
|
-
}
|
|
27059
|
-
});
|
|
27060
|
-
|
|
27061
28047
|
// node_modules/commander/esm.mjs
|
|
27062
28048
|
var import_index = __toESM(require_commander(), 1);
|
|
27063
28049
|
var {
|
|
@@ -27081,37 +28067,7 @@ init_task_run_service();
|
|
|
27081
28067
|
init_task_template_service();
|
|
27082
28068
|
init_database_maintenance_service();
|
|
27083
28069
|
init_db2();
|
|
27084
|
-
|
|
27085
|
-
// src/core/duration.ts
|
|
27086
|
-
var DURATION_REGEX = /^(\d+(?:\.\d+)?)\s*(ms|s|sec|seconds?|min|minutes?|m|h|hours?|d|days?|w|weeks?)$/i;
|
|
27087
|
-
var ISO8601_REGEX = /^P(?:([.\d]+)D)?(?:T(?:([.\d]+)H)?(?:([.\d]+)M)?(?:([.\d]+)S)?)?$/i;
|
|
27088
|
-
function parseDuration(input) {
|
|
27089
|
-
const trimmed = input.trim();
|
|
27090
|
-
const simple = DURATION_REGEX.exec(trimmed);
|
|
27091
|
-
if (simple) {
|
|
27092
|
-
const value = parseFloat(simple[1]);
|
|
27093
|
-
const unit = simple[2].toLowerCase();
|
|
27094
|
-
if (unit === "ms") return value;
|
|
27095
|
-
if (unit === "s" || unit === "sec" || unit === "second" || unit === "seconds") return value * 1e3;
|
|
27096
|
-
if (unit === "min" || unit === "minute" || unit === "minutes" || unit === "m") return value * 6e4;
|
|
27097
|
-
if (unit === "h" || unit === "hour" || unit === "hours") return value * 36e5;
|
|
27098
|
-
if (unit === "d" || unit === "day" || unit === "days") return value * 864e5;
|
|
27099
|
-
if (unit === "w" || unit === "week" || unit === "weeks") return value * 6048e5;
|
|
27100
|
-
}
|
|
27101
|
-
const iso = ISO8601_REGEX.exec(trimmed);
|
|
27102
|
-
if (iso) {
|
|
27103
|
-
const days = parseFloat(iso[1] ?? "0");
|
|
27104
|
-
const hours = parseFloat(iso[2] ?? "0");
|
|
27105
|
-
const minutes = parseFloat(iso[3] ?? "0");
|
|
27106
|
-
const seconds = parseFloat(iso[4] ?? "0");
|
|
27107
|
-
return (days * 86400 + hours * 3600 + minutes * 60 + seconds) * 1e3;
|
|
27108
|
-
}
|
|
27109
|
-
const asNumber = Number(trimmed);
|
|
27110
|
-
if (!isNaN(asNumber) && asNumber > 0) return asNumber;
|
|
27111
|
-
return null;
|
|
27112
|
-
}
|
|
27113
|
-
|
|
27114
|
-
// src/cli/index.ts
|
|
28070
|
+
init_duration();
|
|
27115
28071
|
init_pm2();
|
|
27116
28072
|
init_config();
|
|
27117
28073
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
@@ -27245,6 +28201,7 @@ function parseTaskStatus(value) {
|
|
|
27245
28201
|
}
|
|
27246
28202
|
|
|
27247
28203
|
// src/cli/index.ts
|
|
28204
|
+
init_update2();
|
|
27248
28205
|
async function withDb(fn, formatError = (error) => JSON.stringify({
|
|
27249
28206
|
error: error instanceof Error ? error.message : String(error)
|
|
27250
28207
|
})) {
|
|
@@ -27295,6 +28252,44 @@ program2.command("add").description("\u521B\u5EFA\u65B0\u4EFB\u52A1").requiredOp
|
|
|
27295
28252
|
});
|
|
27296
28253
|
console.log(JSON.stringify({ id: task.id, status: "created" }, null, 2));
|
|
27297
28254
|
}));
|
|
28255
|
+
program2.command("edit").description("\u4FEE\u6539\u5F53\u524D\u9879\u76EE\u4E2D\u5C1A\u672A\u5B8C\u6210\u7684\u4EFB\u52A1").requiredOption("--id <id>", "\u4EFB\u52A1 ID").option("-n, --name <name>", "\u4EFB\u52A1\u540D\u79F0").option("-a, --agent <agent>", "Agent \u540D\u79F0").option("-m, --model <model>", "\u6A21\u578B").option("-p, --prompt <prompt>", "\u63D0\u793A\u8BCD").option("-c, --category <category>", "\u5206\u7C7B").option("-i, --importance <number>", "\u91CD\u8981\u7A0B\u5EA6 (1-5)").option("-u, --urgency <number>", "\u7D27\u6025\u7A0B\u5EA6 (1-5)").option("-b, --batch <batchId>", "\u6279\u6B21 ID").option("--clear-batch", "\u6E05\u7A7A\u6279\u6B21 ID").option("--max-retries <number>", "\u9996\u6B21\u6267\u884C\u4E4B\u5916\u5141\u8BB8\u7684\u91CD\u8BD5\u6B21\u6570").option("--retry-backoff <duration>", "\u91CD\u8BD5\u9000\u907F\u57FA\u7840\u95F4\u9694\uFF0C\u5982 30s / 5min").option("--timeout <duration>", "\u4EFB\u52A1\u786C\u8D85\u65F6\uFF0C\u5982 30min / 2h").option("--clear-timeout", "\u6E05\u7A7A\u4EFB\u52A1\u7EA7\u8D85\u65F6\uFF0C\u6539\u7528 Gateway \u9ED8\u8BA4\u503C").action(async (options) => withDb(async () => {
|
|
28256
|
+
if (options.batch !== void 0 && options.clearBatch) {
|
|
28257
|
+
throw new Error("batch \u548C clear-batch \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
|
|
28258
|
+
}
|
|
28259
|
+
if (options.timeout !== void 0 && options.clearTimeout) {
|
|
28260
|
+
throw new Error("timeout \u548C clear-timeout \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
|
|
28261
|
+
}
|
|
28262
|
+
const update = {};
|
|
28263
|
+
for (const field of ["name", "agent", "model", "prompt", "category"]) {
|
|
28264
|
+
if (options[field] !== void 0) update[field] = options[field];
|
|
28265
|
+
}
|
|
28266
|
+
if (options.importance !== void 0) {
|
|
28267
|
+
update.importance = parseBoundedInteger(options.importance, "importance", 1, 5);
|
|
28268
|
+
}
|
|
28269
|
+
if (options.urgency !== void 0) {
|
|
28270
|
+
update.urgency = parseBoundedInteger(options.urgency, "urgency", 1, 5);
|
|
28271
|
+
}
|
|
28272
|
+
if (options.maxRetries !== void 0) {
|
|
28273
|
+
update.maxRetries = parseBoundedInteger(options.maxRetries, "max-retries", 0, 1e3);
|
|
28274
|
+
}
|
|
28275
|
+
if (options.batch !== void 0 || options.clearBatch) {
|
|
28276
|
+
update.batchId = options.clearBatch ? null : options.batch;
|
|
28277
|
+
}
|
|
28278
|
+
if (options.retryBackoff !== void 0) {
|
|
28279
|
+
const retryBackoffMs = parseDuration(options.retryBackoff);
|
|
28280
|
+
if (retryBackoffMs === null) throw new Error("retry-backoff \u683C\u5F0F\u65E0\u6548");
|
|
28281
|
+
update.retryBackoffMs = retryBackoffMs;
|
|
28282
|
+
}
|
|
28283
|
+
if (options.timeout !== void 0 || options.clearTimeout) {
|
|
28284
|
+
const timeoutMs = options.clearTimeout ? null : parseDuration(options.timeout);
|
|
28285
|
+
if (timeoutMs === null && !options.clearTimeout) throw new Error("timeout \u683C\u5F0F\u65E0\u6548");
|
|
28286
|
+
update.timeoutMs = timeoutMs;
|
|
28287
|
+
}
|
|
28288
|
+
const id = parsePositiveInteger(options.id, "id");
|
|
28289
|
+
const task = await TaskService.update(id, update, { cwd: process.cwd() });
|
|
28290
|
+
if (!task) throw new Error(`\u4EFB\u52A1 #${id} \u4E0D\u5B58\u5728\u4E8E\u5F53\u524D\u9879\u76EE\uFF0C\u6216\u5176\u72B6\u6001\u4E0D\u5141\u8BB8\u7F16\u8F91`);
|
|
28291
|
+
console.log(JSON.stringify({ id: task.id, status: task.status, updated: true }, null, 2));
|
|
28292
|
+
}));
|
|
27298
28293
|
program2.command("next").description("\u83B7\u53D6\u4E0B\u4E00\u4E2A\u5F85\u6267\u884C\u7684\u4EFB\u52A1").action(async () => withDb(async () => {
|
|
27299
28294
|
const task = await TaskService.next({ cwd: process.cwd() });
|
|
27300
28295
|
if (task) {
|
|
@@ -27379,7 +28374,7 @@ program2.command("delete").description("\u5220\u9664\u4EFB\u52A1").requiredOptio
|
|
|
27379
28374
|
console.log(JSON.stringify({ deleted, id }));
|
|
27380
28375
|
}));
|
|
27381
28376
|
program2.command("template").description("\u7BA1\u7406\u4EFB\u52A1\u8C03\u5EA6\u6A21\u677F").addCommand(
|
|
27382
|
-
new Command("add").description("\u521B\u5EFA\u8C03\u5EA6\u6A21\u677F").requiredOption("-n, --name <name>", "\u6A21\u677F\u540D\u79F0").requiredOption("-a, --agent <agent>", "Agent \u540D\u79F0").requiredOption("-p, --prompt <prompt>", "\u63D0\u793A\u8BCD").requiredOption("-t, --type <type>", "\u8C03\u5EA6\u7C7B\u578B\uFF1Acron/delayed/recurring").option("--cron <expr>", "cron \u8868\u8FBE\u5F0F\uFF08cron \u7C7B\u578B\u5FC5\u586B\uFF09").option("--delay <duration>", "\u5EF6\u8FDF\u65F6\u95F4\uFF08delayed \u7C7B\u578B\u5FC5\u586B\uFF09\uFF0C\u5982 30s / 5min / 1h / 2d").option("--interval <duration>", "\u5FAA\u73AF\u95F4\u9694\uFF08recurring \u7C7B\u578B\u5FC5\u586B\uFF09\uFF0C\u5982 1h / 30min / 5s").option("-m, --model <model>", "\u6A21\u578B").option("-c, --category <category>", "\u5206\u7C7B", "general").option("-i, --importance <number>", "\u91CD\u8981\u7A0B\u5EA6 1-5", "3").option("-u, --urgency <number>", "\u7D27\u6025\u7A0B\u5EA6 1-5", "3").option("-b, --batch <batchId>", "\u6A21\u677F\u751F\u6210\u4EFB\u52A1\u7684\u6279\u6B21 ID").option("--max-instances <number>", "\
|
|
28377
|
+
new Command("add").description("\u521B\u5EFA\u8C03\u5EA6\u6A21\u677F").requiredOption("-n, --name <name>", "\u6A21\u677F\u540D\u79F0").requiredOption("-a, --agent <agent>", "Agent \u540D\u79F0").requiredOption("-p, --prompt <prompt>", "\u63D0\u793A\u8BCD").requiredOption("-t, --type <type>", "\u8C03\u5EA6\u7C7B\u578B\uFF1Acron/delayed/recurring").option("--cron <expr>", "cron \u8868\u8FBE\u5F0F\uFF08cron \u7C7B\u578B\u5FC5\u586B\uFF09").option("--delay <duration>", "\u5EF6\u8FDF\u65F6\u95F4\uFF08delayed \u7C7B\u578B\u5FC5\u586B\uFF09\uFF0C\u5982 30s / 5min / 1h / 2d").option("--interval <duration>", "\u5FAA\u73AF\u95F4\u9694\uFF08recurring \u7C7B\u578B\u5FC5\u586B\uFF09\uFF0C\u5982 1h / 30min / 5s").option("-m, --model <model>", "\u6A21\u578B").option("-c, --category <category>", "\u5206\u7C7B", "general").option("-i, --importance <number>", "\u91CD\u8981\u7A0B\u5EA6 1-5", "3").option("-u, --urgency <number>", "\u7D27\u6025\u7A0B\u5EA6 1-5", "3").option("-b, --batch <batchId>", "\u6A21\u677F\u751F\u6210\u4EFB\u52A1\u7684\u6279\u6B21 ID").option("--max-instances <number>", "\u81EA\u52A8\u8C03\u5EA6\u6D3B\u8DC3\u5B9E\u4F8B\u4E0A\u9650\uFF08\u624B\u52A8\u89E6\u53D1\u4E0D\u53D7\u9650\uFF09", "1").option("--max-retries <number>", "\u6700\u5927\u91CD\u8BD5\u6B21\u6570", "3").option("--retry-backoff <duration>", "\u9000\u907F\u57FA\u7840\u95F4\u9694\uFF0C\u5982 30s / 5min", "30s").option("--timeout <duration>", "\u6BCF\u6B21\u4EFB\u52A1\u786C\u8D85\u65F6\uFF0C\u5982 30min / 2h").action(async (options) => withDb(async () => {
|
|
27383
28378
|
let intervalMs = null;
|
|
27384
28379
|
let runAt = null;
|
|
27385
28380
|
const retryBackoffMs = parseDuration(options.retryBackoff);
|
|
@@ -27540,6 +28535,7 @@ program2.command("doctor").description("\u68C0\u67E5 OpenCode\u3001\u6570\u636E\
|
|
|
27540
28535
|
config.watchdog.heartbeatTimeoutMs
|
|
27541
28536
|
);
|
|
27542
28537
|
const gateway = getGatewayDiagnostic();
|
|
28538
|
+
const packageVersion = getPackageVersion();
|
|
27543
28539
|
const opencodeBin2 = process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
27544
28540
|
const opencodeResult = spawnSync4(opencodeBin2, ["--version"], {
|
|
27545
28541
|
encoding: "utf8",
|
|
@@ -27551,6 +28547,7 @@ program2.command("doctor").description("\u68C0\u67E5 OpenCode\u3001\u6570\u636E\
|
|
|
27551
28547
|
version: opencodeResult.status === 0 ? opencodeResult.stdout.trim() : null,
|
|
27552
28548
|
error: opencodeResult.status === 0 ? null : opencodeResult.error?.message || opencodeResult.stderr.trim() || `\u9000\u51FA\u7801 ${opencodeResult.status}`
|
|
27553
28549
|
};
|
|
28550
|
+
const plugin = getOpenCodePluginDiagnostic();
|
|
27554
28551
|
let dashboard = {
|
|
27555
28552
|
enabled: config.dashboard.enabled,
|
|
27556
28553
|
ok: !config.dashboard.enabled,
|
|
@@ -27570,6 +28567,26 @@ program2.command("doctor").description("\u68C0\u67E5 OpenCode\u3001\u6570\u636E\
|
|
|
27570
28567
|
}
|
|
27571
28568
|
}
|
|
27572
28569
|
const warnings = [];
|
|
28570
|
+
const gatewayEntryPinned = gateway.gatewayEntry === null || !/[\\/]opencode-supertask@(latest|next)[\\/]/.test(gateway.gatewayEntry);
|
|
28571
|
+
const gatewayVersionMatchesPackage = gateway.gatewayPackageVersion !== null && gateway.runningVersion === gateway.gatewayPackageVersion;
|
|
28572
|
+
const configuredVersionsMatch = plugin.ok && plugin.version === gateway.gatewayPackageVersion;
|
|
28573
|
+
if (!plugin.ok && plugin.error) {
|
|
28574
|
+
warnings.push(plugin.error);
|
|
28575
|
+
}
|
|
28576
|
+
if (plugin.version !== null && plugin.version !== packageVersion) {
|
|
28577
|
+
warnings.push(`\u5F53\u524D CLI v${packageVersion} \u4E0E OpenCode \u63D2\u4EF6 v${plugin.version} \u4E0D\u4E00\u81F4\uFF1B\u8BF7\u7528\u539F\u5305\u7BA1\u7406\u5668\u5168\u5C40\u5B89\u88C5 opencode-supertask@${plugin.version}\uFF08npm install -g \u6216 bun add -g\uFF09`);
|
|
28578
|
+
}
|
|
28579
|
+
if (!gatewayEntryPinned) {
|
|
28580
|
+
warnings.push(`PM2 Gateway \u4ECD\u4ECE\u6D6E\u52A8\u7F13\u5B58\u8DEF\u5F84\u542F\u52A8\uFF1A${gateway.gatewayEntry}`);
|
|
28581
|
+
}
|
|
28582
|
+
if (gateway.processFound && gateway.gatewayPackageVersion === null) {
|
|
28583
|
+
warnings.push(`\u65E0\u6CD5\u4ECE PM2 Gateway \u5165\u53E3\u786E\u8BA4 opencode-supertask \u5305\u7248\u672C\uFF1A${gateway.gatewayEntry ?? "unknown"}`);
|
|
28584
|
+
} else if (gateway.processFound && !gatewayVersionMatchesPackage) {
|
|
28585
|
+
warnings.push(`Gateway ready \u9501\u7248\u672C ${gateway.runningVersion ?? "unknown"} \u4E0E\u5165\u53E3\u5305\u7248\u672C ${gateway.gatewayPackageVersion ?? "unknown"} \u4E0D\u4E00\u81F4`);
|
|
28586
|
+
}
|
|
28587
|
+
if (plugin.version !== null && gateway.gatewayPackageVersion !== null && plugin.version !== gateway.gatewayPackageVersion) {
|
|
28588
|
+
warnings.push(`OpenCode \u63D2\u4EF6 v${plugin.version} \u4E0E PM2 Gateway v${gateway.gatewayPackageVersion} \u4E0D\u4E00\u81F4\uFF1B\u6267\u884C supertask upgrade`);
|
|
28589
|
+
}
|
|
27573
28590
|
if (gateway.pm2Installed && !gateway.logRotationInstalled) {
|
|
27574
28591
|
warnings.push("\u672A\u68C0\u6D4B\u5230 pm2-logrotate\uFF1B\u957F\u671F\u8FD0\u884C\u524D\u5EFA\u8BAE\u5B89\u88C5\u5E76\u9650\u5236\u65E5\u5FD7\u4FDD\u7559\u91CF");
|
|
27575
28592
|
}
|
|
@@ -27587,12 +28604,13 @@ program2.command("doctor").description("\u68C0\u67E5 OpenCode\u3001\u6570\u636E\
|
|
|
27587
28604
|
`\u65E7\u7248\u9694\u79BB run #${run.runId}\uFF1A${owner}${cancel}\u786E\u8BA4\u6CA1\u6709\u9057\u7559 OpenCode \u8FDB\u7A0B\u540E\u6267\u884C supertask run abandon --id ${run.runId} --confirm ABANDON`
|
|
27588
28605
|
);
|
|
27589
28606
|
}
|
|
27590
|
-
const ok = opencode.ok && database.ok && legacyQuarantinedRuns.length === 0 && gateway.pm2Installed && gateway.status === "online" && gateway.ready && gateway.scopeMatches && gateway.logRotationInstalled && gateway.startupConfigured !== false && dashboard.ok;
|
|
28607
|
+
const ok = opencode.ok && plugin.ok && database.ok && legacyQuarantinedRuns.length === 0 && gateway.pm2Installed && gateway.status === "online" && gateway.ready && gatewayEntryPinned && gatewayVersionMatchesPackage && configuredVersionsMatch && gateway.scopeMatches && gateway.logRotationInstalled && gateway.startupConfigured !== false && dashboard.ok;
|
|
27591
28608
|
const report = {
|
|
27592
28609
|
ok,
|
|
27593
|
-
packageVersion
|
|
28610
|
+
packageVersion,
|
|
27594
28611
|
configPath: getConfigPath(),
|
|
27595
28612
|
opencode,
|
|
28613
|
+
plugin,
|
|
27596
28614
|
database,
|
|
27597
28615
|
legacyQuarantinedRuns,
|
|
27598
28616
|
gateway,
|
|
@@ -27606,8 +28624,9 @@ program2.command("doctor").description("\u68C0\u67E5 OpenCode\u3001\u6570\u636E\
|
|
|
27606
28624
|
const mark = (value) => value ? "\u2713" : "\u2717";
|
|
27607
28625
|
console.log(`SuperTask doctor: ${ok ? "\u6B63\u5E38" : "\u5F02\u5E38"}`);
|
|
27608
28626
|
console.log(`${mark(opencode.ok)} OpenCode ${opencode.version ?? opencode.error ?? "\u4E0D\u53EF\u7528"}`);
|
|
28627
|
+
console.log(`${mark(plugin.ok)} OpenCode \u63D2\u4EF6 ${plugin.spec || plugin.error || "\u672A\u914D\u7F6E"}${plugin.cachedVersion ? `\uFF08\u7F13\u5B58 v${plugin.cachedVersion}\uFF09` : ""}`);
|
|
27609
28628
|
console.log(`${mark(database.ok)} \u6570\u636E\u5E93 ${database.path}\uFF08\u4EFB\u52A1 ${database.counts.tasks}\uFF0C\u8FD0\u884C\u4E2D ${database.runningTasks}\uFF09`);
|
|
27610
|
-
console.log(`${mark(gateway.status === "online" && gateway.ready)} Gateway ${gateway.status ?? "missing"}${gateway.pid ? `\uFF0CPID ${gateway.pid}` : ""}${gateway.runningVersion ? `\uFF0Cv${gateway.runningVersion}` : ""}`);
|
|
28629
|
+
console.log(`${mark(gateway.status === "online" && gateway.ready && gatewayEntryPinned && gatewayVersionMatchesPackage)} Gateway ${gateway.status ?? "missing"}${gateway.pid ? `\uFF0CPID ${gateway.pid}` : ""}${gateway.runningVersion ? `\uFF0Cv${gateway.runningVersion}` : ""}${gateway.gatewayEntry ? `\uFF0C${gateway.gatewayEntry}` : ""}`);
|
|
27611
28630
|
console.log(`${mark(dashboard.ok)} Dashboard ${dashboard.enabled ? dashboard.url : "\u5DF2\u7981\u7528"}`);
|
|
27612
28631
|
for (const warning of warnings) console.log(`! ${warning}`);
|
|
27613
28632
|
}
|
|
@@ -27663,6 +28682,9 @@ program2.command("upgrade").description("Update OpenCode plugin cache and restar
|
|
|
27663
28682
|
console.log(`
|
|
27664
28683
|
SuperTask upgraded: ${result.before ?? "unknown"} \u2192 ${result.after}`);
|
|
27665
28684
|
console.log("Gateway restarted. Please restart opencode to load the new plugin.");
|
|
28685
|
+
if (getPackageVersion() !== installed.version) {
|
|
28686
|
+
console.log(`Global CLI remains v${getPackageVersion()}. Update it with your original package manager to opencode-supertask@${installed.version} (npm install -g or bun add -g).`);
|
|
28687
|
+
}
|
|
27666
28688
|
} catch (err) {
|
|
27667
28689
|
let detail = err instanceof Error ? err.message : String(err);
|
|
27668
28690
|
try {
|