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/cli/index.js
CHANGED
|
@@ -4506,7 +4506,7 @@ var init_sql = __esm({
|
|
|
4506
4506
|
return new SQL([new StringChunk(str)]);
|
|
4507
4507
|
}
|
|
4508
4508
|
sql2.raw = raw2;
|
|
4509
|
-
function
|
|
4509
|
+
function join9(chunks, separator) {
|
|
4510
4510
|
const result = [];
|
|
4511
4511
|
for (const [i, chunk] of chunks.entries()) {
|
|
4512
4512
|
if (i > 0 && separator !== void 0) {
|
|
@@ -4516,7 +4516,7 @@ var init_sql = __esm({
|
|
|
4516
4516
|
}
|
|
4517
4517
|
return new SQL(result);
|
|
4518
4518
|
}
|
|
4519
|
-
sql2.join =
|
|
4519
|
+
sql2.join = join9;
|
|
4520
4520
|
function identifier(value) {
|
|
4521
4521
|
return new Name(value);
|
|
4522
4522
|
}
|
|
@@ -7132,7 +7132,7 @@ var init_select2 = __esm({
|
|
|
7132
7132
|
return (table, on) => {
|
|
7133
7133
|
const baseTableName = this.tableName;
|
|
7134
7134
|
const tableName = getTableLikeName(table);
|
|
7135
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
7135
|
+
if (typeof tableName === "string" && this.config.joins?.some((join9) => join9.alias === tableName)) {
|
|
7136
7136
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
7137
7137
|
}
|
|
7138
7138
|
if (!this.isPartialSelect) {
|
|
@@ -7974,7 +7974,7 @@ var init_update = __esm({
|
|
|
7974
7974
|
createJoin(joinType) {
|
|
7975
7975
|
return (table, on) => {
|
|
7976
7976
|
const tableName = getTableLikeName(table);
|
|
7977
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
7977
|
+
if (typeof tableName === "string" && this.config.joins.some((join9) => join9.alias === tableName)) {
|
|
7978
7978
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
7979
7979
|
}
|
|
7980
7980
|
if (typeof on === "function") {
|
|
@@ -9790,13 +9790,23 @@ var init_task_service = __esm({
|
|
|
9790
9790
|
});
|
|
9791
9791
|
const maxRetries = normalizedData.maxRetries ?? task.maxRetries ?? 3;
|
|
9792
9792
|
const exhausted = task.status === "failed" && (task.retryCount ?? 0) > maxRetries;
|
|
9793
|
-
|
|
9793
|
+
const updated = tx.update(tasks2).set({
|
|
9794
9794
|
...normalizedData,
|
|
9795
9795
|
...exhausted ? {
|
|
9796
9796
|
status: "dead_letter",
|
|
9797
9797
|
retryAfter: null
|
|
9798
9798
|
} : {}
|
|
9799
9799
|
}).where(eq(tasks2.id, id)).returning().get() ?? null;
|
|
9800
|
+
if (exhausted && updated) {
|
|
9801
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
9802
|
+
tx.update(tasks2).set({
|
|
9803
|
+
status: "dead_letter",
|
|
9804
|
+
finishedAt,
|
|
9805
|
+
retryAfter: null,
|
|
9806
|
+
resultLog: `\u4F9D\u8D56\u4EFB\u52A1 #${id} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`
|
|
9807
|
+
}).where(blockedDependentsOf(id)).run();
|
|
9808
|
+
}
|
|
9809
|
+
return updated;
|
|
9800
9810
|
}, { behavior: "immediate" });
|
|
9801
9811
|
}
|
|
9802
9812
|
static validateNewTask(data) {
|
|
@@ -9817,7 +9827,7 @@ var init_task_service = __esm({
|
|
|
9817
9827
|
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
9818
9828
|
}
|
|
9819
9829
|
}
|
|
9820
|
-
static
|
|
9830
|
+
static buildRunnableTaskWhere(scope) {
|
|
9821
9831
|
const baseConditions = [...this.buildScopeWhere(scope)];
|
|
9822
9832
|
const nowMs = Date.now();
|
|
9823
9833
|
const retryAfterFilter = or(
|
|
@@ -9852,40 +9862,43 @@ var init_task_service = __esm({
|
|
|
9852
9862
|
if (batchFilter) {
|
|
9853
9863
|
conditions.push(batchFilter);
|
|
9854
9864
|
}
|
|
9855
|
-
|
|
9865
|
+
return and(
|
|
9856
9866
|
...conditions,
|
|
9857
9867
|
or(
|
|
9858
9868
|
isNull(tasks2.dependsOn),
|
|
9859
9869
|
sql`EXISTS (
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9870
|
+
SELECT 1 FROM tasks AS dependency_task
|
|
9871
|
+
WHERE dependency_task.id = ${tasks2.dependsOn}
|
|
9872
|
+
AND dependency_task.status = 'done'
|
|
9873
|
+
AND dependency_task.cwd IS ${tasks2.cwd}
|
|
9874
|
+
)`
|
|
9865
9875
|
),
|
|
9866
9876
|
or(
|
|
9867
9877
|
isNull(tasks2.batchId),
|
|
9868
9878
|
sql`trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ''`,
|
|
9869
9879
|
sql`NOT EXISTS (
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
)
|
|
9880
|
+
SELECT 1 FROM tasks AS running_batch_task
|
|
9881
|
+
WHERE trim(running_batch_task.batch_id, ${TASK_BATCH_TRIM_CHARACTERS})
|
|
9882
|
+
= trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS})
|
|
9883
|
+
AND (
|
|
9884
|
+
running_batch_task.status = 'running'
|
|
9885
|
+
OR EXISTS (
|
|
9886
|
+
SELECT 1 FROM task_runs AS running_batch_run
|
|
9887
|
+
WHERE running_batch_run.task_id = running_batch_task.id
|
|
9888
|
+
AND running_batch_run.status = 'running'
|
|
9880
9889
|
)
|
|
9881
|
-
|
|
9890
|
+
)
|
|
9891
|
+
)`
|
|
9882
9892
|
),
|
|
9883
9893
|
sql`NOT EXISTS (
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
)
|
|
9894
|
+
SELECT 1 FROM task_runs AS candidate_active_run
|
|
9895
|
+
WHERE candidate_active_run.task_id = ${tasks2.id}
|
|
9896
|
+
AND candidate_active_run.status = 'running'
|
|
9897
|
+
)`
|
|
9898
|
+
);
|
|
9899
|
+
}
|
|
9900
|
+
static async next(scope = {}) {
|
|
9901
|
+
const result = await db.select().from(tasks2).where(this.buildRunnableTaskWhere(scope)).orderBy(
|
|
9889
9902
|
desc(tasks2.urgency),
|
|
9890
9903
|
desc(tasks2.importance),
|
|
9891
9904
|
asc(tasks2.createdAt),
|
|
@@ -9893,6 +9906,22 @@ var init_task_service = __esm({
|
|
|
9893
9906
|
).limit(1);
|
|
9894
9907
|
return result[0] ?? null;
|
|
9895
9908
|
}
|
|
9909
|
+
static async claimNext(scope = {}) {
|
|
9910
|
+
return db.transaction((tx) => {
|
|
9911
|
+
const candidate = tx.select().from(tasks2).where(this.buildRunnableTaskWhere(scope)).orderBy(
|
|
9912
|
+
desc(tasks2.urgency),
|
|
9913
|
+
desc(tasks2.importance),
|
|
9914
|
+
asc(tasks2.createdAt),
|
|
9915
|
+
asc(tasks2.id)
|
|
9916
|
+
).limit(1).get();
|
|
9917
|
+
if (!candidate) return null;
|
|
9918
|
+
return tx.update(tasks2).set({
|
|
9919
|
+
status: "running",
|
|
9920
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
9921
|
+
finishedAt: null
|
|
9922
|
+
}).where(eq(tasks2.id, candidate.id)).returning().get() ?? null;
|
|
9923
|
+
}, { behavior: "immediate" });
|
|
9924
|
+
}
|
|
9896
9925
|
static async countRunning(scope = {}) {
|
|
9897
9926
|
const scopeConditions = scope.legacyCwd ? [sql`${tasks2.cwd} IS NULL OR trim(${tasks2.cwd}) = ''`] : this.buildScopeWhere(scope);
|
|
9898
9927
|
if (scope.batchId !== void 0) {
|
|
@@ -10553,9 +10582,34 @@ var init_launch_protocol = __esm({
|
|
|
10553
10582
|
});
|
|
10554
10583
|
|
|
10555
10584
|
// src/core/process-control.ts
|
|
10556
|
-
import { spawnSync } from "child_process";
|
|
10557
10585
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10558
10586
|
import { basename, dirname as dirname2, resolve } from "path";
|
|
10587
|
+
function runBoundedOsCommand(command, args, captureOutput = true) {
|
|
10588
|
+
const result = Bun.spawnSync({
|
|
10589
|
+
cmd: [
|
|
10590
|
+
process.execPath,
|
|
10591
|
+
"-e",
|
|
10592
|
+
BOUNDED_OS_COMMAND_RUNNER,
|
|
10593
|
+
"supertask-os-command",
|
|
10594
|
+
command,
|
|
10595
|
+
...args
|
|
10596
|
+
],
|
|
10597
|
+
detached: process.platform !== "win32",
|
|
10598
|
+
maxBuffer: OS_COMMAND_MAX_OUTPUT_BYTES + 1024,
|
|
10599
|
+
stdin: "ignore",
|
|
10600
|
+
stdout: "pipe",
|
|
10601
|
+
stderr: "ignore"
|
|
10602
|
+
});
|
|
10603
|
+
const output = new TextDecoder().decode(result.stdout);
|
|
10604
|
+
const markerIndex = output.lastIndexOf(OS_COMMAND_RESULT_MARKER);
|
|
10605
|
+
if (markerIndex < 0) return { status: null, stdout: "" };
|
|
10606
|
+
const statusText2 = output.slice(markerIndex + OS_COMMAND_RESULT_MARKER.length);
|
|
10607
|
+
if (!/^\d+$/.test(statusText2)) return { status: null, stdout: "" };
|
|
10608
|
+
return {
|
|
10609
|
+
status: Number(statusText2),
|
|
10610
|
+
stdout: captureOutput ? output.slice(0, markerIndex) : ""
|
|
10611
|
+
};
|
|
10612
|
+
}
|
|
10559
10613
|
function isSafePid(pid) {
|
|
10560
10614
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid;
|
|
10561
10615
|
}
|
|
@@ -10592,12 +10646,10 @@ function inspectSpawnedProcessTreePresence(pid) {
|
|
|
10592
10646
|
}
|
|
10593
10647
|
}
|
|
10594
10648
|
function inspectUnixProcess(pid) {
|
|
10595
|
-
const result =
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
killSignal: "SIGKILL"
|
|
10600
|
-
});
|
|
10649
|
+
const result = runBoundedOsCommand(
|
|
10650
|
+
"ps",
|
|
10651
|
+
["-o", "pgid=", "-o", "command=", "-p", String(pid)]
|
|
10652
|
+
);
|
|
10601
10653
|
if (result.status !== 0) return null;
|
|
10602
10654
|
const match2 = result.stdout.trim().match(/^(\d+)\s+(.+)$/s);
|
|
10603
10655
|
if (!match2) return null;
|
|
@@ -10605,30 +10657,18 @@ function inspectUnixProcess(pid) {
|
|
|
10605
10657
|
}
|
|
10606
10658
|
function inspectWindowsCommand(pid) {
|
|
10607
10659
|
const script = `(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}").CommandLine`;
|
|
10608
|
-
const result =
|
|
10660
|
+
const result = runBoundedOsCommand(
|
|
10609
10661
|
"powershell.exe",
|
|
10610
|
-
["-NoProfile", "-NonInteractive", "-Command", script]
|
|
10611
|
-
{
|
|
10612
|
-
encoding: "utf8",
|
|
10613
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
10614
|
-
timeout: OS_COMMAND_TIMEOUT_MS,
|
|
10615
|
-
killSignal: "SIGKILL"
|
|
10616
|
-
}
|
|
10662
|
+
["-NoProfile", "-NonInteractive", "-Command", script]
|
|
10617
10663
|
);
|
|
10618
10664
|
if (result.status !== 0) return null;
|
|
10619
10665
|
return result.stdout.trim() || null;
|
|
10620
10666
|
}
|
|
10621
10667
|
function inspectWindowsProcessTree(rootPid) {
|
|
10622
10668
|
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`;
|
|
10623
|
-
const result =
|
|
10669
|
+
const result = runBoundedOsCommand(
|
|
10624
10670
|
"powershell.exe",
|
|
10625
|
-
["-NoProfile", "-NonInteractive", "-Command", script]
|
|
10626
|
-
{
|
|
10627
|
-
encoding: "utf8",
|
|
10628
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
10629
|
-
timeout: OS_COMMAND_TIMEOUT_MS,
|
|
10630
|
-
killSignal: "SIGKILL"
|
|
10631
|
-
}
|
|
10671
|
+
["-NoProfile", "-NonInteractive", "-Command", script]
|
|
10632
10672
|
);
|
|
10633
10673
|
if (result.status !== 0) return null;
|
|
10634
10674
|
return result.stdout.split(/\s+/).filter(Boolean).map(Number).filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
@@ -10699,11 +10739,7 @@ function signalRecordedProcessTreeWithResult(pid, signal, expectedExecutable, la
|
|
|
10699
10739
|
}
|
|
10700
10740
|
const args = ["/PID", String(pid), "/T"];
|
|
10701
10741
|
if (signal === "SIGKILL") args.push("/F");
|
|
10702
|
-
const status =
|
|
10703
|
-
stdio: "ignore",
|
|
10704
|
-
timeout: OS_COMMAND_TIMEOUT_MS,
|
|
10705
|
-
killSignal: "SIGKILL"
|
|
10706
|
-
}).status;
|
|
10742
|
+
const status = runBoundedOsCommand("taskkill", args, false).status;
|
|
10707
10743
|
if (status === 0) return "signalled";
|
|
10708
10744
|
return isProcessAlive(pid) ? "signal-failed" : absentLeaderResult(pid);
|
|
10709
10745
|
}
|
|
@@ -10732,12 +10768,64 @@ async function waitForSpawnedProcessTreeExit(pid, timeoutMs = 5e3) {
|
|
|
10732
10768
|
}
|
|
10733
10769
|
return inspectSpawnedProcessTreePresence(pid) === "not-running";
|
|
10734
10770
|
}
|
|
10735
|
-
var OS_COMMAND_TIMEOUT_MS;
|
|
10771
|
+
var OS_COMMAND_TIMEOUT_MS, OS_COMMAND_MAX_OUTPUT_BYTES, OS_COMMAND_RESULT_MARKER, BOUNDED_OS_COMMAND_RUNNER;
|
|
10736
10772
|
var init_process_control = __esm({
|
|
10737
10773
|
"src/core/process-control.ts"() {
|
|
10738
10774
|
"use strict";
|
|
10739
10775
|
init_launch_protocol();
|
|
10740
10776
|
OS_COMMAND_TIMEOUT_MS = 2e3;
|
|
10777
|
+
OS_COMMAND_MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
10778
|
+
OS_COMMAND_RESULT_MARKER = "\n\0supertask-os-command-result:";
|
|
10779
|
+
BOUNDED_OS_COMMAND_RUNNER = `
|
|
10780
|
+
const { writeSync } = await import('fs');
|
|
10781
|
+
let child;
|
|
10782
|
+
try {
|
|
10783
|
+
child = Bun.spawn(process.argv.slice(2), {
|
|
10784
|
+
stdin: 'ignore',
|
|
10785
|
+
stdout: 'pipe',
|
|
10786
|
+
stderr: 'ignore',
|
|
10787
|
+
});
|
|
10788
|
+
} catch {
|
|
10789
|
+
process.exit(127);
|
|
10790
|
+
}
|
|
10791
|
+
const terminateGroup = () => {
|
|
10792
|
+
if (process.platform !== 'win32') {
|
|
10793
|
+
try {
|
|
10794
|
+
process.kill(-process.pid, 'SIGKILL');
|
|
10795
|
+
} catch {
|
|
10796
|
+
}
|
|
10797
|
+
}
|
|
10798
|
+
try {
|
|
10799
|
+
child.kill('SIGKILL');
|
|
10800
|
+
} catch {
|
|
10801
|
+
}
|
|
10802
|
+
process.exit(1);
|
|
10803
|
+
};
|
|
10804
|
+
const timer = setTimeout(terminateGroup, ${OS_COMMAND_TIMEOUT_MS});
|
|
10805
|
+
try {
|
|
10806
|
+
const chunks = [];
|
|
10807
|
+
let outputBytes = 0;
|
|
10808
|
+
const readOutput = async () => {
|
|
10809
|
+
const reader = child.stdout.getReader();
|
|
10810
|
+
while (true) {
|
|
10811
|
+
const { done, value } = await reader.read();
|
|
10812
|
+
if (done) return;
|
|
10813
|
+
outputBytes += value.byteLength;
|
|
10814
|
+
if (outputBytes > ${OS_COMMAND_MAX_OUTPUT_BYTES}) terminateGroup();
|
|
10815
|
+
chunks.push(value);
|
|
10816
|
+
}
|
|
10817
|
+
};
|
|
10818
|
+
const [exitCode] = await Promise.all([child.exited, readOutput()]);
|
|
10819
|
+
clearTimeout(timer);
|
|
10820
|
+
for (const chunk of chunks) writeSync(1, chunk);
|
|
10821
|
+
writeSync(1, ${JSON.stringify(OS_COMMAND_RESULT_MARKER)} + String(exitCode));
|
|
10822
|
+
if (process.platform !== 'win32') terminateGroup();
|
|
10823
|
+
process.exit(0);
|
|
10824
|
+
} catch {
|
|
10825
|
+
clearTimeout(timer);
|
|
10826
|
+
terminateGroup();
|
|
10827
|
+
}
|
|
10828
|
+
`;
|
|
10741
10829
|
}
|
|
10742
10830
|
});
|
|
10743
10831
|
|
|
@@ -10889,7 +10977,7 @@ var init_task_run_service = __esm({
|
|
|
10889
10977
|
}
|
|
10890
10978
|
if (current.childPid != null) {
|
|
10891
10979
|
throw new LegacyRunAbandonConflictError(
|
|
10892
|
-
`run #${runId} \u5DF2\u8BB0\u5F55 child PID ${current.childPid}\uFF0C\u5FC5\u987B\u7531 Worker/Watchdog \u786E\u8BA4\u8FDB\u7A0B\
|
|
10980
|
+
`run #${runId} \u5DF2\u8BB0\u5F55 child PID ${current.childPid}\uFF0C\u5FC5\u987B\u7531 Worker/Watchdog \u786E\u8BA4\u53D7\u7BA1\u8FDB\u7A0B\u7EC4\u6392\u7A7A`
|
|
10893
10981
|
);
|
|
10894
10982
|
}
|
|
10895
10983
|
if (current.workerPid != null && isProcessAlive(current.workerPid)) {
|
|
@@ -21010,7 +21098,7 @@ __export(pm2_exports, {
|
|
|
21010
21098
|
upgrade: () => upgrade,
|
|
21011
21099
|
withGatewayMaintenance: () => withGatewayMaintenance
|
|
21012
21100
|
});
|
|
21013
|
-
import { execSync, spawnSync
|
|
21101
|
+
import { execSync, spawnSync } from "child_process";
|
|
21014
21102
|
import {
|
|
21015
21103
|
accessSync,
|
|
21016
21104
|
chmodSync as chmodSync2,
|
|
@@ -21115,7 +21203,7 @@ function diagnoseOpenCodeRuntime(runtime = {
|
|
|
21115
21203
|
env: process.env
|
|
21116
21204
|
}) {
|
|
21117
21205
|
const executable = runtimeScope(runtime).opencodePath;
|
|
21118
|
-
const result =
|
|
21206
|
+
const result = spawnSync(executable, ["--version"], {
|
|
21119
21207
|
cwd: runtime.cwd,
|
|
21120
21208
|
env: runtime.env,
|
|
21121
21209
|
encoding: "utf8",
|
|
@@ -21205,7 +21293,7 @@ function xmlEscape(value) {
|
|
|
21205
21293
|
function resolvePm2Bin() {
|
|
21206
21294
|
const configured = pm2Bin();
|
|
21207
21295
|
if (isAbsolute2(configured)) return configured;
|
|
21208
|
-
const result =
|
|
21296
|
+
const result = spawnSync("which", [configured], { encoding: "utf8" });
|
|
21209
21297
|
const resolved = result.status === 0 ? result.stdout.trim().split("\n")[0] : "";
|
|
21210
21298
|
if (!resolved) throw new Error(`[supertask] \u65E0\u6CD5\u89E3\u6790 pm2 \u53EF\u6267\u884C\u6587\u4EF6: ${configured}`);
|
|
21211
21299
|
return resolved;
|
|
@@ -21262,18 +21350,18 @@ function isMacLaunchAgentConfigured(expectedPm2Home, expectedRuntime) {
|
|
|
21262
21350
|
accessSync(bunPath, constants.X_OK);
|
|
21263
21351
|
accessSync(supervisorEntry, constants.R_OK);
|
|
21264
21352
|
accessSync(pm2Path, constants.X_OK);
|
|
21265
|
-
if (
|
|
21353
|
+
if (spawnSync(bunPath, ["--version"], {
|
|
21266
21354
|
stdio: "ignore",
|
|
21267
21355
|
timeout: pm2CommandTimeoutMs(),
|
|
21268
21356
|
killSignal: "SIGKILL"
|
|
21269
21357
|
}).status !== 0) return false;
|
|
21270
|
-
if (
|
|
21358
|
+
if (spawnSync(pm2Path, ["--version"], {
|
|
21271
21359
|
stdio: "ignore",
|
|
21272
21360
|
env: { ...process.env, PM2_HOME: pm2Home },
|
|
21273
21361
|
timeout: pm2CommandTimeoutMs(),
|
|
21274
21362
|
killSignal: "SIGKILL"
|
|
21275
21363
|
}).status !== 0) return false;
|
|
21276
|
-
const loaded =
|
|
21364
|
+
const loaded = spawnSync(
|
|
21277
21365
|
launchctlBin(),
|
|
21278
21366
|
["print", `gui/${process.getuid()}/${MAC_LAUNCH_AGENT_LABEL}`],
|
|
21279
21367
|
{
|
|
@@ -21301,7 +21389,7 @@ function isMacLaunchAgentConfigured(expectedPm2Home, expectedRuntime) {
|
|
|
21301
21389
|
function bootstrapMacLaunchAgent(domain, path) {
|
|
21302
21390
|
const retryDeadline = Date.now() + 2e3;
|
|
21303
21391
|
const sleeper = new Int32Array(new SharedArrayBuffer(4));
|
|
21304
|
-
let result =
|
|
21392
|
+
let result = spawnSync(launchctlBin(), ["bootstrap", domain, path], {
|
|
21305
21393
|
encoding: "utf8",
|
|
21306
21394
|
timeout: pm2CommandTimeoutMs(),
|
|
21307
21395
|
killSignal: "SIGKILL"
|
|
@@ -21310,7 +21398,7 @@ function bootstrapMacLaunchAgent(domain, path) {
|
|
|
21310
21398
|
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
21311
21399
|
if (result.status !== 5 && !output.includes("Bootstrap failed: 5:")) break;
|
|
21312
21400
|
Atomics.wait(sleeper, 0, 0, 100);
|
|
21313
|
-
result =
|
|
21401
|
+
result = spawnSync(launchctlBin(), ["bootstrap", domain, path], {
|
|
21314
21402
|
encoding: "utf8",
|
|
21315
21403
|
timeout: pm2CommandTimeoutMs(),
|
|
21316
21404
|
killSignal: "SIGKILL"
|
|
@@ -21368,7 +21456,7 @@ function installMacLaunchAgent(expectedRuntime = currentGatewayRuntime()) {
|
|
|
21368
21456
|
`;
|
|
21369
21457
|
const domain = `gui/${process.getuid()}`;
|
|
21370
21458
|
const previousPlist = existsSync5(path) ? readFileSync3(path) : null;
|
|
21371
|
-
const wasLoaded = previousPlist != null &&
|
|
21459
|
+
const wasLoaded = previousPlist != null && spawnSync(
|
|
21372
21460
|
launchctlBin(),
|
|
21373
21461
|
["print", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`],
|
|
21374
21462
|
{
|
|
@@ -21380,7 +21468,7 @@ function installMacLaunchAgent(expectedRuntime = currentGatewayRuntime()) {
|
|
|
21380
21468
|
mkdirSync4(dirname6(path), { recursive: true });
|
|
21381
21469
|
writeFileSync2(path, plist, { mode: 384 });
|
|
21382
21470
|
chmodSync2(path, 384);
|
|
21383
|
-
|
|
21471
|
+
spawnSync(launchctlBin(), ["bootout", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`], {
|
|
21384
21472
|
stdio: "ignore",
|
|
21385
21473
|
timeout: pm2CommandTimeoutMs(),
|
|
21386
21474
|
killSignal: "SIGKILL"
|
|
@@ -21398,7 +21486,7 @@ function installMacLaunchAgent(expectedRuntime = currentGatewayRuntime()) {
|
|
|
21398
21486
|
if (loaded.status !== 0 || !verified) {
|
|
21399
21487
|
const output = loaded.status === 0 ? "supervisor \u672A\u4FDD\u6301 running \u6216 PM2 dump \u4E0D\u53EF\u6062\u590D" : `${loaded.stdout ?? ""}${loaded.stderr ?? ""}`.trim();
|
|
21400
21488
|
if (loaded.status === 0) {
|
|
21401
|
-
|
|
21489
|
+
spawnSync(launchctlBin(), ["bootout", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`], {
|
|
21402
21490
|
stdio: "ignore",
|
|
21403
21491
|
timeout: pm2CommandTimeoutMs(),
|
|
21404
21492
|
killSignal: "SIGKILL"
|
|
@@ -21425,7 +21513,7 @@ function systemctlBin(env = process.env) {
|
|
|
21425
21513
|
}
|
|
21426
21514
|
function isLinuxStartupConfigured(env = process.env, cwd = process.cwd(), expectedRuntime) {
|
|
21427
21515
|
const unit = env.SUPERTASK_PM2_SYSTEMD_UNIT ?? `pm2-${userInfo().username}.service`;
|
|
21428
|
-
const enabled =
|
|
21516
|
+
const enabled = spawnSync(systemctlBin(env), ["is-enabled", unit], {
|
|
21429
21517
|
encoding: "utf8",
|
|
21430
21518
|
env,
|
|
21431
21519
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -21433,7 +21521,7 @@ function isLinuxStartupConfigured(env = process.env, cwd = process.cwd(), expect
|
|
|
21433
21521
|
killSignal: "SIGKILL"
|
|
21434
21522
|
});
|
|
21435
21523
|
if (enabled.status !== 0 || enabled.stdout.trim() !== "enabled") return false;
|
|
21436
|
-
const contents =
|
|
21524
|
+
const contents = spawnSync(systemctlBin(env), ["cat", unit], {
|
|
21437
21525
|
encoding: "utf8",
|
|
21438
21526
|
env,
|
|
21439
21527
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -21457,7 +21545,7 @@ function isLinuxStartupConfigured(env = process.env, cwd = process.cwd(), expect
|
|
|
21457
21545
|
}
|
|
21458
21546
|
}
|
|
21459
21547
|
function isPm2Installed(env = process.env) {
|
|
21460
|
-
const result =
|
|
21548
|
+
const result = spawnSync(pm2Bin(env), ["--version"], {
|
|
21461
21549
|
stdio: "ignore",
|
|
21462
21550
|
env,
|
|
21463
21551
|
shell: process.platform === "win32",
|
|
@@ -21482,7 +21570,7 @@ function installPm2() {
|
|
|
21482
21570
|
}
|
|
21483
21571
|
function resolveCommand(command) {
|
|
21484
21572
|
const lookup = process.platform === "win32" ? "where" : "which";
|
|
21485
|
-
const result =
|
|
21573
|
+
const result = spawnSync(lookup, [command], { encoding: "utf8" });
|
|
21486
21574
|
return result.status === 0 ? result.stdout.trim().split("\n")[0] || null : null;
|
|
21487
21575
|
}
|
|
21488
21576
|
function npmPreferredEnvironment() {
|
|
@@ -21504,7 +21592,7 @@ function npmPreferredEnvironment() {
|
|
|
21504
21592
|
function pm2Exec(args, options = {}) {
|
|
21505
21593
|
const npmEnvironment = options.preferNpm ? npmPreferredEnvironment() : null;
|
|
21506
21594
|
const effectiveEnv = options.env ?? npmEnvironment?.env ?? process.env;
|
|
21507
|
-
const result =
|
|
21595
|
+
const result = spawnSync(npmEnvironment?.command ?? pm2Bin(effectiveEnv), args, {
|
|
21508
21596
|
stdio: ["pipe", "pipe", "pipe"],
|
|
21509
21597
|
encoding: "utf-8",
|
|
21510
21598
|
env: effectiveEnv,
|
|
@@ -21579,7 +21667,7 @@ function gatewayRuntimeFromProcess(processInfo) {
|
|
|
21579
21667
|
try {
|
|
21580
21668
|
accessSync(savedBunPath, constants.X_OK);
|
|
21581
21669
|
if (!statSync3(savedCwd).isDirectory()) return null;
|
|
21582
|
-
if (
|
|
21670
|
+
if (spawnSync(savedBunPath, ["--version"], {
|
|
21583
21671
|
stdio: "ignore",
|
|
21584
21672
|
env: savedEnv,
|
|
21585
21673
|
timeout: pm2CommandTimeoutMs(savedEnv),
|
|
@@ -22041,7 +22129,7 @@ function removeMacLaunchAgent() {
|
|
|
22041
22129
|
if (typeof process.getuid !== "function") return;
|
|
22042
22130
|
const path = launchAgentPath();
|
|
22043
22131
|
const domain = `gui/${process.getuid()}`;
|
|
22044
|
-
const loaded =
|
|
22132
|
+
const loaded = spawnSync(
|
|
22045
22133
|
launchctlBin(),
|
|
22046
22134
|
["print", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`],
|
|
22047
22135
|
{
|
|
@@ -22054,7 +22142,7 @@ function removeMacLaunchAgent() {
|
|
|
22054
22142
|
throw new Error(`[supertask] \u65E0\u6CD5\u786E\u8BA4 macOS LaunchAgent \u72B6\u6001: ${loaded.error.message}`);
|
|
22055
22143
|
}
|
|
22056
22144
|
if (loaded.status === 0) {
|
|
22057
|
-
const removed =
|
|
22145
|
+
const removed = spawnSync(
|
|
22058
22146
|
launchctlBin(),
|
|
22059
22147
|
["bootout", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`],
|
|
22060
22148
|
{
|
|
@@ -22265,7 +22353,7 @@ __export(update_exports, {
|
|
|
22265
22353
|
resolveInstalledPluginVersion: () => resolveInstalledPluginVersion,
|
|
22266
22354
|
updateGlobalCli: () => updateGlobalCli
|
|
22267
22355
|
});
|
|
22268
|
-
import { spawnSync as
|
|
22356
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
22269
22357
|
import {
|
|
22270
22358
|
chmodSync as chmodSync3,
|
|
22271
22359
|
closeSync,
|
|
@@ -22330,7 +22418,7 @@ function bunBin() {
|
|
|
22330
22418
|
function resolveGlobalCliExecutable() {
|
|
22331
22419
|
const override = process.env.SUPERTASK_CLI_BIN;
|
|
22332
22420
|
if (override) return existsSync6(override) ? resolve4(override) : null;
|
|
22333
|
-
const result =
|
|
22421
|
+
const result = spawnSync2(process.platform === "win32" ? "where" : "which", ["supertask"], {
|
|
22334
22422
|
encoding: "utf8",
|
|
22335
22423
|
env: process.env,
|
|
22336
22424
|
timeout: 1e4
|
|
@@ -22365,7 +22453,7 @@ function packageVersion(packageDir) {
|
|
|
22365
22453
|
}
|
|
22366
22454
|
}
|
|
22367
22455
|
function npmGlobalRoot() {
|
|
22368
|
-
const result =
|
|
22456
|
+
const result = spawnSync2(npmBin(), ["root", "-g"], {
|
|
22369
22457
|
encoding: "utf8",
|
|
22370
22458
|
env: process.env,
|
|
22371
22459
|
timeout: 3e4
|
|
@@ -22429,7 +22517,7 @@ function updateGlobalCli(version2) {
|
|
|
22429
22517
|
}
|
|
22430
22518
|
const command = before.packageManager === "npm" ? npmBin() : bunBin();
|
|
22431
22519
|
const args = before.packageManager === "npm" ? ["install", "-g", `${PACKAGE_NAME}@${version2}`] : ["add", "-g", `${PACKAGE_NAME}@${version2}`];
|
|
22432
|
-
const result =
|
|
22520
|
+
const result = spawnSync2(command, args, {
|
|
22433
22521
|
encoding: "utf8",
|
|
22434
22522
|
env: process.env,
|
|
22435
22523
|
timeout: 12e4
|
|
@@ -22450,7 +22538,7 @@ function updateGlobalCli(version2) {
|
|
|
22450
22538
|
return { ...after, action: "updated" };
|
|
22451
22539
|
}
|
|
22452
22540
|
function getLatestVersion() {
|
|
22453
|
-
const result =
|
|
22541
|
+
const result = spawnSync2(npmBin(), [
|
|
22454
22542
|
"view",
|
|
22455
22543
|
PACKAGE_NAME,
|
|
22456
22544
|
"dist-tags.latest",
|
|
@@ -22533,7 +22621,7 @@ function getOpenCodePluginDiagnostic() {
|
|
|
22533
22621
|
chmodSync3(outputDirectory, 448);
|
|
22534
22622
|
const outputPath = join5(outputDirectory, "resolved-config.json");
|
|
22535
22623
|
outputFd = openSync(outputPath, "w", 384);
|
|
22536
|
-
const result =
|
|
22624
|
+
const result = spawnSync2(opencodeBin(), ["debug", "config", "--pure"], {
|
|
22537
22625
|
encoding: "utf8",
|
|
22538
22626
|
env: process.env,
|
|
22539
22627
|
timeout: 3e4,
|
|
@@ -22603,7 +22691,7 @@ function installPluginVersion(version2) {
|
|
|
22603
22691
|
if (!isSemanticVersion(version2)) {
|
|
22604
22692
|
throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7248\u672C\u65E0\u6548: ${version2}`);
|
|
22605
22693
|
}
|
|
22606
|
-
const result =
|
|
22694
|
+
const result = spawnSync2(opencodeBin(), [
|
|
22607
22695
|
"plugin",
|
|
22608
22696
|
`${PACKAGE_NAME}@${version2}`,
|
|
22609
22697
|
"--global",
|
|
@@ -22782,7 +22870,7 @@ function runCommandContext(executable, args, cwd) {
|
|
|
22782
22870
|
function assertWorkerProcessIsolationSupported(platform = process.platform) {
|
|
22783
22871
|
if (platform === "win32") {
|
|
22784
22872
|
throw new Error(
|
|
22785
|
-
"Windows Worker \u5DF2\u5B89\u5168\u7981\u7528\uFF1A\u5F53\u524D\u8FD0\u884C\u65F6\u65E0\u6CD5\u7528 Job Object \
|
|
22873
|
+
"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"
|
|
22786
22874
|
);
|
|
22787
22875
|
}
|
|
22788
22876
|
}
|
|
@@ -22806,17 +22894,24 @@ var init_worker = __esm({
|
|
|
22806
22894
|
pollTimer = null;
|
|
22807
22895
|
heartbeatTimer = null;
|
|
22808
22896
|
pollCyclePromise = null;
|
|
22897
|
+
shutdownDeadlineMs = null;
|
|
22898
|
+
settlementRetryWakeups = /* @__PURE__ */ new Set();
|
|
22809
22899
|
cfg;
|
|
22810
22900
|
opencodeBin;
|
|
22811
22901
|
maxOutputChars;
|
|
22902
|
+
settlementRetryDelaysMs;
|
|
22903
|
+
settlementRetryIntervalMs;
|
|
22812
22904
|
constructor(cfg, options = {}) {
|
|
22813
22905
|
this.cfg = cfg.worker;
|
|
22814
22906
|
this.opencodeBin = options.opencodeBin ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
22815
22907
|
this.maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
22908
|
+
this.settlementRetryDelaysMs = options.settlementRetryDelaysMs ?? [250, 1e3, 4e3];
|
|
22909
|
+
this.settlementRetryIntervalMs = options.settlementRetryIntervalMs ?? 5e3;
|
|
22816
22910
|
}
|
|
22817
22911
|
start() {
|
|
22818
22912
|
assertWorkerProcessIsolationSupported();
|
|
22819
22913
|
this.stopped = false;
|
|
22914
|
+
this.shutdownDeadlineMs = null;
|
|
22820
22915
|
markGatewayActivity("worker");
|
|
22821
22916
|
this.poll();
|
|
22822
22917
|
this.heartbeatTimer = setInterval(() => {
|
|
@@ -22825,6 +22920,8 @@ var init_worker = __esm({
|
|
|
22825
22920
|
}
|
|
22826
22921
|
async stop(gracePeriodMs = 0) {
|
|
22827
22922
|
this.stopped = true;
|
|
22923
|
+
this.shutdownDeadlineMs = Date.now() + Math.max(0, gracePeriodMs);
|
|
22924
|
+
for (const wake of [...this.settlementRetryWakeups]) wake();
|
|
22828
22925
|
if (this.pollTimer) {
|
|
22829
22926
|
clearTimeout(this.pollTimer);
|
|
22830
22927
|
this.pollTimer = null;
|
|
@@ -22894,21 +22991,18 @@ var init_worker = __esm({
|
|
|
22894
22991
|
if (databaseRunningCount >= this.cfg.maxConcurrency) break;
|
|
22895
22992
|
let task;
|
|
22896
22993
|
try {
|
|
22897
|
-
task = await TaskService.
|
|
22994
|
+
task = await TaskService.claimNext({ excludedBatchIds: [...this.activeBatchIds] });
|
|
22898
22995
|
} catch (err) {
|
|
22899
22996
|
this.logError("task claim failed", err);
|
|
22900
22997
|
throw err;
|
|
22901
22998
|
}
|
|
22902
22999
|
if (!task) break;
|
|
22903
|
-
if (this.stopped) break;
|
|
22904
|
-
if (!await TaskService.start(task.id)) continue;
|
|
22905
|
-
const batchId = normalizeTaskBatchId(task.batchId);
|
|
22906
|
-
if (batchId) this.activeBatchIds.add(batchId);
|
|
22907
23000
|
if (this.stopped) {
|
|
22908
23001
|
await TaskService.resetRunningToPending([task.id]);
|
|
22909
|
-
this.releaseBatch(task);
|
|
22910
23002
|
break;
|
|
22911
23003
|
}
|
|
23004
|
+
const batchId = normalizeTaskBatchId(task.batchId);
|
|
23005
|
+
if (batchId) this.activeBatchIds.add(batchId);
|
|
22912
23006
|
let runId = null;
|
|
22913
23007
|
try {
|
|
22914
23008
|
const launchIdentity = `gateway-${process.pid}:launch:${randomUUID3()}`;
|
|
@@ -23119,7 +23213,7 @@ var init_worker = __esm({
|
|
|
23119
23213
|
if (!entry.guardianDrained) {
|
|
23120
23214
|
const pid = entry.child.pid;
|
|
23121
23215
|
const presence = pid == null ? spawnError == null ? "unknown" : "not-running" : inspectSpawnedProcessTreePresence(pid);
|
|
23122
|
-
const message = "guardian \u672A\u63D0\u4F9B\u8FDB\u7A0B\
|
|
23216
|
+
const message = "guardian \u672A\u63D0\u4F9B\u53D7\u7BA1\u8FDB\u7A0B\u7EC4\u6392\u7A7A\u8BC1\u660E";
|
|
23123
23217
|
if (presence !== "not-running") {
|
|
23124
23218
|
if (entry.timeoutTimer) {
|
|
23125
23219
|
clearTimeout(entry.timeoutTimer);
|
|
@@ -23155,17 +23249,48 @@ var init_worker = __esm({
|
|
|
23155
23249
|
clearTimeout(entry.timeoutTimer);
|
|
23156
23250
|
entry.timeoutTimer = null;
|
|
23157
23251
|
}
|
|
23158
|
-
const settlement = this.
|
|
23159
|
-
markGatewayFailure("worker", error);
|
|
23160
|
-
this.logError("task settlement failed", error, entry.task.id);
|
|
23161
|
-
return false;
|
|
23162
|
-
}).finally(() => {
|
|
23252
|
+
const settlement = this.commitEntryWithRetry(entry, code, failure).finally(() => {
|
|
23163
23253
|
this.runningTasks.delete(entry.task.id);
|
|
23164
23254
|
this.releaseBatch(entry.task);
|
|
23165
23255
|
});
|
|
23166
23256
|
entry.settlementPromise = settlement;
|
|
23167
23257
|
return settlement;
|
|
23168
23258
|
}
|
|
23259
|
+
async commitEntryWithRetry(entry, code, failure) {
|
|
23260
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
23261
|
+
try {
|
|
23262
|
+
await this.commitEntry(entry, code, failure);
|
|
23263
|
+
return true;
|
|
23264
|
+
} catch (error) {
|
|
23265
|
+
markGatewayFailure("worker", error);
|
|
23266
|
+
const shortRetryDelayMs = this.settlementRetryDelaysMs[attempt];
|
|
23267
|
+
let retryDelayMs = shortRetryDelayMs ?? this.settlementRetryIntervalMs;
|
|
23268
|
+
if (this.stopped) {
|
|
23269
|
+
const remainingMs = Math.max(0, (this.shutdownDeadlineMs ?? 0) - Date.now());
|
|
23270
|
+
if (remainingMs === 0) return false;
|
|
23271
|
+
retryDelayMs = Math.min(retryDelayMs, remainingMs);
|
|
23272
|
+
}
|
|
23273
|
+
this.logError(
|
|
23274
|
+
`task settlement failed; retrying in ${retryDelayMs}ms`,
|
|
23275
|
+
error,
|
|
23276
|
+
entry.task.id
|
|
23277
|
+
);
|
|
23278
|
+
await this.waitForSettlementRetry(retryDelayMs);
|
|
23279
|
+
}
|
|
23280
|
+
}
|
|
23281
|
+
}
|
|
23282
|
+
waitForSettlementRetry(delayMs) {
|
|
23283
|
+
return new Promise((resolve6) => {
|
|
23284
|
+
let timer;
|
|
23285
|
+
const finish = () => {
|
|
23286
|
+
clearTimeout(timer);
|
|
23287
|
+
this.settlementRetryWakeups.delete(finish);
|
|
23288
|
+
resolve6();
|
|
23289
|
+
};
|
|
23290
|
+
timer = setTimeout(finish, delayMs);
|
|
23291
|
+
this.settlementRetryWakeups.add(finish);
|
|
23292
|
+
});
|
|
23293
|
+
}
|
|
23169
23294
|
async commitEntry(entry, code, failure) {
|
|
23170
23295
|
const termination = entry.termination;
|
|
23171
23296
|
if (termination?.kind === "shutdown") return;
|
|
@@ -26260,9 +26385,9 @@ async function loadOpenCodeCatalog(cwd, options = {}) {
|
|
|
26260
26385
|
const executable = options.executable ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
26261
26386
|
const timeoutMs = options.timeoutMs ?? COMMAND_TIMEOUT_MS;
|
|
26262
26387
|
const cacheKey = `${executable}\0${cwd}`;
|
|
26263
|
-
const
|
|
26264
|
-
if (options.useCache !== false &&
|
|
26265
|
-
return
|
|
26388
|
+
const cached2 = catalogCache.get(cacheKey);
|
|
26389
|
+
if (options.useCache !== false && cached2 && cached2.expiresAt > Date.now()) {
|
|
26390
|
+
return cached2.result;
|
|
26266
26391
|
}
|
|
26267
26392
|
const result = Promise.all([
|
|
26268
26393
|
runOpenCode(executable, ["models", "--verbose"], cwd, timeoutMs).catch((error) => {
|
|
@@ -26301,6 +26426,109 @@ var init_opencode_catalog = __esm({
|
|
|
26301
26426
|
}
|
|
26302
26427
|
});
|
|
26303
26428
|
|
|
26429
|
+
// src/web/gateway-diagnostic.ts
|
|
26430
|
+
import { spawn as spawn3 } from "child_process";
|
|
26431
|
+
import { existsSync as existsSync8 } from "fs";
|
|
26432
|
+
import { dirname as dirname9, join as join7 } from "path";
|
|
26433
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
26434
|
+
function killDiagnosticGroup(pid) {
|
|
26435
|
+
if (process.platform !== "win32") {
|
|
26436
|
+
try {
|
|
26437
|
+
process.kill(-pid, "SIGKILL");
|
|
26438
|
+
return;
|
|
26439
|
+
} catch {
|
|
26440
|
+
}
|
|
26441
|
+
}
|
|
26442
|
+
try {
|
|
26443
|
+
process.kill(pid, "SIGKILL");
|
|
26444
|
+
} catch {
|
|
26445
|
+
}
|
|
26446
|
+
}
|
|
26447
|
+
function diagnosticEntry() {
|
|
26448
|
+
const extension = import.meta.url.endsWith(".ts") ? "ts" : "js";
|
|
26449
|
+
const moduleDir = dirname9(fileURLToPath6(import.meta.url));
|
|
26450
|
+
const candidates = [
|
|
26451
|
+
join7(moduleDir, `../daemon/gateway-diagnostic-runner.${extension}`),
|
|
26452
|
+
join7(moduleDir, `../../src/daemon/gateway-diagnostic-runner.${extension}`)
|
|
26453
|
+
];
|
|
26454
|
+
const entry = candidates.find((candidate) => existsSync8(candidate));
|
|
26455
|
+
if (!entry) throw new Error(`Gateway diagnostic runner \u4E0D\u5B58\u5728\uFF1A${candidates.join(", ")}`);
|
|
26456
|
+
return entry;
|
|
26457
|
+
}
|
|
26458
|
+
async function runGatewayDiagnostic() {
|
|
26459
|
+
return new Promise((resolve6, reject) => {
|
|
26460
|
+
const child = spawn3(process.execPath, [diagnosticEntry()], {
|
|
26461
|
+
cwd: process.cwd(),
|
|
26462
|
+
env: process.env,
|
|
26463
|
+
detached: process.platform !== "win32",
|
|
26464
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
26465
|
+
});
|
|
26466
|
+
let stdout = "";
|
|
26467
|
+
let stderr = "";
|
|
26468
|
+
let timedOut = false;
|
|
26469
|
+
if (child.pid) activeProcessGroups.add(child.pid);
|
|
26470
|
+
child.stdout?.on("data", (chunk) => {
|
|
26471
|
+
stdout += chunk.toString();
|
|
26472
|
+
});
|
|
26473
|
+
child.stderr?.on("data", (chunk) => {
|
|
26474
|
+
stderr += chunk.toString();
|
|
26475
|
+
});
|
|
26476
|
+
child.once("error", reject);
|
|
26477
|
+
child.once("close", (exitCode) => {
|
|
26478
|
+
clearTimeout(timer);
|
|
26479
|
+
if (child.pid) {
|
|
26480
|
+
killDiagnosticGroup(child.pid);
|
|
26481
|
+
activeProcessGroups.delete(child.pid);
|
|
26482
|
+
}
|
|
26483
|
+
if (timedOut) {
|
|
26484
|
+
reject(new Error(`Gateway diagnostic runner \u8D85\u8FC7 ${DIAGNOSTIC_TIMEOUT_MS}ms \u672A\u5B8C\u6210`));
|
|
26485
|
+
return;
|
|
26486
|
+
}
|
|
26487
|
+
if (exitCode !== 0) {
|
|
26488
|
+
reject(new Error(stderr.trim() || `Gateway diagnostic runner \u9000\u51FA\u7801 ${exitCode}`));
|
|
26489
|
+
return;
|
|
26490
|
+
}
|
|
26491
|
+
try {
|
|
26492
|
+
resolve6(JSON.parse(stdout));
|
|
26493
|
+
} catch (error) {
|
|
26494
|
+
reject(error);
|
|
26495
|
+
}
|
|
26496
|
+
});
|
|
26497
|
+
const timer = setTimeout(() => {
|
|
26498
|
+
timedOut = true;
|
|
26499
|
+
if (child.pid) killDiagnosticGroup(child.pid);
|
|
26500
|
+
else child.kill("SIGKILL");
|
|
26501
|
+
}, DIAGNOSTIC_TIMEOUT_MS);
|
|
26502
|
+
});
|
|
26503
|
+
}
|
|
26504
|
+
async function getDashboardGatewayDiagnostic(options = {}) {
|
|
26505
|
+
if (!options.fresh && cached && cached.expiresAt > Date.now()) return cached.diagnostic;
|
|
26506
|
+
if (!options.fresh && pending) return pending;
|
|
26507
|
+
const operation = runGatewayDiagnostic().then((diagnostic) => {
|
|
26508
|
+
cached = { expiresAt: Date.now() + CACHE_TTL_MS, diagnostic };
|
|
26509
|
+
return diagnostic;
|
|
26510
|
+
});
|
|
26511
|
+
if (options.fresh) return operation;
|
|
26512
|
+
pending = operation.finally(() => {
|
|
26513
|
+
pending = null;
|
|
26514
|
+
});
|
|
26515
|
+
return pending;
|
|
26516
|
+
}
|
|
26517
|
+
var CACHE_TTL_MS, DIAGNOSTIC_TIMEOUT_MS, cached, pending, activeProcessGroups;
|
|
26518
|
+
var init_gateway_diagnostic = __esm({
|
|
26519
|
+
"src/web/gateway-diagnostic.ts"() {
|
|
26520
|
+
"use strict";
|
|
26521
|
+
CACHE_TTL_MS = 5e3;
|
|
26522
|
+
DIAGNOSTIC_TIMEOUT_MS = 2e4;
|
|
26523
|
+
cached = null;
|
|
26524
|
+
pending = null;
|
|
26525
|
+
activeProcessGroups = /* @__PURE__ */ new Set();
|
|
26526
|
+
process.once("exit", () => {
|
|
26527
|
+
for (const pid of activeProcessGroups) killDiagnosticGroup(pid);
|
|
26528
|
+
});
|
|
26529
|
+
}
|
|
26530
|
+
});
|
|
26531
|
+
|
|
26304
26532
|
// src/web/ui.ts
|
|
26305
26533
|
function t(locale, key, values = {}) {
|
|
26306
26534
|
const template = (locale === "en" ? EN : ZH)[key];
|
|
@@ -26966,7 +27194,7 @@ var init_ui = __esm({
|
|
|
26966
27194
|
"details.enabledYes": "\u5DF2\u542F\u7528",
|
|
26967
27195
|
"details.enabledNo": "\u5DF2\u505C\u7528",
|
|
26968
27196
|
"dialog.cancelTask": "\u53D6\u6D88\u4EFB\u52A1 #{id}\uFF1F",
|
|
26969
|
-
"dialog.cancelTaskBody": "\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u4F1A\u5728\u4E0B\u4E00\u4E2A\u8F6E\u8BE2\u5468\u671F\u7EC8\u6B62\u5BF9\u5E94\u8FDB\u7A0B\
|
|
27197
|
+
"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",
|
|
26970
27198
|
"dialog.retryTask": "\u91CD\u8BD5\u4EFB\u52A1 #{id}\uFF1F",
|
|
26971
27199
|
"dialog.retryTaskBody": "\u4EFB\u52A1\u5C06\u56DE\u5230\u5F85\u6267\u884C\u72B6\u6001\uFF0C\u5E76\u91CD\u7F6E\u81EA\u52A8\u91CD\u8BD5\u9884\u7B97\u3002",
|
|
26972
27200
|
"dialog.deleteTask": "\u5220\u9664\u4EFB\u52A1 #{id}\uFF1F",
|
|
@@ -27271,7 +27499,7 @@ var init_ui = __esm({
|
|
|
27271
27499
|
"details.enabledYes": "Enabled",
|
|
27272
27500
|
"details.enabledNo": "Disabled",
|
|
27273
27501
|
"dialog.cancelTask": "Cancel task #{id}?",
|
|
27274
|
-
"dialog.cancelTaskBody": "A running task will terminate its process
|
|
27502
|
+
"dialog.cancelTaskBody": "A running task will terminate its managed process group on the next worker poll.",
|
|
27275
27503
|
"dialog.retryTask": "Retry task #{id}?",
|
|
27276
27504
|
"dialog.retryTaskBody": "The task returns to pending and its automatic retry budget is reset.",
|
|
27277
27505
|
"dialog.deleteTask": "Delete task #{id}?",
|
|
@@ -27692,7 +27920,7 @@ __export(web_exports, {
|
|
|
27692
27920
|
setDashboardRuntimeConfig: () => setDashboardRuntimeConfig
|
|
27693
27921
|
});
|
|
27694
27922
|
import {
|
|
27695
|
-
existsSync as
|
|
27923
|
+
existsSync as existsSync9,
|
|
27696
27924
|
mkdirSync as mkdirSync5,
|
|
27697
27925
|
readFileSync as readFileSync5,
|
|
27698
27926
|
readdirSync as readdirSync2,
|
|
@@ -27701,7 +27929,7 @@ import {
|
|
|
27701
27929
|
writeFileSync as writeFileSync3
|
|
27702
27930
|
} from "fs";
|
|
27703
27931
|
import { homedir as homedir5 } from "os";
|
|
27704
|
-
import { basename as basename3, dirname as
|
|
27932
|
+
import { basename as basename3, dirname as dirname10, join as join8 } from "path";
|
|
27705
27933
|
function setDashboardRuntimeConfig(config) {
|
|
27706
27934
|
runtimeConfig = config === null ? null : structuredClone(config);
|
|
27707
27935
|
}
|
|
@@ -27726,10 +27954,13 @@ function resolveDashboardConfigState(runtimeAvailable, restartRequired, managedR
|
|
|
27726
27954
|
function isSafeDashboardRestartTarget(diagnostic, currentPid) {
|
|
27727
27955
|
return diagnostic.pm2Installed && diagnostic.processFound && diagnostic.status === "online" && diagnostic.pid === currentPid && diagnostic.ready && diagnostic.scopeMatches;
|
|
27728
27956
|
}
|
|
27729
|
-
function canRestartCurrentGateway() {
|
|
27957
|
+
async function canRestartCurrentGateway(fresh = false) {
|
|
27730
27958
|
if (runtimeConfig === null) return false;
|
|
27731
27959
|
try {
|
|
27732
|
-
return isSafeDashboardRestartTarget(
|
|
27960
|
+
return isSafeDashboardRestartTarget(
|
|
27961
|
+
await getDashboardGatewayDiagnostic({ fresh }),
|
|
27962
|
+
process.pid
|
|
27963
|
+
);
|
|
27733
27964
|
} catch {
|
|
27734
27965
|
return false;
|
|
27735
27966
|
}
|
|
@@ -27744,7 +27975,7 @@ function parseTaskStatus2(value) {
|
|
|
27744
27975
|
}
|
|
27745
27976
|
function listChildDirectories(path) {
|
|
27746
27977
|
return readdirSync2(path, { withFileTypes: true }).flatMap((entry) => {
|
|
27747
|
-
const entryPath =
|
|
27978
|
+
const entryPath = join8(path, entry.name);
|
|
27748
27979
|
if (entry.isDirectory()) return [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }];
|
|
27749
27980
|
if (!entry.isSymbolicLink()) return [];
|
|
27750
27981
|
try {
|
|
@@ -27984,7 +28215,7 @@ function esc(value) {
|
|
|
27984
28215
|
}
|
|
27985
28216
|
function readCurrentConfig() {
|
|
27986
28217
|
const configPath = getConfigPath();
|
|
27987
|
-
if (!
|
|
28218
|
+
if (!existsSync9(configPath)) return {};
|
|
27988
28219
|
try {
|
|
27989
28220
|
return JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
27990
28221
|
} catch {
|
|
@@ -27993,8 +28224,8 @@ function readCurrentConfig() {
|
|
|
27993
28224
|
}
|
|
27994
28225
|
function writeConfig(cfg) {
|
|
27995
28226
|
const configPath = getConfigPath();
|
|
27996
|
-
const dir =
|
|
27997
|
-
if (!
|
|
28227
|
+
const dir = dirname10(configPath);
|
|
28228
|
+
if (!existsSync9(dir)) mkdirSync5(dir, { recursive: true });
|
|
27998
28229
|
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
27999
28230
|
writeFileSync3(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
28000
28231
|
renameSync2(tempPath, configPath);
|
|
@@ -28048,7 +28279,7 @@ function pagination(locale, basePath, page, pages, total, suffix = "") {
|
|
|
28048
28279
|
const next = page < pages ? `<a class="btn" href="${basePath}?page=${page + 1}${suffix}">${t(locale, "pagination.next")}${icon("chevronRight")}</a>` : "";
|
|
28049
28280
|
return `<div class="pagination">${previous}<span class="summary">${t(locale, "pagination.summary", { page, pages, total })}</span>${next}</div>`;
|
|
28050
28281
|
}
|
|
28051
|
-
var app, LEGACY_PROJECT_FILTER, TASK_STATUSES2, SESSION_ID_PATTERN, runtimeConfig, restartScheduled, dashboardApp, web_default;
|
|
28282
|
+
var app, LEGACY_PROJECT_FILTER, TASK_STATUSES2, SESSION_ID_PATTERN, LOOPBACK_HOST_PATTERN, runtimeConfig, restartScheduled, dashboardApp, web_default;
|
|
28052
28283
|
var init_web = __esm({
|
|
28053
28284
|
"src/web/index.tsx"() {
|
|
28054
28285
|
"use strict";
|
|
@@ -28065,7 +28296,7 @@ var init_web = __esm({
|
|
|
28065
28296
|
init_config();
|
|
28066
28297
|
init_health();
|
|
28067
28298
|
init_job_templates();
|
|
28068
|
-
|
|
28299
|
+
init_gateway_diagnostic();
|
|
28069
28300
|
init_ui();
|
|
28070
28301
|
app = new Hono2();
|
|
28071
28302
|
LEGACY_PROJECT_FILTER = "__supertask_legacy__";
|
|
@@ -28078,9 +28309,18 @@ var init_web = __esm({
|
|
|
28078
28309
|
"cancelled"
|
|
28079
28310
|
]);
|
|
28080
28311
|
SESSION_ID_PATTERN = /^ses_[A-Za-z0-9_]+$/;
|
|
28312
|
+
LOOPBACK_HOST_PATTERN = /^(?:localhost|127\.0\.0\.1|\[::1\])(?::([1-9]\d{0,4}))?$/i;
|
|
28081
28313
|
runtimeConfig = null;
|
|
28082
28314
|
restartScheduled = false;
|
|
28083
28315
|
app.use("*", async (c, next) => {
|
|
28316
|
+
const requestHostname = new URL(c.req.url).hostname;
|
|
28317
|
+
const hostHeader = c.req.header("Host");
|
|
28318
|
+
const loopbackHosts = ["localhost", "127.0.0.1", "[::1]"];
|
|
28319
|
+
const hostMatch = hostHeader?.match(LOOPBACK_HOST_PATTERN) ?? null;
|
|
28320
|
+
const hostPort = hostMatch?.[1] === void 0 ? null : Number(hostMatch[1]);
|
|
28321
|
+
if (!loopbackHosts.includes(requestHostname) || hostHeader !== void 0 && (!hostMatch || hostPort !== null && hostPort > 65535)) {
|
|
28322
|
+
return c.json({ error: "invalid dashboard host" }, 421);
|
|
28323
|
+
}
|
|
28084
28324
|
await next();
|
|
28085
28325
|
c.header("X-Content-Type-Options", "nosniff");
|
|
28086
28326
|
c.header("X-Frame-Options", "DENY");
|
|
@@ -28117,7 +28357,7 @@ var init_web = __esm({
|
|
|
28117
28357
|
validateTaskWorkingDirectory(requestedPath);
|
|
28118
28358
|
return c.json({
|
|
28119
28359
|
path: requestedPath,
|
|
28120
|
-
parent:
|
|
28360
|
+
parent: dirname10(requestedPath),
|
|
28121
28361
|
home: homedir5(),
|
|
28122
28362
|
directories: listChildDirectories(requestedPath)
|
|
28123
28363
|
});
|
|
@@ -28452,7 +28692,7 @@ var init_web = __esm({
|
|
|
28452
28692
|
const config = loadConfig();
|
|
28453
28693
|
const activeConfig = runtimeConfig ?? config;
|
|
28454
28694
|
const restartRequired = runtimeConfig !== null && !configsEqual(config, runtimeConfig);
|
|
28455
|
-
const managedRestart = canRestartCurrentGateway();
|
|
28695
|
+
const managedRestart = await canRestartCurrentGateway();
|
|
28456
28696
|
const configState = resolveDashboardConfigState(
|
|
28457
28697
|
runtimeConfig !== null,
|
|
28458
28698
|
restartRequired,
|
|
@@ -28471,7 +28711,7 @@ var init_web = __esm({
|
|
|
28471
28711
|
TaskRunService.getAllRunningRuns(),
|
|
28472
28712
|
TaskTemplateService.stats()
|
|
28473
28713
|
]);
|
|
28474
|
-
const configExists =
|
|
28714
|
+
const configExists = existsSync9(configPath);
|
|
28475
28715
|
const runRows = runningRuns.map((run) => {
|
|
28476
28716
|
const session = maskSessionId(run.sessionId);
|
|
28477
28717
|
return `<tr><td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td><td data-primary data-label="${t(locale, "table.task")}">#${run.taskId}</td><td data-label="${t(locale, "table.session")}" class="m small">${esc(session)}</td>
|
|
@@ -28572,9 +28812,9 @@ var init_web = __esm({
|
|
|
28572
28812
|
if (!isValidSessionId(run.sessionId)) return c.json({ error: "session unavailable" }, 409);
|
|
28573
28813
|
return c.json({ command: sessionCommand(run.sessionId) });
|
|
28574
28814
|
});
|
|
28575
|
-
app.get("/api/gateway/status", (c) => {
|
|
28815
|
+
app.get("/api/gateway/status", async (c) => {
|
|
28576
28816
|
const savedConfig = loadConfig();
|
|
28577
|
-
const managed = canRestartCurrentGateway();
|
|
28817
|
+
const managed = await canRestartCurrentGateway();
|
|
28578
28818
|
return c.json({
|
|
28579
28819
|
pid: process.pid,
|
|
28580
28820
|
managed,
|
|
@@ -28699,7 +28939,7 @@ var init_web = __esm({
|
|
|
28699
28939
|
return c.json({
|
|
28700
28940
|
success: true,
|
|
28701
28941
|
restartRequired: runtimeConfig === null || !configsEqual(savedConfig, runtimeConfig),
|
|
28702
|
-
managed: canRestartCurrentGateway()
|
|
28942
|
+
managed: await canRestartCurrentGateway()
|
|
28703
28943
|
});
|
|
28704
28944
|
} catch (error) {
|
|
28705
28945
|
return c.json({
|
|
@@ -28715,9 +28955,10 @@ var init_web = __esm({
|
|
|
28715
28955
|
return c.json({ error: "confirmation must be RESTART" }, 400);
|
|
28716
28956
|
}
|
|
28717
28957
|
if (restartScheduled) return c.json({ error: "restart already scheduled" }, 409);
|
|
28718
|
-
if (!canRestartCurrentGateway()) {
|
|
28958
|
+
if (!await canRestartCurrentGateway(true)) {
|
|
28719
28959
|
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);
|
|
28720
28960
|
}
|
|
28961
|
+
if (restartScheduled) return c.json({ error: "restart already scheduled" }, 409);
|
|
28721
28962
|
restartScheduled = true;
|
|
28722
28963
|
const previousPid = process.pid;
|
|
28723
28964
|
setTimeout(() => {
|
|
@@ -29686,13 +29927,13 @@ databaseCommand.command("restore").description(t2("\u81EA\u52A8\u5907\u4EFD\u5F5
|
|
|
29686
29927
|
}, (error) => renderDatabaseError(error, { forceJson: options.json })));
|
|
29687
29928
|
program2.addCommand(databaseCommand);
|
|
29688
29929
|
program2.command("init").description(t2("\u521D\u59CB\u5316 SuperTask\uFF08\u521B\u5EFA\u914D\u7F6E\u5E76\u6267\u884C\u8FC1\u79FB\uFF09", "initialize SuperTask (create config and run migrations)")).action(async () => withDb(async () => {
|
|
29689
|
-
const { existsSync:
|
|
29690
|
-
const { dirname:
|
|
29930
|
+
const { existsSync: existsSync10, mkdirSync: mkdirSync6, writeFileSync: writeFileSync4 } = await import("fs");
|
|
29931
|
+
const { dirname: dirname11 } = await import("path");
|
|
29691
29932
|
const { getConfigPath: getConfigPath2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
29692
29933
|
const configPath = getConfigPath2();
|
|
29693
|
-
if (!
|
|
29694
|
-
const dir =
|
|
29695
|
-
if (!
|
|
29934
|
+
if (!existsSync10(configPath)) {
|
|
29935
|
+
const dir = dirname11(configPath);
|
|
29936
|
+
if (!existsSync10(dir)) mkdirSync6(dir, { recursive: true });
|
|
29696
29937
|
writeFileSync4(configPath, JSON.stringify({
|
|
29697
29938
|
configVersion: 2,
|
|
29698
29939
|
worker: { maxConcurrency: 2 },
|