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/cli/index.js
CHANGED
|
@@ -2353,11 +2353,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2353
2353
|
};
|
|
2354
2354
|
this._checkNumberOfArguments();
|
|
2355
2355
|
const processedArgs = [];
|
|
2356
|
-
this.registeredArguments.forEach((declaredArg,
|
|
2356
|
+
this.registeredArguments.forEach((declaredArg, index2) => {
|
|
2357
2357
|
let value = declaredArg.defaultValue;
|
|
2358
2358
|
if (declaredArg.variadic) {
|
|
2359
|
-
if (
|
|
2360
|
-
value = this.args.slice(
|
|
2359
|
+
if (index2 < this.args.length) {
|
|
2360
|
+
value = this.args.slice(index2);
|
|
2361
2361
|
if (declaredArg.parseArg) {
|
|
2362
2362
|
value = value.reduce((processed, v) => {
|
|
2363
2363
|
return myParseArg(declaredArg, v, processed);
|
|
@@ -2366,13 +2366,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2366
2366
|
} else if (value === void 0) {
|
|
2367
2367
|
value = [];
|
|
2368
2368
|
}
|
|
2369
|
-
} else if (
|
|
2370
|
-
value = this.args[
|
|
2369
|
+
} else if (index2 < this.args.length) {
|
|
2370
|
+
value = this.args[index2];
|
|
2371
2371
|
if (declaredArg.parseArg) {
|
|
2372
2372
|
value = myParseArg(declaredArg, value, declaredArg.defaultValue);
|
|
2373
2373
|
}
|
|
2374
2374
|
}
|
|
2375
|
-
processedArgs[
|
|
2375
|
+
processedArgs[index2] = value;
|
|
2376
2376
|
});
|
|
2377
2377
|
this.processedArgs = processedArgs;
|
|
2378
2378
|
}
|
|
@@ -2658,10 +2658,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2658
2658
|
}
|
|
2659
2659
|
}
|
|
2660
2660
|
if (/^--[^=]+=/.test(arg)) {
|
|
2661
|
-
const
|
|
2662
|
-
const option = this._findOption(arg.slice(0,
|
|
2661
|
+
const index2 = arg.indexOf("=");
|
|
2662
|
+
const option = this._findOption(arg.slice(0, index2));
|
|
2663
2663
|
if (option && (option.required || option.optional)) {
|
|
2664
|
-
this.emit(`option:${option.name()}`, arg.slice(
|
|
2664
|
+
this.emit(`option:${option.name()}`, arg.slice(index2 + 1));
|
|
2665
2665
|
continue;
|
|
2666
2666
|
}
|
|
2667
2667
|
}
|
|
@@ -4506,7 +4506,7 @@ var init_sql = __esm({
|
|
|
4506
4506
|
return new SQL([new StringChunk(str)]);
|
|
4507
4507
|
}
|
|
4508
4508
|
sql2.raw = raw2;
|
|
4509
|
-
function
|
|
4509
|
+
function join5(chunks, separator) {
|
|
4510
4510
|
const result = [];
|
|
4511
4511
|
for (const [i, chunk] of chunks.entries()) {
|
|
4512
4512
|
if (i > 0 && separator !== void 0) {
|
|
@@ -4516,7 +4516,7 @@ var init_sql = __esm({
|
|
|
4516
4516
|
}
|
|
4517
4517
|
return new SQL(result);
|
|
4518
4518
|
}
|
|
4519
|
-
sql2.join =
|
|
4519
|
+
sql2.join = join5;
|
|
4520
4520
|
function identifier(value) {
|
|
4521
4521
|
return new Name(value);
|
|
4522
4522
|
}
|
|
@@ -4660,8 +4660,8 @@ function haveSameKeys(left, right) {
|
|
|
4660
4660
|
if (leftKeys.length !== rightKeys.length) {
|
|
4661
4661
|
return false;
|
|
4662
4662
|
}
|
|
4663
|
-
for (const [
|
|
4664
|
-
if (key !== rightKeys[
|
|
4663
|
+
for (const [index2, key] of leftKeys.entries()) {
|
|
4664
|
+
if (key !== rightKeys[index2]) {
|
|
4665
4665
|
return false;
|
|
4666
4666
|
}
|
|
4667
4667
|
}
|
|
@@ -6522,8 +6522,8 @@ var init_dialect = __esm({
|
|
|
6522
6522
|
}
|
|
6523
6523
|
const joinsArray = [];
|
|
6524
6524
|
if (joins) {
|
|
6525
|
-
for (const [
|
|
6526
|
-
if (
|
|
6525
|
+
for (const [index2, joinMeta] of joins.entries()) {
|
|
6526
|
+
if (index2 === 0) {
|
|
6527
6527
|
joinsArray.push(sql` `);
|
|
6528
6528
|
}
|
|
6529
6529
|
const table = joinMeta.table;
|
|
@@ -6540,7 +6540,7 @@ var init_dialect = __esm({
|
|
|
6540
6540
|
sql`${sql.raw(joinMeta.joinType)} join ${table} on ${joinMeta.on}`
|
|
6541
6541
|
);
|
|
6542
6542
|
}
|
|
6543
|
-
if (
|
|
6543
|
+
if (index2 < joins.length - 1) {
|
|
6544
6544
|
joinsArray.push(sql` `);
|
|
6545
6545
|
}
|
|
6546
6546
|
}
|
|
@@ -6553,9 +6553,9 @@ var init_dialect = __esm({
|
|
|
6553
6553
|
buildOrderBy(orderBy) {
|
|
6554
6554
|
const orderByList = [];
|
|
6555
6555
|
if (orderBy) {
|
|
6556
|
-
for (const [
|
|
6556
|
+
for (const [index2, orderByValue] of orderBy.entries()) {
|
|
6557
6557
|
orderByList.push(orderByValue);
|
|
6558
|
-
if (
|
|
6558
|
+
if (index2 < orderBy.length - 1) {
|
|
6559
6559
|
orderByList.push(sql`, `);
|
|
6560
6560
|
}
|
|
6561
6561
|
}
|
|
@@ -6604,9 +6604,9 @@ var init_dialect = __esm({
|
|
|
6604
6604
|
const havingSql = having ? sql` having ${having}` : void 0;
|
|
6605
6605
|
const groupByList = [];
|
|
6606
6606
|
if (groupBy) {
|
|
6607
|
-
for (const [
|
|
6607
|
+
for (const [index2, groupByValue] of groupBy.entries()) {
|
|
6608
6608
|
groupByList.push(groupByValue);
|
|
6609
|
-
if (
|
|
6609
|
+
if (index2 < groupBy.length - 1) {
|
|
6610
6610
|
groupByList.push(sql`, `);
|
|
6611
6611
|
}
|
|
6612
6612
|
}
|
|
@@ -7132,7 +7132,7 @@ var init_select2 = __esm({
|
|
|
7132
7132
|
return (table, on) => {
|
|
7133
7133
|
const baseTableName = this.tableName;
|
|
7134
7134
|
const tableName = getTableLikeName(table);
|
|
7135
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
7135
|
+
if (typeof tableName === "string" && this.config.joins?.some((join5) => join5.alias === tableName)) {
|
|
7136
7136
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
7137
7137
|
}
|
|
7138
7138
|
if (!this.isPartialSelect) {
|
|
@@ -7974,7 +7974,7 @@ var init_update = __esm({
|
|
|
7974
7974
|
createJoin(joinType) {
|
|
7975
7975
|
return (table, on) => {
|
|
7976
7976
|
const tableName = getTableLikeName(table);
|
|
7977
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
7977
|
+
if (typeof tableName === "string" && this.config.joins.some((join5) => join5.alias === tableName)) {
|
|
7978
7978
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
7979
7979
|
}
|
|
7980
7980
|
if (typeof on === "function") {
|
|
@@ -8695,6 +8695,9 @@ var init_checks = __esm({
|
|
|
8695
8695
|
});
|
|
8696
8696
|
|
|
8697
8697
|
// node_modules/drizzle-orm/sqlite-core/indexes.js
|
|
8698
|
+
function index(name) {
|
|
8699
|
+
return new IndexBuilderOn(name, false);
|
|
8700
|
+
}
|
|
8698
8701
|
var IndexBuilderOn, IndexBuilder, Index;
|
|
8699
8702
|
var init_indexes = __esm({
|
|
8700
8703
|
"node_modules/drizzle-orm/sqlite-core/indexes.js"() {
|
|
@@ -9274,10 +9277,6 @@ var init_migrator = __esm({
|
|
|
9274
9277
|
});
|
|
9275
9278
|
|
|
9276
9279
|
// node_modules/drizzle-orm/bun-sqlite/migrator.js
|
|
9277
|
-
var migrator_exports = {};
|
|
9278
|
-
__export(migrator_exports, {
|
|
9279
|
-
migrate: () => migrate
|
|
9280
|
-
});
|
|
9281
9280
|
function migrate(db2, config) {
|
|
9282
9281
|
const migrations = readMigrationFiles(config);
|
|
9283
9282
|
db2.dialect.migrate(migrations, db2.session, config);
|
|
@@ -9327,12 +9326,17 @@ var init_schema = __esm({
|
|
|
9327
9326
|
resultLog: text("result_log"),
|
|
9328
9327
|
retryCount: integer("retry_count").default(0),
|
|
9329
9328
|
maxRetries: integer("max_retries").default(3),
|
|
9329
|
+
retryBackoffMs: integer("retry_backoff_ms").default(3e4),
|
|
9330
9330
|
// Gateway 扩展字段(毫秒)
|
|
9331
9331
|
retryAfter: integer("retry_after"),
|
|
9332
9332
|
timeoutMs: integer("timeout_ms"),
|
|
9333
9333
|
templateId: integer("template_id"),
|
|
9334
9334
|
scheduledAt: integer("scheduled_at")
|
|
9335
|
-
})
|
|
9335
|
+
}, (table) => [
|
|
9336
|
+
index("tasks_queue_idx").on(table.status, table.retryAfter, table.urgency, table.importance, table.createdAt, table.id),
|
|
9337
|
+
index("tasks_batch_status_idx").on(table.batchId, table.status),
|
|
9338
|
+
index("tasks_template_status_idx").on(table.templateId, table.status)
|
|
9339
|
+
]);
|
|
9336
9340
|
TASK_CATEGORIES = [
|
|
9337
9341
|
"translate",
|
|
9338
9342
|
"generate",
|
|
@@ -9342,7 +9346,7 @@ var init_schema = __esm({
|
|
|
9342
9346
|
];
|
|
9343
9347
|
taskRuns = sqliteTable("task_runs", {
|
|
9344
9348
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
9345
|
-
taskId: integer("task_id").notNull().references(() => tasks.id),
|
|
9349
|
+
taskId: integer("task_id").notNull().references(() => tasks.id, { onDelete: "cascade" }),
|
|
9346
9350
|
sessionId: text("session_id"),
|
|
9347
9351
|
model: text("model"),
|
|
9348
9352
|
status: text("status").default("running"),
|
|
@@ -9355,7 +9359,10 @@ var init_schema = __esm({
|
|
|
9355
9359
|
heartbeatAt: integer("heartbeat_at"),
|
|
9356
9360
|
workerPid: integer("worker_pid"),
|
|
9357
9361
|
childPid: integer("child_pid")
|
|
9358
|
-
})
|
|
9362
|
+
}, (table) => [
|
|
9363
|
+
index("task_runs_task_started_idx").on(table.taskId, table.startedAt, table.id),
|
|
9364
|
+
index("task_runs_status_heartbeat_idx").on(table.status, table.heartbeatAt)
|
|
9365
|
+
]);
|
|
9359
9366
|
taskTemplates = sqliteTable("task_templates", {
|
|
9360
9367
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
9361
9368
|
name: text("name").notNull(),
|
|
@@ -9366,6 +9373,7 @@ var init_schema = __esm({
|
|
|
9366
9373
|
category: text("category").default("general"),
|
|
9367
9374
|
importance: integer("importance").default(3),
|
|
9368
9375
|
urgency: integer("urgency").default(3),
|
|
9376
|
+
batchId: text("batch_id"),
|
|
9369
9377
|
scheduleType: text("schedule_type").notNull(),
|
|
9370
9378
|
cronExpr: text("cron_expr"),
|
|
9371
9379
|
intervalMs: integer("interval_ms"),
|
|
@@ -9373,12 +9381,15 @@ var init_schema = __esm({
|
|
|
9373
9381
|
maxInstances: integer("max_instances").default(1),
|
|
9374
9382
|
maxRetries: integer("max_retries").default(3),
|
|
9375
9383
|
retryBackoffMs: integer("retry_backoff_ms").default(3e4),
|
|
9384
|
+
timeoutMs: integer("timeout_ms"),
|
|
9376
9385
|
lastRunAt: integer("last_run_at"),
|
|
9377
9386
|
nextRunAt: integer("next_run_at"),
|
|
9378
9387
|
enabled: integer("enabled", { mode: "boolean" }).default(true),
|
|
9379
9388
|
createdAt: integer("created_at").default(0),
|
|
9380
9389
|
updatedAt: integer("updated_at").default(0)
|
|
9381
|
-
})
|
|
9390
|
+
}, (table) => [
|
|
9391
|
+
index("task_templates_due_idx").on(table.enabled, table.nextRunAt, table.id)
|
|
9392
|
+
]);
|
|
9382
9393
|
}
|
|
9383
9394
|
});
|
|
9384
9395
|
|
|
@@ -9423,16 +9434,30 @@ function initDb() {
|
|
|
9423
9434
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
9424
9435
|
pid INTEGER NOT NULL,
|
|
9425
9436
|
acquired_at INTEGER NOT NULL,
|
|
9426
|
-
heartbeat_at INTEGER NOT NULL
|
|
9437
|
+
heartbeat_at INTEGER NOT NULL,
|
|
9438
|
+
ready_at INTEGER
|
|
9427
9439
|
);
|
|
9428
9440
|
`);
|
|
9441
|
+
const gatewayLockColumns = _sqlite.query("PRAGMA table_info(gateway_lock)").all();
|
|
9442
|
+
if (!gatewayLockColumns.some((column) => column.name === "ready_at")) {
|
|
9443
|
+
_sqlite.exec("ALTER TABLE gateway_lock ADD COLUMN ready_at INTEGER;");
|
|
9444
|
+
}
|
|
9429
9445
|
_db = drizzle(_sqlite, { schema: schema_exports });
|
|
9430
9446
|
if (!_migrationRan) {
|
|
9431
|
-
_migrationRan = true;
|
|
9432
9447
|
try {
|
|
9433
9448
|
migrate(_db, { migrationsFolder: getMigrationsFolder() });
|
|
9449
|
+
_sqlite.exec("PRAGMA foreign_keys = ON;");
|
|
9450
|
+
const violations = _sqlite.query("PRAGMA foreign_key_check;").all();
|
|
9451
|
+
if (violations.length > 0) {
|
|
9452
|
+
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`);
|
|
9453
|
+
}
|
|
9454
|
+
_migrationRan = true;
|
|
9434
9455
|
} catch (err) {
|
|
9435
9456
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9457
|
+
_migrationRan = false;
|
|
9458
|
+
_sqlite.close();
|
|
9459
|
+
_sqlite = null;
|
|
9460
|
+
_db = null;
|
|
9436
9461
|
console.error(`[supertask] migration failed: ${msg}`);
|
|
9437
9462
|
throw new Error(`[supertask] DB migration failed: ${msg}`);
|
|
9438
9463
|
}
|
|
@@ -9452,6 +9477,7 @@ function closeDb() {
|
|
|
9452
9477
|
_sqlite.close();
|
|
9453
9478
|
_sqlite = null;
|
|
9454
9479
|
_db = null;
|
|
9480
|
+
_migrationRan = false;
|
|
9455
9481
|
}
|
|
9456
9482
|
}
|
|
9457
9483
|
var DEFAULT_DB_PATH, DB_FILE_PATH, _sqlite, _db, _migrationRan, db, sqlite;
|
|
@@ -9535,14 +9561,14 @@ var init_backoff = __esm({
|
|
|
9535
9561
|
});
|
|
9536
9562
|
|
|
9537
9563
|
// src/core/services/task.service.ts
|
|
9538
|
-
var tasks2, TaskService;
|
|
9564
|
+
var tasks2, taskRuns2, TaskService;
|
|
9539
9565
|
var init_task_service = __esm({
|
|
9540
9566
|
"src/core/services/task.service.ts"() {
|
|
9541
9567
|
"use strict";
|
|
9542
9568
|
init_db2();
|
|
9543
9569
|
init_drizzle_orm();
|
|
9544
9570
|
init_backoff();
|
|
9545
|
-
({ tasks: tasks2 } = schema_exports);
|
|
9571
|
+
({ tasks: tasks2, taskRuns: taskRuns2 } = schema_exports);
|
|
9546
9572
|
TaskService = class {
|
|
9547
9573
|
static buildScopeWhere(scope) {
|
|
9548
9574
|
const conditions = [];
|
|
@@ -9552,9 +9578,27 @@ var init_task_service = __esm({
|
|
|
9552
9578
|
return conditions;
|
|
9553
9579
|
}
|
|
9554
9580
|
static async add(data) {
|
|
9581
|
+
this.validateNewTask(data);
|
|
9555
9582
|
const result = await db.insert(tasks2).values(data).returning();
|
|
9556
9583
|
return result[0];
|
|
9557
9584
|
}
|
|
9585
|
+
static validateNewTask(data) {
|
|
9586
|
+
if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
9587
|
+
if (!data.agent.trim()) throw new Error("agent \u4E0D\u80FD\u4E3A\u7A7A");
|
|
9588
|
+
if (!data.prompt.trim()) throw new Error("prompt \u4E0D\u80FD\u4E3A\u7A7A");
|
|
9589
|
+
this.validateInteger("importance", data.importance, 1, 5);
|
|
9590
|
+
this.validateInteger("urgency", data.urgency, 1, 5);
|
|
9591
|
+
this.validateInteger("maxRetries", data.maxRetries, 0, 1e3);
|
|
9592
|
+
this.validateInteger("retryBackoffMs", data.retryBackoffMs, 0, 864e5);
|
|
9593
|
+
this.validateInteger("timeoutMs", data.timeoutMs, 1e3, 6048e5);
|
|
9594
|
+
this.validateInteger("dependsOn", data.dependsOn, 1, Number.MAX_SAFE_INTEGER);
|
|
9595
|
+
}
|
|
9596
|
+
static validateInteger(name, value, min, max) {
|
|
9597
|
+
if (value === void 0 || value === null) return;
|
|
9598
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
9599
|
+
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
9600
|
+
}
|
|
9601
|
+
}
|
|
9558
9602
|
static async next(scope = {}) {
|
|
9559
9603
|
const baseConditions = [...this.buildScopeWhere(scope)];
|
|
9560
9604
|
const nowMs = Date.now();
|
|
@@ -9577,7 +9621,7 @@ var init_task_service = __esm({
|
|
|
9577
9621
|
),
|
|
9578
9622
|
and(
|
|
9579
9623
|
eq(tasks2.status, "failed"),
|
|
9580
|
-
sql`${tasks2.retryCount}
|
|
9624
|
+
sql`${tasks2.retryCount} <= ${tasks2.maxRetries}`,
|
|
9581
9625
|
retryAfterFilter
|
|
9582
9626
|
)
|
|
9583
9627
|
);
|
|
@@ -9591,7 +9635,8 @@ var init_task_service = __esm({
|
|
|
9591
9635
|
const allTasks = await db.select().from(tasks2).where(and(...conditions)).orderBy(
|
|
9592
9636
|
desc(tasks2.urgency),
|
|
9593
9637
|
desc(tasks2.importance),
|
|
9594
|
-
asc(tasks2.createdAt)
|
|
9638
|
+
asc(tasks2.createdAt),
|
|
9639
|
+
asc(tasks2.id)
|
|
9595
9640
|
);
|
|
9596
9641
|
for (const task of allTasks) {
|
|
9597
9642
|
if (task.dependsOn) {
|
|
@@ -9612,7 +9657,7 @@ var init_task_service = __esm({
|
|
|
9612
9657
|
eq(tasks2.status, "pending"),
|
|
9613
9658
|
and(
|
|
9614
9659
|
eq(tasks2.status, "failed"),
|
|
9615
|
-
sql`${tasks2.retryCount}
|
|
9660
|
+
sql`${tasks2.retryCount} <= ${tasks2.maxRetries}`
|
|
9616
9661
|
)
|
|
9617
9662
|
),
|
|
9618
9663
|
...this.buildScopeWhere(scope)
|
|
@@ -9625,7 +9670,11 @@ var init_task_service = __esm({
|
|
|
9625
9670
|
return result[0] || null;
|
|
9626
9671
|
}
|
|
9627
9672
|
static async done(id, log, scope = {}) {
|
|
9628
|
-
const conditions = [
|
|
9673
|
+
const conditions = [
|
|
9674
|
+
eq(tasks2.id, id),
|
|
9675
|
+
eq(tasks2.status, "running"),
|
|
9676
|
+
...this.buildScopeWhere(scope)
|
|
9677
|
+
];
|
|
9629
9678
|
const result = await db.update(tasks2).set({
|
|
9630
9679
|
status: "done",
|
|
9631
9680
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
@@ -9636,17 +9685,24 @@ var init_task_service = __esm({
|
|
|
9636
9685
|
}
|
|
9637
9686
|
static async fail(id, log, scope = {}, options) {
|
|
9638
9687
|
const current = await this.getById(id, scope);
|
|
9639
|
-
if (!current) return null;
|
|
9688
|
+
if (!current || current.status !== "running") return null;
|
|
9640
9689
|
const newRetryCount = (current.retryCount ?? 0) + 1;
|
|
9641
9690
|
const maxRetries = current.maxRetries ?? 3;
|
|
9642
|
-
const isDeadLetter = options?.setDeadLetter ?? newRetryCount
|
|
9643
|
-
const conditions = [
|
|
9691
|
+
const isDeadLetter = options?.setDeadLetter ?? newRetryCount > maxRetries;
|
|
9692
|
+
const conditions = [
|
|
9693
|
+
eq(tasks2.id, id),
|
|
9694
|
+
eq(tasks2.status, "running"),
|
|
9695
|
+
...this.buildScopeWhere(scope)
|
|
9696
|
+
];
|
|
9644
9697
|
const result = await db.update(tasks2).set({
|
|
9645
9698
|
status: isDeadLetter ? "dead_letter" : "failed",
|
|
9646
9699
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
9647
9700
|
resultLog: log,
|
|
9648
9701
|
retryCount: newRetryCount,
|
|
9649
|
-
retryAfter: isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
|
|
9702
|
+
retryAfter: isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
|
|
9703
|
+
newRetryCount,
|
|
9704
|
+
current.retryBackoffMs ?? 3e4
|
|
9705
|
+
)
|
|
9650
9706
|
}).where(and(...conditions)).returning();
|
|
9651
9707
|
return result[0] || null;
|
|
9652
9708
|
}
|
|
@@ -9679,8 +9735,20 @@ var init_task_service = __esm({
|
|
|
9679
9735
|
return result.length;
|
|
9680
9736
|
}
|
|
9681
9737
|
static async cancel(id, scope = {}) {
|
|
9682
|
-
const conditions = [
|
|
9683
|
-
|
|
9738
|
+
const conditions = [
|
|
9739
|
+
eq(tasks2.id, id),
|
|
9740
|
+
or(
|
|
9741
|
+
eq(tasks2.status, "pending"),
|
|
9742
|
+
eq(tasks2.status, "running"),
|
|
9743
|
+
eq(tasks2.status, "failed")
|
|
9744
|
+
),
|
|
9745
|
+
...this.buildScopeWhere(scope)
|
|
9746
|
+
];
|
|
9747
|
+
const result = await db.update(tasks2).set({
|
|
9748
|
+
status: "cancelled",
|
|
9749
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
9750
|
+
retryAfter: null
|
|
9751
|
+
}).where(and(...conditions)).returning();
|
|
9684
9752
|
return result[0] || null;
|
|
9685
9753
|
}
|
|
9686
9754
|
static async retry(id, scope = {}) {
|
|
@@ -9692,7 +9760,8 @@ var init_task_service = __esm({
|
|
|
9692
9760
|
status: "pending",
|
|
9693
9761
|
startedAt: null,
|
|
9694
9762
|
finishedAt: null,
|
|
9695
|
-
retryAfter: null
|
|
9763
|
+
retryAfter: null,
|
|
9764
|
+
retryCount: 0
|
|
9696
9765
|
}).where(and(...conditions)).returning();
|
|
9697
9766
|
return result[0] || null;
|
|
9698
9767
|
}
|
|
@@ -9706,7 +9775,8 @@ var init_task_service = __esm({
|
|
|
9706
9775
|
status: "pending",
|
|
9707
9776
|
startedAt: null,
|
|
9708
9777
|
finishedAt: null,
|
|
9709
|
-
retryAfter: null
|
|
9778
|
+
retryAfter: null,
|
|
9779
|
+
retryCount: 0
|
|
9710
9780
|
}).where(and(...conditions)).returning();
|
|
9711
9781
|
return result.length;
|
|
9712
9782
|
}
|
|
@@ -9774,6 +9844,9 @@ var init_task_service = __esm({
|
|
|
9774
9844
|
}
|
|
9775
9845
|
static async delete(id, scope = {}) {
|
|
9776
9846
|
const conditions = [eq(tasks2.id, id), ...this.buildScopeWhere(scope)];
|
|
9847
|
+
const existing = await db.select({ id: tasks2.id }).from(tasks2).where(and(...conditions)).limit(1);
|
|
9848
|
+
if (!existing[0]) return false;
|
|
9849
|
+
await db.delete(taskRuns2).where(eq(taskRuns2.taskId, id));
|
|
9777
9850
|
const result = await db.delete(tasks2).where(and(...conditions)).returning();
|
|
9778
9851
|
return result.length > 0;
|
|
9779
9852
|
}
|
|
@@ -9992,7 +10065,7 @@ var require_CronField = __commonJS({
|
|
|
9992
10065
|
if (!isValidRange) {
|
|
9993
10066
|
throw new Error(`${this.constructor.name} Validation error, got value ${badValue} expected range ${this.min}-${this.max}${charsString}`);
|
|
9994
10067
|
}
|
|
9995
|
-
const duplicate = this.#values.find((value,
|
|
10068
|
+
const duplicate = this.#values.find((value, index2) => this.#values.indexOf(value) !== index2);
|
|
9996
10069
|
if (duplicate) {
|
|
9997
10070
|
throw new Error(`${this.constructor.name} Validation error, duplicate values found: ${duplicate}`);
|
|
9998
10071
|
}
|
|
@@ -17837,11 +17910,11 @@ var require_CronFieldCollection = __commonJS({
|
|
|
17837
17910
|
throw new Error("Unexpected range end");
|
|
17838
17911
|
}
|
|
17839
17912
|
if (step * multiplier > range.end) {
|
|
17840
|
-
const mapFn = (_,
|
|
17913
|
+
const mapFn = (_, index2) => {
|
|
17841
17914
|
if (typeof range.start !== "number") {
|
|
17842
17915
|
throw new Error("Unexpected range start");
|
|
17843
17916
|
}
|
|
17844
|
-
return
|
|
17917
|
+
return index2 % step === 0 ? range.start + index2 : null;
|
|
17845
17918
|
};
|
|
17846
17919
|
if (typeof range.start !== "number") {
|
|
17847
17920
|
throw new Error("Unexpected range start");
|
|
@@ -18733,9 +18806,9 @@ var require_CronExpressionParser = __commonJS({
|
|
|
18733
18806
|
if (field === CronUnit.DayOfWeek && max % 7 === 0) {
|
|
18734
18807
|
stack.push(0);
|
|
18735
18808
|
}
|
|
18736
|
-
for (let
|
|
18737
|
-
if (stack.indexOf(
|
|
18738
|
-
stack.push(
|
|
18809
|
+
for (let index2 = min; index2 <= max; index2 += repeatInterval) {
|
|
18810
|
+
if (stack.indexOf(index2) === -1) {
|
|
18811
|
+
stack.push(index2);
|
|
18739
18812
|
}
|
|
18740
18813
|
}
|
|
18741
18814
|
return stack;
|
|
@@ -18858,8 +18931,8 @@ var require_CronFileParser = __commonJS({
|
|
|
18858
18931
|
* @throws If file cannot be read
|
|
18859
18932
|
*/
|
|
18860
18933
|
static parseFileSync(filePath) {
|
|
18861
|
-
const { readFileSync:
|
|
18862
|
-
const data =
|
|
18934
|
+
const { readFileSync: readFileSync5 } = __require("fs");
|
|
18935
|
+
const data = readFileSync5(filePath, "utf8");
|
|
18863
18936
|
return _CronFileParser.#parseContent(data);
|
|
18864
18937
|
}
|
|
18865
18938
|
/**
|
|
@@ -18958,11 +19031,6 @@ var require_dist = __commonJS({
|
|
|
18958
19031
|
});
|
|
18959
19032
|
|
|
18960
19033
|
// src/core/cron-parser.ts
|
|
18961
|
-
var cron_parser_exports = {};
|
|
18962
|
-
__export(cron_parser_exports, {
|
|
18963
|
-
getNextCronRun: () => getNextCronRun,
|
|
18964
|
-
isValidCronExpr: () => isValidCronExpr
|
|
18965
|
-
});
|
|
18966
19034
|
function getNextCronRun(expr, afterMs) {
|
|
18967
19035
|
try {
|
|
18968
19036
|
const fromDate = afterMs != null ? new Date(afterMs) : /* @__PURE__ */ new Date();
|
|
@@ -19000,6 +19068,7 @@ var init_task_template_service = __esm({
|
|
|
19000
19068
|
({ taskTemplates: taskTemplates2 } = schema_exports);
|
|
19001
19069
|
TaskTemplateService = class {
|
|
19002
19070
|
static async create(data) {
|
|
19071
|
+
this.validate(data);
|
|
19003
19072
|
const now = Date.now();
|
|
19004
19073
|
const result = await db.insert(taskTemplates2).values({ ...data, createdAt: now, updatedAt: now }).returning();
|
|
19005
19074
|
const tmpl = result[0];
|
|
@@ -19015,8 +19084,38 @@ var init_task_template_service = __esm({
|
|
|
19015
19084
|
}
|
|
19016
19085
|
return tmpl;
|
|
19017
19086
|
}
|
|
19087
|
+
static validate(data) {
|
|
19088
|
+
if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
19089
|
+
if (!data.agent.trim()) throw new Error("agent \u4E0D\u80FD\u4E3A\u7A7A");
|
|
19090
|
+
if (!data.prompt.trim()) throw new Error("prompt \u4E0D\u80FD\u4E3A\u7A7A");
|
|
19091
|
+
const scheduleType = data.scheduleType;
|
|
19092
|
+
if (!["cron", "delayed", "recurring"].includes(scheduleType)) {
|
|
19093
|
+
throw new Error("scheduleType \u5FC5\u987B\u662F cron\u3001delayed \u6216 recurring");
|
|
19094
|
+
}
|
|
19095
|
+
if (scheduleType === "cron" && (!data.cronExpr || !isValidCronExpr(data.cronExpr))) {
|
|
19096
|
+
throw new Error("cronExpr \u7F3A\u5931\u6216\u683C\u5F0F\u65E0\u6548");
|
|
19097
|
+
}
|
|
19098
|
+
if (scheduleType === "recurring" && (!Number.isInteger(data.intervalMs) || (data.intervalMs ?? 0) <= 0)) {
|
|
19099
|
+
throw new Error("intervalMs \u5FC5\u987B\u662F\u6B63\u6574\u6570");
|
|
19100
|
+
}
|
|
19101
|
+
if (scheduleType === "delayed" && (!Number.isInteger(data.runAt) || (data.runAt ?? 0) <= 0)) {
|
|
19102
|
+
throw new Error("runAt \u5FC5\u987B\u662F\u6B63\u6574\u6570\u65F6\u95F4\u6233");
|
|
19103
|
+
}
|
|
19104
|
+
this.validateInteger("importance", data.importance, 1, 5);
|
|
19105
|
+
this.validateInteger("urgency", data.urgency, 1, 5);
|
|
19106
|
+
this.validateInteger("maxInstances", data.maxInstances, 1, 1e3);
|
|
19107
|
+
this.validateInteger("maxRetries", data.maxRetries, 0, 1e3);
|
|
19108
|
+
this.validateInteger("retryBackoffMs", data.retryBackoffMs, 0, 864e5);
|
|
19109
|
+
this.validateInteger("timeoutMs", data.timeoutMs, 1e3, 6048e5);
|
|
19110
|
+
}
|
|
19111
|
+
static validateInteger(name, value, min, max) {
|
|
19112
|
+
if (value === void 0 || value === null) return;
|
|
19113
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
19114
|
+
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
19115
|
+
}
|
|
19116
|
+
}
|
|
19018
19117
|
static async list(limit = 50) {
|
|
19019
|
-
return await db.select().from(taskTemplates2).orderBy(desc(taskTemplates2.createdAt)).limit(limit);
|
|
19118
|
+
return await db.select().from(taskTemplates2).orderBy(desc(taskTemplates2.createdAt), desc(taskTemplates2.id)).limit(limit);
|
|
19020
19119
|
}
|
|
19021
19120
|
static async getById(id) {
|
|
19022
19121
|
const result = await db.select().from(taskTemplates2).where(eq(taskTemplates2.id, id));
|
|
@@ -19059,137 +19158,466 @@ var init_task_template_service = __esm({
|
|
|
19059
19158
|
// src/gateway/config.ts
|
|
19060
19159
|
var config_exports = {};
|
|
19061
19160
|
__export(config_exports, {
|
|
19062
|
-
|
|
19063
|
-
|
|
19161
|
+
DEFAULT_CONFIG: () => DEFAULT_CONFIG,
|
|
19162
|
+
DEFAULT_CONFIG_PATH: () => DEFAULT_CONFIG_PATH,
|
|
19163
|
+
getConfigPath: () => getConfigPath,
|
|
19164
|
+
loadConfig: () => loadConfig,
|
|
19165
|
+
validateConfig: () => validateConfig
|
|
19064
19166
|
});
|
|
19065
|
-
import {
|
|
19167
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
19066
19168
|
import { homedir as homedir2 } from "os";
|
|
19067
19169
|
import { join as join2 } from "path";
|
|
19068
|
-
function
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
|
|
19072
|
-
|
|
19073
|
-
|
|
19074
|
-
|
|
19075
|
-
result[key] = deepMerge(baseVal, val);
|
|
19076
|
-
continue;
|
|
19077
|
-
}
|
|
19078
|
-
}
|
|
19079
|
-
if (val !== void 0) {
|
|
19080
|
-
result[key] = val;
|
|
19081
|
-
}
|
|
19170
|
+
function getConfigPath() {
|
|
19171
|
+
return process.env.SUPERTASK_CONFIG_PATH ?? DEFAULT_CONFIG_PATH;
|
|
19172
|
+
}
|
|
19173
|
+
function objectAt(value, path) {
|
|
19174
|
+
if (value === void 0) return {};
|
|
19175
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
19176
|
+
throw new Error(`${path} \u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
19082
19177
|
}
|
|
19083
|
-
return
|
|
19178
|
+
return value;
|
|
19179
|
+
}
|
|
19180
|
+
function integerAt(source, key, fallback, min, max, path) {
|
|
19181
|
+
const value = source[key];
|
|
19182
|
+
if (value === void 0) return fallback;
|
|
19183
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
|
|
19184
|
+
throw new Error(`${path}.${key} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
19185
|
+
}
|
|
19186
|
+
return value;
|
|
19187
|
+
}
|
|
19188
|
+
function booleanAt(source, key, fallback, path) {
|
|
19189
|
+
const value = source[key];
|
|
19190
|
+
if (value === void 0) return fallback;
|
|
19191
|
+
if (typeof value !== "boolean") throw new Error(`${path}.${key} \u5FC5\u987B\u662F\u5E03\u5C14\u503C`);
|
|
19192
|
+
return value;
|
|
19084
19193
|
}
|
|
19085
|
-
function
|
|
19086
|
-
|
|
19087
|
-
|
|
19194
|
+
function validateConfig(input) {
|
|
19195
|
+
const root = objectAt(input, "config");
|
|
19196
|
+
const version2 = root.configVersion ?? 1;
|
|
19197
|
+
if (version2 !== 1 && version2 !== 2) {
|
|
19198
|
+
throw new Error("config.configVersion \u53EA\u652F\u6301 1 \u6216 2");
|
|
19199
|
+
}
|
|
19200
|
+
const worker = objectAt(root.worker, "worker");
|
|
19201
|
+
const scheduler = objectAt(root.scheduler, "scheduler");
|
|
19202
|
+
const watchdog = objectAt(root.watchdog, "watchdog");
|
|
19203
|
+
const dashboard = objectAt(root.dashboard, "dashboard");
|
|
19204
|
+
const heartbeatIntervalMs = integerAt(worker, "heartbeatIntervalMs", DEFAULT_CONFIG.worker.heartbeatIntervalMs, 1e3, 36e5, "worker");
|
|
19205
|
+
const heartbeatTimeoutMs = integerAt(watchdog, "heartbeatTimeoutMs", DEFAULT_CONFIG.watchdog.heartbeatTimeoutMs, 1e3, 864e5, "watchdog");
|
|
19206
|
+
let checkIntervalMs;
|
|
19207
|
+
let cleanupIntervalMs;
|
|
19208
|
+
if (version2 === 1 && watchdog.checkIntervalMs === void 0 && watchdog.cleanupIntervalMs !== void 0) {
|
|
19209
|
+
checkIntervalMs = integerAt(watchdog, "cleanupIntervalMs", DEFAULT_CONFIG.watchdog.checkIntervalMs, 1e3, 36e5, "watchdog");
|
|
19210
|
+
cleanupIntervalMs = DEFAULT_CONFIG.watchdog.cleanupIntervalMs;
|
|
19211
|
+
} else {
|
|
19212
|
+
checkIntervalMs = integerAt(watchdog, "checkIntervalMs", DEFAULT_CONFIG.watchdog.checkIntervalMs, 1e3, 36e5, "watchdog");
|
|
19213
|
+
cleanupIntervalMs = integerAt(watchdog, "cleanupIntervalMs", DEFAULT_CONFIG.watchdog.cleanupIntervalMs, 6e4, 6048e5, "watchdog");
|
|
19214
|
+
}
|
|
19215
|
+
if (heartbeatIntervalMs >= heartbeatTimeoutMs) {
|
|
19216
|
+
throw new Error("worker.heartbeatIntervalMs \u5FC5\u987B\u5C0F\u4E8E watchdog.heartbeatTimeoutMs");
|
|
19088
19217
|
}
|
|
19218
|
+
if (checkIntervalMs > heartbeatTimeoutMs) {
|
|
19219
|
+
throw new Error("watchdog.checkIntervalMs \u4E0D\u80FD\u5927\u4E8E watchdog.heartbeatTimeoutMs");
|
|
19220
|
+
}
|
|
19221
|
+
return {
|
|
19222
|
+
configVersion: 2,
|
|
19223
|
+
worker: {
|
|
19224
|
+
maxConcurrency: integerAt(worker, "maxConcurrency", DEFAULT_CONFIG.worker.maxConcurrency, 1, 64, "worker"),
|
|
19225
|
+
pollIntervalMs: integerAt(worker, "pollIntervalMs", DEFAULT_CONFIG.worker.pollIntervalMs, 50, 6e4, "worker"),
|
|
19226
|
+
heartbeatIntervalMs,
|
|
19227
|
+
taskTimeoutMs: integerAt(worker, "taskTimeoutMs", DEFAULT_CONFIG.worker.taskTimeoutMs, 1e3, 6048e5, "worker"),
|
|
19228
|
+
shutdownGracePeriodMs: integerAt(worker, "shutdownGracePeriodMs", DEFAULT_CONFIG.worker.shutdownGracePeriodMs, 0, 36e5, "worker")
|
|
19229
|
+
},
|
|
19230
|
+
scheduler: {
|
|
19231
|
+
enabled: booleanAt(scheduler, "enabled", DEFAULT_CONFIG.scheduler.enabled, "scheduler"),
|
|
19232
|
+
checkIntervalMs: integerAt(scheduler, "checkIntervalMs", DEFAULT_CONFIG.scheduler.checkIntervalMs, 100, 6e4, "scheduler")
|
|
19233
|
+
},
|
|
19234
|
+
watchdog: {
|
|
19235
|
+
heartbeatTimeoutMs,
|
|
19236
|
+
checkIntervalMs,
|
|
19237
|
+
cleanupIntervalMs,
|
|
19238
|
+
retentionDays: integerAt(watchdog, "retentionDays", DEFAULT_CONFIG.watchdog.retentionDays, 1, 3650, "watchdog")
|
|
19239
|
+
},
|
|
19240
|
+
dashboard: {
|
|
19241
|
+
enabled: booleanAt(dashboard, "enabled", DEFAULT_CONFIG.dashboard.enabled, "dashboard"),
|
|
19242
|
+
port: integerAt(dashboard, "port", DEFAULT_CONFIG.dashboard.port, 1, 65535, "dashboard")
|
|
19243
|
+
}
|
|
19244
|
+
};
|
|
19245
|
+
}
|
|
19246
|
+
function loadConfig(path = getConfigPath()) {
|
|
19247
|
+
if (!existsSync2(path)) return validateConfig({ configVersion: 2 });
|
|
19089
19248
|
try {
|
|
19090
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
19093
|
-
|
|
19094
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
19095
|
-
console.error(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "warn", msg: "config load failed, using defaults", path: CONFIG_PATH, error: msg }));
|
|
19096
|
-
return DEFAULT_CONFIG;
|
|
19249
|
+
return validateConfig(JSON.parse(readFileSync(path, "utf-8")));
|
|
19250
|
+
} catch (error) {
|
|
19251
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19252
|
+
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E ${path}: ${message}`);
|
|
19097
19253
|
}
|
|
19098
19254
|
}
|
|
19099
|
-
var DEFAULT_CONFIG,
|
|
19255
|
+
var DEFAULT_CONFIG, DEFAULT_CONFIG_PATH;
|
|
19100
19256
|
var init_config = __esm({
|
|
19101
19257
|
"src/gateway/config.ts"() {
|
|
19102
19258
|
"use strict";
|
|
19103
19259
|
DEFAULT_CONFIG = {
|
|
19260
|
+
configVersion: 2,
|
|
19104
19261
|
worker: {
|
|
19105
19262
|
maxConcurrency: 2,
|
|
19106
19263
|
pollIntervalMs: 1e3,
|
|
19107
19264
|
heartbeatIntervalMs: 3e4,
|
|
19108
|
-
taskTimeoutMs: 18e5
|
|
19265
|
+
taskTimeoutMs: 18e5,
|
|
19266
|
+
shutdownGracePeriodMs: 3e4
|
|
19109
19267
|
},
|
|
19110
19268
|
scheduler: {
|
|
19111
19269
|
enabled: true,
|
|
19112
|
-
checkIntervalMs: 1e3
|
|
19113
|
-
catchUp: "next"
|
|
19270
|
+
checkIntervalMs: 1e3
|
|
19114
19271
|
},
|
|
19115
19272
|
watchdog: {
|
|
19116
19273
|
heartbeatTimeoutMs: 6e5,
|
|
19117
|
-
|
|
19274
|
+
checkIntervalMs: 6e4,
|
|
19275
|
+
cleanupIntervalMs: 864e5,
|
|
19118
19276
|
retentionDays: 30
|
|
19119
19277
|
},
|
|
19120
19278
|
dashboard: {
|
|
19121
19279
|
enabled: true,
|
|
19122
19280
|
port: 4680
|
|
19123
|
-
},
|
|
19124
|
-
logging: {
|
|
19125
|
-
level: "info",
|
|
19126
|
-
format: "json"
|
|
19127
19281
|
}
|
|
19128
19282
|
};
|
|
19129
|
-
|
|
19283
|
+
DEFAULT_CONFIG_PATH = join2(homedir2(), ".config/opencode/supertask.json");
|
|
19284
|
+
}
|
|
19285
|
+
});
|
|
19286
|
+
|
|
19287
|
+
// src/daemon/pm2.ts
|
|
19288
|
+
var pm2_exports = {};
|
|
19289
|
+
__export(pm2_exports, {
|
|
19290
|
+
ensureGateway: () => ensureGateway,
|
|
19291
|
+
getPackageVersion: () => getPackageVersion,
|
|
19292
|
+
install: () => install,
|
|
19293
|
+
isGatewayReady: () => isGatewayReady,
|
|
19294
|
+
isGatewayRunning: () => isGatewayRunning,
|
|
19295
|
+
isPm2Installed: () => isPm2Installed,
|
|
19296
|
+
resolveGatewayEntry: () => resolveGatewayEntry,
|
|
19297
|
+
uninstall: () => uninstall,
|
|
19298
|
+
upgrade: () => upgrade
|
|
19299
|
+
});
|
|
19300
|
+
import { execSync, spawnSync } from "child_process";
|
|
19301
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
19302
|
+
import { homedir as homedir3 } from "os";
|
|
19303
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
19304
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
19305
|
+
import { Database as Database3 } from "bun:sqlite";
|
|
19306
|
+
function getPackageVersion() {
|
|
19307
|
+
const envVersion = process.env.npm_package_version;
|
|
19308
|
+
if (envVersion) return envVersion;
|
|
19309
|
+
try {
|
|
19310
|
+
const pkgPath = join3(__dirname, "../../package.json");
|
|
19311
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
19312
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
19313
|
+
} catch {
|
|
19314
|
+
return "0.0.0";
|
|
19315
|
+
}
|
|
19316
|
+
}
|
|
19317
|
+
function resolveGatewayEntry() {
|
|
19318
|
+
const override = process.env.SUPERTASK_GATEWAY_ENTRY;
|
|
19319
|
+
if (override) {
|
|
19320
|
+
if (!existsSync3(override)) throw new Error(`[supertask] Gateway entry not found: ${override}`);
|
|
19321
|
+
return override;
|
|
19322
|
+
}
|
|
19323
|
+
const candidates = [
|
|
19324
|
+
join3(__dirname, "../gateway/index.js"),
|
|
19325
|
+
join3(__dirname, "../gateway/index.ts")
|
|
19326
|
+
];
|
|
19327
|
+
const entry = candidates.find((candidate) => existsSync3(candidate));
|
|
19328
|
+
if (!entry) throw new Error(`[supertask] Gateway entry not found. Checked: ${candidates.join(", ")}`);
|
|
19329
|
+
return entry;
|
|
19330
|
+
}
|
|
19331
|
+
function versionFile() {
|
|
19332
|
+
return process.env.SUPERTASK_VERSION_FILE ?? join3(homedir3(), ".local/share/opencode/supertask-gateway-version");
|
|
19333
|
+
}
|
|
19334
|
+
function getRunningVersion() {
|
|
19335
|
+
try {
|
|
19336
|
+
const path = versionFile();
|
|
19337
|
+
if (!existsSync3(path)) return null;
|
|
19338
|
+
return readFileSync2(path, "utf-8").trim() || null;
|
|
19339
|
+
} catch {
|
|
19340
|
+
return null;
|
|
19341
|
+
}
|
|
19342
|
+
}
|
|
19343
|
+
function writeRunningVersion(version2) {
|
|
19344
|
+
const path = versionFile();
|
|
19345
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
19346
|
+
writeFileSync(path, version2, "utf-8");
|
|
19347
|
+
}
|
|
19348
|
+
function pm2Bin() {
|
|
19349
|
+
return process.env.SUPERTASK_PM2_BIN ?? (process.platform === "win32" ? "pm2.cmd" : "pm2");
|
|
19350
|
+
}
|
|
19351
|
+
function isPm2Installed() {
|
|
19352
|
+
const result = spawnSync(pm2Bin(), ["--version"], {
|
|
19353
|
+
stdio: "ignore",
|
|
19354
|
+
shell: process.platform === "win32"
|
|
19355
|
+
});
|
|
19356
|
+
return result.status === 0;
|
|
19357
|
+
}
|
|
19358
|
+
function installPm2() {
|
|
19359
|
+
console.log("[supertask] Installing pm2...");
|
|
19360
|
+
try {
|
|
19361
|
+
execSync("npm install -g pm2", { stdio: "inherit" });
|
|
19362
|
+
return true;
|
|
19363
|
+
} catch {
|
|
19364
|
+
try {
|
|
19365
|
+
execSync("bun install -g pm2", { stdio: "inherit" });
|
|
19366
|
+
return true;
|
|
19367
|
+
} catch {
|
|
19368
|
+
return false;
|
|
19369
|
+
}
|
|
19370
|
+
}
|
|
19371
|
+
}
|
|
19372
|
+
function pm2Exec(args) {
|
|
19373
|
+
const result = spawnSync(pm2Bin(), args, {
|
|
19374
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
19375
|
+
encoding: "utf-8",
|
|
19376
|
+
env: process.env,
|
|
19377
|
+
shell: process.platform === "win32"
|
|
19378
|
+
});
|
|
19379
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
19380
|
+
if (result.error) return { ok: false, output: result.error.message };
|
|
19381
|
+
return { ok: result.status === 0, output };
|
|
19382
|
+
}
|
|
19383
|
+
function requirePm2(args, action) {
|
|
19384
|
+
const result = pm2Exec(args);
|
|
19385
|
+
if (!result.ok) throw new Error(`[supertask] ${action} failed: ${result.output || "unknown pm2 error"}`);
|
|
19386
|
+
return result.output;
|
|
19387
|
+
}
|
|
19388
|
+
function pm2JsonList() {
|
|
19389
|
+
const output = requirePm2(["jlist"], "pm2 jlist");
|
|
19390
|
+
try {
|
|
19391
|
+
const parsed = JSON.parse(output);
|
|
19392
|
+
if (!Array.isArray(parsed)) throw new Error("result is not an array");
|
|
19393
|
+
return parsed;
|
|
19394
|
+
} catch (error) {
|
|
19395
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19396
|
+
throw new Error(`[supertask] Invalid pm2 jlist output: ${message}`);
|
|
19397
|
+
}
|
|
19398
|
+
}
|
|
19399
|
+
function isGatewayRunning() {
|
|
19400
|
+
if (!isPm2Installed()) return false;
|
|
19401
|
+
const proc = pm2JsonList().find((item) => item.name === PROCESS_NAME);
|
|
19402
|
+
return proc?.pm2_env?.status === "online" && typeof proc.pid === "number" && isGatewayReady(proc.pid);
|
|
19403
|
+
}
|
|
19404
|
+
function databasePath() {
|
|
19405
|
+
return process.env.SUPERTASK_DB_PATH ?? join3(homedir3(), ".local/share/opencode/tasks.db");
|
|
19406
|
+
}
|
|
19407
|
+
function isGatewayReady(expectedPid) {
|
|
19408
|
+
const path = databasePath();
|
|
19409
|
+
if (!existsSync3(path)) return false;
|
|
19410
|
+
let database = null;
|
|
19411
|
+
try {
|
|
19412
|
+
database = new Database3(path, { readonly: true });
|
|
19413
|
+
const row = database.query(
|
|
19414
|
+
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
19415
|
+
).get();
|
|
19416
|
+
if (!row || row.ready_at == null) return false;
|
|
19417
|
+
if (expectedPid !== void 0 && row.pid !== expectedPid) return false;
|
|
19418
|
+
const ageMs = Date.now() - row.heartbeat_at;
|
|
19419
|
+
return ageMs >= -5e3 && ageMs < GATEWAY_LOCK_STALE_MS;
|
|
19420
|
+
} catch {
|
|
19421
|
+
return false;
|
|
19422
|
+
} finally {
|
|
19423
|
+
database?.close();
|
|
19424
|
+
}
|
|
19425
|
+
}
|
|
19426
|
+
function readyTimeoutMs() {
|
|
19427
|
+
const value = Number(process.env.SUPERTASK_GATEWAY_READY_TIMEOUT_MS ?? 3e4);
|
|
19428
|
+
return Number.isFinite(value) && value > 0 ? value : 3e4;
|
|
19429
|
+
}
|
|
19430
|
+
function waitForGatewayReady(pid) {
|
|
19431
|
+
const deadline = Date.now() + readyTimeoutMs();
|
|
19432
|
+
const sleeper = new Int32Array(new SharedArrayBuffer(4));
|
|
19433
|
+
while (Date.now() < deadline) {
|
|
19434
|
+
if (isGatewayReady(pid)) return true;
|
|
19435
|
+
Atomics.wait(sleeper, 0, 0, 100);
|
|
19436
|
+
}
|
|
19437
|
+
return isGatewayReady(pid);
|
|
19438
|
+
}
|
|
19439
|
+
function findBunPath() {
|
|
19440
|
+
const override = process.env.SUPERTASK_BUN_BIN;
|
|
19441
|
+
if (override) return override;
|
|
19442
|
+
try {
|
|
19443
|
+
const command = process.platform === "win32" ? "where bun" : "which bun";
|
|
19444
|
+
return execSync(command, { stdio: "pipe" }).toString().trim().split("\n")[0];
|
|
19445
|
+
} catch {
|
|
19446
|
+
return process.execPath;
|
|
19447
|
+
}
|
|
19448
|
+
}
|
|
19449
|
+
function pm2StartGateway(gatewayEntry = resolveGatewayEntry()) {
|
|
19450
|
+
const configuredKillTimeout = Number(process.env.SUPERTASK_PM2_KILL_TIMEOUT_MS);
|
|
19451
|
+
const killTimeoutMs = Number.isInteger(configuredKillTimeout) && configuredKillTimeout >= 5e3 ? configuredKillTimeout : loadConfig().worker.shutdownGracePeriodMs + 5e3;
|
|
19452
|
+
requirePm2([
|
|
19453
|
+
"start",
|
|
19454
|
+
findBunPath(),
|
|
19455
|
+
"--name",
|
|
19456
|
+
PROCESS_NAME,
|
|
19457
|
+
"--interpreter",
|
|
19458
|
+
"none",
|
|
19459
|
+
"--restart-delay",
|
|
19460
|
+
"5000",
|
|
19461
|
+
"--max-restarts",
|
|
19462
|
+
"30",
|
|
19463
|
+
"--kill-timeout",
|
|
19464
|
+
String(killTimeoutMs),
|
|
19465
|
+
"--",
|
|
19466
|
+
gatewayEntry
|
|
19467
|
+
], "pm2 start");
|
|
19468
|
+
const started = pm2JsonList().find((item) => item.name === PROCESS_NAME);
|
|
19469
|
+
if (started?.pm2_env?.status !== "online") {
|
|
19470
|
+
throw new Error(`[supertask] Gateway did not become online (status: ${started?.pm2_env?.status ?? "missing"})`);
|
|
19471
|
+
}
|
|
19472
|
+
if (typeof started.pid !== "number" || !waitForGatewayReady(started.pid)) {
|
|
19473
|
+
throw new Error("[supertask] Gateway \u8FDB\u7A0B online\uFF0C\u4F46\u672A\u5728\u9650\u5B9A\u65F6\u95F4\u5185\u5C31\u7EEA\uFF1B\u8BF7\u67E5\u770B pm2 logs supertask-gateway");
|
|
19474
|
+
}
|
|
19475
|
+
}
|
|
19476
|
+
function savePm2State() {
|
|
19477
|
+
requirePm2(["save"], "pm2 save");
|
|
19478
|
+
}
|
|
19479
|
+
function install() {
|
|
19480
|
+
if (!isPm2Installed() && !installPm2()) {
|
|
19481
|
+
throw new Error("[supertask] Failed to install pm2. Please install it manually: npm install -g pm2");
|
|
19482
|
+
}
|
|
19483
|
+
const existing = pm2JsonList().find((item) => item.name === PROCESS_NAME);
|
|
19484
|
+
if (existing) requirePm2(["delete", PROCESS_NAME], "pm2 delete existing Gateway");
|
|
19485
|
+
const version2 = getPackageVersion();
|
|
19486
|
+
pm2StartGateway();
|
|
19487
|
+
writeRunningVersion(version2);
|
|
19488
|
+
savePm2State();
|
|
19489
|
+
const startup = pm2Exec(["startup"]);
|
|
19490
|
+
if (startup.output) console.log(startup.output);
|
|
19491
|
+
if (!startup.ok) {
|
|
19492
|
+
console.warn("[supertask] pm2 startup \u672A\u5B8C\u6210\uFF1B\u8BF7\u6309 pm2 \u8F93\u51FA\u6267\u884C\u9700\u8981\u7BA1\u7406\u5458\u6743\u9650\u7684\u547D\u4EE4\uFF0C\u7136\u540E\u8FD0\u884C `pm2 save`\u3002");
|
|
19493
|
+
}
|
|
19494
|
+
console.log("[supertask] Gateway installed and running.");
|
|
19495
|
+
console.log("[supertask] Manage with: pm2 status / pm2 logs supertask-gateway");
|
|
19496
|
+
}
|
|
19497
|
+
function uninstall() {
|
|
19498
|
+
if (!isPm2Installed()) throw new Error("[supertask] pm2 is not installed");
|
|
19499
|
+
const existing = pm2JsonList().find((item) => item.name === PROCESS_NAME);
|
|
19500
|
+
if (existing) requirePm2(["delete", PROCESS_NAME], "pm2 delete Gateway");
|
|
19501
|
+
savePm2State();
|
|
19502
|
+
console.log("[supertask] Gateway removed from pm2. Other pm2 startup entries were preserved.");
|
|
19503
|
+
}
|
|
19504
|
+
function upgrade(target) {
|
|
19505
|
+
if (!isPm2Installed()) {
|
|
19506
|
+
throw new Error("[supertask] pm2 is not installed. Run `supertask install` first.");
|
|
19507
|
+
}
|
|
19508
|
+
const before = getRunningVersion();
|
|
19509
|
+
const oldGatewayEntry = resolveGatewayEntry();
|
|
19510
|
+
const currentVersion = target?.version ?? getPackageVersion();
|
|
19511
|
+
const existing = pm2JsonList().find((item) => item.name === PROCESS_NAME);
|
|
19512
|
+
if (existing) requirePm2(["delete", PROCESS_NAME], "pm2 delete old Gateway");
|
|
19513
|
+
try {
|
|
19514
|
+
pm2StartGateway(target?.gatewayEntry ?? oldGatewayEntry);
|
|
19515
|
+
writeRunningVersion(currentVersion);
|
|
19516
|
+
savePm2State();
|
|
19517
|
+
return { before, after: currentVersion, restarted: true };
|
|
19518
|
+
} catch (error) {
|
|
19519
|
+
const failed = pm2JsonList().find((item) => item.name === PROCESS_NAME);
|
|
19520
|
+
if (failed) requirePm2(["delete", PROCESS_NAME], "pm2 delete failed Gateway");
|
|
19521
|
+
try {
|
|
19522
|
+
pm2StartGateway(oldGatewayEntry);
|
|
19523
|
+
if (before) writeRunningVersion(before);
|
|
19524
|
+
savePm2State();
|
|
19525
|
+
} catch (rollbackError) {
|
|
19526
|
+
const original = error instanceof Error ? error.message : String(error);
|
|
19527
|
+
const rollback = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
|
|
19528
|
+
throw new Error(`${original}; \u65E7 Gateway \u56DE\u6EDA\u4E5F\u5931\u8D25: ${rollback}`);
|
|
19529
|
+
}
|
|
19530
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19531
|
+
throw new Error(`${message}; \u5DF2\u56DE\u6EDA\u5230\u65E7 Gateway`);
|
|
19532
|
+
}
|
|
19533
|
+
}
|
|
19534
|
+
function ensureGateway() {
|
|
19535
|
+
if (!isPm2Installed()) {
|
|
19536
|
+
return { ok: false, reason: "pm2-not-installed" };
|
|
19537
|
+
}
|
|
19538
|
+
const currentVersion = getPackageVersion();
|
|
19539
|
+
const processList = pm2JsonList();
|
|
19540
|
+
const existing = processList.find((item) => item.name === PROCESS_NAME);
|
|
19541
|
+
if (existing?.pm2_env?.status === "online" && typeof existing.pid === "number" && isGatewayReady(existing.pid) && getRunningVersion() === currentVersion) {
|
|
19542
|
+
return { ok: true, action: "already-running" };
|
|
19543
|
+
}
|
|
19544
|
+
if (existing) requirePm2(["delete", PROCESS_NAME], "pm2 delete stale Gateway");
|
|
19545
|
+
pm2StartGateway();
|
|
19546
|
+
writeRunningVersion(currentVersion);
|
|
19547
|
+
savePm2State();
|
|
19548
|
+
return { ok: true, action: existing ? "restarted" : "started" };
|
|
19549
|
+
}
|
|
19550
|
+
var __dirname, PROCESS_NAME, GATEWAY_LOCK_STALE_MS;
|
|
19551
|
+
var init_pm2 = __esm({
|
|
19552
|
+
"src/daemon/pm2.ts"() {
|
|
19553
|
+
"use strict";
|
|
19554
|
+
init_config();
|
|
19555
|
+
__dirname = dirname2(fileURLToPath2(import.meta.url));
|
|
19556
|
+
PROCESS_NAME = "supertask-gateway";
|
|
19557
|
+
GATEWAY_LOCK_STALE_MS = 3e4;
|
|
19130
19558
|
}
|
|
19131
19559
|
});
|
|
19132
19560
|
|
|
19133
19561
|
// src/core/services/task-run.service.ts
|
|
19134
|
-
var
|
|
19562
|
+
var taskRuns3, TaskRunService;
|
|
19135
19563
|
var init_task_run_service = __esm({
|
|
19136
19564
|
"src/core/services/task-run.service.ts"() {
|
|
19137
19565
|
"use strict";
|
|
19138
19566
|
init_db2();
|
|
19139
19567
|
init_drizzle_orm();
|
|
19140
|
-
({ taskRuns:
|
|
19568
|
+
({ taskRuns: taskRuns3 } = schema_exports);
|
|
19141
19569
|
TaskRunService = class {
|
|
19142
19570
|
static async create(data) {
|
|
19143
|
-
const result = await db.insert(
|
|
19571
|
+
const result = await db.insert(taskRuns3).values(data).returning();
|
|
19144
19572
|
return result[0];
|
|
19145
19573
|
}
|
|
19146
19574
|
static async updateSessionId(id, sessionId) {
|
|
19147
|
-
const result = await db.update(
|
|
19575
|
+
const result = await db.update(taskRuns3).set({ sessionId }).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
19148
19576
|
return result[0] || null;
|
|
19149
19577
|
}
|
|
19150
19578
|
static async done(id, log) {
|
|
19151
|
-
const result = await db.update(
|
|
19579
|
+
const result = await db.update(taskRuns3).set({
|
|
19152
19580
|
status: "done",
|
|
19153
19581
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
19154
19582
|
log
|
|
19155
|
-
}).where(eq(
|
|
19583
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
19156
19584
|
return result[0] || null;
|
|
19157
19585
|
}
|
|
19158
19586
|
static async fail(id, log) {
|
|
19159
|
-
const result = await db.update(
|
|
19587
|
+
const result = await db.update(taskRuns3).set({
|
|
19160
19588
|
status: "failed",
|
|
19161
19589
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
19162
19590
|
log
|
|
19163
|
-
}).where(eq(
|
|
19591
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
19164
19592
|
return result[0] || null;
|
|
19165
19593
|
}
|
|
19166
19594
|
static async heartbeat(id) {
|
|
19167
|
-
const result = await db.update(
|
|
19595
|
+
const result = await db.update(taskRuns3).set({ heartbeatAt: Date.now() }).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
19168
19596
|
return result[0] || null;
|
|
19169
19597
|
}
|
|
19170
19598
|
static async updatePid(id, workerPid, childPid) {
|
|
19171
|
-
const result = await db.update(
|
|
19599
|
+
const result = await db.update(taskRuns3).set({
|
|
19172
19600
|
workerPid,
|
|
19173
19601
|
childPid,
|
|
19174
19602
|
lockedAt: Date.now(),
|
|
19175
19603
|
lockedBy: `gateway-${process.pid}`
|
|
19176
|
-
}).where(eq(
|
|
19604
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
19177
19605
|
return result[0] || null;
|
|
19178
19606
|
}
|
|
19179
19607
|
static async getById(id) {
|
|
19180
|
-
const result = await db.select().from(
|
|
19608
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.id, id));
|
|
19181
19609
|
return result[0] || null;
|
|
19182
19610
|
}
|
|
19183
19611
|
static async listByTaskId(taskId) {
|
|
19184
|
-
return await db.select().from(
|
|
19612
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
19185
19613
|
}
|
|
19186
19614
|
static async getLatestByTaskId(taskId) {
|
|
19187
|
-
const result = await db.select().from(
|
|
19615
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id)).limit(1);
|
|
19188
19616
|
return result[0] || null;
|
|
19189
19617
|
}
|
|
19190
19618
|
static async getLatestByTaskIds(taskIds) {
|
|
19191
19619
|
if (taskIds.length === 0) return /* @__PURE__ */ new Map();
|
|
19192
|
-
const latestRuns = await db.select().from(
|
|
19620
|
+
const latestRuns = await db.select().from(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
19193
19621
|
const result = /* @__PURE__ */ new Map();
|
|
19194
19622
|
for (const run of latestRuns) {
|
|
19195
19623
|
if (!result.has(run.taskId)) {
|
|
@@ -19203,22 +19631,23 @@ var init_task_run_service = __esm({
|
|
|
19203
19631
|
const cutoffSec = Math.floor(cutoffMs / 1e3);
|
|
19204
19632
|
const { tasks: tasksTable } = schema_exports;
|
|
19205
19633
|
const result = await db.select({
|
|
19206
|
-
runId:
|
|
19207
|
-
taskId:
|
|
19208
|
-
childPid:
|
|
19634
|
+
runId: taskRuns3.id,
|
|
19635
|
+
taskId: taskRuns3.taskId,
|
|
19636
|
+
childPid: taskRuns3.childPid,
|
|
19209
19637
|
taskRetryCount: tasksTable.retryCount,
|
|
19210
|
-
taskMaxRetries: tasksTable.maxRetries
|
|
19211
|
-
|
|
19638
|
+
taskMaxRetries: tasksTable.maxRetries,
|
|
19639
|
+
taskRetryBackoffMs: tasksTable.retryBackoffMs
|
|
19640
|
+
}).from(taskRuns3).innerJoin(tasksTable, eq(taskRuns3.taskId, tasksTable.id)).where(
|
|
19212
19641
|
and(
|
|
19213
|
-
eq(
|
|
19642
|
+
eq(taskRuns3.status, "running"),
|
|
19214
19643
|
or(
|
|
19215
19644
|
and(
|
|
19216
|
-
sql`${
|
|
19217
|
-
sql`${
|
|
19645
|
+
sql`${taskRuns3.heartbeatAt} IS NULL`,
|
|
19646
|
+
sql`${taskRuns3.startedAt} < ${cutoffSec}`
|
|
19218
19647
|
),
|
|
19219
19648
|
and(
|
|
19220
|
-
sql`${
|
|
19221
|
-
sql`${
|
|
19649
|
+
sql`${taskRuns3.heartbeatAt} IS NOT NULL`,
|
|
19650
|
+
sql`${taskRuns3.heartbeatAt} < ${cutoffMs}`
|
|
19222
19651
|
)
|
|
19223
19652
|
)
|
|
19224
19653
|
)
|
|
@@ -19228,65 +19657,234 @@ var init_task_run_service = __esm({
|
|
|
19228
19657
|
taskId: row.taskId,
|
|
19229
19658
|
childPid: row.childPid,
|
|
19230
19659
|
taskRetryCount: row.taskRetryCount ?? 0,
|
|
19231
|
-
taskMaxRetries: row.taskMaxRetries ?? 3
|
|
19660
|
+
taskMaxRetries: row.taskMaxRetries ?? 3,
|
|
19661
|
+
taskRetryBackoffMs: row.taskRetryBackoffMs ?? 3e4
|
|
19232
19662
|
}));
|
|
19233
19663
|
}
|
|
19234
19664
|
static async getRunningRunByTaskId(taskId) {
|
|
19235
|
-
const result = await db.select().from(
|
|
19665
|
+
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);
|
|
19236
19666
|
return result[0] || null;
|
|
19237
19667
|
}
|
|
19238
19668
|
static async deleteByTaskIds(taskIds) {
|
|
19239
19669
|
if (taskIds.length === 0) return 0;
|
|
19240
|
-
const result = await db.delete(
|
|
19670
|
+
const result = await db.delete(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).returning();
|
|
19241
19671
|
return result.length;
|
|
19242
19672
|
}
|
|
19243
19673
|
static async getAllRunningRuns() {
|
|
19244
|
-
return await db.select().from(
|
|
19674
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.status, "running"));
|
|
19245
19675
|
}
|
|
19246
19676
|
};
|
|
19247
19677
|
}
|
|
19248
19678
|
});
|
|
19249
19679
|
|
|
19680
|
+
// src/gateway/health.ts
|
|
19681
|
+
function initializeGatewayHealth(config) {
|
|
19682
|
+
const now = Date.now();
|
|
19683
|
+
state = {
|
|
19684
|
+
startedAt: now,
|
|
19685
|
+
config,
|
|
19686
|
+
lastActivityAt: {
|
|
19687
|
+
worker: now,
|
|
19688
|
+
scheduler: now,
|
|
19689
|
+
watchdog: now
|
|
19690
|
+
}
|
|
19691
|
+
};
|
|
19692
|
+
}
|
|
19693
|
+
function markGatewayActivity(component) {
|
|
19694
|
+
if (state) state.lastActivityAt[component] = Date.now();
|
|
19695
|
+
}
|
|
19696
|
+
function resetGatewayHealth() {
|
|
19697
|
+
state = null;
|
|
19698
|
+
}
|
|
19699
|
+
function componentStatus(component, enabled, maxAgeMs, now) {
|
|
19700
|
+
const lastActivityAt = state?.lastActivityAt[component] ?? null;
|
|
19701
|
+
const ageMs = lastActivityAt == null ? null : Math.max(0, now - lastActivityAt);
|
|
19702
|
+
return {
|
|
19703
|
+
enabled,
|
|
19704
|
+
healthy: !enabled || ageMs != null && ageMs <= maxAgeMs,
|
|
19705
|
+
lastActivityAt,
|
|
19706
|
+
ageMs,
|
|
19707
|
+
maxAgeMs
|
|
19708
|
+
};
|
|
19709
|
+
}
|
|
19710
|
+
function getGatewayHealth(now = Date.now()) {
|
|
19711
|
+
const worker = componentStatus(
|
|
19712
|
+
"worker",
|
|
19713
|
+
true,
|
|
19714
|
+
Math.max((state?.config.workerPollIntervalMs ?? 1e3) * 5, 5e3),
|
|
19715
|
+
now
|
|
19716
|
+
);
|
|
19717
|
+
const scheduler = componentStatus(
|
|
19718
|
+
"scheduler",
|
|
19719
|
+
state?.config.schedulerEnabled ?? false,
|
|
19720
|
+
Math.max((state?.config.schedulerCheckIntervalMs ?? 1e3) * 5, 5e3),
|
|
19721
|
+
now
|
|
19722
|
+
);
|
|
19723
|
+
const watchdog = componentStatus(
|
|
19724
|
+
"watchdog",
|
|
19725
|
+
true,
|
|
19726
|
+
Math.max((state?.config.watchdogCheckIntervalMs ?? 6e4) * 3, 5e3),
|
|
19727
|
+
now
|
|
19728
|
+
);
|
|
19729
|
+
let lock = { pid: null, heartbeatAt: null, readyAt: null, ageMs: null, healthy: false };
|
|
19730
|
+
try {
|
|
19731
|
+
const row = sqlite.prepare(
|
|
19732
|
+
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
19733
|
+
).get();
|
|
19734
|
+
if (row) {
|
|
19735
|
+
const ageMs = Math.max(0, now - row.heartbeat_at);
|
|
19736
|
+
lock = {
|
|
19737
|
+
pid: row.pid,
|
|
19738
|
+
heartbeatAt: row.heartbeat_at,
|
|
19739
|
+
readyAt: row.ready_at,
|
|
19740
|
+
ageMs,
|
|
19741
|
+
healthy: row.pid === process.pid && row.ready_at != null && ageMs < LOCK_STALE_MS
|
|
19742
|
+
};
|
|
19743
|
+
}
|
|
19744
|
+
} catch {
|
|
19745
|
+
}
|
|
19746
|
+
const healthy = state != null && lock.healthy && worker.healthy && scheduler.healthy && watchdog.healthy;
|
|
19747
|
+
return {
|
|
19748
|
+
status: healthy ? "ok" : "degraded",
|
|
19749
|
+
pid: process.pid,
|
|
19750
|
+
uptimeMs: state ? Math.max(0, now - state.startedAt) : 0,
|
|
19751
|
+
lock,
|
|
19752
|
+
components: { worker, scheduler, watchdog }
|
|
19753
|
+
};
|
|
19754
|
+
}
|
|
19755
|
+
var LOCK_STALE_MS, state;
|
|
19756
|
+
var init_health = __esm({
|
|
19757
|
+
"src/gateway/health.ts"() {
|
|
19758
|
+
"use strict";
|
|
19759
|
+
init_db2();
|
|
19760
|
+
LOCK_STALE_MS = 3e4;
|
|
19761
|
+
state = null;
|
|
19762
|
+
}
|
|
19763
|
+
});
|
|
19764
|
+
|
|
19765
|
+
// src/core/process-control.ts
|
|
19766
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
19767
|
+
import { basename } from "path";
|
|
19768
|
+
function isSafePid(pid) {
|
|
19769
|
+
return Number.isInteger(pid) && pid > 1 && pid !== process.pid;
|
|
19770
|
+
}
|
|
19771
|
+
function inspectUnixProcess(pid) {
|
|
19772
|
+
const result = spawnSync2("ps", ["-o", "pgid=", "-o", "command=", "-p", String(pid)], {
|
|
19773
|
+
encoding: "utf8",
|
|
19774
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
19775
|
+
});
|
|
19776
|
+
if (result.status !== 0) return null;
|
|
19777
|
+
const match2 = result.stdout.trim().match(/^(\d+)\s+(.+)$/s);
|
|
19778
|
+
if (!match2) return null;
|
|
19779
|
+
return { processGroupId: Number(match2[1]), command: match2[2] };
|
|
19780
|
+
}
|
|
19781
|
+
function commandMatches(command, expectedExecutable) {
|
|
19782
|
+
const expectedName = basename(expectedExecutable).trim().toLowerCase();
|
|
19783
|
+
if (!expectedName) return false;
|
|
19784
|
+
return command.toLowerCase().includes(expectedName);
|
|
19785
|
+
}
|
|
19786
|
+
function signalSpawnedProcessTree(pid, signal) {
|
|
19787
|
+
if (!isSafePid(pid)) return false;
|
|
19788
|
+
if (process.platform !== "win32") {
|
|
19789
|
+
try {
|
|
19790
|
+
process.kill(-pid, signal);
|
|
19791
|
+
return true;
|
|
19792
|
+
} catch {
|
|
19793
|
+
}
|
|
19794
|
+
}
|
|
19795
|
+
try {
|
|
19796
|
+
process.kill(pid, signal);
|
|
19797
|
+
return true;
|
|
19798
|
+
} catch {
|
|
19799
|
+
return false;
|
|
19800
|
+
}
|
|
19801
|
+
}
|
|
19802
|
+
function signalRecordedProcessTree(pid, signal, expectedExecutable) {
|
|
19803
|
+
if (!isSafePid(pid)) return false;
|
|
19804
|
+
if (process.platform === "win32") {
|
|
19805
|
+
const list = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
|
|
19806
|
+
encoding: "utf8",
|
|
19807
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
19808
|
+
});
|
|
19809
|
+
if (list.status !== 0 || !commandMatches(list.stdout, expectedExecutable)) return false;
|
|
19810
|
+
const args = ["/PID", String(pid), "/T"];
|
|
19811
|
+
if (signal === "SIGKILL") args.push("/F");
|
|
19812
|
+
return spawnSync2("taskkill", args, { stdio: "ignore" }).status === 0;
|
|
19813
|
+
}
|
|
19814
|
+
const info = inspectUnixProcess(pid);
|
|
19815
|
+
if (!info || !commandMatches(info.command, expectedExecutable)) return false;
|
|
19816
|
+
try {
|
|
19817
|
+
if (info.processGroupId === pid) process.kill(-pid, signal);
|
|
19818
|
+
else process.kill(pid, signal);
|
|
19819
|
+
return true;
|
|
19820
|
+
} catch {
|
|
19821
|
+
return false;
|
|
19822
|
+
}
|
|
19823
|
+
}
|
|
19824
|
+
var init_process_control = __esm({
|
|
19825
|
+
"src/core/process-control.ts"() {
|
|
19826
|
+
"use strict";
|
|
19827
|
+
}
|
|
19828
|
+
});
|
|
19829
|
+
|
|
19250
19830
|
// src/worker/index.ts
|
|
19251
19831
|
import { spawn } from "child_process";
|
|
19252
|
-
var WorkerEngine;
|
|
19832
|
+
var DEFAULT_MAX_OUTPUT_CHARS, FORBIDDEN_AGENT, WorkerEngine;
|
|
19253
19833
|
var init_worker = __esm({
|
|
19254
19834
|
"src/worker/index.ts"() {
|
|
19255
19835
|
"use strict";
|
|
19256
19836
|
init_task_service();
|
|
19257
19837
|
init_task_run_service();
|
|
19838
|
+
init_health();
|
|
19839
|
+
init_process_control();
|
|
19840
|
+
DEFAULT_MAX_OUTPUT_CHARS = 64 * 1024;
|
|
19841
|
+
FORBIDDEN_AGENT = "supertask-runner";
|
|
19258
19842
|
WorkerEngine = class {
|
|
19259
19843
|
activeBatchIds = /* @__PURE__ */ new Set();
|
|
19260
19844
|
runningTasks = /* @__PURE__ */ new Map();
|
|
19261
19845
|
stopped = false;
|
|
19262
19846
|
pollTimer = null;
|
|
19263
19847
|
heartbeatTimer = null;
|
|
19848
|
+
pollCyclePromise = null;
|
|
19264
19849
|
cfg;
|
|
19265
|
-
|
|
19850
|
+
opencodeBin;
|
|
19851
|
+
maxOutputChars;
|
|
19852
|
+
constructor(cfg, options = {}) {
|
|
19266
19853
|
this.cfg = cfg.worker;
|
|
19854
|
+
this.opencodeBin = options.opencodeBin ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
19855
|
+
this.maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
19267
19856
|
}
|
|
19268
19857
|
start() {
|
|
19269
19858
|
this.stopped = false;
|
|
19859
|
+
markGatewayActivity("worker");
|
|
19270
19860
|
this.poll();
|
|
19271
19861
|
this.heartbeatTimer = setInterval(() => this.updateHeartbeats(), this.cfg.heartbeatIntervalMs);
|
|
19272
19862
|
}
|
|
19273
|
-
stop() {
|
|
19863
|
+
async stop(gracePeriodMs = 0) {
|
|
19274
19864
|
this.stopped = true;
|
|
19275
19865
|
if (this.pollTimer) {
|
|
19276
19866
|
clearTimeout(this.pollTimer);
|
|
19277
19867
|
this.pollTimer = null;
|
|
19278
19868
|
}
|
|
19869
|
+
if (this.pollCyclePromise) await this.pollCyclePromise;
|
|
19870
|
+
if (gracePeriodMs > 0 && this.runningTasks.size > 0) {
|
|
19871
|
+
const deadline = Date.now() + gracePeriodMs;
|
|
19872
|
+
while (this.runningTasks.size > 0 && Date.now() < deadline) {
|
|
19873
|
+
await Bun.sleep(Math.min(50, deadline - Date.now()));
|
|
19874
|
+
}
|
|
19875
|
+
}
|
|
19279
19876
|
if (this.heartbeatTimer) {
|
|
19280
19877
|
clearInterval(this.heartbeatTimer);
|
|
19281
19878
|
this.heartbeatTimer = null;
|
|
19282
19879
|
}
|
|
19880
|
+
const interruptedTaskIds = [...this.runningTasks.keys()];
|
|
19283
19881
|
const killPromises = [];
|
|
19284
|
-
for (const
|
|
19882
|
+
for (const entry of this.runningTasks.values()) {
|
|
19285
19883
|
entry.shutdown = true;
|
|
19286
19884
|
killPromises.push(this.killEntry(entry));
|
|
19287
19885
|
}
|
|
19288
|
-
|
|
19289
|
-
|
|
19886
|
+
await Promise.allSettled(killPromises);
|
|
19887
|
+
return interruptedTaskIds;
|
|
19290
19888
|
}
|
|
19291
19889
|
getRunningTaskIds() {
|
|
19292
19890
|
return [...this.runningTasks.keys()];
|
|
@@ -19296,136 +19894,244 @@ var init_worker = __esm({
|
|
|
19296
19894
|
}
|
|
19297
19895
|
poll() {
|
|
19298
19896
|
if (this.stopped) return;
|
|
19299
|
-
|
|
19897
|
+
markGatewayActivity("worker");
|
|
19898
|
+
this.pollCyclePromise = this.tryDispatch().finally(() => {
|
|
19899
|
+
this.pollCyclePromise = null;
|
|
19300
19900
|
if (this.stopped) return;
|
|
19301
19901
|
this.pollTimer = setTimeout(() => this.poll(), this.cfg.pollIntervalMs);
|
|
19302
19902
|
});
|
|
19303
19903
|
}
|
|
19304
19904
|
async tryDispatch() {
|
|
19905
|
+
await this.reconcileCancelledTasks();
|
|
19305
19906
|
while (!this.stopped && this.runningTasks.size < this.cfg.maxConcurrency) {
|
|
19907
|
+
let task;
|
|
19908
|
+
try {
|
|
19909
|
+
task = await TaskService.next({ excludedBatchIds: [...this.activeBatchIds] });
|
|
19910
|
+
} catch (err) {
|
|
19911
|
+
this.logError("task claim failed", err);
|
|
19912
|
+
break;
|
|
19913
|
+
}
|
|
19914
|
+
if (!task) break;
|
|
19915
|
+
if (this.stopped) break;
|
|
19916
|
+
if (!await TaskService.start(task.id)) continue;
|
|
19917
|
+
if (task.batchId) this.activeBatchIds.add(task.batchId);
|
|
19918
|
+
if (this.stopped) {
|
|
19919
|
+
await TaskService.resetRunningToPending([task.id]);
|
|
19920
|
+
this.releaseBatch(task);
|
|
19921
|
+
break;
|
|
19922
|
+
}
|
|
19306
19923
|
try {
|
|
19307
|
-
const excludedBatchIds = [...this.activeBatchIds];
|
|
19308
|
-
const task = await TaskService.next({ excludedBatchIds });
|
|
19309
|
-
if (!task) break;
|
|
19310
|
-
if (!await TaskService.start(task.id)) continue;
|
|
19311
|
-
if (task.batchId) {
|
|
19312
|
-
this.activeBatchIds.add(task.batchId);
|
|
19313
|
-
}
|
|
19314
19924
|
const run = await TaskRunService.create({
|
|
19315
19925
|
taskId: task.id,
|
|
19316
19926
|
model: this.resolveModel(task.model),
|
|
19317
19927
|
status: "running"
|
|
19318
19928
|
});
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
|
|
19322
|
-
|
|
19929
|
+
if (this.stopped) {
|
|
19930
|
+
await TaskRunService.fail(run.id, "Gateway shutdown before spawn");
|
|
19931
|
+
await TaskService.resetRunningToPending([task.id]);
|
|
19932
|
+
this.releaseBatch(task);
|
|
19933
|
+
break;
|
|
19323
19934
|
}
|
|
19324
|
-
|
|
19325
|
-
|
|
19326
|
-
|
|
19327
|
-
|
|
19328
|
-
|
|
19329
|
-
|
|
19330
|
-
|
|
19331
|
-
|
|
19332
|
-
|
|
19333
|
-
|
|
19334
|
-
|
|
19335
|
-
|
|
19336
|
-
|
|
19337
|
-
|
|
19338
|
-
|
|
19339
|
-
}
|
|
19340
|
-
|
|
19341
|
-
|
|
19342
|
-
|
|
19343
|
-
|
|
19344
|
-
|
|
19345
|
-
|
|
19346
|
-
|
|
19347
|
-
|
|
19348
|
-
|
|
19349
|
-
|
|
19350
|
-
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
|
|
19355
|
-
|
|
19356
|
-
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
19364
|
-
|
|
19365
|
-
|
|
19366
|
-
|
|
19367
|
-
|
|
19368
|
-
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
}
|
|
19375
|
-
console.error(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "error", msg: "task spawn error", taskId: task.id, error: err.message }));
|
|
19935
|
+
if (task.agent === FORBIDDEN_AGENT) {
|
|
19936
|
+
const message = `\u7981\u6B62\u6267\u884C\u9012\u5F52 Agent: ${FORBIDDEN_AGENT}`;
|
|
19937
|
+
await TaskRunService.fail(run.id, message);
|
|
19938
|
+
await TaskService.fail(task.id, message, {}, { setDeadLetter: true });
|
|
19939
|
+
this.releaseBatch(task);
|
|
19940
|
+
continue;
|
|
19941
|
+
}
|
|
19942
|
+
this.spawnTask(task, run.id);
|
|
19943
|
+
} catch (err) {
|
|
19944
|
+
this.releaseBatch(task);
|
|
19945
|
+
const message = `Worker \u542F\u52A8\u4EFB\u52A1\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`;
|
|
19946
|
+
try {
|
|
19947
|
+
await TaskService.fail(task.id, message);
|
|
19948
|
+
} catch (failErr) {
|
|
19949
|
+
this.logError("failed to compensate task startup", failErr, task.id);
|
|
19950
|
+
}
|
|
19951
|
+
this.logError("task dispatch failed", err, task.id);
|
|
19952
|
+
}
|
|
19953
|
+
}
|
|
19954
|
+
}
|
|
19955
|
+
spawnTask(task, runId) {
|
|
19956
|
+
const model = this.resolveModel(task.model);
|
|
19957
|
+
const args = ["run", "--agent", task.agent, "--format", "json"];
|
|
19958
|
+
if (model) args.push("-m", model);
|
|
19959
|
+
args.push(task.prompt);
|
|
19960
|
+
const child = spawn(this.opencodeBin, args, {
|
|
19961
|
+
cwd: task.cwd || process.cwd(),
|
|
19962
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
19963
|
+
detached: process.platform !== "win32"
|
|
19964
|
+
});
|
|
19965
|
+
const entry = {
|
|
19966
|
+
task,
|
|
19967
|
+
runId,
|
|
19968
|
+
child,
|
|
19969
|
+
output: "",
|
|
19970
|
+
sessionId: null,
|
|
19971
|
+
timeoutTimer: null,
|
|
19972
|
+
shutdown: false,
|
|
19973
|
+
settled: false
|
|
19974
|
+
};
|
|
19975
|
+
this.runningTasks.set(task.id, entry);
|
|
19976
|
+
const handleData = (data) => {
|
|
19977
|
+
const text2 = data.toString();
|
|
19978
|
+
entry.output = (entry.output + text2).slice(-this.maxOutputChars);
|
|
19979
|
+
process.stdout.write(text2);
|
|
19980
|
+
const match2 = entry.output.match(/"sessionID"\s*:\s*"(ses_[^"]+)"/);
|
|
19981
|
+
if (match2?.[1] && match2[1] !== entry.sessionId) {
|
|
19982
|
+
entry.sessionId = match2[1];
|
|
19983
|
+
TaskRunService.updateSessionId(runId, match2[1]).catch((err) => {
|
|
19984
|
+
this.logError("sessionId update failed", err, task.id);
|
|
19376
19985
|
});
|
|
19986
|
+
}
|
|
19987
|
+
};
|
|
19988
|
+
child.stdout?.on("data", handleData);
|
|
19989
|
+
child.stderr?.on("data", handleData);
|
|
19990
|
+
child.once("error", (err) => {
|
|
19991
|
+
void this.finishEntry(entry, null, `\u65E0\u6CD5\u542F\u52A8 opencode\uFF1A${err.message}`);
|
|
19992
|
+
});
|
|
19993
|
+
child.once("close", (code, signal) => {
|
|
19994
|
+
const failure = code === 0 ? void 0 : `opencode \u9000\u51FA\u7801 ${code ?? "null"}${signal ? `\uFF0C\u4FE1\u53F7 ${signal}` : ""}`;
|
|
19995
|
+
void this.finishEntry(entry, code, failure);
|
|
19996
|
+
});
|
|
19997
|
+
const timeoutMs = task.timeoutMs ?? this.cfg.taskTimeoutMs;
|
|
19998
|
+
if (timeoutMs > 0) {
|
|
19999
|
+
entry.timeoutTimer = setTimeout(() => {
|
|
20000
|
+
this.signalEntry(entry, "SIGKILL");
|
|
20001
|
+
void this.finishEntry(entry, null, `\u4EFB\u52A1\u8D85\u65F6\uFF08${timeoutMs}ms\uFF09`);
|
|
20002
|
+
}, timeoutMs);
|
|
20003
|
+
}
|
|
20004
|
+
TaskRunService.updatePid(runId, process.pid, child.pid ?? 0).catch((err) => {
|
|
20005
|
+
this.signalEntry(entry, "SIGKILL");
|
|
20006
|
+
void this.finishEntry(entry, null, `\u8BB0\u5F55 Worker PID \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
20007
|
+
});
|
|
20008
|
+
}
|
|
20009
|
+
async finishEntry(entry, code, failure) {
|
|
20010
|
+
if (entry.settled) return;
|
|
20011
|
+
entry.settled = true;
|
|
20012
|
+
if (entry.timeoutTimer) {
|
|
20013
|
+
clearTimeout(entry.timeoutTimer);
|
|
20014
|
+
entry.timeoutTimer = null;
|
|
20015
|
+
}
|
|
20016
|
+
try {
|
|
20017
|
+
if (entry.shutdown) return;
|
|
20018
|
+
const currentRun = await TaskRunService.getById(entry.runId);
|
|
20019
|
+
if (!currentRun || currentRun.status !== "running") return;
|
|
20020
|
+
const output = entry.output.trim();
|
|
20021
|
+
const log = failure ? `${failure}${output ? `
|
|
20022
|
+
${output}` : ""}` : output;
|
|
20023
|
+
if (code === 0 && !failure) {
|
|
20024
|
+
const completed = await TaskService.done(entry.task.id, log);
|
|
20025
|
+
if (completed) {
|
|
20026
|
+
await TaskRunService.done(entry.runId, log);
|
|
20027
|
+
console.log(JSON.stringify({
|
|
20028
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20029
|
+
level: "info",
|
|
20030
|
+
msg: "task done",
|
|
20031
|
+
taskId: entry.task.id
|
|
20032
|
+
}));
|
|
20033
|
+
return;
|
|
20034
|
+
}
|
|
20035
|
+
await TaskRunService.fail(entry.runId, "\u4EFB\u52A1\u72B6\u6001\u5DF2\u88AB\u5176\u4ED6\u64CD\u4F5C\u6539\u53D8");
|
|
20036
|
+
return;
|
|
20037
|
+
}
|
|
20038
|
+
await TaskRunService.fail(entry.runId, log);
|
|
20039
|
+
const failed = await TaskService.fail(entry.task.id, log);
|
|
20040
|
+
if (!failed) {
|
|
20041
|
+
this.logError("task failure state transition rejected", failure ?? "unknown failure", entry.task.id);
|
|
20042
|
+
}
|
|
20043
|
+
console.error(JSON.stringify({
|
|
20044
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20045
|
+
level: "error",
|
|
20046
|
+
msg: "task failed",
|
|
20047
|
+
taskId: entry.task.id,
|
|
20048
|
+
error: failure
|
|
20049
|
+
}));
|
|
20050
|
+
} finally {
|
|
20051
|
+
this.runningTasks.delete(entry.task.id);
|
|
20052
|
+
this.releaseBatch(entry.task);
|
|
20053
|
+
}
|
|
20054
|
+
}
|
|
20055
|
+
async reconcileCancelledTasks() {
|
|
20056
|
+
for (const entry of [...this.runningTasks.values()]) {
|
|
20057
|
+
try {
|
|
20058
|
+
const task = await TaskService.getById(entry.task.id);
|
|
20059
|
+
if (task?.status === "cancelled") await this.cancelEntry(entry);
|
|
19377
20060
|
} catch (err) {
|
|
19378
|
-
|
|
19379
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19380
|
-
level: "error",
|
|
19381
|
-
msg: "tryDispatch iteration failed",
|
|
19382
|
-
error: err instanceof Error ? err.message : String(err)
|
|
19383
|
-
}));
|
|
19384
|
-
break;
|
|
20061
|
+
this.logError("cancel reconciliation failed", err, entry.task.id);
|
|
19385
20062
|
}
|
|
19386
20063
|
}
|
|
19387
20064
|
}
|
|
20065
|
+
async cancelEntry(entry) {
|
|
20066
|
+
if (entry.settled) return;
|
|
20067
|
+
entry.settled = true;
|
|
20068
|
+
if (entry.timeoutTimer) {
|
|
20069
|
+
clearTimeout(entry.timeoutTimer);
|
|
20070
|
+
entry.timeoutTimer = null;
|
|
20071
|
+
}
|
|
20072
|
+
try {
|
|
20073
|
+
await this.killEntry(entry);
|
|
20074
|
+
const output = entry.output.trim();
|
|
20075
|
+
const log = `\u4EFB\u52A1\u5DF2\u53D6\u6D88${output ? `
|
|
20076
|
+
${output}` : ""}`;
|
|
20077
|
+
await TaskRunService.fail(entry.runId, log);
|
|
20078
|
+
console.log(JSON.stringify({
|
|
20079
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20080
|
+
level: "info",
|
|
20081
|
+
msg: "running task cancelled",
|
|
20082
|
+
taskId: entry.task.id
|
|
20083
|
+
}));
|
|
20084
|
+
} finally {
|
|
20085
|
+
this.runningTasks.delete(entry.task.id);
|
|
20086
|
+
this.releaseBatch(entry.task);
|
|
20087
|
+
}
|
|
20088
|
+
}
|
|
19388
20089
|
async updateHeartbeats() {
|
|
19389
|
-
for (const
|
|
20090
|
+
for (const entry of this.runningTasks.values()) {
|
|
19390
20091
|
try {
|
|
19391
20092
|
await TaskRunService.heartbeat(entry.runId);
|
|
19392
|
-
} catch {
|
|
20093
|
+
} catch (err) {
|
|
20094
|
+
this.logError("heartbeat update failed", err, entry.task.id);
|
|
19393
20095
|
}
|
|
19394
20096
|
}
|
|
19395
20097
|
}
|
|
19396
20098
|
killEntry(entry) {
|
|
19397
|
-
if (entry.child.exitCode !== null) {
|
|
20099
|
+
if (entry.child.exitCode !== null || entry.child.signalCode !== null) {
|
|
19398
20100
|
return Promise.resolve();
|
|
19399
20101
|
}
|
|
19400
20102
|
return new Promise((resolve) => {
|
|
19401
20103
|
const timeout = setTimeout(() => {
|
|
19402
|
-
|
|
19403
|
-
if (entry.child.pid) process.kill(entry.child.pid, "SIGKILL");
|
|
19404
|
-
} catch {
|
|
19405
|
-
}
|
|
20104
|
+
this.signalEntry(entry, "SIGKILL");
|
|
19406
20105
|
resolve();
|
|
19407
20106
|
}, 5e3);
|
|
19408
|
-
entry.child.
|
|
20107
|
+
entry.child.once("close", () => {
|
|
19409
20108
|
clearTimeout(timeout);
|
|
19410
20109
|
resolve();
|
|
19411
20110
|
});
|
|
19412
|
-
|
|
19413
|
-
if (entry.child.pid) {
|
|
19414
|
-
entry.child.kill("SIGTERM");
|
|
19415
|
-
} else {
|
|
19416
|
-
clearTimeout(timeout);
|
|
19417
|
-
resolve();
|
|
19418
|
-
}
|
|
19419
|
-
} catch {
|
|
19420
|
-
clearTimeout(timeout);
|
|
19421
|
-
resolve();
|
|
19422
|
-
}
|
|
20111
|
+
this.signalEntry(entry, "SIGTERM");
|
|
19423
20112
|
});
|
|
19424
20113
|
}
|
|
20114
|
+
signalEntry(entry, signal) {
|
|
20115
|
+
const pid = entry.child.pid;
|
|
20116
|
+
if (!pid) return;
|
|
20117
|
+
signalSpawnedProcessTree(pid, signal);
|
|
20118
|
+
}
|
|
20119
|
+
releaseBatch(task) {
|
|
20120
|
+
if (task.batchId) this.activeBatchIds.delete(task.batchId);
|
|
20121
|
+
}
|
|
19425
20122
|
resolveModel(taskModel) {
|
|
19426
20123
|
if (!taskModel || taskModel === "default") return null;
|
|
19427
20124
|
return taskModel;
|
|
19428
20125
|
}
|
|
20126
|
+
logError(message, error, taskId) {
|
|
20127
|
+
console.error(JSON.stringify({
|
|
20128
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20129
|
+
level: "error",
|
|
20130
|
+
msg: message,
|
|
20131
|
+
taskId,
|
|
20132
|
+
error: error instanceof Error ? error.message : String(error)
|
|
20133
|
+
}));
|
|
20134
|
+
}
|
|
19429
20135
|
};
|
|
19430
20136
|
}
|
|
19431
20137
|
});
|
|
@@ -19437,15 +20143,23 @@ async function checkHeartbeats(heartbeatTimeoutMs) {
|
|
|
19437
20143
|
for (const run of staleRuns) {
|
|
19438
20144
|
try {
|
|
19439
20145
|
if (run.childPid != null && run.childPid > 0) {
|
|
19440
|
-
|
|
19441
|
-
|
|
19442
|
-
|
|
20146
|
+
const expectedExecutable = process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
20147
|
+
const killed = signalRecordedProcessTree(run.childPid, "SIGKILL", expectedExecutable);
|
|
20148
|
+
if (!killed) {
|
|
20149
|
+
console.warn(JSON.stringify({
|
|
20150
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20151
|
+
level: "warn",
|
|
20152
|
+
msg: "stale child process was not killed because identity validation failed",
|
|
20153
|
+
taskId: run.taskId,
|
|
20154
|
+
runId: run.runId,
|
|
20155
|
+
childPid: run.childPid
|
|
20156
|
+
}));
|
|
19443
20157
|
}
|
|
19444
20158
|
}
|
|
19445
20159
|
await TaskRunService.fail(run.runId, `\u5FC3\u8DF3\u8D85\u65F6 (${heartbeatTimeoutMs / 1e3}s)\uFF0CWatchdog kill`);
|
|
19446
20160
|
const newRetryCount = run.taskRetryCount + 1;
|
|
19447
20161
|
const maxRetries = run.taskMaxRetries;
|
|
19448
|
-
if (newRetryCount
|
|
20162
|
+
if (newRetryCount > maxRetries) {
|
|
19449
20163
|
await TaskService.markDeadLetter(run.taskId, newRetryCount);
|
|
19450
20164
|
console.log(JSON.stringify({
|
|
19451
20165
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -19457,7 +20171,7 @@ async function checkHeartbeats(heartbeatTimeoutMs) {
|
|
|
19457
20171
|
maxRetries
|
|
19458
20172
|
}));
|
|
19459
20173
|
} else {
|
|
19460
|
-
const backoffMs = computeBackoff(newRetryCount);
|
|
20174
|
+
const backoffMs = computeBackoff(newRetryCount, run.taskRetryBackoffMs);
|
|
19461
20175
|
const retryAfter = Date.now() + backoffMs;
|
|
19462
20176
|
await TaskService.markPendingForRetry(run.taskId, retryAfter, newRetryCount);
|
|
19463
20177
|
console.log(JSON.stringify({
|
|
@@ -19488,6 +20202,7 @@ var init_heartbeat = __esm({
|
|
|
19488
20202
|
init_task_run_service();
|
|
19489
20203
|
init_task_service();
|
|
19490
20204
|
init_backoff();
|
|
20205
|
+
init_process_control();
|
|
19491
20206
|
}
|
|
19492
20207
|
});
|
|
19493
20208
|
|
|
@@ -19532,6 +20247,7 @@ var init_watchdog = __esm({
|
|
|
19532
20247
|
"use strict";
|
|
19533
20248
|
init_heartbeat();
|
|
19534
20249
|
init_cleanup();
|
|
20250
|
+
init_health();
|
|
19535
20251
|
Watchdog = class {
|
|
19536
20252
|
constructor(cfg) {
|
|
19537
20253
|
this.cfg = cfg;
|
|
@@ -19542,13 +20258,14 @@ var init_watchdog = __esm({
|
|
|
19542
20258
|
cleanupTimer = null;
|
|
19543
20259
|
start() {
|
|
19544
20260
|
this.stopped = false;
|
|
20261
|
+
markGatewayActivity("watchdog");
|
|
19545
20262
|
this.heartbeatTimer = setInterval(
|
|
19546
20263
|
() => this.runHeartbeatCheck(),
|
|
19547
|
-
this.cfg.watchdog.
|
|
20264
|
+
this.cfg.watchdog.checkIntervalMs
|
|
19548
20265
|
);
|
|
19549
20266
|
this.cleanupTimer = setInterval(
|
|
19550
20267
|
() => this.runCleanup(),
|
|
19551
|
-
this.cfg.watchdog.cleanupIntervalMs
|
|
20268
|
+
this.cfg.watchdog.cleanupIntervalMs
|
|
19552
20269
|
);
|
|
19553
20270
|
}
|
|
19554
20271
|
stop() {
|
|
@@ -19564,6 +20281,7 @@ var init_watchdog = __esm({
|
|
|
19564
20281
|
}
|
|
19565
20282
|
async runHeartbeatCheck() {
|
|
19566
20283
|
if (this.stopped) return;
|
|
20284
|
+
markGatewayActivity("watchdog");
|
|
19567
20285
|
try {
|
|
19568
20286
|
await checkHeartbeats(this.cfg.watchdog.heartbeatTimeoutMs);
|
|
19569
20287
|
} catch (err) {
|
|
@@ -19596,7 +20314,7 @@ var init_watchdog = __esm({
|
|
|
19596
20314
|
async function cloneTaskFromTemplate(templateId) {
|
|
19597
20315
|
const rows = await db.select().from(taskTemplates3).where(eq(taskTemplates3.id, templateId)).limit(1);
|
|
19598
20316
|
const tmpl = rows[0];
|
|
19599
|
-
if (!tmpl) return null;
|
|
20317
|
+
if (!tmpl || !tmpl.enabled) return null;
|
|
19600
20318
|
const activeCount = await db.select({ count: sql`count(*)` }).from(schema_exports.tasks).where(
|
|
19601
20319
|
and(
|
|
19602
20320
|
eq(schema_exports.tasks.templateId, templateId),
|
|
@@ -19605,7 +20323,8 @@ async function cloneTaskFromTemplate(templateId) {
|
|
|
19605
20323
|
).then((r) => r[0].count);
|
|
19606
20324
|
if (activeCount >= (tmpl.maxInstances ?? 1)) return null;
|
|
19607
20325
|
const nowMs = Date.now();
|
|
19608
|
-
const
|
|
20326
|
+
const isDelayed = tmpl.scheduleType === "delayed";
|
|
20327
|
+
const nextRunAt = isDelayed ? null : TaskTemplateService.calculateNextRunAt(
|
|
19609
20328
|
tmpl.scheduleType,
|
|
19610
20329
|
tmpl,
|
|
19611
20330
|
nowMs
|
|
@@ -19619,13 +20338,17 @@ async function cloneTaskFromTemplate(templateId) {
|
|
|
19619
20338
|
category: tmpl.category ?? "general",
|
|
19620
20339
|
importance: tmpl.importance ?? 3,
|
|
19621
20340
|
urgency: tmpl.urgency ?? 3,
|
|
20341
|
+
batchId: tmpl.batchId,
|
|
19622
20342
|
maxRetries: tmpl.maxRetries ?? 3,
|
|
20343
|
+
retryBackoffMs: tmpl.retryBackoffMs ?? 3e4,
|
|
20344
|
+
timeoutMs: tmpl.timeoutMs,
|
|
19623
20345
|
templateId: tmpl.id,
|
|
19624
20346
|
scheduledAt: nowMs
|
|
19625
20347
|
});
|
|
19626
20348
|
await db.update(taskTemplates3).set({
|
|
19627
20349
|
lastRunAt: nowMs,
|
|
19628
20350
|
nextRunAt,
|
|
20351
|
+
enabled: isDelayed ? false : tmpl.enabled,
|
|
19629
20352
|
updatedAt: nowMs
|
|
19630
20353
|
}).where(eq(taskTemplates3.id, templateId));
|
|
19631
20354
|
return task;
|
|
@@ -19638,7 +20361,7 @@ async function getDueTemplates() {
|
|
|
19638
20361
|
sql`${taskTemplates3.nextRunAt} IS NOT NULL`,
|
|
19639
20362
|
sql`${taskTemplates3.nextRunAt} <= ${nowMs}`
|
|
19640
20363
|
)
|
|
19641
|
-
);
|
|
20364
|
+
).orderBy(asc(taskTemplates3.nextRunAt), asc(taskTemplates3.id));
|
|
19642
20365
|
}
|
|
19643
20366
|
async function initializeNextRunAt(templateId) {
|
|
19644
20367
|
const rows = await db.select().from(taskTemplates3).where(eq(taskTemplates3.id, templateId)).limit(1);
|
|
@@ -19672,6 +20395,7 @@ var init_scheduler = __esm({
|
|
|
19672
20395
|
init_job_templates();
|
|
19673
20396
|
init_db2();
|
|
19674
20397
|
init_drizzle_orm();
|
|
20398
|
+
init_health();
|
|
19675
20399
|
Scheduler = class {
|
|
19676
20400
|
constructor(cfg) {
|
|
19677
20401
|
this.cfg = cfg;
|
|
@@ -19683,6 +20407,7 @@ var init_scheduler = __esm({
|
|
|
19683
20407
|
async start() {
|
|
19684
20408
|
if (!this.cfg.scheduler.enabled) return;
|
|
19685
20409
|
this.stopped = false;
|
|
20410
|
+
markGatewayActivity("scheduler");
|
|
19686
20411
|
await this.initializeTemplates();
|
|
19687
20412
|
this.timer = setInterval(() => this.tick(), this.cfg.scheduler.checkIntervalMs);
|
|
19688
20413
|
}
|
|
@@ -19695,6 +20420,7 @@ var init_scheduler = __esm({
|
|
|
19695
20420
|
}
|
|
19696
20421
|
async tick() {
|
|
19697
20422
|
if (this.stopped || this.ticking) return;
|
|
20423
|
+
markGatewayActivity("scheduler");
|
|
19698
20424
|
this.ticking = true;
|
|
19699
20425
|
try {
|
|
19700
20426
|
const dueTemplates = await getDueTemplates();
|
|
@@ -19749,13 +20475,13 @@ var init_compose = __esm({
|
|
|
19749
20475
|
"use strict";
|
|
19750
20476
|
compose = (middleware, onError, onNotFound) => {
|
|
19751
20477
|
return (context, next) => {
|
|
19752
|
-
let
|
|
20478
|
+
let index2 = -1;
|
|
19753
20479
|
return dispatch(0);
|
|
19754
20480
|
async function dispatch(i) {
|
|
19755
|
-
if (i <=
|
|
20481
|
+
if (i <= index2) {
|
|
19756
20482
|
throw new Error("next() called multiple times");
|
|
19757
20483
|
}
|
|
19758
|
-
|
|
20484
|
+
index2 = i;
|
|
19759
20485
|
let res;
|
|
19760
20486
|
let isError = false;
|
|
19761
20487
|
let handler;
|
|
@@ -19870,8 +20596,8 @@ var init_body = __esm({
|
|
|
19870
20596
|
handleParsingNestedValues = (form, key, value) => {
|
|
19871
20597
|
let nestedForm = form;
|
|
19872
20598
|
const keys = key.split(".");
|
|
19873
|
-
keys.forEach((key2,
|
|
19874
|
-
if (
|
|
20599
|
+
keys.forEach((key2, index2) => {
|
|
20600
|
+
if (index2 === keys.length - 1) {
|
|
19875
20601
|
nestedForm[key2] = value;
|
|
19876
20602
|
} else {
|
|
19877
20603
|
if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
|
|
@@ -19903,8 +20629,8 @@ var init_url = __esm({
|
|
|
19903
20629
|
};
|
|
19904
20630
|
extractGroupsFromPath = (path) => {
|
|
19905
20631
|
const groups = [];
|
|
19906
|
-
path = path.replace(/\{[^}]+\}/g, (match2,
|
|
19907
|
-
const mark = `@${
|
|
20632
|
+
path = path.replace(/\{[^}]+\}/g, (match2, index2) => {
|
|
20633
|
+
const mark = `@${index2}`;
|
|
19908
20634
|
groups.push([mark, match2]);
|
|
19909
20635
|
return mark;
|
|
19910
20636
|
});
|
|
@@ -20423,10 +21149,10 @@ var init_html = __esm({
|
|
|
20423
21149
|
return;
|
|
20424
21150
|
}
|
|
20425
21151
|
let escape;
|
|
20426
|
-
let
|
|
21152
|
+
let index2;
|
|
20427
21153
|
let lastIndex = 0;
|
|
20428
|
-
for (
|
|
20429
|
-
switch (str.charCodeAt(
|
|
21154
|
+
for (index2 = match2; index2 < str.length; index2++) {
|
|
21155
|
+
switch (str.charCodeAt(index2)) {
|
|
20430
21156
|
case 34:
|
|
20431
21157
|
escape = """;
|
|
20432
21158
|
break;
|
|
@@ -20445,10 +21171,10 @@ var init_html = __esm({
|
|
|
20445
21171
|
default:
|
|
20446
21172
|
continue;
|
|
20447
21173
|
}
|
|
20448
|
-
buffer[0] += str.substring(lastIndex,
|
|
20449
|
-
lastIndex =
|
|
21174
|
+
buffer[0] += str.substring(lastIndex, index2) + escape;
|
|
21175
|
+
lastIndex = index2 + 1;
|
|
20450
21176
|
}
|
|
20451
|
-
buffer[0] += str.substring(lastIndex,
|
|
21177
|
+
buffer[0] += str.substring(lastIndex, index2);
|
|
20452
21178
|
};
|
|
20453
21179
|
resolveCallbackSync = (str) => {
|
|
20454
21180
|
const callbacks = str.callbacks;
|
|
@@ -21324,8 +22050,8 @@ function match(method, path) {
|
|
|
21324
22050
|
if (!match3) {
|
|
21325
22051
|
return [[], emptyParam];
|
|
21326
22052
|
}
|
|
21327
|
-
const
|
|
21328
|
-
return [matcher[1][
|
|
22053
|
+
const index2 = match3.indexOf("", 1);
|
|
22054
|
+
return [matcher[1][index2], match3];
|
|
21329
22055
|
});
|
|
21330
22056
|
this.match = match2;
|
|
21331
22057
|
return match2(method, path);
|
|
@@ -21372,7 +22098,7 @@ var init_node = __esm({
|
|
|
21372
22098
|
#index;
|
|
21373
22099
|
#varIndex;
|
|
21374
22100
|
#children = /* @__PURE__ */ Object.create(null);
|
|
21375
|
-
insert(tokens,
|
|
22101
|
+
insert(tokens, index2, paramMap, context, pathErrorCheckOnly) {
|
|
21376
22102
|
if (tokens.length === 0) {
|
|
21377
22103
|
if (this.#index !== void 0) {
|
|
21378
22104
|
throw PATH_ERROR;
|
|
@@ -21380,7 +22106,7 @@ var init_node = __esm({
|
|
|
21380
22106
|
if (pathErrorCheckOnly) {
|
|
21381
22107
|
return;
|
|
21382
22108
|
}
|
|
21383
|
-
this.#index =
|
|
22109
|
+
this.#index = index2;
|
|
21384
22110
|
return;
|
|
21385
22111
|
}
|
|
21386
22112
|
const [token, ...restTokens] = tokens;
|
|
@@ -21430,7 +22156,7 @@ var init_node = __esm({
|
|
|
21430
22156
|
node = this.#children[token] = new _Node();
|
|
21431
22157
|
}
|
|
21432
22158
|
}
|
|
21433
|
-
node.insert(restTokens,
|
|
22159
|
+
node.insert(restTokens, index2, paramMap, context, pathErrorCheckOnly);
|
|
21434
22160
|
}
|
|
21435
22161
|
buildRegExpStr() {
|
|
21436
22162
|
const childKeys = Object.keys(this.#children).sort(compareKey);
|
|
@@ -21462,7 +22188,7 @@ var init_trie = __esm({
|
|
|
21462
22188
|
Trie = class {
|
|
21463
22189
|
#context = { varIndex: 0 };
|
|
21464
22190
|
#root = new Node();
|
|
21465
|
-
insert(path,
|
|
22191
|
+
insert(path, index2, pathErrorCheckOnly) {
|
|
21466
22192
|
const paramAssoc = [];
|
|
21467
22193
|
const groups = [];
|
|
21468
22194
|
for (let i = 0; ; ) {
|
|
@@ -21488,7 +22214,7 @@ var init_trie = __esm({
|
|
|
21488
22214
|
}
|
|
21489
22215
|
}
|
|
21490
22216
|
}
|
|
21491
|
-
this.#root.insert(tokens,
|
|
22217
|
+
this.#root.insert(tokens, index2, paramAssoc, this.#context, pathErrorCheckOnly);
|
|
21492
22218
|
return paramAssoc;
|
|
21493
22219
|
}
|
|
21494
22220
|
buildRegExp() {
|
|
@@ -22082,8 +22808,19 @@ __export(web_exports, {
|
|
|
22082
22808
|
dashboardApp: () => dashboardApp,
|
|
22083
22809
|
default: () => web_default
|
|
22084
22810
|
});
|
|
22085
|
-
import { existsSync as
|
|
22086
|
-
import { dirname as
|
|
22811
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, renameSync } from "fs";
|
|
22812
|
+
import { dirname as dirname3 } from "path";
|
|
22813
|
+
function parsePositiveInteger(value) {
|
|
22814
|
+
if (!/^\d+$/.test(value)) return null;
|
|
22815
|
+
const parsed = Number(value);
|
|
22816
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
22817
|
+
}
|
|
22818
|
+
function parseTaskStatus(value) {
|
|
22819
|
+
return TASK_STATUSES.has(value) ? value : null;
|
|
22820
|
+
}
|
|
22821
|
+
function safeStatus(value) {
|
|
22822
|
+
return value && TASK_STATUSES.has(value) ? value : "unknown";
|
|
22823
|
+
}
|
|
22087
22824
|
function formatDuration(startAt, endAt) {
|
|
22088
22825
|
if (!startAt) return "-";
|
|
22089
22826
|
const start = new Date(startAt).getTime();
|
|
@@ -22121,17 +22858,21 @@ function esc(s) {
|
|
|
22121
22858
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
22122
22859
|
}
|
|
22123
22860
|
function readCurrentConfig() {
|
|
22124
|
-
|
|
22861
|
+
const configPath = getConfigPath();
|
|
22862
|
+
if (!existsSync4(configPath)) return {};
|
|
22125
22863
|
try {
|
|
22126
|
-
return JSON.parse(
|
|
22864
|
+
return JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
22127
22865
|
} catch {
|
|
22128
22866
|
return {};
|
|
22129
22867
|
}
|
|
22130
22868
|
}
|
|
22131
22869
|
function writeConfig(cfg) {
|
|
22132
|
-
const
|
|
22133
|
-
|
|
22134
|
-
|
|
22870
|
+
const configPath = getConfigPath();
|
|
22871
|
+
const dir = dirname3(configPath);
|
|
22872
|
+
if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
|
|
22873
|
+
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
22874
|
+
writeFileSync2(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
22875
|
+
renameSync(tempPath, configPath);
|
|
22135
22876
|
}
|
|
22136
22877
|
function renderLayout(title, activeTab, body) {
|
|
22137
22878
|
return `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>${title} - SuperTask</title>${SHARED_STYLES}
|
|
@@ -22170,11 +22911,11 @@ async function saveConfig(){
|
|
|
22170
22911
|
scheduler:{
|
|
22171
22912
|
enabled:form.se.checked,
|
|
22172
22913
|
checkIntervalMs:Number(form.si.value),
|
|
22173
|
-
catchUp:form.cu.value,
|
|
22174
22914
|
},
|
|
22175
22915
|
watchdog:{
|
|
22176
22916
|
heartbeatTimeoutMs:Number(form.wt.value)*1000,
|
|
22177
|
-
|
|
22917
|
+
checkIntervalMs:Number(form.wci.value)*1000,
|
|
22918
|
+
cleanupIntervalMs:Number(form.wcl.value)*3600000,
|
|
22178
22919
|
retentionDays:Number(form.rd.value),
|
|
22179
22920
|
}
|
|
22180
22921
|
};
|
|
@@ -22205,7 +22946,7 @@ async function saveConfig(){
|
|
|
22205
22946
|
<dialog id="dd"><div class="dh"><h3 style="margin:0">\u8BE6\u60C5</h3><button class="cb" onclick="document.getElementById('dd').close()">×</button></div><div class="db"><pre id="dc"></pre></div></dialog>
|
|
22206
22947
|
</body></html>`;
|
|
22207
22948
|
}
|
|
22208
|
-
var app, SHARED_STYLES, dashboardApp, web_default;
|
|
22949
|
+
var app, TASK_STATUSES, SHARED_STYLES, dashboardApp, web_default;
|
|
22209
22950
|
var init_web = __esm({
|
|
22210
22951
|
"src/web/index.tsx"() {
|
|
22211
22952
|
"use strict";
|
|
@@ -22217,7 +22958,47 @@ var init_web = __esm({
|
|
|
22217
22958
|
init_drizzle_orm();
|
|
22218
22959
|
init_db2();
|
|
22219
22960
|
init_config();
|
|
22961
|
+
init_health();
|
|
22220
22962
|
app = new Hono2();
|
|
22963
|
+
TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
22964
|
+
"pending",
|
|
22965
|
+
"running",
|
|
22966
|
+
"done",
|
|
22967
|
+
"failed",
|
|
22968
|
+
"dead_letter",
|
|
22969
|
+
"cancelled"
|
|
22970
|
+
]);
|
|
22971
|
+
app.use("*", async (c, next) => {
|
|
22972
|
+
await next();
|
|
22973
|
+
c.header("X-Content-Type-Options", "nosniff");
|
|
22974
|
+
c.header("X-Frame-Options", "DENY");
|
|
22975
|
+
c.header("Referrer-Policy", "no-referrer");
|
|
22976
|
+
});
|
|
22977
|
+
app.use("/api/*", async (c, next) => {
|
|
22978
|
+
if (["GET", "HEAD", "OPTIONS"].includes(c.req.method)) return next();
|
|
22979
|
+
const fetchSite = c.req.header("Sec-Fetch-Site");
|
|
22980
|
+
if (fetchSite && !["same-origin", "none"].includes(fetchSite)) {
|
|
22981
|
+
return c.json({ error: "cross-site request rejected" }, 403);
|
|
22982
|
+
}
|
|
22983
|
+
const origin = c.req.header("Origin");
|
|
22984
|
+
if (origin) {
|
|
22985
|
+
try {
|
|
22986
|
+
const originUrl = new URL(origin);
|
|
22987
|
+
const requestUrl = new URL(c.req.url);
|
|
22988
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(originUrl.hostname);
|
|
22989
|
+
if (!loopback || originUrl.origin !== requestUrl.origin) {
|
|
22990
|
+
return c.json({ error: "cross-site request rejected" }, 403);
|
|
22991
|
+
}
|
|
22992
|
+
} catch {
|
|
22993
|
+
return c.json({ error: "invalid origin" }, 403);
|
|
22994
|
+
}
|
|
22995
|
+
}
|
|
22996
|
+
return next();
|
|
22997
|
+
});
|
|
22998
|
+
app.get("/health", (c) => {
|
|
22999
|
+
const health = getGatewayHealth();
|
|
23000
|
+
return c.json(health, health.status === "ok" ? 200 : 503);
|
|
23001
|
+
});
|
|
22221
23002
|
SHARED_STYLES = html`
|
|
22222
23003
|
<style>
|
|
22223
23004
|
:root { --bg:#0d1117; --card:#161b22; --border:#30363d; --t1:#c9d1d9; --t2:#8b949e; --green:#238636; --red:#da3633; --yellow:#d29922; --blue:#1f6feb; --purple:#8957e5; }
|
|
@@ -22294,12 +23075,16 @@ var init_web = __esm({
|
|
|
22294
23075
|
</style>
|
|
22295
23076
|
`;
|
|
22296
23077
|
app.get("/", async (c) => {
|
|
22297
|
-
const
|
|
23078
|
+
const pageParam = c.req.query("page") || "1";
|
|
23079
|
+
const page = parsePositiveInteger(pageParam);
|
|
23080
|
+
if (page === null) return c.text("invalid page", 400);
|
|
22298
23081
|
const statusFilter = c.req.query("status") || "";
|
|
23082
|
+
const parsedStatus = statusFilter ? parseTaskStatus(statusFilter) : null;
|
|
23083
|
+
if (statusFilter && !parsedStatus) return c.text("invalid status", 400);
|
|
22299
23084
|
const limit = 50;
|
|
22300
23085
|
const offset = (page - 1) * limit;
|
|
22301
23086
|
const [tasks3, statsData] = await Promise.all([
|
|
22302
|
-
TaskService.list({ limit, offset, ...
|
|
23087
|
+
TaskService.list({ limit, offset, ...parsedStatus ? { status: parsedStatus } : {} }),
|
|
22303
23088
|
TaskService.stats({})
|
|
22304
23089
|
]);
|
|
22305
23090
|
const taskIds = tasks3.map((t) => t.id);
|
|
@@ -22322,12 +23107,13 @@ var init_web = __esm({
|
|
|
22322
23107
|
</div>`;
|
|
22323
23108
|
let rows = "";
|
|
22324
23109
|
for (const task of tasks3) {
|
|
22325
|
-
const
|
|
23110
|
+
const status = safeStatus(task.status);
|
|
23111
|
+
const st = status.toUpperCase();
|
|
22326
23112
|
rows += `<tr>
|
|
22327
23113
|
<td class="mu">#${task.id}</td>
|
|
22328
23114
|
<td><div style="font-weight:500">${esc(task.name)}</div><div class="mu sm el">${esc(task.prompt.substring(0, 120))}</div></td>
|
|
22329
23115
|
<td><span class="tag">${esc(task.agent)}</span></td>
|
|
22330
|
-
<td><span class="badge b-${
|
|
23116
|
+
<td><span class="badge b-${status}">${st}</span></td>
|
|
22331
23117
|
<td class="sm ${task.status === "running" ? "" : "mu"}">${formatDuration(task.startedAt, task.finishedAt)}</td>
|
|
22332
23118
|
<td class="mu sm">${(task.retryCount ?? 0) > 0 ? task.retryCount : "-"}</td>
|
|
22333
23119
|
<td>
|
|
@@ -22359,21 +23145,13 @@ var init_web = __esm({
|
|
|
22359
23145
|
});
|
|
22360
23146
|
app.get("/templates", async (c) => {
|
|
22361
23147
|
const templates = await TaskTemplateService.list(100);
|
|
22362
|
-
for (const tmpl of templates) {
|
|
22363
|
-
if (tmpl.enabled && tmpl.scheduleType === "cron" && tmpl.cronExpr) {
|
|
22364
|
-
try {
|
|
22365
|
-
const { getNextCronRun: getNextCronRun2 } = await Promise.resolve().then(() => (init_cron_parser(), cron_parser_exports));
|
|
22366
|
-
tmpl.nextRunAt = getNextCronRun2(tmpl.cronExpr, Date.now());
|
|
22367
|
-
} catch {
|
|
22368
|
-
}
|
|
22369
|
-
}
|
|
22370
|
-
}
|
|
22371
23148
|
const enabled = templates.filter((t) => t.enabled).length;
|
|
22372
23149
|
const disabled = templates.length - enabled;
|
|
22373
23150
|
let rows = "";
|
|
22374
23151
|
for (const t of templates) {
|
|
22375
|
-
const
|
|
22376
|
-
const
|
|
23152
|
+
const scheduleType = ["cron", "recurring", "delayed"].includes(t.scheduleType) ? t.scheduleType : "unknown";
|
|
23153
|
+
const typeLabel = scheduleType === "cron" ? "Cron" : scheduleType === "recurring" ? "\u5FAA\u73AF" : scheduleType === "delayed" ? "\u5B9A\u65F6" : "\u672A\u77E5";
|
|
23154
|
+
const typeClass = "tag t-" + scheduleType;
|
|
22377
23155
|
let rule = "-";
|
|
22378
23156
|
if (t.scheduleType === "cron") rule = t.cronExpr || "-";
|
|
22379
23157
|
else if (t.scheduleType === "recurring") rule = t.intervalMs ? `${Math.floor(t.intervalMs / 6e4)}\u5206\u949F` : "-";
|
|
@@ -22385,7 +23163,7 @@ var init_web = __esm({
|
|
|
22385
23163
|
<td><div style="font-weight:500">${esc(t.name)}</div><div class="mu sm el">${esc(t.prompt.substring(0, 100))}</div>
|
|
22386
23164
|
<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>
|
|
22387
23165
|
<td><span class="${typeClass}">${typeLabel}</span></td>
|
|
22388
|
-
<td class="m sm">${rule}</td>
|
|
23166
|
+
<td class="m sm">${esc(rule)}</td>
|
|
22389
23167
|
<td>${statusBadge}</td>
|
|
22390
23168
|
<td class="sm">${t.lastRunAt ? timeAgo(t.lastRunAt) : "-"}</td>
|
|
22391
23169
|
<td class="sm">${t.nextRunAt ? timeUntil(t.nextRunAt) : "-"}</td>
|
|
@@ -22413,7 +23191,8 @@ var init_web = __esm({
|
|
|
22413
23191
|
return c.html(renderLayout("\u5B9A\u65F6\u4EFB\u52A1", "templates", body));
|
|
22414
23192
|
});
|
|
22415
23193
|
app.get("/runs", async (c) => {
|
|
22416
|
-
const page =
|
|
23194
|
+
const page = parsePositiveInteger(c.req.query("page") || "1");
|
|
23195
|
+
if (page === null) return c.text("invalid page", 400);
|
|
22417
23196
|
const limit = 50;
|
|
22418
23197
|
const offset = (page - 1) * limit;
|
|
22419
23198
|
const { taskRuns: tr, tasks: tk } = schema_exports;
|
|
@@ -22431,13 +23210,14 @@ var init_web = __esm({
|
|
|
22431
23210
|
childPid: tr.childPid,
|
|
22432
23211
|
taskName: tk.name,
|
|
22433
23212
|
taskAgent: tk.agent
|
|
22434
|
-
}).from(tr).innerJoin(tk, eq(tr.taskId, tk.id)).orderBy(desc(tr.startedAt)).limit(limit).offset(offset);
|
|
23213
|
+
}).from(tr).innerJoin(tk, eq(tr.taskId, tk.id)).orderBy(desc(tr.startedAt), desc(tr.id)).limit(limit).offset(offset);
|
|
22435
23214
|
const totalResult = await db.select({ count: sql`count(*)` }).from(tr);
|
|
22436
23215
|
const total = Number(totalResult[0]?.count ?? 0);
|
|
22437
23216
|
const totalPages = Math.ceil(total / limit);
|
|
22438
23217
|
let rows = "";
|
|
22439
23218
|
const logsHtml = [];
|
|
22440
23219
|
for (const run of runs) {
|
|
23220
|
+
const status = safeStatus(run.status);
|
|
22441
23221
|
const shortSession = run.sessionId ? run.sessionId.slice(4, 7) + "***" + run.sessionId.slice(-3) : "-";
|
|
22442
23222
|
const logBtn = run.log ? `<button class="btn btn-sm" onclick="toggleLog(${run.id})">\u65E5\u5FD7</button>` : "";
|
|
22443
23223
|
rows += `<tr>
|
|
@@ -22445,15 +23225,15 @@ var init_web = __esm({
|
|
|
22445
23225
|
<td><div style="font-weight:500">${esc(run.taskName)} <span class="mu">(#${run.taskId})</span></div>
|
|
22446
23226
|
${run.model ? `<div class="sm"><span class="tag">${esc(run.model)}</span></div>` : ""}</td>
|
|
22447
23227
|
<td><span class="tag">${esc(run.taskAgent)}</span></td>
|
|
22448
|
-
<td><span class="badge b-${
|
|
23228
|
+
<td><span class="badge b-${status}">${status.toUpperCase()}</span></td>
|
|
22449
23229
|
<td class="sm">${formatDuration(run.startedAt, run.finishedAt)}</td>
|
|
22450
23230
|
<td class="sm mu">${run.heartbeatAt ? timeAgo(run.heartbeatAt) : "-"}</td>
|
|
22451
23231
|
<td><button class="btn btn-sm" onclick="showRunDetail(${run.id})">\u8BE6\u60C5</button>${logBtn}</td>
|
|
22452
23232
|
</tr>`;
|
|
22453
23233
|
if (run.log) {
|
|
22454
23234
|
logsHtml.push(`<div id="log-${run.id}" style="display:none" class="mt8">
|
|
22455
|
-
<div class="panel"><div class="ph"><h3>Run #${run.id} \u65E5\u5FD7 \u2014 ${run.taskName}</h3></div>
|
|
22456
|
-
<div class="log-box">${run.log
|
|
23235
|
+
<div class="panel"><div class="ph"><h3>Run #${run.id} \u65E5\u5FD7 \u2014 ${esc(run.taskName)}</h3></div>
|
|
23236
|
+
<div class="log-box">${esc(run.log)}</div></div></div>`);
|
|
22457
23237
|
}
|
|
22458
23238
|
}
|
|
22459
23239
|
let paging = `<div class="pn">`;
|
|
@@ -22479,17 +23259,18 @@ var init_web = __esm({
|
|
|
22479
23259
|
});
|
|
22480
23260
|
app.get("/system", async (c) => {
|
|
22481
23261
|
const config = loadConfig();
|
|
23262
|
+
const configPath = getConfigPath();
|
|
22482
23263
|
const stats = await TaskService.stats({});
|
|
22483
23264
|
const runningRuns = await TaskRunService.getAllRunningRuns();
|
|
22484
23265
|
const templates = await TaskTemplateService.list(100);
|
|
22485
|
-
const configExists =
|
|
23266
|
+
const configExists = existsSync4(configPath);
|
|
22486
23267
|
let runRows = "";
|
|
22487
23268
|
if (runningRuns.length > 0) {
|
|
22488
23269
|
for (const run of runningRuns) {
|
|
22489
23270
|
const shortS = run.sessionId ? run.sessionId.slice(4, 7) + "***" + run.sessionId.slice(-3) : "-";
|
|
22490
23271
|
runRows += `<tr>
|
|
22491
23272
|
<td class="mu">#${run.id}</td><td>#${run.taskId}</td>
|
|
22492
|
-
<td class="m sm">${shortS}</td>
|
|
23273
|
+
<td class="m sm">${esc(shortS)}</td>
|
|
22493
23274
|
<td class="sm">${esc(run.model) || "-"}</td>
|
|
22494
23275
|
<td class="sm">${formatDate(run.startedAt)}</td>
|
|
22495
23276
|
<td class="sm">${run.heartbeatAt ? timeAgo(run.heartbeatAt) : "-"}</td>
|
|
@@ -22514,19 +23295,14 @@ var init_web = __esm({
|
|
|
22514
23295
|
<h3 style="margin:0 0 12px;font-size:14px">Scheduler \u914D\u7F6E</h3>
|
|
22515
23296
|
<div class="form-row"><label>\u542F\u7528\u8C03\u5EA6</label><input type="checkbox" name="se" ${config.scheduler.enabled ? "checked" : ""}></div>
|
|
22516
23297
|
<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>
|
|
22517
|
-
<div class="form-row"><label>\u8FFD\u8D76\u6A21\u5F0F</label><select name="cu" style="width:100px">
|
|
22518
|
-
<option value="next" ${config.scheduler.catchUp === "next" ? "selected" : ""}>next</option>
|
|
22519
|
-
<option value="all" ${config.scheduler.catchUp === "all" ? "selected" : ""}>all</option>
|
|
22520
|
-
<option value="latest" ${config.scheduler.catchUp === "latest" ? "selected" : ""}>latest</option>
|
|
22521
|
-
</select></div>
|
|
22522
23298
|
<div class="ir"><span class="ik">\u6D3B\u8DC3\u6A21\u677F</span><span class="iv">${templates.filter((t) => t.enabled).length} / ${templates.length}</span></div>
|
|
22523
23299
|
</div>
|
|
22524
23300
|
<div class="card">
|
|
22525
23301
|
<h3 style="margin:0 0 12px;font-size:14px">Watchdog \u914D\u7F6E</h3>
|
|
22526
23302
|
<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>
|
|
22527
|
-
<div class="form-row"><label>\
|
|
23303
|
+
<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>
|
|
23304
|
+
<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>
|
|
22528
23305
|
<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>
|
|
22529
|
-
<div class="ir"><span class="ik">\u65E5\u5FD7\u683C\u5F0F</span><span class="iv">${config.logging.format}</span></div>
|
|
22530
23306
|
</div>
|
|
22531
23307
|
</div>
|
|
22532
23308
|
<div style="text-align:center;margin-bottom:24px">
|
|
@@ -22555,7 +23331,7 @@ var init_web = __esm({
|
|
|
22555
23331
|
|
|
22556
23332
|
<div class="card mt16">
|
|
22557
23333
|
<h3 style="margin:0 0 12px;font-size:14px">\u914D\u7F6E\u6587\u4EF6</h3>
|
|
22558
|
-
<div class="ir"><span class="ik">\u8DEF\u5F84</span><span class="iv m sm">${
|
|
23334
|
+
<div class="ir"><span class="ik">\u8DEF\u5F84</span><span class="iv m sm">${esc(configPath)}</span></div>
|
|
22559
23335
|
<div class="ir"><span class="ik">\u6587\u4EF6\u5B58\u5728</span><span class="iv">${configFileStatus}</span></div>
|
|
22560
23336
|
</div>
|
|
22561
23337
|
|
|
@@ -22567,46 +23343,61 @@ var init_web = __esm({
|
|
|
22567
23343
|
return c.html(renderLayout("\u7CFB\u7EDF\u72B6\u6001", "system", body));
|
|
22568
23344
|
});
|
|
22569
23345
|
app.get("/api/tasks/:id", async (c) => {
|
|
22570
|
-
const id =
|
|
23346
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23347
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
22571
23348
|
const task = await TaskService.getById(id);
|
|
22572
23349
|
if (!task) return c.json({ error: "not found" }, 404);
|
|
22573
23350
|
const runs = await TaskRunService.listByTaskId(id);
|
|
22574
23351
|
return c.json({ ...task, _runs: runs });
|
|
22575
23352
|
});
|
|
22576
23353
|
app.get("/api/runs/:id", async (c) => {
|
|
22577
|
-
const id =
|
|
23354
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23355
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
22578
23356
|
const run = await TaskRunService.getById(id);
|
|
22579
23357
|
if (!run) return c.json({ error: "not found" }, 404);
|
|
22580
23358
|
return c.json(run);
|
|
22581
23359
|
});
|
|
22582
23360
|
app.get("/api/templates/:id", async (c) => {
|
|
22583
|
-
const id =
|
|
23361
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23362
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
22584
23363
|
const tmpl = await TaskTemplateService.getById(id);
|
|
22585
23364
|
if (!tmpl) return c.json({ error: "not found" }, 404);
|
|
22586
23365
|
return c.json(tmpl);
|
|
22587
23366
|
});
|
|
22588
23367
|
app.post("/api/tasks/:id/retry", async (c) => {
|
|
22589
|
-
|
|
22590
|
-
return c.json({
|
|
23368
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23369
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
23370
|
+
const task = await TaskService.retry(id);
|
|
23371
|
+
if (task) return c.json({ success: true });
|
|
23372
|
+
return await TaskService.getById(id) ? c.json({ error: "task status does not allow retry" }, 409) : c.json({ error: "not found" }, 404);
|
|
22591
23373
|
});
|
|
22592
23374
|
app.delete("/api/tasks/:id", async (c) => {
|
|
22593
|
-
|
|
22594
|
-
return c.json({
|
|
23375
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23376
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
23377
|
+
const deleted = await TaskService.delete(id);
|
|
23378
|
+
return deleted ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
22595
23379
|
});
|
|
22596
23380
|
app.post("/api/templates/:id/enable", async (c) => {
|
|
22597
|
-
const
|
|
22598
|
-
return c.json({
|
|
23381
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23382
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
23383
|
+
const result = await TaskTemplateService.enable(id);
|
|
23384
|
+
return result ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
22599
23385
|
});
|
|
22600
23386
|
app.post("/api/templates/:id/disable", async (c) => {
|
|
22601
|
-
const
|
|
22602
|
-
return c.json({
|
|
23387
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23388
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
23389
|
+
const result = await TaskTemplateService.disable(id);
|
|
23390
|
+
return result ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
22603
23391
|
});
|
|
22604
23392
|
app.delete("/api/templates/:id", async (c) => {
|
|
22605
|
-
const
|
|
22606
|
-
return c.json({
|
|
23393
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23394
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
23395
|
+
const ok = await TaskTemplateService.delete(id);
|
|
23396
|
+
return ok ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
22607
23397
|
});
|
|
22608
23398
|
app.post("/api/templates/:id/trigger", async (c) => {
|
|
22609
|
-
const id =
|
|
23399
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23400
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
22610
23401
|
const tmpl = await TaskTemplateService.getById(id);
|
|
22611
23402
|
if (!tmpl) return c.json({ error: "not found" }, 404);
|
|
22612
23403
|
const task = await TaskService.add({
|
|
@@ -22618,7 +23409,10 @@ var init_web = __esm({
|
|
|
22618
23409
|
category: tmpl.category,
|
|
22619
23410
|
importance: tmpl.importance,
|
|
22620
23411
|
urgency: tmpl.urgency,
|
|
23412
|
+
batchId: tmpl.batchId,
|
|
22621
23413
|
maxRetries: tmpl.maxRetries,
|
|
23414
|
+
retryBackoffMs: tmpl.retryBackoffMs,
|
|
23415
|
+
timeoutMs: tmpl.timeoutMs,
|
|
22622
23416
|
templateId: tmpl.id
|
|
22623
23417
|
});
|
|
22624
23418
|
return c.json({ success: true, taskId: task.id });
|
|
@@ -22633,22 +23427,29 @@ var init_web = __esm({
|
|
|
22633
23427
|
const bW = body.worker ?? {};
|
|
22634
23428
|
const bS = body.scheduler ?? {};
|
|
22635
23429
|
const bD = body.watchdog ?? {};
|
|
22636
|
-
const merged = {
|
|
22637
|
-
|
|
23430
|
+
const merged = {
|
|
23431
|
+
...current,
|
|
23432
|
+
...body,
|
|
23433
|
+
configVersion: 2,
|
|
23434
|
+
worker: { ...curW, ...bW },
|
|
23435
|
+
scheduler: { ...curS, ...bS },
|
|
23436
|
+
watchdog: { ...curD, ...bD }
|
|
23437
|
+
};
|
|
23438
|
+
writeConfig(validateConfig(merged));
|
|
22638
23439
|
return c.json({ success: true });
|
|
22639
23440
|
} catch (err) {
|
|
22640
|
-
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
23441
|
+
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) }, 400);
|
|
22641
23442
|
}
|
|
22642
23443
|
});
|
|
22643
23444
|
app.post("/api/database/clear", async (c) => {
|
|
22644
23445
|
try {
|
|
22645
|
-
const { tasks: tasks3, taskRuns:
|
|
22646
|
-
await db.delete(
|
|
23446
|
+
const { tasks: tasks3, taskRuns: taskRuns4, taskTemplates: taskTemplates4 } = schema_exports;
|
|
23447
|
+
await db.delete(taskRuns4);
|
|
22647
23448
|
await db.delete(taskTemplates4);
|
|
22648
23449
|
await db.delete(tasks3);
|
|
22649
23450
|
return c.json({ success: true });
|
|
22650
23451
|
} catch (err) {
|
|
22651
|
-
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
23452
|
+
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) }, 500);
|
|
22652
23453
|
}
|
|
22653
23454
|
});
|
|
22654
23455
|
dashboardApp = app;
|
|
@@ -22684,7 +23485,7 @@ function acquireLock() {
|
|
|
22684
23485
|
sqlite.exec("DELETE FROM gateway_lock WHERE id = 1");
|
|
22685
23486
|
}
|
|
22686
23487
|
sqlite.exec(
|
|
22687
|
-
"INSERT INTO gateway_lock (id, pid, acquired_at, heartbeat_at) VALUES (1, ?, ?,
|
|
23488
|
+
"INSERT INTO gateway_lock (id, pid, acquired_at, heartbeat_at, ready_at) VALUES (1, ?, ?, ?, NULL)",
|
|
22688
23489
|
[pid, now, now]
|
|
22689
23490
|
);
|
|
22690
23491
|
sqlite.exec("COMMIT");
|
|
@@ -22718,6 +23519,18 @@ function updateLockHeartbeat() {
|
|
|
22718
23519
|
} catch {
|
|
22719
23520
|
}
|
|
22720
23521
|
}
|
|
23522
|
+
function markGatewayReady() {
|
|
23523
|
+
sqlite.exec(
|
|
23524
|
+
"UPDATE gateway_lock SET heartbeat_at = ?, ready_at = ? WHERE pid = ?",
|
|
23525
|
+
[Date.now(), Date.now(), process.pid]
|
|
23526
|
+
);
|
|
23527
|
+
}
|
|
23528
|
+
function markGatewayNotReady() {
|
|
23529
|
+
try {
|
|
23530
|
+
sqlite.exec("UPDATE gateway_lock SET ready_at = NULL WHERE pid = ?", [process.pid]);
|
|
23531
|
+
} catch {
|
|
23532
|
+
}
|
|
23533
|
+
}
|
|
22721
23534
|
async function main() {
|
|
22722
23535
|
console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "info", msg: "SuperTask Gateway starting", pid: process.pid }));
|
|
22723
23536
|
if (!acquireLock()) {
|
|
@@ -22729,6 +23542,12 @@ async function main() {
|
|
|
22729
23542
|
const worker = new WorkerEngine(cfg);
|
|
22730
23543
|
const watchdog = new Watchdog(cfg);
|
|
22731
23544
|
const scheduler = new Scheduler(cfg);
|
|
23545
|
+
initializeGatewayHealth({
|
|
23546
|
+
workerPollIntervalMs: cfg.worker.pollIntervalMs,
|
|
23547
|
+
schedulerEnabled: cfg.scheduler.enabled,
|
|
23548
|
+
schedulerCheckIntervalMs: cfg.scheduler.checkIntervalMs,
|
|
23549
|
+
watchdogCheckIntervalMs: cfg.watchdog.checkIntervalMs
|
|
23550
|
+
});
|
|
22732
23551
|
worker.start();
|
|
22733
23552
|
watchdog.start();
|
|
22734
23553
|
await scheduler.start();
|
|
@@ -22746,6 +23565,7 @@ async function main() {
|
|
|
22746
23565
|
url: `http://localhost:${cfg.dashboard.port}`
|
|
22747
23566
|
}));
|
|
22748
23567
|
}
|
|
23568
|
+
markGatewayReady();
|
|
22749
23569
|
console.log(JSON.stringify({
|
|
22750
23570
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22751
23571
|
level: "info",
|
|
@@ -22759,10 +23579,10 @@ async function main() {
|
|
|
22759
23579
|
shuttingDown = true;
|
|
22760
23580
|
console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "info", msg: `received ${signal}, shutting down...` }));
|
|
22761
23581
|
clearInterval(heartbeatTimer);
|
|
23582
|
+
markGatewayNotReady();
|
|
22762
23583
|
scheduler.stop();
|
|
22763
23584
|
watchdog.stop();
|
|
22764
|
-
const runningIds = worker.
|
|
22765
|
-
await worker.stop();
|
|
23585
|
+
const runningIds = await worker.stop(cfg.worker.shutdownGracePeriodMs);
|
|
22766
23586
|
if (runningIds.length > 0) {
|
|
22767
23587
|
const resetCount = await TaskService.resetRunningToPending(runningIds);
|
|
22768
23588
|
console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "info", msg: "reset running tasks to pending", count: resetCount }));
|
|
@@ -22772,6 +23592,7 @@ async function main() {
|
|
|
22772
23592
|
await TaskRunService.fail(run.id, "Gateway shutdown");
|
|
22773
23593
|
}
|
|
22774
23594
|
releaseLock();
|
|
23595
|
+
resetGatewayHealth();
|
|
22775
23596
|
closeDb();
|
|
22776
23597
|
console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "info", msg: "Gateway stopped" }));
|
|
22777
23598
|
process.exit(0);
|
|
@@ -22799,6 +23620,7 @@ var init_gateway = __esm({
|
|
|
22799
23620
|
init_db2();
|
|
22800
23621
|
init_task_service();
|
|
22801
23622
|
init_task_run_service();
|
|
23623
|
+
init_health();
|
|
22802
23624
|
STALE_THRESHOLD_MS = 3e4;
|
|
22803
23625
|
if (import.meta.main) {
|
|
22804
23626
|
main();
|
|
@@ -22806,247 +23628,87 @@ var init_gateway = __esm({
|
|
|
22806
23628
|
}
|
|
22807
23629
|
});
|
|
22808
23630
|
|
|
22809
|
-
// src/daemon/
|
|
22810
|
-
var
|
|
22811
|
-
__export(
|
|
22812
|
-
|
|
22813
|
-
|
|
22814
|
-
isGatewayRunning: () => isGatewayRunning,
|
|
22815
|
-
uninstall: () => uninstall,
|
|
22816
|
-
upgrade: () => upgrade
|
|
23631
|
+
// src/daemon/update.ts
|
|
23632
|
+
var update_exports = {};
|
|
23633
|
+
__export(update_exports, {
|
|
23634
|
+
installLatestPlugin: () => installLatestPlugin,
|
|
23635
|
+
resolveInstalledPlugin: () => resolveInstalledPlugin
|
|
22817
23636
|
});
|
|
22818
|
-
import {
|
|
22819
|
-
import {
|
|
22820
|
-
import {
|
|
22821
|
-
import {
|
|
22822
|
-
|
|
22823
|
-
|
|
22824
|
-
|
|
22825
|
-
|
|
22826
|
-
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
22827
|
-
return pkg.version || "0.0.0";
|
|
22828
|
-
} catch {
|
|
22829
|
-
return "0.0.0";
|
|
22830
|
-
}
|
|
22831
|
-
}
|
|
22832
|
-
function getRunningVersion() {
|
|
23637
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
23638
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync } from "fs";
|
|
23639
|
+
import { homedir as homedir4 } from "os";
|
|
23640
|
+
import { join as join4 } from "path";
|
|
23641
|
+
function pluginAt(packageDir) {
|
|
23642
|
+
const packageJson = join4(packageDir, "package.json");
|
|
23643
|
+
const gatewayEntry = join4(packageDir, "dist/gateway/index.js");
|
|
23644
|
+
if (!existsSync5(packageJson) || !existsSync5(gatewayEntry)) return null;
|
|
22833
23645
|
try {
|
|
22834
|
-
|
|
22835
|
-
|
|
23646
|
+
const pkg = JSON.parse(readFileSync4(packageJson, "utf8"));
|
|
23647
|
+
if (pkg.name !== PACKAGE_NAME || typeof pkg.version !== "string") return null;
|
|
23648
|
+
return { packageDir, gatewayEntry, version: pkg.version };
|
|
22836
23649
|
} catch {
|
|
22837
23650
|
return null;
|
|
22838
23651
|
}
|
|
22839
23652
|
}
|
|
22840
|
-
function
|
|
22841
|
-
|
|
22842
|
-
|
|
22843
|
-
} catch {
|
|
22844
|
-
}
|
|
23653
|
+
function versionParts(version2) {
|
|
23654
|
+
const match2 = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version2);
|
|
23655
|
+
return match2 ? match2.slice(1).map(Number) : null;
|
|
22845
23656
|
}
|
|
22846
|
-
function
|
|
22847
|
-
|
|
22848
|
-
|
|
22849
|
-
|
|
22850
|
-
|
|
22851
|
-
|
|
22852
|
-
execSync(cmd, { stdio: "pipe" });
|
|
22853
|
-
return true;
|
|
22854
|
-
} catch {
|
|
22855
|
-
return false;
|
|
23657
|
+
function compareVersions(left, right) {
|
|
23658
|
+
const a = versionParts(left);
|
|
23659
|
+
const b = versionParts(right);
|
|
23660
|
+
if (!a || !b) return left.localeCompare(right);
|
|
23661
|
+
for (let index2 = 0; index2 < 3; index2 += 1) {
|
|
23662
|
+
if (a[index2] !== b[index2]) return a[index2] - b[index2];
|
|
22856
23663
|
}
|
|
23664
|
+
return 0;
|
|
22857
23665
|
}
|
|
22858
|
-
function
|
|
22859
|
-
|
|
22860
|
-
try {
|
|
22861
|
-
execSync("npm install -g pm2", { stdio: "inherit" });
|
|
22862
|
-
return true;
|
|
22863
|
-
} catch {
|
|
22864
|
-
try {
|
|
22865
|
-
execSync("bun install -g pm2", { stdio: "inherit" });
|
|
22866
|
-
return true;
|
|
22867
|
-
} catch {
|
|
22868
|
-
return false;
|
|
22869
|
-
}
|
|
22870
|
-
}
|
|
23666
|
+
function cacheRoot() {
|
|
23667
|
+
return process.env.SUPERTASK_OPENCODE_CACHE_DIR ?? join4(homedir4(), ".cache/opencode/packages");
|
|
22871
23668
|
}
|
|
22872
|
-
function
|
|
22873
|
-
const
|
|
22874
|
-
|
|
22875
|
-
const
|
|
22876
|
-
|
|
22877
|
-
|
|
22878
|
-
shell: process.platform === "win32"
|
|
22879
|
-
});
|
|
22880
|
-
const output = (result.stdout ?? "") + (result.stderr ?? "");
|
|
22881
|
-
return { ok: result.status === 0, output };
|
|
22882
|
-
} catch (err) {
|
|
22883
|
-
return { ok: false, output: err instanceof Error ? err.message : String(err) };
|
|
23669
|
+
function resolveInstalledPlugin() {
|
|
23670
|
+
const override = process.env.SUPERTASK_PLUGIN_PACKAGE_DIR;
|
|
23671
|
+
if (override) {
|
|
23672
|
+
const plugin = pluginAt(override);
|
|
23673
|
+
if (!plugin) throw new Error(`[supertask] \u5B89\u88C5\u5305\u65E0\u6548\u6216\u7F3A\u5C11 Gateway \u6784\u5EFA\u4EA7\u7269: ${override}`);
|
|
23674
|
+
return plugin;
|
|
22884
23675
|
}
|
|
22885
|
-
|
|
22886
|
-
|
|
22887
|
-
const
|
|
22888
|
-
if (!
|
|
22889
|
-
|
|
22890
|
-
return JSON.parse(output);
|
|
22891
|
-
} catch {
|
|
22892
|
-
return [];
|
|
23676
|
+
const root = cacheRoot();
|
|
23677
|
+
const packageDirs = existsSync5(root) ? readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && (entry.name === PACKAGE_NAME || entry.name.startsWith(`${PACKAGE_NAME}@`))).map((entry) => join4(root, entry.name, "node_modules", PACKAGE_NAME)) : [];
|
|
23678
|
+
const installed = packageDirs.map(pluginAt).filter((plugin) => plugin !== null).sort((left, right) => compareVersions(right.version, left.version));
|
|
23679
|
+
if (!installed[0]) {
|
|
23680
|
+
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230\u53EF\u8FD0\u884C\u7684 ${PACKAGE_NAME}`);
|
|
22893
23681
|
}
|
|
23682
|
+
return installed[0];
|
|
22894
23683
|
}
|
|
22895
|
-
function
|
|
22896
|
-
|
|
22897
|
-
const proc = list.find((p) => p.name === PROCESS_NAME);
|
|
22898
|
-
if (!proc) return false;
|
|
22899
|
-
return proc.pm2_env?.status === "online";
|
|
23684
|
+
function opencodeBin() {
|
|
23685
|
+
return process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
22900
23686
|
}
|
|
22901
|
-
function
|
|
22902
|
-
|
|
22903
|
-
|
|
22904
|
-
|
|
22905
|
-
|
|
22906
|
-
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
|
|
22911
|
-
|
|
22912
|
-
|
|
22913
|
-
|
|
22914
|
-
|
|
22915
|
-
PROCESS_NAME,
|
|
22916
|
-
"--interpreter",
|
|
22917
|
-
"none",
|
|
22918
|
-
"--restart-delay",
|
|
22919
|
-
"5000",
|
|
22920
|
-
"--max-restarts",
|
|
22921
|
-
"30",
|
|
22922
|
-
"--",
|
|
22923
|
-
GATEWAY_ENTRY
|
|
22924
|
-
]);
|
|
22925
|
-
}
|
|
22926
|
-
function install() {
|
|
22927
|
-
if (!isPm2Installed()) {
|
|
22928
|
-
if (!installPm2()) {
|
|
22929
|
-
throw new Error("[supertask] Failed to install pm2. Please install it manually: npm install -g pm2");
|
|
22930
|
-
}
|
|
22931
|
-
}
|
|
22932
|
-
console.log("[supertask] pm2 ready");
|
|
22933
|
-
const list = pm2JsonList();
|
|
22934
|
-
const existing = list.find((p) => p.name === PROCESS_NAME);
|
|
22935
|
-
if (existing) {
|
|
22936
|
-
console.log("[supertask] Gateway process already registered, reloading...");
|
|
22937
|
-
const { ok } = pm2Exec(["reload", PROCESS_NAME]);
|
|
22938
|
-
if (!ok) {
|
|
22939
|
-
console.error("[supertask] pm2 reload failed, trying restart...");
|
|
22940
|
-
pm2Exec(["restart", PROCESS_NAME]);
|
|
22941
|
-
}
|
|
22942
|
-
} else {
|
|
22943
|
-
console.log("[supertask] Starting Gateway with pm2...");
|
|
22944
|
-
const version2 = getPackageVersion();
|
|
22945
|
-
const { ok, output } = pm2StartGateway(version2);
|
|
22946
|
-
if (!ok) {
|
|
22947
|
-
throw new Error(`[supertask] pm2 start failed: ${output}`);
|
|
22948
|
-
}
|
|
22949
|
-
writeRunningVersion(version2);
|
|
22950
|
-
}
|
|
22951
|
-
pm2Exec(["save"]);
|
|
22952
|
-
console.log("[supertask] Configuring startup...");
|
|
22953
|
-
const { ok: startupOk, output: startupOutput } = pm2Exec(["startup"]);
|
|
22954
|
-
if (!startupOk) {
|
|
22955
|
-
if (startupOutput.includes("sudo") || startupOutput.includes("run as root")) {
|
|
22956
|
-
console.log("[supertask] pm2 startup requires elevated permissions.");
|
|
22957
|
-
console.log("[supertask] Run the command shown above, or manually execute:");
|
|
22958
|
-
console.log(` pm2 startup`);
|
|
22959
|
-
console.log(` pm2 save`);
|
|
22960
|
-
} else if (process.platform === "win32") {
|
|
22961
|
-
console.log("[supertask] On Windows, use pm2-installer for startup:");
|
|
22962
|
-
console.log(" npm install -g pm2-windows-startup");
|
|
22963
|
-
console.log(" pm2-startup install");
|
|
22964
|
-
}
|
|
22965
|
-
}
|
|
22966
|
-
console.log("\n[supertask] Gateway installed and running!");
|
|
22967
|
-
console.log("[supertask] Manage with: pm2 status / pm2 logs supertask-gateway");
|
|
22968
|
-
}
|
|
22969
|
-
function uninstall() {
|
|
22970
|
-
console.log("[supertask] Stopping Gateway...");
|
|
22971
|
-
pm2Exec(["stop", PROCESS_NAME]);
|
|
22972
|
-
console.log("[supertask] Removing Gateway from pm2...");
|
|
22973
|
-
pm2Exec(["delete", PROCESS_NAME]);
|
|
22974
|
-
pm2Exec(["save"]);
|
|
22975
|
-
console.log("\n[supertask] Gateway removed from pm2.");
|
|
22976
|
-
console.log("[supertask] Note: pm2 startup config was not removed (you may have other pm2 processes).");
|
|
22977
|
-
console.log("[supertask] To fully remove pm2 startup: pm2 unstartup");
|
|
22978
|
-
}
|
|
22979
|
-
function upgrade() {
|
|
22980
|
-
const before = getRunningVersion();
|
|
22981
|
-
const currentVersion = getPackageVersion();
|
|
22982
|
-
const list = pm2JsonList();
|
|
22983
|
-
const proc = list.find((p) => p.name === PROCESS_NAME);
|
|
22984
|
-
if (proc) {
|
|
22985
|
-
console.log(`[supertask] Stopping Gateway (version ${before ?? "unknown"})...`);
|
|
22986
|
-
pm2Exec(["delete", PROCESS_NAME]);
|
|
22987
|
-
}
|
|
22988
|
-
console.log(`[supertask] Starting Gateway (version ${currentVersion})...`);
|
|
22989
|
-
const { ok } = pm2StartGateway(currentVersion);
|
|
22990
|
-
if (ok) {
|
|
22991
|
-
writeRunningVersion(currentVersion);
|
|
22992
|
-
pm2Exec(["save"]);
|
|
22993
|
-
console.log(`[supertask] Gateway upgraded: ${before ?? "unknown"} \u2192 ${currentVersion}`);
|
|
22994
|
-
} else {
|
|
22995
|
-
throw new Error(`[supertask] Failed to start Gateway after upgrade`);
|
|
22996
|
-
}
|
|
22997
|
-
return { before, after: currentVersion, restarted: true };
|
|
22998
|
-
}
|
|
22999
|
-
function ensureGateway() {
|
|
23000
|
-
const currentVersion = getPackageVersion();
|
|
23001
|
-
try {
|
|
23002
|
-
const list = pm2JsonList();
|
|
23003
|
-
const proc = list.find((p) => p.name === PROCESS_NAME);
|
|
23004
|
-
if (proc && proc.pm2_env?.status === "online") {
|
|
23005
|
-
const runningVersion = getRunningVersion();
|
|
23006
|
-
if (runningVersion === currentVersion) {
|
|
23007
|
-
return;
|
|
23008
|
-
}
|
|
23009
|
-
console.log(`[supertask] Version changed: ${runningVersion ?? "unknown"} \u2192 ${currentVersion}, reloading Gateway...`);
|
|
23010
|
-
pm2Exec(["delete", PROCESS_NAME]);
|
|
23011
|
-
const { ok } = pm2StartGateway(currentVersion);
|
|
23012
|
-
if (ok) writeRunningVersion(currentVersion);
|
|
23013
|
-
pm2Exec(["save"]);
|
|
23014
|
-
return;
|
|
23015
|
-
}
|
|
23016
|
-
} catch {
|
|
23017
|
-
}
|
|
23018
|
-
if (!isPm2Installed()) {
|
|
23019
|
-
console.log("[supertask] Installing pm2 for Gateway process management...");
|
|
23020
|
-
try {
|
|
23021
|
-
execSync("npm install -g pm2", { stdio: "pipe" });
|
|
23022
|
-
} catch {
|
|
23023
|
-
try {
|
|
23024
|
-
execSync("bun install -g pm2", { stdio: "pipe" });
|
|
23025
|
-
} catch {
|
|
23026
|
-
console.warn("[supertask] Could not install pm2. Gateway will not auto-start. Run `supertask install` manually.");
|
|
23027
|
-
return;
|
|
23028
|
-
}
|
|
23029
|
-
}
|
|
23687
|
+
function installLatestPlugin() {
|
|
23688
|
+
const result = spawnSync3(opencodeBin(), [
|
|
23689
|
+
"plugin",
|
|
23690
|
+
`${PACKAGE_NAME}@latest`,
|
|
23691
|
+
"--global",
|
|
23692
|
+
"--force"
|
|
23693
|
+
], {
|
|
23694
|
+
encoding: "utf8",
|
|
23695
|
+
env: process.env,
|
|
23696
|
+
timeout: 12e4
|
|
23697
|
+
});
|
|
23698
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
23699
|
+
if (result.error) {
|
|
23700
|
+
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u66F4\u65B0\u5931\u8D25: ${result.error.message}`);
|
|
23030
23701
|
}
|
|
23031
|
-
|
|
23032
|
-
|
|
23033
|
-
if (existing) {
|
|
23034
|
-
pm2Exec(["restart", PROCESS_NAME]);
|
|
23035
|
-
} else {
|
|
23036
|
-
const version2 = getPackageVersion();
|
|
23037
|
-
const { ok } = pm2StartGateway(version2);
|
|
23038
|
-
if (ok) writeRunningVersion(version2);
|
|
23702
|
+
if (result.status !== 0) {
|
|
23703
|
+
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u66F4\u65B0\u5931\u8D25: ${output || `\u9000\u51FA\u7801 ${result.status}`}`);
|
|
23039
23704
|
}
|
|
23040
|
-
|
|
23705
|
+
return resolveInstalledPlugin();
|
|
23041
23706
|
}
|
|
23042
|
-
var
|
|
23043
|
-
var
|
|
23044
|
-
"src/daemon/
|
|
23707
|
+
var PACKAGE_NAME;
|
|
23708
|
+
var init_update2 = __esm({
|
|
23709
|
+
"src/daemon/update.ts"() {
|
|
23045
23710
|
"use strict";
|
|
23046
|
-
|
|
23047
|
-
GATEWAY_ENTRY = join3(__dirname, "../gateway/index.js");
|
|
23048
|
-
PROCESS_NAME = "supertask-gateway";
|
|
23049
|
-
VERSION_FILE = join3(homedir3(), ".local/share/opencode/supertask-gateway-version");
|
|
23711
|
+
PACKAGE_NAME = "opencode-supertask";
|
|
23050
23712
|
}
|
|
23051
23713
|
});
|
|
23052
23714
|
|
|
@@ -23102,6 +23764,7 @@ function parseDuration(input) {
|
|
|
23102
23764
|
}
|
|
23103
23765
|
|
|
23104
23766
|
// src/cli/index.ts
|
|
23767
|
+
init_pm2();
|
|
23105
23768
|
async function withDb(fn) {
|
|
23106
23769
|
try {
|
|
23107
23770
|
return await fn();
|
|
@@ -23114,9 +23777,14 @@ async function withDb(fn) {
|
|
|
23114
23777
|
}
|
|
23115
23778
|
}
|
|
23116
23779
|
var program2 = new Command();
|
|
23117
|
-
program2.name("supertask").description("\u901A\u7528\u4EFB\u52A1\u7BA1\u7406\u7CFB\u7EDF - AI Agent \u4EFB\u52A1\u8C03\u5EA6\u5668").version(
|
|
23118
|
-
program2.command("add").description("\u521B\u5EFA\u65B0\u4EFB\u52A1").requiredOption("-n, --name <name>", "\u4EFB\u52A1\u540D\u79F0").requiredOption("-a, --agent <agent>", "\u4E3B Agent \u540D\u79F0").requiredOption("-p, --prompt <prompt>", "\u63D0\u793A\u8BCD").option("-m, --model <model>", "\u6A21\u578B").option("-c, --category <category>", "\u5206\u7C7B", "general").option("-i, --importance <number>", "\u91CD\u8981\u7A0B\u5EA6 (1-5)", "3").option("-u, --urgency <number>", "\u7D27\u6025\u7A0B\u5EA6 (1-5)", "3").option("-b, --batch <batchId>", "\u6279\u6B21 ID").option("-d, --depends <taskId>", "\u4F9D\u8D56\u7684\u4EFB\u52A1 ID").option("-w, --cwd <path>", "(\u5DF2\u5E9F\u5F03) \u5DE5\u4F5C\u76EE\u5F55\u3002\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u8BB0\u5F55\u63D0\u4EA4\u4EFB\u52A1\u65F6\u7684\u5F53\u524D\u76EE\u5F55").action(async (options) => withDb(async () => {
|
|
23780
|
+
program2.name("supertask").description("\u901A\u7528\u4EFB\u52A1\u7BA1\u7406\u7CFB\u7EDF - AI Agent \u4EFB\u52A1\u8C03\u5EA6\u5668").version(getPackageVersion());
|
|
23781
|
+
program2.command("add").description("\u521B\u5EFA\u65B0\u4EFB\u52A1").requiredOption("-n, --name <name>", "\u4EFB\u52A1\u540D\u79F0").requiredOption("-a, --agent <agent>", "\u4E3B Agent \u540D\u79F0").requiredOption("-p, --prompt <prompt>", "\u63D0\u793A\u8BCD").option("-m, --model <model>", "\u6A21\u578B").option("-c, --category <category>", "\u5206\u7C7B", "general").option("-i, --importance <number>", "\u91CD\u8981\u7A0B\u5EA6 (1-5)", "3").option("-u, --urgency <number>", "\u7D27\u6025\u7A0B\u5EA6 (1-5)", "3").option("-b, --batch <batchId>", "\u6279\u6B21 ID").option("-d, --depends <taskId>", "\u4F9D\u8D56\u7684\u4EFB\u52A1 ID").option("--max-retries <number>", "\u9996\u6B21\u6267\u884C\u4E4B\u5916\u5141\u8BB8\u7684\u91CD\u8BD5\u6B21\u6570", "3").option("--retry-backoff <duration>", "\u91CD\u8BD5\u9000\u907F\u57FA\u7840\u95F4\u9694\uFF0C\u5982 30s / 5min", "30s").option("--timeout <duration>", "\u4EFB\u52A1\u786C\u8D85\u65F6\uFF0C\u5982 30min / 2h").option("-w, --cwd <path>", "(\u5DF2\u5E9F\u5F03) \u5DE5\u4F5C\u76EE\u5F55\u3002\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u8BB0\u5F55\u63D0\u4EA4\u4EFB\u52A1\u65F6\u7684\u5F53\u524D\u76EE\u5F55").action(async (options) => withDb(async () => {
|
|
23119
23782
|
const submitCwd = process.cwd();
|
|
23783
|
+
const retryBackoffMs = parseDuration(options.retryBackoff);
|
|
23784
|
+
const timeoutMs = options.timeout ? parseDuration(options.timeout) : null;
|
|
23785
|
+
if (retryBackoffMs === null || options.timeout && timeoutMs === null) {
|
|
23786
|
+
throw new Error("retry-backoff \u6216 timeout \u683C\u5F0F\u65E0\u6548");
|
|
23787
|
+
}
|
|
23120
23788
|
const task = await TaskService.add({
|
|
23121
23789
|
name: options.name,
|
|
23122
23790
|
agent: options.agent,
|
|
@@ -23127,7 +23795,10 @@ program2.command("add").description("\u521B\u5EFA\u65B0\u4EFB\u52A1").requiredOp
|
|
|
23127
23795
|
urgency: parseInt(options.urgency),
|
|
23128
23796
|
batchId: options.batch,
|
|
23129
23797
|
dependsOn: options.depends ? parseInt(options.depends) : void 0,
|
|
23130
|
-
cwd: submitCwd
|
|
23798
|
+
cwd: submitCwd,
|
|
23799
|
+
maxRetries: parseInt(options.maxRetries),
|
|
23800
|
+
retryBackoffMs,
|
|
23801
|
+
timeoutMs
|
|
23131
23802
|
});
|
|
23132
23803
|
console.log(JSON.stringify({ id: task.id, status: "created" }, null, 2));
|
|
23133
23804
|
}));
|
|
@@ -23146,7 +23817,7 @@ program2.command("next").description("\u83B7\u53D6\u4E0B\u4E00\u4E2A\u5F85\u6267
|
|
|
23146
23817
|
urgency: task.urgency
|
|
23147
23818
|
}, null, 2));
|
|
23148
23819
|
} else {
|
|
23149
|
-
console.log(JSON.stringify({ id: null, message: "No
|
|
23820
|
+
console.log(JSON.stringify({ id: null, message: "No executable tasks" }));
|
|
23150
23821
|
}
|
|
23151
23822
|
}));
|
|
23152
23823
|
program2.command("start").description("\u5F00\u59CB\u6267\u884C\u4EFB\u52A1\uFF08\u6807\u8BB0\u4E3A running\uFF09").requiredOption("--id <id>", "\u4EFB\u52A1 ID").action(async (options) => withDb(async () => {
|
|
@@ -23234,9 +23905,14 @@ program2.command("delete").description("\u5220\u9664\u4EFB\u52A1").requiredOptio
|
|
|
23234
23905
|
console.log(JSON.stringify({ deleted, id: parseInt(options.id) }));
|
|
23235
23906
|
}));
|
|
23236
23907
|
program2.command("template").description("\u7BA1\u7406\u4EFB\u52A1\u8C03\u5EA6\u6A21\u677F").addCommand(
|
|
23237
|
-
new Command("add").description("\u521B\u5EFA\u8C03\u5EA6\u6A21\u677F").requiredOption("-n, --name <name>", "\u6A21\u677F\u540D\u79F0").requiredOption("-a, --agent <agent>", "Agent \u540D\u79F0").requiredOption("-p, --prompt <prompt>", "\u63D0\u793A\u8BCD").requiredOption("-t, --type <type>", "\u8C03\u5EA6\u7C7B\u578B\uFF1Acron/delayed/recurring").option("--cron <expr>", "cron \u8868\u8FBE\u5F0F\uFF08cron \u7C7B\u578B\u5FC5\u586B\uFF09").option("--delay <duration>", "\u5EF6\u8FDF\u65F6\u95F4\uFF08delayed \u7C7B\u578B\u5FC5\u586B\uFF09\uFF0C\u5982 30s / 5min / 1h / 2d").option("--interval <duration>", "\u5FAA\u73AF\u95F4\u9694\uFF08recurring \u7C7B\u578B\u5FC5\u586B\uFF09\uFF0C\u5982 1h / 30min / 5s").option("-m, --model <model>", "\u6A21\u578B").option("-c, --category <category>", "\u5206\u7C7B", "general").option("-i, --importance <number>", "\u91CD\u8981\u7A0B\u5EA6 1-5", "3").option("-u, --urgency <number>", "\u7D27\u6025\u7A0B\u5EA6 1-5", "3").option("--max-instances <number>", "\u6700\u5927\u5E76\u53D1\u5B9E\u4F8B\u6570", "1").option("--max-retries <number>", "\u6700\u5927\u91CD\u8BD5\u6B21\u6570", "3").option("--retry-backoff <
|
|
23908
|
+
new Command("add").description("\u521B\u5EFA\u8C03\u5EA6\u6A21\u677F").requiredOption("-n, --name <name>", "\u6A21\u677F\u540D\u79F0").requiredOption("-a, --agent <agent>", "Agent \u540D\u79F0").requiredOption("-p, --prompt <prompt>", "\u63D0\u793A\u8BCD").requiredOption("-t, --type <type>", "\u8C03\u5EA6\u7C7B\u578B\uFF1Acron/delayed/recurring").option("--cron <expr>", "cron \u8868\u8FBE\u5F0F\uFF08cron \u7C7B\u578B\u5FC5\u586B\uFF09").option("--delay <duration>", "\u5EF6\u8FDF\u65F6\u95F4\uFF08delayed \u7C7B\u578B\u5FC5\u586B\uFF09\uFF0C\u5982 30s / 5min / 1h / 2d").option("--interval <duration>", "\u5FAA\u73AF\u95F4\u9694\uFF08recurring \u7C7B\u578B\u5FC5\u586B\uFF09\uFF0C\u5982 1h / 30min / 5s").option("-m, --model <model>", "\u6A21\u578B").option("-c, --category <category>", "\u5206\u7C7B", "general").option("-i, --importance <number>", "\u91CD\u8981\u7A0B\u5EA6 1-5", "3").option("-u, --urgency <number>", "\u7D27\u6025\u7A0B\u5EA6 1-5", "3").option("-b, --batch <batchId>", "\u6A21\u677F\u751F\u6210\u4EFB\u52A1\u7684\u6279\u6B21 ID").option("--max-instances <number>", "\u6700\u5927\u5E76\u53D1\u5B9E\u4F8B\u6570", "1").option("--max-retries <number>", "\u6700\u5927\u91CD\u8BD5\u6B21\u6570", "3").option("--retry-backoff <duration>", "\u9000\u907F\u57FA\u7840\u95F4\u9694\uFF0C\u5982 30s / 5min", "30s").option("--timeout <duration>", "\u6BCF\u6B21\u4EFB\u52A1\u786C\u8D85\u65F6\uFF0C\u5982 30min / 2h").action(async (options) => withDb(async () => {
|
|
23238
23909
|
let intervalMs = null;
|
|
23239
23910
|
let runAt = null;
|
|
23911
|
+
const retryBackoffMs = parseDuration(options.retryBackoff);
|
|
23912
|
+
const timeoutMs = options.timeout ? parseDuration(options.timeout) : null;
|
|
23913
|
+
if (retryBackoffMs === null || options.timeout && timeoutMs === null) {
|
|
23914
|
+
throw new Error("retry-backoff \u6216 timeout \u683C\u5F0F\u65E0\u6548");
|
|
23915
|
+
}
|
|
23240
23916
|
if (options.interval) {
|
|
23241
23917
|
intervalMs = parseDuration(options.interval);
|
|
23242
23918
|
if (intervalMs === null) {
|
|
@@ -23260,13 +23936,16 @@ program2.command("template").description("\u7BA1\u7406\u4EFB\u52A1\u8C03\u5EA6\u
|
|
|
23260
23936
|
category: options.category,
|
|
23261
23937
|
importance: parseInt(options.importance),
|
|
23262
23938
|
urgency: parseInt(options.urgency),
|
|
23939
|
+
cwd: process.cwd(),
|
|
23940
|
+
batchId: options.batch,
|
|
23263
23941
|
scheduleType: options.type,
|
|
23264
23942
|
cronExpr: options.cron,
|
|
23265
23943
|
intervalMs,
|
|
23266
23944
|
runAt,
|
|
23267
23945
|
maxInstances: parseInt(options.maxInstances),
|
|
23268
23946
|
maxRetries: parseInt(options.maxRetries),
|
|
23269
|
-
retryBackoffMs
|
|
23947
|
+
retryBackoffMs,
|
|
23948
|
+
timeoutMs
|
|
23270
23949
|
});
|
|
23271
23950
|
console.log(JSON.stringify({ id: tmpl.id, status: "created", nextRunAt: tmpl.nextRunAt }, null, 2));
|
|
23272
23951
|
}))
|
|
@@ -23302,36 +23981,29 @@ program2.command("template").description("\u7BA1\u7406\u4EFB\u52A1\u8C03\u5EA6\u
|
|
|
23302
23981
|
}))
|
|
23303
23982
|
);
|
|
23304
23983
|
program2.command("init").description("Initialize SuperTask (create config + run migrations)").action(async () => withDb(async () => {
|
|
23305
|
-
const { existsSync:
|
|
23306
|
-
const {
|
|
23307
|
-
const {
|
|
23308
|
-
const
|
|
23309
|
-
if (!
|
|
23310
|
-
const dir = dirname4(
|
|
23311
|
-
if (!
|
|
23312
|
-
writeFileSync3(
|
|
23984
|
+
const { existsSync: existsSync6, mkdirSync: mkdirSync4, writeFileSync: writeFileSync3 } = await import("fs");
|
|
23985
|
+
const { dirname: dirname4 } = await import("path");
|
|
23986
|
+
const { getConfigPath: getConfigPath2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
23987
|
+
const configPath = getConfigPath2();
|
|
23988
|
+
if (!existsSync6(configPath)) {
|
|
23989
|
+
const dir = dirname4(configPath);
|
|
23990
|
+
if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
|
|
23991
|
+
writeFileSync3(configPath, JSON.stringify({
|
|
23992
|
+
configVersion: 2,
|
|
23313
23993
|
worker: { maxConcurrency: 2 },
|
|
23314
23994
|
scheduler: { enabled: true }
|
|
23315
23995
|
}, null, 2) + "\n");
|
|
23316
|
-
console.log(JSON.stringify({ created:
|
|
23996
|
+
console.log(JSON.stringify({ created: configPath }));
|
|
23317
23997
|
} else {
|
|
23318
|
-
console.log(JSON.stringify({ exists:
|
|
23998
|
+
console.log(JSON.stringify({ exists: configPath }));
|
|
23319
23999
|
}
|
|
23320
24000
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db2(), db_exports));
|
|
23321
|
-
|
|
23322
|
-
const { join: pJoin, dirname: pDirname } = await import("path");
|
|
23323
|
-
const { fileURLToPath: fileURLToPath3 } = await import("url");
|
|
23324
|
-
const __dirname2 = pDirname(fileURLToPath3(import.meta.url));
|
|
23325
|
-
migrate2(getDb2(), { migrationsFolder: pJoin(__dirname2, "../../drizzle") });
|
|
24001
|
+
getDb2();
|
|
23326
24002
|
console.log(JSON.stringify({ migrated: true }));
|
|
23327
24003
|
}));
|
|
23328
24004
|
program2.command("migrate").description("Run database migrations").action(async () => withDb(async () => {
|
|
23329
24005
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db2(), db_exports));
|
|
23330
|
-
|
|
23331
|
-
const { join: join4, dirname: dirname4 } = await import("path");
|
|
23332
|
-
const { fileURLToPath: fileURLToPath3 } = await import("url");
|
|
23333
|
-
const __dirname2 = dirname4(fileURLToPath3(import.meta.url));
|
|
23334
|
-
migrate2(getDb2(), { migrationsFolder: join4(__dirname2, "../../drizzle") });
|
|
24006
|
+
getDb2();
|
|
23335
24007
|
console.log(JSON.stringify({ migrated: true }));
|
|
23336
24008
|
}));
|
|
23337
24009
|
program2.command("gateway").description("Start the Gateway process (foreground)").action(async () => {
|
|
@@ -23373,26 +24045,20 @@ program2.command("uninstall").description("Stop and remove Gateway pm2 service")
|
|
|
23373
24045
|
process.exit(1);
|
|
23374
24046
|
}
|
|
23375
24047
|
});
|
|
23376
|
-
program2.command("upgrade").description("Update
|
|
23377
|
-
const { execSync: execSync2 } = await import("child_process");
|
|
23378
|
-
const { homedir: homedir4 } = await import("os");
|
|
23379
|
-
const { join: join4 } = await import("path");
|
|
23380
|
-
const configDir = join4(homedir4(), ".config/opencode");
|
|
24048
|
+
program2.command("upgrade").description("Update OpenCode plugin cache and restart Gateway").action(async () => {
|
|
23381
24049
|
console.log("Updating opencode-supertask...");
|
|
24050
|
+
let installed;
|
|
23382
24051
|
try {
|
|
23383
|
-
|
|
23384
|
-
|
|
23385
|
-
stdio: "inherit",
|
|
23386
|
-
timeout: 6e4
|
|
23387
|
-
});
|
|
24052
|
+
const { installLatestPlugin: installLatestPlugin2 } = await Promise.resolve().then(() => (init_update2(), update_exports));
|
|
24053
|
+
installed = installLatestPlugin2();
|
|
23388
24054
|
} catch (err) {
|
|
23389
|
-
console.error(
|
|
23390
|
-
console.error("Try manually:
|
|
24055
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
24056
|
+
console.error("Try manually: opencode plugin opencode-supertask@latest --global --force");
|
|
23391
24057
|
process.exit(1);
|
|
23392
24058
|
}
|
|
23393
24059
|
try {
|
|
23394
24060
|
const { upgrade: pm2Upgrade } = await Promise.resolve().then(() => (init_pm2(), pm2_exports));
|
|
23395
|
-
const result = pm2Upgrade();
|
|
24061
|
+
const result = pm2Upgrade(installed);
|
|
23396
24062
|
console.log(`
|
|
23397
24063
|
SuperTask upgraded: ${result.before ?? "unknown"} \u2192 ${result.after}`);
|
|
23398
24064
|
console.log("Gateway restarted. Please restart opencode to load the new plugin.");
|