opencode-supertask 0.1.19 → 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 +1256 -587
- package/dist/cli/index.js.map +1 -1
- package/dist/gateway/index.js +849 -302
- package/dist/gateway/index.js.map +1 -1
- package/dist/plugin/supertask.js +585 -233
- package/dist/plugin/supertask.js.map +1 -1
- package/dist/web/index.js +546 -213
- package/dist/web/index.js.map +1 -1
- package/dist/worker/index.d.ts +19 -7
- package/dist/worker/index.js +434 -152
- 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";
|
|
@@ -16566,7 +16589,15 @@ var _db = null;
|
|
|
16566
16589
|
var _migrationRan = false;
|
|
16567
16590
|
function getMigrationsFolder() {
|
|
16568
16591
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16569
|
-
|
|
16592
|
+
let dir = __dirname;
|
|
16593
|
+
for (let i = 0; i < 5; i++) {
|
|
16594
|
+
const candidate = join(dir, "drizzle", "meta", "_journal.json");
|
|
16595
|
+
if (existsSync(candidate)) {
|
|
16596
|
+
return join(dir, "drizzle");
|
|
16597
|
+
}
|
|
16598
|
+
dir = dirname(dir);
|
|
16599
|
+
}
|
|
16600
|
+
return join(__dirname, "../../drizzle");
|
|
16570
16601
|
}
|
|
16571
16602
|
function initDb() {
|
|
16572
16603
|
const dataDir = dirname(DB_FILE_PATH);
|
|
@@ -16581,16 +16612,30 @@ function initDb() {
|
|
|
16581
16612
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
16582
16613
|
pid INTEGER NOT NULL,
|
|
16583
16614
|
acquired_at INTEGER NOT NULL,
|
|
16584
|
-
heartbeat_at INTEGER NOT NULL
|
|
16615
|
+
heartbeat_at INTEGER NOT NULL,
|
|
16616
|
+
ready_at INTEGER
|
|
16585
16617
|
);
|
|
16586
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
|
+
}
|
|
16587
16623
|
_db = drizzle(_sqlite, { schema: schema_exports });
|
|
16588
16624
|
if (!_migrationRan) {
|
|
16589
|
-
_migrationRan = true;
|
|
16590
16625
|
try {
|
|
16591
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;
|
|
16592
16633
|
} catch (err) {
|
|
16593
16634
|
const msg = err instanceof Error ? err.message : String(err);
|
|
16635
|
+
_migrationRan = false;
|
|
16636
|
+
_sqlite.close();
|
|
16637
|
+
_sqlite = null;
|
|
16638
|
+
_db = null;
|
|
16594
16639
|
console.error(`[supertask] migration failed: ${msg}`);
|
|
16595
16640
|
throw new Error(`[supertask] DB migration failed: ${msg}`);
|
|
16596
16641
|
}
|
|
@@ -16629,7 +16674,7 @@ function computeBackoff(retryCount, baseMs = 3e4, maxMs = MAX_BACKOFF_MS) {
|
|
|
16629
16674
|
}
|
|
16630
16675
|
|
|
16631
16676
|
// src/core/services/task.service.ts
|
|
16632
|
-
var { tasks: tasks2 } = schema_exports;
|
|
16677
|
+
var { tasks: tasks2, taskRuns: taskRuns2 } = schema_exports;
|
|
16633
16678
|
var TaskService = class {
|
|
16634
16679
|
static buildScopeWhere(scope) {
|
|
16635
16680
|
const conditions = [];
|
|
@@ -16639,9 +16684,27 @@ var TaskService = class {
|
|
|
16639
16684
|
return conditions;
|
|
16640
16685
|
}
|
|
16641
16686
|
static async add(data) {
|
|
16687
|
+
this.validateNewTask(data);
|
|
16642
16688
|
const result = await db.insert(tasks2).values(data).returning();
|
|
16643
16689
|
return result[0];
|
|
16644
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
|
+
}
|
|
16645
16708
|
static async next(scope = {}) {
|
|
16646
16709
|
const baseConditions = [...this.buildScopeWhere(scope)];
|
|
16647
16710
|
const nowMs = Date.now();
|
|
@@ -16664,7 +16727,7 @@ var TaskService = class {
|
|
|
16664
16727
|
),
|
|
16665
16728
|
and(
|
|
16666
16729
|
eq(tasks2.status, "failed"),
|
|
16667
|
-
sql`${tasks2.retryCount}
|
|
16730
|
+
sql`${tasks2.retryCount} <= ${tasks2.maxRetries}`,
|
|
16668
16731
|
retryAfterFilter
|
|
16669
16732
|
)
|
|
16670
16733
|
);
|
|
@@ -16678,7 +16741,8 @@ var TaskService = class {
|
|
|
16678
16741
|
const allTasks = await db.select().from(tasks2).where(and(...conditions)).orderBy(
|
|
16679
16742
|
desc(tasks2.urgency),
|
|
16680
16743
|
desc(tasks2.importance),
|
|
16681
|
-
asc(tasks2.createdAt)
|
|
16744
|
+
asc(tasks2.createdAt),
|
|
16745
|
+
asc(tasks2.id)
|
|
16682
16746
|
);
|
|
16683
16747
|
for (const task of allTasks) {
|
|
16684
16748
|
if (task.dependsOn) {
|
|
@@ -16699,7 +16763,7 @@ var TaskService = class {
|
|
|
16699
16763
|
eq(tasks2.status, "pending"),
|
|
16700
16764
|
and(
|
|
16701
16765
|
eq(tasks2.status, "failed"),
|
|
16702
|
-
sql`${tasks2.retryCount}
|
|
16766
|
+
sql`${tasks2.retryCount} <= ${tasks2.maxRetries}`
|
|
16703
16767
|
)
|
|
16704
16768
|
),
|
|
16705
16769
|
...this.buildScopeWhere(scope)
|
|
@@ -16712,7 +16776,11 @@ var TaskService = class {
|
|
|
16712
16776
|
return result[0] || null;
|
|
16713
16777
|
}
|
|
16714
16778
|
static async done(id, log, scope = {}) {
|
|
16715
|
-
const conditions = [
|
|
16779
|
+
const conditions = [
|
|
16780
|
+
eq(tasks2.id, id),
|
|
16781
|
+
eq(tasks2.status, "running"),
|
|
16782
|
+
...this.buildScopeWhere(scope)
|
|
16783
|
+
];
|
|
16716
16784
|
const result = await db.update(tasks2).set({
|
|
16717
16785
|
status: "done",
|
|
16718
16786
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
@@ -16723,17 +16791,24 @@ var TaskService = class {
|
|
|
16723
16791
|
}
|
|
16724
16792
|
static async fail(id, log, scope = {}, options) {
|
|
16725
16793
|
const current = await this.getById(id, scope);
|
|
16726
|
-
if (!current) return null;
|
|
16794
|
+
if (!current || current.status !== "running") return null;
|
|
16727
16795
|
const newRetryCount = (current.retryCount ?? 0) + 1;
|
|
16728
16796
|
const maxRetries = current.maxRetries ?? 3;
|
|
16729
|
-
const isDeadLetter = options?.setDeadLetter ?? newRetryCount
|
|
16730
|
-
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
|
+
];
|
|
16731
16803
|
const result = await db.update(tasks2).set({
|
|
16732
16804
|
status: isDeadLetter ? "dead_letter" : "failed",
|
|
16733
16805
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
16734
16806
|
resultLog: log,
|
|
16735
16807
|
retryCount: newRetryCount,
|
|
16736
|
-
retryAfter: isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
|
|
16808
|
+
retryAfter: isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
|
|
16809
|
+
newRetryCount,
|
|
16810
|
+
current.retryBackoffMs ?? 3e4
|
|
16811
|
+
)
|
|
16737
16812
|
}).where(and(...conditions)).returning();
|
|
16738
16813
|
return result[0] || null;
|
|
16739
16814
|
}
|
|
@@ -16766,8 +16841,20 @@ var TaskService = class {
|
|
|
16766
16841
|
return result.length;
|
|
16767
16842
|
}
|
|
16768
16843
|
static async cancel(id, scope = {}) {
|
|
16769
|
-
const conditions = [
|
|
16770
|
-
|
|
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();
|
|
16771
16858
|
return result[0] || null;
|
|
16772
16859
|
}
|
|
16773
16860
|
static async retry(id, scope = {}) {
|
|
@@ -16779,7 +16866,8 @@ var TaskService = class {
|
|
|
16779
16866
|
status: "pending",
|
|
16780
16867
|
startedAt: null,
|
|
16781
16868
|
finishedAt: null,
|
|
16782
|
-
retryAfter: null
|
|
16869
|
+
retryAfter: null,
|
|
16870
|
+
retryCount: 0
|
|
16783
16871
|
}).where(and(...conditions)).returning();
|
|
16784
16872
|
return result[0] || null;
|
|
16785
16873
|
}
|
|
@@ -16793,7 +16881,8 @@ var TaskService = class {
|
|
|
16793
16881
|
status: "pending",
|
|
16794
16882
|
startedAt: null,
|
|
16795
16883
|
finishedAt: null,
|
|
16796
|
-
retryAfter: null
|
|
16884
|
+
retryAfter: null,
|
|
16885
|
+
retryCount: 0
|
|
16797
16886
|
}).where(and(...conditions)).returning();
|
|
16798
16887
|
return result.length;
|
|
16799
16888
|
}
|
|
@@ -16861,6 +16950,9 @@ var TaskService = class {
|
|
|
16861
16950
|
}
|
|
16862
16951
|
static async delete(id, scope = {}) {
|
|
16863
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));
|
|
16864
16956
|
const result = await db.delete(tasks2).where(and(...conditions)).returning();
|
|
16865
16957
|
return result.length > 0;
|
|
16866
16958
|
}
|
|
@@ -16878,59 +16970,59 @@ var TaskService = class {
|
|
|
16878
16970
|
};
|
|
16879
16971
|
|
|
16880
16972
|
// src/core/services/task-run.service.ts
|
|
16881
|
-
var { taskRuns:
|
|
16973
|
+
var { taskRuns: taskRuns3 } = schema_exports;
|
|
16882
16974
|
var TaskRunService = class {
|
|
16883
16975
|
static async create(data) {
|
|
16884
|
-
const result = await db.insert(
|
|
16976
|
+
const result = await db.insert(taskRuns3).values(data).returning();
|
|
16885
16977
|
return result[0];
|
|
16886
16978
|
}
|
|
16887
16979
|
static async updateSessionId(id, sessionId) {
|
|
16888
|
-
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();
|
|
16889
16981
|
return result[0] || null;
|
|
16890
16982
|
}
|
|
16891
16983
|
static async done(id, log) {
|
|
16892
|
-
const result = await db.update(
|
|
16984
|
+
const result = await db.update(taskRuns3).set({
|
|
16893
16985
|
status: "done",
|
|
16894
16986
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
16895
16987
|
log
|
|
16896
|
-
}).where(eq(
|
|
16988
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16897
16989
|
return result[0] || null;
|
|
16898
16990
|
}
|
|
16899
16991
|
static async fail(id, log) {
|
|
16900
|
-
const result = await db.update(
|
|
16992
|
+
const result = await db.update(taskRuns3).set({
|
|
16901
16993
|
status: "failed",
|
|
16902
16994
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
16903
16995
|
log
|
|
16904
|
-
}).where(eq(
|
|
16996
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16905
16997
|
return result[0] || null;
|
|
16906
16998
|
}
|
|
16907
16999
|
static async heartbeat(id) {
|
|
16908
|
-
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();
|
|
16909
17001
|
return result[0] || null;
|
|
16910
17002
|
}
|
|
16911
17003
|
static async updatePid(id, workerPid, childPid) {
|
|
16912
|
-
const result = await db.update(
|
|
17004
|
+
const result = await db.update(taskRuns3).set({
|
|
16913
17005
|
workerPid,
|
|
16914
17006
|
childPid,
|
|
16915
17007
|
lockedAt: Date.now(),
|
|
16916
17008
|
lockedBy: `gateway-${process.pid}`
|
|
16917
|
-
}).where(eq(
|
|
17009
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
16918
17010
|
return result[0] || null;
|
|
16919
17011
|
}
|
|
16920
17012
|
static async getById(id) {
|
|
16921
|
-
const result = await db.select().from(
|
|
17013
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.id, id));
|
|
16922
17014
|
return result[0] || null;
|
|
16923
17015
|
}
|
|
16924
17016
|
static async listByTaskId(taskId) {
|
|
16925
|
-
return await db.select().from(
|
|
17017
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
16926
17018
|
}
|
|
16927
17019
|
static async getLatestByTaskId(taskId) {
|
|
16928
|
-
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);
|
|
16929
17021
|
return result[0] || null;
|
|
16930
17022
|
}
|
|
16931
17023
|
static async getLatestByTaskIds(taskIds) {
|
|
16932
17024
|
if (taskIds.length === 0) return /* @__PURE__ */ new Map();
|
|
16933
|
-
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));
|
|
16934
17026
|
const result = /* @__PURE__ */ new Map();
|
|
16935
17027
|
for (const run of latestRuns) {
|
|
16936
17028
|
if (!result.has(run.taskId)) {
|
|
@@ -16944,22 +17036,23 @@ var TaskRunService = class {
|
|
|
16944
17036
|
const cutoffSec = Math.floor(cutoffMs / 1e3);
|
|
16945
17037
|
const { tasks: tasksTable } = schema_exports;
|
|
16946
17038
|
const result = await db.select({
|
|
16947
|
-
runId:
|
|
16948
|
-
taskId:
|
|
16949
|
-
childPid:
|
|
17039
|
+
runId: taskRuns3.id,
|
|
17040
|
+
taskId: taskRuns3.taskId,
|
|
17041
|
+
childPid: taskRuns3.childPid,
|
|
16950
17042
|
taskRetryCount: tasksTable.retryCount,
|
|
16951
|
-
taskMaxRetries: tasksTable.maxRetries
|
|
16952
|
-
|
|
17043
|
+
taskMaxRetries: tasksTable.maxRetries,
|
|
17044
|
+
taskRetryBackoffMs: tasksTable.retryBackoffMs
|
|
17045
|
+
}).from(taskRuns3).innerJoin(tasksTable, eq(taskRuns3.taskId, tasksTable.id)).where(
|
|
16953
17046
|
and(
|
|
16954
|
-
eq(
|
|
17047
|
+
eq(taskRuns3.status, "running"),
|
|
16955
17048
|
or(
|
|
16956
17049
|
and(
|
|
16957
|
-
sql`${
|
|
16958
|
-
sql`${
|
|
17050
|
+
sql`${taskRuns3.heartbeatAt} IS NULL`,
|
|
17051
|
+
sql`${taskRuns3.startedAt} < ${cutoffSec}`
|
|
16959
17052
|
),
|
|
16960
17053
|
and(
|
|
16961
|
-
sql`${
|
|
16962
|
-
sql`${
|
|
17054
|
+
sql`${taskRuns3.heartbeatAt} IS NOT NULL`,
|
|
17055
|
+
sql`${taskRuns3.heartbeatAt} < ${cutoffMs}`
|
|
16963
17056
|
)
|
|
16964
17057
|
)
|
|
16965
17058
|
)
|
|
@@ -16969,28 +17062,50 @@ var TaskRunService = class {
|
|
|
16969
17062
|
taskId: row.taskId,
|
|
16970
17063
|
childPid: row.childPid,
|
|
16971
17064
|
taskRetryCount: row.taskRetryCount ?? 0,
|
|
16972
|
-
taskMaxRetries: row.taskMaxRetries ?? 3
|
|
17065
|
+
taskMaxRetries: row.taskMaxRetries ?? 3,
|
|
17066
|
+
taskRetryBackoffMs: row.taskRetryBackoffMs ?? 3e4
|
|
16973
17067
|
}));
|
|
16974
17068
|
}
|
|
16975
17069
|
static async getRunningRunByTaskId(taskId) {
|
|
16976
|
-
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);
|
|
16977
17071
|
return result[0] || null;
|
|
16978
17072
|
}
|
|
16979
17073
|
static async deleteByTaskIds(taskIds) {
|
|
16980
17074
|
if (taskIds.length === 0) return 0;
|
|
16981
|
-
const result = await db.delete(
|
|
17075
|
+
const result = await db.delete(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).returning();
|
|
16982
17076
|
return result.length;
|
|
16983
17077
|
}
|
|
16984
17078
|
static async getAllRunningRuns() {
|
|
16985
|
-
return await db.select().from(
|
|
17079
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.status, "running"));
|
|
16986
17080
|
}
|
|
16987
17081
|
};
|
|
16988
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
|
+
|
|
16989
17104
|
// src/core/services/task-template.service.ts
|
|
16990
|
-
init_cron_parser();
|
|
16991
17105
|
var { taskTemplates: taskTemplates2 } = schema_exports;
|
|
16992
17106
|
var TaskTemplateService = class {
|
|
16993
17107
|
static async create(data) {
|
|
17108
|
+
this.validate(data);
|
|
16994
17109
|
const now = Date.now();
|
|
16995
17110
|
const result = await db.insert(taskTemplates2).values({ ...data, createdAt: now, updatedAt: now }).returning();
|
|
16996
17111
|
const tmpl = result[0];
|
|
@@ -17006,8 +17121,38 @@ var TaskTemplateService = class {
|
|
|
17006
17121
|
}
|
|
17007
17122
|
return tmpl;
|
|
17008
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
|
+
}
|
|
17009
17154
|
static async list(limit = 50) {
|
|
17010
|
-
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);
|
|
17011
17156
|
}
|
|
17012
17157
|
static async getById(id) {
|
|
17013
17158
|
const result = await db.select().from(taskTemplates2).where(eq(taskTemplates2.id, id));
|
|
@@ -17046,72 +17191,236 @@ var TaskTemplateService = class {
|
|
|
17046
17191
|
};
|
|
17047
17192
|
|
|
17048
17193
|
// src/gateway/config.ts
|
|
17049
|
-
import {
|
|
17194
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
17050
17195
|
import { homedir as homedir2 } from "os";
|
|
17051
17196
|
import { join as join2 } from "path";
|
|
17052
17197
|
var DEFAULT_CONFIG = {
|
|
17198
|
+
configVersion: 2,
|
|
17053
17199
|
worker: {
|
|
17054
17200
|
maxConcurrency: 2,
|
|
17055
17201
|
pollIntervalMs: 1e3,
|
|
17056
17202
|
heartbeatIntervalMs: 3e4,
|
|
17057
|
-
taskTimeoutMs: 18e5
|
|
17203
|
+
taskTimeoutMs: 18e5,
|
|
17204
|
+
shutdownGracePeriodMs: 3e4
|
|
17058
17205
|
},
|
|
17059
17206
|
scheduler: {
|
|
17060
17207
|
enabled: true,
|
|
17061
|
-
checkIntervalMs: 1e3
|
|
17062
|
-
catchUp: "next"
|
|
17208
|
+
checkIntervalMs: 1e3
|
|
17063
17209
|
},
|
|
17064
17210
|
watchdog: {
|
|
17065
17211
|
heartbeatTimeoutMs: 6e5,
|
|
17066
|
-
|
|
17212
|
+
checkIntervalMs: 6e4,
|
|
17213
|
+
cleanupIntervalMs: 864e5,
|
|
17067
17214
|
retentionDays: 30
|
|
17068
17215
|
},
|
|
17069
17216
|
dashboard: {
|
|
17070
17217
|
enabled: true,
|
|
17071
17218
|
port: 4680
|
|
17072
|
-
},
|
|
17073
|
-
logging: {
|
|
17074
|
-
level: "info",
|
|
17075
|
-
format: "json"
|
|
17076
17219
|
}
|
|
17077
17220
|
};
|
|
17078
|
-
var
|
|
17079
|
-
function
|
|
17080
|
-
|
|
17081
|
-
|
|
17082
|
-
|
|
17083
|
-
|
|
17084
|
-
|
|
17085
|
-
|
|
17086
|
-
result[key] = deepMerge(baseVal, val);
|
|
17087
|
-
continue;
|
|
17088
|
-
}
|
|
17089
|
-
}
|
|
17090
|
-
if (val !== void 0) {
|
|
17091
|
-
result[key] = val;
|
|
17092
|
-
}
|
|
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`);
|
|
17093
17229
|
}
|
|
17094
|
-
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;
|
|
17239
|
+
}
|
|
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;
|
|
17095
17245
|
}
|
|
17096
|
-
function
|
|
17097
|
-
|
|
17098
|
-
|
|
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");
|
|
17099
17269
|
}
|
|
17270
|
+
if (checkIntervalMs > heartbeatTimeoutMs) {
|
|
17271
|
+
throw new Error("watchdog.checkIntervalMs \u4E0D\u80FD\u5927\u4E8E watchdog.heartbeatTimeoutMs");
|
|
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 });
|
|
17100
17300
|
try {
|
|
17101
|
-
|
|
17102
|
-
|
|
17103
|
-
|
|
17104
|
-
|
|
17105
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
17106
|
-
console.error(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "warn", msg: "config load failed, using defaults", path: CONFIG_PATH, error: msg }));
|
|
17107
|
-
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}`);
|
|
17108
17305
|
}
|
|
17109
17306
|
}
|
|
17110
17307
|
|
|
17111
17308
|
// src/web/index.tsx
|
|
17112
|
-
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";
|
|
17113
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
|
|
17114
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
|
+
});
|
|
17115
17424
|
function formatDuration(startAt, endAt) {
|
|
17116
17425
|
if (!startAt) return "-";
|
|
17117
17426
|
const start = new Date(startAt).getTime();
|
|
@@ -17149,17 +17458,21 @@ function esc(s) {
|
|
|
17149
17458
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
17150
17459
|
}
|
|
17151
17460
|
function readCurrentConfig() {
|
|
17152
|
-
|
|
17461
|
+
const configPath = getConfigPath();
|
|
17462
|
+
if (!existsSync3(configPath)) return {};
|
|
17153
17463
|
try {
|
|
17154
|
-
return JSON.parse(readFileSync2(
|
|
17464
|
+
return JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
17155
17465
|
} catch {
|
|
17156
17466
|
return {};
|
|
17157
17467
|
}
|
|
17158
17468
|
}
|
|
17159
17469
|
function writeConfig(cfg) {
|
|
17160
|
-
const
|
|
17470
|
+
const configPath = getConfigPath();
|
|
17471
|
+
const dir = dirname2(configPath);
|
|
17161
17472
|
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
17162
|
-
|
|
17473
|
+
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
17474
|
+
writeFileSync(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
17475
|
+
renameSync(tempPath, configPath);
|
|
17163
17476
|
}
|
|
17164
17477
|
var SHARED_STYLES = html`
|
|
17165
17478
|
<style>
|
|
@@ -17273,11 +17586,11 @@ async function saveConfig(){
|
|
|
17273
17586
|
scheduler:{
|
|
17274
17587
|
enabled:form.se.checked,
|
|
17275
17588
|
checkIntervalMs:Number(form.si.value),
|
|
17276
|
-
catchUp:form.cu.value,
|
|
17277
17589
|
},
|
|
17278
17590
|
watchdog:{
|
|
17279
17591
|
heartbeatTimeoutMs:Number(form.wt.value)*1000,
|
|
17280
|
-
|
|
17592
|
+
checkIntervalMs:Number(form.wci.value)*1000,
|
|
17593
|
+
cleanupIntervalMs:Number(form.wcl.value)*3600000,
|
|
17281
17594
|
retentionDays:Number(form.rd.value),
|
|
17282
17595
|
}
|
|
17283
17596
|
};
|
|
@@ -17309,12 +17622,16 @@ async function saveConfig(){
|
|
|
17309
17622
|
</body></html>`;
|
|
17310
17623
|
}
|
|
17311
17624
|
app.get("/", async (c) => {
|
|
17312
|
-
const
|
|
17625
|
+
const pageParam = c.req.query("page") || "1";
|
|
17626
|
+
const page = parsePositiveInteger(pageParam);
|
|
17627
|
+
if (page === null) return c.text("invalid page", 400);
|
|
17313
17628
|
const statusFilter = c.req.query("status") || "";
|
|
17629
|
+
const parsedStatus = statusFilter ? parseTaskStatus(statusFilter) : null;
|
|
17630
|
+
if (statusFilter && !parsedStatus) return c.text("invalid status", 400);
|
|
17314
17631
|
const limit = 50;
|
|
17315
17632
|
const offset = (page - 1) * limit;
|
|
17316
17633
|
const [tasks3, statsData] = await Promise.all([
|
|
17317
|
-
TaskService.list({ limit, offset, ...
|
|
17634
|
+
TaskService.list({ limit, offset, ...parsedStatus ? { status: parsedStatus } : {} }),
|
|
17318
17635
|
TaskService.stats({})
|
|
17319
17636
|
]);
|
|
17320
17637
|
const taskIds = tasks3.map((t) => t.id);
|
|
@@ -17337,12 +17654,13 @@ app.get("/", async (c) => {
|
|
|
17337
17654
|
</div>`;
|
|
17338
17655
|
let rows = "";
|
|
17339
17656
|
for (const task of tasks3) {
|
|
17340
|
-
const
|
|
17657
|
+
const status = safeStatus(task.status);
|
|
17658
|
+
const st = status.toUpperCase();
|
|
17341
17659
|
rows += `<tr>
|
|
17342
17660
|
<td class="mu">#${task.id}</td>
|
|
17343
17661
|
<td><div style="font-weight:500">${esc(task.name)}</div><div class="mu sm el">${esc(task.prompt.substring(0, 120))}</div></td>
|
|
17344
17662
|
<td><span class="tag">${esc(task.agent)}</span></td>
|
|
17345
|
-
<td><span class="badge b-${
|
|
17663
|
+
<td><span class="badge b-${status}">${st}</span></td>
|
|
17346
17664
|
<td class="sm ${task.status === "running" ? "" : "mu"}">${formatDuration(task.startedAt, task.finishedAt)}</td>
|
|
17347
17665
|
<td class="mu sm">${(task.retryCount ?? 0) > 0 ? task.retryCount : "-"}</td>
|
|
17348
17666
|
<td>
|
|
@@ -17374,21 +17692,13 @@ app.get("/", async (c) => {
|
|
|
17374
17692
|
});
|
|
17375
17693
|
app.get("/templates", async (c) => {
|
|
17376
17694
|
const templates = await TaskTemplateService.list(100);
|
|
17377
|
-
for (const tmpl of templates) {
|
|
17378
|
-
if (tmpl.enabled && tmpl.scheduleType === "cron" && tmpl.cronExpr) {
|
|
17379
|
-
try {
|
|
17380
|
-
const { getNextCronRun: getNextCronRun2 } = await Promise.resolve().then(() => (init_cron_parser(), cron_parser_exports));
|
|
17381
|
-
tmpl.nextRunAt = getNextCronRun2(tmpl.cronExpr, Date.now());
|
|
17382
|
-
} catch {
|
|
17383
|
-
}
|
|
17384
|
-
}
|
|
17385
|
-
}
|
|
17386
17695
|
const enabled = templates.filter((t) => t.enabled).length;
|
|
17387
17696
|
const disabled = templates.length - enabled;
|
|
17388
17697
|
let rows = "";
|
|
17389
17698
|
for (const t of templates) {
|
|
17390
|
-
const
|
|
17391
|
-
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;
|
|
17392
17702
|
let rule = "-";
|
|
17393
17703
|
if (t.scheduleType === "cron") rule = t.cronExpr || "-";
|
|
17394
17704
|
else if (t.scheduleType === "recurring") rule = t.intervalMs ? `${Math.floor(t.intervalMs / 6e4)}\u5206\u949F` : "-";
|
|
@@ -17400,7 +17710,7 @@ app.get("/templates", async (c) => {
|
|
|
17400
17710
|
<td><div style="font-weight:500">${esc(t.name)}</div><div class="mu sm el">${esc(t.prompt.substring(0, 100))}</div>
|
|
17401
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>
|
|
17402
17712
|
<td><span class="${typeClass}">${typeLabel}</span></td>
|
|
17403
|
-
<td class="m sm">${rule}</td>
|
|
17713
|
+
<td class="m sm">${esc(rule)}</td>
|
|
17404
17714
|
<td>${statusBadge}</td>
|
|
17405
17715
|
<td class="sm">${t.lastRunAt ? timeAgo(t.lastRunAt) : "-"}</td>
|
|
17406
17716
|
<td class="sm">${t.nextRunAt ? timeUntil(t.nextRunAt) : "-"}</td>
|
|
@@ -17428,7 +17738,8 @@ app.get("/templates", async (c) => {
|
|
|
17428
17738
|
return c.html(renderLayout("\u5B9A\u65F6\u4EFB\u52A1", "templates", body));
|
|
17429
17739
|
});
|
|
17430
17740
|
app.get("/runs", async (c) => {
|
|
17431
|
-
const page =
|
|
17741
|
+
const page = parsePositiveInteger(c.req.query("page") || "1");
|
|
17742
|
+
if (page === null) return c.text("invalid page", 400);
|
|
17432
17743
|
const limit = 50;
|
|
17433
17744
|
const offset = (page - 1) * limit;
|
|
17434
17745
|
const { taskRuns: tr, tasks: tk } = schema_exports;
|
|
@@ -17446,13 +17757,14 @@ app.get("/runs", async (c) => {
|
|
|
17446
17757
|
childPid: tr.childPid,
|
|
17447
17758
|
taskName: tk.name,
|
|
17448
17759
|
taskAgent: tk.agent
|
|
17449
|
-
}).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);
|
|
17450
17761
|
const totalResult = await db.select({ count: sql`count(*)` }).from(tr);
|
|
17451
17762
|
const total = Number(totalResult[0]?.count ?? 0);
|
|
17452
17763
|
const totalPages = Math.ceil(total / limit);
|
|
17453
17764
|
let rows = "";
|
|
17454
17765
|
const logsHtml = [];
|
|
17455
17766
|
for (const run of runs) {
|
|
17767
|
+
const status = safeStatus(run.status);
|
|
17456
17768
|
const shortSession = run.sessionId ? run.sessionId.slice(4, 7) + "***" + run.sessionId.slice(-3) : "-";
|
|
17457
17769
|
const logBtn = run.log ? `<button class="btn btn-sm" onclick="toggleLog(${run.id})">\u65E5\u5FD7</button>` : "";
|
|
17458
17770
|
rows += `<tr>
|
|
@@ -17460,15 +17772,15 @@ app.get("/runs", async (c) => {
|
|
|
17460
17772
|
<td><div style="font-weight:500">${esc(run.taskName)} <span class="mu">(#${run.taskId})</span></div>
|
|
17461
17773
|
${run.model ? `<div class="sm"><span class="tag">${esc(run.model)}</span></div>` : ""}</td>
|
|
17462
17774
|
<td><span class="tag">${esc(run.taskAgent)}</span></td>
|
|
17463
|
-
<td><span class="badge b-${
|
|
17775
|
+
<td><span class="badge b-${status}">${status.toUpperCase()}</span></td>
|
|
17464
17776
|
<td class="sm">${formatDuration(run.startedAt, run.finishedAt)}</td>
|
|
17465
17777
|
<td class="sm mu">${run.heartbeatAt ? timeAgo(run.heartbeatAt) : "-"}</td>
|
|
17466
17778
|
<td><button class="btn btn-sm" onclick="showRunDetail(${run.id})">\u8BE6\u60C5</button>${logBtn}</td>
|
|
17467
17779
|
</tr>`;
|
|
17468
17780
|
if (run.log) {
|
|
17469
17781
|
logsHtml.push(`<div id="log-${run.id}" style="display:none" class="mt8">
|
|
17470
|
-
<div class="panel"><div class="ph"><h3>Run #${run.id} \u65E5\u5FD7 \u2014 ${run.taskName}</h3></div>
|
|
17471
|
-
<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>`);
|
|
17472
17784
|
}
|
|
17473
17785
|
}
|
|
17474
17786
|
let paging = `<div class="pn">`;
|
|
@@ -17494,17 +17806,18 @@ app.get("/runs", async (c) => {
|
|
|
17494
17806
|
});
|
|
17495
17807
|
app.get("/system", async (c) => {
|
|
17496
17808
|
const config = loadConfig();
|
|
17809
|
+
const configPath = getConfigPath();
|
|
17497
17810
|
const stats = await TaskService.stats({});
|
|
17498
17811
|
const runningRuns = await TaskRunService.getAllRunningRuns();
|
|
17499
17812
|
const templates = await TaskTemplateService.list(100);
|
|
17500
|
-
const configExists = existsSync3(
|
|
17813
|
+
const configExists = existsSync3(configPath);
|
|
17501
17814
|
let runRows = "";
|
|
17502
17815
|
if (runningRuns.length > 0) {
|
|
17503
17816
|
for (const run of runningRuns) {
|
|
17504
17817
|
const shortS = run.sessionId ? run.sessionId.slice(4, 7) + "***" + run.sessionId.slice(-3) : "-";
|
|
17505
17818
|
runRows += `<tr>
|
|
17506
17819
|
<td class="mu">#${run.id}</td><td>#${run.taskId}</td>
|
|
17507
|
-
<td class="m sm">${shortS}</td>
|
|
17820
|
+
<td class="m sm">${esc(shortS)}</td>
|
|
17508
17821
|
<td class="sm">${esc(run.model) || "-"}</td>
|
|
17509
17822
|
<td class="sm">${formatDate(run.startedAt)}</td>
|
|
17510
17823
|
<td class="sm">${run.heartbeatAt ? timeAgo(run.heartbeatAt) : "-"}</td>
|
|
@@ -17529,19 +17842,14 @@ app.get("/system", async (c) => {
|
|
|
17529
17842
|
<h3 style="margin:0 0 12px;font-size:14px">Scheduler \u914D\u7F6E</h3>
|
|
17530
17843
|
<div class="form-row"><label>\u542F\u7528\u8C03\u5EA6</label><input type="checkbox" name="se" ${config.scheduler.enabled ? "checked" : ""}></div>
|
|
17531
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>
|
|
17532
|
-
<div class="form-row"><label>\u8FFD\u8D76\u6A21\u5F0F</label><select name="cu" style="width:100px">
|
|
17533
|
-
<option value="next" ${config.scheduler.catchUp === "next" ? "selected" : ""}>next</option>
|
|
17534
|
-
<option value="all" ${config.scheduler.catchUp === "all" ? "selected" : ""}>all</option>
|
|
17535
|
-
<option value="latest" ${config.scheduler.catchUp === "latest" ? "selected" : ""}>latest</option>
|
|
17536
|
-
</select></div>
|
|
17537
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>
|
|
17538
17846
|
</div>
|
|
17539
17847
|
<div class="card">
|
|
17540
17848
|
<h3 style="margin:0 0 12px;font-size:14px">Watchdog \u914D\u7F6E</h3>
|
|
17541
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>
|
|
17542
|
-
<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>
|
|
17543
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>
|
|
17544
|
-
<div class="ir"><span class="ik">\u65E5\u5FD7\u683C\u5F0F</span><span class="iv">${config.logging.format}</span></div>
|
|
17545
17853
|
</div>
|
|
17546
17854
|
</div>
|
|
17547
17855
|
<div style="text-align:center;margin-bottom:24px">
|
|
@@ -17570,7 +17878,7 @@ app.get("/system", async (c) => {
|
|
|
17570
17878
|
|
|
17571
17879
|
<div class="card mt16">
|
|
17572
17880
|
<h3 style="margin:0 0 12px;font-size:14px">\u914D\u7F6E\u6587\u4EF6</h3>
|
|
17573
|
-
<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>
|
|
17574
17882
|
<div class="ir"><span class="ik">\u6587\u4EF6\u5B58\u5728</span><span class="iv">${configFileStatus}</span></div>
|
|
17575
17883
|
</div>
|
|
17576
17884
|
|
|
@@ -17582,46 +17890,61 @@ app.get("/system", async (c) => {
|
|
|
17582
17890
|
return c.html(renderLayout("\u7CFB\u7EDF\u72B6\u6001", "system", body));
|
|
17583
17891
|
});
|
|
17584
17892
|
app.get("/api/tasks/:id", async (c) => {
|
|
17585
|
-
const id =
|
|
17893
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17894
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17586
17895
|
const task = await TaskService.getById(id);
|
|
17587
17896
|
if (!task) return c.json({ error: "not found" }, 404);
|
|
17588
17897
|
const runs = await TaskRunService.listByTaskId(id);
|
|
17589
17898
|
return c.json({ ...task, _runs: runs });
|
|
17590
17899
|
});
|
|
17591
17900
|
app.get("/api/runs/:id", async (c) => {
|
|
17592
|
-
const id =
|
|
17901
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17902
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17593
17903
|
const run = await TaskRunService.getById(id);
|
|
17594
17904
|
if (!run) return c.json({ error: "not found" }, 404);
|
|
17595
17905
|
return c.json(run);
|
|
17596
17906
|
});
|
|
17597
17907
|
app.get("/api/templates/:id", async (c) => {
|
|
17598
|
-
const id =
|
|
17908
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17909
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17599
17910
|
const tmpl = await TaskTemplateService.getById(id);
|
|
17600
17911
|
if (!tmpl) return c.json({ error: "not found" }, 404);
|
|
17601
17912
|
return c.json(tmpl);
|
|
17602
17913
|
});
|
|
17603
17914
|
app.post("/api/tasks/:id/retry", async (c) => {
|
|
17604
|
-
|
|
17605
|
-
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);
|
|
17606
17920
|
});
|
|
17607
17921
|
app.delete("/api/tasks/:id", async (c) => {
|
|
17608
|
-
|
|
17609
|
-
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);
|
|
17610
17926
|
});
|
|
17611
17927
|
app.post("/api/templates/:id/enable", async (c) => {
|
|
17612
|
-
const
|
|
17613
|
-
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);
|
|
17614
17932
|
});
|
|
17615
17933
|
app.post("/api/templates/:id/disable", async (c) => {
|
|
17616
|
-
const
|
|
17617
|
-
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);
|
|
17618
17938
|
});
|
|
17619
17939
|
app.delete("/api/templates/:id", async (c) => {
|
|
17620
|
-
const
|
|
17621
|
-
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);
|
|
17622
17944
|
});
|
|
17623
17945
|
app.post("/api/templates/:id/trigger", async (c) => {
|
|
17624
|
-
const id =
|
|
17946
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
17947
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
17625
17948
|
const tmpl = await TaskTemplateService.getById(id);
|
|
17626
17949
|
if (!tmpl) return c.json({ error: "not found" }, 404);
|
|
17627
17950
|
const task = await TaskService.add({
|
|
@@ -17633,7 +17956,10 @@ app.post("/api/templates/:id/trigger", async (c) => {
|
|
|
17633
17956
|
category: tmpl.category,
|
|
17634
17957
|
importance: tmpl.importance,
|
|
17635
17958
|
urgency: tmpl.urgency,
|
|
17959
|
+
batchId: tmpl.batchId,
|
|
17636
17960
|
maxRetries: tmpl.maxRetries,
|
|
17961
|
+
retryBackoffMs: tmpl.retryBackoffMs,
|
|
17962
|
+
timeoutMs: tmpl.timeoutMs,
|
|
17637
17963
|
templateId: tmpl.id
|
|
17638
17964
|
});
|
|
17639
17965
|
return c.json({ success: true, taskId: task.id });
|
|
@@ -17648,22 +17974,29 @@ app.put("/api/config", async (c) => {
|
|
|
17648
17974
|
const bW = body.worker ?? {};
|
|
17649
17975
|
const bS = body.scheduler ?? {};
|
|
17650
17976
|
const bD = body.watchdog ?? {};
|
|
17651
|
-
const merged = {
|
|
17652
|
-
|
|
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));
|
|
17653
17986
|
return c.json({ success: true });
|
|
17654
17987
|
} catch (err) {
|
|
17655
|
-
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);
|
|
17656
17989
|
}
|
|
17657
17990
|
});
|
|
17658
17991
|
app.post("/api/database/clear", async (c) => {
|
|
17659
17992
|
try {
|
|
17660
|
-
const { tasks: tasks3, taskRuns:
|
|
17661
|
-
await db.delete(
|
|
17993
|
+
const { tasks: tasks3, taskRuns: taskRuns4, taskTemplates: taskTemplates3 } = schema_exports;
|
|
17994
|
+
await db.delete(taskRuns4);
|
|
17662
17995
|
await db.delete(taskTemplates3);
|
|
17663
17996
|
await db.delete(tasks3);
|
|
17664
17997
|
return c.json({ success: true });
|
|
17665
17998
|
} catch (err) {
|
|
17666
|
-
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);
|
|
17667
18000
|
}
|
|
17668
18001
|
});
|
|
17669
18002
|
var dashboardApp = app;
|