opencode-supertask 0.1.38 → 0.1.40

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
@@ -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,
@@ -20106,12 +20138,14 @@ var init_task_template_service = __esm({
20106
20138
  init_cron_parser();
20107
20139
  init_task_working_directory();
20108
20140
  init_task_batch();
20141
+ init_model_variant();
20109
20142
  ({ taskTemplates: taskTemplates2 } = schema_exports);
20110
20143
  TaskTemplateService = class {
20111
20144
  static async create(data) {
20112
20145
  const normalizedData = {
20113
20146
  ...data,
20114
- batchId: normalizeTaskBatchId(data.batchId)
20147
+ batchId: normalizeTaskBatchId(data.batchId),
20148
+ variant: normalizeModelVariant(data.variant)
20115
20149
  };
20116
20150
  this.validate(normalizedData);
20117
20151
  const now = Date.now();
@@ -20177,7 +20211,8 @@ var init_task_template_service = __esm({
20177
20211
  static async update(id, data) {
20178
20212
  const normalizedData = {
20179
20213
  ...data,
20180
- batchId: normalizeTaskBatchId(data.batchId) ?? null
20214
+ batchId: normalizeTaskBatchId(data.batchId) ?? null,
20215
+ ...data.variant === void 0 ? {} : { variant: normalizeModelVariant(data.variant) ?? null }
20181
20216
  };
20182
20217
  this.validate(normalizedData);
20183
20218
  const now = Date.now();
@@ -22220,9 +22255,11 @@ var init_semver = __esm({
22220
22255
  var update_exports = {};
22221
22256
  __export(update_exports, {
22222
22257
  getGlobalCliDiagnostic: () => getGlobalCliDiagnostic,
22258
+ getLatestVersion: () => getLatestVersion,
22223
22259
  getOpenCodePluginDiagnostic: () => getOpenCodePluginDiagnostic,
22224
22260
  installLatestPlugin: () => installLatestPlugin,
22225
22261
  installPluginVersion: () => installPluginVersion,
22262
+ isVersionConverged: () => isVersionConverged,
22226
22263
  resolveConfiguredPluginSpec: () => resolveConfiguredPluginSpec,
22227
22264
  resolveInstalledPlugin: () => resolveInstalledPlugin,
22228
22265
  resolveInstalledPluginVersion: () => resolveInstalledPluginVersion,
@@ -22242,6 +22279,9 @@ import {
22242
22279
  } from "fs";
22243
22280
  import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
22244
22281
  import { dirname as dirname7, join as join5, resolve as resolve4 } from "path";
22282
+ function isVersionConverged(version2, state2) {
22283
+ return state2.packageVersion === version2 && state2.plugin.ok && state2.plugin.version === version2 && state2.plugin.cachedVersion === version2 && (!state2.cli.installed || state2.cli.version === version2) && state2.gateway.pm2Installed && state2.gateway.processFound && state2.gateway.status === "online" && state2.gateway.ready && state2.gateway.runningVersion === version2 && state2.gateway.gatewayPackageVersion === version2 && state2.gateway.scopeMatches;
22284
+ }
22245
22285
  function pluginAt(packageDir) {
22246
22286
  const packageJson = join5(packageDir, "package.json");
22247
22287
  const gatewayEntry = join5(packageDir, "dist/gateway/index.js");
@@ -22409,7 +22449,7 @@ function updateGlobalCli(version2) {
22409
22449
  }
22410
22450
  return { ...after, action: "updated" };
22411
22451
  }
22412
- function latestVersion() {
22452
+ function getLatestVersion() {
22413
22453
  const result = spawnSync3(npmBin(), [
22414
22454
  "view",
22415
22455
  PACKAGE_NAME,
@@ -22583,7 +22623,7 @@ function installPluginVersion(version2) {
22583
22623
  return resolveInstalledPluginVersion(version2);
22584
22624
  }
22585
22625
  function installLatestPlugin() {
22586
- return installPluginVersion(latestVersion());
22626
+ return installPluginVersion(getLatestVersion());
22587
22627
  }
22588
22628
  var PACKAGE_NAME;
22589
22629
  var init_update2 = __esm({
@@ -22875,6 +22915,7 @@ var init_worker = __esm({
22875
22915
  const run = await TaskRunService.create({
22876
22916
  taskId: task.id,
22877
22917
  model: this.resolveModel(task.model),
22918
+ variant: this.resolveVariant(task.variant),
22878
22919
  status: "running",
22879
22920
  workerPid: process.pid,
22880
22921
  lockedAt: Date.now(),
@@ -22937,8 +22978,10 @@ var init_worker = __esm({
22937
22978
  }
22938
22979
  async spawnTask(task, runId, launchIdentity) {
22939
22980
  const model = this.resolveModel(task.model);
22981
+ const variant = this.resolveVariant(task.variant);
22940
22982
  const args = ["run", "--agent", task.agent, "--format", "json"];
22941
22983
  if (model) args.push("-m", model);
22984
+ if (variant) args.push("--variant", variant);
22942
22985
  args.push(task.prompt);
22943
22986
  const cwd = task.cwd || process.cwd();
22944
22987
  const child = spawn(process.execPath, [
@@ -23097,7 +23140,7 @@ var init_worker = __esm({
23097
23140
  );
23098
23141
  return;
23099
23142
  }
23100
- 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`;
23143
+ 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`;
23101
23144
  this.runDetached(
23102
23145
  this.settleEntry(entry, code, failure),
23103
23146
  "task settlement failed",
@@ -23286,6 +23329,9 @@ ${output}` : ""}`;
23286
23329
  if (!taskModel || taskModel === "default") return null;
23287
23330
  return taskModel;
23288
23331
  }
23332
+ resolveVariant(taskVariant) {
23333
+ return taskVariant?.trim() || null;
23334
+ }
23289
23335
  runDetached(operation, message, taskId) {
23290
23336
  operation.catch((error) => {
23291
23337
  markGatewayFailure("worker", error);
@@ -23663,6 +23709,7 @@ function createTaskFromTemplate(templateId, options) {
23663
23709
  name: `${options.namePrefix ?? ""}${tmpl.name}`,
23664
23710
  agent: tmpl.agent,
23665
23711
  model: tmpl.model ?? "default",
23712
+ variant: tmpl.variant,
23666
23713
  prompt: tmpl.prompt,
23667
23714
  cwd: tmpl.cwd ?? null,
23668
23715
  category: tmpl.category ?? "general",
@@ -26068,17 +26115,49 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
26068
26115
  const child = spawn2(executable, args, {
26069
26116
  cwd,
26070
26117
  env: process.env,
26071
- stdio: ["ignore", "pipe", "pipe"]
26118
+ stdio: ["ignore", "pipe", "pipe"],
26119
+ detached: process.platform !== "win32"
26072
26120
  });
26073
26121
  let stdout = "";
26074
26122
  let stderr = "";
26075
26123
  let failure = null;
26076
- let finished = false;
26124
+ let settled = false;
26125
+ let forceKillTimer = null;
26126
+ let finalRejectTimer = null;
26127
+ let timer;
26128
+ const signalProcessTree = (signal) => {
26129
+ if (process.platform !== "win32" && child.pid) {
26130
+ try {
26131
+ process.kill(-child.pid, signal);
26132
+ return;
26133
+ } catch {
26134
+ }
26135
+ }
26136
+ child.kill(signal);
26137
+ };
26138
+ const rejectOnce = (error) => {
26139
+ failure ??= error;
26140
+ if (settled) return;
26141
+ settled = true;
26142
+ clearTimeout(timer);
26143
+ if (forceKillTimer) clearTimeout(forceKillTimer);
26144
+ if (finalRejectTimer) clearTimeout(finalRejectTimer);
26145
+ reject(failure);
26146
+ };
26147
+ const terminate = (error) => {
26148
+ if (failure) return;
26149
+ failure = error;
26150
+ signalProcessTree("SIGTERM");
26151
+ forceKillTimer = setTimeout(() => {
26152
+ signalProcessTree("SIGKILL");
26153
+ rejectOnce(error);
26154
+ }, 1e3);
26155
+ finalRejectTimer = setTimeout(() => rejectOnce(error), 2e3);
26156
+ };
26077
26157
  const append = (current, chunk) => {
26078
26158
  const next = current + chunk.toString();
26079
26159
  if (Buffer.byteLength(next) > MAX_OUTPUT_BYTES && failure === null) {
26080
- failure = new Error(`OpenCode \u8F93\u51FA\u8D85\u8FC7 ${MAX_OUTPUT_BYTES} bytes`);
26081
- child.kill("SIGTERM");
26160
+ terminate(new Error(`OpenCode \u8F93\u51FA\u8D85\u8FC7 ${MAX_OUTPUT_BYTES} bytes`));
26082
26161
  }
26083
26162
  return next.slice(-MAX_OUTPUT_BYTES);
26084
26163
  };
@@ -26089,23 +26168,25 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
26089
26168
  stderr = append(stderr, chunk);
26090
26169
  });
26091
26170
  child.once("error", (error) => {
26092
- failure ??= error;
26171
+ if (forceKillTimer) return;
26172
+ rejectOnce(error);
26093
26173
  });
26094
- const timer = setTimeout(() => {
26095
- failure ??= new Error(`OpenCode \u547D\u4EE4\u8D85\u8FC7 ${timeoutMs}ms \u672A\u5B8C\u6210`);
26096
- child.kill("SIGTERM");
26174
+ timer = setTimeout(() => {
26175
+ terminate(new Error(`OpenCode \u547D\u4EE4\u8D85\u8FC7 ${timeoutMs}ms \u672A\u5B8C\u6210`));
26097
26176
  }, timeoutMs);
26098
26177
  child.once("close", (code) => {
26099
- if (finished) return;
26100
- finished = true;
26101
26178
  clearTimeout(timer);
26179
+ if (forceKillTimer) return;
26180
+ if (finalRejectTimer) clearTimeout(finalRejectTimer);
26181
+ if (settled) return;
26182
+ settled = true;
26102
26183
  if (failure) {
26103
26184
  reject(failure);
26104
26185
  return;
26105
26186
  }
26106
26187
  if (code !== 0) {
26107
26188
  const detail = cleanOutput(stderr).trim() || `\u9000\u51FA\u7801 ${code ?? "null"}`;
26108
- reject(new Error(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
26189
+ reject(new OpenCodeCommandExitError(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
26109
26190
  return;
26110
26191
  }
26111
26192
  resolve6(cleanOutput(stdout));
@@ -26115,6 +26196,52 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
26115
26196
  function parseOpenCodeModels(output) {
26116
26197
  return [...new Set(cleanOutput(output).split("\n").map((line) => line.trim()).filter((line) => /^[^\s/]+\/.+/.test(line)))].sort((left, right) => left.localeCompare(right));
26117
26198
  }
26199
+ function isRecord(value) {
26200
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26201
+ }
26202
+ function findJsonObjectEnd(value, start) {
26203
+ let depth = 0;
26204
+ let inString = false;
26205
+ let escaped = false;
26206
+ for (let index2 = start; index2 < value.length; index2 += 1) {
26207
+ const character = value[index2];
26208
+ if (inString) {
26209
+ if (escaped) escaped = false;
26210
+ else if (character === "\\") escaped = true;
26211
+ else if (character === '"') inString = false;
26212
+ continue;
26213
+ }
26214
+ if (character === '"') inString = true;
26215
+ else if (character === "{") depth += 1;
26216
+ else if (character === "}" && --depth === 0) return index2 + 1;
26217
+ }
26218
+ return null;
26219
+ }
26220
+ function parseOpenCodeModelMetadata(output) {
26221
+ const cleaned = cleanOutput(output);
26222
+ const models = /* @__PURE__ */ new Set();
26223
+ const variantsByModel = {};
26224
+ const headingPattern = /^([^\s/]+\/[^\r\n]+)\r?\n\s*(?=\{)/gm;
26225
+ for (const match2 of cleaned.matchAll(headingPattern)) {
26226
+ const model = match2[1].trim();
26227
+ models.add(model);
26228
+ let objectStart = (match2.index ?? 0) + match2[0].length;
26229
+ while (/\s/.test(cleaned[objectStart] ?? "")) objectStart += 1;
26230
+ if (cleaned[objectStart] !== "{") continue;
26231
+ const objectEnd = findJsonObjectEnd(cleaned, objectStart);
26232
+ if (objectEnd === null) continue;
26233
+ try {
26234
+ const metadata = JSON.parse(cleaned.slice(objectStart, objectEnd));
26235
+ if (!isRecord(metadata) || !isRecord(metadata.variants)) continue;
26236
+ variantsByModel[model] = Object.keys(metadata.variants).filter((variant) => variant.trim().length > 0).sort((left, right) => left.localeCompare(right));
26237
+ } catch {
26238
+ }
26239
+ }
26240
+ return {
26241
+ models: [...models].sort((left, right) => left.localeCompare(right)),
26242
+ variantsByModel
26243
+ };
26244
+ }
26118
26245
  function parseOpenCodeAgents(output) {
26119
26246
  const agents = /* @__PURE__ */ new Map();
26120
26247
  for (const line of cleanOutput(output).split("\n")) {
@@ -26138,14 +26265,18 @@ async function loadOpenCodeCatalog(cwd, options = {}) {
26138
26265
  return cached.result;
26139
26266
  }
26140
26267
  const result = Promise.all([
26141
- runOpenCode(executable, ["models"], cwd, timeoutMs),
26268
+ runOpenCode(executable, ["models", "--verbose"], cwd, timeoutMs).catch((error) => {
26269
+ if (!(error instanceof OpenCodeCommandExitError)) throw error;
26270
+ return runOpenCode(executable, ["models"], cwd, timeoutMs);
26271
+ }),
26142
26272
  runOpenCode(executable, ["agent", "list"], cwd, timeoutMs)
26143
26273
  ]).then(([modelsOutput, agentsOutput]) => {
26144
- const models = parseOpenCodeModels(modelsOutput);
26274
+ const metadata = parseOpenCodeModelMetadata(modelsOutput);
26275
+ const models = metadata.models.length > 0 ? metadata.models : parseOpenCodeModels(modelsOutput);
26145
26276
  const agents = parseOpenCodeAgents(agentsOutput).filter((agent) => agent.mode !== "subagent");
26146
26277
  if (models.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u7528\u6A21\u578B");
26147
26278
  if (agents.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u76F4\u63A5\u8FD0\u884C\u7684\u4E3B Agent");
26148
- return { cwd, models, agents };
26279
+ return { cwd, models, variantsByModel: metadata.variantsByModel, agents };
26149
26280
  });
26150
26281
  if (options.useCache !== false) {
26151
26282
  catalogCache.set(cacheKey, { expiresAt: Date.now() + CATALOG_CACHE_MS, result });
@@ -26155,7 +26286,7 @@ async function loadOpenCodeCatalog(cwd, options = {}) {
26155
26286
  }
26156
26287
  return result;
26157
26288
  }
26158
- var CATALOG_CACHE_MS, COMMAND_TIMEOUT_MS, MAX_OUTPUT_BYTES, ANSI_PATTERN, catalogCache;
26289
+ var CATALOG_CACHE_MS, COMMAND_TIMEOUT_MS, MAX_OUTPUT_BYTES, ANSI_PATTERN, OpenCodeCommandExitError, catalogCache;
26159
26290
  var init_opencode_catalog = __esm({
26160
26291
  "src/core/opencode-catalog.ts"() {
26161
26292
  "use strict";
@@ -26164,6 +26295,8 @@ var init_opencode_catalog = __esm({
26164
26295
  COMMAND_TIMEOUT_MS = 2e4;
26165
26296
  MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
26166
26297
  ANSI_PATTERN = /\x1B(?:[@-_][0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
26298
+ OpenCodeCommandExitError = class extends Error {
26299
+ };
26167
26300
  catalogCache = /* @__PURE__ */ new Map();
26168
26301
  }
26169
26302
  });
@@ -26254,6 +26387,7 @@ function clientMessages(locale) {
26254
26387
  "details.copySuccess",
26255
26388
  "details.id",
26256
26389
  "details.project",
26390
+ "details.variant",
26257
26391
  "details.prompt",
26258
26392
  "details.result",
26259
26393
  "details.category",
@@ -26354,6 +26488,7 @@ function clientMessages(locale) {
26354
26488
  "catalog.chooseProject",
26355
26489
  "catalog.defaultModel",
26356
26490
  "catalog.defaultProvider",
26491
+ "catalog.defaultVariant",
26357
26492
  "catalog.loading",
26358
26493
  "catalog.loaded",
26359
26494
  "catalog.failed",
@@ -26478,23 +26613,23 @@ function renderLayout(options) {
26478
26613
  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)}
26479
26614
  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')}
26480
26615
  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}
26481
- 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}
26616
+ 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}
26482
26617
  function detailFields(type,data){if(type==='task')return [
26483
26618
  [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}],
26484
- [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
26619
+ [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}],
26485
26620
  [text('details.category'),data.category],[text('details.batch'),data.batchId],[text('details.dependency'),data.dependsOn?'#'+data.dependsOn:text('details.none')],
26486
26621
  [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.retryCount'),String(data.retryCount??0)+' / '+String(data.maxRetries??0)],
26487
26622
  [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.scheduledAt'),detailDate(data.scheduledAt)],
26488
26623
  [text('details.createdAt'),detailDate(data.createdAt)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
26489
26624
  [text('details.result'),detailTaskResult(data),{wide:true,long:true}]
26490
26625
  ];if(type==='run'){const started=data.startedAt?new Date(data.startedAt).getTime():null;const finished=data.finishedAt?new Date(data.finishedAt).getTime():null;return [
26491
- [text('details.id'),'Run #'+data.id],[text('details.taskId'),'#'+data.taskId],[text('table.status'),detailStatus('run',data.status)],[text('table.model'),detailModel(data.model)],
26626
+ [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')],
26492
26627
  [text('details.session'),detailSession(data.sessionId)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
26493
26628
  [text('table.duration'),started!==null?detailDuration((finished??Date.now())-started):text('details.none')],[text('details.heartbeat'),detailDate(data.heartbeatAt)],
26494
26629
  [text('details.process'),'Worker PID '+String(data.workerPid??'\u2014')+' \xB7 OpenCode PID '+String(data.childPid??'\u2014'),{wide:true,mono:true}]
26495
26630
  ];}const scheduleRule=data.scheduleType==='cron'?data.cronExpr:data.scheduleType==='recurring'?detailDuration(data.intervalMs):detailDate(data.runAt);return [
26496
26631
  [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}],
26497
- [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
26632
+ [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}],
26498
26633
  [text('template.scheduleType'),detailScheduleType(data.scheduleType)],[text('details.scheduleRule'),scheduleRule],[text('details.category'),data.category],[text('details.batch'),data.batchId],
26499
26634
  [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.maxInstances'),data.maxInstances],[text('details.maxRetries'),data.maxRetries??0],
26500
26635
  [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.lastRun'),detailDate(data.lastRunAt)],[text('details.nextRun'),detailDate(data.nextRunAt)],
@@ -26511,12 +26646,20 @@ function renderLayout(options) {
26511
26646
  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')}
26512
26647
  const catalogTimers={};const catalogRequests={};const catalogModels={};
26513
26648
  function catalogField(prefix,name){return document.getElementById(prefix+'-'+name)}
26514
- 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='';}
26649
+ 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='';}
26515
26650
  function appendCurrentOption(select,value){if(!value||[...select.options].some(option=>option.value===value))return;select.appendChild(new Option(value,value));}
26651
+ 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}
26652
+ function invalidateVariantRequest(prefix){const key=prefix+'-variant';catalogRequests[key]=(catalogRequests[key]||0)+1}
26653
+ function invalidateCatalogRequests(prefix){clearTimeout(catalogTimers[prefix]);catalogRequests[prefix]=(catalogRequests[prefix]||0)+1;invalidateVariantRequest(prefix)}
26654
+ 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}
26655
+ function handleProviderChange(prefix){clearVariantPreference(prefix);populateModelOptions(prefix)}
26656
+ function handleModelChange(prefix){clearVariantPreference(prefix);populateVariantOptions(prefix)}
26657
+ function handleVariantChange(prefix){const variant=catalogField(prefix,'variant');if(!variant)return;invalidateVariantRequest(prefix);variant.dataset.preferred='';variant.dataset.preferredModel=''}
26516
26658
  function modelProvider(value){const slash=value.indexOf('/');return slash>0?value.slice(0,slash):''}
26517
- 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}
26659
+ 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)}
26660
+ 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}}
26518
26661
  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}}}
26519
- function scheduleCatalogLoad(prefix){clearTimeout(catalogTimers[prefix]);catalogTimers[prefix]=setTimeout(()=>loadCatalog(prefix),450)}
26662
+ function scheduleCatalogLoad(prefix){invalidateCatalogRequests(prefix);resetCatalog(prefix);catalogTimers[prefix]=setTimeout(()=>loadCatalog(prefix),450)}
26520
26663
  let directoryTargetId='';let directoryCurrent='';let directoryEntries=[];let directoryShowHidden=false;
26521
26664
  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)}}
26522
26665
  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}}
@@ -26526,16 +26669,16 @@ function renderLayout(options) {
26526
26669
  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'}
26527
26670
  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}
26528
26671
  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)}
26529
- 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)}
26530
- 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')}}
26531
- 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}}
26672
+ 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)}
26673
+ 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')}}
26674
+ 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}}
26532
26675
  function localDateTime(milliseconds){const date=new Date(milliseconds);const local=new Date(milliseconds-date.getTimezoneOffset()*60000);return local.toISOString().slice(0,23)}
26533
26676
  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')}}
26534
26677
  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}
26535
26678
  function selectedRunAt(){const input=templateField('run-at');return resolveEditedRunAt(input.dataset.originalEpoch?Number(input.dataset.originalEpoch):null,input.dataset.originalLocal||'',input.value)}
26536
- 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)}
26537
- 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')}}
26538
- 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}}
26679
+ 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)}
26680
+ 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')}}
26681
+ 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}}
26539
26682
  async function enableTmpl(id){try{await readJson(await fetch('/api/templates/'+id+'/enable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
26540
26683
  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')}}
26541
26684
  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')}}
@@ -26717,6 +26860,7 @@ var init_ui = __esm({
26717
26860
  "template.cwdHint": "OpenCode \u4F1A\u5728\u8FD9\u4E2A\u76EE\u5F55\u4E2D\u6267\u884C\u4EFB\u52A1\u3002",
26718
26861
  "template.agent": "Agent",
26719
26862
  "template.model": "\u6A21\u578B",
26863
+ "template.variant": "\u6A21\u578B Variant",
26720
26864
  "template.prompt": "\u63D0\u793A\u8BCD",
26721
26865
  "template.scheduleType": "\u6267\u884C\u65B9\u5F0F",
26722
26866
  "template.cronExpr": "Cron \u8868\u8FBE\u5F0F",
@@ -26754,9 +26898,11 @@ var init_ui = __esm({
26754
26898
  "catalog.chooseProject": "\u8BF7\u5148\u9009\u62E9\u9879\u76EE\u76EE\u5F55",
26755
26899
  "catalog.defaultModel": "\u8DDF\u968F Agent / OpenCode \u9ED8\u8BA4\u6A21\u578B",
26756
26900
  "catalog.defaultProvider": "\u9ED8\u8BA4\u6A21\u578B",
26901
+ "catalog.defaultVariant": "\u8DDF\u968F Agent / \u6A21\u578B\u9ED8\u8BA4\u8BBE\u7F6E",
26757
26902
  "catalog.provider": "\u6A21\u578B\u63D0\u4F9B\u5546",
26758
26903
  "catalog.model": "\u5177\u4F53\u6A21\u578B",
26759
- "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",
26904
+ "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",
26905
+ "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",
26760
26906
  "catalog.agentHint": "\u6765\u81EA\u5F53\u524D\u9879\u76EE\u7684 opencode agent list\u3002",
26761
26907
  "catalog.loading": "\u6B63\u5728\u8BFB\u53D6\u6B64\u9879\u76EE\u53EF\u7528\u7684 Agent \u548C\u6A21\u578B\u2026",
26762
26908
  "catalog.loaded": "\u5DF2\u4ECE\u672C\u673A OpenCode \u8BFB\u53D6 {agents} \u4E2A Agent\u3001{models} \u4E2A\u6A21\u578B\u3002",
@@ -26787,6 +26933,7 @@ var init_ui = __esm({
26787
26933
  "details.copySuccess": "\u539F\u59CB\u6570\u636E\u5DF2\u590D\u5236",
26788
26934
  "details.id": "\u7F16\u53F7",
26789
26935
  "details.project": "\u9879\u76EE\u76EE\u5F55",
26936
+ "details.variant": "\u6A21\u578B Variant",
26790
26937
  "details.prompt": "\u63D0\u793A\u8BCD",
26791
26938
  "details.result": "\u6267\u884C\u7ED3\u679C / \u5931\u8D25\u539F\u56E0",
26792
26939
  "details.category": "\u5206\u7C7B",
@@ -27018,6 +27165,7 @@ var init_ui = __esm({
27018
27165
  "template.cwdHint": "OpenCode runs the task in this directory.",
27019
27166
  "template.agent": "Agent",
27020
27167
  "template.model": "Model",
27168
+ "template.variant": "Model variant",
27021
27169
  "template.prompt": "Prompt",
27022
27170
  "template.scheduleType": "Schedule",
27023
27171
  "template.cronExpr": "Cron expression",
@@ -27055,9 +27203,11 @@ var init_ui = __esm({
27055
27203
  "catalog.chooseProject": "Choose a project directory first",
27056
27204
  "catalog.defaultModel": "Use the Agent / OpenCode default model",
27057
27205
  "catalog.defaultProvider": "Default model",
27206
+ "catalog.defaultVariant": "Use the Agent / model default",
27058
27207
  "catalog.provider": "Model provider",
27059
27208
  "catalog.model": "Model",
27060
- "catalog.modelHint": "Choose a provider, then a model returned by opencode models for this project. Default does not pass -m.",
27209
+ "catalog.modelHint": "Choose a provider, then a model returned by opencode models --verbose for this project. Default does not pass -m.",
27210
+ "catalog.variantHint": "Only variants declared by the selected model are shown. Default does not pass --variant.",
27061
27211
  "catalog.agentHint": "Loaded from opencode agent list for this project.",
27062
27212
  "catalog.loading": "Loading Agents and models available to this project\u2026",
27063
27213
  "catalog.loaded": "Loaded {agents} Agents and {models} models from local OpenCode.",
@@ -27088,6 +27238,7 @@ var init_ui = __esm({
27088
27238
  "details.copySuccess": "Raw data copied",
27089
27239
  "details.id": "ID",
27090
27240
  "details.project": "Project directory",
27241
+ "details.variant": "Model variant",
27091
27242
  "details.prompt": "Prompt",
27092
27243
  "details.result": "Result / failure reason",
27093
27244
  "details.category": "Category",
@@ -27650,6 +27801,7 @@ function parseTaskPayload(value) {
27650
27801
  cwd: requiredString("cwd"),
27651
27802
  agent: requiredString("agent"),
27652
27803
  model: optionalString("model", "default"),
27804
+ variant: Object.hasOwn(input, "variant") ? optionalString("variant") : void 0,
27653
27805
  prompt: requiredString("prompt"),
27654
27806
  category: optionalString("category", "general"),
27655
27807
  batchId: optionalString("batchId"),
@@ -27665,6 +27817,7 @@ function editableTaskPayload(input) {
27665
27817
  name: input.name,
27666
27818
  agent: input.agent,
27667
27819
  model: input.model ?? "default",
27820
+ ...input.variant === void 0 ? {} : { variant: input.variant },
27668
27821
  prompt: input.prompt,
27669
27822
  category: input.category ?? "general",
27670
27823
  batchId: input.batchId ?? null,
@@ -27720,6 +27873,7 @@ function parseTemplatePayload(value) {
27720
27873
  name: requiredString("name"),
27721
27874
  agent: requiredString("agent"),
27722
27875
  model: optionalString("model", "default"),
27876
+ variant: Object.hasOwn(input, "variant") ? optionalString("variant") : void 0,
27723
27877
  prompt: requiredString("prompt"),
27724
27878
  cwd: requiredString("cwd"),
27725
27879
  category: optionalString("category", "general"),
@@ -28053,7 +28207,7 @@ var init_web = __esm({
28053
28207
  const latestRun = latestRuns.get(task.id);
28054
28208
  const executionActive = latestRun?.status === "running";
28055
28209
  const batchId = task.batchId?.trim() || null;
28056
- const searchable = esc(`${task.name} ${task.agent} ${task.prompt} ${task.cwd ?? ""} ${task.batchId ?? ""} ${task.category ?? ""}`);
28210
+ const searchable = esc(`${task.name} ${task.agent} ${task.model ?? ""} ${task.variant ?? ""} ${task.prompt} ${task.cwd ?? ""} ${task.batchId ?? ""} ${task.category ?? ""}`);
28057
28211
  return `<tr data-task-row data-search="${searchable}">
28058
28212
  <td class="faint" data-label="${t(locale, "table.id")}">#${task.id}</td>
28059
28213
  <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>
@@ -28131,11 +28285,12 @@ var init_web = __esm({
28131
28285
  <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>
28132
28286
  <datalist id="task-cwd-options">${projects.map((project) => `<option value="${esc(project.cwd)}"></option>`).join("")}</datalist>
28133
28287
  <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>
28134
- <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>
28288
+ <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>
28289
+ <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>
28135
28290
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="task-prompt" rows="6" required></textarea></label>
28136
28291
  </div>
28137
28292
  <p id="task-project-status" class="form-note"></p>
28138
- <p id="task-catalog-status" class="form-note catalog-status"></p>
28293
+ <p id="task-catalog-status" class="form-note catalog-status" role="status" aria-live="polite"></p>
28139
28294
  <details class="advanced-fields">
28140
28295
  <summary>${t(locale, "template.advanced")}</summary>
28141
28296
  <div class="template-form-grid">
@@ -28177,7 +28332,7 @@ var init_web = __esm({
28177
28332
  return `<tr>
28178
28333
  <td class="faint" data-label="${t(locale, "table.id")}">#${template.id}</td>
28179
28334
  <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>
28180
- <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>
28335
+ <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>
28181
28336
  <td data-label="${t(locale, "table.type")}"><span class="tag t-${scheduleType}">${typeLabel}</span></td>
28182
28337
  <td data-label="${t(locale, "table.rule")}" class="m small">${esc(rule)}</td>
28183
28338
  <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>
@@ -28207,14 +28362,15 @@ var init_web = __esm({
28207
28362
  <label class="form-field"><span>${t(locale, "template.name")}</span><input id="template-name" required maxlength="200" autocomplete="off"></label>
28208
28363
  <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>
28209
28364
  <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>
28210
- <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>
28365
+ <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>
28366
+ <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>
28211
28367
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="template-prompt" rows="6" required></textarea></label>
28212
28368
  <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>
28213
28369
  <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>
28214
28370
  <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>
28215
28371
  <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>
28216
28372
  </div>
28217
- <p id="template-catalog-status" class="form-note catalog-status"></p>
28373
+ <p id="template-catalog-status" class="form-note catalog-status" role="status" aria-live="polite"></p>
28218
28374
  <details class="advanced-fields">
28219
28375
  <summary>${t(locale, "template.advanced")}</summary>
28220
28376
  <div class="template-form-grid">
@@ -28248,6 +28404,7 @@ var init_web = __esm({
28248
28404
  taskId: taskRuns4.taskId,
28249
28405
  sessionId: taskRuns4.sessionId,
28250
28406
  model: taskRuns4.model,
28407
+ variant: taskRuns4.variant,
28251
28408
  status: taskRuns4.status,
28252
28409
  startedAt: taskRuns4.startedAt,
28253
28410
  finishedAt: taskRuns4.finishedAt,
@@ -28268,7 +28425,7 @@ var init_web = __esm({
28268
28425
  const log = run.log ? renderRunLog(run.id, run.taskName, run.log, locale, run.status !== "done") : "";
28269
28426
  return `<tr class="run-summary-row">
28270
28427
  <td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td>
28271
- <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>
28428
+ <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>
28272
28429
  <td data-label="${t(locale, "table.agent")}"><span class="tag">${esc(run.taskAgent)}</span></td>
28273
28430
  <td data-label="${t(locale, "table.session")}" class="m small">${esc(maskSessionId(run.sessionId))}</td>
28274
28431
  <td data-label="${t(locale, "table.status")}"><span class="badge b-${status}">${runStatusText(locale, run.status ?? "unknown")}</span></td>
@@ -28318,7 +28475,7 @@ var init_web = __esm({
28318
28475
  const runRows = runningRuns.map((run) => {
28319
28476
  const session = maskSessionId(run.sessionId);
28320
28477
  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>
28321
- <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>
28478
+ <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>
28322
28479
  <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>
28323
28480
  <td data-label="${t(locale, "table.duration")}" class="small">${formatDuration(run.startedAt, null)}</td></tr>`;
28324
28481
  }).join("");
@@ -29147,11 +29304,13 @@ async function runDoctorSmoke(options) {
29147
29304
  const cwd = resolve5(options.cwd);
29148
29305
  const marker = options.marker ?? `SUPERTASK_SMOKE_${randomUUID2().replaceAll("-", "").toUpperCase()}`;
29149
29306
  const model = options.model && options.model !== "default" ? options.model : void 0;
29307
+ const variant = options.variant?.trim() || void 0;
29150
29308
  const startedAt = Date.now();
29151
29309
  const task = await TaskService.add({
29152
29310
  name: "[doctor] Gateway real smoke test",
29153
29311
  agent: options.agent,
29154
29312
  model,
29313
+ variant,
29155
29314
  prompt: `\u4E0D\u8981\u8C03\u7528\u4EFB\u4F55\u5DE5\u5177\u3002\u53EA\u56DE\u590D\u8FD9\u4E00\u884C\uFF1A${marker}`,
29156
29315
  cwd,
29157
29316
  category: "diagnostic",
@@ -29175,6 +29334,7 @@ async function runDoctorSmoke(options) {
29175
29334
  status: "missing",
29176
29335
  agent: options.agent,
29177
29336
  model: model ?? null,
29337
+ variant: variant ?? null,
29178
29338
  cwd,
29179
29339
  durationMs: Date.now() - startedAt,
29180
29340
  error: "\u5192\u70DF\u4EFB\u52A1\u5728\u5B8C\u6210\u524D\u88AB\u5220\u9664"
@@ -29190,6 +29350,7 @@ async function runDoctorSmoke(options) {
29190
29350
  status: current2.status,
29191
29351
  agent: options.agent,
29192
29352
  model: model ?? null,
29353
+ variant: variant ?? null,
29193
29354
  cwd,
29194
29355
  durationMs: Date.now() - startedAt,
29195
29356
  error: markerObserved ? null : "OpenCode \u5DF2\u9000\u51FA\u6210\u529F\uFF0C\u4F46\u6A21\u578B\u6587\u672C\u4E0D\u662F\u9884\u671F\u7684\u7CBE\u786E\u6807\u8BB0"
@@ -29203,6 +29364,7 @@ async function runDoctorSmoke(options) {
29203
29364
  status: current2.status,
29204
29365
  agent: options.agent,
29205
29366
  model: model ?? null,
29367
+ variant: variant ?? null,
29206
29368
  cwd,
29207
29369
  durationMs: Date.now() - startedAt,
29208
29370
  error: tail(run2?.log ?? current2.resultLog) ?? `\u4EFB\u52A1\u8FDB\u5165 ${current2.status}`
@@ -29220,6 +29382,7 @@ async function runDoctorSmoke(options) {
29220
29382
  status: current?.status ?? "missing",
29221
29383
  agent: options.agent,
29222
29384
  model: model ?? null,
29385
+ variant: variant ?? null,
29223
29386
  cwd,
29224
29387
  durationMs: Date.now() - startedAt,
29225
29388
  error: `\u7B49\u5F85 Gateway \u6267\u884C\u8D85\u8FC7 ${options.timeoutMs}ms\uFF0C\u5192\u70DF\u4EFB\u52A1\u5DF2\u8BF7\u6C42\u53D6\u6D88`
@@ -29260,7 +29423,7 @@ if (cliLocale === "zh-CN") {
29260
29423
  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"))
29261
29424
  });
29262
29425
  }
29263
- 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 () => {
29426
+ 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 () => {
29264
29427
  const submitCwd = process.cwd();
29265
29428
  const retryBackoffMs = parseDuration(options.retryBackoff);
29266
29429
  const timeoutMs = options.timeout ? parseDuration(options.timeout) : null;
@@ -29272,6 +29435,7 @@ program2.command("add").description(t2("\u521B\u5EFA\u65B0\u4EFB\u52A1", "create
29272
29435
  agent: options.agent,
29273
29436
  prompt: options.prompt,
29274
29437
  model: options.model,
29438
+ variant: options.variant,
29275
29439
  category: options.category,
29276
29440
  importance: parseBoundedInteger(options.importance, "importance", 1, 5),
29277
29441
  urgency: parseBoundedInteger(options.urgency, "urgency", 1, 5),
@@ -29284,17 +29448,23 @@ program2.command("add").description(t2("\u521B\u5EFA\u65B0\u4EFB\u52A1", "create
29284
29448
  });
29285
29449
  console.log(JSON.stringify({ id: task.id, status: "created" }, null, 2));
29286
29450
  }));
29287
- 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 () => {
29451
+ 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 () => {
29288
29452
  if (options.batch !== void 0 && options.clearBatch) {
29289
29453
  throw new Error("batch \u548C clear-batch \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
29290
29454
  }
29291
29455
  if (options.timeout !== void 0 && options.clearTimeout) {
29292
29456
  throw new Error("timeout \u548C clear-timeout \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
29293
29457
  }
29458
+ if (options.variant !== void 0 && options.clearVariant) {
29459
+ throw new Error("variant \u548C clear-variant \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
29460
+ }
29294
29461
  const update = {};
29295
29462
  for (const field of ["name", "agent", "model", "prompt", "category"]) {
29296
29463
  if (options[field] !== void 0) update[field] = options[field];
29297
29464
  }
29465
+ if (options.variant !== void 0 || options.clearVariant) {
29466
+ update.variant = options.clearVariant ? null : options.variant;
29467
+ }
29298
29468
  if (options.importance !== void 0) {
29299
29469
  update.importance = parseBoundedInteger(options.importance, "importance", 1, 5);
29300
29470
  }
@@ -29330,6 +29500,7 @@ program2.command("next").description(t2("\u83B7\u53D6\u4E0B\u4E00\u4E2A\u5F85\u6
29330
29500
  name: task.name,
29331
29501
  agent: task.agent,
29332
29502
  model: task.model,
29503
+ variant: task.variant,
29333
29504
  prompt: task.prompt,
29334
29505
  cwd: task.cwd,
29335
29506
  category: task.category,
@@ -29406,7 +29577,7 @@ program2.command("delete").description(t2("\u5220\u9664\u4EFB\u52A1", "delete a
29406
29577
  console.log(JSON.stringify({ deleted, id }));
29407
29578
  }));
29408
29579
  program2.command("template").description(t2("\u7BA1\u7406\u5B9A\u65F6\u4EFB\u52A1\u6A21\u677F", "manage scheduled task templates")).addCommand(
29409
- 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 () => {
29580
+ 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 () => {
29410
29581
  let intervalMs = null;
29411
29582
  let runAt = null;
29412
29583
  const retryBackoffMs = parseDuration(options.retryBackoff);
@@ -29434,6 +29605,7 @@ program2.command("template").description(t2("\u7BA1\u7406\u5B9A\u65F6\u4EFB\u52A
29434
29605
  agent: options.agent,
29435
29606
  prompt: options.prompt,
29436
29607
  model: options.model,
29608
+ variant: options.variant,
29437
29609
  category: options.category,
29438
29610
  importance: parseBoundedInteger(options.importance, "importance", 1, 5),
29439
29611
  urgency: parseBoundedInteger(options.urgency, "urgency", 1, 5),
@@ -29560,7 +29732,7 @@ program2.command("config").description(t2("\u663E\u793A\u5F53\u524D\u914D\u7F6E"
29560
29732
  const cfg = loadConfig2();
29561
29733
  console.log(JSON.stringify(cfg, null, 2));
29562
29734
  });
29563
- 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 () => {
29735
+ 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 () => {
29564
29736
  const config = loadConfig();
29565
29737
  const database = DatabaseMaintenanceService.check();
29566
29738
  const legacyQuarantinedRuns = await TaskRunService.listLegacyQuarantinedRuns(
@@ -29673,6 +29845,7 @@ program2.command("doctor").description(t2("\u68C0\u67E5 OpenCode\u3001\u6570\u63
29673
29845
  smoke = await runDoctorSmoke({
29674
29846
  agent: options.smokeAgent,
29675
29847
  model: options.smokeModel,
29848
+ variant: options.smokeVariant,
29676
29849
  cwd: options.smokeCwd ?? process.cwd(),
29677
29850
  timeoutMs: smokeTimeoutMs
29678
29851
  });
@@ -29732,25 +29905,43 @@ program2.command("uninstall").description(t2("\u505C\u6B62\u5E76\u79FB\u9664 PM2
29732
29905
  process.exit(1);
29733
29906
  }
29734
29907
  });
29735
- program2.command("upgrade").description(t2("\u66F4\u65B0 OpenCode \u63D2\u4EF6\u7F13\u5B58\u5E76\u91CD\u542F Gateway", "update the OpenCode plugin cache and restart the Gateway")).action(async () => {
29736
- console.log(t2("\u6B63\u5728\u66F4\u65B0 opencode-supertask...", "Updating opencode-supertask..."));
29908
+ program2.command("upgrade").description(t2("\u66F4\u65B0 OpenCode \u63D2\u4EF6\u3001CLI \u548C Gateway\uFF1B\u5DF2\u662F\u6700\u65B0\u7248\u672C\u65F6\u4E0D\u91CD\u542F", "update the OpenCode plugin, CLI, and Gateway without restarting when already current")).option("--force", t2("\u5373\u4F7F\u5DF2\u662F\u6700\u65B0\u7248\u672C\u4E5F\u91CD\u65B0\u5B89\u88C5\u5E76\u91CD\u542F Gateway", "reinstall and restart the Gateway even when already current")).action(async (options) => {
29909
+ console.log(t2("\u6B63\u5728\u68C0\u67E5 opencode-supertask \u66F4\u65B0...", "Checking for opencode-supertask updates..."));
29737
29910
  let installed;
29738
29911
  let previousVersion;
29912
+ let targetVersion;
29913
+ let updater;
29739
29914
  try {
29740
- const { resolveInstalledPlugin: resolveInstalledPlugin2 } = await Promise.resolve().then(() => (init_update2(), update_exports));
29741
- previousVersion = resolveInstalledPlugin2().version;
29915
+ updater = await Promise.resolve().then(() => (init_update2(), update_exports));
29916
+ const { getGatewayDiagnostic: getGatewayDiagnostic2 } = await Promise.resolve().then(() => (init_pm2(), pm2_exports));
29917
+ targetVersion = updater.getLatestVersion();
29918
+ const plugin = updater.getOpenCodePluginDiagnostic();
29919
+ const cli = updater.getGlobalCliDiagnostic();
29920
+ const gateway = getGatewayDiagnostic2();
29921
+ if (!options.force && updater.isVersionConverged(targetVersion, {
29922
+ packageVersion: getPackageVersion(),
29923
+ plugin,
29924
+ cli,
29925
+ gateway
29926
+ })) {
29927
+ console.log(t2(
29928
+ `SuperTask \u5DF2\u662F\u6700\u65B0\u7248\u672C v${targetVersion}\uFF0C\u65E0\u9700\u5347\u7EA7\uFF1BGateway \u672A\u91CD\u542F\u3002`,
29929
+ `SuperTask is already up to date at v${targetVersion}; the Gateway was not restarted.`
29930
+ ));
29931
+ return;
29932
+ }
29933
+ previousVersion = plugin.version ?? updater.resolveInstalledPlugin().version;
29742
29934
  } catch (error) {
29743
- console.error(t2("\u65E0\u6CD5\u786E\u8BA4\u5F53\u524D OpenCode \u63D2\u4EF6\u7248\u672C\uFF0C\u5DF2\u53D6\u6D88\u5347\u7EA7\uFF1A", "Could not determine the current OpenCode plugin version; upgrade cancelled: ") + (error instanceof Error ? error.message : String(error)));
29935
+ console.error(t2("\u65E0\u6CD5\u68C0\u67E5\u5F53\u524D\u7248\u672C\uFF0C\u5DF2\u53D6\u6D88\u5347\u7EA7\uFF1A", "Could not check the current version; upgrade cancelled: ") + (error instanceof Error ? error.message : String(error)));
29744
29936
  process.exit(1);
29745
29937
  }
29938
+ console.log(t2("\u6B63\u5728\u66F4\u65B0 opencode-supertask...", "Updating opencode-supertask..."));
29746
29939
  try {
29747
- const { installLatestPlugin: installLatestPlugin2 } = await Promise.resolve().then(() => (init_update2(), update_exports));
29748
- installed = installLatestPlugin2();
29940
+ installed = updater.installPluginVersion(targetVersion);
29749
29941
  } catch (err) {
29750
29942
  let detail = err instanceof Error ? err.message : String(err);
29751
29943
  try {
29752
- const { installPluginVersion: installPluginVersion2 } = await Promise.resolve().then(() => (init_update2(), update_exports));
29753
- installPluginVersion2(previousVersion);
29944
+ updater.installPluginVersion(previousVersion);
29754
29945
  } catch (rollbackError) {
29755
29946
  detail += `; OpenCode \u63D2\u4EF6\u56DE\u6EDA\u5931\u8D25: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`;
29756
29947
  }
@@ -29794,8 +29985,7 @@ SuperTask upgraded: ${result.before ?? "unknown"} \u2192 ${result.after}`
29794
29985
  let detail = err instanceof Error ? err.message : String(err);
29795
29986
  try {
29796
29987
  if (previousVersion !== installed.version) {
29797
- const { installPluginVersion: installPluginVersion2 } = await Promise.resolve().then(() => (init_update2(), update_exports));
29798
- installPluginVersion2(previousVersion);
29988
+ updater.installPluginVersion(previousVersion);
29799
29989
  }
29800
29990
  } catch (rollbackError) {
29801
29991
  detail += `; Gateway \u5DF2\u56DE\u6EDA\uFF0C\u4F46 OpenCode \u63D2\u4EF6\u56DE\u6EDA\u5931\u8D25: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`;