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/web/index.js CHANGED
@@ -16395,6 +16395,7 @@ var tasks = sqliteTable("tasks", {
16395
16395
  name: text("name").notNull(),
16396
16396
  agent: text("agent").notNull(),
16397
16397
  model: text("model").default("default"),
16398
+ variant: text("variant"),
16398
16399
  prompt: text("prompt").notNull(),
16399
16400
  cwd: text("cwd"),
16400
16401
  // 分类与优先级
@@ -16439,6 +16440,7 @@ var taskRuns = sqliteTable("task_runs", {
16439
16440
  taskId: integer("task_id").notNull().references(() => tasks.id, { onDelete: "cascade" }),
16440
16441
  sessionId: text("session_id"),
16441
16442
  model: text("model"),
16443
+ variant: text("variant"),
16442
16444
  status: text("status").default("running"),
16443
16445
  startedAt: integer("started_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()),
16444
16446
  finishedAt: integer("finished_at", { mode: "timestamp" }),
@@ -16459,6 +16461,7 @@ var taskTemplates = sqliteTable("task_templates", {
16459
16461
  name: text("name").notNull(),
16460
16462
  agent: text("agent").notNull(),
16461
16463
  model: text("model").default("default"),
16464
+ variant: text("variant"),
16462
16465
  prompt: text("prompt").notNull(),
16463
16466
  cwd: text("cwd"),
16464
16467
  category: text("category").default("general"),
@@ -16657,6 +16660,8 @@ var CATALOG_CACHE_MS = 3e4;
16657
16660
  var COMMAND_TIMEOUT_MS = 2e4;
16658
16661
  var MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
16659
16662
  var ANSI_PATTERN = /\x1B(?:[@-_][0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
16663
+ var OpenCodeCommandExitError = class extends Error {
16664
+ };
16660
16665
  var catalogCache = /* @__PURE__ */ new Map();
16661
16666
  function cleanOutput(value) {
16662
16667
  return value.replace(ANSI_PATTERN, "").replace(/\r/g, "");
@@ -16666,17 +16671,49 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
16666
16671
  const child = spawn(executable, args, {
16667
16672
  cwd,
16668
16673
  env: process.env,
16669
- stdio: ["ignore", "pipe", "pipe"]
16674
+ stdio: ["ignore", "pipe", "pipe"],
16675
+ detached: process.platform !== "win32"
16670
16676
  });
16671
16677
  let stdout = "";
16672
16678
  let stderr = "";
16673
16679
  let failure = null;
16674
- let finished = false;
16680
+ let settled = false;
16681
+ let forceKillTimer = null;
16682
+ let finalRejectTimer = null;
16683
+ let timer;
16684
+ const signalProcessTree = (signal) => {
16685
+ if (process.platform !== "win32" && child.pid) {
16686
+ try {
16687
+ process.kill(-child.pid, signal);
16688
+ return;
16689
+ } catch {
16690
+ }
16691
+ }
16692
+ child.kill(signal);
16693
+ };
16694
+ const rejectOnce = (error) => {
16695
+ failure ??= error;
16696
+ if (settled) return;
16697
+ settled = true;
16698
+ clearTimeout(timer);
16699
+ if (forceKillTimer) clearTimeout(forceKillTimer);
16700
+ if (finalRejectTimer) clearTimeout(finalRejectTimer);
16701
+ reject(failure);
16702
+ };
16703
+ const terminate = (error) => {
16704
+ if (failure) return;
16705
+ failure = error;
16706
+ signalProcessTree("SIGTERM");
16707
+ forceKillTimer = setTimeout(() => {
16708
+ signalProcessTree("SIGKILL");
16709
+ rejectOnce(error);
16710
+ }, 1e3);
16711
+ finalRejectTimer = setTimeout(() => rejectOnce(error), 2e3);
16712
+ };
16675
16713
  const append = (current, chunk) => {
16676
16714
  const next = current + chunk.toString();
16677
16715
  if (Buffer.byteLength(next) > MAX_OUTPUT_BYTES && failure === null) {
16678
- failure = new Error(`OpenCode \u8F93\u51FA\u8D85\u8FC7 ${MAX_OUTPUT_BYTES} bytes`);
16679
- child.kill("SIGTERM");
16716
+ terminate(new Error(`OpenCode \u8F93\u51FA\u8D85\u8FC7 ${MAX_OUTPUT_BYTES} bytes`));
16680
16717
  }
16681
16718
  return next.slice(-MAX_OUTPUT_BYTES);
16682
16719
  };
@@ -16687,23 +16724,25 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
16687
16724
  stderr = append(stderr, chunk);
16688
16725
  });
16689
16726
  child.once("error", (error) => {
16690
- failure ??= error;
16727
+ if (forceKillTimer) return;
16728
+ rejectOnce(error);
16691
16729
  });
16692
- const timer = setTimeout(() => {
16693
- failure ??= new Error(`OpenCode \u547D\u4EE4\u8D85\u8FC7 ${timeoutMs}ms \u672A\u5B8C\u6210`);
16694
- child.kill("SIGTERM");
16730
+ timer = setTimeout(() => {
16731
+ terminate(new Error(`OpenCode \u547D\u4EE4\u8D85\u8FC7 ${timeoutMs}ms \u672A\u5B8C\u6210`));
16695
16732
  }, timeoutMs);
16696
16733
  child.once("close", (code) => {
16697
- if (finished) return;
16698
- finished = true;
16699
16734
  clearTimeout(timer);
16735
+ if (forceKillTimer) return;
16736
+ if (finalRejectTimer) clearTimeout(finalRejectTimer);
16737
+ if (settled) return;
16738
+ settled = true;
16700
16739
  if (failure) {
16701
16740
  reject(failure);
16702
16741
  return;
16703
16742
  }
16704
16743
  if (code !== 0) {
16705
16744
  const detail = cleanOutput(stderr).trim() || `\u9000\u51FA\u7801 ${code ?? "null"}`;
16706
- reject(new Error(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
16745
+ reject(new OpenCodeCommandExitError(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
16707
16746
  return;
16708
16747
  }
16709
16748
  resolve3(cleanOutput(stdout));
@@ -16713,6 +16752,52 @@ function runOpenCode(executable, args, cwd, timeoutMs) {
16713
16752
  function parseOpenCodeModels(output) {
16714
16753
  return [...new Set(cleanOutput(output).split("\n").map((line) => line.trim()).filter((line) => /^[^\s/]+\/.+/.test(line)))].sort((left, right) => left.localeCompare(right));
16715
16754
  }
16755
+ function isRecord(value) {
16756
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16757
+ }
16758
+ function findJsonObjectEnd(value, start) {
16759
+ let depth = 0;
16760
+ let inString = false;
16761
+ let escaped = false;
16762
+ for (let index2 = start; index2 < value.length; index2 += 1) {
16763
+ const character = value[index2];
16764
+ if (inString) {
16765
+ if (escaped) escaped = false;
16766
+ else if (character === "\\") escaped = true;
16767
+ else if (character === '"') inString = false;
16768
+ continue;
16769
+ }
16770
+ if (character === '"') inString = true;
16771
+ else if (character === "{") depth += 1;
16772
+ else if (character === "}" && --depth === 0) return index2 + 1;
16773
+ }
16774
+ return null;
16775
+ }
16776
+ function parseOpenCodeModelMetadata(output) {
16777
+ const cleaned = cleanOutput(output);
16778
+ const models = /* @__PURE__ */ new Set();
16779
+ const variantsByModel = {};
16780
+ const headingPattern = /^([^\s/]+\/[^\r\n]+)\r?\n\s*(?=\{)/gm;
16781
+ for (const match2 of cleaned.matchAll(headingPattern)) {
16782
+ const model = match2[1].trim();
16783
+ models.add(model);
16784
+ let objectStart = (match2.index ?? 0) + match2[0].length;
16785
+ while (/\s/.test(cleaned[objectStart] ?? "")) objectStart += 1;
16786
+ if (cleaned[objectStart] !== "{") continue;
16787
+ const objectEnd = findJsonObjectEnd(cleaned, objectStart);
16788
+ if (objectEnd === null) continue;
16789
+ try {
16790
+ const metadata = JSON.parse(cleaned.slice(objectStart, objectEnd));
16791
+ if (!isRecord(metadata) || !isRecord(metadata.variants)) continue;
16792
+ variantsByModel[model] = Object.keys(metadata.variants).filter((variant) => variant.trim().length > 0).sort((left, right) => left.localeCompare(right));
16793
+ } catch {
16794
+ }
16795
+ }
16796
+ return {
16797
+ models: [...models].sort((left, right) => left.localeCompare(right)),
16798
+ variantsByModel
16799
+ };
16800
+ }
16716
16801
  function parseOpenCodeAgents(output) {
16717
16802
  const agents = /* @__PURE__ */ new Map();
16718
16803
  for (const line of cleanOutput(output).split("\n")) {
@@ -16736,14 +16821,18 @@ async function loadOpenCodeCatalog(cwd, options = {}) {
16736
16821
  return cached.result;
16737
16822
  }
16738
16823
  const result = Promise.all([
16739
- runOpenCode(executable, ["models"], cwd, timeoutMs),
16824
+ runOpenCode(executable, ["models", "--verbose"], cwd, timeoutMs).catch((error) => {
16825
+ if (!(error instanceof OpenCodeCommandExitError)) throw error;
16826
+ return runOpenCode(executable, ["models"], cwd, timeoutMs);
16827
+ }),
16740
16828
  runOpenCode(executable, ["agent", "list"], cwd, timeoutMs)
16741
16829
  ]).then(([modelsOutput, agentsOutput]) => {
16742
- const models = parseOpenCodeModels(modelsOutput);
16830
+ const metadata = parseOpenCodeModelMetadata(modelsOutput);
16831
+ const models = metadata.models.length > 0 ? metadata.models : parseOpenCodeModels(modelsOutput);
16743
16832
  const agents = parseOpenCodeAgents(agentsOutput).filter((agent) => agent.mode !== "subagent");
16744
16833
  if (models.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u7528\u6A21\u578B");
16745
16834
  if (agents.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u76F4\u63A5\u8FD0\u884C\u7684\u4E3B Agent");
16746
- return { cwd, models, agents };
16835
+ return { cwd, models, variantsByModel: metadata.variantsByModel, agents };
16747
16836
  });
16748
16837
  if (options.useCache !== false) {
16749
16838
  catalogCache.set(cacheKey, { expiresAt: Date.now() + CATALOG_CACHE_MS, result });
@@ -17394,6 +17483,22 @@ function normalizeTaskBatchId(batchId) {
17394
17483
  return batchId.trim() || null;
17395
17484
  }
17396
17485
 
17486
+ // src/core/model-variant.ts
17487
+ var MAX_MODEL_VARIANT_LENGTH = 128;
17488
+ var CONTROL_CHARACTER_PATTERN = /[\u0000-\u001F\u007F]/;
17489
+ function normalizeModelVariant(value) {
17490
+ if (value === void 0 || value === null) return value;
17491
+ const normalized = value.trim();
17492
+ if (!normalized) return null;
17493
+ if (normalized.length > MAX_MODEL_VARIANT_LENGTH) {
17494
+ throw new Error(`variant \u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${MAX_MODEL_VARIANT_LENGTH} \u4E2A\u5B57\u7B26`);
17495
+ }
17496
+ if (CONTROL_CHARACTER_PATTERN.test(normalized)) {
17497
+ throw new Error("variant \u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26");
17498
+ }
17499
+ return normalized;
17500
+ }
17501
+
17397
17502
  // src/core/services/task.service.ts
17398
17503
  var { tasks: tasks3, taskRuns: taskRuns3 } = schema_exports;
17399
17504
  var cleanupInvocation = 0;
@@ -17459,7 +17564,8 @@ var TaskService = class {
17459
17564
  static async add(data) {
17460
17565
  const normalizedData = {
17461
17566
  ...data,
17462
- batchId: normalizeTaskBatchId(data.batchId)
17567
+ batchId: normalizeTaskBatchId(data.batchId),
17568
+ variant: normalizeModelVariant(data.variant)
17463
17569
  };
17464
17570
  this.validateNewTask(normalizedData);
17465
17571
  return db.transaction((tx) => {
@@ -17487,7 +17593,11 @@ var TaskService = class {
17487
17593
  }
17488
17594
  static async update(id, data, scope = {}) {
17489
17595
  if (Object.keys(data).length === 0) throw new Error("\u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A\u8981\u4FEE\u6539\u7684\u5B57\u6BB5");
17490
- const normalizedData = data.batchId === void 0 ? data : { ...data, batchId: normalizeTaskBatchId(data.batchId) ?? null };
17596
+ const normalizedData = {
17597
+ ...data,
17598
+ ...data.batchId === void 0 ? {} : { batchId: normalizeTaskBatchId(data.batchId) ?? null },
17599
+ ...data.variant === void 0 ? {} : { variant: normalizeModelVariant(data.variant) ?? null }
17600
+ };
17491
17601
  return db.transaction((tx) => {
17492
17602
  const task = tx.select().from(tasks3).where(and(
17493
17603
  eq(tasks3.id, id),
@@ -17499,6 +17609,7 @@ var TaskService = class {
17499
17609
  name: normalizedData.name ?? task.name,
17500
17610
  agent: normalizedData.agent ?? task.agent,
17501
17611
  model: normalizedData.model ?? task.model,
17612
+ variant: normalizedData.variant === void 0 ? task.variant : normalizedData.variant,
17502
17613
  prompt: normalizedData.prompt ?? task.prompt,
17503
17614
  cwd: task.cwd,
17504
17615
  category: normalizedData.category ?? task.category,
@@ -18273,7 +18384,8 @@ var TaskTemplateService = class {
18273
18384
  static async create(data) {
18274
18385
  const normalizedData = {
18275
18386
  ...data,
18276
- batchId: normalizeTaskBatchId(data.batchId)
18387
+ batchId: normalizeTaskBatchId(data.batchId),
18388
+ variant: normalizeModelVariant(data.variant)
18277
18389
  };
18278
18390
  this.validate(normalizedData);
18279
18391
  const now = Date.now();
@@ -18339,7 +18451,8 @@ var TaskTemplateService = class {
18339
18451
  static async update(id, data) {
18340
18452
  const normalizedData = {
18341
18453
  ...data,
18342
- batchId: normalizeTaskBatchId(data.batchId) ?? null
18454
+ batchId: normalizeTaskBatchId(data.batchId) ?? null,
18455
+ ...data.variant === void 0 ? {} : { variant: normalizeModelVariant(data.variant) ?? null }
18343
18456
  };
18344
18457
  this.validate(normalizedData);
18345
18458
  const now = Date.now();
@@ -18680,6 +18793,7 @@ function createTaskFromTemplate(templateId, options) {
18680
18793
  name: `${options.namePrefix ?? ""}${tmpl.name}`,
18681
18794
  agent: tmpl.agent,
18682
18795
  model: tmpl.model ?? "default",
18796
+ variant: tmpl.variant,
18683
18797
  prompt: tmpl.prompt,
18684
18798
  cwd: tmpl.cwd ?? null,
18685
18799
  category: tmpl.category ?? "general",
@@ -19316,6 +19430,7 @@ var ZH = {
19316
19430
  "template.cwdHint": "OpenCode \u4F1A\u5728\u8FD9\u4E2A\u76EE\u5F55\u4E2D\u6267\u884C\u4EFB\u52A1\u3002",
19317
19431
  "template.agent": "Agent",
19318
19432
  "template.model": "\u6A21\u578B",
19433
+ "template.variant": "\u6A21\u578B Variant",
19319
19434
  "template.prompt": "\u63D0\u793A\u8BCD",
19320
19435
  "template.scheduleType": "\u6267\u884C\u65B9\u5F0F",
19321
19436
  "template.cronExpr": "Cron \u8868\u8FBE\u5F0F",
@@ -19353,9 +19468,11 @@ var ZH = {
19353
19468
  "catalog.chooseProject": "\u8BF7\u5148\u9009\u62E9\u9879\u76EE\u76EE\u5F55",
19354
19469
  "catalog.defaultModel": "\u8DDF\u968F Agent / OpenCode \u9ED8\u8BA4\u6A21\u578B",
19355
19470
  "catalog.defaultProvider": "\u9ED8\u8BA4\u6A21\u578B",
19471
+ "catalog.defaultVariant": "\u8DDF\u968F Agent / \u6A21\u578B\u9ED8\u8BA4\u8BBE\u7F6E",
19356
19472
  "catalog.provider": "\u6A21\u578B\u63D0\u4F9B\u5546",
19357
19473
  "catalog.model": "\u5177\u4F53\u6A21\u578B",
19358
- "catalog.modelHint": "\u5148\u9009\u63D0\u4F9B\u5546\uFF0C\u518D\u9009\u672C\u9879\u76EE opencode models \u8FD4\u56DE\u7684\u6A21\u578B\uFF1B\u9ED8\u8BA4\u9009\u9879\u4E0D\u4F1A\u4F20\u5165 -m\u3002",
19474
+ "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",
19475
+ "catalog.variantHint": "\u4EC5\u663E\u793A\u6240\u9009\u6A21\u578B\u58F0\u660E\u652F\u6301\u7684 variants\uFF1B\u9ED8\u8BA4\u9009\u9879\u4E0D\u4F1A\u4F20\u5165 --variant\u3002",
19359
19476
  "catalog.agentHint": "\u6765\u81EA\u5F53\u524D\u9879\u76EE\u7684 opencode agent list\u3002",
19360
19477
  "catalog.loading": "\u6B63\u5728\u8BFB\u53D6\u6B64\u9879\u76EE\u53EF\u7528\u7684 Agent \u548C\u6A21\u578B\u2026",
19361
19478
  "catalog.loaded": "\u5DF2\u4ECE\u672C\u673A OpenCode \u8BFB\u53D6 {agents} \u4E2A Agent\u3001{models} \u4E2A\u6A21\u578B\u3002",
@@ -19386,6 +19503,7 @@ var ZH = {
19386
19503
  "details.copySuccess": "\u539F\u59CB\u6570\u636E\u5DF2\u590D\u5236",
19387
19504
  "details.id": "\u7F16\u53F7",
19388
19505
  "details.project": "\u9879\u76EE\u76EE\u5F55",
19506
+ "details.variant": "\u6A21\u578B Variant",
19389
19507
  "details.prompt": "\u63D0\u793A\u8BCD",
19390
19508
  "details.result": "\u6267\u884C\u7ED3\u679C / \u5931\u8D25\u539F\u56E0",
19391
19509
  "details.category": "\u5206\u7C7B",
@@ -19617,6 +19735,7 @@ var EN = {
19617
19735
  "template.cwdHint": "OpenCode runs the task in this directory.",
19618
19736
  "template.agent": "Agent",
19619
19737
  "template.model": "Model",
19738
+ "template.variant": "Model variant",
19620
19739
  "template.prompt": "Prompt",
19621
19740
  "template.scheduleType": "Schedule",
19622
19741
  "template.cronExpr": "Cron expression",
@@ -19654,9 +19773,11 @@ var EN = {
19654
19773
  "catalog.chooseProject": "Choose a project directory first",
19655
19774
  "catalog.defaultModel": "Use the Agent / OpenCode default model",
19656
19775
  "catalog.defaultProvider": "Default model",
19776
+ "catalog.defaultVariant": "Use the Agent / model default",
19657
19777
  "catalog.provider": "Model provider",
19658
19778
  "catalog.model": "Model",
19659
- "catalog.modelHint": "Choose a provider, then a model returned by opencode models for this project. Default does not pass -m.",
19779
+ "catalog.modelHint": "Choose a provider, then a model returned by opencode models --verbose for this project. Default does not pass -m.",
19780
+ "catalog.variantHint": "Only variants declared by the selected model are shown. Default does not pass --variant.",
19660
19781
  "catalog.agentHint": "Loaded from opencode agent list for this project.",
19661
19782
  "catalog.loading": "Loading Agents and models available to this project\u2026",
19662
19783
  "catalog.loaded": "Loaded {agents} Agents and {models} models from local OpenCode.",
@@ -19687,6 +19808,7 @@ var EN = {
19687
19808
  "details.copySuccess": "Raw data copied",
19688
19809
  "details.id": "ID",
19689
19810
  "details.project": "Project directory",
19811
+ "details.variant": "Model variant",
19690
19812
  "details.prompt": "Prompt",
19691
19813
  "details.result": "Result / failure reason",
19692
19814
  "details.category": "Category",
@@ -20211,6 +20333,7 @@ function clientMessages(locale) {
20211
20333
  "details.copySuccess",
20212
20334
  "details.id",
20213
20335
  "details.project",
20336
+ "details.variant",
20214
20337
  "details.prompt",
20215
20338
  "details.result",
20216
20339
  "details.category",
@@ -20311,6 +20434,7 @@ function clientMessages(locale) {
20311
20434
  "catalog.chooseProject",
20312
20435
  "catalog.defaultModel",
20313
20436
  "catalog.defaultProvider",
20437
+ "catalog.defaultVariant",
20314
20438
  "catalog.loading",
20315
20439
  "catalog.loaded",
20316
20440
  "catalog.failed",
@@ -20435,23 +20559,23 @@ function renderLayout(options) {
20435
20559
  function detailSession(value){if(!value)return text('details.none');const session=String(value);return session.length<=10?session:session.slice(0,6)+'***'+session.slice(-4)}
20436
20560
  function detailTaskResult(data){const presentation=data._resultPresentation;if(!presentation)return text('details.none');const parts=[];if(Array.isArray(presentation.errors)&&presentation.errors.length)parts.push(presentation.errors.join('\\n'));if(presentation.text)parts.push(presentation.text);return parts.join('\\n\\n')||text('details.none')}
20437
20561
  function detailField(label,value,options={}){const item=document.createElement('div');item.className='detail-item'+(options.wide?' wide':'');const name=document.createElement('div');name.className='detail-label';name.textContent=label;const content=options.long?document.createElement('pre'):document.createElement('div');content.className='detail-value'+(options.mono?' mono':'')+(options.long?' long':'');content.textContent=value===null||value===undefined||value===''?text('details.none'):String(value);item.append(name,content);return item}
20438
- function renderDetailHistory(runs){const section=document.createElement('section');section.className='detail-history';const title=document.createElement('h3');title.textContent=text('details.history');section.appendChild(title);if(!Array.isArray(runs)||runs.length===0){const empty=document.createElement('div');empty.className='muted small';empty.textContent=text('details.noHistory');section.appendChild(empty);return section}const list=document.createElement('div');list.className='detail-history-list';for(const run of runs){const item=document.createElement('div');item.className='detail-history-item';const primary=document.createElement('strong');primary.textContent='Run #'+run.id+' \xB7 '+detailStatus('run',run.status);const secondary=document.createElement('span');secondary.className='muted';secondary.textContent=detailDate(run.startedAt)+(run.model?' \xB7 '+detailModel(run.model):'');item.append(primary,secondary);list.appendChild(item)}section.appendChild(list);return section}
20562
+ function renderDetailHistory(runs){const section=document.createElement('section');section.className='detail-history';const title=document.createElement('h3');title.textContent=text('details.history');section.appendChild(title);if(!Array.isArray(runs)||runs.length===0){const empty=document.createElement('div');empty.className='muted small';empty.textContent=text('details.noHistory');section.appendChild(empty);return section}const list=document.createElement('div');list.className='detail-history-list';for(const run of runs){const item=document.createElement('div');item.className='detail-history-item';const primary=document.createElement('strong');primary.textContent='Run #'+run.id+' \xB7 '+detailStatus('run',run.status);const secondary=document.createElement('span');secondary.className='muted';secondary.textContent=detailDate(run.startedAt)+(run.model?' \xB7 '+detailModel(run.model):'')+(run.variant?' \xB7 '+run.variant:'');item.append(primary,secondary);list.appendChild(item)}section.appendChild(list);return section}
20439
20563
  function detailFields(type,data){if(type==='task')return [
20440
20564
  [text('details.id'),'#'+data.id],[text('table.name'),data.name],[text('table.status'),detailStatus('task',data.status)],[text('details.project'),data.cwd,{wide:true,mono:true}],
20441
- [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
20565
+ [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.variant'),data.variant||text('details.default')],[text('details.prompt'),data.prompt,{wide:true,long:true}],
20442
20566
  [text('details.category'),data.category],[text('details.batch'),data.batchId],[text('details.dependency'),data.dependsOn?'#'+data.dependsOn:text('details.none')],
20443
20567
  [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.retryCount'),String(data.retryCount??0)+' / '+String(data.maxRetries??0)],
20444
20568
  [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.scheduledAt'),detailDate(data.scheduledAt)],
20445
20569
  [text('details.createdAt'),detailDate(data.createdAt)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
20446
20570
  [text('details.result'),detailTaskResult(data),{wide:true,long:true}]
20447
20571
  ];if(type==='run'){const started=data.startedAt?new Date(data.startedAt).getTime():null;const finished=data.finishedAt?new Date(data.finishedAt).getTime():null;return [
20448
- [text('details.id'),'Run #'+data.id],[text('details.taskId'),'#'+data.taskId],[text('table.status'),detailStatus('run',data.status)],[text('table.model'),detailModel(data.model)],
20572
+ [text('details.id'),'Run #'+data.id],[text('details.taskId'),'#'+data.taskId],[text('table.status'),detailStatus('run',data.status)],[text('table.model'),detailModel(data.model)],[text('details.variant'),data.variant||text('details.default')],
20449
20573
  [text('details.session'),detailSession(data.sessionId)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
20450
20574
  [text('table.duration'),started!==null?detailDuration((finished??Date.now())-started):text('details.none')],[text('details.heartbeat'),detailDate(data.heartbeatAt)],
20451
20575
  [text('details.process'),'Worker PID '+String(data.workerPid??'\u2014')+' \xB7 OpenCode PID '+String(data.childPid??'\u2014'),{wide:true,mono:true}]
20452
20576
  ];}const scheduleRule=data.scheduleType==='cron'?data.cronExpr:data.scheduleType==='recurring'?detailDuration(data.intervalMs):detailDate(data.runAt);return [
20453
20577
  [text('details.id'),'#'+data.id],[text('table.name'),data.name],[text('details.enabled'),data.enabled?text('details.enabledYes'):text('details.enabledNo')],[text('details.project'),data.cwd,{wide:true,mono:true}],
20454
- [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
20578
+ [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.variant'),data.variant||text('details.default')],[text('details.prompt'),data.prompt,{wide:true,long:true}],
20455
20579
  [text('template.scheduleType'),detailScheduleType(data.scheduleType)],[text('details.scheduleRule'),scheduleRule],[text('details.category'),data.category],[text('details.batch'),data.batchId],
20456
20580
  [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.maxInstances'),data.maxInstances],[text('details.maxRetries'),data.maxRetries??0],
20457
20581
  [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.lastRun'),detailDate(data.lastRunAt)],[text('details.nextRun'),detailDate(data.nextRunAt)],
@@ -20468,12 +20592,20 @@ function renderLayout(options) {
20468
20592
  function updateTaskProjectStatus(){const node=taskField('project-status');if(!node)return;const cwd=taskField('cwd').value.trim();if(!cwd){node.textContent='';return}const project=taskProjects()[cwd];node.textContent=project?text('task.projectExisting',project):text('task.projectNew')}
20469
20593
  const catalogTimers={};const catalogRequests={};const catalogModels={};
20470
20594
  function catalogField(prefix,name){return document.getElementById(prefix+'-'+name)}
20471
- function resetCatalog(prefix){const agent=catalogField(prefix,'agent');const provider=catalogField(prefix,'model-provider');const model=catalogField(prefix,'model');if(!agent||!provider||!model)return;catalogModels[prefix]=[];agent.replaceChildren(new Option(text('catalog.chooseProject'),''));provider.replaceChildren(new Option(text('catalog.defaultProvider'),''));model.replaceChildren(new Option(text('catalog.defaultModel'),'default'));agent.disabled=false;provider.disabled=true;model.disabled=true;catalogField(prefix,'catalog-status').textContent='';}
20595
+ function resetCatalog(prefix){const agent=catalogField(prefix,'agent');const provider=catalogField(prefix,'model-provider');const model=catalogField(prefix,'model');const variant=catalogField(prefix,'variant');if(!agent||!provider||!model||!variant)return;const preferredVariant=variant.dataset.preferred||'';catalogModels[prefix]=[];agent.replaceChildren(new Option(text('catalog.chooseProject'),''));provider.replaceChildren(new Option(text('catalog.defaultProvider'),''));model.replaceChildren(new Option(text('catalog.defaultModel'),'default'));variant.replaceChildren(new Option(text('catalog.defaultVariant'),''));if(preferredVariant)appendCurrentOption(variant,preferredVariant);variant.value=preferredVariant;agent.disabled=false;provider.disabled=true;model.disabled=true;variant.disabled=!preferredVariant;catalogField(prefix,'catalog-status').textContent='';}
20472
20596
  function appendCurrentOption(select,value){if(!value||[...select.options].some(option=>option.value===value))return;select.appendChild(new Option(value,value));}
20597
+ 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}
20598
+ function invalidateVariantRequest(prefix){const key=prefix+'-variant';catalogRequests[key]=(catalogRequests[key]||0)+1}
20599
+ function invalidateCatalogRequests(prefix){clearTimeout(catalogTimers[prefix]);catalogRequests[prefix]=(catalogRequests[prefix]||0)+1;invalidateVariantRequest(prefix)}
20600
+ 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}
20601
+ function handleProviderChange(prefix){clearVariantPreference(prefix);populateModelOptions(prefix)}
20602
+ function handleModelChange(prefix){clearVariantPreference(prefix);populateVariantOptions(prefix)}
20603
+ function handleVariantChange(prefix){const variant=catalogField(prefix,'variant');if(!variant)return;invalidateVariantRequest(prefix);variant.dataset.preferred='';variant.dataset.preferredModel=''}
20473
20604
  function modelProvider(value){const slash=value.indexOf('/');return slash>0?value.slice(0,slash):''}
20474
- function populateModelOptions(prefix,preferredModel=''){const provider=catalogField(prefix,'model-provider');const model=catalogField(prefix,'model');if(!provider||!model)return;const selectedProvider=provider.value;model.replaceChildren();if(!selectedProvider){model.appendChild(new Option(text('catalog.defaultModel'),'default'));model.disabled=true;return}const available=(catalogModels[prefix]||[]).filter(value=>modelProvider(value)===selectedProvider);for(const value of available)model.appendChild(new Option(value,value));if(preferredModel&&available.includes(preferredModel))model.value=preferredModel;model.disabled=available.length===0}
20605
+ 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)}
20606
+ async function populateVariantOptions(prefix,preferredVariant=''){const cwd=catalogField(prefix,'cwd')?.value.trim()||'';const model=catalogField(prefix,'model');const variant=catalogField(prefix,'variant');if(!model||!variant)return;const selectedModel=model.value;const preferred=preferredVariant||(variant.dataset.preferredModel===selectedModel?variant.dataset.preferred||'':'');if(!cwd||!selectedModel||selectedModel==='default'){variant.replaceChildren(new Option(text('catalog.defaultVariant'),''));if(preferred)appendCurrentOption(variant,preferred);variant.value=preferred;variant.dataset.preferred='';variant.dataset.preferredModel='';variant.disabled=!preferred;return}if(!preferred){variant.replaceChildren(new Option(text('catalog.defaultVariant'),''));variant.value=''}const requestKey=prefix+'-variant';const request=(catalogRequests[requestKey]||0)+1;catalogRequests[requestKey]=request;variant.disabled=true;try{const data=await readJson(await fetch('/api/opencode/catalog?cwd='+encodeURIComponent(cwd)));if(catalogRequests[requestKey]!==request||catalogField(prefix,'cwd')?.value.trim()!==cwd||model.value!==selectedModel)return;const available=Array.isArray(data.variantsByModel?.[selectedModel])?data.variantsByModel[selectedModel]:[];variant.replaceChildren(new Option(text('catalog.defaultVariant'),''));for(const value of available)variant.appendChild(new Option(value,value));if(preferred)appendCurrentOption(variant,preferred);variant.value=[...variant.options].some(option=>option.value===preferred)?preferred:'';variant.dataset.preferred='';variant.dataset.preferredModel='';variant.disabled=available.length===0&&!preferred}catch(error){if(catalogRequests[requestKey]!==request||catalogField(prefix,'cwd')?.value.trim()!==cwd||model.value!==selectedModel)return;if(preferred)appendCurrentOption(variant,preferred);variant.value=preferred;variant.dataset.preferred=preferred;variant.dataset.preferredModel=selectedModel;variant.disabled=!preferred}}
20475
20607
  async function loadCatalog(prefix,preferredAgent='',preferredModel='default',preserveUnavailable=false){const cwd=catalogField(prefix,'cwd')?.value.trim()||'';const status=catalogField(prefix,'catalog-status');const agent=catalogField(prefix,'agent');const provider=catalogField(prefix,'model-provider');const model=catalogField(prefix,'model');if(!cwd||!status||!agent||!provider||!model){if(!cwd)resetCatalog(prefix);return}const request=(catalogRequests[prefix]||0)+1;catalogRequests[prefix]=request;status.dataset.state='loading';status.textContent=text('catalog.loading');agent.disabled=true;provider.disabled=true;model.disabled=true;try{const data=await readJson(await fetch('/api/opencode/catalog?cwd='+encodeURIComponent(cwd)));if(catalogRequests[prefix]!==request||catalogField(prefix,'cwd').value.trim()!==cwd)return;agent.replaceChildren();for(const item of data.agents){const label=item.name+' \u2014 '+text('catalog.'+item.mode);agent.appendChild(new Option(label,item.name))}if(preserveUnavailable)appendCurrentOption(agent,preferredAgent);const defaultAgent=preferredAgent||data.agents.find(item=>item.name==='build')?.name||data.agents.find(item=>item.mode==='primary')?.name||data.agents[0]?.name||'';agent.value=[...agent.options].some(option=>option.value===defaultAgent)?defaultAgent:(agent.options[0]?.value||'');catalogModels[prefix]=[...data.models];if(preserveUnavailable&&preferredModel&&preferredModel!=='default'&&!catalogModels[prefix].includes(preferredModel))catalogModels[prefix].push(preferredModel);provider.replaceChildren(new Option(text('catalog.defaultProvider'),''));for(const name of [...new Set(catalogModels[prefix].map(modelProvider).filter(Boolean))].sort())provider.appendChild(new Option(name,name));const preferredProvider=preferredModel==='default'?'':modelProvider(preferredModel);provider.value=[...provider.options].some(option=>option.value===preferredProvider)?preferredProvider:'';populateModelOptions(prefix,preferredModel);status.dataset.state='ready';status.textContent=text('catalog.loaded',{agents:data.agents.length,models:data.models.length})}catch(error){if(catalogRequests[prefix]!==request)return;resetCatalog(prefix);if(preserveUnavailable){appendCurrentOption(agent,preferredAgent);if(preferredModel&&preferredModel!=='default'){catalogModels[prefix]=[preferredModel];appendCurrentOption(provider,modelProvider(preferredModel));provider.value=modelProvider(preferredModel);populateModelOptions(prefix,preferredModel)}}status.dataset.state='error';status.textContent=text('catalog.failed',{error:error.message})}finally{if(catalogRequests[prefix]===request){agent.disabled=false;provider.disabled=false;if(provider.value)model.disabled=false}}}
20476
- function scheduleCatalogLoad(prefix){clearTimeout(catalogTimers[prefix]);catalogTimers[prefix]=setTimeout(()=>loadCatalog(prefix),450)}
20608
+ function scheduleCatalogLoad(prefix){invalidateCatalogRequests(prefix);resetCatalog(prefix);catalogTimers[prefix]=setTimeout(()=>loadCatalog(prefix),450)}
20477
20609
  let directoryTargetId='';let directoryCurrent='';let directoryEntries=[];let directoryShowHidden=false;
20478
20610
  function renderDirectoryEntries(){const list=document.getElementById('directory-list');list.replaceChildren();const entries=directoryEntries.filter(entry=>directoryShowHidden||!entry.hidden);const hidden=document.getElementById('directory-hidden');hidden.textContent=text(directoryShowHidden?'action.hideHidden':'action.showHidden');if(entries.length===0){const empty=document.createElement('div');empty.className='directory-empty';empty.textContent=text('directory.empty');list.appendChild(empty);return}for(const entry of entries){const button=document.createElement('button');button.type='button';button.className='directory-item';button.innerHTML='${icon("folder")}<span></span>';button.querySelector('span').textContent=entry.name;button.onclick=()=>browseDirectory(entry.path);list.appendChild(button)}}
20479
20611
  async function browseDirectory(path=''){const choose=document.getElementById('directory-choose');choose.disabled=true;try{const suffix=path?'?path='+encodeURIComponent(path):'';const data=await readJson(await fetch('/api/filesystem/directories'+suffix));directoryCurrent=data.path;directoryEntries=data.directories;document.getElementById('directory-path').textContent=data.path;document.getElementById('directory-up').disabled=data.parent===data.path;document.getElementById('directory-up').onclick=()=>browseDirectory(data.parent);document.getElementById('directory-home').onclick=()=>browseDirectory(data.home);renderDirectoryEntries();choose.disabled=false;return true}catch(error){directoryCurrent='';directoryEntries=[];document.getElementById('directory-path').textContent='';renderDirectoryEntries();showToast(error.message,'error');return false}}
@@ -20483,16 +20615,16 @@ function renderLayout(options) {
20483
20615
  function updateDurationControl(id){const preset=document.getElementById(id+'-preset');const custom=document.getElementById(id+'-custom');const input=document.getElementById(id+'-value');const visible=preset.value==='custom';custom.hidden=!visible;input.required=visible;if(visible&&!input.value)input.value='1'}
20484
20616
  function readDuration(id){const preset=document.getElementById(id+'-preset').value;if(preset==='')return '';if(preset!=='custom')return preset==='0'?'0':preset+'ms';const value=document.getElementById(id+'-value').value.trim();return value===''?'':value+document.getElementById(id+'-unit').value}
20485
20617
  function setDuration(id,milliseconds){const preset=document.getElementById(id+'-preset');const exact=milliseconds==null?'':String(milliseconds);if([...preset.options].some(option=>option.value===exact)){preset.value=exact;updateDurationControl(id);return}preset.value='custom';const input=document.getElementById(id+'-value');const unit=document.getElementById(id+'-unit');const units=[['d',86400000],['h',3600000],['min',60000],['s',1000]];let matched=false;for(const [name,factor] of units){if(milliseconds!=null&&(milliseconds===0||milliseconds%factor===0)){input.value=String(milliseconds/factor);unit.value=name;matched=true;break}}if(!matched){input.value=String((milliseconds??60000)/1000);unit.value='s'}updateDurationControl(id)}
20486
- function openTaskCreator(){const form=document.getElementById('task-form');form.reset();taskField('id').value='';taskField('cwd').readOnly=false;taskField('cwd-picker').hidden=false;taskField('cwd').value=form.dataset.defaultCwd||'';setDuration('task-retry-backoff',30000);setDuration('task-timeout',null);taskField('dialog-title').textContent=text('task.createTitle');taskField('save').textContent=text('action.saveTask');updateTaskProjectStatus();resetCatalog('task');document.getElementById('task-dialog').showModal();if(taskField('cwd').value)loadCatalog('task');setTimeout(()=>taskField('name').focus(),50)}
20487
- async function openTaskEditor(id){try{const data=await readJson(await fetch('/api/tasks/'+id));taskField('id').value=String(id);taskField('cwd').value=data.cwd||'';taskField('cwd').readOnly=true;taskField('cwd-picker').hidden=true;taskField('name').value=data.name||'';taskField('prompt').value=data.prompt||'';taskField('category').value=data.category||'general';taskField('batch').value=data.batchId||'';taskField('importance').value=String(data.importance??3);taskField('urgency').value=String(data.urgency??3);taskField('max-retries').value=String(data.maxRetries??3);setDuration('task-retry-backoff',data.retryBackoffMs??30000);setDuration('task-timeout',data.timeoutMs);taskField('dialog-title').textContent=text('task.editTitle');taskField('save').textContent=text('action.updateTask');updateTaskProjectStatus();resetCatalog('task');document.getElementById('task-dialog').showModal();loadCatalog('task',data.agent||'',data.model||'default',true);setTimeout(()=>taskField('name').focus(),50)}catch(error){showToast(error.message,'error')}}
20488
- async function saveTask(event){event.preventDefault();const form=document.getElementById('task-form');if(!form.reportValidity())return;const id=taskField('id').value;const body={name:taskField('name').value,cwd:taskField('cwd').value,agent:taskField('agent').value,model:taskField('model').value,prompt:taskField('prompt').value,category:taskField('category').value,batchId:taskField('batch').value,importance:Number(taskField('importance').value),urgency:Number(taskField('urgency').value),maxRetries:Number(taskField('max-retries').value),retryBackoff:readDuration('task-retry-backoff'),timeout:readDuration('task-timeout')};const button=taskField('save');button.disabled=true;try{const data=await readJson(await fetch(id?'/api/tasks/'+id:'/api/tasks',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}));showToast(text(id?'feedback.taskUpdated':'feedback.taskCreated',{id:data.task.id}));document.getElementById('task-dialog').close();setTimeout(()=>location.assign(id?location.href:'/?cwd='+encodeURIComponent(data.task.cwd||'')),450)}catch(error){showToast(error.message,'error')}finally{button.disabled=false}}
20618
+ 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)}
20619
+ 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')}}
20620
+ async function saveTask(event){event.preventDefault();const form=document.getElementById('task-form');if(!form.reportValidity())return;const id=taskField('id').value;const body={name:taskField('name').value,cwd:taskField('cwd').value,agent:taskField('agent').value,model:taskField('model').value,variant:taskField('variant').value,prompt:taskField('prompt').value,category:taskField('category').value,batchId:taskField('batch').value,importance:Number(taskField('importance').value),urgency:Number(taskField('urgency').value),maxRetries:Number(taskField('max-retries').value),retryBackoff:readDuration('task-retry-backoff'),timeout:readDuration('task-timeout')};const button=taskField('save');button.disabled=true;try{const data=await readJson(await fetch(id?'/api/tasks/'+id:'/api/tasks',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}));showToast(text(id?'feedback.taskUpdated':'feedback.taskCreated',{id:data.task.id}));document.getElementById('task-dialog').close();setTimeout(()=>location.assign(id?location.href:'/?cwd='+encodeURIComponent(data.task.cwd||'')),450)}catch(error){showToast(error.message,'error')}finally{button.disabled=false}}
20489
20621
  function localDateTime(milliseconds){const date=new Date(milliseconds);const local=new Date(milliseconds-date.getTimezoneOffset()*60000);return local.toISOString().slice(0,23)}
20490
20622
  function updateTemplateScheduleFields(){const type=templateField('schedule-type').value;const fields={cron:templateField('cron-field'),recurring:templateField('interval-field'),delayed:templateField('run-at-field')};for(const [name,node] of Object.entries(fields)){node.hidden=name!==type;for(const control of node.querySelectorAll('input,select'))control.required=false;const required=name==='recurring'?node.querySelector('[id$="-preset"]'):node.querySelector('input');if(required)required.required=name===type;if(name==='recurring'&&name===type)updateDurationControl('template-interval')}}
20491
20623
  function setOriginalRunAt(epoch){const input=templateField('run-at');const local=epoch?localDateTime(epoch):'';input.value=local;input.dataset.originalEpoch=epoch?String(epoch):'';input.dataset.originalLocal=local}
20492
20624
  function selectedRunAt(){const input=templateField('run-at');return resolveEditedRunAt(input.dataset.originalEpoch?Number(input.dataset.originalEpoch):null,input.dataset.originalLocal||'',input.value)}
20493
- function openTemplateCreator(){const form=document.getElementById('template-form');form.reset();templateField('id').value='';templateField('dialog-title').textContent=text('template.createTitle');setDuration('template-interval',3600000);setDuration('template-retry-backoff',30000);setDuration('template-timeout',null);setOriginalRunAt(null);templateField('run-at').value=localDateTime(Date.now()+3600000);updateTemplateScheduleFields();resetCatalog('template');document.getElementById('template-dialog').showModal();if(templateField('cwd').value)loadCatalog('template');setTimeout(()=>templateField('name').focus(),50)}
20494
- async function openTemplateEditor(id){try{const data=await readJson(await fetch('/api/templates/'+id));templateField('id').value=String(id);templateField('dialog-title').textContent=text('template.editTitle');templateField('name').value=data.name||'';templateField('cwd').value=data.cwd||'';templateField('prompt').value=data.prompt||'';templateField('schedule-type').value=data.scheduleType;templateField('cron').value=data.cronExpr||'';setDuration('template-interval',data.intervalMs);setOriginalRunAt(data.runAt||null);if(!data.runAt)templateField('run-at').value=localDateTime(Date.now()+3600000);templateField('category').value=data.category||'general';templateField('batch').value=data.batchId||'';templateField('importance').value=String(data.importance??3);templateField('urgency').value=String(data.urgency??3);templateField('max-instances').value=String(data.maxInstances??1);templateField('max-retries').value=String(data.maxRetries??3);setDuration('template-retry-backoff',data.retryBackoffMs??30000);setDuration('template-timeout',data.timeoutMs);updateTemplateScheduleFields();resetCatalog('template');document.getElementById('template-dialog').showModal();loadCatalog('template',data.agent||'',data.model||'default',true);setTimeout(()=>templateField('name').focus(),50)}catch(error){showToast(error.message,'error')}}
20495
- async function saveTemplate(event){event.preventDefault();const form=document.getElementById('template-form');if(!form.reportValidity())return;const id=templateField('id').value;const type=templateField('schedule-type').value;const body={name:templateField('name').value,cwd:templateField('cwd').value,agent:templateField('agent').value,model:templateField('model').value,prompt:templateField('prompt').value,scheduleType:type,cronExpr:templateField('cron').value,interval:readDuration('template-interval'),runAt:type==='delayed'?selectedRunAt():null,category:templateField('category').value,batchId:templateField('batch').value,importance:Number(templateField('importance').value),urgency:Number(templateField('urgency').value),maxInstances:Number(templateField('max-instances').value),maxRetries:Number(templateField('max-retries').value),retryBackoff:readDuration('template-retry-backoff'),timeout:readDuration('template-timeout')};const button=templateField('save');button.disabled=true;try{await readJson(await fetch(id?'/api/templates/'+id:'/api/templates',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}));showToast(text(id?'feedback.templateUpdated':'feedback.templateCreated'));document.getElementById('template-dialog').close();setTimeout(()=>location.assign(id?location.href:'/templates'),450)}catch(error){showToast(error.message,'error')}finally{button.disabled=false}}
20625
+ 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)}
20626
+ 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')}}
20627
+ async function saveTemplate(event){event.preventDefault();const form=document.getElementById('template-form');if(!form.reportValidity())return;const id=templateField('id').value;const type=templateField('schedule-type').value;const body={name:templateField('name').value,cwd:templateField('cwd').value,agent:templateField('agent').value,model:templateField('model').value,variant:templateField('variant').value,prompt:templateField('prompt').value,scheduleType:type,cronExpr:templateField('cron').value,interval:readDuration('template-interval'),runAt:type==='delayed'?selectedRunAt():null,category:templateField('category').value,batchId:templateField('batch').value,importance:Number(templateField('importance').value),urgency:Number(templateField('urgency').value),maxInstances:Number(templateField('max-instances').value),maxRetries:Number(templateField('max-retries').value),retryBackoff:readDuration('template-retry-backoff'),timeout:readDuration('template-timeout')};const button=templateField('save');button.disabled=true;try{await readJson(await fetch(id?'/api/templates/'+id:'/api/templates',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}));showToast(text(id?'feedback.templateUpdated':'feedback.templateCreated'));document.getElementById('template-dialog').close();setTimeout(()=>location.assign(id?location.href:'/templates'),450)}catch(error){showToast(error.message,'error')}finally{button.disabled=false}}
20496
20628
  async function enableTmpl(id){try{await readJson(await fetch('/api/templates/'+id+'/enable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
20497
20629
  async function disableTmpl(id){if(!await ask(text('dialog.disableTemplate'),text('dialog.disableTemplateBody')))return;try{await readJson(await fetch('/api/templates/'+id+'/disable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
20498
20630
  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')}}
@@ -20621,6 +20753,7 @@ function parseTaskPayload(value) {
20621
20753
  cwd: requiredString("cwd"),
20622
20754
  agent: requiredString("agent"),
20623
20755
  model: optionalString("model", "default"),
20756
+ variant: Object.hasOwn(input, "variant") ? optionalString("variant") : void 0,
20624
20757
  prompt: requiredString("prompt"),
20625
20758
  category: optionalString("category", "general"),
20626
20759
  batchId: optionalString("batchId"),
@@ -20636,6 +20769,7 @@ function editableTaskPayload(input) {
20636
20769
  name: input.name,
20637
20770
  agent: input.agent,
20638
20771
  model: input.model ?? "default",
20772
+ ...input.variant === void 0 ? {} : { variant: input.variant },
20639
20773
  prompt: input.prompt,
20640
20774
  category: input.category ?? "general",
20641
20775
  batchId: input.batchId ?? null,
@@ -20691,6 +20825,7 @@ function parseTemplatePayload(value) {
20691
20825
  name: requiredString("name"),
20692
20826
  agent: requiredString("agent"),
20693
20827
  model: optionalString("model", "default"),
20828
+ variant: Object.hasOwn(input, "variant") ? optionalString("variant") : void 0,
20694
20829
  prompt: requiredString("prompt"),
20695
20830
  cwd: requiredString("cwd"),
20696
20831
  category: optionalString("category", "general"),
@@ -20992,7 +21127,7 @@ app.get("/", async (c) => {
20992
21127
  const latestRun = latestRuns.get(task.id);
20993
21128
  const executionActive = latestRun?.status === "running";
20994
21129
  const batchId = task.batchId?.trim() || null;
20995
- const searchable = esc(`${task.name} ${task.agent} ${task.prompt} ${task.cwd ?? ""} ${task.batchId ?? ""} ${task.category ?? ""}`);
21130
+ const searchable = esc(`${task.name} ${task.agent} ${task.model ?? ""} ${task.variant ?? ""} ${task.prompt} ${task.cwd ?? ""} ${task.batchId ?? ""} ${task.category ?? ""}`);
20996
21131
  return `<tr data-task-row data-search="${searchable}">
20997
21132
  <td class="faint" data-label="${t(locale, "table.id")}">#${task.id}</td>
20998
21133
  <td data-primary data-label="${t(locale, "table.task")}"><div class="task-name">${esc(task.name)}</div><div class="task-prompt" title="${esc(task.prompt)}">${esc(task.prompt.substring(0, 160))}</div>
@@ -21070,11 +21205,12 @@ app.get("/", async (c) => {
21070
21205
  <label class="form-field"><span>${t(locale, "template.cwd")}</span><div class="field-action"><input id="task-cwd" required autocomplete="off" list="task-cwd-options" placeholder="/path/to/project" oninput="updateTaskProjectStatus();scheduleCatalogLoad('task')"><button id="task-cwd-picker" type="button" class="btn" onclick="openDirectoryPicker('task-cwd')">${icon("folder")}${t(locale, "action.chooseFolder")}</button></div><small>${t(locale, "template.cwdHint")}</small></label>
21071
21206
  <datalist id="task-cwd-options">${projects.map((project) => `<option value="${esc(project.cwd)}"></option>`).join("")}</datalist>
21072
21207
  <label class="form-field"><span>${t(locale, "template.agent")}</span><select id="task-agent" required><option value="">${t(locale, "catalog.chooseProject")}</option></select><small>${t(locale, "catalog.agentHint")}</small></label>
21073
- <label class="form-field"><span>${t(locale, "template.model")}</span><div class="model-selector"><select id="task-model-provider" aria-label="${t(locale, "catalog.provider")}" onchange="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>
21208
+ <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>
21209
+ <label class="form-field"><span>${t(locale, "template.variant")}</span><select id="task-variant" onchange="handleVariantChange('task')" disabled><option value="">${t(locale, "catalog.defaultVariant")}</option></select><small>${t(locale, "catalog.variantHint")}</small></label>
21074
21210
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="task-prompt" rows="6" required></textarea></label>
21075
21211
  </div>
21076
21212
  <p id="task-project-status" class="form-note"></p>
21077
- <p id="task-catalog-status" class="form-note catalog-status"></p>
21213
+ <p id="task-catalog-status" class="form-note catalog-status" role="status" aria-live="polite"></p>
21078
21214
  <details class="advanced-fields">
21079
21215
  <summary>${t(locale, "template.advanced")}</summary>
21080
21216
  <div class="template-form-grid">
@@ -21116,7 +21252,7 @@ app.get("/templates", async (c) => {
21116
21252
  return `<tr>
21117
21253
  <td class="faint" data-label="${t(locale, "table.id")}">#${template.id}</td>
21118
21254
  <td data-primary data-label="${t(locale, "table.name")}"><div class="task-name">${esc(template.name)}</div><div class="task-prompt" title="${esc(template.prompt)}">${esc(template.prompt.substring(0, 140))}</div>
21119
- <div class="actions" style="margin-top:5px"><span class="tag">${esc(template.agent)}</span>${template.model && template.model !== "default" ? `<span class="tag">${esc(template.model)}</span>` : ""}</div></td>
21255
+ <div class="actions" style="margin-top:5px"><span class="tag">${esc(template.agent)}</span>${template.model && template.model !== "default" ? `<span class="tag">${esc(template.model)}</span>` : ""}${template.variant ? `<span class="tag">${esc(template.variant)}</span>` : ""}</div></td>
21120
21256
  <td data-label="${t(locale, "table.type")}"><span class="tag t-${scheduleType}">${typeLabel}</span></td>
21121
21257
  <td data-label="${t(locale, "table.rule")}" class="m small">${esc(rule)}</td>
21122
21258
  <td data-label="${t(locale, "table.status")}"><span class="badge ${template.enabled ? "b-done" : "b-cancelled"}">${t(locale, template.enabled ? "schedule.enabled" : "schedule.disabled")}</span></td>
@@ -21146,14 +21282,15 @@ app.get("/templates", async (c) => {
21146
21282
  <label class="form-field"><span>${t(locale, "template.name")}</span><input id="template-name" required maxlength="200" autocomplete="off"></label>
21147
21283
  <label class="form-field"><span>${t(locale, "template.cwd")}</span><div class="field-action"><input id="template-cwd" required autocomplete="off" placeholder="/path/to/project" oninput="scheduleCatalogLoad('template')"><button type="button" class="btn" onclick="openDirectoryPicker('template-cwd')">${icon("folder")}${t(locale, "action.chooseFolder")}</button></div><small>${t(locale, "template.cwdHint")}</small></label>
21148
21284
  <label class="form-field"><span>${t(locale, "template.agent")}</span><select id="template-agent" required><option value="">${t(locale, "catalog.chooseProject")}</option></select><small>${t(locale, "catalog.agentHint")}</small></label>
21149
- <label class="form-field"><span>${t(locale, "template.model")}</span><div class="model-selector"><select id="template-model-provider" aria-label="${t(locale, "catalog.provider")}" onchange="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>
21285
+ <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>
21286
+ <label class="form-field"><span>${t(locale, "template.variant")}</span><select id="template-variant" onchange="handleVariantChange('template')" disabled><option value="">${t(locale, "catalog.defaultVariant")}</option></select><small>${t(locale, "catalog.variantHint")}</small></label>
21150
21287
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="template-prompt" rows="6" required></textarea></label>
21151
21288
  <label class="form-field"><span>${t(locale, "template.scheduleType")}</span><select id="template-schedule-type" onchange="updateTemplateScheduleFields()"><option value="recurring">${t(locale, "schedule.recurring")}</option><option value="delayed">${t(locale, "schedule.delayed")}</option><option value="cron">${t(locale, "schedule.cron")}</option></select></label>
21152
21289
  <label id="template-cron-field" class="form-field" hidden><span>${t(locale, "template.cronExpr")}</span><input id="template-cron" autocomplete="off" placeholder="0 9 * * *"><small>${t(locale, "template.cronHint")}</small></label>
21153
21290
  <label id="template-interval-field" class="form-field"><span>${t(locale, "template.interval")}</span>${durationControl(locale, "template-interval", 36e5, "interval")}<small>${t(locale, "template.intervalHint")}</small></label>
21154
21291
  <label id="template-run-at-field" class="form-field" hidden><span>${t(locale, "template.runAt")}</span><input id="template-run-at" type="datetime-local" step="0.001"></label>
21155
21292
  </div>
21156
- <p id="template-catalog-status" class="form-note catalog-status"></p>
21293
+ <p id="template-catalog-status" class="form-note catalog-status" role="status" aria-live="polite"></p>
21157
21294
  <details class="advanced-fields">
21158
21295
  <summary>${t(locale, "template.advanced")}</summary>
21159
21296
  <div class="template-form-grid">
@@ -21187,6 +21324,7 @@ app.get("/runs", async (c) => {
21187
21324
  taskId: taskRuns4.taskId,
21188
21325
  sessionId: taskRuns4.sessionId,
21189
21326
  model: taskRuns4.model,
21327
+ variant: taskRuns4.variant,
21190
21328
  status: taskRuns4.status,
21191
21329
  startedAt: taskRuns4.startedAt,
21192
21330
  finishedAt: taskRuns4.finishedAt,
@@ -21207,7 +21345,7 @@ app.get("/runs", async (c) => {
21207
21345
  const log = run.log ? renderRunLog(run.id, run.taskName, run.log, locale, run.status !== "done") : "";
21208
21346
  return `<tr class="run-summary-row">
21209
21347
  <td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td>
21210
- <td data-primary data-label="${t(locale, "table.task")}"><div class="task-name">${esc(run.taskName)} <span class="faint">#${run.taskId}</span></div>${run.model ? `<div style="margin-top:4px"><span class="tag">${esc(run.model)}</span></div>` : ""}</td>
21348
+ <td data-primary data-label="${t(locale, "table.task")}"><div class="task-name">${esc(run.taskName)} <span class="faint">#${run.taskId}</span></div>${run.model || run.variant ? `<div class="actions" style="margin-top:4px">${run.model ? `<span class="tag">${esc(run.model)}</span>` : ""}${run.variant ? `<span class="tag">${esc(run.variant)}</span>` : ""}</div>` : ""}</td>
21211
21349
  <td data-label="${t(locale, "table.agent")}"><span class="tag">${esc(run.taskAgent)}</span></td>
21212
21350
  <td data-label="${t(locale, "table.session")}" class="m small">${esc(maskSessionId(run.sessionId))}</td>
21213
21351
  <td data-label="${t(locale, "table.status")}"><span class="badge b-${status}">${runStatusText(locale, run.status ?? "unknown")}</span></td>
@@ -21257,7 +21395,7 @@ app.get("/system", async (c) => {
21257
21395
  const runRows = runningRuns.map((run) => {
21258
21396
  const session = maskSessionId(run.sessionId);
21259
21397
  return `<tr><td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td><td data-primary data-label="${t(locale, "table.task")}">#${run.taskId}</td><td data-label="${t(locale, "table.session")}" class="m small">${esc(session)}</td>
21260
- <td data-label="${t(locale, "table.model")}" class="small">${esc(run.model) || "\u2014"}</td><td data-label="${t(locale, "table.startedAt")}" class="small">${formatDateTime(run.startedAt, locale)}</td>
21398
+ <td data-label="${t(locale, "table.model")}" class="small">${esc(run.model) || "\u2014"}${run.variant ? ` \xB7 ${esc(run.variant)}` : ""}</td><td data-label="${t(locale, "table.startedAt")}" class="small">${formatDateTime(run.startedAt, locale)}</td>
21261
21399
  <td data-label="${t(locale, "table.heartbeat")}" class="small muted">${formatRelative(run.heartbeatAt, locale)}</td><td data-label="${t(locale, "table.pid")}" class="m small">W:${run.workerPid ?? "\u2014"} C:${run.childPid ?? "\u2014"}</td>
21262
21400
  <td data-label="${t(locale, "table.duration")}" class="small">${formatDuration(run.startedAt, null)}</td></tr>`;
21263
21401
  }).join("");