opencode-supertask 0.1.34 → 0.1.36

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.
@@ -1204,7 +1204,7 @@ var init_sql = __esm({
1204
1204
  return new SQL([new StringChunk(str)]);
1205
1205
  }
1206
1206
  sql2.raw = raw2;
1207
- function join6(chunks, separator) {
1207
+ function join7(chunks, separator) {
1208
1208
  const result = [];
1209
1209
  for (const [i, chunk] of chunks.entries()) {
1210
1210
  if (i > 0 && separator !== void 0) {
@@ -1214,7 +1214,7 @@ var init_sql = __esm({
1214
1214
  }
1215
1215
  return new SQL(result);
1216
1216
  }
1217
- sql2.join = join6;
1217
+ sql2.join = join7;
1218
1218
  function identifier(value) {
1219
1219
  return new Name(value);
1220
1220
  }
@@ -3830,7 +3830,7 @@ var init_select2 = __esm({
3830
3830
  return (table, on) => {
3831
3831
  const baseTableName = this.tableName;
3832
3832
  const tableName = getTableLikeName(table);
3833
- if (typeof tableName === "string" && this.config.joins?.some((join6) => join6.alias === tableName)) {
3833
+ if (typeof tableName === "string" && this.config.joins?.some((join7) => join7.alias === tableName)) {
3834
3834
  throw new Error(`Alias "${tableName}" is already used in this query`);
3835
3835
  }
3836
3836
  if (!this.isPartialSelect) {
@@ -4672,7 +4672,7 @@ var init_update = __esm({
4672
4672
  createJoin(joinType) {
4673
4673
  return (table, on) => {
4674
4674
  const tableName = getTableLikeName(table);
4675
- if (typeof tableName === "string" && this.config.joins.some((join6) => join6.alias === tableName)) {
4675
+ if (typeof tableName === "string" && this.config.joins.some((join7) => join7.alias === tableName)) {
4676
4676
  throw new Error(`Alias "${tableName}" is already used in this query`);
4677
4677
  }
4678
4678
  if (typeof on === "function") {
@@ -19628,6 +19628,116 @@ var init_duration = __esm({
19628
19628
  }
19629
19629
  });
19630
19630
 
19631
+ // src/core/opencode-catalog.ts
19632
+ import { spawn as spawn2 } from "child_process";
19633
+ function cleanOutput(value) {
19634
+ return value.replace(ANSI_PATTERN, "").replace(/\r/g, "");
19635
+ }
19636
+ function runOpenCode(executable, args, cwd, timeoutMs) {
19637
+ return new Promise((resolve4, reject) => {
19638
+ const child = spawn2(executable, args, {
19639
+ cwd,
19640
+ env: process.env,
19641
+ stdio: ["ignore", "pipe", "pipe"]
19642
+ });
19643
+ let stdout = "";
19644
+ let stderr = "";
19645
+ let failure = null;
19646
+ let finished = false;
19647
+ const append = (current, chunk) => {
19648
+ const next = current + chunk.toString();
19649
+ if (Buffer.byteLength(next) > MAX_OUTPUT_BYTES && failure === null) {
19650
+ failure = new Error(`OpenCode \u8F93\u51FA\u8D85\u8FC7 ${MAX_OUTPUT_BYTES} bytes`);
19651
+ child.kill("SIGTERM");
19652
+ }
19653
+ return next.slice(-MAX_OUTPUT_BYTES);
19654
+ };
19655
+ child.stdout?.on("data", (chunk) => {
19656
+ stdout = append(stdout, chunk);
19657
+ });
19658
+ child.stderr?.on("data", (chunk) => {
19659
+ stderr = append(stderr, chunk);
19660
+ });
19661
+ child.once("error", (error) => {
19662
+ failure ??= error;
19663
+ });
19664
+ const timer = setTimeout(() => {
19665
+ failure ??= new Error(`OpenCode \u547D\u4EE4\u8D85\u8FC7 ${timeoutMs}ms \u672A\u5B8C\u6210`);
19666
+ child.kill("SIGTERM");
19667
+ }, timeoutMs);
19668
+ child.once("close", (code) => {
19669
+ if (finished) return;
19670
+ finished = true;
19671
+ clearTimeout(timer);
19672
+ if (failure) {
19673
+ reject(failure);
19674
+ return;
19675
+ }
19676
+ if (code !== 0) {
19677
+ const detail = cleanOutput(stderr).trim() || `\u9000\u51FA\u7801 ${code ?? "null"}`;
19678
+ reject(new Error(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
19679
+ return;
19680
+ }
19681
+ resolve4(cleanOutput(stdout));
19682
+ });
19683
+ });
19684
+ }
19685
+ function parseOpenCodeModels(output) {
19686
+ return [...new Set(cleanOutput(output).split("\n").map((line) => line.trim()).filter((line) => /^[^\s/]+\/.+/.test(line)))].sort((left, right) => left.localeCompare(right));
19687
+ }
19688
+ function parseOpenCodeAgents(output) {
19689
+ const agents = /* @__PURE__ */ new Map();
19690
+ for (const line of cleanOutput(output).split("\n")) {
19691
+ const match2 = /^([^\s()]+) \((primary|subagent|all)\)$/.exec(line.trim());
19692
+ if (!match2) continue;
19693
+ const name = match2[1];
19694
+ const mode = match2[2];
19695
+ if (name === "supertask-runner") continue;
19696
+ agents.set(name, { name, mode });
19697
+ }
19698
+ const rank = { primary: 0, all: 1, subagent: 2 };
19699
+ return [...agents.values()].sort((left, right) => rank[left.mode] - rank[right.mode] || left.name.localeCompare(right.name));
19700
+ }
19701
+ async function loadOpenCodeCatalog(cwd, options = {}) {
19702
+ validateTaskWorkingDirectory(cwd);
19703
+ const executable = options.executable ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
19704
+ const timeoutMs = options.timeoutMs ?? COMMAND_TIMEOUT_MS;
19705
+ const cacheKey = `${executable}\0${cwd}`;
19706
+ const cached = catalogCache.get(cacheKey);
19707
+ if (options.useCache !== false && cached && cached.expiresAt > Date.now()) {
19708
+ return cached.result;
19709
+ }
19710
+ const result = Promise.all([
19711
+ runOpenCode(executable, ["models"], cwd, timeoutMs),
19712
+ runOpenCode(executable, ["agent", "list"], cwd, timeoutMs)
19713
+ ]).then(([modelsOutput, agentsOutput]) => {
19714
+ const models = parseOpenCodeModels(modelsOutput);
19715
+ const agents = parseOpenCodeAgents(agentsOutput).filter((agent) => agent.mode !== "subagent");
19716
+ if (models.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u7528\u6A21\u578B");
19717
+ if (agents.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u76F4\u63A5\u8FD0\u884C\u7684\u4E3B Agent");
19718
+ return { cwd, models, agents };
19719
+ });
19720
+ if (options.useCache !== false) {
19721
+ catalogCache.set(cacheKey, { expiresAt: Date.now() + CATALOG_CACHE_MS, result });
19722
+ result.catch(() => {
19723
+ if (catalogCache.get(cacheKey)?.result === result) catalogCache.delete(cacheKey);
19724
+ });
19725
+ }
19726
+ return result;
19727
+ }
19728
+ var CATALOG_CACHE_MS, COMMAND_TIMEOUT_MS, MAX_OUTPUT_BYTES, ANSI_PATTERN, catalogCache;
19729
+ var init_opencode_catalog = __esm({
19730
+ "src/core/opencode-catalog.ts"() {
19731
+ "use strict";
19732
+ init_task_working_directory();
19733
+ CATALOG_CACHE_MS = 3e4;
19734
+ COMMAND_TIMEOUT_MS = 2e4;
19735
+ MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
19736
+ ANSI_PATTERN = /\x1B(?:[@-_][0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
19737
+ catalogCache = /* @__PURE__ */ new Map();
19738
+ }
19739
+ });
19740
+
19631
19741
  // src/core/services/database-maintenance.service.ts
19632
19742
  import { Database as Database3 } from "bun:sqlite";
19633
19743
  import { randomUUID as randomUUID2 } from "crypto";
@@ -20159,7 +20269,27 @@ function scopesMatch(left, right) {
20159
20269
  function currentScope() {
20160
20270
  return runtimeScope({ cwd: process.cwd(), env: process.env });
20161
20271
  }
20162
- function getGatewayDiagnostic() {
20272
+ function diagnoseOpenCodeRuntime(runtime = {
20273
+ cwd: process.cwd(),
20274
+ env: process.env
20275
+ }) {
20276
+ const executable = runtimeScope(runtime).opencodePath;
20277
+ const result = spawnSync2(executable, ["--version"], {
20278
+ cwd: runtime.cwd,
20279
+ env: runtime.env,
20280
+ encoding: "utf8",
20281
+ timeout: 1e4,
20282
+ killSignal: "SIGKILL"
20283
+ });
20284
+ const ok = result.status === 0 && result.error === void 0;
20285
+ return {
20286
+ ok,
20287
+ executable,
20288
+ version: ok ? result.stdout.trim() || result.stderr.trim() || null : null,
20289
+ error: ok ? null : result.error?.message || result.stderr.trim() || result.stdout.trim() || `exit code ${result.status}`
20290
+ };
20291
+ }
20292
+ function getGatewayDiagnostic(options = {}) {
20163
20293
  const producerScope = currentScope();
20164
20294
  if (!isPm2Installed()) {
20165
20295
  return {
@@ -20175,7 +20305,8 @@ function getGatewayDiagnostic() {
20175
20305
  startupConfigured: process.platform === "darwin" || process.platform === "linux" ? false : null,
20176
20306
  currentScope: producerScope,
20177
20307
  gatewayScope: null,
20178
- scopeMatches: false
20308
+ scopeMatches: false,
20309
+ gatewayOpenCode: null
20179
20310
  };
20180
20311
  }
20181
20312
  const processes = pm2JsonList();
@@ -20203,7 +20334,8 @@ function getGatewayDiagnostic() {
20203
20334
  ) : process.platform === "linux" ? isLinuxStartupConfigured(gatewayEnv, runtime?.cwd, runtime ?? void 0) : null,
20204
20335
  currentScope: producerScope,
20205
20336
  gatewayScope: managedScope,
20206
- scopeMatches: managedScope != null && scopesMatch(producerScope, managedScope)
20337
+ scopeMatches: managedScope != null && scopesMatch(producerScope, managedScope),
20338
+ gatewayOpenCode: runtime === null || !options.probeOpenCode ? null : diagnoseOpenCodeRuntime(runtime)
20207
20339
  };
20208
20340
  }
20209
20341
  function pm2Bin(env = process.env) {
@@ -20566,7 +20698,8 @@ function icon(name, className = "icon") {
20566
20698
  check: '<path d="m5 12 4 4L19 6"/>',
20567
20699
  alert: '<path d="M12 3 2.8 19h18.4L12 3Z"/><path d="M12 9v4M12 17h.01"/>',
20568
20700
  clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
20569
- database: '<ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v6c0 1.66 3.58 3 8 3s8-1.34 8-3V5M4 11v6c0 1.66 3.58 3 8 3s8-1.34 8-3v-6"/>'
20701
+ database: '<ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v6c0 1.66 3.58 3 8 3s8-1.34 8-3V5M4 11v6c0 1.66 3.58 3 8 3s8-1.34 8-3v-6"/>',
20702
+ folder: '<path d="M3 7.5A2.5 2.5 0 0 1 5.5 5H10l2 2h6.5A2.5 2.5 0 0 1 21 9.5v7A2.5 2.5 0 0 1 18.5 19h-13A2.5 2.5 0 0 1 3 16.5v-9Z"/>'
20570
20703
  };
20571
20704
  return `<svg class="${className}" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">${paths[name]}</svg>`;
20572
20705
  }
@@ -20579,7 +20712,70 @@ function clientMessages(locale) {
20579
20712
  "action.refresh",
20580
20713
  "action.logs",
20581
20714
  "action.hideLogs",
20715
+ "details.title",
20716
+ "details.subtitle",
20717
+ "details.taskTitle",
20718
+ "details.runTitle",
20719
+ "details.templateTitle",
20720
+ "details.raw",
20582
20721
  "details.copySuccess",
20722
+ "details.id",
20723
+ "details.project",
20724
+ "details.prompt",
20725
+ "details.result",
20726
+ "details.category",
20727
+ "details.batch",
20728
+ "details.dependency",
20729
+ "details.importance",
20730
+ "details.urgency",
20731
+ "details.retryCount",
20732
+ "details.retryBackoff",
20733
+ "details.timeout",
20734
+ "details.createdAt",
20735
+ "details.updatedAt",
20736
+ "details.startedAt",
20737
+ "details.finishedAt",
20738
+ "details.scheduledAt",
20739
+ "details.enabled",
20740
+ "details.scheduleRule",
20741
+ "details.maxInstances",
20742
+ "details.maxRetries",
20743
+ "details.lastRun",
20744
+ "details.nextRun",
20745
+ "details.taskId",
20746
+ "details.session",
20747
+ "details.heartbeat",
20748
+ "details.process",
20749
+ "details.history",
20750
+ "details.noHistory",
20751
+ "details.none",
20752
+ "details.default",
20753
+ "details.enabledYes",
20754
+ "details.enabledNo",
20755
+ "table.name",
20756
+ "table.agent",
20757
+ "table.model",
20758
+ "table.status",
20759
+ "table.duration",
20760
+ "template.scheduleType",
20761
+ "status.pending",
20762
+ "status.running",
20763
+ "status.done",
20764
+ "status.failed",
20765
+ "status.dead_letter",
20766
+ "status.cancelled",
20767
+ "status.unknown",
20768
+ "runStatus.running",
20769
+ "runStatus.done",
20770
+ "runStatus.failed",
20771
+ "schedule.cron",
20772
+ "schedule.recurring",
20773
+ "schedule.delayed",
20774
+ "schedule.unknown",
20775
+ "duration.seconds",
20776
+ "duration.minutes",
20777
+ "duration.hours",
20778
+ "duration.days",
20583
20779
  "feedback.copyFailed",
20584
20780
  "dialog.cancelTask",
20585
20781
  "dialog.cancelTaskBody",
@@ -20621,7 +20817,20 @@ function clientMessages(locale) {
20621
20817
  "feedback.restartTimeout",
20622
20818
  "template.createTitle",
20623
20819
  "template.editTitle",
20624
- "filter.noResults"
20820
+ "filter.noResults",
20821
+ "catalog.chooseProject",
20822
+ "catalog.defaultModel",
20823
+ "catalog.defaultProvider",
20824
+ "catalog.loading",
20825
+ "catalog.loaded",
20826
+ "catalog.failed",
20827
+ "catalog.primary",
20828
+ "catalog.subagent",
20829
+ "catalog.all",
20830
+ "directory.empty",
20831
+ "feedback.commandCopied",
20832
+ "action.showHidden",
20833
+ "action.hideHidden"
20625
20834
  ];
20626
20835
  return Object.fromEntries(keys.map((key) => [key, t(locale, key)]));
20627
20836
  }
@@ -20689,9 +20898,14 @@ function renderLayout(options) {
20689
20898
  <footer>${t(locale, "app.footer")}</footer>
20690
20899
  </div>
20691
20900
  <div id="toast-region" class="toast-region" role="status" aria-live="polite"></div>
20901
+ <dialog id="directory-dialog" class="directory-dialog">
20902
+ <div class="dialog-head"><div><h2>${t(locale, "directory.title")}</h2><p>${t(locale, "directory.subtitle")}</p></div><button type="button" class="icon-button" onclick="document.getElementById('directory-dialog').close()" aria-label="${t(locale, "action.close")}">${icon("close")}</button></div>
20903
+ <div class="dialog-body"><div class="directory-toolbar"><button id="directory-home" type="button" class="btn">${t(locale, "action.home")}</button><button id="directory-up" type="button" class="btn">${t(locale, "action.up")}</button><button id="directory-hidden" type="button" class="btn">${t(locale, "action.showHidden")}</button><div id="directory-path" class="directory-path"></div></div><div id="directory-list" class="directory-list"></div></div>
20904
+ <div class="dialog-actions"><button type="button" class="btn" onclick="document.getElementById('directory-dialog').close()">${t(locale, "action.cancel")}</button><button id="directory-choose" type="button" class="btn btn-primary">${icon("folder")}${t(locale, "action.chooseThisFolder")}</button></div>
20905
+ </dialog>
20692
20906
  <dialog id="detail-dialog">
20693
- <div class="dialog-head"><div><h2>${t(locale, "details.title")}</h2><p>${t(locale, "details.subtitle")}</p></div><button class="icon-button" onclick="document.getElementById('detail-dialog').close()" aria-label="${t(locale, "action.close")}">${icon("close")}</button></div>
20694
- <div class="dialog-body"><pre id="detail-content" class="json-view"></pre></div>
20907
+ <div class="dialog-head"><div><h2 id="detail-title">${t(locale, "details.title")}</h2><p>${t(locale, "details.subtitle")}</p></div><button class="icon-button" onclick="document.getElementById('detail-dialog').close()" aria-label="${t(locale, "action.close")}">${icon("close")}</button></div>
20908
+ <div class="dialog-body"><div id="detail-content" class="detail-view"></div><details class="detail-raw"><summary>${t(locale, "details.raw")}</summary><pre id="detail-raw" class="json-view"></pre></details></div>
20695
20909
  <div class="dialog-actions"><button class="btn" onclick="copyDetails()">${icon("copy")}${t(locale, "action.copy")}</button><button class="btn btn-primary" onclick="document.getElementById('detail-dialog').close()">${t(locale, "action.close")}</button></div>
20696
20910
  </dialog>
20697
20911
  <dialog id="confirm-dialog">
@@ -20723,30 +20937,77 @@ function renderLayout(options) {
20723
20937
  async function retryTask(id){if(!await ask(text('dialog.retryTask',{id}),text('dialog.retryTaskBody')))return;try{await readJson(await fetch('/api/tasks/'+id+'/retry',{method:'POST'}));location.reload()}catch(error){showToast(text('feedback.retryFailed')+': '+error.message,'error')}}
20724
20938
  async function cancelTask(id){if(!await ask(text('dialog.cancelTask',{id}),text('dialog.cancelTaskBody'),true))return;try{await readJson(await fetch('/api/tasks/'+id+'/cancel',{method:'POST'}));location.reload()}catch(error){showToast(text('feedback.cancelFailed')+': '+error.message,'error')}}
20725
20939
  async function deleteTask(id){if(!await ask(text('dialog.deleteTask',{id}),text('dialog.deleteTaskBody'),true))return;try{await readJson(await fetch('/api/tasks/'+id,{method:'DELETE'}));location.reload()}catch(error){showToast(text('feedback.deleteFailed')+': '+error.message,'error')}}
20726
- async function showRecord(url){try{const data=await readJson(await fetch(url));document.getElementById('detail-content').textContent=JSON.stringify(data,null,2);document.getElementById('detail-dialog').showModal()}catch(error){showToast(error.message,'error')}}
20727
- const showDetail=id=>showRecord('/api/tasks/'+id);const showRunDetail=id=>showRecord('/api/runs/'+id);const showTemplateDetail=id=>showRecord('/api/templates/'+id);
20728
- async function copyDetails(){try{await navigator.clipboard.writeText(document.getElementById('detail-content').textContent);showToast(text('details.copySuccess'))}catch{showToast(text('feedback.copyFailed'),'error')}}
20940
+ function detailDate(value){if(value===null||value===undefined||value==='')return text('details.none');const epoch=typeof value==='number'&&value<100000000000?value*1000:value;const date=new Date(epoch);return Number.isNaN(date.getTime())?String(value):date.toLocaleString(document.documentElement.lang)}
20941
+ function detailDuration(value){if(value===null||value===undefined)return text('details.default');const milliseconds=Number(value);if(!Number.isFinite(milliseconds))return String(value);if(milliseconds===0)return '0 ms';const units=[[86400000,text('duration.days')],[3600000,text('duration.hours')],[60000,text('duration.minutes')],[1000,text('duration.seconds')]];for(const [size,label] of units){if(milliseconds>=size&&milliseconds%size===0)return String(milliseconds/size)+' '+label}return String(milliseconds)+' ms'}
20942
+ function detailStatus(type,value){const prefix=type==='run'?'runStatus.':'status.';const key=prefix+String(value||'unknown');return Object.prototype.hasOwnProperty.call(UI,key)?text(key):text('status.unknown')}
20943
+ function detailScheduleType(value){const key='schedule.'+String(value||'unknown');return Object.prototype.hasOwnProperty.call(UI,key)?text(key):text('schedule.unknown')}
20944
+ function detailModel(value){return !value||value==='default'?text('details.default'):String(value)}
20945
+ 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)}
20946
+ 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')}
20947
+ 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}
20948
+ 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}
20949
+ function detailFields(type,data){if(type==='task')return [
20950
+ [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}],
20951
+ [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
20952
+ [text('details.category'),data.category],[text('details.batch'),data.batchId],[text('details.dependency'),data.dependsOn?'#'+data.dependsOn:text('details.none')],
20953
+ [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.retryCount'),String(data.retryCount??0)+' / '+String(data.maxRetries??0)],
20954
+ [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.scheduledAt'),detailDate(data.scheduledAt)],
20955
+ [text('details.createdAt'),detailDate(data.createdAt)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
20956
+ [text('details.result'),detailTaskResult(data),{wide:true,long:true}]
20957
+ ];if(type==='run'){const started=data.startedAt?new Date(data.startedAt).getTime():null;const finished=data.finishedAt?new Date(data.finishedAt).getTime():null;return [
20958
+ [text('details.id'),'Run #'+data.id],[text('details.taskId'),'#'+data.taskId],[text('table.status'),detailStatus('run',data.status)],[text('table.model'),detailModel(data.model)],
20959
+ [text('details.session'),detailSession(data.sessionId)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
20960
+ [text('table.duration'),started!==null?detailDuration((finished??Date.now())-started):text('details.none')],[text('details.heartbeat'),detailDate(data.heartbeatAt)],
20961
+ [text('details.process'),'Worker PID '+String(data.workerPid??'\u2014')+' \xB7 OpenCode PID '+String(data.childPid??'\u2014'),{wide:true,mono:true}]
20962
+ ];}const scheduleRule=data.scheduleType==='cron'?data.cronExpr:data.scheduleType==='recurring'?detailDuration(data.intervalMs):detailDate(data.runAt);return [
20963
+ [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}],
20964
+ [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
20965
+ [text('template.scheduleType'),detailScheduleType(data.scheduleType)],[text('details.scheduleRule'),scheduleRule],[text('details.category'),data.category],[text('details.batch'),data.batchId],
20966
+ [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.maxInstances'),data.maxInstances],[text('details.maxRetries'),data.maxRetries??0],
20967
+ [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.lastRun'),detailDate(data.lastRunAt)],[text('details.nextRun'),detailDate(data.nextRunAt)],
20968
+ [text('details.createdAt'),detailDate(data.createdAt)],[text('details.updatedAt'),detailDate(data.updatedAt)]
20969
+ ]}
20970
+ async function showRecord(url,type){try{const data=await readJson(await fetch(url));const content=document.getElementById('detail-content');content.replaceChildren();const grid=document.createElement('div');grid.className='detail-grid';for(const [label,value,options] of detailFields(type,data))grid.appendChild(detailField(label,value,options));content.appendChild(grid);if(type==='task')content.appendChild(renderDetailHistory(data._runs));document.getElementById('detail-title').textContent=text(type==='task'?'details.taskTitle':type==='run'?'details.runTitle':'details.templateTitle');document.getElementById('detail-raw').textContent=JSON.stringify(data,null,2);document.querySelector('#detail-dialog .detail-raw').open=false;document.getElementById('detail-dialog').showModal()}catch(error){showToast(error.message,'error')}}
20971
+ const showDetail=id=>showRecord('/api/tasks/'+id,'task');const showRunDetail=id=>showRecord('/api/runs/'+id,'run');const showTemplateDetail=id=>showRecord('/api/templates/'+id,'template');
20972
+ async function copyDetails(){try{await navigator.clipboard.writeText(document.getElementById('detail-raw').textContent);showToast(text('details.copySuccess'))}catch{showToast(text('feedback.copyFailed'),'error')}}
20729
20973
  async function copySessionCommand(id){try{const data=await readJson(await fetch('/api/runs/'+id+'/session-command'));await navigator.clipboard.writeText(data.command);showToast(text('feedback.sessionCommandCopied'))}catch(error){showToast(error.message||text('feedback.copyFailed'),'error')}}
20974
+ async function copyRunCommand(id){try{await navigator.clipboard.writeText(document.getElementById('command-'+id).textContent);showToast(text('feedback.commandCopied'))}catch{showToast(text('feedback.copyFailed'),'error')}}
20730
20975
  function taskField(name){return document.getElementById('task-'+name)}
20976
+ function templateField(name){return document.getElementById('template-'+name)}
20731
20977
  function taskProjects(){const node=document.getElementById('task-project-data');if(!node)return {};try{return JSON.parse(node.textContent||'{}')}catch{return {}}}
20732
20978
  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')}
20733
- function openTaskCreator(){const form=document.getElementById('task-form');form.reset();taskField('id').value='';taskField('cwd').readOnly=false;taskField('cwd').value=form.dataset.defaultCwd||'';taskField('dialog-title').textContent=text('task.createTitle');taskField('save').textContent=text('action.saveTask');updateTaskProjectStatus();document.getElementById('task-dialog').showModal();setTimeout(()=>taskField('name').focus(),50)}
20734
- async function openTaskEditor(id){try{const data=await readJson(await fetch('/api/tasks/'+id));taskField('id').value=String(id);taskField('name').value=data.name||'';taskField('cwd').value=data.cwd||'';taskField('cwd').readOnly=true;taskField('agent').value=data.agent||'';taskField('model').value=data.model||'default';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);taskField('retry-backoff').value=durationInput(data.retryBackoffMs??30000);taskField('timeout').value=durationInput(data.timeoutMs);taskField('dialog-title').textContent=text('task.editTitle');taskField('save').textContent=text('action.updateTask');updateTaskProjectStatus();document.getElementById('task-dialog').showModal();setTimeout(()=>taskField('name').focus(),50)}catch(error){showToast(error.message,'error')}}
20735
- 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:taskField('retry-backoff').value,timeout:taskField('timeout').value};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}}
20736
- function templateField(name){return document.getElementById('template-'+name)}
20737
- function durationInput(milliseconds){if(milliseconds==null)return '';if(milliseconds===0)return '0';const units=[['d',86400000],['h',3600000],['min',60000],['s',1000],['ms',1]];for(const [unit,factor] of units){if(milliseconds%factor===0)return String(milliseconds/factor)+unit}return String(milliseconds)+'ms'}
20979
+ const catalogTimers={};const catalogRequests={};const catalogModels={};
20980
+ function catalogField(prefix,name){return document.getElementById(prefix+'-'+name)}
20981
+ 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='';}
20982
+ function appendCurrentOption(select,value){if(!value||[...select.options].some(option=>option.value===value))return;select.appendChild(new Option(value,value));}
20983
+ function modelProvider(value){const slash=value.indexOf('/');return slash>0?value.slice(0,slash):''}
20984
+ 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}
20985
+ 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}}}
20986
+ function scheduleCatalogLoad(prefix){clearTimeout(catalogTimers[prefix]);catalogTimers[prefix]=setTimeout(()=>loadCatalog(prefix),450)}
20987
+ let directoryTargetId='';let directoryCurrent='';let directoryEntries=[];let directoryShowHidden=false;
20988
+ 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)}}
20989
+ 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}}
20990
+ document.getElementById('directory-hidden').onclick=()=>{directoryShowHidden=!directoryShowHidden;renderDirectoryEntries()};
20991
+ async function openDirectoryPicker(targetId){const input=document.getElementById(targetId);if(!input||input.readOnly)return;directoryTargetId=targetId;directoryShowHidden=false;document.getElementById('directory-dialog').showModal();if(!await browseDirectory(input.value.trim()||''))await browseDirectory('')}
20992
+ document.getElementById('directory-choose').onclick=()=>{const input=document.getElementById(directoryTargetId);if(!input||!directoryCurrent)return;input.value=directoryCurrent;input.dispatchEvent(new Event('input',{bubbles:true}));document.getElementById('directory-dialog').close();const prefix=directoryTargetId.startsWith('task-')?'task':'template';loadCatalog(prefix)};
20993
+ 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'}
20994
+ 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}
20995
+ 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)}
20996
+ 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)}
20997
+ 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')}}
20998
+ 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}}
20738
20999
  function localDateTime(milliseconds){const date=new Date(milliseconds);const local=new Date(milliseconds-date.getTimezoneOffset()*60000);return local.toISOString().slice(0,23)}
20739
- 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;node.querySelector('input').required=name===type}}
21000
+ 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')}}
20740
21001
  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}
20741
21002
  function selectedRunAt(){const input=templateField('run-at');return resolveEditedRunAt(input.dataset.originalEpoch?Number(input.dataset.originalEpoch):null,input.dataset.originalLocal||'',input.value)}
20742
- function openTemplateCreator(){const form=document.getElementById('template-form');form.reset();templateField('id').value='';templateField('dialog-title').textContent=text('template.createTitle');setOriginalRunAt(null);templateField('run-at').value=localDateTime(Date.now()+3600000);updateTemplateScheduleFields();document.getElementById('template-dialog').showModal();setTimeout(()=>templateField('name').focus(),50)}
20743
- 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('agent').value=data.agent||'';templateField('model').value=data.model||'default';templateField('prompt').value=data.prompt||'';templateField('schedule-type').value=data.scheduleType;templateField('cron').value=data.cronExpr||'';templateField('interval').value=durationInput(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);templateField('retry-backoff').value=durationInput(data.retryBackoffMs??30000);templateField('timeout').value=durationInput(data.timeoutMs);updateTemplateScheduleFields();document.getElementById('template-dialog').showModal();setTimeout(()=>templateField('name').focus(),50)}catch(error){showToast(error.message,'error')}}
20744
- 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:templateField('interval').value,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:templateField('retry-backoff').value,timeout:templateField('timeout').value};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}}
21003
+ 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)}
21004
+ 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')}}
21005
+ 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}}
20745
21006
  async function enableTmpl(id){try{await readJson(await fetch('/api/templates/'+id+'/enable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
20746
21007
  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')}}
20747
21008
  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')}}
20748
21009
  async function triggerTmpl(id){if(!await ask(text('dialog.triggerTemplate'),text('dialog.triggerTemplateBody')))return;try{const data=await readJson(await fetch('/api/templates/'+id+'/trigger',{method:'POST'}));showToast(text('feedback.triggered',{id:data.taskId}));setTimeout(()=>location.reload(),550)}catch(error){showToast(error.message,'error')}}
20749
- function toggleLog(id,button){const panel=document.getElementById('log-'+id);const hidden=!panel.hidden;panel.hidden=hidden;button.setAttribute('aria-expanded',String(!hidden));button.textContent=text(hidden?'action.logs':'action.hideLogs');}
21010
+ function toggleLog(id,button){const panel=document.getElementById('log-'+id);const hidden=!panel.hidden;panel.hidden=hidden;button.setAttribute('aria-expanded',String(!hidden));button.textContent=text(hidden?'action.logs':'action.hideLogs');if(!hidden)requestAnimationFrame(()=>panel.scrollIntoView({block:'nearest',behavior:'smooth'}));}
20750
21011
  function filterTasks(value){const query=value.trim().toLocaleLowerCase();let visible=0;document.querySelectorAll('[data-task-row]').forEach(row=>{const match=!query||row.dataset.search.toLocaleLowerCase().includes(query);row.hidden=!match;if(match)visible++});const empty=document.getElementById('search-empty');if(empty)empty.hidden=visible!==0;}
20751
21012
  async function clearDatabase(){if(!await askDanger())return;try{const data=await readJson(await fetch('/api/database/clear',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({confirmation:'CLEAR'})}));showToast(text('feedback.databaseCleared',{path:data.backupPath}));setTimeout(()=>location.reload(),1000)}catch(error){showToast(error.message,'error')}}
20752
21013
  async function confirmGatewayRestart(runningCount=0){const body=runningCount>0?text('dialog.restartGatewayRunningBody',{count:runningCount}):text('dialog.restartGatewayBody');return await ask(text('dialog.restartGateway'),body)}
@@ -20792,7 +21053,7 @@ var init_ui = __esm({
20792
21053
  "action.hideLogs": "\u6536\u8D77\u65E5\u5FD7",
20793
21054
  "action.save": "\u4FDD\u5B58\u8BBE\u7F6E",
20794
21055
  "action.saveAndRestart": "\u4FDD\u5B58\u5E76\u91CD\u542F",
20795
- "action.copy": "\u590D\u5236 JSON",
21056
+ "action.copy": "\u590D\u5236\u539F\u59CB\u6570\u636E",
20796
21057
  "action.close": "\u5173\u95ED",
20797
21058
  "action.confirm": "\u786E\u8BA4",
20798
21059
  "action.clearDatabase": "\u6E05\u7A7A\u6570\u636E\u5E93",
@@ -20801,6 +21062,13 @@ var init_ui = __esm({
20801
21062
  "action.updateTask": "\u4FDD\u5B58\u4FEE\u6539",
20802
21063
  "action.createTemplate": "\u65B0\u5EFA\u5B9A\u65F6\u4EFB\u52A1",
20803
21064
  "action.saveTemplate": "\u4FDD\u5B58\u5B9A\u65F6\u4EFB\u52A1",
21065
+ "action.chooseFolder": "\u9009\u62E9\u6587\u4EF6\u5939",
21066
+ "action.chooseThisFolder": "\u9009\u62E9\u5F53\u524D\u6587\u4EF6\u5939",
21067
+ "action.home": "\u4E3B\u76EE\u5F55",
21068
+ "action.up": "\u4E0A\u4E00\u7EA7",
21069
+ "action.copyCommand": "\u590D\u5236\u547D\u4EE4",
21070
+ "action.showHidden": "\u663E\u793A\u9690\u85CF\u6587\u4EF6\u5939",
21071
+ "action.hideHidden": "\u9690\u85CF\u9690\u85CF\u6587\u4EF6\u5939",
20804
21072
  "status.pending": "\u5F85\u6267\u884C",
20805
21073
  "status.running": "\u8FD0\u884C\u4E2D",
20806
21074
  "status.done": "\u5DF2\u5B8C\u6210",
@@ -20867,6 +21135,15 @@ var init_ui = __esm({
20867
21135
  "schedule.hours": "{count} \u5C0F\u65F6",
20868
21136
  "schedule.days": "{count} \u5929",
20869
21137
  "schedule.overdue": "\u5DF2\u5230\u671F",
21138
+ "duration.unit": "\u65F6\u95F4\u5355\u4F4D",
21139
+ "duration.seconds": "\u79D2",
21140
+ "duration.minutes": "\u5206\u949F",
21141
+ "duration.hours": "\u5C0F\u65F6",
21142
+ "duration.days": "\u5929",
21143
+ "duration.systemDefault": "\u4F7F\u7528 Gateway \u9ED8\u8BA4\u8D85\u65F6",
21144
+ "duration.immediate": "\u7ACB\u5373\u91CD\u8BD5",
21145
+ "duration.custom": "\u81EA\u5B9A\u4E49\u2026",
21146
+ "duration.every": "\u6BCF {duration}",
20870
21147
  "system.worker": "\u4EFB\u52A1\u6267\u884C",
20871
21148
  "system.scheduler": "\u5B9A\u65F6\u4EFB\u52A1\u670D\u52A1",
20872
21149
  "system.watchdog": "\u8FD0\u884C\u76D1\u63A7",
@@ -20914,6 +21191,9 @@ var init_ui = __esm({
20914
21191
  "template.interval": "\u6267\u884C\u95F4\u9694",
20915
21192
  "template.runAt": "\u6267\u884C\u65F6\u95F4",
20916
21193
  "template.durationHint": "\u652F\u6301 30s\u30015min\u30011h\u30012d",
21194
+ "template.intervalHint": "\u76F4\u63A5\u9009\u62E9\u5E38\u7528\u9891\u7387\uFF1B\u53EA\u6709\u7279\u6B8A\u9700\u6C42\u624D\u9700\u8981\u81EA\u5B9A\u4E49\u3002",
21195
+ "template.retryBackoffHint": "\u4E00\u6B21\u5931\u8D25\u540E\uFF0C\u7B49\u5F85\u591A\u4E45\u518D\u91CD\u8BD5\u3002",
21196
+ "template.timeoutHint": "\u7559\u7A7A\u8868\u793A\u4F7F\u7528 Gateway \u7684\u9ED8\u8BA4\u4EFB\u52A1\u8D85\u65F6\u3002",
20917
21197
  "template.advanced": "\u66F4\u591A\u6267\u884C\u8BBE\u7F6E",
20918
21198
  "template.category": "\u5206\u7C7B",
20919
21199
  "template.batchId": "\u6279\u6B21 ID",
@@ -20938,14 +21218,73 @@ var init_ui = __esm({
20938
21218
  "task.batchHint": "\u76F8\u540C\u975E\u7A7A\u6279\u6B21 ID \u7684\u4EFB\u52A1\u4E25\u683C\u4E32\u884C\uFF1B\u7559\u7A7A\u5219\u4E0D\u53D7\u6279\u6B21\u4E32\u884C\u9650\u5236\uFF0C\u4F46\u4ECD\u53D7\u5168\u5C40\u5E76\u53D1\u548C\u4F9D\u8D56\u7EA6\u675F\u3002",
20939
21219
  "task.projectExisting": "\u6B64\u9879\u76EE\u73B0\u6709 {total} \u4E2A\u4EFB\u52A1\uFF1A\u8FD0\u884C {running}\uFF0C\u6392\u961F {pending}\uFF0C\u5F02\u5E38 {failed}\u3002",
20940
21220
  "task.projectNew": "\u8FD9\u662F\u4E00\u4E2A\u65B0\u9879\u76EE\u5206\u7EC4\uFF1B\u521B\u5EFA\u540E\u4F1A\u51FA\u73B0\u5728\u9879\u76EE\u5217\u8868\u4E2D\u3002",
21221
+ "catalog.chooseProject": "\u8BF7\u5148\u9009\u62E9\u9879\u76EE\u76EE\u5F55",
21222
+ "catalog.defaultModel": "\u8DDF\u968F Agent / OpenCode \u9ED8\u8BA4\u6A21\u578B",
21223
+ "catalog.defaultProvider": "\u9ED8\u8BA4\u6A21\u578B",
21224
+ "catalog.provider": "\u6A21\u578B\u63D0\u4F9B\u5546",
21225
+ "catalog.model": "\u5177\u4F53\u6A21\u578B",
21226
+ "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",
21227
+ "catalog.agentHint": "\u6765\u81EA\u5F53\u524D\u9879\u76EE\u7684 opencode agent list\u3002",
21228
+ "catalog.loading": "\u6B63\u5728\u8BFB\u53D6\u6B64\u9879\u76EE\u53EF\u7528\u7684 Agent \u548C\u6A21\u578B\u2026",
21229
+ "catalog.loaded": "\u5DF2\u4ECE\u672C\u673A OpenCode \u8BFB\u53D6 {agents} \u4E2A Agent\u3001{models} \u4E2A\u6A21\u578B\u3002",
21230
+ "catalog.failed": "\u65E0\u6CD5\u8BFB\u53D6\u6B64\u9879\u76EE\u7684 OpenCode \u914D\u7F6E\uFF1A{error}",
21231
+ "catalog.primary": "\u4E3B Agent",
21232
+ "catalog.subagent": "\u5B50 Agent",
21233
+ "catalog.all": "\u901A\u7528 Agent",
21234
+ "directory.title": "\u9009\u62E9\u9879\u76EE\u76EE\u5F55",
21235
+ "directory.subtitle": "\u9009\u62E9\u540E\uFF0C\u7CFB\u7EDF\u4F1A\u5728\u8BE5\u76EE\u5F55\u8FD0\u884C OpenCode\uFF0C\u5E76\u8BFB\u53D6\u8BE5\u9879\u76EE\u53EF\u7528\u7684 Agent \u548C\u6A21\u578B\u3002",
21236
+ "directory.empty": "\u8FD9\u4E2A\u6587\u4EF6\u5939\u4E2D\u6CA1\u6709\u5B50\u6587\u4EF6\u5939",
21237
+ "logs.command": "\u5B9E\u9645\u6267\u884C\u547D\u4EE4",
21238
+ "logs.output": "Agent \u8F93\u51FA",
21239
+ "logs.error": "\u5931\u8D25\u539F\u56E0",
21240
+ "logs.tools": "\u5DE5\u5177\u8C03\u7528",
21241
+ "logs.raw": "\u67E5\u770B\u539F\u59CB\u6267\u884C\u65E5\u5FD7",
21242
+ "logs.noText": "\u8FD9\u6B21\u6267\u884C\u6CA1\u6709\u4EA7\u751F\u53EF\u5C55\u793A\u7684\u6587\u672C\u8F93\u51FA\uFF0C\u8BF7\u67E5\u770B\u539F\u59CB\u65E5\u5FD7\u3002",
20941
21243
  "theme.label": "\u4E3B\u9898",
20942
21244
  "theme.system": "\u8DDF\u968F\u7CFB\u7EDF",
20943
21245
  "theme.light": "\u6D45\u8272",
20944
21246
  "theme.dark": "\u6DF1\u8272",
20945
21247
  "language.label": "\u8BED\u8A00",
20946
- "details.title": "\u6570\u636E\u8BE6\u60C5",
20947
- "details.subtitle": "\u539F\u59CB\u8BB0\u5F55\uFF08JSON\uFF09",
20948
- "details.copySuccess": "JSON \u5DF2\u590D\u5236",
21248
+ "details.title": "\u8BE6\u60C5",
21249
+ "details.subtitle": "\u91CD\u70B9\u4FE1\u606F\u5DF2\u6574\u7406\uFF1B\u539F\u59CB\u6570\u636E\u4EC5\u7528\u4E8E\u6392\u969C\u3002",
21250
+ "details.taskTitle": "\u4EFB\u52A1\u8BE6\u60C5",
21251
+ "details.runTitle": "\u6267\u884C\u8BE6\u60C5",
21252
+ "details.templateTitle": "\u5B9A\u65F6\u4EFB\u52A1\u8BE6\u60C5",
21253
+ "details.raw": "\u67E5\u770B\u539F\u59CB\u6570\u636E\uFF08JSON\uFF09",
21254
+ "details.copySuccess": "\u539F\u59CB\u6570\u636E\u5DF2\u590D\u5236",
21255
+ "details.id": "\u7F16\u53F7",
21256
+ "details.project": "\u9879\u76EE\u76EE\u5F55",
21257
+ "details.prompt": "\u63D0\u793A\u8BCD",
21258
+ "details.result": "\u6267\u884C\u7ED3\u679C / \u5931\u8D25\u539F\u56E0",
21259
+ "details.category": "\u5206\u7C7B",
21260
+ "details.batch": "\u6279\u6B21",
21261
+ "details.dependency": "\u4F9D\u8D56\u4EFB\u52A1",
21262
+ "details.importance": "\u91CD\u8981\u7A0B\u5EA6",
21263
+ "details.urgency": "\u7D27\u6025\u7A0B\u5EA6",
21264
+ "details.retryCount": "\u5DF2\u91CD\u8BD5 / \u6700\u591A\u91CD\u8BD5",
21265
+ "details.retryBackoff": "\u91CD\u8BD5\u7B49\u5F85",
21266
+ "details.timeout": "\u6267\u884C\u8D85\u65F6",
21267
+ "details.createdAt": "\u521B\u5EFA\u65F6\u95F4",
21268
+ "details.updatedAt": "\u66F4\u65B0\u65F6\u95F4",
21269
+ "details.startedAt": "\u5F00\u59CB\u65F6\u95F4",
21270
+ "details.finishedAt": "\u7ED3\u675F\u65F6\u95F4",
21271
+ "details.scheduledAt": "\u8BA1\u5212\u65F6\u95F4",
21272
+ "details.enabled": "\u81EA\u52A8\u8FD0\u884C",
21273
+ "details.scheduleRule": "\u8FD0\u884C\u89C4\u5219",
21274
+ "details.maxInstances": "\u81EA\u52A8\u8C03\u5EA6\u4E0A\u9650",
21275
+ "details.maxRetries": "\u5931\u8D25\u91CD\u8BD5\u6B21\u6570",
21276
+ "details.lastRun": "\u4E0A\u6B21\u8FD0\u884C",
21277
+ "details.nextRun": "\u4E0B\u6B21\u8FD0\u884C",
21278
+ "details.taskId": "\u6240\u5C5E\u4EFB\u52A1",
21279
+ "details.session": "OpenCode \u4F1A\u8BDD",
21280
+ "details.heartbeat": "\u6700\u8FD1\u5FC3\u8DF3",
21281
+ "details.process": "\u8FDB\u7A0B",
21282
+ "details.history": "\u6267\u884C\u5386\u53F2",
21283
+ "details.noHistory": "\u8FD8\u6CA1\u6709\u6267\u884C\u8BB0\u5F55",
21284
+ "details.none": "\u65E0",
21285
+ "details.default": "\u8DDF\u968F\u7CFB\u7EDF\u9ED8\u8BA4",
21286
+ "details.enabledYes": "\u5DF2\u542F\u7528",
21287
+ "details.enabledNo": "\u5DF2\u505C\u7528",
20949
21288
  "dialog.cancelTask": "\u53D6\u6D88\u4EFB\u52A1 #{id}\uFF1F",
20950
21289
  "dialog.cancelTaskBody": "\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u4F1A\u5728\u4E0B\u4E00\u4E2A\u8F6E\u8BE2\u5468\u671F\u7EC8\u6B62\u5BF9\u5E94\u8FDB\u7A0B\u6811\u3002",
20951
21290
  "dialog.retryTask": "\u91CD\u8BD5\u4EFB\u52A1 #{id}\uFF1F",
@@ -20975,6 +21314,7 @@ var init_ui = __esm({
20975
21314
  "feedback.templateUpdated": "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u66F4\u65B0",
20976
21315
  "feedback.configSaved": "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58",
20977
21316
  "feedback.sessionCommandCopied": "\u4F1A\u8BDD\u547D\u4EE4\u5DF2\u590D\u5236\uFF0C\u53EF\u76F4\u63A5\u7C98\u8D34\u5230\u7EC8\u7AEF\u8FD0\u884C",
21317
+ "feedback.commandCopied": "\u6267\u884C\u547D\u4EE4\u5DF2\u590D\u5236",
20978
21318
  "feedback.restarting": "Gateway \u6B63\u5728\u91CD\u542F\uFF0C\u8BF7\u7A0D\u5019\u2026",
20979
21319
  "feedback.restartTimeout": "Gateway \u5C1A\u672A\u6062\u590D\uFF0C\u8BF7\u7A0D\u540E\u5237\u65B0\u6216\u68C0\u67E5 PM2 \u72B6\u6001",
20980
21320
  "feedback.databaseCleared": "\u6570\u636E\u5E93\u5DF2\u6E05\u7A7A\uFF0C\u5907\u4EFD\u4F4D\u4E8E\uFF1A{path}",
@@ -21014,7 +21354,7 @@ var init_ui = __esm({
21014
21354
  "action.hideLogs": "Hide log",
21015
21355
  "action.save": "Save settings",
21016
21356
  "action.saveAndRestart": "Save and restart",
21017
- "action.copy": "Copy JSON",
21357
+ "action.copy": "Copy raw data",
21018
21358
  "action.close": "Close",
21019
21359
  "action.confirm": "Confirm",
21020
21360
  "action.clearDatabase": "Clear database",
@@ -21023,6 +21363,13 @@ var init_ui = __esm({
21023
21363
  "action.updateTask": "Save changes",
21024
21364
  "action.createTemplate": "New scheduled task",
21025
21365
  "action.saveTemplate": "Save scheduled task",
21366
+ "action.chooseFolder": "Choose folder",
21367
+ "action.chooseThisFolder": "Choose this folder",
21368
+ "action.home": "Home",
21369
+ "action.up": "Up",
21370
+ "action.copyCommand": "Copy command",
21371
+ "action.showHidden": "Show hidden folders",
21372
+ "action.hideHidden": "Hide hidden folders",
21026
21373
  "status.pending": "Pending",
21027
21374
  "status.running": "Running",
21028
21375
  "status.done": "Done",
@@ -21089,6 +21436,15 @@ var init_ui = __esm({
21089
21436
  "schedule.hours": "{count} hr",
21090
21437
  "schedule.days": "{count} days",
21091
21438
  "schedule.overdue": "Overdue",
21439
+ "duration.unit": "Time unit",
21440
+ "duration.seconds": "seconds",
21441
+ "duration.minutes": "minutes",
21442
+ "duration.hours": "hours",
21443
+ "duration.days": "days",
21444
+ "duration.systemDefault": "Use Gateway default timeout",
21445
+ "duration.immediate": "Retry immediately",
21446
+ "duration.custom": "Custom\u2026",
21447
+ "duration.every": "Every {duration}",
21092
21448
  "system.worker": "Task execution",
21093
21449
  "system.scheduler": "Scheduled task service",
21094
21450
  "system.watchdog": "Runtime monitor",
@@ -21136,6 +21492,9 @@ var init_ui = __esm({
21136
21492
  "template.interval": "Interval",
21137
21493
  "template.runAt": "Run at",
21138
21494
  "template.durationHint": "Supports 30s, 5min, 1h, 2d",
21495
+ "template.intervalHint": "Choose a common frequency directly; customize only when necessary.",
21496
+ "template.retryBackoffHint": "How long to wait after a failure before retrying.",
21497
+ "template.timeoutHint": "Leave blank to use the Gateway default task timeout.",
21139
21498
  "template.advanced": "More execution settings",
21140
21499
  "template.category": "Category",
21141
21500
  "template.batchId": "Batch ID",
@@ -21160,14 +21519,73 @@ var init_ui = __esm({
21160
21519
  "task.batchHint": "Tasks with the same non-empty batch ID run serially. Blank removes the batch constraint; global concurrency and dependencies still apply.",
21161
21520
  "task.projectExisting": "This project has {total} tasks: {running} running, {pending} queued, and {failed} with issues.",
21162
21521
  "task.projectNew": "This is a new project group. It appears in the project list after creation.",
21522
+ "catalog.chooseProject": "Choose a project directory first",
21523
+ "catalog.defaultModel": "Use the Agent / OpenCode default model",
21524
+ "catalog.defaultProvider": "Default model",
21525
+ "catalog.provider": "Model provider",
21526
+ "catalog.model": "Model",
21527
+ "catalog.modelHint": "Choose a provider, then a model returned by opencode models for this project. Default does not pass -m.",
21528
+ "catalog.agentHint": "Loaded from opencode agent list for this project.",
21529
+ "catalog.loading": "Loading Agents and models available to this project\u2026",
21530
+ "catalog.loaded": "Loaded {agents} Agents and {models} models from local OpenCode.",
21531
+ "catalog.failed": "Could not load this project\u2019s OpenCode configuration: {error}",
21532
+ "catalog.primary": "primary Agent",
21533
+ "catalog.subagent": "subagent",
21534
+ "catalog.all": "general Agent",
21535
+ "directory.title": "Choose project directory",
21536
+ "directory.subtitle": "OpenCode runs in this directory, and its project-specific Agents and models are loaded.",
21537
+ "directory.empty": "This folder has no subfolders",
21538
+ "logs.command": "Executed command",
21539
+ "logs.output": "Agent output",
21540
+ "logs.error": "Failure reason",
21541
+ "logs.tools": "Tool calls",
21542
+ "logs.raw": "View raw execution log",
21543
+ "logs.noText": "This run produced no displayable text. Inspect the raw log for details.",
21163
21544
  "theme.label": "Theme",
21164
21545
  "theme.system": "System",
21165
21546
  "theme.light": "Light",
21166
21547
  "theme.dark": "Dark",
21167
21548
  "language.label": "Language",
21168
- "details.title": "Data details",
21169
- "details.subtitle": "Raw record (JSON)",
21170
- "details.copySuccess": "JSON copied",
21549
+ "details.title": "Details",
21550
+ "details.subtitle": "Key information is organized below; raw data is only for troubleshooting.",
21551
+ "details.taskTitle": "Task details",
21552
+ "details.runTitle": "Run details",
21553
+ "details.templateTitle": "Scheduled task details",
21554
+ "details.raw": "View raw data (JSON)",
21555
+ "details.copySuccess": "Raw data copied",
21556
+ "details.id": "ID",
21557
+ "details.project": "Project directory",
21558
+ "details.prompt": "Prompt",
21559
+ "details.result": "Result / failure reason",
21560
+ "details.category": "Category",
21561
+ "details.batch": "Batch",
21562
+ "details.dependency": "Dependency",
21563
+ "details.importance": "Importance",
21564
+ "details.urgency": "Urgency",
21565
+ "details.retryCount": "Retries used / allowed",
21566
+ "details.retryBackoff": "Retry delay",
21567
+ "details.timeout": "Run timeout",
21568
+ "details.createdAt": "Created",
21569
+ "details.updatedAt": "Updated",
21570
+ "details.startedAt": "Started",
21571
+ "details.finishedAt": "Finished",
21572
+ "details.scheduledAt": "Scheduled for",
21573
+ "details.enabled": "Automatic runs",
21574
+ "details.scheduleRule": "Schedule rule",
21575
+ "details.maxInstances": "Automatic scheduling limit",
21576
+ "details.maxRetries": "Failure retries",
21577
+ "details.lastRun": "Last run",
21578
+ "details.nextRun": "Next run",
21579
+ "details.taskId": "Task",
21580
+ "details.session": "OpenCode session",
21581
+ "details.heartbeat": "Latest heartbeat",
21582
+ "details.process": "Processes",
21583
+ "details.history": "Run history",
21584
+ "details.noHistory": "No runs yet",
21585
+ "details.none": "None",
21586
+ "details.default": "Use system default",
21587
+ "details.enabledYes": "Enabled",
21588
+ "details.enabledNo": "Disabled",
21171
21589
  "dialog.cancelTask": "Cancel task #{id}?",
21172
21590
  "dialog.cancelTaskBody": "A running task will terminate its process tree on the next worker poll.",
21173
21591
  "dialog.retryTask": "Retry task #{id}?",
@@ -21197,6 +21615,7 @@ var init_ui = __esm({
21197
21615
  "feedback.templateUpdated": "Scheduled task updated",
21198
21616
  "feedback.configSaved": "Settings saved",
21199
21617
  "feedback.sessionCommandCopied": "Session command copied. Paste it into a terminal to continue.",
21618
+ "feedback.commandCopied": "Execution command copied",
21200
21619
  "feedback.restarting": "Gateway is restarting\u2026",
21201
21620
  "feedback.restartTimeout": "Gateway has not recovered yet. Refresh later or check PM2 status.",
21202
21621
  "feedback.databaseCleared": "Database cleared. Backup: {path}",
@@ -21407,6 +21826,20 @@ var init_ui = __esm({
21407
21826
  .danger-card h2 .icon { width:17px; height:17px; }
21408
21827
  .danger-card p { max-width:800px; margin:0 0 14px; color:var(--text-2); font-size:12px; }
21409
21828
  .log-panel { margin:12px 0; animation:reveal .18s ease both; }
21829
+ .run-log-row td { padding:0 16px 16px; background:color-mix(in srgb,var(--surface-2) 64%,var(--surface)); }
21830
+ .run-log-row .log-panel { margin:0; box-shadow:none; }
21831
+ .log-content { display:grid; gap:14px; padding:16px; }
21832
+ .log-section-head { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:7px; }
21833
+ .run-command,.run-output,.run-error,.run-tools { min-width:0; }
21834
+ .run-command strong,.run-output>strong,.run-error>strong,.run-tools>strong { display:block; margin-bottom:7px; font-size:12px; }
21835
+ .command-cwd { margin-bottom:6px; color:var(--text-3); font-family:"SFMono-Regular",Consolas,monospace; font-size:10px; overflow-wrap:anywhere; }
21836
+ .run-command pre,.run-output pre,.run-error pre { margin:0; padding:12px; overflow:auto; border:1px solid var(--border); border-radius:9px;
21837
+ color:var(--text-2); background:var(--surface-2); font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; white-space:pre-wrap; overflow-wrap:anywhere; }
21838
+ .run-output pre { color:var(--text); font-family:inherit; font-size:13px; line-height:1.65; }
21839
+ .run-error pre { color:var(--red); border-color:color-mix(in srgb,var(--red) 28%,var(--border)); background:var(--red-soft); }
21840
+ .raw-log { padding-top:12px; border-top:1px solid var(--border); }
21841
+ .raw-log summary { color:var(--text-2); cursor:pointer; font-size:12px; font-weight:650; }
21842
+ .raw-log .log-box { margin-top:10px; border-radius:9px; }
21410
21843
  .log-box { max-height:360px; overflow:auto; padding:16px; color:var(--text-2); background:#0b1018; font-family:"SFMono-Regular",Consolas,monospace;
21411
21844
  font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
21412
21845
  :root[data-theme="light"] .log-box { color:#dbe5f3; }
@@ -21425,14 +21858,48 @@ var init_ui = __esm({
21425
21858
  .form-field-wide { grid-column:1 / -1; }
21426
21859
  .form-field input,.form-field select,.form-field textarea { width:100%; min-height:39px; padding:8px 10px; border:1px solid var(--border); border-radius:9px;
21427
21860
  outline:none; color:var(--text); background:var(--surface-2); font-weight:450; }
21861
+ .field-action { display:flex; align-items:stretch; gap:7px; }
21862
+ .field-action input { min-width:0; flex:1; }
21863
+ .field-action .btn { flex:0 0 auto; white-space:nowrap; }
21864
+ .model-selector { display:grid; grid-template-columns:minmax(120px,.75fr) minmax(0,1.25fr); gap:7px; }
21865
+ .duration-picker { display:grid; gap:7px; }
21866
+ .duration-control { display:grid; grid-template-columns:minmax(0,1fr) 112px; gap:7px; }
21867
+ .duration-control input,.duration-control select { min-width:0; }
21428
21868
  .form-field textarea { resize:vertical; line-height:1.5; }
21429
21869
  .form-field input:focus,.form-field select:focus,.form-field textarea:focus { border-color:var(--primary); box-shadow:var(--focus); background:var(--surface); }
21430
21870
  .form-field small { color:var(--text-3); font-size:10px; font-weight:450; }
21431
21871
  .advanced-fields { margin-top:18px; padding-top:14px; border-top:1px solid var(--border); }
21432
21872
  .advanced-fields summary { margin-bottom:14px; color:var(--text-2); cursor:pointer; font-size:12px; font-weight:700; }
21433
21873
  .form-note { margin:16px 0 0; color:var(--text-3); font-size:11px; }
21434
- .json-view { min-height:160px; margin:0; padding:15px; overflow:auto; border:1px solid var(--border); border-radius:10px; color:var(--text-2);
21435
- background:var(--surface-2); font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
21874
+ .catalog-status[data-state="loading"] { color:var(--blue); }
21875
+ .catalog-status[data-state="ready"] { color:var(--green); }
21876
+ .catalog-status[data-state="error"] { color:var(--red); }
21877
+ .directory-dialog { width:min(720px,calc(100% - 32px)); }
21878
+ .directory-toolbar { display:flex; align-items:center; gap:8px; margin-bottom:12px; }
21879
+ .directory-path { min-width:0; flex:1; padding:9px 11px; overflow:hidden; border:1px solid var(--border); border-radius:9px;
21880
+ color:var(--text-2); background:var(--surface-2); font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; text-overflow:ellipsis; white-space:nowrap; }
21881
+ .directory-list { min-height:260px; max-height:48vh; display:grid; align-content:start; gap:6px; overflow:auto; }
21882
+ .directory-item { width:100%; min-height:40px; display:flex; align-items:center; gap:9px; padding:0 11px; border:1px solid transparent; border-radius:9px;
21883
+ color:var(--text-2); background:transparent; cursor:pointer; text-align:left; transition:background-color .15s ease,border-color .15s ease,color .15s ease,transform .12s ease; }
21884
+ .directory-item:hover { color:var(--text); border-color:var(--border); background:var(--surface-2); }
21885
+ .directory-item:active { transform:scale(.99); }
21886
+ .directory-item .icon { width:17px; height:17px; color:var(--primary); flex:0 0 auto; }
21887
+ .directory-empty { min-height:220px; display:grid; place-items:center; color:var(--text-3); }
21888
+ .detail-view { display:grid; gap:16px; }
21889
+ .detail-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; }
21890
+ .detail-item { min-width:0; padding:12px 13px; border:1px solid var(--border); border-radius:10px; background:var(--surface-2); }
21891
+ .detail-item.wide { grid-column:1 / -1; }
21892
+ .detail-label { margin-bottom:5px; color:var(--text-3); font-size:10px; font-weight:750; letter-spacing:.045em; text-transform:uppercase; }
21893
+ .detail-value { color:var(--text); font-size:13px; line-height:1.55; overflow-wrap:anywhere; }
21894
+ .detail-value.mono { font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; }
21895
+ .detail-value.long { max-height:240px; margin:0; overflow:auto; white-space:pre-wrap; }
21896
+ .detail-history h3 { margin:0 0 8px; font-size:13px; }
21897
+ .detail-history-list { display:grid; gap:7px; }
21898
+ .detail-history-item { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:9px 11px; border:1px solid var(--border); border-radius:9px; background:var(--surface-2); font-size:11px; }
21899
+ .detail-raw { padding-top:12px; border-top:1px solid var(--border); }
21900
+ .detail-raw summary { color:var(--text-2); cursor:pointer; font-size:12px; font-weight:650; }
21901
+ .json-view { max-height:320px; margin:10px 0 0; padding:15px; overflow:auto; border:1px solid var(--border); border-radius:10px; color:var(--text-2);
21902
+ background:var(--surface-2); font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; white-space:pre-wrap; overflow-wrap:anywhere; }
21436
21903
  .confirm-copy { color:var(--text-2); margin:0; }
21437
21904
  .confirm-copy strong { display:block; margin-bottom:5px; color:var(--text); font-size:15px; }
21438
21905
  .danger-input { width:100%; height:40px; margin-top:14px; padding:0 12px; border:1px solid var(--border); border-radius:9px; outline:none;
@@ -21496,9 +21963,17 @@ var init_ui = __esm({
21496
21963
  .responsive-table td[data-primary]::before { display:none; }
21497
21964
  .responsive-table .task-prompt { max-width:100%; }
21498
21965
  .responsive-table .actions { justify-content:flex-start; }
21966
+ .responsive-table .run-log-row { padding:0; overflow:hidden; }
21967
+ .responsive-table .run-log-cell { display:block; padding:0; }
21968
+ .responsive-table .run-log-cell::before { display:none; }
21499
21969
  .project-grid { grid-template-columns:1fr; }
21500
21970
  .template-form-grid { grid-template-columns:1fr; }
21971
+ .detail-grid { grid-template-columns:1fr; }
21972
+ .detail-item.wide { grid-column:auto; }
21501
21973
  .form-field-wide { grid-column:auto; }
21974
+ .field-action { align-items:stretch; flex-direction:column; }
21975
+ .field-action .btn { width:100%; }
21976
+ .model-selector { grid-template-columns:1fr; }
21502
21977
  }
21503
21978
  @media (max-width:520px) {
21504
21979
  .stats-grid,.stats-grid.three { grid-template-columns:1fr 1fr; }
@@ -21528,11 +22003,21 @@ __export(web_exports, {
21528
22003
  dashboardApp: () => dashboardApp,
21529
22004
  default: () => web_default,
21530
22005
  isSafeDashboardRestartTarget: () => isSafeDashboardRestartTarget,
22006
+ presentRunLog: () => presentRunLog,
21531
22007
  resolveDashboardConfigState: () => resolveDashboardConfigState,
21532
22008
  setDashboardRuntimeConfig: () => setDashboardRuntimeConfig
21533
22009
  });
21534
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
21535
- import { basename as basename3, dirname as dirname7 } from "path";
22010
+ import {
22011
+ existsSync as existsSync7,
22012
+ mkdirSync as mkdirSync4,
22013
+ readFileSync as readFileSync4,
22014
+ readdirSync,
22015
+ renameSync as renameSync2,
22016
+ statSync as statSync4,
22017
+ writeFileSync as writeFileSync3
22018
+ } from "fs";
22019
+ import { homedir as homedir4 } from "os";
22020
+ import { basename as basename3, dirname as dirname7, join as join6 } from "path";
21536
22021
  function setDashboardRuntimeConfig(config) {
21537
22022
  runtimeConfig = config === null ? null : structuredClone(config);
21538
22023
  }
@@ -21573,6 +22058,23 @@ function parsePositiveInteger(value) {
21573
22058
  function parseTaskStatus(value) {
21574
22059
  return TASK_STATUSES.has(value) ? value : null;
21575
22060
  }
22061
+ function listChildDirectories(path) {
22062
+ return readdirSync(path, { withFileTypes: true }).flatMap((entry) => {
22063
+ const entryPath = join6(path, entry.name);
22064
+ if (entry.isDirectory()) return [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }];
22065
+ if (!entry.isSymbolicLink()) return [];
22066
+ try {
22067
+ return statSync4(entryPath).isDirectory() ? [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }] : [];
22068
+ } catch {
22069
+ return [];
22070
+ }
22071
+ }).sort((left, right) => {
22072
+ const leftHidden = left.name.startsWith(".");
22073
+ const rightHidden = right.name.startsWith(".");
22074
+ if (leftHidden !== rightHidden) return leftHidden ? 1 : -1;
22075
+ return left.name.localeCompare(right.name);
22076
+ });
22077
+ }
21576
22078
  function parseTaskPayload(value) {
21577
22079
  if (!value || typeof value !== "object" || Array.isArray(value)) {
21578
22080
  throw new Error("\u8BF7\u6C42\u5185\u5BB9\u5FC5\u987B\u662F\u5BF9\u8C61");
@@ -21729,6 +22231,66 @@ function formatDuration(startAt, endAt) {
21729
22231
  if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
21730
22232
  return `${Math.floor(seconds / 3600)}h ${Math.floor(seconds % 3600 / 60)}m`;
21731
22233
  }
22234
+ function recordValue(value) {
22235
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
22236
+ }
22237
+ function shellQuote(value) {
22238
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value;
22239
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
22240
+ }
22241
+ function presentRunLog(log) {
22242
+ let command = null;
22243
+ const textParts = [];
22244
+ const errors = [];
22245
+ const tools = [];
22246
+ for (const line of log.split("\n")) {
22247
+ const trimmed = line.trim();
22248
+ if (!trimmed) continue;
22249
+ let parsed = null;
22250
+ try {
22251
+ parsed = recordValue(JSON.parse(trimmed));
22252
+ } catch {
22253
+ errors.push(line);
22254
+ continue;
22255
+ }
22256
+ if (!parsed) continue;
22257
+ if (parsed.type === "supertask_command") {
22258
+ const executable = typeof parsed.executable === "string" ? parsed.executable : null;
22259
+ const cwd = typeof parsed.cwd === "string" ? parsed.cwd : null;
22260
+ const args = Array.isArray(parsed.args) && parsed.args.every((item) => typeof item === "string") ? parsed.args : null;
22261
+ if (executable && cwd && args) {
22262
+ command = {
22263
+ cwd,
22264
+ command: `cd ${shellQuote(cwd)} && ${[executable, ...args].map(shellQuote).join(" ")}`
22265
+ };
22266
+ }
22267
+ continue;
22268
+ }
22269
+ const part = recordValue(parsed.part);
22270
+ const eventType = typeof parsed.type === "string" ? parsed.type : "";
22271
+ const partType = typeof part?.type === "string" ? part.type : "";
22272
+ const text2 = typeof part?.text === "string" ? part.text : typeof parsed.text === "string" ? parsed.text : null;
22273
+ if (text2 && (eventType === "text" || partType === "text")) textParts.push(text2);
22274
+ const tool = typeof part?.tool === "string" ? part.tool : typeof parsed.tool === "string" ? parsed.tool : null;
22275
+ if (tool && (eventType === "tool_use" || partType === "tool")) tools.push(tool);
22276
+ const error = typeof parsed.error === "string" ? parsed.error : typeof part?.error === "string" ? part.error : null;
22277
+ if (error) errors.push(error);
22278
+ }
22279
+ return {
22280
+ command,
22281
+ text: textParts.join("\n").trim(),
22282
+ errors: [...new Set(errors)],
22283
+ tools
22284
+ };
22285
+ }
22286
+ function renderRunLog(runId, taskName, log, locale) {
22287
+ const presentation = presentRunLog(log);
22288
+ const command = presentation.command ? `<div class="run-command"><div class="log-section-head"><strong>${t(locale, "logs.command")}</strong><button type="button" class="btn" onclick="copyRunCommand(${runId})">${icon("copy")}${t(locale, "action.copyCommand")}</button></div><div class="command-cwd">${esc(presentation.command.cwd)}</div><pre id="command-${runId}">${esc(presentation.command.command)}</pre></div>` : "";
22289
+ const errors = presentation.errors.length > 0 ? `<div class="run-error"><strong>${t(locale, "logs.error")}</strong><pre>${esc(presentation.errors.join("\n"))}</pre></div>` : "";
22290
+ const tools = presentation.tools.length > 0 ? `<div class="run-tools"><strong>${t(locale, "logs.tools")}</strong><div class="actions">${presentation.tools.map((tool) => `<span class="tag">${esc(tool)}</span>`).join("")}</div></div>` : "";
22291
+ const output = `<div class="run-output"><strong>${t(locale, "logs.output")}</strong><pre>${esc(presentation.text || t(locale, "logs.noText"))}</pre></div>`;
22292
+ return `<section class="panel log-panel"><div class="panel-head"><h3>Run #${runId} \xB7 ${esc(taskName)}</h3></div><div class="log-content">${command}${errors}${output}${tools}<details class="raw-log"><summary>${t(locale, "logs.raw")}</summary><div class="log-box">${esc(log)}</div></details></div></section>`;
22293
+ }
21732
22294
  function esc(value) {
21733
22295
  if (!value) return "";
21734
22296
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -21760,6 +22322,25 @@ function emptyState(title, hint, code = "") {
21760
22322
  return `<div class="empty-state"><div><div class="empty-icon">${icon("inbox")}</div>
21761
22323
  <h3>${title}</h3><p>${hint}</p>${code ? `<code>${code}</code>` : ""}</div></div>`;
21762
22324
  }
22325
+ function durationControl(locale, id, value, kind) {
22326
+ const units = [
22327
+ { value: "s", label: t(locale, "duration.seconds") },
22328
+ { value: "min", label: t(locale, "duration.minutes") },
22329
+ { value: "h", label: t(locale, "duration.hours") },
22330
+ { value: "d", label: t(locale, "duration.days") }
22331
+ ];
22332
+ const values = kind === "interval" ? [3e5, 9e5, 18e5, 36e5, 216e5, 432e5, 864e5] : kind === "retry" ? [0, 1e4, 3e4, 6e4, 3e5] : [3e5, 9e5, 18e5, 36e5, 72e5, 144e5];
22333
+ const presets = [
22334
+ ...kind === "timeout" ? [{ value: "", label: t(locale, "duration.systemDefault") }] : [],
22335
+ ...values.map((milliseconds) => ({
22336
+ value: String(milliseconds),
22337
+ label: milliseconds === 0 ? t(locale, "duration.immediate") : kind === "interval" ? t(locale, "duration.every", { duration: formatInterval(milliseconds, locale) }) : formatInterval(milliseconds, locale)
22338
+ })),
22339
+ { value: "custom", label: t(locale, "duration.custom") }
22340
+ ];
22341
+ const selected = value === null ? "" : String(value);
22342
+ return `<div class="duration-picker"><select id="${id}-preset" onchange="updateDurationControl('${id}')">${presets.map((item) => `<option value="${item.value}" ${item.value === selected ? "selected" : ""}>${item.label}</option>`).join("")}</select><div id="${id}-custom" class="duration-control" hidden><input id="${id}-value" type="number" min="${kind === "retry" ? 0 : 0.1}" step="0.1" inputmode="decimal"><select id="${id}-unit" aria-label="${t(locale, "duration.unit")}">${units.map((item) => `<option value="${item.value}">${item.label}</option>`).join("")}</select></div></div>`;
22343
+ }
21763
22344
  function formatInterval(milliseconds, locale) {
21764
22345
  const formatter = new Intl.NumberFormat(locale === "en" ? "en-US" : "zh-CN", {
21765
22346
  maximumFractionDigits: 1
@@ -21788,6 +22369,8 @@ var init_web = __esm({
21788
22369
  init_drizzle_orm();
21789
22370
  init_db2();
21790
22371
  init_duration();
22372
+ init_opencode_catalog();
22373
+ init_task_working_directory();
21791
22374
  init_database_maintenance_service();
21792
22375
  init_task_run_service();
21793
22376
  init_task_service();
@@ -21841,6 +22424,33 @@ var init_web = __esm({
21841
22424
  const health = getGatewayHealth();
21842
22425
  return c.json(health, health.status === "ok" ? 200 : 503);
21843
22426
  });
22427
+ app.get("/api/filesystem/directories", (c) => {
22428
+ const requestedPath = c.req.query("path")?.trim() || homedir4();
22429
+ try {
22430
+ validateTaskWorkingDirectory(requestedPath);
22431
+ return c.json({
22432
+ path: requestedPath,
22433
+ parent: dirname7(requestedPath),
22434
+ home: homedir4(),
22435
+ directories: listChildDirectories(requestedPath)
22436
+ });
22437
+ } catch (error) {
22438
+ return c.json({
22439
+ error: error instanceof Error ? error.message : String(error)
22440
+ }, 400);
22441
+ }
22442
+ });
22443
+ app.get("/api/opencode/catalog", async (c) => {
22444
+ const cwd = c.req.query("cwd")?.trim();
22445
+ if (!cwd) return c.json({ error: "cwd \u4E0D\u80FD\u4E3A\u7A7A" }, 400);
22446
+ try {
22447
+ return c.json(await loadOpenCodeCatalog(cwd));
22448
+ } catch (error) {
22449
+ return c.json({
22450
+ error: error instanceof Error ? error.message : String(error)
22451
+ }, 400);
22452
+ }
22453
+ });
21844
22454
  app.get("/", async (c) => {
21845
22455
  const locale = resolveLocale(c);
21846
22456
  const page = parsePositiveInteger(c.req.query("page") || "1");
@@ -21985,13 +22595,14 @@ var init_web = __esm({
21985
22595
  <div class="dialog-body">
21986
22596
  <div class="template-form-grid">
21987
22597
  <label class="form-field"><span>${t(locale, "template.name")}</span><input id="task-name" required maxlength="200" autocomplete="off"></label>
21988
- <label class="form-field"><span>${t(locale, "template.cwd")}</span><input id="task-cwd" required autocomplete="off" list="task-cwd-options" placeholder="/path/to/project" oninput="updateTaskProjectStatus()"><small>${t(locale, "template.cwdHint")}</small></label>
22598
+ <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>
21989
22599
  <datalist id="task-cwd-options">${projects.map((project) => `<option value="${esc(project.cwd)}"></option>`).join("")}</datalist>
21990
- <label class="form-field"><span>${t(locale, "template.agent")}</span><input id="task-agent" required autocomplete="off" value="build" placeholder="build"></label>
21991
- <label class="form-field"><span>${t(locale, "template.model")}</span><input id="task-model" required autocomplete="off" value="default" placeholder="default"></label>
22600
+ <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>
22601
+ <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>
21992
22602
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="task-prompt" rows="6" required></textarea></label>
21993
22603
  </div>
21994
22604
  <p id="task-project-status" class="form-note"></p>
22605
+ <p id="task-catalog-status" class="form-note catalog-status"></p>
21995
22606
  <details class="advanced-fields">
21996
22607
  <summary>${t(locale, "template.advanced")}</summary>
21997
22608
  <div class="template-form-grid">
@@ -22000,8 +22611,8 @@ var init_web = __esm({
22000
22611
  <label class="form-field"><span>${t(locale, "template.importance")}</span><input id="task-importance" type="number" min="1" max="5" step="1" value="3" required></label>
22001
22612
  <label class="form-field"><span>${t(locale, "template.urgency")}</span><input id="task-urgency" type="number" min="1" max="5" step="1" value="3" required></label>
22002
22613
  <label class="form-field"><span>${t(locale, "template.maxRetries")}</span><input id="task-max-retries" type="number" min="0" max="1000" step="1" value="3" required></label>
22003
- <label class="form-field"><span>${t(locale, "template.retryBackoff")}</span><input id="task-retry-backoff" autocomplete="off" value="30s"><small>${t(locale, "template.durationHint")}</small></label>
22004
- <label class="form-field"><span>${t(locale, "template.timeout")}</span><input id="task-timeout" autocomplete="off" placeholder="30min"><small>${t(locale, "template.optional")}</small></label>
22614
+ <label class="form-field"><span>${t(locale, "template.retryBackoff")}</span>${durationControl(locale, "task-retry-backoff", 3e4, "retry")}<small>${t(locale, "template.retryBackoffHint")}</small></label>
22615
+ <label class="form-field"><span>${t(locale, "template.timeout")}</span>${durationControl(locale, "task-timeout", null, "timeout")}<small>${t(locale, "template.timeoutHint")}</small></label>
22005
22616
  </div>
22006
22617
  </details>
22007
22618
  </div>
@@ -22061,15 +22672,16 @@ var init_web = __esm({
22061
22672
  <div class="dialog-body">
22062
22673
  <div class="template-form-grid">
22063
22674
  <label class="form-field"><span>${t(locale, "template.name")}</span><input id="template-name" required maxlength="200" autocomplete="off"></label>
22064
- <label class="form-field"><span>${t(locale, "template.cwd")}</span><input id="template-cwd" required autocomplete="off" placeholder="/path/to/project"><small>${t(locale, "template.cwdHint")}</small></label>
22065
- <label class="form-field"><span>${t(locale, "template.agent")}</span><input id="template-agent" required autocomplete="off" placeholder="build"></label>
22066
- <label class="form-field"><span>${t(locale, "template.model")}</span><input id="template-model" required autocomplete="off" value="default" placeholder="default"></label>
22675
+ <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>
22676
+ <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>
22677
+ <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>
22067
22678
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="template-prompt" rows="6" required></textarea></label>
22068
22679
  <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>
22069
22680
  <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>
22070
- <label id="template-interval-field" class="form-field"><span>${t(locale, "template.interval")}</span><input id="template-interval" autocomplete="off" value="1h" placeholder="30s / 5min / 1h"><small>${t(locale, "template.durationHint")}</small></label>
22681
+ <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>
22071
22682
  <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>
22072
22683
  </div>
22684
+ <p id="template-catalog-status" class="form-note catalog-status"></p>
22073
22685
  <details class="advanced-fields">
22074
22686
  <summary>${t(locale, "template.advanced")}</summary>
22075
22687
  <div class="template-form-grid">
@@ -22079,8 +22691,8 @@ var init_web = __esm({
22079
22691
  <label class="form-field"><span>${t(locale, "template.urgency")}</span><input id="template-urgency" type="number" min="1" max="5" step="1" value="3" required></label>
22080
22692
  <label class="form-field"><span>${t(locale, "template.maxInstances")}</span><input id="template-max-instances" type="number" min="1" max="1000" step="1" value="1" required><small>${t(locale, "template.maxInstancesHint")}</small></label>
22081
22693
  <label class="form-field"><span>${t(locale, "template.maxRetries")}</span><input id="template-max-retries" type="number" min="0" max="1000" step="1" value="3" required></label>
22082
- <label class="form-field"><span>${t(locale, "template.retryBackoff")}</span><input id="template-retry-backoff" autocomplete="off" value="30s"><small>${t(locale, "template.durationHint")}</small></label>
22083
- <label class="form-field"><span>${t(locale, "template.timeout")}</span><input id="template-timeout" autocomplete="off" placeholder="30min"><small>${t(locale, "template.optional")}</small></label>
22694
+ <label class="form-field"><span>${t(locale, "template.retryBackoff")}</span>${durationControl(locale, "template-retry-backoff", 3e4, "retry")}<small>${t(locale, "template.retryBackoffHint")}</small></label>
22695
+ <label class="form-field"><span>${t(locale, "template.timeout")}</span>${durationControl(locale, "template-timeout", null, "timeout")}<small>${t(locale, "template.timeoutHint")}</small></label>
22084
22696
  </div>
22085
22697
  </details>
22086
22698
  <p class="form-note">${t(locale, "template.futureOnly")}</p>
@@ -22117,14 +22729,11 @@ var init_web = __esm({
22117
22729
  const total = Number(totalResult[0]?.count ?? 0);
22118
22730
  const totalPages = Math.max(1, Math.ceil(total / limit));
22119
22731
  if (page > totalPages) return c.redirect(`/runs?page=${totalPages}`);
22120
- const logs = [];
22121
22732
  const rows = runs.map((run) => {
22122
22733
  const status = safeStatus(run.status);
22123
22734
  const resumable = isValidSessionId(run.sessionId);
22124
- if (run.log) {
22125
- logs.push(`<section id="log-${run.id}" class="panel log-panel" hidden><div class="panel-head"><h3>Run #${run.id} \xB7 ${esc(run.taskName)}</h3></div><div class="log-box">${esc(run.log)}</div></section>`);
22126
- }
22127
- return `<tr>
22735
+ const log = run.log ? renderRunLog(run.id, run.taskName, run.log, locale) : "";
22736
+ return `<tr class="run-summary-row">
22128
22737
  <td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td>
22129
22738
  <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>
22130
22739
  <td data-label="${t(locale, "table.agent")}"><span class="tag">${esc(run.taskAgent)}</span></td>
@@ -22135,7 +22744,7 @@ var init_web = __esm({
22135
22744
  <td data-label="${t(locale, "table.actions")}"><div class="actions"><button type="button" class="btn" onclick="showRunDetail(${run.id})">${t(locale, "action.details")}</button>
22136
22745
  ${resumable ? `<button type="button" class="btn" onclick="copySessionCommand(${run.id})">${icon("copy")}${t(locale, "action.continueSession")}</button>` : ""}
22137
22746
  ${run.log ? `<button type="button" class="btn" aria-expanded="false" onclick="toggleLog(${run.id},this)">${t(locale, "action.logs")}</button>` : ""}</div></td>
22138
- </tr>`;
22747
+ </tr>${log ? `<tr id="log-${run.id}" class="run-log-row" hidden><td class="run-log-cell" colspan="8">${log}</td></tr>` : ""}`;
22139
22748
  }).join("");
22140
22749
  const body = `
22141
22750
  <div class="stats-grid">
@@ -22145,7 +22754,7 @@ var init_web = __esm({
22145
22754
  ${statCard(runs.filter((run) => run.status === "running").length, t(locale, "stats.pageRunning"), "tone-blue", icon("activity"), "reveal-delay-2")}
22146
22755
  </div>
22147
22756
  <section class="panel reveal reveal-delay-2">${runs.length === 0 ? emptyState(t(locale, "empty.runs"), "") : `<div class="table-wrap"><table class="responsive-table"><thead><tr><th>${t(locale, "table.run")}</th><th>${t(locale, "table.task")}</th><th>${t(locale, "table.agent")}</th><th>${t(locale, "table.session")}</th><th>${t(locale, "table.status")}</th><th>${t(locale, "table.duration")}</th><th>${t(locale, "table.heartbeat")}</th><th>${t(locale, "table.actions")}</th></tr></thead><tbody>${rows}</tbody></table></div>`}</section>
22148
- ${logs.join("")}${pagination(locale, "/runs", page, totalPages, total)}`;
22757
+ ${pagination(locale, "/runs", page, totalPages, total)}`;
22149
22758
  return c.html(renderLayout({ locale, activeTab: "runs", body }));
22150
22759
  });
22151
22760
  app.get("/system", async (c) => {
@@ -22252,7 +22861,11 @@ var init_web = __esm({
22252
22861
  const task = await TaskService.getById(id);
22253
22862
  if (!task) return c.json({ error: "not found" }, 404);
22254
22863
  const runs = await TaskRunService.listByTaskId(id);
22255
- return c.json({ ...task, _runs: runs });
22864
+ return c.json({
22865
+ ...task,
22866
+ _resultPresentation: task.resultLog ? presentRunLog(task.resultLog) : null,
22867
+ _runs: runs
22868
+ });
22256
22869
  });
22257
22870
  app.get("/api/runs/:id", async (c) => {
22258
22871
  const id = parsePositiveInteger(c.req.param("id"));
@@ -22467,6 +23080,14 @@ import { dirname as dirname3, join as join3 } from "path";
22467
23080
  import { randomUUID } from "crypto";
22468
23081
  var DEFAULT_MAX_OUTPUT_CHARS = 64 * 1024;
22469
23082
  var FORBIDDEN_AGENT = "supertask-runner";
23083
+ function runCommandContext(executable, args, cwd) {
23084
+ return JSON.stringify({
23085
+ type: "supertask_command",
23086
+ executable,
23087
+ args,
23088
+ cwd
23089
+ });
23090
+ }
22470
23091
  function assertWorkerProcessIsolationSupported(platform = process.platform) {
22471
23092
  if (platform === "win32") {
22472
23093
  throw new Error(
@@ -22675,6 +23296,7 @@ var WorkerEngine = class {
22675
23296
  runId,
22676
23297
  launchIdentity,
22677
23298
  child,
23299
+ commandContext: runCommandContext(this.opencodeBin, args, task.cwd || process.cwd()),
22678
23300
  output: "",
22679
23301
  sessionId: null,
22680
23302
  timeoutTimer: null,
@@ -22839,7 +23461,7 @@ var WorkerEngine = class {
22839
23461
  const termination = entry.termination;
22840
23462
  if (termination?.kind === "shutdown") return;
22841
23463
  if (termination?.kind === "cancel") {
22842
- const output2 = entry.output.trim();
23464
+ const output2 = this.outputWithCommand(entry);
22843
23465
  const log2 = `${termination.message}${output2 ? `
22844
23466
  ${output2}` : ""}`;
22845
23467
  await TaskRunService.fail(entry.runId, log2);
@@ -22853,7 +23475,7 @@ ${output2}` : ""}`;
22853
23475
  }
22854
23476
  const currentRun = await TaskRunService.getById(entry.runId);
22855
23477
  if (!currentRun || currentRun.status !== "running") return;
22856
- const output = entry.output.trim();
23478
+ const output = this.outputWithCommand(entry);
22857
23479
  const log = failure ? `${failure}${output ? `
22858
23480
  ${output}` : ""}` : output;
22859
23481
  if (code === 0 && !failure) {
@@ -22989,6 +23611,11 @@ ${output}` : ""}` : output;
22989
23611
  const batchId = normalizeTaskBatchId(task.batchId);
22990
23612
  if (batchId) this.activeBatchIds.delete(batchId);
22991
23613
  }
23614
+ outputWithCommand(entry) {
23615
+ const output = entry.output.trim();
23616
+ return `${entry.commandContext}${output ? `
23617
+ ${output}` : ""}`;
23618
+ }
22992
23619
  resolveModel(taskModel) {
22993
23620
  if (!taskModel || taskModel === "default") return null;
22994
23621
  return taskModel;