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/web/index.js
CHANGED
|
@@ -9100,8 +9100,8 @@ var require_CronFileParser = __commonJS({
|
|
|
9100
9100
|
* @throws If file cannot be read
|
|
9101
9101
|
*/
|
|
9102
9102
|
static parseFileSync(filePath) {
|
|
9103
|
-
const { readFileSync:
|
|
9104
|
-
const data =
|
|
9103
|
+
const { readFileSync: readFileSync3 } = __require("fs");
|
|
9104
|
+
const data = readFileSync3(filePath, "utf8");
|
|
9105
9105
|
return _CronFileParser.#parseContent(data);
|
|
9106
9106
|
}
|
|
9107
9107
|
/**
|
|
@@ -13085,14 +13085,14 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
|
|
|
13085
13085
|
// src/web/index.tsx
|
|
13086
13086
|
import {
|
|
13087
13087
|
existsSync as existsSync5,
|
|
13088
|
-
mkdirSync as
|
|
13089
|
-
readFileSync as
|
|
13088
|
+
mkdirSync as mkdirSync3,
|
|
13089
|
+
readFileSync as readFileSync2,
|
|
13090
13090
|
readdirSync,
|
|
13091
13091
|
renameSync as renameSync2,
|
|
13092
|
-
statSync as
|
|
13093
|
-
writeFileSync as
|
|
13092
|
+
statSync as statSync3,
|
|
13093
|
+
writeFileSync as writeFileSync2
|
|
13094
13094
|
} from "fs";
|
|
13095
|
-
import { homedir as
|
|
13095
|
+
import { homedir as homedir3 } from "os";
|
|
13096
13096
|
import { basename as basename2, dirname as dirname4, join as join4 } from "path";
|
|
13097
13097
|
|
|
13098
13098
|
// src/core/db/index.ts
|
|
@@ -16502,8 +16502,8 @@ var _sqlite = null;
|
|
|
16502
16502
|
var _db = null;
|
|
16503
16503
|
var _migrationRan = false;
|
|
16504
16504
|
function getMigrationsFolder() {
|
|
16505
|
-
const
|
|
16506
|
-
let dir =
|
|
16505
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16506
|
+
let dir = __dirname;
|
|
16507
16507
|
for (let i = 0; i < 5; i++) {
|
|
16508
16508
|
const candidate = join(dir, "drizzle", "meta", "_journal.json");
|
|
16509
16509
|
if (existsSync(candidate)) {
|
|
@@ -16511,7 +16511,7 @@ function getMigrationsFolder() {
|
|
|
16511
16511
|
}
|
|
16512
16512
|
dir = dirname(dir);
|
|
16513
16513
|
}
|
|
16514
|
-
return join(
|
|
16514
|
+
return join(__dirname, "../../drizzle");
|
|
16515
16515
|
}
|
|
16516
16516
|
function ensureGatewayLock(sqliteDb) {
|
|
16517
16517
|
sqliteDb.exec(`
|
|
@@ -16667,7 +16667,7 @@ function cleanOutput(value) {
|
|
|
16667
16667
|
return value.replace(ANSI_PATTERN, "").replace(/\r/g, "");
|
|
16668
16668
|
}
|
|
16669
16669
|
function runOpenCode(executable, args, cwd, timeoutMs) {
|
|
16670
|
-
return new Promise((
|
|
16670
|
+
return new Promise((resolve2, reject) => {
|
|
16671
16671
|
const child = spawn(executable, args, {
|
|
16672
16672
|
cwd,
|
|
16673
16673
|
env: process.env,
|
|
@@ -16745,7 +16745,7 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
|
|
|
16745
16745
|
reject(new OpenCodeCommandExitError(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
|
|
16746
16746
|
return;
|
|
16747
16747
|
}
|
|
16748
|
-
|
|
16748
|
+
resolve2(cleanOutput(stdout));
|
|
16749
16749
|
});
|
|
16750
16750
|
});
|
|
16751
16751
|
}
|
|
@@ -16816,9 +16816,9 @@ async function loadOpenCodeCatalog(cwd, options = {}) {
|
|
|
16816
16816
|
const executable = options.executable ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
16817
16817
|
const timeoutMs = options.timeoutMs ?? COMMAND_TIMEOUT_MS;
|
|
16818
16818
|
const cacheKey = `${executable}\0${cwd}`;
|
|
16819
|
-
const
|
|
16820
|
-
if (options.useCache !== false &&
|
|
16821
|
-
return
|
|
16819
|
+
const cached2 = catalogCache.get(cacheKey);
|
|
16820
|
+
if (options.useCache !== false && cached2 && cached2.expiresAt > Date.now()) {
|
|
16821
|
+
return cached2.result;
|
|
16822
16822
|
}
|
|
16823
16823
|
const result = Promise.all([
|
|
16824
16824
|
runOpenCode(executable, ["models", "--verbose"], cwd, timeoutMs).catch((error) => {
|
|
@@ -16859,6 +16859,59 @@ import { tmpdir } from "os";
|
|
|
16859
16859
|
import { basename, dirname as dirname2, resolve } from "path";
|
|
16860
16860
|
|
|
16861
16861
|
// src/core/process-control.ts
|
|
16862
|
+
var OS_COMMAND_TIMEOUT_MS = 2e3;
|
|
16863
|
+
var OS_COMMAND_MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
16864
|
+
var OS_COMMAND_RESULT_MARKER = "\n\0supertask-os-command-result:";
|
|
16865
|
+
var BOUNDED_OS_COMMAND_RUNNER = `
|
|
16866
|
+
const { writeSync } = await import('fs');
|
|
16867
|
+
let child;
|
|
16868
|
+
try {
|
|
16869
|
+
child = Bun.spawn(process.argv.slice(2), {
|
|
16870
|
+
stdin: 'ignore',
|
|
16871
|
+
stdout: 'pipe',
|
|
16872
|
+
stderr: 'ignore',
|
|
16873
|
+
});
|
|
16874
|
+
} catch {
|
|
16875
|
+
process.exit(127);
|
|
16876
|
+
}
|
|
16877
|
+
const terminateGroup = () => {
|
|
16878
|
+
if (process.platform !== 'win32') {
|
|
16879
|
+
try {
|
|
16880
|
+
process.kill(-process.pid, 'SIGKILL');
|
|
16881
|
+
} catch {
|
|
16882
|
+
}
|
|
16883
|
+
}
|
|
16884
|
+
try {
|
|
16885
|
+
child.kill('SIGKILL');
|
|
16886
|
+
} catch {
|
|
16887
|
+
}
|
|
16888
|
+
process.exit(1);
|
|
16889
|
+
};
|
|
16890
|
+
const timer = setTimeout(terminateGroup, ${OS_COMMAND_TIMEOUT_MS});
|
|
16891
|
+
try {
|
|
16892
|
+
const chunks = [];
|
|
16893
|
+
let outputBytes = 0;
|
|
16894
|
+
const readOutput = async () => {
|
|
16895
|
+
const reader = child.stdout.getReader();
|
|
16896
|
+
while (true) {
|
|
16897
|
+
const { done, value } = await reader.read();
|
|
16898
|
+
if (done) return;
|
|
16899
|
+
outputBytes += value.byteLength;
|
|
16900
|
+
if (outputBytes > ${OS_COMMAND_MAX_OUTPUT_BYTES}) terminateGroup();
|
|
16901
|
+
chunks.push(value);
|
|
16902
|
+
}
|
|
16903
|
+
};
|
|
16904
|
+
const [exitCode] = await Promise.all([child.exited, readOutput()]);
|
|
16905
|
+
clearTimeout(timer);
|
|
16906
|
+
for (const chunk of chunks) writeSync(1, chunk);
|
|
16907
|
+
writeSync(1, ${JSON.stringify(OS_COMMAND_RESULT_MARKER)} + String(exitCode));
|
|
16908
|
+
if (process.platform !== 'win32') terminateGroup();
|
|
16909
|
+
process.exit(0);
|
|
16910
|
+
} catch {
|
|
16911
|
+
clearTimeout(timer);
|
|
16912
|
+
terminateGroup();
|
|
16913
|
+
}
|
|
16914
|
+
`;
|
|
16862
16915
|
function isProcessAlive(pid) {
|
|
16863
16916
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
16864
16917
|
try {
|
|
@@ -17425,7 +17478,7 @@ var TaskRunService = class {
|
|
|
17425
17478
|
}
|
|
17426
17479
|
if (current.childPid != null) {
|
|
17427
17480
|
throw new LegacyRunAbandonConflictError(
|
|
17428
|
-
`run #${runId} \u5DF2\u8BB0\u5F55 child PID ${current.childPid}\uFF0C\u5FC5\u987B\u7531 Worker/Watchdog \u786E\u8BA4\u8FDB\u7A0B\
|
|
17481
|
+
`run #${runId} \u5DF2\u8BB0\u5F55 child PID ${current.childPid}\uFF0C\u5FC5\u987B\u7531 Worker/Watchdog \u786E\u8BA4\u53D7\u7BA1\u8FDB\u7A0B\u7EC4\u6392\u7A7A`
|
|
17429
17482
|
);
|
|
17430
17483
|
}
|
|
17431
17484
|
if (current.workerPid != null && isProcessAlive(current.workerPid)) {
|
|
@@ -17623,13 +17676,23 @@ var TaskService = class {
|
|
|
17623
17676
|
});
|
|
17624
17677
|
const maxRetries = normalizedData.maxRetries ?? task.maxRetries ?? 3;
|
|
17625
17678
|
const exhausted = task.status === "failed" && (task.retryCount ?? 0) > maxRetries;
|
|
17626
|
-
|
|
17679
|
+
const updated = tx.update(tasks3).set({
|
|
17627
17680
|
...normalizedData,
|
|
17628
17681
|
...exhausted ? {
|
|
17629
17682
|
status: "dead_letter",
|
|
17630
17683
|
retryAfter: null
|
|
17631
17684
|
} : {}
|
|
17632
17685
|
}).where(eq(tasks3.id, id)).returning().get() ?? null;
|
|
17686
|
+
if (exhausted && updated) {
|
|
17687
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
17688
|
+
tx.update(tasks3).set({
|
|
17689
|
+
status: "dead_letter",
|
|
17690
|
+
finishedAt,
|
|
17691
|
+
retryAfter: null,
|
|
17692
|
+
resultLog: `\u4F9D\u8D56\u4EFB\u52A1 #${id} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`
|
|
17693
|
+
}).where(blockedDependentsOf(id)).run();
|
|
17694
|
+
}
|
|
17695
|
+
return updated;
|
|
17633
17696
|
}, { behavior: "immediate" });
|
|
17634
17697
|
}
|
|
17635
17698
|
static validateNewTask(data) {
|
|
@@ -17650,7 +17713,7 @@ var TaskService = class {
|
|
|
17650
17713
|
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
17651
17714
|
}
|
|
17652
17715
|
}
|
|
17653
|
-
static
|
|
17716
|
+
static buildRunnableTaskWhere(scope) {
|
|
17654
17717
|
const baseConditions = [...this.buildScopeWhere(scope)];
|
|
17655
17718
|
const nowMs = Date.now();
|
|
17656
17719
|
const retryAfterFilter = or(
|
|
@@ -17685,40 +17748,43 @@ var TaskService = class {
|
|
|
17685
17748
|
if (batchFilter) {
|
|
17686
17749
|
conditions.push(batchFilter);
|
|
17687
17750
|
}
|
|
17688
|
-
|
|
17751
|
+
return and(
|
|
17689
17752
|
...conditions,
|
|
17690
17753
|
or(
|
|
17691
17754
|
isNull(tasks3.dependsOn),
|
|
17692
17755
|
sql`EXISTS (
|
|
17693
|
-
|
|
17694
|
-
|
|
17695
|
-
|
|
17696
|
-
|
|
17697
|
-
|
|
17756
|
+
SELECT 1 FROM tasks AS dependency_task
|
|
17757
|
+
WHERE dependency_task.id = ${tasks3.dependsOn}
|
|
17758
|
+
AND dependency_task.status = 'done'
|
|
17759
|
+
AND dependency_task.cwd IS ${tasks3.cwd}
|
|
17760
|
+
)`
|
|
17698
17761
|
),
|
|
17699
17762
|
or(
|
|
17700
17763
|
isNull(tasks3.batchId),
|
|
17701
17764
|
sql`trim(${tasks3.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ''`,
|
|
17702
17765
|
sql`NOT EXISTS (
|
|
17703
|
-
|
|
17704
|
-
|
|
17705
|
-
|
|
17706
|
-
|
|
17707
|
-
|
|
17708
|
-
|
|
17709
|
-
|
|
17710
|
-
|
|
17711
|
-
|
|
17712
|
-
)
|
|
17766
|
+
SELECT 1 FROM tasks AS running_batch_task
|
|
17767
|
+
WHERE trim(running_batch_task.batch_id, ${TASK_BATCH_TRIM_CHARACTERS})
|
|
17768
|
+
= trim(${tasks3.batchId}, ${TASK_BATCH_TRIM_CHARACTERS})
|
|
17769
|
+
AND (
|
|
17770
|
+
running_batch_task.status = 'running'
|
|
17771
|
+
OR EXISTS (
|
|
17772
|
+
SELECT 1 FROM task_runs AS running_batch_run
|
|
17773
|
+
WHERE running_batch_run.task_id = running_batch_task.id
|
|
17774
|
+
AND running_batch_run.status = 'running'
|
|
17713
17775
|
)
|
|
17714
|
-
|
|
17776
|
+
)
|
|
17777
|
+
)`
|
|
17715
17778
|
),
|
|
17716
17779
|
sql`NOT EXISTS (
|
|
17717
|
-
|
|
17718
|
-
|
|
17719
|
-
|
|
17720
|
-
|
|
17721
|
-
)
|
|
17780
|
+
SELECT 1 FROM task_runs AS candidate_active_run
|
|
17781
|
+
WHERE candidate_active_run.task_id = ${tasks3.id}
|
|
17782
|
+
AND candidate_active_run.status = 'running'
|
|
17783
|
+
)`
|
|
17784
|
+
);
|
|
17785
|
+
}
|
|
17786
|
+
static async next(scope = {}) {
|
|
17787
|
+
const result = await db.select().from(tasks3).where(this.buildRunnableTaskWhere(scope)).orderBy(
|
|
17722
17788
|
desc(tasks3.urgency),
|
|
17723
17789
|
desc(tasks3.importance),
|
|
17724
17790
|
asc(tasks3.createdAt),
|
|
@@ -17726,6 +17792,22 @@ var TaskService = class {
|
|
|
17726
17792
|
).limit(1);
|
|
17727
17793
|
return result[0] ?? null;
|
|
17728
17794
|
}
|
|
17795
|
+
static async claimNext(scope = {}) {
|
|
17796
|
+
return db.transaction((tx) => {
|
|
17797
|
+
const candidate = tx.select().from(tasks3).where(this.buildRunnableTaskWhere(scope)).orderBy(
|
|
17798
|
+
desc(tasks3.urgency),
|
|
17799
|
+
desc(tasks3.importance),
|
|
17800
|
+
asc(tasks3.createdAt),
|
|
17801
|
+
asc(tasks3.id)
|
|
17802
|
+
).limit(1).get();
|
|
17803
|
+
if (!candidate) return null;
|
|
17804
|
+
return tx.update(tasks3).set({
|
|
17805
|
+
status: "running",
|
|
17806
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
17807
|
+
finishedAt: null
|
|
17808
|
+
}).where(eq(tasks3.id, candidate.id)).returning().get() ?? null;
|
|
17809
|
+
}, { behavior: "immediate" });
|
|
17810
|
+
}
|
|
17729
17811
|
static async countRunning(scope = {}) {
|
|
17730
17812
|
const scopeConditions = scope.legacyCwd ? [sql`${tasks3.cwd} IS NULL OR trim(${tasks3.cwd}) = ''`] : this.buildScopeWhere(scope);
|
|
17731
17813
|
if (scope.batchId !== void 0) {
|
|
@@ -18818,452 +18900,101 @@ function createTaskFromTemplate(templateId, options) {
|
|
|
18818
18900
|
}, { behavior: "immediate" });
|
|
18819
18901
|
}
|
|
18820
18902
|
|
|
18821
|
-
// src/
|
|
18822
|
-
import {
|
|
18823
|
-
import {
|
|
18824
|
-
|
|
18825
|
-
chmodSync as chmodSync2,
|
|
18826
|
-
constants,
|
|
18827
|
-
existsSync as existsSync4,
|
|
18828
|
-
mkdirSync as mkdirSync3,
|
|
18829
|
-
readFileSync as readFileSync2,
|
|
18830
|
-
rmSync,
|
|
18831
|
-
statSync as statSync3,
|
|
18832
|
-
writeFileSync as writeFileSync2
|
|
18833
|
-
} from "fs";
|
|
18834
|
-
import { homedir as homedir3, userInfo } from "os";
|
|
18835
|
-
import { delimiter, dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "path";
|
|
18903
|
+
// src/web/gateway-diagnostic.ts
|
|
18904
|
+
import { spawn as spawn2 } from "child_process";
|
|
18905
|
+
import { existsSync as existsSync4 } from "fs";
|
|
18906
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
18836
18907
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18837
|
-
|
|
18838
|
-
|
|
18839
|
-
|
|
18840
|
-
|
|
18841
|
-
|
|
18842
|
-
|
|
18843
|
-
|
|
18844
|
-
var PROCESS_NAME = "supertask-gateway";
|
|
18845
|
-
var MAC_LAUNCH_AGENT_LABEL = "com.supertask.pm2-resurrect";
|
|
18846
|
-
var GATEWAY_LOCK_STALE_MS = 3e4;
|
|
18847
|
-
function runtimeHome(env) {
|
|
18848
|
-
return resolve2(env.HOME || homedir3());
|
|
18849
|
-
}
|
|
18850
|
-
function runtimePath(value, cwd) {
|
|
18851
|
-
return resolve2(cwd, value);
|
|
18852
|
-
}
|
|
18853
|
-
function versionFile(env = process.env, cwd = process.cwd()) {
|
|
18854
|
-
return env.SUPERTASK_VERSION_FILE ? runtimePath(env.SUPERTASK_VERSION_FILE, cwd) : join3(runtimeHome(env), ".local/share/opencode/supertask-gateway-version");
|
|
18855
|
-
}
|
|
18856
|
-
function getRunningVersion(env = process.env, cwd = process.cwd()) {
|
|
18857
|
-
try {
|
|
18858
|
-
const path = versionFile(env, cwd);
|
|
18859
|
-
if (!existsSync4(path)) return null;
|
|
18860
|
-
return readFileSync2(path, "utf-8").trim() || null;
|
|
18861
|
-
} catch {
|
|
18862
|
-
return null;
|
|
18863
|
-
}
|
|
18864
|
-
}
|
|
18865
|
-
function packageVersionFromGatewayEntry(gatewayEntry) {
|
|
18866
|
-
if (gatewayEntry === null) return null;
|
|
18867
|
-
let directory = dirname3(gatewayEntry);
|
|
18868
|
-
for (let depth = 0; depth < 6; depth += 1) {
|
|
18869
|
-
const packagePath = join3(directory, "package.json");
|
|
18870
|
-
if (existsSync4(packagePath)) {
|
|
18871
|
-
try {
|
|
18872
|
-
const pkg = JSON.parse(readFileSync2(packagePath, "utf8"));
|
|
18873
|
-
if (pkg.name === "opencode-supertask" && typeof pkg.version === "string") {
|
|
18874
|
-
return pkg.version;
|
|
18875
|
-
}
|
|
18876
|
-
} catch {
|
|
18877
|
-
}
|
|
18878
|
-
}
|
|
18879
|
-
const parent = dirname3(directory);
|
|
18880
|
-
if (parent === directory) break;
|
|
18881
|
-
directory = parent;
|
|
18882
|
-
}
|
|
18883
|
-
return null;
|
|
18884
|
-
}
|
|
18885
|
-
function resolveRuntimeExecutable(command, env, cwd) {
|
|
18886
|
-
if (isAbsolute2(command) || command.includes("/")) return runtimePath(command, cwd);
|
|
18887
|
-
for (const entry of (env.PATH ?? "").split(delimiter)) {
|
|
18888
|
-
if (!entry) continue;
|
|
18889
|
-
const candidate = resolve2(cwd, entry, command);
|
|
18908
|
+
var CACHE_TTL_MS = 5e3;
|
|
18909
|
+
var DIAGNOSTIC_TIMEOUT_MS = 2e4;
|
|
18910
|
+
var cached = null;
|
|
18911
|
+
var pending = null;
|
|
18912
|
+
var activeProcessGroups = /* @__PURE__ */ new Set();
|
|
18913
|
+
function killDiagnosticGroup(pid) {
|
|
18914
|
+
if (process.platform !== "win32") {
|
|
18890
18915
|
try {
|
|
18891
|
-
|
|
18892
|
-
return
|
|
18916
|
+
process.kill(-pid, "SIGKILL");
|
|
18917
|
+
return;
|
|
18893
18918
|
} catch {
|
|
18894
18919
|
}
|
|
18895
18920
|
}
|
|
18896
|
-
return command;
|
|
18897
|
-
}
|
|
18898
|
-
function runtimeScope(runtime) {
|
|
18899
|
-
const { cwd, env } = runtime;
|
|
18900
|
-
const home = runtimeHome(env);
|
|
18901
|
-
return {
|
|
18902
|
-
cwd: resolve2(cwd),
|
|
18903
|
-
databasePath: env.SUPERTASK_DB_PATH ? runtimePath(env.SUPERTASK_DB_PATH, cwd) : join3(home, ".local/share/opencode/tasks.db"),
|
|
18904
|
-
configPath: env.SUPERTASK_CONFIG_PATH ? runtimePath(env.SUPERTASK_CONFIG_PATH, cwd) : join3(home, ".config/opencode/supertask.json"),
|
|
18905
|
-
opencodePath: resolveRuntimeExecutable(env.SUPERTASK_OPENCODE_BIN ?? "opencode", env, cwd),
|
|
18906
|
-
home,
|
|
18907
|
-
pm2Home: env.PM2_HOME ? runtimePath(env.PM2_HOME, cwd) : join3(home, ".pm2"),
|
|
18908
|
-
managementLockPath: canonicalManagementLockPath(env, cwd)
|
|
18909
|
-
};
|
|
18910
|
-
}
|
|
18911
|
-
function scopesMatch(left, right) {
|
|
18912
|
-
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;
|
|
18913
|
-
}
|
|
18914
|
-
function currentScope() {
|
|
18915
|
-
return runtimeScope({ cwd: process.cwd(), env: process.env });
|
|
18916
|
-
}
|
|
18917
|
-
function diagnoseOpenCodeRuntime(runtime = {
|
|
18918
|
-
cwd: process.cwd(),
|
|
18919
|
-
env: process.env
|
|
18920
|
-
}) {
|
|
18921
|
-
const executable = runtimeScope(runtime).opencodePath;
|
|
18922
|
-
const result = spawnSync(executable, ["--version"], {
|
|
18923
|
-
cwd: runtime.cwd,
|
|
18924
|
-
env: runtime.env,
|
|
18925
|
-
encoding: "utf8",
|
|
18926
|
-
timeout: 1e4,
|
|
18927
|
-
killSignal: "SIGKILL"
|
|
18928
|
-
});
|
|
18929
|
-
const ok = result.status === 0 && result.error === void 0;
|
|
18930
|
-
return {
|
|
18931
|
-
ok,
|
|
18932
|
-
executable,
|
|
18933
|
-
version: ok ? result.stdout.trim() || result.stderr.trim() || null : null,
|
|
18934
|
-
error: ok ? null : result.error?.message || result.stderr.trim() || result.stdout.trim() || `exit code ${result.status}`
|
|
18935
|
-
};
|
|
18936
|
-
}
|
|
18937
|
-
function getGatewayDiagnostic(options = {}) {
|
|
18938
|
-
const producerScope = currentScope();
|
|
18939
|
-
if (!isPm2Installed()) {
|
|
18940
|
-
return {
|
|
18941
|
-
pm2Installed: false,
|
|
18942
|
-
processFound: false,
|
|
18943
|
-
status: null,
|
|
18944
|
-
pid: null,
|
|
18945
|
-
ready: false,
|
|
18946
|
-
runningVersion: getRunningVersion(),
|
|
18947
|
-
gatewayEntry: null,
|
|
18948
|
-
gatewayPackageVersion: null,
|
|
18949
|
-
logRotationInstalled: false,
|
|
18950
|
-
startupConfigured: process.platform === "darwin" || process.platform === "linux" ? false : null,
|
|
18951
|
-
currentScope: producerScope,
|
|
18952
|
-
gatewayScope: null,
|
|
18953
|
-
scopeMatches: false,
|
|
18954
|
-
gatewayOpenCode: null
|
|
18955
|
-
};
|
|
18956
|
-
}
|
|
18957
|
-
const processes = pm2JsonList();
|
|
18958
|
-
const gateway = processes.find((item) => item.name === PROCESS_NAME);
|
|
18959
|
-
const runtime = gatewayRuntimeFromProcess(gateway);
|
|
18960
|
-
const gatewayEnv = runtime?.env ?? process.env;
|
|
18961
|
-
const managedScope = runtime ? runtimeScope(runtime) : null;
|
|
18962
|
-
const pid = typeof gateway?.pid === "number" ? gateway.pid : null;
|
|
18963
|
-
const readyPath = managedScope?.databasePath ?? databasePath();
|
|
18964
|
-
const lockedVersion = pid == null ? void 0 : gatewayVersionFromLock(pid, readyPath);
|
|
18965
|
-
const gatewayEntry = runtime?.gatewayEntry ?? null;
|
|
18966
|
-
return {
|
|
18967
|
-
pm2Installed: true,
|
|
18968
|
-
processFound: gateway != null,
|
|
18969
|
-
status: gateway?.pm2_env?.status ?? null,
|
|
18970
|
-
pid,
|
|
18971
|
-
ready: pid != null && isGatewayReady(pid, readyPath),
|
|
18972
|
-
runningVersion: lockedVersion === void 0 ? getRunningVersion(gatewayEnv, runtime?.cwd) : lockedVersion,
|
|
18973
|
-
gatewayEntry,
|
|
18974
|
-
gatewayPackageVersion: packageVersionFromGatewayEntry(gatewayEntry),
|
|
18975
|
-
logRotationInstalled: processes.some((item) => item.name === "pm2-logrotate" && item.pm2_env?.status === "online"),
|
|
18976
|
-
startupConfigured: process.platform === "darwin" ? isMacLaunchAgentConfigured(
|
|
18977
|
-
gatewayEnv.PM2_HOME ?? join3(runtimeHome(gatewayEnv), ".pm2"),
|
|
18978
|
-
runtime ?? void 0
|
|
18979
|
-
) : process.platform === "linux" ? isLinuxStartupConfigured(gatewayEnv, runtime?.cwd, runtime ?? void 0) : null,
|
|
18980
|
-
currentScope: producerScope,
|
|
18981
|
-
gatewayScope: managedScope,
|
|
18982
|
-
scopeMatches: managedScope != null && scopesMatch(producerScope, managedScope),
|
|
18983
|
-
gatewayOpenCode: runtime === null || !options.probeOpenCode ? null : diagnoseOpenCodeRuntime(runtime)
|
|
18984
|
-
};
|
|
18985
|
-
}
|
|
18986
|
-
function pm2Bin(env = process.env) {
|
|
18987
|
-
return env.SUPERTASK_PM2_BIN ?? (process.platform === "win32" ? "pm2.cmd" : "pm2");
|
|
18988
|
-
}
|
|
18989
|
-
function pm2CommandTimeoutMs(env = process.env) {
|
|
18990
|
-
const configured = Number(env.SUPERTASK_PM2_COMMAND_TIMEOUT_MS ?? 15e3);
|
|
18991
|
-
return Number.isFinite(configured) && configured > 0 ? configured : 15e3;
|
|
18992
|
-
}
|
|
18993
|
-
function resolvePm2Bin() {
|
|
18994
|
-
const configured = pm2Bin();
|
|
18995
|
-
if (isAbsolute2(configured)) return configured;
|
|
18996
|
-
const result = spawnSync("which", [configured], { encoding: "utf8" });
|
|
18997
|
-
const resolved = result.status === 0 ? result.stdout.trim().split("\n")[0] : "";
|
|
18998
|
-
if (!resolved) throw new Error(`[supertask] \u65E0\u6CD5\u89E3\u6790 pm2 \u53EF\u6267\u884C\u6587\u4EF6: ${configured}`);
|
|
18999
|
-
return resolved;
|
|
19000
|
-
}
|
|
19001
|
-
function launchAgentPath() {
|
|
19002
|
-
return process.env.SUPERTASK_LAUNCH_AGENT_PATH ?? join3(runtimeHome(process.env), "Library/LaunchAgents", `${MAC_LAUNCH_AGENT_LABEL}.plist`);
|
|
19003
|
-
}
|
|
19004
|
-
function launchctlBin() {
|
|
19005
|
-
return process.env.SUPERTASK_LAUNCHCTL_BIN ?? "launchctl";
|
|
19006
|
-
}
|
|
19007
|
-
function xmlUnescape(value) {
|
|
19008
|
-
return value.replaceAll("'", "'").replaceAll(""", '"').replaceAll(">", ">").replaceAll("<", "<").replaceAll("&", "&");
|
|
19009
|
-
}
|
|
19010
|
-
function plistValue(contents, key) {
|
|
19011
|
-
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
19012
|
-
const match2 = contents.match(new RegExp(`<key>\\s*${escapedKey}\\s*</key>\\s*<string>([^<]*)</string>`));
|
|
19013
|
-
return match2?.[1] ? xmlUnescape(match2[1]) : null;
|
|
19014
|
-
}
|
|
19015
|
-
function isMacLaunchAgentConfigured(expectedPm2Home, expectedRuntime) {
|
|
19016
|
-
if (typeof process.getuid !== "function") return false;
|
|
19017
|
-
const plistPath = launchAgentPath();
|
|
19018
|
-
if (!existsSync4(plistPath)) return false;
|
|
19019
18921
|
try {
|
|
19020
|
-
|
|
19021
|
-
const argumentsBlock = contents.match(
|
|
19022
|
-
/<key>\s*ProgramArguments\s*<\/key>\s*<array>([\s\S]*?)<\/array>/
|
|
19023
|
-
)?.[1];
|
|
19024
|
-
const programArguments = argumentsBlock ? [...argumentsBlock.matchAll(/<string>([^<]*)<\/string>/g)].map((match2) => xmlUnescape(match2[1])) : [];
|
|
19025
|
-
const bunPath = programArguments[0];
|
|
19026
|
-
const supervisorEntry = programArguments[1];
|
|
19027
|
-
const pm2Path = programArguments[2];
|
|
19028
|
-
const pm2Home = plistValue(contents, "PM2_HOME");
|
|
19029
|
-
const managementLock = plistValue(contents, "SUPERTASK_PM2_MANAGEMENT_LOCK");
|
|
19030
|
-
if (!bunPath || !supervisorEntry || !pm2Path || !pm2Home || !managementLock) return false;
|
|
19031
|
-
if (expectedPm2Home && pm2Home !== expectedPm2Home) return false;
|
|
19032
|
-
const expectedManagementLock = expectedRuntime ? managementLockPath(expectedRuntime.env, expectedRuntime.cwd) : process.env.SUPERTASK_PM2_MANAGEMENT_LOCK ? managementLockPath() : join3(expectedPm2Home ?? currentScope().pm2Home, "supertask-gateway.manage.sqlite");
|
|
19033
|
-
if (managementLock !== expectedManagementLock) return false;
|
|
19034
|
-
accessSync(bunPath, constants.X_OK);
|
|
19035
|
-
accessSync(supervisorEntry, constants.R_OK);
|
|
19036
|
-
accessSync(pm2Path, constants.X_OK);
|
|
19037
|
-
if (spawnSync(bunPath, ["--version"], {
|
|
19038
|
-
stdio: "ignore",
|
|
19039
|
-
timeout: pm2CommandTimeoutMs(),
|
|
19040
|
-
killSignal: "SIGKILL"
|
|
19041
|
-
}).status !== 0) return false;
|
|
19042
|
-
if (spawnSync(pm2Path, ["--version"], {
|
|
19043
|
-
stdio: "ignore",
|
|
19044
|
-
env: { ...process.env, PM2_HOME: pm2Home },
|
|
19045
|
-
timeout: pm2CommandTimeoutMs(),
|
|
19046
|
-
killSignal: "SIGKILL"
|
|
19047
|
-
}).status !== 0) return false;
|
|
19048
|
-
const loaded = spawnSync(
|
|
19049
|
-
launchctlBin(),
|
|
19050
|
-
["print", `gui/${process.getuid()}/${MAC_LAUNCH_AGENT_LABEL}`],
|
|
19051
|
-
{
|
|
19052
|
-
encoding: "utf8",
|
|
19053
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
19054
|
-
timeout: pm2CommandTimeoutMs(),
|
|
19055
|
-
killSignal: "SIGKILL"
|
|
19056
|
-
}
|
|
19057
|
-
);
|
|
19058
|
-
if (loaded.status !== 0) return false;
|
|
19059
|
-
if (!loaded.stdout.includes("state = running")) return false;
|
|
19060
|
-
if (!loaded.stdout.includes(`path = ${plistPath}`)) return false;
|
|
19061
|
-
if (!loaded.stdout.includes(`program = ${bunPath}`)) return false;
|
|
19062
|
-
const dumpPath = join3(pm2Home, "dump.pm2");
|
|
19063
|
-
const dump = JSON.parse(readFileSync2(dumpPath, "utf8"));
|
|
19064
|
-
if (!Array.isArray(dump)) return false;
|
|
19065
|
-
const gateway = dump.find((item) => item.name === PROCESS_NAME);
|
|
19066
|
-
const dumpRuntime = gatewayRuntimeFromProcess(gateway);
|
|
19067
|
-
if (!dumpRuntime || !hasRestorableSavedGatewayRuntime(gateway)) return false;
|
|
19068
|
-
return expectedRuntime === void 0 || dumpRuntime.gatewayEntry === expectedRuntime.gatewayEntry && dumpRuntime.bunPath === expectedRuntime.bunPath && dumpRuntime.cwd === expectedRuntime.cwd && scopesMatch(runtimeScope(dumpRuntime), runtimeScope(expectedRuntime));
|
|
18922
|
+
process.kill(pid, "SIGKILL");
|
|
19069
18923
|
} catch {
|
|
19070
|
-
return false;
|
|
19071
18924
|
}
|
|
19072
18925
|
}
|
|
19073
|
-
|
|
19074
|
-
|
|
18926
|
+
process.once("exit", () => {
|
|
18927
|
+
for (const pid of activeProcessGroups) killDiagnosticGroup(pid);
|
|
18928
|
+
});
|
|
18929
|
+
function diagnosticEntry() {
|
|
18930
|
+
const extension = import.meta.url.endsWith(".ts") ? "ts" : "js";
|
|
18931
|
+
const moduleDir = dirname3(fileURLToPath2(import.meta.url));
|
|
18932
|
+
const candidates = [
|
|
18933
|
+
join3(moduleDir, `../daemon/gateway-diagnostic-runner.${extension}`),
|
|
18934
|
+
join3(moduleDir, `../../src/daemon/gateway-diagnostic-runner.${extension}`)
|
|
18935
|
+
];
|
|
18936
|
+
const entry = candidates.find((candidate) => existsSync4(candidate));
|
|
18937
|
+
if (!entry) throw new Error(`Gateway diagnostic runner \u4E0D\u5B58\u5728\uFF1A${candidates.join(", ")}`);
|
|
18938
|
+
return entry;
|
|
19075
18939
|
}
|
|
19076
|
-
function
|
|
19077
|
-
|
|
19078
|
-
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
|
|
19084
|
-
|
|
19085
|
-
|
|
19086
|
-
|
|
19087
|
-
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
|
|
18940
|
+
async function runGatewayDiagnostic() {
|
|
18941
|
+
return new Promise((resolve2, reject) => {
|
|
18942
|
+
const child = spawn2(process.execPath, [diagnosticEntry()], {
|
|
18943
|
+
cwd: process.cwd(),
|
|
18944
|
+
env: process.env,
|
|
18945
|
+
detached: process.platform !== "win32",
|
|
18946
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
18947
|
+
});
|
|
18948
|
+
let stdout = "";
|
|
18949
|
+
let stderr = "";
|
|
18950
|
+
let timedOut = false;
|
|
18951
|
+
if (child.pid) activeProcessGroups.add(child.pid);
|
|
18952
|
+
child.stdout?.on("data", (chunk) => {
|
|
18953
|
+
stdout += chunk.toString();
|
|
18954
|
+
});
|
|
18955
|
+
child.stderr?.on("data", (chunk) => {
|
|
18956
|
+
stderr += chunk.toString();
|
|
18957
|
+
});
|
|
18958
|
+
child.once("error", reject);
|
|
18959
|
+
child.once("close", (exitCode) => {
|
|
18960
|
+
clearTimeout(timer);
|
|
18961
|
+
if (child.pid) {
|
|
18962
|
+
killDiagnosticGroup(child.pid);
|
|
18963
|
+
activeProcessGroups.delete(child.pid);
|
|
18964
|
+
}
|
|
18965
|
+
if (timedOut) {
|
|
18966
|
+
reject(new Error(`Gateway diagnostic runner \u8D85\u8FC7 ${DIAGNOSTIC_TIMEOUT_MS}ms \u672A\u5B8C\u6210`));
|
|
18967
|
+
return;
|
|
18968
|
+
}
|
|
18969
|
+
if (exitCode !== 0) {
|
|
18970
|
+
reject(new Error(stderr.trim() || `Gateway diagnostic runner \u9000\u51FA\u7801 ${exitCode}`));
|
|
18971
|
+
return;
|
|
18972
|
+
}
|
|
18973
|
+
try {
|
|
18974
|
+
resolve2(JSON.parse(stdout));
|
|
18975
|
+
} catch (error) {
|
|
18976
|
+
reject(error);
|
|
18977
|
+
}
|
|
18978
|
+
});
|
|
18979
|
+
const timer = setTimeout(() => {
|
|
18980
|
+
timedOut = true;
|
|
18981
|
+
if (child.pid) killDiagnosticGroup(child.pid);
|
|
18982
|
+
else child.kill("SIGKILL");
|
|
18983
|
+
}, DIAGNOSTIC_TIMEOUT_MS);
|
|
19092
18984
|
});
|
|
19093
|
-
if (contents.status !== 0) return false;
|
|
19094
|
-
const expectedPm2Home = env.PM2_HOME ?? join3(runtimeHome(env), ".pm2");
|
|
19095
|
-
if (!contents.stdout.includes("resurrect") || !contents.stdout.includes(expectedPm2Home)) {
|
|
19096
|
-
return false;
|
|
19097
|
-
}
|
|
19098
|
-
try {
|
|
19099
|
-
const dump = JSON.parse(readFileSync2(join3(expectedPm2Home, "dump.pm2"), "utf8"));
|
|
19100
|
-
if (!Array.isArray(dump)) return false;
|
|
19101
|
-
const gateway = dump.find((item) => item.name === PROCESS_NAME);
|
|
19102
|
-
const runtime = gatewayRuntimeFromProcess(gateway);
|
|
19103
|
-
if (!runtime || !hasRestorableSavedGatewayRuntime(gateway)) return false;
|
|
19104
|
-
return runtime.cwd === resolve2(cwd) && scopesMatch(runtimeScope(runtime), runtimeScope({ cwd, env })) && (expectedRuntime === void 0 || runtime.gatewayEntry === expectedRuntime.gatewayEntry && runtime.bunPath === expectedRuntime.bunPath);
|
|
19105
|
-
} catch {
|
|
19106
|
-
return false;
|
|
19107
|
-
}
|
|
19108
18985
|
}
|
|
19109
|
-
function
|
|
19110
|
-
|
|
19111
|
-
|
|
19112
|
-
|
|
19113
|
-
|
|
19114
|
-
|
|
19115
|
-
killSignal: "SIGKILL"
|
|
18986
|
+
async function getDashboardGatewayDiagnostic(options = {}) {
|
|
18987
|
+
if (!options.fresh && cached && cached.expiresAt > Date.now()) return cached.diagnostic;
|
|
18988
|
+
if (!options.fresh && pending) return pending;
|
|
18989
|
+
const operation = runGatewayDiagnostic().then((diagnostic) => {
|
|
18990
|
+
cached = { expiresAt: Date.now() + CACHE_TTL_MS, diagnostic };
|
|
18991
|
+
return diagnostic;
|
|
19116
18992
|
});
|
|
19117
|
-
|
|
19118
|
-
|
|
19119
|
-
|
|
19120
|
-
const lookup = process.platform === "win32" ? "where" : "which";
|
|
19121
|
-
const result = spawnSync(lookup, [command], { encoding: "utf8" });
|
|
19122
|
-
return result.status === 0 ? result.stdout.trim().split("\n")[0] || null : null;
|
|
19123
|
-
}
|
|
19124
|
-
function npmPreferredEnvironment() {
|
|
19125
|
-
if (process.platform === "win32") return null;
|
|
19126
|
-
const npm = resolveCommand("npm");
|
|
19127
|
-
const node = resolveCommand("node");
|
|
19128
|
-
if (!npm || !node) return null;
|
|
19129
|
-
const command = resolvePm2Bin();
|
|
19130
|
-
const path = [.../* @__PURE__ */ new Set([
|
|
19131
|
-
dirname3(command),
|
|
19132
|
-
dirname3(npm),
|
|
19133
|
-
dirname3(node),
|
|
19134
|
-
"/usr/local/bin",
|
|
19135
|
-
"/usr/bin",
|
|
19136
|
-
"/bin"
|
|
19137
|
-
])].join(delimiter);
|
|
19138
|
-
return { command, env: { ...process.env, PATH: path } };
|
|
19139
|
-
}
|
|
19140
|
-
function pm2Exec(args, options = {}) {
|
|
19141
|
-
const npmEnvironment = options.preferNpm ? npmPreferredEnvironment() : null;
|
|
19142
|
-
const effectiveEnv = options.env ?? npmEnvironment?.env ?? process.env;
|
|
19143
|
-
const result = spawnSync(npmEnvironment?.command ?? pm2Bin(effectiveEnv), args, {
|
|
19144
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
19145
|
-
encoding: "utf-8",
|
|
19146
|
-
env: effectiveEnv,
|
|
19147
|
-
shell: process.platform === "win32",
|
|
19148
|
-
timeout: options.timeoutMs ?? pm2CommandTimeoutMs(effectiveEnv),
|
|
19149
|
-
killSignal: "SIGKILL"
|
|
18993
|
+
if (options.fresh) return operation;
|
|
18994
|
+
pending = operation.finally(() => {
|
|
18995
|
+
pending = null;
|
|
19150
18996
|
});
|
|
19151
|
-
|
|
19152
|
-
if (result.error) return { ok: false, output: result.error.message };
|
|
19153
|
-
return { ok: result.status === 0, output };
|
|
19154
|
-
}
|
|
19155
|
-
function requirePm2(args, action, options = {}) {
|
|
19156
|
-
const result = pm2Exec(args, options);
|
|
19157
|
-
if (!result.ok) throw new Error(`[supertask] ${action} failed: ${result.output || "unknown pm2 error"}`);
|
|
19158
|
-
return result.output;
|
|
19159
|
-
}
|
|
19160
|
-
function pm2JsonList(env = process.env) {
|
|
19161
|
-
const output = requirePm2(["jlist"], "pm2 jlist", { env });
|
|
19162
|
-
try {
|
|
19163
|
-
const parsed = JSON.parse(output);
|
|
19164
|
-
if (!Array.isArray(parsed)) throw new Error("result is not an array");
|
|
19165
|
-
return parsed;
|
|
19166
|
-
} catch (error) {
|
|
19167
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
19168
|
-
throw new Error(`[supertask] Invalid pm2 jlist output: ${message}`);
|
|
19169
|
-
}
|
|
19170
|
-
}
|
|
19171
|
-
function gatewayEntryFromProcess(processInfo) {
|
|
19172
|
-
const args = processInfo?.pm2_env?.args ?? processInfo?.args;
|
|
19173
|
-
const candidates = Array.isArray(args) ? [...args].reverse() : typeof args === "string" ? [args] : [];
|
|
19174
|
-
const savedCwd = processInfo?.pm2_env?.pm_cwd ?? processInfo?.pm_cwd;
|
|
19175
|
-
for (const candidate of candidates) {
|
|
19176
|
-
const path = typeof savedCwd === "string" ? runtimePath(candidate, savedCwd) : candidate;
|
|
19177
|
-
if (existsSync4(path)) return resolve2(path);
|
|
19178
|
-
}
|
|
19179
|
-
return null;
|
|
19180
|
-
}
|
|
19181
|
-
function gatewayEnvironmentFromProcess(processInfo) {
|
|
19182
|
-
const saved = processInfo?.pm2_env?.env ?? processInfo?.env;
|
|
19183
|
-
if (!saved) return { ...process.env };
|
|
19184
|
-
const env = {};
|
|
19185
|
-
for (const [key, value] of Object.entries(saved)) {
|
|
19186
|
-
if (typeof value === "string") env[key] = value;
|
|
19187
|
-
}
|
|
19188
|
-
return env;
|
|
19189
|
-
}
|
|
19190
|
-
function gatewayRuntimeFromProcess(processInfo) {
|
|
19191
|
-
const gatewayEntry = gatewayEntryFromProcess(processInfo);
|
|
19192
|
-
if (!gatewayEntry) return null;
|
|
19193
|
-
const savedBunPath = processInfo?.pm2_env?.pm_exec_path ?? processInfo?.pm_exec_path;
|
|
19194
|
-
const savedCwd = processInfo?.pm2_env?.pm_cwd ?? processInfo?.pm_cwd;
|
|
19195
|
-
const savedEnv = gatewayEnvironmentFromProcess(processInfo);
|
|
19196
|
-
if (typeof savedBunPath !== "string" || typeof savedCwd !== "string") return null;
|
|
19197
|
-
try {
|
|
19198
|
-
accessSync(savedBunPath, constants.X_OK);
|
|
19199
|
-
if (!statSync3(savedCwd).isDirectory()) return null;
|
|
19200
|
-
if (spawnSync(savedBunPath, ["--version"], {
|
|
19201
|
-
stdio: "ignore",
|
|
19202
|
-
env: savedEnv,
|
|
19203
|
-
timeout: pm2CommandTimeoutMs(savedEnv),
|
|
19204
|
-
killSignal: "SIGKILL"
|
|
19205
|
-
}).status !== 0) return null;
|
|
19206
|
-
} catch {
|
|
19207
|
-
return null;
|
|
19208
|
-
}
|
|
19209
|
-
const killTimeout = processInfo?.pm2_env?.kill_timeout ?? processInfo?.kill_timeout;
|
|
19210
|
-
return {
|
|
19211
|
-
gatewayEntry,
|
|
19212
|
-
bunPath: savedBunPath,
|
|
19213
|
-
cwd: resolve2(savedCwd),
|
|
19214
|
-
env: savedEnv,
|
|
19215
|
-
killTimeoutMs: Number.isInteger(killTimeout) && killTimeout >= 5e3 ? killTimeout : void 0
|
|
19216
|
-
};
|
|
19217
|
-
}
|
|
19218
|
-
function hasRestorableSavedGatewayRuntime(processInfo) {
|
|
19219
|
-
return gatewayRuntimeFromProcess(processInfo) !== null;
|
|
19220
|
-
}
|
|
19221
|
-
function databasePath(env = process.env, cwd = process.cwd()) {
|
|
19222
|
-
return env.SUPERTASK_DB_PATH ? runtimePath(env.SUPERTASK_DB_PATH, cwd) : join3(runtimeHome(env), ".local/share/opencode/tasks.db");
|
|
19223
|
-
}
|
|
19224
|
-
function isGatewayReady(expectedPid, path = databasePath()) {
|
|
19225
|
-
if (!existsSync4(path)) return false;
|
|
19226
|
-
let database = null;
|
|
19227
|
-
try {
|
|
19228
|
-
database = new Database5(path, { readonly: true });
|
|
19229
|
-
const row = database.query(
|
|
19230
|
-
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
19231
|
-
).get();
|
|
19232
|
-
if (!row || row.ready_at == null) return false;
|
|
19233
|
-
if (expectedPid !== void 0 && row.pid !== expectedPid) return false;
|
|
19234
|
-
const ageMs = Date.now() - row.heartbeat_at;
|
|
19235
|
-
return ageMs >= -5e3 && ageMs < GATEWAY_LOCK_STALE_MS;
|
|
19236
|
-
} catch {
|
|
19237
|
-
return false;
|
|
19238
|
-
} finally {
|
|
19239
|
-
database?.close();
|
|
19240
|
-
}
|
|
19241
|
-
}
|
|
19242
|
-
function gatewayVersionFromLock(expectedPid, path) {
|
|
19243
|
-
if (!existsSync4(path)) return void 0;
|
|
19244
|
-
let database = null;
|
|
19245
|
-
try {
|
|
19246
|
-
database = new Database5(path, { readonly: true });
|
|
19247
|
-
const row = database.query(
|
|
19248
|
-
"SELECT pid, version FROM gateway_lock WHERE id = 1"
|
|
19249
|
-
).get();
|
|
19250
|
-
if (!row || row.pid !== expectedPid) return void 0;
|
|
19251
|
-
return row.version;
|
|
19252
|
-
} catch {
|
|
19253
|
-
return void 0;
|
|
19254
|
-
} finally {
|
|
19255
|
-
database?.close();
|
|
19256
|
-
}
|
|
19257
|
-
}
|
|
19258
|
-
function managementLockPath(env = process.env, cwd = process.cwd()) {
|
|
19259
|
-
const override = env.SUPERTASK_PM2_MANAGEMENT_LOCK;
|
|
19260
|
-
if (override) return runtimePath(override, cwd);
|
|
19261
|
-
return join3(runtimeScope({ env, cwd }).pm2Home, "supertask-gateway.manage.sqlite");
|
|
19262
|
-
}
|
|
19263
|
-
function canonicalManagementLockPath(env = process.env, cwd = process.cwd()) {
|
|
19264
|
-
const home = runtimeHome(env);
|
|
19265
|
-
const pm2Home = env.PM2_HOME ? runtimePath(env.PM2_HOME, cwd) : join3(home, ".pm2");
|
|
19266
|
-
return join3(pm2Home, "supertask-gateway.manage.sqlite");
|
|
18997
|
+
return pending;
|
|
19267
18998
|
}
|
|
19268
18999
|
|
|
19269
19000
|
// src/web/ui.ts
|
|
@@ -19536,7 +19267,7 @@ var ZH = {
|
|
|
19536
19267
|
"details.enabledYes": "\u5DF2\u542F\u7528",
|
|
19537
19268
|
"details.enabledNo": "\u5DF2\u505C\u7528",
|
|
19538
19269
|
"dialog.cancelTask": "\u53D6\u6D88\u4EFB\u52A1 #{id}\uFF1F",
|
|
19539
|
-
"dialog.cancelTaskBody": "\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u4F1A\u5728\u4E0B\u4E00\u4E2A\u8F6E\u8BE2\u5468\u671F\u7EC8\u6B62\u5BF9\u5E94\u8FDB\u7A0B\
|
|
19270
|
+
"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",
|
|
19540
19271
|
"dialog.retryTask": "\u91CD\u8BD5\u4EFB\u52A1 #{id}\uFF1F",
|
|
19541
19272
|
"dialog.retryTaskBody": "\u4EFB\u52A1\u5C06\u56DE\u5230\u5F85\u6267\u884C\u72B6\u6001\uFF0C\u5E76\u91CD\u7F6E\u81EA\u52A8\u91CD\u8BD5\u9884\u7B97\u3002",
|
|
19542
19273
|
"dialog.deleteTask": "\u5220\u9664\u4EFB\u52A1 #{id}\uFF1F",
|
|
@@ -19841,7 +19572,7 @@ var EN = {
|
|
|
19841
19572
|
"details.enabledYes": "Enabled",
|
|
19842
19573
|
"details.enabledNo": "Disabled",
|
|
19843
19574
|
"dialog.cancelTask": "Cancel task #{id}?",
|
|
19844
|
-
"dialog.cancelTaskBody": "A running task will terminate its process
|
|
19575
|
+
"dialog.cancelTaskBody": "A running task will terminate its managed process group on the next worker poll.",
|
|
19845
19576
|
"dialog.retryTask": "Retry task #{id}?",
|
|
19846
19577
|
"dialog.retryTaskBody": "The task returns to pending and its automatic retry budget is reset.",
|
|
19847
19578
|
"dialog.deleteTask": "Delete task #{id}?",
|
|
@@ -20652,6 +20383,7 @@ var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
|
20652
20383
|
"cancelled"
|
|
20653
20384
|
]);
|
|
20654
20385
|
var SESSION_ID_PATTERN = /^ses_[A-Za-z0-9_]+$/;
|
|
20386
|
+
var LOOPBACK_HOST_PATTERN = /^(?:localhost|127\.0\.0\.1|\[::1\])(?::([1-9]\d{0,4}))?$/i;
|
|
20655
20387
|
var runtimeConfig = null;
|
|
20656
20388
|
var restartScheduled = false;
|
|
20657
20389
|
function setDashboardRuntimeConfig(config) {
|
|
@@ -20678,10 +20410,13 @@ function resolveDashboardConfigState(runtimeAvailable, restartRequired, managedR
|
|
|
20678
20410
|
function isSafeDashboardRestartTarget(diagnostic, currentPid) {
|
|
20679
20411
|
return diagnostic.pm2Installed && diagnostic.processFound && diagnostic.status === "online" && diagnostic.pid === currentPid && diagnostic.ready && diagnostic.scopeMatches;
|
|
20680
20412
|
}
|
|
20681
|
-
function canRestartCurrentGateway() {
|
|
20413
|
+
async function canRestartCurrentGateway(fresh = false) {
|
|
20682
20414
|
if (runtimeConfig === null) return false;
|
|
20683
20415
|
try {
|
|
20684
|
-
return isSafeDashboardRestartTarget(
|
|
20416
|
+
return isSafeDashboardRestartTarget(
|
|
20417
|
+
await getDashboardGatewayDiagnostic({ fresh }),
|
|
20418
|
+
process.pid
|
|
20419
|
+
);
|
|
20685
20420
|
} catch {
|
|
20686
20421
|
return false;
|
|
20687
20422
|
}
|
|
@@ -20700,7 +20435,7 @@ function listChildDirectories(path) {
|
|
|
20700
20435
|
if (entry.isDirectory()) return [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }];
|
|
20701
20436
|
if (!entry.isSymbolicLink()) return [];
|
|
20702
20437
|
try {
|
|
20703
|
-
return
|
|
20438
|
+
return statSync3(entryPath).isDirectory() ? [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }] : [];
|
|
20704
20439
|
} catch {
|
|
20705
20440
|
return [];
|
|
20706
20441
|
}
|
|
@@ -20861,6 +20596,14 @@ function resolveLocale(c) {
|
|
|
20861
20596
|
return accepted.startsWith("en") ? "en" : "zh-CN";
|
|
20862
20597
|
}
|
|
20863
20598
|
app.use("*", async (c, next) => {
|
|
20599
|
+
const requestHostname = new URL(c.req.url).hostname;
|
|
20600
|
+
const hostHeader = c.req.header("Host");
|
|
20601
|
+
const loopbackHosts = ["localhost", "127.0.0.1", "[::1]"];
|
|
20602
|
+
const hostMatch = hostHeader?.match(LOOPBACK_HOST_PATTERN) ?? null;
|
|
20603
|
+
const hostPort = hostMatch?.[1] === void 0 ? null : Number(hostMatch[1]);
|
|
20604
|
+
if (!loopbackHosts.includes(requestHostname) || hostHeader !== void 0 && (!hostMatch || hostPort !== null && hostPort > 65535)) {
|
|
20605
|
+
return c.json({ error: "invalid dashboard host" }, 421);
|
|
20606
|
+
}
|
|
20864
20607
|
await next();
|
|
20865
20608
|
c.header("X-Content-Type-Options", "nosniff");
|
|
20866
20609
|
c.header("X-Frame-Options", "DENY");
|
|
@@ -20892,13 +20635,13 @@ app.get("/health", (c) => {
|
|
|
20892
20635
|
return c.json(health, health.status === "ok" ? 200 : 503);
|
|
20893
20636
|
});
|
|
20894
20637
|
app.get("/api/filesystem/directories", (c) => {
|
|
20895
|
-
const requestedPath = c.req.query("path")?.trim() ||
|
|
20638
|
+
const requestedPath = c.req.query("path")?.trim() || homedir3();
|
|
20896
20639
|
try {
|
|
20897
20640
|
validateTaskWorkingDirectory(requestedPath);
|
|
20898
20641
|
return c.json({
|
|
20899
20642
|
path: requestedPath,
|
|
20900
20643
|
parent: dirname4(requestedPath),
|
|
20901
|
-
home:
|
|
20644
|
+
home: homedir3(),
|
|
20902
20645
|
directories: listChildDirectories(requestedPath)
|
|
20903
20646
|
});
|
|
20904
20647
|
} catch (error) {
|
|
@@ -20996,7 +20739,7 @@ function readCurrentConfig() {
|
|
|
20996
20739
|
const configPath = getConfigPath();
|
|
20997
20740
|
if (!existsSync5(configPath)) return {};
|
|
20998
20741
|
try {
|
|
20999
|
-
return JSON.parse(
|
|
20742
|
+
return JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
21000
20743
|
} catch {
|
|
21001
20744
|
return {};
|
|
21002
20745
|
}
|
|
@@ -21004,9 +20747,9 @@ function readCurrentConfig() {
|
|
|
21004
20747
|
function writeConfig(cfg) {
|
|
21005
20748
|
const configPath = getConfigPath();
|
|
21006
20749
|
const dir = dirname4(configPath);
|
|
21007
|
-
if (!existsSync5(dir))
|
|
20750
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
21008
20751
|
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
21009
|
-
|
|
20752
|
+
writeFileSync2(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
21010
20753
|
renameSync2(tempPath, configPath);
|
|
21011
20754
|
}
|
|
21012
20755
|
function statCard(value, label, tone, cardIcon, delay = "") {
|
|
@@ -21372,7 +21115,7 @@ app.get("/system", async (c) => {
|
|
|
21372
21115
|
const config = loadConfig();
|
|
21373
21116
|
const activeConfig = runtimeConfig ?? config;
|
|
21374
21117
|
const restartRequired = runtimeConfig !== null && !configsEqual(config, runtimeConfig);
|
|
21375
|
-
const managedRestart = canRestartCurrentGateway();
|
|
21118
|
+
const managedRestart = await canRestartCurrentGateway();
|
|
21376
21119
|
const configState = resolveDashboardConfigState(
|
|
21377
21120
|
runtimeConfig !== null,
|
|
21378
21121
|
restartRequired,
|
|
@@ -21492,9 +21235,9 @@ app.get("/api/runs/:id/session-command", async (c) => {
|
|
|
21492
21235
|
if (!isValidSessionId(run.sessionId)) return c.json({ error: "session unavailable" }, 409);
|
|
21493
21236
|
return c.json({ command: sessionCommand(run.sessionId) });
|
|
21494
21237
|
});
|
|
21495
|
-
app.get("/api/gateway/status", (c) => {
|
|
21238
|
+
app.get("/api/gateway/status", async (c) => {
|
|
21496
21239
|
const savedConfig = loadConfig();
|
|
21497
|
-
const managed = canRestartCurrentGateway();
|
|
21240
|
+
const managed = await canRestartCurrentGateway();
|
|
21498
21241
|
return c.json({
|
|
21499
21242
|
pid: process.pid,
|
|
21500
21243
|
managed,
|
|
@@ -21619,7 +21362,7 @@ app.put("/api/config", async (c) => {
|
|
|
21619
21362
|
return c.json({
|
|
21620
21363
|
success: true,
|
|
21621
21364
|
restartRequired: runtimeConfig === null || !configsEqual(savedConfig, runtimeConfig),
|
|
21622
|
-
managed: canRestartCurrentGateway()
|
|
21365
|
+
managed: await canRestartCurrentGateway()
|
|
21623
21366
|
});
|
|
21624
21367
|
} catch (error) {
|
|
21625
21368
|
return c.json({
|
|
@@ -21635,9 +21378,10 @@ app.post("/api/gateway/restart", async (c) => {
|
|
|
21635
21378
|
return c.json({ error: "confirmation must be RESTART" }, 400);
|
|
21636
21379
|
}
|
|
21637
21380
|
if (restartScheduled) return c.json({ error: "restart already scheduled" }, 409);
|
|
21638
|
-
if (!canRestartCurrentGateway()) {
|
|
21381
|
+
if (!await canRestartCurrentGateway(true)) {
|
|
21639
21382
|
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);
|
|
21640
21383
|
}
|
|
21384
|
+
if (restartScheduled) return c.json({ error: "restart already scheduled" }, 409);
|
|
21641
21385
|
restartScheduled = true;
|
|
21642
21386
|
const previousPid = process.pid;
|
|
21643
21387
|
setTimeout(() => {
|