opencode-supertask 0.1.39 → 0.1.41
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/CHANGELOG.md +27 -1
- package/LICENSE +21 -0
- package/README.md +159 -352
- package/README.zh-CN.md +250 -0
- package/dist/cli/index.js +580 -171
- package/dist/cli/index.js.map +1 -1
- package/dist/daemon/gateway-diagnostic-runner.d.ts +2 -0
- package/dist/daemon/gateway-diagnostic-runner.js +461 -0
- package/dist/daemon/gateway-diagnostic-runner.js.map +1 -0
- package/dist/gateway/index.js +543 -615
- package/dist/gateway/index.js.map +1 -1
- package/dist/plugin/supertask.js +90 -28
- package/dist/plugin/supertask.js.map +1 -1
- package/dist/web/index.js +410 -528
- package/dist/web/index.js.map +1 -1
- package/dist/worker/index.d.ts +9 -0
- package/dist/worker/index.js +225 -69
- package/dist/worker/index.js.map +1 -1
- package/dist/worker/launcher.js.map +1 -1
- package/drizzle/0008_good_smasher.sql +3 -0
- package/drizzle/meta/0008_snapshot.json +606 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +12 -3
package/dist/worker/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { G as GatewayConfig } from '../config-CCUuD6Az.js';
|
|
|
3
3
|
interface WorkerEngineOptions {
|
|
4
4
|
opencodeBin?: string;
|
|
5
5
|
maxOutputChars?: number;
|
|
6
|
+
settlementRetryDelaysMs?: number[];
|
|
7
|
+
settlementRetryIntervalMs?: number;
|
|
6
8
|
}
|
|
7
9
|
interface InterruptedTaskRun {
|
|
8
10
|
taskId: number;
|
|
@@ -17,9 +19,13 @@ declare class WorkerEngine {
|
|
|
17
19
|
private pollTimer;
|
|
18
20
|
private heartbeatTimer;
|
|
19
21
|
private pollCyclePromise;
|
|
22
|
+
private shutdownDeadlineMs;
|
|
23
|
+
private settlementRetryWakeups;
|
|
20
24
|
private cfg;
|
|
21
25
|
private opencodeBin;
|
|
22
26
|
private maxOutputChars;
|
|
27
|
+
private settlementRetryDelaysMs;
|
|
28
|
+
private settlementRetryIntervalMs;
|
|
23
29
|
constructor(cfg: GatewayConfig, options?: WorkerEngineOptions);
|
|
24
30
|
start(): void;
|
|
25
31
|
stop(gracePeriodMs?: number): Promise<InterruptedTaskRun[]>;
|
|
@@ -32,6 +38,8 @@ declare class WorkerEngine {
|
|
|
32
38
|
private spawnTask;
|
|
33
39
|
private handleChildClose;
|
|
34
40
|
private settleEntry;
|
|
41
|
+
private commitEntryWithRetry;
|
|
42
|
+
private waitForSettlementRetry;
|
|
35
43
|
private commitEntry;
|
|
36
44
|
private reconcileCancelledTasks;
|
|
37
45
|
private reconcileQuarantinedTasks;
|
|
@@ -45,6 +53,7 @@ declare class WorkerEngine {
|
|
|
45
53
|
private releaseBatch;
|
|
46
54
|
private outputWithCommand;
|
|
47
55
|
private resolveModel;
|
|
56
|
+
private resolveVariant;
|
|
48
57
|
private runDetached;
|
|
49
58
|
private logError;
|
|
50
59
|
}
|
package/dist/worker/index.js
CHANGED
|
@@ -5157,6 +5157,7 @@ var tasks = sqliteTable("tasks", {
|
|
|
5157
5157
|
name: text("name").notNull(),
|
|
5158
5158
|
agent: text("agent").notNull(),
|
|
5159
5159
|
model: text("model").default("default"),
|
|
5160
|
+
variant: text("variant"),
|
|
5160
5161
|
prompt: text("prompt").notNull(),
|
|
5161
5162
|
cwd: text("cwd"),
|
|
5162
5163
|
// 分类与优先级
|
|
@@ -5201,6 +5202,7 @@ var taskRuns = sqliteTable("task_runs", {
|
|
|
5201
5202
|
taskId: integer("task_id").notNull().references(() => tasks.id, { onDelete: "cascade" }),
|
|
5202
5203
|
sessionId: text("session_id"),
|
|
5203
5204
|
model: text("model"),
|
|
5205
|
+
variant: text("variant"),
|
|
5204
5206
|
status: text("status").default("running"),
|
|
5205
5207
|
startedAt: integer("started_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
5206
5208
|
finishedAt: integer("finished_at", { mode: "timestamp" }),
|
|
@@ -5221,6 +5223,7 @@ var taskTemplates = sqliteTable("task_templates", {
|
|
|
5221
5223
|
name: text("name").notNull(),
|
|
5222
5224
|
agent: text("agent").notNull(),
|
|
5223
5225
|
model: text("model").default("default"),
|
|
5226
|
+
variant: text("variant"),
|
|
5224
5227
|
prompt: text("prompt").notNull(),
|
|
5225
5228
|
cwd: text("cwd"),
|
|
5226
5229
|
category: text("category").default("general"),
|
|
@@ -5395,6 +5398,22 @@ function normalizeTaskBatchId(batchId) {
|
|
|
5395
5398
|
return batchId.trim() || null;
|
|
5396
5399
|
}
|
|
5397
5400
|
|
|
5401
|
+
// src/core/model-variant.ts
|
|
5402
|
+
var MAX_MODEL_VARIANT_LENGTH = 128;
|
|
5403
|
+
var CONTROL_CHARACTER_PATTERN = /[\u0000-\u001F\u007F]/;
|
|
5404
|
+
function normalizeModelVariant(value) {
|
|
5405
|
+
if (value === void 0 || value === null) return value;
|
|
5406
|
+
const normalized = value.trim();
|
|
5407
|
+
if (!normalized) return null;
|
|
5408
|
+
if (normalized.length > MAX_MODEL_VARIANT_LENGTH) {
|
|
5409
|
+
throw new Error(`variant \u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${MAX_MODEL_VARIANT_LENGTH} \u4E2A\u5B57\u7B26`);
|
|
5410
|
+
}
|
|
5411
|
+
if (CONTROL_CHARACTER_PATTERN.test(normalized)) {
|
|
5412
|
+
throw new Error("variant \u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26");
|
|
5413
|
+
}
|
|
5414
|
+
return normalized;
|
|
5415
|
+
}
|
|
5416
|
+
|
|
5398
5417
|
// src/core/services/task.service.ts
|
|
5399
5418
|
var { tasks: tasks2, taskRuns: taskRuns2 } = schema_exports;
|
|
5400
5419
|
var cleanupInvocation = 0;
|
|
@@ -5460,7 +5479,8 @@ var TaskService = class {
|
|
|
5460
5479
|
static async add(data) {
|
|
5461
5480
|
const normalizedData = {
|
|
5462
5481
|
...data,
|
|
5463
|
-
batchId: normalizeTaskBatchId(data.batchId)
|
|
5482
|
+
batchId: normalizeTaskBatchId(data.batchId),
|
|
5483
|
+
variant: normalizeModelVariant(data.variant)
|
|
5464
5484
|
};
|
|
5465
5485
|
this.validateNewTask(normalizedData);
|
|
5466
5486
|
return db.transaction((tx) => {
|
|
@@ -5488,7 +5508,11 @@ var TaskService = class {
|
|
|
5488
5508
|
}
|
|
5489
5509
|
static async update(id, data, scope = {}) {
|
|
5490
5510
|
if (Object.keys(data).length === 0) throw new Error("\u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A\u8981\u4FEE\u6539\u7684\u5B57\u6BB5");
|
|
5491
|
-
const normalizedData =
|
|
5511
|
+
const normalizedData = {
|
|
5512
|
+
...data,
|
|
5513
|
+
...data.batchId === void 0 ? {} : { batchId: normalizeTaskBatchId(data.batchId) ?? null },
|
|
5514
|
+
...data.variant === void 0 ? {} : { variant: normalizeModelVariant(data.variant) ?? null }
|
|
5515
|
+
};
|
|
5492
5516
|
return db.transaction((tx) => {
|
|
5493
5517
|
const task = tx.select().from(tasks2).where(and(
|
|
5494
5518
|
eq(tasks2.id, id),
|
|
@@ -5500,6 +5524,7 @@ var TaskService = class {
|
|
|
5500
5524
|
name: normalizedData.name ?? task.name,
|
|
5501
5525
|
agent: normalizedData.agent ?? task.agent,
|
|
5502
5526
|
model: normalizedData.model ?? task.model,
|
|
5527
|
+
variant: normalizedData.variant === void 0 ? task.variant : normalizedData.variant,
|
|
5503
5528
|
prompt: normalizedData.prompt ?? task.prompt,
|
|
5504
5529
|
cwd: task.cwd,
|
|
5505
5530
|
category: normalizedData.category ?? task.category,
|
|
@@ -5513,13 +5538,23 @@ var TaskService = class {
|
|
|
5513
5538
|
});
|
|
5514
5539
|
const maxRetries = normalizedData.maxRetries ?? task.maxRetries ?? 3;
|
|
5515
5540
|
const exhausted = task.status === "failed" && (task.retryCount ?? 0) > maxRetries;
|
|
5516
|
-
|
|
5541
|
+
const updated = tx.update(tasks2).set({
|
|
5517
5542
|
...normalizedData,
|
|
5518
5543
|
...exhausted ? {
|
|
5519
5544
|
status: "dead_letter",
|
|
5520
5545
|
retryAfter: null
|
|
5521
5546
|
} : {}
|
|
5522
5547
|
}).where(eq(tasks2.id, id)).returning().get() ?? null;
|
|
5548
|
+
if (exhausted && updated) {
|
|
5549
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
5550
|
+
tx.update(tasks2).set({
|
|
5551
|
+
status: "dead_letter",
|
|
5552
|
+
finishedAt,
|
|
5553
|
+
retryAfter: null,
|
|
5554
|
+
resultLog: `\u4F9D\u8D56\u4EFB\u52A1 #${id} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`
|
|
5555
|
+
}).where(blockedDependentsOf(id)).run();
|
|
5556
|
+
}
|
|
5557
|
+
return updated;
|
|
5523
5558
|
}, { behavior: "immediate" });
|
|
5524
5559
|
}
|
|
5525
5560
|
static validateNewTask(data) {
|
|
@@ -5540,7 +5575,7 @@ var TaskService = class {
|
|
|
5540
5575
|
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
5541
5576
|
}
|
|
5542
5577
|
}
|
|
5543
|
-
static
|
|
5578
|
+
static buildRunnableTaskWhere(scope) {
|
|
5544
5579
|
const baseConditions = [...this.buildScopeWhere(scope)];
|
|
5545
5580
|
const nowMs = Date.now();
|
|
5546
5581
|
const retryAfterFilter = or(
|
|
@@ -5575,40 +5610,43 @@ var TaskService = class {
|
|
|
5575
5610
|
if (batchFilter) {
|
|
5576
5611
|
conditions.push(batchFilter);
|
|
5577
5612
|
}
|
|
5578
|
-
|
|
5613
|
+
return and(
|
|
5579
5614
|
...conditions,
|
|
5580
5615
|
or(
|
|
5581
5616
|
isNull(tasks2.dependsOn),
|
|
5582
5617
|
sql`EXISTS (
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5618
|
+
SELECT 1 FROM tasks AS dependency_task
|
|
5619
|
+
WHERE dependency_task.id = ${tasks2.dependsOn}
|
|
5620
|
+
AND dependency_task.status = 'done'
|
|
5621
|
+
AND dependency_task.cwd IS ${tasks2.cwd}
|
|
5622
|
+
)`
|
|
5588
5623
|
),
|
|
5589
5624
|
or(
|
|
5590
5625
|
isNull(tasks2.batchId),
|
|
5591
5626
|
sql`trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ''`,
|
|
5592
5627
|
sql`NOT EXISTS (
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
)
|
|
5628
|
+
SELECT 1 FROM tasks AS running_batch_task
|
|
5629
|
+
WHERE trim(running_batch_task.batch_id, ${TASK_BATCH_TRIM_CHARACTERS})
|
|
5630
|
+
= trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS})
|
|
5631
|
+
AND (
|
|
5632
|
+
running_batch_task.status = 'running'
|
|
5633
|
+
OR EXISTS (
|
|
5634
|
+
SELECT 1 FROM task_runs AS running_batch_run
|
|
5635
|
+
WHERE running_batch_run.task_id = running_batch_task.id
|
|
5636
|
+
AND running_batch_run.status = 'running'
|
|
5603
5637
|
)
|
|
5604
|
-
|
|
5638
|
+
)
|
|
5639
|
+
)`
|
|
5605
5640
|
),
|
|
5606
5641
|
sql`NOT EXISTS (
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
)
|
|
5642
|
+
SELECT 1 FROM task_runs AS candidate_active_run
|
|
5643
|
+
WHERE candidate_active_run.task_id = ${tasks2.id}
|
|
5644
|
+
AND candidate_active_run.status = 'running'
|
|
5645
|
+
)`
|
|
5646
|
+
);
|
|
5647
|
+
}
|
|
5648
|
+
static async next(scope = {}) {
|
|
5649
|
+
const result = await db.select().from(tasks2).where(this.buildRunnableTaskWhere(scope)).orderBy(
|
|
5612
5650
|
desc(tasks2.urgency),
|
|
5613
5651
|
desc(tasks2.importance),
|
|
5614
5652
|
asc(tasks2.createdAt),
|
|
@@ -5616,6 +5654,22 @@ var TaskService = class {
|
|
|
5616
5654
|
).limit(1);
|
|
5617
5655
|
return result[0] ?? null;
|
|
5618
5656
|
}
|
|
5657
|
+
static async claimNext(scope = {}) {
|
|
5658
|
+
return db.transaction((tx) => {
|
|
5659
|
+
const candidate = tx.select().from(tasks2).where(this.buildRunnableTaskWhere(scope)).orderBy(
|
|
5660
|
+
desc(tasks2.urgency),
|
|
5661
|
+
desc(tasks2.importance),
|
|
5662
|
+
asc(tasks2.createdAt),
|
|
5663
|
+
asc(tasks2.id)
|
|
5664
|
+
).limit(1).get();
|
|
5665
|
+
if (!candidate) return null;
|
|
5666
|
+
return tx.update(tasks2).set({
|
|
5667
|
+
status: "running",
|
|
5668
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
5669
|
+
finishedAt: null
|
|
5670
|
+
}).where(eq(tasks2.id, candidate.id)).returning().get() ?? null;
|
|
5671
|
+
}, { behavior: "immediate" });
|
|
5672
|
+
}
|
|
5619
5673
|
static async countRunning(scope = {}) {
|
|
5620
5674
|
const scopeConditions = scope.legacyCwd ? [sql`${tasks2.cwd} IS NULL OR trim(${tasks2.cwd}) = ''`] : this.buildScopeWhere(scope);
|
|
5621
5675
|
if (scope.batchId !== void 0) {
|
|
@@ -6248,7 +6302,6 @@ var TaskService = class {
|
|
|
6248
6302
|
};
|
|
6249
6303
|
|
|
6250
6304
|
// src/core/process-control.ts
|
|
6251
|
-
import { spawnSync } from "child_process";
|
|
6252
6305
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6253
6306
|
import { basename, dirname as dirname2, resolve } from "path";
|
|
6254
6307
|
|
|
@@ -6273,6 +6326,84 @@ function isMatchingDrainProof(message, launchIdentity) {
|
|
|
6273
6326
|
|
|
6274
6327
|
// src/core/process-control.ts
|
|
6275
6328
|
var OS_COMMAND_TIMEOUT_MS = 2e3;
|
|
6329
|
+
var OS_COMMAND_MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
6330
|
+
var OS_COMMAND_RESULT_MARKER = "\n\0supertask-os-command-result:";
|
|
6331
|
+
var BOUNDED_OS_COMMAND_RUNNER = `
|
|
6332
|
+
const { writeSync } = await import('fs');
|
|
6333
|
+
let child;
|
|
6334
|
+
try {
|
|
6335
|
+
child = Bun.spawn(process.argv.slice(2), {
|
|
6336
|
+
stdin: 'ignore',
|
|
6337
|
+
stdout: 'pipe',
|
|
6338
|
+
stderr: 'ignore',
|
|
6339
|
+
});
|
|
6340
|
+
} catch {
|
|
6341
|
+
process.exit(127);
|
|
6342
|
+
}
|
|
6343
|
+
const terminateGroup = () => {
|
|
6344
|
+
if (process.platform !== 'win32') {
|
|
6345
|
+
try {
|
|
6346
|
+
process.kill(-process.pid, 'SIGKILL');
|
|
6347
|
+
} catch {
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6350
|
+
try {
|
|
6351
|
+
child.kill('SIGKILL');
|
|
6352
|
+
} catch {
|
|
6353
|
+
}
|
|
6354
|
+
process.exit(1);
|
|
6355
|
+
};
|
|
6356
|
+
const timer = setTimeout(terminateGroup, ${OS_COMMAND_TIMEOUT_MS});
|
|
6357
|
+
try {
|
|
6358
|
+
const chunks = [];
|
|
6359
|
+
let outputBytes = 0;
|
|
6360
|
+
const readOutput = async () => {
|
|
6361
|
+
const reader = child.stdout.getReader();
|
|
6362
|
+
while (true) {
|
|
6363
|
+
const { done, value } = await reader.read();
|
|
6364
|
+
if (done) return;
|
|
6365
|
+
outputBytes += value.byteLength;
|
|
6366
|
+
if (outputBytes > ${OS_COMMAND_MAX_OUTPUT_BYTES}) terminateGroup();
|
|
6367
|
+
chunks.push(value);
|
|
6368
|
+
}
|
|
6369
|
+
};
|
|
6370
|
+
const [exitCode] = await Promise.all([child.exited, readOutput()]);
|
|
6371
|
+
clearTimeout(timer);
|
|
6372
|
+
for (const chunk of chunks) writeSync(1, chunk);
|
|
6373
|
+
writeSync(1, ${JSON.stringify(OS_COMMAND_RESULT_MARKER)} + String(exitCode));
|
|
6374
|
+
if (process.platform !== 'win32') terminateGroup();
|
|
6375
|
+
process.exit(0);
|
|
6376
|
+
} catch {
|
|
6377
|
+
clearTimeout(timer);
|
|
6378
|
+
terminateGroup();
|
|
6379
|
+
}
|
|
6380
|
+
`;
|
|
6381
|
+
function runBoundedOsCommand(command, args, captureOutput = true) {
|
|
6382
|
+
const result = Bun.spawnSync({
|
|
6383
|
+
cmd: [
|
|
6384
|
+
process.execPath,
|
|
6385
|
+
"-e",
|
|
6386
|
+
BOUNDED_OS_COMMAND_RUNNER,
|
|
6387
|
+
"supertask-os-command",
|
|
6388
|
+
command,
|
|
6389
|
+
...args
|
|
6390
|
+
],
|
|
6391
|
+
detached: process.platform !== "win32",
|
|
6392
|
+
maxBuffer: OS_COMMAND_MAX_OUTPUT_BYTES + 1024,
|
|
6393
|
+
stdin: "ignore",
|
|
6394
|
+
stdout: "pipe",
|
|
6395
|
+
stderr: "ignore"
|
|
6396
|
+
});
|
|
6397
|
+
const output = new TextDecoder().decode(result.stdout);
|
|
6398
|
+
const markerIndex = output.lastIndexOf(OS_COMMAND_RESULT_MARKER);
|
|
6399
|
+
if (markerIndex < 0) return { status: null, stdout: "" };
|
|
6400
|
+
const statusText = output.slice(markerIndex + OS_COMMAND_RESULT_MARKER.length);
|
|
6401
|
+
if (!/^\d+$/.test(statusText)) return { status: null, stdout: "" };
|
|
6402
|
+
return {
|
|
6403
|
+
status: Number(statusText),
|
|
6404
|
+
stdout: captureOutput ? output.slice(0, markerIndex) : ""
|
|
6405
|
+
};
|
|
6406
|
+
}
|
|
6276
6407
|
function isSafePid(pid) {
|
|
6277
6408
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid;
|
|
6278
6409
|
}
|
|
@@ -6306,12 +6437,10 @@ function inspectSpawnedProcessTreePresence(pid) {
|
|
|
6306
6437
|
}
|
|
6307
6438
|
}
|
|
6308
6439
|
function inspectUnixProcess(pid) {
|
|
6309
|
-
const result =
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
killSignal: "SIGKILL"
|
|
6314
|
-
});
|
|
6440
|
+
const result = runBoundedOsCommand(
|
|
6441
|
+
"ps",
|
|
6442
|
+
["-o", "pgid=", "-o", "command=", "-p", String(pid)]
|
|
6443
|
+
);
|
|
6315
6444
|
if (result.status !== 0) return null;
|
|
6316
6445
|
const match = result.stdout.trim().match(/^(\d+)\s+(.+)$/s);
|
|
6317
6446
|
if (!match) return null;
|
|
@@ -6319,30 +6448,18 @@ function inspectUnixProcess(pid) {
|
|
|
6319
6448
|
}
|
|
6320
6449
|
function inspectWindowsCommand(pid) {
|
|
6321
6450
|
const script = `(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}").CommandLine`;
|
|
6322
|
-
const result =
|
|
6451
|
+
const result = runBoundedOsCommand(
|
|
6323
6452
|
"powershell.exe",
|
|
6324
|
-
["-NoProfile", "-NonInteractive", "-Command", script]
|
|
6325
|
-
{
|
|
6326
|
-
encoding: "utf8",
|
|
6327
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
6328
|
-
timeout: OS_COMMAND_TIMEOUT_MS,
|
|
6329
|
-
killSignal: "SIGKILL"
|
|
6330
|
-
}
|
|
6453
|
+
["-NoProfile", "-NonInteractive", "-Command", script]
|
|
6331
6454
|
);
|
|
6332
6455
|
if (result.status !== 0) return null;
|
|
6333
6456
|
return result.stdout.trim() || null;
|
|
6334
6457
|
}
|
|
6335
6458
|
function inspectWindowsProcessTree(rootPid) {
|
|
6336
6459
|
const script = `$root=${rootPid}; $all=@(Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId); $ids=New-Object 'System.Collections.Generic.HashSet[int]'; [void]$ids.Add($root); do { $added=$false; foreach($p in $all) { if($ids.Contains([int]$p.ParentProcessId) -and $ids.Add([int]$p.ProcessId)) { $added=$true } } } while($added); $all | Where-Object { $ids.Contains([int]$_.ProcessId) } | Select-Object -ExpandProperty ProcessId`;
|
|
6337
|
-
const result =
|
|
6460
|
+
const result = runBoundedOsCommand(
|
|
6338
6461
|
"powershell.exe",
|
|
6339
|
-
["-NoProfile", "-NonInteractive", "-Command", script]
|
|
6340
|
-
{
|
|
6341
|
-
encoding: "utf8",
|
|
6342
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
6343
|
-
timeout: OS_COMMAND_TIMEOUT_MS,
|
|
6344
|
-
killSignal: "SIGKILL"
|
|
6345
|
-
}
|
|
6462
|
+
["-NoProfile", "-NonInteractive", "-Command", script]
|
|
6346
6463
|
);
|
|
6347
6464
|
if (result.status !== 0) return null;
|
|
6348
6465
|
return result.stdout.split(/\s+/).filter(Boolean).map(Number).filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
@@ -6399,11 +6516,7 @@ function signalRecordedProcessTreeWithResult(pid, signal, expectedExecutable, la
|
|
|
6399
6516
|
}
|
|
6400
6517
|
const args = ["/PID", String(pid), "/T"];
|
|
6401
6518
|
if (signal === "SIGKILL") args.push("/F");
|
|
6402
|
-
const status =
|
|
6403
|
-
stdio: "ignore",
|
|
6404
|
-
timeout: OS_COMMAND_TIMEOUT_MS,
|
|
6405
|
-
killSignal: "SIGKILL"
|
|
6406
|
-
}).status;
|
|
6519
|
+
const status = runBoundedOsCommand("taskkill", args, false).status;
|
|
6407
6520
|
if (status === 0) return "signalled";
|
|
6408
6521
|
return isProcessAlive(pid) ? "signal-failed" : absentLeaderResult(pid);
|
|
6409
6522
|
}
|
|
@@ -6574,7 +6687,7 @@ var TaskRunService = class {
|
|
|
6574
6687
|
}
|
|
6575
6688
|
if (current.childPid != null) {
|
|
6576
6689
|
throw new LegacyRunAbandonConflictError(
|
|
6577
|
-
`run #${runId} \u5DF2\u8BB0\u5F55 child PID ${current.childPid}\uFF0C\u5FC5\u987B\u7531 Worker/Watchdog \u786E\u8BA4\u8FDB\u7A0B\
|
|
6690
|
+
`run #${runId} \u5DF2\u8BB0\u5F55 child PID ${current.childPid}\uFF0C\u5FC5\u987B\u7531 Worker/Watchdog \u786E\u8BA4\u53D7\u7BA1\u8FDB\u7A0B\u7EC4\u6392\u7A7A`
|
|
6578
6691
|
);
|
|
6579
6692
|
}
|
|
6580
6693
|
if (current.workerPid != null && isProcessAlive(current.workerPid)) {
|
|
@@ -6663,7 +6776,7 @@ function runCommandContext(executable, args, cwd) {
|
|
|
6663
6776
|
function assertWorkerProcessIsolationSupported(platform = process.platform) {
|
|
6664
6777
|
if (platform === "win32") {
|
|
6665
6778
|
throw new Error(
|
|
6666
|
-
"Windows Worker \u5DF2\u5B89\u5168\u7981\u7528\uFF1A\u5F53\u524D\u8FD0\u884C\u65F6\u65E0\u6CD5\u7528 Job Object \
|
|
6779
|
+
"Windows Worker \u5DF2\u5B89\u5168\u7981\u7528\uFF1A\u5F53\u524D\u8FD0\u884C\u65F6\u65E0\u6CD5\u7528 Job Object \u63D0\u4F9B\u7B49\u4EF7\u7684\u53D7\u7BA1\u8FDB\u7A0B\u9694\u79BB\u4E0E\u6392\u7A7A\u8BC1\u660E"
|
|
6667
6780
|
);
|
|
6668
6781
|
}
|
|
6669
6782
|
}
|
|
@@ -6674,17 +6787,24 @@ var WorkerEngine = class {
|
|
|
6674
6787
|
pollTimer = null;
|
|
6675
6788
|
heartbeatTimer = null;
|
|
6676
6789
|
pollCyclePromise = null;
|
|
6790
|
+
shutdownDeadlineMs = null;
|
|
6791
|
+
settlementRetryWakeups = /* @__PURE__ */ new Set();
|
|
6677
6792
|
cfg;
|
|
6678
6793
|
opencodeBin;
|
|
6679
6794
|
maxOutputChars;
|
|
6795
|
+
settlementRetryDelaysMs;
|
|
6796
|
+
settlementRetryIntervalMs;
|
|
6680
6797
|
constructor(cfg, options = {}) {
|
|
6681
6798
|
this.cfg = cfg.worker;
|
|
6682
6799
|
this.opencodeBin = options.opencodeBin ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
6683
6800
|
this.maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
6801
|
+
this.settlementRetryDelaysMs = options.settlementRetryDelaysMs ?? [250, 1e3, 4e3];
|
|
6802
|
+
this.settlementRetryIntervalMs = options.settlementRetryIntervalMs ?? 5e3;
|
|
6684
6803
|
}
|
|
6685
6804
|
start() {
|
|
6686
6805
|
assertWorkerProcessIsolationSupported();
|
|
6687
6806
|
this.stopped = false;
|
|
6807
|
+
this.shutdownDeadlineMs = null;
|
|
6688
6808
|
markGatewayActivity("worker");
|
|
6689
6809
|
this.poll();
|
|
6690
6810
|
this.heartbeatTimer = setInterval(() => {
|
|
@@ -6693,6 +6813,8 @@ var WorkerEngine = class {
|
|
|
6693
6813
|
}
|
|
6694
6814
|
async stop(gracePeriodMs = 0) {
|
|
6695
6815
|
this.stopped = true;
|
|
6816
|
+
this.shutdownDeadlineMs = Date.now() + Math.max(0, gracePeriodMs);
|
|
6817
|
+
for (const wake of [...this.settlementRetryWakeups]) wake();
|
|
6696
6818
|
if (this.pollTimer) {
|
|
6697
6819
|
clearTimeout(this.pollTimer);
|
|
6698
6820
|
this.pollTimer = null;
|
|
@@ -6762,27 +6884,25 @@ var WorkerEngine = class {
|
|
|
6762
6884
|
if (databaseRunningCount >= this.cfg.maxConcurrency) break;
|
|
6763
6885
|
let task;
|
|
6764
6886
|
try {
|
|
6765
|
-
task = await TaskService.
|
|
6887
|
+
task = await TaskService.claimNext({ excludedBatchIds: [...this.activeBatchIds] });
|
|
6766
6888
|
} catch (err) {
|
|
6767
6889
|
this.logError("task claim failed", err);
|
|
6768
6890
|
throw err;
|
|
6769
6891
|
}
|
|
6770
6892
|
if (!task) break;
|
|
6771
|
-
if (this.stopped) break;
|
|
6772
|
-
if (!await TaskService.start(task.id)) continue;
|
|
6773
|
-
const batchId = normalizeTaskBatchId(task.batchId);
|
|
6774
|
-
if (batchId) this.activeBatchIds.add(batchId);
|
|
6775
6893
|
if (this.stopped) {
|
|
6776
6894
|
await TaskService.resetRunningToPending([task.id]);
|
|
6777
|
-
this.releaseBatch(task);
|
|
6778
6895
|
break;
|
|
6779
6896
|
}
|
|
6897
|
+
const batchId = normalizeTaskBatchId(task.batchId);
|
|
6898
|
+
if (batchId) this.activeBatchIds.add(batchId);
|
|
6780
6899
|
let runId = null;
|
|
6781
6900
|
try {
|
|
6782
6901
|
const launchIdentity = `gateway-${process.pid}:launch:${randomUUID()}`;
|
|
6783
6902
|
const run = await TaskRunService.create({
|
|
6784
6903
|
taskId: task.id,
|
|
6785
6904
|
model: this.resolveModel(task.model),
|
|
6905
|
+
variant: this.resolveVariant(task.variant),
|
|
6786
6906
|
status: "running",
|
|
6787
6907
|
workerPid: process.pid,
|
|
6788
6908
|
lockedAt: Date.now(),
|
|
@@ -6845,8 +6965,10 @@ var WorkerEngine = class {
|
|
|
6845
6965
|
}
|
|
6846
6966
|
async spawnTask(task, runId, launchIdentity) {
|
|
6847
6967
|
const model = this.resolveModel(task.model);
|
|
6968
|
+
const variant = this.resolveVariant(task.variant);
|
|
6848
6969
|
const args = ["run", "--agent", task.agent, "--format", "json"];
|
|
6849
6970
|
if (model) args.push("-m", model);
|
|
6971
|
+
if (variant) args.push("--variant", variant);
|
|
6850
6972
|
args.push(task.prompt);
|
|
6851
6973
|
const cwd = task.cwd || process.cwd();
|
|
6852
6974
|
const child = spawn(process.execPath, [
|
|
@@ -6984,7 +7106,7 @@ var WorkerEngine = class {
|
|
|
6984
7106
|
if (!entry.guardianDrained) {
|
|
6985
7107
|
const pid = entry.child.pid;
|
|
6986
7108
|
const presence = pid == null ? spawnError == null ? "unknown" : "not-running" : inspectSpawnedProcessTreePresence(pid);
|
|
6987
|
-
const message = "guardian \u672A\u63D0\u4F9B\u8FDB\u7A0B\
|
|
7109
|
+
const message = "guardian \u672A\u63D0\u4F9B\u53D7\u7BA1\u8FDB\u7A0B\u7EC4\u6392\u7A7A\u8BC1\u660E";
|
|
6988
7110
|
if (presence !== "not-running") {
|
|
6989
7111
|
if (entry.timeoutTimer) {
|
|
6990
7112
|
clearTimeout(entry.timeoutTimer);
|
|
@@ -7005,7 +7127,7 @@ var WorkerEngine = class {
|
|
|
7005
7127
|
);
|
|
7006
7128
|
return;
|
|
7007
7129
|
}
|
|
7008
|
-
const failure = code === 0 ? void 0 : `${spawnError ? "\u65E0\u6CD5\u542F\u52A8 opencode" : "opencode \u9000\u51FA\u7801"} ${spawnError?.message ?? code ?? "null"}${signal ? `\uFF0C\u4FE1\u53F7 ${signal}` : ""}\uFF08agent=${entry.task.agent}\uFF0Cmodel=${this.resolveModel(entry.task.model) ?? "Agent/\u9ED8\u8BA4\u914D\u7F6E"}\uFF0Ccwd=${entry.task.cwd ?? process.cwd()}\uFF09`;
|
|
7130
|
+
const failure = code === 0 ? void 0 : `${spawnError ? "\u65E0\u6CD5\u542F\u52A8 opencode" : "opencode \u9000\u51FA\u7801"} ${spawnError?.message ?? code ?? "null"}${signal ? `\uFF0C\u4FE1\u53F7 ${signal}` : ""}\uFF08agent=${entry.task.agent}\uFF0Cmodel=${this.resolveModel(entry.task.model) ?? "Agent/\u9ED8\u8BA4\u914D\u7F6E"}\uFF0Cvariant=${this.resolveVariant(entry.task.variant) ?? "Agent/\u6A21\u578B\u9ED8\u8BA4\u914D\u7F6E"}\uFF0Ccwd=${entry.task.cwd ?? process.cwd()}\uFF09`;
|
|
7009
7131
|
this.runDetached(
|
|
7010
7132
|
this.settleEntry(entry, code, failure),
|
|
7011
7133
|
"task settlement failed",
|
|
@@ -7020,17 +7142,48 @@ var WorkerEngine = class {
|
|
|
7020
7142
|
clearTimeout(entry.timeoutTimer);
|
|
7021
7143
|
entry.timeoutTimer = null;
|
|
7022
7144
|
}
|
|
7023
|
-
const settlement = this.
|
|
7024
|
-
markGatewayFailure("worker", error);
|
|
7025
|
-
this.logError("task settlement failed", error, entry.task.id);
|
|
7026
|
-
return false;
|
|
7027
|
-
}).finally(() => {
|
|
7145
|
+
const settlement = this.commitEntryWithRetry(entry, code, failure).finally(() => {
|
|
7028
7146
|
this.runningTasks.delete(entry.task.id);
|
|
7029
7147
|
this.releaseBatch(entry.task);
|
|
7030
7148
|
});
|
|
7031
7149
|
entry.settlementPromise = settlement;
|
|
7032
7150
|
return settlement;
|
|
7033
7151
|
}
|
|
7152
|
+
async commitEntryWithRetry(entry, code, failure) {
|
|
7153
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
7154
|
+
try {
|
|
7155
|
+
await this.commitEntry(entry, code, failure);
|
|
7156
|
+
return true;
|
|
7157
|
+
} catch (error) {
|
|
7158
|
+
markGatewayFailure("worker", error);
|
|
7159
|
+
const shortRetryDelayMs = this.settlementRetryDelaysMs[attempt];
|
|
7160
|
+
let retryDelayMs = shortRetryDelayMs ?? this.settlementRetryIntervalMs;
|
|
7161
|
+
if (this.stopped) {
|
|
7162
|
+
const remainingMs = Math.max(0, (this.shutdownDeadlineMs ?? 0) - Date.now());
|
|
7163
|
+
if (remainingMs === 0) return false;
|
|
7164
|
+
retryDelayMs = Math.min(retryDelayMs, remainingMs);
|
|
7165
|
+
}
|
|
7166
|
+
this.logError(
|
|
7167
|
+
`task settlement failed; retrying in ${retryDelayMs}ms`,
|
|
7168
|
+
error,
|
|
7169
|
+
entry.task.id
|
|
7170
|
+
);
|
|
7171
|
+
await this.waitForSettlementRetry(retryDelayMs);
|
|
7172
|
+
}
|
|
7173
|
+
}
|
|
7174
|
+
}
|
|
7175
|
+
waitForSettlementRetry(delayMs) {
|
|
7176
|
+
return new Promise((resolve2) => {
|
|
7177
|
+
let timer;
|
|
7178
|
+
const finish = () => {
|
|
7179
|
+
clearTimeout(timer);
|
|
7180
|
+
this.settlementRetryWakeups.delete(finish);
|
|
7181
|
+
resolve2();
|
|
7182
|
+
};
|
|
7183
|
+
timer = setTimeout(finish, delayMs);
|
|
7184
|
+
this.settlementRetryWakeups.add(finish);
|
|
7185
|
+
});
|
|
7186
|
+
}
|
|
7034
7187
|
async commitEntry(entry, code, failure) {
|
|
7035
7188
|
const termination = entry.termination;
|
|
7036
7189
|
if (termination?.kind === "shutdown") return;
|
|
@@ -7194,6 +7347,9 @@ ${output}` : ""}`;
|
|
|
7194
7347
|
if (!taskModel || taskModel === "default") return null;
|
|
7195
7348
|
return taskModel;
|
|
7196
7349
|
}
|
|
7350
|
+
resolveVariant(taskVariant) {
|
|
7351
|
+
return taskVariant?.trim() || null;
|
|
7352
|
+
}
|
|
7197
7353
|
runDetached(operation, message, taskId) {
|
|
7198
7354
|
operation.catch((error) => {
|
|
7199
7355
|
markGatewayFailure("worker", error);
|