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/dist/cli/index.js CHANGED
@@ -4506,7 +4506,7 @@ var init_sql = __esm({
4506
4506
  return new SQL([new StringChunk(str)]);
4507
4507
  }
4508
4508
  sql2.raw = raw2;
4509
- function join8(chunks, separator) {
4509
+ function join9(chunks, separator) {
4510
4510
  const result = [];
4511
4511
  for (const [i, chunk] of chunks.entries()) {
4512
4512
  if (i > 0 && separator !== void 0) {
@@ -4516,7 +4516,7 @@ var init_sql = __esm({
4516
4516
  }
4517
4517
  return new SQL(result);
4518
4518
  }
4519
- sql2.join = join8;
4519
+ sql2.join = join9;
4520
4520
  function identifier(value) {
4521
4521
  return new Name(value);
4522
4522
  }
@@ -7132,7 +7132,7 @@ var init_select2 = __esm({
7132
7132
  return (table, on) => {
7133
7133
  const baseTableName = this.tableName;
7134
7134
  const tableName = getTableLikeName(table);
7135
- if (typeof tableName === "string" && this.config.joins?.some((join8) => join8.alias === tableName)) {
7135
+ if (typeof tableName === "string" && this.config.joins?.some((join9) => join9.alias === tableName)) {
7136
7136
  throw new Error(`Alias "${tableName}" is already used in this query`);
7137
7137
  }
7138
7138
  if (!this.isPartialSelect) {
@@ -7974,7 +7974,7 @@ var init_update = __esm({
7974
7974
  createJoin(joinType) {
7975
7975
  return (table, on) => {
7976
7976
  const tableName = getTableLikeName(table);
7977
- if (typeof tableName === "string" && this.config.joins.some((join8) => join8.alias === tableName)) {
7977
+ if (typeof tableName === "string" && this.config.joins.some((join9) => join9.alias === tableName)) {
7978
7978
  throw new Error(`Alias "${tableName}" is already used in this query`);
7979
7979
  }
7980
7980
  if (typeof on === "function") {
@@ -9307,6 +9307,7 @@ var init_schema = __esm({
9307
9307
  name: text("name").notNull(),
9308
9308
  agent: text("agent").notNull(),
9309
9309
  model: text("model").default("default"),
9310
+ variant: text("variant"),
9310
9311
  prompt: text("prompt").notNull(),
9311
9312
  cwd: text("cwd"),
9312
9313
  // 分类与优先级
@@ -9351,6 +9352,7 @@ var init_schema = __esm({
9351
9352
  taskId: integer("task_id").notNull().references(() => tasks.id, { onDelete: "cascade" }),
9352
9353
  sessionId: text("session_id"),
9353
9354
  model: text("model"),
9355
+ variant: text("variant"),
9354
9356
  status: text("status").default("running"),
9355
9357
  startedAt: integer("started_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()),
9356
9358
  finishedAt: integer("finished_at", { mode: "timestamp" }),
@@ -9371,6 +9373,7 @@ var init_schema = __esm({
9371
9373
  name: text("name").notNull(),
9372
9374
  agent: text("agent").notNull(),
9373
9375
  model: text("model").default("default"),
9376
+ variant: text("variant"),
9374
9377
  prompt: text("prompt").notNull(),
9375
9378
  cwd: text("cwd"),
9376
9379
  category: text("category").default("general"),
@@ -9631,6 +9634,28 @@ var init_task_batch = __esm({
9631
9634
  }
9632
9635
  });
9633
9636
 
9637
+ // src/core/model-variant.ts
9638
+ function normalizeModelVariant(value) {
9639
+ if (value === void 0 || value === null) return value;
9640
+ const normalized = value.trim();
9641
+ if (!normalized) return null;
9642
+ if (normalized.length > MAX_MODEL_VARIANT_LENGTH) {
9643
+ throw new Error(`variant \u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${MAX_MODEL_VARIANT_LENGTH} \u4E2A\u5B57\u7B26`);
9644
+ }
9645
+ if (CONTROL_CHARACTER_PATTERN.test(normalized)) {
9646
+ throw new Error("variant \u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26");
9647
+ }
9648
+ return normalized;
9649
+ }
9650
+ var MAX_MODEL_VARIANT_LENGTH, CONTROL_CHARACTER_PATTERN;
9651
+ var init_model_variant = __esm({
9652
+ "src/core/model-variant.ts"() {
9653
+ "use strict";
9654
+ MAX_MODEL_VARIANT_LENGTH = 128;
9655
+ CONTROL_CHARACTER_PATTERN = /[\u0000-\u001F\u007F]/;
9656
+ }
9657
+ });
9658
+
9634
9659
  // src/core/services/task.service.ts
9635
9660
  function hasNoExecutableDependents() {
9636
9661
  return sql`NOT EXISTS (
@@ -9686,6 +9711,7 @@ var init_task_service = __esm({
9686
9711
  init_backoff();
9687
9712
  init_task_working_directory();
9688
9713
  init_task_batch();
9714
+ init_model_variant();
9689
9715
  ({ tasks: tasks2, taskRuns: taskRuns2 } = schema_exports);
9690
9716
  cleanupInvocation = 0;
9691
9717
  TaskDeletionConflictError = class extends Error {
@@ -9705,7 +9731,8 @@ var init_task_service = __esm({
9705
9731
  static async add(data) {
9706
9732
  const normalizedData = {
9707
9733
  ...data,
9708
- batchId: normalizeTaskBatchId(data.batchId)
9734
+ batchId: normalizeTaskBatchId(data.batchId),
9735
+ variant: normalizeModelVariant(data.variant)
9709
9736
  };
9710
9737
  this.validateNewTask(normalizedData);
9711
9738
  return db.transaction((tx) => {
@@ -9733,7 +9760,11 @@ var init_task_service = __esm({
9733
9760
  }
9734
9761
  static async update(id, data, scope = {}) {
9735
9762
  if (Object.keys(data).length === 0) throw new Error("\u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A\u8981\u4FEE\u6539\u7684\u5B57\u6BB5");
9736
- const normalizedData = data.batchId === void 0 ? data : { ...data, batchId: normalizeTaskBatchId(data.batchId) ?? null };
9763
+ const normalizedData = {
9764
+ ...data,
9765
+ ...data.batchId === void 0 ? {} : { batchId: normalizeTaskBatchId(data.batchId) ?? null },
9766
+ ...data.variant === void 0 ? {} : { variant: normalizeModelVariant(data.variant) ?? null }
9767
+ };
9737
9768
  return db.transaction((tx) => {
9738
9769
  const task = tx.select().from(tasks2).where(and(
9739
9770
  eq(tasks2.id, id),
@@ -9745,6 +9776,7 @@ var init_task_service = __esm({
9745
9776
  name: normalizedData.name ?? task.name,
9746
9777
  agent: normalizedData.agent ?? task.agent,
9747
9778
  model: normalizedData.model ?? task.model,
9779
+ variant: normalizedData.variant === void 0 ? task.variant : normalizedData.variant,
9748
9780
  prompt: normalizedData.prompt ?? task.prompt,
9749
9781
  cwd: task.cwd,
9750
9782
  category: normalizedData.category ?? task.category,
@@ -9758,13 +9790,23 @@ var init_task_service = __esm({
9758
9790
  });
9759
9791
  const maxRetries = normalizedData.maxRetries ?? task.maxRetries ?? 3;
9760
9792
  const exhausted = task.status === "failed" && (task.retryCount ?? 0) > maxRetries;
9761
- return tx.update(tasks2).set({
9793
+ const updated = tx.update(tasks2).set({
9762
9794
  ...normalizedData,
9763
9795
  ...exhausted ? {
9764
9796
  status: "dead_letter",
9765
9797
  retryAfter: null
9766
9798
  } : {}
9767
9799
  }).where(eq(tasks2.id, id)).returning().get() ?? null;
9800
+ if (exhausted && updated) {
9801
+ const finishedAt = /* @__PURE__ */ new Date();
9802
+ tx.update(tasks2).set({
9803
+ status: "dead_letter",
9804
+ finishedAt,
9805
+ retryAfter: null,
9806
+ resultLog: `\u4F9D\u8D56\u4EFB\u52A1 #${id} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`
9807
+ }).where(blockedDependentsOf(id)).run();
9808
+ }
9809
+ return updated;
9768
9810
  }, { behavior: "immediate" });
9769
9811
  }
9770
9812
  static validateNewTask(data) {
@@ -9785,7 +9827,7 @@ var init_task_service = __esm({
9785
9827
  throw new Error(`${name} \u5FC5\u987B\u662F ${min} \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
9786
9828
  }
9787
9829
  }
9788
- static async next(scope = {}) {
9830
+ static buildRunnableTaskWhere(scope) {
9789
9831
  const baseConditions = [...this.buildScopeWhere(scope)];
9790
9832
  const nowMs = Date.now();
9791
9833
  const retryAfterFilter = or(
@@ -9820,40 +9862,43 @@ var init_task_service = __esm({
9820
9862
  if (batchFilter) {
9821
9863
  conditions.push(batchFilter);
9822
9864
  }
9823
- const result = await db.select().from(tasks2).where(and(
9865
+ return and(
9824
9866
  ...conditions,
9825
9867
  or(
9826
9868
  isNull(tasks2.dependsOn),
9827
9869
  sql`EXISTS (
9828
- SELECT 1 FROM tasks AS dependency_task
9829
- WHERE dependency_task.id = ${tasks2.dependsOn}
9830
- AND dependency_task.status = 'done'
9831
- AND dependency_task.cwd IS ${tasks2.cwd}
9832
- )`
9870
+ SELECT 1 FROM tasks AS dependency_task
9871
+ WHERE dependency_task.id = ${tasks2.dependsOn}
9872
+ AND dependency_task.status = 'done'
9873
+ AND dependency_task.cwd IS ${tasks2.cwd}
9874
+ )`
9833
9875
  ),
9834
9876
  or(
9835
9877
  isNull(tasks2.batchId),
9836
9878
  sql`trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS}) = ''`,
9837
9879
  sql`NOT EXISTS (
9838
- SELECT 1 FROM tasks AS running_batch_task
9839
- WHERE trim(running_batch_task.batch_id, ${TASK_BATCH_TRIM_CHARACTERS})
9840
- = trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS})
9841
- AND (
9842
- running_batch_task.status = 'running'
9843
- OR EXISTS (
9844
- SELECT 1 FROM task_runs AS running_batch_run
9845
- WHERE running_batch_run.task_id = running_batch_task.id
9846
- AND running_batch_run.status = 'running'
9847
- )
9880
+ SELECT 1 FROM tasks AS running_batch_task
9881
+ WHERE trim(running_batch_task.batch_id, ${TASK_BATCH_TRIM_CHARACTERS})
9882
+ = trim(${tasks2.batchId}, ${TASK_BATCH_TRIM_CHARACTERS})
9883
+ AND (
9884
+ running_batch_task.status = 'running'
9885
+ OR EXISTS (
9886
+ SELECT 1 FROM task_runs AS running_batch_run
9887
+ WHERE running_batch_run.task_id = running_batch_task.id
9888
+ AND running_batch_run.status = 'running'
9848
9889
  )
9849
- )`
9890
+ )
9891
+ )`
9850
9892
  ),
9851
9893
  sql`NOT EXISTS (
9852
- SELECT 1 FROM task_runs AS candidate_active_run
9853
- WHERE candidate_active_run.task_id = ${tasks2.id}
9854
- AND candidate_active_run.status = 'running'
9855
- )`
9856
- )).orderBy(
9894
+ SELECT 1 FROM task_runs AS candidate_active_run
9895
+ WHERE candidate_active_run.task_id = ${tasks2.id}
9896
+ AND candidate_active_run.status = 'running'
9897
+ )`
9898
+ );
9899
+ }
9900
+ static async next(scope = {}) {
9901
+ const result = await db.select().from(tasks2).where(this.buildRunnableTaskWhere(scope)).orderBy(
9857
9902
  desc(tasks2.urgency),
9858
9903
  desc(tasks2.importance),
9859
9904
  asc(tasks2.createdAt),
@@ -9861,6 +9906,22 @@ var init_task_service = __esm({
9861
9906
  ).limit(1);
9862
9907
  return result[0] ?? null;
9863
9908
  }
9909
+ static async claimNext(scope = {}) {
9910
+ return db.transaction((tx) => {
9911
+ const candidate = tx.select().from(tasks2).where(this.buildRunnableTaskWhere(scope)).orderBy(
9912
+ desc(tasks2.urgency),
9913
+ desc(tasks2.importance),
9914
+ asc(tasks2.createdAt),
9915
+ asc(tasks2.id)
9916
+ ).limit(1).get();
9917
+ if (!candidate) return null;
9918
+ return tx.update(tasks2).set({
9919
+ status: "running",
9920
+ startedAt: /* @__PURE__ */ new Date(),
9921
+ finishedAt: null
9922
+ }).where(eq(tasks2.id, candidate.id)).returning().get() ?? null;
9923
+ }, { behavior: "immediate" });
9924
+ }
9864
9925
  static async countRunning(scope = {}) {
9865
9926
  const scopeConditions = scope.legacyCwd ? [sql`${tasks2.cwd} IS NULL OR trim(${tasks2.cwd}) = ''`] : this.buildScopeWhere(scope);
9866
9927
  if (scope.batchId !== void 0) {
@@ -10521,9 +10582,34 @@ var init_launch_protocol = __esm({
10521
10582
  });
10522
10583
 
10523
10584
  // src/core/process-control.ts
10524
- import { spawnSync } from "child_process";
10525
10585
  import { fileURLToPath as fileURLToPath2 } from "url";
10526
10586
  import { basename, dirname as dirname2, resolve } from "path";
10587
+ function runBoundedOsCommand(command, args, captureOutput = true) {
10588
+ const result = Bun.spawnSync({
10589
+ cmd: [
10590
+ process.execPath,
10591
+ "-e",
10592
+ BOUNDED_OS_COMMAND_RUNNER,
10593
+ "supertask-os-command",
10594
+ command,
10595
+ ...args
10596
+ ],
10597
+ detached: process.platform !== "win32",
10598
+ maxBuffer: OS_COMMAND_MAX_OUTPUT_BYTES + 1024,
10599
+ stdin: "ignore",
10600
+ stdout: "pipe",
10601
+ stderr: "ignore"
10602
+ });
10603
+ const output = new TextDecoder().decode(result.stdout);
10604
+ const markerIndex = output.lastIndexOf(OS_COMMAND_RESULT_MARKER);
10605
+ if (markerIndex < 0) return { status: null, stdout: "" };
10606
+ const statusText2 = output.slice(markerIndex + OS_COMMAND_RESULT_MARKER.length);
10607
+ if (!/^\d+$/.test(statusText2)) return { status: null, stdout: "" };
10608
+ return {
10609
+ status: Number(statusText2),
10610
+ stdout: captureOutput ? output.slice(0, markerIndex) : ""
10611
+ };
10612
+ }
10527
10613
  function isSafePid(pid) {
10528
10614
  return Number.isInteger(pid) && pid > 1 && pid !== process.pid;
10529
10615
  }
@@ -10560,12 +10646,10 @@ function inspectSpawnedProcessTreePresence(pid) {
10560
10646
  }
10561
10647
  }
10562
10648
  function inspectUnixProcess(pid) {
10563
- const result = spawnSync("ps", ["-o", "pgid=", "-o", "command=", "-p", String(pid)], {
10564
- encoding: "utf8",
10565
- stdio: ["ignore", "pipe", "ignore"],
10566
- timeout: OS_COMMAND_TIMEOUT_MS,
10567
- killSignal: "SIGKILL"
10568
- });
10649
+ const result = runBoundedOsCommand(
10650
+ "ps",
10651
+ ["-o", "pgid=", "-o", "command=", "-p", String(pid)]
10652
+ );
10569
10653
  if (result.status !== 0) return null;
10570
10654
  const match2 = result.stdout.trim().match(/^(\d+)\s+(.+)$/s);
10571
10655
  if (!match2) return null;
@@ -10573,30 +10657,18 @@ function inspectUnixProcess(pid) {
10573
10657
  }
10574
10658
  function inspectWindowsCommand(pid) {
10575
10659
  const script = `(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}").CommandLine`;
10576
- const result = spawnSync(
10660
+ const result = runBoundedOsCommand(
10577
10661
  "powershell.exe",
10578
- ["-NoProfile", "-NonInteractive", "-Command", script],
10579
- {
10580
- encoding: "utf8",
10581
- stdio: ["ignore", "pipe", "ignore"],
10582
- timeout: OS_COMMAND_TIMEOUT_MS,
10583
- killSignal: "SIGKILL"
10584
- }
10662
+ ["-NoProfile", "-NonInteractive", "-Command", script]
10585
10663
  );
10586
10664
  if (result.status !== 0) return null;
10587
10665
  return result.stdout.trim() || null;
10588
10666
  }
10589
10667
  function inspectWindowsProcessTree(rootPid) {
10590
10668
  const script = `$root=${rootPid}; $all=@(Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId); $ids=New-Object 'System.Collections.Generic.HashSet[int]'; [void]$ids.Add($root); do { $added=$false; foreach($p in $all) { if($ids.Contains([int]$p.ParentProcessId) -and $ids.Add([int]$p.ProcessId)) { $added=$true } } } while($added); $all | Where-Object { $ids.Contains([int]$_.ProcessId) } | Select-Object -ExpandProperty ProcessId`;
10591
- const result = spawnSync(
10669
+ const result = runBoundedOsCommand(
10592
10670
  "powershell.exe",
10593
- ["-NoProfile", "-NonInteractive", "-Command", script],
10594
- {
10595
- encoding: "utf8",
10596
- stdio: ["ignore", "pipe", "ignore"],
10597
- timeout: OS_COMMAND_TIMEOUT_MS,
10598
- killSignal: "SIGKILL"
10599
- }
10671
+ ["-NoProfile", "-NonInteractive", "-Command", script]
10600
10672
  );
10601
10673
  if (result.status !== 0) return null;
10602
10674
  return result.stdout.split(/\s+/).filter(Boolean).map(Number).filter((pid) => Number.isInteger(pid) && pid > 0);
@@ -10667,11 +10739,7 @@ function signalRecordedProcessTreeWithResult(pid, signal, expectedExecutable, la
10667
10739
  }
10668
10740
  const args = ["/PID", String(pid), "/T"];
10669
10741
  if (signal === "SIGKILL") args.push("/F");
10670
- const status = spawnSync("taskkill", args, {
10671
- stdio: "ignore",
10672
- timeout: OS_COMMAND_TIMEOUT_MS,
10673
- killSignal: "SIGKILL"
10674
- }).status;
10742
+ const status = runBoundedOsCommand("taskkill", args, false).status;
10675
10743
  if (status === 0) return "signalled";
10676
10744
  return isProcessAlive(pid) ? "signal-failed" : absentLeaderResult(pid);
10677
10745
  }
@@ -10700,12 +10768,64 @@ async function waitForSpawnedProcessTreeExit(pid, timeoutMs = 5e3) {
10700
10768
  }
10701
10769
  return inspectSpawnedProcessTreePresence(pid) === "not-running";
10702
10770
  }
10703
- var OS_COMMAND_TIMEOUT_MS;
10771
+ var OS_COMMAND_TIMEOUT_MS, OS_COMMAND_MAX_OUTPUT_BYTES, OS_COMMAND_RESULT_MARKER, BOUNDED_OS_COMMAND_RUNNER;
10704
10772
  var init_process_control = __esm({
10705
10773
  "src/core/process-control.ts"() {
10706
10774
  "use strict";
10707
10775
  init_launch_protocol();
10708
10776
  OS_COMMAND_TIMEOUT_MS = 2e3;
10777
+ OS_COMMAND_MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
10778
+ OS_COMMAND_RESULT_MARKER = "\n\0supertask-os-command-result:";
10779
+ BOUNDED_OS_COMMAND_RUNNER = `
10780
+ const { writeSync } = await import('fs');
10781
+ let child;
10782
+ try {
10783
+ child = Bun.spawn(process.argv.slice(2), {
10784
+ stdin: 'ignore',
10785
+ stdout: 'pipe',
10786
+ stderr: 'ignore',
10787
+ });
10788
+ } catch {
10789
+ process.exit(127);
10790
+ }
10791
+ const terminateGroup = () => {
10792
+ if (process.platform !== 'win32') {
10793
+ try {
10794
+ process.kill(-process.pid, 'SIGKILL');
10795
+ } catch {
10796
+ }
10797
+ }
10798
+ try {
10799
+ child.kill('SIGKILL');
10800
+ } catch {
10801
+ }
10802
+ process.exit(1);
10803
+ };
10804
+ const timer = setTimeout(terminateGroup, ${OS_COMMAND_TIMEOUT_MS});
10805
+ try {
10806
+ const chunks = [];
10807
+ let outputBytes = 0;
10808
+ const readOutput = async () => {
10809
+ const reader = child.stdout.getReader();
10810
+ while (true) {
10811
+ const { done, value } = await reader.read();
10812
+ if (done) return;
10813
+ outputBytes += value.byteLength;
10814
+ if (outputBytes > ${OS_COMMAND_MAX_OUTPUT_BYTES}) terminateGroup();
10815
+ chunks.push(value);
10816
+ }
10817
+ };
10818
+ const [exitCode] = await Promise.all([child.exited, readOutput()]);
10819
+ clearTimeout(timer);
10820
+ for (const chunk of chunks) writeSync(1, chunk);
10821
+ writeSync(1, ${JSON.stringify(OS_COMMAND_RESULT_MARKER)} + String(exitCode));
10822
+ if (process.platform !== 'win32') terminateGroup();
10823
+ process.exit(0);
10824
+ } catch {
10825
+ clearTimeout(timer);
10826
+ terminateGroup();
10827
+ }
10828
+ `;
10709
10829
  }
10710
10830
  });
10711
10831
 
@@ -10857,7 +10977,7 @@ var init_task_run_service = __esm({
10857
10977
  }
10858
10978
  if (current.childPid != null) {
10859
10979
  throw new LegacyRunAbandonConflictError(
10860
- `run #${runId} \u5DF2\u8BB0\u5F55 child PID ${current.childPid}\uFF0C\u5FC5\u987B\u7531 Worker/Watchdog \u786E\u8BA4\u8FDB\u7A0B\u6811\u9000\u51FA`
10980
+ `run #${runId} \u5DF2\u8BB0\u5F55 child PID ${current.childPid}\uFF0C\u5FC5\u987B\u7531 Worker/Watchdog \u786E\u8BA4\u53D7\u7BA1\u8FDB\u7A0B\u7EC4\u6392\u7A7A`
10861
10981
  );
10862
10982
  }
10863
10983
  if (current.workerPid != null && isProcessAlive(current.workerPid)) {
@@ -20106,12 +20226,14 @@ var init_task_template_service = __esm({
20106
20226
  init_cron_parser();
20107
20227
  init_task_working_directory();
20108
20228
  init_task_batch();
20229
+ init_model_variant();
20109
20230
  ({ taskTemplates: taskTemplates2 } = schema_exports);
20110
20231
  TaskTemplateService = class {
20111
20232
  static async create(data) {
20112
20233
  const normalizedData = {
20113
20234
  ...data,
20114
- batchId: normalizeTaskBatchId(data.batchId)
20235
+ batchId: normalizeTaskBatchId(data.batchId),
20236
+ variant: normalizeModelVariant(data.variant)
20115
20237
  };
20116
20238
  this.validate(normalizedData);
20117
20239
  const now = Date.now();
@@ -20177,7 +20299,8 @@ var init_task_template_service = __esm({
20177
20299
  static async update(id, data) {
20178
20300
  const normalizedData = {
20179
20301
  ...data,
20180
- batchId: normalizeTaskBatchId(data.batchId) ?? null
20302
+ batchId: normalizeTaskBatchId(data.batchId) ?? null,
20303
+ ...data.variant === void 0 ? {} : { variant: normalizeModelVariant(data.variant) ?? null }
20181
20304
  };
20182
20305
  this.validate(normalizedData);
20183
20306
  const now = Date.now();
@@ -20975,7 +21098,7 @@ __export(pm2_exports, {
20975
21098
  upgrade: () => upgrade,
20976
21099
  withGatewayMaintenance: () => withGatewayMaintenance
20977
21100
  });
20978
- import { execSync, spawnSync as spawnSync2 } from "child_process";
21101
+ import { execSync, spawnSync } from "child_process";
20979
21102
  import {
20980
21103
  accessSync,
20981
21104
  chmodSync as chmodSync2,
@@ -21080,7 +21203,7 @@ function diagnoseOpenCodeRuntime(runtime = {
21080
21203
  env: process.env
21081
21204
  }) {
21082
21205
  const executable = runtimeScope(runtime).opencodePath;
21083
- const result = spawnSync2(executable, ["--version"], {
21206
+ const result = spawnSync(executable, ["--version"], {
21084
21207
  cwd: runtime.cwd,
21085
21208
  env: runtime.env,
21086
21209
  encoding: "utf8",
@@ -21170,7 +21293,7 @@ function xmlEscape(value) {
21170
21293
  function resolvePm2Bin() {
21171
21294
  const configured = pm2Bin();
21172
21295
  if (isAbsolute2(configured)) return configured;
21173
- const result = spawnSync2("which", [configured], { encoding: "utf8" });
21296
+ const result = spawnSync("which", [configured], { encoding: "utf8" });
21174
21297
  const resolved = result.status === 0 ? result.stdout.trim().split("\n")[0] : "";
21175
21298
  if (!resolved) throw new Error(`[supertask] \u65E0\u6CD5\u89E3\u6790 pm2 \u53EF\u6267\u884C\u6587\u4EF6: ${configured}`);
21176
21299
  return resolved;
@@ -21227,18 +21350,18 @@ function isMacLaunchAgentConfigured(expectedPm2Home, expectedRuntime) {
21227
21350
  accessSync(bunPath, constants.X_OK);
21228
21351
  accessSync(supervisorEntry, constants.R_OK);
21229
21352
  accessSync(pm2Path, constants.X_OK);
21230
- if (spawnSync2(bunPath, ["--version"], {
21353
+ if (spawnSync(bunPath, ["--version"], {
21231
21354
  stdio: "ignore",
21232
21355
  timeout: pm2CommandTimeoutMs(),
21233
21356
  killSignal: "SIGKILL"
21234
21357
  }).status !== 0) return false;
21235
- if (spawnSync2(pm2Path, ["--version"], {
21358
+ if (spawnSync(pm2Path, ["--version"], {
21236
21359
  stdio: "ignore",
21237
21360
  env: { ...process.env, PM2_HOME: pm2Home },
21238
21361
  timeout: pm2CommandTimeoutMs(),
21239
21362
  killSignal: "SIGKILL"
21240
21363
  }).status !== 0) return false;
21241
- const loaded = spawnSync2(
21364
+ const loaded = spawnSync(
21242
21365
  launchctlBin(),
21243
21366
  ["print", `gui/${process.getuid()}/${MAC_LAUNCH_AGENT_LABEL}`],
21244
21367
  {
@@ -21266,7 +21389,7 @@ function isMacLaunchAgentConfigured(expectedPm2Home, expectedRuntime) {
21266
21389
  function bootstrapMacLaunchAgent(domain, path) {
21267
21390
  const retryDeadline = Date.now() + 2e3;
21268
21391
  const sleeper = new Int32Array(new SharedArrayBuffer(4));
21269
- let result = spawnSync2(launchctlBin(), ["bootstrap", domain, path], {
21392
+ let result = spawnSync(launchctlBin(), ["bootstrap", domain, path], {
21270
21393
  encoding: "utf8",
21271
21394
  timeout: pm2CommandTimeoutMs(),
21272
21395
  killSignal: "SIGKILL"
@@ -21275,7 +21398,7 @@ function bootstrapMacLaunchAgent(domain, path) {
21275
21398
  const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
21276
21399
  if (result.status !== 5 && !output.includes("Bootstrap failed: 5:")) break;
21277
21400
  Atomics.wait(sleeper, 0, 0, 100);
21278
- result = spawnSync2(launchctlBin(), ["bootstrap", domain, path], {
21401
+ result = spawnSync(launchctlBin(), ["bootstrap", domain, path], {
21279
21402
  encoding: "utf8",
21280
21403
  timeout: pm2CommandTimeoutMs(),
21281
21404
  killSignal: "SIGKILL"
@@ -21333,7 +21456,7 @@ function installMacLaunchAgent(expectedRuntime = currentGatewayRuntime()) {
21333
21456
  `;
21334
21457
  const domain = `gui/${process.getuid()}`;
21335
21458
  const previousPlist = existsSync5(path) ? readFileSync3(path) : null;
21336
- const wasLoaded = previousPlist != null && spawnSync2(
21459
+ const wasLoaded = previousPlist != null && spawnSync(
21337
21460
  launchctlBin(),
21338
21461
  ["print", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`],
21339
21462
  {
@@ -21345,7 +21468,7 @@ function installMacLaunchAgent(expectedRuntime = currentGatewayRuntime()) {
21345
21468
  mkdirSync4(dirname6(path), { recursive: true });
21346
21469
  writeFileSync2(path, plist, { mode: 384 });
21347
21470
  chmodSync2(path, 384);
21348
- spawnSync2(launchctlBin(), ["bootout", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`], {
21471
+ spawnSync(launchctlBin(), ["bootout", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`], {
21349
21472
  stdio: "ignore",
21350
21473
  timeout: pm2CommandTimeoutMs(),
21351
21474
  killSignal: "SIGKILL"
@@ -21363,7 +21486,7 @@ function installMacLaunchAgent(expectedRuntime = currentGatewayRuntime()) {
21363
21486
  if (loaded.status !== 0 || !verified) {
21364
21487
  const output = loaded.status === 0 ? "supervisor \u672A\u4FDD\u6301 running \u6216 PM2 dump \u4E0D\u53EF\u6062\u590D" : `${loaded.stdout ?? ""}${loaded.stderr ?? ""}`.trim();
21365
21488
  if (loaded.status === 0) {
21366
- spawnSync2(launchctlBin(), ["bootout", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`], {
21489
+ spawnSync(launchctlBin(), ["bootout", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`], {
21367
21490
  stdio: "ignore",
21368
21491
  timeout: pm2CommandTimeoutMs(),
21369
21492
  killSignal: "SIGKILL"
@@ -21390,7 +21513,7 @@ function systemctlBin(env = process.env) {
21390
21513
  }
21391
21514
  function isLinuxStartupConfigured(env = process.env, cwd = process.cwd(), expectedRuntime) {
21392
21515
  const unit = env.SUPERTASK_PM2_SYSTEMD_UNIT ?? `pm2-${userInfo().username}.service`;
21393
- const enabled = spawnSync2(systemctlBin(env), ["is-enabled", unit], {
21516
+ const enabled = spawnSync(systemctlBin(env), ["is-enabled", unit], {
21394
21517
  encoding: "utf8",
21395
21518
  env,
21396
21519
  stdio: ["ignore", "pipe", "ignore"],
@@ -21398,7 +21521,7 @@ function isLinuxStartupConfigured(env = process.env, cwd = process.cwd(), expect
21398
21521
  killSignal: "SIGKILL"
21399
21522
  });
21400
21523
  if (enabled.status !== 0 || enabled.stdout.trim() !== "enabled") return false;
21401
- const contents = spawnSync2(systemctlBin(env), ["cat", unit], {
21524
+ const contents = spawnSync(systemctlBin(env), ["cat", unit], {
21402
21525
  encoding: "utf8",
21403
21526
  env,
21404
21527
  stdio: ["ignore", "pipe", "ignore"],
@@ -21422,7 +21545,7 @@ function isLinuxStartupConfigured(env = process.env, cwd = process.cwd(), expect
21422
21545
  }
21423
21546
  }
21424
21547
  function isPm2Installed(env = process.env) {
21425
- const result = spawnSync2(pm2Bin(env), ["--version"], {
21548
+ const result = spawnSync(pm2Bin(env), ["--version"], {
21426
21549
  stdio: "ignore",
21427
21550
  env,
21428
21551
  shell: process.platform === "win32",
@@ -21447,7 +21570,7 @@ function installPm2() {
21447
21570
  }
21448
21571
  function resolveCommand(command) {
21449
21572
  const lookup = process.platform === "win32" ? "where" : "which";
21450
- const result = spawnSync2(lookup, [command], { encoding: "utf8" });
21573
+ const result = spawnSync(lookup, [command], { encoding: "utf8" });
21451
21574
  return result.status === 0 ? result.stdout.trim().split("\n")[0] || null : null;
21452
21575
  }
21453
21576
  function npmPreferredEnvironment() {
@@ -21469,7 +21592,7 @@ function npmPreferredEnvironment() {
21469
21592
  function pm2Exec(args, options = {}) {
21470
21593
  const npmEnvironment = options.preferNpm ? npmPreferredEnvironment() : null;
21471
21594
  const effectiveEnv = options.env ?? npmEnvironment?.env ?? process.env;
21472
- const result = spawnSync2(npmEnvironment?.command ?? pm2Bin(effectiveEnv), args, {
21595
+ const result = spawnSync(npmEnvironment?.command ?? pm2Bin(effectiveEnv), args, {
21473
21596
  stdio: ["pipe", "pipe", "pipe"],
21474
21597
  encoding: "utf-8",
21475
21598
  env: effectiveEnv,
@@ -21544,7 +21667,7 @@ function gatewayRuntimeFromProcess(processInfo) {
21544
21667
  try {
21545
21668
  accessSync(savedBunPath, constants.X_OK);
21546
21669
  if (!statSync3(savedCwd).isDirectory()) return null;
21547
- if (spawnSync2(savedBunPath, ["--version"], {
21670
+ if (spawnSync(savedBunPath, ["--version"], {
21548
21671
  stdio: "ignore",
21549
21672
  env: savedEnv,
21550
21673
  timeout: pm2CommandTimeoutMs(savedEnv),
@@ -22006,7 +22129,7 @@ function removeMacLaunchAgent() {
22006
22129
  if (typeof process.getuid !== "function") return;
22007
22130
  const path = launchAgentPath();
22008
22131
  const domain = `gui/${process.getuid()}`;
22009
- const loaded = spawnSync2(
22132
+ const loaded = spawnSync(
22010
22133
  launchctlBin(),
22011
22134
  ["print", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`],
22012
22135
  {
@@ -22019,7 +22142,7 @@ function removeMacLaunchAgent() {
22019
22142
  throw new Error(`[supertask] \u65E0\u6CD5\u786E\u8BA4 macOS LaunchAgent \u72B6\u6001: ${loaded.error.message}`);
22020
22143
  }
22021
22144
  if (loaded.status === 0) {
22022
- const removed = spawnSync2(
22145
+ const removed = spawnSync(
22023
22146
  launchctlBin(),
22024
22147
  ["bootout", `${domain}/${MAC_LAUNCH_AGENT_LABEL}`],
22025
22148
  {
@@ -22230,7 +22353,7 @@ __export(update_exports, {
22230
22353
  resolveInstalledPluginVersion: () => resolveInstalledPluginVersion,
22231
22354
  updateGlobalCli: () => updateGlobalCli
22232
22355
  });
22233
- import { spawnSync as spawnSync3 } from "child_process";
22356
+ import { spawnSync as spawnSync2 } from "child_process";
22234
22357
  import {
22235
22358
  chmodSync as chmodSync3,
22236
22359
  closeSync,
@@ -22295,7 +22418,7 @@ function bunBin() {
22295
22418
  function resolveGlobalCliExecutable() {
22296
22419
  const override = process.env.SUPERTASK_CLI_BIN;
22297
22420
  if (override) return existsSync6(override) ? resolve4(override) : null;
22298
- const result = spawnSync3(process.platform === "win32" ? "where" : "which", ["supertask"], {
22421
+ const result = spawnSync2(process.platform === "win32" ? "where" : "which", ["supertask"], {
22299
22422
  encoding: "utf8",
22300
22423
  env: process.env,
22301
22424
  timeout: 1e4
@@ -22330,7 +22453,7 @@ function packageVersion(packageDir) {
22330
22453
  }
22331
22454
  }
22332
22455
  function npmGlobalRoot() {
22333
- const result = spawnSync3(npmBin(), ["root", "-g"], {
22456
+ const result = spawnSync2(npmBin(), ["root", "-g"], {
22334
22457
  encoding: "utf8",
22335
22458
  env: process.env,
22336
22459
  timeout: 3e4
@@ -22394,7 +22517,7 @@ function updateGlobalCli(version2) {
22394
22517
  }
22395
22518
  const command = before.packageManager === "npm" ? npmBin() : bunBin();
22396
22519
  const args = before.packageManager === "npm" ? ["install", "-g", `${PACKAGE_NAME}@${version2}`] : ["add", "-g", `${PACKAGE_NAME}@${version2}`];
22397
- const result = spawnSync3(command, args, {
22520
+ const result = spawnSync2(command, args, {
22398
22521
  encoding: "utf8",
22399
22522
  env: process.env,
22400
22523
  timeout: 12e4
@@ -22415,7 +22538,7 @@ function updateGlobalCli(version2) {
22415
22538
  return { ...after, action: "updated" };
22416
22539
  }
22417
22540
  function getLatestVersion() {
22418
- const result = spawnSync3(npmBin(), [
22541
+ const result = spawnSync2(npmBin(), [
22419
22542
  "view",
22420
22543
  PACKAGE_NAME,
22421
22544
  "dist-tags.latest",
@@ -22498,7 +22621,7 @@ function getOpenCodePluginDiagnostic() {
22498
22621
  chmodSync3(outputDirectory, 448);
22499
22622
  const outputPath = join5(outputDirectory, "resolved-config.json");
22500
22623
  outputFd = openSync(outputPath, "w", 384);
22501
- const result = spawnSync3(opencodeBin(), ["debug", "config", "--pure"], {
22624
+ const result = spawnSync2(opencodeBin(), ["debug", "config", "--pure"], {
22502
22625
  encoding: "utf8",
22503
22626
  env: process.env,
22504
22627
  timeout: 3e4,
@@ -22568,7 +22691,7 @@ function installPluginVersion(version2) {
22568
22691
  if (!isSemanticVersion(version2)) {
22569
22692
  throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7248\u672C\u65E0\u6548: ${version2}`);
22570
22693
  }
22571
- const result = spawnSync3(opencodeBin(), [
22694
+ const result = spawnSync2(opencodeBin(), [
22572
22695
  "plugin",
22573
22696
  `${PACKAGE_NAME}@${version2}`,
22574
22697
  "--global",
@@ -22747,7 +22870,7 @@ function runCommandContext(executable, args, cwd) {
22747
22870
  function assertWorkerProcessIsolationSupported(platform = process.platform) {
22748
22871
  if (platform === "win32") {
22749
22872
  throw new Error(
22750
- "Windows Worker \u5DF2\u5B89\u5168\u7981\u7528\uFF1A\u5F53\u524D\u8FD0\u884C\u65F6\u65E0\u6CD5\u7528 Job Object \u8BC1\u660E\u6574\u4E2A OpenCode \u8FDB\u7A0B\u6811\u5DF2\u9000\u51FA"
22873
+ "Windows Worker \u5DF2\u5B89\u5168\u7981\u7528\uFF1A\u5F53\u524D\u8FD0\u884C\u65F6\u65E0\u6CD5\u7528 Job Object \u63D0\u4F9B\u7B49\u4EF7\u7684\u53D7\u7BA1\u8FDB\u7A0B\u9694\u79BB\u4E0E\u6392\u7A7A\u8BC1\u660E"
22751
22874
  );
22752
22875
  }
22753
22876
  }
@@ -22771,17 +22894,24 @@ var init_worker = __esm({
22771
22894
  pollTimer = null;
22772
22895
  heartbeatTimer = null;
22773
22896
  pollCyclePromise = null;
22897
+ shutdownDeadlineMs = null;
22898
+ settlementRetryWakeups = /* @__PURE__ */ new Set();
22774
22899
  cfg;
22775
22900
  opencodeBin;
22776
22901
  maxOutputChars;
22902
+ settlementRetryDelaysMs;
22903
+ settlementRetryIntervalMs;
22777
22904
  constructor(cfg, options = {}) {
22778
22905
  this.cfg = cfg.worker;
22779
22906
  this.opencodeBin = options.opencodeBin ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
22780
22907
  this.maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
22908
+ this.settlementRetryDelaysMs = options.settlementRetryDelaysMs ?? [250, 1e3, 4e3];
22909
+ this.settlementRetryIntervalMs = options.settlementRetryIntervalMs ?? 5e3;
22781
22910
  }
22782
22911
  start() {
22783
22912
  assertWorkerProcessIsolationSupported();
22784
22913
  this.stopped = false;
22914
+ this.shutdownDeadlineMs = null;
22785
22915
  markGatewayActivity("worker");
22786
22916
  this.poll();
22787
22917
  this.heartbeatTimer = setInterval(() => {
@@ -22790,6 +22920,8 @@ var init_worker = __esm({
22790
22920
  }
22791
22921
  async stop(gracePeriodMs = 0) {
22792
22922
  this.stopped = true;
22923
+ this.shutdownDeadlineMs = Date.now() + Math.max(0, gracePeriodMs);
22924
+ for (const wake of [...this.settlementRetryWakeups]) wake();
22793
22925
  if (this.pollTimer) {
22794
22926
  clearTimeout(this.pollTimer);
22795
22927
  this.pollTimer = null;
@@ -22859,27 +22991,25 @@ var init_worker = __esm({
22859
22991
  if (databaseRunningCount >= this.cfg.maxConcurrency) break;
22860
22992
  let task;
22861
22993
  try {
22862
- task = await TaskService.next({ excludedBatchIds: [...this.activeBatchIds] });
22994
+ task = await TaskService.claimNext({ excludedBatchIds: [...this.activeBatchIds] });
22863
22995
  } catch (err) {
22864
22996
  this.logError("task claim failed", err);
22865
22997
  throw err;
22866
22998
  }
22867
22999
  if (!task) break;
22868
- if (this.stopped) break;
22869
- if (!await TaskService.start(task.id)) continue;
22870
- const batchId = normalizeTaskBatchId(task.batchId);
22871
- if (batchId) this.activeBatchIds.add(batchId);
22872
23000
  if (this.stopped) {
22873
23001
  await TaskService.resetRunningToPending([task.id]);
22874
- this.releaseBatch(task);
22875
23002
  break;
22876
23003
  }
23004
+ const batchId = normalizeTaskBatchId(task.batchId);
23005
+ if (batchId) this.activeBatchIds.add(batchId);
22877
23006
  let runId = null;
22878
23007
  try {
22879
23008
  const launchIdentity = `gateway-${process.pid}:launch:${randomUUID3()}`;
22880
23009
  const run = await TaskRunService.create({
22881
23010
  taskId: task.id,
22882
23011
  model: this.resolveModel(task.model),
23012
+ variant: this.resolveVariant(task.variant),
22883
23013
  status: "running",
22884
23014
  workerPid: process.pid,
22885
23015
  lockedAt: Date.now(),
@@ -22942,8 +23072,10 @@ var init_worker = __esm({
22942
23072
  }
22943
23073
  async spawnTask(task, runId, launchIdentity) {
22944
23074
  const model = this.resolveModel(task.model);
23075
+ const variant = this.resolveVariant(task.variant);
22945
23076
  const args = ["run", "--agent", task.agent, "--format", "json"];
22946
23077
  if (model) args.push("-m", model);
23078
+ if (variant) args.push("--variant", variant);
22947
23079
  args.push(task.prompt);
22948
23080
  const cwd = task.cwd || process.cwd();
22949
23081
  const child = spawn(process.execPath, [
@@ -23081,7 +23213,7 @@ var init_worker = __esm({
23081
23213
  if (!entry.guardianDrained) {
23082
23214
  const pid = entry.child.pid;
23083
23215
  const presence = pid == null ? spawnError == null ? "unknown" : "not-running" : inspectSpawnedProcessTreePresence(pid);
23084
- const message = "guardian \u672A\u63D0\u4F9B\u8FDB\u7A0B\u6811\u6392\u7A7A\u8BC1\u660E";
23216
+ const message = "guardian \u672A\u63D0\u4F9B\u53D7\u7BA1\u8FDB\u7A0B\u7EC4\u6392\u7A7A\u8BC1\u660E";
23085
23217
  if (presence !== "not-running") {
23086
23218
  if (entry.timeoutTimer) {
23087
23219
  clearTimeout(entry.timeoutTimer);
@@ -23102,7 +23234,7 @@ var init_worker = __esm({
23102
23234
  );
23103
23235
  return;
23104
23236
  }
23105
- const failure = code === 0 ? void 0 : `${spawnError ? "\u65E0\u6CD5\u542F\u52A8 opencode" : "opencode \u9000\u51FA\u7801"} ${spawnError?.message ?? code ?? "null"}${signal ? `\uFF0C\u4FE1\u53F7 ${signal}` : ""}\uFF08agent=${entry.task.agent}\uFF0Cmodel=${this.resolveModel(entry.task.model) ?? "Agent/\u9ED8\u8BA4\u914D\u7F6E"}\uFF0Ccwd=${entry.task.cwd ?? process.cwd()}\uFF09`;
23237
+ const failure = code === 0 ? void 0 : `${spawnError ? "\u65E0\u6CD5\u542F\u52A8 opencode" : "opencode \u9000\u51FA\u7801"} ${spawnError?.message ?? code ?? "null"}${signal ? `\uFF0C\u4FE1\u53F7 ${signal}` : ""}\uFF08agent=${entry.task.agent}\uFF0Cmodel=${this.resolveModel(entry.task.model) ?? "Agent/\u9ED8\u8BA4\u914D\u7F6E"}\uFF0Cvariant=${this.resolveVariant(entry.task.variant) ?? "Agent/\u6A21\u578B\u9ED8\u8BA4\u914D\u7F6E"}\uFF0Ccwd=${entry.task.cwd ?? process.cwd()}\uFF09`;
23106
23238
  this.runDetached(
23107
23239
  this.settleEntry(entry, code, failure),
23108
23240
  "task settlement failed",
@@ -23117,17 +23249,48 @@ var init_worker = __esm({
23117
23249
  clearTimeout(entry.timeoutTimer);
23118
23250
  entry.timeoutTimer = null;
23119
23251
  }
23120
- const settlement = this.commitEntry(entry, code, failure).then(() => true).catch((error) => {
23121
- markGatewayFailure("worker", error);
23122
- this.logError("task settlement failed", error, entry.task.id);
23123
- return false;
23124
- }).finally(() => {
23252
+ const settlement = this.commitEntryWithRetry(entry, code, failure).finally(() => {
23125
23253
  this.runningTasks.delete(entry.task.id);
23126
23254
  this.releaseBatch(entry.task);
23127
23255
  });
23128
23256
  entry.settlementPromise = settlement;
23129
23257
  return settlement;
23130
23258
  }
23259
+ async commitEntryWithRetry(entry, code, failure) {
23260
+ for (let attempt = 0; ; attempt += 1) {
23261
+ try {
23262
+ await this.commitEntry(entry, code, failure);
23263
+ return true;
23264
+ } catch (error) {
23265
+ markGatewayFailure("worker", error);
23266
+ const shortRetryDelayMs = this.settlementRetryDelaysMs[attempt];
23267
+ let retryDelayMs = shortRetryDelayMs ?? this.settlementRetryIntervalMs;
23268
+ if (this.stopped) {
23269
+ const remainingMs = Math.max(0, (this.shutdownDeadlineMs ?? 0) - Date.now());
23270
+ if (remainingMs === 0) return false;
23271
+ retryDelayMs = Math.min(retryDelayMs, remainingMs);
23272
+ }
23273
+ this.logError(
23274
+ `task settlement failed; retrying in ${retryDelayMs}ms`,
23275
+ error,
23276
+ entry.task.id
23277
+ );
23278
+ await this.waitForSettlementRetry(retryDelayMs);
23279
+ }
23280
+ }
23281
+ }
23282
+ waitForSettlementRetry(delayMs) {
23283
+ return new Promise((resolve6) => {
23284
+ let timer;
23285
+ const finish = () => {
23286
+ clearTimeout(timer);
23287
+ this.settlementRetryWakeups.delete(finish);
23288
+ resolve6();
23289
+ };
23290
+ timer = setTimeout(finish, delayMs);
23291
+ this.settlementRetryWakeups.add(finish);
23292
+ });
23293
+ }
23131
23294
  async commitEntry(entry, code, failure) {
23132
23295
  const termination = entry.termination;
23133
23296
  if (termination?.kind === "shutdown") return;
@@ -23291,6 +23454,9 @@ ${output}` : ""}`;
23291
23454
  if (!taskModel || taskModel === "default") return null;
23292
23455
  return taskModel;
23293
23456
  }
23457
+ resolveVariant(taskVariant) {
23458
+ return taskVariant?.trim() || null;
23459
+ }
23294
23460
  runDetached(operation, message, taskId) {
23295
23461
  operation.catch((error) => {
23296
23462
  markGatewayFailure("worker", error);
@@ -23668,6 +23834,7 @@ function createTaskFromTemplate(templateId, options) {
23668
23834
  name: `${options.namePrefix ?? ""}${tmpl.name}`,
23669
23835
  agent: tmpl.agent,
23670
23836
  model: tmpl.model ?? "default",
23837
+ variant: tmpl.variant,
23671
23838
  prompt: tmpl.prompt,
23672
23839
  cwd: tmpl.cwd ?? null,
23673
23840
  category: tmpl.category ?? "general",
@@ -26073,17 +26240,49 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
26073
26240
  const child = spawn2(executable, args, {
26074
26241
  cwd,
26075
26242
  env: process.env,
26076
- stdio: ["ignore", "pipe", "pipe"]
26243
+ stdio: ["ignore", "pipe", "pipe"],
26244
+ detached: process.platform !== "win32"
26077
26245
  });
26078
26246
  let stdout = "";
26079
26247
  let stderr = "";
26080
26248
  let failure = null;
26081
- let finished = false;
26249
+ let settled = false;
26250
+ let forceKillTimer = null;
26251
+ let finalRejectTimer = null;
26252
+ let timer;
26253
+ const signalProcessTree = (signal) => {
26254
+ if (process.platform !== "win32" && child.pid) {
26255
+ try {
26256
+ process.kill(-child.pid, signal);
26257
+ return;
26258
+ } catch {
26259
+ }
26260
+ }
26261
+ child.kill(signal);
26262
+ };
26263
+ const rejectOnce = (error) => {
26264
+ failure ??= error;
26265
+ if (settled) return;
26266
+ settled = true;
26267
+ clearTimeout(timer);
26268
+ if (forceKillTimer) clearTimeout(forceKillTimer);
26269
+ if (finalRejectTimer) clearTimeout(finalRejectTimer);
26270
+ reject(failure);
26271
+ };
26272
+ const terminate = (error) => {
26273
+ if (failure) return;
26274
+ failure = error;
26275
+ signalProcessTree("SIGTERM");
26276
+ forceKillTimer = setTimeout(() => {
26277
+ signalProcessTree("SIGKILL");
26278
+ rejectOnce(error);
26279
+ }, 1e3);
26280
+ finalRejectTimer = setTimeout(() => rejectOnce(error), 2e3);
26281
+ };
26082
26282
  const append = (current, chunk) => {
26083
26283
  const next = current + chunk.toString();
26084
26284
  if (Buffer.byteLength(next) > MAX_OUTPUT_BYTES && failure === null) {
26085
- failure = new Error(`OpenCode \u8F93\u51FA\u8D85\u8FC7 ${MAX_OUTPUT_BYTES} bytes`);
26086
- child.kill("SIGTERM");
26285
+ terminate(new Error(`OpenCode \u8F93\u51FA\u8D85\u8FC7 ${MAX_OUTPUT_BYTES} bytes`));
26087
26286
  }
26088
26287
  return next.slice(-MAX_OUTPUT_BYTES);
26089
26288
  };
@@ -26094,23 +26293,25 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
26094
26293
  stderr = append(stderr, chunk);
26095
26294
  });
26096
26295
  child.once("error", (error) => {
26097
- failure ??= error;
26296
+ if (forceKillTimer) return;
26297
+ rejectOnce(error);
26098
26298
  });
26099
- const timer = setTimeout(() => {
26100
- failure ??= new Error(`OpenCode \u547D\u4EE4\u8D85\u8FC7 ${timeoutMs}ms \u672A\u5B8C\u6210`);
26101
- child.kill("SIGTERM");
26299
+ timer = setTimeout(() => {
26300
+ terminate(new Error(`OpenCode \u547D\u4EE4\u8D85\u8FC7 ${timeoutMs}ms \u672A\u5B8C\u6210`));
26102
26301
  }, timeoutMs);
26103
26302
  child.once("close", (code) => {
26104
- if (finished) return;
26105
- finished = true;
26106
26303
  clearTimeout(timer);
26304
+ if (forceKillTimer) return;
26305
+ if (finalRejectTimer) clearTimeout(finalRejectTimer);
26306
+ if (settled) return;
26307
+ settled = true;
26107
26308
  if (failure) {
26108
26309
  reject(failure);
26109
26310
  return;
26110
26311
  }
26111
26312
  if (code !== 0) {
26112
26313
  const detail = cleanOutput(stderr).trim() || `\u9000\u51FA\u7801 ${code ?? "null"}`;
26113
- reject(new Error(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
26314
+ reject(new OpenCodeCommandExitError(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
26114
26315
  return;
26115
26316
  }
26116
26317
  resolve6(cleanOutput(stdout));
@@ -26120,6 +26321,52 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
26120
26321
  function parseOpenCodeModels(output) {
26121
26322
  return [...new Set(cleanOutput(output).split("\n").map((line) => line.trim()).filter((line) => /^[^\s/]+\/.+/.test(line)))].sort((left, right) => left.localeCompare(right));
26122
26323
  }
26324
+ function isRecord(value) {
26325
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26326
+ }
26327
+ function findJsonObjectEnd(value, start) {
26328
+ let depth = 0;
26329
+ let inString = false;
26330
+ let escaped = false;
26331
+ for (let index2 = start; index2 < value.length; index2 += 1) {
26332
+ const character = value[index2];
26333
+ if (inString) {
26334
+ if (escaped) escaped = false;
26335
+ else if (character === "\\") escaped = true;
26336
+ else if (character === '"') inString = false;
26337
+ continue;
26338
+ }
26339
+ if (character === '"') inString = true;
26340
+ else if (character === "{") depth += 1;
26341
+ else if (character === "}" && --depth === 0) return index2 + 1;
26342
+ }
26343
+ return null;
26344
+ }
26345
+ function parseOpenCodeModelMetadata(output) {
26346
+ const cleaned = cleanOutput(output);
26347
+ const models = /* @__PURE__ */ new Set();
26348
+ const variantsByModel = {};
26349
+ const headingPattern = /^([^\s/]+\/[^\r\n]+)\r?\n\s*(?=\{)/gm;
26350
+ for (const match2 of cleaned.matchAll(headingPattern)) {
26351
+ const model = match2[1].trim();
26352
+ models.add(model);
26353
+ let objectStart = (match2.index ?? 0) + match2[0].length;
26354
+ while (/\s/.test(cleaned[objectStart] ?? "")) objectStart += 1;
26355
+ if (cleaned[objectStart] !== "{") continue;
26356
+ const objectEnd = findJsonObjectEnd(cleaned, objectStart);
26357
+ if (objectEnd === null) continue;
26358
+ try {
26359
+ const metadata = JSON.parse(cleaned.slice(objectStart, objectEnd));
26360
+ if (!isRecord(metadata) || !isRecord(metadata.variants)) continue;
26361
+ variantsByModel[model] = Object.keys(metadata.variants).filter((variant) => variant.trim().length > 0).sort((left, right) => left.localeCompare(right));
26362
+ } catch {
26363
+ }
26364
+ }
26365
+ return {
26366
+ models: [...models].sort((left, right) => left.localeCompare(right)),
26367
+ variantsByModel
26368
+ };
26369
+ }
26123
26370
  function parseOpenCodeAgents(output) {
26124
26371
  const agents = /* @__PURE__ */ new Map();
26125
26372
  for (const line of cleanOutput(output).split("\n")) {
@@ -26138,19 +26385,23 @@ async function loadOpenCodeCatalog(cwd, options = {}) {
26138
26385
  const executable = options.executable ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
26139
26386
  const timeoutMs = options.timeoutMs ?? COMMAND_TIMEOUT_MS;
26140
26387
  const cacheKey = `${executable}\0${cwd}`;
26141
- const cached = catalogCache.get(cacheKey);
26142
- if (options.useCache !== false && cached && cached.expiresAt > Date.now()) {
26143
- return cached.result;
26388
+ const cached2 = catalogCache.get(cacheKey);
26389
+ if (options.useCache !== false && cached2 && cached2.expiresAt > Date.now()) {
26390
+ return cached2.result;
26144
26391
  }
26145
26392
  const result = Promise.all([
26146
- runOpenCode(executable, ["models"], cwd, timeoutMs),
26393
+ runOpenCode(executable, ["models", "--verbose"], cwd, timeoutMs).catch((error) => {
26394
+ if (!(error instanceof OpenCodeCommandExitError)) throw error;
26395
+ return runOpenCode(executable, ["models"], cwd, timeoutMs);
26396
+ }),
26147
26397
  runOpenCode(executable, ["agent", "list"], cwd, timeoutMs)
26148
26398
  ]).then(([modelsOutput, agentsOutput]) => {
26149
- const models = parseOpenCodeModels(modelsOutput);
26399
+ const metadata = parseOpenCodeModelMetadata(modelsOutput);
26400
+ const models = metadata.models.length > 0 ? metadata.models : parseOpenCodeModels(modelsOutput);
26150
26401
  const agents = parseOpenCodeAgents(agentsOutput).filter((agent) => agent.mode !== "subagent");
26151
26402
  if (models.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u7528\u6A21\u578B");
26152
26403
  if (agents.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u76F4\u63A5\u8FD0\u884C\u7684\u4E3B Agent");
26153
- return { cwd, models, agents };
26404
+ return { cwd, models, variantsByModel: metadata.variantsByModel, agents };
26154
26405
  });
26155
26406
  if (options.useCache !== false) {
26156
26407
  catalogCache.set(cacheKey, { expiresAt: Date.now() + CATALOG_CACHE_MS, result });
@@ -26160,7 +26411,7 @@ async function loadOpenCodeCatalog(cwd, options = {}) {
26160
26411
  }
26161
26412
  return result;
26162
26413
  }
26163
- var CATALOG_CACHE_MS, COMMAND_TIMEOUT_MS, MAX_OUTPUT_BYTES, ANSI_PATTERN, catalogCache;
26414
+ var CATALOG_CACHE_MS, COMMAND_TIMEOUT_MS, MAX_OUTPUT_BYTES, ANSI_PATTERN, OpenCodeCommandExitError, catalogCache;
26164
26415
  var init_opencode_catalog = __esm({
26165
26416
  "src/core/opencode-catalog.ts"() {
26166
26417
  "use strict";
@@ -26169,10 +26420,115 @@ var init_opencode_catalog = __esm({
26169
26420
  COMMAND_TIMEOUT_MS = 2e4;
26170
26421
  MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
26171
26422
  ANSI_PATTERN = /\x1B(?:[@-_][0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
26423
+ OpenCodeCommandExitError = class extends Error {
26424
+ };
26172
26425
  catalogCache = /* @__PURE__ */ new Map();
26173
26426
  }
26174
26427
  });
26175
26428
 
26429
+ // src/web/gateway-diagnostic.ts
26430
+ import { spawn as spawn3 } from "child_process";
26431
+ import { existsSync as existsSync8 } from "fs";
26432
+ import { dirname as dirname9, join as join7 } from "path";
26433
+ import { fileURLToPath as fileURLToPath6 } from "url";
26434
+ function killDiagnosticGroup(pid) {
26435
+ if (process.platform !== "win32") {
26436
+ try {
26437
+ process.kill(-pid, "SIGKILL");
26438
+ return;
26439
+ } catch {
26440
+ }
26441
+ }
26442
+ try {
26443
+ process.kill(pid, "SIGKILL");
26444
+ } catch {
26445
+ }
26446
+ }
26447
+ function diagnosticEntry() {
26448
+ const extension = import.meta.url.endsWith(".ts") ? "ts" : "js";
26449
+ const moduleDir = dirname9(fileURLToPath6(import.meta.url));
26450
+ const candidates = [
26451
+ join7(moduleDir, `../daemon/gateway-diagnostic-runner.${extension}`),
26452
+ join7(moduleDir, `../../src/daemon/gateway-diagnostic-runner.${extension}`)
26453
+ ];
26454
+ const entry = candidates.find((candidate) => existsSync8(candidate));
26455
+ if (!entry) throw new Error(`Gateway diagnostic runner \u4E0D\u5B58\u5728\uFF1A${candidates.join(", ")}`);
26456
+ return entry;
26457
+ }
26458
+ async function runGatewayDiagnostic() {
26459
+ return new Promise((resolve6, reject) => {
26460
+ const child = spawn3(process.execPath, [diagnosticEntry()], {
26461
+ cwd: process.cwd(),
26462
+ env: process.env,
26463
+ detached: process.platform !== "win32",
26464
+ stdio: ["ignore", "pipe", "pipe"]
26465
+ });
26466
+ let stdout = "";
26467
+ let stderr = "";
26468
+ let timedOut = false;
26469
+ if (child.pid) activeProcessGroups.add(child.pid);
26470
+ child.stdout?.on("data", (chunk) => {
26471
+ stdout += chunk.toString();
26472
+ });
26473
+ child.stderr?.on("data", (chunk) => {
26474
+ stderr += chunk.toString();
26475
+ });
26476
+ child.once("error", reject);
26477
+ child.once("close", (exitCode) => {
26478
+ clearTimeout(timer);
26479
+ if (child.pid) {
26480
+ killDiagnosticGroup(child.pid);
26481
+ activeProcessGroups.delete(child.pid);
26482
+ }
26483
+ if (timedOut) {
26484
+ reject(new Error(`Gateway diagnostic runner \u8D85\u8FC7 ${DIAGNOSTIC_TIMEOUT_MS}ms \u672A\u5B8C\u6210`));
26485
+ return;
26486
+ }
26487
+ if (exitCode !== 0) {
26488
+ reject(new Error(stderr.trim() || `Gateway diagnostic runner \u9000\u51FA\u7801 ${exitCode}`));
26489
+ return;
26490
+ }
26491
+ try {
26492
+ resolve6(JSON.parse(stdout));
26493
+ } catch (error) {
26494
+ reject(error);
26495
+ }
26496
+ });
26497
+ const timer = setTimeout(() => {
26498
+ timedOut = true;
26499
+ if (child.pid) killDiagnosticGroup(child.pid);
26500
+ else child.kill("SIGKILL");
26501
+ }, DIAGNOSTIC_TIMEOUT_MS);
26502
+ });
26503
+ }
26504
+ async function getDashboardGatewayDiagnostic(options = {}) {
26505
+ if (!options.fresh && cached && cached.expiresAt > Date.now()) return cached.diagnostic;
26506
+ if (!options.fresh && pending) return pending;
26507
+ const operation = runGatewayDiagnostic().then((diagnostic) => {
26508
+ cached = { expiresAt: Date.now() + CACHE_TTL_MS, diagnostic };
26509
+ return diagnostic;
26510
+ });
26511
+ if (options.fresh) return operation;
26512
+ pending = operation.finally(() => {
26513
+ pending = null;
26514
+ });
26515
+ return pending;
26516
+ }
26517
+ var CACHE_TTL_MS, DIAGNOSTIC_TIMEOUT_MS, cached, pending, activeProcessGroups;
26518
+ var init_gateway_diagnostic = __esm({
26519
+ "src/web/gateway-diagnostic.ts"() {
26520
+ "use strict";
26521
+ CACHE_TTL_MS = 5e3;
26522
+ DIAGNOSTIC_TIMEOUT_MS = 2e4;
26523
+ cached = null;
26524
+ pending = null;
26525
+ activeProcessGroups = /* @__PURE__ */ new Set();
26526
+ process.once("exit", () => {
26527
+ for (const pid of activeProcessGroups) killDiagnosticGroup(pid);
26528
+ });
26529
+ }
26530
+ });
26531
+
26176
26532
  // src/web/ui.ts
26177
26533
  function t(locale, key, values = {}) {
26178
26534
  const template = (locale === "en" ? EN : ZH)[key];
@@ -26259,6 +26615,7 @@ function clientMessages(locale) {
26259
26615
  "details.copySuccess",
26260
26616
  "details.id",
26261
26617
  "details.project",
26618
+ "details.variant",
26262
26619
  "details.prompt",
26263
26620
  "details.result",
26264
26621
  "details.category",
@@ -26359,6 +26716,7 @@ function clientMessages(locale) {
26359
26716
  "catalog.chooseProject",
26360
26717
  "catalog.defaultModel",
26361
26718
  "catalog.defaultProvider",
26719
+ "catalog.defaultVariant",
26362
26720
  "catalog.loading",
26363
26721
  "catalog.loaded",
26364
26722
  "catalog.failed",
@@ -26483,23 +26841,23 @@ function renderLayout(options) {
26483
26841
  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)}
26484
26842
  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')}
26485
26843
  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}
26486
- 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}
26844
+ 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}
26487
26845
  function detailFields(type,data){if(type==='task')return [
26488
26846
  [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}],
26489
- [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
26847
+ [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}],
26490
26848
  [text('details.category'),data.category],[text('details.batch'),data.batchId],[text('details.dependency'),data.dependsOn?'#'+data.dependsOn:text('details.none')],
26491
26849
  [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.retryCount'),String(data.retryCount??0)+' / '+String(data.maxRetries??0)],
26492
26850
  [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.scheduledAt'),detailDate(data.scheduledAt)],
26493
26851
  [text('details.createdAt'),detailDate(data.createdAt)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
26494
26852
  [text('details.result'),detailTaskResult(data),{wide:true,long:true}]
26495
26853
  ];if(type==='run'){const started=data.startedAt?new Date(data.startedAt).getTime():null;const finished=data.finishedAt?new Date(data.finishedAt).getTime():null;return [
26496
- [text('details.id'),'Run #'+data.id],[text('details.taskId'),'#'+data.taskId],[text('table.status'),detailStatus('run',data.status)],[text('table.model'),detailModel(data.model)],
26854
+ [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')],
26497
26855
  [text('details.session'),detailSession(data.sessionId)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
26498
26856
  [text('table.duration'),started!==null?detailDuration((finished??Date.now())-started):text('details.none')],[text('details.heartbeat'),detailDate(data.heartbeatAt)],
26499
26857
  [text('details.process'),'Worker PID '+String(data.workerPid??'\u2014')+' \xB7 OpenCode PID '+String(data.childPid??'\u2014'),{wide:true,mono:true}]
26500
26858
  ];}const scheduleRule=data.scheduleType==='cron'?data.cronExpr:data.scheduleType==='recurring'?detailDuration(data.intervalMs):detailDate(data.runAt);return [
26501
26859
  [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}],
26502
- [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
26860
+ [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}],
26503
26861
  [text('template.scheduleType'),detailScheduleType(data.scheduleType)],[text('details.scheduleRule'),scheduleRule],[text('details.category'),data.category],[text('details.batch'),data.batchId],
26504
26862
  [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.maxInstances'),data.maxInstances],[text('details.maxRetries'),data.maxRetries??0],
26505
26863
  [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.lastRun'),detailDate(data.lastRunAt)],[text('details.nextRun'),detailDate(data.nextRunAt)],
@@ -26516,12 +26874,20 @@ function renderLayout(options) {
26516
26874
  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')}
26517
26875
  const catalogTimers={};const catalogRequests={};const catalogModels={};
26518
26876
  function catalogField(prefix,name){return document.getElementById(prefix+'-'+name)}
26519
- 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='';}
26877
+ 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='';}
26520
26878
  function appendCurrentOption(select,value){if(!value||[...select.options].some(option=>option.value===value))return;select.appendChild(new Option(value,value));}
26879
+ 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}
26880
+ function invalidateVariantRequest(prefix){const key=prefix+'-variant';catalogRequests[key]=(catalogRequests[key]||0)+1}
26881
+ function invalidateCatalogRequests(prefix){clearTimeout(catalogTimers[prefix]);catalogRequests[prefix]=(catalogRequests[prefix]||0)+1;invalidateVariantRequest(prefix)}
26882
+ 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}
26883
+ function handleProviderChange(prefix){clearVariantPreference(prefix);populateModelOptions(prefix)}
26884
+ function handleModelChange(prefix){clearVariantPreference(prefix);populateVariantOptions(prefix)}
26885
+ function handleVariantChange(prefix){const variant=catalogField(prefix,'variant');if(!variant)return;invalidateVariantRequest(prefix);variant.dataset.preferred='';variant.dataset.preferredModel=''}
26521
26886
  function modelProvider(value){const slash=value.indexOf('/');return slash>0?value.slice(0,slash):''}
26522
- 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}
26887
+ 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)}
26888
+ 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}}
26523
26889
  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}}}
26524
- function scheduleCatalogLoad(prefix){clearTimeout(catalogTimers[prefix]);catalogTimers[prefix]=setTimeout(()=>loadCatalog(prefix),450)}
26890
+ function scheduleCatalogLoad(prefix){invalidateCatalogRequests(prefix);resetCatalog(prefix);catalogTimers[prefix]=setTimeout(()=>loadCatalog(prefix),450)}
26525
26891
  let directoryTargetId='';let directoryCurrent='';let directoryEntries=[];let directoryShowHidden=false;
26526
26892
  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)}}
26527
26893
  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}}
@@ -26531,16 +26897,16 @@ function renderLayout(options) {
26531
26897
  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'}
26532
26898
  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}
26533
26899
  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)}
26534
- 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)}
26535
- 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')}}
26536
- 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}}
26900
+ 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)}
26901
+ 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')}}
26902
+ 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}}
26537
26903
  function localDateTime(milliseconds){const date=new Date(milliseconds);const local=new Date(milliseconds-date.getTimezoneOffset()*60000);return local.toISOString().slice(0,23)}
26538
26904
  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')}}
26539
26905
  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}
26540
26906
  function selectedRunAt(){const input=templateField('run-at');return resolveEditedRunAt(input.dataset.originalEpoch?Number(input.dataset.originalEpoch):null,input.dataset.originalLocal||'',input.value)}
26541
- 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)}
26542
- 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')}}
26543
- 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}}
26907
+ 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)}
26908
+ 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')}}
26909
+ 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}}
26544
26910
  async function enableTmpl(id){try{await readJson(await fetch('/api/templates/'+id+'/enable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
26545
26911
  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')}}
26546
26912
  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')}}
@@ -26722,6 +27088,7 @@ var init_ui = __esm({
26722
27088
  "template.cwdHint": "OpenCode \u4F1A\u5728\u8FD9\u4E2A\u76EE\u5F55\u4E2D\u6267\u884C\u4EFB\u52A1\u3002",
26723
27089
  "template.agent": "Agent",
26724
27090
  "template.model": "\u6A21\u578B",
27091
+ "template.variant": "\u6A21\u578B Variant",
26725
27092
  "template.prompt": "\u63D0\u793A\u8BCD",
26726
27093
  "template.scheduleType": "\u6267\u884C\u65B9\u5F0F",
26727
27094
  "template.cronExpr": "Cron \u8868\u8FBE\u5F0F",
@@ -26759,9 +27126,11 @@ var init_ui = __esm({
26759
27126
  "catalog.chooseProject": "\u8BF7\u5148\u9009\u62E9\u9879\u76EE\u76EE\u5F55",
26760
27127
  "catalog.defaultModel": "\u8DDF\u968F Agent / OpenCode \u9ED8\u8BA4\u6A21\u578B",
26761
27128
  "catalog.defaultProvider": "\u9ED8\u8BA4\u6A21\u578B",
27129
+ "catalog.defaultVariant": "\u8DDF\u968F Agent / \u6A21\u578B\u9ED8\u8BA4\u8BBE\u7F6E",
26762
27130
  "catalog.provider": "\u6A21\u578B\u63D0\u4F9B\u5546",
26763
27131
  "catalog.model": "\u5177\u4F53\u6A21\u578B",
26764
- "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",
27132
+ "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",
27133
+ "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",
26765
27134
  "catalog.agentHint": "\u6765\u81EA\u5F53\u524D\u9879\u76EE\u7684 opencode agent list\u3002",
26766
27135
  "catalog.loading": "\u6B63\u5728\u8BFB\u53D6\u6B64\u9879\u76EE\u53EF\u7528\u7684 Agent \u548C\u6A21\u578B\u2026",
26767
27136
  "catalog.loaded": "\u5DF2\u4ECE\u672C\u673A OpenCode \u8BFB\u53D6 {agents} \u4E2A Agent\u3001{models} \u4E2A\u6A21\u578B\u3002",
@@ -26792,6 +27161,7 @@ var init_ui = __esm({
26792
27161
  "details.copySuccess": "\u539F\u59CB\u6570\u636E\u5DF2\u590D\u5236",
26793
27162
  "details.id": "\u7F16\u53F7",
26794
27163
  "details.project": "\u9879\u76EE\u76EE\u5F55",
27164
+ "details.variant": "\u6A21\u578B Variant",
26795
27165
  "details.prompt": "\u63D0\u793A\u8BCD",
26796
27166
  "details.result": "\u6267\u884C\u7ED3\u679C / \u5931\u8D25\u539F\u56E0",
26797
27167
  "details.category": "\u5206\u7C7B",
@@ -26824,7 +27194,7 @@ var init_ui = __esm({
26824
27194
  "details.enabledYes": "\u5DF2\u542F\u7528",
26825
27195
  "details.enabledNo": "\u5DF2\u505C\u7528",
26826
27196
  "dialog.cancelTask": "\u53D6\u6D88\u4EFB\u52A1 #{id}\uFF1F",
26827
- "dialog.cancelTaskBody": "\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u4F1A\u5728\u4E0B\u4E00\u4E2A\u8F6E\u8BE2\u5468\u671F\u7EC8\u6B62\u5BF9\u5E94\u8FDB\u7A0B\u6811\u3002",
27197
+ "dialog.cancelTaskBody": "\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u4F1A\u5728\u4E0B\u4E00\u4E2A\u8F6E\u8BE2\u5468\u671F\u7EC8\u6B62\u5BF9\u5E94\u7684\u53D7\u7BA1\u8FDB\u7A0B\u7EC4\u3002",
26828
27198
  "dialog.retryTask": "\u91CD\u8BD5\u4EFB\u52A1 #{id}\uFF1F",
26829
27199
  "dialog.retryTaskBody": "\u4EFB\u52A1\u5C06\u56DE\u5230\u5F85\u6267\u884C\u72B6\u6001\uFF0C\u5E76\u91CD\u7F6E\u81EA\u52A8\u91CD\u8BD5\u9884\u7B97\u3002",
26830
27200
  "dialog.deleteTask": "\u5220\u9664\u4EFB\u52A1 #{id}\uFF1F",
@@ -27023,6 +27393,7 @@ var init_ui = __esm({
27023
27393
  "template.cwdHint": "OpenCode runs the task in this directory.",
27024
27394
  "template.agent": "Agent",
27025
27395
  "template.model": "Model",
27396
+ "template.variant": "Model variant",
27026
27397
  "template.prompt": "Prompt",
27027
27398
  "template.scheduleType": "Schedule",
27028
27399
  "template.cronExpr": "Cron expression",
@@ -27060,9 +27431,11 @@ var init_ui = __esm({
27060
27431
  "catalog.chooseProject": "Choose a project directory first",
27061
27432
  "catalog.defaultModel": "Use the Agent / OpenCode default model",
27062
27433
  "catalog.defaultProvider": "Default model",
27434
+ "catalog.defaultVariant": "Use the Agent / model default",
27063
27435
  "catalog.provider": "Model provider",
27064
27436
  "catalog.model": "Model",
27065
- "catalog.modelHint": "Choose a provider, then a model returned by opencode models for this project. Default does not pass -m.",
27437
+ "catalog.modelHint": "Choose a provider, then a model returned by opencode models --verbose for this project. Default does not pass -m.",
27438
+ "catalog.variantHint": "Only variants declared by the selected model are shown. Default does not pass --variant.",
27066
27439
  "catalog.agentHint": "Loaded from opencode agent list for this project.",
27067
27440
  "catalog.loading": "Loading Agents and models available to this project\u2026",
27068
27441
  "catalog.loaded": "Loaded {agents} Agents and {models} models from local OpenCode.",
@@ -27093,6 +27466,7 @@ var init_ui = __esm({
27093
27466
  "details.copySuccess": "Raw data copied",
27094
27467
  "details.id": "ID",
27095
27468
  "details.project": "Project directory",
27469
+ "details.variant": "Model variant",
27096
27470
  "details.prompt": "Prompt",
27097
27471
  "details.result": "Result / failure reason",
27098
27472
  "details.category": "Category",
@@ -27125,7 +27499,7 @@ var init_ui = __esm({
27125
27499
  "details.enabledYes": "Enabled",
27126
27500
  "details.enabledNo": "Disabled",
27127
27501
  "dialog.cancelTask": "Cancel task #{id}?",
27128
- "dialog.cancelTaskBody": "A running task will terminate its process tree on the next worker poll.",
27502
+ "dialog.cancelTaskBody": "A running task will terminate its managed process group on the next worker poll.",
27129
27503
  "dialog.retryTask": "Retry task #{id}?",
27130
27504
  "dialog.retryTaskBody": "The task returns to pending and its automatic retry budget is reset.",
27131
27505
  "dialog.deleteTask": "Delete task #{id}?",
@@ -27546,7 +27920,7 @@ __export(web_exports, {
27546
27920
  setDashboardRuntimeConfig: () => setDashboardRuntimeConfig
27547
27921
  });
27548
27922
  import {
27549
- existsSync as existsSync8,
27923
+ existsSync as existsSync9,
27550
27924
  mkdirSync as mkdirSync5,
27551
27925
  readFileSync as readFileSync5,
27552
27926
  readdirSync as readdirSync2,
@@ -27555,7 +27929,7 @@ import {
27555
27929
  writeFileSync as writeFileSync3
27556
27930
  } from "fs";
27557
27931
  import { homedir as homedir5 } from "os";
27558
- import { basename as basename3, dirname as dirname9, join as join7 } from "path";
27932
+ import { basename as basename3, dirname as dirname10, join as join8 } from "path";
27559
27933
  function setDashboardRuntimeConfig(config) {
27560
27934
  runtimeConfig = config === null ? null : structuredClone(config);
27561
27935
  }
@@ -27580,10 +27954,13 @@ function resolveDashboardConfigState(runtimeAvailable, restartRequired, managedR
27580
27954
  function isSafeDashboardRestartTarget(diagnostic, currentPid) {
27581
27955
  return diagnostic.pm2Installed && diagnostic.processFound && diagnostic.status === "online" && diagnostic.pid === currentPid && diagnostic.ready && diagnostic.scopeMatches;
27582
27956
  }
27583
- function canRestartCurrentGateway() {
27957
+ async function canRestartCurrentGateway(fresh = false) {
27584
27958
  if (runtimeConfig === null) return false;
27585
27959
  try {
27586
- return isSafeDashboardRestartTarget(getGatewayDiagnostic(), process.pid);
27960
+ return isSafeDashboardRestartTarget(
27961
+ await getDashboardGatewayDiagnostic({ fresh }),
27962
+ process.pid
27963
+ );
27587
27964
  } catch {
27588
27965
  return false;
27589
27966
  }
@@ -27598,7 +27975,7 @@ function parseTaskStatus2(value) {
27598
27975
  }
27599
27976
  function listChildDirectories(path) {
27600
27977
  return readdirSync2(path, { withFileTypes: true }).flatMap((entry) => {
27601
- const entryPath = join7(path, entry.name);
27978
+ const entryPath = join8(path, entry.name);
27602
27979
  if (entry.isDirectory()) return [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }];
27603
27980
  if (!entry.isSymbolicLink()) return [];
27604
27981
  try {
@@ -27655,6 +28032,7 @@ function parseTaskPayload(value) {
27655
28032
  cwd: requiredString("cwd"),
27656
28033
  agent: requiredString("agent"),
27657
28034
  model: optionalString("model", "default"),
28035
+ variant: Object.hasOwn(input, "variant") ? optionalString("variant") : void 0,
27658
28036
  prompt: requiredString("prompt"),
27659
28037
  category: optionalString("category", "general"),
27660
28038
  batchId: optionalString("batchId"),
@@ -27670,6 +28048,7 @@ function editableTaskPayload(input) {
27670
28048
  name: input.name,
27671
28049
  agent: input.agent,
27672
28050
  model: input.model ?? "default",
28051
+ ...input.variant === void 0 ? {} : { variant: input.variant },
27673
28052
  prompt: input.prompt,
27674
28053
  category: input.category ?? "general",
27675
28054
  batchId: input.batchId ?? null,
@@ -27725,6 +28104,7 @@ function parseTemplatePayload(value) {
27725
28104
  name: requiredString("name"),
27726
28105
  agent: requiredString("agent"),
27727
28106
  model: optionalString("model", "default"),
28107
+ variant: Object.hasOwn(input, "variant") ? optionalString("variant") : void 0,
27728
28108
  prompt: requiredString("prompt"),
27729
28109
  cwd: requiredString("cwd"),
27730
28110
  category: optionalString("category", "general"),
@@ -27835,7 +28215,7 @@ function esc(value) {
27835
28215
  }
27836
28216
  function readCurrentConfig() {
27837
28217
  const configPath = getConfigPath();
27838
- if (!existsSync8(configPath)) return {};
28218
+ if (!existsSync9(configPath)) return {};
27839
28219
  try {
27840
28220
  return JSON.parse(readFileSync5(configPath, "utf-8"));
27841
28221
  } catch {
@@ -27844,8 +28224,8 @@ function readCurrentConfig() {
27844
28224
  }
27845
28225
  function writeConfig(cfg) {
27846
28226
  const configPath = getConfigPath();
27847
- const dir = dirname9(configPath);
27848
- if (!existsSync8(dir)) mkdirSync5(dir, { recursive: true });
28227
+ const dir = dirname10(configPath);
28228
+ if (!existsSync9(dir)) mkdirSync5(dir, { recursive: true });
27849
28229
  const tempPath = `${configPath}.${process.pid}.tmp`;
27850
28230
  writeFileSync3(tempPath, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
27851
28231
  renameSync2(tempPath, configPath);
@@ -27899,7 +28279,7 @@ function pagination(locale, basePath, page, pages, total, suffix = "") {
27899
28279
  const next = page < pages ? `<a class="btn" href="${basePath}?page=${page + 1}${suffix}">${t(locale, "pagination.next")}${icon("chevronRight")}</a>` : "";
27900
28280
  return `<div class="pagination">${previous}<span class="summary">${t(locale, "pagination.summary", { page, pages, total })}</span>${next}</div>`;
27901
28281
  }
27902
- var app, LEGACY_PROJECT_FILTER, TASK_STATUSES2, SESSION_ID_PATTERN, runtimeConfig, restartScheduled, dashboardApp, web_default;
28282
+ var app, LEGACY_PROJECT_FILTER, TASK_STATUSES2, SESSION_ID_PATTERN, LOOPBACK_HOST_PATTERN, runtimeConfig, restartScheduled, dashboardApp, web_default;
27903
28283
  var init_web = __esm({
27904
28284
  "src/web/index.tsx"() {
27905
28285
  "use strict";
@@ -27916,7 +28296,7 @@ var init_web = __esm({
27916
28296
  init_config();
27917
28297
  init_health();
27918
28298
  init_job_templates();
27919
- init_pm2();
28299
+ init_gateway_diagnostic();
27920
28300
  init_ui();
27921
28301
  app = new Hono2();
27922
28302
  LEGACY_PROJECT_FILTER = "__supertask_legacy__";
@@ -27929,9 +28309,18 @@ var init_web = __esm({
27929
28309
  "cancelled"
27930
28310
  ]);
27931
28311
  SESSION_ID_PATTERN = /^ses_[A-Za-z0-9_]+$/;
28312
+ LOOPBACK_HOST_PATTERN = /^(?:localhost|127\.0\.0\.1|\[::1\])(?::([1-9]\d{0,4}))?$/i;
27932
28313
  runtimeConfig = null;
27933
28314
  restartScheduled = false;
27934
28315
  app.use("*", async (c, next) => {
28316
+ const requestHostname = new URL(c.req.url).hostname;
28317
+ const hostHeader = c.req.header("Host");
28318
+ const loopbackHosts = ["localhost", "127.0.0.1", "[::1]"];
28319
+ const hostMatch = hostHeader?.match(LOOPBACK_HOST_PATTERN) ?? null;
28320
+ const hostPort = hostMatch?.[1] === void 0 ? null : Number(hostMatch[1]);
28321
+ if (!loopbackHosts.includes(requestHostname) || hostHeader !== void 0 && (!hostMatch || hostPort !== null && hostPort > 65535)) {
28322
+ return c.json({ error: "invalid dashboard host" }, 421);
28323
+ }
27935
28324
  await next();
27936
28325
  c.header("X-Content-Type-Options", "nosniff");
27937
28326
  c.header("X-Frame-Options", "DENY");
@@ -27968,7 +28357,7 @@ var init_web = __esm({
27968
28357
  validateTaskWorkingDirectory(requestedPath);
27969
28358
  return c.json({
27970
28359
  path: requestedPath,
27971
- parent: dirname9(requestedPath),
28360
+ parent: dirname10(requestedPath),
27972
28361
  home: homedir5(),
27973
28362
  directories: listChildDirectories(requestedPath)
27974
28363
  });
@@ -28058,7 +28447,7 @@ var init_web = __esm({
28058
28447
  const latestRun = latestRuns.get(task.id);
28059
28448
  const executionActive = latestRun?.status === "running";
28060
28449
  const batchId = task.batchId?.trim() || null;
28061
- const searchable = esc(`${task.name} ${task.agent} ${task.prompt} ${task.cwd ?? ""} ${task.batchId ?? ""} ${task.category ?? ""}`);
28450
+ const searchable = esc(`${task.name} ${task.agent} ${task.model ?? ""} ${task.variant ?? ""} ${task.prompt} ${task.cwd ?? ""} ${task.batchId ?? ""} ${task.category ?? ""}`);
28062
28451
  return `<tr data-task-row data-search="${searchable}">
28063
28452
  <td class="faint" data-label="${t(locale, "table.id")}">#${task.id}</td>
28064
28453
  <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>
@@ -28136,11 +28525,12 @@ var init_web = __esm({
28136
28525
  <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>
28137
28526
  <datalist id="task-cwd-options">${projects.map((project) => `<option value="${esc(project.cwd)}"></option>`).join("")}</datalist>
28138
28527
  <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>
28139
- <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="populateModelOptions('task')"><option value="">${t(locale, "catalog.defaultProvider")}</option></select><select id="task-model" required aria-label="${t(locale, "catalog.model")}" disabled><option value="default">${t(locale, "catalog.defaultModel")}</option></select></div><small>${t(locale, "catalog.modelHint")}</small></label>
28528
+ <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>
28529
+ <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>
28140
28530
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="task-prompt" rows="6" required></textarea></label>
28141
28531
  </div>
28142
28532
  <p id="task-project-status" class="form-note"></p>
28143
- <p id="task-catalog-status" class="form-note catalog-status"></p>
28533
+ <p id="task-catalog-status" class="form-note catalog-status" role="status" aria-live="polite"></p>
28144
28534
  <details class="advanced-fields">
28145
28535
  <summary>${t(locale, "template.advanced")}</summary>
28146
28536
  <div class="template-form-grid">
@@ -28182,7 +28572,7 @@ var init_web = __esm({
28182
28572
  return `<tr>
28183
28573
  <td class="faint" data-label="${t(locale, "table.id")}">#${template.id}</td>
28184
28574
  <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>
28185
- <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>
28575
+ <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>
28186
28576
  <td data-label="${t(locale, "table.type")}"><span class="tag t-${scheduleType}">${typeLabel}</span></td>
28187
28577
  <td data-label="${t(locale, "table.rule")}" class="m small">${esc(rule)}</td>
28188
28578
  <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>
@@ -28212,14 +28602,15 @@ var init_web = __esm({
28212
28602
  <label class="form-field"><span>${t(locale, "template.name")}</span><input id="template-name" required maxlength="200" autocomplete="off"></label>
28213
28603
  <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>
28214
28604
  <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>
28215
- <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="populateModelOptions('template')"><option value="">${t(locale, "catalog.defaultProvider")}</option></select><select id="template-model" required aria-label="${t(locale, "catalog.model")}" disabled><option value="default">${t(locale, "catalog.defaultModel")}</option></select></div><small>${t(locale, "catalog.modelHint")}</small></label>
28605
+ <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>
28606
+ <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>
28216
28607
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="template-prompt" rows="6" required></textarea></label>
28217
28608
  <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>
28218
28609
  <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>
28219
28610
  <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>
28220
28611
  <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>
28221
28612
  </div>
28222
- <p id="template-catalog-status" class="form-note catalog-status"></p>
28613
+ <p id="template-catalog-status" class="form-note catalog-status" role="status" aria-live="polite"></p>
28223
28614
  <details class="advanced-fields">
28224
28615
  <summary>${t(locale, "template.advanced")}</summary>
28225
28616
  <div class="template-form-grid">
@@ -28253,6 +28644,7 @@ var init_web = __esm({
28253
28644
  taskId: taskRuns4.taskId,
28254
28645
  sessionId: taskRuns4.sessionId,
28255
28646
  model: taskRuns4.model,
28647
+ variant: taskRuns4.variant,
28256
28648
  status: taskRuns4.status,
28257
28649
  startedAt: taskRuns4.startedAt,
28258
28650
  finishedAt: taskRuns4.finishedAt,
@@ -28273,7 +28665,7 @@ var init_web = __esm({
28273
28665
  const log = run.log ? renderRunLog(run.id, run.taskName, run.log, locale, run.status !== "done") : "";
28274
28666
  return `<tr class="run-summary-row">
28275
28667
  <td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td>
28276
- <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"><span class="tag">${esc(run.model)}</span></div>` : ""}</td>
28668
+ <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>
28277
28669
  <td data-label="${t(locale, "table.agent")}"><span class="tag">${esc(run.taskAgent)}</span></td>
28278
28670
  <td data-label="${t(locale, "table.session")}" class="m small">${esc(maskSessionId(run.sessionId))}</td>
28279
28671
  <td data-label="${t(locale, "table.status")}"><span class="badge b-${status}">${runStatusText(locale, run.status ?? "unknown")}</span></td>
@@ -28300,7 +28692,7 @@ var init_web = __esm({
28300
28692
  const config = loadConfig();
28301
28693
  const activeConfig = runtimeConfig ?? config;
28302
28694
  const restartRequired = runtimeConfig !== null && !configsEqual(config, runtimeConfig);
28303
- const managedRestart = canRestartCurrentGateway();
28695
+ const managedRestart = await canRestartCurrentGateway();
28304
28696
  const configState = resolveDashboardConfigState(
28305
28697
  runtimeConfig !== null,
28306
28698
  restartRequired,
@@ -28319,11 +28711,11 @@ var init_web = __esm({
28319
28711
  TaskRunService.getAllRunningRuns(),
28320
28712
  TaskTemplateService.stats()
28321
28713
  ]);
28322
- const configExists = existsSync8(configPath);
28714
+ const configExists = existsSync9(configPath);
28323
28715
  const runRows = runningRuns.map((run) => {
28324
28716
  const session = maskSessionId(run.sessionId);
28325
28717
  return `<tr><td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td><td data-primary data-label="${t(locale, "table.task")}">#${run.taskId}</td><td data-label="${t(locale, "table.session")}" class="m small">${esc(session)}</td>
28326
- <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>
28718
+ <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>
28327
28719
  <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>
28328
28720
  <td data-label="${t(locale, "table.duration")}" class="small">${formatDuration(run.startedAt, null)}</td></tr>`;
28329
28721
  }).join("");
@@ -28420,9 +28812,9 @@ var init_web = __esm({
28420
28812
  if (!isValidSessionId(run.sessionId)) return c.json({ error: "session unavailable" }, 409);
28421
28813
  return c.json({ command: sessionCommand(run.sessionId) });
28422
28814
  });
28423
- app.get("/api/gateway/status", (c) => {
28815
+ app.get("/api/gateway/status", async (c) => {
28424
28816
  const savedConfig = loadConfig();
28425
- const managed = canRestartCurrentGateway();
28817
+ const managed = await canRestartCurrentGateway();
28426
28818
  return c.json({
28427
28819
  pid: process.pid,
28428
28820
  managed,
@@ -28547,7 +28939,7 @@ var init_web = __esm({
28547
28939
  return c.json({
28548
28940
  success: true,
28549
28941
  restartRequired: runtimeConfig === null || !configsEqual(savedConfig, runtimeConfig),
28550
- managed: canRestartCurrentGateway()
28942
+ managed: await canRestartCurrentGateway()
28551
28943
  });
28552
28944
  } catch (error) {
28553
28945
  return c.json({
@@ -28563,9 +28955,10 @@ var init_web = __esm({
28563
28955
  return c.json({ error: "confirmation must be RESTART" }, 400);
28564
28956
  }
28565
28957
  if (restartScheduled) return c.json({ error: "restart already scheduled" }, 409);
28566
- if (!canRestartCurrentGateway()) {
28958
+ if (!await canRestartCurrentGateway(true)) {
28567
28959
  return c.json({ error: "\u5F53\u524D Gateway \u4E0D\u662F\u7531\u5339\u914D\u8FD0\u884C\u4F5C\u7528\u57DF\u7684 PM2 \u8FDB\u7A0B\u6258\u7BA1\uFF0C\u65E0\u6CD5\u4ECE\u7F51\u9875\u5B89\u5168\u91CD\u542F" }, 409);
28568
28960
  }
28961
+ if (restartScheduled) return c.json({ error: "restart already scheduled" }, 409);
28569
28962
  restartScheduled = true;
28570
28963
  const previousPid = process.pid;
28571
28964
  setTimeout(() => {
@@ -29152,11 +29545,13 @@ async function runDoctorSmoke(options) {
29152
29545
  const cwd = resolve5(options.cwd);
29153
29546
  const marker = options.marker ?? `SUPERTASK_SMOKE_${randomUUID2().replaceAll("-", "").toUpperCase()}`;
29154
29547
  const model = options.model && options.model !== "default" ? options.model : void 0;
29548
+ const variant = options.variant?.trim() || void 0;
29155
29549
  const startedAt = Date.now();
29156
29550
  const task = await TaskService.add({
29157
29551
  name: "[doctor] Gateway real smoke test",
29158
29552
  agent: options.agent,
29159
29553
  model,
29554
+ variant,
29160
29555
  prompt: `\u4E0D\u8981\u8C03\u7528\u4EFB\u4F55\u5DE5\u5177\u3002\u53EA\u56DE\u590D\u8FD9\u4E00\u884C\uFF1A${marker}`,
29161
29556
  cwd,
29162
29557
  category: "diagnostic",
@@ -29180,6 +29575,7 @@ async function runDoctorSmoke(options) {
29180
29575
  status: "missing",
29181
29576
  agent: options.agent,
29182
29577
  model: model ?? null,
29578
+ variant: variant ?? null,
29183
29579
  cwd,
29184
29580
  durationMs: Date.now() - startedAt,
29185
29581
  error: "\u5192\u70DF\u4EFB\u52A1\u5728\u5B8C\u6210\u524D\u88AB\u5220\u9664"
@@ -29195,6 +29591,7 @@ async function runDoctorSmoke(options) {
29195
29591
  status: current2.status,
29196
29592
  agent: options.agent,
29197
29593
  model: model ?? null,
29594
+ variant: variant ?? null,
29198
29595
  cwd,
29199
29596
  durationMs: Date.now() - startedAt,
29200
29597
  error: markerObserved ? null : "OpenCode \u5DF2\u9000\u51FA\u6210\u529F\uFF0C\u4F46\u6A21\u578B\u6587\u672C\u4E0D\u662F\u9884\u671F\u7684\u7CBE\u786E\u6807\u8BB0"
@@ -29208,6 +29605,7 @@ async function runDoctorSmoke(options) {
29208
29605
  status: current2.status,
29209
29606
  agent: options.agent,
29210
29607
  model: model ?? null,
29608
+ variant: variant ?? null,
29211
29609
  cwd,
29212
29610
  durationMs: Date.now() - startedAt,
29213
29611
  error: tail(run2?.log ?? current2.resultLog) ?? `\u4EFB\u52A1\u8FDB\u5165 ${current2.status}`
@@ -29225,6 +29623,7 @@ async function runDoctorSmoke(options) {
29225
29623
  status: current?.status ?? "missing",
29226
29624
  agent: options.agent,
29227
29625
  model: model ?? null,
29626
+ variant: variant ?? null,
29228
29627
  cwd,
29229
29628
  durationMs: Date.now() - startedAt,
29230
29629
  error: `\u7B49\u5F85 Gateway \u6267\u884C\u8D85\u8FC7 ${options.timeoutMs}ms\uFF0C\u5192\u70DF\u4EFB\u52A1\u5DF2\u8BF7\u6C42\u53D6\u6D88`
@@ -29265,7 +29664,7 @@ if (cliLocale === "zh-CN") {
29265
29664
  writeOut: (value) => process.stdout.write(value.replace(/^Usage:/gm, "\u7528\u6CD5\uFF1A").replace(/^Options:/gm, "\u9009\u9879\uFF1A").replace(/^Commands:/gm, "\u547D\u4EE4\uFF1A").replace(/^Arguments:/gm, "\u53C2\u6570\uFF1A"))
29266
29665
  });
29267
29666
  }
29268
- program2.command("add").description(t2("\u521B\u5EFA\u65B0\u4EFB\u52A1", "create a queued task")).requiredOption("-n, --name <name>", t2("\u4EFB\u52A1\u540D\u79F0", "task name")).requiredOption("-a, --agent <agent>", t2("\u4E3B Agent \u540D\u79F0", "primary agent name")).requiredOption("-p, --prompt <prompt>", t2("\u63D0\u793A\u8BCD", "prompt")).option("-m, --model <model>", t2("\u6A21\u578B", "model")).option("-c, --category <category>", t2("\u5206\u7C7B", "category"), "general").option("-i, --importance <number>", t2("\u91CD\u8981\u7A0B\u5EA6 (1-5)", "importance (1-5)"), "3").option("-u, --urgency <number>", t2("\u7D27\u6025\u7A0B\u5EA6 (1-5)", "urgency (1-5)"), "3").option("-b, --batch <batchId>", t2("\u6279\u6B21 ID", "batch ID")).option("-d, --depends <taskId>", t2("\u4F9D\u8D56\u7684\u4EFB\u52A1 ID", "dependency task ID")).option("--max-retries <number>", t2("\u9996\u6B21\u6267\u884C\u4E4B\u5916\u5141\u8BB8\u7684\u91CD\u8BD5\u6B21\u6570", "retries allowed after the first attempt"), "3").option("--retry-backoff <duration>", t2("\u91CD\u8BD5\u9000\u907F\u57FA\u7840\u95F4\u9694\uFF0C\u5982 30s / 5min", "retry backoff base, e.g. 30s / 5min"), "30s").option("--timeout <duration>", t2("\u4EFB\u52A1\u786C\u8D85\u65F6\uFF0C\u5982 30min / 2h", "hard timeout, e.g. 30min / 2h")).option("-w, --cwd <path>", t2("(\u5DF2\u5E9F\u5F03) \u7CFB\u7EDF\u4F1A\u81EA\u52A8\u8BB0\u5F55\u63D0\u4EA4\u65F6\u7684\u5F53\u524D\u76EE\u5F55", "(deprecated) the submission directory is recorded automatically")).action(async (options) => withDb(async () => {
29667
+ program2.command("add").description(t2("\u521B\u5EFA\u65B0\u4EFB\u52A1", "create a queued task")).requiredOption("-n, --name <name>", t2("\u4EFB\u52A1\u540D\u79F0", "task name")).requiredOption("-a, --agent <agent>", t2("\u4E3B Agent \u540D\u79F0", "primary agent name")).requiredOption("-p, --prompt <prompt>", t2("\u63D0\u793A\u8BCD", "prompt")).option("-m, --model <model>", t2("\u6A21\u578B", "model")).option("--variant <variant>", t2("\u6A21\u578B variant\uFF0C\u5982 high / xhigh", "model variant, e.g. high / xhigh")).option("-c, --category <category>", t2("\u5206\u7C7B", "category"), "general").option("-i, --importance <number>", t2("\u91CD\u8981\u7A0B\u5EA6 (1-5)", "importance (1-5)"), "3").option("-u, --urgency <number>", t2("\u7D27\u6025\u7A0B\u5EA6 (1-5)", "urgency (1-5)"), "3").option("-b, --batch <batchId>", t2("\u6279\u6B21 ID", "batch ID")).option("-d, --depends <taskId>", t2("\u4F9D\u8D56\u7684\u4EFB\u52A1 ID", "dependency task ID")).option("--max-retries <number>", t2("\u9996\u6B21\u6267\u884C\u4E4B\u5916\u5141\u8BB8\u7684\u91CD\u8BD5\u6B21\u6570", "retries allowed after the first attempt"), "3").option("--retry-backoff <duration>", t2("\u91CD\u8BD5\u9000\u907F\u57FA\u7840\u95F4\u9694\uFF0C\u5982 30s / 5min", "retry backoff base, e.g. 30s / 5min"), "30s").option("--timeout <duration>", t2("\u4EFB\u52A1\u786C\u8D85\u65F6\uFF0C\u5982 30min / 2h", "hard timeout, e.g. 30min / 2h")).option("-w, --cwd <path>", t2("(\u5DF2\u5E9F\u5F03) \u7CFB\u7EDF\u4F1A\u81EA\u52A8\u8BB0\u5F55\u63D0\u4EA4\u65F6\u7684\u5F53\u524D\u76EE\u5F55", "(deprecated) the submission directory is recorded automatically")).action(async (options) => withDb(async () => {
29269
29668
  const submitCwd = process.cwd();
29270
29669
  const retryBackoffMs = parseDuration(options.retryBackoff);
29271
29670
  const timeoutMs = options.timeout ? parseDuration(options.timeout) : null;
@@ -29277,6 +29676,7 @@ program2.command("add").description(t2("\u521B\u5EFA\u65B0\u4EFB\u52A1", "create
29277
29676
  agent: options.agent,
29278
29677
  prompt: options.prompt,
29279
29678
  model: options.model,
29679
+ variant: options.variant,
29280
29680
  category: options.category,
29281
29681
  importance: parseBoundedInteger(options.importance, "importance", 1, 5),
29282
29682
  urgency: parseBoundedInteger(options.urgency, "urgency", 1, 5),
@@ -29289,17 +29689,23 @@ program2.command("add").description(t2("\u521B\u5EFA\u65B0\u4EFB\u52A1", "create
29289
29689
  });
29290
29690
  console.log(JSON.stringify({ id: task.id, status: "created" }, null, 2));
29291
29691
  }));
29292
- program2.command("edit").description(t2("\u4FEE\u6539\u5F53\u524D\u9879\u76EE\u4E2D\u5C1A\u672A\u5B8C\u6210\u7684\u4EFB\u52A1", "edit an unfinished task in the current project")).requiredOption("--id <id>", t2("\u4EFB\u52A1 ID", "task ID")).option("-n, --name <name>", t2("\u4EFB\u52A1\u540D\u79F0", "task name")).option("-a, --agent <agent>", t2("Agent \u540D\u79F0", "agent name")).option("-m, --model <model>", t2("\u6A21\u578B", "model")).option("-p, --prompt <prompt>", t2("\u63D0\u793A\u8BCD", "prompt")).option("-c, --category <category>", t2("\u5206\u7C7B", "category")).option("-i, --importance <number>", t2("\u91CD\u8981\u7A0B\u5EA6 (1-5)", "importance (1-5)")).option("-u, --urgency <number>", t2("\u7D27\u6025\u7A0B\u5EA6 (1-5)", "urgency (1-5)")).option("-b, --batch <batchId>", t2("\u6279\u6B21 ID", "batch ID")).option("--clear-batch", t2("\u6E05\u7A7A\u6279\u6B21 ID", "clear the batch ID")).option("--max-retries <number>", t2("\u9996\u6B21\u6267\u884C\u4E4B\u5916\u5141\u8BB8\u7684\u91CD\u8BD5\u6B21\u6570", "retries allowed after the first attempt")).option("--retry-backoff <duration>", t2("\u91CD\u8BD5\u9000\u907F\u57FA\u7840\u95F4\u9694\uFF0C\u5982 30s / 5min", "retry backoff base, e.g. 30s / 5min")).option("--timeout <duration>", t2("\u4EFB\u52A1\u786C\u8D85\u65F6\uFF0C\u5982 30min / 2h", "hard timeout, e.g. 30min / 2h")).option("--clear-timeout", t2("\u6E05\u7A7A\u4EFB\u52A1\u7EA7\u8D85\u65F6\uFF0C\u6539\u7528 Gateway \u9ED8\u8BA4\u503C", "clear the task timeout and use the Gateway default")).action(async (options) => withDb(async () => {
29692
+ program2.command("edit").description(t2("\u4FEE\u6539\u5F53\u524D\u9879\u76EE\u4E2D\u5C1A\u672A\u5B8C\u6210\u7684\u4EFB\u52A1", "edit an unfinished task in the current project")).requiredOption("--id <id>", t2("\u4EFB\u52A1 ID", "task ID")).option("-n, --name <name>", t2("\u4EFB\u52A1\u540D\u79F0", "task name")).option("-a, --agent <agent>", t2("Agent \u540D\u79F0", "agent name")).option("-m, --model <model>", t2("\u6A21\u578B", "model")).option("--variant <variant>", t2("\u6A21\u578B variant\uFF0C\u5982 high / xhigh", "model variant, e.g. high / xhigh")).option("--clear-variant", t2("\u6E05\u7A7A\u4EFB\u52A1\u7EA7 variant\uFF0C\u8DDF\u968F Agent / \u6A21\u578B\u9ED8\u8BA4\u503C", "clear the task variant and use the Agent / model default")).option("-p, --prompt <prompt>", t2("\u63D0\u793A\u8BCD", "prompt")).option("-c, --category <category>", t2("\u5206\u7C7B", "category")).option("-i, --importance <number>", t2("\u91CD\u8981\u7A0B\u5EA6 (1-5)", "importance (1-5)")).option("-u, --urgency <number>", t2("\u7D27\u6025\u7A0B\u5EA6 (1-5)", "urgency (1-5)")).option("-b, --batch <batchId>", t2("\u6279\u6B21 ID", "batch ID")).option("--clear-batch", t2("\u6E05\u7A7A\u6279\u6B21 ID", "clear the batch ID")).option("--max-retries <number>", t2("\u9996\u6B21\u6267\u884C\u4E4B\u5916\u5141\u8BB8\u7684\u91CD\u8BD5\u6B21\u6570", "retries allowed after the first attempt")).option("--retry-backoff <duration>", t2("\u91CD\u8BD5\u9000\u907F\u57FA\u7840\u95F4\u9694\uFF0C\u5982 30s / 5min", "retry backoff base, e.g. 30s / 5min")).option("--timeout <duration>", t2("\u4EFB\u52A1\u786C\u8D85\u65F6\uFF0C\u5982 30min / 2h", "hard timeout, e.g. 30min / 2h")).option("--clear-timeout", t2("\u6E05\u7A7A\u4EFB\u52A1\u7EA7\u8D85\u65F6\uFF0C\u6539\u7528 Gateway \u9ED8\u8BA4\u503C", "clear the task timeout and use the Gateway default")).action(async (options) => withDb(async () => {
29293
29693
  if (options.batch !== void 0 && options.clearBatch) {
29294
29694
  throw new Error("batch \u548C clear-batch \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
29295
29695
  }
29296
29696
  if (options.timeout !== void 0 && options.clearTimeout) {
29297
29697
  throw new Error("timeout \u548C clear-timeout \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
29298
29698
  }
29699
+ if (options.variant !== void 0 && options.clearVariant) {
29700
+ throw new Error("variant \u548C clear-variant \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
29701
+ }
29299
29702
  const update = {};
29300
29703
  for (const field of ["name", "agent", "model", "prompt", "category"]) {
29301
29704
  if (options[field] !== void 0) update[field] = options[field];
29302
29705
  }
29706
+ if (options.variant !== void 0 || options.clearVariant) {
29707
+ update.variant = options.clearVariant ? null : options.variant;
29708
+ }
29303
29709
  if (options.importance !== void 0) {
29304
29710
  update.importance = parseBoundedInteger(options.importance, "importance", 1, 5);
29305
29711
  }
@@ -29335,6 +29741,7 @@ program2.command("next").description(t2("\u83B7\u53D6\u4E0B\u4E00\u4E2A\u5F85\u6
29335
29741
  name: task.name,
29336
29742
  agent: task.agent,
29337
29743
  model: task.model,
29744
+ variant: task.variant,
29338
29745
  prompt: task.prompt,
29339
29746
  cwd: task.cwd,
29340
29747
  category: task.category,
@@ -29411,7 +29818,7 @@ program2.command("delete").description(t2("\u5220\u9664\u4EFB\u52A1", "delete a
29411
29818
  console.log(JSON.stringify({ deleted, id }));
29412
29819
  }));
29413
29820
  program2.command("template").description(t2("\u7BA1\u7406\u5B9A\u65F6\u4EFB\u52A1\u6A21\u677F", "manage scheduled task templates")).addCommand(
29414
- new Command("add").description(t2("\u521B\u5EFA\u5B9A\u65F6\u4EFB\u52A1\u6A21\u677F", "create a scheduled task template")).requiredOption("-n, --name <name>", t2("\u6A21\u677F\u540D\u79F0", "template name")).requiredOption("-a, --agent <agent>", t2("Agent \u540D\u79F0", "agent name")).requiredOption("-p, --prompt <prompt>", t2("\u63D0\u793A\u8BCD", "prompt")).requiredOption("-t, --type <type>", t2("\u5B9A\u65F6\u7C7B\u578B\uFF1Acron/delayed/recurring", "schedule type: cron/delayed/recurring")).option("--cron <expr>", t2("cron \u8868\u8FBE\u5F0F\uFF08cron \u7C7B\u578B\u5FC5\u586B\uFF09", "cron expression (required for cron)")).option("--delay <duration>", t2("\u5EF6\u8FDF\u65F6\u95F4\uFF08delayed \u5FC5\u586B\uFF09\uFF0C\u5982 30s / 5min / 1h / 2d", "delay (required for delayed), e.g. 30s / 5min / 1h / 2d")).option("--interval <duration>", t2("\u5FAA\u73AF\u95F4\u9694\uFF08recurring \u5FC5\u586B\uFF09\uFF0C\u5982 1h / 30min / 5s", "interval (required for recurring), e.g. 1h / 30min / 5s")).option("-m, --model <model>", t2("\u6A21\u578B", "model")).option("-c, --category <category>", t2("\u5206\u7C7B", "category"), "general").option("-i, --importance <number>", t2("\u91CD\u8981\u7A0B\u5EA6 1-5", "importance 1-5"), "3").option("-u, --urgency <number>", t2("\u7D27\u6025\u7A0B\u5EA6 1-5", "urgency 1-5"), "3").option("-b, --batch <batchId>", t2("\u6A21\u677F\u751F\u6210\u4EFB\u52A1\u7684\u6279\u6B21 ID", "batch ID for generated tasks")).option("--max-instances <number>", t2("\u81EA\u52A8\u5B9A\u65F6\u4EFB\u52A1\u7684\u6D3B\u8DC3\u5B9E\u4F8B\u4E0A\u9650\uFF08\u7ACB\u5373\u89E6\u53D1\u4E0D\u53D7\u9650\uFF09", "active instance limit for automatic scheduling (Run now is unrestricted)"), "1").option("--max-retries <number>", t2("\u6700\u5927\u91CD\u8BD5\u6B21\u6570", "maximum retries"), "3").option("--retry-backoff <duration>", t2("\u9000\u907F\u57FA\u7840\u95F4\u9694\uFF0C\u5982 30s / 5min", "retry backoff base, e.g. 30s / 5min"), "30s").option("--timeout <duration>", t2("\u6BCF\u6B21\u4EFB\u52A1\u786C\u8D85\u65F6\uFF0C\u5982 30min / 2h", "hard timeout per task, e.g. 30min / 2h")).action(async (options) => withDb(async () => {
29821
+ new Command("add").description(t2("\u521B\u5EFA\u5B9A\u65F6\u4EFB\u52A1\u6A21\u677F", "create a scheduled task template")).requiredOption("-n, --name <name>", t2("\u6A21\u677F\u540D\u79F0", "template name")).requiredOption("-a, --agent <agent>", t2("Agent \u540D\u79F0", "agent name")).requiredOption("-p, --prompt <prompt>", t2("\u63D0\u793A\u8BCD", "prompt")).requiredOption("-t, --type <type>", t2("\u5B9A\u65F6\u7C7B\u578B\uFF1Acron/delayed/recurring", "schedule type: cron/delayed/recurring")).option("--cron <expr>", t2("cron \u8868\u8FBE\u5F0F\uFF08cron \u7C7B\u578B\u5FC5\u586B\uFF09", "cron expression (required for cron)")).option("--delay <duration>", t2("\u5EF6\u8FDF\u65F6\u95F4\uFF08delayed \u5FC5\u586B\uFF09\uFF0C\u5982 30s / 5min / 1h / 2d", "delay (required for delayed), e.g. 30s / 5min / 1h / 2d")).option("--interval <duration>", t2("\u5FAA\u73AF\u95F4\u9694\uFF08recurring \u5FC5\u586B\uFF09\uFF0C\u5982 1h / 30min / 5s", "interval (required for recurring), e.g. 1h / 30min / 5s")).option("-m, --model <model>", t2("\u6A21\u578B", "model")).option("--variant <variant>", t2("\u6A21\u578B variant\uFF0C\u5982 high / xhigh", "model variant, e.g. high / xhigh")).option("-c, --category <category>", t2("\u5206\u7C7B", "category"), "general").option("-i, --importance <number>", t2("\u91CD\u8981\u7A0B\u5EA6 1-5", "importance 1-5"), "3").option("-u, --urgency <number>", t2("\u7D27\u6025\u7A0B\u5EA6 1-5", "urgency 1-5"), "3").option("-b, --batch <batchId>", t2("\u6A21\u677F\u751F\u6210\u4EFB\u52A1\u7684\u6279\u6B21 ID", "batch ID for generated tasks")).option("--max-instances <number>", t2("\u81EA\u52A8\u5B9A\u65F6\u4EFB\u52A1\u7684\u6D3B\u8DC3\u5B9E\u4F8B\u4E0A\u9650\uFF08\u7ACB\u5373\u89E6\u53D1\u4E0D\u53D7\u9650\uFF09", "active instance limit for automatic scheduling (Run now is unrestricted)"), "1").option("--max-retries <number>", t2("\u6700\u5927\u91CD\u8BD5\u6B21\u6570", "maximum retries"), "3").option("--retry-backoff <duration>", t2("\u9000\u907F\u57FA\u7840\u95F4\u9694\uFF0C\u5982 30s / 5min", "retry backoff base, e.g. 30s / 5min"), "30s").option("--timeout <duration>", t2("\u6BCF\u6B21\u4EFB\u52A1\u786C\u8D85\u65F6\uFF0C\u5982 30min / 2h", "hard timeout per task, e.g. 30min / 2h")).action(async (options) => withDb(async () => {
29415
29822
  let intervalMs = null;
29416
29823
  let runAt = null;
29417
29824
  const retryBackoffMs = parseDuration(options.retryBackoff);
@@ -29439,6 +29846,7 @@ program2.command("template").description(t2("\u7BA1\u7406\u5B9A\u65F6\u4EFB\u52A
29439
29846
  agent: options.agent,
29440
29847
  prompt: options.prompt,
29441
29848
  model: options.model,
29849
+ variant: options.variant,
29442
29850
  category: options.category,
29443
29851
  importance: parseBoundedInteger(options.importance, "importance", 1, 5),
29444
29852
  urgency: parseBoundedInteger(options.urgency, "urgency", 1, 5),
@@ -29519,13 +29927,13 @@ databaseCommand.command("restore").description(t2("\u81EA\u52A8\u5907\u4EFD\u5F5
29519
29927
  }, (error) => renderDatabaseError(error, { forceJson: options.json })));
29520
29928
  program2.addCommand(databaseCommand);
29521
29929
  program2.command("init").description(t2("\u521D\u59CB\u5316 SuperTask\uFF08\u521B\u5EFA\u914D\u7F6E\u5E76\u6267\u884C\u8FC1\u79FB\uFF09", "initialize SuperTask (create config and run migrations)")).action(async () => withDb(async () => {
29522
- const { existsSync: existsSync9, mkdirSync: mkdirSync6, writeFileSync: writeFileSync4 } = await import("fs");
29523
- const { dirname: dirname10 } = await import("path");
29930
+ const { existsSync: existsSync10, mkdirSync: mkdirSync6, writeFileSync: writeFileSync4 } = await import("fs");
29931
+ const { dirname: dirname11 } = await import("path");
29524
29932
  const { getConfigPath: getConfigPath2 } = await Promise.resolve().then(() => (init_config(), config_exports));
29525
29933
  const configPath = getConfigPath2();
29526
- if (!existsSync9(configPath)) {
29527
- const dir = dirname10(configPath);
29528
- if (!existsSync9(dir)) mkdirSync6(dir, { recursive: true });
29934
+ if (!existsSync10(configPath)) {
29935
+ const dir = dirname11(configPath);
29936
+ if (!existsSync10(dir)) mkdirSync6(dir, { recursive: true });
29529
29937
  writeFileSync4(configPath, JSON.stringify({
29530
29938
  configVersion: 2,
29531
29939
  worker: { maxConcurrency: 2 },
@@ -29565,7 +29973,7 @@ program2.command("config").description(t2("\u663E\u793A\u5F53\u524D\u914D\u7F6E"
29565
29973
  const cfg = loadConfig2();
29566
29974
  console.log(JSON.stringify(cfg, null, 2));
29567
29975
  });
29568
- program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u636E\u5E93\u3001Gateway\u3001Web \u754C\u9762\u548C\u65E5\u5FD7\u8F6E\u8F6C", "diagnose OpenCode, database, Gateway, Dashboard, and log rotation")).option("--json", t2("\u5F3A\u5236\u8F93\u51FA JSON", "force JSON output")).option("--smoke", t2("\u901A\u8FC7 Gateway \u63D0\u4EA4\u4E00\u4E2A\u771F\u5B9E OpenCode \u4EFB\u52A1\u5E76\u9A8C\u8BC1\u8F93\u51FA", "queue a real OpenCode task through Gateway and verify its output")).option("--smoke-agent <agent>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u4F7F\u7528\u7684 Agent", "Agent used by the real smoke task"), "build").option("--smoke-model <model>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u4F7F\u7528\u7684\u6A21\u578B\uFF1B\u9ED8\u8BA4\u8DDF\u968F Agent \u914D\u7F6E", "model used by the real smoke task; defaults to the Agent configuration")).option("--smoke-cwd <path>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u7684\u9879\u76EE\u76EE\u5F55\uFF1B\u9ED8\u8BA4\u4E3A\u5F53\u524D\u76EE\u5F55", "project directory for the real smoke task; defaults to the current directory")).option("--smoke-timeout <duration>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u7B49\u5F85\u4E0A\u9650\uFF0C\u5982 2min / 5min", "real smoke task timeout, e.g. 2min / 5min"), "3min").action(async (options) => withDb(async () => {
29976
+ program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u636E\u5E93\u3001Gateway\u3001Web \u754C\u9762\u548C\u65E5\u5FD7\u8F6E\u8F6C", "diagnose OpenCode, database, Gateway, Dashboard, and log rotation")).option("--json", t2("\u5F3A\u5236\u8F93\u51FA JSON", "force JSON output")).option("--smoke", t2("\u901A\u8FC7 Gateway \u63D0\u4EA4\u4E00\u4E2A\u771F\u5B9E OpenCode \u4EFB\u52A1\u5E76\u9A8C\u8BC1\u8F93\u51FA", "queue a real OpenCode task through Gateway and verify its output")).option("--smoke-agent <agent>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u4F7F\u7528\u7684 Agent", "Agent used by the real smoke task"), "build").option("--smoke-model <model>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u4F7F\u7528\u7684\u6A21\u578B\uFF1B\u9ED8\u8BA4\u8DDF\u968F Agent \u914D\u7F6E", "model used by the real smoke task; defaults to the Agent configuration")).option("--smoke-variant <variant>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u4F7F\u7528\u7684\u6A21\u578B variant", "model variant used by the real smoke task")).option("--smoke-cwd <path>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u7684\u9879\u76EE\u76EE\u5F55\uFF1B\u9ED8\u8BA4\u4E3A\u5F53\u524D\u76EE\u5F55", "project directory for the real smoke task; defaults to the current directory")).option("--smoke-timeout <duration>", t2("\u771F\u5B9E\u5192\u70DF\u4EFB\u52A1\u7B49\u5F85\u4E0A\u9650\uFF0C\u5982 2min / 5min", "real smoke task timeout, e.g. 2min / 5min"), "3min").action(async (options) => withDb(async () => {
29569
29977
  const config = loadConfig();
29570
29978
  const database = DatabaseMaintenanceService.check();
29571
29979
  const legacyQuarantinedRuns = await TaskRunService.listLegacyQuarantinedRuns(
@@ -29678,6 +30086,7 @@ program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u63
29678
30086
  smoke = await runDoctorSmoke({
29679
30087
  agent: options.smokeAgent,
29680
30088
  model: options.smokeModel,
30089
+ variant: options.smokeVariant,
29681
30090
  cwd: options.smokeCwd ?? process.cwd(),
29682
30091
  timeoutMs: smokeTimeoutMs
29683
30092
  });