opencode-supertask 0.1.20 → 0.1.22
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 +51 -25
- package/dist/cli/index.js +1374 -581
- package/dist/cli/index.js.map +1 -1
- package/dist/gateway/index.d.ts +3 -1
- package/dist/gateway/index.js +878 -296
- package/dist/gateway/index.js.map +1 -1
- package/dist/plugin/supertask.js +593 -232
- package/dist/plugin/supertask.js.map +1 -1
- package/dist/web/index.js +554 -212
- package/dist/web/index.js.map +1 -1
- package/dist/worker/index.d.ts +19 -7
- package/dist/worker/index.js +442 -151
- package/dist/worker/index.js.map +1 -1
- package/drizzle/0004_reliability_fields.sql +31 -0
- package/drizzle/meta/0004_snapshot.json +85 -13
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
package/dist/web/index.js
CHANGED
|
@@ -10,9 +10,6 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
10
10
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
11
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
12
|
});
|
|
13
|
-
var __esm = (fn, res) => function __init() {
|
|
14
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
15
|
-
};
|
|
16
13
|
var __commonJS = (cb, mod) => function __require2() {
|
|
17
14
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
18
15
|
};
|
|
@@ -237,7 +234,7 @@ var require_CronField = __commonJS({
|
|
|
237
234
|
if (!isValidRange) {
|
|
238
235
|
throw new Error(`${this.constructor.name} Validation error, got value ${badValue} expected range ${this.min}-${this.max}${charsString}`);
|
|
239
236
|
}
|
|
240
|
-
const duplicate = this.#values.find((value,
|
|
237
|
+
const duplicate = this.#values.find((value, index2) => this.#values.indexOf(value) !== index2);
|
|
241
238
|
if (duplicate) {
|
|
242
239
|
throw new Error(`${this.constructor.name} Validation error, duplicate values found: ${duplicate}`);
|
|
243
240
|
}
|
|
@@ -8082,11 +8079,11 @@ var require_CronFieldCollection = __commonJS({
|
|
|
8082
8079
|
throw new Error("Unexpected range end");
|
|
8083
8080
|
}
|
|
8084
8081
|
if (step * multiplier > range.end) {
|
|
8085
|
-
const mapFn = (_,
|
|
8082
|
+
const mapFn = (_, index2) => {
|
|
8086
8083
|
if (typeof range.start !== "number") {
|
|
8087
8084
|
throw new Error("Unexpected range start");
|
|
8088
8085
|
}
|
|
8089
|
-
return
|
|
8086
|
+
return index2 % step === 0 ? range.start + index2 : null;
|
|
8090
8087
|
};
|
|
8091
8088
|
if (typeof range.start !== "number") {
|
|
8092
8089
|
throw new Error("Unexpected range start");
|
|
@@ -8978,9 +8975,9 @@ var require_CronExpressionParser = __commonJS({
|
|
|
8978
8975
|
if (field === CronUnit.DayOfWeek && max % 7 === 0) {
|
|
8979
8976
|
stack.push(0);
|
|
8980
8977
|
}
|
|
8981
|
-
for (let
|
|
8982
|
-
if (stack.indexOf(
|
|
8983
|
-
stack.push(
|
|
8978
|
+
for (let index2 = min; index2 <= max; index2 += repeatInterval) {
|
|
8979
|
+
if (stack.indexOf(index2) === -1) {
|
|
8980
|
+
stack.push(index2);
|
|
8984
8981
|
}
|
|
8985
8982
|
}
|
|
8986
8983
|
return stack;
|
|
@@ -9202,48 +9199,16 @@ var require_dist = __commonJS({
|
|
|
9202
9199
|
}
|
|
9203
9200
|
});
|
|
9204
9201
|
|
|
9205
|
-
// src/core/cron-parser.ts
|
|
9206
|
-
var cron_parser_exports = {};
|
|
9207
|
-
__export(cron_parser_exports, {
|
|
9208
|
-
getNextCronRun: () => getNextCronRun,
|
|
9209
|
-
isValidCronExpr: () => isValidCronExpr
|
|
9210
|
-
});
|
|
9211
|
-
function getNextCronRun(expr, afterMs) {
|
|
9212
|
-
try {
|
|
9213
|
-
const fromDate = afterMs != null ? new Date(afterMs) : /* @__PURE__ */ new Date();
|
|
9214
|
-
const interval = import_cron_parser.CronExpressionParser.parse(expr, { currentDate: fromDate });
|
|
9215
|
-
const next = interval.next();
|
|
9216
|
-
return next.getTime();
|
|
9217
|
-
} catch {
|
|
9218
|
-
return null;
|
|
9219
|
-
}
|
|
9220
|
-
}
|
|
9221
|
-
function isValidCronExpr(expr) {
|
|
9222
|
-
try {
|
|
9223
|
-
import_cron_parser.CronExpressionParser.parse(expr);
|
|
9224
|
-
return true;
|
|
9225
|
-
} catch {
|
|
9226
|
-
return false;
|
|
9227
|
-
}
|
|
9228
|
-
}
|
|
9229
|
-
var import_cron_parser;
|
|
9230
|
-
var init_cron_parser = __esm({
|
|
9231
|
-
"src/core/cron-parser.ts"() {
|
|
9232
|
-
"use strict";
|
|
9233
|
-
import_cron_parser = __toESM(require_dist(), 1);
|
|
9234
|
-
}
|
|
9235
|
-
});
|
|
9236
|
-
|
|
9237
9202
|
// node_modules/hono/dist/compose.js
|
|
9238
9203
|
var compose = (middleware, onError, onNotFound) => {
|
|
9239
9204
|
return (context, next) => {
|
|
9240
|
-
let
|
|
9205
|
+
let index2 = -1;
|
|
9241
9206
|
return dispatch(0);
|
|
9242
9207
|
async function dispatch(i) {
|
|
9243
|
-
if (i <=
|
|
9208
|
+
if (i <= index2) {
|
|
9244
9209
|
throw new Error("next() called multiple times");
|
|
9245
9210
|
}
|
|
9246
|
-
|
|
9211
|
+
index2 = i;
|
|
9247
9212
|
let res;
|
|
9248
9213
|
let isError = false;
|
|
9249
9214
|
let handler;
|
|
@@ -9338,8 +9303,8 @@ var handleParsingAllValues = (form, key, value) => {
|
|
|
9338
9303
|
var handleParsingNestedValues = (form, key, value) => {
|
|
9339
9304
|
let nestedForm = form;
|
|
9340
9305
|
const keys = key.split(".");
|
|
9341
|
-
keys.forEach((key2,
|
|
9342
|
-
if (
|
|
9306
|
+
keys.forEach((key2, index2) => {
|
|
9307
|
+
if (index2 === keys.length - 1) {
|
|
9343
9308
|
nestedForm[key2] = value;
|
|
9344
9309
|
} else {
|
|
9345
9310
|
if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
|
|
@@ -9365,8 +9330,8 @@ var splitRoutingPath = (routePath) => {
|
|
|
9365
9330
|
};
|
|
9366
9331
|
var extractGroupsFromPath = (path) => {
|
|
9367
9332
|
const groups = [];
|
|
9368
|
-
path = path.replace(/\{[^}]+\}/g, (match2,
|
|
9369
|
-
const mark = `@${
|
|
9333
|
+
path = path.replace(/\{[^}]+\}/g, (match2, index2) => {
|
|
9334
|
+
const mark = `@${index2}`;
|
|
9370
9335
|
groups.push([mark, match2]);
|
|
9371
9336
|
return mark;
|
|
9372
9337
|
});
|
|
@@ -9869,10 +9834,10 @@ var escapeToBuffer = (str, buffer) => {
|
|
|
9869
9834
|
return;
|
|
9870
9835
|
}
|
|
9871
9836
|
let escape;
|
|
9872
|
-
let
|
|
9837
|
+
let index2;
|
|
9873
9838
|
let lastIndex = 0;
|
|
9874
|
-
for (
|
|
9875
|
-
switch (str.charCodeAt(
|
|
9839
|
+
for (index2 = match2; index2 < str.length; index2++) {
|
|
9840
|
+
switch (str.charCodeAt(index2)) {
|
|
9876
9841
|
case 34:
|
|
9877
9842
|
escape = """;
|
|
9878
9843
|
break;
|
|
@@ -9891,10 +9856,10 @@ var escapeToBuffer = (str, buffer) => {
|
|
|
9891
9856
|
default:
|
|
9892
9857
|
continue;
|
|
9893
9858
|
}
|
|
9894
|
-
buffer[0] += str.substring(lastIndex,
|
|
9895
|
-
lastIndex =
|
|
9859
|
+
buffer[0] += str.substring(lastIndex, index2) + escape;
|
|
9860
|
+
lastIndex = index2 + 1;
|
|
9896
9861
|
}
|
|
9897
|
-
buffer[0] += str.substring(lastIndex,
|
|
9862
|
+
buffer[0] += str.substring(lastIndex, index2);
|
|
9898
9863
|
};
|
|
9899
9864
|
var resolveCallbackSync = (str) => {
|
|
9900
9865
|
const callbacks = str.callbacks;
|
|
@@ -10738,8 +10703,8 @@ function match(method, path) {
|
|
|
10738
10703
|
if (!match3) {
|
|
10739
10704
|
return [[], emptyParam];
|
|
10740
10705
|
}
|
|
10741
|
-
const
|
|
10742
|
-
return [matcher[1][
|
|
10706
|
+
const index2 = match3.indexOf("", 1);
|
|
10707
|
+
return [matcher[1][index2], match3];
|
|
10743
10708
|
});
|
|
10744
10709
|
this.match = match2;
|
|
10745
10710
|
return match2(method, path);
|
|
@@ -10774,7 +10739,7 @@ var Node = class _Node {
|
|
|
10774
10739
|
#index;
|
|
10775
10740
|
#varIndex;
|
|
10776
10741
|
#children = /* @__PURE__ */ Object.create(null);
|
|
10777
|
-
insert(tokens,
|
|
10742
|
+
insert(tokens, index2, paramMap, context, pathErrorCheckOnly) {
|
|
10778
10743
|
if (tokens.length === 0) {
|
|
10779
10744
|
if (this.#index !== void 0) {
|
|
10780
10745
|
throw PATH_ERROR;
|
|
@@ -10782,7 +10747,7 @@ var Node = class _Node {
|
|
|
10782
10747
|
if (pathErrorCheckOnly) {
|
|
10783
10748
|
return;
|
|
10784
10749
|
}
|
|
10785
|
-
this.#index =
|
|
10750
|
+
this.#index = index2;
|
|
10786
10751
|
return;
|
|
10787
10752
|
}
|
|
10788
10753
|
const [token, ...restTokens] = tokens;
|
|
@@ -10832,7 +10797,7 @@ var Node = class _Node {
|
|
|
10832
10797
|
node = this.#children[token] = new _Node();
|
|
10833
10798
|
}
|
|
10834
10799
|
}
|
|
10835
|
-
node.insert(restTokens,
|
|
10800
|
+
node.insert(restTokens, index2, paramMap, context, pathErrorCheckOnly);
|
|
10836
10801
|
}
|
|
10837
10802
|
buildRegExpStr() {
|
|
10838
10803
|
const childKeys = Object.keys(this.#children).sort(compareKey);
|
|
@@ -10857,7 +10822,7 @@ var Node = class _Node {
|
|
|
10857
10822
|
var Trie = class {
|
|
10858
10823
|
#context = { varIndex: 0 };
|
|
10859
10824
|
#root = new Node();
|
|
10860
|
-
insert(path,
|
|
10825
|
+
insert(path, index2, pathErrorCheckOnly) {
|
|
10861
10826
|
const paramAssoc = [];
|
|
10862
10827
|
const groups = [];
|
|
10863
10828
|
for (let i = 0; ; ) {
|
|
@@ -10883,7 +10848,7 @@ var Trie = class {
|
|
|
10883
10848
|
}
|
|
10884
10849
|
}
|
|
10885
10850
|
}
|
|
10886
|
-
this.#root.insert(tokens,
|
|
10851
|
+
this.#root.insert(tokens, index2, paramAssoc, this.#context, pathErrorCheckOnly);
|
|
10887
10852
|
return paramAssoc;
|
|
10888
10853
|
}
|
|
10889
10854
|
buildRegExp() {
|
|
@@ -12576,8 +12541,8 @@ function haveSameKeys(left, right) {
|
|
|
12576
12541
|
if (leftKeys.length !== rightKeys.length) {
|
|
12577
12542
|
return false;
|
|
12578
12543
|
}
|
|
12579
|
-
for (const [
|
|
12580
|
-
if (key !== rightKeys[
|
|
12544
|
+
for (const [index2, key] of leftKeys.entries()) {
|
|
12545
|
+
if (key !== rightKeys[index2]) {
|
|
12581
12546
|
return false;
|
|
12582
12547
|
}
|
|
12583
12548
|
}
|
|
@@ -14143,8 +14108,8 @@ var SQLiteDialect = class {
|
|
|
14143
14108
|
}
|
|
14144
14109
|
const joinsArray = [];
|
|
14145
14110
|
if (joins) {
|
|
14146
|
-
for (const [
|
|
14147
|
-
if (
|
|
14111
|
+
for (const [index2, joinMeta] of joins.entries()) {
|
|
14112
|
+
if (index2 === 0) {
|
|
14148
14113
|
joinsArray.push(sql` `);
|
|
14149
14114
|
}
|
|
14150
14115
|
const table = joinMeta.table;
|
|
@@ -14161,7 +14126,7 @@ var SQLiteDialect = class {
|
|
|
14161
14126
|
sql`${sql.raw(joinMeta.joinType)} join ${table} on ${joinMeta.on}`
|
|
14162
14127
|
);
|
|
14163
14128
|
}
|
|
14164
|
-
if (
|
|
14129
|
+
if (index2 < joins.length - 1) {
|
|
14165
14130
|
joinsArray.push(sql` `);
|
|
14166
14131
|
}
|
|
14167
14132
|
}
|
|
@@ -14174,9 +14139,9 @@ var SQLiteDialect = class {
|
|
|
14174
14139
|
buildOrderBy(orderBy) {
|
|
14175
14140
|
const orderByList = [];
|
|
14176
14141
|
if (orderBy) {
|
|
14177
|
-
for (const [
|
|
14142
|
+
for (const [index2, orderByValue] of orderBy.entries()) {
|
|
14178
14143
|
orderByList.push(orderByValue);
|
|
14179
|
-
if (
|
|
14144
|
+
if (index2 < orderBy.length - 1) {
|
|
14180
14145
|
orderByList.push(sql`, `);
|
|
14181
14146
|
}
|
|
14182
14147
|
}
|
|
@@ -14225,9 +14190,9 @@ var SQLiteDialect = class {
|
|
|
14225
14190
|
const havingSql = having ? sql` having ${having}` : void 0;
|
|
14226
14191
|
const groupByList = [];
|
|
14227
14192
|
if (groupBy) {
|
|
14228
|
-
for (const [
|
|
14193
|
+
for (const [index2, groupByValue] of groupBy.entries()) {
|
|
14229
14194
|
groupByList.push(groupByValue);
|
|
14230
|
-
if (
|
|
14195
|
+
if (index2 < groupBy.length - 1) {
|
|
14231
14196
|
groupByList.push(sql`, `);
|
|
14232
14197
|
}
|
|
14233
14198
|
}
|
|
@@ -16155,6 +16120,52 @@ var BaseSQLiteDatabase = class {
|
|
|
16155
16120
|
}
|
|
16156
16121
|
};
|
|
16157
16122
|
|
|
16123
|
+
// node_modules/drizzle-orm/sqlite-core/indexes.js
|
|
16124
|
+
var IndexBuilderOn = class {
|
|
16125
|
+
constructor(name, unique) {
|
|
16126
|
+
this.name = name;
|
|
16127
|
+
this.unique = unique;
|
|
16128
|
+
}
|
|
16129
|
+
static [entityKind] = "SQLiteIndexBuilderOn";
|
|
16130
|
+
on(...columns) {
|
|
16131
|
+
return new IndexBuilder(this.name, columns, this.unique);
|
|
16132
|
+
}
|
|
16133
|
+
};
|
|
16134
|
+
var IndexBuilder = class {
|
|
16135
|
+
static [entityKind] = "SQLiteIndexBuilder";
|
|
16136
|
+
/** @internal */
|
|
16137
|
+
config;
|
|
16138
|
+
constructor(name, columns, unique) {
|
|
16139
|
+
this.config = {
|
|
16140
|
+
name,
|
|
16141
|
+
columns,
|
|
16142
|
+
unique,
|
|
16143
|
+
where: void 0
|
|
16144
|
+
};
|
|
16145
|
+
}
|
|
16146
|
+
/**
|
|
16147
|
+
* Condition for partial index.
|
|
16148
|
+
*/
|
|
16149
|
+
where(condition) {
|
|
16150
|
+
this.config.where = condition;
|
|
16151
|
+
return this;
|
|
16152
|
+
}
|
|
16153
|
+
/** @internal */
|
|
16154
|
+
build(table) {
|
|
16155
|
+
return new Index(this.config, table);
|
|
16156
|
+
}
|
|
16157
|
+
};
|
|
16158
|
+
var Index = class {
|
|
16159
|
+
static [entityKind] = "SQLiteIndex";
|
|
16160
|
+
config;
|
|
16161
|
+
constructor(config, table) {
|
|
16162
|
+
this.config = { ...config, table };
|
|
16163
|
+
}
|
|
16164
|
+
};
|
|
16165
|
+
function index(name) {
|
|
16166
|
+
return new IndexBuilderOn(name, false);
|
|
16167
|
+
}
|
|
16168
|
+
|
|
16158
16169
|
// node_modules/drizzle-orm/sqlite-core/session.js
|
|
16159
16170
|
var ExecuteResultSync = class extends QueryPromise {
|
|
16160
16171
|
constructor(resultCb) {
|
|
@@ -16501,12 +16512,17 @@ var tasks = sqliteTable("tasks", {
|
|
|
16501
16512
|
resultLog: text("result_log"),
|
|
16502
16513
|
retryCount: integer("retry_count").default(0),
|
|
16503
16514
|
maxRetries: integer("max_retries").default(3),
|
|
16515
|
+
retryBackoffMs: integer("retry_backoff_ms").default(3e4),
|
|
16504
16516
|
// Gateway 扩展字段(毫秒)
|
|
16505
16517
|
retryAfter: integer("retry_after"),
|
|
16506
16518
|
timeoutMs: integer("timeout_ms"),
|
|
16507
16519
|
templateId: integer("template_id"),
|
|
16508
16520
|
scheduledAt: integer("scheduled_at")
|
|
16509
|
-
})
|
|
16521
|
+
}, (table) => [
|
|
16522
|
+
index("tasks_queue_idx").on(table.status, table.retryAfter, table.urgency, table.importance, table.createdAt, table.id),
|
|
16523
|
+
index("tasks_batch_status_idx").on(table.batchId, table.status),
|
|
16524
|
+
index("tasks_template_status_idx").on(table.templateId, table.status)
|
|
16525
|
+
]);
|
|
16510
16526
|
var TASK_CATEGORIES = [
|
|
16511
16527
|
"translate",
|
|
16512
16528
|
"generate",
|
|
@@ -16516,7 +16532,7 @@ var TASK_CATEGORIES = [
|
|
|
16516
16532
|
];
|
|
16517
16533
|
var taskRuns = sqliteTable("task_runs", {
|
|
16518
16534
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
16519
|
-
taskId: integer("task_id").notNull().references(() => tasks.id),
|
|
16535
|
+
taskId: integer("task_id").notNull().references(() => tasks.id, { onDelete: "cascade" }),
|
|
16520
16536
|
sessionId: text("session_id"),
|
|
16521
16537
|
model: text("model"),
|
|
16522
16538
|
status: text("status").default("running"),
|
|
@@ -16529,7 +16545,10 @@ var taskRuns = sqliteTable("task_runs", {
|
|
|
16529
16545
|
heartbeatAt: integer("heartbeat_at"),
|
|
16530
16546
|
workerPid: integer("worker_pid"),
|
|
16531
16547
|
childPid: integer("child_pid")
|
|
16532
|
-
})
|
|
16548
|
+
}, (table) => [
|
|
16549
|
+
index("task_runs_task_started_idx").on(table.taskId, table.startedAt, table.id),
|
|
16550
|
+
index("task_runs_status_heartbeat_idx").on(table.status, table.heartbeatAt)
|
|
16551
|
+
]);
|
|
16533
16552
|
var taskTemplates = sqliteTable("task_templates", {
|
|
16534
16553
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
16535
16554
|
name: text("name").notNull(),
|
|
@@ -16540,6 +16559,7 @@ var taskTemplates = sqliteTable("task_templates", {
|
|
|
16540
16559
|
category: text("category").default("general"),
|
|
16541
16560
|
importance: integer("importance").default(3),
|
|
16542
16561
|
urgency: integer("urgency").default(3),
|
|
16562
|
+
batchId: text("batch_id"),
|
|
16543
16563
|
scheduleType: text("schedule_type").notNull(),
|
|
16544
16564
|
cronExpr: text("cron_expr"),
|
|
16545
16565
|
intervalMs: integer("interval_ms"),
|
|
@@ -16547,12 +16567,15 @@ var taskTemplates = sqliteTable("task_templates", {
|
|
|
16547
16567
|
maxInstances: integer("max_instances").default(1),
|
|
16548
16568
|
maxRetries: integer("max_retries").default(3),
|
|
16549
16569
|
retryBackoffMs: integer("retry_backoff_ms").default(3e4),
|
|
16570
|
+
timeoutMs: integer("timeout_ms"),
|
|
16550
16571
|
lastRunAt: integer("last_run_at"),
|
|
16551
16572
|
nextRunAt: integer("next_run_at"),
|
|
16552
16573
|
enabled: integer("enabled", { mode: "boolean" }).default(true),
|
|
16553
16574
|
createdAt: integer("created_at").default(0),
|
|
16554
16575
|
updatedAt: integer("updated_at").default(0)
|
|
16555
|
-
})
|
|
16576
|
+
}, (table) => [
|
|
16577
|
+
index("task_templates_due_idx").on(table.enabled, table.nextRunAt, table.id)
|
|
16578
|
+
]);
|
|
16556
16579
|
|
|
16557
16580
|
// src/core/db/index.ts
|
|
16558
16581
|
import { existsSync, mkdirSync } from "fs";
|
|
@@ -16589,16 +16612,30 @@ function initDb() {
|
|
|
16589
16612
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
16590
16613
|
pid INTEGER NOT NULL,
|
|
16591
16614
|
acquired_at INTEGER NOT NULL,
|
|
16592
|
-
heartbeat_at INTEGER NOT NULL
|
|
16615
|
+
heartbeat_at INTEGER NOT NULL,
|
|
16616
|
+
ready_at INTEGER
|
|
16593
16617
|
);
|
|
16594
16618
|
`);
|
|
16619
|
+
const gatewayLockColumns = _sqlite.query("PRAGMA table_info(gateway_lock)").all();
|
|
16620
|
+
if (!gatewayLockColumns.some((column) => column.name === "ready_at")) {
|
|
16621
|
+
_sqlite.exec("ALTER TABLE gateway_lock ADD COLUMN ready_at INTEGER;");
|
|
16622
|
+
}
|
|
16595
16623
|
_db = drizzle(_sqlite, { schema: schema_exports });
|
|
16596
16624
|
if (!_migrationRan) {
|
|
16597
|
-
_migrationRan = true;
|
|
16598
16625
|
try {
|
|
16599
16626
|
migrate(_db, { migrationsFolder: getMigrationsFolder() });
|
|
16627
|
+
_sqlite.exec("PRAGMA foreign_keys = ON;");
|
|
16628
|
+
const violations = _sqlite.query("PRAGMA foreign_key_check;").all();
|
|
16629
|
+
if (violations.length > 0) {
|
|
16630
|
+
throw new Error(`\u68C0\u6D4B\u5230 ${violations.length} \u6761\u5B64\u7ACB\u5173\u8054\u8BB0\u5F55\uFF0C\u8BF7\u5148\u4FEE\u590D\u6570\u636E\u518D\u542F\u52A8`);
|
|
16631
|
+
}
|
|
16632
|
+
_migrationRan = true;
|
|
16600
16633
|
} catch (err) {
|
|
16601
16634
|
const msg = err instanceof Error ? err.message : String(err);
|
|
16635
|
+
_migrationRan = false;
|
|
16636
|
+
_sqlite.close();
|
|
16637
|
+
_sqlite = null;
|
|
16638
|
+
_db = null;
|
|
16602
16639
|
console.error(`[supertask] migration failed: ${msg}`);
|
|
16603
16640
|
throw new Error(`[supertask] DB migration failed: ${msg}`);
|
|
16604
16641
|
}
|
|
@@ -16637,7 +16674,7 @@ function computeBackoff(retryCount, baseMs = 3e4, maxMs = MAX_BACKOFF_MS) {
|
|
|
16637
16674
|
}
|
|
16638
16675
|
|
|
16639
16676
|
// src/core/services/task.service.ts
|
|
16640
|
-
var { tasks: tasks2 } = schema_exports;
|
|
16677
|
+
var { tasks: tasks2, taskRuns: taskRuns2 } = schema_exports;
|
|
16641
16678
|
var TaskService = class {
|
|
16642
16679
|
static buildScopeWhere(scope) {
|
|
16643
16680
|
const conditions = [];
|
|
@@ -16647,9 +16684,27 @@ var TaskService = class {
|
|
|
16647
16684
|
return conditions;
|
|
16648
16685
|
}
|
|
16649
16686
|
static async add(data) {
|
|
16687
|
+
this.validateNewTask(data);
|
|
16650
16688
|
const result = await db.insert(tasks2).values(data).returning();
|
|
16651
16689
|
return result[0];
|
|
16652
16690
|
}
|
|
16691
|
+
static validateNewTask(data) {
|
|
16692
|
+
if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16693
|
+
if (!data.agent.trim()) throw new Error("agent \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16694
|
+
if (!data.prompt.trim()) throw new Error("prompt \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16695
|
+
this.validateInteger("importance", data.importance, 1, 5);
|
|
16696
|
+
this.validateInteger("urgency", data.urgency, 1, 5);
|
|
16697
|
+
this.validateInteger("maxRetries", data.maxRetries, 0, 1e3);
|
|
16698
|
+
this.validateInteger("retryBackoffMs", data.retryBackoffMs, 0, 864e5);
|
|
16699
|
+
this.validateInteger("timeoutMs", data.timeoutMs, 1e3, 6048e5);
|
|
16700
|
+
this.validateInteger("dependsOn", data.dependsOn, 1, Number.MAX_SAFE_INTEGER);
|
|
16701
|
+
}
|
|
16702
|
+
static validateInteger(name, value, min, max) {
|
|
16703
|
+
if (value === void 0 || value === null) return;
|
|
16704
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
16705
|
+
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
16706
|
+
}
|
|
16707
|
+
}
|
|
16653
16708
|
static async next(scope = {}) {
|
|
16654
16709
|
const baseConditions = [...this.buildScopeWhere(scope)];
|
|
16655
16710
|
const nowMs = Date.now();
|
|
@@ -16672,7 +16727,7 @@ var TaskService = class {
|
|
|
16672
16727
|
),
|
|
16673
16728
|
and(
|
|
16674
16729
|
eq(tasks2.status, "failed"),
|
|
16675
|
-
sql`${tasks2.retryCount}
|
|
16730
|
+
sql`${tasks2.retryCount} <= ${tasks2.maxRetries}`,
|
|
16676
16731
|
retryAfterFilter
|
|
16677
16732
|
)
|
|
16678
16733
|
);
|
|
@@ -16686,7 +16741,8 @@ var TaskService = class {
|
|
|
16686
16741
|
const allTasks = await db.select().from(tasks2).where(and(...conditions)).orderBy(
|
|
16687
16742
|
desc(tasks2.urgency),
|
|
16688
16743
|
desc(tasks2.importance),
|
|
16689
|
-
asc(tasks2.createdAt)
|
|
16744
|
+
asc(tasks2.createdAt),
|
|
16745
|
+
asc(tasks2.id)
|
|
16690
16746
|
);
|
|
16691
16747
|
for (const task of allTasks) {
|
|
16692
16748
|
if (task.dependsOn) {
|
|
@@ -16707,7 +16763,7 @@ var TaskService = class {
|
|
|
16707
16763
|
eq(tasks2.status, "pending"),
|
|
16708
16764
|
and(
|
|
16709
16765
|
eq(tasks2.status, "failed"),
|
|
16710
|
-
sql`${tasks2.retryCount}
|
|
16766
|
+
sql`${tasks2.retryCount} <= ${tasks2.maxRetries}`
|
|
16711
16767
|
)
|
|
16712
16768
|
),
|
|
16713
16769
|
...this.buildScopeWhere(scope)
|
|
@@ -16720,7 +16776,11 @@ var TaskService = class {
|
|
|
16720
16776
|
return result[0] || null;
|
|
16721
16777
|
}
|
|
16722
16778
|
static async done(id, log, scope = {}) {
|
|
16723
|
-
const conditions = [
|
|
16779
|
+
const conditions = [
|
|
16780
|
+
eq(tasks2.id, id),
|
|
16781
|
+
eq(tasks2.status, "running"),
|
|
16782
|
+
...this.buildScopeWhere(scope)
|
|
16783
|
+
];
|
|
16724
16784
|
const result = await db.update(tasks2).set({
|
|
16725
16785
|
status: "done",
|
|
16726
16786
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
@@ -16731,17 +16791,24 @@ var TaskService = class {
|
|
|
16731
16791
|
}
|
|
16732
16792
|
static async fail(id, log, scope = {}, options) {
|
|
16733
16793
|
const current = await this.getById(id, scope);
|
|
16734
|
-
if (!current) return null;
|
|
16794
|
+
if (!current || current.status !== "running") return null;
|
|
16735
16795
|
const newRetryCount = (current.retryCount ?? 0) + 1;
|
|
16736
16796
|
const maxRetries = current.maxRetries ?? 3;
|
|
16737
|
-
const isDeadLetter = options?.setDeadLetter ?? newRetryCount
|
|
16738
|
-
const conditions = [
|
|
16797
|
+
const isDeadLetter = options?.setDeadLetter ?? newRetryCount > maxRetries;
|
|
16798
|
+
const conditions = [
|
|
16799
|
+
eq(tasks2.id, id),
|
|
16800
|
+
eq(tasks2.status, "running"),
|
|
16801
|
+
...this.buildScopeWhere(scope)
|
|
16802
|
+
];
|
|
16739
16803
|
const result = await db.update(tasks2).set({
|
|
16740
16804
|
status: isDeadLetter ? "dead_letter" : "failed",
|
|
16741
16805
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
16742
16806
|
resultLog: log,
|
|
16743
16807
|
retryCount: newRetryCount,
|
|
16744
|
-
retryAfter: isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
|
|
16808
|
+
retryAfter: isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
|
|
16809
|
+
newRetryCount,
|
|
16810
|
+
current.retryBackoffMs ?? 3e4
|
|
16811
|
+
)
|
|
16745
16812
|
}).where(and(...conditions)).returning();
|
|
16746
16813
|
return result[0] || null;
|
|
16747
16814
|
}
|
|
@@ -16773,9 +16840,38 @@ var TaskService = class {
|
|
|
16773
16840
|
).returning();
|
|
16774
16841
|
return result.length;
|
|
16775
16842
|
}
|
|
16843
|
+
static async resetOrphanRunningToPending() {
|
|
16844
|
+
const result = await db.update(tasks2).set({
|
|
16845
|
+
status: "pending",
|
|
16846
|
+
startedAt: null,
|
|
16847
|
+
finishedAt: null
|
|
16848
|
+
}).where(
|
|
16849
|
+
and(
|
|
16850
|
+
eq(tasks2.status, "running"),
|
|
16851
|
+
sql`NOT EXISTS (
|
|
16852
|
+
SELECT 1 FROM ${taskRuns2}
|
|
16853
|
+
WHERE ${taskRuns2.taskId} = ${tasks2.id}
|
|
16854
|
+
AND ${taskRuns2.status} = 'running'
|
|
16855
|
+
)`
|
|
16856
|
+
)
|
|
16857
|
+
).returning();
|
|
16858
|
+
return result.length;
|
|
16859
|
+
}
|
|
16776
16860
|
static async cancel(id, scope = {}) {
|
|
16777
|
-
const conditions = [
|
|
16778
|
-
|
|
16861
|
+
const conditions = [
|
|
16862
|
+
eq(tasks2.id, id),
|
|
16863
|
+
or(
|
|
16864
|
+
eq(tasks2.status, "pending"),
|
|
16865
|
+
eq(tasks2.status, "running"),
|
|
16866
|
+
eq(tasks2.status, "failed")
|
|
16867
|
+
),
|
|
16868
|
+
...this.buildScopeWhere(scope)
|
|
16869
|
+
];
|
|
16870
|
+
const result = await db.update(tasks2).set({
|
|
16871
|
+
status: "cancelled",
|
|
16872
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
16873
|
+
retryAfter: null
|
|
16874
|
+
}).where(and(...conditions)).returning();
|
|
16779
16875
|
return result[0] || null;
|
|
16780
16876
|
}
|
|
16781
16877
|
static async retry(id, scope = {}) {
|
|
@@ -16787,7 +16883,8 @@ var TaskService = class {
|
|
|
16787
16883
|
status: "pending",
|
|
16788
16884
|
startedAt: null,
|
|
16789
16885
|
finishedAt: null,
|
|
16790
|
-
retryAfter: null
|
|
16886
|
+
retryAfter: null,
|
|
16887
|
+
retryCount: 0
|
|
16791
16888
|
}).where(and(...conditions)).returning();
|
|
16792
16889
|
return result[0] || null;
|
|
16793
16890
|
}
|
|
@@ -16801,7 +16898,8 @@ var TaskService = class {
|
|
|
16801
16898
|
status: "pending",
|
|
16802
16899
|
startedAt: null,
|
|
16803
16900
|
finishedAt: null,
|
|
16804
|
-
retryAfter: null
|
|
16901
|
+
retryAfter: null,
|
|
16902
|
+
retryCount: 0
|
|
16805
16903
|
}).where(and(...conditions)).returning();
|
|
16806
16904
|
return result.length;
|
|
16807
16905
|
}
|
|
@@ -16869,6 +16967,9 @@ var TaskService = class {
|
|
|
16869
16967
|
}
|
|
16870
16968
|
static async delete(id, scope = {}) {
|
|
16871
16969
|
const conditions = [eq(tasks2.id, id), ...this.buildScopeWhere(scope)];
|
|
16970
|
+
const existing = await db.select({ id: tasks2.id }).from(tasks2).where(and(...conditions)).limit(1);
|
|
16971
|
+
if (!existing[0]) return false;
|
|
16972
|
+
await db.delete(taskRuns2).where(eq(taskRuns2.taskId, id));
|
|
16872
16973
|
const result = await db.delete(tasks2).where(and(...conditions)).returning();
|
|
16873
16974
|
return result.length > 0;
|
|
16874
16975
|
}
|
|
@@ -16886,59 +16987,59 @@ var TaskService = class {
|
|
|
16886
16987
|
};
|
|
16887
16988
|
|
|
16888
16989
|
// src/core/services/task-run.service.ts
|
|
16889
|
-
var { taskRuns:
|
|
16990
|
+
var { taskRuns: taskRuns3 } = schema_exports;
|
|
16890
16991
|
var TaskRunService = class {
|
|
16891
16992
|
static async create(data) {
|
|
16892
|
-
const result = await db.insert(
|
|
16993
|
+
const result = await db.insert(taskRuns3).values(data).returning();
|
|
16893
16994
|
return result[0];
|
|
16894
16995
|
}
|
|
16895
16996
|
static async updateSessionId(id, sessionId) {
|
|
16896
|
-
const result = await db.update(
|
|
16997
|
+
const result = await db.update(taskRuns3).set({ sessionId }).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16897
16998
|
return result[0] || null;
|
|
16898
16999
|
}
|
|
16899
17000
|
static async done(id, log) {
|
|
16900
|
-
const result = await db.update(
|
|
17001
|
+
const result = await db.update(taskRuns3).set({
|
|
16901
17002
|
status: "done",
|
|
16902
17003
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
16903
17004
|
log
|
|
16904
|
-
}).where(eq(
|
|
17005
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16905
17006
|
return result[0] || null;
|
|
16906
17007
|
}
|
|
16907
17008
|
static async fail(id, log) {
|
|
16908
|
-
const result = await db.update(
|
|
17009
|
+
const result = await db.update(taskRuns3).set({
|
|
16909
17010
|
status: "failed",
|
|
16910
17011
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
16911
17012
|
log
|
|
16912
|
-
}).where(eq(
|
|
17013
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16913
17014
|
return result[0] || null;
|
|
16914
17015
|
}
|
|
16915
17016
|
static async heartbeat(id) {
|
|
16916
|
-
const result = await db.update(
|
|
17017
|
+
const result = await db.update(taskRuns3).set({ heartbeatAt: Date.now() }).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16917
17018
|
return result[0] || null;
|
|
16918
17019
|
}
|
|
16919
17020
|
static async updatePid(id, workerPid, childPid) {
|
|
16920
|
-
const result = await db.update(
|
|
17021
|
+
const result = await db.update(taskRuns3).set({
|
|
16921
17022
|
workerPid,
|
|
16922
17023
|
childPid,
|
|
16923
17024
|
lockedAt: Date.now(),
|
|
16924
17025
|
lockedBy: `gateway-${process.pid}`
|
|
16925
|
-
}).where(eq(
|
|
17026
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16926
17027
|
return result[0] || null;
|
|
16927
17028
|
}
|
|
16928
17029
|
static async getById(id) {
|
|
16929
|
-
const result = await db.select().from(
|
|
17030
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.id, id));
|
|
16930
17031
|
return result[0] || null;
|
|
16931
17032
|
}
|
|
16932
17033
|
static async listByTaskId(taskId) {
|
|
16933
|
-
return await db.select().from(
|
|
17034
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
16934
17035
|
}
|
|
16935
17036
|
static async getLatestByTaskId(taskId) {
|
|
16936
|
-
const result = await db.select().from(
|
|
17037
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id)).limit(1);
|
|
16937
17038
|
return result[0] || null;
|
|
16938
17039
|
}
|
|
16939
17040
|
static async getLatestByTaskIds(taskIds) {
|
|
16940
17041
|
if (taskIds.length === 0) return /* @__PURE__ */ new Map();
|
|
16941
|
-
const latestRuns = await db.select().from(
|
|
17042
|
+
const latestRuns = await db.select().from(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
16942
17043
|
const result = /* @__PURE__ */ new Map();
|
|
16943
17044
|
for (const run of latestRuns) {
|
|
16944
17045
|
if (!result.has(run.taskId)) {
|
|
@@ -16952,22 +17053,23 @@ var TaskRunService = class {
|
|
|
16952
17053
|
const cutoffSec = Math.floor(cutoffMs / 1e3);
|
|
16953
17054
|
const { tasks: tasksTable } = schema_exports;
|
|
16954
17055
|
const result = await db.select({
|
|
16955
|
-
runId:
|
|
16956
|
-
taskId:
|
|
16957
|
-
childPid:
|
|
17056
|
+
runId: taskRuns3.id,
|
|
17057
|
+
taskId: taskRuns3.taskId,
|
|
17058
|
+
childPid: taskRuns3.childPid,
|
|
16958
17059
|
taskRetryCount: tasksTable.retryCount,
|
|
16959
|
-
taskMaxRetries: tasksTable.maxRetries
|
|
16960
|
-
|
|
17060
|
+
taskMaxRetries: tasksTable.maxRetries,
|
|
17061
|
+
taskRetryBackoffMs: tasksTable.retryBackoffMs
|
|
17062
|
+
}).from(taskRuns3).innerJoin(tasksTable, eq(taskRuns3.taskId, tasksTable.id)).where(
|
|
16961
17063
|
and(
|
|
16962
|
-
eq(
|
|
17064
|
+
eq(taskRuns3.status, "running"),
|
|
16963
17065
|
or(
|
|
16964
17066
|
and(
|
|
16965
|
-
sql`${
|
|
16966
|
-
sql`${
|
|
17067
|
+
sql`${taskRuns3.heartbeatAt} IS NULL`,
|
|
17068
|
+
sql`${taskRuns3.startedAt} < ${cutoffSec}`
|
|
16967
17069
|
),
|
|
16968
17070
|
and(
|
|
16969
|
-
sql`${
|
|
16970
|
-
sql`${
|
|
17071
|
+
sql`${taskRuns3.heartbeatAt} IS NOT NULL`,
|
|
17072
|
+
sql`${taskRuns3.heartbeatAt} < ${cutoffMs}`
|
|
16971
17073
|
)
|
|
16972
17074
|
)
|
|
16973
17075
|
)
|
|
@@ -16977,28 +17079,50 @@ var TaskRunService = class {
|
|
|
16977
17079
|
taskId: row.taskId,
|
|
16978
17080
|
childPid: row.childPid,
|
|
16979
17081
|
taskRetryCount: row.taskRetryCount ?? 0,
|
|
16980
|
-
taskMaxRetries: row.taskMaxRetries ?? 3
|
|
17082
|
+
taskMaxRetries: row.taskMaxRetries ?? 3,
|
|
17083
|
+
taskRetryBackoffMs: row.taskRetryBackoffMs ?? 3e4
|
|
16981
17084
|
}));
|
|
16982
17085
|
}
|
|
16983
17086
|
static async getRunningRunByTaskId(taskId) {
|
|
16984
|
-
const result = await db.select().from(
|
|
17087
|
+
const result = await db.select().from(taskRuns3).where(and(eq(taskRuns3.taskId, taskId), eq(taskRuns3.status, "running"))).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id)).limit(1);
|
|
16985
17088
|
return result[0] || null;
|
|
16986
17089
|
}
|
|
16987
17090
|
static async deleteByTaskIds(taskIds) {
|
|
16988
17091
|
if (taskIds.length === 0) return 0;
|
|
16989
|
-
const result = await db.delete(
|
|
17092
|
+
const result = await db.delete(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).returning();
|
|
16990
17093
|
return result.length;
|
|
16991
17094
|
}
|
|
16992
17095
|
static async getAllRunningRuns() {
|
|
16993
|
-
return await db.select().from(
|
|
17096
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.status, "running"));
|
|
16994
17097
|
}
|
|
16995
17098
|
};
|
|
16996
17099
|
|
|
17100
|
+
// src/core/cron-parser.ts
|
|
17101
|
+
var import_cron_parser = __toESM(require_dist(), 1);
|
|
17102
|
+
function getNextCronRun(expr, afterMs) {
|
|
17103
|
+
try {
|
|
17104
|
+
const fromDate = afterMs != null ? new Date(afterMs) : /* @__PURE__ */ new Date();
|
|
17105
|
+
const interval = import_cron_parser.CronExpressionParser.parse(expr, { currentDate: fromDate });
|
|
17106
|
+
const next = interval.next();
|
|
17107
|
+
return next.getTime();
|
|
17108
|
+
} catch {
|
|
17109
|
+
return null;
|
|
17110
|
+
}
|
|
17111
|
+
}
|
|
17112
|
+
function isValidCronExpr(expr) {
|
|
17113
|
+
try {
|
|
17114
|
+
import_cron_parser.CronExpressionParser.parse(expr);
|
|
17115
|
+
return true;
|
|
17116
|
+
} catch {
|
|
17117
|
+
return false;
|
|
17118
|
+
}
|
|
17119
|
+
}
|
|
17120
|
+
|
|
16997
17121
|
// src/core/services/task-template.service.ts
|
|
16998
|
-
init_cron_parser();
|
|
16999
17122
|
var { taskTemplates: taskTemplates2 } = schema_exports;
|
|
17000
17123
|
var TaskTemplateService = class {
|
|
17001
17124
|
static async create(data) {
|
|
17125
|
+
this.validate(data);
|
|
17002
17126
|
const now = Date.now();
|
|
17003
17127
|
const result = await db.insert(taskTemplates2).values({ ...data, createdAt: now, updatedAt: now }).returning();
|
|
17004
17128
|
const tmpl = result[0];
|
|
@@ -17014,8 +17138,38 @@ var TaskTemplateService = class {
|
|
|
17014
17138
|
}
|
|
17015
17139
|
return tmpl;
|
|
17016
17140
|
}
|
|
17141
|
+
static validate(data) {
|
|
17142
|
+
if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
17143
|
+
if (!data.agent.trim()) throw new Error("agent \u4E0D\u80FD\u4E3A\u7A7A");
|
|
17144
|
+
if (!data.prompt.trim()) throw new Error("prompt \u4E0D\u80FD\u4E3A\u7A7A");
|
|
17145
|
+
const scheduleType = data.scheduleType;
|
|
17146
|
+
if (!["cron", "delayed", "recurring"].includes(scheduleType)) {
|
|
17147
|
+
throw new Error("scheduleType \u5FC5\u987B\u662F cron\u3001delayed \u6216 recurring");
|
|
17148
|
+
}
|
|
17149
|
+
if (scheduleType === "cron" && (!data.cronExpr || !isValidCronExpr(data.cronExpr))) {
|
|
17150
|
+
throw new Error("cronExpr \u7F3A\u5931\u6216\u683C\u5F0F\u65E0\u6548");
|
|
17151
|
+
}
|
|
17152
|
+
if (scheduleType === "recurring" && (!Number.isInteger(data.intervalMs) || (data.intervalMs ?? 0) <= 0)) {
|
|
17153
|
+
throw new Error("intervalMs \u5FC5\u987B\u662F\u6B63\u6574\u6570");
|
|
17154
|
+
}
|
|
17155
|
+
if (scheduleType === "delayed" && (!Number.isInteger(data.runAt) || (data.runAt ?? 0) <= 0)) {
|
|
17156
|
+
throw new Error("runAt \u5FC5\u987B\u662F\u6B63\u6574\u6570\u65F6\u95F4\u6233");
|
|
17157
|
+
}
|
|
17158
|
+
this.validateInteger("importance", data.importance, 1, 5);
|
|
17159
|
+
this.validateInteger("urgency", data.urgency, 1, 5);
|
|
17160
|
+
this.validateInteger("maxInstances", data.maxInstances, 1, 1e3);
|
|
17161
|
+
this.validateInteger("maxRetries", data.maxRetries, 0, 1e3);
|
|
17162
|
+
this.validateInteger("retryBackoffMs", data.retryBackoffMs, 0, 864e5);
|
|
17163
|
+
this.validateInteger("timeoutMs", data.timeoutMs, 1e3, 6048e5);
|
|
17164
|
+
}
|
|
17165
|
+
static validateInteger(name, value, min, max) {
|
|
17166
|
+
if (value === void 0 || value === null) return;
|
|
17167
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
17168
|
+
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
17169
|
+
}
|
|
17170
|
+
}
|
|
17017
17171
|
static async list(limit = 50) {
|
|
17018
|
-
return await db.select().from(taskTemplates2).orderBy(desc(taskTemplates2.createdAt)).limit(limit);
|
|
17172
|
+
return await db.select().from(taskTemplates2).orderBy(desc(taskTemplates2.createdAt), desc(taskTemplates2.id)).limit(limit);
|
|
17019
17173
|
}
|
|
17020
17174
|
static async getById(id) {
|
|
17021
17175
|
const result = await db.select().from(taskTemplates2).where(eq(taskTemplates2.id, id));
|
|
@@ -17054,72 +17208,236 @@ var TaskTemplateService = class {
|
|
|
17054
17208
|
};
|
|
17055
17209
|
|
|
17056
17210
|
// src/gateway/config.ts
|
|
17057
|
-
import {
|
|
17211
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
17058
17212
|
import { homedir as homedir2 } from "os";
|
|
17059
17213
|
import { join as join2 } from "path";
|
|
17060
17214
|
var DEFAULT_CONFIG = {
|
|
17215
|
+
configVersion: 2,
|
|
17061
17216
|
worker: {
|
|
17062
17217
|
maxConcurrency: 2,
|
|
17063
17218
|
pollIntervalMs: 1e3,
|
|
17064
17219
|
heartbeatIntervalMs: 3e4,
|
|
17065
|
-
taskTimeoutMs: 18e5
|
|
17220
|
+
taskTimeoutMs: 18e5,
|
|
17221
|
+
shutdownGracePeriodMs: 3e4
|
|
17066
17222
|
},
|
|
17067
17223
|
scheduler: {
|
|
17068
17224
|
enabled: true,
|
|
17069
|
-
checkIntervalMs: 1e3
|
|
17070
|
-
catchUp: "next"
|
|
17225
|
+
checkIntervalMs: 1e3
|
|
17071
17226
|
},
|
|
17072
17227
|
watchdog: {
|
|
17073
17228
|
heartbeatTimeoutMs: 6e5,
|
|
17074
|
-
|
|
17229
|
+
checkIntervalMs: 6e4,
|
|
17230
|
+
cleanupIntervalMs: 864e5,
|
|
17075
17231
|
retentionDays: 30
|
|
17076
17232
|
},
|
|
17077
17233
|
dashboard: {
|
|
17078
17234
|
enabled: true,
|
|
17079
17235
|
port: 4680
|
|
17080
|
-
},
|
|
17081
|
-
logging: {
|
|
17082
|
-
level: "info",
|
|
17083
|
-
format: "json"
|
|
17084
17236
|
}
|
|
17085
17237
|
};
|
|
17086
|
-
var
|
|
17087
|
-
function
|
|
17088
|
-
|
|
17089
|
-
|
|
17090
|
-
|
|
17091
|
-
|
|
17092
|
-
|
|
17093
|
-
|
|
17094
|
-
result[key] = deepMerge(baseVal, val);
|
|
17095
|
-
continue;
|
|
17096
|
-
}
|
|
17097
|
-
}
|
|
17098
|
-
if (val !== void 0) {
|
|
17099
|
-
result[key] = val;
|
|
17100
|
-
}
|
|
17238
|
+
var DEFAULT_CONFIG_PATH = join2(homedir2(), ".config/opencode/supertask.json");
|
|
17239
|
+
function getConfigPath() {
|
|
17240
|
+
return process.env.SUPERTASK_CONFIG_PATH ?? DEFAULT_CONFIG_PATH;
|
|
17241
|
+
}
|
|
17242
|
+
function objectAt(value, path) {
|
|
17243
|
+
if (value === void 0) return {};
|
|
17244
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
17245
|
+
throw new Error(`${path} \u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
17101
17246
|
}
|
|
17102
|
-
return
|
|
17247
|
+
return value;
|
|
17248
|
+
}
|
|
17249
|
+
function integerAt(source, key, fallback, min, max, path) {
|
|
17250
|
+
const value = source[key];
|
|
17251
|
+
if (value === void 0) return fallback;
|
|
17252
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
|
|
17253
|
+
throw new Error(`${path}.${key} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
17254
|
+
}
|
|
17255
|
+
return value;
|
|
17256
|
+
}
|
|
17257
|
+
function booleanAt(source, key, fallback, path) {
|
|
17258
|
+
const value = source[key];
|
|
17259
|
+
if (value === void 0) return fallback;
|
|
17260
|
+
if (typeof value !== "boolean") throw new Error(`${path}.${key} \u5FC5\u987B\u662F\u5E03\u5C14\u503C`);
|
|
17261
|
+
return value;
|
|
17103
17262
|
}
|
|
17104
|
-
function
|
|
17105
|
-
|
|
17106
|
-
|
|
17263
|
+
function validateConfig(input) {
|
|
17264
|
+
const root = objectAt(input, "config");
|
|
17265
|
+
const version2 = root.configVersion ?? 1;
|
|
17266
|
+
if (version2 !== 1 && version2 !== 2) {
|
|
17267
|
+
throw new Error("config.configVersion \u53EA\u652F\u6301 1 \u6216 2");
|
|
17268
|
+
}
|
|
17269
|
+
const worker = objectAt(root.worker, "worker");
|
|
17270
|
+
const scheduler = objectAt(root.scheduler, "scheduler");
|
|
17271
|
+
const watchdog = objectAt(root.watchdog, "watchdog");
|
|
17272
|
+
const dashboard = objectAt(root.dashboard, "dashboard");
|
|
17273
|
+
const heartbeatIntervalMs = integerAt(worker, "heartbeatIntervalMs", DEFAULT_CONFIG.worker.heartbeatIntervalMs, 1e3, 36e5, "worker");
|
|
17274
|
+
const heartbeatTimeoutMs = integerAt(watchdog, "heartbeatTimeoutMs", DEFAULT_CONFIG.watchdog.heartbeatTimeoutMs, 1e3, 864e5, "watchdog");
|
|
17275
|
+
let checkIntervalMs;
|
|
17276
|
+
let cleanupIntervalMs;
|
|
17277
|
+
if (version2 === 1 && watchdog.checkIntervalMs === void 0 && watchdog.cleanupIntervalMs !== void 0) {
|
|
17278
|
+
checkIntervalMs = integerAt(watchdog, "cleanupIntervalMs", DEFAULT_CONFIG.watchdog.checkIntervalMs, 1e3, 36e5, "watchdog");
|
|
17279
|
+
cleanupIntervalMs = DEFAULT_CONFIG.watchdog.cleanupIntervalMs;
|
|
17280
|
+
} else {
|
|
17281
|
+
checkIntervalMs = integerAt(watchdog, "checkIntervalMs", DEFAULT_CONFIG.watchdog.checkIntervalMs, 1e3, 36e5, "watchdog");
|
|
17282
|
+
cleanupIntervalMs = integerAt(watchdog, "cleanupIntervalMs", DEFAULT_CONFIG.watchdog.cleanupIntervalMs, 6e4, 6048e5, "watchdog");
|
|
17283
|
+
}
|
|
17284
|
+
if (heartbeatIntervalMs >= heartbeatTimeoutMs) {
|
|
17285
|
+
throw new Error("worker.heartbeatIntervalMs \u5FC5\u987B\u5C0F\u4E8E watchdog.heartbeatTimeoutMs");
|
|
17286
|
+
}
|
|
17287
|
+
if (checkIntervalMs > heartbeatTimeoutMs) {
|
|
17288
|
+
throw new Error("watchdog.checkIntervalMs \u4E0D\u80FD\u5927\u4E8E watchdog.heartbeatTimeoutMs");
|
|
17107
17289
|
}
|
|
17290
|
+
return {
|
|
17291
|
+
configVersion: 2,
|
|
17292
|
+
worker: {
|
|
17293
|
+
maxConcurrency: integerAt(worker, "maxConcurrency", DEFAULT_CONFIG.worker.maxConcurrency, 1, 64, "worker"),
|
|
17294
|
+
pollIntervalMs: integerAt(worker, "pollIntervalMs", DEFAULT_CONFIG.worker.pollIntervalMs, 50, 6e4, "worker"),
|
|
17295
|
+
heartbeatIntervalMs,
|
|
17296
|
+
taskTimeoutMs: integerAt(worker, "taskTimeoutMs", DEFAULT_CONFIG.worker.taskTimeoutMs, 1e3, 6048e5, "worker"),
|
|
17297
|
+
shutdownGracePeriodMs: integerAt(worker, "shutdownGracePeriodMs", DEFAULT_CONFIG.worker.shutdownGracePeriodMs, 0, 36e5, "worker")
|
|
17298
|
+
},
|
|
17299
|
+
scheduler: {
|
|
17300
|
+
enabled: booleanAt(scheduler, "enabled", DEFAULT_CONFIG.scheduler.enabled, "scheduler"),
|
|
17301
|
+
checkIntervalMs: integerAt(scheduler, "checkIntervalMs", DEFAULT_CONFIG.scheduler.checkIntervalMs, 100, 6e4, "scheduler")
|
|
17302
|
+
},
|
|
17303
|
+
watchdog: {
|
|
17304
|
+
heartbeatTimeoutMs,
|
|
17305
|
+
checkIntervalMs,
|
|
17306
|
+
cleanupIntervalMs,
|
|
17307
|
+
retentionDays: integerAt(watchdog, "retentionDays", DEFAULT_CONFIG.watchdog.retentionDays, 1, 3650, "watchdog")
|
|
17308
|
+
},
|
|
17309
|
+
dashboard: {
|
|
17310
|
+
enabled: booleanAt(dashboard, "enabled", DEFAULT_CONFIG.dashboard.enabled, "dashboard"),
|
|
17311
|
+
port: integerAt(dashboard, "port", DEFAULT_CONFIG.dashboard.port, 1, 65535, "dashboard")
|
|
17312
|
+
}
|
|
17313
|
+
};
|
|
17314
|
+
}
|
|
17315
|
+
function loadConfig(path = getConfigPath()) {
|
|
17316
|
+
if (!existsSync2(path)) return validateConfig({ configVersion: 2 });
|
|
17108
17317
|
try {
|
|
17109
|
-
|
|
17110
|
-
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
17114
|
-
console.error(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "warn", msg: "config load failed, using defaults", path: CONFIG_PATH, error: msg }));
|
|
17115
|
-
return DEFAULT_CONFIG;
|
|
17318
|
+
return validateConfig(JSON.parse(readFileSync(path, "utf-8")));
|
|
17319
|
+
} catch (error) {
|
|
17320
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
17321
|
+
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E ${path}: ${message}`);
|
|
17116
17322
|
}
|
|
17117
17323
|
}
|
|
17118
17324
|
|
|
17119
17325
|
// src/web/index.tsx
|
|
17120
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
17326
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2, renameSync } from "fs";
|
|
17121
17327
|
import { dirname as dirname2 } from "path";
|
|
17328
|
+
|
|
17329
|
+
// src/gateway/health.ts
|
|
17330
|
+
var LOCK_STALE_MS = 3e4;
|
|
17331
|
+
var state = null;
|
|
17332
|
+
function componentStatus(component, enabled, maxAgeMs, now) {
|
|
17333
|
+
const lastActivityAt = state?.lastActivityAt[component] ?? null;
|
|
17334
|
+
const ageMs = lastActivityAt == null ? null : Math.max(0, now - lastActivityAt);
|
|
17335
|
+
return {
|
|
17336
|
+
enabled,
|
|
17337
|
+
healthy: !enabled || ageMs != null && ageMs <= maxAgeMs,
|
|
17338
|
+
lastActivityAt,
|
|
17339
|
+
ageMs,
|
|
17340
|
+
maxAgeMs
|
|
17341
|
+
};
|
|
17342
|
+
}
|
|
17343
|
+
function getGatewayHealth(now = Date.now()) {
|
|
17344
|
+
const worker = componentStatus(
|
|
17345
|
+
"worker",
|
|
17346
|
+
true,
|
|
17347
|
+
Math.max((state?.config.workerPollIntervalMs ?? 1e3) * 5, 5e3),
|
|
17348
|
+
now
|
|
17349
|
+
);
|
|
17350
|
+
const scheduler = componentStatus(
|
|
17351
|
+
"scheduler",
|
|
17352
|
+
state?.config.schedulerEnabled ?? false,
|
|
17353
|
+
Math.max((state?.config.schedulerCheckIntervalMs ?? 1e3) * 5, 5e3),
|
|
17354
|
+
now
|
|
17355
|
+
);
|
|
17356
|
+
const watchdog = componentStatus(
|
|
17357
|
+
"watchdog",
|
|
17358
|
+
true,
|
|
17359
|
+
Math.max((state?.config.watchdogCheckIntervalMs ?? 6e4) * 3, 5e3),
|
|
17360
|
+
now
|
|
17361
|
+
);
|
|
17362
|
+
let lock = { pid: null, heartbeatAt: null, readyAt: null, ageMs: null, healthy: false };
|
|
17363
|
+
try {
|
|
17364
|
+
const row = sqlite.prepare(
|
|
17365
|
+
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
17366
|
+
).get();
|
|
17367
|
+
if (row) {
|
|
17368
|
+
const ageMs = Math.max(0, now - row.heartbeat_at);
|
|
17369
|
+
lock = {
|
|
17370
|
+
pid: row.pid,
|
|
17371
|
+
heartbeatAt: row.heartbeat_at,
|
|
17372
|
+
readyAt: row.ready_at,
|
|
17373
|
+
ageMs,
|
|
17374
|
+
healthy: row.pid === process.pid && row.ready_at != null && ageMs < LOCK_STALE_MS
|
|
17375
|
+
};
|
|
17376
|
+
}
|
|
17377
|
+
} catch {
|
|
17378
|
+
}
|
|
17379
|
+
const healthy = state != null && lock.healthy && worker.healthy && scheduler.healthy && watchdog.healthy;
|
|
17380
|
+
return {
|
|
17381
|
+
status: healthy ? "ok" : "degraded",
|
|
17382
|
+
pid: process.pid,
|
|
17383
|
+
uptimeMs: state ? Math.max(0, now - state.startedAt) : 0,
|
|
17384
|
+
lock,
|
|
17385
|
+
components: { worker, scheduler, watchdog }
|
|
17386
|
+
};
|
|
17387
|
+
}
|
|
17388
|
+
|
|
17389
|
+
// src/web/index.tsx
|
|
17122
17390
|
var app = new Hono2();
|
|
17391
|
+
var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
17392
|
+
"pending",
|
|
17393
|
+
"running",
|
|
17394
|
+
"done",
|
|
17395
|
+
"failed",
|
|
17396
|
+
"dead_letter",
|
|
17397
|
+
"cancelled"
|
|
17398
|
+
]);
|
|
17399
|
+
function parsePositiveInteger(value) {
|
|
17400
|
+
if (!/^\d+$/.test(value)) return null;
|
|
17401
|
+
const parsed = Number(value);
|
|
17402
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
17403
|
+
}
|
|
17404
|
+
function parseTaskStatus(value) {
|
|
17405
|
+
return TASK_STATUSES.has(value) ? value : null;
|
|
17406
|
+
}
|
|
17407
|
+
function safeStatus(value) {
|
|
17408
|
+
return value && TASK_STATUSES.has(value) ? value : "unknown";
|
|
17409
|
+
}
|
|
17410
|
+
app.use("*", async (c, next) => {
|
|
17411
|
+
await next();
|
|
17412
|
+
c.header("X-Content-Type-Options", "nosniff");
|
|
17413
|
+
c.header("X-Frame-Options", "DENY");
|
|
17414
|
+
c.header("Referrer-Policy", "no-referrer");
|
|
17415
|
+
});
|
|
17416
|
+
app.use("/api/*", async (c, next) => {
|
|
17417
|
+
if (["GET", "HEAD", "OPTIONS"].includes(c.req.method)) return next();
|
|
17418
|
+
const fetchSite = c.req.header("Sec-Fetch-Site");
|
|
17419
|
+
if (fetchSite && !["same-origin", "none"].includes(fetchSite)) {
|
|
17420
|
+
return c.json({ error: "cross-site request rejected" }, 403);
|
|
17421
|
+
}
|
|
17422
|
+
const origin = c.req.header("Origin");
|
|
17423
|
+
if (origin) {
|
|
17424
|
+
try {
|
|
17425
|
+
const originUrl = new URL(origin);
|
|
17426
|
+
const requestUrl = new URL(c.req.url);
|
|
17427
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(originUrl.hostname);
|
|
17428
|
+
if (!loopback || originUrl.origin !== requestUrl.origin) {
|
|
17429
|
+
return c.json({ error: "cross-site request rejected" }, 403);
|
|
17430
|
+
}
|
|
17431
|
+
} catch {
|
|
17432
|
+
return c.json({ error: "invalid origin" }, 403);
|
|
17433
|
+
}
|
|
17434
|
+
}
|
|
17435
|
+
return next();
|
|
17436
|
+
});
|
|
17437
|
+
app.get("/health", (c) => {
|
|
17438
|
+
const health = getGatewayHealth();
|
|
17439
|
+
return c.json(health, health.status === "ok" ? 200 : 503);
|
|
17440
|
+
});
|
|
17123
17441
|
function formatDuration(startAt, endAt) {
|
|
17124
17442
|
if (!startAt) return "-";
|
|
17125
17443
|
const start = new Date(startAt).getTime();
|
|
@@ -17157,17 +17475,21 @@ function esc(s) {
|
|
|
17157
17475
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
17158
17476
|
}
|
|
17159
17477
|
function readCurrentConfig() {
|
|
17160
|
-
|
|
17478
|
+
const configPath = getConfigPath();
|
|
17479
|
+
if (!existsSync3(configPath)) return {};
|
|
17161
17480
|
try {
|
|
17162
|
-
return JSON.parse(readFileSync2(
|
|
17481
|
+
return JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
17163
17482
|
} catch {
|
|
17164
17483
|
return {};
|
|
17165
17484
|
}
|
|
17166
17485
|
}
|
|
17167
17486
|
function writeConfig(cfg) {
|
|
17168
|
-
const
|
|
17487
|
+
const configPath = getConfigPath();
|
|
17488
|
+
const dir = dirname2(configPath);
|
|
17169
17489
|
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
17170
|
-
|
|
17490
|
+
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
17491
|
+
writeFileSync(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
17492
|
+
renameSync(tempPath, configPath);
|
|
17171
17493
|
}
|
|
17172
17494
|
var SHARED_STYLES = html`
|
|
17173
17495
|
<style>
|
|
@@ -17281,11 +17603,11 @@ async function saveConfig(){
|
|
|
17281
17603
|
scheduler:{
|
|
17282
17604
|
enabled:form.se.checked,
|
|
17283
17605
|
checkIntervalMs:Number(form.si.value),
|
|
17284
|
-
catchUp:form.cu.value,
|
|
17285
17606
|
},
|
|
17286
17607
|
watchdog:{
|
|
17287
17608
|
heartbeatTimeoutMs:Number(form.wt.value)*1000,
|
|
17288
|
-
|
|
17609
|
+
checkIntervalMs:Number(form.wci.value)*1000,
|
|
17610
|
+
cleanupIntervalMs:Number(form.wcl.value)*3600000,
|
|
17289
17611
|
retentionDays:Number(form.rd.value),
|
|
17290
17612
|
}
|
|
17291
17613
|
};
|
|
@@ -17317,12 +17639,16 @@ async function saveConfig(){
|
|
|
17317
17639
|
</body></html>`;
|
|
17318
17640
|
}
|
|
17319
17641
|
app.get("/", async (c) => {
|
|
17320
|
-
const
|
|
17642
|
+
const pageParam = c.req.query("page") || "1";
|
|
17643
|
+
const page = parsePositiveInteger(pageParam);
|
|
17644
|
+
if (page === null) return c.text("invalid page", 400);
|
|
17321
17645
|
const statusFilter = c.req.query("status") || "";
|
|
17646
|
+
const parsedStatus = statusFilter ? parseTaskStatus(statusFilter) : null;
|
|
17647
|
+
if (statusFilter && !parsedStatus) return c.text("invalid status", 400);
|
|
17322
17648
|
const limit = 50;
|
|
17323
17649
|
const offset = (page - 1) * limit;
|
|
17324
17650
|
const [tasks3, statsData] = await Promise.all([
|
|
17325
|
-
TaskService.list({ limit, offset, ...
|
|
17651
|
+
TaskService.list({ limit, offset, ...parsedStatus ? { status: parsedStatus } : {} }),
|
|
17326
17652
|
TaskService.stats({})
|
|
17327
17653
|
]);
|
|
17328
17654
|
const taskIds = tasks3.map((t) => t.id);
|
|
@@ -17345,12 +17671,13 @@ app.get("/", async (c) => {
|
|
|
17345
17671
|
</div>`;
|
|
17346
17672
|
let rows = "";
|
|
17347
17673
|
for (const task of tasks3) {
|
|
17348
|
-
const
|
|
17674
|
+
const status = safeStatus(task.status);
|
|
17675
|
+
const st = status.toUpperCase();
|
|
17349
17676
|
rows += `<tr>
|
|
17350
17677
|
<td class="mu">#${task.id}</td>
|
|
17351
17678
|
<td><div style="font-weight:500">${esc(task.name)}</div><div class="mu sm el">${esc(task.prompt.substring(0, 120))}</div></td>
|
|
17352
17679
|
<td><span class="tag">${esc(task.agent)}</span></td>
|
|
17353
|
-
<td><span class="badge b-${
|
|
17680
|
+
<td><span class="badge b-${status}">${st}</span></td>
|
|
17354
17681
|
<td class="sm ${task.status === "running" ? "" : "mu"}">${formatDuration(task.startedAt, task.finishedAt)}</td>
|
|
17355
17682
|
<td class="mu sm">${(task.retryCount ?? 0) > 0 ? task.retryCount : "-"}</td>
|
|
17356
17683
|
<td>
|
|
@@ -17382,21 +17709,13 @@ app.get("/", async (c) => {
|
|
|
17382
17709
|
});
|
|
17383
17710
|
app.get("/templates", async (c) => {
|
|
17384
17711
|
const templates = await TaskTemplateService.list(100);
|
|
17385
|
-
for (const tmpl of templates) {
|
|
17386
|
-
if (tmpl.enabled && tmpl.scheduleType === "cron" && tmpl.cronExpr) {
|
|
17387
|
-
try {
|
|
17388
|
-
const { getNextCronRun: getNextCronRun2 } = await Promise.resolve().then(() => (init_cron_parser(), cron_parser_exports));
|
|
17389
|
-
tmpl.nextRunAt = getNextCronRun2(tmpl.cronExpr, Date.now());
|
|
17390
|
-
} catch {
|
|
17391
|
-
}
|
|
17392
|
-
}
|
|
17393
|
-
}
|
|
17394
17712
|
const enabled = templates.filter((t) => t.enabled).length;
|
|
17395
17713
|
const disabled = templates.length - enabled;
|
|
17396
17714
|
let rows = "";
|
|
17397
17715
|
for (const t of templates) {
|
|
17398
|
-
const
|
|
17399
|
-
const
|
|
17716
|
+
const scheduleType = ["cron", "recurring", "delayed"].includes(t.scheduleType) ? t.scheduleType : "unknown";
|
|
17717
|
+
const typeLabel = scheduleType === "cron" ? "Cron" : scheduleType === "recurring" ? "\u5FAA\u73AF" : scheduleType === "delayed" ? "\u5B9A\u65F6" : "\u672A\u77E5";
|
|
17718
|
+
const typeClass = "tag t-" + scheduleType;
|
|
17400
17719
|
let rule = "-";
|
|
17401
17720
|
if (t.scheduleType === "cron") rule = t.cronExpr || "-";
|
|
17402
17721
|
else if (t.scheduleType === "recurring") rule = t.intervalMs ? `${Math.floor(t.intervalMs / 6e4)}\u5206\u949F` : "-";
|
|
@@ -17408,7 +17727,7 @@ app.get("/templates", async (c) => {
|
|
|
17408
17727
|
<td><div style="font-weight:500">${esc(t.name)}</div><div class="mu sm el">${esc(t.prompt.substring(0, 100))}</div>
|
|
17409
17728
|
<div class="sm" style="margin-top:2px"><span class="tag">${esc(t.agent)}</span>${t.model && t.model !== "default" ? ` <span class="tag">${esc(t.model)}</span>` : ""}</div></td>
|
|
17410
17729
|
<td><span class="${typeClass}">${typeLabel}</span></td>
|
|
17411
|
-
<td class="m sm">${rule}</td>
|
|
17730
|
+
<td class="m sm">${esc(rule)}</td>
|
|
17412
17731
|
<td>${statusBadge}</td>
|
|
17413
17732
|
<td class="sm">${t.lastRunAt ? timeAgo(t.lastRunAt) : "-"}</td>
|
|
17414
17733
|
<td class="sm">${t.nextRunAt ? timeUntil(t.nextRunAt) : "-"}</td>
|
|
@@ -17436,7 +17755,8 @@ app.get("/templates", async (c) => {
|
|
|
17436
17755
|
return c.html(renderLayout("\u5B9A\u65F6\u4EFB\u52A1", "templates", body));
|
|
17437
17756
|
});
|
|
17438
17757
|
app.get("/runs", async (c) => {
|
|
17439
|
-
const page =
|
|
17758
|
+
const page = parsePositiveInteger(c.req.query("page") || "1");
|
|
17759
|
+
if (page === null) return c.text("invalid page", 400);
|
|
17440
17760
|
const limit = 50;
|
|
17441
17761
|
const offset = (page - 1) * limit;
|
|
17442
17762
|
const { taskRuns: tr, tasks: tk } = schema_exports;
|
|
@@ -17454,13 +17774,14 @@ app.get("/runs", async (c) => {
|
|
|
17454
17774
|
childPid: tr.childPid,
|
|
17455
17775
|
taskName: tk.name,
|
|
17456
17776
|
taskAgent: tk.agent
|
|
17457
|
-
}).from(tr).innerJoin(tk, eq(tr.taskId, tk.id)).orderBy(desc(tr.startedAt)).limit(limit).offset(offset);
|
|
17777
|
+
}).from(tr).innerJoin(tk, eq(tr.taskId, tk.id)).orderBy(desc(tr.startedAt), desc(tr.id)).limit(limit).offset(offset);
|
|
17458
17778
|
const totalResult = await db.select({ count: sql`count(*)` }).from(tr);
|
|
17459
17779
|
const total = Number(totalResult[0]?.count ?? 0);
|
|
17460
17780
|
const totalPages = Math.ceil(total / limit);
|
|
17461
17781
|
let rows = "";
|
|
17462
17782
|
const logsHtml = [];
|
|
17463
17783
|
for (const run of runs) {
|
|
17784
|
+
const status = safeStatus(run.status);
|
|
17464
17785
|
const shortSession = run.sessionId ? run.sessionId.slice(4, 7) + "***" + run.sessionId.slice(-3) : "-";
|
|
17465
17786
|
const logBtn = run.log ? `<button class="btn btn-sm" onclick="toggleLog(${run.id})">\u65E5\u5FD7</button>` : "";
|
|
17466
17787
|
rows += `<tr>
|
|
@@ -17468,15 +17789,15 @@ app.get("/runs", async (c) => {
|
|
|
17468
17789
|
<td><div style="font-weight:500">${esc(run.taskName)} <span class="mu">(#${run.taskId})</span></div>
|
|
17469
17790
|
${run.model ? `<div class="sm"><span class="tag">${esc(run.model)}</span></div>` : ""}</td>
|
|
17470
17791
|
<td><span class="tag">${esc(run.taskAgent)}</span></td>
|
|
17471
|
-
<td><span class="badge b-${
|
|
17792
|
+
<td><span class="badge b-${status}">${status.toUpperCase()}</span></td>
|
|
17472
17793
|
<td class="sm">${formatDuration(run.startedAt, run.finishedAt)}</td>
|
|
17473
17794
|
<td class="sm mu">${run.heartbeatAt ? timeAgo(run.heartbeatAt) : "-"}</td>
|
|
17474
17795
|
<td><button class="btn btn-sm" onclick="showRunDetail(${run.id})">\u8BE6\u60C5</button>${logBtn}</td>
|
|
17475
17796
|
</tr>`;
|
|
17476
17797
|
if (run.log) {
|
|
17477
17798
|
logsHtml.push(`<div id="log-${run.id}" style="display:none" class="mt8">
|
|
17478
|
-
<div class="panel"><div class="ph"><h3>Run #${run.id} \u65E5\u5FD7 \u2014 ${run.taskName}</h3></div>
|
|
17479
|
-
<div class="log-box">${run.log
|
|
17799
|
+
<div class="panel"><div class="ph"><h3>Run #${run.id} \u65E5\u5FD7 \u2014 ${esc(run.taskName)}</h3></div>
|
|
17800
|
+
<div class="log-box">${esc(run.log)}</div></div></div>`);
|
|
17480
17801
|
}
|
|
17481
17802
|
}
|
|
17482
17803
|
let paging = `<div class="pn">`;
|
|
@@ -17502,17 +17823,18 @@ app.get("/runs", async (c) => {
|
|
|
17502
17823
|
});
|
|
17503
17824
|
app.get("/system", async (c) => {
|
|
17504
17825
|
const config = loadConfig();
|
|
17826
|
+
const configPath = getConfigPath();
|
|
17505
17827
|
const stats = await TaskService.stats({});
|
|
17506
17828
|
const runningRuns = await TaskRunService.getAllRunningRuns();
|
|
17507
17829
|
const templates = await TaskTemplateService.list(100);
|
|
17508
|
-
const configExists = existsSync3(
|
|
17830
|
+
const configExists = existsSync3(configPath);
|
|
17509
17831
|
let runRows = "";
|
|
17510
17832
|
if (runningRuns.length > 0) {
|
|
17511
17833
|
for (const run of runningRuns) {
|
|
17512
17834
|
const shortS = run.sessionId ? run.sessionId.slice(4, 7) + "***" + run.sessionId.slice(-3) : "-";
|
|
17513
17835
|
runRows += `<tr>
|
|
17514
17836
|
<td class="mu">#${run.id}</td><td>#${run.taskId}</td>
|
|
17515
|
-
<td class="m sm">${shortS}</td>
|
|
17837
|
+
<td class="m sm">${esc(shortS)}</td>
|
|
17516
17838
|
<td class="sm">${esc(run.model) || "-"}</td>
|
|
17517
17839
|
<td class="sm">${formatDate(run.startedAt)}</td>
|
|
17518
17840
|
<td class="sm">${run.heartbeatAt ? timeAgo(run.heartbeatAt) : "-"}</td>
|
|
@@ -17537,19 +17859,14 @@ app.get("/system", async (c) => {
|
|
|
17537
17859
|
<h3 style="margin:0 0 12px;font-size:14px">Scheduler \u914D\u7F6E</h3>
|
|
17538
17860
|
<div class="form-row"><label>\u542F\u7528\u8C03\u5EA6</label><input type="checkbox" name="se" ${config.scheduler.enabled ? "checked" : ""}></div>
|
|
17539
17861
|
<div class="form-row"><label>\u68C0\u67E5\u95F4\u9694(ms)</label><input type="number" name="si" value="${config.scheduler.checkIntervalMs}" min="100" style="width:100px"></div>
|
|
17540
|
-
<div class="form-row"><label>\u8FFD\u8D76\u6A21\u5F0F</label><select name="cu" style="width:100px">
|
|
17541
|
-
<option value="next" ${config.scheduler.catchUp === "next" ? "selected" : ""}>next</option>
|
|
17542
|
-
<option value="all" ${config.scheduler.catchUp === "all" ? "selected" : ""}>all</option>
|
|
17543
|
-
<option value="latest" ${config.scheduler.catchUp === "latest" ? "selected" : ""}>latest</option>
|
|
17544
|
-
</select></div>
|
|
17545
17862
|
<div class="ir"><span class="ik">\u6D3B\u8DC3\u6A21\u677F</span><span class="iv">${templates.filter((t) => t.enabled).length} / ${templates.length}</span></div>
|
|
17546
17863
|
</div>
|
|
17547
17864
|
<div class="card">
|
|
17548
17865
|
<h3 style="margin:0 0 12px;font-size:14px">Watchdog \u914D\u7F6E</h3>
|
|
17549
17866
|
<div class="form-row"><label>\u5FC3\u8DF3\u8D85\u65F6(\u79D2)</label><input type="number" name="wt" value="${config.watchdog.heartbeatTimeoutMs / 1e3}" min="10" style="width:100px"></div>
|
|
17550
|
-
<div class="form-row"><label>\
|
|
17867
|
+
<div class="form-row"><label>\u68C0\u67E5\u95F4\u9694(\u79D2)</label><input type="number" name="wci" value="${config.watchdog.checkIntervalMs / 1e3}" min="1" style="width:100px"></div>
|
|
17868
|
+
<div class="form-row"><label>\u6E05\u7406\u95F4\u9694(\u5C0F\u65F6)</label><input type="number" name="wcl" value="${config.watchdog.cleanupIntervalMs / 36e5}" min="1" style="width:100px"></div>
|
|
17551
17869
|
<div class="form-row"><label>\u6570\u636E\u4FDD\u7559(\u5929)</label><input type="number" name="rd" value="${config.watchdog.retentionDays}" min="1" style="width:100px"></div>
|
|
17552
|
-
<div class="ir"><span class="ik">\u65E5\u5FD7\u683C\u5F0F</span><span class="iv">${config.logging.format}</span></div>
|
|
17553
17870
|
</div>
|
|
17554
17871
|
</div>
|
|
17555
17872
|
<div style="text-align:center;margin-bottom:24px">
|
|
@@ -17578,7 +17895,7 @@ app.get("/system", async (c) => {
|
|
|
17578
17895
|
|
|
17579
17896
|
<div class="card mt16">
|
|
17580
17897
|
<h3 style="margin:0 0 12px;font-size:14px">\u914D\u7F6E\u6587\u4EF6</h3>
|
|
17581
|
-
<div class="ir"><span class="ik">\u8DEF\u5F84</span><span class="iv m sm">${
|
|
17898
|
+
<div class="ir"><span class="ik">\u8DEF\u5F84</span><span class="iv m sm">${esc(configPath)}</span></div>
|
|
17582
17899
|
<div class="ir"><span class="ik">\u6587\u4EF6\u5B58\u5728</span><span class="iv">${configFileStatus}</span></div>
|
|
17583
17900
|
</div>
|
|
17584
17901
|
|
|
@@ -17590,46 +17907,61 @@ app.get("/system", async (c) => {
|
|
|
17590
17907
|
return c.html(renderLayout("\u7CFB\u7EDF\u72B6\u6001", "system", body));
|
|
17591
17908
|
});
|
|
17592
17909
|
app.get("/api/tasks/:id", async (c) => {
|
|
17593
|
-
const id =
|
|
17910
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17911
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17594
17912
|
const task = await TaskService.getById(id);
|
|
17595
17913
|
if (!task) return c.json({ error: "not found" }, 404);
|
|
17596
17914
|
const runs = await TaskRunService.listByTaskId(id);
|
|
17597
17915
|
return c.json({ ...task, _runs: runs });
|
|
17598
17916
|
});
|
|
17599
17917
|
app.get("/api/runs/:id", async (c) => {
|
|
17600
|
-
const id =
|
|
17918
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17919
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17601
17920
|
const run = await TaskRunService.getById(id);
|
|
17602
17921
|
if (!run) return c.json({ error: "not found" }, 404);
|
|
17603
17922
|
return c.json(run);
|
|
17604
17923
|
});
|
|
17605
17924
|
app.get("/api/templates/:id", async (c) => {
|
|
17606
|
-
const id =
|
|
17925
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17926
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17607
17927
|
const tmpl = await TaskTemplateService.getById(id);
|
|
17608
17928
|
if (!tmpl) return c.json({ error: "not found" }, 404);
|
|
17609
17929
|
return c.json(tmpl);
|
|
17610
17930
|
});
|
|
17611
17931
|
app.post("/api/tasks/:id/retry", async (c) => {
|
|
17612
|
-
|
|
17613
|
-
return c.json({
|
|
17932
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17933
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17934
|
+
const task = await TaskService.retry(id);
|
|
17935
|
+
if (task) return c.json({ success: true });
|
|
17936
|
+
return await TaskService.getById(id) ? c.json({ error: "task status does not allow retry" }, 409) : c.json({ error: "not found" }, 404);
|
|
17614
17937
|
});
|
|
17615
17938
|
app.delete("/api/tasks/:id", async (c) => {
|
|
17616
|
-
|
|
17617
|
-
return c.json({
|
|
17939
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17940
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17941
|
+
const deleted = await TaskService.delete(id);
|
|
17942
|
+
return deleted ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
17618
17943
|
});
|
|
17619
17944
|
app.post("/api/templates/:id/enable", async (c) => {
|
|
17620
|
-
const
|
|
17621
|
-
return c.json({
|
|
17945
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17946
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17947
|
+
const result = await TaskTemplateService.enable(id);
|
|
17948
|
+
return result ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
17622
17949
|
});
|
|
17623
17950
|
app.post("/api/templates/:id/disable", async (c) => {
|
|
17624
|
-
const
|
|
17625
|
-
return c.json({
|
|
17951
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17952
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17953
|
+
const result = await TaskTemplateService.disable(id);
|
|
17954
|
+
return result ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
17626
17955
|
});
|
|
17627
17956
|
app.delete("/api/templates/:id", async (c) => {
|
|
17628
|
-
const
|
|
17629
|
-
return c.json({
|
|
17957
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17958
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17959
|
+
const ok = await TaskTemplateService.delete(id);
|
|
17960
|
+
return ok ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
17630
17961
|
});
|
|
17631
17962
|
app.post("/api/templates/:id/trigger", async (c) => {
|
|
17632
|
-
const id =
|
|
17963
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17964
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17633
17965
|
const tmpl = await TaskTemplateService.getById(id);
|
|
17634
17966
|
if (!tmpl) return c.json({ error: "not found" }, 404);
|
|
17635
17967
|
const task = await TaskService.add({
|
|
@@ -17641,7 +17973,10 @@ app.post("/api/templates/:id/trigger", async (c) => {
|
|
|
17641
17973
|
category: tmpl.category,
|
|
17642
17974
|
importance: tmpl.importance,
|
|
17643
17975
|
urgency: tmpl.urgency,
|
|
17976
|
+
batchId: tmpl.batchId,
|
|
17644
17977
|
maxRetries: tmpl.maxRetries,
|
|
17978
|
+
retryBackoffMs: tmpl.retryBackoffMs,
|
|
17979
|
+
timeoutMs: tmpl.timeoutMs,
|
|
17645
17980
|
templateId: tmpl.id
|
|
17646
17981
|
});
|
|
17647
17982
|
return c.json({ success: true, taskId: task.id });
|
|
@@ -17656,22 +17991,29 @@ app.put("/api/config", async (c) => {
|
|
|
17656
17991
|
const bW = body.worker ?? {};
|
|
17657
17992
|
const bS = body.scheduler ?? {};
|
|
17658
17993
|
const bD = body.watchdog ?? {};
|
|
17659
|
-
const merged = {
|
|
17660
|
-
|
|
17994
|
+
const merged = {
|
|
17995
|
+
...current,
|
|
17996
|
+
...body,
|
|
17997
|
+
configVersion: 2,
|
|
17998
|
+
worker: { ...curW, ...bW },
|
|
17999
|
+
scheduler: { ...curS, ...bS },
|
|
18000
|
+
watchdog: { ...curD, ...bD }
|
|
18001
|
+
};
|
|
18002
|
+
writeConfig(validateConfig(merged));
|
|
17661
18003
|
return c.json({ success: true });
|
|
17662
18004
|
} catch (err) {
|
|
17663
|
-
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
18005
|
+
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) }, 400);
|
|
17664
18006
|
}
|
|
17665
18007
|
});
|
|
17666
18008
|
app.post("/api/database/clear", async (c) => {
|
|
17667
18009
|
try {
|
|
17668
|
-
const { tasks: tasks3, taskRuns:
|
|
17669
|
-
await db.delete(
|
|
18010
|
+
const { tasks: tasks3, taskRuns: taskRuns4, taskTemplates: taskTemplates3 } = schema_exports;
|
|
18011
|
+
await db.delete(taskRuns4);
|
|
17670
18012
|
await db.delete(taskTemplates3);
|
|
17671
18013
|
await db.delete(tasks3);
|
|
17672
18014
|
return c.json({ success: true });
|
|
17673
18015
|
} catch (err) {
|
|
17674
|
-
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
18016
|
+
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) }, 500);
|
|
17675
18017
|
}
|
|
17676
18018
|
});
|
|
17677
18019
|
var dashboardApp = app;
|