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/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";
|
|
@@ -5218,16 +5276,30 @@ function initDb() {
|
|
|
5218
5276
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
5219
5277
|
pid INTEGER NOT NULL,
|
|
5220
5278
|
acquired_at INTEGER NOT NULL,
|
|
5221
|
-
heartbeat_at INTEGER NOT NULL
|
|
5279
|
+
heartbeat_at INTEGER NOT NULL,
|
|
5280
|
+
ready_at INTEGER
|
|
5222
5281
|
);
|
|
5223
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
|
+
}
|
|
5224
5287
|
_db = drizzle(_sqlite, { schema: schema_exports });
|
|
5225
5288
|
if (!_migrationRan) {
|
|
5226
|
-
_migrationRan = true;
|
|
5227
5289
|
try {
|
|
5228
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;
|
|
5229
5297
|
} catch (err) {
|
|
5230
5298
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5299
|
+
_migrationRan = false;
|
|
5300
|
+
_sqlite.close();
|
|
5301
|
+
_sqlite = null;
|
|
5302
|
+
_db = null;
|
|
5231
5303
|
console.error(`[supertask] migration failed: ${msg}`);
|
|
5232
5304
|
throw new Error(`[supertask] DB migration failed: ${msg}`);
|
|
5233
5305
|
}
|
|
@@ -5266,7 +5338,7 @@ function computeBackoff(retryCount, baseMs = 3e4, maxMs = MAX_BACKOFF_MS) {
|
|
|
5266
5338
|
}
|
|
5267
5339
|
|
|
5268
5340
|
// src/core/services/task.service.ts
|
|
5269
|
-
var { tasks: tasks2 } = schema_exports;
|
|
5341
|
+
var { tasks: tasks2, taskRuns: taskRuns2 } = schema_exports;
|
|
5270
5342
|
var TaskService = class {
|
|
5271
5343
|
static buildScopeWhere(scope) {
|
|
5272
5344
|
const conditions = [];
|
|
@@ -5276,9 +5348,27 @@ var TaskService = class {
|
|
|
5276
5348
|
return conditions;
|
|
5277
5349
|
}
|
|
5278
5350
|
static async add(data) {
|
|
5351
|
+
this.validateNewTask(data);
|
|
5279
5352
|
const result = await db.insert(tasks2).values(data).returning();
|
|
5280
5353
|
return result[0];
|
|
5281
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
|
+
}
|
|
5282
5372
|
static async next(scope = {}) {
|
|
5283
5373
|
const baseConditions = [...this.buildScopeWhere(scope)];
|
|
5284
5374
|
const nowMs = Date.now();
|
|
@@ -5301,7 +5391,7 @@ var TaskService = class {
|
|
|
5301
5391
|
),
|
|
5302
5392
|
and(
|
|
5303
5393
|
eq(tasks2.status, "failed"),
|
|
5304
|
-
sql`${tasks2.retryCount}
|
|
5394
|
+
sql`${tasks2.retryCount} <= ${tasks2.maxRetries}`,
|
|
5305
5395
|
retryAfterFilter
|
|
5306
5396
|
)
|
|
5307
5397
|
);
|
|
@@ -5315,7 +5405,8 @@ var TaskService = class {
|
|
|
5315
5405
|
const allTasks = await db.select().from(tasks2).where(and(...conditions)).orderBy(
|
|
5316
5406
|
desc(tasks2.urgency),
|
|
5317
5407
|
desc(tasks2.importance),
|
|
5318
|
-
asc(tasks2.createdAt)
|
|
5408
|
+
asc(tasks2.createdAt),
|
|
5409
|
+
asc(tasks2.id)
|
|
5319
5410
|
);
|
|
5320
5411
|
for (const task of allTasks) {
|
|
5321
5412
|
if (task.dependsOn) {
|
|
@@ -5336,7 +5427,7 @@ var TaskService = class {
|
|
|
5336
5427
|
eq(tasks2.status, "pending"),
|
|
5337
5428
|
and(
|
|
5338
5429
|
eq(tasks2.status, "failed"),
|
|
5339
|
-
sql`${tasks2.retryCount}
|
|
5430
|
+
sql`${tasks2.retryCount} <= ${tasks2.maxRetries}`
|
|
5340
5431
|
)
|
|
5341
5432
|
),
|
|
5342
5433
|
...this.buildScopeWhere(scope)
|
|
@@ -5349,7 +5440,11 @@ var TaskService = class {
|
|
|
5349
5440
|
return result[0] || null;
|
|
5350
5441
|
}
|
|
5351
5442
|
static async done(id, log, scope = {}) {
|
|
5352
|
-
const conditions = [
|
|
5443
|
+
const conditions = [
|
|
5444
|
+
eq(tasks2.id, id),
|
|
5445
|
+
eq(tasks2.status, "running"),
|
|
5446
|
+
...this.buildScopeWhere(scope)
|
|
5447
|
+
];
|
|
5353
5448
|
const result = await db.update(tasks2).set({
|
|
5354
5449
|
status: "done",
|
|
5355
5450
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
@@ -5360,17 +5455,24 @@ var TaskService = class {
|
|
|
5360
5455
|
}
|
|
5361
5456
|
static async fail(id, log, scope = {}, options) {
|
|
5362
5457
|
const current = await this.getById(id, scope);
|
|
5363
|
-
if (!current) return null;
|
|
5458
|
+
if (!current || current.status !== "running") return null;
|
|
5364
5459
|
const newRetryCount = (current.retryCount ?? 0) + 1;
|
|
5365
5460
|
const maxRetries = current.maxRetries ?? 3;
|
|
5366
|
-
const isDeadLetter = options?.setDeadLetter ?? newRetryCount
|
|
5367
|
-
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
|
+
];
|
|
5368
5467
|
const result = await db.update(tasks2).set({
|
|
5369
5468
|
status: isDeadLetter ? "dead_letter" : "failed",
|
|
5370
5469
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
5371
5470
|
resultLog: log,
|
|
5372
5471
|
retryCount: newRetryCount,
|
|
5373
|
-
retryAfter: isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
|
|
5472
|
+
retryAfter: isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
|
|
5473
|
+
newRetryCount,
|
|
5474
|
+
current.retryBackoffMs ?? 3e4
|
|
5475
|
+
)
|
|
5374
5476
|
}).where(and(...conditions)).returning();
|
|
5375
5477
|
return result[0] || null;
|
|
5376
5478
|
}
|
|
@@ -5402,9 +5504,38 @@ var TaskService = class {
|
|
|
5402
5504
|
).returning();
|
|
5403
5505
|
return result.length;
|
|
5404
5506
|
}
|
|
5507
|
+
static async resetOrphanRunningToPending() {
|
|
5508
|
+
const result = await db.update(tasks2).set({
|
|
5509
|
+
status: "pending",
|
|
5510
|
+
startedAt: null,
|
|
5511
|
+
finishedAt: null
|
|
5512
|
+
}).where(
|
|
5513
|
+
and(
|
|
5514
|
+
eq(tasks2.status, "running"),
|
|
5515
|
+
sql`NOT EXISTS (
|
|
5516
|
+
SELECT 1 FROM ${taskRuns2}
|
|
5517
|
+
WHERE ${taskRuns2.taskId} = ${tasks2.id}
|
|
5518
|
+
AND ${taskRuns2.status} = 'running'
|
|
5519
|
+
)`
|
|
5520
|
+
)
|
|
5521
|
+
).returning();
|
|
5522
|
+
return result.length;
|
|
5523
|
+
}
|
|
5405
5524
|
static async cancel(id, scope = {}) {
|
|
5406
|
-
const conditions = [
|
|
5407
|
-
|
|
5525
|
+
const conditions = [
|
|
5526
|
+
eq(tasks2.id, id),
|
|
5527
|
+
or(
|
|
5528
|
+
eq(tasks2.status, "pending"),
|
|
5529
|
+
eq(tasks2.status, "running"),
|
|
5530
|
+
eq(tasks2.status, "failed")
|
|
5531
|
+
),
|
|
5532
|
+
...this.buildScopeWhere(scope)
|
|
5533
|
+
];
|
|
5534
|
+
const result = await db.update(tasks2).set({
|
|
5535
|
+
status: "cancelled",
|
|
5536
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
5537
|
+
retryAfter: null
|
|
5538
|
+
}).where(and(...conditions)).returning();
|
|
5408
5539
|
return result[0] || null;
|
|
5409
5540
|
}
|
|
5410
5541
|
static async retry(id, scope = {}) {
|
|
@@ -5416,7 +5547,8 @@ var TaskService = class {
|
|
|
5416
5547
|
status: "pending",
|
|
5417
5548
|
startedAt: null,
|
|
5418
5549
|
finishedAt: null,
|
|
5419
|
-
retryAfter: null
|
|
5550
|
+
retryAfter: null,
|
|
5551
|
+
retryCount: 0
|
|
5420
5552
|
}).where(and(...conditions)).returning();
|
|
5421
5553
|
return result[0] || null;
|
|
5422
5554
|
}
|
|
@@ -5430,7 +5562,8 @@ var TaskService = class {
|
|
|
5430
5562
|
status: "pending",
|
|
5431
5563
|
startedAt: null,
|
|
5432
5564
|
finishedAt: null,
|
|
5433
|
-
retryAfter: null
|
|
5565
|
+
retryAfter: null,
|
|
5566
|
+
retryCount: 0
|
|
5434
5567
|
}).where(and(...conditions)).returning();
|
|
5435
5568
|
return result.length;
|
|
5436
5569
|
}
|
|
@@ -5498,6 +5631,9 @@ var TaskService = class {
|
|
|
5498
5631
|
}
|
|
5499
5632
|
static async delete(id, scope = {}) {
|
|
5500
5633
|
const conditions = [eq(tasks2.id, id), ...this.buildScopeWhere(scope)];
|
|
5634
|
+
const existing = await db.select({ id: tasks2.id }).from(tasks2).where(and(...conditions)).limit(1);
|
|
5635
|
+
if (!existing[0]) return false;
|
|
5636
|
+
await db.delete(taskRuns2).where(eq(taskRuns2.taskId, id));
|
|
5501
5637
|
const result = await db.delete(tasks2).where(and(...conditions)).returning();
|
|
5502
5638
|
return result.length > 0;
|
|
5503
5639
|
}
|
|
@@ -5515,59 +5651,59 @@ var TaskService = class {
|
|
|
5515
5651
|
};
|
|
5516
5652
|
|
|
5517
5653
|
// src/core/services/task-run.service.ts
|
|
5518
|
-
var { taskRuns:
|
|
5654
|
+
var { taskRuns: taskRuns3 } = schema_exports;
|
|
5519
5655
|
var TaskRunService = class {
|
|
5520
5656
|
static async create(data) {
|
|
5521
|
-
const result = await db.insert(
|
|
5657
|
+
const result = await db.insert(taskRuns3).values(data).returning();
|
|
5522
5658
|
return result[0];
|
|
5523
5659
|
}
|
|
5524
5660
|
static async updateSessionId(id, sessionId) {
|
|
5525
|
-
const result = await db.update(
|
|
5661
|
+
const result = await db.update(taskRuns3).set({ sessionId }).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
5526
5662
|
return result[0] || null;
|
|
5527
5663
|
}
|
|
5528
5664
|
static async done(id, log) {
|
|
5529
|
-
const result = await db.update(
|
|
5665
|
+
const result = await db.update(taskRuns3).set({
|
|
5530
5666
|
status: "done",
|
|
5531
5667
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
5532
5668
|
log
|
|
5533
|
-
}).where(eq(
|
|
5669
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
5534
5670
|
return result[0] || null;
|
|
5535
5671
|
}
|
|
5536
5672
|
static async fail(id, log) {
|
|
5537
|
-
const result = await db.update(
|
|
5673
|
+
const result = await db.update(taskRuns3).set({
|
|
5538
5674
|
status: "failed",
|
|
5539
5675
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
5540
5676
|
log
|
|
5541
|
-
}).where(eq(
|
|
5677
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
5542
5678
|
return result[0] || null;
|
|
5543
5679
|
}
|
|
5544
5680
|
static async heartbeat(id) {
|
|
5545
|
-
const result = await db.update(
|
|
5681
|
+
const result = await db.update(taskRuns3).set({ heartbeatAt: Date.now() }).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
5546
5682
|
return result[0] || null;
|
|
5547
5683
|
}
|
|
5548
5684
|
static async updatePid(id, workerPid, childPid) {
|
|
5549
|
-
const result = await db.update(
|
|
5685
|
+
const result = await db.update(taskRuns3).set({
|
|
5550
5686
|
workerPid,
|
|
5551
5687
|
childPid,
|
|
5552
5688
|
lockedAt: Date.now(),
|
|
5553
5689
|
lockedBy: `gateway-${process.pid}`
|
|
5554
|
-
}).where(eq(
|
|
5690
|
+
}).where(and(eq(taskRuns3.id, id), eq(taskRuns3.status, "running"))).returning();
|
|
5555
5691
|
return result[0] || null;
|
|
5556
5692
|
}
|
|
5557
5693
|
static async getById(id) {
|
|
5558
|
-
const result = await db.select().from(
|
|
5694
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.id, id));
|
|
5559
5695
|
return result[0] || null;
|
|
5560
5696
|
}
|
|
5561
5697
|
static async listByTaskId(taskId) {
|
|
5562
|
-
return await db.select().from(
|
|
5698
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
5563
5699
|
}
|
|
5564
5700
|
static async getLatestByTaskId(taskId) {
|
|
5565
|
-
const result = await db.select().from(
|
|
5701
|
+
const result = await db.select().from(taskRuns3).where(eq(taskRuns3.taskId, taskId)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id)).limit(1);
|
|
5566
5702
|
return result[0] || null;
|
|
5567
5703
|
}
|
|
5568
5704
|
static async getLatestByTaskIds(taskIds) {
|
|
5569
5705
|
if (taskIds.length === 0) return /* @__PURE__ */ new Map();
|
|
5570
|
-
const latestRuns = await db.select().from(
|
|
5706
|
+
const latestRuns = await db.select().from(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).orderBy(desc(taskRuns3.startedAt), desc(taskRuns3.id));
|
|
5571
5707
|
const result = /* @__PURE__ */ new Map();
|
|
5572
5708
|
for (const run of latestRuns) {
|
|
5573
5709
|
if (!result.has(run.taskId)) {
|
|
@@ -5581,22 +5717,23 @@ var TaskRunService = class {
|
|
|
5581
5717
|
const cutoffSec = Math.floor(cutoffMs / 1e3);
|
|
5582
5718
|
const { tasks: tasksTable } = schema_exports;
|
|
5583
5719
|
const result = await db.select({
|
|
5584
|
-
runId:
|
|
5585
|
-
taskId:
|
|
5586
|
-
childPid:
|
|
5720
|
+
runId: taskRuns3.id,
|
|
5721
|
+
taskId: taskRuns3.taskId,
|
|
5722
|
+
childPid: taskRuns3.childPid,
|
|
5587
5723
|
taskRetryCount: tasksTable.retryCount,
|
|
5588
|
-
taskMaxRetries: tasksTable.maxRetries
|
|
5589
|
-
|
|
5724
|
+
taskMaxRetries: tasksTable.maxRetries,
|
|
5725
|
+
taskRetryBackoffMs: tasksTable.retryBackoffMs
|
|
5726
|
+
}).from(taskRuns3).innerJoin(tasksTable, eq(taskRuns3.taskId, tasksTable.id)).where(
|
|
5590
5727
|
and(
|
|
5591
|
-
eq(
|
|
5728
|
+
eq(taskRuns3.status, "running"),
|
|
5592
5729
|
or(
|
|
5593
5730
|
and(
|
|
5594
|
-
sql`${
|
|
5595
|
-
sql`${
|
|
5731
|
+
sql`${taskRuns3.heartbeatAt} IS NULL`,
|
|
5732
|
+
sql`${taskRuns3.startedAt} < ${cutoffSec}`
|
|
5596
5733
|
),
|
|
5597
5734
|
and(
|
|
5598
|
-
sql`${
|
|
5599
|
-
sql`${
|
|
5735
|
+
sql`${taskRuns3.heartbeatAt} IS NOT NULL`,
|
|
5736
|
+
sql`${taskRuns3.heartbeatAt} < ${cutoffMs}`
|
|
5600
5737
|
)
|
|
5601
5738
|
)
|
|
5602
5739
|
)
|
|
@@ -5606,57 +5743,103 @@ var TaskRunService = class {
|
|
|
5606
5743
|
taskId: row.taskId,
|
|
5607
5744
|
childPid: row.childPid,
|
|
5608
5745
|
taskRetryCount: row.taskRetryCount ?? 0,
|
|
5609
|
-
taskMaxRetries: row.taskMaxRetries ?? 3
|
|
5746
|
+
taskMaxRetries: row.taskMaxRetries ?? 3,
|
|
5747
|
+
taskRetryBackoffMs: row.taskRetryBackoffMs ?? 3e4
|
|
5610
5748
|
}));
|
|
5611
5749
|
}
|
|
5612
5750
|
static async getRunningRunByTaskId(taskId) {
|
|
5613
|
-
const result = await db.select().from(
|
|
5751
|
+
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);
|
|
5614
5752
|
return result[0] || null;
|
|
5615
5753
|
}
|
|
5616
5754
|
static async deleteByTaskIds(taskIds) {
|
|
5617
5755
|
if (taskIds.length === 0) return 0;
|
|
5618
|
-
const result = await db.delete(
|
|
5756
|
+
const result = await db.delete(taskRuns3).where(inArray(taskRuns3.taskId, taskIds)).returning();
|
|
5619
5757
|
return result.length;
|
|
5620
5758
|
}
|
|
5621
5759
|
static async getAllRunningRuns() {
|
|
5622
|
-
return await db.select().from(
|
|
5760
|
+
return await db.select().from(taskRuns3).where(eq(taskRuns3.status, "running"));
|
|
5623
5761
|
}
|
|
5624
5762
|
};
|
|
5625
5763
|
|
|
5626
5764
|
// src/worker/index.ts
|
|
5627
5765
|
import { spawn } from "child_process";
|
|
5766
|
+
|
|
5767
|
+
// src/gateway/health.ts
|
|
5768
|
+
var state = null;
|
|
5769
|
+
function markGatewayActivity(component) {
|
|
5770
|
+
if (state) state.lastActivityAt[component] = Date.now();
|
|
5771
|
+
}
|
|
5772
|
+
|
|
5773
|
+
// src/core/process-control.ts
|
|
5774
|
+
function isSafePid(pid) {
|
|
5775
|
+
return Number.isInteger(pid) && pid > 1 && pid !== process.pid;
|
|
5776
|
+
}
|
|
5777
|
+
function signalSpawnedProcessTree(pid, signal) {
|
|
5778
|
+
if (!isSafePid(pid)) return false;
|
|
5779
|
+
if (process.platform !== "win32") {
|
|
5780
|
+
try {
|
|
5781
|
+
process.kill(-pid, signal);
|
|
5782
|
+
return true;
|
|
5783
|
+
} catch {
|
|
5784
|
+
}
|
|
5785
|
+
}
|
|
5786
|
+
try {
|
|
5787
|
+
process.kill(pid, signal);
|
|
5788
|
+
return true;
|
|
5789
|
+
} catch {
|
|
5790
|
+
return false;
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
|
|
5794
|
+
// src/worker/index.ts
|
|
5795
|
+
var DEFAULT_MAX_OUTPUT_CHARS = 64 * 1024;
|
|
5796
|
+
var FORBIDDEN_AGENT = "supertask-runner";
|
|
5628
5797
|
var WorkerEngine = class {
|
|
5629
5798
|
activeBatchIds = /* @__PURE__ */ new Set();
|
|
5630
5799
|
runningTasks = /* @__PURE__ */ new Map();
|
|
5631
5800
|
stopped = false;
|
|
5632
5801
|
pollTimer = null;
|
|
5633
5802
|
heartbeatTimer = null;
|
|
5803
|
+
pollCyclePromise = null;
|
|
5634
5804
|
cfg;
|
|
5635
|
-
|
|
5805
|
+
opencodeBin;
|
|
5806
|
+
maxOutputChars;
|
|
5807
|
+
constructor(cfg, options = {}) {
|
|
5636
5808
|
this.cfg = cfg.worker;
|
|
5809
|
+
this.opencodeBin = options.opencodeBin ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
5810
|
+
this.maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
5637
5811
|
}
|
|
5638
5812
|
start() {
|
|
5639
5813
|
this.stopped = false;
|
|
5814
|
+
markGatewayActivity("worker");
|
|
5640
5815
|
this.poll();
|
|
5641
5816
|
this.heartbeatTimer = setInterval(() => this.updateHeartbeats(), this.cfg.heartbeatIntervalMs);
|
|
5642
5817
|
}
|
|
5643
|
-
stop() {
|
|
5818
|
+
async stop(gracePeriodMs = 0) {
|
|
5644
5819
|
this.stopped = true;
|
|
5645
5820
|
if (this.pollTimer) {
|
|
5646
5821
|
clearTimeout(this.pollTimer);
|
|
5647
5822
|
this.pollTimer = null;
|
|
5648
5823
|
}
|
|
5824
|
+
if (this.pollCyclePromise) await this.pollCyclePromise;
|
|
5825
|
+
if (gracePeriodMs > 0 && this.runningTasks.size > 0) {
|
|
5826
|
+
const deadline = Date.now() + gracePeriodMs;
|
|
5827
|
+
while (this.runningTasks.size > 0 && Date.now() < deadline) {
|
|
5828
|
+
await Bun.sleep(Math.min(50, deadline - Date.now()));
|
|
5829
|
+
}
|
|
5830
|
+
}
|
|
5649
5831
|
if (this.heartbeatTimer) {
|
|
5650
5832
|
clearInterval(this.heartbeatTimer);
|
|
5651
5833
|
this.heartbeatTimer = null;
|
|
5652
5834
|
}
|
|
5835
|
+
const interruptedTaskIds = [...this.runningTasks.keys()];
|
|
5653
5836
|
const killPromises = [];
|
|
5654
|
-
for (const
|
|
5837
|
+
for (const entry of this.runningTasks.values()) {
|
|
5655
5838
|
entry.shutdown = true;
|
|
5656
5839
|
killPromises.push(this.killEntry(entry));
|
|
5657
5840
|
}
|
|
5658
|
-
|
|
5659
|
-
|
|
5841
|
+
await Promise.allSettled(killPromises);
|
|
5842
|
+
return interruptedTaskIds;
|
|
5660
5843
|
}
|
|
5661
5844
|
getRunningTaskIds() {
|
|
5662
5845
|
return [...this.runningTasks.keys()];
|
|
@@ -5666,136 +5849,244 @@ var WorkerEngine = class {
|
|
|
5666
5849
|
}
|
|
5667
5850
|
poll() {
|
|
5668
5851
|
if (this.stopped) return;
|
|
5669
|
-
|
|
5852
|
+
markGatewayActivity("worker");
|
|
5853
|
+
this.pollCyclePromise = this.tryDispatch().finally(() => {
|
|
5854
|
+
this.pollCyclePromise = null;
|
|
5670
5855
|
if (this.stopped) return;
|
|
5671
5856
|
this.pollTimer = setTimeout(() => this.poll(), this.cfg.pollIntervalMs);
|
|
5672
5857
|
});
|
|
5673
5858
|
}
|
|
5674
5859
|
async tryDispatch() {
|
|
5860
|
+
await this.reconcileCancelledTasks();
|
|
5675
5861
|
while (!this.stopped && this.runningTasks.size < this.cfg.maxConcurrency) {
|
|
5862
|
+
let task;
|
|
5863
|
+
try {
|
|
5864
|
+
task = await TaskService.next({ excludedBatchIds: [...this.activeBatchIds] });
|
|
5865
|
+
} catch (err) {
|
|
5866
|
+
this.logError("task claim failed", err);
|
|
5867
|
+
break;
|
|
5868
|
+
}
|
|
5869
|
+
if (!task) break;
|
|
5870
|
+
if (this.stopped) break;
|
|
5871
|
+
if (!await TaskService.start(task.id)) continue;
|
|
5872
|
+
if (task.batchId) this.activeBatchIds.add(task.batchId);
|
|
5873
|
+
if (this.stopped) {
|
|
5874
|
+
await TaskService.resetRunningToPending([task.id]);
|
|
5875
|
+
this.releaseBatch(task);
|
|
5876
|
+
break;
|
|
5877
|
+
}
|
|
5676
5878
|
try {
|
|
5677
|
-
const excludedBatchIds = [...this.activeBatchIds];
|
|
5678
|
-
const task = await TaskService.next({ excludedBatchIds });
|
|
5679
|
-
if (!task) break;
|
|
5680
|
-
if (!await TaskService.start(task.id)) continue;
|
|
5681
|
-
if (task.batchId) {
|
|
5682
|
-
this.activeBatchIds.add(task.batchId);
|
|
5683
|
-
}
|
|
5684
5879
|
const run = await TaskRunService.create({
|
|
5685
5880
|
taskId: task.id,
|
|
5686
5881
|
model: this.resolveModel(task.model),
|
|
5687
5882
|
status: "running"
|
|
5688
5883
|
});
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5884
|
+
if (this.stopped) {
|
|
5885
|
+
await TaskRunService.fail(run.id, "Gateway shutdown before spawn");
|
|
5886
|
+
await TaskService.resetRunningToPending([task.id]);
|
|
5887
|
+
this.releaseBatch(task);
|
|
5888
|
+
break;
|
|
5693
5889
|
}
|
|
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
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
}
|
|
5745
|
-
console.error(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "error", msg: "task spawn error", taskId: task.id, error: err.message }));
|
|
5890
|
+
if (task.agent === FORBIDDEN_AGENT) {
|
|
5891
|
+
const message = `\u7981\u6B62\u6267\u884C\u9012\u5F52 Agent: ${FORBIDDEN_AGENT}`;
|
|
5892
|
+
await TaskRunService.fail(run.id, message);
|
|
5893
|
+
await TaskService.fail(task.id, message, {}, { setDeadLetter: true });
|
|
5894
|
+
this.releaseBatch(task);
|
|
5895
|
+
continue;
|
|
5896
|
+
}
|
|
5897
|
+
this.spawnTask(task, run.id);
|
|
5898
|
+
} catch (err) {
|
|
5899
|
+
this.releaseBatch(task);
|
|
5900
|
+
const message = `Worker \u542F\u52A8\u4EFB\u52A1\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`;
|
|
5901
|
+
try {
|
|
5902
|
+
await TaskService.fail(task.id, message);
|
|
5903
|
+
} catch (failErr) {
|
|
5904
|
+
this.logError("failed to compensate task startup", failErr, task.id);
|
|
5905
|
+
}
|
|
5906
|
+
this.logError("task dispatch failed", err, task.id);
|
|
5907
|
+
}
|
|
5908
|
+
}
|
|
5909
|
+
}
|
|
5910
|
+
spawnTask(task, runId) {
|
|
5911
|
+
const model = this.resolveModel(task.model);
|
|
5912
|
+
const args = ["run", "--agent", task.agent, "--format", "json"];
|
|
5913
|
+
if (model) args.push("-m", model);
|
|
5914
|
+
args.push(task.prompt);
|
|
5915
|
+
const child = spawn(this.opencodeBin, args, {
|
|
5916
|
+
cwd: task.cwd || process.cwd(),
|
|
5917
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5918
|
+
detached: process.platform !== "win32"
|
|
5919
|
+
});
|
|
5920
|
+
const entry = {
|
|
5921
|
+
task,
|
|
5922
|
+
runId,
|
|
5923
|
+
child,
|
|
5924
|
+
output: "",
|
|
5925
|
+
sessionId: null,
|
|
5926
|
+
timeoutTimer: null,
|
|
5927
|
+
shutdown: false,
|
|
5928
|
+
settled: false
|
|
5929
|
+
};
|
|
5930
|
+
this.runningTasks.set(task.id, entry);
|
|
5931
|
+
const handleData = (data) => {
|
|
5932
|
+
const text2 = data.toString();
|
|
5933
|
+
entry.output = (entry.output + text2).slice(-this.maxOutputChars);
|
|
5934
|
+
process.stdout.write(text2);
|
|
5935
|
+
const match = entry.output.match(/"sessionID"\s*:\s*"(ses_[^"]+)"/);
|
|
5936
|
+
if (match?.[1] && match[1] !== entry.sessionId) {
|
|
5937
|
+
entry.sessionId = match[1];
|
|
5938
|
+
TaskRunService.updateSessionId(runId, match[1]).catch((err) => {
|
|
5939
|
+
this.logError("sessionId update failed", err, task.id);
|
|
5746
5940
|
});
|
|
5941
|
+
}
|
|
5942
|
+
};
|
|
5943
|
+
child.stdout?.on("data", handleData);
|
|
5944
|
+
child.stderr?.on("data", handleData);
|
|
5945
|
+
child.once("error", (err) => {
|
|
5946
|
+
void this.finishEntry(entry, null, `\u65E0\u6CD5\u542F\u52A8 opencode\uFF1A${err.message}`);
|
|
5947
|
+
});
|
|
5948
|
+
child.once("close", (code, signal) => {
|
|
5949
|
+
const failure = code === 0 ? void 0 : `opencode \u9000\u51FA\u7801 ${code ?? "null"}${signal ? `\uFF0C\u4FE1\u53F7 ${signal}` : ""}`;
|
|
5950
|
+
void this.finishEntry(entry, code, failure);
|
|
5951
|
+
});
|
|
5952
|
+
const timeoutMs = task.timeoutMs ?? this.cfg.taskTimeoutMs;
|
|
5953
|
+
if (timeoutMs > 0) {
|
|
5954
|
+
entry.timeoutTimer = setTimeout(() => {
|
|
5955
|
+
this.signalEntry(entry, "SIGKILL");
|
|
5956
|
+
void this.finishEntry(entry, null, `\u4EFB\u52A1\u8D85\u65F6\uFF08${timeoutMs}ms\uFF09`);
|
|
5957
|
+
}, timeoutMs);
|
|
5958
|
+
}
|
|
5959
|
+
TaskRunService.updatePid(runId, process.pid, child.pid ?? 0).catch((err) => {
|
|
5960
|
+
this.signalEntry(entry, "SIGKILL");
|
|
5961
|
+
void this.finishEntry(entry, null, `\u8BB0\u5F55 Worker PID \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
5962
|
+
});
|
|
5963
|
+
}
|
|
5964
|
+
async finishEntry(entry, code, failure) {
|
|
5965
|
+
if (entry.settled) return;
|
|
5966
|
+
entry.settled = true;
|
|
5967
|
+
if (entry.timeoutTimer) {
|
|
5968
|
+
clearTimeout(entry.timeoutTimer);
|
|
5969
|
+
entry.timeoutTimer = null;
|
|
5970
|
+
}
|
|
5971
|
+
try {
|
|
5972
|
+
if (entry.shutdown) return;
|
|
5973
|
+
const currentRun = await TaskRunService.getById(entry.runId);
|
|
5974
|
+
if (!currentRun || currentRun.status !== "running") return;
|
|
5975
|
+
const output = entry.output.trim();
|
|
5976
|
+
const log = failure ? `${failure}${output ? `
|
|
5977
|
+
${output}` : ""}` : output;
|
|
5978
|
+
if (code === 0 && !failure) {
|
|
5979
|
+
const completed = await TaskService.done(entry.task.id, log);
|
|
5980
|
+
if (completed) {
|
|
5981
|
+
await TaskRunService.done(entry.runId, log);
|
|
5982
|
+
console.log(JSON.stringify({
|
|
5983
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5984
|
+
level: "info",
|
|
5985
|
+
msg: "task done",
|
|
5986
|
+
taskId: entry.task.id
|
|
5987
|
+
}));
|
|
5988
|
+
return;
|
|
5989
|
+
}
|
|
5990
|
+
await TaskRunService.fail(entry.runId, "\u4EFB\u52A1\u72B6\u6001\u5DF2\u88AB\u5176\u4ED6\u64CD\u4F5C\u6539\u53D8");
|
|
5991
|
+
return;
|
|
5992
|
+
}
|
|
5993
|
+
await TaskRunService.fail(entry.runId, log);
|
|
5994
|
+
const failed = await TaskService.fail(entry.task.id, log);
|
|
5995
|
+
if (!failed) {
|
|
5996
|
+
this.logError("task failure state transition rejected", failure ?? "unknown failure", entry.task.id);
|
|
5997
|
+
}
|
|
5998
|
+
console.error(JSON.stringify({
|
|
5999
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6000
|
+
level: "error",
|
|
6001
|
+
msg: "task failed",
|
|
6002
|
+
taskId: entry.task.id,
|
|
6003
|
+
error: failure
|
|
6004
|
+
}));
|
|
6005
|
+
} finally {
|
|
6006
|
+
this.runningTasks.delete(entry.task.id);
|
|
6007
|
+
this.releaseBatch(entry.task);
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
6010
|
+
async reconcileCancelledTasks() {
|
|
6011
|
+
for (const entry of [...this.runningTasks.values()]) {
|
|
6012
|
+
try {
|
|
6013
|
+
const task = await TaskService.getById(entry.task.id);
|
|
6014
|
+
if (task?.status === "cancelled") await this.cancelEntry(entry);
|
|
5747
6015
|
} catch (err) {
|
|
5748
|
-
|
|
5749
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5750
|
-
level: "error",
|
|
5751
|
-
msg: "tryDispatch iteration failed",
|
|
5752
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5753
|
-
}));
|
|
5754
|
-
break;
|
|
6016
|
+
this.logError("cancel reconciliation failed", err, entry.task.id);
|
|
5755
6017
|
}
|
|
5756
6018
|
}
|
|
5757
6019
|
}
|
|
6020
|
+
async cancelEntry(entry) {
|
|
6021
|
+
if (entry.settled) return;
|
|
6022
|
+
entry.settled = true;
|
|
6023
|
+
if (entry.timeoutTimer) {
|
|
6024
|
+
clearTimeout(entry.timeoutTimer);
|
|
6025
|
+
entry.timeoutTimer = null;
|
|
6026
|
+
}
|
|
6027
|
+
try {
|
|
6028
|
+
await this.killEntry(entry);
|
|
6029
|
+
const output = entry.output.trim();
|
|
6030
|
+
const log = `\u4EFB\u52A1\u5DF2\u53D6\u6D88${output ? `
|
|
6031
|
+
${output}` : ""}`;
|
|
6032
|
+
await TaskRunService.fail(entry.runId, log);
|
|
6033
|
+
console.log(JSON.stringify({
|
|
6034
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6035
|
+
level: "info",
|
|
6036
|
+
msg: "running task cancelled",
|
|
6037
|
+
taskId: entry.task.id
|
|
6038
|
+
}));
|
|
6039
|
+
} finally {
|
|
6040
|
+
this.runningTasks.delete(entry.task.id);
|
|
6041
|
+
this.releaseBatch(entry.task);
|
|
6042
|
+
}
|
|
6043
|
+
}
|
|
5758
6044
|
async updateHeartbeats() {
|
|
5759
|
-
for (const
|
|
6045
|
+
for (const entry of this.runningTasks.values()) {
|
|
5760
6046
|
try {
|
|
5761
6047
|
await TaskRunService.heartbeat(entry.runId);
|
|
5762
|
-
} catch {
|
|
6048
|
+
} catch (err) {
|
|
6049
|
+
this.logError("heartbeat update failed", err, entry.task.id);
|
|
5763
6050
|
}
|
|
5764
6051
|
}
|
|
5765
6052
|
}
|
|
5766
6053
|
killEntry(entry) {
|
|
5767
|
-
if (entry.child.exitCode !== null) {
|
|
6054
|
+
if (entry.child.exitCode !== null || entry.child.signalCode !== null) {
|
|
5768
6055
|
return Promise.resolve();
|
|
5769
6056
|
}
|
|
5770
6057
|
return new Promise((resolve) => {
|
|
5771
6058
|
const timeout = setTimeout(() => {
|
|
5772
|
-
|
|
5773
|
-
if (entry.child.pid) process.kill(entry.child.pid, "SIGKILL");
|
|
5774
|
-
} catch {
|
|
5775
|
-
}
|
|
6059
|
+
this.signalEntry(entry, "SIGKILL");
|
|
5776
6060
|
resolve();
|
|
5777
6061
|
}, 5e3);
|
|
5778
|
-
entry.child.
|
|
6062
|
+
entry.child.once("close", () => {
|
|
5779
6063
|
clearTimeout(timeout);
|
|
5780
6064
|
resolve();
|
|
5781
6065
|
});
|
|
5782
|
-
|
|
5783
|
-
if (entry.child.pid) {
|
|
5784
|
-
entry.child.kill("SIGTERM");
|
|
5785
|
-
} else {
|
|
5786
|
-
clearTimeout(timeout);
|
|
5787
|
-
resolve();
|
|
5788
|
-
}
|
|
5789
|
-
} catch {
|
|
5790
|
-
clearTimeout(timeout);
|
|
5791
|
-
resolve();
|
|
5792
|
-
}
|
|
6066
|
+
this.signalEntry(entry, "SIGTERM");
|
|
5793
6067
|
});
|
|
5794
6068
|
}
|
|
6069
|
+
signalEntry(entry, signal) {
|
|
6070
|
+
const pid = entry.child.pid;
|
|
6071
|
+
if (!pid) return;
|
|
6072
|
+
signalSpawnedProcessTree(pid, signal);
|
|
6073
|
+
}
|
|
6074
|
+
releaseBatch(task) {
|
|
6075
|
+
if (task.batchId) this.activeBatchIds.delete(task.batchId);
|
|
6076
|
+
}
|
|
5795
6077
|
resolveModel(taskModel) {
|
|
5796
6078
|
if (!taskModel || taskModel === "default") return null;
|
|
5797
6079
|
return taskModel;
|
|
5798
6080
|
}
|
|
6081
|
+
logError(message, error, taskId) {
|
|
6082
|
+
console.error(JSON.stringify({
|
|
6083
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6084
|
+
level: "error",
|
|
6085
|
+
msg: message,
|
|
6086
|
+
taskId,
|
|
6087
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6088
|
+
}));
|
|
6089
|
+
}
|
|
5799
6090
|
};
|
|
5800
6091
|
export {
|
|
5801
6092
|
WorkerEngine
|