opencode-supertask 0.1.34 → 0.1.35

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";
@@ -20566,7 +20676,8 @@ function icon(name, className = "icon") {
20566
20676
  check: '<path d="m5 12 4 4L19 6"/>',
20567
20677
  alert: '<path d="M12 3 2.8 19h18.4L12 3Z"/><path d="M12 9v4M12 17h.01"/>',
20568
20678
  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"/>'
20679
+ 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"/>',
20680
+ 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
20681
  };
20571
20682
  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
20683
  }
@@ -20621,7 +20732,20 @@ function clientMessages(locale) {
20621
20732
  "feedback.restartTimeout",
20622
20733
  "template.createTitle",
20623
20734
  "template.editTitle",
20624
- "filter.noResults"
20735
+ "filter.noResults",
20736
+ "catalog.chooseProject",
20737
+ "catalog.defaultModel",
20738
+ "catalog.defaultProvider",
20739
+ "catalog.loading",
20740
+ "catalog.loaded",
20741
+ "catalog.failed",
20742
+ "catalog.primary",
20743
+ "catalog.subagent",
20744
+ "catalog.all",
20745
+ "directory.empty",
20746
+ "feedback.commandCopied",
20747
+ "action.showHidden",
20748
+ "action.hideHidden"
20625
20749
  ];
20626
20750
  return Object.fromEntries(keys.map((key) => [key, t(locale, key)]));
20627
20751
  }
@@ -20689,6 +20813,11 @@ function renderLayout(options) {
20689
20813
  <footer>${t(locale, "app.footer")}</footer>
20690
20814
  </div>
20691
20815
  <div id="toast-region" class="toast-region" role="status" aria-live="polite"></div>
20816
+ <dialog id="directory-dialog" class="directory-dialog">
20817
+ <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>
20818
+ <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>
20819
+ <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>
20820
+ </dialog>
20692
20821
  <dialog id="detail-dialog">
20693
20822
  <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
20823
  <div class="dialog-body"><pre id="detail-content" class="json-view"></pre></div>
@@ -20727,21 +20856,38 @@ function renderLayout(options) {
20727
20856
  const showDetail=id=>showRecord('/api/tasks/'+id);const showRunDetail=id=>showRecord('/api/runs/'+id);const showTemplateDetail=id=>showRecord('/api/templates/'+id);
20728
20857
  async function copyDetails(){try{await navigator.clipboard.writeText(document.getElementById('detail-content').textContent);showToast(text('details.copySuccess'))}catch{showToast(text('feedback.copyFailed'),'error')}}
20729
20858
  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')}}
20859
+ 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
20860
  function taskField(name){return document.getElementById('task-'+name)}
20861
+ function templateField(name){return document.getElementById('template-'+name)}
20731
20862
  function taskProjects(){const node=document.getElementById('task-project-data');if(!node)return {};try{return JSON.parse(node.textContent||'{}')}catch{return {}}}
20732
20863
  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'}
20864
+ const catalogTimers={};const catalogRequests={};const catalogModels={};
20865
+ function catalogField(prefix,name){return document.getElementById(prefix+'-'+name)}
20866
+ 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='';}
20867
+ function appendCurrentOption(select,value){if(!value||[...select.options].some(option=>option.value===value))return;select.appendChild(new Option(value,value));}
20868
+ function modelProvider(value){const slash=value.indexOf('/');return slash>0?value.slice(0,slash):''}
20869
+ 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}
20870
+ 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}}}
20871
+ function scheduleCatalogLoad(prefix){clearTimeout(catalogTimers[prefix]);catalogTimers[prefix]=setTimeout(()=>loadCatalog(prefix),450)}
20872
+ let directoryTargetId='';let directoryCurrent='';let directoryEntries=[];let directoryShowHidden=false;
20873
+ 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)}}
20874
+ 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}}
20875
+ document.getElementById('directory-hidden').onclick=()=>{directoryShowHidden=!directoryShowHidden;renderDirectoryEntries()};
20876
+ 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('')}
20877
+ 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)};
20878
+ 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'}
20879
+ 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}
20880
+ 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)}
20881
+ 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)}
20882
+ 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')}}
20883
+ 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
20884
  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}}
20885
+ 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
20886
  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
20887
  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}}
20888
+ 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)}
20889
+ 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')}}
20890
+ 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
20891
  async function enableTmpl(id){try{await readJson(await fetch('/api/templates/'+id+'/enable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
20746
20892
  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
20893
  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')}}
@@ -20801,6 +20947,13 @@ var init_ui = __esm({
20801
20947
  "action.updateTask": "\u4FDD\u5B58\u4FEE\u6539",
20802
20948
  "action.createTemplate": "\u65B0\u5EFA\u5B9A\u65F6\u4EFB\u52A1",
20803
20949
  "action.saveTemplate": "\u4FDD\u5B58\u5B9A\u65F6\u4EFB\u52A1",
20950
+ "action.chooseFolder": "\u9009\u62E9\u6587\u4EF6\u5939",
20951
+ "action.chooseThisFolder": "\u9009\u62E9\u5F53\u524D\u6587\u4EF6\u5939",
20952
+ "action.home": "\u4E3B\u76EE\u5F55",
20953
+ "action.up": "\u4E0A\u4E00\u7EA7",
20954
+ "action.copyCommand": "\u590D\u5236\u547D\u4EE4",
20955
+ "action.showHidden": "\u663E\u793A\u9690\u85CF\u6587\u4EF6\u5939",
20956
+ "action.hideHidden": "\u9690\u85CF\u9690\u85CF\u6587\u4EF6\u5939",
20804
20957
  "status.pending": "\u5F85\u6267\u884C",
20805
20958
  "status.running": "\u8FD0\u884C\u4E2D",
20806
20959
  "status.done": "\u5DF2\u5B8C\u6210",
@@ -20867,6 +21020,15 @@ var init_ui = __esm({
20867
21020
  "schedule.hours": "{count} \u5C0F\u65F6",
20868
21021
  "schedule.days": "{count} \u5929",
20869
21022
  "schedule.overdue": "\u5DF2\u5230\u671F",
21023
+ "duration.unit": "\u65F6\u95F4\u5355\u4F4D",
21024
+ "duration.seconds": "\u79D2",
21025
+ "duration.minutes": "\u5206\u949F",
21026
+ "duration.hours": "\u5C0F\u65F6",
21027
+ "duration.days": "\u5929",
21028
+ "duration.systemDefault": "\u4F7F\u7528 Gateway \u9ED8\u8BA4\u8D85\u65F6",
21029
+ "duration.immediate": "\u7ACB\u5373\u91CD\u8BD5",
21030
+ "duration.custom": "\u81EA\u5B9A\u4E49\u2026",
21031
+ "duration.every": "\u6BCF {duration}",
20870
21032
  "system.worker": "\u4EFB\u52A1\u6267\u884C",
20871
21033
  "system.scheduler": "\u5B9A\u65F6\u4EFB\u52A1\u670D\u52A1",
20872
21034
  "system.watchdog": "\u8FD0\u884C\u76D1\u63A7",
@@ -20914,6 +21076,9 @@ var init_ui = __esm({
20914
21076
  "template.interval": "\u6267\u884C\u95F4\u9694",
20915
21077
  "template.runAt": "\u6267\u884C\u65F6\u95F4",
20916
21078
  "template.durationHint": "\u652F\u6301 30s\u30015min\u30011h\u30012d",
21079
+ "template.intervalHint": "\u76F4\u63A5\u9009\u62E9\u5E38\u7528\u9891\u7387\uFF1B\u53EA\u6709\u7279\u6B8A\u9700\u6C42\u624D\u9700\u8981\u81EA\u5B9A\u4E49\u3002",
21080
+ "template.retryBackoffHint": "\u4E00\u6B21\u5931\u8D25\u540E\uFF0C\u7B49\u5F85\u591A\u4E45\u518D\u91CD\u8BD5\u3002",
21081
+ "template.timeoutHint": "\u7559\u7A7A\u8868\u793A\u4F7F\u7528 Gateway \u7684\u9ED8\u8BA4\u4EFB\u52A1\u8D85\u65F6\u3002",
20917
21082
  "template.advanced": "\u66F4\u591A\u6267\u884C\u8BBE\u7F6E",
20918
21083
  "template.category": "\u5206\u7C7B",
20919
21084
  "template.batchId": "\u6279\u6B21 ID",
@@ -20938,6 +21103,28 @@ var init_ui = __esm({
20938
21103
  "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
21104
  "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
21105
  "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",
21106
+ "catalog.chooseProject": "\u8BF7\u5148\u9009\u62E9\u9879\u76EE\u76EE\u5F55",
21107
+ "catalog.defaultModel": "\u8DDF\u968F Agent / OpenCode \u9ED8\u8BA4\u6A21\u578B",
21108
+ "catalog.defaultProvider": "\u9ED8\u8BA4\u6A21\u578B",
21109
+ "catalog.provider": "\u6A21\u578B\u63D0\u4F9B\u5546",
21110
+ "catalog.model": "\u5177\u4F53\u6A21\u578B",
21111
+ "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",
21112
+ "catalog.agentHint": "\u6765\u81EA\u5F53\u524D\u9879\u76EE\u7684 opencode agent list\u3002",
21113
+ "catalog.loading": "\u6B63\u5728\u8BFB\u53D6\u6B64\u9879\u76EE\u53EF\u7528\u7684 Agent \u548C\u6A21\u578B\u2026",
21114
+ "catalog.loaded": "\u5DF2\u4ECE\u672C\u673A OpenCode \u8BFB\u53D6 {agents} \u4E2A Agent\u3001{models} \u4E2A\u6A21\u578B\u3002",
21115
+ "catalog.failed": "\u65E0\u6CD5\u8BFB\u53D6\u6B64\u9879\u76EE\u7684 OpenCode \u914D\u7F6E\uFF1A{error}",
21116
+ "catalog.primary": "\u4E3B Agent",
21117
+ "catalog.subagent": "\u5B50 Agent",
21118
+ "catalog.all": "\u901A\u7528 Agent",
21119
+ "directory.title": "\u9009\u62E9\u9879\u76EE\u76EE\u5F55",
21120
+ "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",
21121
+ "directory.empty": "\u8FD9\u4E2A\u6587\u4EF6\u5939\u4E2D\u6CA1\u6709\u5B50\u6587\u4EF6\u5939",
21122
+ "logs.command": "\u5B9E\u9645\u6267\u884C\u547D\u4EE4",
21123
+ "logs.output": "Agent \u8F93\u51FA",
21124
+ "logs.error": "\u5931\u8D25\u539F\u56E0",
21125
+ "logs.tools": "\u5DE5\u5177\u8C03\u7528",
21126
+ "logs.raw": "\u67E5\u770B\u539F\u59CB\u6267\u884C\u65E5\u5FD7",
21127
+ "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
21128
  "theme.label": "\u4E3B\u9898",
20942
21129
  "theme.system": "\u8DDF\u968F\u7CFB\u7EDF",
20943
21130
  "theme.light": "\u6D45\u8272",
@@ -20975,6 +21162,7 @@ var init_ui = __esm({
20975
21162
  "feedback.templateUpdated": "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u66F4\u65B0",
20976
21163
  "feedback.configSaved": "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58",
20977
21164
  "feedback.sessionCommandCopied": "\u4F1A\u8BDD\u547D\u4EE4\u5DF2\u590D\u5236\uFF0C\u53EF\u76F4\u63A5\u7C98\u8D34\u5230\u7EC8\u7AEF\u8FD0\u884C",
21165
+ "feedback.commandCopied": "\u6267\u884C\u547D\u4EE4\u5DF2\u590D\u5236",
20978
21166
  "feedback.restarting": "Gateway \u6B63\u5728\u91CD\u542F\uFF0C\u8BF7\u7A0D\u5019\u2026",
20979
21167
  "feedback.restartTimeout": "Gateway \u5C1A\u672A\u6062\u590D\uFF0C\u8BF7\u7A0D\u540E\u5237\u65B0\u6216\u68C0\u67E5 PM2 \u72B6\u6001",
20980
21168
  "feedback.databaseCleared": "\u6570\u636E\u5E93\u5DF2\u6E05\u7A7A\uFF0C\u5907\u4EFD\u4F4D\u4E8E\uFF1A{path}",
@@ -21023,6 +21211,13 @@ var init_ui = __esm({
21023
21211
  "action.updateTask": "Save changes",
21024
21212
  "action.createTemplate": "New scheduled task",
21025
21213
  "action.saveTemplate": "Save scheduled task",
21214
+ "action.chooseFolder": "Choose folder",
21215
+ "action.chooseThisFolder": "Choose this folder",
21216
+ "action.home": "Home",
21217
+ "action.up": "Up",
21218
+ "action.copyCommand": "Copy command",
21219
+ "action.showHidden": "Show hidden folders",
21220
+ "action.hideHidden": "Hide hidden folders",
21026
21221
  "status.pending": "Pending",
21027
21222
  "status.running": "Running",
21028
21223
  "status.done": "Done",
@@ -21089,6 +21284,15 @@ var init_ui = __esm({
21089
21284
  "schedule.hours": "{count} hr",
21090
21285
  "schedule.days": "{count} days",
21091
21286
  "schedule.overdue": "Overdue",
21287
+ "duration.unit": "Time unit",
21288
+ "duration.seconds": "seconds",
21289
+ "duration.minutes": "minutes",
21290
+ "duration.hours": "hours",
21291
+ "duration.days": "days",
21292
+ "duration.systemDefault": "Use Gateway default timeout",
21293
+ "duration.immediate": "Retry immediately",
21294
+ "duration.custom": "Custom\u2026",
21295
+ "duration.every": "Every {duration}",
21092
21296
  "system.worker": "Task execution",
21093
21297
  "system.scheduler": "Scheduled task service",
21094
21298
  "system.watchdog": "Runtime monitor",
@@ -21136,6 +21340,9 @@ var init_ui = __esm({
21136
21340
  "template.interval": "Interval",
21137
21341
  "template.runAt": "Run at",
21138
21342
  "template.durationHint": "Supports 30s, 5min, 1h, 2d",
21343
+ "template.intervalHint": "Choose a common frequency directly; customize only when necessary.",
21344
+ "template.retryBackoffHint": "How long to wait after a failure before retrying.",
21345
+ "template.timeoutHint": "Leave blank to use the Gateway default task timeout.",
21139
21346
  "template.advanced": "More execution settings",
21140
21347
  "template.category": "Category",
21141
21348
  "template.batchId": "Batch ID",
@@ -21160,6 +21367,28 @@ var init_ui = __esm({
21160
21367
  "task.batchHint": "Tasks with the same non-empty batch ID run serially. Blank removes the batch constraint; global concurrency and dependencies still apply.",
21161
21368
  "task.projectExisting": "This project has {total} tasks: {running} running, {pending} queued, and {failed} with issues.",
21162
21369
  "task.projectNew": "This is a new project group. It appears in the project list after creation.",
21370
+ "catalog.chooseProject": "Choose a project directory first",
21371
+ "catalog.defaultModel": "Use the Agent / OpenCode default model",
21372
+ "catalog.defaultProvider": "Default model",
21373
+ "catalog.provider": "Model provider",
21374
+ "catalog.model": "Model",
21375
+ "catalog.modelHint": "Choose a provider, then a model returned by opencode models for this project. Default does not pass -m.",
21376
+ "catalog.agentHint": "Loaded from opencode agent list for this project.",
21377
+ "catalog.loading": "Loading Agents and models available to this project\u2026",
21378
+ "catalog.loaded": "Loaded {agents} Agents and {models} models from local OpenCode.",
21379
+ "catalog.failed": "Could not load this project\u2019s OpenCode configuration: {error}",
21380
+ "catalog.primary": "primary Agent",
21381
+ "catalog.subagent": "subagent",
21382
+ "catalog.all": "general Agent",
21383
+ "directory.title": "Choose project directory",
21384
+ "directory.subtitle": "OpenCode runs in this directory, and its project-specific Agents and models are loaded.",
21385
+ "directory.empty": "This folder has no subfolders",
21386
+ "logs.command": "Executed command",
21387
+ "logs.output": "Agent output",
21388
+ "logs.error": "Failure reason",
21389
+ "logs.tools": "Tool calls",
21390
+ "logs.raw": "View raw execution log",
21391
+ "logs.noText": "This run produced no displayable text. Inspect the raw log for details.",
21163
21392
  "theme.label": "Theme",
21164
21393
  "theme.system": "System",
21165
21394
  "theme.light": "Light",
@@ -21197,6 +21426,7 @@ var init_ui = __esm({
21197
21426
  "feedback.templateUpdated": "Scheduled task updated",
21198
21427
  "feedback.configSaved": "Settings saved",
21199
21428
  "feedback.sessionCommandCopied": "Session command copied. Paste it into a terminal to continue.",
21429
+ "feedback.commandCopied": "Execution command copied",
21200
21430
  "feedback.restarting": "Gateway is restarting\u2026",
21201
21431
  "feedback.restartTimeout": "Gateway has not recovered yet. Refresh later or check PM2 status.",
21202
21432
  "feedback.databaseCleared": "Database cleared. Backup: {path}",
@@ -21407,6 +21637,18 @@ var init_ui = __esm({
21407
21637
  .danger-card h2 .icon { width:17px; height:17px; }
21408
21638
  .danger-card p { max-width:800px; margin:0 0 14px; color:var(--text-2); font-size:12px; }
21409
21639
  .log-panel { margin:12px 0; animation:reveal .18s ease both; }
21640
+ .log-content { display:grid; gap:14px; padding:16px; }
21641
+ .log-section-head { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:7px; }
21642
+ .run-command,.run-output,.run-error,.run-tools { min-width:0; }
21643
+ .run-command strong,.run-output>strong,.run-error>strong,.run-tools>strong { display:block; margin-bottom:7px; font-size:12px; }
21644
+ .command-cwd { margin-bottom:6px; color:var(--text-3); font-family:"SFMono-Regular",Consolas,monospace; font-size:10px; overflow-wrap:anywhere; }
21645
+ .run-command pre,.run-output pre,.run-error pre { margin:0; padding:12px; overflow:auto; border:1px solid var(--border); border-radius:9px;
21646
+ color:var(--text-2); background:var(--surface-2); font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; white-space:pre-wrap; overflow-wrap:anywhere; }
21647
+ .run-output pre { color:var(--text); font-family:inherit; font-size:13px; line-height:1.65; }
21648
+ .run-error pre { color:var(--red); border-color:color-mix(in srgb,var(--red) 28%,var(--border)); background:var(--red-soft); }
21649
+ .raw-log { padding-top:12px; border-top:1px solid var(--border); }
21650
+ .raw-log summary { color:var(--text-2); cursor:pointer; font-size:12px; font-weight:650; }
21651
+ .raw-log .log-box { margin-top:10px; border-radius:9px; }
21410
21652
  .log-box { max-height:360px; overflow:auto; padding:16px; color:var(--text-2); background:#0b1018; font-family:"SFMono-Regular",Consolas,monospace;
21411
21653
  font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
21412
21654
  :root[data-theme="light"] .log-box { color:#dbe5f3; }
@@ -21425,12 +21667,33 @@ var init_ui = __esm({
21425
21667
  .form-field-wide { grid-column:1 / -1; }
21426
21668
  .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
21669
  outline:none; color:var(--text); background:var(--surface-2); font-weight:450; }
21670
+ .field-action { display:flex; align-items:stretch; gap:7px; }
21671
+ .field-action input { min-width:0; flex:1; }
21672
+ .field-action .btn { flex:0 0 auto; white-space:nowrap; }
21673
+ .model-selector { display:grid; grid-template-columns:minmax(120px,.75fr) minmax(0,1.25fr); gap:7px; }
21674
+ .duration-picker { display:grid; gap:7px; }
21675
+ .duration-control { display:grid; grid-template-columns:minmax(0,1fr) 112px; gap:7px; }
21676
+ .duration-control input,.duration-control select { min-width:0; }
21428
21677
  .form-field textarea { resize:vertical; line-height:1.5; }
21429
21678
  .form-field input:focus,.form-field select:focus,.form-field textarea:focus { border-color:var(--primary); box-shadow:var(--focus); background:var(--surface); }
21430
21679
  .form-field small { color:var(--text-3); font-size:10px; font-weight:450; }
21431
21680
  .advanced-fields { margin-top:18px; padding-top:14px; border-top:1px solid var(--border); }
21432
21681
  .advanced-fields summary { margin-bottom:14px; color:var(--text-2); cursor:pointer; font-size:12px; font-weight:700; }
21433
21682
  .form-note { margin:16px 0 0; color:var(--text-3); font-size:11px; }
21683
+ .catalog-status[data-state="loading"] { color:var(--blue); }
21684
+ .catalog-status[data-state="ready"] { color:var(--green); }
21685
+ .catalog-status[data-state="error"] { color:var(--red); }
21686
+ .directory-dialog { width:min(720px,calc(100% - 32px)); }
21687
+ .directory-toolbar { display:flex; align-items:center; gap:8px; margin-bottom:12px; }
21688
+ .directory-path { min-width:0; flex:1; padding:9px 11px; overflow:hidden; border:1px solid var(--border); border-radius:9px;
21689
+ color:var(--text-2); background:var(--surface-2); font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; text-overflow:ellipsis; white-space:nowrap; }
21690
+ .directory-list { min-height:260px; max-height:48vh; display:grid; align-content:start; gap:6px; overflow:auto; }
21691
+ .directory-item { width:100%; min-height:40px; display:flex; align-items:center; gap:9px; padding:0 11px; border:1px solid transparent; border-radius:9px;
21692
+ 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; }
21693
+ .directory-item:hover { color:var(--text); border-color:var(--border); background:var(--surface-2); }
21694
+ .directory-item:active { transform:scale(.99); }
21695
+ .directory-item .icon { width:17px; height:17px; color:var(--primary); flex:0 0 auto; }
21696
+ .directory-empty { min-height:220px; display:grid; place-items:center; color:var(--text-3); }
21434
21697
  .json-view { min-height:160px; margin:0; padding:15px; overflow:auto; border:1px solid var(--border); border-radius:10px; color:var(--text-2);
21435
21698
  background:var(--surface-2); font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
21436
21699
  .confirm-copy { color:var(--text-2); margin:0; }
@@ -21499,6 +21762,9 @@ var init_ui = __esm({
21499
21762
  .project-grid { grid-template-columns:1fr; }
21500
21763
  .template-form-grid { grid-template-columns:1fr; }
21501
21764
  .form-field-wide { grid-column:auto; }
21765
+ .field-action { align-items:stretch; flex-direction:column; }
21766
+ .field-action .btn { width:100%; }
21767
+ .model-selector { grid-template-columns:1fr; }
21502
21768
  }
21503
21769
  @media (max-width:520px) {
21504
21770
  .stats-grid,.stats-grid.three { grid-template-columns:1fr 1fr; }
@@ -21528,11 +21794,21 @@ __export(web_exports, {
21528
21794
  dashboardApp: () => dashboardApp,
21529
21795
  default: () => web_default,
21530
21796
  isSafeDashboardRestartTarget: () => isSafeDashboardRestartTarget,
21797
+ presentRunLog: () => presentRunLog,
21531
21798
  resolveDashboardConfigState: () => resolveDashboardConfigState,
21532
21799
  setDashboardRuntimeConfig: () => setDashboardRuntimeConfig
21533
21800
  });
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";
21801
+ import {
21802
+ existsSync as existsSync7,
21803
+ mkdirSync as mkdirSync4,
21804
+ readFileSync as readFileSync4,
21805
+ readdirSync,
21806
+ renameSync as renameSync2,
21807
+ statSync as statSync4,
21808
+ writeFileSync as writeFileSync3
21809
+ } from "fs";
21810
+ import { homedir as homedir4 } from "os";
21811
+ import { basename as basename3, dirname as dirname7, join as join6 } from "path";
21536
21812
  function setDashboardRuntimeConfig(config) {
21537
21813
  runtimeConfig = config === null ? null : structuredClone(config);
21538
21814
  }
@@ -21573,6 +21849,23 @@ function parsePositiveInteger(value) {
21573
21849
  function parseTaskStatus(value) {
21574
21850
  return TASK_STATUSES.has(value) ? value : null;
21575
21851
  }
21852
+ function listChildDirectories(path) {
21853
+ return readdirSync(path, { withFileTypes: true }).flatMap((entry) => {
21854
+ const entryPath = join6(path, entry.name);
21855
+ if (entry.isDirectory()) return [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }];
21856
+ if (!entry.isSymbolicLink()) return [];
21857
+ try {
21858
+ return statSync4(entryPath).isDirectory() ? [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }] : [];
21859
+ } catch {
21860
+ return [];
21861
+ }
21862
+ }).sort((left, right) => {
21863
+ const leftHidden = left.name.startsWith(".");
21864
+ const rightHidden = right.name.startsWith(".");
21865
+ if (leftHidden !== rightHidden) return leftHidden ? 1 : -1;
21866
+ return left.name.localeCompare(right.name);
21867
+ });
21868
+ }
21576
21869
  function parseTaskPayload(value) {
21577
21870
  if (!value || typeof value !== "object" || Array.isArray(value)) {
21578
21871
  throw new Error("\u8BF7\u6C42\u5185\u5BB9\u5FC5\u987B\u662F\u5BF9\u8C61");
@@ -21729,6 +22022,66 @@ function formatDuration(startAt, endAt) {
21729
22022
  if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
21730
22023
  return `${Math.floor(seconds / 3600)}h ${Math.floor(seconds % 3600 / 60)}m`;
21731
22024
  }
22025
+ function recordValue(value) {
22026
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
22027
+ }
22028
+ function shellQuote(value) {
22029
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value;
22030
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
22031
+ }
22032
+ function presentRunLog(log) {
22033
+ let command = null;
22034
+ const textParts = [];
22035
+ const errors = [];
22036
+ const tools = [];
22037
+ for (const line of log.split("\n")) {
22038
+ const trimmed = line.trim();
22039
+ if (!trimmed) continue;
22040
+ let parsed = null;
22041
+ try {
22042
+ parsed = recordValue(JSON.parse(trimmed));
22043
+ } catch {
22044
+ errors.push(line);
22045
+ continue;
22046
+ }
22047
+ if (!parsed) continue;
22048
+ if (parsed.type === "supertask_command") {
22049
+ const executable = typeof parsed.executable === "string" ? parsed.executable : null;
22050
+ const cwd = typeof parsed.cwd === "string" ? parsed.cwd : null;
22051
+ const args = Array.isArray(parsed.args) && parsed.args.every((item) => typeof item === "string") ? parsed.args : null;
22052
+ if (executable && cwd && args) {
22053
+ command = {
22054
+ cwd,
22055
+ command: `cd ${shellQuote(cwd)} && ${[executable, ...args].map(shellQuote).join(" ")}`
22056
+ };
22057
+ }
22058
+ continue;
22059
+ }
22060
+ const part = recordValue(parsed.part);
22061
+ const eventType = typeof parsed.type === "string" ? parsed.type : "";
22062
+ const partType = typeof part?.type === "string" ? part.type : "";
22063
+ const text2 = typeof part?.text === "string" ? part.text : typeof parsed.text === "string" ? parsed.text : null;
22064
+ if (text2 && (eventType === "text" || partType === "text")) textParts.push(text2);
22065
+ const tool = typeof part?.tool === "string" ? part.tool : typeof parsed.tool === "string" ? parsed.tool : null;
22066
+ if (tool && (eventType === "tool_use" || partType === "tool")) tools.push(tool);
22067
+ const error = typeof parsed.error === "string" ? parsed.error : typeof part?.error === "string" ? part.error : null;
22068
+ if (error) errors.push(error);
22069
+ }
22070
+ return {
22071
+ command,
22072
+ text: textParts.join("\n").trim(),
22073
+ errors: [...new Set(errors)],
22074
+ tools
22075
+ };
22076
+ }
22077
+ function renderRunLog(runId, taskName, log, locale) {
22078
+ const presentation = presentRunLog(log);
22079
+ 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>` : "";
22080
+ const errors = presentation.errors.length > 0 ? `<div class="run-error"><strong>${t(locale, "logs.error")}</strong><pre>${esc(presentation.errors.join("\n"))}</pre></div>` : "";
22081
+ 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>` : "";
22082
+ const output = `<div class="run-output"><strong>${t(locale, "logs.output")}</strong><pre>${esc(presentation.text || t(locale, "logs.noText"))}</pre></div>`;
22083
+ return `<section id="log-${runId}" class="panel log-panel" hidden><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>`;
22084
+ }
21732
22085
  function esc(value) {
21733
22086
  if (!value) return "";
21734
22087
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -21760,6 +22113,25 @@ function emptyState(title, hint, code = "") {
21760
22113
  return `<div class="empty-state"><div><div class="empty-icon">${icon("inbox")}</div>
21761
22114
  <h3>${title}</h3><p>${hint}</p>${code ? `<code>${code}</code>` : ""}</div></div>`;
21762
22115
  }
22116
+ function durationControl(locale, id, value, kind) {
22117
+ const units = [
22118
+ { value: "s", label: t(locale, "duration.seconds") },
22119
+ { value: "min", label: t(locale, "duration.minutes") },
22120
+ { value: "h", label: t(locale, "duration.hours") },
22121
+ { value: "d", label: t(locale, "duration.days") }
22122
+ ];
22123
+ const values = kind === "interval" ? [3e5, 9e5, 18e5, 36e5, 216e5, 432e5, 864e5] : kind === "retry" ? [0, 1e4, 3e4, 6e4, 3e5] : [3e5, 9e5, 18e5, 36e5, 72e5, 144e5];
22124
+ const presets = [
22125
+ ...kind === "timeout" ? [{ value: "", label: t(locale, "duration.systemDefault") }] : [],
22126
+ ...values.map((milliseconds) => ({
22127
+ value: String(milliseconds),
22128
+ label: milliseconds === 0 ? t(locale, "duration.immediate") : kind === "interval" ? t(locale, "duration.every", { duration: formatInterval(milliseconds, locale) }) : formatInterval(milliseconds, locale)
22129
+ })),
22130
+ { value: "custom", label: t(locale, "duration.custom") }
22131
+ ];
22132
+ const selected = value === null ? "" : String(value);
22133
+ 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>`;
22134
+ }
21763
22135
  function formatInterval(milliseconds, locale) {
21764
22136
  const formatter = new Intl.NumberFormat(locale === "en" ? "en-US" : "zh-CN", {
21765
22137
  maximumFractionDigits: 1
@@ -21788,6 +22160,8 @@ var init_web = __esm({
21788
22160
  init_drizzle_orm();
21789
22161
  init_db2();
21790
22162
  init_duration();
22163
+ init_opencode_catalog();
22164
+ init_task_working_directory();
21791
22165
  init_database_maintenance_service();
21792
22166
  init_task_run_service();
21793
22167
  init_task_service();
@@ -21841,6 +22215,33 @@ var init_web = __esm({
21841
22215
  const health = getGatewayHealth();
21842
22216
  return c.json(health, health.status === "ok" ? 200 : 503);
21843
22217
  });
22218
+ app.get("/api/filesystem/directories", (c) => {
22219
+ const requestedPath = c.req.query("path")?.trim() || homedir4();
22220
+ try {
22221
+ validateTaskWorkingDirectory(requestedPath);
22222
+ return c.json({
22223
+ path: requestedPath,
22224
+ parent: dirname7(requestedPath),
22225
+ home: homedir4(),
22226
+ directories: listChildDirectories(requestedPath)
22227
+ });
22228
+ } catch (error) {
22229
+ return c.json({
22230
+ error: error instanceof Error ? error.message : String(error)
22231
+ }, 400);
22232
+ }
22233
+ });
22234
+ app.get("/api/opencode/catalog", async (c) => {
22235
+ const cwd = c.req.query("cwd")?.trim();
22236
+ if (!cwd) return c.json({ error: "cwd \u4E0D\u80FD\u4E3A\u7A7A" }, 400);
22237
+ try {
22238
+ return c.json(await loadOpenCodeCatalog(cwd));
22239
+ } catch (error) {
22240
+ return c.json({
22241
+ error: error instanceof Error ? error.message : String(error)
22242
+ }, 400);
22243
+ }
22244
+ });
21844
22245
  app.get("/", async (c) => {
21845
22246
  const locale = resolveLocale(c);
21846
22247
  const page = parsePositiveInteger(c.req.query("page") || "1");
@@ -21985,13 +22386,14 @@ var init_web = __esm({
21985
22386
  <div class="dialog-body">
21986
22387
  <div class="template-form-grid">
21987
22388
  <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>
22389
+ <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
22390
  <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>
22391
+ <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>
22392
+ <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
22393
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="task-prompt" rows="6" required></textarea></label>
21993
22394
  </div>
21994
22395
  <p id="task-project-status" class="form-note"></p>
22396
+ <p id="task-catalog-status" class="form-note catalog-status"></p>
21995
22397
  <details class="advanced-fields">
21996
22398
  <summary>${t(locale, "template.advanced")}</summary>
21997
22399
  <div class="template-form-grid">
@@ -22000,8 +22402,8 @@ var init_web = __esm({
22000
22402
  <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
22403
  <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
22404
  <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>
22405
+ <label class="form-field"><span>${t(locale, "template.retryBackoff")}</span>${durationControl(locale, "task-retry-backoff", 3e4, "retry")}<small>${t(locale, "template.retryBackoffHint")}</small></label>
22406
+ <label class="form-field"><span>${t(locale, "template.timeout")}</span>${durationControl(locale, "task-timeout", null, "timeout")}<small>${t(locale, "template.timeoutHint")}</small></label>
22005
22407
  </div>
22006
22408
  </details>
22007
22409
  </div>
@@ -22061,15 +22463,16 @@ var init_web = __esm({
22061
22463
  <div class="dialog-body">
22062
22464
  <div class="template-form-grid">
22063
22465
  <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>
22466
+ <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>
22467
+ <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>
22468
+ <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
22469
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="template-prompt" rows="6" required></textarea></label>
22068
22470
  <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
22471
  <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>
22472
+ <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
22473
  <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
22474
  </div>
22475
+ <p id="template-catalog-status" class="form-note catalog-status"></p>
22073
22476
  <details class="advanced-fields">
22074
22477
  <summary>${t(locale, "template.advanced")}</summary>
22075
22478
  <div class="template-form-grid">
@@ -22079,8 +22482,8 @@ var init_web = __esm({
22079
22482
  <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
22483
  <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
22484
  <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>
22485
+ <label class="form-field"><span>${t(locale, "template.retryBackoff")}</span>${durationControl(locale, "template-retry-backoff", 3e4, "retry")}<small>${t(locale, "template.retryBackoffHint")}</small></label>
22486
+ <label class="form-field"><span>${t(locale, "template.timeout")}</span>${durationControl(locale, "template-timeout", null, "timeout")}<small>${t(locale, "template.timeoutHint")}</small></label>
22084
22487
  </div>
22085
22488
  </details>
22086
22489
  <p class="form-note">${t(locale, "template.futureOnly")}</p>
@@ -22122,7 +22525,7 @@ var init_web = __esm({
22122
22525
  const status = safeStatus(run.status);
22123
22526
  const resumable = isValidSessionId(run.sessionId);
22124
22527
  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>`);
22528
+ logs.push(renderRunLog(run.id, run.taskName, run.log, locale));
22126
22529
  }
22127
22530
  return `<tr>
22128
22531
  <td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td>
@@ -22467,6 +22870,14 @@ import { dirname as dirname3, join as join3 } from "path";
22467
22870
  import { randomUUID } from "crypto";
22468
22871
  var DEFAULT_MAX_OUTPUT_CHARS = 64 * 1024;
22469
22872
  var FORBIDDEN_AGENT = "supertask-runner";
22873
+ function runCommandContext(executable, args, cwd) {
22874
+ return JSON.stringify({
22875
+ type: "supertask_command",
22876
+ executable,
22877
+ args,
22878
+ cwd
22879
+ });
22880
+ }
22470
22881
  function assertWorkerProcessIsolationSupported(platform = process.platform) {
22471
22882
  if (platform === "win32") {
22472
22883
  throw new Error(
@@ -22675,6 +23086,7 @@ var WorkerEngine = class {
22675
23086
  runId,
22676
23087
  launchIdentity,
22677
23088
  child,
23089
+ commandContext: runCommandContext(this.opencodeBin, args, task.cwd || process.cwd()),
22678
23090
  output: "",
22679
23091
  sessionId: null,
22680
23092
  timeoutTimer: null,
@@ -22839,7 +23251,7 @@ var WorkerEngine = class {
22839
23251
  const termination = entry.termination;
22840
23252
  if (termination?.kind === "shutdown") return;
22841
23253
  if (termination?.kind === "cancel") {
22842
- const output2 = entry.output.trim();
23254
+ const output2 = this.outputWithCommand(entry);
22843
23255
  const log2 = `${termination.message}${output2 ? `
22844
23256
  ${output2}` : ""}`;
22845
23257
  await TaskRunService.fail(entry.runId, log2);
@@ -22853,7 +23265,7 @@ ${output2}` : ""}`;
22853
23265
  }
22854
23266
  const currentRun = await TaskRunService.getById(entry.runId);
22855
23267
  if (!currentRun || currentRun.status !== "running") return;
22856
- const output = entry.output.trim();
23268
+ const output = this.outputWithCommand(entry);
22857
23269
  const log = failure ? `${failure}${output ? `
22858
23270
  ${output}` : ""}` : output;
22859
23271
  if (code === 0 && !failure) {
@@ -22989,6 +23401,11 @@ ${output}` : ""}` : output;
22989
23401
  const batchId = normalizeTaskBatchId(task.batchId);
22990
23402
  if (batchId) this.activeBatchIds.delete(batchId);
22991
23403
  }
23404
+ outputWithCommand(entry) {
23405
+ const output = entry.output.trim();
23406
+ return `${entry.commandContext}${output ? `
23407
+ ${output}` : ""}`;
23408
+ }
22992
23409
  resolveModel(taskModel) {
22993
23410
  if (!taskModel || taskModel === "default") return null;
22994
23411
  return taskModel;