opencode-supertask 0.1.40 → 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 +18 -1
- package/LICENSE +21 -0
- package/README.md +10 -3
- package/README.zh-CN.md +10 -3
- package/dist/cli/index.js +365 -124
- 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 +348 -572
- package/dist/gateway/index.js.map +1 -1
- package/dist/plugin/supertask.js +53 -24
- package/dist/plugin/supertask.js.map +1 -1
- package/dist/web/index.js +231 -487
- package/dist/web/index.js.map +1 -1
- package/dist/worker/index.d.ts +8 -0
- package/dist/worker/index.js +191 -66
- package/dist/worker/index.js.map +1 -1
- package/dist/worker/launcher.js.map +1 -1
- package/package.json +12 -3
package/dist/gateway/index.js
CHANGED
|
@@ -6110,8 +6110,8 @@ import { homedir } from "os";
|
|
|
6110
6110
|
import { join, dirname } from "path";
|
|
6111
6111
|
import { fileURLToPath } from "url";
|
|
6112
6112
|
function getMigrationsFolder() {
|
|
6113
|
-
const
|
|
6114
|
-
let dir =
|
|
6113
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6114
|
+
let dir = __dirname;
|
|
6115
6115
|
for (let i = 0; i < 5; i++) {
|
|
6116
6116
|
const candidate = join(dir, "drizzle", "meta", "_journal.json");
|
|
6117
6117
|
if (existsSync(candidate)) {
|
|
@@ -6119,7 +6119,7 @@ function getMigrationsFolder() {
|
|
|
6119
6119
|
}
|
|
6120
6120
|
dir = dirname(dir);
|
|
6121
6121
|
}
|
|
6122
|
-
return join(
|
|
6122
|
+
return join(__dirname, "../../drizzle");
|
|
6123
6123
|
}
|
|
6124
6124
|
function ensureGatewayLock(sqliteDb) {
|
|
6125
6125
|
sqliteDb.exec(`
|
|
@@ -6598,13 +6598,23 @@ var init_task_service = __esm({
|
|
|
6598
6598
|
});
|
|
6599
6599
|
const maxRetries = normalizedData.maxRetries ?? task.maxRetries ?? 3;
|
|
6600
6600
|
const exhausted = task.status === "failed" && (task.retryCount ?? 0) > maxRetries;
|
|
6601
|
-
|
|
6601
|
+
const updated = tx.update(tasks2).set({
|
|
6602
6602
|
...normalizedData,
|
|
6603
6603
|
...exhausted ? {
|
|
6604
6604
|
status: "dead_letter",
|
|
6605
6605
|
retryAfter: null
|
|
6606
6606
|
} : {}
|
|
6607
6607
|
}).where(eq(tasks2.id, id)).returning().get() ?? null;
|
|
6608
|
+
if (exhausted && updated) {
|
|
6609
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
6610
|
+
tx.update(tasks2).set({
|
|
6611
|
+
status: "dead_letter",
|
|
6612
|
+
finishedAt,
|
|
6613
|
+
retryAfter: null,
|
|
6614
|
+
resultLog: `\u4F9D\u8D56\u4EFB\u52A1 #${id} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`
|
|
6615
|
+
}).where(blockedDependentsOf(id)).run();
|
|
6616
|
+
}
|
|
6617
|
+
return updated;
|
|
6608
6618
|
}, { behavior: "immediate" });
|
|
6609
6619
|
}
|
|
6610
6620
|
static validateNewTask(data) {
|
|
@@ -6625,7 +6635,7 @@ var init_task_service = __esm({
|
|
|
6625
6635
|
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
6626
6636
|
}
|
|
6627
6637
|
}
|
|
6628
|
-
static
|
|
6638
|
+
static buildRunnableTaskWhere(scope) {
|
|
6629
6639
|
const baseConditions = [...this.buildScopeWhere(scope)];
|
|
6630
6640
|
const nowMs = Date.now();
|
|
6631
6641
|
const retryAfterFilter = or(
|
|
@@ -6660,40 +6670,43 @@ var init_task_service = __esm({
|
|
|
6660
6670
|
if (batchFilter) {
|
|
6661
6671
|
conditions.push(batchFilter);
|
|
6662
6672
|
}
|
|
6663
|
-
|
|
6673
|
+
return and(
|
|
6664
6674
|
...conditions,
|
|
6665
6675
|
or(
|
|
6666
6676
|
isNull(tasks2.dependsOn),
|
|
6667
6677
|
sql`EXISTS (
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6678
|
+
SELECT 1 FROM tasks AS dependency_task
|
|
6679
|
+
WHERE dependency_task.id = ${tasks2.dependsOn}
|
|
6680
|
+
AND dependency_task.status = 'done'
|
|
6681
|
+
AND dependency_task.cwd IS ${tasks2.cwd}
|
|
6682
|
+
)`
|
|
6673
6683
|
),
|
|
6674
6684
|
or(
|
|
6675
6685
|
isNull(tasks2.batchId),
|
|
6676
6686
|
sql`trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ''`,
|
|
6677
6687
|
sql`NOT EXISTS (
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
)
|
|
6688
|
+
SELECT 1 FROM tasks AS running_batch_task
|
|
6689
|
+
WHERE trim(running_batch_task.batch_id, ${TASK_BATCH_TRIM_CHARACTERS})
|
|
6690
|
+
= trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS})
|
|
6691
|
+
AND (
|
|
6692
|
+
running_batch_task.status = 'running'
|
|
6693
|
+
OR EXISTS (
|
|
6694
|
+
SELECT 1 FROM task_runs AS running_batch_run
|
|
6695
|
+
WHERE running_batch_run.task_id = running_batch_task.id
|
|
6696
|
+
AND running_batch_run.status = 'running'
|
|
6688
6697
|
)
|
|
6689
|
-
|
|
6698
|
+
)
|
|
6699
|
+
)`
|
|
6690
6700
|
),
|
|
6691
6701
|
sql`NOT EXISTS (
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
)
|
|
6702
|
+
SELECT 1 FROM task_runs AS candidate_active_run
|
|
6703
|
+
WHERE candidate_active_run.task_id = ${tasks2.id}
|
|
6704
|
+
AND candidate_active_run.status = 'running'
|
|
6705
|
+
)`
|
|
6706
|
+
);
|
|
6707
|
+
}
|
|
6708
|
+
static async next(scope = {}) {
|
|
6709
|
+
const result = await db.select().from(tasks2).where(this.buildRunnableTaskWhere(scope)).orderBy(
|
|
6697
6710
|
desc(tasks2.urgency),
|
|
6698
6711
|
desc(tasks2.importance),
|
|
6699
6712
|
asc(tasks2.createdAt),
|
|
@@ -6701,6 +6714,22 @@ var init_task_service = __esm({
|
|
|
6701
6714
|
).limit(1);
|
|
6702
6715
|
return result[0] ?? null;
|
|
6703
6716
|
}
|
|
6717
|
+
static async claimNext(scope = {}) {
|
|
6718
|
+
return db.transaction((tx) => {
|
|
6719
|
+
const candidate = tx.select().from(tasks2).where(this.buildRunnableTaskWhere(scope)).orderBy(
|
|
6720
|
+
desc(tasks2.urgency),
|
|
6721
|
+
desc(tasks2.importance),
|
|
6722
|
+
asc(tasks2.createdAt),
|
|
6723
|
+
asc(tasks2.id)
|
|
6724
|
+
).limit(1).get();
|
|
6725
|
+
if (!candidate) return null;
|
|
6726
|
+
return tx.update(tasks2).set({
|
|
6727
|
+
status: "running",
|
|
6728
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
6729
|
+
finishedAt: null
|
|
6730
|
+
}).where(eq(tasks2.id, candidate.id)).returning().get() ?? null;
|
|
6731
|
+
}, { behavior: "immediate" });
|
|
6732
|
+
}
|
|
6704
6733
|
static async countRunning(scope = {}) {
|
|
6705
6734
|
const scopeConditions = scope.legacyCwd ? [sql`${tasks2.cwd} IS NULL OR trim(${tasks2.cwd}) = ''`] : this.buildScopeWhere(scope);
|
|
6706
6735
|
if (scope.batchId !== void 0) {
|
|
@@ -7361,9 +7390,34 @@ var init_launch_protocol = __esm({
|
|
|
7361
7390
|
});
|
|
7362
7391
|
|
|
7363
7392
|
// src/core/process-control.ts
|
|
7364
|
-
import { spawnSync } from "child_process";
|
|
7365
7393
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7366
7394
|
import { basename, dirname as dirname2, resolve } from "path";
|
|
7395
|
+
function runBoundedOsCommand(command, args, captureOutput = true) {
|
|
7396
|
+
const result = Bun.spawnSync({
|
|
7397
|
+
cmd: [
|
|
7398
|
+
process.execPath,
|
|
7399
|
+
"-e",
|
|
7400
|
+
BOUNDED_OS_COMMAND_RUNNER,
|
|
7401
|
+
"supertask-os-command",
|
|
7402
|
+
command,
|
|
7403
|
+
...args
|
|
7404
|
+
],
|
|
7405
|
+
detached: process.platform !== "win32",
|
|
7406
|
+
maxBuffer: OS_COMMAND_MAX_OUTPUT_BYTES + 1024,
|
|
7407
|
+
stdin: "ignore",
|
|
7408
|
+
stdout: "pipe",
|
|
7409
|
+
stderr: "ignore"
|
|
7410
|
+
});
|
|
7411
|
+
const output = new TextDecoder().decode(result.stdout);
|
|
7412
|
+
const markerIndex = output.lastIndexOf(OS_COMMAND_RESULT_MARKER);
|
|
7413
|
+
if (markerIndex < 0) return { status: null, stdout: "" };
|
|
7414
|
+
const statusText2 = output.slice(markerIndex + OS_COMMAND_RESULT_MARKER.length);
|
|
7415
|
+
if (!/^\d+$/.test(statusText2)) return { status: null, stdout: "" };
|
|
7416
|
+
return {
|
|
7417
|
+
status: Number(statusText2),
|
|
7418
|
+
stdout: captureOutput ? output.slice(0, markerIndex) : ""
|
|
7419
|
+
};
|
|
7420
|
+
}
|
|
7367
7421
|
function isSafePid(pid) {
|
|
7368
7422
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid;
|
|
7369
7423
|
}
|
|
@@ -7400,12 +7454,10 @@ function inspectSpawnedProcessTreePresence(pid) {
|
|
|
7400
7454
|
}
|
|
7401
7455
|
}
|
|
7402
7456
|
function inspectUnixProcess(pid) {
|
|
7403
|
-
const result =
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
killSignal: "SIGKILL"
|
|
7408
|
-
});
|
|
7457
|
+
const result = runBoundedOsCommand(
|
|
7458
|
+
"ps",
|
|
7459
|
+
["-o", "pgid=", "-o", "command=", "-p", String(pid)]
|
|
7460
|
+
);
|
|
7409
7461
|
if (result.status !== 0) return null;
|
|
7410
7462
|
const match2 = result.stdout.trim().match(/^(\d+)\s+(.+)$/s);
|
|
7411
7463
|
if (!match2) return null;
|
|
@@ -7413,30 +7465,18 @@ function inspectUnixProcess(pid) {
|
|
|
7413
7465
|
}
|
|
7414
7466
|
function inspectWindowsCommand(pid) {
|
|
7415
7467
|
const script = `(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}").CommandLine`;
|
|
7416
|
-
const result =
|
|
7468
|
+
const result = runBoundedOsCommand(
|
|
7417
7469
|
"powershell.exe",
|
|
7418
|
-
["-NoProfile", "-NonInteractive", "-Command", script]
|
|
7419
|
-
{
|
|
7420
|
-
encoding: "utf8",
|
|
7421
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
7422
|
-
timeout: OS_COMMAND_TIMEOUT_MS,
|
|
7423
|
-
killSignal: "SIGKILL"
|
|
7424
|
-
}
|
|
7470
|
+
["-NoProfile", "-NonInteractive", "-Command", script]
|
|
7425
7471
|
);
|
|
7426
7472
|
if (result.status !== 0) return null;
|
|
7427
7473
|
return result.stdout.trim() || null;
|
|
7428
7474
|
}
|
|
7429
7475
|
function inspectWindowsProcessTree(rootPid) {
|
|
7430
7476
|
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`;
|
|
7431
|
-
const result =
|
|
7477
|
+
const result = runBoundedOsCommand(
|
|
7432
7478
|
"powershell.exe",
|
|
7433
|
-
["-NoProfile", "-NonInteractive", "-Command", script]
|
|
7434
|
-
{
|
|
7435
|
-
encoding: "utf8",
|
|
7436
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
7437
|
-
timeout: OS_COMMAND_TIMEOUT_MS,
|
|
7438
|
-
killSignal: "SIGKILL"
|
|
7439
|
-
}
|
|
7479
|
+
["-NoProfile", "-NonInteractive", "-Command", script]
|
|
7440
7480
|
);
|
|
7441
7481
|
if (result.status !== 0) return null;
|
|
7442
7482
|
return result.stdout.split(/\s+/).filter(Boolean).map(Number).filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
@@ -7507,11 +7547,7 @@ function signalRecordedProcessTreeWithResult(pid, signal, expectedExecutable, la
|
|
|
7507
7547
|
}
|
|
7508
7548
|
const args = ["/PID", String(pid), "/T"];
|
|
7509
7549
|
if (signal === "SIGKILL") args.push("/F");
|
|
7510
|
-
const status =
|
|
7511
|
-
stdio: "ignore",
|
|
7512
|
-
timeout: OS_COMMAND_TIMEOUT_MS,
|
|
7513
|
-
killSignal: "SIGKILL"
|
|
7514
|
-
}).status;
|
|
7550
|
+
const status = runBoundedOsCommand("taskkill", args, false).status;
|
|
7515
7551
|
if (status === 0) return "signalled";
|
|
7516
7552
|
return isProcessAlive(pid) ? "signal-failed" : absentLeaderResult(pid);
|
|
7517
7553
|
}
|
|
@@ -7540,12 +7576,64 @@ async function waitForSpawnedProcessTreeExit(pid, timeoutMs = 5e3) {
|
|
|
7540
7576
|
}
|
|
7541
7577
|
return inspectSpawnedProcessTreePresence(pid) === "not-running";
|
|
7542
7578
|
}
|
|
7543
|
-
var OS_COMMAND_TIMEOUT_MS;
|
|
7579
|
+
var OS_COMMAND_TIMEOUT_MS, OS_COMMAND_MAX_OUTPUT_BYTES, OS_COMMAND_RESULT_MARKER, BOUNDED_OS_COMMAND_RUNNER;
|
|
7544
7580
|
var init_process_control = __esm({
|
|
7545
7581
|
"src/core/process-control.ts"() {
|
|
7546
7582
|
"use strict";
|
|
7547
7583
|
init_launch_protocol();
|
|
7548
7584
|
OS_COMMAND_TIMEOUT_MS = 2e3;
|
|
7585
|
+
OS_COMMAND_MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
7586
|
+
OS_COMMAND_RESULT_MARKER = "\n\0supertask-os-command-result:";
|
|
7587
|
+
BOUNDED_OS_COMMAND_RUNNER = `
|
|
7588
|
+
const { writeSync } = await import('fs');
|
|
7589
|
+
let child;
|
|
7590
|
+
try {
|
|
7591
|
+
child = Bun.spawn(process.argv.slice(2), {
|
|
7592
|
+
stdin: 'ignore',
|
|
7593
|
+
stdout: 'pipe',
|
|
7594
|
+
stderr: 'ignore',
|
|
7595
|
+
});
|
|
7596
|
+
} catch {
|
|
7597
|
+
process.exit(127);
|
|
7598
|
+
}
|
|
7599
|
+
const terminateGroup = () => {
|
|
7600
|
+
if (process.platform !== 'win32') {
|
|
7601
|
+
try {
|
|
7602
|
+
process.kill(-process.pid, 'SIGKILL');
|
|
7603
|
+
} catch {
|
|
7604
|
+
}
|
|
7605
|
+
}
|
|
7606
|
+
try {
|
|
7607
|
+
child.kill('SIGKILL');
|
|
7608
|
+
} catch {
|
|
7609
|
+
}
|
|
7610
|
+
process.exit(1);
|
|
7611
|
+
};
|
|
7612
|
+
const timer = setTimeout(terminateGroup, ${OS_COMMAND_TIMEOUT_MS});
|
|
7613
|
+
try {
|
|
7614
|
+
const chunks = [];
|
|
7615
|
+
let outputBytes = 0;
|
|
7616
|
+
const readOutput = async () => {
|
|
7617
|
+
const reader = child.stdout.getReader();
|
|
7618
|
+
while (true) {
|
|
7619
|
+
const { done, value } = await reader.read();
|
|
7620
|
+
if (done) return;
|
|
7621
|
+
outputBytes += value.byteLength;
|
|
7622
|
+
if (outputBytes > ${OS_COMMAND_MAX_OUTPUT_BYTES}) terminateGroup();
|
|
7623
|
+
chunks.push(value);
|
|
7624
|
+
}
|
|
7625
|
+
};
|
|
7626
|
+
const [exitCode] = await Promise.all([child.exited, readOutput()]);
|
|
7627
|
+
clearTimeout(timer);
|
|
7628
|
+
for (const chunk of chunks) writeSync(1, chunk);
|
|
7629
|
+
writeSync(1, ${JSON.stringify(OS_COMMAND_RESULT_MARKER)} + String(exitCode));
|
|
7630
|
+
if (process.platform !== 'win32') terminateGroup();
|
|
7631
|
+
process.exit(0);
|
|
7632
|
+
} catch {
|
|
7633
|
+
clearTimeout(timer);
|
|
7634
|
+
terminateGroup();
|
|
7635
|
+
}
|
|
7636
|
+
`;
|
|
7549
7637
|
}
|
|
7550
7638
|
});
|
|
7551
7639
|
|
|
@@ -7697,7 +7785,7 @@ var init_task_run_service = __esm({
|
|
|
7697
7785
|
}
|
|
7698
7786
|
if (current.childPid != null) {
|
|
7699
7787
|
throw new LegacyRunAbandonConflictError(
|
|
7700
|
-
`run #${runId} \u5DF2\u8BB0\u5F55 child PID ${current.childPid}\uFF0C\u5FC5\u987B\u7531 Worker/Watchdog \u786E\u8BA4\u8FDB\u7A0B\
|
|
7788
|
+
`run #${runId} \u5DF2\u8BB0\u5F55 child PID ${current.childPid}\uFF0C\u5FC5\u987B\u7531 Worker/Watchdog \u786E\u8BA4\u53D7\u7BA1\u8FDB\u7A0B\u7EC4\u6392\u7A7A`
|
|
7701
7789
|
);
|
|
7702
7790
|
}
|
|
7703
7791
|
if (current.workerPid != null && isProcessAlive(current.workerPid)) {
|
|
@@ -16941,8 +17029,8 @@ var require_CronFileParser = __commonJS({
|
|
|
16941
17029
|
* @throws If file cannot be read
|
|
16942
17030
|
*/
|
|
16943
17031
|
static parseFileSync(filePath) {
|
|
16944
|
-
const { readFileSync:
|
|
16945
|
-
const data =
|
|
17032
|
+
const { readFileSync: readFileSync4 } = __require("fs");
|
|
17033
|
+
const data = readFileSync4(filePath, "utf8");
|
|
16946
17034
|
return _CronFileParser.#parseContent(data);
|
|
16947
17035
|
}
|
|
16948
17036
|
/**
|
|
@@ -17387,32 +17475,6 @@ var init_job_templates = __esm({
|
|
|
17387
17475
|
}
|
|
17388
17476
|
});
|
|
17389
17477
|
|
|
17390
|
-
// src/core/package-version.ts
|
|
17391
|
-
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
17392
|
-
import { dirname as dirname4, join as join4 } from "path";
|
|
17393
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
17394
|
-
function getPackageVersion() {
|
|
17395
|
-
let directory = dirname4(fileURLToPath4(import.meta.url));
|
|
17396
|
-
for (let depth = 0; depth < 5; depth += 1) {
|
|
17397
|
-
const packagePath = join4(directory, "package.json");
|
|
17398
|
-
if (existsSync4(packagePath)) {
|
|
17399
|
-
try {
|
|
17400
|
-
const pkg = JSON.parse(readFileSync2(packagePath, "utf8"));
|
|
17401
|
-
if (typeof pkg.version === "string") return pkg.version;
|
|
17402
|
-
} catch {
|
|
17403
|
-
return "0.0.0";
|
|
17404
|
-
}
|
|
17405
|
-
}
|
|
17406
|
-
directory = dirname4(directory);
|
|
17407
|
-
}
|
|
17408
|
-
return "0.0.0";
|
|
17409
|
-
}
|
|
17410
|
-
var init_package_version = __esm({
|
|
17411
|
-
"src/core/package-version.ts"() {
|
|
17412
|
-
"use strict";
|
|
17413
|
-
}
|
|
17414
|
-
});
|
|
17415
|
-
|
|
17416
17478
|
// node_modules/hono/dist/compose.js
|
|
17417
17479
|
var compose;
|
|
17418
17480
|
var init_compose = __esm({
|
|
@@ -19670,7 +19732,7 @@ function cleanOutput(value) {
|
|
|
19670
19732
|
return value.replace(ANSI_PATTERN, "").replace(/\r/g, "");
|
|
19671
19733
|
}
|
|
19672
19734
|
function runOpenCode(executable, args, cwd, timeoutMs) {
|
|
19673
|
-
return new Promise((
|
|
19735
|
+
return new Promise((resolve3, reject) => {
|
|
19674
19736
|
const child = spawn2(executable, args, {
|
|
19675
19737
|
cwd,
|
|
19676
19738
|
env: process.env,
|
|
@@ -19748,7 +19810,7 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
|
|
|
19748
19810
|
reject(new OpenCodeCommandExitError(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
|
|
19749
19811
|
return;
|
|
19750
19812
|
}
|
|
19751
|
-
|
|
19813
|
+
resolve3(cleanOutput(stdout));
|
|
19752
19814
|
});
|
|
19753
19815
|
});
|
|
19754
19816
|
}
|
|
@@ -19819,9 +19881,9 @@ async function loadOpenCodeCatalog(cwd, options = {}) {
|
|
|
19819
19881
|
const executable = options.executable ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
19820
19882
|
const timeoutMs = options.timeoutMs ?? COMMAND_TIMEOUT_MS;
|
|
19821
19883
|
const cacheKey = `${executable}\0${cwd}`;
|
|
19822
|
-
const
|
|
19823
|
-
if (options.useCache !== false &&
|
|
19824
|
-
return
|
|
19884
|
+
const cached2 = catalogCache.get(cacheKey);
|
|
19885
|
+
if (options.useCache !== false && cached2 && cached2.expiresAt > Date.now()) {
|
|
19886
|
+
return cached2.result;
|
|
19825
19887
|
}
|
|
19826
19888
|
const result = Promise.all([
|
|
19827
19889
|
runOpenCode(executable, ["models", "--verbose"], cwd, timeoutMs).catch((error) => {
|
|
@@ -20296,464 +20358,106 @@ var init_database_maintenance_service = __esm({
|
|
|
20296
20358
|
}
|
|
20297
20359
|
});
|
|
20298
20360
|
|
|
20299
|
-
// src/
|
|
20300
|
-
import {
|
|
20301
|
-
|
|
20302
|
-
|
|
20303
|
-
"use strict";
|
|
20304
|
-
}
|
|
20305
|
-
});
|
|
20306
|
-
|
|
20307
|
-
// src/daemon/pm2.ts
|
|
20308
|
-
import { execSync, spawnSync as spawnSync2 } from "child_process";
|
|
20309
|
-
import {
|
|
20310
|
-
accessSync,
|
|
20311
|
-
chmodSync as chmodSync2,
|
|
20312
|
-
constants,
|
|
20313
|
-
existsSync as existsSync6,
|
|
20314
|
-
mkdirSync as mkdirSync3,
|
|
20315
|
-
readFileSync as readFileSync3,
|
|
20316
|
-
rmSync,
|
|
20317
|
-
statSync as statSync3,
|
|
20318
|
-
writeFileSync as writeFileSync2
|
|
20319
|
-
} from "fs";
|
|
20320
|
-
import { homedir as homedir3, userInfo } from "os";
|
|
20321
|
-
import { delimiter, dirname as dirname6, isAbsolute as isAbsolute2, join as join5, resolve as resolve3 } from "path";
|
|
20361
|
+
// src/web/gateway-diagnostic.ts
|
|
20362
|
+
import { spawn as spawn3 } from "child_process";
|
|
20363
|
+
import { existsSync as existsSync6 } from "fs";
|
|
20364
|
+
import { dirname as dirname6, join as join5 } from "path";
|
|
20322
20365
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
20323
|
-
|
|
20324
|
-
|
|
20325
|
-
return resolve3(env.HOME || homedir3());
|
|
20326
|
-
}
|
|
20327
|
-
function runtimePath(value, cwd) {
|
|
20328
|
-
return resolve3(cwd, value);
|
|
20329
|
-
}
|
|
20330
|
-
function versionFile(env = process.env, cwd = process.cwd()) {
|
|
20331
|
-
return env.SUPERTASK_VERSION_FILE ? runtimePath(env.SUPERTASK_VERSION_FILE, cwd) : join5(runtimeHome(env), ".local/share/opencode/supertask-gateway-version");
|
|
20332
|
-
}
|
|
20333
|
-
function getRunningVersion(env = process.env, cwd = process.cwd()) {
|
|
20334
|
-
try {
|
|
20335
|
-
const path = versionFile(env, cwd);
|
|
20336
|
-
if (!existsSync6(path)) return null;
|
|
20337
|
-
return readFileSync3(path, "utf-8").trim() || null;
|
|
20338
|
-
} catch {
|
|
20339
|
-
return null;
|
|
20340
|
-
}
|
|
20341
|
-
}
|
|
20342
|
-
function packageVersionFromGatewayEntry(gatewayEntry) {
|
|
20343
|
-
if (gatewayEntry === null) return null;
|
|
20344
|
-
let directory = dirname6(gatewayEntry);
|
|
20345
|
-
for (let depth = 0; depth < 6; depth += 1) {
|
|
20346
|
-
const packagePath = join5(directory, "package.json");
|
|
20347
|
-
if (existsSync6(packagePath)) {
|
|
20348
|
-
try {
|
|
20349
|
-
const pkg = JSON.parse(readFileSync3(packagePath, "utf8"));
|
|
20350
|
-
if (pkg.name === "opencode-supertask" && typeof pkg.version === "string") {
|
|
20351
|
-
return pkg.version;
|
|
20352
|
-
}
|
|
20353
|
-
} catch {
|
|
20354
|
-
}
|
|
20355
|
-
}
|
|
20356
|
-
const parent = dirname6(directory);
|
|
20357
|
-
if (parent === directory) break;
|
|
20358
|
-
directory = parent;
|
|
20359
|
-
}
|
|
20360
|
-
return null;
|
|
20361
|
-
}
|
|
20362
|
-
function resolveRuntimeExecutable(command, env, cwd) {
|
|
20363
|
-
if (isAbsolute2(command) || command.includes("/")) return runtimePath(command, cwd);
|
|
20364
|
-
for (const entry of (env.PATH ?? "").split(delimiter)) {
|
|
20365
|
-
if (!entry) continue;
|
|
20366
|
-
const candidate = resolve3(cwd, entry, command);
|
|
20366
|
+
function killDiagnosticGroup(pid) {
|
|
20367
|
+
if (process.platform !== "win32") {
|
|
20367
20368
|
try {
|
|
20368
|
-
|
|
20369
|
-
return
|
|
20369
|
+
process.kill(-pid, "SIGKILL");
|
|
20370
|
+
return;
|
|
20370
20371
|
} catch {
|
|
20371
20372
|
}
|
|
20372
20373
|
}
|
|
20373
|
-
return command;
|
|
20374
|
-
}
|
|
20375
|
-
function runtimeScope(runtime) {
|
|
20376
|
-
const { cwd, env } = runtime;
|
|
20377
|
-
const home = runtimeHome(env);
|
|
20378
|
-
return {
|
|
20379
|
-
cwd: resolve3(cwd),
|
|
20380
|
-
databasePath: env.SUPERTASK_DB_PATH ? runtimePath(env.SUPERTASK_DB_PATH, cwd) : join5(home, ".local/share/opencode/tasks.db"),
|
|
20381
|
-
configPath: env.SUPERTASK_CONFIG_PATH ? runtimePath(env.SUPERTASK_CONFIG_PATH, cwd) : join5(home, ".config/opencode/supertask.json"),
|
|
20382
|
-
opencodePath: resolveRuntimeExecutable(env.SUPERTASK_OPENCODE_BIN ?? "opencode", env, cwd),
|
|
20383
|
-
home,
|
|
20384
|
-
pm2Home: env.PM2_HOME ? runtimePath(env.PM2_HOME, cwd) : join5(home, ".pm2"),
|
|
20385
|
-
managementLockPath: canonicalManagementLockPath(env, cwd)
|
|
20386
|
-
};
|
|
20387
|
-
}
|
|
20388
|
-
function scopesMatch(left, right) {
|
|
20389
|
-
return left.databasePath === right.databasePath && left.configPath === right.configPath && left.opencodePath === right.opencodePath && left.home === right.home && left.pm2Home === right.pm2Home && left.managementLockPath === right.managementLockPath;
|
|
20390
|
-
}
|
|
20391
|
-
function currentScope() {
|
|
20392
|
-
return runtimeScope({ cwd: process.cwd(), env: process.env });
|
|
20393
|
-
}
|
|
20394
|
-
function diagnoseOpenCodeRuntime(runtime = {
|
|
20395
|
-
cwd: process.cwd(),
|
|
20396
|
-
env: process.env
|
|
20397
|
-
}) {
|
|
20398
|
-
const executable = runtimeScope(runtime).opencodePath;
|
|
20399
|
-
const result = spawnSync2(executable, ["--version"], {
|
|
20400
|
-
cwd: runtime.cwd,
|
|
20401
|
-
env: runtime.env,
|
|
20402
|
-
encoding: "utf8",
|
|
20403
|
-
timeout: 1e4,
|
|
20404
|
-
killSignal: "SIGKILL"
|
|
20405
|
-
});
|
|
20406
|
-
const ok = result.status === 0 && result.error === void 0;
|
|
20407
|
-
return {
|
|
20408
|
-
ok,
|
|
20409
|
-
executable,
|
|
20410
|
-
version: ok ? result.stdout.trim() || result.stderr.trim() || null : null,
|
|
20411
|
-
error: ok ? null : result.error?.message || result.stderr.trim() || result.stdout.trim() || `exit code ${result.status}`
|
|
20412
|
-
};
|
|
20413
|
-
}
|
|
20414
|
-
function getGatewayDiagnostic(options = {}) {
|
|
20415
|
-
const producerScope = currentScope();
|
|
20416
|
-
if (!isPm2Installed()) {
|
|
20417
|
-
return {
|
|
20418
|
-
pm2Installed: false,
|
|
20419
|
-
processFound: false,
|
|
20420
|
-
status: null,
|
|
20421
|
-
pid: null,
|
|
20422
|
-
ready: false,
|
|
20423
|
-
runningVersion: getRunningVersion(),
|
|
20424
|
-
gatewayEntry: null,
|
|
20425
|
-
gatewayPackageVersion: null,
|
|
20426
|
-
logRotationInstalled: false,
|
|
20427
|
-
startupConfigured: process.platform === "darwin" || process.platform === "linux" ? false : null,
|
|
20428
|
-
currentScope: producerScope,
|
|
20429
|
-
gatewayScope: null,
|
|
20430
|
-
scopeMatches: false,
|
|
20431
|
-
gatewayOpenCode: null
|
|
20432
|
-
};
|
|
20433
|
-
}
|
|
20434
|
-
const processes = pm2JsonList();
|
|
20435
|
-
const gateway = processes.find((item) => item.name === PROCESS_NAME);
|
|
20436
|
-
const runtime = gatewayRuntimeFromProcess(gateway);
|
|
20437
|
-
const gatewayEnv = runtime?.env ?? process.env;
|
|
20438
|
-
const managedScope = runtime ? runtimeScope(runtime) : null;
|
|
20439
|
-
const pid = typeof gateway?.pid === "number" ? gateway.pid : null;
|
|
20440
|
-
const readyPath = managedScope?.databasePath ?? databasePath();
|
|
20441
|
-
const lockedVersion = pid == null ? void 0 : gatewayVersionFromLock(pid, readyPath);
|
|
20442
|
-
const gatewayEntry = runtime?.gatewayEntry ?? null;
|
|
20443
|
-
return {
|
|
20444
|
-
pm2Installed: true,
|
|
20445
|
-
processFound: gateway != null,
|
|
20446
|
-
status: gateway?.pm2_env?.status ?? null,
|
|
20447
|
-
pid,
|
|
20448
|
-
ready: pid != null && isGatewayReady(pid, readyPath),
|
|
20449
|
-
runningVersion: lockedVersion === void 0 ? getRunningVersion(gatewayEnv, runtime?.cwd) : lockedVersion,
|
|
20450
|
-
gatewayEntry,
|
|
20451
|
-
gatewayPackageVersion: packageVersionFromGatewayEntry(gatewayEntry),
|
|
20452
|
-
logRotationInstalled: processes.some((item) => item.name === "pm2-logrotate" && item.pm2_env?.status === "online"),
|
|
20453
|
-
startupConfigured: process.platform === "darwin" ? isMacLaunchAgentConfigured(
|
|
20454
|
-
gatewayEnv.PM2_HOME ?? join5(runtimeHome(gatewayEnv), ".pm2"),
|
|
20455
|
-
runtime ?? void 0
|
|
20456
|
-
) : process.platform === "linux" ? isLinuxStartupConfigured(gatewayEnv, runtime?.cwd, runtime ?? void 0) : null,
|
|
20457
|
-
currentScope: producerScope,
|
|
20458
|
-
gatewayScope: managedScope,
|
|
20459
|
-
scopeMatches: managedScope != null && scopesMatch(producerScope, managedScope),
|
|
20460
|
-
gatewayOpenCode: runtime === null || !options.probeOpenCode ? null : diagnoseOpenCodeRuntime(runtime)
|
|
20461
|
-
};
|
|
20462
|
-
}
|
|
20463
|
-
function pm2Bin(env = process.env) {
|
|
20464
|
-
return env.SUPERTASK_PM2_BIN ?? (process.platform === "win32" ? "pm2.cmd" : "pm2");
|
|
20465
|
-
}
|
|
20466
|
-
function pm2CommandTimeoutMs(env = process.env) {
|
|
20467
|
-
const configured = Number(env.SUPERTASK_PM2_COMMAND_TIMEOUT_MS ?? 15e3);
|
|
20468
|
-
return Number.isFinite(configured) && configured > 0 ? configured : 15e3;
|
|
20469
|
-
}
|
|
20470
|
-
function resolvePm2Bin() {
|
|
20471
|
-
const configured = pm2Bin();
|
|
20472
|
-
if (isAbsolute2(configured)) return configured;
|
|
20473
|
-
const result = spawnSync2("which", [configured], { encoding: "utf8" });
|
|
20474
|
-
const resolved = result.status === 0 ? result.stdout.trim().split("\n")[0] : "";
|
|
20475
|
-
if (!resolved) throw new Error(`[supertask] \u65E0\u6CD5\u89E3\u6790 pm2 \u53EF\u6267\u884C\u6587\u4EF6: ${configured}`);
|
|
20476
|
-
return resolved;
|
|
20477
|
-
}
|
|
20478
|
-
function launchAgentPath() {
|
|
20479
|
-
return process.env.SUPERTASK_LAUNCH_AGENT_PATH ?? join5(runtimeHome(process.env), "Library/LaunchAgents", `${MAC_LAUNCH_AGENT_LABEL}.plist`);
|
|
20480
|
-
}
|
|
20481
|
-
function launchctlBin() {
|
|
20482
|
-
return process.env.SUPERTASK_LAUNCHCTL_BIN ?? "launchctl";
|
|
20483
|
-
}
|
|
20484
|
-
function xmlUnescape(value) {
|
|
20485
|
-
return value.replaceAll("'", "'").replaceAll(""", '"').replaceAll(">", ">").replaceAll("<", "<").replaceAll("&", "&");
|
|
20486
|
-
}
|
|
20487
|
-
function plistValue(contents, key) {
|
|
20488
|
-
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20489
|
-
const match2 = contents.match(new RegExp(`<key>\\s*${escapedKey}\\s*</key>\\s*<string>([^<]*)</string>`));
|
|
20490
|
-
return match2?.[1] ? xmlUnescape(match2[1]) : null;
|
|
20491
|
-
}
|
|
20492
|
-
function isMacLaunchAgentConfigured(expectedPm2Home, expectedRuntime) {
|
|
20493
|
-
if (typeof process.getuid !== "function") return false;
|
|
20494
|
-
const plistPath = launchAgentPath();
|
|
20495
|
-
if (!existsSync6(plistPath)) return false;
|
|
20496
20374
|
try {
|
|
20497
|
-
|
|
20498
|
-
const argumentsBlock = contents.match(
|
|
20499
|
-
/<key>\s*ProgramArguments\s*<\/key>\s*<array>([\s\S]*?)<\/array>/
|
|
20500
|
-
)?.[1];
|
|
20501
|
-
const programArguments = argumentsBlock ? [...argumentsBlock.matchAll(/<string>([^<]*)<\/string>/g)].map((match2) => xmlUnescape(match2[1])) : [];
|
|
20502
|
-
const bunPath = programArguments[0];
|
|
20503
|
-
const supervisorEntry = programArguments[1];
|
|
20504
|
-
const pm2Path = programArguments[2];
|
|
20505
|
-
const pm2Home = plistValue(contents, "PM2_HOME");
|
|
20506
|
-
const managementLock = plistValue(contents, "SUPERTASK_PM2_MANAGEMENT_LOCK");
|
|
20507
|
-
if (!bunPath || !supervisorEntry || !pm2Path || !pm2Home || !managementLock) return false;
|
|
20508
|
-
if (expectedPm2Home && pm2Home !== expectedPm2Home) return false;
|
|
20509
|
-
const expectedManagementLock = expectedRuntime ? managementLockPath(expectedRuntime.env, expectedRuntime.cwd) : process.env.SUPERTASK_PM2_MANAGEMENT_LOCK ? managementLockPath() : join5(expectedPm2Home ?? currentScope().pm2Home, "supertask-gateway.manage.sqlite");
|
|
20510
|
-
if (managementLock !== expectedManagementLock) return false;
|
|
20511
|
-
accessSync(bunPath, constants.X_OK);
|
|
20512
|
-
accessSync(supervisorEntry, constants.R_OK);
|
|
20513
|
-
accessSync(pm2Path, constants.X_OK);
|
|
20514
|
-
if (spawnSync2(bunPath, ["--version"], {
|
|
20515
|
-
stdio: "ignore",
|
|
20516
|
-
timeout: pm2CommandTimeoutMs(),
|
|
20517
|
-
killSignal: "SIGKILL"
|
|
20518
|
-
}).status !== 0) return false;
|
|
20519
|
-
if (spawnSync2(pm2Path, ["--version"], {
|
|
20520
|
-
stdio: "ignore",
|
|
20521
|
-
env: { ...process.env, PM2_HOME: pm2Home },
|
|
20522
|
-
timeout: pm2CommandTimeoutMs(),
|
|
20523
|
-
killSignal: "SIGKILL"
|
|
20524
|
-
}).status !== 0) return false;
|
|
20525
|
-
const loaded = spawnSync2(
|
|
20526
|
-
launchctlBin(),
|
|
20527
|
-
["print", `gui/${process.getuid()}/${MAC_LAUNCH_AGENT_LABEL}`],
|
|
20528
|
-
{
|
|
20529
|
-
encoding: "utf8",
|
|
20530
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
20531
|
-
timeout: pm2CommandTimeoutMs(),
|
|
20532
|
-
killSignal: "SIGKILL"
|
|
20533
|
-
}
|
|
20534
|
-
);
|
|
20535
|
-
if (loaded.status !== 0) return false;
|
|
20536
|
-
if (!loaded.stdout.includes("state = running")) return false;
|
|
20537
|
-
if (!loaded.stdout.includes(`path = ${plistPath}`)) return false;
|
|
20538
|
-
if (!loaded.stdout.includes(`program = ${bunPath}`)) return false;
|
|
20539
|
-
const dumpPath = join5(pm2Home, "dump.pm2");
|
|
20540
|
-
const dump = JSON.parse(readFileSync3(dumpPath, "utf8"));
|
|
20541
|
-
if (!Array.isArray(dump)) return false;
|
|
20542
|
-
const gateway = dump.find((item) => item.name === PROCESS_NAME);
|
|
20543
|
-
const dumpRuntime = gatewayRuntimeFromProcess(gateway);
|
|
20544
|
-
if (!dumpRuntime || !hasRestorableSavedGatewayRuntime(gateway)) return false;
|
|
20545
|
-
return expectedRuntime === void 0 || dumpRuntime.gatewayEntry === expectedRuntime.gatewayEntry && dumpRuntime.bunPath === expectedRuntime.bunPath && dumpRuntime.cwd === expectedRuntime.cwd && scopesMatch(runtimeScope(dumpRuntime), runtimeScope(expectedRuntime));
|
|
20375
|
+
process.kill(pid, "SIGKILL");
|
|
20546
20376
|
} catch {
|
|
20547
|
-
return false;
|
|
20548
20377
|
}
|
|
20549
20378
|
}
|
|
20550
|
-
function
|
|
20551
|
-
|
|
20379
|
+
function diagnosticEntry() {
|
|
20380
|
+
const extension = import.meta.url.endsWith(".ts") ? "ts" : "js";
|
|
20381
|
+
const moduleDir = dirname6(fileURLToPath5(import.meta.url));
|
|
20382
|
+
const candidates = [
|
|
20383
|
+
join5(moduleDir, `../daemon/gateway-diagnostic-runner.${extension}`),
|
|
20384
|
+
join5(moduleDir, `../../src/daemon/gateway-diagnostic-runner.${extension}`)
|
|
20385
|
+
];
|
|
20386
|
+
const entry = candidates.find((candidate) => existsSync6(candidate));
|
|
20387
|
+
if (!entry) throw new Error(`Gateway diagnostic runner \u4E0D\u5B58\u5728\uFF1A${candidates.join(", ")}`);
|
|
20388
|
+
return entry;
|
|
20552
20389
|
}
|
|
20553
|
-
function
|
|
20554
|
-
|
|
20555
|
-
|
|
20556
|
-
|
|
20557
|
-
|
|
20558
|
-
|
|
20559
|
-
|
|
20560
|
-
|
|
20561
|
-
|
|
20562
|
-
|
|
20563
|
-
|
|
20564
|
-
|
|
20565
|
-
|
|
20566
|
-
|
|
20567
|
-
|
|
20568
|
-
|
|
20390
|
+
async function runGatewayDiagnostic() {
|
|
20391
|
+
return new Promise((resolve3, reject) => {
|
|
20392
|
+
const child = spawn3(process.execPath, [diagnosticEntry()], {
|
|
20393
|
+
cwd: process.cwd(),
|
|
20394
|
+
env: process.env,
|
|
20395
|
+
detached: process.platform !== "win32",
|
|
20396
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
20397
|
+
});
|
|
20398
|
+
let stdout = "";
|
|
20399
|
+
let stderr = "";
|
|
20400
|
+
let timedOut = false;
|
|
20401
|
+
if (child.pid) activeProcessGroups.add(child.pid);
|
|
20402
|
+
child.stdout?.on("data", (chunk) => {
|
|
20403
|
+
stdout += chunk.toString();
|
|
20404
|
+
});
|
|
20405
|
+
child.stderr?.on("data", (chunk) => {
|
|
20406
|
+
stderr += chunk.toString();
|
|
20407
|
+
});
|
|
20408
|
+
child.once("error", reject);
|
|
20409
|
+
child.once("close", (exitCode) => {
|
|
20410
|
+
clearTimeout(timer);
|
|
20411
|
+
if (child.pid) {
|
|
20412
|
+
killDiagnosticGroup(child.pid);
|
|
20413
|
+
activeProcessGroups.delete(child.pid);
|
|
20414
|
+
}
|
|
20415
|
+
if (timedOut) {
|
|
20416
|
+
reject(new Error(`Gateway diagnostic runner \u8D85\u8FC7 ${DIAGNOSTIC_TIMEOUT_MS}ms \u672A\u5B8C\u6210`));
|
|
20417
|
+
return;
|
|
20418
|
+
}
|
|
20419
|
+
if (exitCode !== 0) {
|
|
20420
|
+
reject(new Error(stderr.trim() || `Gateway diagnostic runner \u9000\u51FA\u7801 ${exitCode}`));
|
|
20421
|
+
return;
|
|
20422
|
+
}
|
|
20423
|
+
try {
|
|
20424
|
+
resolve3(JSON.parse(stdout));
|
|
20425
|
+
} catch (error) {
|
|
20426
|
+
reject(error);
|
|
20427
|
+
}
|
|
20428
|
+
});
|
|
20429
|
+
const timer = setTimeout(() => {
|
|
20430
|
+
timedOut = true;
|
|
20431
|
+
if (child.pid) killDiagnosticGroup(child.pid);
|
|
20432
|
+
else child.kill("SIGKILL");
|
|
20433
|
+
}, DIAGNOSTIC_TIMEOUT_MS);
|
|
20569
20434
|
});
|
|
20570
|
-
if (contents.status !== 0) return false;
|
|
20571
|
-
const expectedPm2Home = env.PM2_HOME ?? join5(runtimeHome(env), ".pm2");
|
|
20572
|
-
if (!contents.stdout.includes("resurrect") || !contents.stdout.includes(expectedPm2Home)) {
|
|
20573
|
-
return false;
|
|
20574
|
-
}
|
|
20575
|
-
try {
|
|
20576
|
-
const dump = JSON.parse(readFileSync3(join5(expectedPm2Home, "dump.pm2"), "utf8"));
|
|
20577
|
-
if (!Array.isArray(dump)) return false;
|
|
20578
|
-
const gateway = dump.find((item) => item.name === PROCESS_NAME);
|
|
20579
|
-
const runtime = gatewayRuntimeFromProcess(gateway);
|
|
20580
|
-
if (!runtime || !hasRestorableSavedGatewayRuntime(gateway)) return false;
|
|
20581
|
-
return runtime.cwd === resolve3(cwd) && scopesMatch(runtimeScope(runtime), runtimeScope({ cwd, env })) && (expectedRuntime === void 0 || runtime.gatewayEntry === expectedRuntime.gatewayEntry && runtime.bunPath === expectedRuntime.bunPath);
|
|
20582
|
-
} catch {
|
|
20583
|
-
return false;
|
|
20584
|
-
}
|
|
20585
20435
|
}
|
|
20586
|
-
function
|
|
20587
|
-
|
|
20588
|
-
|
|
20589
|
-
|
|
20590
|
-
|
|
20591
|
-
|
|
20592
|
-
killSignal: "SIGKILL"
|
|
20436
|
+
async function getDashboardGatewayDiagnostic(options = {}) {
|
|
20437
|
+
if (!options.fresh && cached && cached.expiresAt > Date.now()) return cached.diagnostic;
|
|
20438
|
+
if (!options.fresh && pending) return pending;
|
|
20439
|
+
const operation = runGatewayDiagnostic().then((diagnostic) => {
|
|
20440
|
+
cached = { expiresAt: Date.now() + CACHE_TTL_MS, diagnostic };
|
|
20441
|
+
return diagnostic;
|
|
20593
20442
|
});
|
|
20594
|
-
|
|
20595
|
-
|
|
20596
|
-
|
|
20597
|
-
const lookup = process.platform === "win32" ? "where" : "which";
|
|
20598
|
-
const result = spawnSync2(lookup, [command], { encoding: "utf8" });
|
|
20599
|
-
return result.status === 0 ? result.stdout.trim().split("\n")[0] || null : null;
|
|
20600
|
-
}
|
|
20601
|
-
function npmPreferredEnvironment() {
|
|
20602
|
-
if (process.platform === "win32") return null;
|
|
20603
|
-
const npm = resolveCommand("npm");
|
|
20604
|
-
const node = resolveCommand("node");
|
|
20605
|
-
if (!npm || !node) return null;
|
|
20606
|
-
const command = resolvePm2Bin();
|
|
20607
|
-
const path = [.../* @__PURE__ */ new Set([
|
|
20608
|
-
dirname6(command),
|
|
20609
|
-
dirname6(npm),
|
|
20610
|
-
dirname6(node),
|
|
20611
|
-
"/usr/local/bin",
|
|
20612
|
-
"/usr/bin",
|
|
20613
|
-
"/bin"
|
|
20614
|
-
])].join(delimiter);
|
|
20615
|
-
return { command, env: { ...process.env, PATH: path } };
|
|
20616
|
-
}
|
|
20617
|
-
function pm2Exec(args, options = {}) {
|
|
20618
|
-
const npmEnvironment = options.preferNpm ? npmPreferredEnvironment() : null;
|
|
20619
|
-
const effectiveEnv = options.env ?? npmEnvironment?.env ?? process.env;
|
|
20620
|
-
const result = spawnSync2(npmEnvironment?.command ?? pm2Bin(effectiveEnv), args, {
|
|
20621
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
20622
|
-
encoding: "utf-8",
|
|
20623
|
-
env: effectiveEnv,
|
|
20624
|
-
shell: process.platform === "win32",
|
|
20625
|
-
timeout: options.timeoutMs ?? pm2CommandTimeoutMs(effectiveEnv),
|
|
20626
|
-
killSignal: "SIGKILL"
|
|
20443
|
+
if (options.fresh) return operation;
|
|
20444
|
+
pending = operation.finally(() => {
|
|
20445
|
+
pending = null;
|
|
20627
20446
|
});
|
|
20628
|
-
|
|
20629
|
-
if (result.error) return { ok: false, output: result.error.message };
|
|
20630
|
-
return { ok: result.status === 0, output };
|
|
20631
|
-
}
|
|
20632
|
-
function requirePm2(args, action, options = {}) {
|
|
20633
|
-
const result = pm2Exec(args, options);
|
|
20634
|
-
if (!result.ok) throw new Error(`[supertask] ${action} failed: ${result.output || "unknown pm2 error"}`);
|
|
20635
|
-
return result.output;
|
|
20636
|
-
}
|
|
20637
|
-
function pm2JsonList(env = process.env) {
|
|
20638
|
-
const output = requirePm2(["jlist"], "pm2 jlist", { env });
|
|
20639
|
-
try {
|
|
20640
|
-
const parsed = JSON.parse(output);
|
|
20641
|
-
if (!Array.isArray(parsed)) throw new Error("result is not an array");
|
|
20642
|
-
return parsed;
|
|
20643
|
-
} catch (error) {
|
|
20644
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
20645
|
-
throw new Error(`[supertask] Invalid pm2 jlist output: ${message}`);
|
|
20646
|
-
}
|
|
20647
|
-
}
|
|
20648
|
-
function gatewayEntryFromProcess(processInfo) {
|
|
20649
|
-
const args = processInfo?.pm2_env?.args ?? processInfo?.args;
|
|
20650
|
-
const candidates = Array.isArray(args) ? [...args].reverse() : typeof args === "string" ? [args] : [];
|
|
20651
|
-
const savedCwd = processInfo?.pm2_env?.pm_cwd ?? processInfo?.pm_cwd;
|
|
20652
|
-
for (const candidate of candidates) {
|
|
20653
|
-
const path = typeof savedCwd === "string" ? runtimePath(candidate, savedCwd) : candidate;
|
|
20654
|
-
if (existsSync6(path)) return resolve3(path);
|
|
20655
|
-
}
|
|
20656
|
-
return null;
|
|
20657
|
-
}
|
|
20658
|
-
function gatewayEnvironmentFromProcess(processInfo) {
|
|
20659
|
-
const saved = processInfo?.pm2_env?.env ?? processInfo?.env;
|
|
20660
|
-
if (!saved) return { ...process.env };
|
|
20661
|
-
const env = {};
|
|
20662
|
-
for (const [key, value] of Object.entries(saved)) {
|
|
20663
|
-
if (typeof value === "string") env[key] = value;
|
|
20664
|
-
}
|
|
20665
|
-
return env;
|
|
20666
|
-
}
|
|
20667
|
-
function gatewayRuntimeFromProcess(processInfo) {
|
|
20668
|
-
const gatewayEntry = gatewayEntryFromProcess(processInfo);
|
|
20669
|
-
if (!gatewayEntry) return null;
|
|
20670
|
-
const savedBunPath = processInfo?.pm2_env?.pm_exec_path ?? processInfo?.pm_exec_path;
|
|
20671
|
-
const savedCwd = processInfo?.pm2_env?.pm_cwd ?? processInfo?.pm_cwd;
|
|
20672
|
-
const savedEnv = gatewayEnvironmentFromProcess(processInfo);
|
|
20673
|
-
if (typeof savedBunPath !== "string" || typeof savedCwd !== "string") return null;
|
|
20674
|
-
try {
|
|
20675
|
-
accessSync(savedBunPath, constants.X_OK);
|
|
20676
|
-
if (!statSync3(savedCwd).isDirectory()) return null;
|
|
20677
|
-
if (spawnSync2(savedBunPath, ["--version"], {
|
|
20678
|
-
stdio: "ignore",
|
|
20679
|
-
env: savedEnv,
|
|
20680
|
-
timeout: pm2CommandTimeoutMs(savedEnv),
|
|
20681
|
-
killSignal: "SIGKILL"
|
|
20682
|
-
}).status !== 0) return null;
|
|
20683
|
-
} catch {
|
|
20684
|
-
return null;
|
|
20685
|
-
}
|
|
20686
|
-
const killTimeout = processInfo?.pm2_env?.kill_timeout ?? processInfo?.kill_timeout;
|
|
20687
|
-
return {
|
|
20688
|
-
gatewayEntry,
|
|
20689
|
-
bunPath: savedBunPath,
|
|
20690
|
-
cwd: resolve3(savedCwd),
|
|
20691
|
-
env: savedEnv,
|
|
20692
|
-
killTimeoutMs: Number.isInteger(killTimeout) && killTimeout >= 5e3 ? killTimeout : void 0
|
|
20693
|
-
};
|
|
20694
|
-
}
|
|
20695
|
-
function hasRestorableSavedGatewayRuntime(processInfo) {
|
|
20696
|
-
return gatewayRuntimeFromProcess(processInfo) !== null;
|
|
20697
|
-
}
|
|
20698
|
-
function databasePath(env = process.env, cwd = process.cwd()) {
|
|
20699
|
-
return env.SUPERTASK_DB_PATH ? runtimePath(env.SUPERTASK_DB_PATH, cwd) : join5(runtimeHome(env), ".local/share/opencode/tasks.db");
|
|
20700
|
-
}
|
|
20701
|
-
function isGatewayReady(expectedPid, path = databasePath()) {
|
|
20702
|
-
if (!existsSync6(path)) return false;
|
|
20703
|
-
let database = null;
|
|
20704
|
-
try {
|
|
20705
|
-
database = new Database5(path, { readonly: true });
|
|
20706
|
-
const row = database.query(
|
|
20707
|
-
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
20708
|
-
).get();
|
|
20709
|
-
if (!row || row.ready_at == null) return false;
|
|
20710
|
-
if (expectedPid !== void 0 && row.pid !== expectedPid) return false;
|
|
20711
|
-
const ageMs = Date.now() - row.heartbeat_at;
|
|
20712
|
-
return ageMs >= -5e3 && ageMs < GATEWAY_LOCK_STALE_MS;
|
|
20713
|
-
} catch {
|
|
20714
|
-
return false;
|
|
20715
|
-
} finally {
|
|
20716
|
-
database?.close();
|
|
20717
|
-
}
|
|
20718
|
-
}
|
|
20719
|
-
function gatewayVersionFromLock(expectedPid, path) {
|
|
20720
|
-
if (!existsSync6(path)) return void 0;
|
|
20721
|
-
let database = null;
|
|
20722
|
-
try {
|
|
20723
|
-
database = new Database5(path, { readonly: true });
|
|
20724
|
-
const row = database.query(
|
|
20725
|
-
"SELECT pid, version FROM gateway_lock WHERE id = 1"
|
|
20726
|
-
).get();
|
|
20727
|
-
if (!row || row.pid !== expectedPid) return void 0;
|
|
20728
|
-
return row.version;
|
|
20729
|
-
} catch {
|
|
20730
|
-
return void 0;
|
|
20731
|
-
} finally {
|
|
20732
|
-
database?.close();
|
|
20733
|
-
}
|
|
20447
|
+
return pending;
|
|
20734
20448
|
}
|
|
20735
|
-
|
|
20736
|
-
|
|
20737
|
-
|
|
20738
|
-
return join5(runtimeScope({ env, cwd }).pm2Home, "supertask-gateway.manage.sqlite");
|
|
20739
|
-
}
|
|
20740
|
-
function canonicalManagementLockPath(env = process.env, cwd = process.cwd()) {
|
|
20741
|
-
const home = runtimeHome(env);
|
|
20742
|
-
const pm2Home = env.PM2_HOME ? runtimePath(env.PM2_HOME, cwd) : join5(home, ".pm2");
|
|
20743
|
-
return join5(pm2Home, "supertask-gateway.manage.sqlite");
|
|
20744
|
-
}
|
|
20745
|
-
var __dirname, PROCESS_NAME, MAC_LAUNCH_AGENT_LABEL, GATEWAY_LOCK_STALE_MS;
|
|
20746
|
-
var init_pm2 = __esm({
|
|
20747
|
-
"src/daemon/pm2.ts"() {
|
|
20449
|
+
var CACHE_TTL_MS, DIAGNOSTIC_TIMEOUT_MS, cached, pending, activeProcessGroups;
|
|
20450
|
+
var init_gateway_diagnostic = __esm({
|
|
20451
|
+
"src/web/gateway-diagnostic.ts"() {
|
|
20748
20452
|
"use strict";
|
|
20749
|
-
|
|
20750
|
-
|
|
20751
|
-
|
|
20752
|
-
|
|
20753
|
-
|
|
20754
|
-
|
|
20755
|
-
|
|
20756
|
-
|
|
20453
|
+
CACHE_TTL_MS = 5e3;
|
|
20454
|
+
DIAGNOSTIC_TIMEOUT_MS = 2e4;
|
|
20455
|
+
cached = null;
|
|
20456
|
+
pending = null;
|
|
20457
|
+
activeProcessGroups = /* @__PURE__ */ new Set();
|
|
20458
|
+
process.once("exit", () => {
|
|
20459
|
+
for (const pid of activeProcessGroups) killDiagnosticGroup(pid);
|
|
20460
|
+
});
|
|
20757
20461
|
}
|
|
20758
20462
|
});
|
|
20759
20463
|
|
|
@@ -21422,7 +21126,7 @@ var init_ui = __esm({
|
|
|
21422
21126
|
"details.enabledYes": "\u5DF2\u542F\u7528",
|
|
21423
21127
|
"details.enabledNo": "\u5DF2\u505C\u7528",
|
|
21424
21128
|
"dialog.cancelTask": "\u53D6\u6D88\u4EFB\u52A1 #{id}\uFF1F",
|
|
21425
|
-
"dialog.cancelTaskBody": "\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u4F1A\u5728\u4E0B\u4E00\u4E2A\u8F6E\u8BE2\u5468\u671F\u7EC8\u6B62\u5BF9\u5E94\u8FDB\u7A0B\
|
|
21129
|
+
"dialog.cancelTaskBody": "\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u4F1A\u5728\u4E0B\u4E00\u4E2A\u8F6E\u8BE2\u5468\u671F\u7EC8\u6B62\u5BF9\u5E94\u7684\u53D7\u7BA1\u8FDB\u7A0B\u7EC4\u3002",
|
|
21426
21130
|
"dialog.retryTask": "\u91CD\u8BD5\u4EFB\u52A1 #{id}\uFF1F",
|
|
21427
21131
|
"dialog.retryTaskBody": "\u4EFB\u52A1\u5C06\u56DE\u5230\u5F85\u6267\u884C\u72B6\u6001\uFF0C\u5E76\u91CD\u7F6E\u81EA\u52A8\u91CD\u8BD5\u9884\u7B97\u3002",
|
|
21428
21132
|
"dialog.deleteTask": "\u5220\u9664\u4EFB\u52A1 #{id}\uFF1F",
|
|
@@ -21727,7 +21431,7 @@ var init_ui = __esm({
|
|
|
21727
21431
|
"details.enabledYes": "Enabled",
|
|
21728
21432
|
"details.enabledNo": "Disabled",
|
|
21729
21433
|
"dialog.cancelTask": "Cancel task #{id}?",
|
|
21730
|
-
"dialog.cancelTaskBody": "A running task will terminate its process
|
|
21434
|
+
"dialog.cancelTaskBody": "A running task will terminate its managed process group on the next worker poll.",
|
|
21731
21435
|
"dialog.retryTask": "Retry task #{id}?",
|
|
21732
21436
|
"dialog.retryTaskBody": "The task returns to pending and its automatic retry budget is reset.",
|
|
21733
21437
|
"dialog.deleteTask": "Delete task #{id}?",
|
|
@@ -22149,14 +21853,14 @@ __export(web_exports, {
|
|
|
22149
21853
|
});
|
|
22150
21854
|
import {
|
|
22151
21855
|
existsSync as existsSync7,
|
|
22152
|
-
mkdirSync as
|
|
22153
|
-
readFileSync as
|
|
21856
|
+
mkdirSync as mkdirSync3,
|
|
21857
|
+
readFileSync as readFileSync3,
|
|
22154
21858
|
readdirSync,
|
|
22155
21859
|
renameSync as renameSync2,
|
|
22156
|
-
statSync as
|
|
22157
|
-
writeFileSync as
|
|
21860
|
+
statSync as statSync3,
|
|
21861
|
+
writeFileSync as writeFileSync2
|
|
22158
21862
|
} from "fs";
|
|
22159
|
-
import { homedir as
|
|
21863
|
+
import { homedir as homedir3 } from "os";
|
|
22160
21864
|
import { basename as basename3, dirname as dirname7, join as join6 } from "path";
|
|
22161
21865
|
function setDashboardRuntimeConfig(config) {
|
|
22162
21866
|
runtimeConfig = config === null ? null : structuredClone(config);
|
|
@@ -22182,10 +21886,13 @@ function resolveDashboardConfigState(runtimeAvailable, restartRequired, managedR
|
|
|
22182
21886
|
function isSafeDashboardRestartTarget(diagnostic, currentPid) {
|
|
22183
21887
|
return diagnostic.pm2Installed && diagnostic.processFound && diagnostic.status === "online" && diagnostic.pid === currentPid && diagnostic.ready && diagnostic.scopeMatches;
|
|
22184
21888
|
}
|
|
22185
|
-
function canRestartCurrentGateway() {
|
|
21889
|
+
async function canRestartCurrentGateway(fresh = false) {
|
|
22186
21890
|
if (runtimeConfig === null) return false;
|
|
22187
21891
|
try {
|
|
22188
|
-
return isSafeDashboardRestartTarget(
|
|
21892
|
+
return isSafeDashboardRestartTarget(
|
|
21893
|
+
await getDashboardGatewayDiagnostic({ fresh }),
|
|
21894
|
+
process.pid
|
|
21895
|
+
);
|
|
22189
21896
|
} catch {
|
|
22190
21897
|
return false;
|
|
22191
21898
|
}
|
|
@@ -22204,7 +21911,7 @@ function listChildDirectories(path) {
|
|
|
22204
21911
|
if (entry.isDirectory()) return [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }];
|
|
22205
21912
|
if (!entry.isSymbolicLink()) return [];
|
|
22206
21913
|
try {
|
|
22207
|
-
return
|
|
21914
|
+
return statSync3(entryPath).isDirectory() ? [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }] : [];
|
|
22208
21915
|
} catch {
|
|
22209
21916
|
return [];
|
|
22210
21917
|
}
|
|
@@ -22442,7 +22149,7 @@ function readCurrentConfig() {
|
|
|
22442
22149
|
const configPath = getConfigPath();
|
|
22443
22150
|
if (!existsSync7(configPath)) return {};
|
|
22444
22151
|
try {
|
|
22445
|
-
return JSON.parse(
|
|
22152
|
+
return JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
22446
22153
|
} catch {
|
|
22447
22154
|
return {};
|
|
22448
22155
|
}
|
|
@@ -22450,9 +22157,9 @@ function readCurrentConfig() {
|
|
|
22450
22157
|
function writeConfig(cfg) {
|
|
22451
22158
|
const configPath = getConfigPath();
|
|
22452
22159
|
const dir = dirname7(configPath);
|
|
22453
|
-
if (!existsSync7(dir))
|
|
22160
|
+
if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
|
|
22454
22161
|
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
22455
|
-
|
|
22162
|
+
writeFileSync2(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
22456
22163
|
renameSync2(tempPath, configPath);
|
|
22457
22164
|
}
|
|
22458
22165
|
function statCard(value, label, tone, cardIcon, delay = "") {
|
|
@@ -22504,7 +22211,7 @@ function pagination(locale, basePath, page, pages, total, suffix = "") {
|
|
|
22504
22211
|
const next = page < pages ? `<a class="btn" href="${basePath}?page=${page + 1}${suffix}">${t(locale, "pagination.next")}${icon("chevronRight")}</a>` : "";
|
|
22505
22212
|
return `<div class="pagination">${previous}<span class="summary">${t(locale, "pagination.summary", { page, pages, total })}</span>${next}</div>`;
|
|
22506
22213
|
}
|
|
22507
|
-
var app, LEGACY_PROJECT_FILTER, TASK_STATUSES, SESSION_ID_PATTERN, runtimeConfig, restartScheduled, dashboardApp, web_default;
|
|
22214
|
+
var app, LEGACY_PROJECT_FILTER, TASK_STATUSES, SESSION_ID_PATTERN, LOOPBACK_HOST_PATTERN, runtimeConfig, restartScheduled, dashboardApp, web_default;
|
|
22508
22215
|
var init_web = __esm({
|
|
22509
22216
|
"src/web/index.tsx"() {
|
|
22510
22217
|
"use strict";
|
|
@@ -22521,7 +22228,7 @@ var init_web = __esm({
|
|
|
22521
22228
|
init_config();
|
|
22522
22229
|
init_health();
|
|
22523
22230
|
init_job_templates();
|
|
22524
|
-
|
|
22231
|
+
init_gateway_diagnostic();
|
|
22525
22232
|
init_ui();
|
|
22526
22233
|
app = new Hono2();
|
|
22527
22234
|
LEGACY_PROJECT_FILTER = "__supertask_legacy__";
|
|
@@ -22534,9 +22241,18 @@ var init_web = __esm({
|
|
|
22534
22241
|
"cancelled"
|
|
22535
22242
|
]);
|
|
22536
22243
|
SESSION_ID_PATTERN = /^ses_[A-Za-z0-9_]+$/;
|
|
22244
|
+
LOOPBACK_HOST_PATTERN = /^(?:localhost|127\.0\.0\.1|\[::1\])(?::([1-9]\d{0,4}))?$/i;
|
|
22537
22245
|
runtimeConfig = null;
|
|
22538
22246
|
restartScheduled = false;
|
|
22539
22247
|
app.use("*", async (c, next) => {
|
|
22248
|
+
const requestHostname = new URL(c.req.url).hostname;
|
|
22249
|
+
const hostHeader = c.req.header("Host");
|
|
22250
|
+
const loopbackHosts = ["localhost", "127.0.0.1", "[::1]"];
|
|
22251
|
+
const hostMatch = hostHeader?.match(LOOPBACK_HOST_PATTERN) ?? null;
|
|
22252
|
+
const hostPort = hostMatch?.[1] === void 0 ? null : Number(hostMatch[1]);
|
|
22253
|
+
if (!loopbackHosts.includes(requestHostname) || hostHeader !== void 0 && (!hostMatch || hostPort !== null && hostPort > 65535)) {
|
|
22254
|
+
return c.json({ error: "invalid dashboard host" }, 421);
|
|
22255
|
+
}
|
|
22540
22256
|
await next();
|
|
22541
22257
|
c.header("X-Content-Type-Options", "nosniff");
|
|
22542
22258
|
c.header("X-Frame-Options", "DENY");
|
|
@@ -22568,13 +22284,13 @@ var init_web = __esm({
|
|
|
22568
22284
|
return c.json(health, health.status === "ok" ? 200 : 503);
|
|
22569
22285
|
});
|
|
22570
22286
|
app.get("/api/filesystem/directories", (c) => {
|
|
22571
|
-
const requestedPath = c.req.query("path")?.trim() ||
|
|
22287
|
+
const requestedPath = c.req.query("path")?.trim() || homedir3();
|
|
22572
22288
|
try {
|
|
22573
22289
|
validateTaskWorkingDirectory(requestedPath);
|
|
22574
22290
|
return c.json({
|
|
22575
22291
|
path: requestedPath,
|
|
22576
22292
|
parent: dirname7(requestedPath),
|
|
22577
|
-
home:
|
|
22293
|
+
home: homedir3(),
|
|
22578
22294
|
directories: listChildDirectories(requestedPath)
|
|
22579
22295
|
});
|
|
22580
22296
|
} catch (error) {
|
|
@@ -22908,7 +22624,7 @@ var init_web = __esm({
|
|
|
22908
22624
|
const config = loadConfig();
|
|
22909
22625
|
const activeConfig = runtimeConfig ?? config;
|
|
22910
22626
|
const restartRequired = runtimeConfig !== null && !configsEqual(config, runtimeConfig);
|
|
22911
|
-
const managedRestart = canRestartCurrentGateway();
|
|
22627
|
+
const managedRestart = await canRestartCurrentGateway();
|
|
22912
22628
|
const configState = resolveDashboardConfigState(
|
|
22913
22629
|
runtimeConfig !== null,
|
|
22914
22630
|
restartRequired,
|
|
@@ -23028,9 +22744,9 @@ var init_web = __esm({
|
|
|
23028
22744
|
if (!isValidSessionId(run.sessionId)) return c.json({ error: "session unavailable" }, 409);
|
|
23029
22745
|
return c.json({ command: sessionCommand(run.sessionId) });
|
|
23030
22746
|
});
|
|
23031
|
-
app.get("/api/gateway/status", (c) => {
|
|
22747
|
+
app.get("/api/gateway/status", async (c) => {
|
|
23032
22748
|
const savedConfig = loadConfig();
|
|
23033
|
-
const managed = canRestartCurrentGateway();
|
|
22749
|
+
const managed = await canRestartCurrentGateway();
|
|
23034
22750
|
return c.json({
|
|
23035
22751
|
pid: process.pid,
|
|
23036
22752
|
managed,
|
|
@@ -23155,7 +22871,7 @@ var init_web = __esm({
|
|
|
23155
22871
|
return c.json({
|
|
23156
22872
|
success: true,
|
|
23157
22873
|
restartRequired: runtimeConfig === null || !configsEqual(savedConfig, runtimeConfig),
|
|
23158
|
-
managed: canRestartCurrentGateway()
|
|
22874
|
+
managed: await canRestartCurrentGateway()
|
|
23159
22875
|
});
|
|
23160
22876
|
} catch (error) {
|
|
23161
22877
|
return c.json({
|
|
@@ -23171,9 +22887,10 @@ var init_web = __esm({
|
|
|
23171
22887
|
return c.json({ error: "confirmation must be RESTART" }, 400);
|
|
23172
22888
|
}
|
|
23173
22889
|
if (restartScheduled) return c.json({ error: "restart already scheduled" }, 409);
|
|
23174
|
-
if (!canRestartCurrentGateway()) {
|
|
22890
|
+
if (!await canRestartCurrentGateway(true)) {
|
|
23175
22891
|
return c.json({ error: "\u5F53\u524D Gateway \u4E0D\u662F\u7531\u5339\u914D\u8FD0\u884C\u4F5C\u7528\u57DF\u7684 PM2 \u8FDB\u7A0B\u6258\u7BA1\uFF0C\u65E0\u6CD5\u4ECE\u7F51\u9875\u5B89\u5168\u91CD\u542F" }, 409);
|
|
23176
22892
|
}
|
|
22893
|
+
if (restartScheduled) return c.json({ error: "restart already scheduled" }, 409);
|
|
23177
22894
|
restartScheduled = true;
|
|
23178
22895
|
const previousPid = process.pid;
|
|
23179
22896
|
setTimeout(() => {
|
|
@@ -23237,7 +22954,7 @@ function runCommandContext(executable, args, cwd) {
|
|
|
23237
22954
|
function assertWorkerProcessIsolationSupported(platform = process.platform) {
|
|
23238
22955
|
if (platform === "win32") {
|
|
23239
22956
|
throw new Error(
|
|
23240
|
-
"Windows Worker \u5DF2\u5B89\u5168\u7981\u7528\uFF1A\u5F53\u524D\u8FD0\u884C\u65F6\u65E0\u6CD5\u7528 Job Object \
|
|
22957
|
+
"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"
|
|
23241
22958
|
);
|
|
23242
22959
|
}
|
|
23243
22960
|
}
|
|
@@ -23248,17 +22965,24 @@ var WorkerEngine = class {
|
|
|
23248
22965
|
pollTimer = null;
|
|
23249
22966
|
heartbeatTimer = null;
|
|
23250
22967
|
pollCyclePromise = null;
|
|
22968
|
+
shutdownDeadlineMs = null;
|
|
22969
|
+
settlementRetryWakeups = /* @__PURE__ */ new Set();
|
|
23251
22970
|
cfg;
|
|
23252
22971
|
opencodeBin;
|
|
23253
22972
|
maxOutputChars;
|
|
22973
|
+
settlementRetryDelaysMs;
|
|
22974
|
+
settlementRetryIntervalMs;
|
|
23254
22975
|
constructor(cfg, options = {}) {
|
|
23255
22976
|
this.cfg = cfg.worker;
|
|
23256
22977
|
this.opencodeBin = options.opencodeBin ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
23257
22978
|
this.maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
22979
|
+
this.settlementRetryDelaysMs = options.settlementRetryDelaysMs ?? [250, 1e3, 4e3];
|
|
22980
|
+
this.settlementRetryIntervalMs = options.settlementRetryIntervalMs ?? 5e3;
|
|
23258
22981
|
}
|
|
23259
22982
|
start() {
|
|
23260
22983
|
assertWorkerProcessIsolationSupported();
|
|
23261
22984
|
this.stopped = false;
|
|
22985
|
+
this.shutdownDeadlineMs = null;
|
|
23262
22986
|
markGatewayActivity("worker");
|
|
23263
22987
|
this.poll();
|
|
23264
22988
|
this.heartbeatTimer = setInterval(() => {
|
|
@@ -23267,6 +22991,8 @@ var WorkerEngine = class {
|
|
|
23267
22991
|
}
|
|
23268
22992
|
async stop(gracePeriodMs = 0) {
|
|
23269
22993
|
this.stopped = true;
|
|
22994
|
+
this.shutdownDeadlineMs = Date.now() + Math.max(0, gracePeriodMs);
|
|
22995
|
+
for (const wake of [...this.settlementRetryWakeups]) wake();
|
|
23270
22996
|
if (this.pollTimer) {
|
|
23271
22997
|
clearTimeout(this.pollTimer);
|
|
23272
22998
|
this.pollTimer = null;
|
|
@@ -23336,21 +23062,18 @@ var WorkerEngine = class {
|
|
|
23336
23062
|
if (databaseRunningCount >= this.cfg.maxConcurrency) break;
|
|
23337
23063
|
let task;
|
|
23338
23064
|
try {
|
|
23339
|
-
task = await TaskService.
|
|
23065
|
+
task = await TaskService.claimNext({ excludedBatchIds: [...this.activeBatchIds] });
|
|
23340
23066
|
} catch (err) {
|
|
23341
23067
|
this.logError("task claim failed", err);
|
|
23342
23068
|
throw err;
|
|
23343
23069
|
}
|
|
23344
23070
|
if (!task) break;
|
|
23345
|
-
if (this.stopped) break;
|
|
23346
|
-
if (!await TaskService.start(task.id)) continue;
|
|
23347
|
-
const batchId = normalizeTaskBatchId(task.batchId);
|
|
23348
|
-
if (batchId) this.activeBatchIds.add(batchId);
|
|
23349
23071
|
if (this.stopped) {
|
|
23350
23072
|
await TaskService.resetRunningToPending([task.id]);
|
|
23351
|
-
this.releaseBatch(task);
|
|
23352
23073
|
break;
|
|
23353
23074
|
}
|
|
23075
|
+
const batchId = normalizeTaskBatchId(task.batchId);
|
|
23076
|
+
if (batchId) this.activeBatchIds.add(batchId);
|
|
23354
23077
|
let runId = null;
|
|
23355
23078
|
try {
|
|
23356
23079
|
const launchIdentity = `gateway-${process.pid}:launch:${randomUUID()}`;
|
|
@@ -23483,8 +23206,8 @@ var WorkerEngine = class {
|
|
|
23483
23206
|
}
|
|
23484
23207
|
});
|
|
23485
23208
|
let spawnError = null;
|
|
23486
|
-
const spawned = new Promise((
|
|
23487
|
-
child.once("spawn",
|
|
23209
|
+
const spawned = new Promise((resolve3, reject) => {
|
|
23210
|
+
child.once("spawn", resolve3);
|
|
23488
23211
|
child.once("error", (error) => {
|
|
23489
23212
|
spawnError = error;
|
|
23490
23213
|
reject(error);
|
|
@@ -23532,10 +23255,10 @@ var WorkerEngine = class {
|
|
|
23532
23255
|
return;
|
|
23533
23256
|
}
|
|
23534
23257
|
try {
|
|
23535
|
-
await new Promise((
|
|
23258
|
+
await new Promise((resolve3, reject) => {
|
|
23536
23259
|
child.stdin.end("START\n", (error) => {
|
|
23537
23260
|
if (error) reject(error);
|
|
23538
|
-
else
|
|
23261
|
+
else resolve3();
|
|
23539
23262
|
});
|
|
23540
23263
|
});
|
|
23541
23264
|
} catch (error) {
|
|
@@ -23561,7 +23284,7 @@ var WorkerEngine = class {
|
|
|
23561
23284
|
if (!entry.guardianDrained) {
|
|
23562
23285
|
const pid = entry.child.pid;
|
|
23563
23286
|
const presence = pid == null ? spawnError == null ? "unknown" : "not-running" : inspectSpawnedProcessTreePresence(pid);
|
|
23564
|
-
const message = "guardian \u672A\u63D0\u4F9B\u8FDB\u7A0B\
|
|
23287
|
+
const message = "guardian \u672A\u63D0\u4F9B\u53D7\u7BA1\u8FDB\u7A0B\u7EC4\u6392\u7A7A\u8BC1\u660E";
|
|
23565
23288
|
if (presence !== "not-running") {
|
|
23566
23289
|
if (entry.timeoutTimer) {
|
|
23567
23290
|
clearTimeout(entry.timeoutTimer);
|
|
@@ -23597,17 +23320,48 @@ var WorkerEngine = class {
|
|
|
23597
23320
|
clearTimeout(entry.timeoutTimer);
|
|
23598
23321
|
entry.timeoutTimer = null;
|
|
23599
23322
|
}
|
|
23600
|
-
const settlement = this.
|
|
23601
|
-
markGatewayFailure("worker", error);
|
|
23602
|
-
this.logError("task settlement failed", error, entry.task.id);
|
|
23603
|
-
return false;
|
|
23604
|
-
}).finally(() => {
|
|
23323
|
+
const settlement = this.commitEntryWithRetry(entry, code, failure).finally(() => {
|
|
23605
23324
|
this.runningTasks.delete(entry.task.id);
|
|
23606
23325
|
this.releaseBatch(entry.task);
|
|
23607
23326
|
});
|
|
23608
23327
|
entry.settlementPromise = settlement;
|
|
23609
23328
|
return settlement;
|
|
23610
23329
|
}
|
|
23330
|
+
async commitEntryWithRetry(entry, code, failure) {
|
|
23331
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
23332
|
+
try {
|
|
23333
|
+
await this.commitEntry(entry, code, failure);
|
|
23334
|
+
return true;
|
|
23335
|
+
} catch (error) {
|
|
23336
|
+
markGatewayFailure("worker", error);
|
|
23337
|
+
const shortRetryDelayMs = this.settlementRetryDelaysMs[attempt];
|
|
23338
|
+
let retryDelayMs = shortRetryDelayMs ?? this.settlementRetryIntervalMs;
|
|
23339
|
+
if (this.stopped) {
|
|
23340
|
+
const remainingMs = Math.max(0, (this.shutdownDeadlineMs ?? 0) - Date.now());
|
|
23341
|
+
if (remainingMs === 0) return false;
|
|
23342
|
+
retryDelayMs = Math.min(retryDelayMs, remainingMs);
|
|
23343
|
+
}
|
|
23344
|
+
this.logError(
|
|
23345
|
+
`task settlement failed; retrying in ${retryDelayMs}ms`,
|
|
23346
|
+
error,
|
|
23347
|
+
entry.task.id
|
|
23348
|
+
);
|
|
23349
|
+
await this.waitForSettlementRetry(retryDelayMs);
|
|
23350
|
+
}
|
|
23351
|
+
}
|
|
23352
|
+
}
|
|
23353
|
+
waitForSettlementRetry(delayMs) {
|
|
23354
|
+
return new Promise((resolve3) => {
|
|
23355
|
+
let timer;
|
|
23356
|
+
const finish = () => {
|
|
23357
|
+
clearTimeout(timer);
|
|
23358
|
+
this.settlementRetryWakeups.delete(finish);
|
|
23359
|
+
resolve3();
|
|
23360
|
+
};
|
|
23361
|
+
timer = setTimeout(finish, delayMs);
|
|
23362
|
+
this.settlementRetryWakeups.add(finish);
|
|
23363
|
+
});
|
|
23364
|
+
}
|
|
23611
23365
|
async commitEntry(entry, code, failure) {
|
|
23612
23366
|
const termination = entry.termination;
|
|
23613
23367
|
if (termination?.kind === "shutdown") return;
|
|
@@ -24174,7 +23928,29 @@ init_db2();
|
|
|
24174
23928
|
init_task_service();
|
|
24175
23929
|
init_health();
|
|
24176
23930
|
init_process_control();
|
|
24177
|
-
|
|
23931
|
+
|
|
23932
|
+
// src/core/package-version.ts
|
|
23933
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
23934
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
23935
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
23936
|
+
function getPackageVersion() {
|
|
23937
|
+
let directory = dirname4(fileURLToPath4(import.meta.url));
|
|
23938
|
+
for (let depth = 0; depth < 5; depth += 1) {
|
|
23939
|
+
const packagePath = join4(directory, "package.json");
|
|
23940
|
+
if (existsSync4(packagePath)) {
|
|
23941
|
+
try {
|
|
23942
|
+
const pkg = JSON.parse(readFileSync2(packagePath, "utf8"));
|
|
23943
|
+
if (typeof pkg.version === "string") return pkg.version;
|
|
23944
|
+
} catch {
|
|
23945
|
+
return "0.0.0";
|
|
23946
|
+
}
|
|
23947
|
+
}
|
|
23948
|
+
directory = dirname4(directory);
|
|
23949
|
+
}
|
|
23950
|
+
return "0.0.0";
|
|
23951
|
+
}
|
|
23952
|
+
|
|
23953
|
+
// src/gateway/index.ts
|
|
24178
23954
|
var STALE_THRESHOLD_MS = 3e4;
|
|
24179
23955
|
async function runGatewayShutdownStep(failures, step, operation) {
|
|
24180
23956
|
try {
|