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.
package/dist/cli/index.js CHANGED
@@ -4506,7 +4506,7 @@ var init_sql = __esm({
4506
4506
  return new SQL([new StringChunk(str)]);
4507
4507
  }
4508
4508
  sql2.raw = raw2;
4509
- function join7(chunks, separator) {
4509
+ function join8(chunks, separator) {
4510
4510
  const result = [];
4511
4511
  for (const [i, chunk] of chunks.entries()) {
4512
4512
  if (i > 0 && separator !== void 0) {
@@ -4516,7 +4516,7 @@ var init_sql = __esm({
4516
4516
  }
4517
4517
  return new SQL(result);
4518
4518
  }
4519
- sql2.join = join7;
4519
+ sql2.join = join8;
4520
4520
  function identifier(value) {
4521
4521
  return new Name(value);
4522
4522
  }
@@ -7132,7 +7132,7 @@ var init_select2 = __esm({
7132
7132
  return (table, on) => {
7133
7133
  const baseTableName = this.tableName;
7134
7134
  const tableName = getTableLikeName(table);
7135
- if (typeof tableName === "string" && this.config.joins?.some((join7) => join7.alias === tableName)) {
7135
+ if (typeof tableName === "string" && this.config.joins?.some((join8) => join8.alias === tableName)) {
7136
7136
  throw new Error(`Alias "${tableName}" is already used in this query`);
7137
7137
  }
7138
7138
  if (!this.isPartialSelect) {
@@ -7974,7 +7974,7 @@ var init_update = __esm({
7974
7974
  createJoin(joinType) {
7975
7975
  return (table, on) => {
7976
7976
  const tableName = getTableLikeName(table);
7977
- if (typeof tableName === "string" && this.config.joins.some((join7) => join7.alias === tableName)) {
7977
+ if (typeof tableName === "string" && this.config.joins.some((join8) => join8.alias === tableName)) {
7978
7978
  throw new Error(`Alias "${tableName}" is already used in this query`);
7979
7979
  }
7980
7980
  if (typeof on === "function") {
@@ -22666,6 +22666,14 @@ import { fileURLToPath as fileURLToPath5 } from "url";
22666
22666
  import { existsSync as existsSync7 } from "fs";
22667
22667
  import { dirname as dirname8, join as join6 } from "path";
22668
22668
  import { randomUUID as randomUUID2 } from "crypto";
22669
+ function runCommandContext(executable, args, cwd) {
22670
+ return JSON.stringify({
22671
+ type: "supertask_command",
22672
+ executable,
22673
+ args,
22674
+ cwd
22675
+ });
22676
+ }
22669
22677
  function assertWorkerProcessIsolationSupported(platform = process.platform) {
22670
22678
  if (platform === "win32") {
22671
22679
  throw new Error(
@@ -22887,6 +22895,7 @@ var init_worker = __esm({
22887
22895
  runId,
22888
22896
  launchIdentity,
22889
22897
  child,
22898
+ commandContext: runCommandContext(this.opencodeBin, args, task.cwd || process.cwd()),
22890
22899
  output: "",
22891
22900
  sessionId: null,
22892
22901
  timeoutTimer: null,
@@ -23051,7 +23060,7 @@ var init_worker = __esm({
23051
23060
  const termination = entry.termination;
23052
23061
  if (termination?.kind === "shutdown") return;
23053
23062
  if (termination?.kind === "cancel") {
23054
- const output2 = entry.output.trim();
23063
+ const output2 = this.outputWithCommand(entry);
23055
23064
  const log2 = `${termination.message}${output2 ? `
23056
23065
  ${output2}` : ""}`;
23057
23066
  await TaskRunService.fail(entry.runId, log2);
@@ -23065,7 +23074,7 @@ ${output2}` : ""}`;
23065
23074
  }
23066
23075
  const currentRun = await TaskRunService.getById(entry.runId);
23067
23076
  if (!currentRun || currentRun.status !== "running") return;
23068
- const output = entry.output.trim();
23077
+ const output = this.outputWithCommand(entry);
23069
23078
  const log = failure ? `${failure}${output ? `
23070
23079
  ${output}` : ""}` : output;
23071
23080
  if (code === 0 && !failure) {
@@ -23201,6 +23210,11 @@ ${output}` : ""}` : output;
23201
23210
  const batchId = normalizeTaskBatchId(task.batchId);
23202
23211
  if (batchId) this.activeBatchIds.delete(batchId);
23203
23212
  }
23213
+ outputWithCommand(entry) {
23214
+ const output = entry.output.trim();
23215
+ return `${entry.commandContext}${output ? `
23216
+ ${output}` : ""}`;
23217
+ }
23204
23218
  resolveModel(taskModel) {
23205
23219
  if (!taskModel || taskModel === "default") return null;
23206
23220
  return taskModel;
@@ -25977,6 +25991,116 @@ var init_dist = __esm({
25977
25991
  }
25978
25992
  });
25979
25993
 
25994
+ // src/core/opencode-catalog.ts
25995
+ import { spawn as spawn2 } from "child_process";
25996
+ function cleanOutput(value) {
25997
+ return value.replace(ANSI_PATTERN, "").replace(/\r/g, "");
25998
+ }
25999
+ function runOpenCode(executable, args, cwd, timeoutMs) {
26000
+ return new Promise((resolve5, reject) => {
26001
+ const child = spawn2(executable, args, {
26002
+ cwd,
26003
+ env: process.env,
26004
+ stdio: ["ignore", "pipe", "pipe"]
26005
+ });
26006
+ let stdout = "";
26007
+ let stderr = "";
26008
+ let failure = null;
26009
+ let finished = false;
26010
+ const append = (current, chunk) => {
26011
+ const next = current + chunk.toString();
26012
+ if (Buffer.byteLength(next) > MAX_OUTPUT_BYTES && failure === null) {
26013
+ failure = new Error(`OpenCode \u8F93\u51FA\u8D85\u8FC7 ${MAX_OUTPUT_BYTES} bytes`);
26014
+ child.kill("SIGTERM");
26015
+ }
26016
+ return next.slice(-MAX_OUTPUT_BYTES);
26017
+ };
26018
+ child.stdout?.on("data", (chunk) => {
26019
+ stdout = append(stdout, chunk);
26020
+ });
26021
+ child.stderr?.on("data", (chunk) => {
26022
+ stderr = append(stderr, chunk);
26023
+ });
26024
+ child.once("error", (error) => {
26025
+ failure ??= error;
26026
+ });
26027
+ const timer = setTimeout(() => {
26028
+ failure ??= new Error(`OpenCode \u547D\u4EE4\u8D85\u8FC7 ${timeoutMs}ms \u672A\u5B8C\u6210`);
26029
+ child.kill("SIGTERM");
26030
+ }, timeoutMs);
26031
+ child.once("close", (code) => {
26032
+ if (finished) return;
26033
+ finished = true;
26034
+ clearTimeout(timer);
26035
+ if (failure) {
26036
+ reject(failure);
26037
+ return;
26038
+ }
26039
+ if (code !== 0) {
26040
+ const detail = cleanOutput(stderr).trim() || `\u9000\u51FA\u7801 ${code ?? "null"}`;
26041
+ reject(new Error(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
26042
+ return;
26043
+ }
26044
+ resolve5(cleanOutput(stdout));
26045
+ });
26046
+ });
26047
+ }
26048
+ function parseOpenCodeModels(output) {
26049
+ return [...new Set(cleanOutput(output).split("\n").map((line) => line.trim()).filter((line) => /^[^\s/]+\/.+/.test(line)))].sort((left, right) => left.localeCompare(right));
26050
+ }
26051
+ function parseOpenCodeAgents(output) {
26052
+ const agents = /* @__PURE__ */ new Map();
26053
+ for (const line of cleanOutput(output).split("\n")) {
26054
+ const match2 = /^([^\s()]+) \((primary|subagent|all)\)$/.exec(line.trim());
26055
+ if (!match2) continue;
26056
+ const name = match2[1];
26057
+ const mode = match2[2];
26058
+ if (name === "supertask-runner") continue;
26059
+ agents.set(name, { name, mode });
26060
+ }
26061
+ const rank = { primary: 0, all: 1, subagent: 2 };
26062
+ return [...agents.values()].sort((left, right) => rank[left.mode] - rank[right.mode] || left.name.localeCompare(right.name));
26063
+ }
26064
+ async function loadOpenCodeCatalog(cwd, options = {}) {
26065
+ validateTaskWorkingDirectory(cwd);
26066
+ const executable = options.executable ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
26067
+ const timeoutMs = options.timeoutMs ?? COMMAND_TIMEOUT_MS;
26068
+ const cacheKey = `${executable}\0${cwd}`;
26069
+ const cached = catalogCache.get(cacheKey);
26070
+ if (options.useCache !== false && cached && cached.expiresAt > Date.now()) {
26071
+ return cached.result;
26072
+ }
26073
+ const result = Promise.all([
26074
+ runOpenCode(executable, ["models"], cwd, timeoutMs),
26075
+ runOpenCode(executable, ["agent", "list"], cwd, timeoutMs)
26076
+ ]).then(([modelsOutput, agentsOutput]) => {
26077
+ const models = parseOpenCodeModels(modelsOutput);
26078
+ const agents = parseOpenCodeAgents(agentsOutput).filter((agent) => agent.mode !== "subagent");
26079
+ if (models.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u7528\u6A21\u578B");
26080
+ if (agents.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u76F4\u63A5\u8FD0\u884C\u7684\u4E3B Agent");
26081
+ return { cwd, models, agents };
26082
+ });
26083
+ if (options.useCache !== false) {
26084
+ catalogCache.set(cacheKey, { expiresAt: Date.now() + CATALOG_CACHE_MS, result });
26085
+ result.catch(() => {
26086
+ if (catalogCache.get(cacheKey)?.result === result) catalogCache.delete(cacheKey);
26087
+ });
26088
+ }
26089
+ return result;
26090
+ }
26091
+ var CATALOG_CACHE_MS, COMMAND_TIMEOUT_MS, MAX_OUTPUT_BYTES, ANSI_PATTERN, catalogCache;
26092
+ var init_opencode_catalog = __esm({
26093
+ "src/core/opencode-catalog.ts"() {
26094
+ "use strict";
26095
+ init_task_working_directory();
26096
+ CATALOG_CACHE_MS = 3e4;
26097
+ COMMAND_TIMEOUT_MS = 2e4;
26098
+ MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
26099
+ ANSI_PATTERN = /\x1B(?:[@-_][0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
26100
+ catalogCache = /* @__PURE__ */ new Map();
26101
+ }
26102
+ });
26103
+
25980
26104
  // src/web/ui.ts
25981
26105
  function t(locale, key, values = {}) {
25982
26106
  const template = (locale === "en" ? EN : ZH)[key];
@@ -26040,7 +26164,8 @@ function icon(name, className = "icon") {
26040
26164
  check: '<path d="m5 12 4 4L19 6"/>',
26041
26165
  alert: '<path d="M12 3 2.8 19h18.4L12 3Z"/><path d="M12 9v4M12 17h.01"/>',
26042
26166
  clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
26043
- 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"/>'
26167
+ 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"/>',
26168
+ 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"/>'
26044
26169
  };
26045
26170
  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>`;
26046
26171
  }
@@ -26095,7 +26220,20 @@ function clientMessages(locale) {
26095
26220
  "feedback.restartTimeout",
26096
26221
  "template.createTitle",
26097
26222
  "template.editTitle",
26098
- "filter.noResults"
26223
+ "filter.noResults",
26224
+ "catalog.chooseProject",
26225
+ "catalog.defaultModel",
26226
+ "catalog.defaultProvider",
26227
+ "catalog.loading",
26228
+ "catalog.loaded",
26229
+ "catalog.failed",
26230
+ "catalog.primary",
26231
+ "catalog.subagent",
26232
+ "catalog.all",
26233
+ "directory.empty",
26234
+ "feedback.commandCopied",
26235
+ "action.showHidden",
26236
+ "action.hideHidden"
26099
26237
  ];
26100
26238
  return Object.fromEntries(keys.map((key) => [key, t(locale, key)]));
26101
26239
  }
@@ -26163,6 +26301,11 @@ function renderLayout(options) {
26163
26301
  <footer>${t(locale, "app.footer")}</footer>
26164
26302
  </div>
26165
26303
  <div id="toast-region" class="toast-region" role="status" aria-live="polite"></div>
26304
+ <dialog id="directory-dialog" class="directory-dialog">
26305
+ <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>
26306
+ <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>
26307
+ <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>
26308
+ </dialog>
26166
26309
  <dialog id="detail-dialog">
26167
26310
  <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>
26168
26311
  <div class="dialog-body"><pre id="detail-content" class="json-view"></pre></div>
@@ -26201,21 +26344,38 @@ function renderLayout(options) {
26201
26344
  const showDetail=id=>showRecord('/api/tasks/'+id);const showRunDetail=id=>showRecord('/api/runs/'+id);const showTemplateDetail=id=>showRecord('/api/templates/'+id);
26202
26345
  async function copyDetails(){try{await navigator.clipboard.writeText(document.getElementById('detail-content').textContent);showToast(text('details.copySuccess'))}catch{showToast(text('feedback.copyFailed'),'error')}}
26203
26346
  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')}}
26347
+ async function copyRunCommand(id){try{await navigator.clipboard.writeText(document.getElementById('command-'+id).textContent);showToast(text('feedback.commandCopied'))}catch{showToast(text('feedback.copyFailed'),'error')}}
26204
26348
  function taskField(name){return document.getElementById('task-'+name)}
26349
+ function templateField(name){return document.getElementById('template-'+name)}
26205
26350
  function taskProjects(){const node=document.getElementById('task-project-data');if(!node)return {};try{return JSON.parse(node.textContent||'{}')}catch{return {}}}
26206
26351
  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')}
26207
- 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)}
26208
- 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')}}
26209
- 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}}
26210
- function templateField(name){return document.getElementById('template-'+name)}
26211
- 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'}
26352
+ const catalogTimers={};const catalogRequests={};const catalogModels={};
26353
+ function catalogField(prefix,name){return document.getElementById(prefix+'-'+name)}
26354
+ 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='';}
26355
+ function appendCurrentOption(select,value){if(!value||[...select.options].some(option=>option.value===value))return;select.appendChild(new Option(value,value));}
26356
+ function modelProvider(value){const slash=value.indexOf('/');return slash>0?value.slice(0,slash):''}
26357
+ 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}
26358
+ 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}}}
26359
+ function scheduleCatalogLoad(prefix){clearTimeout(catalogTimers[prefix]);catalogTimers[prefix]=setTimeout(()=>loadCatalog(prefix),450)}
26360
+ let directoryTargetId='';let directoryCurrent='';let directoryEntries=[];let directoryShowHidden=false;
26361
+ 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)}}
26362
+ 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}}
26363
+ document.getElementById('directory-hidden').onclick=()=>{directoryShowHidden=!directoryShowHidden;renderDirectoryEntries()};
26364
+ 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('')}
26365
+ 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)};
26366
+ 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'}
26367
+ 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}
26368
+ 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)}
26369
+ 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)}
26370
+ 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')}}
26371
+ 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}}
26212
26372
  function localDateTime(milliseconds){const date=new Date(milliseconds);const local=new Date(milliseconds-date.getTimezoneOffset()*60000);return local.toISOString().slice(0,23)}
26213
- 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}}
26373
+ 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')}}
26214
26374
  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}
26215
26375
  function selectedRunAt(){const input=templateField('run-at');return resolveEditedRunAt(input.dataset.originalEpoch?Number(input.dataset.originalEpoch):null,input.dataset.originalLocal||'',input.value)}
26216
- 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)}
26217
- 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')}}
26218
- 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}}
26376
+ 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)}
26377
+ 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')}}
26378
+ 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}}
26219
26379
  async function enableTmpl(id){try{await readJson(await fetch('/api/templates/'+id+'/enable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
26220
26380
  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')}}
26221
26381
  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')}}
@@ -26275,6 +26435,13 @@ var init_ui = __esm({
26275
26435
  "action.updateTask": "\u4FDD\u5B58\u4FEE\u6539",
26276
26436
  "action.createTemplate": "\u65B0\u5EFA\u5B9A\u65F6\u4EFB\u52A1",
26277
26437
  "action.saveTemplate": "\u4FDD\u5B58\u5B9A\u65F6\u4EFB\u52A1",
26438
+ "action.chooseFolder": "\u9009\u62E9\u6587\u4EF6\u5939",
26439
+ "action.chooseThisFolder": "\u9009\u62E9\u5F53\u524D\u6587\u4EF6\u5939",
26440
+ "action.home": "\u4E3B\u76EE\u5F55",
26441
+ "action.up": "\u4E0A\u4E00\u7EA7",
26442
+ "action.copyCommand": "\u590D\u5236\u547D\u4EE4",
26443
+ "action.showHidden": "\u663E\u793A\u9690\u85CF\u6587\u4EF6\u5939",
26444
+ "action.hideHidden": "\u9690\u85CF\u9690\u85CF\u6587\u4EF6\u5939",
26278
26445
  "status.pending": "\u5F85\u6267\u884C",
26279
26446
  "status.running": "\u8FD0\u884C\u4E2D",
26280
26447
  "status.done": "\u5DF2\u5B8C\u6210",
@@ -26341,6 +26508,15 @@ var init_ui = __esm({
26341
26508
  "schedule.hours": "{count} \u5C0F\u65F6",
26342
26509
  "schedule.days": "{count} \u5929",
26343
26510
  "schedule.overdue": "\u5DF2\u5230\u671F",
26511
+ "duration.unit": "\u65F6\u95F4\u5355\u4F4D",
26512
+ "duration.seconds": "\u79D2",
26513
+ "duration.minutes": "\u5206\u949F",
26514
+ "duration.hours": "\u5C0F\u65F6",
26515
+ "duration.days": "\u5929",
26516
+ "duration.systemDefault": "\u4F7F\u7528 Gateway \u9ED8\u8BA4\u8D85\u65F6",
26517
+ "duration.immediate": "\u7ACB\u5373\u91CD\u8BD5",
26518
+ "duration.custom": "\u81EA\u5B9A\u4E49\u2026",
26519
+ "duration.every": "\u6BCF {duration}",
26344
26520
  "system.worker": "\u4EFB\u52A1\u6267\u884C",
26345
26521
  "system.scheduler": "\u5B9A\u65F6\u4EFB\u52A1\u670D\u52A1",
26346
26522
  "system.watchdog": "\u8FD0\u884C\u76D1\u63A7",
@@ -26388,6 +26564,9 @@ var init_ui = __esm({
26388
26564
  "template.interval": "\u6267\u884C\u95F4\u9694",
26389
26565
  "template.runAt": "\u6267\u884C\u65F6\u95F4",
26390
26566
  "template.durationHint": "\u652F\u6301 30s\u30015min\u30011h\u30012d",
26567
+ "template.intervalHint": "\u76F4\u63A5\u9009\u62E9\u5E38\u7528\u9891\u7387\uFF1B\u53EA\u6709\u7279\u6B8A\u9700\u6C42\u624D\u9700\u8981\u81EA\u5B9A\u4E49\u3002",
26568
+ "template.retryBackoffHint": "\u4E00\u6B21\u5931\u8D25\u540E\uFF0C\u7B49\u5F85\u591A\u4E45\u518D\u91CD\u8BD5\u3002",
26569
+ "template.timeoutHint": "\u7559\u7A7A\u8868\u793A\u4F7F\u7528 Gateway \u7684\u9ED8\u8BA4\u4EFB\u52A1\u8D85\u65F6\u3002",
26391
26570
  "template.advanced": "\u66F4\u591A\u6267\u884C\u8BBE\u7F6E",
26392
26571
  "template.category": "\u5206\u7C7B",
26393
26572
  "template.batchId": "\u6279\u6B21 ID",
@@ -26412,6 +26591,28 @@ var init_ui = __esm({
26412
26591
  "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",
26413
26592
  "task.projectExisting": "\u6B64\u9879\u76EE\u73B0\u6709 {total} \u4E2A\u4EFB\u52A1\uFF1A\u8FD0\u884C {running}\uFF0C\u6392\u961F {pending}\uFF0C\u5F02\u5E38 {failed}\u3002",
26414
26593
  "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",
26594
+ "catalog.chooseProject": "\u8BF7\u5148\u9009\u62E9\u9879\u76EE\u76EE\u5F55",
26595
+ "catalog.defaultModel": "\u8DDF\u968F Agent / OpenCode \u9ED8\u8BA4\u6A21\u578B",
26596
+ "catalog.defaultProvider": "\u9ED8\u8BA4\u6A21\u578B",
26597
+ "catalog.provider": "\u6A21\u578B\u63D0\u4F9B\u5546",
26598
+ "catalog.model": "\u5177\u4F53\u6A21\u578B",
26599
+ "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",
26600
+ "catalog.agentHint": "\u6765\u81EA\u5F53\u524D\u9879\u76EE\u7684 opencode agent list\u3002",
26601
+ "catalog.loading": "\u6B63\u5728\u8BFB\u53D6\u6B64\u9879\u76EE\u53EF\u7528\u7684 Agent \u548C\u6A21\u578B\u2026",
26602
+ "catalog.loaded": "\u5DF2\u4ECE\u672C\u673A OpenCode \u8BFB\u53D6 {agents} \u4E2A Agent\u3001{models} \u4E2A\u6A21\u578B\u3002",
26603
+ "catalog.failed": "\u65E0\u6CD5\u8BFB\u53D6\u6B64\u9879\u76EE\u7684 OpenCode \u914D\u7F6E\uFF1A{error}",
26604
+ "catalog.primary": "\u4E3B Agent",
26605
+ "catalog.subagent": "\u5B50 Agent",
26606
+ "catalog.all": "\u901A\u7528 Agent",
26607
+ "directory.title": "\u9009\u62E9\u9879\u76EE\u76EE\u5F55",
26608
+ "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",
26609
+ "directory.empty": "\u8FD9\u4E2A\u6587\u4EF6\u5939\u4E2D\u6CA1\u6709\u5B50\u6587\u4EF6\u5939",
26610
+ "logs.command": "\u5B9E\u9645\u6267\u884C\u547D\u4EE4",
26611
+ "logs.output": "Agent \u8F93\u51FA",
26612
+ "logs.error": "\u5931\u8D25\u539F\u56E0",
26613
+ "logs.tools": "\u5DE5\u5177\u8C03\u7528",
26614
+ "logs.raw": "\u67E5\u770B\u539F\u59CB\u6267\u884C\u65E5\u5FD7",
26615
+ "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",
26415
26616
  "theme.label": "\u4E3B\u9898",
26416
26617
  "theme.system": "\u8DDF\u968F\u7CFB\u7EDF",
26417
26618
  "theme.light": "\u6D45\u8272",
@@ -26449,6 +26650,7 @@ var init_ui = __esm({
26449
26650
  "feedback.templateUpdated": "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u66F4\u65B0",
26450
26651
  "feedback.configSaved": "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58",
26451
26652
  "feedback.sessionCommandCopied": "\u4F1A\u8BDD\u547D\u4EE4\u5DF2\u590D\u5236\uFF0C\u53EF\u76F4\u63A5\u7C98\u8D34\u5230\u7EC8\u7AEF\u8FD0\u884C",
26653
+ "feedback.commandCopied": "\u6267\u884C\u547D\u4EE4\u5DF2\u590D\u5236",
26452
26654
  "feedback.restarting": "Gateway \u6B63\u5728\u91CD\u542F\uFF0C\u8BF7\u7A0D\u5019\u2026",
26453
26655
  "feedback.restartTimeout": "Gateway \u5C1A\u672A\u6062\u590D\uFF0C\u8BF7\u7A0D\u540E\u5237\u65B0\u6216\u68C0\u67E5 PM2 \u72B6\u6001",
26454
26656
  "feedback.databaseCleared": "\u6570\u636E\u5E93\u5DF2\u6E05\u7A7A\uFF0C\u5907\u4EFD\u4F4D\u4E8E\uFF1A{path}",
@@ -26497,6 +26699,13 @@ var init_ui = __esm({
26497
26699
  "action.updateTask": "Save changes",
26498
26700
  "action.createTemplate": "New scheduled task",
26499
26701
  "action.saveTemplate": "Save scheduled task",
26702
+ "action.chooseFolder": "Choose folder",
26703
+ "action.chooseThisFolder": "Choose this folder",
26704
+ "action.home": "Home",
26705
+ "action.up": "Up",
26706
+ "action.copyCommand": "Copy command",
26707
+ "action.showHidden": "Show hidden folders",
26708
+ "action.hideHidden": "Hide hidden folders",
26500
26709
  "status.pending": "Pending",
26501
26710
  "status.running": "Running",
26502
26711
  "status.done": "Done",
@@ -26563,6 +26772,15 @@ var init_ui = __esm({
26563
26772
  "schedule.hours": "{count} hr",
26564
26773
  "schedule.days": "{count} days",
26565
26774
  "schedule.overdue": "Overdue",
26775
+ "duration.unit": "Time unit",
26776
+ "duration.seconds": "seconds",
26777
+ "duration.minutes": "minutes",
26778
+ "duration.hours": "hours",
26779
+ "duration.days": "days",
26780
+ "duration.systemDefault": "Use Gateway default timeout",
26781
+ "duration.immediate": "Retry immediately",
26782
+ "duration.custom": "Custom\u2026",
26783
+ "duration.every": "Every {duration}",
26566
26784
  "system.worker": "Task execution",
26567
26785
  "system.scheduler": "Scheduled task service",
26568
26786
  "system.watchdog": "Runtime monitor",
@@ -26610,6 +26828,9 @@ var init_ui = __esm({
26610
26828
  "template.interval": "Interval",
26611
26829
  "template.runAt": "Run at",
26612
26830
  "template.durationHint": "Supports 30s, 5min, 1h, 2d",
26831
+ "template.intervalHint": "Choose a common frequency directly; customize only when necessary.",
26832
+ "template.retryBackoffHint": "How long to wait after a failure before retrying.",
26833
+ "template.timeoutHint": "Leave blank to use the Gateway default task timeout.",
26613
26834
  "template.advanced": "More execution settings",
26614
26835
  "template.category": "Category",
26615
26836
  "template.batchId": "Batch ID",
@@ -26634,6 +26855,28 @@ var init_ui = __esm({
26634
26855
  "task.batchHint": "Tasks with the same non-empty batch ID run serially. Blank removes the batch constraint; global concurrency and dependencies still apply.",
26635
26856
  "task.projectExisting": "This project has {total} tasks: {running} running, {pending} queued, and {failed} with issues.",
26636
26857
  "task.projectNew": "This is a new project group. It appears in the project list after creation.",
26858
+ "catalog.chooseProject": "Choose a project directory first",
26859
+ "catalog.defaultModel": "Use the Agent / OpenCode default model",
26860
+ "catalog.defaultProvider": "Default model",
26861
+ "catalog.provider": "Model provider",
26862
+ "catalog.model": "Model",
26863
+ "catalog.modelHint": "Choose a provider, then a model returned by opencode models for this project. Default does not pass -m.",
26864
+ "catalog.agentHint": "Loaded from opencode agent list for this project.",
26865
+ "catalog.loading": "Loading Agents and models available to this project\u2026",
26866
+ "catalog.loaded": "Loaded {agents} Agents and {models} models from local OpenCode.",
26867
+ "catalog.failed": "Could not load this project\u2019s OpenCode configuration: {error}",
26868
+ "catalog.primary": "primary Agent",
26869
+ "catalog.subagent": "subagent",
26870
+ "catalog.all": "general Agent",
26871
+ "directory.title": "Choose project directory",
26872
+ "directory.subtitle": "OpenCode runs in this directory, and its project-specific Agents and models are loaded.",
26873
+ "directory.empty": "This folder has no subfolders",
26874
+ "logs.command": "Executed command",
26875
+ "logs.output": "Agent output",
26876
+ "logs.error": "Failure reason",
26877
+ "logs.tools": "Tool calls",
26878
+ "logs.raw": "View raw execution log",
26879
+ "logs.noText": "This run produced no displayable text. Inspect the raw log for details.",
26637
26880
  "theme.label": "Theme",
26638
26881
  "theme.system": "System",
26639
26882
  "theme.light": "Light",
@@ -26671,6 +26914,7 @@ var init_ui = __esm({
26671
26914
  "feedback.templateUpdated": "Scheduled task updated",
26672
26915
  "feedback.configSaved": "Settings saved",
26673
26916
  "feedback.sessionCommandCopied": "Session command copied. Paste it into a terminal to continue.",
26917
+ "feedback.commandCopied": "Execution command copied",
26674
26918
  "feedback.restarting": "Gateway is restarting\u2026",
26675
26919
  "feedback.restartTimeout": "Gateway has not recovered yet. Refresh later or check PM2 status.",
26676
26920
  "feedback.databaseCleared": "Database cleared. Backup: {path}",
@@ -26881,6 +27125,18 @@ var init_ui = __esm({
26881
27125
  .danger-card h2 .icon { width:17px; height:17px; }
26882
27126
  .danger-card p { max-width:800px; margin:0 0 14px; color:var(--text-2); font-size:12px; }
26883
27127
  .log-panel { margin:12px 0; animation:reveal .18s ease both; }
27128
+ .log-content { display:grid; gap:14px; padding:16px; }
27129
+ .log-section-head { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:7px; }
27130
+ .run-command,.run-output,.run-error,.run-tools { min-width:0; }
27131
+ .run-command strong,.run-output>strong,.run-error>strong,.run-tools>strong { display:block; margin-bottom:7px; font-size:12px; }
27132
+ .command-cwd { margin-bottom:6px; color:var(--text-3); font-family:"SFMono-Regular",Consolas,monospace; font-size:10px; overflow-wrap:anywhere; }
27133
+ .run-command pre,.run-output pre,.run-error pre { margin:0; padding:12px; overflow:auto; border:1px solid var(--border); border-radius:9px;
27134
+ color:var(--text-2); background:var(--surface-2); font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; white-space:pre-wrap; overflow-wrap:anywhere; }
27135
+ .run-output pre { color:var(--text); font-family:inherit; font-size:13px; line-height:1.65; }
27136
+ .run-error pre { color:var(--red); border-color:color-mix(in srgb,var(--red) 28%,var(--border)); background:var(--red-soft); }
27137
+ .raw-log { padding-top:12px; border-top:1px solid var(--border); }
27138
+ .raw-log summary { color:var(--text-2); cursor:pointer; font-size:12px; font-weight:650; }
27139
+ .raw-log .log-box { margin-top:10px; border-radius:9px; }
26884
27140
  .log-box { max-height:360px; overflow:auto; padding:16px; color:var(--text-2); background:#0b1018; font-family:"SFMono-Regular",Consolas,monospace;
26885
27141
  font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
26886
27142
  :root[data-theme="light"] .log-box { color:#dbe5f3; }
@@ -26899,12 +27155,33 @@ var init_ui = __esm({
26899
27155
  .form-field-wide { grid-column:1 / -1; }
26900
27156
  .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;
26901
27157
  outline:none; color:var(--text); background:var(--surface-2); font-weight:450; }
27158
+ .field-action { display:flex; align-items:stretch; gap:7px; }
27159
+ .field-action input { min-width:0; flex:1; }
27160
+ .field-action .btn { flex:0 0 auto; white-space:nowrap; }
27161
+ .model-selector { display:grid; grid-template-columns:minmax(120px,.75fr) minmax(0,1.25fr); gap:7px; }
27162
+ .duration-picker { display:grid; gap:7px; }
27163
+ .duration-control { display:grid; grid-template-columns:minmax(0,1fr) 112px; gap:7px; }
27164
+ .duration-control input,.duration-control select { min-width:0; }
26902
27165
  .form-field textarea { resize:vertical; line-height:1.5; }
26903
27166
  .form-field input:focus,.form-field select:focus,.form-field textarea:focus { border-color:var(--primary); box-shadow:var(--focus); background:var(--surface); }
26904
27167
  .form-field small { color:var(--text-3); font-size:10px; font-weight:450; }
26905
27168
  .advanced-fields { margin-top:18px; padding-top:14px; border-top:1px solid var(--border); }
26906
27169
  .advanced-fields summary { margin-bottom:14px; color:var(--text-2); cursor:pointer; font-size:12px; font-weight:700; }
26907
27170
  .form-note { margin:16px 0 0; color:var(--text-3); font-size:11px; }
27171
+ .catalog-status[data-state="loading"] { color:var(--blue); }
27172
+ .catalog-status[data-state="ready"] { color:var(--green); }
27173
+ .catalog-status[data-state="error"] { color:var(--red); }
27174
+ .directory-dialog { width:min(720px,calc(100% - 32px)); }
27175
+ .directory-toolbar { display:flex; align-items:center; gap:8px; margin-bottom:12px; }
27176
+ .directory-path { min-width:0; flex:1; padding:9px 11px; overflow:hidden; border:1px solid var(--border); border-radius:9px;
27177
+ color:var(--text-2); background:var(--surface-2); font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; text-overflow:ellipsis; white-space:nowrap; }
27178
+ .directory-list { min-height:260px; max-height:48vh; display:grid; align-content:start; gap:6px; overflow:auto; }
27179
+ .directory-item { width:100%; min-height:40px; display:flex; align-items:center; gap:9px; padding:0 11px; border:1px solid transparent; border-radius:9px;
27180
+ 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; }
27181
+ .directory-item:hover { color:var(--text); border-color:var(--border); background:var(--surface-2); }
27182
+ .directory-item:active { transform:scale(.99); }
27183
+ .directory-item .icon { width:17px; height:17px; color:var(--primary); flex:0 0 auto; }
27184
+ .directory-empty { min-height:220px; display:grid; place-items:center; color:var(--text-3); }
26908
27185
  .json-view { min-height:160px; margin:0; padding:15px; overflow:auto; border:1px solid var(--border); border-radius:10px; color:var(--text-2);
26909
27186
  background:var(--surface-2); font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
26910
27187
  .confirm-copy { color:var(--text-2); margin:0; }
@@ -26973,6 +27250,9 @@ var init_ui = __esm({
26973
27250
  .project-grid { grid-template-columns:1fr; }
26974
27251
  .template-form-grid { grid-template-columns:1fr; }
26975
27252
  .form-field-wide { grid-column:auto; }
27253
+ .field-action { align-items:stretch; flex-direction:column; }
27254
+ .field-action .btn { width:100%; }
27255
+ .model-selector { grid-template-columns:1fr; }
26976
27256
  }
26977
27257
  @media (max-width:520px) {
26978
27258
  .stats-grid,.stats-grid.three { grid-template-columns:1fr 1fr; }
@@ -27002,11 +27282,21 @@ __export(web_exports, {
27002
27282
  dashboardApp: () => dashboardApp,
27003
27283
  default: () => web_default,
27004
27284
  isSafeDashboardRestartTarget: () => isSafeDashboardRestartTarget,
27285
+ presentRunLog: () => presentRunLog,
27005
27286
  resolveDashboardConfigState: () => resolveDashboardConfigState,
27006
27287
  setDashboardRuntimeConfig: () => setDashboardRuntimeConfig
27007
27288
  });
27008
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
27009
- import { basename as basename3, dirname as dirname9 } from "path";
27289
+ import {
27290
+ existsSync as existsSync8,
27291
+ mkdirSync as mkdirSync5,
27292
+ readFileSync as readFileSync5,
27293
+ readdirSync as readdirSync2,
27294
+ renameSync as renameSync2,
27295
+ statSync as statSync4,
27296
+ writeFileSync as writeFileSync3
27297
+ } from "fs";
27298
+ import { homedir as homedir5 } from "os";
27299
+ import { basename as basename3, dirname as dirname9, join as join7 } from "path";
27010
27300
  function setDashboardRuntimeConfig(config) {
27011
27301
  runtimeConfig = config === null ? null : structuredClone(config);
27012
27302
  }
@@ -27047,6 +27337,23 @@ function parsePositiveInteger2(value) {
27047
27337
  function parseTaskStatus2(value) {
27048
27338
  return TASK_STATUSES2.has(value) ? value : null;
27049
27339
  }
27340
+ function listChildDirectories(path) {
27341
+ return readdirSync2(path, { withFileTypes: true }).flatMap((entry) => {
27342
+ const entryPath = join7(path, entry.name);
27343
+ if (entry.isDirectory()) return [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }];
27344
+ if (!entry.isSymbolicLink()) return [];
27345
+ try {
27346
+ return statSync4(entryPath).isDirectory() ? [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }] : [];
27347
+ } catch {
27348
+ return [];
27349
+ }
27350
+ }).sort((left, right) => {
27351
+ const leftHidden = left.name.startsWith(".");
27352
+ const rightHidden = right.name.startsWith(".");
27353
+ if (leftHidden !== rightHidden) return leftHidden ? 1 : -1;
27354
+ return left.name.localeCompare(right.name);
27355
+ });
27356
+ }
27050
27357
  function parseTaskPayload(value) {
27051
27358
  if (!value || typeof value !== "object" || Array.isArray(value)) {
27052
27359
  throw new Error("\u8BF7\u6C42\u5185\u5BB9\u5FC5\u987B\u662F\u5BF9\u8C61");
@@ -27203,6 +27510,66 @@ function formatDuration(startAt, endAt) {
27203
27510
  if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
27204
27511
  return `${Math.floor(seconds / 3600)}h ${Math.floor(seconds % 3600 / 60)}m`;
27205
27512
  }
27513
+ function recordValue(value) {
27514
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
27515
+ }
27516
+ function shellQuote(value) {
27517
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value;
27518
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
27519
+ }
27520
+ function presentRunLog(log) {
27521
+ let command = null;
27522
+ const textParts = [];
27523
+ const errors = [];
27524
+ const tools = [];
27525
+ for (const line of log.split("\n")) {
27526
+ const trimmed = line.trim();
27527
+ if (!trimmed) continue;
27528
+ let parsed = null;
27529
+ try {
27530
+ parsed = recordValue(JSON.parse(trimmed));
27531
+ } catch {
27532
+ errors.push(line);
27533
+ continue;
27534
+ }
27535
+ if (!parsed) continue;
27536
+ if (parsed.type === "supertask_command") {
27537
+ const executable = typeof parsed.executable === "string" ? parsed.executable : null;
27538
+ const cwd = typeof parsed.cwd === "string" ? parsed.cwd : null;
27539
+ const args = Array.isArray(parsed.args) && parsed.args.every((item) => typeof item === "string") ? parsed.args : null;
27540
+ if (executable && cwd && args) {
27541
+ command = {
27542
+ cwd,
27543
+ command: `cd ${shellQuote(cwd)} && ${[executable, ...args].map(shellQuote).join(" ")}`
27544
+ };
27545
+ }
27546
+ continue;
27547
+ }
27548
+ const part = recordValue(parsed.part);
27549
+ const eventType = typeof parsed.type === "string" ? parsed.type : "";
27550
+ const partType = typeof part?.type === "string" ? part.type : "";
27551
+ const text2 = typeof part?.text === "string" ? part.text : typeof parsed.text === "string" ? parsed.text : null;
27552
+ if (text2 && (eventType === "text" || partType === "text")) textParts.push(text2);
27553
+ const tool = typeof part?.tool === "string" ? part.tool : typeof parsed.tool === "string" ? parsed.tool : null;
27554
+ if (tool && (eventType === "tool_use" || partType === "tool")) tools.push(tool);
27555
+ const error = typeof parsed.error === "string" ? parsed.error : typeof part?.error === "string" ? part.error : null;
27556
+ if (error) errors.push(error);
27557
+ }
27558
+ return {
27559
+ command,
27560
+ text: textParts.join("\n").trim(),
27561
+ errors: [...new Set(errors)],
27562
+ tools
27563
+ };
27564
+ }
27565
+ function renderRunLog(runId, taskName, log, locale) {
27566
+ const presentation = presentRunLog(log);
27567
+ 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>` : "";
27568
+ const errors = presentation.errors.length > 0 ? `<div class="run-error"><strong>${t(locale, "logs.error")}</strong><pre>${esc(presentation.errors.join("\n"))}</pre></div>` : "";
27569
+ 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>` : "";
27570
+ const output = `<div class="run-output"><strong>${t(locale, "logs.output")}</strong><pre>${esc(presentation.text || t(locale, "logs.noText"))}</pre></div>`;
27571
+ 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>`;
27572
+ }
27206
27573
  function esc(value) {
27207
27574
  if (!value) return "";
27208
27575
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -27234,6 +27601,25 @@ function emptyState(title, hint, code = "") {
27234
27601
  return `<div class="empty-state"><div><div class="empty-icon">${icon("inbox")}</div>
27235
27602
  <h3>${title}</h3><p>${hint}</p>${code ? `<code>${code}</code>` : ""}</div></div>`;
27236
27603
  }
27604
+ function durationControl(locale, id, value, kind) {
27605
+ const units = [
27606
+ { value: "s", label: t(locale, "duration.seconds") },
27607
+ { value: "min", label: t(locale, "duration.minutes") },
27608
+ { value: "h", label: t(locale, "duration.hours") },
27609
+ { value: "d", label: t(locale, "duration.days") }
27610
+ ];
27611
+ const values = kind === "interval" ? [3e5, 9e5, 18e5, 36e5, 216e5, 432e5, 864e5] : kind === "retry" ? [0, 1e4, 3e4, 6e4, 3e5] : [3e5, 9e5, 18e5, 36e5, 72e5, 144e5];
27612
+ const presets = [
27613
+ ...kind === "timeout" ? [{ value: "", label: t(locale, "duration.systemDefault") }] : [],
27614
+ ...values.map((milliseconds) => ({
27615
+ value: String(milliseconds),
27616
+ label: milliseconds === 0 ? t(locale, "duration.immediate") : kind === "interval" ? t(locale, "duration.every", { duration: formatInterval(milliseconds, locale) }) : formatInterval(milliseconds, locale)
27617
+ })),
27618
+ { value: "custom", label: t(locale, "duration.custom") }
27619
+ ];
27620
+ const selected = value === null ? "" : String(value);
27621
+ 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>`;
27622
+ }
27237
27623
  function formatInterval(milliseconds, locale) {
27238
27624
  const formatter = new Intl.NumberFormat(locale === "en" ? "en-US" : "zh-CN", {
27239
27625
  maximumFractionDigits: 1
@@ -27262,6 +27648,8 @@ var init_web = __esm({
27262
27648
  init_drizzle_orm();
27263
27649
  init_db2();
27264
27650
  init_duration();
27651
+ init_opencode_catalog();
27652
+ init_task_working_directory();
27265
27653
  init_database_maintenance_service();
27266
27654
  init_task_run_service();
27267
27655
  init_task_service();
@@ -27315,6 +27703,33 @@ var init_web = __esm({
27315
27703
  const health = getGatewayHealth();
27316
27704
  return c.json(health, health.status === "ok" ? 200 : 503);
27317
27705
  });
27706
+ app.get("/api/filesystem/directories", (c) => {
27707
+ const requestedPath = c.req.query("path")?.trim() || homedir5();
27708
+ try {
27709
+ validateTaskWorkingDirectory(requestedPath);
27710
+ return c.json({
27711
+ path: requestedPath,
27712
+ parent: dirname9(requestedPath),
27713
+ home: homedir5(),
27714
+ directories: listChildDirectories(requestedPath)
27715
+ });
27716
+ } catch (error) {
27717
+ return c.json({
27718
+ error: error instanceof Error ? error.message : String(error)
27719
+ }, 400);
27720
+ }
27721
+ });
27722
+ app.get("/api/opencode/catalog", async (c) => {
27723
+ const cwd = c.req.query("cwd")?.trim();
27724
+ if (!cwd) return c.json({ error: "cwd \u4E0D\u80FD\u4E3A\u7A7A" }, 400);
27725
+ try {
27726
+ return c.json(await loadOpenCodeCatalog(cwd));
27727
+ } catch (error) {
27728
+ return c.json({
27729
+ error: error instanceof Error ? error.message : String(error)
27730
+ }, 400);
27731
+ }
27732
+ });
27318
27733
  app.get("/", async (c) => {
27319
27734
  const locale = resolveLocale(c);
27320
27735
  const page = parsePositiveInteger2(c.req.query("page") || "1");
@@ -27459,13 +27874,14 @@ var init_web = __esm({
27459
27874
  <div class="dialog-body">
27460
27875
  <div class="template-form-grid">
27461
27876
  <label class="form-field"><span>${t(locale, "template.name")}</span><input id="task-name" required maxlength="200" autocomplete="off"></label>
27462
- <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>
27877
+ <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>
27463
27878
  <datalist id="task-cwd-options">${projects.map((project) => `<option value="${esc(project.cwd)}"></option>`).join("")}</datalist>
27464
- <label class="form-field"><span>${t(locale, "template.agent")}</span><input id="task-agent" required autocomplete="off" value="build" placeholder="build"></label>
27465
- <label class="form-field"><span>${t(locale, "template.model")}</span><input id="task-model" required autocomplete="off" value="default" placeholder="default"></label>
27879
+ <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>
27880
+ <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>
27466
27881
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="task-prompt" rows="6" required></textarea></label>
27467
27882
  </div>
27468
27883
  <p id="task-project-status" class="form-note"></p>
27884
+ <p id="task-catalog-status" class="form-note catalog-status"></p>
27469
27885
  <details class="advanced-fields">
27470
27886
  <summary>${t(locale, "template.advanced")}</summary>
27471
27887
  <div class="template-form-grid">
@@ -27474,8 +27890,8 @@ var init_web = __esm({
27474
27890
  <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>
27475
27891
  <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>
27476
27892
  <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>
27477
- <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>
27478
- <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>
27893
+ <label class="form-field"><span>${t(locale, "template.retryBackoff")}</span>${durationControl(locale, "task-retry-backoff", 3e4, "retry")}<small>${t(locale, "template.retryBackoffHint")}</small></label>
27894
+ <label class="form-field"><span>${t(locale, "template.timeout")}</span>${durationControl(locale, "task-timeout", null, "timeout")}<small>${t(locale, "template.timeoutHint")}</small></label>
27479
27895
  </div>
27480
27896
  </details>
27481
27897
  </div>
@@ -27535,15 +27951,16 @@ var init_web = __esm({
27535
27951
  <div class="dialog-body">
27536
27952
  <div class="template-form-grid">
27537
27953
  <label class="form-field"><span>${t(locale, "template.name")}</span><input id="template-name" required maxlength="200" autocomplete="off"></label>
27538
- <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>
27539
- <label class="form-field"><span>${t(locale, "template.agent")}</span><input id="template-agent" required autocomplete="off" placeholder="build"></label>
27540
- <label class="form-field"><span>${t(locale, "template.model")}</span><input id="template-model" required autocomplete="off" value="default" placeholder="default"></label>
27954
+ <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>
27955
+ <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>
27956
+ <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>
27541
27957
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="template-prompt" rows="6" required></textarea></label>
27542
27958
  <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>
27543
27959
  <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>
27544
- <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>
27960
+ <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>
27545
27961
  <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>
27546
27962
  </div>
27963
+ <p id="template-catalog-status" class="form-note catalog-status"></p>
27547
27964
  <details class="advanced-fields">
27548
27965
  <summary>${t(locale, "template.advanced")}</summary>
27549
27966
  <div class="template-form-grid">
@@ -27553,8 +27970,8 @@ var init_web = __esm({
27553
27970
  <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>
27554
27971
  <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>
27555
27972
  <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>
27556
- <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>
27557
- <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>
27973
+ <label class="form-field"><span>${t(locale, "template.retryBackoff")}</span>${durationControl(locale, "template-retry-backoff", 3e4, "retry")}<small>${t(locale, "template.retryBackoffHint")}</small></label>
27974
+ <label class="form-field"><span>${t(locale, "template.timeout")}</span>${durationControl(locale, "template-timeout", null, "timeout")}<small>${t(locale, "template.timeoutHint")}</small></label>
27558
27975
  </div>
27559
27976
  </details>
27560
27977
  <p class="form-note">${t(locale, "template.futureOnly")}</p>
@@ -27596,7 +28013,7 @@ var init_web = __esm({
27596
28013
  const status = safeStatus(run.status);
27597
28014
  const resumable = isValidSessionId(run.sessionId);
27598
28015
  if (run.log) {
27599
- 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>`);
28016
+ logs.push(renderRunLog(run.id, run.taskName, run.log, locale));
27600
28017
  }
27601
28018
  return `<tr>
27602
28019
  <td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td>