opencode-supertask 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -25
- package/dist/cli/index.js +1256 -587
- package/dist/cli/index.js.map +1 -1
- package/dist/gateway/index.js +849 -302
- package/dist/gateway/index.js.map +1 -1
- package/dist/plugin/supertask.js +585 -233
- package/dist/plugin/supertask.js.map +1 -1
- package/dist/web/index.js +546 -213
- package/dist/web/index.js.map +1 -1
- package/dist/worker/index.d.ts +19 -7
- package/dist/worker/index.js +434 -152
- package/dist/worker/index.js.map +1 -1
- package/drizzle/0004_reliability_fields.sql +31 -0
- package/drizzle/meta/0004_snapshot.json +85 -13
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
package/dist/worker/index.js
CHANGED
|
@@ -1205,8 +1205,8 @@ function haveSameKeys(left, right) {
|
|
|
1205
1205
|
if (leftKeys.length !== rightKeys.length) {
|
|
1206
1206
|
return false;
|
|
1207
1207
|
}
|
|
1208
|
-
for (const [
|
|
1209
|
-
if (key !== rightKeys[
|
|
1208
|
+
for (const [index2, key] of leftKeys.entries()) {
|
|
1209
|
+
if (key !== rightKeys[index2]) {
|
|
1210
1210
|
return false;
|
|
1211
1211
|
}
|
|
1212
1212
|
}
|
|
@@ -2772,8 +2772,8 @@ var SQLiteDialect = class {
|
|
|
2772
2772
|
}
|
|
2773
2773
|
const joinsArray = [];
|
|
2774
2774
|
if (joins) {
|
|
2775
|
-
for (const [
|
|
2776
|
-
if (
|
|
2775
|
+
for (const [index2, joinMeta] of joins.entries()) {
|
|
2776
|
+
if (index2 === 0) {
|
|
2777
2777
|
joinsArray.push(sql` `);
|
|
2778
2778
|
}
|
|
2779
2779
|
const table = joinMeta.table;
|
|
@@ -2790,7 +2790,7 @@ var SQLiteDialect = class {
|
|
|
2790
2790
|
sql`${sql.raw(joinMeta.joinType)} join ${table} on ${joinMeta.on}`
|
|
2791
2791
|
);
|
|
2792
2792
|
}
|
|
2793
|
-
if (
|
|
2793
|
+
if (index2 < joins.length - 1) {
|
|
2794
2794
|
joinsArray.push(sql` `);
|
|
2795
2795
|
}
|
|
2796
2796
|
}
|
|
@@ -2803,9 +2803,9 @@ var SQLiteDialect = class {
|
|
|
2803
2803
|
buildOrderBy(orderBy) {
|
|
2804
2804
|
const orderByList = [];
|
|
2805
2805
|
if (orderBy) {
|
|
2806
|
-
for (const [
|
|
2806
|
+
for (const [index2, orderByValue] of orderBy.entries()) {
|
|
2807
2807
|
orderByList.push(orderByValue);
|
|
2808
|
-
if (
|
|
2808
|
+
if (index2 < orderBy.length - 1) {
|
|
2809
2809
|
orderByList.push(sql`, `);
|
|
2810
2810
|
}
|
|
2811
2811
|
}
|
|
@@ -2854,9 +2854,9 @@ var SQLiteDialect = class {
|
|
|
2854
2854
|
const havingSql = having ? sql` having ${having}` : void 0;
|
|
2855
2855
|
const groupByList = [];
|
|
2856
2856
|
if (groupBy) {
|
|
2857
|
-
for (const [
|
|
2857
|
+
for (const [index2, groupByValue] of groupBy.entries()) {
|
|
2858
2858
|
groupByList.push(groupByValue);
|
|
2859
|
-
if (
|
|
2859
|
+
if (index2 < groupBy.length - 1) {
|
|
2860
2860
|
groupByList.push(sql`, `);
|
|
2861
2861
|
}
|
|
2862
2862
|
}
|
|
@@ -4784,6 +4784,52 @@ var BaseSQLiteDatabase = class {
|
|
|
4784
4784
|
}
|
|
4785
4785
|
};
|
|
4786
4786
|
|
|
4787
|
+
// node_modules/drizzle-orm/sqlite-core/indexes.js
|
|
4788
|
+
var IndexBuilderOn = class {
|
|
4789
|
+
constructor(name, unique) {
|
|
4790
|
+
this.name = name;
|
|
4791
|
+
this.unique = unique;
|
|
4792
|
+
}
|
|
4793
|
+
static [entityKind] = "SQLiteIndexBuilderOn";
|
|
4794
|
+
on(...columns) {
|
|
4795
|
+
return new IndexBuilder(this.name, columns, this.unique);
|
|
4796
|
+
}
|
|
4797
|
+
};
|
|
4798
|
+
var IndexBuilder = class {
|
|
4799
|
+
static [entityKind] = "SQLiteIndexBuilder";
|
|
4800
|
+
/** @internal */
|
|
4801
|
+
config;
|
|
4802
|
+
constructor(name, columns, unique) {
|
|
4803
|
+
this.config = {
|
|
4804
|
+
name,
|
|
4805
|
+
columns,
|
|
4806
|
+
unique,
|
|
4807
|
+
where: void 0
|
|
4808
|
+
};
|
|
4809
|
+
}
|
|
4810
|
+
/**
|
|
4811
|
+
* Condition for partial index.
|
|
4812
|
+
*/
|
|
4813
|
+
where(condition) {
|
|
4814
|
+
this.config.where = condition;
|
|
4815
|
+
return this;
|
|
4816
|
+
}
|
|
4817
|
+
/** @internal */
|
|
4818
|
+
build(table) {
|
|
4819
|
+
return new Index(this.config, table);
|
|
4820
|
+
}
|
|
4821
|
+
};
|
|
4822
|
+
var Index = class {
|
|
4823
|
+
static [entityKind] = "SQLiteIndex";
|
|
4824
|
+
config;
|
|
4825
|
+
constructor(config, table) {
|
|
4826
|
+
this.config = { ...config, table };
|
|
4827
|
+
}
|
|
4828
|
+
};
|
|
4829
|
+
function index(name) {
|
|
4830
|
+
return new IndexBuilderOn(name, false);
|
|
4831
|
+
}
|
|
4832
|
+
|
|
4787
4833
|
// node_modules/drizzle-orm/sqlite-core/session.js
|
|
4788
4834
|
var ExecuteResultSync = class extends QueryPromise {
|
|
4789
4835
|
constructor(resultCb) {
|
|
@@ -5130,12 +5176,17 @@ var tasks = sqliteTable("tasks", {
|
|
|
5130
5176
|
resultLog: text("result_log"),
|
|
5131
5177
|
retryCount: integer("retry_count").default(0),
|
|
5132
5178
|
maxRetries: integer("max_retries").default(3),
|
|
5179
|
+
retryBackoffMs: integer("retry_backoff_ms").default(3e4),
|
|
5133
5180
|
// Gateway 扩展字段(毫秒)
|
|
5134
5181
|
retryAfter: integer("retry_after"),
|
|
5135
5182
|
timeoutMs: integer("timeout_ms"),
|
|
5136
5183
|
templateId: integer("template_id"),
|
|
5137
5184
|
scheduledAt: integer("scheduled_at")
|
|
5138
|
-
})
|
|
5185
|
+
}, (table) => [
|
|
5186
|
+
index("tasks_queue_idx").on(table.status, table.retryAfter, table.urgency, table.importance, table.createdAt, table.id),
|
|
5187
|
+
index("tasks_batch_status_idx").on(table.batchId, table.status),
|
|
5188
|
+
index("tasks_template_status_idx").on(table.templateId, table.status)
|
|
5189
|
+
]);
|
|
5139
5190
|
var TASK_CATEGORIES = [
|
|
5140
5191
|
"translate",
|
|
5141
5192
|
"generate",
|
|
@@ -5145,7 +5196,7 @@ var TASK_CATEGORIES = [
|
|
|
5145
5196
|
];
|
|
5146
5197
|
var taskRuns = sqliteTable("task_runs", {
|
|
5147
5198
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
5148
|
-
taskId: integer("task_id").notNull().references(() => tasks.id),
|
|
5199
|
+
taskId: integer("task_id").notNull().references(() => tasks.id, { onDelete: "cascade" }),
|
|
5149
5200
|
sessionId: text("session_id"),
|
|
5150
5201
|
model: text("model"),
|
|
5151
5202
|
status: text("status").default("running"),
|
|
@@ -5158,7 +5209,10 @@ var taskRuns = sqliteTable("task_runs", {
|
|
|
5158
5209
|
heartbeatAt: integer("heartbeat_at"),
|
|
5159
5210
|
workerPid: integer("worker_pid"),
|
|
5160
5211
|
childPid: integer("child_pid")
|
|
5161
|
-
})
|
|
5212
|
+
}, (table) => [
|
|
5213
|
+
index("task_runs_task_started_idx").on(table.taskId, table.startedAt, table.id),
|
|
5214
|
+
index("task_runs_status_heartbeat_idx").on(table.status, table.heartbeatAt)
|
|
5215
|
+
]);
|
|
5162
5216
|
var taskTemplates = sqliteTable("task_templates", {
|
|
5163
5217
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
5164
5218
|
name: text("name").notNull(),
|
|
@@ -5169,6 +5223,7 @@ var taskTemplates = sqliteTable("task_templates", {
|
|
|
5169
5223
|
category: text("category").default("general"),
|
|
5170
5224
|
importance: integer("importance").default(3),
|
|
5171
5225
|
urgency: integer("urgency").default(3),
|
|
5226
|
+
batchId: text("batch_id"),
|
|
5172
5227
|
scheduleType: text("schedule_type").notNull(),
|
|
5173
5228
|
cronExpr: text("cron_expr"),
|
|
5174
5229
|
intervalMs: integer("interval_ms"),
|
|
@@ -5176,12 +5231,15 @@ var taskTemplates = sqliteTable("task_templates", {
|
|
|
5176
5231
|
maxInstances: integer("max_instances").default(1),
|
|
5177
5232
|
maxRetries: integer("max_retries").default(3),
|
|
5178
5233
|
retryBackoffMs: integer("retry_backoff_ms").default(3e4),
|
|
5234
|
+
timeoutMs: integer("timeout_ms"),
|
|
5179
5235
|
lastRunAt: integer("last_run_at"),
|
|
5180
5236
|
nextRunAt: integer("next_run_at"),
|
|
5181
5237
|
enabled: integer("enabled", { mode: "boolean" }).default(true),
|
|
5182
5238
|
createdAt: integer("created_at").default(0),
|
|
5183
5239
|
updatedAt: integer("updated_at").default(0)
|
|
5184
|
-
})
|
|
5240
|
+
}, (table) => [
|
|
5241
|
+
index("task_templates_due_idx").on(table.enabled, table.nextRunAt, table.id)
|
|
5242
|
+
]);
|
|
5185
5243
|
|
|
5186
5244
|
// src/core/db/index.ts
|
|
5187
5245
|
import { existsSync, mkdirSync } from "fs";
|
|
@@ -5195,7 +5253,15 @@ var _db = null;
|
|
|
5195
5253
|
var _migrationRan = false;
|
|
5196
5254
|
function getMigrationsFolder() {
|
|
5197
5255
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5198
|
-
|
|
5256
|
+
let dir = __dirname;
|
|
5257
|
+
for (let i = 0; i < 5; i++) {
|
|
5258
|
+
const candidate = join(dir, "drizzle", "meta", "_journal.json");
|
|
5259
|
+
if (existsSync(candidate)) {
|
|
5260
|
+
return join(dir, "drizzle");
|
|
5261
|
+
}
|
|
5262
|
+
dir = dirname(dir);
|
|
5263
|
+
}
|
|
5264
|
+
return join(__dirname, "../../drizzle");
|
|
5199
5265
|
}
|
|
5200
5266
|
function initDb() {
|
|
5201
5267
|
const dataDir = dirname(DB_FILE_PATH);
|
|
@@ -5210,16 +5276,30 @@ function initDb() {
|
|
|
5210
5276
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
5211
5277
|
pid INTEGER NOT NULL,
|
|
5212
5278
|
acquired_at INTEGER NOT NULL,
|
|
5213
|
-
heartbeat_at INTEGER NOT NULL
|
|
5279
|
+
heartbeat_at INTEGER NOT NULL,
|
|
5280
|
+
ready_at INTEGER
|
|
5214
5281
|
);
|
|
5215
5282
|
`);
|
|
5283
|
+
const gatewayLockColumns = _sqlite.query("PRAGMA table_info(gateway_lock)").all();
|
|
5284
|
+
if (!gatewayLockColumns.some((column) => column.name === "ready_at")) {
|
|
5285
|
+
_sqlite.exec("ALTER TABLE gateway_lock ADD COLUMN ready_at INTEGER;");
|
|
5286
|
+
}
|
|
5216
5287
|
_db = drizzle(_sqlite, { schema: schema_exports });
|
|
5217
5288
|
if (!_migrationRan) {
|
|
5218
|
-
_migrationRan = true;
|
|
5219
5289
|
try {
|
|
5220
5290
|
migrate(_db, { migrationsFolder: getMigrationsFolder() });
|
|
5291
|
+
_sqlite.exec("PRAGMA foreign_keys = ON;");
|
|
5292
|
+
const violations = _sqlite.query("PRAGMA foreign_key_check;").all();
|
|
5293
|
+
if (violations.length > 0) {
|
|
5294
|
+
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`);
|
|
5295
|
+
}
|
|
5296
|
+
_migrationRan = true;
|
|
5221
5297
|
} catch (err) {
|
|
5222
5298
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5299
|
+
_migrationRan = false;
|
|
5300
|
+
_sqlite.close();
|
|
5301
|
+
_sqlite = null;
|
|
5302
|
+
_db = null;
|
|
5223
5303
|
console.error(`[supertask] migration failed: ${msg}`);
|
|
5224
5304
|
throw new Error(`[supertask] DB migration failed: ${msg}`);
|
|
5225
5305
|
}
|
|
@@ -5258,7 +5338,7 @@ function computeBackoff(retryCount, baseMs = 3e4, maxMs = MAX_BACKOFF_MS) {
|
|
|
5258
5338
|
}
|
|
5259
5339
|
|
|
5260
5340
|
// src/core/services/task.service.ts
|
|
5261
|
-
var { tasks: tasks2 } = schema_exports;
|
|
5341
|
+
var { tasks: tasks2, taskRuns: taskRuns2 } = schema_exports;
|
|
5262
5342
|
var TaskService = class {
|
|
5263
5343
|
static buildScopeWhere(scope) {
|
|
5264
5344
|
const conditions = [];
|
|
@@ -5268,9 +5348,27 @@ var TaskService = class {
|
|
|
5268
5348
|
return conditions;
|
|
5269
5349
|
}
|
|
5270
5350
|
static async add(data) {
|
|
5351
|
+
this.validateNewTask(data);
|
|
5271
5352
|
const result = await db.insert(tasks2).values(data).returning();
|
|
5272
5353
|
return result[0];
|
|
5273
5354
|
}
|
|
5355
|
+
static validateNewTask(data) {
|
|
5356
|
+
if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
5357
|
+
if (!data.agent.trim()) throw new Error("agent \u4E0D\u80FD\u4E3A\u7A7A");
|
|
5358
|
+
if (!data.prompt.trim()) throw new Error("prompt \u4E0D\u80FD\u4E3A\u7A7A");
|
|
5359
|
+
this.validateInteger("importance", data.importance, 1, 5);
|
|
5360
|
+
this.validateInteger("urgency", data.urgency, 1, 5);
|
|
5361
|
+
this.validateInteger("maxRetries", data.maxRetries, 0, 1e3);
|
|
5362
|
+
this.validateInteger("retryBackoffMs", data.retryBackoffMs, 0, 864e5);
|
|
5363
|
+
this.validateInteger("timeoutMs", data.timeoutMs, 1e3, 6048e5);
|
|
5364
|
+
this.validateInteger("dependsOn", data.dependsOn, 1, Number.MAX_SAFE_INTEGER);
|
|
5365
|
+
}
|
|
5366
|
+
static validateInteger(name, value, min, max) {
|
|
5367
|
+
if (value === void 0 || value === null) return;
|
|
5368
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
5369
|
+
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
5274
5372
|
static async next(scope = {}) {
|
|
5275
5373
|
const baseConditions = [...this.buildScopeWhere(scope)];
|
|
5276
5374
|
const nowMs = Date.now();
|
|
@@ -5293,7 +5391,7 @@ var TaskService = class {
|
|
|
5293
5391
|
),
|
|
5294
5392
|
and(
|
|
5295
5393
|
eq(tasks2.status, "failed"),
|
|
5296
|
-
sql`${tasks2.retryCount}
|
|
5394
|
+
sql`${tasks2.retryCount} <= ${tasks2.maxRetries}`,
|
|
5297
5395
|
retryAfterFilter
|
|
5298
5396
|
)
|
|
5299
5397
|
);
|
|
@@ -5307,7 +5405,8 @@ var TaskService = class {
|
|
|
5307
5405
|
const allTasks = await db.select().from(tasks2).where(and(...conditions)).orderBy(
|
|
5308
5406
|
desc(tasks2.urgency),
|
|
5309
5407
|
desc(tasks2.importance),
|
|
5310
|
-
asc(tasks2.createdAt)
|
|
5408
|
+
asc(tasks2.createdAt),
|
|
5409
|
+
asc(tasks2.id)
|
|
5311
5410
|
);
|
|
5312
5411
|
for (const task of allTasks) {
|
|
5313
5412
|
if (task.dependsOn) {
|
|
@@ -5328,7 +5427,7 @@ var TaskService = class {
|
|
|
5328
5427
|
eq(tasks2.status, "pending"),
|
|
5329
5428
|
and(
|
|
5330
5429
|
eq(tasks2.status, "failed"),
|
|
5331
|
-
sql`${tasks2.retryCount}
|
|
5430
|
+
sql`${tasks2.retryCount} <= ${tasks2.maxRetries}`
|
|
5332
5431
|
)
|
|
5333
5432
|
),
|
|
5334
5433
|
...this.buildScopeWhere(scope)
|
|
@@ -5341,7 +5440,11 @@ var TaskService = class {
|
|
|
5341
5440
|
return result[0] || null;
|
|
5342
5441
|
}
|
|
5343
5442
|
static async done(id, log, scope = {}) {
|
|
5344
|
-
const conditions = [
|
|
5443
|
+
const conditions = [
|
|
5444
|
+
eq(tasks2.id, id),
|
|
5445
|
+
eq(tasks2.status, "running"),
|
|
5446
|
+
...this.buildScopeWhere(scope)
|
|
5447
|
+
];
|
|
5345
5448
|
const result = await db.update(tasks2).set({
|
|
5346
5449
|
status: "done",
|
|
5347
5450
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
@@ -5352,17 +5455,24 @@ var TaskService = class {
|
|
|
5352
5455
|
}
|
|
5353
5456
|
static async fail(id, log, scope = {}, options) {
|
|
5354
5457
|
const current = await this.getById(id, scope);
|
|
5355
|
-
if (!current) return null;
|
|
5458
|
+
if (!current || current.status !== "running") return null;
|
|
5356
5459
|
const newRetryCount = (current.retryCount ?? 0) + 1;
|
|
5357
5460
|
const maxRetries = current.maxRetries ?? 3;
|
|
5358
|
-
const isDeadLetter = options?.setDeadLetter ?? newRetryCount
|
|
5359
|
-
const conditions = [
|
|
5461
|
+
const isDeadLetter = options?.setDeadLetter ?? newRetryCount > maxRetries;
|
|
5462
|
+
const conditions = [
|
|
5463
|
+
eq(tasks2.id, id),
|
|
5464
|
+
eq(tasks2.status, "running"),
|
|
5465
|
+
...this.buildScopeWhere(scope)
|
|
5466
|
+
];
|
|
5360
5467
|
const result = await db.update(tasks2).set({
|
|
5361
5468
|
status: isDeadLetter ? "dead_letter" : "failed",
|
|
5362
5469
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
5363
5470
|
resultLog: log,
|
|
5364
5471
|
retryCount: newRetryCount,
|
|
5365
|
-
retryAfter: isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
|
|
5472
|
+
retryAfter: isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
|
|
5473
|
+
newRetryCount,
|
|
5474
|
+
current.retryBackoffMs ?? 3e4
|
|
5475
|
+
)
|
|
5366
5476
|
}).where(and(...conditions)).returning();
|
|
5367
5477
|
return result[0] || null;
|
|
5368
5478
|
}
|
|
@@ -5395,8 +5505,20 @@ var TaskService = class {
|
|
|
5395
5505
|
return result.length;
|
|
5396
5506
|
}
|
|
5397
5507
|
static async cancel(id, scope = {}) {
|
|
5398
|
-
const conditions = [
|
|
5399
|
-
|
|
5508
|
+
const conditions = [
|
|
5509
|
+
eq(tasks2.id, id),
|
|
5510
|
+
or(
|
|
5511
|
+
eq(tasks2.status, "pending"),
|
|
5512
|
+
eq(tasks2.status, "running"),
|
|
5513
|
+
eq(tasks2.status, "failed")
|
|
5514
|
+
),
|
|
5515
|
+
...this.buildScopeWhere(scope)
|
|
5516
|
+
];
|
|
5517
|
+
const result = await db.update(tasks2).set({
|
|
5518
|
+
status: "cancelled",
|
|
5519
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
5520
|
+
retryAfter: null
|
|
5521
|
+
}).where(and(...conditions)).returning();
|
|
5400
5522
|
return result[0] || null;
|
|
5401
5523
|
}
|
|
5402
5524
|
static async retry(id, scope = {}) {
|
|
@@ -5408,7 +5530,8 @@ var TaskService = class {
|
|
|
5408
5530
|
status: "pending",
|
|
5409
5531
|
startedAt: null,
|
|
5410
5532
|
finishedAt: null,
|
|
5411
|
-
retryAfter: null
|
|
5533
|
+
retryAfter: null,
|
|
5534
|
+
retryCount: 0
|
|
5412
5535
|
}).where(and(...conditions)).returning();
|
|
5413
5536
|
return result[0] || null;
|
|
5414
5537
|
}
|
|
@@ -5422,7 +5545,8 @@ var TaskService = class {
|
|
|
5422
5545
|
status: "pending",
|
|
5423
5546
|
startedAt: null,
|
|
5424
5547
|
finishedAt: null,
|
|
5425
|
-
retryAfter: null
|
|
5548
|
+
retryAfter: null,
|
|
5549
|
+
retryCount: 0
|
|
5426
5550
|
}).where(and(...conditions)).returning();
|
|
5427
5551
|
return result.length;
|
|
5428
5552
|
}
|
|
@@ -5490,6 +5614,9 @@ var TaskService = class {
|
|
|
5490
5614
|
}
|
|
5491
5615
|
static async delete(id, scope = {}) {
|
|
5492
5616
|
const conditions = [eq(tasks2.id, id), ...this.buildScopeWhere(scope)];
|
|
5617
|
+
const existing = await db.select({ id: tasks2.id }).from(tasks2).where(and(...conditions)).limit(1);
|
|
5618
|
+
if (!existing[0]) return false;
|
|
5619
|
+
await db.delete(taskRuns2).where(eq(taskRuns2.taskId, id));
|
|
5493
5620
|
const result = await db.delete(tasks2).where(and(...conditions)).returning();
|
|
5494
5621
|
return result.length > 0;
|
|
5495
5622
|
}
|
|
@@ -5507,59 +5634,59 @@ var TaskService = class {
|
|
|
5507
5634
|
};
|
|
5508
5635
|
|
|
5509
5636
|
// src/core/services/task-run.service.ts
|
|
5510
|
-
var { taskRuns:
|
|
5637
|
+
var { taskRuns: taskRuns3 } = schema_exports;
|
|
5511
5638
|
var TaskRunService = class {
|
|
5512
5639
|
static async create(data) {
|
|
5513
|
-
const result = await db.insert(
|
|
5640
|
+
const result = await db.insert(taskRuns3).values(data).returning();
|
|
5514
5641
|
return result[0];
|
|
5515
5642
|
}
|
|
5516
5643
|
static async updateSessionId(id, sessionId) {
|
|
5517
|
-
const result = await db.update(
|
|
5644
|
+
const result = await db.update(taskRuns3).set({ sessionId }).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
5518
5645
|
return result[0] || null;
|
|
5519
5646
|
}
|
|
5520
5647
|
static async done(id, log) {
|
|
5521
|
-
const result = await db.update(
|
|
5648
|
+
const result = await db.update(taskRuns3).set({
|
|
5522
5649
|
status: "done",
|
|
5523
5650
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
5524
5651
|
log
|
|
5525
|
-
}).where(eq(
|
|
5652
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
5526
5653
|
return result[0] || null;
|
|
5527
5654
|
}
|
|
5528
5655
|
static async fail(id, log) {
|
|
5529
|
-
const result = await db.update(
|
|
5656
|
+
const result = await db.update(taskRuns3).set({
|
|
5530
5657
|
status: "failed",
|
|
5531
5658
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
5532
5659
|
log
|
|
5533
|
-
}).where(eq(
|
|
5660
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
5534
5661
|
return result[0] || null;
|
|
5535
5662
|
}
|
|
5536
5663
|
static async heartbeat(id) {
|
|
5537
|
-
const result = await db.update(
|
|
5664
|
+
const result = await db.update(taskRuns3).set({ heartbeatAt: Date.now() }).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
5538
5665
|
return result[0] || null;
|
|
5539
5666
|
}
|
|
5540
5667
|
static async updatePid(id, workerPid, childPid) {
|
|
5541
|
-
const result = await db.update(
|
|
5668
|
+
const result = await db.update(taskRuns3).set({
|
|
5542
5669
|
workerPid,
|
|
5543
5670
|
childPid,
|
|
5544
5671
|
lockedAt: Date.now(),
|
|
5545
5672
|
lockedBy: `gateway-${process.pid}`
|
|
5546
|
-
}).where(eq(
|
|
5673
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
5547
5674
|
return result[0] || null;
|
|
5548
5675
|
}
|
|
5549
5676
|
static async getById(id) {
|
|
5550
|
-
const result = await db.select().from(
|
|
5677
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.id, id));
|
|
5551
5678
|
return result[0] || null;
|
|
5552
5679
|
}
|
|
5553
5680
|
static async listByTaskId(taskId) {
|
|
5554
|
-
return await db.select().from(
|
|
5681
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
5555
5682
|
}
|
|
5556
5683
|
static async getLatestByTaskId(taskId) {
|
|
5557
|
-
const result = await db.select().from(
|
|
5684
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id)).limit(1);
|
|
5558
5685
|
return result[0] || null;
|
|
5559
5686
|
}
|
|
5560
5687
|
static async getLatestByTaskIds(taskIds) {
|
|
5561
5688
|
if (taskIds.length === 0) return /* @__PURE__ */ new Map();
|
|
5562
|
-
const latestRuns = await db.select().from(
|
|
5689
|
+
const latestRuns = await db.select().from(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
5563
5690
|
const result = /* @__PURE__ */ new Map();
|
|
5564
5691
|
for (const run of latestRuns) {
|
|
5565
5692
|
if (!result.has(run.taskId)) {
|
|
@@ -5573,22 +5700,23 @@ var TaskRunService = class {
|
|
|
5573
5700
|
const cutoffSec = Math.floor(cutoffMs / 1e3);
|
|
5574
5701
|
const { tasks: tasksTable } = schema_exports;
|
|
5575
5702
|
const result = await db.select({
|
|
5576
|
-
runId:
|
|
5577
|
-
taskId:
|
|
5578
|
-
childPid:
|
|
5703
|
+
runId: taskRuns3.id,
|
|
5704
|
+
taskId: taskRuns3.taskId,
|
|
5705
|
+
childPid: taskRuns3.childPid,
|
|
5579
5706
|
taskRetryCount: tasksTable.retryCount,
|
|
5580
|
-
taskMaxRetries: tasksTable.maxRetries
|
|
5581
|
-
|
|
5707
|
+
taskMaxRetries: tasksTable.maxRetries,
|
|
5708
|
+
taskRetryBackoffMs: tasksTable.retryBackoffMs
|
|
5709
|
+
}).from(taskRuns3).innerJoin(tasksTable, eq(taskRuns3.taskId, tasksTable.id)).where(
|
|
5582
5710
|
and(
|
|
5583
|
-
eq(
|
|
5711
|
+
eq(taskRuns3.status, "running"),
|
|
5584
5712
|
or(
|
|
5585
5713
|
and(
|
|
5586
|
-
sql`${
|
|
5587
|
-
sql`${
|
|
5714
|
+
sql`${taskRuns3.heartbeatAt} IS NULL`,
|
|
5715
|
+
sql`${taskRuns3.startedAt} < ${cutoffSec}`
|
|
5588
5716
|
),
|
|
5589
5717
|
and(
|
|
5590
|
-
sql`${
|
|
5591
|
-
sql`${
|
|
5718
|
+
sql`${taskRuns3.heartbeatAt} IS NOT NULL`,
|
|
5719
|
+
sql`${taskRuns3.heartbeatAt} < ${cutoffMs}`
|
|
5592
5720
|
)
|
|
5593
5721
|
)
|
|
5594
5722
|
)
|
|
@@ -5598,57 +5726,103 @@ var TaskRunService = class {
|
|
|
5598
5726
|
taskId: row.taskId,
|
|
5599
5727
|
childPid: row.childPid,
|
|
5600
5728
|
taskRetryCount: row.taskRetryCount ?? 0,
|
|
5601
|
-
taskMaxRetries: row.taskMaxRetries ?? 3
|
|
5729
|
+
taskMaxRetries: row.taskMaxRetries ?? 3,
|
|
5730
|
+
taskRetryBackoffMs: row.taskRetryBackoffMs ?? 3e4
|
|
5602
5731
|
}));
|
|
5603
5732
|
}
|
|
5604
5733
|
static async getRunningRunByTaskId(taskId) {
|
|
5605
|
-
const result = await db.select().from(
|
|
5734
|
+
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);
|
|
5606
5735
|
return result[0] || null;
|
|
5607
5736
|
}
|
|
5608
5737
|
static async deleteByTaskIds(taskIds) {
|
|
5609
5738
|
if (taskIds.length === 0) return 0;
|
|
5610
|
-
const result = await db.delete(
|
|
5739
|
+
const result = await db.delete(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).returning();
|
|
5611
5740
|
return result.length;
|
|
5612
5741
|
}
|
|
5613
5742
|
static async getAllRunningRuns() {
|
|
5614
|
-
return await db.select().from(
|
|
5743
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.status, "running"));
|
|
5615
5744
|
}
|
|
5616
5745
|
};
|
|
5617
5746
|
|
|
5618
5747
|
// src/worker/index.ts
|
|
5619
5748
|
import { spawn } from "child_process";
|
|
5749
|
+
|
|
5750
|
+
// src/gateway/health.ts
|
|
5751
|
+
var state = null;
|
|
5752
|
+
function markGatewayActivity(component) {
|
|
5753
|
+
if (state) state.lastActivityAt[component] = Date.now();
|
|
5754
|
+
}
|
|
5755
|
+
|
|
5756
|
+
// src/core/process-control.ts
|
|
5757
|
+
function isSafePid(pid) {
|
|
5758
|
+
return Number.isInteger(pid) && pid > 1 && pid !== process.pid;
|
|
5759
|
+
}
|
|
5760
|
+
function signalSpawnedProcessTree(pid, signal) {
|
|
5761
|
+
if (!isSafePid(pid)) return false;
|
|
5762
|
+
if (process.platform !== "win32") {
|
|
5763
|
+
try {
|
|
5764
|
+
process.kill(-pid, signal);
|
|
5765
|
+
return true;
|
|
5766
|
+
} catch {
|
|
5767
|
+
}
|
|
5768
|
+
}
|
|
5769
|
+
try {
|
|
5770
|
+
process.kill(pid, signal);
|
|
5771
|
+
return true;
|
|
5772
|
+
} catch {
|
|
5773
|
+
return false;
|
|
5774
|
+
}
|
|
5775
|
+
}
|
|
5776
|
+
|
|
5777
|
+
// src/worker/index.ts
|
|
5778
|
+
var DEFAULT_MAX_OUTPUT_CHARS = 64 * 1024;
|
|
5779
|
+
var FORBIDDEN_AGENT = "supertask-runner";
|
|
5620
5780
|
var WorkerEngine = class {
|
|
5621
5781
|
activeBatchIds = /* @__PURE__ */ new Set();
|
|
5622
5782
|
runningTasks = /* @__PURE__ */ new Map();
|
|
5623
5783
|
stopped = false;
|
|
5624
5784
|
pollTimer = null;
|
|
5625
5785
|
heartbeatTimer = null;
|
|
5786
|
+
pollCyclePromise = null;
|
|
5626
5787
|
cfg;
|
|
5627
|
-
|
|
5788
|
+
opencodeBin;
|
|
5789
|
+
maxOutputChars;
|
|
5790
|
+
constructor(cfg, options = {}) {
|
|
5628
5791
|
this.cfg = cfg.worker;
|
|
5792
|
+
this.opencodeBin = options.opencodeBin ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
5793
|
+
this.maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
5629
5794
|
}
|
|
5630
5795
|
start() {
|
|
5631
5796
|
this.stopped = false;
|
|
5797
|
+
markGatewayActivity("worker");
|
|
5632
5798
|
this.poll();
|
|
5633
5799
|
this.heartbeatTimer = setInterval(() => this.updateHeartbeats(), this.cfg.heartbeatIntervalMs);
|
|
5634
5800
|
}
|
|
5635
|
-
stop() {
|
|
5801
|
+
async stop(gracePeriodMs = 0) {
|
|
5636
5802
|
this.stopped = true;
|
|
5637
5803
|
if (this.pollTimer) {
|
|
5638
5804
|
clearTimeout(this.pollTimer);
|
|
5639
5805
|
this.pollTimer = null;
|
|
5640
5806
|
}
|
|
5807
|
+
if (this.pollCyclePromise) await this.pollCyclePromise;
|
|
5808
|
+
if (gracePeriodMs > 0 && this.runningTasks.size > 0) {
|
|
5809
|
+
const deadline = Date.now() + gracePeriodMs;
|
|
5810
|
+
while (this.runningTasks.size > 0 && Date.now() < deadline) {
|
|
5811
|
+
await Bun.sleep(Math.min(50, deadline - Date.now()));
|
|
5812
|
+
}
|
|
5813
|
+
}
|
|
5641
5814
|
if (this.heartbeatTimer) {
|
|
5642
5815
|
clearInterval(this.heartbeatTimer);
|
|
5643
5816
|
this.heartbeatTimer = null;
|
|
5644
5817
|
}
|
|
5818
|
+
const interruptedTaskIds = [...this.runningTasks.keys()];
|
|
5645
5819
|
const killPromises = [];
|
|
5646
|
-
for (const
|
|
5820
|
+
for (const entry of this.runningTasks.values()) {
|
|
5647
5821
|
entry.shutdown = true;
|
|
5648
5822
|
killPromises.push(this.killEntry(entry));
|
|
5649
5823
|
}
|
|
5650
|
-
|
|
5651
|
-
|
|
5824
|
+
await Promise.allSettled(killPromises);
|
|
5825
|
+
return interruptedTaskIds;
|
|
5652
5826
|
}
|
|
5653
5827
|
getRunningTaskIds() {
|
|
5654
5828
|
return [...this.runningTasks.keys()];
|
|
@@ -5658,136 +5832,244 @@ var WorkerEngine = class {
|
|
|
5658
5832
|
}
|
|
5659
5833
|
poll() {
|
|
5660
5834
|
if (this.stopped) return;
|
|
5661
|
-
|
|
5835
|
+
markGatewayActivity("worker");
|
|
5836
|
+
this.pollCyclePromise = this.tryDispatch().finally(() => {
|
|
5837
|
+
this.pollCyclePromise = null;
|
|
5662
5838
|
if (this.stopped) return;
|
|
5663
5839
|
this.pollTimer = setTimeout(() => this.poll(), this.cfg.pollIntervalMs);
|
|
5664
5840
|
});
|
|
5665
5841
|
}
|
|
5666
5842
|
async tryDispatch() {
|
|
5843
|
+
await this.reconcileCancelledTasks();
|
|
5667
5844
|
while (!this.stopped && this.runningTasks.size < this.cfg.maxConcurrency) {
|
|
5845
|
+
let task;
|
|
5846
|
+
try {
|
|
5847
|
+
task = await TaskService.next({ excludedBatchIds: [...this.activeBatchIds] });
|
|
5848
|
+
} catch (err) {
|
|
5849
|
+
this.logError("task claim failed", err);
|
|
5850
|
+
break;
|
|
5851
|
+
}
|
|
5852
|
+
if (!task) break;
|
|
5853
|
+
if (this.stopped) break;
|
|
5854
|
+
if (!await TaskService.start(task.id)) continue;
|
|
5855
|
+
if (task.batchId) this.activeBatchIds.add(task.batchId);
|
|
5856
|
+
if (this.stopped) {
|
|
5857
|
+
await TaskService.resetRunningToPending([task.id]);
|
|
5858
|
+
this.releaseBatch(task);
|
|
5859
|
+
break;
|
|
5860
|
+
}
|
|
5668
5861
|
try {
|
|
5669
|
-
const excludedBatchIds = [...this.activeBatchIds];
|
|
5670
|
-
const task = await TaskService.next({ excludedBatchIds });
|
|
5671
|
-
if (!task) break;
|
|
5672
|
-
if (!await TaskService.start(task.id)) continue;
|
|
5673
|
-
if (task.batchId) {
|
|
5674
|
-
this.activeBatchIds.add(task.batchId);
|
|
5675
|
-
}
|
|
5676
5862
|
const run = await TaskRunService.create({
|
|
5677
5863
|
taskId: task.id,
|
|
5678
5864
|
model: this.resolveModel(task.model),
|
|
5679
5865
|
status: "running"
|
|
5680
5866
|
});
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5867
|
+
if (this.stopped) {
|
|
5868
|
+
await TaskRunService.fail(run.id, "Gateway shutdown before spawn");
|
|
5869
|
+
await TaskService.resetRunningToPending([task.id]);
|
|
5870
|
+
this.releaseBatch(task);
|
|
5871
|
+
break;
|
|
5685
5872
|
}
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
}
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
}
|
|
5737
|
-
console.error(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "error", msg: "task spawn error", taskId: task.id, error: err.message }));
|
|
5873
|
+
if (task.agent === FORBIDDEN_AGENT) {
|
|
5874
|
+
const message = `\u7981\u6B62\u6267\u884C\u9012\u5F52 Agent: ${FORBIDDEN_AGENT}`;
|
|
5875
|
+
await TaskRunService.fail(run.id, message);
|
|
5876
|
+
await TaskService.fail(task.id, message, {}, { setDeadLetter: true });
|
|
5877
|
+
this.releaseBatch(task);
|
|
5878
|
+
continue;
|
|
5879
|
+
}
|
|
5880
|
+
this.spawnTask(task, run.id);
|
|
5881
|
+
} catch (err) {
|
|
5882
|
+
this.releaseBatch(task);
|
|
5883
|
+
const message = `Worker \u542F\u52A8\u4EFB\u52A1\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`;
|
|
5884
|
+
try {
|
|
5885
|
+
await TaskService.fail(task.id, message);
|
|
5886
|
+
} catch (failErr) {
|
|
5887
|
+
this.logError("failed to compensate task startup", failErr, task.id);
|
|
5888
|
+
}
|
|
5889
|
+
this.logError("task dispatch failed", err, task.id);
|
|
5890
|
+
}
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
5893
|
+
spawnTask(task, runId) {
|
|
5894
|
+
const model = this.resolveModel(task.model);
|
|
5895
|
+
const args = ["run", "--agent", task.agent, "--format", "json"];
|
|
5896
|
+
if (model) args.push("-m", model);
|
|
5897
|
+
args.push(task.prompt);
|
|
5898
|
+
const child = spawn(this.opencodeBin, args, {
|
|
5899
|
+
cwd: task.cwd || process.cwd(),
|
|
5900
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5901
|
+
detached: process.platform !== "win32"
|
|
5902
|
+
});
|
|
5903
|
+
const entry = {
|
|
5904
|
+
task,
|
|
5905
|
+
runId,
|
|
5906
|
+
child,
|
|
5907
|
+
output: "",
|
|
5908
|
+
sessionId: null,
|
|
5909
|
+
timeoutTimer: null,
|
|
5910
|
+
shutdown: false,
|
|
5911
|
+
settled: false
|
|
5912
|
+
};
|
|
5913
|
+
this.runningTasks.set(task.id, entry);
|
|
5914
|
+
const handleData = (data) => {
|
|
5915
|
+
const text2 = data.toString();
|
|
5916
|
+
entry.output = (entry.output + text2).slice(-this.maxOutputChars);
|
|
5917
|
+
process.stdout.write(text2);
|
|
5918
|
+
const match = entry.output.match(/"sessionID"\s*:\s*"(ses_[^"]+)"/);
|
|
5919
|
+
if (match?.[1] && match[1] !== entry.sessionId) {
|
|
5920
|
+
entry.sessionId = match[1];
|
|
5921
|
+
TaskRunService.updateSessionId(runId, match[1]).catch((err) => {
|
|
5922
|
+
this.logError("sessionId update failed", err, task.id);
|
|
5738
5923
|
});
|
|
5924
|
+
}
|
|
5925
|
+
};
|
|
5926
|
+
child.stdout?.on("data", handleData);
|
|
5927
|
+
child.stderr?.on("data", handleData);
|
|
5928
|
+
child.once("error", (err) => {
|
|
5929
|
+
void this.finishEntry(entry, null, `\u65E0\u6CD5\u542F\u52A8 opencode\uFF1A${err.message}`);
|
|
5930
|
+
});
|
|
5931
|
+
child.once("close", (code, signal) => {
|
|
5932
|
+
const failure = code === 0 ? void 0 : `opencode \u9000\u51FA\u7801 ${code ?? "null"}${signal ? `\uFF0C\u4FE1\u53F7 ${signal}` : ""}`;
|
|
5933
|
+
void this.finishEntry(entry, code, failure);
|
|
5934
|
+
});
|
|
5935
|
+
const timeoutMs = task.timeoutMs ?? this.cfg.taskTimeoutMs;
|
|
5936
|
+
if (timeoutMs > 0) {
|
|
5937
|
+
entry.timeoutTimer = setTimeout(() => {
|
|
5938
|
+
this.signalEntry(entry, "SIGKILL");
|
|
5939
|
+
void this.finishEntry(entry, null, `\u4EFB\u52A1\u8D85\u65F6\uFF08${timeoutMs}ms\uFF09`);
|
|
5940
|
+
}, timeoutMs);
|
|
5941
|
+
}
|
|
5942
|
+
TaskRunService.updatePid(runId, process.pid, child.pid ?? 0).catch((err) => {
|
|
5943
|
+
this.signalEntry(entry, "SIGKILL");
|
|
5944
|
+
void this.finishEntry(entry, null, `\u8BB0\u5F55 Worker PID \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
5945
|
+
});
|
|
5946
|
+
}
|
|
5947
|
+
async finishEntry(entry, code, failure) {
|
|
5948
|
+
if (entry.settled) return;
|
|
5949
|
+
entry.settled = true;
|
|
5950
|
+
if (entry.timeoutTimer) {
|
|
5951
|
+
clearTimeout(entry.timeoutTimer);
|
|
5952
|
+
entry.timeoutTimer = null;
|
|
5953
|
+
}
|
|
5954
|
+
try {
|
|
5955
|
+
if (entry.shutdown) return;
|
|
5956
|
+
const currentRun = await TaskRunService.getById(entry.runId);
|
|
5957
|
+
if (!currentRun || currentRun.status !== "running") return;
|
|
5958
|
+
const output = entry.output.trim();
|
|
5959
|
+
const log = failure ? `${failure}${output ? `
|
|
5960
|
+
${output}` : ""}` : output;
|
|
5961
|
+
if (code === 0 && !failure) {
|
|
5962
|
+
const completed = await TaskService.done(entry.task.id, log);
|
|
5963
|
+
if (completed) {
|
|
5964
|
+
await TaskRunService.done(entry.runId, log);
|
|
5965
|
+
console.log(JSON.stringify({
|
|
5966
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5967
|
+
level: "info",
|
|
5968
|
+
msg: "task done",
|
|
5969
|
+
taskId: entry.task.id
|
|
5970
|
+
}));
|
|
5971
|
+
return;
|
|
5972
|
+
}
|
|
5973
|
+
await TaskRunService.fail(entry.runId, "\u4EFB\u52A1\u72B6\u6001\u5DF2\u88AB\u5176\u4ED6\u64CD\u4F5C\u6539\u53D8");
|
|
5974
|
+
return;
|
|
5975
|
+
}
|
|
5976
|
+
await TaskRunService.fail(entry.runId, log);
|
|
5977
|
+
const failed = await TaskService.fail(entry.task.id, log);
|
|
5978
|
+
if (!failed) {
|
|
5979
|
+
this.logError("task failure state transition rejected", failure ?? "unknown failure", entry.task.id);
|
|
5980
|
+
}
|
|
5981
|
+
console.error(JSON.stringify({
|
|
5982
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5983
|
+
level: "error",
|
|
5984
|
+
msg: "task failed",
|
|
5985
|
+
taskId: entry.task.id,
|
|
5986
|
+
error: failure
|
|
5987
|
+
}));
|
|
5988
|
+
} finally {
|
|
5989
|
+
this.runningTasks.delete(entry.task.id);
|
|
5990
|
+
this.releaseBatch(entry.task);
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
async reconcileCancelledTasks() {
|
|
5994
|
+
for (const entry of [...this.runningTasks.values()]) {
|
|
5995
|
+
try {
|
|
5996
|
+
const task = await TaskService.getById(entry.task.id);
|
|
5997
|
+
if (task?.status === "cancelled") await this.cancelEntry(entry);
|
|
5739
5998
|
} catch (err) {
|
|
5740
|
-
|
|
5741
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5742
|
-
level: "error",
|
|
5743
|
-
msg: "tryDispatch iteration failed",
|
|
5744
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5745
|
-
}));
|
|
5746
|
-
break;
|
|
5999
|
+
this.logError("cancel reconciliation failed", err, entry.task.id);
|
|
5747
6000
|
}
|
|
5748
6001
|
}
|
|
5749
6002
|
}
|
|
6003
|
+
async cancelEntry(entry) {
|
|
6004
|
+
if (entry.settled) return;
|
|
6005
|
+
entry.settled = true;
|
|
6006
|
+
if (entry.timeoutTimer) {
|
|
6007
|
+
clearTimeout(entry.timeoutTimer);
|
|
6008
|
+
entry.timeoutTimer = null;
|
|
6009
|
+
}
|
|
6010
|
+
try {
|
|
6011
|
+
await this.killEntry(entry);
|
|
6012
|
+
const output = entry.output.trim();
|
|
6013
|
+
const log = `\u4EFB\u52A1\u5DF2\u53D6\u6D88${output ? `
|
|
6014
|
+
${output}` : ""}`;
|
|
6015
|
+
await TaskRunService.fail(entry.runId, log);
|
|
6016
|
+
console.log(JSON.stringify({
|
|
6017
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6018
|
+
level: "info",
|
|
6019
|
+
msg: "running task cancelled",
|
|
6020
|
+
taskId: entry.task.id
|
|
6021
|
+
}));
|
|
6022
|
+
} finally {
|
|
6023
|
+
this.runningTasks.delete(entry.task.id);
|
|
6024
|
+
this.releaseBatch(entry.task);
|
|
6025
|
+
}
|
|
6026
|
+
}
|
|
5750
6027
|
async updateHeartbeats() {
|
|
5751
|
-
for (const
|
|
6028
|
+
for (const entry of this.runningTasks.values()) {
|
|
5752
6029
|
try {
|
|
5753
6030
|
await TaskRunService.heartbeat(entry.runId);
|
|
5754
|
-
} catch {
|
|
6031
|
+
} catch (err) {
|
|
6032
|
+
this.logError("heartbeat update failed", err, entry.task.id);
|
|
5755
6033
|
}
|
|
5756
6034
|
}
|
|
5757
6035
|
}
|
|
5758
6036
|
killEntry(entry) {
|
|
5759
|
-
if (entry.child.exitCode !== null) {
|
|
6037
|
+
if (entry.child.exitCode !== null || entry.child.signalCode !== null) {
|
|
5760
6038
|
return Promise.resolve();
|
|
5761
6039
|
}
|
|
5762
6040
|
return new Promise((resolve) => {
|
|
5763
6041
|
const timeout = setTimeout(() => {
|
|
5764
|
-
|
|
5765
|
-
if (entry.child.pid) process.kill(entry.child.pid, "SIGKILL");
|
|
5766
|
-
} catch {
|
|
5767
|
-
}
|
|
6042
|
+
this.signalEntry(entry, "SIGKILL");
|
|
5768
6043
|
resolve();
|
|
5769
6044
|
}, 5e3);
|
|
5770
|
-
entry.child.
|
|
6045
|
+
entry.child.once("close", () => {
|
|
5771
6046
|
clearTimeout(timeout);
|
|
5772
6047
|
resolve();
|
|
5773
6048
|
});
|
|
5774
|
-
|
|
5775
|
-
if (entry.child.pid) {
|
|
5776
|
-
entry.child.kill("SIGTERM");
|
|
5777
|
-
} else {
|
|
5778
|
-
clearTimeout(timeout);
|
|
5779
|
-
resolve();
|
|
5780
|
-
}
|
|
5781
|
-
} catch {
|
|
5782
|
-
clearTimeout(timeout);
|
|
5783
|
-
resolve();
|
|
5784
|
-
}
|
|
6049
|
+
this.signalEntry(entry, "SIGTERM");
|
|
5785
6050
|
});
|
|
5786
6051
|
}
|
|
6052
|
+
signalEntry(entry, signal) {
|
|
6053
|
+
const pid = entry.child.pid;
|
|
6054
|
+
if (!pid) return;
|
|
6055
|
+
signalSpawnedProcessTree(pid, signal);
|
|
6056
|
+
}
|
|
6057
|
+
releaseBatch(task) {
|
|
6058
|
+
if (task.batchId) this.activeBatchIds.delete(task.batchId);
|
|
6059
|
+
}
|
|
5787
6060
|
resolveModel(taskModel) {
|
|
5788
6061
|
if (!taskModel || taskModel === "default") return null;
|
|
5789
6062
|
return taskModel;
|
|
5790
6063
|
}
|
|
6064
|
+
logError(message, error, taskId) {
|
|
6065
|
+
console.error(JSON.stringify({
|
|
6066
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6067
|
+
level: "error",
|
|
6068
|
+
msg: message,
|
|
6069
|
+
taskId,
|
|
6070
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6071
|
+
}));
|
|
6072
|
+
}
|
|
5791
6073
|
};
|
|
5792
6074
|
export {
|
|
5793
6075
|
WorkerEngine
|