opencode-supertask 0.1.39 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -1
- package/LICENSE +21 -0
- package/README.md +159 -352
- package/README.zh-CN.md +250 -0
- package/dist/cli/index.js +580 -171
- package/dist/cli/index.js.map +1 -1
- package/dist/daemon/gateway-diagnostic-runner.d.ts +2 -0
- package/dist/daemon/gateway-diagnostic-runner.js +461 -0
- package/dist/daemon/gateway-diagnostic-runner.js.map +1 -0
- package/dist/gateway/index.js +543 -615
- package/dist/gateway/index.js.map +1 -1
- package/dist/plugin/supertask.js +90 -28
- package/dist/plugin/supertask.js.map +1 -1
- package/dist/web/index.js +410 -528
- package/dist/web/index.js.map +1 -1
- package/dist/worker/index.d.ts +9 -0
- package/dist/worker/index.js +225 -69
- package/dist/worker/index.js.map +1 -1
- package/dist/worker/launcher.js.map +1 -1
- package/drizzle/0008_good_smasher.sql +3 -0
- package/drizzle/meta/0008_snapshot.json +606 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +12 -3
package/dist/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
|
|
@@ -16395,6 +16395,7 @@ var tasks = sqliteTable("tasks", {
|
|
|
16395
16395
|
name: text("name").notNull(),
|
|
16396
16396
|
agent: text("agent").notNull(),
|
|
16397
16397
|
model: text("model").default("default"),
|
|
16398
|
+
variant: text("variant"),
|
|
16398
16399
|
prompt: text("prompt").notNull(),
|
|
16399
16400
|
cwd: text("cwd"),
|
|
16400
16401
|
// 分类与优先级
|
|
@@ -16439,6 +16440,7 @@ var taskRuns = sqliteTable("task_runs", {
|
|
|
16439
16440
|
taskId: integer("task_id").notNull().references(() => tasks.id, { onDelete: "cascade" }),
|
|
16440
16441
|
sessionId: text("session_id"),
|
|
16441
16442
|
model: text("model"),
|
|
16443
|
+
variant: text("variant"),
|
|
16442
16444
|
status: text("status").default("running"),
|
|
16443
16445
|
startedAt: integer("started_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
16444
16446
|
finishedAt: integer("finished_at", { mode: "timestamp" }),
|
|
@@ -16459,6 +16461,7 @@ var taskTemplates = sqliteTable("task_templates", {
|
|
|
16459
16461
|
name: text("name").notNull(),
|
|
16460
16462
|
agent: text("agent").notNull(),
|
|
16461
16463
|
model: text("model").default("default"),
|
|
16464
|
+
variant: text("variant"),
|
|
16462
16465
|
prompt: text("prompt").notNull(),
|
|
16463
16466
|
cwd: text("cwd"),
|
|
16464
16467
|
category: text("category").default("general"),
|
|
@@ -16499,8 +16502,8 @@ var _sqlite = null;
|
|
|
16499
16502
|
var _db = null;
|
|
16500
16503
|
var _migrationRan = false;
|
|
16501
16504
|
function getMigrationsFolder() {
|
|
16502
|
-
const
|
|
16503
|
-
let dir =
|
|
16505
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16506
|
+
let dir = __dirname;
|
|
16504
16507
|
for (let i = 0; i < 5; i++) {
|
|
16505
16508
|
const candidate = join(dir, "drizzle", "meta", "_journal.json");
|
|
16506
16509
|
if (existsSync(candidate)) {
|
|
@@ -16508,7 +16511,7 @@ function getMigrationsFolder() {
|
|
|
16508
16511
|
}
|
|
16509
16512
|
dir = dirname(dir);
|
|
16510
16513
|
}
|
|
16511
|
-
return join(
|
|
16514
|
+
return join(__dirname, "../../drizzle");
|
|
16512
16515
|
}
|
|
16513
16516
|
function ensureGatewayLock(sqliteDb) {
|
|
16514
16517
|
sqliteDb.exec(`
|
|
@@ -16657,26 +16660,60 @@ var CATALOG_CACHE_MS = 3e4;
|
|
|
16657
16660
|
var COMMAND_TIMEOUT_MS = 2e4;
|
|
16658
16661
|
var MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
16659
16662
|
var ANSI_PATTERN = /\x1B(?:[@-_][0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
|
|
16663
|
+
var OpenCodeCommandExitError = class extends Error {
|
|
16664
|
+
};
|
|
16660
16665
|
var catalogCache = /* @__PURE__ */ new Map();
|
|
16661
16666
|
function cleanOutput(value) {
|
|
16662
16667
|
return value.replace(ANSI_PATTERN, "").replace(/\r/g, "");
|
|
16663
16668
|
}
|
|
16664
16669
|
function runOpenCode(executable, args, cwd, timeoutMs) {
|
|
16665
|
-
return new Promise((
|
|
16670
|
+
return new Promise((resolve2, reject) => {
|
|
16666
16671
|
const child = spawn(executable, args, {
|
|
16667
16672
|
cwd,
|
|
16668
16673
|
env: process.env,
|
|
16669
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
16674
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
16675
|
+
detached: process.platform !== "win32"
|
|
16670
16676
|
});
|
|
16671
16677
|
let stdout = "";
|
|
16672
16678
|
let stderr = "";
|
|
16673
16679
|
let failure = null;
|
|
16674
|
-
let
|
|
16680
|
+
let settled = false;
|
|
16681
|
+
let forceKillTimer = null;
|
|
16682
|
+
let finalRejectTimer = null;
|
|
16683
|
+
let timer;
|
|
16684
|
+
const signalProcessTree = (signal) => {
|
|
16685
|
+
if (process.platform !== "win32" && child.pid) {
|
|
16686
|
+
try {
|
|
16687
|
+
process.kill(-child.pid, signal);
|
|
16688
|
+
return;
|
|
16689
|
+
} catch {
|
|
16690
|
+
}
|
|
16691
|
+
}
|
|
16692
|
+
child.kill(signal);
|
|
16693
|
+
};
|
|
16694
|
+
const rejectOnce = (error) => {
|
|
16695
|
+
failure ??= error;
|
|
16696
|
+
if (settled) return;
|
|
16697
|
+
settled = true;
|
|
16698
|
+
clearTimeout(timer);
|
|
16699
|
+
if (forceKillTimer) clearTimeout(forceKillTimer);
|
|
16700
|
+
if (finalRejectTimer) clearTimeout(finalRejectTimer);
|
|
16701
|
+
reject(failure);
|
|
16702
|
+
};
|
|
16703
|
+
const terminate = (error) => {
|
|
16704
|
+
if (failure) return;
|
|
16705
|
+
failure = error;
|
|
16706
|
+
signalProcessTree("SIGTERM");
|
|
16707
|
+
forceKillTimer = setTimeout(() => {
|
|
16708
|
+
signalProcessTree("SIGKILL");
|
|
16709
|
+
rejectOnce(error);
|
|
16710
|
+
}, 1e3);
|
|
16711
|
+
finalRejectTimer = setTimeout(() => rejectOnce(error), 2e3);
|
|
16712
|
+
};
|
|
16675
16713
|
const append = (current, chunk) => {
|
|
16676
16714
|
const next = current + chunk.toString();
|
|
16677
16715
|
if (Buffer.byteLength(next) > MAX_OUTPUT_BYTES && failure === null) {
|
|
16678
|
-
|
|
16679
|
-
child.kill("SIGTERM");
|
|
16716
|
+
terminate(new Error(`OpenCode \u8F93\u51FA\u8D85\u8FC7 ${MAX_OUTPUT_BYTES} bytes`));
|
|
16680
16717
|
}
|
|
16681
16718
|
return next.slice(-MAX_OUTPUT_BYTES);
|
|
16682
16719
|
};
|
|
@@ -16687,32 +16724,80 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
|
|
|
16687
16724
|
stderr = append(stderr, chunk);
|
|
16688
16725
|
});
|
|
16689
16726
|
child.once("error", (error) => {
|
|
16690
|
-
|
|
16727
|
+
if (forceKillTimer) return;
|
|
16728
|
+
rejectOnce(error);
|
|
16691
16729
|
});
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
child.kill("SIGTERM");
|
|
16730
|
+
timer = setTimeout(() => {
|
|
16731
|
+
terminate(new Error(`OpenCode \u547D\u4EE4\u8D85\u8FC7 ${timeoutMs}ms \u672A\u5B8C\u6210`));
|
|
16695
16732
|
}, timeoutMs);
|
|
16696
16733
|
child.once("close", (code) => {
|
|
16697
|
-
if (finished) return;
|
|
16698
|
-
finished = true;
|
|
16699
16734
|
clearTimeout(timer);
|
|
16735
|
+
if (forceKillTimer) return;
|
|
16736
|
+
if (finalRejectTimer) clearTimeout(finalRejectTimer);
|
|
16737
|
+
if (settled) return;
|
|
16738
|
+
settled = true;
|
|
16700
16739
|
if (failure) {
|
|
16701
16740
|
reject(failure);
|
|
16702
16741
|
return;
|
|
16703
16742
|
}
|
|
16704
16743
|
if (code !== 0) {
|
|
16705
16744
|
const detail = cleanOutput(stderr).trim() || `\u9000\u51FA\u7801 ${code ?? "null"}`;
|
|
16706
|
-
reject(new
|
|
16745
|
+
reject(new OpenCodeCommandExitError(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
|
|
16707
16746
|
return;
|
|
16708
16747
|
}
|
|
16709
|
-
|
|
16748
|
+
resolve2(cleanOutput(stdout));
|
|
16710
16749
|
});
|
|
16711
16750
|
});
|
|
16712
16751
|
}
|
|
16713
16752
|
function parseOpenCodeModels(output) {
|
|
16714
16753
|
return [...new Set(cleanOutput(output).split("\n").map((line) => line.trim()).filter((line) => /^[^\s/]+\/.+/.test(line)))].sort((left, right) => left.localeCompare(right));
|
|
16715
16754
|
}
|
|
16755
|
+
function isRecord(value) {
|
|
16756
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16757
|
+
}
|
|
16758
|
+
function findJsonObjectEnd(value, start) {
|
|
16759
|
+
let depth = 0;
|
|
16760
|
+
let inString = false;
|
|
16761
|
+
let escaped = false;
|
|
16762
|
+
for (let index2 = start; index2 < value.length; index2 += 1) {
|
|
16763
|
+
const character = value[index2];
|
|
16764
|
+
if (inString) {
|
|
16765
|
+
if (escaped) escaped = false;
|
|
16766
|
+
else if (character === "\\") escaped = true;
|
|
16767
|
+
else if (character === '"') inString = false;
|
|
16768
|
+
continue;
|
|
16769
|
+
}
|
|
16770
|
+
if (character === '"') inString = true;
|
|
16771
|
+
else if (character === "{") depth += 1;
|
|
16772
|
+
else if (character === "}" && --depth === 0) return index2 + 1;
|
|
16773
|
+
}
|
|
16774
|
+
return null;
|
|
16775
|
+
}
|
|
16776
|
+
function parseOpenCodeModelMetadata(output) {
|
|
16777
|
+
const cleaned = cleanOutput(output);
|
|
16778
|
+
const models = /* @__PURE__ */ new Set();
|
|
16779
|
+
const variantsByModel = {};
|
|
16780
|
+
const headingPattern = /^([^\s/]+\/[^\r\n]+)\r?\n\s*(?=\{)/gm;
|
|
16781
|
+
for (const match2 of cleaned.matchAll(headingPattern)) {
|
|
16782
|
+
const model = match2[1].trim();
|
|
16783
|
+
models.add(model);
|
|
16784
|
+
let objectStart = (match2.index ?? 0) + match2[0].length;
|
|
16785
|
+
while (/\s/.test(cleaned[objectStart] ?? "")) objectStart += 1;
|
|
16786
|
+
if (cleaned[objectStart] !== "{") continue;
|
|
16787
|
+
const objectEnd = findJsonObjectEnd(cleaned, objectStart);
|
|
16788
|
+
if (objectEnd === null) continue;
|
|
16789
|
+
try {
|
|
16790
|
+
const metadata = JSON.parse(cleaned.slice(objectStart, objectEnd));
|
|
16791
|
+
if (!isRecord(metadata) || !isRecord(metadata.variants)) continue;
|
|
16792
|
+
variantsByModel[model] = Object.keys(metadata.variants).filter((variant) => variant.trim().length > 0).sort((left, right) => left.localeCompare(right));
|
|
16793
|
+
} catch {
|
|
16794
|
+
}
|
|
16795
|
+
}
|
|
16796
|
+
return {
|
|
16797
|
+
models: [...models].sort((left, right) => left.localeCompare(right)),
|
|
16798
|
+
variantsByModel
|
|
16799
|
+
};
|
|
16800
|
+
}
|
|
16716
16801
|
function parseOpenCodeAgents(output) {
|
|
16717
16802
|
const agents = /* @__PURE__ */ new Map();
|
|
16718
16803
|
for (const line of cleanOutput(output).split("\n")) {
|
|
@@ -16731,19 +16816,23 @@ async function loadOpenCodeCatalog(cwd, options = {}) {
|
|
|
16731
16816
|
const executable = options.executable ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
|
|
16732
16817
|
const timeoutMs = options.timeoutMs ?? COMMAND_TIMEOUT_MS;
|
|
16733
16818
|
const cacheKey = `${executable}\0${cwd}`;
|
|
16734
|
-
const
|
|
16735
|
-
if (options.useCache !== false &&
|
|
16736
|
-
return
|
|
16819
|
+
const cached2 = catalogCache.get(cacheKey);
|
|
16820
|
+
if (options.useCache !== false && cached2 && cached2.expiresAt > Date.now()) {
|
|
16821
|
+
return cached2.result;
|
|
16737
16822
|
}
|
|
16738
16823
|
const result = Promise.all([
|
|
16739
|
-
runOpenCode(executable, ["models"], cwd, timeoutMs)
|
|
16824
|
+
runOpenCode(executable, ["models", "--verbose"], cwd, timeoutMs).catch((error) => {
|
|
16825
|
+
if (!(error instanceof OpenCodeCommandExitError)) throw error;
|
|
16826
|
+
return runOpenCode(executable, ["models"], cwd, timeoutMs);
|
|
16827
|
+
}),
|
|
16740
16828
|
runOpenCode(executable, ["agent", "list"], cwd, timeoutMs)
|
|
16741
16829
|
]).then(([modelsOutput, agentsOutput]) => {
|
|
16742
|
-
const
|
|
16830
|
+
const metadata = parseOpenCodeModelMetadata(modelsOutput);
|
|
16831
|
+
const models = metadata.models.length > 0 ? metadata.models : parseOpenCodeModels(modelsOutput);
|
|
16743
16832
|
const agents = parseOpenCodeAgents(agentsOutput).filter((agent) => agent.mode !== "subagent");
|
|
16744
16833
|
if (models.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u7528\u6A21\u578B");
|
|
16745
16834
|
if (agents.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u76F4\u63A5\u8FD0\u884C\u7684\u4E3B Agent");
|
|
16746
|
-
return { cwd, models, agents };
|
|
16835
|
+
return { cwd, models, variantsByModel: metadata.variantsByModel, agents };
|
|
16747
16836
|
});
|
|
16748
16837
|
if (options.useCache !== false) {
|
|
16749
16838
|
catalogCache.set(cacheKey, { expiresAt: Date.now() + CATALOG_CACHE_MS, result });
|
|
@@ -16770,6 +16859,59 @@ import { tmpdir } from "os";
|
|
|
16770
16859
|
import { basename, dirname as dirname2, resolve } from "path";
|
|
16771
16860
|
|
|
16772
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
|
+
`;
|
|
16773
16915
|
function isProcessAlive(pid) {
|
|
16774
16916
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
16775
16917
|
try {
|
|
@@ -17336,7 +17478,7 @@ var TaskRunService = class {
|
|
|
17336
17478
|
}
|
|
17337
17479
|
if (current.childPid != null) {
|
|
17338
17480
|
throw new LegacyRunAbandonConflictError(
|
|
17339
|
-
`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`
|
|
17340
17482
|
);
|
|
17341
17483
|
}
|
|
17342
17484
|
if (current.workerPid != null && isProcessAlive(current.workerPid)) {
|
|
@@ -17394,6 +17536,22 @@ function normalizeTaskBatchId(batchId) {
|
|
|
17394
17536
|
return batchId.trim() || null;
|
|
17395
17537
|
}
|
|
17396
17538
|
|
|
17539
|
+
// src/core/model-variant.ts
|
|
17540
|
+
var MAX_MODEL_VARIANT_LENGTH = 128;
|
|
17541
|
+
var CONTROL_CHARACTER_PATTERN = /[\u0000-\u001F\u007F]/;
|
|
17542
|
+
function normalizeModelVariant(value) {
|
|
17543
|
+
if (value === void 0 || value === null) return value;
|
|
17544
|
+
const normalized = value.trim();
|
|
17545
|
+
if (!normalized) return null;
|
|
17546
|
+
if (normalized.length > MAX_MODEL_VARIANT_LENGTH) {
|
|
17547
|
+
throw new Error(`variant \u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${MAX_MODEL_VARIANT_LENGTH} \u4E2A\u5B57\u7B26`);
|
|
17548
|
+
}
|
|
17549
|
+
if (CONTROL_CHARACTER_PATTERN.test(normalized)) {
|
|
17550
|
+
throw new Error("variant \u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26");
|
|
17551
|
+
}
|
|
17552
|
+
return normalized;
|
|
17553
|
+
}
|
|
17554
|
+
|
|
17397
17555
|
// src/core/services/task.service.ts
|
|
17398
17556
|
var { tasks: tasks3, taskRuns: taskRuns3 } = schema_exports;
|
|
17399
17557
|
var cleanupInvocation = 0;
|
|
@@ -17459,7 +17617,8 @@ var TaskService = class {
|
|
|
17459
17617
|
static async add(data) {
|
|
17460
17618
|
const normalizedData = {
|
|
17461
17619
|
...data,
|
|
17462
|
-
batchId: normalizeTaskBatchId(data.batchId)
|
|
17620
|
+
batchId: normalizeTaskBatchId(data.batchId),
|
|
17621
|
+
variant: normalizeModelVariant(data.variant)
|
|
17463
17622
|
};
|
|
17464
17623
|
this.validateNewTask(normalizedData);
|
|
17465
17624
|
return db.transaction((tx) => {
|
|
@@ -17487,7 +17646,11 @@ var TaskService = class {
|
|
|
17487
17646
|
}
|
|
17488
17647
|
static async update(id, data, scope = {}) {
|
|
17489
17648
|
if (Object.keys(data).length === 0) throw new Error("\u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A\u8981\u4FEE\u6539\u7684\u5B57\u6BB5");
|
|
17490
|
-
const normalizedData =
|
|
17649
|
+
const normalizedData = {
|
|
17650
|
+
...data,
|
|
17651
|
+
...data.batchId === void 0 ? {} : { batchId: normalizeTaskBatchId(data.batchId) ?? null },
|
|
17652
|
+
...data.variant === void 0 ? {} : { variant: normalizeModelVariant(data.variant) ?? null }
|
|
17653
|
+
};
|
|
17491
17654
|
return db.transaction((tx) => {
|
|
17492
17655
|
const task = tx.select().from(tasks3).where(and(
|
|
17493
17656
|
eq(tasks3.id, id),
|
|
@@ -17499,6 +17662,7 @@ var TaskService = class {
|
|
|
17499
17662
|
name: normalizedData.name ?? task.name,
|
|
17500
17663
|
agent: normalizedData.agent ?? task.agent,
|
|
17501
17664
|
model: normalizedData.model ?? task.model,
|
|
17665
|
+
variant: normalizedData.variant === void 0 ? task.variant : normalizedData.variant,
|
|
17502
17666
|
prompt: normalizedData.prompt ?? task.prompt,
|
|
17503
17667
|
cwd: task.cwd,
|
|
17504
17668
|
category: normalizedData.category ?? task.category,
|
|
@@ -17512,13 +17676,23 @@ var TaskService = class {
|
|
|
17512
17676
|
});
|
|
17513
17677
|
const maxRetries = normalizedData.maxRetries ?? task.maxRetries ?? 3;
|
|
17514
17678
|
const exhausted = task.status === "failed" && (task.retryCount ?? 0) > maxRetries;
|
|
17515
|
-
|
|
17679
|
+
const updated = tx.update(tasks3).set({
|
|
17516
17680
|
...normalizedData,
|
|
17517
17681
|
...exhausted ? {
|
|
17518
17682
|
status: "dead_letter",
|
|
17519
17683
|
retryAfter: null
|
|
17520
17684
|
} : {}
|
|
17521
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;
|
|
17522
17696
|
}, { behavior: "immediate" });
|
|
17523
17697
|
}
|
|
17524
17698
|
static validateNewTask(data) {
|
|
@@ -17539,7 +17713,7 @@ var TaskService = class {
|
|
|
17539
17713
|
throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
|
|
17540
17714
|
}
|
|
17541
17715
|
}
|
|
17542
|
-
static
|
|
17716
|
+
static buildRunnableTaskWhere(scope) {
|
|
17543
17717
|
const baseConditions = [...this.buildScopeWhere(scope)];
|
|
17544
17718
|
const nowMs = Date.now();
|
|
17545
17719
|
const retryAfterFilter = or(
|
|
@@ -17574,40 +17748,43 @@ var TaskService = class {
|
|
|
17574
17748
|
if (batchFilter) {
|
|
17575
17749
|
conditions.push(batchFilter);
|
|
17576
17750
|
}
|
|
17577
|
-
|
|
17751
|
+
return and(
|
|
17578
17752
|
...conditions,
|
|
17579
17753
|
or(
|
|
17580
17754
|
isNull(tasks3.dependsOn),
|
|
17581
17755
|
sql`EXISTS (
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17585
|
-
|
|
17586
|
-
|
|
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
|
+
)`
|
|
17587
17761
|
),
|
|
17588
17762
|
or(
|
|
17589
17763
|
isNull(tasks3.batchId),
|
|
17590
17764
|
sql`trim(${tasks3.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ''`,
|
|
17591
17765
|
sql`NOT EXISTS (
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
17595
|
-
|
|
17596
|
-
|
|
17597
|
-
|
|
17598
|
-
|
|
17599
|
-
|
|
17600
|
-
|
|
17601
|
-
)
|
|
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'
|
|
17602
17775
|
)
|
|
17603
|
-
|
|
17776
|
+
)
|
|
17777
|
+
)`
|
|
17604
17778
|
),
|
|
17605
17779
|
sql`NOT EXISTS (
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17609
|
-
|
|
17610
|
-
)
|
|
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(
|
|
17611
17788
|
desc(tasks3.urgency),
|
|
17612
17789
|
desc(tasks3.importance),
|
|
17613
17790
|
asc(tasks3.createdAt),
|
|
@@ -17615,6 +17792,22 @@ var TaskService = class {
|
|
|
17615
17792
|
).limit(1);
|
|
17616
17793
|
return result[0] ?? null;
|
|
17617
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
|
+
}
|
|
17618
17811
|
static async countRunning(scope = {}) {
|
|
17619
17812
|
const scopeConditions = scope.legacyCwd ? [sql`${tasks3.cwd} IS NULL OR trim(${tasks3.cwd}) = ''`] : this.buildScopeWhere(scope);
|
|
17620
17813
|
if (scope.batchId !== void 0) {
|
|
@@ -18273,7 +18466,8 @@ var TaskTemplateService = class {
|
|
|
18273
18466
|
static async create(data) {
|
|
18274
18467
|
const normalizedData = {
|
|
18275
18468
|
...data,
|
|
18276
|
-
batchId: normalizeTaskBatchId(data.batchId)
|
|
18469
|
+
batchId: normalizeTaskBatchId(data.batchId),
|
|
18470
|
+
variant: normalizeModelVariant(data.variant)
|
|
18277
18471
|
};
|
|
18278
18472
|
this.validate(normalizedData);
|
|
18279
18473
|
const now = Date.now();
|
|
@@ -18339,7 +18533,8 @@ var TaskTemplateService = class {
|
|
|
18339
18533
|
static async update(id, data) {
|
|
18340
18534
|
const normalizedData = {
|
|
18341
18535
|
...data,
|
|
18342
|
-
batchId: normalizeTaskBatchId(data.batchId) ?? null
|
|
18536
|
+
batchId: normalizeTaskBatchId(data.batchId) ?? null,
|
|
18537
|
+
...data.variant === void 0 ? {} : { variant: normalizeModelVariant(data.variant) ?? null }
|
|
18343
18538
|
};
|
|
18344
18539
|
this.validate(normalizedData);
|
|
18345
18540
|
const now = Date.now();
|
|
@@ -18680,6 +18875,7 @@ function createTaskFromTemplate(templateId, options) {
|
|
|
18680
18875
|
name: `${options.namePrefix ?? ""}${tmpl.name}`,
|
|
18681
18876
|
agent: tmpl.agent,
|
|
18682
18877
|
model: tmpl.model ?? "default",
|
|
18878
|
+
variant: tmpl.variant,
|
|
18683
18879
|
prompt: tmpl.prompt,
|
|
18684
18880
|
cwd: tmpl.cwd ?? null,
|
|
18685
18881
|
category: tmpl.category ?? "general",
|
|
@@ -18704,452 +18900,101 @@ function createTaskFromTemplate(templateId, options) {
|
|
|
18704
18900
|
}, { behavior: "immediate" });
|
|
18705
18901
|
}
|
|
18706
18902
|
|
|
18707
|
-
// src/
|
|
18708
|
-
import {
|
|
18709
|
-
import {
|
|
18710
|
-
|
|
18711
|
-
chmodSync as chmodSync2,
|
|
18712
|
-
constants,
|
|
18713
|
-
existsSync as existsSync4,
|
|
18714
|
-
mkdirSync as mkdirSync3,
|
|
18715
|
-
readFileSync as readFileSync2,
|
|
18716
|
-
rmSync,
|
|
18717
|
-
statSync as statSync3,
|
|
18718
|
-
writeFileSync as writeFileSync2
|
|
18719
|
-
} from "fs";
|
|
18720
|
-
import { homedir as homedir3, userInfo } from "os";
|
|
18721
|
-
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";
|
|
18722
18907
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18723
|
-
|
|
18724
|
-
|
|
18725
|
-
|
|
18726
|
-
|
|
18727
|
-
|
|
18728
|
-
|
|
18729
|
-
|
|
18730
|
-
var PROCESS_NAME = "supertask-gateway";
|
|
18731
|
-
var MAC_LAUNCH_AGENT_LABEL = "com.supertask.pm2-resurrect";
|
|
18732
|
-
var GATEWAY_LOCK_STALE_MS = 3e4;
|
|
18733
|
-
function runtimeHome(env) {
|
|
18734
|
-
return resolve2(env.HOME || homedir3());
|
|
18735
|
-
}
|
|
18736
|
-
function runtimePath(value, cwd) {
|
|
18737
|
-
return resolve2(cwd, value);
|
|
18738
|
-
}
|
|
18739
|
-
function versionFile(env = process.env, cwd = process.cwd()) {
|
|
18740
|
-
return env.SUPERTASK_VERSION_FILE ? runtimePath(env.SUPERTASK_VERSION_FILE, cwd) : join3(runtimeHome(env), ".local/share/opencode/supertask-gateway-version");
|
|
18741
|
-
}
|
|
18742
|
-
function getRunningVersion(env = process.env, cwd = process.cwd()) {
|
|
18743
|
-
try {
|
|
18744
|
-
const path = versionFile(env, cwd);
|
|
18745
|
-
if (!existsSync4(path)) return null;
|
|
18746
|
-
return readFileSync2(path, "utf-8").trim() || null;
|
|
18747
|
-
} catch {
|
|
18748
|
-
return null;
|
|
18749
|
-
}
|
|
18750
|
-
}
|
|
18751
|
-
function packageVersionFromGatewayEntry(gatewayEntry) {
|
|
18752
|
-
if (gatewayEntry === null) return null;
|
|
18753
|
-
let directory = dirname3(gatewayEntry);
|
|
18754
|
-
for (let depth = 0; depth < 6; depth += 1) {
|
|
18755
|
-
const packagePath = join3(directory, "package.json");
|
|
18756
|
-
if (existsSync4(packagePath)) {
|
|
18757
|
-
try {
|
|
18758
|
-
const pkg = JSON.parse(readFileSync2(packagePath, "utf8"));
|
|
18759
|
-
if (pkg.name === "opencode-supertask" && typeof pkg.version === "string") {
|
|
18760
|
-
return pkg.version;
|
|
18761
|
-
}
|
|
18762
|
-
} catch {
|
|
18763
|
-
}
|
|
18764
|
-
}
|
|
18765
|
-
const parent = dirname3(directory);
|
|
18766
|
-
if (parent === directory) break;
|
|
18767
|
-
directory = parent;
|
|
18768
|
-
}
|
|
18769
|
-
return null;
|
|
18770
|
-
}
|
|
18771
|
-
function resolveRuntimeExecutable(command, env, cwd) {
|
|
18772
|
-
if (isAbsolute2(command) || command.includes("/")) return runtimePath(command, cwd);
|
|
18773
|
-
for (const entry of (env.PATH ?? "").split(delimiter)) {
|
|
18774
|
-
if (!entry) continue;
|
|
18775
|
-
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") {
|
|
18776
18915
|
try {
|
|
18777
|
-
|
|
18778
|
-
return
|
|
18916
|
+
process.kill(-pid, "SIGKILL");
|
|
18917
|
+
return;
|
|
18779
18918
|
} catch {
|
|
18780
18919
|
}
|
|
18781
18920
|
}
|
|
18782
|
-
return command;
|
|
18783
|
-
}
|
|
18784
|
-
function runtimeScope(runtime) {
|
|
18785
|
-
const { cwd, env } = runtime;
|
|
18786
|
-
const home = runtimeHome(env);
|
|
18787
|
-
return {
|
|
18788
|
-
cwd: resolve2(cwd),
|
|
18789
|
-
databasePath: env.SUPERTASK_DB_PATH ? runtimePath(env.SUPERTASK_DB_PATH, cwd) : join3(home, ".local/share/opencode/tasks.db"),
|
|
18790
|
-
configPath: env.SUPERTASK_CONFIG_PATH ? runtimePath(env.SUPERTASK_CONFIG_PATH, cwd) : join3(home, ".config/opencode/supertask.json"),
|
|
18791
|
-
opencodePath: resolveRuntimeExecutable(env.SUPERTASK_OPENCODE_BIN ?? "opencode", env, cwd),
|
|
18792
|
-
home,
|
|
18793
|
-
pm2Home: env.PM2_HOME ? runtimePath(env.PM2_HOME, cwd) : join3(home, ".pm2"),
|
|
18794
|
-
managementLockPath: canonicalManagementLockPath(env, cwd)
|
|
18795
|
-
};
|
|
18796
|
-
}
|
|
18797
|
-
function scopesMatch(left, right) {
|
|
18798
|
-
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;
|
|
18799
|
-
}
|
|
18800
|
-
function currentScope() {
|
|
18801
|
-
return runtimeScope({ cwd: process.cwd(), env: process.env });
|
|
18802
|
-
}
|
|
18803
|
-
function diagnoseOpenCodeRuntime(runtime = {
|
|
18804
|
-
cwd: process.cwd(),
|
|
18805
|
-
env: process.env
|
|
18806
|
-
}) {
|
|
18807
|
-
const executable = runtimeScope(runtime).opencodePath;
|
|
18808
|
-
const result = spawnSync(executable, ["--version"], {
|
|
18809
|
-
cwd: runtime.cwd,
|
|
18810
|
-
env: runtime.env,
|
|
18811
|
-
encoding: "utf8",
|
|
18812
|
-
timeout: 1e4,
|
|
18813
|
-
killSignal: "SIGKILL"
|
|
18814
|
-
});
|
|
18815
|
-
const ok = result.status === 0 && result.error === void 0;
|
|
18816
|
-
return {
|
|
18817
|
-
ok,
|
|
18818
|
-
executable,
|
|
18819
|
-
version: ok ? result.stdout.trim() || result.stderr.trim() || null : null,
|
|
18820
|
-
error: ok ? null : result.error?.message || result.stderr.trim() || result.stdout.trim() || `exit code ${result.status}`
|
|
18821
|
-
};
|
|
18822
|
-
}
|
|
18823
|
-
function getGatewayDiagnostic(options = {}) {
|
|
18824
|
-
const producerScope = currentScope();
|
|
18825
|
-
if (!isPm2Installed()) {
|
|
18826
|
-
return {
|
|
18827
|
-
pm2Installed: false,
|
|
18828
|
-
processFound: false,
|
|
18829
|
-
status: null,
|
|
18830
|
-
pid: null,
|
|
18831
|
-
ready: false,
|
|
18832
|
-
runningVersion: getRunningVersion(),
|
|
18833
|
-
gatewayEntry: null,
|
|
18834
|
-
gatewayPackageVersion: null,
|
|
18835
|
-
logRotationInstalled: false,
|
|
18836
|
-
startupConfigured: process.platform === "darwin" || process.platform === "linux" ? false : null,
|
|
18837
|
-
currentScope: producerScope,
|
|
18838
|
-
gatewayScope: null,
|
|
18839
|
-
scopeMatches: false,
|
|
18840
|
-
gatewayOpenCode: null
|
|
18841
|
-
};
|
|
18842
|
-
}
|
|
18843
|
-
const processes = pm2JsonList();
|
|
18844
|
-
const gateway = processes.find((item) => item.name === PROCESS_NAME);
|
|
18845
|
-
const runtime = gatewayRuntimeFromProcess(gateway);
|
|
18846
|
-
const gatewayEnv = runtime?.env ?? process.env;
|
|
18847
|
-
const managedScope = runtime ? runtimeScope(runtime) : null;
|
|
18848
|
-
const pid = typeof gateway?.pid === "number" ? gateway.pid : null;
|
|
18849
|
-
const readyPath = managedScope?.databasePath ?? databasePath();
|
|
18850
|
-
const lockedVersion = pid == null ? void 0 : gatewayVersionFromLock(pid, readyPath);
|
|
18851
|
-
const gatewayEntry = runtime?.gatewayEntry ?? null;
|
|
18852
|
-
return {
|
|
18853
|
-
pm2Installed: true,
|
|
18854
|
-
processFound: gateway != null,
|
|
18855
|
-
status: gateway?.pm2_env?.status ?? null,
|
|
18856
|
-
pid,
|
|
18857
|
-
ready: pid != null && isGatewayReady(pid, readyPath),
|
|
18858
|
-
runningVersion: lockedVersion === void 0 ? getRunningVersion(gatewayEnv, runtime?.cwd) : lockedVersion,
|
|
18859
|
-
gatewayEntry,
|
|
18860
|
-
gatewayPackageVersion: packageVersionFromGatewayEntry(gatewayEntry),
|
|
18861
|
-
logRotationInstalled: processes.some((item) => item.name === "pm2-logrotate" && item.pm2_env?.status === "online"),
|
|
18862
|
-
startupConfigured: process.platform === "darwin" ? isMacLaunchAgentConfigured(
|
|
18863
|
-
gatewayEnv.PM2_HOME ?? join3(runtimeHome(gatewayEnv), ".pm2"),
|
|
18864
|
-
runtime ?? void 0
|
|
18865
|
-
) : process.platform === "linux" ? isLinuxStartupConfigured(gatewayEnv, runtime?.cwd, runtime ?? void 0) : null,
|
|
18866
|
-
currentScope: producerScope,
|
|
18867
|
-
gatewayScope: managedScope,
|
|
18868
|
-
scopeMatches: managedScope != null && scopesMatch(producerScope, managedScope),
|
|
18869
|
-
gatewayOpenCode: runtime === null || !options.probeOpenCode ? null : diagnoseOpenCodeRuntime(runtime)
|
|
18870
|
-
};
|
|
18871
|
-
}
|
|
18872
|
-
function pm2Bin(env = process.env) {
|
|
18873
|
-
return env.SUPERTASK_PM2_BIN ?? (process.platform === "win32" ? "pm2.cmd" : "pm2");
|
|
18874
|
-
}
|
|
18875
|
-
function pm2CommandTimeoutMs(env = process.env) {
|
|
18876
|
-
const configured = Number(env.SUPERTASK_PM2_COMMAND_TIMEOUT_MS ?? 15e3);
|
|
18877
|
-
return Number.isFinite(configured) && configured > 0 ? configured : 15e3;
|
|
18878
|
-
}
|
|
18879
|
-
function resolvePm2Bin() {
|
|
18880
|
-
const configured = pm2Bin();
|
|
18881
|
-
if (isAbsolute2(configured)) return configured;
|
|
18882
|
-
const result = spawnSync("which", [configured], { encoding: "utf8" });
|
|
18883
|
-
const resolved = result.status === 0 ? result.stdout.trim().split("\n")[0] : "";
|
|
18884
|
-
if (!resolved) throw new Error(`[supertask] \u65E0\u6CD5\u89E3\u6790 pm2 \u53EF\u6267\u884C\u6587\u4EF6: ${configured}`);
|
|
18885
|
-
return resolved;
|
|
18886
|
-
}
|
|
18887
|
-
function launchAgentPath() {
|
|
18888
|
-
return process.env.SUPERTASK_LAUNCH_AGENT_PATH ?? join3(runtimeHome(process.env), "Library/LaunchAgents", `${MAC_LAUNCH_AGENT_LABEL}.plist`);
|
|
18889
|
-
}
|
|
18890
|
-
function launchctlBin() {
|
|
18891
|
-
return process.env.SUPERTASK_LAUNCHCTL_BIN ?? "launchctl";
|
|
18892
|
-
}
|
|
18893
|
-
function xmlUnescape(value) {
|
|
18894
|
-
return value.replaceAll("'", "'").replaceAll(""", '"').replaceAll(">", ">").replaceAll("<", "<").replaceAll("&", "&");
|
|
18895
|
-
}
|
|
18896
|
-
function plistValue(contents, key) {
|
|
18897
|
-
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18898
|
-
const match2 = contents.match(new RegExp(`<key>\\s*${escapedKey}\\s*</key>\\s*<string>([^<]*)</string>`));
|
|
18899
|
-
return match2?.[1] ? xmlUnescape(match2[1]) : null;
|
|
18900
|
-
}
|
|
18901
|
-
function isMacLaunchAgentConfigured(expectedPm2Home, expectedRuntime) {
|
|
18902
|
-
if (typeof process.getuid !== "function") return false;
|
|
18903
|
-
const plistPath = launchAgentPath();
|
|
18904
|
-
if (!existsSync4(plistPath)) return false;
|
|
18905
18921
|
try {
|
|
18906
|
-
|
|
18907
|
-
const argumentsBlock = contents.match(
|
|
18908
|
-
/<key>\s*ProgramArguments\s*<\/key>\s*<array>([\s\S]*?)<\/array>/
|
|
18909
|
-
)?.[1];
|
|
18910
|
-
const programArguments = argumentsBlock ? [...argumentsBlock.matchAll(/<string>([^<]*)<\/string>/g)].map((match2) => xmlUnescape(match2[1])) : [];
|
|
18911
|
-
const bunPath = programArguments[0];
|
|
18912
|
-
const supervisorEntry = programArguments[1];
|
|
18913
|
-
const pm2Path = programArguments[2];
|
|
18914
|
-
const pm2Home = plistValue(contents, "PM2_HOME");
|
|
18915
|
-
const managementLock = plistValue(contents, "SUPERTASK_PM2_MANAGEMENT_LOCK");
|
|
18916
|
-
if (!bunPath || !supervisorEntry || !pm2Path || !pm2Home || !managementLock) return false;
|
|
18917
|
-
if (expectedPm2Home && pm2Home !== expectedPm2Home) return false;
|
|
18918
|
-
const expectedManagementLock = expectedRuntime ? managementLockPath(expectedRuntime.env, expectedRuntime.cwd) : process.env.SUPERTASK_PM2_MANAGEMENT_LOCK ? managementLockPath() : join3(expectedPm2Home ?? currentScope().pm2Home, "supertask-gateway.manage.sqlite");
|
|
18919
|
-
if (managementLock !== expectedManagementLock) return false;
|
|
18920
|
-
accessSync(bunPath, constants.X_OK);
|
|
18921
|
-
accessSync(supervisorEntry, constants.R_OK);
|
|
18922
|
-
accessSync(pm2Path, constants.X_OK);
|
|
18923
|
-
if (spawnSync(bunPath, ["--version"], {
|
|
18924
|
-
stdio: "ignore",
|
|
18925
|
-
timeout: pm2CommandTimeoutMs(),
|
|
18926
|
-
killSignal: "SIGKILL"
|
|
18927
|
-
}).status !== 0) return false;
|
|
18928
|
-
if (spawnSync(pm2Path, ["--version"], {
|
|
18929
|
-
stdio: "ignore",
|
|
18930
|
-
env: { ...process.env, PM2_HOME: pm2Home },
|
|
18931
|
-
timeout: pm2CommandTimeoutMs(),
|
|
18932
|
-
killSignal: "SIGKILL"
|
|
18933
|
-
}).status !== 0) return false;
|
|
18934
|
-
const loaded = spawnSync(
|
|
18935
|
-
launchctlBin(),
|
|
18936
|
-
["print", `gui/${process.getuid()}/${MAC_LAUNCH_AGENT_LABEL}`],
|
|
18937
|
-
{
|
|
18938
|
-
encoding: "utf8",
|
|
18939
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
18940
|
-
timeout: pm2CommandTimeoutMs(),
|
|
18941
|
-
killSignal: "SIGKILL"
|
|
18942
|
-
}
|
|
18943
|
-
);
|
|
18944
|
-
if (loaded.status !== 0) return false;
|
|
18945
|
-
if (!loaded.stdout.includes("state = running")) return false;
|
|
18946
|
-
if (!loaded.stdout.includes(`path = ${plistPath}`)) return false;
|
|
18947
|
-
if (!loaded.stdout.includes(`program = ${bunPath}`)) return false;
|
|
18948
|
-
const dumpPath = join3(pm2Home, "dump.pm2");
|
|
18949
|
-
const dump = JSON.parse(readFileSync2(dumpPath, "utf8"));
|
|
18950
|
-
if (!Array.isArray(dump)) return false;
|
|
18951
|
-
const gateway = dump.find((item) => item.name === PROCESS_NAME);
|
|
18952
|
-
const dumpRuntime = gatewayRuntimeFromProcess(gateway);
|
|
18953
|
-
if (!dumpRuntime || !hasRestorableSavedGatewayRuntime(gateway)) return false;
|
|
18954
|
-
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");
|
|
18955
18923
|
} catch {
|
|
18956
|
-
return false;
|
|
18957
18924
|
}
|
|
18958
18925
|
}
|
|
18959
|
-
|
|
18960
|
-
|
|
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;
|
|
18961
18939
|
}
|
|
18962
|
-
function
|
|
18963
|
-
|
|
18964
|
-
|
|
18965
|
-
|
|
18966
|
-
|
|
18967
|
-
|
|
18968
|
-
|
|
18969
|
-
|
|
18970
|
-
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
18976
|
-
|
|
18977
|
-
|
|
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);
|
|
18978
18984
|
});
|
|
18979
|
-
if (contents.status !== 0) return false;
|
|
18980
|
-
const expectedPm2Home = env.PM2_HOME ?? join3(runtimeHome(env), ".pm2");
|
|
18981
|
-
if (!contents.stdout.includes("resurrect") || !contents.stdout.includes(expectedPm2Home)) {
|
|
18982
|
-
return false;
|
|
18983
|
-
}
|
|
18984
|
-
try {
|
|
18985
|
-
const dump = JSON.parse(readFileSync2(join3(expectedPm2Home, "dump.pm2"), "utf8"));
|
|
18986
|
-
if (!Array.isArray(dump)) return false;
|
|
18987
|
-
const gateway = dump.find((item) => item.name === PROCESS_NAME);
|
|
18988
|
-
const runtime = gatewayRuntimeFromProcess(gateway);
|
|
18989
|
-
if (!runtime || !hasRestorableSavedGatewayRuntime(gateway)) return false;
|
|
18990
|
-
return runtime.cwd === resolve2(cwd) && scopesMatch(runtimeScope(runtime), runtimeScope({ cwd, env })) && (expectedRuntime === void 0 || runtime.gatewayEntry === expectedRuntime.gatewayEntry && runtime.bunPath === expectedRuntime.bunPath);
|
|
18991
|
-
} catch {
|
|
18992
|
-
return false;
|
|
18993
|
-
}
|
|
18994
18985
|
}
|
|
18995
|
-
function
|
|
18996
|
-
|
|
18997
|
-
|
|
18998
|
-
|
|
18999
|
-
|
|
19000
|
-
|
|
19001
|
-
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;
|
|
19002
18992
|
});
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
const lookup = process.platform === "win32" ? "where" : "which";
|
|
19007
|
-
const result = spawnSync(lookup, [command], { encoding: "utf8" });
|
|
19008
|
-
return result.status === 0 ? result.stdout.trim().split("\n")[0] || null : null;
|
|
19009
|
-
}
|
|
19010
|
-
function npmPreferredEnvironment() {
|
|
19011
|
-
if (process.platform === "win32") return null;
|
|
19012
|
-
const npm = resolveCommand("npm");
|
|
19013
|
-
const node = resolveCommand("node");
|
|
19014
|
-
if (!npm || !node) return null;
|
|
19015
|
-
const command = resolvePm2Bin();
|
|
19016
|
-
const path = [.../* @__PURE__ */ new Set([
|
|
19017
|
-
dirname3(command),
|
|
19018
|
-
dirname3(npm),
|
|
19019
|
-
dirname3(node),
|
|
19020
|
-
"/usr/local/bin",
|
|
19021
|
-
"/usr/bin",
|
|
19022
|
-
"/bin"
|
|
19023
|
-
])].join(delimiter);
|
|
19024
|
-
return { command, env: { ...process.env, PATH: path } };
|
|
19025
|
-
}
|
|
19026
|
-
function pm2Exec(args, options = {}) {
|
|
19027
|
-
const npmEnvironment = options.preferNpm ? npmPreferredEnvironment() : null;
|
|
19028
|
-
const effectiveEnv = options.env ?? npmEnvironment?.env ?? process.env;
|
|
19029
|
-
const result = spawnSync(npmEnvironment?.command ?? pm2Bin(effectiveEnv), args, {
|
|
19030
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
19031
|
-
encoding: "utf-8",
|
|
19032
|
-
env: effectiveEnv,
|
|
19033
|
-
shell: process.platform === "win32",
|
|
19034
|
-
timeout: options.timeoutMs ?? pm2CommandTimeoutMs(effectiveEnv),
|
|
19035
|
-
killSignal: "SIGKILL"
|
|
18993
|
+
if (options.fresh) return operation;
|
|
18994
|
+
pending = operation.finally(() => {
|
|
18995
|
+
pending = null;
|
|
19036
18996
|
});
|
|
19037
|
-
|
|
19038
|
-
if (result.error) return { ok: false, output: result.error.message };
|
|
19039
|
-
return { ok: result.status === 0, output };
|
|
19040
|
-
}
|
|
19041
|
-
function requirePm2(args, action, options = {}) {
|
|
19042
|
-
const result = pm2Exec(args, options);
|
|
19043
|
-
if (!result.ok) throw new Error(`[supertask] ${action} failed: ${result.output || "unknown pm2 error"}`);
|
|
19044
|
-
return result.output;
|
|
19045
|
-
}
|
|
19046
|
-
function pm2JsonList(env = process.env) {
|
|
19047
|
-
const output = requirePm2(["jlist"], "pm2 jlist", { env });
|
|
19048
|
-
try {
|
|
19049
|
-
const parsed = JSON.parse(output);
|
|
19050
|
-
if (!Array.isArray(parsed)) throw new Error("result is not an array");
|
|
19051
|
-
return parsed;
|
|
19052
|
-
} catch (error) {
|
|
19053
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
19054
|
-
throw new Error(`[supertask] Invalid pm2 jlist output: ${message}`);
|
|
19055
|
-
}
|
|
19056
|
-
}
|
|
19057
|
-
function gatewayEntryFromProcess(processInfo) {
|
|
19058
|
-
const args = processInfo?.pm2_env?.args ?? processInfo?.args;
|
|
19059
|
-
const candidates = Array.isArray(args) ? [...args].reverse() : typeof args === "string" ? [args] : [];
|
|
19060
|
-
const savedCwd = processInfo?.pm2_env?.pm_cwd ?? processInfo?.pm_cwd;
|
|
19061
|
-
for (const candidate of candidates) {
|
|
19062
|
-
const path = typeof savedCwd === "string" ? runtimePath(candidate, savedCwd) : candidate;
|
|
19063
|
-
if (existsSync4(path)) return resolve2(path);
|
|
19064
|
-
}
|
|
19065
|
-
return null;
|
|
19066
|
-
}
|
|
19067
|
-
function gatewayEnvironmentFromProcess(processInfo) {
|
|
19068
|
-
const saved = processInfo?.pm2_env?.env ?? processInfo?.env;
|
|
19069
|
-
if (!saved) return { ...process.env };
|
|
19070
|
-
const env = {};
|
|
19071
|
-
for (const [key, value] of Object.entries(saved)) {
|
|
19072
|
-
if (typeof value === "string") env[key] = value;
|
|
19073
|
-
}
|
|
19074
|
-
return env;
|
|
19075
|
-
}
|
|
19076
|
-
function gatewayRuntimeFromProcess(processInfo) {
|
|
19077
|
-
const gatewayEntry = gatewayEntryFromProcess(processInfo);
|
|
19078
|
-
if (!gatewayEntry) return null;
|
|
19079
|
-
const savedBunPath = processInfo?.pm2_env?.pm_exec_path ?? processInfo?.pm_exec_path;
|
|
19080
|
-
const savedCwd = processInfo?.pm2_env?.pm_cwd ?? processInfo?.pm_cwd;
|
|
19081
|
-
const savedEnv = gatewayEnvironmentFromProcess(processInfo);
|
|
19082
|
-
if (typeof savedBunPath !== "string" || typeof savedCwd !== "string") return null;
|
|
19083
|
-
try {
|
|
19084
|
-
accessSync(savedBunPath, constants.X_OK);
|
|
19085
|
-
if (!statSync3(savedCwd).isDirectory()) return null;
|
|
19086
|
-
if (spawnSync(savedBunPath, ["--version"], {
|
|
19087
|
-
stdio: "ignore",
|
|
19088
|
-
env: savedEnv,
|
|
19089
|
-
timeout: pm2CommandTimeoutMs(savedEnv),
|
|
19090
|
-
killSignal: "SIGKILL"
|
|
19091
|
-
}).status !== 0) return null;
|
|
19092
|
-
} catch {
|
|
19093
|
-
return null;
|
|
19094
|
-
}
|
|
19095
|
-
const killTimeout = processInfo?.pm2_env?.kill_timeout ?? processInfo?.kill_timeout;
|
|
19096
|
-
return {
|
|
19097
|
-
gatewayEntry,
|
|
19098
|
-
bunPath: savedBunPath,
|
|
19099
|
-
cwd: resolve2(savedCwd),
|
|
19100
|
-
env: savedEnv,
|
|
19101
|
-
killTimeoutMs: Number.isInteger(killTimeout) && killTimeout >= 5e3 ? killTimeout : void 0
|
|
19102
|
-
};
|
|
19103
|
-
}
|
|
19104
|
-
function hasRestorableSavedGatewayRuntime(processInfo) {
|
|
19105
|
-
return gatewayRuntimeFromProcess(processInfo) !== null;
|
|
19106
|
-
}
|
|
19107
|
-
function databasePath(env = process.env, cwd = process.cwd()) {
|
|
19108
|
-
return env.SUPERTASK_DB_PATH ? runtimePath(env.SUPERTASK_DB_PATH, cwd) : join3(runtimeHome(env), ".local/share/opencode/tasks.db");
|
|
19109
|
-
}
|
|
19110
|
-
function isGatewayReady(expectedPid, path = databasePath()) {
|
|
19111
|
-
if (!existsSync4(path)) return false;
|
|
19112
|
-
let database = null;
|
|
19113
|
-
try {
|
|
19114
|
-
database = new Database5(path, { readonly: true });
|
|
19115
|
-
const row = database.query(
|
|
19116
|
-
"SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
|
|
19117
|
-
).get();
|
|
19118
|
-
if (!row || row.ready_at == null) return false;
|
|
19119
|
-
if (expectedPid !== void 0 && row.pid !== expectedPid) return false;
|
|
19120
|
-
const ageMs = Date.now() - row.heartbeat_at;
|
|
19121
|
-
return ageMs >= -5e3 && ageMs < GATEWAY_LOCK_STALE_MS;
|
|
19122
|
-
} catch {
|
|
19123
|
-
return false;
|
|
19124
|
-
} finally {
|
|
19125
|
-
database?.close();
|
|
19126
|
-
}
|
|
19127
|
-
}
|
|
19128
|
-
function gatewayVersionFromLock(expectedPid, path) {
|
|
19129
|
-
if (!existsSync4(path)) return void 0;
|
|
19130
|
-
let database = null;
|
|
19131
|
-
try {
|
|
19132
|
-
database = new Database5(path, { readonly: true });
|
|
19133
|
-
const row = database.query(
|
|
19134
|
-
"SELECT pid, version FROM gateway_lock WHERE id = 1"
|
|
19135
|
-
).get();
|
|
19136
|
-
if (!row || row.pid !== expectedPid) return void 0;
|
|
19137
|
-
return row.version;
|
|
19138
|
-
} catch {
|
|
19139
|
-
return void 0;
|
|
19140
|
-
} finally {
|
|
19141
|
-
database?.close();
|
|
19142
|
-
}
|
|
19143
|
-
}
|
|
19144
|
-
function managementLockPath(env = process.env, cwd = process.cwd()) {
|
|
19145
|
-
const override = env.SUPERTASK_PM2_MANAGEMENT_LOCK;
|
|
19146
|
-
if (override) return runtimePath(override, cwd);
|
|
19147
|
-
return join3(runtimeScope({ env, cwd }).pm2Home, "supertask-gateway.manage.sqlite");
|
|
19148
|
-
}
|
|
19149
|
-
function canonicalManagementLockPath(env = process.env, cwd = process.cwd()) {
|
|
19150
|
-
const home = runtimeHome(env);
|
|
19151
|
-
const pm2Home = env.PM2_HOME ? runtimePath(env.PM2_HOME, cwd) : join3(home, ".pm2");
|
|
19152
|
-
return join3(pm2Home, "supertask-gateway.manage.sqlite");
|
|
18997
|
+
return pending;
|
|
19153
18998
|
}
|
|
19154
18999
|
|
|
19155
19000
|
// src/web/ui.ts
|
|
@@ -19316,6 +19161,7 @@ var ZH = {
|
|
|
19316
19161
|
"template.cwdHint": "OpenCode \u4F1A\u5728\u8FD9\u4E2A\u76EE\u5F55\u4E2D\u6267\u884C\u4EFB\u52A1\u3002",
|
|
19317
19162
|
"template.agent": "Agent",
|
|
19318
19163
|
"template.model": "\u6A21\u578B",
|
|
19164
|
+
"template.variant": "\u6A21\u578B Variant",
|
|
19319
19165
|
"template.prompt": "\u63D0\u793A\u8BCD",
|
|
19320
19166
|
"template.scheduleType": "\u6267\u884C\u65B9\u5F0F",
|
|
19321
19167
|
"template.cronExpr": "Cron \u8868\u8FBE\u5F0F",
|
|
@@ -19353,9 +19199,11 @@ var ZH = {
|
|
|
19353
19199
|
"catalog.chooseProject": "\u8BF7\u5148\u9009\u62E9\u9879\u76EE\u76EE\u5F55",
|
|
19354
19200
|
"catalog.defaultModel": "\u8DDF\u968F Agent / OpenCode \u9ED8\u8BA4\u6A21\u578B",
|
|
19355
19201
|
"catalog.defaultProvider": "\u9ED8\u8BA4\u6A21\u578B",
|
|
19202
|
+
"catalog.defaultVariant": "\u8DDF\u968F Agent / \u6A21\u578B\u9ED8\u8BA4\u8BBE\u7F6E",
|
|
19356
19203
|
"catalog.provider": "\u6A21\u578B\u63D0\u4F9B\u5546",
|
|
19357
19204
|
"catalog.model": "\u5177\u4F53\u6A21\u578B",
|
|
19358
|
-
"catalog.modelHint": "\u5148\u9009\u63D0\u4F9B\u5546\uFF0C\u518D\u9009\u672C\u9879\u76EE opencode models \u8FD4\u56DE\u7684\u6A21\u578B\uFF1B\u9ED8\u8BA4\u9009\u9879\u4E0D\u4F1A\u4F20\u5165 -m\u3002",
|
|
19205
|
+
"catalog.modelHint": "\u5148\u9009\u63D0\u4F9B\u5546\uFF0C\u518D\u9009\u672C\u9879\u76EE opencode models --verbose \u8FD4\u56DE\u7684\u6A21\u578B\uFF1B\u9ED8\u8BA4\u9009\u9879\u4E0D\u4F1A\u4F20\u5165 -m\u3002",
|
|
19206
|
+
"catalog.variantHint": "\u4EC5\u663E\u793A\u6240\u9009\u6A21\u578B\u58F0\u660E\u652F\u6301\u7684 variants\uFF1B\u9ED8\u8BA4\u9009\u9879\u4E0D\u4F1A\u4F20\u5165 --variant\u3002",
|
|
19359
19207
|
"catalog.agentHint": "\u6765\u81EA\u5F53\u524D\u9879\u76EE\u7684 opencode agent list\u3002",
|
|
19360
19208
|
"catalog.loading": "\u6B63\u5728\u8BFB\u53D6\u6B64\u9879\u76EE\u53EF\u7528\u7684 Agent \u548C\u6A21\u578B\u2026",
|
|
19361
19209
|
"catalog.loaded": "\u5DF2\u4ECE\u672C\u673A OpenCode \u8BFB\u53D6 {agents} \u4E2A Agent\u3001{models} \u4E2A\u6A21\u578B\u3002",
|
|
@@ -19386,6 +19234,7 @@ var ZH = {
|
|
|
19386
19234
|
"details.copySuccess": "\u539F\u59CB\u6570\u636E\u5DF2\u590D\u5236",
|
|
19387
19235
|
"details.id": "\u7F16\u53F7",
|
|
19388
19236
|
"details.project": "\u9879\u76EE\u76EE\u5F55",
|
|
19237
|
+
"details.variant": "\u6A21\u578B Variant",
|
|
19389
19238
|
"details.prompt": "\u63D0\u793A\u8BCD",
|
|
19390
19239
|
"details.result": "\u6267\u884C\u7ED3\u679C / \u5931\u8D25\u539F\u56E0",
|
|
19391
19240
|
"details.category": "\u5206\u7C7B",
|
|
@@ -19418,7 +19267,7 @@ var ZH = {
|
|
|
19418
19267
|
"details.enabledYes": "\u5DF2\u542F\u7528",
|
|
19419
19268
|
"details.enabledNo": "\u5DF2\u505C\u7528",
|
|
19420
19269
|
"dialog.cancelTask": "\u53D6\u6D88\u4EFB\u52A1 #{id}\uFF1F",
|
|
19421
|
-
"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",
|
|
19422
19271
|
"dialog.retryTask": "\u91CD\u8BD5\u4EFB\u52A1 #{id}\uFF1F",
|
|
19423
19272
|
"dialog.retryTaskBody": "\u4EFB\u52A1\u5C06\u56DE\u5230\u5F85\u6267\u884C\u72B6\u6001\uFF0C\u5E76\u91CD\u7F6E\u81EA\u52A8\u91CD\u8BD5\u9884\u7B97\u3002",
|
|
19424
19273
|
"dialog.deleteTask": "\u5220\u9664\u4EFB\u52A1 #{id}\uFF1F",
|
|
@@ -19617,6 +19466,7 @@ var EN = {
|
|
|
19617
19466
|
"template.cwdHint": "OpenCode runs the task in this directory.",
|
|
19618
19467
|
"template.agent": "Agent",
|
|
19619
19468
|
"template.model": "Model",
|
|
19469
|
+
"template.variant": "Model variant",
|
|
19620
19470
|
"template.prompt": "Prompt",
|
|
19621
19471
|
"template.scheduleType": "Schedule",
|
|
19622
19472
|
"template.cronExpr": "Cron expression",
|
|
@@ -19654,9 +19504,11 @@ var EN = {
|
|
|
19654
19504
|
"catalog.chooseProject": "Choose a project directory first",
|
|
19655
19505
|
"catalog.defaultModel": "Use the Agent / OpenCode default model",
|
|
19656
19506
|
"catalog.defaultProvider": "Default model",
|
|
19507
|
+
"catalog.defaultVariant": "Use the Agent / model default",
|
|
19657
19508
|
"catalog.provider": "Model provider",
|
|
19658
19509
|
"catalog.model": "Model",
|
|
19659
|
-
"catalog.modelHint": "Choose a provider, then a model returned by opencode models for this project. Default does not pass -m.",
|
|
19510
|
+
"catalog.modelHint": "Choose a provider, then a model returned by opencode models --verbose for this project. Default does not pass -m.",
|
|
19511
|
+
"catalog.variantHint": "Only variants declared by the selected model are shown. Default does not pass --variant.",
|
|
19660
19512
|
"catalog.agentHint": "Loaded from opencode agent list for this project.",
|
|
19661
19513
|
"catalog.loading": "Loading Agents and models available to this project\u2026",
|
|
19662
19514
|
"catalog.loaded": "Loaded {agents} Agents and {models} models from local OpenCode.",
|
|
@@ -19687,6 +19539,7 @@ var EN = {
|
|
|
19687
19539
|
"details.copySuccess": "Raw data copied",
|
|
19688
19540
|
"details.id": "ID",
|
|
19689
19541
|
"details.project": "Project directory",
|
|
19542
|
+
"details.variant": "Model variant",
|
|
19690
19543
|
"details.prompt": "Prompt",
|
|
19691
19544
|
"details.result": "Result / failure reason",
|
|
19692
19545
|
"details.category": "Category",
|
|
@@ -19719,7 +19572,7 @@ var EN = {
|
|
|
19719
19572
|
"details.enabledYes": "Enabled",
|
|
19720
19573
|
"details.enabledNo": "Disabled",
|
|
19721
19574
|
"dialog.cancelTask": "Cancel task #{id}?",
|
|
19722
|
-
"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.",
|
|
19723
19576
|
"dialog.retryTask": "Retry task #{id}?",
|
|
19724
19577
|
"dialog.retryTaskBody": "The task returns to pending and its automatic retry budget is reset.",
|
|
19725
19578
|
"dialog.deleteTask": "Delete task #{id}?",
|
|
@@ -20211,6 +20064,7 @@ function clientMessages(locale) {
|
|
|
20211
20064
|
"details.copySuccess",
|
|
20212
20065
|
"details.id",
|
|
20213
20066
|
"details.project",
|
|
20067
|
+
"details.variant",
|
|
20214
20068
|
"details.prompt",
|
|
20215
20069
|
"details.result",
|
|
20216
20070
|
"details.category",
|
|
@@ -20311,6 +20165,7 @@ function clientMessages(locale) {
|
|
|
20311
20165
|
"catalog.chooseProject",
|
|
20312
20166
|
"catalog.defaultModel",
|
|
20313
20167
|
"catalog.defaultProvider",
|
|
20168
|
+
"catalog.defaultVariant",
|
|
20314
20169
|
"catalog.loading",
|
|
20315
20170
|
"catalog.loaded",
|
|
20316
20171
|
"catalog.failed",
|
|
@@ -20435,23 +20290,23 @@ function renderLayout(options) {
|
|
|
20435
20290
|
function detailSession(value){if(!value)return text('details.none');const session=String(value);return session.length<=10?session:session.slice(0,6)+'***'+session.slice(-4)}
|
|
20436
20291
|
function detailTaskResult(data){const presentation=data._resultPresentation;if(!presentation)return text('details.none');const parts=[];if(Array.isArray(presentation.errors)&&presentation.errors.length)parts.push(presentation.errors.join('\\n'));if(presentation.text)parts.push(presentation.text);return parts.join('\\n\\n')||text('details.none')}
|
|
20437
20292
|
function detailField(label,value,options={}){const item=document.createElement('div');item.className='detail-item'+(options.wide?' wide':'');const name=document.createElement('div');name.className='detail-label';name.textContent=label;const content=options.long?document.createElement('pre'):document.createElement('div');content.className='detail-value'+(options.mono?' mono':'')+(options.long?' long':'');content.textContent=value===null||value===undefined||value===''?text('details.none'):String(value);item.append(name,content);return item}
|
|
20438
|
-
function renderDetailHistory(runs){const section=document.createElement('section');section.className='detail-history';const title=document.createElement('h3');title.textContent=text('details.history');section.appendChild(title);if(!Array.isArray(runs)||runs.length===0){const empty=document.createElement('div');empty.className='muted small';empty.textContent=text('details.noHistory');section.appendChild(empty);return section}const list=document.createElement('div');list.className='detail-history-list';for(const run of runs){const item=document.createElement('div');item.className='detail-history-item';const primary=document.createElement('strong');primary.textContent='Run #'+run.id+' \xB7 '+detailStatus('run',run.status);const secondary=document.createElement('span');secondary.className='muted';secondary.textContent=detailDate(run.startedAt)+(run.model?' \xB7 '+detailModel(run.model):'');item.append(primary,secondary);list.appendChild(item)}section.appendChild(list);return section}
|
|
20293
|
+
function renderDetailHistory(runs){const section=document.createElement('section');section.className='detail-history';const title=document.createElement('h3');title.textContent=text('details.history');section.appendChild(title);if(!Array.isArray(runs)||runs.length===0){const empty=document.createElement('div');empty.className='muted small';empty.textContent=text('details.noHistory');section.appendChild(empty);return section}const list=document.createElement('div');list.className='detail-history-list';for(const run of runs){const item=document.createElement('div');item.className='detail-history-item';const primary=document.createElement('strong');primary.textContent='Run #'+run.id+' \xB7 '+detailStatus('run',run.status);const secondary=document.createElement('span');secondary.className='muted';secondary.textContent=detailDate(run.startedAt)+(run.model?' \xB7 '+detailModel(run.model):'')+(run.variant?' \xB7 '+run.variant:'');item.append(primary,secondary);list.appendChild(item)}section.appendChild(list);return section}
|
|
20439
20294
|
function detailFields(type,data){if(type==='task')return [
|
|
20440
20295
|
[text('details.id'),'#'+data.id],[text('table.name'),data.name],[text('table.status'),detailStatus('task',data.status)],[text('details.project'),data.cwd,{wide:true,mono:true}],
|
|
20441
|
-
[text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
|
|
20296
|
+
[text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.variant'),data.variant||text('details.default')],[text('details.prompt'),data.prompt,{wide:true,long:true}],
|
|
20442
20297
|
[text('details.category'),data.category],[text('details.batch'),data.batchId],[text('details.dependency'),data.dependsOn?'#'+data.dependsOn:text('details.none')],
|
|
20443
20298
|
[text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.retryCount'),String(data.retryCount??0)+' / '+String(data.maxRetries??0)],
|
|
20444
20299
|
[text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.scheduledAt'),detailDate(data.scheduledAt)],
|
|
20445
20300
|
[text('details.createdAt'),detailDate(data.createdAt)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
|
|
20446
20301
|
[text('details.result'),detailTaskResult(data),{wide:true,long:true}]
|
|
20447
20302
|
];if(type==='run'){const started=data.startedAt?new Date(data.startedAt).getTime():null;const finished=data.finishedAt?new Date(data.finishedAt).getTime():null;return [
|
|
20448
|
-
[text('details.id'),'Run #'+data.id],[text('details.taskId'),'#'+data.taskId],[text('table.status'),detailStatus('run',data.status)],[text('table.model'),detailModel(data.model)],
|
|
20303
|
+
[text('details.id'),'Run #'+data.id],[text('details.taskId'),'#'+data.taskId],[text('table.status'),detailStatus('run',data.status)],[text('table.model'),detailModel(data.model)],[text('details.variant'),data.variant||text('details.default')],
|
|
20449
20304
|
[text('details.session'),detailSession(data.sessionId)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
|
|
20450
20305
|
[text('table.duration'),started!==null?detailDuration((finished??Date.now())-started):text('details.none')],[text('details.heartbeat'),detailDate(data.heartbeatAt)],
|
|
20451
20306
|
[text('details.process'),'Worker PID '+String(data.workerPid??'\u2014')+' \xB7 OpenCode PID '+String(data.childPid??'\u2014'),{wide:true,mono:true}]
|
|
20452
20307
|
];}const scheduleRule=data.scheduleType==='cron'?data.cronExpr:data.scheduleType==='recurring'?detailDuration(data.intervalMs):detailDate(data.runAt);return [
|
|
20453
20308
|
[text('details.id'),'#'+data.id],[text('table.name'),data.name],[text('details.enabled'),data.enabled?text('details.enabledYes'):text('details.enabledNo')],[text('details.project'),data.cwd,{wide:true,mono:true}],
|
|
20454
|
-
[text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
|
|
20309
|
+
[text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.variant'),data.variant||text('details.default')],[text('details.prompt'),data.prompt,{wide:true,long:true}],
|
|
20455
20310
|
[text('template.scheduleType'),detailScheduleType(data.scheduleType)],[text('details.scheduleRule'),scheduleRule],[text('details.category'),data.category],[text('details.batch'),data.batchId],
|
|
20456
20311
|
[text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.maxInstances'),data.maxInstances],[text('details.maxRetries'),data.maxRetries??0],
|
|
20457
20312
|
[text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.lastRun'),detailDate(data.lastRunAt)],[text('details.nextRun'),detailDate(data.nextRunAt)],
|
|
@@ -20468,12 +20323,20 @@ function renderLayout(options) {
|
|
|
20468
20323
|
function updateTaskProjectStatus(){const node=taskField('project-status');if(!node)return;const cwd=taskField('cwd').value.trim();if(!cwd){node.textContent='';return}const project=taskProjects()[cwd];node.textContent=project?text('task.projectExisting',project):text('task.projectNew')}
|
|
20469
20324
|
const catalogTimers={};const catalogRequests={};const catalogModels={};
|
|
20470
20325
|
function catalogField(prefix,name){return document.getElementById(prefix+'-'+name)}
|
|
20471
|
-
function resetCatalog(prefix){const agent=catalogField(prefix,'agent');const provider=catalogField(prefix,'model-provider');const model=catalogField(prefix,'model');if(!agent||!provider||!model)return;catalogModels[prefix]=[];agent.replaceChildren(new Option(text('catalog.chooseProject'),''));provider.replaceChildren(new Option(text('catalog.defaultProvider'),''));model.replaceChildren(new Option(text('catalog.defaultModel'),'default'));agent.disabled=false;provider.disabled=true;model.disabled=true;catalogField(prefix,'catalog-status').textContent='';}
|
|
20326
|
+
function resetCatalog(prefix){const agent=catalogField(prefix,'agent');const provider=catalogField(prefix,'model-provider');const model=catalogField(prefix,'model');const variant=catalogField(prefix,'variant');if(!agent||!provider||!model||!variant)return;const preferredVariant=variant.dataset.preferred||'';catalogModels[prefix]=[];agent.replaceChildren(new Option(text('catalog.chooseProject'),''));provider.replaceChildren(new Option(text('catalog.defaultProvider'),''));model.replaceChildren(new Option(text('catalog.defaultModel'),'default'));variant.replaceChildren(new Option(text('catalog.defaultVariant'),''));if(preferredVariant)appendCurrentOption(variant,preferredVariant);variant.value=preferredVariant;agent.disabled=false;provider.disabled=true;model.disabled=true;variant.disabled=!preferredVariant;catalogField(prefix,'catalog-status').textContent='';}
|
|
20472
20327
|
function appendCurrentOption(select,value){if(!value||[...select.options].some(option=>option.value===value))return;select.appendChild(new Option(value,value));}
|
|
20328
|
+
function setPreferredVariant(prefix,modelValue,variantValue){const variant=catalogField(prefix,'variant');if(!variant)return;const preferred=variantValue||'';variant.dataset.preferred=preferred;variant.dataset.preferredModel=modelValue||'default';variant.replaceChildren(new Option(text('catalog.defaultVariant'),''));if(preferred)appendCurrentOption(variant,preferred);variant.value=preferred;variant.disabled=!preferred}
|
|
20329
|
+
function invalidateVariantRequest(prefix){const key=prefix+'-variant';catalogRequests[key]=(catalogRequests[key]||0)+1}
|
|
20330
|
+
function invalidateCatalogRequests(prefix){clearTimeout(catalogTimers[prefix]);catalogRequests[prefix]=(catalogRequests[prefix]||0)+1;invalidateVariantRequest(prefix)}
|
|
20331
|
+
function clearVariantPreference(prefix){const variant=catalogField(prefix,'variant');if(!variant)return;invalidateVariantRequest(prefix);variant.dataset.preferred='';variant.dataset.preferredModel='';variant.replaceChildren(new Option(text('catalog.defaultVariant'),''));variant.value='';variant.disabled=true}
|
|
20332
|
+
function handleProviderChange(prefix){clearVariantPreference(prefix);populateModelOptions(prefix)}
|
|
20333
|
+
function handleModelChange(prefix){clearVariantPreference(prefix);populateVariantOptions(prefix)}
|
|
20334
|
+
function handleVariantChange(prefix){const variant=catalogField(prefix,'variant');if(!variant)return;invalidateVariantRequest(prefix);variant.dataset.preferred='';variant.dataset.preferredModel=''}
|
|
20473
20335
|
function modelProvider(value){const slash=value.indexOf('/');return slash>0?value.slice(0,slash):''}
|
|
20474
|
-
function populateModelOptions(prefix,preferredModel=''){const provider=catalogField(prefix,'model-provider');const model=catalogField(prefix,'model');if(!provider||!model)return;const selectedProvider=provider.value;model.replaceChildren();if(!selectedProvider){model.appendChild(new Option(text('catalog.defaultModel'),'default'));model.disabled=true;return}const available=(catalogModels[prefix]||[]).filter(value=>modelProvider(value)===selectedProvider);for(const value of available)model.appendChild(new Option(value,value));if(preferredModel&&available.includes(preferredModel))model.value=preferredModel;model.disabled=available.length===0}
|
|
20336
|
+
function populateModelOptions(prefix,preferredModel=''){const provider=catalogField(prefix,'model-provider');const model=catalogField(prefix,'model');if(!provider||!model)return;const selectedProvider=provider.value;model.replaceChildren();if(!selectedProvider){model.appendChild(new Option(text('catalog.defaultModel'),'default'));model.disabled=true;populateVariantOptions(prefix);return}const available=(catalogModels[prefix]||[]).filter(value=>modelProvider(value)===selectedProvider);for(const value of available)model.appendChild(new Option(value,value));if(preferredModel&&available.includes(preferredModel))model.value=preferredModel;model.disabled=available.length===0;populateVariantOptions(prefix)}
|
|
20337
|
+
async function populateVariantOptions(prefix,preferredVariant=''){const cwd=catalogField(prefix,'cwd')?.value.trim()||'';const model=catalogField(prefix,'model');const variant=catalogField(prefix,'variant');if(!model||!variant)return;const selectedModel=model.value;const preferred=preferredVariant||(variant.dataset.preferredModel===selectedModel?variant.dataset.preferred||'':'');if(!cwd||!selectedModel||selectedModel==='default'){variant.replaceChildren(new Option(text('catalog.defaultVariant'),''));if(preferred)appendCurrentOption(variant,preferred);variant.value=preferred;variant.dataset.preferred='';variant.dataset.preferredModel='';variant.disabled=!preferred;return}if(!preferred){variant.replaceChildren(new Option(text('catalog.defaultVariant'),''));variant.value=''}const requestKey=prefix+'-variant';const request=(catalogRequests[requestKey]||0)+1;catalogRequests[requestKey]=request;variant.disabled=true;try{const data=await readJson(await fetch('/api/opencode/catalog?cwd='+encodeURIComponent(cwd)));if(catalogRequests[requestKey]!==request||catalogField(prefix,'cwd')?.value.trim()!==cwd||model.value!==selectedModel)return;const available=Array.isArray(data.variantsByModel?.[selectedModel])?data.variantsByModel[selectedModel]:[];variant.replaceChildren(new Option(text('catalog.defaultVariant'),''));for(const value of available)variant.appendChild(new Option(value,value));if(preferred)appendCurrentOption(variant,preferred);variant.value=[...variant.options].some(option=>option.value===preferred)?preferred:'';variant.dataset.preferred='';variant.dataset.preferredModel='';variant.disabled=available.length===0&&!preferred}catch(error){if(catalogRequests[requestKey]!==request||catalogField(prefix,'cwd')?.value.trim()!==cwd||model.value!==selectedModel)return;if(preferred)appendCurrentOption(variant,preferred);variant.value=preferred;variant.dataset.preferred=preferred;variant.dataset.preferredModel=selectedModel;variant.disabled=!preferred}}
|
|
20475
20338
|
async function loadCatalog(prefix,preferredAgent='',preferredModel='default',preserveUnavailable=false){const cwd=catalogField(prefix,'cwd')?.value.trim()||'';const status=catalogField(prefix,'catalog-status');const agent=catalogField(prefix,'agent');const provider=catalogField(prefix,'model-provider');const model=catalogField(prefix,'model');if(!cwd||!status||!agent||!provider||!model){if(!cwd)resetCatalog(prefix);return}const request=(catalogRequests[prefix]||0)+1;catalogRequests[prefix]=request;status.dataset.state='loading';status.textContent=text('catalog.loading');agent.disabled=true;provider.disabled=true;model.disabled=true;try{const data=await readJson(await fetch('/api/opencode/catalog?cwd='+encodeURIComponent(cwd)));if(catalogRequests[prefix]!==request||catalogField(prefix,'cwd').value.trim()!==cwd)return;agent.replaceChildren();for(const item of data.agents){const label=item.name+' \u2014 '+text('catalog.'+item.mode);agent.appendChild(new Option(label,item.name))}if(preserveUnavailable)appendCurrentOption(agent,preferredAgent);const defaultAgent=preferredAgent||data.agents.find(item=>item.name==='build')?.name||data.agents.find(item=>item.mode==='primary')?.name||data.agents[0]?.name||'';agent.value=[...agent.options].some(option=>option.value===defaultAgent)?defaultAgent:(agent.options[0]?.value||'');catalogModels[prefix]=[...data.models];if(preserveUnavailable&&preferredModel&&preferredModel!=='default'&&!catalogModels[prefix].includes(preferredModel))catalogModels[prefix].push(preferredModel);provider.replaceChildren(new Option(text('catalog.defaultProvider'),''));for(const name of [...new Set(catalogModels[prefix].map(modelProvider).filter(Boolean))].sort())provider.appendChild(new Option(name,name));const preferredProvider=preferredModel==='default'?'':modelProvider(preferredModel);provider.value=[...provider.options].some(option=>option.value===preferredProvider)?preferredProvider:'';populateModelOptions(prefix,preferredModel);status.dataset.state='ready';status.textContent=text('catalog.loaded',{agents:data.agents.length,models:data.models.length})}catch(error){if(catalogRequests[prefix]!==request)return;resetCatalog(prefix);if(preserveUnavailable){appendCurrentOption(agent,preferredAgent);if(preferredModel&&preferredModel!=='default'){catalogModels[prefix]=[preferredModel];appendCurrentOption(provider,modelProvider(preferredModel));provider.value=modelProvider(preferredModel);populateModelOptions(prefix,preferredModel)}}status.dataset.state='error';status.textContent=text('catalog.failed',{error:error.message})}finally{if(catalogRequests[prefix]===request){agent.disabled=false;provider.disabled=false;if(provider.value)model.disabled=false}}}
|
|
20476
|
-
function scheduleCatalogLoad(prefix){
|
|
20339
|
+
function scheduleCatalogLoad(prefix){invalidateCatalogRequests(prefix);resetCatalog(prefix);catalogTimers[prefix]=setTimeout(()=>loadCatalog(prefix),450)}
|
|
20477
20340
|
let directoryTargetId='';let directoryCurrent='';let directoryEntries=[];let directoryShowHidden=false;
|
|
20478
20341
|
function renderDirectoryEntries(){const list=document.getElementById('directory-list');list.replaceChildren();const entries=directoryEntries.filter(entry=>directoryShowHidden||!entry.hidden);const hidden=document.getElementById('directory-hidden');hidden.textContent=text(directoryShowHidden?'action.hideHidden':'action.showHidden');if(entries.length===0){const empty=document.createElement('div');empty.className='directory-empty';empty.textContent=text('directory.empty');list.appendChild(empty);return}for(const entry of entries){const button=document.createElement('button');button.type='button';button.className='directory-item';button.innerHTML='${icon("folder")}<span></span>';button.querySelector('span').textContent=entry.name;button.onclick=()=>browseDirectory(entry.path);list.appendChild(button)}}
|
|
20479
20342
|
async function browseDirectory(path=''){const choose=document.getElementById('directory-choose');choose.disabled=true;try{const suffix=path?'?path='+encodeURIComponent(path):'';const data=await readJson(await fetch('/api/filesystem/directories'+suffix));directoryCurrent=data.path;directoryEntries=data.directories;document.getElementById('directory-path').textContent=data.path;document.getElementById('directory-up').disabled=data.parent===data.path;document.getElementById('directory-up').onclick=()=>browseDirectory(data.parent);document.getElementById('directory-home').onclick=()=>browseDirectory(data.home);renderDirectoryEntries();choose.disabled=false;return true}catch(error){directoryCurrent='';directoryEntries=[];document.getElementById('directory-path').textContent='';renderDirectoryEntries();showToast(error.message,'error');return false}}
|
|
@@ -20483,16 +20346,16 @@ function renderLayout(options) {
|
|
|
20483
20346
|
function updateDurationControl(id){const preset=document.getElementById(id+'-preset');const custom=document.getElementById(id+'-custom');const input=document.getElementById(id+'-value');const visible=preset.value==='custom';custom.hidden=!visible;input.required=visible;if(visible&&!input.value)input.value='1'}
|
|
20484
20347
|
function readDuration(id){const preset=document.getElementById(id+'-preset').value;if(preset==='')return '';if(preset!=='custom')return preset==='0'?'0':preset+'ms';const value=document.getElementById(id+'-value').value.trim();return value===''?'':value+document.getElementById(id+'-unit').value}
|
|
20485
20348
|
function setDuration(id,milliseconds){const preset=document.getElementById(id+'-preset');const exact=milliseconds==null?'':String(milliseconds);if([...preset.options].some(option=>option.value===exact)){preset.value=exact;updateDurationControl(id);return}preset.value='custom';const input=document.getElementById(id+'-value');const unit=document.getElementById(id+'-unit');const units=[['d',86400000],['h',3600000],['min',60000],['s',1000]];let matched=false;for(const [name,factor] of units){if(milliseconds!=null&&(milliseconds===0||milliseconds%factor===0)){input.value=String(milliseconds/factor);unit.value=name;matched=true;break}}if(!matched){input.value=String((milliseconds??60000)/1000);unit.value='s'}updateDurationControl(id)}
|
|
20486
|
-
function openTaskCreator(){const form=document.getElementById('task-form');form.reset();taskField('id').value='';taskField('cwd').readOnly=false;taskField('cwd-picker').hidden=false;taskField('cwd').value=form.dataset.defaultCwd||'';setDuration('task-retry-backoff',30000);setDuration('task-timeout',null);taskField('dialog-title').textContent=text('task.createTitle');taskField('save').textContent=text('action.saveTask');updateTaskProjectStatus();resetCatalog('task');document.getElementById('task-dialog').showModal();if(taskField('cwd').value)loadCatalog('task');setTimeout(()=>taskField('name').focus(),50)}
|
|
20487
|
-
async function openTaskEditor(id){try{const data=await readJson(await fetch('/api/tasks/'+id));taskField('id').value=String(id);taskField('cwd').value=data.cwd||'';taskField('cwd').readOnly=true;taskField('cwd-picker').hidden=true;taskField('name').value=data.name||'';taskField('prompt').value=data.prompt||'';taskField('category').value=data.category||'general';taskField('batch').value=data.batchId||'';taskField('importance').value=String(data.importance??3);taskField('urgency').value=String(data.urgency??3);taskField('max-retries').value=String(data.maxRetries??3);setDuration('task-retry-backoff',data.retryBackoffMs??30000);setDuration('task-timeout',data.timeoutMs);taskField('dialog-title').textContent=text('task.editTitle');taskField('save').textContent=text('action.updateTask');updateTaskProjectStatus();resetCatalog('task');document.getElementById('task-dialog').showModal();loadCatalog('task',data.agent||'',data.model||'default',true);setTimeout(()=>taskField('name').focus(),50)}catch(error){showToast(error.message,'error')}}
|
|
20488
|
-
async function saveTask(event){event.preventDefault();const form=document.getElementById('task-form');if(!form.reportValidity())return;const id=taskField('id').value;const body={name:taskField('name').value,cwd:taskField('cwd').value,agent:taskField('agent').value,model:taskField('model').value,prompt:taskField('prompt').value,category:taskField('category').value,batchId:taskField('batch').value,importance:Number(taskField('importance').value),urgency:Number(taskField('urgency').value),maxRetries:Number(taskField('max-retries').value),retryBackoff:readDuration('task-retry-backoff'),timeout:readDuration('task-timeout')};const button=taskField('save');button.disabled=true;try{const data=await readJson(await fetch(id?'/api/tasks/'+id:'/api/tasks',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}));showToast(text(id?'feedback.taskUpdated':'feedback.taskCreated',{id:data.task.id}));document.getElementById('task-dialog').close();setTimeout(()=>location.assign(id?location.href:'/?cwd='+encodeURIComponent(data.task.cwd||'')),450)}catch(error){showToast(error.message,'error')}finally{button.disabled=false}}
|
|
20349
|
+
function openTaskCreator(){invalidateCatalogRequests('task');const form=document.getElementById('task-form');form.reset();taskField('id').value='';setPreferredVariant('task','default','');taskField('cwd').readOnly=false;taskField('cwd-picker').hidden=false;taskField('cwd').value=form.dataset.defaultCwd||'';setDuration('task-retry-backoff',30000);setDuration('task-timeout',null);taskField('dialog-title').textContent=text('task.createTitle');taskField('save').textContent=text('action.saveTask');updateTaskProjectStatus();resetCatalog('task');document.getElementById('task-dialog').showModal();if(taskField('cwd').value)loadCatalog('task');setTimeout(()=>taskField('name').focus(),50)}
|
|
20350
|
+
async function openTaskEditor(id){invalidateCatalogRequests('task');try{const data=await readJson(await fetch('/api/tasks/'+id));document.getElementById('task-form').reset();taskField('id').value=String(id);taskField('cwd').value=data.cwd||'';taskField('cwd').readOnly=true;taskField('cwd-picker').hidden=true;taskField('name').value=data.name||'';taskField('prompt').value=data.prompt||'';taskField('category').value=data.category||'general';taskField('batch').value=data.batchId||'';taskField('importance').value=String(data.importance??3);taskField('urgency').value=String(data.urgency??3);taskField('max-retries').value=String(data.maxRetries??3);setDuration('task-retry-backoff',data.retryBackoffMs??30000);setDuration('task-timeout',data.timeoutMs);taskField('dialog-title').textContent=text('task.editTitle');taskField('save').textContent=text('action.updateTask');updateTaskProjectStatus();setPreferredVariant('task',data.model||'default',data.variant||'');resetCatalog('task');document.getElementById('task-dialog').showModal();loadCatalog('task',data.agent||'',data.model||'default',true);setTimeout(()=>taskField('name').focus(),50)}catch(error){showToast(error.message,'error')}}
|
|
20351
|
+
async function saveTask(event){event.preventDefault();const form=document.getElementById('task-form');if(!form.reportValidity())return;const id=taskField('id').value;const body={name:taskField('name').value,cwd:taskField('cwd').value,agent:taskField('agent').value,model:taskField('model').value,variant:taskField('variant').value,prompt:taskField('prompt').value,category:taskField('category').value,batchId:taskField('batch').value,importance:Number(taskField('importance').value),urgency:Number(taskField('urgency').value),maxRetries:Number(taskField('max-retries').value),retryBackoff:readDuration('task-retry-backoff'),timeout:readDuration('task-timeout')};const button=taskField('save');button.disabled=true;try{const data=await readJson(await fetch(id?'/api/tasks/'+id:'/api/tasks',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}));showToast(text(id?'feedback.taskUpdated':'feedback.taskCreated',{id:data.task.id}));document.getElementById('task-dialog').close();setTimeout(()=>location.assign(id?location.href:'/?cwd='+encodeURIComponent(data.task.cwd||'')),450)}catch(error){showToast(error.message,'error')}finally{button.disabled=false}}
|
|
20489
20352
|
function localDateTime(milliseconds){const date=new Date(milliseconds);const local=new Date(milliseconds-date.getTimezoneOffset()*60000);return local.toISOString().slice(0,23)}
|
|
20490
20353
|
function updateTemplateScheduleFields(){const type=templateField('schedule-type').value;const fields={cron:templateField('cron-field'),recurring:templateField('interval-field'),delayed:templateField('run-at-field')};for(const [name,node] of Object.entries(fields)){node.hidden=name!==type;for(const control of node.querySelectorAll('input,select'))control.required=false;const required=name==='recurring'?node.querySelector('[id$="-preset"]'):node.querySelector('input');if(required)required.required=name===type;if(name==='recurring'&&name===type)updateDurationControl('template-interval')}}
|
|
20491
20354
|
function setOriginalRunAt(epoch){const input=templateField('run-at');const local=epoch?localDateTime(epoch):'';input.value=local;input.dataset.originalEpoch=epoch?String(epoch):'';input.dataset.originalLocal=local}
|
|
20492
20355
|
function selectedRunAt(){const input=templateField('run-at');return resolveEditedRunAt(input.dataset.originalEpoch?Number(input.dataset.originalEpoch):null,input.dataset.originalLocal||'',input.value)}
|
|
20493
|
-
function openTemplateCreator(){const form=document.getElementById('template-form');form.reset();templateField('id').value='';templateField('dialog-title').textContent=text('template.createTitle');setDuration('template-interval',3600000);setDuration('template-retry-backoff',30000);setDuration('template-timeout',null);setOriginalRunAt(null);templateField('run-at').value=localDateTime(Date.now()+3600000);updateTemplateScheduleFields();resetCatalog('template');document.getElementById('template-dialog').showModal();if(templateField('cwd').value)loadCatalog('template');setTimeout(()=>templateField('name').focus(),50)}
|
|
20494
|
-
async function openTemplateEditor(id){try{const data=await readJson(await fetch('/api/templates/'+id));templateField('id').value=String(id);templateField('dialog-title').textContent=text('template.editTitle');templateField('name').value=data.name||'';templateField('cwd').value=data.cwd||'';templateField('prompt').value=data.prompt||'';templateField('schedule-type').value=data.scheduleType;templateField('cron').value=data.cronExpr||'';setDuration('template-interval',data.intervalMs);setOriginalRunAt(data.runAt||null);if(!data.runAt)templateField('run-at').value=localDateTime(Date.now()+3600000);templateField('category').value=data.category||'general';templateField('batch').value=data.batchId||'';templateField('importance').value=String(data.importance??3);templateField('urgency').value=String(data.urgency??3);templateField('max-instances').value=String(data.maxInstances??1);templateField('max-retries').value=String(data.maxRetries??3);setDuration('template-retry-backoff',data.retryBackoffMs??30000);setDuration('template-timeout',data.timeoutMs);updateTemplateScheduleFields();resetCatalog('template');document.getElementById('template-dialog').showModal();loadCatalog('template',data.agent||'',data.model||'default',true);setTimeout(()=>templateField('name').focus(),50)}catch(error){showToast(error.message,'error')}}
|
|
20495
|
-
async function saveTemplate(event){event.preventDefault();const form=document.getElementById('template-form');if(!form.reportValidity())return;const id=templateField('id').value;const type=templateField('schedule-type').value;const body={name:templateField('name').value,cwd:templateField('cwd').value,agent:templateField('agent').value,model:templateField('model').value,prompt:templateField('prompt').value,scheduleType:type,cronExpr:templateField('cron').value,interval:readDuration('template-interval'),runAt:type==='delayed'?selectedRunAt():null,category:templateField('category').value,batchId:templateField('batch').value,importance:Number(templateField('importance').value),urgency:Number(templateField('urgency').value),maxInstances:Number(templateField('max-instances').value),maxRetries:Number(templateField('max-retries').value),retryBackoff:readDuration('template-retry-backoff'),timeout:readDuration('template-timeout')};const button=templateField('save');button.disabled=true;try{await readJson(await fetch(id?'/api/templates/'+id:'/api/templates',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}));showToast(text(id?'feedback.templateUpdated':'feedback.templateCreated'));document.getElementById('template-dialog').close();setTimeout(()=>location.assign(id?location.href:'/templates'),450)}catch(error){showToast(error.message,'error')}finally{button.disabled=false}}
|
|
20356
|
+
function openTemplateCreator(){invalidateCatalogRequests('template');const form=document.getElementById('template-form');form.reset();templateField('id').value='';setPreferredVariant('template','default','');templateField('dialog-title').textContent=text('template.createTitle');setDuration('template-interval',3600000);setDuration('template-retry-backoff',30000);setDuration('template-timeout',null);setOriginalRunAt(null);templateField('run-at').value=localDateTime(Date.now()+3600000);updateTemplateScheduleFields();resetCatalog('template');document.getElementById('template-dialog').showModal();if(templateField('cwd').value)loadCatalog('template');setTimeout(()=>templateField('name').focus(),50)}
|
|
20357
|
+
async function openTemplateEditor(id){invalidateCatalogRequests('template');try{const data=await readJson(await fetch('/api/templates/'+id));document.getElementById('template-form').reset();templateField('id').value=String(id);templateField('dialog-title').textContent=text('template.editTitle');templateField('name').value=data.name||'';templateField('cwd').value=data.cwd||'';templateField('prompt').value=data.prompt||'';templateField('schedule-type').value=data.scheduleType;templateField('cron').value=data.cronExpr||'';setDuration('template-interval',data.intervalMs);setOriginalRunAt(data.runAt||null);if(!data.runAt)templateField('run-at').value=localDateTime(Date.now()+3600000);templateField('category').value=data.category||'general';templateField('batch').value=data.batchId||'';templateField('importance').value=String(data.importance??3);templateField('urgency').value=String(data.urgency??3);templateField('max-instances').value=String(data.maxInstances??1);templateField('max-retries').value=String(data.maxRetries??3);setDuration('template-retry-backoff',data.retryBackoffMs??30000);setDuration('template-timeout',data.timeoutMs);updateTemplateScheduleFields();setPreferredVariant('template',data.model||'default',data.variant||'');resetCatalog('template');document.getElementById('template-dialog').showModal();loadCatalog('template',data.agent||'',data.model||'default',true);setTimeout(()=>templateField('name').focus(),50)}catch(error){showToast(error.message,'error')}}
|
|
20358
|
+
async function saveTemplate(event){event.preventDefault();const form=document.getElementById('template-form');if(!form.reportValidity())return;const id=templateField('id').value;const type=templateField('schedule-type').value;const body={name:templateField('name').value,cwd:templateField('cwd').value,agent:templateField('agent').value,model:templateField('model').value,variant:templateField('variant').value,prompt:templateField('prompt').value,scheduleType:type,cronExpr:templateField('cron').value,interval:readDuration('template-interval'),runAt:type==='delayed'?selectedRunAt():null,category:templateField('category').value,batchId:templateField('batch').value,importance:Number(templateField('importance').value),urgency:Number(templateField('urgency').value),maxInstances:Number(templateField('max-instances').value),maxRetries:Number(templateField('max-retries').value),retryBackoff:readDuration('template-retry-backoff'),timeout:readDuration('template-timeout')};const button=templateField('save');button.disabled=true;try{await readJson(await fetch(id?'/api/templates/'+id:'/api/templates',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}));showToast(text(id?'feedback.templateUpdated':'feedback.templateCreated'));document.getElementById('template-dialog').close();setTimeout(()=>location.assign(id?location.href:'/templates'),450)}catch(error){showToast(error.message,'error')}finally{button.disabled=false}}
|
|
20496
20359
|
async function enableTmpl(id){try{await readJson(await fetch('/api/templates/'+id+'/enable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
|
|
20497
20360
|
async function disableTmpl(id){if(!await ask(text('dialog.disableTemplate'),text('dialog.disableTemplateBody')))return;try{await readJson(await fetch('/api/templates/'+id+'/disable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
|
|
20498
20361
|
async function deleteTmpl(id){if(!await ask(text('dialog.deleteTemplate'),text('dialog.deleteTemplateBody'),true))return;try{await readJson(await fetch('/api/templates/'+id,{method:'DELETE'}));location.reload()}catch(error){showToast(error.message,'error')}}
|
|
@@ -20520,6 +20383,7 @@ var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
|
20520
20383
|
"cancelled"
|
|
20521
20384
|
]);
|
|
20522
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;
|
|
20523
20387
|
var runtimeConfig = null;
|
|
20524
20388
|
var restartScheduled = false;
|
|
20525
20389
|
function setDashboardRuntimeConfig(config) {
|
|
@@ -20546,10 +20410,13 @@ function resolveDashboardConfigState(runtimeAvailable, restartRequired, managedR
|
|
|
20546
20410
|
function isSafeDashboardRestartTarget(diagnostic, currentPid) {
|
|
20547
20411
|
return diagnostic.pm2Installed && diagnostic.processFound && diagnostic.status === "online" && diagnostic.pid === currentPid && diagnostic.ready && diagnostic.scopeMatches;
|
|
20548
20412
|
}
|
|
20549
|
-
function canRestartCurrentGateway() {
|
|
20413
|
+
async function canRestartCurrentGateway(fresh = false) {
|
|
20550
20414
|
if (runtimeConfig === null) return false;
|
|
20551
20415
|
try {
|
|
20552
|
-
return isSafeDashboardRestartTarget(
|
|
20416
|
+
return isSafeDashboardRestartTarget(
|
|
20417
|
+
await getDashboardGatewayDiagnostic({ fresh }),
|
|
20418
|
+
process.pid
|
|
20419
|
+
);
|
|
20553
20420
|
} catch {
|
|
20554
20421
|
return false;
|
|
20555
20422
|
}
|
|
@@ -20568,7 +20435,7 @@ function listChildDirectories(path) {
|
|
|
20568
20435
|
if (entry.isDirectory()) return [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }];
|
|
20569
20436
|
if (!entry.isSymbolicLink()) return [];
|
|
20570
20437
|
try {
|
|
20571
|
-
return
|
|
20438
|
+
return statSync3(entryPath).isDirectory() ? [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }] : [];
|
|
20572
20439
|
} catch {
|
|
20573
20440
|
return [];
|
|
20574
20441
|
}
|
|
@@ -20621,6 +20488,7 @@ function parseTaskPayload(value) {
|
|
|
20621
20488
|
cwd: requiredString("cwd"),
|
|
20622
20489
|
agent: requiredString("agent"),
|
|
20623
20490
|
model: optionalString("model", "default"),
|
|
20491
|
+
variant: Object.hasOwn(input, "variant") ? optionalString("variant") : void 0,
|
|
20624
20492
|
prompt: requiredString("prompt"),
|
|
20625
20493
|
category: optionalString("category", "general"),
|
|
20626
20494
|
batchId: optionalString("batchId"),
|
|
@@ -20636,6 +20504,7 @@ function editableTaskPayload(input) {
|
|
|
20636
20504
|
name: input.name,
|
|
20637
20505
|
agent: input.agent,
|
|
20638
20506
|
model: input.model ?? "default",
|
|
20507
|
+
...input.variant === void 0 ? {} : { variant: input.variant },
|
|
20639
20508
|
prompt: input.prompt,
|
|
20640
20509
|
category: input.category ?? "general",
|
|
20641
20510
|
batchId: input.batchId ?? null,
|
|
@@ -20691,6 +20560,7 @@ function parseTemplatePayload(value) {
|
|
|
20691
20560
|
name: requiredString("name"),
|
|
20692
20561
|
agent: requiredString("agent"),
|
|
20693
20562
|
model: optionalString("model", "default"),
|
|
20563
|
+
variant: Object.hasOwn(input, "variant") ? optionalString("variant") : void 0,
|
|
20694
20564
|
prompt: requiredString("prompt"),
|
|
20695
20565
|
cwd: requiredString("cwd"),
|
|
20696
20566
|
category: optionalString("category", "general"),
|
|
@@ -20726,6 +20596,14 @@ function resolveLocale(c) {
|
|
|
20726
20596
|
return accepted.startsWith("en") ? "en" : "zh-CN";
|
|
20727
20597
|
}
|
|
20728
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
|
+
}
|
|
20729
20607
|
await next();
|
|
20730
20608
|
c.header("X-Content-Type-Options", "nosniff");
|
|
20731
20609
|
c.header("X-Frame-Options", "DENY");
|
|
@@ -20757,13 +20635,13 @@ app.get("/health", (c) => {
|
|
|
20757
20635
|
return c.json(health, health.status === "ok" ? 200 : 503);
|
|
20758
20636
|
});
|
|
20759
20637
|
app.get("/api/filesystem/directories", (c) => {
|
|
20760
|
-
const requestedPath = c.req.query("path")?.trim() ||
|
|
20638
|
+
const requestedPath = c.req.query("path")?.trim() || homedir3();
|
|
20761
20639
|
try {
|
|
20762
20640
|
validateTaskWorkingDirectory(requestedPath);
|
|
20763
20641
|
return c.json({
|
|
20764
20642
|
path: requestedPath,
|
|
20765
20643
|
parent: dirname4(requestedPath),
|
|
20766
|
-
home:
|
|
20644
|
+
home: homedir3(),
|
|
20767
20645
|
directories: listChildDirectories(requestedPath)
|
|
20768
20646
|
});
|
|
20769
20647
|
} catch (error) {
|
|
@@ -20861,7 +20739,7 @@ function readCurrentConfig() {
|
|
|
20861
20739
|
const configPath = getConfigPath();
|
|
20862
20740
|
if (!existsSync5(configPath)) return {};
|
|
20863
20741
|
try {
|
|
20864
|
-
return JSON.parse(
|
|
20742
|
+
return JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
20865
20743
|
} catch {
|
|
20866
20744
|
return {};
|
|
20867
20745
|
}
|
|
@@ -20869,9 +20747,9 @@ function readCurrentConfig() {
|
|
|
20869
20747
|
function writeConfig(cfg) {
|
|
20870
20748
|
const configPath = getConfigPath();
|
|
20871
20749
|
const dir = dirname4(configPath);
|
|
20872
|
-
if (!existsSync5(dir))
|
|
20750
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
20873
20751
|
const tempPath = `${configPath}.${process.pid}.tmp`;
|
|
20874
|
-
|
|
20752
|
+
writeFileSync2(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
20875
20753
|
renameSync2(tempPath, configPath);
|
|
20876
20754
|
}
|
|
20877
20755
|
function statCard(value, label, tone, cardIcon, delay = "") {
|
|
@@ -20992,7 +20870,7 @@ app.get("/", async (c) => {
|
|
|
20992
20870
|
const latestRun = latestRuns.get(task.id);
|
|
20993
20871
|
const executionActive = latestRun?.status === "running";
|
|
20994
20872
|
const batchId = task.batchId?.trim() || null;
|
|
20995
|
-
const searchable = esc(`${task.name} ${task.agent} ${task.prompt} ${task.cwd ?? ""} ${task.batchId ?? ""} ${task.category ?? ""}`);
|
|
20873
|
+
const searchable = esc(`${task.name} ${task.agent} ${task.model ?? ""} ${task.variant ?? ""} ${task.prompt} ${task.cwd ?? ""} ${task.batchId ?? ""} ${task.category ?? ""}`);
|
|
20996
20874
|
return `<tr data-task-row data-search="${searchable}">
|
|
20997
20875
|
<td class="faint" data-label="${t(locale, "table.id")}">#${task.id}</td>
|
|
20998
20876
|
<td data-primary data-label="${t(locale, "table.task")}"><div class="task-name">${esc(task.name)}</div><div class="task-prompt" title="${esc(task.prompt)}">${esc(task.prompt.substring(0, 160))}</div>
|
|
@@ -21070,11 +20948,12 @@ app.get("/", async (c) => {
|
|
|
21070
20948
|
<label class="form-field"><span>${t(locale, "template.cwd")}</span><div class="field-action"><input id="task-cwd" required autocomplete="off" list="task-cwd-options" placeholder="/path/to/project" oninput="updateTaskProjectStatus();scheduleCatalogLoad('task')"><button id="task-cwd-picker" type="button" class="btn" onclick="openDirectoryPicker('task-cwd')">${icon("folder")}${t(locale, "action.chooseFolder")}</button></div><small>${t(locale, "template.cwdHint")}</small></label>
|
|
21071
20949
|
<datalist id="task-cwd-options">${projects.map((project) => `<option value="${esc(project.cwd)}"></option>`).join("")}</datalist>
|
|
21072
20950
|
<label class="form-field"><span>${t(locale, "template.agent")}</span><select id="task-agent" required><option value="">${t(locale, "catalog.chooseProject")}</option></select><small>${t(locale, "catalog.agentHint")}</small></label>
|
|
21073
|
-
<label class="form-field"><span>${t(locale, "template.model")}</span><div class="model-selector"><select id="task-model-provider" aria-label="${t(locale, "catalog.provider")}" onchange="
|
|
20951
|
+
<label class="form-field"><span>${t(locale, "template.model")}</span><div class="model-selector"><select id="task-model-provider" aria-label="${t(locale, "catalog.provider")}" onchange="handleProviderChange('task')"><option value="">${t(locale, "catalog.defaultProvider")}</option></select><select id="task-model" required aria-label="${t(locale, "catalog.model")}" onchange="handleModelChange('task')" disabled><option value="default">${t(locale, "catalog.defaultModel")}</option></select></div><small>${t(locale, "catalog.modelHint")}</small></label>
|
|
20952
|
+
<label class="form-field"><span>${t(locale, "template.variant")}</span><select id="task-variant" onchange="handleVariantChange('task')" disabled><option value="">${t(locale, "catalog.defaultVariant")}</option></select><small>${t(locale, "catalog.variantHint")}</small></label>
|
|
21074
20953
|
<label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="task-prompt" rows="6" required></textarea></label>
|
|
21075
20954
|
</div>
|
|
21076
20955
|
<p id="task-project-status" class="form-note"></p>
|
|
21077
|
-
<p id="task-catalog-status" class="form-note catalog-status"></p>
|
|
20956
|
+
<p id="task-catalog-status" class="form-note catalog-status" role="status" aria-live="polite"></p>
|
|
21078
20957
|
<details class="advanced-fields">
|
|
21079
20958
|
<summary>${t(locale, "template.advanced")}</summary>
|
|
21080
20959
|
<div class="template-form-grid">
|
|
@@ -21116,7 +20995,7 @@ app.get("/templates", async (c) => {
|
|
|
21116
20995
|
return `<tr>
|
|
21117
20996
|
<td class="faint" data-label="${t(locale, "table.id")}">#${template.id}</td>
|
|
21118
20997
|
<td data-primary data-label="${t(locale, "table.name")}"><div class="task-name">${esc(template.name)}</div><div class="task-prompt" title="${esc(template.prompt)}">${esc(template.prompt.substring(0, 140))}</div>
|
|
21119
|
-
<div class="actions" style="margin-top:5px"><span class="tag">${esc(template.agent)}</span>${template.model && template.model !== "default" ? `<span class="tag">${esc(template.model)}</span>` : ""}</div></td>
|
|
20998
|
+
<div class="actions" style="margin-top:5px"><span class="tag">${esc(template.agent)}</span>${template.model && template.model !== "default" ? `<span class="tag">${esc(template.model)}</span>` : ""}${template.variant ? `<span class="tag">${esc(template.variant)}</span>` : ""}</div></td>
|
|
21120
20999
|
<td data-label="${t(locale, "table.type")}"><span class="tag t-${scheduleType}">${typeLabel}</span></td>
|
|
21121
21000
|
<td data-label="${t(locale, "table.rule")}" class="m small">${esc(rule)}</td>
|
|
21122
21001
|
<td data-label="${t(locale, "table.status")}"><span class="badge ${template.enabled ? "b-done" : "b-cancelled"}">${t(locale, template.enabled ? "schedule.enabled" : "schedule.disabled")}</span></td>
|
|
@@ -21146,14 +21025,15 @@ app.get("/templates", async (c) => {
|
|
|
21146
21025
|
<label class="form-field"><span>${t(locale, "template.name")}</span><input id="template-name" required maxlength="200" autocomplete="off"></label>
|
|
21147
21026
|
<label class="form-field"><span>${t(locale, "template.cwd")}</span><div class="field-action"><input id="template-cwd" required autocomplete="off" placeholder="/path/to/project" oninput="scheduleCatalogLoad('template')"><button type="button" class="btn" onclick="openDirectoryPicker('template-cwd')">${icon("folder")}${t(locale, "action.chooseFolder")}</button></div><small>${t(locale, "template.cwdHint")}</small></label>
|
|
21148
21027
|
<label class="form-field"><span>${t(locale, "template.agent")}</span><select id="template-agent" required><option value="">${t(locale, "catalog.chooseProject")}</option></select><small>${t(locale, "catalog.agentHint")}</small></label>
|
|
21149
|
-
<label class="form-field"><span>${t(locale, "template.model")}</span><div class="model-selector"><select id="template-model-provider" aria-label="${t(locale, "catalog.provider")}" onchange="
|
|
21028
|
+
<label class="form-field"><span>${t(locale, "template.model")}</span><div class="model-selector"><select id="template-model-provider" aria-label="${t(locale, "catalog.provider")}" onchange="handleProviderChange('template')"><option value="">${t(locale, "catalog.defaultProvider")}</option></select><select id="template-model" required aria-label="${t(locale, "catalog.model")}" onchange="handleModelChange('template')" disabled><option value="default">${t(locale, "catalog.defaultModel")}</option></select></div><small>${t(locale, "catalog.modelHint")}</small></label>
|
|
21029
|
+
<label class="form-field"><span>${t(locale, "template.variant")}</span><select id="template-variant" onchange="handleVariantChange('template')" disabled><option value="">${t(locale, "catalog.defaultVariant")}</option></select><small>${t(locale, "catalog.variantHint")}</small></label>
|
|
21150
21030
|
<label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="template-prompt" rows="6" required></textarea></label>
|
|
21151
21031
|
<label class="form-field"><span>${t(locale, "template.scheduleType")}</span><select id="template-schedule-type" onchange="updateTemplateScheduleFields()"><option value="recurring">${t(locale, "schedule.recurring")}</option><option value="delayed">${t(locale, "schedule.delayed")}</option><option value="cron">${t(locale, "schedule.cron")}</option></select></label>
|
|
21152
21032
|
<label id="template-cron-field" class="form-field" hidden><span>${t(locale, "template.cronExpr")}</span><input id="template-cron" autocomplete="off" placeholder="0 9 * * *"><small>${t(locale, "template.cronHint")}</small></label>
|
|
21153
21033
|
<label id="template-interval-field" class="form-field"><span>${t(locale, "template.interval")}</span>${durationControl(locale, "template-interval", 36e5, "interval")}<small>${t(locale, "template.intervalHint")}</small></label>
|
|
21154
21034
|
<label id="template-run-at-field" class="form-field" hidden><span>${t(locale, "template.runAt")}</span><input id="template-run-at" type="datetime-local" step="0.001"></label>
|
|
21155
21035
|
</div>
|
|
21156
|
-
<p id="template-catalog-status" class="form-note catalog-status"></p>
|
|
21036
|
+
<p id="template-catalog-status" class="form-note catalog-status" role="status" aria-live="polite"></p>
|
|
21157
21037
|
<details class="advanced-fields">
|
|
21158
21038
|
<summary>${t(locale, "template.advanced")}</summary>
|
|
21159
21039
|
<div class="template-form-grid">
|
|
@@ -21187,6 +21067,7 @@ app.get("/runs", async (c) => {
|
|
|
21187
21067
|
taskId: taskRuns4.taskId,
|
|
21188
21068
|
sessionId: taskRuns4.sessionId,
|
|
21189
21069
|
model: taskRuns4.model,
|
|
21070
|
+
variant: taskRuns4.variant,
|
|
21190
21071
|
status: taskRuns4.status,
|
|
21191
21072
|
startedAt: taskRuns4.startedAt,
|
|
21192
21073
|
finishedAt: taskRuns4.finishedAt,
|
|
@@ -21207,7 +21088,7 @@ app.get("/runs", async (c) => {
|
|
|
21207
21088
|
const log = run.log ? renderRunLog(run.id, run.taskName, run.log, locale, run.status !== "done") : "";
|
|
21208
21089
|
return `<tr class="run-summary-row">
|
|
21209
21090
|
<td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td>
|
|
21210
|
-
<td data-primary data-label="${t(locale, "table.task")}"><div class="task-name">${esc(run.taskName)} <span class="faint">#${run.taskId}</span></div>${run.model ? `<div style="margin-top:4px"
|
|
21091
|
+
<td data-primary data-label="${t(locale, "table.task")}"><div class="task-name">${esc(run.taskName)} <span class="faint">#${run.taskId}</span></div>${run.model || run.variant ? `<div class="actions" style="margin-top:4px">${run.model ? `<span class="tag">${esc(run.model)}</span>` : ""}${run.variant ? `<span class="tag">${esc(run.variant)}</span>` : ""}</div>` : ""}</td>
|
|
21211
21092
|
<td data-label="${t(locale, "table.agent")}"><span class="tag">${esc(run.taskAgent)}</span></td>
|
|
21212
21093
|
<td data-label="${t(locale, "table.session")}" class="m small">${esc(maskSessionId(run.sessionId))}</td>
|
|
21213
21094
|
<td data-label="${t(locale, "table.status")}"><span class="badge b-${status}">${runStatusText(locale, run.status ?? "unknown")}</span></td>
|
|
@@ -21234,7 +21115,7 @@ app.get("/system", async (c) => {
|
|
|
21234
21115
|
const config = loadConfig();
|
|
21235
21116
|
const activeConfig = runtimeConfig ?? config;
|
|
21236
21117
|
const restartRequired = runtimeConfig !== null && !configsEqual(config, runtimeConfig);
|
|
21237
|
-
const managedRestart = canRestartCurrentGateway();
|
|
21118
|
+
const managedRestart = await canRestartCurrentGateway();
|
|
21238
21119
|
const configState = resolveDashboardConfigState(
|
|
21239
21120
|
runtimeConfig !== null,
|
|
21240
21121
|
restartRequired,
|
|
@@ -21257,7 +21138,7 @@ app.get("/system", async (c) => {
|
|
|
21257
21138
|
const runRows = runningRuns.map((run) => {
|
|
21258
21139
|
const session = maskSessionId(run.sessionId);
|
|
21259
21140
|
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>
|
|
21260
|
-
<td data-label="${t(locale, "table.model")}" class="small">${esc(run.model) || "\u2014"}</td><td data-label="${t(locale, "table.startedAt")}" class="small">${formatDateTime(run.startedAt, locale)}</td>
|
|
21141
|
+
<td data-label="${t(locale, "table.model")}" class="small">${esc(run.model) || "\u2014"}${run.variant ? ` \xB7 ${esc(run.variant)}` : ""}</td><td data-label="${t(locale, "table.startedAt")}" class="small">${formatDateTime(run.startedAt, locale)}</td>
|
|
21261
21142
|
<td data-label="${t(locale, "table.heartbeat")}" class="small muted">${formatRelative(run.heartbeatAt, locale)}</td><td data-label="${t(locale, "table.pid")}" class="m small">W:${run.workerPid ?? "\u2014"} C:${run.childPid ?? "\u2014"}</td>
|
|
21262
21143
|
<td data-label="${t(locale, "table.duration")}" class="small">${formatDuration(run.startedAt, null)}</td></tr>`;
|
|
21263
21144
|
}).join("");
|
|
@@ -21354,9 +21235,9 @@ app.get("/api/runs/:id/session-command", async (c) => {
|
|
|
21354
21235
|
if (!isValidSessionId(run.sessionId)) return c.json({ error: "session unavailable" }, 409);
|
|
21355
21236
|
return c.json({ command: sessionCommand(run.sessionId) });
|
|
21356
21237
|
});
|
|
21357
|
-
app.get("/api/gateway/status", (c) => {
|
|
21238
|
+
app.get("/api/gateway/status", async (c) => {
|
|
21358
21239
|
const savedConfig = loadConfig();
|
|
21359
|
-
const managed = canRestartCurrentGateway();
|
|
21240
|
+
const managed = await canRestartCurrentGateway();
|
|
21360
21241
|
return c.json({
|
|
21361
21242
|
pid: process.pid,
|
|
21362
21243
|
managed,
|
|
@@ -21481,7 +21362,7 @@ app.put("/api/config", async (c) => {
|
|
|
21481
21362
|
return c.json({
|
|
21482
21363
|
success: true,
|
|
21483
21364
|
restartRequired: runtimeConfig === null || !configsEqual(savedConfig, runtimeConfig),
|
|
21484
|
-
managed: canRestartCurrentGateway()
|
|
21365
|
+
managed: await canRestartCurrentGateway()
|
|
21485
21366
|
});
|
|
21486
21367
|
} catch (error) {
|
|
21487
21368
|
return c.json({
|
|
@@ -21497,9 +21378,10 @@ app.post("/api/gateway/restart", async (c) => {
|
|
|
21497
21378
|
return c.json({ error: "confirmation must be RESTART" }, 400);
|
|
21498
21379
|
}
|
|
21499
21380
|
if (restartScheduled) return c.json({ error: "restart already scheduled" }, 409);
|
|
21500
|
-
if (!canRestartCurrentGateway()) {
|
|
21381
|
+
if (!await canRestartCurrentGateway(true)) {
|
|
21501
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);
|
|
21502
21383
|
}
|
|
21384
|
+
if (restartScheduled) return c.json({ error: "restart already scheduled" }, 409);
|
|
21503
21385
|
restartScheduled = true;
|
|
21504
21386
|
const previousPid = process.pid;
|
|
21505
21387
|
setTimeout(() => {
|