opencode-supertask 0.1.20 → 0.1.21
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 +1245 -579
- package/dist/cli/index.js.map +1 -1
- package/dist/gateway/index.js +838 -294
- package/dist/gateway/index.js.map +1 -1
- package/dist/plugin/supertask.js +576 -232
- package/dist/plugin/supertask.js.map +1 -1
- package/dist/web/index.js +537 -212
- package/dist/web/index.js.map +1 -1
- package/dist/worker/index.d.ts +19 -7
- package/dist/worker/index.js +425 -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
|
}
|
|
@@ -16774,8 +16841,20 @@ var TaskService = class {
|
|
|
16774
16841
|
return result.length;
|
|
16775
16842
|
}
|
|
16776
16843
|
static async cancel(id, scope = {}) {
|
|
16777
|
-
const conditions = [
|
|
16778
|
-
|
|
16844
|
+
const conditions = [
|
|
16845
|
+
eq(tasks2.id, id),
|
|
16846
|
+
or(
|
|
16847
|
+
eq(tasks2.status, "pending"),
|
|
16848
|
+
eq(tasks2.status, "running"),
|
|
16849
|
+
eq(tasks2.status, "failed")
|
|
16850
|
+
),
|
|
16851
|
+
...this.buildScopeWhere(scope)
|
|
16852
|
+
];
|
|
16853
|
+
const result = await db.update(tasks2).set({
|
|
16854
|
+
status: "cancelled",
|
|
16855
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
16856
|
+
retryAfter: null
|
|
16857
|
+
}).where(and(...conditions)).returning();
|
|
16779
16858
|
return result[0] || null;
|
|
16780
16859
|
}
|
|
16781
16860
|
static async retry(id, scope = {}) {
|
|
@@ -16787,7 +16866,8 @@ var TaskService = class {
|
|
|
16787
16866
|
status: "pending",
|
|
16788
16867
|
startedAt: null,
|
|
16789
16868
|
finishedAt: null,
|
|
16790
|
-
retryAfter: null
|
|
16869
|
+
retryAfter: null,
|
|
16870
|
+
retryCount: 0
|
|
16791
16871
|
}).where(and(...conditions)).returning();
|
|
16792
16872
|
return result[0] || null;
|
|
16793
16873
|
}
|
|
@@ -16801,7 +16881,8 @@ var TaskService = class {
|
|
|
16801
16881
|
status: "pending",
|
|
16802
16882
|
startedAt: null,
|
|
16803
16883
|
finishedAt: null,
|
|
16804
|
-
retryAfter: null
|
|
16884
|
+
retryAfter: null,
|
|
16885
|
+
retryCount: 0
|
|
16805
16886
|
}).where(and(...conditions)).returning();
|
|
16806
16887
|
return result.length;
|
|
16807
16888
|
}
|
|
@@ -16869,6 +16950,9 @@ var TaskService = class {
|
|
|
16869
16950
|
}
|
|
16870
16951
|
static async delete(id, scope = {}) {
|
|
16871
16952
|
const conditions = [eq(tasks2.id, id), ...this.buildScopeWhere(scope)];
|
|
16953
|
+
const existing = await db.select({ id: tasks2.id }).from(tasks2).where(and(...conditions)).limit(1);
|
|
16954
|
+
if (!existing[0]) return false;
|
|
16955
|
+
await db.delete(taskRuns2).where(eq(taskRuns2.taskId, id));
|
|
16872
16956
|
const result = await db.delete(tasks2).where(and(...conditions)).returning();
|
|
16873
16957
|
return result.length > 0;
|
|
16874
16958
|
}
|
|
@@ -16886,59 +16970,59 @@ var TaskService = class {
|
|
|
16886
16970
|
};
|
|
16887
16971
|
|
|
16888
16972
|
// src/core/services/task-run.service.ts
|
|
16889
|
-
var { taskRuns:
|
|
16973
|
+
var { taskRuns: taskRuns3 } = schema_exports;
|
|
16890
16974
|
var TaskRunService = class {
|
|
16891
16975
|
static async create(data) {
|
|
16892
|
-
const result = await db.insert(
|
|
16976
|
+
const result = await db.insert(taskRuns3).values(data).returning();
|
|
16893
16977
|
return result[0];
|
|
16894
16978
|
}
|
|
16895
16979
|
static async updateSessionId(id, sessionId) {
|
|
16896
|
-
const result = await db.update(
|
|
16980
|
+
const result = await db.update(taskRuns3).set({ sessionId }).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16897
16981
|
return result[0] || null;
|
|
16898
16982
|
}
|
|
16899
16983
|
static async done(id, log) {
|
|
16900
|
-
const result = await db.update(
|
|
16984
|
+
const result = await db.update(taskRuns3).set({
|
|
16901
16985
|
status: "done",
|
|
16902
16986
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
16903
16987
|
log
|
|
16904
|
-
}).where(eq(
|
|
16988
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16905
16989
|
return result[0] || null;
|
|
16906
16990
|
}
|
|
16907
16991
|
static async fail(id, log) {
|
|
16908
|
-
const result = await db.update(
|
|
16992
|
+
const result = await db.update(taskRuns3).set({
|
|
16909
16993
|
status: "failed",
|
|
16910
16994
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
16911
16995
|
log
|
|
16912
|
-
}).where(eq(
|
|
16996
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16913
16997
|
return result[0] || null;
|
|
16914
16998
|
}
|
|
16915
16999
|
static async heartbeat(id) {
|
|
16916
|
-
const result = await db.update(
|
|
17000
|
+
const result = await db.update(taskRuns3).set({ heartbeatAt: Date.now() }).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16917
17001
|
return result[0] || null;
|
|
16918
17002
|
}
|
|
16919
17003
|
static async updatePid(id, workerPid, childPid) {
|
|
16920
|
-
const result = await db.update(
|
|
17004
|
+
const result = await db.update(taskRuns3).set({
|
|
16921
17005
|
workerPid,
|
|
16922
17006
|
childPid,
|
|
16923
17007
|
lockedAt: Date.now(),
|
|
16924
17008
|
lockedBy: `gateway-${process.pid}`
|
|
16925
|
-
}).where(eq(
|
|
17009
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16926
17010
|
return result[0] || null;
|
|
16927
17011
|
}
|
|
16928
17012
|
static async getById(id) {
|
|
16929
|
-
const result = await db.select().from(
|
|
17013
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.id, id));
|
|
16930
17014
|
return result[0] || null;
|
|
16931
17015
|
}
|
|
16932
17016
|
static async listByTaskId(taskId) {
|
|
16933
|
-
return await db.select().from(
|
|
17017
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
16934
17018
|
}
|
|
16935
17019
|
static async getLatestByTaskId(taskId) {
|
|
16936
|
-
const result = await db.select().from(
|
|
17020
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id)).limit(1);
|
|
16937
17021
|
return result[0] || null;
|
|
16938
17022
|
}
|
|
16939
17023
|
static async getLatestByTaskIds(taskIds) {
|
|
16940
17024
|
if (taskIds.length === 0) return /* @__PURE__ */ new Map();
|
|
16941
|
-
const latestRuns = await db.select().from(
|
|
17025
|
+
const latestRuns = await db.select().from(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
16942
17026
|
const result = /* @__PURE__ */ new Map();
|
|
16943
17027
|
for (const run of latestRuns) {
|
|
16944
17028
|
if (!result.has(run.taskId)) {
|
|
@@ -16952,22 +17036,23 @@ var TaskRunService = class {
|
|
|
16952
17036
|
const cutoffSec = Math.floor(cutoffMs / 1e3);
|
|
16953
17037
|
const { tasks: tasksTable } = schema_exports;
|
|
16954
17038
|
const result = await db.select({
|
|
16955
|
-
runId:
|
|
16956
|
-
taskId:
|
|
16957
|
-
childPid:
|
|
17039
|
+
runId: taskRuns3.id,
|
|
17040
|
+
taskId: taskRuns3.taskId,
|
|
17041
|
+
childPid: taskRuns3.childPid,
|
|
16958
17042
|
taskRetryCount: tasksTable.retryCount,
|
|
16959
|
-
taskMaxRetries: tasksTable.maxRetries
|
|
16960
|
-
|
|
17043
|
+
taskMaxRetries: tasksTable.maxRetries,
|
|
17044
|
+
taskRetryBackoffMs: tasksTable.retryBackoffMs
|
|
17045
|
+
}).from(taskRuns3).innerJoin(tasksTable, eq(taskRuns3.taskId, tasksTable.id)).where(
|
|
16961
17046
|
and(
|
|
16962
|
-
eq(
|
|
17047
|
+
eq(taskRuns3.status, "running"),
|
|
16963
17048
|
or(
|
|
16964
17049
|
and(
|
|
16965
|
-
sql`${
|
|
16966
|
-
sql`${
|
|
17050
|
+
sql`${taskRuns3.heartbeatAt} IS NULL`,
|
|
17051
|
+
sql`${taskRuns3.startedAt} < ${cutoffSec}`
|
|
16967
17052
|
),
|
|
16968
17053
|
and(
|
|
16969
|
-
sql`${
|
|
16970
|
-
sql`${
|
|
17054
|
+
sql`${taskRuns3.heartbeatAt} IS NOT NULL`,
|
|
17055
|
+
sql`${taskRuns3.heartbeatAt} < ${cutoffMs}`
|
|
16971
17056
|
)
|
|
16972
17057
|
)
|
|
16973
17058
|
)
|
|
@@ -16977,28 +17062,50 @@ var TaskRunService = class {
|
|
|
16977
17062
|
taskId: row.taskId,
|
|
16978
17063
|
childPid: row.childPid,
|
|
16979
17064
|
taskRetryCount: row.taskRetryCount ?? 0,
|
|
16980
|
-
taskMaxRetries: row.taskMaxRetries ?? 3
|
|
17065
|
+
taskMaxRetries: row.taskMaxRetries ?? 3,
|
|
17066
|
+
taskRetryBackoffMs: row.taskRetryBackoffMs ?? 3e4
|
|
16981
17067
|
}));
|
|
16982
17068
|
}
|
|
16983
17069
|
static async getRunningRunByTaskId(taskId) {
|
|
16984
|
-
const result = await db.select().from(
|
|
17070
|
+
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
17071
|
return result[0] || null;
|
|
16986
17072
|
}
|
|
16987
17073
|
static async deleteByTaskIds(taskIds) {
|
|
16988
17074
|
if (taskIds.length === 0) return 0;
|
|
16989
|
-
const result = await db.delete(
|
|
17075
|
+
const result = await db.delete(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).returning();
|
|
16990
17076
|
return result.length;
|
|
16991
17077
|
}
|
|
16992
17078
|
static async getAllRunningRuns() {
|
|
16993
|
-
return await db.select().from(
|
|
17079
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.status, "running"));
|
|
16994
17080
|
}
|
|
16995
17081
|
};
|
|
16996
17082
|
|
|
17083
|
+
// src/core/cron-parser.ts
|
|
17084
|
+
var import_cron_parser = __toESM(require_dist(), 1);
|
|
17085
|
+
function getNextCronRun(expr, afterMs) {
|
|
17086
|
+
try {
|
|
17087
|
+
const fromDate = afterMs != null ? new Date(afterMs) : /* @__PURE__ */ new Date();
|
|
17088
|
+
const interval = import_cron_parser.CronExpressionParser.parse(expr, { currentDate: fromDate });
|
|
17089
|
+
const next = interval.next();
|
|
17090
|
+
return next.getTime();
|
|
17091
|
+
} catch {
|
|
17092
|
+
return null;
|
|
17093
|
+
}
|
|
17094
|
+
}
|
|
17095
|
+
function isValidCronExpr(expr) {
|
|
17096
|
+
try {
|
|
17097
|
+
import_cron_parser.CronExpressionParser.parse(expr);
|
|
17098
|
+
return true;
|
|
17099
|
+
} catch {
|
|
17100
|
+
return false;
|
|
17101
|
+
}
|
|
17102
|
+
}
|
|
17103
|
+
|
|
16997
17104
|
// src/core/services/task-template.service.ts
|
|
16998
|
-
init_cron_parser();
|
|
16999
17105
|
var { taskTemplates: taskTemplates2 } = schema_exports;
|
|
17000
17106
|
var TaskTemplateService = class {
|
|
17001
17107
|
static async create(data) {
|
|
17108
|
+
this.validate(data);
|
|
17002
17109
|
const now = Date.now();
|
|
17003
17110
|
const result = await db.insert(taskTemplates2).values({ ...data, createdAt: now, updatedAt: now }).returning();
|
|
17004
17111
|
const tmpl = result[0];
|
|
@@ -17014,8 +17121,38 @@ var TaskTemplateService = class {
|
|
|
17014
17121
|
}
|
|
17015
17122
|
return tmpl;
|
|
17016
17123
|
}
|
|
17124
|
+
static validate(data) {
|
|
17125
|
+
if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
17126
|
+
if (!data.agent.trim()) throw new Error("agent \u4E0D\u80FD\u4E3A\u7A7A");
|
|
17127
|
+
if (!data.prompt.trim()) throw new Error("prompt \u4E0D\u80FD\u4E3A\u7A7A");
|
|
17128
|
+
const scheduleType = data.scheduleType;
|
|
17129
|
+
if (!["cron", "delayed", "recurring"].includes(scheduleType)) {
|
|
17130
|
+
throw new Error("scheduleType \u5FC5\u987B\u662F cron\u3001delayed \u6216 recurring");
|
|
17131
|
+
}
|
|
17132
|
+
if (scheduleType === "cron" && (!data.cronExpr || !isValidCronExpr(data.cronExpr))) {
|
|
17133
|
+
throw new Error("cronExpr \u7F3A\u5931\u6216\u683C\u5F0F\u65E0\u6548");
|
|
17134
|
+
}
|
|
17135
|
+
if (scheduleType === "recurring" && (!Number.isInteger(data.intervalMs) || (data.intervalMs ?? 0) <= 0)) {
|
|
17136
|
+
throw new Error("intervalMs \u5FC5\u987B\u662F\u6B63\u6574\u6570");
|
|
17137
|
+
}
|
|
17138
|
+
if (scheduleType === "delayed" && (!Number.isInteger(data.runAt) || (data.runAt ?? 0) <= 0)) {
|
|
17139
|
+
throw new Error("runAt \u5FC5\u987B\u662F\u6B63\u6574\u6570\u65F6\u95F4\u6233");
|
|
17140
|
+
}
|
|
17141
|
+
this.validateInteger("importance", data.importance, 1, 5);
|
|
17142
|
+
this.validateInteger("urgency", data.urgency, 1, 5);
|
|
17143
|
+
this.validateInteger("maxInstances", data.maxInstances, 1, 1e3);
|
|
17144
|
+
this.validateInteger("maxRetries", data.maxRetries, 0, 1e3);
|
|
17145
|
+
this.validateInteger("retryBackoffMs", data.retryBackoffMs, 0, 864e5);
|
|
17146
|
+
this.validateInteger("timeoutMs", data.timeoutMs, 1e3, 6048e5);
|
|
17147
|
+
}
|
|
17148
|
+
static validateInteger(name, value, min, max) {
|
|
17149
|
+
if (value === void 0 || value === null) return;
|
|
17150
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
17151
|
+
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
17152
|
+
}
|
|
17153
|
+
}
|
|
17017
17154
|
static async list(limit = 50) {
|
|
17018
|
-
return await db.select().from(taskTemplates2).orderBy(desc(taskTemplates2.createdAt)).limit(limit);
|
|
17155
|
+
return await db.select().from(taskTemplates2).orderBy(desc(taskTemplates2.createdAt), desc(taskTemplates2.id)).limit(limit);
|
|
17019
17156
|
}
|
|
17020
17157
|
static async getById(id) {
|
|
17021
17158
|
const result = await db.select().from(taskTemplates2).where(eq(taskTemplates2.id, id));
|
|
@@ -17054,72 +17191,236 @@ var TaskTemplateService = class {
|
|
|
17054
17191
|
};
|
|
17055
17192
|
|
|
17056
17193
|
// src/gateway/config.ts
|
|
17057
|
-
import {
|
|
17194
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
17058
17195
|
import { homedir as homedir2 } from "os";
|
|
17059
17196
|
import { join as join2 } from "path";
|
|
17060
17197
|
var DEFAULT_CONFIG = {
|
|
17198
|
+
configVersion: 2,
|
|
17061
17199
|
worker: {
|
|
17062
17200
|
maxConcurrency: 2,
|
|
17063
17201
|
pollIntervalMs: 1e3,
|
|
17064
17202
|
heartbeatIntervalMs: 3e4,
|
|
17065
|
-
taskTimeoutMs: 18e5
|
|
17203
|
+
taskTimeoutMs: 18e5,
|
|
17204
|
+
shutdownGracePeriodMs: 3e4
|
|
17066
17205
|
},
|
|
17067
17206
|
scheduler: {
|
|
17068
17207
|
enabled: true,
|
|
17069
|
-
checkIntervalMs: 1e3
|
|
17070
|
-
catchUp: "next"
|
|
17208
|
+
checkIntervalMs: 1e3
|
|
17071
17209
|
},
|
|
17072
17210
|
watchdog: {
|
|
17073
17211
|
heartbeatTimeoutMs: 6e5,
|
|
17074
|
-
|
|
17212
|
+
checkIntervalMs: 6e4,
|
|
17213
|
+
cleanupIntervalMs: 864e5,
|
|
17075
17214
|
retentionDays: 30
|
|
17076
17215
|
},
|
|
17077
17216
|
dashboard: {
|
|
17078
17217
|
enabled: true,
|
|
17079
17218
|
port: 4680
|
|
17080
|
-
},
|
|
17081
|
-
logging: {
|
|
17082
|
-
level: "info",
|
|
17083
|
-
format: "json"
|
|
17084
17219
|
}
|
|
17085
17220
|
};
|
|
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
|
-
}
|
|
17221
|
+
var DEFAULT_CONFIG_PATH = join2(homedir2(), ".config/opencode/supertask.json");
|
|
17222
|
+
function getConfigPath() {
|
|
17223
|
+
return process.env.SUPERTASK_CONFIG_PATH ?? DEFAULT_CONFIG_PATH;
|
|
17224
|
+
}
|
|
17225
|
+
function objectAt(value, path) {
|
|
17226
|
+
if (value === void 0) return {};
|
|
17227
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
17228
|
+
throw new Error(`${path} \u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
17101
17229
|
}
|
|
17102
|
-
return
|
|
17230
|
+
return value;
|
|
17231
|
+
}
|
|
17232
|
+
function integerAt(source, key, fallback, min, max, path) {
|
|
17233
|
+
const value = source[key];
|
|
17234
|
+
if (value === void 0) return fallback;
|
|
17235
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
|
|
17236
|
+
throw new Error(`${path}.${key} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
17237
|
+
}
|
|
17238
|
+
return value;
|
|
17103
17239
|
}
|
|
17104
|
-
function
|
|
17105
|
-
|
|
17106
|
-
|
|
17240
|
+
function booleanAt(source, key, fallback, path) {
|
|
17241
|
+
const value = source[key];
|
|
17242
|
+
if (value === void 0) return fallback;
|
|
17243
|
+
if (typeof value !== "boolean") throw new Error(`${path}.${key} \u5FC5\u987B\u662F\u5E03\u5C14\u503C`);
|
|
17244
|
+
return value;
|
|
17245
|
+
}
|
|
17246
|
+
function validateConfig(input) {
|
|
17247
|
+
const root = objectAt(input, "config");
|
|
17248
|
+
const version2 = root.configVersion ?? 1;
|
|
17249
|
+
if (version2 !== 1 && version2 !== 2) {
|
|
17250
|
+
throw new Error("config.configVersion \u53EA\u652F\u6301 1 \u6216 2");
|
|
17251
|
+
}
|
|
17252
|
+
const worker = objectAt(root.worker, "worker");
|
|
17253
|
+
const scheduler = objectAt(root.scheduler, "scheduler");
|
|
17254
|
+
const watchdog = objectAt(root.watchdog, "watchdog");
|
|
17255
|
+
const dashboard = objectAt(root.dashboard, "dashboard");
|
|
17256
|
+
const heartbeatIntervalMs = integerAt(worker, "heartbeatIntervalMs", DEFAULT_CONFIG.worker.heartbeatIntervalMs, 1e3, 36e5, "worker");
|
|
17257
|
+
const heartbeatTimeoutMs = integerAt(watchdog, "heartbeatTimeoutMs", DEFAULT_CONFIG.watchdog.heartbeatTimeoutMs, 1e3, 864e5, "watchdog");
|
|
17258
|
+
let checkIntervalMs;
|
|
17259
|
+
let cleanupIntervalMs;
|
|
17260
|
+
if (version2 === 1 && watchdog.checkIntervalMs === void 0 && watchdog.cleanupIntervalMs !== void 0) {
|
|
17261
|
+
checkIntervalMs = integerAt(watchdog, "cleanupIntervalMs", DEFAULT_CONFIG.watchdog.checkIntervalMs, 1e3, 36e5, "watchdog");
|
|
17262
|
+
cleanupIntervalMs = DEFAULT_CONFIG.watchdog.cleanupIntervalMs;
|
|
17263
|
+
} else {
|
|
17264
|
+
checkIntervalMs = integerAt(watchdog, "checkIntervalMs", DEFAULT_CONFIG.watchdog.checkIntervalMs, 1e3, 36e5, "watchdog");
|
|
17265
|
+
cleanupIntervalMs = integerAt(watchdog, "cleanupIntervalMs", DEFAULT_CONFIG.watchdog.cleanupIntervalMs, 6e4, 6048e5, "watchdog");
|
|
17266
|
+
}
|
|
17267
|
+
if (heartbeatIntervalMs >= heartbeatTimeoutMs) {
|
|
17268
|
+
throw new Error("worker.heartbeatIntervalMs \u5FC5\u987B\u5C0F\u4E8E watchdog.heartbeatTimeoutMs");
|
|
17269
|
+
}
|
|
17270
|
+
if (checkIntervalMs > heartbeatTimeoutMs) {
|
|
17271
|
+
throw new Error("watchdog.checkIntervalMs \u4E0D\u80FD\u5927\u4E8E watchdog.heartbeatTimeoutMs");
|
|
17107
17272
|
}
|
|
17273
|
+
return {
|
|
17274
|
+
configVersion: 2,
|
|
17275
|
+
worker: {
|
|
17276
|
+
maxConcurrency: integerAt(worker, "maxConcurrency", DEFAULT_CONFIG.worker.maxConcurrency, 1, 64, "worker"),
|
|
17277
|
+
pollIntervalMs: integerAt(worker, "pollIntervalMs", DEFAULT_CONFIG.worker.pollIntervalMs, 50, 6e4, "worker"),
|
|
17278
|
+
heartbeatIntervalMs,
|
|
17279
|
+
taskTimeoutMs: integerAt(worker, "taskTimeoutMs", DEFAULT_CONFIG.worker.taskTimeoutMs, 1e3, 6048e5, "worker"),
|
|
17280
|
+
shutdownGracePeriodMs: integerAt(worker, "shutdownGracePeriodMs", DEFAULT_CONFIG.worker.shutdownGracePeriodMs, 0, 36e5, "worker")
|
|
17281
|
+
},
|
|
17282
|
+
scheduler: {
|
|
17283
|
+
enabled: booleanAt(scheduler, "enabled", DEFAULT_CONFIG.scheduler.enabled, "scheduler"),
|
|
17284
|
+
checkIntervalMs: integerAt(scheduler, "checkIntervalMs", DEFAULT_CONFIG.scheduler.checkIntervalMs, 100, 6e4, "scheduler")
|
|
17285
|
+
},
|
|
17286
|
+
watchdog: {
|
|
17287
|
+
heartbeatTimeoutMs,
|
|
17288
|
+
checkIntervalMs,
|
|
17289
|
+
cleanupIntervalMs,
|
|
17290
|
+
retentionDays: integerAt(watchdog, "retentionDays", DEFAULT_CONFIG.watchdog.retentionDays, 1, 3650, "watchdog")
|
|
17291
|
+
},
|
|
17292
|
+
dashboard: {
|
|
17293
|
+
enabled: booleanAt(dashboard, "enabled", DEFAULT_CONFIG.dashboard.enabled, "dashboard"),
|
|
17294
|
+
port: integerAt(dashboard, "port", DEFAULT_CONFIG.dashboard.port, 1, 65535, "dashboard")
|
|
17295
|
+
}
|
|
17296
|
+
};
|
|
17297
|
+
}
|
|
17298
|
+
function loadConfig(path = getConfigPath()) {
|
|
17299
|
+
if (!existsSync2(path)) return validateConfig({ configVersion: 2 });
|
|
17108
17300
|
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;
|
|
17301
|
+
return validateConfig(JSON.parse(readFileSync(path, "utf-8")));
|
|
17302
|
+
} catch (error) {
|
|
17303
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
17304
|
+
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E ${path}: ${message}`);
|
|
17116
17305
|
}
|
|
17117
17306
|
}
|
|
17118
17307
|
|
|
17119
17308
|
// src/web/index.tsx
|
|
17120
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
17309
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2, renameSync } from "fs";
|
|
17121
17310
|
import { dirname as dirname2 } from "path";
|
|
17311
|
+
|
|
17312
|
+
// src/gateway/health.ts
|
|
17313
|
+
var LOCK_STALE_MS = 3e4;
|
|
17314
|
+
var state = null;
|
|
17315
|
+
function componentStatus(component, enabled, maxAgeMs, now) {
|
|
17316
|
+
const lastActivityAt = state?.lastActivityAt[component] ?? null;
|
|
17317
|
+
const ageMs = lastActivityAt == null ? null : Math.max(0, now - lastActivityAt);
|
|
17318
|
+
return {
|
|
17319
|
+
enabled,
|
|
17320
|
+
healthy: !enabled || ageMs != null && ageMs <= maxAgeMs,
|
|
17321
|
+
lastActivityAt,
|
|
17322
|
+
ageMs,
|
|
17323
|
+
maxAgeMs
|
|
17324
|
+
};
|
|
17325
|
+
}
|
|
17326
|
+
function getGatewayHealth(now = Date.now()) {
|
|
17327
|
+
const worker = componentStatus(
|
|
17328
|
+
"worker",
|
|
17329
|
+
true,
|
|
17330
|
+
Math.max((state?.config.workerPollIntervalMs ?? 1e3) * 5, 5e3),
|
|
17331
|
+
now
|
|
17332
|
+
);
|
|
17333
|
+
const scheduler = componentStatus(
|
|
17334
|
+
"scheduler",
|
|
17335
|
+
state?.config.schedulerEnabled ?? false,
|
|
17336
|
+
Math.max((state?.config.schedulerCheckIntervalMs ?? 1e3) * 5, 5e3),
|
|
17337
|
+
now
|
|
17338
|
+
);
|
|
17339
|
+
const watchdog = componentStatus(
|
|
17340
|
+
"watchdog",
|
|
17341
|
+
true,
|
|
17342
|
+
Math.max((state?.config.watchdogCheckIntervalMs ?? 6e4) * 3, 5e3),
|
|
17343
|
+
now
|
|
17344
|
+
);
|
|
17345
|
+
let lock = { pid: null, heartbeatAt: null, readyAt: null, ageMs: null, healthy: false };
|
|
17346
|
+
try {
|
|
17347
|
+
const row = sqlite.prepare(
|
|
17348
|
+
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
17349
|
+
).get();
|
|
17350
|
+
if (row) {
|
|
17351
|
+
const ageMs = Math.max(0, now - row.heartbeat_at);
|
|
17352
|
+
lock = {
|
|
17353
|
+
pid: row.pid,
|
|
17354
|
+
heartbeatAt: row.heartbeat_at,
|
|
17355
|
+
readyAt: row.ready_at,
|
|
17356
|
+
ageMs,
|
|
17357
|
+
healthy: row.pid === process.pid && row.ready_at != null && ageMs < LOCK_STALE_MS
|
|
17358
|
+
};
|
|
17359
|
+
}
|
|
17360
|
+
} catch {
|
|
17361
|
+
}
|
|
17362
|
+
const healthy = state != null && lock.healthy && worker.healthy && scheduler.healthy && watchdog.healthy;
|
|
17363
|
+
return {
|
|
17364
|
+
status: healthy ? "ok" : "degraded",
|
|
17365
|
+
pid: process.pid,
|
|
17366
|
+
uptimeMs: state ? Math.max(0, now - state.startedAt) : 0,
|
|
17367
|
+
lock,
|
|
17368
|
+
components: { worker, scheduler, watchdog }
|
|
17369
|
+
};
|
|
17370
|
+
}
|
|
17371
|
+
|
|
17372
|
+
// src/web/index.tsx
|
|
17122
17373
|
var app = new Hono2();
|
|
17374
|
+
var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
17375
|
+
"pending",
|
|
17376
|
+
"running",
|
|
17377
|
+
"done",
|
|
17378
|
+
"failed",
|
|
17379
|
+
"dead_letter",
|
|
17380
|
+
"cancelled"
|
|
17381
|
+
]);
|
|
17382
|
+
function parsePositiveInteger(value) {
|
|
17383
|
+
if (!/^\d+$/.test(value)) return null;
|
|
17384
|
+
const parsed = Number(value);
|
|
17385
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
17386
|
+
}
|
|
17387
|
+
function parseTaskStatus(value) {
|
|
17388
|
+
return TASK_STATUSES.has(value) ? value : null;
|
|
17389
|
+
}
|
|
17390
|
+
function safeStatus(value) {
|
|
17391
|
+
return value && TASK_STATUSES.has(value) ? value : "unknown";
|
|
17392
|
+
}
|
|
17393
|
+
app.use("*", async (c, next) => {
|
|
17394
|
+
await next();
|
|
17395
|
+
c.header("X-Content-Type-Options", "nosniff");
|
|
17396
|
+
c.header("X-Frame-Options", "DENY");
|
|
17397
|
+
c.header("Referrer-Policy", "no-referrer");
|
|
17398
|
+
});
|
|
17399
|
+
app.use("/api/*", async (c, next) => {
|
|
17400
|
+
if (["GET", "HEAD", "OPTIONS"].includes(c.req.method)) return next();
|
|
17401
|
+
const fetchSite = c.req.header("Sec-Fetch-Site");
|
|
17402
|
+
if (fetchSite && !["same-origin", "none"].includes(fetchSite)) {
|
|
17403
|
+
return c.json({ error: "cross-site request rejected" }, 403);
|
|
17404
|
+
}
|
|
17405
|
+
const origin = c.req.header("Origin");
|
|
17406
|
+
if (origin) {
|
|
17407
|
+
try {
|
|
17408
|
+
const originUrl = new URL(origin);
|
|
17409
|
+
const requestUrl = new URL(c.req.url);
|
|
17410
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(originUrl.hostname);
|
|
17411
|
+
if (!loopback || originUrl.origin !== requestUrl.origin) {
|
|
17412
|
+
return c.json({ error: "cross-site request rejected" }, 403);
|
|
17413
|
+
}
|
|
17414
|
+
} catch {
|
|
17415
|
+
return c.json({ error: "invalid origin" }, 403);
|
|
17416
|
+
}
|
|
17417
|
+
}
|
|
17418
|
+
return next();
|
|
17419
|
+
});
|
|
17420
|
+
app.get("/health", (c) => {
|
|
17421
|
+
const health = getGatewayHealth();
|
|
17422
|
+
return c.json(health, health.status === "ok" ? 200 : 503);
|
|
17423
|
+
});
|
|
17123
17424
|
function formatDuration(startAt, endAt) {
|
|
17124
17425
|
if (!startAt) return "-";
|
|
17125
17426
|
const start = new Date(startAt).getTime();
|
|
@@ -17157,17 +17458,21 @@ function esc(s) {
|
|
|
17157
17458
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
17158
17459
|
}
|
|
17159
17460
|
function readCurrentConfig() {
|
|
17160
|
-
|
|
17461
|
+
const configPath = getConfigPath();
|
|
17462
|
+
if (!existsSync3(configPath)) return {};
|
|
17161
17463
|
try {
|
|
17162
|
-
return JSON.parse(readFileSync2(
|
|
17464
|
+
return JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
17163
17465
|
} catch {
|
|
17164
17466
|
return {};
|
|
17165
17467
|
}
|
|
17166
17468
|
}
|
|
17167
17469
|
function writeConfig(cfg) {
|
|
17168
|
-
const
|
|
17470
|
+
const configPath = getConfigPath();
|
|
17471
|
+
const dir = dirname2(configPath);
|
|
17169
17472
|
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
17170
|
-
|
|
17473
|
+
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
17474
|
+
writeFileSync(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
17475
|
+
renameSync(tempPath, configPath);
|
|
17171
17476
|
}
|
|
17172
17477
|
var SHARED_STYLES = html`
|
|
17173
17478
|
<style>
|
|
@@ -17281,11 +17586,11 @@ async function saveConfig(){
|
|
|
17281
17586
|
scheduler:{
|
|
17282
17587
|
enabled:form.se.checked,
|
|
17283
17588
|
checkIntervalMs:Number(form.si.value),
|
|
17284
|
-
catchUp:form.cu.value,
|
|
17285
17589
|
},
|
|
17286
17590
|
watchdog:{
|
|
17287
17591
|
heartbeatTimeoutMs:Number(form.wt.value)*1000,
|
|
17288
|
-
|
|
17592
|
+
checkIntervalMs:Number(form.wci.value)*1000,
|
|
17593
|
+
cleanupIntervalMs:Number(form.wcl.value)*3600000,
|
|
17289
17594
|
retentionDays:Number(form.rd.value),
|
|
17290
17595
|
}
|
|
17291
17596
|
};
|
|
@@ -17317,12 +17622,16 @@ async function saveConfig(){
|
|
|
17317
17622
|
</body></html>`;
|
|
17318
17623
|
}
|
|
17319
17624
|
app.get("/", async (c) => {
|
|
17320
|
-
const
|
|
17625
|
+
const pageParam = c.req.query("page") || "1";
|
|
17626
|
+
const page = parsePositiveInteger(pageParam);
|
|
17627
|
+
if (page === null) return c.text("invalid page", 400);
|
|
17321
17628
|
const statusFilter = c.req.query("status") || "";
|
|
17629
|
+
const parsedStatus = statusFilter ? parseTaskStatus(statusFilter) : null;
|
|
17630
|
+
if (statusFilter && !parsedStatus) return c.text("invalid status", 400);
|
|
17322
17631
|
const limit = 50;
|
|
17323
17632
|
const offset = (page - 1) * limit;
|
|
17324
17633
|
const [tasks3, statsData] = await Promise.all([
|
|
17325
|
-
TaskService.list({ limit, offset, ...
|
|
17634
|
+
TaskService.list({ limit, offset, ...parsedStatus ? { status: parsedStatus } : {} }),
|
|
17326
17635
|
TaskService.stats({})
|
|
17327
17636
|
]);
|
|
17328
17637
|
const taskIds = tasks3.map((t) => t.id);
|
|
@@ -17345,12 +17654,13 @@ app.get("/", async (c) => {
|
|
|
17345
17654
|
</div>`;
|
|
17346
17655
|
let rows = "";
|
|
17347
17656
|
for (const task of tasks3) {
|
|
17348
|
-
const
|
|
17657
|
+
const status = safeStatus(task.status);
|
|
17658
|
+
const st = status.toUpperCase();
|
|
17349
17659
|
rows += `<tr>
|
|
17350
17660
|
<td class="mu">#${task.id}</td>
|
|
17351
17661
|
<td><div style="font-weight:500">${esc(task.name)}</div><div class="mu sm el">${esc(task.prompt.substring(0, 120))}</div></td>
|
|
17352
17662
|
<td><span class="tag">${esc(task.agent)}</span></td>
|
|
17353
|
-
<td><span class="badge b-${
|
|
17663
|
+
<td><span class="badge b-${status}">${st}</span></td>
|
|
17354
17664
|
<td class="sm ${task.status === "running" ? "" : "mu"}">${formatDuration(task.startedAt, task.finishedAt)}</td>
|
|
17355
17665
|
<td class="mu sm">${(task.retryCount ?? 0) > 0 ? task.retryCount : "-"}</td>
|
|
17356
17666
|
<td>
|
|
@@ -17382,21 +17692,13 @@ app.get("/", async (c) => {
|
|
|
17382
17692
|
});
|
|
17383
17693
|
app.get("/templates", async (c) => {
|
|
17384
17694
|
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
17695
|
const enabled = templates.filter((t) => t.enabled).length;
|
|
17395
17696
|
const disabled = templates.length - enabled;
|
|
17396
17697
|
let rows = "";
|
|
17397
17698
|
for (const t of templates) {
|
|
17398
|
-
const
|
|
17399
|
-
const
|
|
17699
|
+
const scheduleType = ["cron", "recurring", "delayed"].includes(t.scheduleType) ? t.scheduleType : "unknown";
|
|
17700
|
+
const typeLabel = scheduleType === "cron" ? "Cron" : scheduleType === "recurring" ? "\u5FAA\u73AF" : scheduleType === "delayed" ? "\u5B9A\u65F6" : "\u672A\u77E5";
|
|
17701
|
+
const typeClass = "tag t-" + scheduleType;
|
|
17400
17702
|
let rule = "-";
|
|
17401
17703
|
if (t.scheduleType === "cron") rule = t.cronExpr || "-";
|
|
17402
17704
|
else if (t.scheduleType === "recurring") rule = t.intervalMs ? `${Math.floor(t.intervalMs / 6e4)}\u5206\u949F` : "-";
|
|
@@ -17408,7 +17710,7 @@ app.get("/templates", async (c) => {
|
|
|
17408
17710
|
<td><div style="font-weight:500">${esc(t.name)}</div><div class="mu sm el">${esc(t.prompt.substring(0, 100))}</div>
|
|
17409
17711
|
<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
17712
|
<td><span class="${typeClass}">${typeLabel}</span></td>
|
|
17411
|
-
<td class="m sm">${rule}</td>
|
|
17713
|
+
<td class="m sm">${esc(rule)}</td>
|
|
17412
17714
|
<td>${statusBadge}</td>
|
|
17413
17715
|
<td class="sm">${t.lastRunAt ? timeAgo(t.lastRunAt) : "-"}</td>
|
|
17414
17716
|
<td class="sm">${t.nextRunAt ? timeUntil(t.nextRunAt) : "-"}</td>
|
|
@@ -17436,7 +17738,8 @@ app.get("/templates", async (c) => {
|
|
|
17436
17738
|
return c.html(renderLayout("\u5B9A\u65F6\u4EFB\u52A1", "templates", body));
|
|
17437
17739
|
});
|
|
17438
17740
|
app.get("/runs", async (c) => {
|
|
17439
|
-
const page =
|
|
17741
|
+
const page = parsePositiveInteger(c.req.query("page") || "1");
|
|
17742
|
+
if (page === null) return c.text("invalid page", 400);
|
|
17440
17743
|
const limit = 50;
|
|
17441
17744
|
const offset = (page - 1) * limit;
|
|
17442
17745
|
const { taskRuns: tr, tasks: tk } = schema_exports;
|
|
@@ -17454,13 +17757,14 @@ app.get("/runs", async (c) => {
|
|
|
17454
17757
|
childPid: tr.childPid,
|
|
17455
17758
|
taskName: tk.name,
|
|
17456
17759
|
taskAgent: tk.agent
|
|
17457
|
-
}).from(tr).innerJoin(tk, eq(tr.taskId, tk.id)).orderBy(desc(tr.startedAt)).limit(limit).offset(offset);
|
|
17760
|
+
}).from(tr).innerJoin(tk, eq(tr.taskId, tk.id)).orderBy(desc(tr.startedAt), desc(tr.id)).limit(limit).offset(offset);
|
|
17458
17761
|
const totalResult = await db.select({ count: sql`count(*)` }).from(tr);
|
|
17459
17762
|
const total = Number(totalResult[0]?.count ?? 0);
|
|
17460
17763
|
const totalPages = Math.ceil(total / limit);
|
|
17461
17764
|
let rows = "";
|
|
17462
17765
|
const logsHtml = [];
|
|
17463
17766
|
for (const run of runs) {
|
|
17767
|
+
const status = safeStatus(run.status);
|
|
17464
17768
|
const shortSession = run.sessionId ? run.sessionId.slice(4, 7) + "***" + run.sessionId.slice(-3) : "-";
|
|
17465
17769
|
const logBtn = run.log ? `<button class="btn btn-sm" onclick="toggleLog(${run.id})">\u65E5\u5FD7</button>` : "";
|
|
17466
17770
|
rows += `<tr>
|
|
@@ -17468,15 +17772,15 @@ app.get("/runs", async (c) => {
|
|
|
17468
17772
|
<td><div style="font-weight:500">${esc(run.taskName)} <span class="mu">(#${run.taskId})</span></div>
|
|
17469
17773
|
${run.model ? `<div class="sm"><span class="tag">${esc(run.model)}</span></div>` : ""}</td>
|
|
17470
17774
|
<td><span class="tag">${esc(run.taskAgent)}</span></td>
|
|
17471
|
-
<td><span class="badge b-${
|
|
17775
|
+
<td><span class="badge b-${status}">${status.toUpperCase()}</span></td>
|
|
17472
17776
|
<td class="sm">${formatDuration(run.startedAt, run.finishedAt)}</td>
|
|
17473
17777
|
<td class="sm mu">${run.heartbeatAt ? timeAgo(run.heartbeatAt) : "-"}</td>
|
|
17474
17778
|
<td><button class="btn btn-sm" onclick="showRunDetail(${run.id})">\u8BE6\u60C5</button>${logBtn}</td>
|
|
17475
17779
|
</tr>`;
|
|
17476
17780
|
if (run.log) {
|
|
17477
17781
|
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
|
|
17782
|
+
<div class="panel"><div class="ph"><h3>Run #${run.id} \u65E5\u5FD7 \u2014 ${esc(run.taskName)}</h3></div>
|
|
17783
|
+
<div class="log-box">${esc(run.log)}</div></div></div>`);
|
|
17480
17784
|
}
|
|
17481
17785
|
}
|
|
17482
17786
|
let paging = `<div class="pn">`;
|
|
@@ -17502,17 +17806,18 @@ app.get("/runs", async (c) => {
|
|
|
17502
17806
|
});
|
|
17503
17807
|
app.get("/system", async (c) => {
|
|
17504
17808
|
const config = loadConfig();
|
|
17809
|
+
const configPath = getConfigPath();
|
|
17505
17810
|
const stats = await TaskService.stats({});
|
|
17506
17811
|
const runningRuns = await TaskRunService.getAllRunningRuns();
|
|
17507
17812
|
const templates = await TaskTemplateService.list(100);
|
|
17508
|
-
const configExists = existsSync3(
|
|
17813
|
+
const configExists = existsSync3(configPath);
|
|
17509
17814
|
let runRows = "";
|
|
17510
17815
|
if (runningRuns.length > 0) {
|
|
17511
17816
|
for (const run of runningRuns) {
|
|
17512
17817
|
const shortS = run.sessionId ? run.sessionId.slice(4, 7) + "***" + run.sessionId.slice(-3) : "-";
|
|
17513
17818
|
runRows += `<tr>
|
|
17514
17819
|
<td class="mu">#${run.id}</td><td>#${run.taskId}</td>
|
|
17515
|
-
<td class="m sm">${shortS}</td>
|
|
17820
|
+
<td class="m sm">${esc(shortS)}</td>
|
|
17516
17821
|
<td class="sm">${esc(run.model) || "-"}</td>
|
|
17517
17822
|
<td class="sm">${formatDate(run.startedAt)}</td>
|
|
17518
17823
|
<td class="sm">${run.heartbeatAt ? timeAgo(run.heartbeatAt) : "-"}</td>
|
|
@@ -17537,19 +17842,14 @@ app.get("/system", async (c) => {
|
|
|
17537
17842
|
<h3 style="margin:0 0 12px;font-size:14px">Scheduler \u914D\u7F6E</h3>
|
|
17538
17843
|
<div class="form-row"><label>\u542F\u7528\u8C03\u5EA6</label><input type="checkbox" name="se" ${config.scheduler.enabled ? "checked" : ""}></div>
|
|
17539
17844
|
<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
17845
|
<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
17846
|
</div>
|
|
17547
17847
|
<div class="card">
|
|
17548
17848
|
<h3 style="margin:0 0 12px;font-size:14px">Watchdog \u914D\u7F6E</h3>
|
|
17549
17849
|
<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>\
|
|
17850
|
+
<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>
|
|
17851
|
+
<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
17852
|
<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
17853
|
</div>
|
|
17554
17854
|
</div>
|
|
17555
17855
|
<div style="text-align:center;margin-bottom:24px">
|
|
@@ -17578,7 +17878,7 @@ app.get("/system", async (c) => {
|
|
|
17578
17878
|
|
|
17579
17879
|
<div class="card mt16">
|
|
17580
17880
|
<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">${
|
|
17881
|
+
<div class="ir"><span class="ik">\u8DEF\u5F84</span><span class="iv m sm">${esc(configPath)}</span></div>
|
|
17582
17882
|
<div class="ir"><span class="ik">\u6587\u4EF6\u5B58\u5728</span><span class="iv">${configFileStatus}</span></div>
|
|
17583
17883
|
</div>
|
|
17584
17884
|
|
|
@@ -17590,46 +17890,61 @@ app.get("/system", async (c) => {
|
|
|
17590
17890
|
return c.html(renderLayout("\u7CFB\u7EDF\u72B6\u6001", "system", body));
|
|
17591
17891
|
});
|
|
17592
17892
|
app.get("/api/tasks/:id", async (c) => {
|
|
17593
|
-
const id =
|
|
17893
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17894
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17594
17895
|
const task = await TaskService.getById(id);
|
|
17595
17896
|
if (!task) return c.json({ error: "not found" }, 404);
|
|
17596
17897
|
const runs = await TaskRunService.listByTaskId(id);
|
|
17597
17898
|
return c.json({ ...task, _runs: runs });
|
|
17598
17899
|
});
|
|
17599
17900
|
app.get("/api/runs/:id", async (c) => {
|
|
17600
|
-
const id =
|
|
17901
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17902
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17601
17903
|
const run = await TaskRunService.getById(id);
|
|
17602
17904
|
if (!run) return c.json({ error: "not found" }, 404);
|
|
17603
17905
|
return c.json(run);
|
|
17604
17906
|
});
|
|
17605
17907
|
app.get("/api/templates/:id", async (c) => {
|
|
17606
|
-
const id =
|
|
17908
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17909
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17607
17910
|
const tmpl = await TaskTemplateService.getById(id);
|
|
17608
17911
|
if (!tmpl) return c.json({ error: "not found" }, 404);
|
|
17609
17912
|
return c.json(tmpl);
|
|
17610
17913
|
});
|
|
17611
17914
|
app.post("/api/tasks/:id/retry", async (c) => {
|
|
17612
|
-
|
|
17613
|
-
return c.json({
|
|
17915
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17916
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17917
|
+
const task = await TaskService.retry(id);
|
|
17918
|
+
if (task) return c.json({ success: true });
|
|
17919
|
+
return await TaskService.getById(id) ? c.json({ error: "task status does not allow retry" }, 409) : c.json({ error: "not found" }, 404);
|
|
17614
17920
|
});
|
|
17615
17921
|
app.delete("/api/tasks/:id", async (c) => {
|
|
17616
|
-
|
|
17617
|
-
return c.json({
|
|
17922
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17923
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17924
|
+
const deleted = await TaskService.delete(id);
|
|
17925
|
+
return deleted ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
17618
17926
|
});
|
|
17619
17927
|
app.post("/api/templates/:id/enable", async (c) => {
|
|
17620
|
-
const
|
|
17621
|
-
return c.json({
|
|
17928
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17929
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17930
|
+
const result = await TaskTemplateService.enable(id);
|
|
17931
|
+
return result ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
17622
17932
|
});
|
|
17623
17933
|
app.post("/api/templates/:id/disable", async (c) => {
|
|
17624
|
-
const
|
|
17625
|
-
return c.json({
|
|
17934
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17935
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17936
|
+
const result = await TaskTemplateService.disable(id);
|
|
17937
|
+
return result ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
17626
17938
|
});
|
|
17627
17939
|
app.delete("/api/templates/:id", async (c) => {
|
|
17628
|
-
const
|
|
17629
|
-
return c.json({
|
|
17940
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17941
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17942
|
+
const ok = await TaskTemplateService.delete(id);
|
|
17943
|
+
return ok ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
17630
17944
|
});
|
|
17631
17945
|
app.post("/api/templates/:id/trigger", async (c) => {
|
|
17632
|
-
const id =
|
|
17946
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17947
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17633
17948
|
const tmpl = await TaskTemplateService.getById(id);
|
|
17634
17949
|
if (!tmpl) return c.json({ error: "not found" }, 404);
|
|
17635
17950
|
const task = await TaskService.add({
|
|
@@ -17641,7 +17956,10 @@ app.post("/api/templates/:id/trigger", async (c) => {
|
|
|
17641
17956
|
category: tmpl.category,
|
|
17642
17957
|
importance: tmpl.importance,
|
|
17643
17958
|
urgency: tmpl.urgency,
|
|
17959
|
+
batchId: tmpl.batchId,
|
|
17644
17960
|
maxRetries: tmpl.maxRetries,
|
|
17961
|
+
retryBackoffMs: tmpl.retryBackoffMs,
|
|
17962
|
+
timeoutMs: tmpl.timeoutMs,
|
|
17645
17963
|
templateId: tmpl.id
|
|
17646
17964
|
});
|
|
17647
17965
|
return c.json({ success: true, taskId: task.id });
|
|
@@ -17656,22 +17974,29 @@ app.put("/api/config", async (c) => {
|
|
|
17656
17974
|
const bW = body.worker ?? {};
|
|
17657
17975
|
const bS = body.scheduler ?? {};
|
|
17658
17976
|
const bD = body.watchdog ?? {};
|
|
17659
|
-
const merged = {
|
|
17660
|
-
|
|
17977
|
+
const merged = {
|
|
17978
|
+
...current,
|
|
17979
|
+
...body,
|
|
17980
|
+
configVersion: 2,
|
|
17981
|
+
worker: { ...curW, ...bW },
|
|
17982
|
+
scheduler: { ...curS, ...bS },
|
|
17983
|
+
watchdog: { ...curD, ...bD }
|
|
17984
|
+
};
|
|
17985
|
+
writeConfig(validateConfig(merged));
|
|
17661
17986
|
return c.json({ success: true });
|
|
17662
17987
|
} catch (err) {
|
|
17663
|
-
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
17988
|
+
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) }, 400);
|
|
17664
17989
|
}
|
|
17665
17990
|
});
|
|
17666
17991
|
app.post("/api/database/clear", async (c) => {
|
|
17667
17992
|
try {
|
|
17668
|
-
const { tasks: tasks3, taskRuns:
|
|
17669
|
-
await db.delete(
|
|
17993
|
+
const { tasks: tasks3, taskRuns: taskRuns4, taskTemplates: taskTemplates3 } = schema_exports;
|
|
17994
|
+
await db.delete(taskRuns4);
|
|
17670
17995
|
await db.delete(taskTemplates3);
|
|
17671
17996
|
await db.delete(tasks3);
|
|
17672
17997
|
return c.json({ success: true });
|
|
17673
17998
|
} catch (err) {
|
|
17674
|
-
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
17999
|
+
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) }, 500);
|
|
17675
18000
|
}
|
|
17676
18001
|
});
|
|
17677
18002
|
var dashboardApp = app;
|