opencode-supertask 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -25
- package/dist/cli/index.js +1374 -581
- package/dist/cli/index.js.map +1 -1
- package/dist/gateway/index.d.ts +3 -1
- package/dist/gateway/index.js +878 -296
- package/dist/gateway/index.js.map +1 -1
- package/dist/plugin/supertask.js +593 -232
- package/dist/plugin/supertask.js.map +1 -1
- package/dist/web/index.js +554 -212
- package/dist/web/index.js.map +1 -1
- package/dist/worker/index.d.ts +19 -7
- package/dist/worker/index.js +442 -151
- package/dist/worker/index.js.map +1 -1
- package/drizzle/0004_reliability_fields.sql +31 -0
- package/drizzle/meta/0004_snapshot.json +85 -13
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
package/dist/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
|
}
|
|
@@ -9678,9 +9734,38 @@ var init_task_service = __esm({
|
|
|
9678
9734
|
).returning();
|
|
9679
9735
|
return result.length;
|
|
9680
9736
|
}
|
|
9737
|
+
static async resetOrphanRunningToPending() {
|
|
9738
|
+
const result = await db.update(tasks2).set({
|
|
9739
|
+
status: "pending",
|
|
9740
|
+
startedAt: null,
|
|
9741
|
+
finishedAt: null
|
|
9742
|
+
}).where(
|
|
9743
|
+
and(
|
|
9744
|
+
eq(tasks2.status, "running"),
|
|
9745
|
+
sql`NOT EXISTS (
|
|
9746
|
+
SELECT 1 FROM ${taskRuns2}
|
|
9747
|
+
WHERE ${taskRuns2.taskId} = ${tasks2.id}
|
|
9748
|
+
AND ${taskRuns2.status} = 'running'
|
|
9749
|
+
)`
|
|
9750
|
+
)
|
|
9751
|
+
).returning();
|
|
9752
|
+
return result.length;
|
|
9753
|
+
}
|
|
9681
9754
|
static async cancel(id, scope = {}) {
|
|
9682
|
-
const conditions = [
|
|
9683
|
-
|
|
9755
|
+
const conditions = [
|
|
9756
|
+
eq(tasks2.id, id),
|
|
9757
|
+
or(
|
|
9758
|
+
eq(tasks2.status, "pending"),
|
|
9759
|
+
eq(tasks2.status, "running"),
|
|
9760
|
+
eq(tasks2.status, "failed")
|
|
9761
|
+
),
|
|
9762
|
+
...this.buildScopeWhere(scope)
|
|
9763
|
+
];
|
|
9764
|
+
const result = await db.update(tasks2).set({
|
|
9765
|
+
status: "cancelled",
|
|
9766
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
9767
|
+
retryAfter: null
|
|
9768
|
+
}).where(and(...conditions)).returning();
|
|
9684
9769
|
return result[0] || null;
|
|
9685
9770
|
}
|
|
9686
9771
|
static async retry(id, scope = {}) {
|
|
@@ -9692,7 +9777,8 @@ var init_task_service = __esm({
|
|
|
9692
9777
|
status: "pending",
|
|
9693
9778
|
startedAt: null,
|
|
9694
9779
|
finishedAt: null,
|
|
9695
|
-
retryAfter: null
|
|
9780
|
+
retryAfter: null,
|
|
9781
|
+
retryCount: 0
|
|
9696
9782
|
}).where(and(...conditions)).returning();
|
|
9697
9783
|
return result[0] || null;
|
|
9698
9784
|
}
|
|
@@ -9706,7 +9792,8 @@ var init_task_service = __esm({
|
|
|
9706
9792
|
status: "pending",
|
|
9707
9793
|
startedAt: null,
|
|
9708
9794
|
finishedAt: null,
|
|
9709
|
-
retryAfter: null
|
|
9795
|
+
retryAfter: null,
|
|
9796
|
+
retryCount: 0
|
|
9710
9797
|
}).where(and(...conditions)).returning();
|
|
9711
9798
|
return result.length;
|
|
9712
9799
|
}
|
|
@@ -9774,6 +9861,9 @@ var init_task_service = __esm({
|
|
|
9774
9861
|
}
|
|
9775
9862
|
static async delete(id, scope = {}) {
|
|
9776
9863
|
const conditions = [eq(tasks2.id, id), ...this.buildScopeWhere(scope)];
|
|
9864
|
+
const existing = await db.select({ id: tasks2.id }).from(tasks2).where(and(...conditions)).limit(1);
|
|
9865
|
+
if (!existing[0]) return false;
|
|
9866
|
+
await db.delete(taskRuns2).where(eq(taskRuns2.taskId, id));
|
|
9777
9867
|
const result = await db.delete(tasks2).where(and(...conditions)).returning();
|
|
9778
9868
|
return result.length > 0;
|
|
9779
9869
|
}
|
|
@@ -9992,7 +10082,7 @@ var require_CronField = __commonJS({
|
|
|
9992
10082
|
if (!isValidRange) {
|
|
9993
10083
|
throw new Error(`${this.constructor.name} Validation error, got value ${badValue} expected range ${this.min}-${this.max}${charsString}`);
|
|
9994
10084
|
}
|
|
9995
|
-
const duplicate = this.#values.find((value,
|
|
10085
|
+
const duplicate = this.#values.find((value, index2) => this.#values.indexOf(value) !== index2);
|
|
9996
10086
|
if (duplicate) {
|
|
9997
10087
|
throw new Error(`${this.constructor.name} Validation error, duplicate values found: ${duplicate}`);
|
|
9998
10088
|
}
|
|
@@ -17837,11 +17927,11 @@ var require_CronFieldCollection = __commonJS({
|
|
|
17837
17927
|
throw new Error("Unexpected range end");
|
|
17838
17928
|
}
|
|
17839
17929
|
if (step * multiplier > range.end) {
|
|
17840
|
-
const mapFn = (_,
|
|
17930
|
+
const mapFn = (_, index2) => {
|
|
17841
17931
|
if (typeof range.start !== "number") {
|
|
17842
17932
|
throw new Error("Unexpected range start");
|
|
17843
17933
|
}
|
|
17844
|
-
return
|
|
17934
|
+
return index2 % step === 0 ? range.start + index2 : null;
|
|
17845
17935
|
};
|
|
17846
17936
|
if (typeof range.start !== "number") {
|
|
17847
17937
|
throw new Error("Unexpected range start");
|
|
@@ -18733,9 +18823,9 @@ var require_CronExpressionParser = __commonJS({
|
|
|
18733
18823
|
if (field === CronUnit.DayOfWeek && max % 7 === 0) {
|
|
18734
18824
|
stack.push(0);
|
|
18735
18825
|
}
|
|
18736
|
-
for (let
|
|
18737
|
-
if (stack.indexOf(
|
|
18738
|
-
stack.push(
|
|
18826
|
+
for (let index2 = min; index2 <= max; index2 += repeatInterval) {
|
|
18827
|
+
if (stack.indexOf(index2) === -1) {
|
|
18828
|
+
stack.push(index2);
|
|
18739
18829
|
}
|
|
18740
18830
|
}
|
|
18741
18831
|
return stack;
|
|
@@ -18858,8 +18948,8 @@ var require_CronFileParser = __commonJS({
|
|
|
18858
18948
|
* @throws If file cannot be read
|
|
18859
18949
|
*/
|
|
18860
18950
|
static parseFileSync(filePath) {
|
|
18861
|
-
const { readFileSync:
|
|
18862
|
-
const data =
|
|
18951
|
+
const { readFileSync: readFileSync5 } = __require("fs");
|
|
18952
|
+
const data = readFileSync5(filePath, "utf8");
|
|
18863
18953
|
return _CronFileParser.#parseContent(data);
|
|
18864
18954
|
}
|
|
18865
18955
|
/**
|
|
@@ -18958,11 +19048,6 @@ var require_dist = __commonJS({
|
|
|
18958
19048
|
});
|
|
18959
19049
|
|
|
18960
19050
|
// src/core/cron-parser.ts
|
|
18961
|
-
var cron_parser_exports = {};
|
|
18962
|
-
__export(cron_parser_exports, {
|
|
18963
|
-
getNextCronRun: () => getNextCronRun,
|
|
18964
|
-
isValidCronExpr: () => isValidCronExpr
|
|
18965
|
-
});
|
|
18966
19051
|
function getNextCronRun(expr, afterMs) {
|
|
18967
19052
|
try {
|
|
18968
19053
|
const fromDate = afterMs != null ? new Date(afterMs) : /* @__PURE__ */ new Date();
|
|
@@ -19000,6 +19085,7 @@ var init_task_template_service = __esm({
|
|
|
19000
19085
|
({ taskTemplates: taskTemplates2 } = schema_exports);
|
|
19001
19086
|
TaskTemplateService = class {
|
|
19002
19087
|
static async create(data) {
|
|
19088
|
+
this.validate(data);
|
|
19003
19089
|
const now = Date.now();
|
|
19004
19090
|
const result = await db.insert(taskTemplates2).values({ ...data, createdAt: now, updatedAt: now }).returning();
|
|
19005
19091
|
const tmpl = result[0];
|
|
@@ -19015,8 +19101,38 @@ var init_task_template_service = __esm({
|
|
|
19015
19101
|
}
|
|
19016
19102
|
return tmpl;
|
|
19017
19103
|
}
|
|
19104
|
+
static validate(data) {
|
|
19105
|
+
if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
19106
|
+
if (!data.agent.trim()) throw new Error("agent \u4E0D\u80FD\u4E3A\u7A7A");
|
|
19107
|
+
if (!data.prompt.trim()) throw new Error("prompt \u4E0D\u80FD\u4E3A\u7A7A");
|
|
19108
|
+
const scheduleType = data.scheduleType;
|
|
19109
|
+
if (!["cron", "delayed", "recurring"].includes(scheduleType)) {
|
|
19110
|
+
throw new Error("scheduleType \u5FC5\u987B\u662F cron\u3001delayed \u6216 recurring");
|
|
19111
|
+
}
|
|
19112
|
+
if (scheduleType === "cron" && (!data.cronExpr || !isValidCronExpr(data.cronExpr))) {
|
|
19113
|
+
throw new Error("cronExpr \u7F3A\u5931\u6216\u683C\u5F0F\u65E0\u6548");
|
|
19114
|
+
}
|
|
19115
|
+
if (scheduleType === "recurring" && (!Number.isInteger(data.intervalMs) || (data.intervalMs ?? 0) <= 0)) {
|
|
19116
|
+
throw new Error("intervalMs \u5FC5\u987B\u662F\u6B63\u6574\u6570");
|
|
19117
|
+
}
|
|
19118
|
+
if (scheduleType === "delayed" && (!Number.isInteger(data.runAt) || (data.runAt ?? 0) <= 0)) {
|
|
19119
|
+
throw new Error("runAt \u5FC5\u987B\u662F\u6B63\u6574\u6570\u65F6\u95F4\u6233");
|
|
19120
|
+
}
|
|
19121
|
+
this.validateInteger("importance", data.importance, 1, 5);
|
|
19122
|
+
this.validateInteger("urgency", data.urgency, 1, 5);
|
|
19123
|
+
this.validateInteger("maxInstances", data.maxInstances, 1, 1e3);
|
|
19124
|
+
this.validateInteger("maxRetries", data.maxRetries, 0, 1e3);
|
|
19125
|
+
this.validateInteger("retryBackoffMs", data.retryBackoffMs, 0, 864e5);
|
|
19126
|
+
this.validateInteger("timeoutMs", data.timeoutMs, 1e3, 6048e5);
|
|
19127
|
+
}
|
|
19128
|
+
static validateInteger(name, value, min, max) {
|
|
19129
|
+
if (value === void 0 || value === null) return;
|
|
19130
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
19131
|
+
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
19132
|
+
}
|
|
19133
|
+
}
|
|
19018
19134
|
static async list(limit = 50) {
|
|
19019
|
-
return await db.select().from(taskTemplates2).orderBy(desc(taskTemplates2.createdAt)).limit(limit);
|
|
19135
|
+
return await db.select().from(taskTemplates2).orderBy(desc(taskTemplates2.createdAt), desc(taskTemplates2.id)).limit(limit);
|
|
19020
19136
|
}
|
|
19021
19137
|
static async getById(id) {
|
|
19022
19138
|
const result = await db.select().from(taskTemplates2).where(eq(taskTemplates2.id, id));
|
|
@@ -19059,137 +19175,554 @@ var init_task_template_service = __esm({
|
|
|
19059
19175
|
// src/gateway/config.ts
|
|
19060
19176
|
var config_exports = {};
|
|
19061
19177
|
__export(config_exports, {
|
|
19062
|
-
|
|
19063
|
-
|
|
19178
|
+
DEFAULT_CONFIG: () => DEFAULT_CONFIG,
|
|
19179
|
+
DEFAULT_CONFIG_PATH: () => DEFAULT_CONFIG_PATH,
|
|
19180
|
+
getConfigPath: () => getConfigPath,
|
|
19181
|
+
loadConfig: () => loadConfig,
|
|
19182
|
+
validateConfig: () => validateConfig
|
|
19064
19183
|
});
|
|
19065
|
-
import {
|
|
19184
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
19066
19185
|
import { homedir as homedir2 } from "os";
|
|
19067
19186
|
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
|
-
}
|
|
19187
|
+
function getConfigPath() {
|
|
19188
|
+
return process.env.SUPERTASK_CONFIG_PATH ?? DEFAULT_CONFIG_PATH;
|
|
19189
|
+
}
|
|
19190
|
+
function objectAt(value, path) {
|
|
19191
|
+
if (value === void 0) return {};
|
|
19192
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
19193
|
+
throw new Error(`${path} \u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
19082
19194
|
}
|
|
19083
|
-
return
|
|
19195
|
+
return value;
|
|
19196
|
+
}
|
|
19197
|
+
function integerAt(source, key, fallback, min, max, path) {
|
|
19198
|
+
const value = source[key];
|
|
19199
|
+
if (value === void 0) return fallback;
|
|
19200
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
|
|
19201
|
+
throw new Error(`${path}.${key} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
19202
|
+
}
|
|
19203
|
+
return value;
|
|
19204
|
+
}
|
|
19205
|
+
function booleanAt(source, key, fallback, path) {
|
|
19206
|
+
const value = source[key];
|
|
19207
|
+
if (value === void 0) return fallback;
|
|
19208
|
+
if (typeof value !== "boolean") throw new Error(`${path}.${key} \u5FC5\u987B\u662F\u5E03\u5C14\u503C`);
|
|
19209
|
+
return value;
|
|
19084
19210
|
}
|
|
19085
|
-
function
|
|
19086
|
-
|
|
19087
|
-
|
|
19211
|
+
function validateConfig(input) {
|
|
19212
|
+
const root = objectAt(input, "config");
|
|
19213
|
+
const version2 = root.configVersion ?? 1;
|
|
19214
|
+
if (version2 !== 1 && version2 !== 2) {
|
|
19215
|
+
throw new Error("config.configVersion \u53EA\u652F\u6301 1 \u6216 2");
|
|
19216
|
+
}
|
|
19217
|
+
const worker = objectAt(root.worker, "worker");
|
|
19218
|
+
const scheduler = objectAt(root.scheduler, "scheduler");
|
|
19219
|
+
const watchdog = objectAt(root.watchdog, "watchdog");
|
|
19220
|
+
const dashboard = objectAt(root.dashboard, "dashboard");
|
|
19221
|
+
const heartbeatIntervalMs = integerAt(worker, "heartbeatIntervalMs", DEFAULT_CONFIG.worker.heartbeatIntervalMs, 1e3, 36e5, "worker");
|
|
19222
|
+
const heartbeatTimeoutMs = integerAt(watchdog, "heartbeatTimeoutMs", DEFAULT_CONFIG.watchdog.heartbeatTimeoutMs, 1e3, 864e5, "watchdog");
|
|
19223
|
+
let checkIntervalMs;
|
|
19224
|
+
let cleanupIntervalMs;
|
|
19225
|
+
if (version2 === 1 && watchdog.checkIntervalMs === void 0 && watchdog.cleanupIntervalMs !== void 0) {
|
|
19226
|
+
checkIntervalMs = integerAt(watchdog, "cleanupIntervalMs", DEFAULT_CONFIG.watchdog.checkIntervalMs, 1e3, 36e5, "watchdog");
|
|
19227
|
+
cleanupIntervalMs = DEFAULT_CONFIG.watchdog.cleanupIntervalMs;
|
|
19228
|
+
} else {
|
|
19229
|
+
checkIntervalMs = integerAt(watchdog, "checkIntervalMs", DEFAULT_CONFIG.watchdog.checkIntervalMs, 1e3, 36e5, "watchdog");
|
|
19230
|
+
cleanupIntervalMs = integerAt(watchdog, "cleanupIntervalMs", DEFAULT_CONFIG.watchdog.cleanupIntervalMs, 6e4, 6048e5, "watchdog");
|
|
19231
|
+
}
|
|
19232
|
+
if (heartbeatIntervalMs >= heartbeatTimeoutMs) {
|
|
19233
|
+
throw new Error("worker.heartbeatIntervalMs \u5FC5\u987B\u5C0F\u4E8E watchdog.heartbeatTimeoutMs");
|
|
19088
19234
|
}
|
|
19235
|
+
if (checkIntervalMs > heartbeatTimeoutMs) {
|
|
19236
|
+
throw new Error("watchdog.checkIntervalMs \u4E0D\u80FD\u5927\u4E8E watchdog.heartbeatTimeoutMs");
|
|
19237
|
+
}
|
|
19238
|
+
return {
|
|
19239
|
+
configVersion: 2,
|
|
19240
|
+
worker: {
|
|
19241
|
+
maxConcurrency: integerAt(worker, "maxConcurrency", DEFAULT_CONFIG.worker.maxConcurrency, 1, 64, "worker"),
|
|
19242
|
+
pollIntervalMs: integerAt(worker, "pollIntervalMs", DEFAULT_CONFIG.worker.pollIntervalMs, 50, 6e4, "worker"),
|
|
19243
|
+
heartbeatIntervalMs,
|
|
19244
|
+
taskTimeoutMs: integerAt(worker, "taskTimeoutMs", DEFAULT_CONFIG.worker.taskTimeoutMs, 1e3, 6048e5, "worker"),
|
|
19245
|
+
shutdownGracePeriodMs: integerAt(worker, "shutdownGracePeriodMs", DEFAULT_CONFIG.worker.shutdownGracePeriodMs, 0, 36e5, "worker")
|
|
19246
|
+
},
|
|
19247
|
+
scheduler: {
|
|
19248
|
+
enabled: booleanAt(scheduler, "enabled", DEFAULT_CONFIG.scheduler.enabled, "scheduler"),
|
|
19249
|
+
checkIntervalMs: integerAt(scheduler, "checkIntervalMs", DEFAULT_CONFIG.scheduler.checkIntervalMs, 100, 6e4, "scheduler")
|
|
19250
|
+
},
|
|
19251
|
+
watchdog: {
|
|
19252
|
+
heartbeatTimeoutMs,
|
|
19253
|
+
checkIntervalMs,
|
|
19254
|
+
cleanupIntervalMs,
|
|
19255
|
+
retentionDays: integerAt(watchdog, "retentionDays", DEFAULT_CONFIG.watchdog.retentionDays, 1, 3650, "watchdog")
|
|
19256
|
+
},
|
|
19257
|
+
dashboard: {
|
|
19258
|
+
enabled: booleanAt(dashboard, "enabled", DEFAULT_CONFIG.dashboard.enabled, "dashboard"),
|
|
19259
|
+
port: integerAt(dashboard, "port", DEFAULT_CONFIG.dashboard.port, 1, 65535, "dashboard")
|
|
19260
|
+
}
|
|
19261
|
+
};
|
|
19262
|
+
}
|
|
19263
|
+
function loadConfig(path = getConfigPath()) {
|
|
19264
|
+
if (!existsSync2(path)) return validateConfig({ configVersion: 2 });
|
|
19089
19265
|
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;
|
|
19266
|
+
return validateConfig(JSON.parse(readFileSync(path, "utf-8")));
|
|
19267
|
+
} catch (error) {
|
|
19268
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19269
|
+
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E ${path}: ${message}`);
|
|
19097
19270
|
}
|
|
19098
19271
|
}
|
|
19099
|
-
var DEFAULT_CONFIG,
|
|
19272
|
+
var DEFAULT_CONFIG, DEFAULT_CONFIG_PATH;
|
|
19100
19273
|
var init_config = __esm({
|
|
19101
19274
|
"src/gateway/config.ts"() {
|
|
19102
19275
|
"use strict";
|
|
19103
19276
|
DEFAULT_CONFIG = {
|
|
19277
|
+
configVersion: 2,
|
|
19104
19278
|
worker: {
|
|
19105
19279
|
maxConcurrency: 2,
|
|
19106
19280
|
pollIntervalMs: 1e3,
|
|
19107
19281
|
heartbeatIntervalMs: 3e4,
|
|
19108
|
-
taskTimeoutMs: 18e5
|
|
19282
|
+
taskTimeoutMs: 18e5,
|
|
19283
|
+
shutdownGracePeriodMs: 3e4
|
|
19109
19284
|
},
|
|
19110
19285
|
scheduler: {
|
|
19111
19286
|
enabled: true,
|
|
19112
|
-
checkIntervalMs: 1e3
|
|
19113
|
-
catchUp: "next"
|
|
19287
|
+
checkIntervalMs: 1e3
|
|
19114
19288
|
},
|
|
19115
19289
|
watchdog: {
|
|
19116
19290
|
heartbeatTimeoutMs: 6e5,
|
|
19117
|
-
|
|
19291
|
+
checkIntervalMs: 6e4,
|
|
19292
|
+
cleanupIntervalMs: 864e5,
|
|
19118
19293
|
retentionDays: 30
|
|
19119
19294
|
},
|
|
19120
19295
|
dashboard: {
|
|
19121
19296
|
enabled: true,
|
|
19122
19297
|
port: 4680
|
|
19123
|
-
},
|
|
19124
|
-
logging: {
|
|
19125
|
-
level: "info",
|
|
19126
|
-
format: "json"
|
|
19127
19298
|
}
|
|
19128
19299
|
};
|
|
19129
|
-
|
|
19300
|
+
DEFAULT_CONFIG_PATH = join2(homedir2(), ".config/opencode/supertask.json");
|
|
19301
|
+
}
|
|
19302
|
+
});
|
|
19303
|
+
|
|
19304
|
+
// src/daemon/pm2.ts
|
|
19305
|
+
var pm2_exports = {};
|
|
19306
|
+
__export(pm2_exports, {
|
|
19307
|
+
ensureGateway: () => ensureGateway,
|
|
19308
|
+
getPackageVersion: () => getPackageVersion,
|
|
19309
|
+
install: () => install,
|
|
19310
|
+
installMacLaunchAgent: () => installMacLaunchAgent,
|
|
19311
|
+
isGatewayReady: () => isGatewayReady,
|
|
19312
|
+
isGatewayRunning: () => isGatewayRunning,
|
|
19313
|
+
isPm2Installed: () => isPm2Installed,
|
|
19314
|
+
resolveGatewayEntry: () => resolveGatewayEntry,
|
|
19315
|
+
uninstall: () => uninstall,
|
|
19316
|
+
upgrade: () => upgrade
|
|
19317
|
+
});
|
|
19318
|
+
import { execSync, spawnSync } from "child_process";
|
|
19319
|
+
import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
19320
|
+
import { homedir as homedir3 } from "os";
|
|
19321
|
+
import { dirname as dirname2, isAbsolute, join as join3 } from "path";
|
|
19322
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
19323
|
+
import { Database as Database3 } from "bun:sqlite";
|
|
19324
|
+
function getPackageVersion() {
|
|
19325
|
+
const envVersion = process.env.npm_package_version;
|
|
19326
|
+
if (envVersion) return envVersion;
|
|
19327
|
+
try {
|
|
19328
|
+
const pkgPath = join3(__dirname, "../../package.json");
|
|
19329
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
19330
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
19331
|
+
} catch {
|
|
19332
|
+
return "0.0.0";
|
|
19333
|
+
}
|
|
19334
|
+
}
|
|
19335
|
+
function resolveGatewayEntry() {
|
|
19336
|
+
const override = process.env.SUPERTASK_GATEWAY_ENTRY;
|
|
19337
|
+
if (override) {
|
|
19338
|
+
if (!existsSync3(override)) throw new Error(`[supertask] Gateway entry not found: ${override}`);
|
|
19339
|
+
return override;
|
|
19340
|
+
}
|
|
19341
|
+
const candidates = [
|
|
19342
|
+
join3(__dirname, "../gateway/index.js"),
|
|
19343
|
+
join3(__dirname, "../gateway/index.ts")
|
|
19344
|
+
];
|
|
19345
|
+
const entry = candidates.find((candidate) => existsSync3(candidate));
|
|
19346
|
+
if (!entry) throw new Error(`[supertask] Gateway entry not found. Checked: ${candidates.join(", ")}`);
|
|
19347
|
+
return entry;
|
|
19348
|
+
}
|
|
19349
|
+
function versionFile() {
|
|
19350
|
+
return process.env.SUPERTASK_VERSION_FILE ?? join3(homedir3(), ".local/share/opencode/supertask-gateway-version");
|
|
19351
|
+
}
|
|
19352
|
+
function getRunningVersion() {
|
|
19353
|
+
try {
|
|
19354
|
+
const path = versionFile();
|
|
19355
|
+
if (!existsSync3(path)) return null;
|
|
19356
|
+
return readFileSync2(path, "utf-8").trim() || null;
|
|
19357
|
+
} catch {
|
|
19358
|
+
return null;
|
|
19359
|
+
}
|
|
19360
|
+
}
|
|
19361
|
+
function writeRunningVersion(version2) {
|
|
19362
|
+
const path = versionFile();
|
|
19363
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
19364
|
+
writeFileSync(path, version2, "utf-8");
|
|
19365
|
+
}
|
|
19366
|
+
function pm2Bin() {
|
|
19367
|
+
return process.env.SUPERTASK_PM2_BIN ?? (process.platform === "win32" ? "pm2.cmd" : "pm2");
|
|
19368
|
+
}
|
|
19369
|
+
function xmlEscape(value) {
|
|
19370
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
19371
|
+
}
|
|
19372
|
+
function resolvePm2Bin() {
|
|
19373
|
+
const configured = pm2Bin();
|
|
19374
|
+
if (isAbsolute(configured)) return configured;
|
|
19375
|
+
const result = spawnSync("which", [configured], { encoding: "utf8" });
|
|
19376
|
+
const resolved = result.status === 0 ? result.stdout.trim().split("\n")[0] : "";
|
|
19377
|
+
if (!resolved) throw new Error(`[supertask] \u65E0\u6CD5\u89E3\u6790 pm2 \u53EF\u6267\u884C\u6587\u4EF6: ${configured}`);
|
|
19378
|
+
return resolved;
|
|
19379
|
+
}
|
|
19380
|
+
function launchAgentPath() {
|
|
19381
|
+
return process.env.SUPERTASK_LAUNCH_AGENT_PATH ?? join3(homedir3(), "Library/LaunchAgents", `${MAC_LAUNCH_AGENT_LABEL}.plist`);
|
|
19382
|
+
}
|
|
19383
|
+
function launchctlBin() {
|
|
19384
|
+
return process.env.SUPERTASK_LAUNCHCTL_BIN ?? "launchctl";
|
|
19385
|
+
}
|
|
19386
|
+
function installMacLaunchAgent() {
|
|
19387
|
+
if (typeof process.getuid !== "function") {
|
|
19388
|
+
throw new Error("[supertask] \u5F53\u524D\u8FD0\u884C\u65F6\u65E0\u6CD5\u83B7\u53D6 macOS \u7528\u6237 ID");
|
|
19389
|
+
}
|
|
19390
|
+
const path = launchAgentPath();
|
|
19391
|
+
const home = homedir3();
|
|
19392
|
+
const pm2Home = process.env.PM2_HOME ?? join3(home, ".pm2");
|
|
19393
|
+
const environmentPath = process.env.PATH ?? "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
19394
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
19395
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
19396
|
+
<plist version="1.0">
|
|
19397
|
+
<dict>
|
|
19398
|
+
<key>Label</key>
|
|
19399
|
+
<string>${MAC_LAUNCH_AGENT_LABEL}</string>
|
|
19400
|
+
<key>ProgramArguments</key>
|
|
19401
|
+
<array>
|
|
19402
|
+
<string>${xmlEscape(resolvePm2Bin())}</string>
|
|
19403
|
+
<string>resurrect</string>
|
|
19404
|
+
</array>
|
|
19405
|
+
<key>RunAtLoad</key>
|
|
19406
|
+
<true/>
|
|
19407
|
+
<key>KeepAlive</key>
|
|
19408
|
+
<dict>
|
|
19409
|
+
<key>SuccessfulExit</key>
|
|
19410
|
+
<false/>
|
|
19411
|
+
</dict>
|
|
19412
|
+
<key>ThrottleInterval</key>
|
|
19413
|
+
<integer>10</integer>
|
|
19414
|
+
<key>EnvironmentVariables</key>
|
|
19415
|
+
<dict>
|
|
19416
|
+
<key>HOME</key>
|
|
19417
|
+
<string>${xmlEscape(home)}</string>
|
|
19418
|
+
<key>PATH</key>
|
|
19419
|
+
<string>${xmlEscape(environmentPath)}</string>
|
|
19420
|
+
<key>PM2_HOME</key>
|
|
19421
|
+
<string>${xmlEscape(pm2Home)}</string>
|
|
19422
|
+
</dict>
|
|
19423
|
+
<key>StandardErrorPath</key>
|
|
19424
|
+
<string>${xmlEscape(join3(pm2Home, "supertask-launchd-error.log"))}</string>
|
|
19425
|
+
<key>StandardOutPath</key>
|
|
19426
|
+
<string>${xmlEscape(join3(pm2Home, "supertask-launchd-output.log"))}</string>
|
|
19427
|
+
</dict>
|
|
19428
|
+
</plist>
|
|
19429
|
+
`;
|
|
19430
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
19431
|
+
writeFileSync(path, plist, { mode: 384 });
|
|
19432
|
+
chmodSync(path, 384);
|
|
19433
|
+
const domain = `gui/${process.getuid()}`;
|
|
19434
|
+
spawnSync(launchctlBin(), ["bootout", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`], {
|
|
19435
|
+
stdio: "ignore"
|
|
19436
|
+
});
|
|
19437
|
+
const loaded = spawnSync(launchctlBin(), ["bootstrap", domain, path], {
|
|
19438
|
+
encoding: "utf8"
|
|
19439
|
+
});
|
|
19440
|
+
if (loaded.status !== 0) {
|
|
19441
|
+
const output = `${loaded.stdout ?? ""}${loaded.stderr ?? ""}`.trim();
|
|
19442
|
+
throw new Error(`[supertask] macOS LaunchAgent \u52A0\u8F7D\u5931\u8D25: ${output || `\u9000\u51FA\u7801 ${loaded.status}`}`);
|
|
19443
|
+
}
|
|
19444
|
+
return path;
|
|
19445
|
+
}
|
|
19446
|
+
function isPm2Installed() {
|
|
19447
|
+
const result = spawnSync(pm2Bin(), ["--version"], {
|
|
19448
|
+
stdio: "ignore",
|
|
19449
|
+
shell: process.platform === "win32"
|
|
19450
|
+
});
|
|
19451
|
+
return result.status === 0;
|
|
19452
|
+
}
|
|
19453
|
+
function installPm2() {
|
|
19454
|
+
console.log("[supertask] Installing pm2...");
|
|
19455
|
+
try {
|
|
19456
|
+
execSync("npm install -g pm2", { stdio: "inherit" });
|
|
19457
|
+
return true;
|
|
19458
|
+
} catch {
|
|
19459
|
+
try {
|
|
19460
|
+
execSync("bun install -g pm2", { stdio: "inherit" });
|
|
19461
|
+
return true;
|
|
19462
|
+
} catch {
|
|
19463
|
+
return false;
|
|
19464
|
+
}
|
|
19465
|
+
}
|
|
19466
|
+
}
|
|
19467
|
+
function pm2Exec(args) {
|
|
19468
|
+
const result = spawnSync(pm2Bin(), args, {
|
|
19469
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
19470
|
+
encoding: "utf-8",
|
|
19471
|
+
env: process.env,
|
|
19472
|
+
shell: process.platform === "win32"
|
|
19473
|
+
});
|
|
19474
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
19475
|
+
if (result.error) return { ok: false, output: result.error.message };
|
|
19476
|
+
return { ok: result.status === 0, output };
|
|
19477
|
+
}
|
|
19478
|
+
function requirePm2(args, action) {
|
|
19479
|
+
const result = pm2Exec(args);
|
|
19480
|
+
if (!result.ok) throw new Error(`[supertask] ${action} failed: ${result.output || "unknown pm2 error"}`);
|
|
19481
|
+
return result.output;
|
|
19482
|
+
}
|
|
19483
|
+
function pm2JsonList() {
|
|
19484
|
+
const output = requirePm2(["jlist"], "pm2 jlist");
|
|
19485
|
+
try {
|
|
19486
|
+
const parsed = JSON.parse(output);
|
|
19487
|
+
if (!Array.isArray(parsed)) throw new Error("result is not an array");
|
|
19488
|
+
return parsed;
|
|
19489
|
+
} catch (error) {
|
|
19490
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19491
|
+
throw new Error(`[supertask] Invalid pm2 jlist output: ${message}`);
|
|
19492
|
+
}
|
|
19493
|
+
}
|
|
19494
|
+
function isGatewayRunning() {
|
|
19495
|
+
if (!isPm2Installed()) return false;
|
|
19496
|
+
const proc = pm2JsonList().find((item) => item.name === PROCESS_NAME);
|
|
19497
|
+
return proc?.pm2_env?.status === "online" && typeof proc.pid === "number" && isGatewayReady(proc.pid);
|
|
19498
|
+
}
|
|
19499
|
+
function databasePath() {
|
|
19500
|
+
return process.env.SUPERTASK_DB_PATH ?? join3(homedir3(), ".local/share/opencode/tasks.db");
|
|
19501
|
+
}
|
|
19502
|
+
function isGatewayReady(expectedPid) {
|
|
19503
|
+
const path = databasePath();
|
|
19504
|
+
if (!existsSync3(path)) return false;
|
|
19505
|
+
let database = null;
|
|
19506
|
+
try {
|
|
19507
|
+
database = new Database3(path, { readonly: true });
|
|
19508
|
+
const row = database.query(
|
|
19509
|
+
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
19510
|
+
).get();
|
|
19511
|
+
if (!row || row.ready_at == null) return false;
|
|
19512
|
+
if (expectedPid !== void 0 && row.pid !== expectedPid) return false;
|
|
19513
|
+
const ageMs = Date.now() - row.heartbeat_at;
|
|
19514
|
+
return ageMs >= -5e3 && ageMs < GATEWAY_LOCK_STALE_MS;
|
|
19515
|
+
} catch {
|
|
19516
|
+
return false;
|
|
19517
|
+
} finally {
|
|
19518
|
+
database?.close();
|
|
19519
|
+
}
|
|
19520
|
+
}
|
|
19521
|
+
function readyTimeoutMs() {
|
|
19522
|
+
const value = Number(process.env.SUPERTASK_GATEWAY_READY_TIMEOUT_MS ?? 3e4);
|
|
19523
|
+
return Number.isFinite(value) && value > 0 ? value : 3e4;
|
|
19524
|
+
}
|
|
19525
|
+
function waitForGatewayReady(pid) {
|
|
19526
|
+
const deadline = Date.now() + readyTimeoutMs();
|
|
19527
|
+
const sleeper = new Int32Array(new SharedArrayBuffer(4));
|
|
19528
|
+
while (Date.now() < deadline) {
|
|
19529
|
+
if (isGatewayReady(pid)) return true;
|
|
19530
|
+
Atomics.wait(sleeper, 0, 0, 100);
|
|
19531
|
+
}
|
|
19532
|
+
return isGatewayReady(pid);
|
|
19533
|
+
}
|
|
19534
|
+
function findBunPath() {
|
|
19535
|
+
const override = process.env.SUPERTASK_BUN_BIN;
|
|
19536
|
+
if (override) return override;
|
|
19537
|
+
try {
|
|
19538
|
+
const command = process.platform === "win32" ? "where bun" : "which bun";
|
|
19539
|
+
return execSync(command, { stdio: "pipe" }).toString().trim().split("\n")[0];
|
|
19540
|
+
} catch {
|
|
19541
|
+
return process.execPath;
|
|
19542
|
+
}
|
|
19543
|
+
}
|
|
19544
|
+
function pm2StartGateway(gatewayEntry = resolveGatewayEntry()) {
|
|
19545
|
+
const configuredKillTimeout = Number(process.env.SUPERTASK_PM2_KILL_TIMEOUT_MS);
|
|
19546
|
+
const killTimeoutMs = Number.isInteger(configuredKillTimeout) && configuredKillTimeout >= 5e3 ? configuredKillTimeout : loadConfig().worker.shutdownGracePeriodMs + 5e3;
|
|
19547
|
+
requirePm2([
|
|
19548
|
+
"start",
|
|
19549
|
+
findBunPath(),
|
|
19550
|
+
"--name",
|
|
19551
|
+
PROCESS_NAME,
|
|
19552
|
+
"--interpreter",
|
|
19553
|
+
"none",
|
|
19554
|
+
"--restart-delay",
|
|
19555
|
+
"5000",
|
|
19556
|
+
"--max-restarts",
|
|
19557
|
+
"30",
|
|
19558
|
+
"--kill-timeout",
|
|
19559
|
+
String(killTimeoutMs),
|
|
19560
|
+
"--",
|
|
19561
|
+
gatewayEntry
|
|
19562
|
+
], "pm2 start");
|
|
19563
|
+
const started = pm2JsonList().find((item) => item.name === PROCESS_NAME);
|
|
19564
|
+
if (started?.pm2_env?.status !== "online") {
|
|
19565
|
+
throw new Error(`[supertask] Gateway did not become online (status: ${started?.pm2_env?.status ?? "missing"})`);
|
|
19566
|
+
}
|
|
19567
|
+
if (typeof started.pid !== "number" || !waitForGatewayReady(started.pid)) {
|
|
19568
|
+
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");
|
|
19569
|
+
}
|
|
19570
|
+
}
|
|
19571
|
+
function savePm2State() {
|
|
19572
|
+
requirePm2(["save"], "pm2 save");
|
|
19573
|
+
}
|
|
19574
|
+
function install() {
|
|
19575
|
+
if (!isPm2Installed() && !installPm2()) {
|
|
19576
|
+
throw new Error("[supertask] Failed to install pm2. Please install it manually: npm install -g pm2");
|
|
19577
|
+
}
|
|
19578
|
+
const existing = pm2JsonList().find((item) => item.name === PROCESS_NAME);
|
|
19579
|
+
if (existing) requirePm2(["delete", PROCESS_NAME], "pm2 delete existing Gateway");
|
|
19580
|
+
const version2 = getPackageVersion();
|
|
19581
|
+
pm2StartGateway();
|
|
19582
|
+
writeRunningVersion(version2);
|
|
19583
|
+
savePm2State();
|
|
19584
|
+
if (process.platform === "darwin") {
|
|
19585
|
+
try {
|
|
19586
|
+
const path = installMacLaunchAgent();
|
|
19587
|
+
console.log(`[supertask] macOS LaunchAgent installed: ${path}`);
|
|
19588
|
+
} catch (error) {
|
|
19589
|
+
console.warn(error instanceof Error ? error.message : String(error));
|
|
19590
|
+
}
|
|
19591
|
+
} else {
|
|
19592
|
+
const startup = pm2Exec(["startup"]);
|
|
19593
|
+
if (startup.output) console.log(startup.output);
|
|
19594
|
+
if (!startup.ok) {
|
|
19595
|
+
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");
|
|
19596
|
+
}
|
|
19597
|
+
}
|
|
19598
|
+
console.log("[supertask] Gateway installed and running.");
|
|
19599
|
+
console.log("[supertask] Manage with: pm2 status / pm2 logs supertask-gateway");
|
|
19600
|
+
}
|
|
19601
|
+
function uninstall() {
|
|
19602
|
+
if (!isPm2Installed()) throw new Error("[supertask] pm2 is not installed");
|
|
19603
|
+
const existing = pm2JsonList().find((item) => item.name === PROCESS_NAME);
|
|
19604
|
+
if (existing) requirePm2(["delete", PROCESS_NAME], "pm2 delete Gateway");
|
|
19605
|
+
savePm2State();
|
|
19606
|
+
console.log("[supertask] Gateway removed from pm2. Other pm2 startup entries were preserved.");
|
|
19607
|
+
}
|
|
19608
|
+
function upgrade(target) {
|
|
19609
|
+
if (!isPm2Installed()) {
|
|
19610
|
+
throw new Error("[supertask] pm2 is not installed. Run `supertask install` first.");
|
|
19611
|
+
}
|
|
19612
|
+
const before = getRunningVersion();
|
|
19613
|
+
const oldGatewayEntry = resolveGatewayEntry();
|
|
19614
|
+
const currentVersion = target?.version ?? getPackageVersion();
|
|
19615
|
+
const existing = pm2JsonList().find((item) => item.name === PROCESS_NAME);
|
|
19616
|
+
if (existing) requirePm2(["delete", PROCESS_NAME], "pm2 delete old Gateway");
|
|
19617
|
+
try {
|
|
19618
|
+
pm2StartGateway(target?.gatewayEntry ?? oldGatewayEntry);
|
|
19619
|
+
writeRunningVersion(currentVersion);
|
|
19620
|
+
savePm2State();
|
|
19621
|
+
return { before, after: currentVersion, restarted: true };
|
|
19622
|
+
} catch (error) {
|
|
19623
|
+
const failed = pm2JsonList().find((item) => item.name === PROCESS_NAME);
|
|
19624
|
+
if (failed) requirePm2(["delete", PROCESS_NAME], "pm2 delete failed Gateway");
|
|
19625
|
+
try {
|
|
19626
|
+
pm2StartGateway(oldGatewayEntry);
|
|
19627
|
+
if (before) writeRunningVersion(before);
|
|
19628
|
+
savePm2State();
|
|
19629
|
+
} catch (rollbackError) {
|
|
19630
|
+
const original = error instanceof Error ? error.message : String(error);
|
|
19631
|
+
const rollback = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
|
|
19632
|
+
throw new Error(`${original}; \u65E7 Gateway \u56DE\u6EDA\u4E5F\u5931\u8D25: ${rollback}`);
|
|
19633
|
+
}
|
|
19634
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19635
|
+
throw new Error(`${message}; \u5DF2\u56DE\u6EDA\u5230\u65E7 Gateway`);
|
|
19636
|
+
}
|
|
19637
|
+
}
|
|
19638
|
+
function ensureGateway() {
|
|
19639
|
+
if (!isPm2Installed()) {
|
|
19640
|
+
return { ok: false, reason: "pm2-not-installed" };
|
|
19641
|
+
}
|
|
19642
|
+
const currentVersion = getPackageVersion();
|
|
19643
|
+
const processList = pm2JsonList();
|
|
19644
|
+
const existing = processList.find((item) => item.name === PROCESS_NAME);
|
|
19645
|
+
if (existing?.pm2_env?.status === "online" && typeof existing.pid === "number" && isGatewayReady(existing.pid) && getRunningVersion() === currentVersion) {
|
|
19646
|
+
return { ok: true, action: "already-running" };
|
|
19647
|
+
}
|
|
19648
|
+
if (existing) requirePm2(["delete", PROCESS_NAME], "pm2 delete stale Gateway");
|
|
19649
|
+
pm2StartGateway();
|
|
19650
|
+
writeRunningVersion(currentVersion);
|
|
19651
|
+
savePm2State();
|
|
19652
|
+
return { ok: true, action: existing ? "restarted" : "started" };
|
|
19653
|
+
}
|
|
19654
|
+
var __dirname, PROCESS_NAME, MAC_LAUNCH_AGENT_LABEL, GATEWAY_LOCK_STALE_MS;
|
|
19655
|
+
var init_pm2 = __esm({
|
|
19656
|
+
"src/daemon/pm2.ts"() {
|
|
19657
|
+
"use strict";
|
|
19658
|
+
init_config();
|
|
19659
|
+
__dirname = dirname2(fileURLToPath2(import.meta.url));
|
|
19660
|
+
PROCESS_NAME = "supertask-gateway";
|
|
19661
|
+
MAC_LAUNCH_AGENT_LABEL = "com.supertask.pm2-resurrect";
|
|
19662
|
+
GATEWAY_LOCK_STALE_MS = 3e4;
|
|
19130
19663
|
}
|
|
19131
19664
|
});
|
|
19132
19665
|
|
|
19133
19666
|
// src/core/services/task-run.service.ts
|
|
19134
|
-
var
|
|
19667
|
+
var taskRuns3, TaskRunService;
|
|
19135
19668
|
var init_task_run_service = __esm({
|
|
19136
19669
|
"src/core/services/task-run.service.ts"() {
|
|
19137
19670
|
"use strict";
|
|
19138
19671
|
init_db2();
|
|
19139
19672
|
init_drizzle_orm();
|
|
19140
|
-
({ taskRuns:
|
|
19673
|
+
({ taskRuns: taskRuns3 } = schema_exports);
|
|
19141
19674
|
TaskRunService = class {
|
|
19142
19675
|
static async create(data) {
|
|
19143
|
-
const result = await db.insert(
|
|
19676
|
+
const result = await db.insert(taskRuns3).values(data).returning();
|
|
19144
19677
|
return result[0];
|
|
19145
19678
|
}
|
|
19146
19679
|
static async updateSessionId(id, sessionId) {
|
|
19147
|
-
const result = await db.update(
|
|
19680
|
+
const result = await db.update(taskRuns3).set({ sessionId }).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
19148
19681
|
return result[0] || null;
|
|
19149
19682
|
}
|
|
19150
19683
|
static async done(id, log) {
|
|
19151
|
-
const result = await db.update(
|
|
19684
|
+
const result = await db.update(taskRuns3).set({
|
|
19152
19685
|
status: "done",
|
|
19153
19686
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
19154
19687
|
log
|
|
19155
|
-
}).where(eq(
|
|
19688
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
19156
19689
|
return result[0] || null;
|
|
19157
19690
|
}
|
|
19158
19691
|
static async fail(id, log) {
|
|
19159
|
-
const result = await db.update(
|
|
19692
|
+
const result = await db.update(taskRuns3).set({
|
|
19160
19693
|
status: "failed",
|
|
19161
19694
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
19162
19695
|
log
|
|
19163
|
-
}).where(eq(
|
|
19696
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
19164
19697
|
return result[0] || null;
|
|
19165
19698
|
}
|
|
19166
19699
|
static async heartbeat(id) {
|
|
19167
|
-
const result = await db.update(
|
|
19700
|
+
const result = await db.update(taskRuns3).set({ heartbeatAt: Date.now() }).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
19168
19701
|
return result[0] || null;
|
|
19169
19702
|
}
|
|
19170
19703
|
static async updatePid(id, workerPid, childPid) {
|
|
19171
|
-
const result = await db.update(
|
|
19704
|
+
const result = await db.update(taskRuns3).set({
|
|
19172
19705
|
workerPid,
|
|
19173
19706
|
childPid,
|
|
19174
19707
|
lockedAt: Date.now(),
|
|
19175
19708
|
lockedBy: `gateway-${process.pid}`
|
|
19176
|
-
}).where(eq(
|
|
19709
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
19177
19710
|
return result[0] || null;
|
|
19178
19711
|
}
|
|
19179
19712
|
static async getById(id) {
|
|
19180
|
-
const result = await db.select().from(
|
|
19713
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.id, id));
|
|
19181
19714
|
return result[0] || null;
|
|
19182
19715
|
}
|
|
19183
19716
|
static async listByTaskId(taskId) {
|
|
19184
|
-
return await db.select().from(
|
|
19717
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
19185
19718
|
}
|
|
19186
19719
|
static async getLatestByTaskId(taskId) {
|
|
19187
|
-
const result = await db.select().from(
|
|
19720
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id)).limit(1);
|
|
19188
19721
|
return result[0] || null;
|
|
19189
19722
|
}
|
|
19190
19723
|
static async getLatestByTaskIds(taskIds) {
|
|
19191
19724
|
if (taskIds.length === 0) return /* @__PURE__ */ new Map();
|
|
19192
|
-
const latestRuns = await db.select().from(
|
|
19725
|
+
const latestRuns = await db.select().from(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
19193
19726
|
const result = /* @__PURE__ */ new Map();
|
|
19194
19727
|
for (const run of latestRuns) {
|
|
19195
19728
|
if (!result.has(run.taskId)) {
|
|
@@ -19203,22 +19736,23 @@ var init_task_run_service = __esm({
|
|
|
19203
19736
|
const cutoffSec = Math.floor(cutoffMs / 1e3);
|
|
19204
19737
|
const { tasks: tasksTable } = schema_exports;
|
|
19205
19738
|
const result = await db.select({
|
|
19206
|
-
runId:
|
|
19207
|
-
taskId:
|
|
19208
|
-
childPid:
|
|
19739
|
+
runId: taskRuns3.id,
|
|
19740
|
+
taskId: taskRuns3.taskId,
|
|
19741
|
+
childPid: taskRuns3.childPid,
|
|
19209
19742
|
taskRetryCount: tasksTable.retryCount,
|
|
19210
|
-
taskMaxRetries: tasksTable.maxRetries
|
|
19211
|
-
|
|
19743
|
+
taskMaxRetries: tasksTable.maxRetries,
|
|
19744
|
+
taskRetryBackoffMs: tasksTable.retryBackoffMs
|
|
19745
|
+
}).from(taskRuns3).innerJoin(tasksTable, eq(taskRuns3.taskId, tasksTable.id)).where(
|
|
19212
19746
|
and(
|
|
19213
|
-
eq(
|
|
19747
|
+
eq(taskRuns3.status, "running"),
|
|
19214
19748
|
or(
|
|
19215
19749
|
and(
|
|
19216
|
-
sql`${
|
|
19217
|
-
sql`${
|
|
19750
|
+
sql`${taskRuns3.heartbeatAt} IS NULL`,
|
|
19751
|
+
sql`${taskRuns3.startedAt} < ${cutoffSec}`
|
|
19218
19752
|
),
|
|
19219
19753
|
and(
|
|
19220
|
-
sql`${
|
|
19221
|
-
sql`${
|
|
19754
|
+
sql`${taskRuns3.heartbeatAt} IS NOT NULL`,
|
|
19755
|
+
sql`${taskRuns3.heartbeatAt} < ${cutoffMs}`
|
|
19222
19756
|
)
|
|
19223
19757
|
)
|
|
19224
19758
|
)
|
|
@@ -19228,65 +19762,243 @@ var init_task_run_service = __esm({
|
|
|
19228
19762
|
taskId: row.taskId,
|
|
19229
19763
|
childPid: row.childPid,
|
|
19230
19764
|
taskRetryCount: row.taskRetryCount ?? 0,
|
|
19231
|
-
taskMaxRetries: row.taskMaxRetries ?? 3
|
|
19765
|
+
taskMaxRetries: row.taskMaxRetries ?? 3,
|
|
19766
|
+
taskRetryBackoffMs: row.taskRetryBackoffMs ?? 3e4
|
|
19232
19767
|
}));
|
|
19233
19768
|
}
|
|
19234
19769
|
static async getRunningRunByTaskId(taskId) {
|
|
19235
|
-
const result = await db.select().from(
|
|
19770
|
+
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
19771
|
return result[0] || null;
|
|
19237
19772
|
}
|
|
19238
19773
|
static async deleteByTaskIds(taskIds) {
|
|
19239
19774
|
if (taskIds.length === 0) return 0;
|
|
19240
|
-
const result = await db.delete(
|
|
19775
|
+
const result = await db.delete(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).returning();
|
|
19241
19776
|
return result.length;
|
|
19242
19777
|
}
|
|
19243
19778
|
static async getAllRunningRuns() {
|
|
19244
|
-
return await db.select().from(
|
|
19779
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.status, "running"));
|
|
19245
19780
|
}
|
|
19246
19781
|
};
|
|
19247
19782
|
}
|
|
19248
19783
|
});
|
|
19249
19784
|
|
|
19785
|
+
// src/gateway/health.ts
|
|
19786
|
+
function initializeGatewayHealth(config) {
|
|
19787
|
+
const now = Date.now();
|
|
19788
|
+
state = {
|
|
19789
|
+
startedAt: now,
|
|
19790
|
+
config,
|
|
19791
|
+
lastActivityAt: {
|
|
19792
|
+
worker: now,
|
|
19793
|
+
scheduler: now,
|
|
19794
|
+
watchdog: now
|
|
19795
|
+
}
|
|
19796
|
+
};
|
|
19797
|
+
}
|
|
19798
|
+
function markGatewayActivity(component) {
|
|
19799
|
+
if (state) state.lastActivityAt[component] = Date.now();
|
|
19800
|
+
}
|
|
19801
|
+
function resetGatewayHealth() {
|
|
19802
|
+
state = null;
|
|
19803
|
+
}
|
|
19804
|
+
function componentStatus(component, enabled, maxAgeMs, now) {
|
|
19805
|
+
const lastActivityAt = state?.lastActivityAt[component] ?? null;
|
|
19806
|
+
const ageMs = lastActivityAt == null ? null : Math.max(0, now - lastActivityAt);
|
|
19807
|
+
return {
|
|
19808
|
+
enabled,
|
|
19809
|
+
healthy: !enabled || ageMs != null && ageMs <= maxAgeMs,
|
|
19810
|
+
lastActivityAt,
|
|
19811
|
+
ageMs,
|
|
19812
|
+
maxAgeMs
|
|
19813
|
+
};
|
|
19814
|
+
}
|
|
19815
|
+
function getGatewayHealth(now = Date.now()) {
|
|
19816
|
+
const worker = componentStatus(
|
|
19817
|
+
"worker",
|
|
19818
|
+
true,
|
|
19819
|
+
Math.max((state?.config.workerPollIntervalMs ?? 1e3) * 5, 5e3),
|
|
19820
|
+
now
|
|
19821
|
+
);
|
|
19822
|
+
const scheduler = componentStatus(
|
|
19823
|
+
"scheduler",
|
|
19824
|
+
state?.config.schedulerEnabled ?? false,
|
|
19825
|
+
Math.max((state?.config.schedulerCheckIntervalMs ?? 1e3) * 5, 5e3),
|
|
19826
|
+
now
|
|
19827
|
+
);
|
|
19828
|
+
const watchdog = componentStatus(
|
|
19829
|
+
"watchdog",
|
|
19830
|
+
true,
|
|
19831
|
+
Math.max((state?.config.watchdogCheckIntervalMs ?? 6e4) * 3, 5e3),
|
|
19832
|
+
now
|
|
19833
|
+
);
|
|
19834
|
+
let lock = { pid: null, heartbeatAt: null, readyAt: null, ageMs: null, healthy: false };
|
|
19835
|
+
try {
|
|
19836
|
+
const row = sqlite.prepare(
|
|
19837
|
+
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
19838
|
+
).get();
|
|
19839
|
+
if (row) {
|
|
19840
|
+
const ageMs = Math.max(0, now - row.heartbeat_at);
|
|
19841
|
+
lock = {
|
|
19842
|
+
pid: row.pid,
|
|
19843
|
+
heartbeatAt: row.heartbeat_at,
|
|
19844
|
+
readyAt: row.ready_at,
|
|
19845
|
+
ageMs,
|
|
19846
|
+
healthy: row.pid === process.pid && row.ready_at != null && ageMs < LOCK_STALE_MS
|
|
19847
|
+
};
|
|
19848
|
+
}
|
|
19849
|
+
} catch {
|
|
19850
|
+
}
|
|
19851
|
+
const healthy = state != null && lock.healthy && worker.healthy && scheduler.healthy && watchdog.healthy;
|
|
19852
|
+
return {
|
|
19853
|
+
status: healthy ? "ok" : "degraded",
|
|
19854
|
+
pid: process.pid,
|
|
19855
|
+
uptimeMs: state ? Math.max(0, now - state.startedAt) : 0,
|
|
19856
|
+
lock,
|
|
19857
|
+
components: { worker, scheduler, watchdog }
|
|
19858
|
+
};
|
|
19859
|
+
}
|
|
19860
|
+
var LOCK_STALE_MS, state;
|
|
19861
|
+
var init_health = __esm({
|
|
19862
|
+
"src/gateway/health.ts"() {
|
|
19863
|
+
"use strict";
|
|
19864
|
+
init_db2();
|
|
19865
|
+
LOCK_STALE_MS = 3e4;
|
|
19866
|
+
state = null;
|
|
19867
|
+
}
|
|
19868
|
+
});
|
|
19869
|
+
|
|
19870
|
+
// src/core/process-control.ts
|
|
19871
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
19872
|
+
import { basename } from "path";
|
|
19873
|
+
function isSafePid(pid) {
|
|
19874
|
+
return Number.isInteger(pid) && pid > 1 && pid !== process.pid;
|
|
19875
|
+
}
|
|
19876
|
+
function isProcessAlive(pid) {
|
|
19877
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
19878
|
+
try {
|
|
19879
|
+
process.kill(pid, 0);
|
|
19880
|
+
return true;
|
|
19881
|
+
} catch (error) {
|
|
19882
|
+
return error instanceof Error && "code" in error && error.code === "EPERM";
|
|
19883
|
+
}
|
|
19884
|
+
}
|
|
19885
|
+
function inspectUnixProcess(pid) {
|
|
19886
|
+
const result = spawnSync2("ps", ["-o", "pgid=", "-o", "command=", "-p", String(pid)], {
|
|
19887
|
+
encoding: "utf8",
|
|
19888
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
19889
|
+
});
|
|
19890
|
+
if (result.status !== 0) return null;
|
|
19891
|
+
const match2 = result.stdout.trim().match(/^(\d+)\s+(.+)$/s);
|
|
19892
|
+
if (!match2) return null;
|
|
19893
|
+
return { processGroupId: Number(match2[1]), command: match2[2] };
|
|
19894
|
+
}
|
|
19895
|
+
function commandMatches(command, expectedExecutable) {
|
|
19896
|
+
const expectedName = basename(expectedExecutable).trim().toLowerCase();
|
|
19897
|
+
if (!expectedName) return false;
|
|
19898
|
+
return command.toLowerCase().includes(expectedName);
|
|
19899
|
+
}
|
|
19900
|
+
function signalSpawnedProcessTree(pid, signal) {
|
|
19901
|
+
if (!isSafePid(pid)) return false;
|
|
19902
|
+
if (process.platform !== "win32") {
|
|
19903
|
+
try {
|
|
19904
|
+
process.kill(-pid, signal);
|
|
19905
|
+
return true;
|
|
19906
|
+
} catch {
|
|
19907
|
+
}
|
|
19908
|
+
}
|
|
19909
|
+
try {
|
|
19910
|
+
process.kill(pid, signal);
|
|
19911
|
+
return true;
|
|
19912
|
+
} catch {
|
|
19913
|
+
return false;
|
|
19914
|
+
}
|
|
19915
|
+
}
|
|
19916
|
+
function signalRecordedProcessTree(pid, signal, expectedExecutable) {
|
|
19917
|
+
if (!isSafePid(pid)) return false;
|
|
19918
|
+
if (process.platform === "win32") {
|
|
19919
|
+
const list = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
|
|
19920
|
+
encoding: "utf8",
|
|
19921
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
19922
|
+
});
|
|
19923
|
+
if (list.status !== 0 || !commandMatches(list.stdout, expectedExecutable)) return false;
|
|
19924
|
+
const args = ["/PID", String(pid), "/T"];
|
|
19925
|
+
if (signal === "SIGKILL") args.push("/F");
|
|
19926
|
+
return spawnSync2("taskkill", args, { stdio: "ignore" }).status === 0;
|
|
19927
|
+
}
|
|
19928
|
+
const info = inspectUnixProcess(pid);
|
|
19929
|
+
if (!info || !commandMatches(info.command, expectedExecutable)) return false;
|
|
19930
|
+
try {
|
|
19931
|
+
if (info.processGroupId === pid) process.kill(-pid, signal);
|
|
19932
|
+
else process.kill(pid, signal);
|
|
19933
|
+
return true;
|
|
19934
|
+
} catch {
|
|
19935
|
+
return false;
|
|
19936
|
+
}
|
|
19937
|
+
}
|
|
19938
|
+
var init_process_control = __esm({
|
|
19939
|
+
"src/core/process-control.ts"() {
|
|
19940
|
+
"use strict";
|
|
19941
|
+
}
|
|
19942
|
+
});
|
|
19943
|
+
|
|
19250
19944
|
// src/worker/index.ts
|
|
19251
19945
|
import { spawn } from "child_process";
|
|
19252
|
-
var WorkerEngine;
|
|
19946
|
+
var DEFAULT_MAX_OUTPUT_CHARS, FORBIDDEN_AGENT, WorkerEngine;
|
|
19253
19947
|
var init_worker = __esm({
|
|
19254
19948
|
"src/worker/index.ts"() {
|
|
19255
19949
|
"use strict";
|
|
19256
19950
|
init_task_service();
|
|
19257
19951
|
init_task_run_service();
|
|
19952
|
+
init_health();
|
|
19953
|
+
init_process_control();
|
|
19954
|
+
DEFAULT_MAX_OUTPUT_CHARS = 64 * 1024;
|
|
19955
|
+
FORBIDDEN_AGENT = "supertask-runner";
|
|
19258
19956
|
WorkerEngine = class {
|
|
19259
19957
|
activeBatchIds = /* @__PURE__ */ new Set();
|
|
19260
19958
|
runningTasks = /* @__PURE__ */ new Map();
|
|
19261
19959
|
stopped = false;
|
|
19262
19960
|
pollTimer = null;
|
|
19263
19961
|
heartbeatTimer = null;
|
|
19962
|
+
pollCyclePromise = null;
|
|
19264
19963
|
cfg;
|
|
19265
|
-
|
|
19964
|
+
opencodeBin;
|
|
19965
|
+
maxOutputChars;
|
|
19966
|
+
constructor(cfg, options = {}) {
|
|
19266
19967
|
this.cfg = cfg.worker;
|
|
19968
|
+
this.opencodeBin = options.opencodeBin ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
19969
|
+
this.maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
19267
19970
|
}
|
|
19268
19971
|
start() {
|
|
19269
19972
|
this.stopped = false;
|
|
19973
|
+
markGatewayActivity("worker");
|
|
19270
19974
|
this.poll();
|
|
19271
19975
|
this.heartbeatTimer = setInterval(() => this.updateHeartbeats(), this.cfg.heartbeatIntervalMs);
|
|
19272
19976
|
}
|
|
19273
|
-
stop() {
|
|
19977
|
+
async stop(gracePeriodMs = 0) {
|
|
19274
19978
|
this.stopped = true;
|
|
19275
19979
|
if (this.pollTimer) {
|
|
19276
19980
|
clearTimeout(this.pollTimer);
|
|
19277
19981
|
this.pollTimer = null;
|
|
19278
19982
|
}
|
|
19983
|
+
if (this.pollCyclePromise) await this.pollCyclePromise;
|
|
19984
|
+
if (gracePeriodMs > 0 && this.runningTasks.size > 0) {
|
|
19985
|
+
const deadline = Date.now() + gracePeriodMs;
|
|
19986
|
+
while (this.runningTasks.size > 0 && Date.now() < deadline) {
|
|
19987
|
+
await Bun.sleep(Math.min(50, deadline - Date.now()));
|
|
19988
|
+
}
|
|
19989
|
+
}
|
|
19279
19990
|
if (this.heartbeatTimer) {
|
|
19280
19991
|
clearInterval(this.heartbeatTimer);
|
|
19281
19992
|
this.heartbeatTimer = null;
|
|
19282
19993
|
}
|
|
19994
|
+
const interruptedTaskIds = [...this.runningTasks.keys()];
|
|
19283
19995
|
const killPromises = [];
|
|
19284
|
-
for (const
|
|
19996
|
+
for (const entry of this.runningTasks.values()) {
|
|
19285
19997
|
entry.shutdown = true;
|
|
19286
19998
|
killPromises.push(this.killEntry(entry));
|
|
19287
19999
|
}
|
|
19288
|
-
|
|
19289
|
-
|
|
20000
|
+
await Promise.allSettled(killPromises);
|
|
20001
|
+
return interruptedTaskIds;
|
|
19290
20002
|
}
|
|
19291
20003
|
getRunningTaskIds() {
|
|
19292
20004
|
return [...this.runningTasks.keys()];
|
|
@@ -19296,136 +20008,244 @@ var init_worker = __esm({
|
|
|
19296
20008
|
}
|
|
19297
20009
|
poll() {
|
|
19298
20010
|
if (this.stopped) return;
|
|
19299
|
-
|
|
20011
|
+
markGatewayActivity("worker");
|
|
20012
|
+
this.pollCyclePromise = this.tryDispatch().finally(() => {
|
|
20013
|
+
this.pollCyclePromise = null;
|
|
19300
20014
|
if (this.stopped) return;
|
|
19301
20015
|
this.pollTimer = setTimeout(() => this.poll(), this.cfg.pollIntervalMs);
|
|
19302
20016
|
});
|
|
19303
20017
|
}
|
|
19304
20018
|
async tryDispatch() {
|
|
20019
|
+
await this.reconcileCancelledTasks();
|
|
19305
20020
|
while (!this.stopped && this.runningTasks.size < this.cfg.maxConcurrency) {
|
|
20021
|
+
let task;
|
|
20022
|
+
try {
|
|
20023
|
+
task = await TaskService.next({ excludedBatchIds: [...this.activeBatchIds] });
|
|
20024
|
+
} catch (err) {
|
|
20025
|
+
this.logError("task claim failed", err);
|
|
20026
|
+
break;
|
|
20027
|
+
}
|
|
20028
|
+
if (!task) break;
|
|
20029
|
+
if (this.stopped) break;
|
|
20030
|
+
if (!await TaskService.start(task.id)) continue;
|
|
20031
|
+
if (task.batchId) this.activeBatchIds.add(task.batchId);
|
|
20032
|
+
if (this.stopped) {
|
|
20033
|
+
await TaskService.resetRunningToPending([task.id]);
|
|
20034
|
+
this.releaseBatch(task);
|
|
20035
|
+
break;
|
|
20036
|
+
}
|
|
19306
20037
|
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
20038
|
const run = await TaskRunService.create({
|
|
19315
20039
|
taskId: task.id,
|
|
19316
20040
|
model: this.resolveModel(task.model),
|
|
19317
20041
|
status: "running"
|
|
19318
20042
|
});
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
|
|
19322
|
-
|
|
20043
|
+
if (this.stopped) {
|
|
20044
|
+
await TaskRunService.fail(run.id, "Gateway shutdown before spawn");
|
|
20045
|
+
await TaskService.resetRunningToPending([task.id]);
|
|
20046
|
+
this.releaseBatch(task);
|
|
20047
|
+
break;
|
|
19323
20048
|
}
|
|
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 }));
|
|
20049
|
+
if (task.agent === FORBIDDEN_AGENT) {
|
|
20050
|
+
const message = `\u7981\u6B62\u6267\u884C\u9012\u5F52 Agent: ${FORBIDDEN_AGENT}`;
|
|
20051
|
+
await TaskRunService.fail(run.id, message);
|
|
20052
|
+
await TaskService.fail(task.id, message, {}, { setDeadLetter: true });
|
|
20053
|
+
this.releaseBatch(task);
|
|
20054
|
+
continue;
|
|
20055
|
+
}
|
|
20056
|
+
this.spawnTask(task, run.id);
|
|
20057
|
+
} catch (err) {
|
|
20058
|
+
this.releaseBatch(task);
|
|
20059
|
+
const message = `Worker \u542F\u52A8\u4EFB\u52A1\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`;
|
|
20060
|
+
try {
|
|
20061
|
+
await TaskService.fail(task.id, message);
|
|
20062
|
+
} catch (failErr) {
|
|
20063
|
+
this.logError("failed to compensate task startup", failErr, task.id);
|
|
20064
|
+
}
|
|
20065
|
+
this.logError("task dispatch failed", err, task.id);
|
|
20066
|
+
}
|
|
20067
|
+
}
|
|
20068
|
+
}
|
|
20069
|
+
spawnTask(task, runId) {
|
|
20070
|
+
const model = this.resolveModel(task.model);
|
|
20071
|
+
const args = ["run", "--agent", task.agent, "--format", "json"];
|
|
20072
|
+
if (model) args.push("-m", model);
|
|
20073
|
+
args.push(task.prompt);
|
|
20074
|
+
const child = spawn(this.opencodeBin, args, {
|
|
20075
|
+
cwd: task.cwd || process.cwd(),
|
|
20076
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
20077
|
+
detached: process.platform !== "win32"
|
|
20078
|
+
});
|
|
20079
|
+
const entry = {
|
|
20080
|
+
task,
|
|
20081
|
+
runId,
|
|
20082
|
+
child,
|
|
20083
|
+
output: "",
|
|
20084
|
+
sessionId: null,
|
|
20085
|
+
timeoutTimer: null,
|
|
20086
|
+
shutdown: false,
|
|
20087
|
+
settled: false
|
|
20088
|
+
};
|
|
20089
|
+
this.runningTasks.set(task.id, entry);
|
|
20090
|
+
const handleData = (data) => {
|
|
20091
|
+
const text2 = data.toString();
|
|
20092
|
+
entry.output = (entry.output + text2).slice(-this.maxOutputChars);
|
|
20093
|
+
process.stdout.write(text2);
|
|
20094
|
+
const match2 = entry.output.match(/"sessionID"\s*:\s*"(ses_[^"]+)"/);
|
|
20095
|
+
if (match2?.[1] && match2[1] !== entry.sessionId) {
|
|
20096
|
+
entry.sessionId = match2[1];
|
|
20097
|
+
TaskRunService.updateSessionId(runId, match2[1]).catch((err) => {
|
|
20098
|
+
this.logError("sessionId update failed", err, task.id);
|
|
19376
20099
|
});
|
|
20100
|
+
}
|
|
20101
|
+
};
|
|
20102
|
+
child.stdout?.on("data", handleData);
|
|
20103
|
+
child.stderr?.on("data", handleData);
|
|
20104
|
+
child.once("error", (err) => {
|
|
20105
|
+
void this.finishEntry(entry, null, `\u65E0\u6CD5\u542F\u52A8 opencode\uFF1A${err.message}`);
|
|
20106
|
+
});
|
|
20107
|
+
child.once("close", (code, signal) => {
|
|
20108
|
+
const failure = code === 0 ? void 0 : `opencode \u9000\u51FA\u7801 ${code ?? "null"}${signal ? `\uFF0C\u4FE1\u53F7 ${signal}` : ""}`;
|
|
20109
|
+
void this.finishEntry(entry, code, failure);
|
|
20110
|
+
});
|
|
20111
|
+
const timeoutMs = task.timeoutMs ?? this.cfg.taskTimeoutMs;
|
|
20112
|
+
if (timeoutMs > 0) {
|
|
20113
|
+
entry.timeoutTimer = setTimeout(() => {
|
|
20114
|
+
this.signalEntry(entry, "SIGKILL");
|
|
20115
|
+
void this.finishEntry(entry, null, `\u4EFB\u52A1\u8D85\u65F6\uFF08${timeoutMs}ms\uFF09`);
|
|
20116
|
+
}, timeoutMs);
|
|
20117
|
+
}
|
|
20118
|
+
TaskRunService.updatePid(runId, process.pid, child.pid ?? 0).catch((err) => {
|
|
20119
|
+
this.signalEntry(entry, "SIGKILL");
|
|
20120
|
+
void this.finishEntry(entry, null, `\u8BB0\u5F55 Worker PID \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
20121
|
+
});
|
|
20122
|
+
}
|
|
20123
|
+
async finishEntry(entry, code, failure) {
|
|
20124
|
+
if (entry.settled) return;
|
|
20125
|
+
entry.settled = true;
|
|
20126
|
+
if (entry.timeoutTimer) {
|
|
20127
|
+
clearTimeout(entry.timeoutTimer);
|
|
20128
|
+
entry.timeoutTimer = null;
|
|
20129
|
+
}
|
|
20130
|
+
try {
|
|
20131
|
+
if (entry.shutdown) return;
|
|
20132
|
+
const currentRun = await TaskRunService.getById(entry.runId);
|
|
20133
|
+
if (!currentRun || currentRun.status !== "running") return;
|
|
20134
|
+
const output = entry.output.trim();
|
|
20135
|
+
const log = failure ? `${failure}${output ? `
|
|
20136
|
+
${output}` : ""}` : output;
|
|
20137
|
+
if (code === 0 && !failure) {
|
|
20138
|
+
const completed = await TaskService.done(entry.task.id, log);
|
|
20139
|
+
if (completed) {
|
|
20140
|
+
await TaskRunService.done(entry.runId, log);
|
|
20141
|
+
console.log(JSON.stringify({
|
|
20142
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20143
|
+
level: "info",
|
|
20144
|
+
msg: "task done",
|
|
20145
|
+
taskId: entry.task.id
|
|
20146
|
+
}));
|
|
20147
|
+
return;
|
|
20148
|
+
}
|
|
20149
|
+
await TaskRunService.fail(entry.runId, "\u4EFB\u52A1\u72B6\u6001\u5DF2\u88AB\u5176\u4ED6\u64CD\u4F5C\u6539\u53D8");
|
|
20150
|
+
return;
|
|
20151
|
+
}
|
|
20152
|
+
await TaskRunService.fail(entry.runId, log);
|
|
20153
|
+
const failed = await TaskService.fail(entry.task.id, log);
|
|
20154
|
+
if (!failed) {
|
|
20155
|
+
this.logError("task failure state transition rejected", failure ?? "unknown failure", entry.task.id);
|
|
20156
|
+
}
|
|
20157
|
+
console.error(JSON.stringify({
|
|
20158
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20159
|
+
level: "error",
|
|
20160
|
+
msg: "task failed",
|
|
20161
|
+
taskId: entry.task.id,
|
|
20162
|
+
error: failure
|
|
20163
|
+
}));
|
|
20164
|
+
} finally {
|
|
20165
|
+
this.runningTasks.delete(entry.task.id);
|
|
20166
|
+
this.releaseBatch(entry.task);
|
|
20167
|
+
}
|
|
20168
|
+
}
|
|
20169
|
+
async reconcileCancelledTasks() {
|
|
20170
|
+
for (const entry of [...this.runningTasks.values()]) {
|
|
20171
|
+
try {
|
|
20172
|
+
const task = await TaskService.getById(entry.task.id);
|
|
20173
|
+
if (task?.status === "cancelled") await this.cancelEntry(entry);
|
|
19377
20174
|
} 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;
|
|
20175
|
+
this.logError("cancel reconciliation failed", err, entry.task.id);
|
|
19385
20176
|
}
|
|
19386
20177
|
}
|
|
19387
20178
|
}
|
|
20179
|
+
async cancelEntry(entry) {
|
|
20180
|
+
if (entry.settled) return;
|
|
20181
|
+
entry.settled = true;
|
|
20182
|
+
if (entry.timeoutTimer) {
|
|
20183
|
+
clearTimeout(entry.timeoutTimer);
|
|
20184
|
+
entry.timeoutTimer = null;
|
|
20185
|
+
}
|
|
20186
|
+
try {
|
|
20187
|
+
await this.killEntry(entry);
|
|
20188
|
+
const output = entry.output.trim();
|
|
20189
|
+
const log = `\u4EFB\u52A1\u5DF2\u53D6\u6D88${output ? `
|
|
20190
|
+
${output}` : ""}`;
|
|
20191
|
+
await TaskRunService.fail(entry.runId, log);
|
|
20192
|
+
console.log(JSON.stringify({
|
|
20193
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20194
|
+
level: "info",
|
|
20195
|
+
msg: "running task cancelled",
|
|
20196
|
+
taskId: entry.task.id
|
|
20197
|
+
}));
|
|
20198
|
+
} finally {
|
|
20199
|
+
this.runningTasks.delete(entry.task.id);
|
|
20200
|
+
this.releaseBatch(entry.task);
|
|
20201
|
+
}
|
|
20202
|
+
}
|
|
19388
20203
|
async updateHeartbeats() {
|
|
19389
|
-
for (const
|
|
20204
|
+
for (const entry of this.runningTasks.values()) {
|
|
19390
20205
|
try {
|
|
19391
20206
|
await TaskRunService.heartbeat(entry.runId);
|
|
19392
|
-
} catch {
|
|
20207
|
+
} catch (err) {
|
|
20208
|
+
this.logError("heartbeat update failed", err, entry.task.id);
|
|
19393
20209
|
}
|
|
19394
20210
|
}
|
|
19395
20211
|
}
|
|
19396
20212
|
killEntry(entry) {
|
|
19397
|
-
if (entry.child.exitCode !== null) {
|
|
20213
|
+
if (entry.child.exitCode !== null || entry.child.signalCode !== null) {
|
|
19398
20214
|
return Promise.resolve();
|
|
19399
20215
|
}
|
|
19400
20216
|
return new Promise((resolve) => {
|
|
19401
20217
|
const timeout = setTimeout(() => {
|
|
19402
|
-
|
|
19403
|
-
if (entry.child.pid) process.kill(entry.child.pid, "SIGKILL");
|
|
19404
|
-
} catch {
|
|
19405
|
-
}
|
|
20218
|
+
this.signalEntry(entry, "SIGKILL");
|
|
19406
20219
|
resolve();
|
|
19407
20220
|
}, 5e3);
|
|
19408
|
-
entry.child.
|
|
20221
|
+
entry.child.once("close", () => {
|
|
19409
20222
|
clearTimeout(timeout);
|
|
19410
20223
|
resolve();
|
|
19411
20224
|
});
|
|
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
|
-
}
|
|
20225
|
+
this.signalEntry(entry, "SIGTERM");
|
|
19423
20226
|
});
|
|
19424
20227
|
}
|
|
20228
|
+
signalEntry(entry, signal) {
|
|
20229
|
+
const pid = entry.child.pid;
|
|
20230
|
+
if (!pid) return;
|
|
20231
|
+
signalSpawnedProcessTree(pid, signal);
|
|
20232
|
+
}
|
|
20233
|
+
releaseBatch(task) {
|
|
20234
|
+
if (task.batchId) this.activeBatchIds.delete(task.batchId);
|
|
20235
|
+
}
|
|
19425
20236
|
resolveModel(taskModel) {
|
|
19426
20237
|
if (!taskModel || taskModel === "default") return null;
|
|
19427
20238
|
return taskModel;
|
|
19428
20239
|
}
|
|
20240
|
+
logError(message, error, taskId) {
|
|
20241
|
+
console.error(JSON.stringify({
|
|
20242
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20243
|
+
level: "error",
|
|
20244
|
+
msg: message,
|
|
20245
|
+
taskId,
|
|
20246
|
+
error: error instanceof Error ? error.message : String(error)
|
|
20247
|
+
}));
|
|
20248
|
+
}
|
|
19429
20249
|
};
|
|
19430
20250
|
}
|
|
19431
20251
|
});
|
|
@@ -19437,15 +20257,23 @@ async function checkHeartbeats(heartbeatTimeoutMs) {
|
|
|
19437
20257
|
for (const run of staleRuns) {
|
|
19438
20258
|
try {
|
|
19439
20259
|
if (run.childPid != null && run.childPid > 0) {
|
|
19440
|
-
|
|
19441
|
-
|
|
19442
|
-
|
|
20260
|
+
const expectedExecutable = process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
20261
|
+
const killed = signalRecordedProcessTree(run.childPid, "SIGKILL", expectedExecutable);
|
|
20262
|
+
if (!killed) {
|
|
20263
|
+
console.warn(JSON.stringify({
|
|
20264
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20265
|
+
level: "warn",
|
|
20266
|
+
msg: "stale child process was not killed because identity validation failed",
|
|
20267
|
+
taskId: run.taskId,
|
|
20268
|
+
runId: run.runId,
|
|
20269
|
+
childPid: run.childPid
|
|
20270
|
+
}));
|
|
19443
20271
|
}
|
|
19444
20272
|
}
|
|
19445
20273
|
await TaskRunService.fail(run.runId, `\u5FC3\u8DF3\u8D85\u65F6 (${heartbeatTimeoutMs / 1e3}s)\uFF0CWatchdog kill`);
|
|
19446
20274
|
const newRetryCount = run.taskRetryCount + 1;
|
|
19447
20275
|
const maxRetries = run.taskMaxRetries;
|
|
19448
|
-
if (newRetryCount
|
|
20276
|
+
if (newRetryCount > maxRetries) {
|
|
19449
20277
|
await TaskService.markDeadLetter(run.taskId, newRetryCount);
|
|
19450
20278
|
console.log(JSON.stringify({
|
|
19451
20279
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -19457,7 +20285,7 @@ async function checkHeartbeats(heartbeatTimeoutMs) {
|
|
|
19457
20285
|
maxRetries
|
|
19458
20286
|
}));
|
|
19459
20287
|
} else {
|
|
19460
|
-
const backoffMs = computeBackoff(newRetryCount);
|
|
20288
|
+
const backoffMs = computeBackoff(newRetryCount, run.taskRetryBackoffMs);
|
|
19461
20289
|
const retryAfter = Date.now() + backoffMs;
|
|
19462
20290
|
await TaskService.markPendingForRetry(run.taskId, retryAfter, newRetryCount);
|
|
19463
20291
|
console.log(JSON.stringify({
|
|
@@ -19488,6 +20316,7 @@ var init_heartbeat = __esm({
|
|
|
19488
20316
|
init_task_run_service();
|
|
19489
20317
|
init_task_service();
|
|
19490
20318
|
init_backoff();
|
|
20319
|
+
init_process_control();
|
|
19491
20320
|
}
|
|
19492
20321
|
});
|
|
19493
20322
|
|
|
@@ -19532,6 +20361,7 @@ var init_watchdog = __esm({
|
|
|
19532
20361
|
"use strict";
|
|
19533
20362
|
init_heartbeat();
|
|
19534
20363
|
init_cleanup();
|
|
20364
|
+
init_health();
|
|
19535
20365
|
Watchdog = class {
|
|
19536
20366
|
constructor(cfg) {
|
|
19537
20367
|
this.cfg = cfg;
|
|
@@ -19542,13 +20372,14 @@ var init_watchdog = __esm({
|
|
|
19542
20372
|
cleanupTimer = null;
|
|
19543
20373
|
start() {
|
|
19544
20374
|
this.stopped = false;
|
|
20375
|
+
markGatewayActivity("watchdog");
|
|
19545
20376
|
this.heartbeatTimer = setInterval(
|
|
19546
20377
|
() => this.runHeartbeatCheck(),
|
|
19547
|
-
this.cfg.watchdog.
|
|
20378
|
+
this.cfg.watchdog.checkIntervalMs
|
|
19548
20379
|
);
|
|
19549
20380
|
this.cleanupTimer = setInterval(
|
|
19550
20381
|
() => this.runCleanup(),
|
|
19551
|
-
this.cfg.watchdog.cleanupIntervalMs
|
|
20382
|
+
this.cfg.watchdog.cleanupIntervalMs
|
|
19552
20383
|
);
|
|
19553
20384
|
}
|
|
19554
20385
|
stop() {
|
|
@@ -19564,6 +20395,7 @@ var init_watchdog = __esm({
|
|
|
19564
20395
|
}
|
|
19565
20396
|
async runHeartbeatCheck() {
|
|
19566
20397
|
if (this.stopped) return;
|
|
20398
|
+
markGatewayActivity("watchdog");
|
|
19567
20399
|
try {
|
|
19568
20400
|
await checkHeartbeats(this.cfg.watchdog.heartbeatTimeoutMs);
|
|
19569
20401
|
} catch (err) {
|
|
@@ -19596,7 +20428,7 @@ var init_watchdog = __esm({
|
|
|
19596
20428
|
async function cloneTaskFromTemplate(templateId) {
|
|
19597
20429
|
const rows = await db.select().from(taskTemplates3).where(eq(taskTemplates3.id, templateId)).limit(1);
|
|
19598
20430
|
const tmpl = rows[0];
|
|
19599
|
-
if (!tmpl) return null;
|
|
20431
|
+
if (!tmpl || !tmpl.enabled) return null;
|
|
19600
20432
|
const activeCount = await db.select({ count: sql`count(*)` }).from(schema_exports.tasks).where(
|
|
19601
20433
|
and(
|
|
19602
20434
|
eq(schema_exports.tasks.templateId, templateId),
|
|
@@ -19605,7 +20437,8 @@ async function cloneTaskFromTemplate(templateId) {
|
|
|
19605
20437
|
).then((r) => r[0].count);
|
|
19606
20438
|
if (activeCount >= (tmpl.maxInstances ?? 1)) return null;
|
|
19607
20439
|
const nowMs = Date.now();
|
|
19608
|
-
const
|
|
20440
|
+
const isDelayed = tmpl.scheduleType === "delayed";
|
|
20441
|
+
const nextRunAt = isDelayed ? null : TaskTemplateService.calculateNextRunAt(
|
|
19609
20442
|
tmpl.scheduleType,
|
|
19610
20443
|
tmpl,
|
|
19611
20444
|
nowMs
|
|
@@ -19619,13 +20452,17 @@ async function cloneTaskFromTemplate(templateId) {
|
|
|
19619
20452
|
category: tmpl.category ?? "general",
|
|
19620
20453
|
importance: tmpl.importance ?? 3,
|
|
19621
20454
|
urgency: tmpl.urgency ?? 3,
|
|
20455
|
+
batchId: tmpl.batchId,
|
|
19622
20456
|
maxRetries: tmpl.maxRetries ?? 3,
|
|
20457
|
+
retryBackoffMs: tmpl.retryBackoffMs ?? 3e4,
|
|
20458
|
+
timeoutMs: tmpl.timeoutMs,
|
|
19623
20459
|
templateId: tmpl.id,
|
|
19624
20460
|
scheduledAt: nowMs
|
|
19625
20461
|
});
|
|
19626
20462
|
await db.update(taskTemplates3).set({
|
|
19627
20463
|
lastRunAt: nowMs,
|
|
19628
20464
|
nextRunAt,
|
|
20465
|
+
enabled: isDelayed ? false : tmpl.enabled,
|
|
19629
20466
|
updatedAt: nowMs
|
|
19630
20467
|
}).where(eq(taskTemplates3.id, templateId));
|
|
19631
20468
|
return task;
|
|
@@ -19638,7 +20475,7 @@ async function getDueTemplates() {
|
|
|
19638
20475
|
sql`${taskTemplates3.nextRunAt} IS NOT NULL`,
|
|
19639
20476
|
sql`${taskTemplates3.nextRunAt} <= ${nowMs}`
|
|
19640
20477
|
)
|
|
19641
|
-
);
|
|
20478
|
+
).orderBy(asc(taskTemplates3.nextRunAt), asc(taskTemplates3.id));
|
|
19642
20479
|
}
|
|
19643
20480
|
async function initializeNextRunAt(templateId) {
|
|
19644
20481
|
const rows = await db.select().from(taskTemplates3).where(eq(taskTemplates3.id, templateId)).limit(1);
|
|
@@ -19672,6 +20509,7 @@ var init_scheduler = __esm({
|
|
|
19672
20509
|
init_job_templates();
|
|
19673
20510
|
init_db2();
|
|
19674
20511
|
init_drizzle_orm();
|
|
20512
|
+
init_health();
|
|
19675
20513
|
Scheduler = class {
|
|
19676
20514
|
constructor(cfg) {
|
|
19677
20515
|
this.cfg = cfg;
|
|
@@ -19683,6 +20521,7 @@ var init_scheduler = __esm({
|
|
|
19683
20521
|
async start() {
|
|
19684
20522
|
if (!this.cfg.scheduler.enabled) return;
|
|
19685
20523
|
this.stopped = false;
|
|
20524
|
+
markGatewayActivity("scheduler");
|
|
19686
20525
|
await this.initializeTemplates();
|
|
19687
20526
|
this.timer = setInterval(() => this.tick(), this.cfg.scheduler.checkIntervalMs);
|
|
19688
20527
|
}
|
|
@@ -19695,6 +20534,7 @@ var init_scheduler = __esm({
|
|
|
19695
20534
|
}
|
|
19696
20535
|
async tick() {
|
|
19697
20536
|
if (this.stopped || this.ticking) return;
|
|
20537
|
+
markGatewayActivity("scheduler");
|
|
19698
20538
|
this.ticking = true;
|
|
19699
20539
|
try {
|
|
19700
20540
|
const dueTemplates = await getDueTemplates();
|
|
@@ -19749,13 +20589,13 @@ var init_compose = __esm({
|
|
|
19749
20589
|
"use strict";
|
|
19750
20590
|
compose = (middleware, onError, onNotFound) => {
|
|
19751
20591
|
return (context, next) => {
|
|
19752
|
-
let
|
|
20592
|
+
let index2 = -1;
|
|
19753
20593
|
return dispatch(0);
|
|
19754
20594
|
async function dispatch(i) {
|
|
19755
|
-
if (i <=
|
|
20595
|
+
if (i <= index2) {
|
|
19756
20596
|
throw new Error("next() called multiple times");
|
|
19757
20597
|
}
|
|
19758
|
-
|
|
20598
|
+
index2 = i;
|
|
19759
20599
|
let res;
|
|
19760
20600
|
let isError = false;
|
|
19761
20601
|
let handler;
|
|
@@ -19870,8 +20710,8 @@ var init_body = __esm({
|
|
|
19870
20710
|
handleParsingNestedValues = (form, key, value) => {
|
|
19871
20711
|
let nestedForm = form;
|
|
19872
20712
|
const keys = key.split(".");
|
|
19873
|
-
keys.forEach((key2,
|
|
19874
|
-
if (
|
|
20713
|
+
keys.forEach((key2, index2) => {
|
|
20714
|
+
if (index2 === keys.length - 1) {
|
|
19875
20715
|
nestedForm[key2] = value;
|
|
19876
20716
|
} else {
|
|
19877
20717
|
if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
|
|
@@ -19903,8 +20743,8 @@ var init_url = __esm({
|
|
|
19903
20743
|
};
|
|
19904
20744
|
extractGroupsFromPath = (path) => {
|
|
19905
20745
|
const groups = [];
|
|
19906
|
-
path = path.replace(/\{[^}]+\}/g, (match2,
|
|
19907
|
-
const mark = `@${
|
|
20746
|
+
path = path.replace(/\{[^}]+\}/g, (match2, index2) => {
|
|
20747
|
+
const mark = `@${index2}`;
|
|
19908
20748
|
groups.push([mark, match2]);
|
|
19909
20749
|
return mark;
|
|
19910
20750
|
});
|
|
@@ -20423,10 +21263,10 @@ var init_html = __esm({
|
|
|
20423
21263
|
return;
|
|
20424
21264
|
}
|
|
20425
21265
|
let escape;
|
|
20426
|
-
let
|
|
21266
|
+
let index2;
|
|
20427
21267
|
let lastIndex = 0;
|
|
20428
|
-
for (
|
|
20429
|
-
switch (str.charCodeAt(
|
|
21268
|
+
for (index2 = match2; index2 < str.length; index2++) {
|
|
21269
|
+
switch (str.charCodeAt(index2)) {
|
|
20430
21270
|
case 34:
|
|
20431
21271
|
escape = """;
|
|
20432
21272
|
break;
|
|
@@ -20445,10 +21285,10 @@ var init_html = __esm({
|
|
|
20445
21285
|
default:
|
|
20446
21286
|
continue;
|
|
20447
21287
|
}
|
|
20448
|
-
buffer[0] += str.substring(lastIndex,
|
|
20449
|
-
lastIndex =
|
|
21288
|
+
buffer[0] += str.substring(lastIndex, index2) + escape;
|
|
21289
|
+
lastIndex = index2 + 1;
|
|
20450
21290
|
}
|
|
20451
|
-
buffer[0] += str.substring(lastIndex,
|
|
21291
|
+
buffer[0] += str.substring(lastIndex, index2);
|
|
20452
21292
|
};
|
|
20453
21293
|
resolveCallbackSync = (str) => {
|
|
20454
21294
|
const callbacks = str.callbacks;
|
|
@@ -21324,8 +22164,8 @@ function match(method, path) {
|
|
|
21324
22164
|
if (!match3) {
|
|
21325
22165
|
return [[], emptyParam];
|
|
21326
22166
|
}
|
|
21327
|
-
const
|
|
21328
|
-
return [matcher[1][
|
|
22167
|
+
const index2 = match3.indexOf("", 1);
|
|
22168
|
+
return [matcher[1][index2], match3];
|
|
21329
22169
|
});
|
|
21330
22170
|
this.match = match2;
|
|
21331
22171
|
return match2(method, path);
|
|
@@ -21372,7 +22212,7 @@ var init_node = __esm({
|
|
|
21372
22212
|
#index;
|
|
21373
22213
|
#varIndex;
|
|
21374
22214
|
#children = /* @__PURE__ */ Object.create(null);
|
|
21375
|
-
insert(tokens,
|
|
22215
|
+
insert(tokens, index2, paramMap, context, pathErrorCheckOnly) {
|
|
21376
22216
|
if (tokens.length === 0) {
|
|
21377
22217
|
if (this.#index !== void 0) {
|
|
21378
22218
|
throw PATH_ERROR;
|
|
@@ -21380,7 +22220,7 @@ var init_node = __esm({
|
|
|
21380
22220
|
if (pathErrorCheckOnly) {
|
|
21381
22221
|
return;
|
|
21382
22222
|
}
|
|
21383
|
-
this.#index =
|
|
22223
|
+
this.#index = index2;
|
|
21384
22224
|
return;
|
|
21385
22225
|
}
|
|
21386
22226
|
const [token, ...restTokens] = tokens;
|
|
@@ -21430,7 +22270,7 @@ var init_node = __esm({
|
|
|
21430
22270
|
node = this.#children[token] = new _Node();
|
|
21431
22271
|
}
|
|
21432
22272
|
}
|
|
21433
|
-
node.insert(restTokens,
|
|
22273
|
+
node.insert(restTokens, index2, paramMap, context, pathErrorCheckOnly);
|
|
21434
22274
|
}
|
|
21435
22275
|
buildRegExpStr() {
|
|
21436
22276
|
const childKeys = Object.keys(this.#children).sort(compareKey);
|
|
@@ -21462,7 +22302,7 @@ var init_trie = __esm({
|
|
|
21462
22302
|
Trie = class {
|
|
21463
22303
|
#context = { varIndex: 0 };
|
|
21464
22304
|
#root = new Node();
|
|
21465
|
-
insert(path,
|
|
22305
|
+
insert(path, index2, pathErrorCheckOnly) {
|
|
21466
22306
|
const paramAssoc = [];
|
|
21467
22307
|
const groups = [];
|
|
21468
22308
|
for (let i = 0; ; ) {
|
|
@@ -21488,7 +22328,7 @@ var init_trie = __esm({
|
|
|
21488
22328
|
}
|
|
21489
22329
|
}
|
|
21490
22330
|
}
|
|
21491
|
-
this.#root.insert(tokens,
|
|
22331
|
+
this.#root.insert(tokens, index2, paramAssoc, this.#context, pathErrorCheckOnly);
|
|
21492
22332
|
return paramAssoc;
|
|
21493
22333
|
}
|
|
21494
22334
|
buildRegExp() {
|
|
@@ -22082,8 +22922,19 @@ __export(web_exports, {
|
|
|
22082
22922
|
dashboardApp: () => dashboardApp,
|
|
22083
22923
|
default: () => web_default
|
|
22084
22924
|
});
|
|
22085
|
-
import { existsSync as
|
|
22086
|
-
import { dirname as
|
|
22925
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, renameSync } from "fs";
|
|
22926
|
+
import { dirname as dirname3 } from "path";
|
|
22927
|
+
function parsePositiveInteger(value) {
|
|
22928
|
+
if (!/^\d+$/.test(value)) return null;
|
|
22929
|
+
const parsed = Number(value);
|
|
22930
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
22931
|
+
}
|
|
22932
|
+
function parseTaskStatus(value) {
|
|
22933
|
+
return TASK_STATUSES.has(value) ? value : null;
|
|
22934
|
+
}
|
|
22935
|
+
function safeStatus(value) {
|
|
22936
|
+
return value && TASK_STATUSES.has(value) ? value : "unknown";
|
|
22937
|
+
}
|
|
22087
22938
|
function formatDuration(startAt, endAt) {
|
|
22088
22939
|
if (!startAt) return "-";
|
|
22089
22940
|
const start = new Date(startAt).getTime();
|
|
@@ -22121,17 +22972,21 @@ function esc(s) {
|
|
|
22121
22972
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
22122
22973
|
}
|
|
22123
22974
|
function readCurrentConfig() {
|
|
22124
|
-
|
|
22975
|
+
const configPath = getConfigPath();
|
|
22976
|
+
if (!existsSync4(configPath)) return {};
|
|
22125
22977
|
try {
|
|
22126
|
-
return JSON.parse(
|
|
22978
|
+
return JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
22127
22979
|
} catch {
|
|
22128
22980
|
return {};
|
|
22129
22981
|
}
|
|
22130
22982
|
}
|
|
22131
22983
|
function writeConfig(cfg) {
|
|
22132
|
-
const
|
|
22133
|
-
|
|
22134
|
-
|
|
22984
|
+
const configPath = getConfigPath();
|
|
22985
|
+
const dir = dirname3(configPath);
|
|
22986
|
+
if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
|
|
22987
|
+
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
22988
|
+
writeFileSync2(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
22989
|
+
renameSync(tempPath, configPath);
|
|
22135
22990
|
}
|
|
22136
22991
|
function renderLayout(title, activeTab, body) {
|
|
22137
22992
|
return `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>${title} - SuperTask</title>${SHARED_STYLES}
|
|
@@ -22170,11 +23025,11 @@ async function saveConfig(){
|
|
|
22170
23025
|
scheduler:{
|
|
22171
23026
|
enabled:form.se.checked,
|
|
22172
23027
|
checkIntervalMs:Number(form.si.value),
|
|
22173
|
-
catchUp:form.cu.value,
|
|
22174
23028
|
},
|
|
22175
23029
|
watchdog:{
|
|
22176
23030
|
heartbeatTimeoutMs:Number(form.wt.value)*1000,
|
|
22177
|
-
|
|
23031
|
+
checkIntervalMs:Number(form.wci.value)*1000,
|
|
23032
|
+
cleanupIntervalMs:Number(form.wcl.value)*3600000,
|
|
22178
23033
|
retentionDays:Number(form.rd.value),
|
|
22179
23034
|
}
|
|
22180
23035
|
};
|
|
@@ -22205,7 +23060,7 @@ async function saveConfig(){
|
|
|
22205
23060
|
<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
23061
|
</body></html>`;
|
|
22207
23062
|
}
|
|
22208
|
-
var app, SHARED_STYLES, dashboardApp, web_default;
|
|
23063
|
+
var app, TASK_STATUSES, SHARED_STYLES, dashboardApp, web_default;
|
|
22209
23064
|
var init_web = __esm({
|
|
22210
23065
|
"src/web/index.tsx"() {
|
|
22211
23066
|
"use strict";
|
|
@@ -22217,7 +23072,47 @@ var init_web = __esm({
|
|
|
22217
23072
|
init_drizzle_orm();
|
|
22218
23073
|
init_db2();
|
|
22219
23074
|
init_config();
|
|
23075
|
+
init_health();
|
|
22220
23076
|
app = new Hono2();
|
|
23077
|
+
TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
23078
|
+
"pending",
|
|
23079
|
+
"running",
|
|
23080
|
+
"done",
|
|
23081
|
+
"failed",
|
|
23082
|
+
"dead_letter",
|
|
23083
|
+
"cancelled"
|
|
23084
|
+
]);
|
|
23085
|
+
app.use("*", async (c, next) => {
|
|
23086
|
+
await next();
|
|
23087
|
+
c.header("X-Content-Type-Options", "nosniff");
|
|
23088
|
+
c.header("X-Frame-Options", "DENY");
|
|
23089
|
+
c.header("Referrer-Policy", "no-referrer");
|
|
23090
|
+
});
|
|
23091
|
+
app.use("/api/*", async (c, next) => {
|
|
23092
|
+
if (["GET", "HEAD", "OPTIONS"].includes(c.req.method)) return next();
|
|
23093
|
+
const fetchSite = c.req.header("Sec-Fetch-Site");
|
|
23094
|
+
if (fetchSite && !["same-origin", "none"].includes(fetchSite)) {
|
|
23095
|
+
return c.json({ error: "cross-site request rejected" }, 403);
|
|
23096
|
+
}
|
|
23097
|
+
const origin = c.req.header("Origin");
|
|
23098
|
+
if (origin) {
|
|
23099
|
+
try {
|
|
23100
|
+
const originUrl = new URL(origin);
|
|
23101
|
+
const requestUrl = new URL(c.req.url);
|
|
23102
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(originUrl.hostname);
|
|
23103
|
+
if (!loopback || originUrl.origin !== requestUrl.origin) {
|
|
23104
|
+
return c.json({ error: "cross-site request rejected" }, 403);
|
|
23105
|
+
}
|
|
23106
|
+
} catch {
|
|
23107
|
+
return c.json({ error: "invalid origin" }, 403);
|
|
23108
|
+
}
|
|
23109
|
+
}
|
|
23110
|
+
return next();
|
|
23111
|
+
});
|
|
23112
|
+
app.get("/health", (c) => {
|
|
23113
|
+
const health = getGatewayHealth();
|
|
23114
|
+
return c.json(health, health.status === "ok" ? 200 : 503);
|
|
23115
|
+
});
|
|
22221
23116
|
SHARED_STYLES = html`
|
|
22222
23117
|
<style>
|
|
22223
23118
|
:root { --bg:#0d1117; --card:#161b22; --border:#30363d; --t1:#c9d1d9; --t2:#8b949e; --green:#238636; --red:#da3633; --yellow:#d29922; --blue:#1f6feb; --purple:#8957e5; }
|
|
@@ -22294,12 +23189,16 @@ var init_web = __esm({
|
|
|
22294
23189
|
</style>
|
|
22295
23190
|
`;
|
|
22296
23191
|
app.get("/", async (c) => {
|
|
22297
|
-
const
|
|
23192
|
+
const pageParam = c.req.query("page") || "1";
|
|
23193
|
+
const page = parsePositiveInteger(pageParam);
|
|
23194
|
+
if (page === null) return c.text("invalid page", 400);
|
|
22298
23195
|
const statusFilter = c.req.query("status") || "";
|
|
23196
|
+
const parsedStatus = statusFilter ? parseTaskStatus(statusFilter) : null;
|
|
23197
|
+
if (statusFilter && !parsedStatus) return c.text("invalid status", 400);
|
|
22299
23198
|
const limit = 50;
|
|
22300
23199
|
const offset = (page - 1) * limit;
|
|
22301
23200
|
const [tasks3, statsData] = await Promise.all([
|
|
22302
|
-
TaskService.list({ limit, offset, ...
|
|
23201
|
+
TaskService.list({ limit, offset, ...parsedStatus ? { status: parsedStatus } : {} }),
|
|
22303
23202
|
TaskService.stats({})
|
|
22304
23203
|
]);
|
|
22305
23204
|
const taskIds = tasks3.map((t) => t.id);
|
|
@@ -22322,12 +23221,13 @@ var init_web = __esm({
|
|
|
22322
23221
|
</div>`;
|
|
22323
23222
|
let rows = "";
|
|
22324
23223
|
for (const task of tasks3) {
|
|
22325
|
-
const
|
|
23224
|
+
const status = safeStatus(task.status);
|
|
23225
|
+
const st = status.toUpperCase();
|
|
22326
23226
|
rows += `<tr>
|
|
22327
23227
|
<td class="mu">#${task.id}</td>
|
|
22328
23228
|
<td><div style="font-weight:500">${esc(task.name)}</div><div class="mu sm el">${esc(task.prompt.substring(0, 120))}</div></td>
|
|
22329
23229
|
<td><span class="tag">${esc(task.agent)}</span></td>
|
|
22330
|
-
<td><span class="badge b-${
|
|
23230
|
+
<td><span class="badge b-${status}">${st}</span></td>
|
|
22331
23231
|
<td class="sm ${task.status === "running" ? "" : "mu"}">${formatDuration(task.startedAt, task.finishedAt)}</td>
|
|
22332
23232
|
<td class="mu sm">${(task.retryCount ?? 0) > 0 ? task.retryCount : "-"}</td>
|
|
22333
23233
|
<td>
|
|
@@ -22359,21 +23259,13 @@ var init_web = __esm({
|
|
|
22359
23259
|
});
|
|
22360
23260
|
app.get("/templates", async (c) => {
|
|
22361
23261
|
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
23262
|
const enabled = templates.filter((t) => t.enabled).length;
|
|
22372
23263
|
const disabled = templates.length - enabled;
|
|
22373
23264
|
let rows = "";
|
|
22374
23265
|
for (const t of templates) {
|
|
22375
|
-
const
|
|
22376
|
-
const
|
|
23266
|
+
const scheduleType = ["cron", "recurring", "delayed"].includes(t.scheduleType) ? t.scheduleType : "unknown";
|
|
23267
|
+
const typeLabel = scheduleType === "cron" ? "Cron" : scheduleType === "recurring" ? "\u5FAA\u73AF" : scheduleType === "delayed" ? "\u5B9A\u65F6" : "\u672A\u77E5";
|
|
23268
|
+
const typeClass = "tag t-" + scheduleType;
|
|
22377
23269
|
let rule = "-";
|
|
22378
23270
|
if (t.scheduleType === "cron") rule = t.cronExpr || "-";
|
|
22379
23271
|
else if (t.scheduleType === "recurring") rule = t.intervalMs ? `${Math.floor(t.intervalMs / 6e4)}\u5206\u949F` : "-";
|
|
@@ -22385,7 +23277,7 @@ var init_web = __esm({
|
|
|
22385
23277
|
<td><div style="font-weight:500">${esc(t.name)}</div><div class="mu sm el">${esc(t.prompt.substring(0, 100))}</div>
|
|
22386
23278
|
<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
23279
|
<td><span class="${typeClass}">${typeLabel}</span></td>
|
|
22388
|
-
<td class="m sm">${rule}</td>
|
|
23280
|
+
<td class="m sm">${esc(rule)}</td>
|
|
22389
23281
|
<td>${statusBadge}</td>
|
|
22390
23282
|
<td class="sm">${t.lastRunAt ? timeAgo(t.lastRunAt) : "-"}</td>
|
|
22391
23283
|
<td class="sm">${t.nextRunAt ? timeUntil(t.nextRunAt) : "-"}</td>
|
|
@@ -22413,7 +23305,8 @@ var init_web = __esm({
|
|
|
22413
23305
|
return c.html(renderLayout("\u5B9A\u65F6\u4EFB\u52A1", "templates", body));
|
|
22414
23306
|
});
|
|
22415
23307
|
app.get("/runs", async (c) => {
|
|
22416
|
-
const page =
|
|
23308
|
+
const page = parsePositiveInteger(c.req.query("page") || "1");
|
|
23309
|
+
if (page === null) return c.text("invalid page", 400);
|
|
22417
23310
|
const limit = 50;
|
|
22418
23311
|
const offset = (page - 1) * limit;
|
|
22419
23312
|
const { taskRuns: tr, tasks: tk } = schema_exports;
|
|
@@ -22431,13 +23324,14 @@ var init_web = __esm({
|
|
|
22431
23324
|
childPid: tr.childPid,
|
|
22432
23325
|
taskName: tk.name,
|
|
22433
23326
|
taskAgent: tk.agent
|
|
22434
|
-
}).from(tr).innerJoin(tk, eq(tr.taskId, tk.id)).orderBy(desc(tr.startedAt)).limit(limit).offset(offset);
|
|
23327
|
+
}).from(tr).innerJoin(tk, eq(tr.taskId, tk.id)).orderBy(desc(tr.startedAt), desc(tr.id)).limit(limit).offset(offset);
|
|
22435
23328
|
const totalResult = await db.select({ count: sql`count(*)` }).from(tr);
|
|
22436
23329
|
const total = Number(totalResult[0]?.count ?? 0);
|
|
22437
23330
|
const totalPages = Math.ceil(total / limit);
|
|
22438
23331
|
let rows = "";
|
|
22439
23332
|
const logsHtml = [];
|
|
22440
23333
|
for (const run of runs) {
|
|
23334
|
+
const status = safeStatus(run.status);
|
|
22441
23335
|
const shortSession = run.sessionId ? run.sessionId.slice(4, 7) + "***" + run.sessionId.slice(-3) : "-";
|
|
22442
23336
|
const logBtn = run.log ? `<button class="btn btn-sm" onclick="toggleLog(${run.id})">\u65E5\u5FD7</button>` : "";
|
|
22443
23337
|
rows += `<tr>
|
|
@@ -22445,15 +23339,15 @@ var init_web = __esm({
|
|
|
22445
23339
|
<td><div style="font-weight:500">${esc(run.taskName)} <span class="mu">(#${run.taskId})</span></div>
|
|
22446
23340
|
${run.model ? `<div class="sm"><span class="tag">${esc(run.model)}</span></div>` : ""}</td>
|
|
22447
23341
|
<td><span class="tag">${esc(run.taskAgent)}</span></td>
|
|
22448
|
-
<td><span class="badge b-${
|
|
23342
|
+
<td><span class="badge b-${status}">${status.toUpperCase()}</span></td>
|
|
22449
23343
|
<td class="sm">${formatDuration(run.startedAt, run.finishedAt)}</td>
|
|
22450
23344
|
<td class="sm mu">${run.heartbeatAt ? timeAgo(run.heartbeatAt) : "-"}</td>
|
|
22451
23345
|
<td><button class="btn btn-sm" onclick="showRunDetail(${run.id})">\u8BE6\u60C5</button>${logBtn}</td>
|
|
22452
23346
|
</tr>`;
|
|
22453
23347
|
if (run.log) {
|
|
22454
23348
|
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
|
|
23349
|
+
<div class="panel"><div class="ph"><h3>Run #${run.id} \u65E5\u5FD7 \u2014 ${esc(run.taskName)}</h3></div>
|
|
23350
|
+
<div class="log-box">${esc(run.log)}</div></div></div>`);
|
|
22457
23351
|
}
|
|
22458
23352
|
}
|
|
22459
23353
|
let paging = `<div class="pn">`;
|
|
@@ -22479,17 +23373,18 @@ var init_web = __esm({
|
|
|
22479
23373
|
});
|
|
22480
23374
|
app.get("/system", async (c) => {
|
|
22481
23375
|
const config = loadConfig();
|
|
23376
|
+
const configPath = getConfigPath();
|
|
22482
23377
|
const stats = await TaskService.stats({});
|
|
22483
23378
|
const runningRuns = await TaskRunService.getAllRunningRuns();
|
|
22484
23379
|
const templates = await TaskTemplateService.list(100);
|
|
22485
|
-
const configExists =
|
|
23380
|
+
const configExists = existsSync4(configPath);
|
|
22486
23381
|
let runRows = "";
|
|
22487
23382
|
if (runningRuns.length > 0) {
|
|
22488
23383
|
for (const run of runningRuns) {
|
|
22489
23384
|
const shortS = run.sessionId ? run.sessionId.slice(4, 7) + "***" + run.sessionId.slice(-3) : "-";
|
|
22490
23385
|
runRows += `<tr>
|
|
22491
23386
|
<td class="mu">#${run.id}</td><td>#${run.taskId}</td>
|
|
22492
|
-
<td class="m sm">${shortS}</td>
|
|
23387
|
+
<td class="m sm">${esc(shortS)}</td>
|
|
22493
23388
|
<td class="sm">${esc(run.model) || "-"}</td>
|
|
22494
23389
|
<td class="sm">${formatDate(run.startedAt)}</td>
|
|
22495
23390
|
<td class="sm">${run.heartbeatAt ? timeAgo(run.heartbeatAt) : "-"}</td>
|
|
@@ -22514,19 +23409,14 @@ var init_web = __esm({
|
|
|
22514
23409
|
<h3 style="margin:0 0 12px;font-size:14px">Scheduler \u914D\u7F6E</h3>
|
|
22515
23410
|
<div class="form-row"><label>\u542F\u7528\u8C03\u5EA6</label><input type="checkbox" name="se" ${config.scheduler.enabled ? "checked" : ""}></div>
|
|
22516
23411
|
<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
23412
|
<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
23413
|
</div>
|
|
22524
23414
|
<div class="card">
|
|
22525
23415
|
<h3 style="margin:0 0 12px;font-size:14px">Watchdog \u914D\u7F6E</h3>
|
|
22526
23416
|
<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>\
|
|
23417
|
+
<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>
|
|
23418
|
+
<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
23419
|
<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
23420
|
</div>
|
|
22531
23421
|
</div>
|
|
22532
23422
|
<div style="text-align:center;margin-bottom:24px">
|
|
@@ -22555,7 +23445,7 @@ var init_web = __esm({
|
|
|
22555
23445
|
|
|
22556
23446
|
<div class="card mt16">
|
|
22557
23447
|
<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">${
|
|
23448
|
+
<div class="ir"><span class="ik">\u8DEF\u5F84</span><span class="iv m sm">${esc(configPath)}</span></div>
|
|
22559
23449
|
<div class="ir"><span class="ik">\u6587\u4EF6\u5B58\u5728</span><span class="iv">${configFileStatus}</span></div>
|
|
22560
23450
|
</div>
|
|
22561
23451
|
|
|
@@ -22567,46 +23457,61 @@ var init_web = __esm({
|
|
|
22567
23457
|
return c.html(renderLayout("\u7CFB\u7EDF\u72B6\u6001", "system", body));
|
|
22568
23458
|
});
|
|
22569
23459
|
app.get("/api/tasks/:id", async (c) => {
|
|
22570
|
-
const id =
|
|
23460
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23461
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
22571
23462
|
const task = await TaskService.getById(id);
|
|
22572
23463
|
if (!task) return c.json({ error: "not found" }, 404);
|
|
22573
23464
|
const runs = await TaskRunService.listByTaskId(id);
|
|
22574
23465
|
return c.json({ ...task, _runs: runs });
|
|
22575
23466
|
});
|
|
22576
23467
|
app.get("/api/runs/:id", async (c) => {
|
|
22577
|
-
const id =
|
|
23468
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23469
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
22578
23470
|
const run = await TaskRunService.getById(id);
|
|
22579
23471
|
if (!run) return c.json({ error: "not found" }, 404);
|
|
22580
23472
|
return c.json(run);
|
|
22581
23473
|
});
|
|
22582
23474
|
app.get("/api/templates/:id", async (c) => {
|
|
22583
|
-
const id =
|
|
23475
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23476
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
22584
23477
|
const tmpl = await TaskTemplateService.getById(id);
|
|
22585
23478
|
if (!tmpl) return c.json({ error: "not found" }, 404);
|
|
22586
23479
|
return c.json(tmpl);
|
|
22587
23480
|
});
|
|
22588
23481
|
app.post("/api/tasks/:id/retry", async (c) => {
|
|
22589
|
-
|
|
22590
|
-
return c.json({
|
|
23482
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23483
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
23484
|
+
const task = await TaskService.retry(id);
|
|
23485
|
+
if (task) return c.json({ success: true });
|
|
23486
|
+
return await TaskService.getById(id) ? c.json({ error: "task status does not allow retry" }, 409) : c.json({ error: "not found" }, 404);
|
|
22591
23487
|
});
|
|
22592
23488
|
app.delete("/api/tasks/:id", async (c) => {
|
|
22593
|
-
|
|
22594
|
-
return c.json({
|
|
23489
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23490
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
23491
|
+
const deleted = await TaskService.delete(id);
|
|
23492
|
+
return deleted ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
22595
23493
|
});
|
|
22596
23494
|
app.post("/api/templates/:id/enable", async (c) => {
|
|
22597
|
-
const
|
|
22598
|
-
return c.json({
|
|
23495
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23496
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
23497
|
+
const result = await TaskTemplateService.enable(id);
|
|
23498
|
+
return result ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
22599
23499
|
});
|
|
22600
23500
|
app.post("/api/templates/:id/disable", async (c) => {
|
|
22601
|
-
const
|
|
22602
|
-
return c.json({
|
|
23501
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23502
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
23503
|
+
const result = await TaskTemplateService.disable(id);
|
|
23504
|
+
return result ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
22603
23505
|
});
|
|
22604
23506
|
app.delete("/api/templates/:id", async (c) => {
|
|
22605
|
-
const
|
|
22606
|
-
return c.json({
|
|
23507
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23508
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
23509
|
+
const ok = await TaskTemplateService.delete(id);
|
|
23510
|
+
return ok ? c.json({ success: true }) : c.json({ error: "not found" }, 404);
|
|
22607
23511
|
});
|
|
22608
23512
|
app.post("/api/templates/:id/trigger", async (c) => {
|
|
22609
|
-
const id =
|
|
23513
|
+
const id = parsePositiveInteger(c.req.param("id"));
|
|
23514
|
+
if (id === null) return c.json({ error: "invalid id" }, 400);
|
|
22610
23515
|
const tmpl = await TaskTemplateService.getById(id);
|
|
22611
23516
|
if (!tmpl) return c.json({ error: "not found" }, 404);
|
|
22612
23517
|
const task = await TaskService.add({
|
|
@@ -22618,7 +23523,10 @@ var init_web = __esm({
|
|
|
22618
23523
|
category: tmpl.category,
|
|
22619
23524
|
importance: tmpl.importance,
|
|
22620
23525
|
urgency: tmpl.urgency,
|
|
23526
|
+
batchId: tmpl.batchId,
|
|
22621
23527
|
maxRetries: tmpl.maxRetries,
|
|
23528
|
+
retryBackoffMs: tmpl.retryBackoffMs,
|
|
23529
|
+
timeoutMs: tmpl.timeoutMs,
|
|
22622
23530
|
templateId: tmpl.id
|
|
22623
23531
|
});
|
|
22624
23532
|
return c.json({ success: true, taskId: task.id });
|
|
@@ -22633,22 +23541,29 @@ var init_web = __esm({
|
|
|
22633
23541
|
const bW = body.worker ?? {};
|
|
22634
23542
|
const bS = body.scheduler ?? {};
|
|
22635
23543
|
const bD = body.watchdog ?? {};
|
|
22636
|
-
const merged = {
|
|
22637
|
-
|
|
23544
|
+
const merged = {
|
|
23545
|
+
...current,
|
|
23546
|
+
...body,
|
|
23547
|
+
configVersion: 2,
|
|
23548
|
+
worker: { ...curW, ...bW },
|
|
23549
|
+
scheduler: { ...curS, ...bS },
|
|
23550
|
+
watchdog: { ...curD, ...bD }
|
|
23551
|
+
};
|
|
23552
|
+
writeConfig(validateConfig(merged));
|
|
22638
23553
|
return c.json({ success: true });
|
|
22639
23554
|
} catch (err) {
|
|
22640
|
-
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
23555
|
+
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) }, 400);
|
|
22641
23556
|
}
|
|
22642
23557
|
});
|
|
22643
23558
|
app.post("/api/database/clear", async (c) => {
|
|
22644
23559
|
try {
|
|
22645
|
-
const { tasks: tasks3, taskRuns:
|
|
22646
|
-
await db.delete(
|
|
23560
|
+
const { tasks: tasks3, taskRuns: taskRuns4, taskTemplates: taskTemplates4 } = schema_exports;
|
|
23561
|
+
await db.delete(taskRuns4);
|
|
22647
23562
|
await db.delete(taskTemplates4);
|
|
22648
23563
|
await db.delete(tasks3);
|
|
22649
23564
|
return c.json({ success: true });
|
|
22650
23565
|
} catch (err) {
|
|
22651
|
-
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
23566
|
+
return c.json({ success: false, error: err instanceof Error ? err.message : String(err) }, 500);
|
|
22652
23567
|
}
|
|
22653
23568
|
});
|
|
22654
23569
|
dashboardApp = app;
|
|
@@ -22662,7 +23577,9 @@ var init_web = __esm({
|
|
|
22662
23577
|
// src/gateway/index.ts
|
|
22663
23578
|
var gateway_exports = {};
|
|
22664
23579
|
__export(gateway_exports, {
|
|
22665
|
-
|
|
23580
|
+
acquireLock: () => acquireLock,
|
|
23581
|
+
main: () => main,
|
|
23582
|
+
releaseLock: () => releaseLock
|
|
22666
23583
|
});
|
|
22667
23584
|
function acquireLock() {
|
|
22668
23585
|
const now = Date.now();
|
|
@@ -22671,7 +23588,8 @@ function acquireLock() {
|
|
|
22671
23588
|
sqlite.exec("BEGIN IMMEDIATE");
|
|
22672
23589
|
const existing = sqlite.prepare("SELECT id, pid, heartbeat_at FROM gateway_lock WHERE id = 1").get();
|
|
22673
23590
|
if (existing) {
|
|
22674
|
-
|
|
23591
|
+
const lockHolderAlive = existing.pid !== pid && isProcessAlive(existing.pid);
|
|
23592
|
+
if (now - existing.heartbeat_at < STALE_THRESHOLD_MS && lockHolderAlive) {
|
|
22675
23593
|
sqlite.exec("ROLLBACK");
|
|
22676
23594
|
console.error(JSON.stringify({
|
|
22677
23595
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -22684,7 +23602,7 @@ function acquireLock() {
|
|
|
22684
23602
|
sqlite.exec("DELETE FROM gateway_lock WHERE id = 1");
|
|
22685
23603
|
}
|
|
22686
23604
|
sqlite.exec(
|
|
22687
|
-
"INSERT INTO gateway_lock (id, pid, acquired_at, heartbeat_at) VALUES (1, ?, ?,
|
|
23605
|
+
"INSERT INTO gateway_lock (id, pid, acquired_at, heartbeat_at, ready_at) VALUES (1, ?, ?, ?, NULL)",
|
|
22688
23606
|
[pid, now, now]
|
|
22689
23607
|
);
|
|
22690
23608
|
sqlite.exec("COMMIT");
|
|
@@ -22718,6 +23636,18 @@ function updateLockHeartbeat() {
|
|
|
22718
23636
|
} catch {
|
|
22719
23637
|
}
|
|
22720
23638
|
}
|
|
23639
|
+
function markGatewayReady() {
|
|
23640
|
+
sqlite.exec(
|
|
23641
|
+
"UPDATE gateway_lock SET heartbeat_at = ?, ready_at = ? WHERE pid = ?",
|
|
23642
|
+
[Date.now(), Date.now(), process.pid]
|
|
23643
|
+
);
|
|
23644
|
+
}
|
|
23645
|
+
function markGatewayNotReady() {
|
|
23646
|
+
try {
|
|
23647
|
+
sqlite.exec("UPDATE gateway_lock SET ready_at = NULL WHERE pid = ?", [process.pid]);
|
|
23648
|
+
} catch {
|
|
23649
|
+
}
|
|
23650
|
+
}
|
|
22721
23651
|
async function main() {
|
|
22722
23652
|
console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "info", msg: "SuperTask Gateway starting", pid: process.pid }));
|
|
22723
23653
|
if (!acquireLock()) {
|
|
@@ -22729,6 +23659,21 @@ async function main() {
|
|
|
22729
23659
|
const worker = new WorkerEngine(cfg);
|
|
22730
23660
|
const watchdog = new Watchdog(cfg);
|
|
22731
23661
|
const scheduler = new Scheduler(cfg);
|
|
23662
|
+
const recoveredOrphans = await TaskService.resetOrphanRunningToPending();
|
|
23663
|
+
if (recoveredOrphans > 0) {
|
|
23664
|
+
console.log(JSON.stringify({
|
|
23665
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23666
|
+
level: "warn",
|
|
23667
|
+
msg: "reset orphan running tasks to pending",
|
|
23668
|
+
count: recoveredOrphans
|
|
23669
|
+
}));
|
|
23670
|
+
}
|
|
23671
|
+
initializeGatewayHealth({
|
|
23672
|
+
workerPollIntervalMs: cfg.worker.pollIntervalMs,
|
|
23673
|
+
schedulerEnabled: cfg.scheduler.enabled,
|
|
23674
|
+
schedulerCheckIntervalMs: cfg.scheduler.checkIntervalMs,
|
|
23675
|
+
watchdogCheckIntervalMs: cfg.watchdog.checkIntervalMs
|
|
23676
|
+
});
|
|
22732
23677
|
worker.start();
|
|
22733
23678
|
watchdog.start();
|
|
22734
23679
|
await scheduler.start();
|
|
@@ -22746,6 +23691,7 @@ async function main() {
|
|
|
22746
23691
|
url: `http://localhost:${cfg.dashboard.port}`
|
|
22747
23692
|
}));
|
|
22748
23693
|
}
|
|
23694
|
+
markGatewayReady();
|
|
22749
23695
|
console.log(JSON.stringify({
|
|
22750
23696
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22751
23697
|
level: "info",
|
|
@@ -22759,10 +23705,10 @@ async function main() {
|
|
|
22759
23705
|
shuttingDown = true;
|
|
22760
23706
|
console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "info", msg: `received ${signal}, shutting down...` }));
|
|
22761
23707
|
clearInterval(heartbeatTimer);
|
|
23708
|
+
markGatewayNotReady();
|
|
22762
23709
|
scheduler.stop();
|
|
22763
23710
|
watchdog.stop();
|
|
22764
|
-
const runningIds = worker.
|
|
22765
|
-
await worker.stop();
|
|
23711
|
+
const runningIds = await worker.stop(cfg.worker.shutdownGracePeriodMs);
|
|
22766
23712
|
if (runningIds.length > 0) {
|
|
22767
23713
|
const resetCount = await TaskService.resetRunningToPending(runningIds);
|
|
22768
23714
|
console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "info", msg: "reset running tasks to pending", count: resetCount }));
|
|
@@ -22772,6 +23718,7 @@ async function main() {
|
|
|
22772
23718
|
await TaskRunService.fail(run.id, "Gateway shutdown");
|
|
22773
23719
|
}
|
|
22774
23720
|
releaseLock();
|
|
23721
|
+
resetGatewayHealth();
|
|
22775
23722
|
closeDb();
|
|
22776
23723
|
console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "info", msg: "Gateway stopped" }));
|
|
22777
23724
|
process.exit(0);
|
|
@@ -22799,6 +23746,8 @@ var init_gateway = __esm({
|
|
|
22799
23746
|
init_db2();
|
|
22800
23747
|
init_task_service();
|
|
22801
23748
|
init_task_run_service();
|
|
23749
|
+
init_health();
|
|
23750
|
+
init_process_control();
|
|
22802
23751
|
STALE_THRESHOLD_MS = 3e4;
|
|
22803
23752
|
if (import.meta.main) {
|
|
22804
23753
|
main();
|
|
@@ -22806,247 +23755,87 @@ var init_gateway = __esm({
|
|
|
22806
23755
|
}
|
|
22807
23756
|
});
|
|
22808
23757
|
|
|
22809
|
-
// src/daemon/
|
|
22810
|
-
var
|
|
22811
|
-
__export(
|
|
22812
|
-
|
|
22813
|
-
|
|
22814
|
-
isGatewayRunning: () => isGatewayRunning,
|
|
22815
|
-
uninstall: () => uninstall,
|
|
22816
|
-
upgrade: () => upgrade
|
|
23758
|
+
// src/daemon/update.ts
|
|
23759
|
+
var update_exports = {};
|
|
23760
|
+
__export(update_exports, {
|
|
23761
|
+
installLatestPlugin: () => installLatestPlugin,
|
|
23762
|
+
resolveInstalledPlugin: () => resolveInstalledPlugin
|
|
22817
23763
|
});
|
|
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() {
|
|
23764
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
23765
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync } from "fs";
|
|
23766
|
+
import { homedir as homedir4 } from "os";
|
|
23767
|
+
import { join as join4 } from "path";
|
|
23768
|
+
function pluginAt(packageDir) {
|
|
23769
|
+
const packageJson = join4(packageDir, "package.json");
|
|
23770
|
+
const gatewayEntry = join4(packageDir, "dist/gateway/index.js");
|
|
23771
|
+
if (!existsSync5(packageJson) || !existsSync5(gatewayEntry)) return null;
|
|
22833
23772
|
try {
|
|
22834
|
-
|
|
22835
|
-
|
|
23773
|
+
const pkg = JSON.parse(readFileSync4(packageJson, "utf8"));
|
|
23774
|
+
if (pkg.name !== PACKAGE_NAME || typeof pkg.version !== "string") return null;
|
|
23775
|
+
return { packageDir, gatewayEntry, version: pkg.version };
|
|
22836
23776
|
} catch {
|
|
22837
23777
|
return null;
|
|
22838
23778
|
}
|
|
22839
23779
|
}
|
|
22840
|
-
function
|
|
22841
|
-
|
|
22842
|
-
|
|
22843
|
-
} catch {
|
|
22844
|
-
}
|
|
22845
|
-
}
|
|
22846
|
-
function pm2Bin() {
|
|
22847
|
-
return process.platform === "win32" ? "pm2.cmd" : "pm2";
|
|
22848
|
-
}
|
|
22849
|
-
function isPm2Installed() {
|
|
22850
|
-
try {
|
|
22851
|
-
const cmd = process.platform === "win32" ? "where pm2" : "which pm2";
|
|
22852
|
-
execSync(cmd, { stdio: "pipe" });
|
|
22853
|
-
return true;
|
|
22854
|
-
} catch {
|
|
22855
|
-
return false;
|
|
22856
|
-
}
|
|
22857
|
-
}
|
|
22858
|
-
function installPm2() {
|
|
22859
|
-
console.log("[supertask] Installing pm2...");
|
|
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
|
-
}
|
|
22871
|
-
}
|
|
22872
|
-
function pm2Exec(args) {
|
|
22873
|
-
const bin = pm2Bin();
|
|
22874
|
-
try {
|
|
22875
|
-
const result = spawnSync(bin, args, {
|
|
22876
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
22877
|
-
encoding: "utf-8",
|
|
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) };
|
|
22884
|
-
}
|
|
22885
|
-
}
|
|
22886
|
-
function pm2JsonList() {
|
|
22887
|
-
const { ok, output } = pm2Exec(["jlist"]);
|
|
22888
|
-
if (!ok) return [];
|
|
22889
|
-
try {
|
|
22890
|
-
return JSON.parse(output);
|
|
22891
|
-
} catch {
|
|
22892
|
-
return [];
|
|
22893
|
-
}
|
|
22894
|
-
}
|
|
22895
|
-
function isGatewayRunning() {
|
|
22896
|
-
const list = pm2JsonList();
|
|
22897
|
-
const proc = list.find((p) => p.name === PROCESS_NAME);
|
|
22898
|
-
if (!proc) return false;
|
|
22899
|
-
return proc.pm2_env?.status === "online";
|
|
23780
|
+
function versionParts(version2) {
|
|
23781
|
+
const match2 = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version2);
|
|
23782
|
+
return match2 ? match2.slice(1).map(Number) : null;
|
|
22900
23783
|
}
|
|
22901
|
-
function
|
|
22902
|
-
|
|
22903
|
-
|
|
22904
|
-
|
|
22905
|
-
|
|
22906
|
-
return
|
|
23784
|
+
function compareVersions(left, right) {
|
|
23785
|
+
const a = versionParts(left);
|
|
23786
|
+
const b = versionParts(right);
|
|
23787
|
+
if (!a || !b) return left.localeCompare(right);
|
|
23788
|
+
for (let index2 = 0; index2 < 3; index2 += 1) {
|
|
23789
|
+
if (a[index2] !== b[index2]) return a[index2] - b[index2];
|
|
22907
23790
|
}
|
|
23791
|
+
return 0;
|
|
22908
23792
|
}
|
|
22909
|
-
function
|
|
22910
|
-
|
|
22911
|
-
return pm2Exec([
|
|
22912
|
-
"start",
|
|
22913
|
-
bunPath,
|
|
22914
|
-
"--name",
|
|
22915
|
-
PROCESS_NAME,
|
|
22916
|
-
"--interpreter",
|
|
22917
|
-
"none",
|
|
22918
|
-
"--restart-delay",
|
|
22919
|
-
"5000",
|
|
22920
|
-
"--max-restarts",
|
|
22921
|
-
"30",
|
|
22922
|
-
"--",
|
|
22923
|
-
GATEWAY_ENTRY
|
|
22924
|
-
]);
|
|
23793
|
+
function cacheRoot() {
|
|
23794
|
+
return process.env.SUPERTASK_OPENCODE_CACHE_DIR ?? join4(homedir4(), ".cache/opencode/packages");
|
|
22925
23795
|
}
|
|
22926
|
-
function
|
|
22927
|
-
|
|
22928
|
-
|
|
22929
|
-
|
|
22930
|
-
}
|
|
23796
|
+
function resolveInstalledPlugin() {
|
|
23797
|
+
const override = process.env.SUPERTASK_PLUGIN_PACKAGE_DIR;
|
|
23798
|
+
if (override) {
|
|
23799
|
+
const plugin = pluginAt(override);
|
|
23800
|
+
if (!plugin) throw new Error(`[supertask] \u5B89\u88C5\u5305\u65E0\u6548\u6216\u7F3A\u5C11 Gateway \u6784\u5EFA\u4EA7\u7269: ${override}`);
|
|
23801
|
+
return plugin;
|
|
22931
23802
|
}
|
|
22932
|
-
|
|
22933
|
-
const
|
|
22934
|
-
const
|
|
22935
|
-
if (
|
|
22936
|
-
|
|
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
|
-
}
|
|
23803
|
+
const root = cacheRoot();
|
|
23804
|
+
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)) : [];
|
|
23805
|
+
const installed = packageDirs.map(pluginAt).filter((plugin) => plugin !== null).sort((left, right) => compareVersions(right.version, left.version));
|
|
23806
|
+
if (!installed[0]) {
|
|
23807
|
+
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7F13\u5B58\u4E2D\u627E\u4E0D\u5230\u53EF\u8FD0\u884C\u7684 ${PACKAGE_NAME}`);
|
|
22965
23808
|
}
|
|
22966
|
-
|
|
22967
|
-
console.log("[supertask] Manage with: pm2 status / pm2 logs supertask-gateway");
|
|
23809
|
+
return installed[0];
|
|
22968
23810
|
}
|
|
22969
|
-
function
|
|
22970
|
-
|
|
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");
|
|
23811
|
+
function opencodeBin() {
|
|
23812
|
+
return process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
22978
23813
|
}
|
|
22979
|
-
function
|
|
22980
|
-
const
|
|
22981
|
-
|
|
22982
|
-
|
|
22983
|
-
|
|
22984
|
-
|
|
22985
|
-
|
|
22986
|
-
|
|
22987
|
-
|
|
22988
|
-
|
|
22989
|
-
|
|
22990
|
-
|
|
22991
|
-
|
|
22992
|
-
|
|
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
|
-
}
|
|
23814
|
+
function installLatestPlugin() {
|
|
23815
|
+
const result = spawnSync3(opencodeBin(), [
|
|
23816
|
+
"plugin",
|
|
23817
|
+
`${PACKAGE_NAME}@latest`,
|
|
23818
|
+
"--global",
|
|
23819
|
+
"--force"
|
|
23820
|
+
], {
|
|
23821
|
+
encoding: "utf8",
|
|
23822
|
+
env: process.env,
|
|
23823
|
+
timeout: 12e4
|
|
23824
|
+
});
|
|
23825
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
23826
|
+
if (result.error) {
|
|
23827
|
+
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u66F4\u65B0\u5931\u8D25: ${result.error.message}`);
|
|
23030
23828
|
}
|
|
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);
|
|
23829
|
+
if (result.status !== 0) {
|
|
23830
|
+
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u66F4\u65B0\u5931\u8D25: ${output || `\u9000\u51FA\u7801 ${result.status}`}`);
|
|
23039
23831
|
}
|
|
23040
|
-
|
|
23832
|
+
return resolveInstalledPlugin();
|
|
23041
23833
|
}
|
|
23042
|
-
var
|
|
23043
|
-
var
|
|
23044
|
-
"src/daemon/
|
|
23834
|
+
var PACKAGE_NAME;
|
|
23835
|
+
var init_update2 = __esm({
|
|
23836
|
+
"src/daemon/update.ts"() {
|
|
23045
23837
|
"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");
|
|
23838
|
+
PACKAGE_NAME = "opencode-supertask";
|
|
23050
23839
|
}
|
|
23051
23840
|
});
|
|
23052
23841
|
|
|
@@ -23102,6 +23891,7 @@ function parseDuration(input) {
|
|
|
23102
23891
|
}
|
|
23103
23892
|
|
|
23104
23893
|
// src/cli/index.ts
|
|
23894
|
+
init_pm2();
|
|
23105
23895
|
async function withDb(fn) {
|
|
23106
23896
|
try {
|
|
23107
23897
|
return await fn();
|
|
@@ -23114,9 +23904,14 @@ async function withDb(fn) {
|
|
|
23114
23904
|
}
|
|
23115
23905
|
}
|
|
23116
23906
|
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 () => {
|
|
23907
|
+
program2.name("supertask").description("\u901A\u7528\u4EFB\u52A1\u7BA1\u7406\u7CFB\u7EDF - AI Agent \u4EFB\u52A1\u8C03\u5EA6\u5668").version(getPackageVersion());
|
|
23908
|
+
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
23909
|
const submitCwd = process.cwd();
|
|
23910
|
+
const retryBackoffMs = parseDuration(options.retryBackoff);
|
|
23911
|
+
const timeoutMs = options.timeout ? parseDuration(options.timeout) : null;
|
|
23912
|
+
if (retryBackoffMs === null || options.timeout && timeoutMs === null) {
|
|
23913
|
+
throw new Error("retry-backoff \u6216 timeout \u683C\u5F0F\u65E0\u6548");
|
|
23914
|
+
}
|
|
23120
23915
|
const task = await TaskService.add({
|
|
23121
23916
|
name: options.name,
|
|
23122
23917
|
agent: options.agent,
|
|
@@ -23127,7 +23922,10 @@ program2.command("add").description("\u521B\u5EFA\u65B0\u4EFB\u52A1").requiredOp
|
|
|
23127
23922
|
urgency: parseInt(options.urgency),
|
|
23128
23923
|
batchId: options.batch,
|
|
23129
23924
|
dependsOn: options.depends ? parseInt(options.depends) : void 0,
|
|
23130
|
-
cwd: submitCwd
|
|
23925
|
+
cwd: submitCwd,
|
|
23926
|
+
maxRetries: parseInt(options.maxRetries),
|
|
23927
|
+
retryBackoffMs,
|
|
23928
|
+
timeoutMs
|
|
23131
23929
|
});
|
|
23132
23930
|
console.log(JSON.stringify({ id: task.id, status: "created" }, null, 2));
|
|
23133
23931
|
}));
|
|
@@ -23146,7 +23944,7 @@ program2.command("next").description("\u83B7\u53D6\u4E0B\u4E00\u4E2A\u5F85\u6267
|
|
|
23146
23944
|
urgency: task.urgency
|
|
23147
23945
|
}, null, 2));
|
|
23148
23946
|
} else {
|
|
23149
|
-
console.log(JSON.stringify({ id: null, message: "No
|
|
23947
|
+
console.log(JSON.stringify({ id: null, message: "No executable tasks" }));
|
|
23150
23948
|
}
|
|
23151
23949
|
}));
|
|
23152
23950
|
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 +24032,14 @@ program2.command("delete").description("\u5220\u9664\u4EFB\u52A1").requiredOptio
|
|
|
23234
24032
|
console.log(JSON.stringify({ deleted, id: parseInt(options.id) }));
|
|
23235
24033
|
}));
|
|
23236
24034
|
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 <
|
|
24035
|
+
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
24036
|
let intervalMs = null;
|
|
23239
24037
|
let runAt = null;
|
|
24038
|
+
const retryBackoffMs = parseDuration(options.retryBackoff);
|
|
24039
|
+
const timeoutMs = options.timeout ? parseDuration(options.timeout) : null;
|
|
24040
|
+
if (retryBackoffMs === null || options.timeout && timeoutMs === null) {
|
|
24041
|
+
throw new Error("retry-backoff \u6216 timeout \u683C\u5F0F\u65E0\u6548");
|
|
24042
|
+
}
|
|
23240
24043
|
if (options.interval) {
|
|
23241
24044
|
intervalMs = parseDuration(options.interval);
|
|
23242
24045
|
if (intervalMs === null) {
|
|
@@ -23260,13 +24063,16 @@ program2.command("template").description("\u7BA1\u7406\u4EFB\u52A1\u8C03\u5EA6\u
|
|
|
23260
24063
|
category: options.category,
|
|
23261
24064
|
importance: parseInt(options.importance),
|
|
23262
24065
|
urgency: parseInt(options.urgency),
|
|
24066
|
+
cwd: process.cwd(),
|
|
24067
|
+
batchId: options.batch,
|
|
23263
24068
|
scheduleType: options.type,
|
|
23264
24069
|
cronExpr: options.cron,
|
|
23265
24070
|
intervalMs,
|
|
23266
24071
|
runAt,
|
|
23267
24072
|
maxInstances: parseInt(options.maxInstances),
|
|
23268
24073
|
maxRetries: parseInt(options.maxRetries),
|
|
23269
|
-
retryBackoffMs
|
|
24074
|
+
retryBackoffMs,
|
|
24075
|
+
timeoutMs
|
|
23270
24076
|
});
|
|
23271
24077
|
console.log(JSON.stringify({ id: tmpl.id, status: "created", nextRunAt: tmpl.nextRunAt }, null, 2));
|
|
23272
24078
|
}))
|
|
@@ -23302,36 +24108,29 @@ program2.command("template").description("\u7BA1\u7406\u4EFB\u52A1\u8C03\u5EA6\u
|
|
|
23302
24108
|
}))
|
|
23303
24109
|
);
|
|
23304
24110
|
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(
|
|
24111
|
+
const { existsSync: existsSync6, mkdirSync: mkdirSync4, writeFileSync: writeFileSync3 } = await import("fs");
|
|
24112
|
+
const { dirname: dirname4 } = await import("path");
|
|
24113
|
+
const { getConfigPath: getConfigPath2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
24114
|
+
const configPath = getConfigPath2();
|
|
24115
|
+
if (!existsSync6(configPath)) {
|
|
24116
|
+
const dir = dirname4(configPath);
|
|
24117
|
+
if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
|
|
24118
|
+
writeFileSync3(configPath, JSON.stringify({
|
|
24119
|
+
configVersion: 2,
|
|
23313
24120
|
worker: { maxConcurrency: 2 },
|
|
23314
24121
|
scheduler: { enabled: true }
|
|
23315
24122
|
}, null, 2) + "\n");
|
|
23316
|
-
console.log(JSON.stringify({ created:
|
|
24123
|
+
console.log(JSON.stringify({ created: configPath }));
|
|
23317
24124
|
} else {
|
|
23318
|
-
console.log(JSON.stringify({ exists:
|
|
24125
|
+
console.log(JSON.stringify({ exists: configPath }));
|
|
23319
24126
|
}
|
|
23320
24127
|
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") });
|
|
24128
|
+
getDb2();
|
|
23326
24129
|
console.log(JSON.stringify({ migrated: true }));
|
|
23327
24130
|
}));
|
|
23328
24131
|
program2.command("migrate").description("Run database migrations").action(async () => withDb(async () => {
|
|
23329
24132
|
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") });
|
|
24133
|
+
getDb2();
|
|
23335
24134
|
console.log(JSON.stringify({ migrated: true }));
|
|
23336
24135
|
}));
|
|
23337
24136
|
program2.command("gateway").description("Start the Gateway process (foreground)").action(async () => {
|
|
@@ -23373,26 +24172,20 @@ program2.command("uninstall").description("Stop and remove Gateway pm2 service")
|
|
|
23373
24172
|
process.exit(1);
|
|
23374
24173
|
}
|
|
23375
24174
|
});
|
|
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");
|
|
24175
|
+
program2.command("upgrade").description("Update OpenCode plugin cache and restart Gateway").action(async () => {
|
|
23381
24176
|
console.log("Updating opencode-supertask...");
|
|
24177
|
+
let installed;
|
|
23382
24178
|
try {
|
|
23383
|
-
|
|
23384
|
-
|
|
23385
|
-
stdio: "inherit",
|
|
23386
|
-
timeout: 6e4
|
|
23387
|
-
});
|
|
24179
|
+
const { installLatestPlugin: installLatestPlugin2 } = await Promise.resolve().then(() => (init_update2(), update_exports));
|
|
24180
|
+
installed = installLatestPlugin2();
|
|
23388
24181
|
} catch (err) {
|
|
23389
|
-
console.error(
|
|
23390
|
-
console.error("Try manually:
|
|
24182
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
24183
|
+
console.error("Try manually: opencode plugin opencode-supertask@latest --global --force");
|
|
23391
24184
|
process.exit(1);
|
|
23392
24185
|
}
|
|
23393
24186
|
try {
|
|
23394
24187
|
const { upgrade: pm2Upgrade } = await Promise.resolve().then(() => (init_pm2(), pm2_exports));
|
|
23395
|
-
const result = pm2Upgrade();
|
|
24188
|
+
const result = pm2Upgrade(installed);
|
|
23396
24189
|
console.log(`
|
|
23397
24190
|
SuperTask upgraded: ${result.before ?? "unknown"} \u2192 ${result.after}`);
|
|
23398
24191
|
console.log("Gateway restarted. Please restart opencode to load the new plugin.");
|