opencode-supertask 0.1.34 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/web/index.js CHANGED
@@ -12224,7 +12224,7 @@ function sql(strings, ...params) {
12224
12224
  return new SQL([new StringChunk(str)]);
12225
12225
  }
12226
12226
  sql2.raw = raw2;
12227
- function join4(chunks, separator) {
12227
+ function join5(chunks, separator) {
12228
12228
  const result = [];
12229
12229
  for (const [i, chunk] of chunks.entries()) {
12230
12230
  if (i > 0 && separator !== void 0) {
@@ -12234,7 +12234,7 @@ function sql(strings, ...params) {
12234
12234
  }
12235
12235
  return new SQL(result);
12236
12236
  }
12237
- sql2.join = join4;
12237
+ sql2.join = join5;
12238
12238
  function identifier(value) {
12239
12239
  return new Name(value);
12240
12240
  }
@@ -13083,8 +13083,17 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
13083
13083
  }
13084
13084
 
13085
13085
  // src/web/index.tsx
13086
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
13087
- import { basename as basename2, dirname as dirname4 } from "path";
13086
+ import {
13087
+ existsSync as existsSync5,
13088
+ mkdirSync as mkdirSync4,
13089
+ readFileSync as readFileSync3,
13090
+ readdirSync,
13091
+ renameSync as renameSync2,
13092
+ statSync as statSync4,
13093
+ writeFileSync as writeFileSync3
13094
+ } from "fs";
13095
+ import { homedir as homedir4 } from "os";
13096
+ import { basename as basename2, dirname as dirname4, join as join4 } from "path";
13088
13097
 
13089
13098
  // src/core/db/index.ts
13090
13099
  import { Database as Database2 } from "bun:sqlite";
@@ -14571,7 +14580,7 @@ var SQLiteSelectQueryBuilderBase = class extends TypedQueryBuilder {
14571
14580
  return (table, on) => {
14572
14581
  const baseTableName = this.tableName;
14573
14582
  const tableName = getTableLikeName(table);
14574
- if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.alias === tableName)) {
14583
+ if (typeof tableName === "string" && this.config.joins?.some((join5) => join5.alias === tableName)) {
14575
14584
  throw new Error(`Alias "${tableName}" is already used in this query`);
14576
14585
  }
14577
14586
  if (!this.isPartialSelect) {
@@ -15384,7 +15393,7 @@ var SQLiteUpdateBase = class extends QueryPromise {
15384
15393
  createJoin(joinType) {
15385
15394
  return (table, on) => {
15386
15395
  const tableName = getTableLikeName(table);
15387
- if (typeof tableName === "string" && this.config.joins.some((join4) => join4.alias === tableName)) {
15396
+ if (typeof tableName === "string" && this.config.joins.some((join5) => join5.alias === tableName)) {
15388
15397
  throw new Error(`Alias "${tableName}" is already used in this query`);
15389
15398
  }
15390
15399
  if (typeof on === "function") {
@@ -16611,6 +16620,140 @@ function parseDuration(input) {
16611
16620
  return null;
16612
16621
  }
16613
16622
 
16623
+ // src/core/opencode-catalog.ts
16624
+ import { spawn } from "child_process";
16625
+
16626
+ // src/core/task-working-directory.ts
16627
+ import { statSync } from "fs";
16628
+ import { isAbsolute } from "path";
16629
+ var InvalidTaskWorkingDirectoryError = class extends Error {
16630
+ constructor(message) {
16631
+ super(message);
16632
+ this.name = "InvalidTaskWorkingDirectoryError";
16633
+ }
16634
+ };
16635
+ function validateTaskWorkingDirectory(cwd) {
16636
+ if (cwd == null) return;
16637
+ if (!cwd.trim()) {
16638
+ throw new InvalidTaskWorkingDirectoryError("\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A");
16639
+ }
16640
+ if (!isAbsolute(cwd)) {
16641
+ throw new InvalidTaskWorkingDirectoryError(`\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF1A${cwd}`);
16642
+ }
16643
+ let stat;
16644
+ try {
16645
+ stat = statSync(cwd);
16646
+ } catch (error) {
16647
+ const detail = error instanceof Error ? error.message : String(error);
16648
+ throw new InvalidTaskWorkingDirectoryError(`\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6CD5\u8BBF\u95EE\uFF1A${cwd}\uFF08${detail}\uFF09`);
16649
+ }
16650
+ if (!stat.isDirectory()) {
16651
+ throw new InvalidTaskWorkingDirectoryError(`\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F\u76EE\u5F55\uFF1A${cwd}`);
16652
+ }
16653
+ }
16654
+
16655
+ // src/core/opencode-catalog.ts
16656
+ var CATALOG_CACHE_MS = 3e4;
16657
+ var COMMAND_TIMEOUT_MS = 2e4;
16658
+ var MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
16659
+ var ANSI_PATTERN = /\x1B(?:[@-_][0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
16660
+ var catalogCache = /* @__PURE__ */ new Map();
16661
+ function cleanOutput(value) {
16662
+ return value.replace(ANSI_PATTERN, "").replace(/\r/g, "");
16663
+ }
16664
+ function runOpenCode(executable, args, cwd, timeoutMs) {
16665
+ return new Promise((resolve3, reject) => {
16666
+ const child = spawn(executable, args, {
16667
+ cwd,
16668
+ env: process.env,
16669
+ stdio: ["ignore", "pipe", "pipe"]
16670
+ });
16671
+ let stdout = "";
16672
+ let stderr = "";
16673
+ let failure = null;
16674
+ let finished = false;
16675
+ const append = (current, chunk) => {
16676
+ const next = current + chunk.toString();
16677
+ if (Buffer.byteLength(next) > MAX_OUTPUT_BYTES && failure === null) {
16678
+ failure = new Error(`OpenCode \u8F93\u51FA\u8D85\u8FC7 ${MAX_OUTPUT_BYTES} bytes`);
16679
+ child.kill("SIGTERM");
16680
+ }
16681
+ return next.slice(-MAX_OUTPUT_BYTES);
16682
+ };
16683
+ child.stdout?.on("data", (chunk) => {
16684
+ stdout = append(stdout, chunk);
16685
+ });
16686
+ child.stderr?.on("data", (chunk) => {
16687
+ stderr = append(stderr, chunk);
16688
+ });
16689
+ child.once("error", (error) => {
16690
+ failure ??= error;
16691
+ });
16692
+ const timer = setTimeout(() => {
16693
+ failure ??= new Error(`OpenCode \u547D\u4EE4\u8D85\u8FC7 ${timeoutMs}ms \u672A\u5B8C\u6210`);
16694
+ child.kill("SIGTERM");
16695
+ }, timeoutMs);
16696
+ child.once("close", (code) => {
16697
+ if (finished) return;
16698
+ finished = true;
16699
+ clearTimeout(timer);
16700
+ if (failure) {
16701
+ reject(failure);
16702
+ return;
16703
+ }
16704
+ if (code !== 0) {
16705
+ const detail = cleanOutput(stderr).trim() || `\u9000\u51FA\u7801 ${code ?? "null"}`;
16706
+ reject(new Error(`OpenCode ${args.join(" ")} \u5931\u8D25\uFF1A${detail}`));
16707
+ return;
16708
+ }
16709
+ resolve3(cleanOutput(stdout));
16710
+ });
16711
+ });
16712
+ }
16713
+ function parseOpenCodeModels(output) {
16714
+ return [...new Set(cleanOutput(output).split("\n").map((line) => line.trim()).filter((line) => /^[^\s/]+\/.+/.test(line)))].sort((left, right) => left.localeCompare(right));
16715
+ }
16716
+ function parseOpenCodeAgents(output) {
16717
+ const agents = /* @__PURE__ */ new Map();
16718
+ for (const line of cleanOutput(output).split("\n")) {
16719
+ const match2 = /^([^\s()]+) \((primary|subagent|all)\)$/.exec(line.trim());
16720
+ if (!match2) continue;
16721
+ const name = match2[1];
16722
+ const mode = match2[2];
16723
+ if (name === "supertask-runner") continue;
16724
+ agents.set(name, { name, mode });
16725
+ }
16726
+ const rank = { primary: 0, all: 1, subagent: 2 };
16727
+ return [...agents.values()].sort((left, right) => rank[left.mode] - rank[right.mode] || left.name.localeCompare(right.name));
16728
+ }
16729
+ async function loadOpenCodeCatalog(cwd, options = {}) {
16730
+ validateTaskWorkingDirectory(cwd);
16731
+ const executable = options.executable ?? process.env.SUPERTASK_OPENCODE_BIN ?? "opencode";
16732
+ const timeoutMs = options.timeoutMs ?? COMMAND_TIMEOUT_MS;
16733
+ const cacheKey = `${executable}\0${cwd}`;
16734
+ const cached = catalogCache.get(cacheKey);
16735
+ if (options.useCache !== false && cached && cached.expiresAt > Date.now()) {
16736
+ return cached.result;
16737
+ }
16738
+ const result = Promise.all([
16739
+ runOpenCode(executable, ["models"], cwd, timeoutMs),
16740
+ runOpenCode(executable, ["agent", "list"], cwd, timeoutMs)
16741
+ ]).then(([modelsOutput, agentsOutput]) => {
16742
+ const models = parseOpenCodeModels(modelsOutput);
16743
+ const agents = parseOpenCodeAgents(agentsOutput).filter((agent) => agent.mode !== "subagent");
16744
+ if (models.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u7528\u6A21\u578B");
16745
+ if (agents.length === 0) throw new Error("OpenCode \u6CA1\u6709\u8FD4\u56DE\u53EF\u76F4\u63A5\u8FD0\u884C\u7684\u4E3B Agent");
16746
+ return { cwd, models, agents };
16747
+ });
16748
+ if (options.useCache !== false) {
16749
+ catalogCache.set(cacheKey, { expiresAt: Date.now() + CATALOG_CACHE_MS, result });
16750
+ result.catch(() => {
16751
+ if (catalogCache.get(cacheKey)?.result === result) catalogCache.delete(cacheKey);
16752
+ });
16753
+ }
16754
+ return result;
16755
+ }
16756
+
16614
16757
  // src/core/services/database-maintenance.service.ts
16615
16758
  import { Database as Database3 } from "bun:sqlite";
16616
16759
  import { randomUUID } from "crypto";
@@ -16619,7 +16762,7 @@ import {
16619
16762
  existsSync as existsSync2,
16620
16763
  mkdirSync as mkdirSync2,
16621
16764
  renameSync,
16622
- statSync,
16765
+ statSync as statSync2,
16623
16766
  unlinkSync,
16624
16767
  writeFileSync
16625
16768
  } from "fs";
@@ -16728,9 +16871,9 @@ var DatabaseMaintenanceService = class {
16728
16871
  if (!existsSync2(source)) {
16729
16872
  throw new Error(`\u5907\u4EFD\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${source}`);
16730
16873
  }
16731
- const sourceStat = statSync(source);
16874
+ const sourceStat = statSync2(source);
16732
16875
  if (!sourceStat.isFile()) throw new Error(`\u5907\u4EFD\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6\uFF1A${source}`);
16733
- const liveStat = statSync(livePath);
16876
+ const liveStat = statSync2(livePath);
16734
16877
  if (sourceStat.dev === liveStat.dev && sourceStat.ino === liveStat.ino) {
16735
16878
  throw new Error("\u6062\u590D\u6765\u6E90\u4E0D\u80FD\u662F\u5F53\u524D\u6570\u636E\u5E93\u7684\u7B26\u53F7\u94FE\u63A5\u6216\u786C\u94FE\u63A5\u522B\u540D");
16736
16879
  }
@@ -16971,7 +17114,7 @@ var DatabaseMaintenanceService = class {
16971
17114
  const counts = missingTables.length === 0 ? this.readCounts(sqlite2) : { tasks: 0, taskRuns: 0, taskTemplates: 0 };
16972
17115
  const runningTasks = missingTables.includes("tasks") ? 0 : this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM tasks WHERE status = 'running'");
16973
17116
  const runningRuns = missingTables.includes("task_runs") ? 0 : this.scalar(sqlite2, "SELECT COUNT(*) AS count FROM task_runs WHERE status = 'running'");
16974
- const sizeBytes = path === ":memory:" || !existsSync2(path) ? sqlite2.serialize().byteLength : statSync(path).size;
17117
+ const sizeBytes = path === ":memory:" || !existsSync2(path) ? sqlite2.serialize().byteLength : statSync2(path).size;
16975
17118
  return {
16976
17119
  ok: integrityMessages.length === 1 && integrityMessages[0] === "ok" && foreignKeyViolations === 0 && missingTables.length === 0,
16977
17120
  path: normalizedPath(path),
@@ -17008,7 +17151,7 @@ var DatabaseMaintenanceService = class {
17008
17151
  const check = this.inspectFile(temporary);
17009
17152
  if (!check.ok) throw new Error("\u65B0\u5907\u4EFD\u672A\u901A\u8FC7\u5B8C\u6574\u6027\u6821\u9A8C");
17010
17153
  renameSync(temporary, output);
17011
- const finalCheck = { ...check, path: output, sizeBytes: statSync(output).size };
17154
+ const finalCheck = { ...check, path: output, sizeBytes: statSync2(output).size };
17012
17155
  return { path: output, sizeBytes: finalCheck.sizeBytes, check: finalCheck };
17013
17156
  } catch (error) {
17014
17157
  safeUnlink(temporary);
@@ -17244,35 +17387,6 @@ function computeBackoff(retryCount, baseMs = 3e4, maxMs = MAX_BACKOFF_MS) {
17244
17387
  return Math.min(baseMs * Math.pow(2, retryCount - 1), maxMs);
17245
17388
  }
17246
17389
 
17247
- // src/core/task-working-directory.ts
17248
- import { statSync as statSync2 } from "fs";
17249
- import { isAbsolute } from "path";
17250
- var InvalidTaskWorkingDirectoryError = class extends Error {
17251
- constructor(message) {
17252
- super(message);
17253
- this.name = "InvalidTaskWorkingDirectoryError";
17254
- }
17255
- };
17256
- function validateTaskWorkingDirectory(cwd) {
17257
- if (cwd == null) return;
17258
- if (!cwd.trim()) {
17259
- throw new InvalidTaskWorkingDirectoryError("\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A");
17260
- }
17261
- if (!isAbsolute(cwd)) {
17262
- throw new InvalidTaskWorkingDirectoryError(`\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF1A${cwd}`);
17263
- }
17264
- let stat;
17265
- try {
17266
- stat = statSync2(cwd);
17267
- } catch (error) {
17268
- const detail = error instanceof Error ? error.message : String(error);
17269
- throw new InvalidTaskWorkingDirectoryError(`\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6CD5\u8BBF\u95EE\uFF1A${cwd}\uFF08${detail}\uFF09`);
17270
- }
17271
- if (!stat.isDirectory()) {
17272
- throw new InvalidTaskWorkingDirectoryError(`\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F\u76EE\u5F55\uFF1A${cwd}`);
17273
- }
17274
- }
17275
-
17276
17390
  // src/core/task-batch.ts
17277
17391
  var TASK_BATCH_TRIM_CHARACTERS = " \n\v\f\r\xA0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF";
17278
17392
  function normalizeTaskBatchId(batchId) {
@@ -18686,7 +18800,27 @@ function scopesMatch(left, right) {
18686
18800
  function currentScope() {
18687
18801
  return runtimeScope({ cwd: process.cwd(), env: process.env });
18688
18802
  }
18689
- function getGatewayDiagnostic() {
18803
+ function diagnoseOpenCodeRuntime(runtime = {
18804
+ cwd: process.cwd(),
18805
+ env: process.env
18806
+ }) {
18807
+ const executable = runtimeScope(runtime).opencodePath;
18808
+ const result = spawnSync(executable, ["--version"], {
18809
+ cwd: runtime.cwd,
18810
+ env: runtime.env,
18811
+ encoding: "utf8",
18812
+ timeout: 1e4,
18813
+ killSignal: "SIGKILL"
18814
+ });
18815
+ const ok = result.status === 0 && result.error === void 0;
18816
+ return {
18817
+ ok,
18818
+ executable,
18819
+ version: ok ? result.stdout.trim() || result.stderr.trim() || null : null,
18820
+ error: ok ? null : result.error?.message || result.stderr.trim() || result.stdout.trim() || `exit code ${result.status}`
18821
+ };
18822
+ }
18823
+ function getGatewayDiagnostic(options = {}) {
18690
18824
  const producerScope = currentScope();
18691
18825
  if (!isPm2Installed()) {
18692
18826
  return {
@@ -18702,7 +18836,8 @@ function getGatewayDiagnostic() {
18702
18836
  startupConfigured: process.platform === "darwin" || process.platform === "linux" ? false : null,
18703
18837
  currentScope: producerScope,
18704
18838
  gatewayScope: null,
18705
- scopeMatches: false
18839
+ scopeMatches: false,
18840
+ gatewayOpenCode: null
18706
18841
  };
18707
18842
  }
18708
18843
  const processes = pm2JsonList();
@@ -18730,7 +18865,8 @@ function getGatewayDiagnostic() {
18730
18865
  ) : process.platform === "linux" ? isLinuxStartupConfigured(gatewayEnv, runtime?.cwd, runtime ?? void 0) : null,
18731
18866
  currentScope: producerScope,
18732
18867
  gatewayScope: managedScope,
18733
- scopeMatches: managedScope != null && scopesMatch(producerScope, managedScope)
18868
+ scopeMatches: managedScope != null && scopesMatch(producerScope, managedScope),
18869
+ gatewayOpenCode: runtime === null || !options.probeOpenCode ? null : diagnoseOpenCodeRuntime(runtime)
18734
18870
  };
18735
18871
  }
18736
18872
  function pm2Bin(env = process.env) {
@@ -19049,7 +19185,7 @@ var ZH = {
19049
19185
  "action.hideLogs": "\u6536\u8D77\u65E5\u5FD7",
19050
19186
  "action.save": "\u4FDD\u5B58\u8BBE\u7F6E",
19051
19187
  "action.saveAndRestart": "\u4FDD\u5B58\u5E76\u91CD\u542F",
19052
- "action.copy": "\u590D\u5236 JSON",
19188
+ "action.copy": "\u590D\u5236\u539F\u59CB\u6570\u636E",
19053
19189
  "action.close": "\u5173\u95ED",
19054
19190
  "action.confirm": "\u786E\u8BA4",
19055
19191
  "action.clearDatabase": "\u6E05\u7A7A\u6570\u636E\u5E93",
@@ -19058,6 +19194,13 @@ var ZH = {
19058
19194
  "action.updateTask": "\u4FDD\u5B58\u4FEE\u6539",
19059
19195
  "action.createTemplate": "\u65B0\u5EFA\u5B9A\u65F6\u4EFB\u52A1",
19060
19196
  "action.saveTemplate": "\u4FDD\u5B58\u5B9A\u65F6\u4EFB\u52A1",
19197
+ "action.chooseFolder": "\u9009\u62E9\u6587\u4EF6\u5939",
19198
+ "action.chooseThisFolder": "\u9009\u62E9\u5F53\u524D\u6587\u4EF6\u5939",
19199
+ "action.home": "\u4E3B\u76EE\u5F55",
19200
+ "action.up": "\u4E0A\u4E00\u7EA7",
19201
+ "action.copyCommand": "\u590D\u5236\u547D\u4EE4",
19202
+ "action.showHidden": "\u663E\u793A\u9690\u85CF\u6587\u4EF6\u5939",
19203
+ "action.hideHidden": "\u9690\u85CF\u9690\u85CF\u6587\u4EF6\u5939",
19061
19204
  "status.pending": "\u5F85\u6267\u884C",
19062
19205
  "status.running": "\u8FD0\u884C\u4E2D",
19063
19206
  "status.done": "\u5DF2\u5B8C\u6210",
@@ -19124,6 +19267,15 @@ var ZH = {
19124
19267
  "schedule.hours": "{count} \u5C0F\u65F6",
19125
19268
  "schedule.days": "{count} \u5929",
19126
19269
  "schedule.overdue": "\u5DF2\u5230\u671F",
19270
+ "duration.unit": "\u65F6\u95F4\u5355\u4F4D",
19271
+ "duration.seconds": "\u79D2",
19272
+ "duration.minutes": "\u5206\u949F",
19273
+ "duration.hours": "\u5C0F\u65F6",
19274
+ "duration.days": "\u5929",
19275
+ "duration.systemDefault": "\u4F7F\u7528 Gateway \u9ED8\u8BA4\u8D85\u65F6",
19276
+ "duration.immediate": "\u7ACB\u5373\u91CD\u8BD5",
19277
+ "duration.custom": "\u81EA\u5B9A\u4E49\u2026",
19278
+ "duration.every": "\u6BCF {duration}",
19127
19279
  "system.worker": "\u4EFB\u52A1\u6267\u884C",
19128
19280
  "system.scheduler": "\u5B9A\u65F6\u4EFB\u52A1\u670D\u52A1",
19129
19281
  "system.watchdog": "\u8FD0\u884C\u76D1\u63A7",
@@ -19171,6 +19323,9 @@ var ZH = {
19171
19323
  "template.interval": "\u6267\u884C\u95F4\u9694",
19172
19324
  "template.runAt": "\u6267\u884C\u65F6\u95F4",
19173
19325
  "template.durationHint": "\u652F\u6301 30s\u30015min\u30011h\u30012d",
19326
+ "template.intervalHint": "\u76F4\u63A5\u9009\u62E9\u5E38\u7528\u9891\u7387\uFF1B\u53EA\u6709\u7279\u6B8A\u9700\u6C42\u624D\u9700\u8981\u81EA\u5B9A\u4E49\u3002",
19327
+ "template.retryBackoffHint": "\u4E00\u6B21\u5931\u8D25\u540E\uFF0C\u7B49\u5F85\u591A\u4E45\u518D\u91CD\u8BD5\u3002",
19328
+ "template.timeoutHint": "\u7559\u7A7A\u8868\u793A\u4F7F\u7528 Gateway \u7684\u9ED8\u8BA4\u4EFB\u52A1\u8D85\u65F6\u3002",
19174
19329
  "template.advanced": "\u66F4\u591A\u6267\u884C\u8BBE\u7F6E",
19175
19330
  "template.category": "\u5206\u7C7B",
19176
19331
  "template.batchId": "\u6279\u6B21 ID",
@@ -19195,14 +19350,73 @@ var ZH = {
19195
19350
  "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",
19196
19351
  "task.projectExisting": "\u6B64\u9879\u76EE\u73B0\u6709 {total} \u4E2A\u4EFB\u52A1\uFF1A\u8FD0\u884C {running}\uFF0C\u6392\u961F {pending}\uFF0C\u5F02\u5E38 {failed}\u3002",
19197
19352
  "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",
19353
+ "catalog.chooseProject": "\u8BF7\u5148\u9009\u62E9\u9879\u76EE\u76EE\u5F55",
19354
+ "catalog.defaultModel": "\u8DDF\u968F Agent / OpenCode \u9ED8\u8BA4\u6A21\u578B",
19355
+ "catalog.defaultProvider": "\u9ED8\u8BA4\u6A21\u578B",
19356
+ "catalog.provider": "\u6A21\u578B\u63D0\u4F9B\u5546",
19357
+ "catalog.model": "\u5177\u4F53\u6A21\u578B",
19358
+ "catalog.modelHint": "\u5148\u9009\u63D0\u4F9B\u5546\uFF0C\u518D\u9009\u672C\u9879\u76EE opencode models \u8FD4\u56DE\u7684\u6A21\u578B\uFF1B\u9ED8\u8BA4\u9009\u9879\u4E0D\u4F1A\u4F20\u5165 -m\u3002",
19359
+ "catalog.agentHint": "\u6765\u81EA\u5F53\u524D\u9879\u76EE\u7684 opencode agent list\u3002",
19360
+ "catalog.loading": "\u6B63\u5728\u8BFB\u53D6\u6B64\u9879\u76EE\u53EF\u7528\u7684 Agent \u548C\u6A21\u578B\u2026",
19361
+ "catalog.loaded": "\u5DF2\u4ECE\u672C\u673A OpenCode \u8BFB\u53D6 {agents} \u4E2A Agent\u3001{models} \u4E2A\u6A21\u578B\u3002",
19362
+ "catalog.failed": "\u65E0\u6CD5\u8BFB\u53D6\u6B64\u9879\u76EE\u7684 OpenCode \u914D\u7F6E\uFF1A{error}",
19363
+ "catalog.primary": "\u4E3B Agent",
19364
+ "catalog.subagent": "\u5B50 Agent",
19365
+ "catalog.all": "\u901A\u7528 Agent",
19366
+ "directory.title": "\u9009\u62E9\u9879\u76EE\u76EE\u5F55",
19367
+ "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",
19368
+ "directory.empty": "\u8FD9\u4E2A\u6587\u4EF6\u5939\u4E2D\u6CA1\u6709\u5B50\u6587\u4EF6\u5939",
19369
+ "logs.command": "\u5B9E\u9645\u6267\u884C\u547D\u4EE4",
19370
+ "logs.output": "Agent \u8F93\u51FA",
19371
+ "logs.error": "\u5931\u8D25\u539F\u56E0",
19372
+ "logs.tools": "\u5DE5\u5177\u8C03\u7528",
19373
+ "logs.raw": "\u67E5\u770B\u539F\u59CB\u6267\u884C\u65E5\u5FD7",
19374
+ "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",
19198
19375
  "theme.label": "\u4E3B\u9898",
19199
19376
  "theme.system": "\u8DDF\u968F\u7CFB\u7EDF",
19200
19377
  "theme.light": "\u6D45\u8272",
19201
19378
  "theme.dark": "\u6DF1\u8272",
19202
19379
  "language.label": "\u8BED\u8A00",
19203
- "details.title": "\u6570\u636E\u8BE6\u60C5",
19204
- "details.subtitle": "\u539F\u59CB\u8BB0\u5F55\uFF08JSON\uFF09",
19205
- "details.copySuccess": "JSON \u5DF2\u590D\u5236",
19380
+ "details.title": "\u8BE6\u60C5",
19381
+ "details.subtitle": "\u91CD\u70B9\u4FE1\u606F\u5DF2\u6574\u7406\uFF1B\u539F\u59CB\u6570\u636E\u4EC5\u7528\u4E8E\u6392\u969C\u3002",
19382
+ "details.taskTitle": "\u4EFB\u52A1\u8BE6\u60C5",
19383
+ "details.runTitle": "\u6267\u884C\u8BE6\u60C5",
19384
+ "details.templateTitle": "\u5B9A\u65F6\u4EFB\u52A1\u8BE6\u60C5",
19385
+ "details.raw": "\u67E5\u770B\u539F\u59CB\u6570\u636E\uFF08JSON\uFF09",
19386
+ "details.copySuccess": "\u539F\u59CB\u6570\u636E\u5DF2\u590D\u5236",
19387
+ "details.id": "\u7F16\u53F7",
19388
+ "details.project": "\u9879\u76EE\u76EE\u5F55",
19389
+ "details.prompt": "\u63D0\u793A\u8BCD",
19390
+ "details.result": "\u6267\u884C\u7ED3\u679C / \u5931\u8D25\u539F\u56E0",
19391
+ "details.category": "\u5206\u7C7B",
19392
+ "details.batch": "\u6279\u6B21",
19393
+ "details.dependency": "\u4F9D\u8D56\u4EFB\u52A1",
19394
+ "details.importance": "\u91CD\u8981\u7A0B\u5EA6",
19395
+ "details.urgency": "\u7D27\u6025\u7A0B\u5EA6",
19396
+ "details.retryCount": "\u5DF2\u91CD\u8BD5 / \u6700\u591A\u91CD\u8BD5",
19397
+ "details.retryBackoff": "\u91CD\u8BD5\u7B49\u5F85",
19398
+ "details.timeout": "\u6267\u884C\u8D85\u65F6",
19399
+ "details.createdAt": "\u521B\u5EFA\u65F6\u95F4",
19400
+ "details.updatedAt": "\u66F4\u65B0\u65F6\u95F4",
19401
+ "details.startedAt": "\u5F00\u59CB\u65F6\u95F4",
19402
+ "details.finishedAt": "\u7ED3\u675F\u65F6\u95F4",
19403
+ "details.scheduledAt": "\u8BA1\u5212\u65F6\u95F4",
19404
+ "details.enabled": "\u81EA\u52A8\u8FD0\u884C",
19405
+ "details.scheduleRule": "\u8FD0\u884C\u89C4\u5219",
19406
+ "details.maxInstances": "\u81EA\u52A8\u8C03\u5EA6\u4E0A\u9650",
19407
+ "details.maxRetries": "\u5931\u8D25\u91CD\u8BD5\u6B21\u6570",
19408
+ "details.lastRun": "\u4E0A\u6B21\u8FD0\u884C",
19409
+ "details.nextRun": "\u4E0B\u6B21\u8FD0\u884C",
19410
+ "details.taskId": "\u6240\u5C5E\u4EFB\u52A1",
19411
+ "details.session": "OpenCode \u4F1A\u8BDD",
19412
+ "details.heartbeat": "\u6700\u8FD1\u5FC3\u8DF3",
19413
+ "details.process": "\u8FDB\u7A0B",
19414
+ "details.history": "\u6267\u884C\u5386\u53F2",
19415
+ "details.noHistory": "\u8FD8\u6CA1\u6709\u6267\u884C\u8BB0\u5F55",
19416
+ "details.none": "\u65E0",
19417
+ "details.default": "\u8DDF\u968F\u7CFB\u7EDF\u9ED8\u8BA4",
19418
+ "details.enabledYes": "\u5DF2\u542F\u7528",
19419
+ "details.enabledNo": "\u5DF2\u505C\u7528",
19206
19420
  "dialog.cancelTask": "\u53D6\u6D88\u4EFB\u52A1 #{id}\uFF1F",
19207
19421
  "dialog.cancelTaskBody": "\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u4F1A\u5728\u4E0B\u4E00\u4E2A\u8F6E\u8BE2\u5468\u671F\u7EC8\u6B62\u5BF9\u5E94\u8FDB\u7A0B\u6811\u3002",
19208
19422
  "dialog.retryTask": "\u91CD\u8BD5\u4EFB\u52A1 #{id}\uFF1F",
@@ -19232,6 +19446,7 @@ var ZH = {
19232
19446
  "feedback.templateUpdated": "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u66F4\u65B0",
19233
19447
  "feedback.configSaved": "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58",
19234
19448
  "feedback.sessionCommandCopied": "\u4F1A\u8BDD\u547D\u4EE4\u5DF2\u590D\u5236\uFF0C\u53EF\u76F4\u63A5\u7C98\u8D34\u5230\u7EC8\u7AEF\u8FD0\u884C",
19449
+ "feedback.commandCopied": "\u6267\u884C\u547D\u4EE4\u5DF2\u590D\u5236",
19235
19450
  "feedback.restarting": "Gateway \u6B63\u5728\u91CD\u542F\uFF0C\u8BF7\u7A0D\u5019\u2026",
19236
19451
  "feedback.restartTimeout": "Gateway \u5C1A\u672A\u6062\u590D\uFF0C\u8BF7\u7A0D\u540E\u5237\u65B0\u6216\u68C0\u67E5 PM2 \u72B6\u6001",
19237
19452
  "feedback.databaseCleared": "\u6570\u636E\u5E93\u5DF2\u6E05\u7A7A\uFF0C\u5907\u4EFD\u4F4D\u4E8E\uFF1A{path}",
@@ -19271,7 +19486,7 @@ var EN = {
19271
19486
  "action.hideLogs": "Hide log",
19272
19487
  "action.save": "Save settings",
19273
19488
  "action.saveAndRestart": "Save and restart",
19274
- "action.copy": "Copy JSON",
19489
+ "action.copy": "Copy raw data",
19275
19490
  "action.close": "Close",
19276
19491
  "action.confirm": "Confirm",
19277
19492
  "action.clearDatabase": "Clear database",
@@ -19280,6 +19495,13 @@ var EN = {
19280
19495
  "action.updateTask": "Save changes",
19281
19496
  "action.createTemplate": "New scheduled task",
19282
19497
  "action.saveTemplate": "Save scheduled task",
19498
+ "action.chooseFolder": "Choose folder",
19499
+ "action.chooseThisFolder": "Choose this folder",
19500
+ "action.home": "Home",
19501
+ "action.up": "Up",
19502
+ "action.copyCommand": "Copy command",
19503
+ "action.showHidden": "Show hidden folders",
19504
+ "action.hideHidden": "Hide hidden folders",
19283
19505
  "status.pending": "Pending",
19284
19506
  "status.running": "Running",
19285
19507
  "status.done": "Done",
@@ -19346,6 +19568,15 @@ var EN = {
19346
19568
  "schedule.hours": "{count} hr",
19347
19569
  "schedule.days": "{count} days",
19348
19570
  "schedule.overdue": "Overdue",
19571
+ "duration.unit": "Time unit",
19572
+ "duration.seconds": "seconds",
19573
+ "duration.minutes": "minutes",
19574
+ "duration.hours": "hours",
19575
+ "duration.days": "days",
19576
+ "duration.systemDefault": "Use Gateway default timeout",
19577
+ "duration.immediate": "Retry immediately",
19578
+ "duration.custom": "Custom\u2026",
19579
+ "duration.every": "Every {duration}",
19349
19580
  "system.worker": "Task execution",
19350
19581
  "system.scheduler": "Scheduled task service",
19351
19582
  "system.watchdog": "Runtime monitor",
@@ -19393,6 +19624,9 @@ var EN = {
19393
19624
  "template.interval": "Interval",
19394
19625
  "template.runAt": "Run at",
19395
19626
  "template.durationHint": "Supports 30s, 5min, 1h, 2d",
19627
+ "template.intervalHint": "Choose a common frequency directly; customize only when necessary.",
19628
+ "template.retryBackoffHint": "How long to wait after a failure before retrying.",
19629
+ "template.timeoutHint": "Leave blank to use the Gateway default task timeout.",
19396
19630
  "template.advanced": "More execution settings",
19397
19631
  "template.category": "Category",
19398
19632
  "template.batchId": "Batch ID",
@@ -19417,14 +19651,73 @@ var EN = {
19417
19651
  "task.batchHint": "Tasks with the same non-empty batch ID run serially. Blank removes the batch constraint; global concurrency and dependencies still apply.",
19418
19652
  "task.projectExisting": "This project has {total} tasks: {running} running, {pending} queued, and {failed} with issues.",
19419
19653
  "task.projectNew": "This is a new project group. It appears in the project list after creation.",
19654
+ "catalog.chooseProject": "Choose a project directory first",
19655
+ "catalog.defaultModel": "Use the Agent / OpenCode default model",
19656
+ "catalog.defaultProvider": "Default model",
19657
+ "catalog.provider": "Model provider",
19658
+ "catalog.model": "Model",
19659
+ "catalog.modelHint": "Choose a provider, then a model returned by opencode models for this project. Default does not pass -m.",
19660
+ "catalog.agentHint": "Loaded from opencode agent list for this project.",
19661
+ "catalog.loading": "Loading Agents and models available to this project\u2026",
19662
+ "catalog.loaded": "Loaded {agents} Agents and {models} models from local OpenCode.",
19663
+ "catalog.failed": "Could not load this project\u2019s OpenCode configuration: {error}",
19664
+ "catalog.primary": "primary Agent",
19665
+ "catalog.subagent": "subagent",
19666
+ "catalog.all": "general Agent",
19667
+ "directory.title": "Choose project directory",
19668
+ "directory.subtitle": "OpenCode runs in this directory, and its project-specific Agents and models are loaded.",
19669
+ "directory.empty": "This folder has no subfolders",
19670
+ "logs.command": "Executed command",
19671
+ "logs.output": "Agent output",
19672
+ "logs.error": "Failure reason",
19673
+ "logs.tools": "Tool calls",
19674
+ "logs.raw": "View raw execution log",
19675
+ "logs.noText": "This run produced no displayable text. Inspect the raw log for details.",
19420
19676
  "theme.label": "Theme",
19421
19677
  "theme.system": "System",
19422
19678
  "theme.light": "Light",
19423
19679
  "theme.dark": "Dark",
19424
19680
  "language.label": "Language",
19425
- "details.title": "Data details",
19426
- "details.subtitle": "Raw record (JSON)",
19427
- "details.copySuccess": "JSON copied",
19681
+ "details.title": "Details",
19682
+ "details.subtitle": "Key information is organized below; raw data is only for troubleshooting.",
19683
+ "details.taskTitle": "Task details",
19684
+ "details.runTitle": "Run details",
19685
+ "details.templateTitle": "Scheduled task details",
19686
+ "details.raw": "View raw data (JSON)",
19687
+ "details.copySuccess": "Raw data copied",
19688
+ "details.id": "ID",
19689
+ "details.project": "Project directory",
19690
+ "details.prompt": "Prompt",
19691
+ "details.result": "Result / failure reason",
19692
+ "details.category": "Category",
19693
+ "details.batch": "Batch",
19694
+ "details.dependency": "Dependency",
19695
+ "details.importance": "Importance",
19696
+ "details.urgency": "Urgency",
19697
+ "details.retryCount": "Retries used / allowed",
19698
+ "details.retryBackoff": "Retry delay",
19699
+ "details.timeout": "Run timeout",
19700
+ "details.createdAt": "Created",
19701
+ "details.updatedAt": "Updated",
19702
+ "details.startedAt": "Started",
19703
+ "details.finishedAt": "Finished",
19704
+ "details.scheduledAt": "Scheduled for",
19705
+ "details.enabled": "Automatic runs",
19706
+ "details.scheduleRule": "Schedule rule",
19707
+ "details.maxInstances": "Automatic scheduling limit",
19708
+ "details.maxRetries": "Failure retries",
19709
+ "details.lastRun": "Last run",
19710
+ "details.nextRun": "Next run",
19711
+ "details.taskId": "Task",
19712
+ "details.session": "OpenCode session",
19713
+ "details.heartbeat": "Latest heartbeat",
19714
+ "details.process": "Processes",
19715
+ "details.history": "Run history",
19716
+ "details.noHistory": "No runs yet",
19717
+ "details.none": "None",
19718
+ "details.default": "Use system default",
19719
+ "details.enabledYes": "Enabled",
19720
+ "details.enabledNo": "Disabled",
19428
19721
  "dialog.cancelTask": "Cancel task #{id}?",
19429
19722
  "dialog.cancelTaskBody": "A running task will terminate its process tree on the next worker poll.",
19430
19723
  "dialog.retryTask": "Retry task #{id}?",
@@ -19454,6 +19747,7 @@ var EN = {
19454
19747
  "feedback.templateUpdated": "Scheduled task updated",
19455
19748
  "feedback.configSaved": "Settings saved",
19456
19749
  "feedback.sessionCommandCopied": "Session command copied. Paste it into a terminal to continue.",
19750
+ "feedback.commandCopied": "Execution command copied",
19457
19751
  "feedback.restarting": "Gateway is restarting\u2026",
19458
19752
  "feedback.restartTimeout": "Gateway has not recovered yet. Refresh later or check PM2 status.",
19459
19753
  "feedback.databaseCleared": "Database cleared. Backup: {path}",
@@ -19523,7 +19817,8 @@ function icon(name, className = "icon") {
19523
19817
  check: '<path d="m5 12 4 4L19 6"/>',
19524
19818
  alert: '<path d="M12 3 2.8 19h18.4L12 3Z"/><path d="M12 9v4M12 17h.01"/>',
19525
19819
  clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
19526
- 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"/>'
19820
+ 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"/>',
19821
+ 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"/>'
19527
19822
  };
19528
19823
  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>`;
19529
19824
  }
@@ -19730,6 +20025,20 @@ var STYLES = `
19730
20025
  .danger-card h2 .icon { width:17px; height:17px; }
19731
20026
  .danger-card p { max-width:800px; margin:0 0 14px; color:var(--text-2); font-size:12px; }
19732
20027
  .log-panel { margin:12px 0; animation:reveal .18s ease both; }
20028
+ .run-log-row td { padding:0 16px 16px; background:color-mix(in srgb,var(--surface-2) 64%,var(--surface)); }
20029
+ .run-log-row .log-panel { margin:0; box-shadow:none; }
20030
+ .log-content { display:grid; gap:14px; padding:16px; }
20031
+ .log-section-head { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:7px; }
20032
+ .run-command,.run-output,.run-error,.run-tools { min-width:0; }
20033
+ .run-command strong,.run-output>strong,.run-error>strong,.run-tools>strong { display:block; margin-bottom:7px; font-size:12px; }
20034
+ .command-cwd { margin-bottom:6px; color:var(--text-3); font-family:"SFMono-Regular",Consolas,monospace; font-size:10px; overflow-wrap:anywhere; }
20035
+ .run-command pre,.run-output pre,.run-error pre { margin:0; padding:12px; overflow:auto; border:1px solid var(--border); border-radius:9px;
20036
+ color:var(--text-2); background:var(--surface-2); font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; white-space:pre-wrap; overflow-wrap:anywhere; }
20037
+ .run-output pre { color:var(--text); font-family:inherit; font-size:13px; line-height:1.65; }
20038
+ .run-error pre { color:var(--red); border-color:color-mix(in srgb,var(--red) 28%,var(--border)); background:var(--red-soft); }
20039
+ .raw-log { padding-top:12px; border-top:1px solid var(--border); }
20040
+ .raw-log summary { color:var(--text-2); cursor:pointer; font-size:12px; font-weight:650; }
20041
+ .raw-log .log-box { margin-top:10px; border-radius:9px; }
19733
20042
  .log-box { max-height:360px; overflow:auto; padding:16px; color:var(--text-2); background:#0b1018; font-family:"SFMono-Regular",Consolas,monospace;
19734
20043
  font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
19735
20044
  :root[data-theme="light"] .log-box { color:#dbe5f3; }
@@ -19748,14 +20057,48 @@ var STYLES = `
19748
20057
  .form-field-wide { grid-column:1 / -1; }
19749
20058
  .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;
19750
20059
  outline:none; color:var(--text); background:var(--surface-2); font-weight:450; }
20060
+ .field-action { display:flex; align-items:stretch; gap:7px; }
20061
+ .field-action input { min-width:0; flex:1; }
20062
+ .field-action .btn { flex:0 0 auto; white-space:nowrap; }
20063
+ .model-selector { display:grid; grid-template-columns:minmax(120px,.75fr) minmax(0,1.25fr); gap:7px; }
20064
+ .duration-picker { display:grid; gap:7px; }
20065
+ .duration-control { display:grid; grid-template-columns:minmax(0,1fr) 112px; gap:7px; }
20066
+ .duration-control input,.duration-control select { min-width:0; }
19751
20067
  .form-field textarea { resize:vertical; line-height:1.5; }
19752
20068
  .form-field input:focus,.form-field select:focus,.form-field textarea:focus { border-color:var(--primary); box-shadow:var(--focus); background:var(--surface); }
19753
20069
  .form-field small { color:var(--text-3); font-size:10px; font-weight:450; }
19754
20070
  .advanced-fields { margin-top:18px; padding-top:14px; border-top:1px solid var(--border); }
19755
20071
  .advanced-fields summary { margin-bottom:14px; color:var(--text-2); cursor:pointer; font-size:12px; font-weight:700; }
19756
20072
  .form-note { margin:16px 0 0; color:var(--text-3); font-size:11px; }
19757
- .json-view { min-height:160px; margin:0; padding:15px; overflow:auto; border:1px solid var(--border); border-radius:10px; color:var(--text-2);
19758
- background:var(--surface-2); font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
20073
+ .catalog-status[data-state="loading"] { color:var(--blue); }
20074
+ .catalog-status[data-state="ready"] { color:var(--green); }
20075
+ .catalog-status[data-state="error"] { color:var(--red); }
20076
+ .directory-dialog { width:min(720px,calc(100% - 32px)); }
20077
+ .directory-toolbar { display:flex; align-items:center; gap:8px; margin-bottom:12px; }
20078
+ .directory-path { min-width:0; flex:1; padding:9px 11px; overflow:hidden; border:1px solid var(--border); border-radius:9px;
20079
+ color:var(--text-2); background:var(--surface-2); font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; text-overflow:ellipsis; white-space:nowrap; }
20080
+ .directory-list { min-height:260px; max-height:48vh; display:grid; align-content:start; gap:6px; overflow:auto; }
20081
+ .directory-item { width:100%; min-height:40px; display:flex; align-items:center; gap:9px; padding:0 11px; border:1px solid transparent; border-radius:9px;
20082
+ 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; }
20083
+ .directory-item:hover { color:var(--text); border-color:var(--border); background:var(--surface-2); }
20084
+ .directory-item:active { transform:scale(.99); }
20085
+ .directory-item .icon { width:17px; height:17px; color:var(--primary); flex:0 0 auto; }
20086
+ .directory-empty { min-height:220px; display:grid; place-items:center; color:var(--text-3); }
20087
+ .detail-view { display:grid; gap:16px; }
20088
+ .detail-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; }
20089
+ .detail-item { min-width:0; padding:12px 13px; border:1px solid var(--border); border-radius:10px; background:var(--surface-2); }
20090
+ .detail-item.wide { grid-column:1 / -1; }
20091
+ .detail-label { margin-bottom:5px; color:var(--text-3); font-size:10px; font-weight:750; letter-spacing:.045em; text-transform:uppercase; }
20092
+ .detail-value { color:var(--text); font-size:13px; line-height:1.55; overflow-wrap:anywhere; }
20093
+ .detail-value.mono { font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; }
20094
+ .detail-value.long { max-height:240px; margin:0; overflow:auto; white-space:pre-wrap; }
20095
+ .detail-history h3 { margin:0 0 8px; font-size:13px; }
20096
+ .detail-history-list { display:grid; gap:7px; }
20097
+ .detail-history-item { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:9px 11px; border:1px solid var(--border); border-radius:9px; background:var(--surface-2); font-size:11px; }
20098
+ .detail-raw { padding-top:12px; border-top:1px solid var(--border); }
20099
+ .detail-raw summary { color:var(--text-2); cursor:pointer; font-size:12px; font-weight:650; }
20100
+ .json-view { max-height:320px; margin:10px 0 0; padding:15px; overflow:auto; border:1px solid var(--border); border-radius:10px; color:var(--text-2);
20101
+ background:var(--surface-2); font-family:"SFMono-Regular",Consolas,monospace; font-size:11px; white-space:pre-wrap; overflow-wrap:anywhere; }
19759
20102
  .confirm-copy { color:var(--text-2); margin:0; }
19760
20103
  .confirm-copy strong { display:block; margin-bottom:5px; color:var(--text); font-size:15px; }
19761
20104
  .danger-input { width:100%; height:40px; margin-top:14px; padding:0 12px; border:1px solid var(--border); border-radius:9px; outline:none;
@@ -19819,9 +20162,17 @@ var STYLES = `
19819
20162
  .responsive-table td[data-primary]::before { display:none; }
19820
20163
  .responsive-table .task-prompt { max-width:100%; }
19821
20164
  .responsive-table .actions { justify-content:flex-start; }
20165
+ .responsive-table .run-log-row { padding:0; overflow:hidden; }
20166
+ .responsive-table .run-log-cell { display:block; padding:0; }
20167
+ .responsive-table .run-log-cell::before { display:none; }
19822
20168
  .project-grid { grid-template-columns:1fr; }
19823
20169
  .template-form-grid { grid-template-columns:1fr; }
20170
+ .detail-grid { grid-template-columns:1fr; }
20171
+ .detail-item.wide { grid-column:auto; }
19824
20172
  .form-field-wide { grid-column:auto; }
20173
+ .field-action { align-items:stretch; flex-direction:column; }
20174
+ .field-action .btn { width:100%; }
20175
+ .model-selector { grid-template-columns:1fr; }
19825
20176
  }
19826
20177
  @media (max-width:520px) {
19827
20178
  .stats-grid,.stats-grid.three { grid-template-columns:1fr 1fr; }
@@ -19851,7 +20202,70 @@ function clientMessages(locale) {
19851
20202
  "action.refresh",
19852
20203
  "action.logs",
19853
20204
  "action.hideLogs",
20205
+ "details.title",
20206
+ "details.subtitle",
20207
+ "details.taskTitle",
20208
+ "details.runTitle",
20209
+ "details.templateTitle",
20210
+ "details.raw",
19854
20211
  "details.copySuccess",
20212
+ "details.id",
20213
+ "details.project",
20214
+ "details.prompt",
20215
+ "details.result",
20216
+ "details.category",
20217
+ "details.batch",
20218
+ "details.dependency",
20219
+ "details.importance",
20220
+ "details.urgency",
20221
+ "details.retryCount",
20222
+ "details.retryBackoff",
20223
+ "details.timeout",
20224
+ "details.createdAt",
20225
+ "details.updatedAt",
20226
+ "details.startedAt",
20227
+ "details.finishedAt",
20228
+ "details.scheduledAt",
20229
+ "details.enabled",
20230
+ "details.scheduleRule",
20231
+ "details.maxInstances",
20232
+ "details.maxRetries",
20233
+ "details.lastRun",
20234
+ "details.nextRun",
20235
+ "details.taskId",
20236
+ "details.session",
20237
+ "details.heartbeat",
20238
+ "details.process",
20239
+ "details.history",
20240
+ "details.noHistory",
20241
+ "details.none",
20242
+ "details.default",
20243
+ "details.enabledYes",
20244
+ "details.enabledNo",
20245
+ "table.name",
20246
+ "table.agent",
20247
+ "table.model",
20248
+ "table.status",
20249
+ "table.duration",
20250
+ "template.scheduleType",
20251
+ "status.pending",
20252
+ "status.running",
20253
+ "status.done",
20254
+ "status.failed",
20255
+ "status.dead_letter",
20256
+ "status.cancelled",
20257
+ "status.unknown",
20258
+ "runStatus.running",
20259
+ "runStatus.done",
20260
+ "runStatus.failed",
20261
+ "schedule.cron",
20262
+ "schedule.recurring",
20263
+ "schedule.delayed",
20264
+ "schedule.unknown",
20265
+ "duration.seconds",
20266
+ "duration.minutes",
20267
+ "duration.hours",
20268
+ "duration.days",
19855
20269
  "feedback.copyFailed",
19856
20270
  "dialog.cancelTask",
19857
20271
  "dialog.cancelTaskBody",
@@ -19893,7 +20307,20 @@ function clientMessages(locale) {
19893
20307
  "feedback.restartTimeout",
19894
20308
  "template.createTitle",
19895
20309
  "template.editTitle",
19896
- "filter.noResults"
20310
+ "filter.noResults",
20311
+ "catalog.chooseProject",
20312
+ "catalog.defaultModel",
20313
+ "catalog.defaultProvider",
20314
+ "catalog.loading",
20315
+ "catalog.loaded",
20316
+ "catalog.failed",
20317
+ "catalog.primary",
20318
+ "catalog.subagent",
20319
+ "catalog.all",
20320
+ "directory.empty",
20321
+ "feedback.commandCopied",
20322
+ "action.showHidden",
20323
+ "action.hideHidden"
19897
20324
  ];
19898
20325
  return Object.fromEntries(keys.map((key) => [key, t(locale, key)]));
19899
20326
  }
@@ -19961,9 +20388,14 @@ function renderLayout(options) {
19961
20388
  <footer>${t(locale, "app.footer")}</footer>
19962
20389
  </div>
19963
20390
  <div id="toast-region" class="toast-region" role="status" aria-live="polite"></div>
20391
+ <dialog id="directory-dialog" class="directory-dialog">
20392
+ <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>
20393
+ <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>
20394
+ <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>
20395
+ </dialog>
19964
20396
  <dialog id="detail-dialog">
19965
- <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>
19966
- <div class="dialog-body"><pre id="detail-content" class="json-view"></pre></div>
20397
+ <div class="dialog-head"><div><h2 id="detail-title">${t(locale, "details.title")}</h2><p>${t(locale, "details.subtitle")}</p></div><button class="icon-button" onclick="document.getElementById('detail-dialog').close()" aria-label="${t(locale, "action.close")}">${icon("close")}</button></div>
20398
+ <div class="dialog-body"><div id="detail-content" class="detail-view"></div><details class="detail-raw"><summary>${t(locale, "details.raw")}</summary><pre id="detail-raw" class="json-view"></pre></details></div>
19967
20399
  <div class="dialog-actions"><button class="btn" onclick="copyDetails()">${icon("copy")}${t(locale, "action.copy")}</button><button class="btn btn-primary" onclick="document.getElementById('detail-dialog').close()">${t(locale, "action.close")}</button></div>
19968
20400
  </dialog>
19969
20401
  <dialog id="confirm-dialog">
@@ -19995,30 +20427,77 @@ function renderLayout(options) {
19995
20427
  async function retryTask(id){if(!await ask(text('dialog.retryTask',{id}),text('dialog.retryTaskBody')))return;try{await readJson(await fetch('/api/tasks/'+id+'/retry',{method:'POST'}));location.reload()}catch(error){showToast(text('feedback.retryFailed')+': '+error.message,'error')}}
19996
20428
  async function cancelTask(id){if(!await ask(text('dialog.cancelTask',{id}),text('dialog.cancelTaskBody'),true))return;try{await readJson(await fetch('/api/tasks/'+id+'/cancel',{method:'POST'}));location.reload()}catch(error){showToast(text('feedback.cancelFailed')+': '+error.message,'error')}}
19997
20429
  async function deleteTask(id){if(!await ask(text('dialog.deleteTask',{id}),text('dialog.deleteTaskBody'),true))return;try{await readJson(await fetch('/api/tasks/'+id,{method:'DELETE'}));location.reload()}catch(error){showToast(text('feedback.deleteFailed')+': '+error.message,'error')}}
19998
- async function showRecord(url){try{const data=await readJson(await fetch(url));document.getElementById('detail-content').textContent=JSON.stringify(data,null,2);document.getElementById('detail-dialog').showModal()}catch(error){showToast(error.message,'error')}}
19999
- const showDetail=id=>showRecord('/api/tasks/'+id);const showRunDetail=id=>showRecord('/api/runs/'+id);const showTemplateDetail=id=>showRecord('/api/templates/'+id);
20000
- async function copyDetails(){try{await navigator.clipboard.writeText(document.getElementById('detail-content').textContent);showToast(text('details.copySuccess'))}catch{showToast(text('feedback.copyFailed'),'error')}}
20430
+ function detailDate(value){if(value===null||value===undefined||value==='')return text('details.none');const epoch=typeof value==='number'&&value<100000000000?value*1000:value;const date=new Date(epoch);return Number.isNaN(date.getTime())?String(value):date.toLocaleString(document.documentElement.lang)}
20431
+ function detailDuration(value){if(value===null||value===undefined)return text('details.default');const milliseconds=Number(value);if(!Number.isFinite(milliseconds))return String(value);if(milliseconds===0)return '0 ms';const units=[[86400000,text('duration.days')],[3600000,text('duration.hours')],[60000,text('duration.minutes')],[1000,text('duration.seconds')]];for(const [size,label] of units){if(milliseconds>=size&&milliseconds%size===0)return String(milliseconds/size)+' '+label}return String(milliseconds)+' ms'}
20432
+ function detailStatus(type,value){const prefix=type==='run'?'runStatus.':'status.';const key=prefix+String(value||'unknown');return Object.prototype.hasOwnProperty.call(UI,key)?text(key):text('status.unknown')}
20433
+ function detailScheduleType(value){const key='schedule.'+String(value||'unknown');return Object.prototype.hasOwnProperty.call(UI,key)?text(key):text('schedule.unknown')}
20434
+ function detailModel(value){return !value||value==='default'?text('details.default'):String(value)}
20435
+ function detailSession(value){if(!value)return text('details.none');const session=String(value);return session.length<=10?session:session.slice(0,6)+'***'+session.slice(-4)}
20436
+ function detailTaskResult(data){const presentation=data._resultPresentation;if(!presentation)return text('details.none');const parts=[];if(Array.isArray(presentation.errors)&&presentation.errors.length)parts.push(presentation.errors.join('\\n'));if(presentation.text)parts.push(presentation.text);return parts.join('\\n\\n')||text('details.none')}
20437
+ function detailField(label,value,options={}){const item=document.createElement('div');item.className='detail-item'+(options.wide?' wide':'');const name=document.createElement('div');name.className='detail-label';name.textContent=label;const content=options.long?document.createElement('pre'):document.createElement('div');content.className='detail-value'+(options.mono?' mono':'')+(options.long?' long':'');content.textContent=value===null||value===undefined||value===''?text('details.none'):String(value);item.append(name,content);return item}
20438
+ function renderDetailHistory(runs){const section=document.createElement('section');section.className='detail-history';const title=document.createElement('h3');title.textContent=text('details.history');section.appendChild(title);if(!Array.isArray(runs)||runs.length===0){const empty=document.createElement('div');empty.className='muted small';empty.textContent=text('details.noHistory');section.appendChild(empty);return section}const list=document.createElement('div');list.className='detail-history-list';for(const run of runs){const item=document.createElement('div');item.className='detail-history-item';const primary=document.createElement('strong');primary.textContent='Run #'+run.id+' \xB7 '+detailStatus('run',run.status);const secondary=document.createElement('span');secondary.className='muted';secondary.textContent=detailDate(run.startedAt)+(run.model?' \xB7 '+detailModel(run.model):'');item.append(primary,secondary);list.appendChild(item)}section.appendChild(list);return section}
20439
+ function detailFields(type,data){if(type==='task')return [
20440
+ [text('details.id'),'#'+data.id],[text('table.name'),data.name],[text('table.status'),detailStatus('task',data.status)],[text('details.project'),data.cwd,{wide:true,mono:true}],
20441
+ [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
20442
+ [text('details.category'),data.category],[text('details.batch'),data.batchId],[text('details.dependency'),data.dependsOn?'#'+data.dependsOn:text('details.none')],
20443
+ [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.retryCount'),String(data.retryCount??0)+' / '+String(data.maxRetries??0)],
20444
+ [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.scheduledAt'),detailDate(data.scheduledAt)],
20445
+ [text('details.createdAt'),detailDate(data.createdAt)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
20446
+ [text('details.result'),detailTaskResult(data),{wide:true,long:true}]
20447
+ ];if(type==='run'){const started=data.startedAt?new Date(data.startedAt).getTime():null;const finished=data.finishedAt?new Date(data.finishedAt).getTime():null;return [
20448
+ [text('details.id'),'Run #'+data.id],[text('details.taskId'),'#'+data.taskId],[text('table.status'),detailStatus('run',data.status)],[text('table.model'),detailModel(data.model)],
20449
+ [text('details.session'),detailSession(data.sessionId)],[text('details.startedAt'),detailDate(data.startedAt)],[text('details.finishedAt'),detailDate(data.finishedAt)],
20450
+ [text('table.duration'),started!==null?detailDuration((finished??Date.now())-started):text('details.none')],[text('details.heartbeat'),detailDate(data.heartbeatAt)],
20451
+ [text('details.process'),'Worker PID '+String(data.workerPid??'\u2014')+' \xB7 OpenCode PID '+String(data.childPid??'\u2014'),{wide:true,mono:true}]
20452
+ ];}const scheduleRule=data.scheduleType==='cron'?data.cronExpr:data.scheduleType==='recurring'?detailDuration(data.intervalMs):detailDate(data.runAt);return [
20453
+ [text('details.id'),'#'+data.id],[text('table.name'),data.name],[text('details.enabled'),data.enabled?text('details.enabledYes'):text('details.enabledNo')],[text('details.project'),data.cwd,{wide:true,mono:true}],
20454
+ [text('table.agent'),data.agent],[text('table.model'),detailModel(data.model)],[text('details.prompt'),data.prompt,{wide:true,long:true}],
20455
+ [text('template.scheduleType'),detailScheduleType(data.scheduleType)],[text('details.scheduleRule'),scheduleRule],[text('details.category'),data.category],[text('details.batch'),data.batchId],
20456
+ [text('details.importance'),data.importance],[text('details.urgency'),data.urgency],[text('details.maxInstances'),data.maxInstances],[text('details.maxRetries'),data.maxRetries??0],
20457
+ [text('details.retryBackoff'),detailDuration(data.retryBackoffMs)],[text('details.timeout'),detailDuration(data.timeoutMs)],[text('details.lastRun'),detailDate(data.lastRunAt)],[text('details.nextRun'),detailDate(data.nextRunAt)],
20458
+ [text('details.createdAt'),detailDate(data.createdAt)],[text('details.updatedAt'),detailDate(data.updatedAt)]
20459
+ ]}
20460
+ async function showRecord(url,type){try{const data=await readJson(await fetch(url));const content=document.getElementById('detail-content');content.replaceChildren();const grid=document.createElement('div');grid.className='detail-grid';for(const [label,value,options] of detailFields(type,data))grid.appendChild(detailField(label,value,options));content.appendChild(grid);if(type==='task')content.appendChild(renderDetailHistory(data._runs));document.getElementById('detail-title').textContent=text(type==='task'?'details.taskTitle':type==='run'?'details.runTitle':'details.templateTitle');document.getElementById('detail-raw').textContent=JSON.stringify(data,null,2);document.querySelector('#detail-dialog .detail-raw').open=false;document.getElementById('detail-dialog').showModal()}catch(error){showToast(error.message,'error')}}
20461
+ const showDetail=id=>showRecord('/api/tasks/'+id,'task');const showRunDetail=id=>showRecord('/api/runs/'+id,'run');const showTemplateDetail=id=>showRecord('/api/templates/'+id,'template');
20462
+ async function copyDetails(){try{await navigator.clipboard.writeText(document.getElementById('detail-raw').textContent);showToast(text('details.copySuccess'))}catch{showToast(text('feedback.copyFailed'),'error')}}
20001
20463
  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')}}
20464
+ async function copyRunCommand(id){try{await navigator.clipboard.writeText(document.getElementById('command-'+id).textContent);showToast(text('feedback.commandCopied'))}catch{showToast(text('feedback.copyFailed'),'error')}}
20002
20465
  function taskField(name){return document.getElementById('task-'+name)}
20466
+ function templateField(name){return document.getElementById('template-'+name)}
20003
20467
  function taskProjects(){const node=document.getElementById('task-project-data');if(!node)return {};try{return JSON.parse(node.textContent||'{}')}catch{return {}}}
20004
20468
  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')}
20005
- 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)}
20006
- 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')}}
20007
- 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}}
20008
- function templateField(name){return document.getElementById('template-'+name)}
20009
- 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'}
20469
+ const catalogTimers={};const catalogRequests={};const catalogModels={};
20470
+ function catalogField(prefix,name){return document.getElementById(prefix+'-'+name)}
20471
+ function resetCatalog(prefix){const agent=catalogField(prefix,'agent');const provider=catalogField(prefix,'model-provider');const model=catalogField(prefix,'model');if(!agent||!provider||!model)return;catalogModels[prefix]=[];agent.replaceChildren(new Option(text('catalog.chooseProject'),''));provider.replaceChildren(new Option(text('catalog.defaultProvider'),''));model.replaceChildren(new Option(text('catalog.defaultModel'),'default'));agent.disabled=false;provider.disabled=true;model.disabled=true;catalogField(prefix,'catalog-status').textContent='';}
20472
+ function appendCurrentOption(select,value){if(!value||[...select.options].some(option=>option.value===value))return;select.appendChild(new Option(value,value));}
20473
+ function modelProvider(value){const slash=value.indexOf('/');return slash>0?value.slice(0,slash):''}
20474
+ function populateModelOptions(prefix,preferredModel=''){const provider=catalogField(prefix,'model-provider');const model=catalogField(prefix,'model');if(!provider||!model)return;const selectedProvider=provider.value;model.replaceChildren();if(!selectedProvider){model.appendChild(new Option(text('catalog.defaultModel'),'default'));model.disabled=true;return}const available=(catalogModels[prefix]||[]).filter(value=>modelProvider(value)===selectedProvider);for(const value of available)model.appendChild(new Option(value,value));if(preferredModel&&available.includes(preferredModel))model.value=preferredModel;model.disabled=available.length===0}
20475
+ async function loadCatalog(prefix,preferredAgent='',preferredModel='default',preserveUnavailable=false){const cwd=catalogField(prefix,'cwd')?.value.trim()||'';const status=catalogField(prefix,'catalog-status');const agent=catalogField(prefix,'agent');const provider=catalogField(prefix,'model-provider');const model=catalogField(prefix,'model');if(!cwd||!status||!agent||!provider||!model){if(!cwd)resetCatalog(prefix);return}const request=(catalogRequests[prefix]||0)+1;catalogRequests[prefix]=request;status.dataset.state='loading';status.textContent=text('catalog.loading');agent.disabled=true;provider.disabled=true;model.disabled=true;try{const data=await readJson(await fetch('/api/opencode/catalog?cwd='+encodeURIComponent(cwd)));if(catalogRequests[prefix]!==request||catalogField(prefix,'cwd').value.trim()!==cwd)return;agent.replaceChildren();for(const item of data.agents){const label=item.name+' \u2014 '+text('catalog.'+item.mode);agent.appendChild(new Option(label,item.name))}if(preserveUnavailable)appendCurrentOption(agent,preferredAgent);const defaultAgent=preferredAgent||data.agents.find(item=>item.name==='build')?.name||data.agents.find(item=>item.mode==='primary')?.name||data.agents[0]?.name||'';agent.value=[...agent.options].some(option=>option.value===defaultAgent)?defaultAgent:(agent.options[0]?.value||'');catalogModels[prefix]=[...data.models];if(preserveUnavailable&&preferredModel&&preferredModel!=='default'&&!catalogModels[prefix].includes(preferredModel))catalogModels[prefix].push(preferredModel);provider.replaceChildren(new Option(text('catalog.defaultProvider'),''));for(const name of [...new Set(catalogModels[prefix].map(modelProvider).filter(Boolean))].sort())provider.appendChild(new Option(name,name));const preferredProvider=preferredModel==='default'?'':modelProvider(preferredModel);provider.value=[...provider.options].some(option=>option.value===preferredProvider)?preferredProvider:'';populateModelOptions(prefix,preferredModel);status.dataset.state='ready';status.textContent=text('catalog.loaded',{agents:data.agents.length,models:data.models.length})}catch(error){if(catalogRequests[prefix]!==request)return;resetCatalog(prefix);if(preserveUnavailable){appendCurrentOption(agent,preferredAgent);if(preferredModel&&preferredModel!=='default'){catalogModels[prefix]=[preferredModel];appendCurrentOption(provider,modelProvider(preferredModel));provider.value=modelProvider(preferredModel);populateModelOptions(prefix,preferredModel)}}status.dataset.state='error';status.textContent=text('catalog.failed',{error:error.message})}finally{if(catalogRequests[prefix]===request){agent.disabled=false;provider.disabled=false;if(provider.value)model.disabled=false}}}
20476
+ function scheduleCatalogLoad(prefix){clearTimeout(catalogTimers[prefix]);catalogTimers[prefix]=setTimeout(()=>loadCatalog(prefix),450)}
20477
+ let directoryTargetId='';let directoryCurrent='';let directoryEntries=[];let directoryShowHidden=false;
20478
+ function renderDirectoryEntries(){const list=document.getElementById('directory-list');list.replaceChildren();const entries=directoryEntries.filter(entry=>directoryShowHidden||!entry.hidden);const hidden=document.getElementById('directory-hidden');hidden.textContent=text(directoryShowHidden?'action.hideHidden':'action.showHidden');if(entries.length===0){const empty=document.createElement('div');empty.className='directory-empty';empty.textContent=text('directory.empty');list.appendChild(empty);return}for(const entry of entries){const button=document.createElement('button');button.type='button';button.className='directory-item';button.innerHTML='${icon("folder")}<span></span>';button.querySelector('span').textContent=entry.name;button.onclick=()=>browseDirectory(entry.path);list.appendChild(button)}}
20479
+ 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}}
20480
+ document.getElementById('directory-hidden').onclick=()=>{directoryShowHidden=!directoryShowHidden;renderDirectoryEntries()};
20481
+ 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('')}
20482
+ 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)};
20483
+ function updateDurationControl(id){const preset=document.getElementById(id+'-preset');const custom=document.getElementById(id+'-custom');const input=document.getElementById(id+'-value');const visible=preset.value==='custom';custom.hidden=!visible;input.required=visible;if(visible&&!input.value)input.value='1'}
20484
+ function readDuration(id){const preset=document.getElementById(id+'-preset').value;if(preset==='')return '';if(preset!=='custom')return preset==='0'?'0':preset+'ms';const value=document.getElementById(id+'-value').value.trim();return value===''?'':value+document.getElementById(id+'-unit').value}
20485
+ function setDuration(id,milliseconds){const preset=document.getElementById(id+'-preset');const exact=milliseconds==null?'':String(milliseconds);if([...preset.options].some(option=>option.value===exact)){preset.value=exact;updateDurationControl(id);return}preset.value='custom';const input=document.getElementById(id+'-value');const unit=document.getElementById(id+'-unit');const units=[['d',86400000],['h',3600000],['min',60000],['s',1000]];let matched=false;for(const [name,factor] of units){if(milliseconds!=null&&(milliseconds===0||milliseconds%factor===0)){input.value=String(milliseconds/factor);unit.value=name;matched=true;break}}if(!matched){input.value=String((milliseconds??60000)/1000);unit.value='s'}updateDurationControl(id)}
20486
+ function openTaskCreator(){const form=document.getElementById('task-form');form.reset();taskField('id').value='';taskField('cwd').readOnly=false;taskField('cwd-picker').hidden=false;taskField('cwd').value=form.dataset.defaultCwd||'';setDuration('task-retry-backoff',30000);setDuration('task-timeout',null);taskField('dialog-title').textContent=text('task.createTitle');taskField('save').textContent=text('action.saveTask');updateTaskProjectStatus();resetCatalog('task');document.getElementById('task-dialog').showModal();if(taskField('cwd').value)loadCatalog('task');setTimeout(()=>taskField('name').focus(),50)}
20487
+ async function openTaskEditor(id){try{const data=await readJson(await fetch('/api/tasks/'+id));taskField('id').value=String(id);taskField('cwd').value=data.cwd||'';taskField('cwd').readOnly=true;taskField('cwd-picker').hidden=true;taskField('name').value=data.name||'';taskField('prompt').value=data.prompt||'';taskField('category').value=data.category||'general';taskField('batch').value=data.batchId||'';taskField('importance').value=String(data.importance??3);taskField('urgency').value=String(data.urgency??3);taskField('max-retries').value=String(data.maxRetries??3);setDuration('task-retry-backoff',data.retryBackoffMs??30000);setDuration('task-timeout',data.timeoutMs);taskField('dialog-title').textContent=text('task.editTitle');taskField('save').textContent=text('action.updateTask');updateTaskProjectStatus();resetCatalog('task');document.getElementById('task-dialog').showModal();loadCatalog('task',data.agent||'',data.model||'default',true);setTimeout(()=>taskField('name').focus(),50)}catch(error){showToast(error.message,'error')}}
20488
+ async function saveTask(event){event.preventDefault();const form=document.getElementById('task-form');if(!form.reportValidity())return;const id=taskField('id').value;const body={name:taskField('name').value,cwd:taskField('cwd').value,agent:taskField('agent').value,model:taskField('model').value,prompt:taskField('prompt').value,category:taskField('category').value,batchId:taskField('batch').value,importance:Number(taskField('importance').value),urgency:Number(taskField('urgency').value),maxRetries:Number(taskField('max-retries').value),retryBackoff:readDuration('task-retry-backoff'),timeout:readDuration('task-timeout')};const button=taskField('save');button.disabled=true;try{const data=await readJson(await fetch(id?'/api/tasks/'+id:'/api/tasks',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}));showToast(text(id?'feedback.taskUpdated':'feedback.taskCreated',{id:data.task.id}));document.getElementById('task-dialog').close();setTimeout(()=>location.assign(id?location.href:'/?cwd='+encodeURIComponent(data.task.cwd||'')),450)}catch(error){showToast(error.message,'error')}finally{button.disabled=false}}
20010
20489
  function localDateTime(milliseconds){const date=new Date(milliseconds);const local=new Date(milliseconds-date.getTimezoneOffset()*60000);return local.toISOString().slice(0,23)}
20011
- 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}}
20490
+ 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')}}
20012
20491
  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}
20013
20492
  function selectedRunAt(){const input=templateField('run-at');return resolveEditedRunAt(input.dataset.originalEpoch?Number(input.dataset.originalEpoch):null,input.dataset.originalLocal||'',input.value)}
20014
- 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)}
20015
- 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')}}
20016
- 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}}
20493
+ function openTemplateCreator(){const form=document.getElementById('template-form');form.reset();templateField('id').value='';templateField('dialog-title').textContent=text('template.createTitle');setDuration('template-interval',3600000);setDuration('template-retry-backoff',30000);setDuration('template-timeout',null);setOriginalRunAt(null);templateField('run-at').value=localDateTime(Date.now()+3600000);updateTemplateScheduleFields();resetCatalog('template');document.getElementById('template-dialog').showModal();if(templateField('cwd').value)loadCatalog('template');setTimeout(()=>templateField('name').focus(),50)}
20494
+ async function openTemplateEditor(id){try{const data=await readJson(await fetch('/api/templates/'+id));templateField('id').value=String(id);templateField('dialog-title').textContent=text('template.editTitle');templateField('name').value=data.name||'';templateField('cwd').value=data.cwd||'';templateField('prompt').value=data.prompt||'';templateField('schedule-type').value=data.scheduleType;templateField('cron').value=data.cronExpr||'';setDuration('template-interval',data.intervalMs);setOriginalRunAt(data.runAt||null);if(!data.runAt)templateField('run-at').value=localDateTime(Date.now()+3600000);templateField('category').value=data.category||'general';templateField('batch').value=data.batchId||'';templateField('importance').value=String(data.importance??3);templateField('urgency').value=String(data.urgency??3);templateField('max-instances').value=String(data.maxInstances??1);templateField('max-retries').value=String(data.maxRetries??3);setDuration('template-retry-backoff',data.retryBackoffMs??30000);setDuration('template-timeout',data.timeoutMs);updateTemplateScheduleFields();resetCatalog('template');document.getElementById('template-dialog').showModal();loadCatalog('template',data.agent||'',data.model||'default',true);setTimeout(()=>templateField('name').focus(),50)}catch(error){showToast(error.message,'error')}}
20495
+ async function saveTemplate(event){event.preventDefault();const form=document.getElementById('template-form');if(!form.reportValidity())return;const id=templateField('id').value;const type=templateField('schedule-type').value;const body={name:templateField('name').value,cwd:templateField('cwd').value,agent:templateField('agent').value,model:templateField('model').value,prompt:templateField('prompt').value,scheduleType:type,cronExpr:templateField('cron').value,interval:readDuration('template-interval'),runAt:type==='delayed'?selectedRunAt():null,category:templateField('category').value,batchId:templateField('batch').value,importance:Number(templateField('importance').value),urgency:Number(templateField('urgency').value),maxInstances:Number(templateField('max-instances').value),maxRetries:Number(templateField('max-retries').value),retryBackoff:readDuration('template-retry-backoff'),timeout:readDuration('template-timeout')};const button=templateField('save');button.disabled=true;try{await readJson(await fetch(id?'/api/templates/'+id:'/api/templates',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}));showToast(text(id?'feedback.templateUpdated':'feedback.templateCreated'));document.getElementById('template-dialog').close();setTimeout(()=>location.assign(id?location.href:'/templates'),450)}catch(error){showToast(error.message,'error')}finally{button.disabled=false}}
20017
20496
  async function enableTmpl(id){try{await readJson(await fetch('/api/templates/'+id+'/enable',{method:'POST'}));location.reload()}catch(error){showToast(error.message,'error')}}
20018
20497
  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')}}
20019
20498
  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')}}
20020
20499
  async function triggerTmpl(id){if(!await ask(text('dialog.triggerTemplate'),text('dialog.triggerTemplateBody')))return;try{const data=await readJson(await fetch('/api/templates/'+id+'/trigger',{method:'POST'}));showToast(text('feedback.triggered',{id:data.taskId}));setTimeout(()=>location.reload(),550)}catch(error){showToast(error.message,'error')}}
20021
- function toggleLog(id,button){const panel=document.getElementById('log-'+id);const hidden=!panel.hidden;panel.hidden=hidden;button.setAttribute('aria-expanded',String(!hidden));button.textContent=text(hidden?'action.logs':'action.hideLogs');}
20500
+ function toggleLog(id,button){const panel=document.getElementById('log-'+id);const hidden=!panel.hidden;panel.hidden=hidden;button.setAttribute('aria-expanded',String(!hidden));button.textContent=text(hidden?'action.logs':'action.hideLogs');if(!hidden)requestAnimationFrame(()=>panel.scrollIntoView({block:'nearest',behavior:'smooth'}));}
20022
20501
  function filterTasks(value){const query=value.trim().toLocaleLowerCase();let visible=0;document.querySelectorAll('[data-task-row]').forEach(row=>{const match=!query||row.dataset.search.toLocaleLowerCase().includes(query);row.hidden=!match;if(match)visible++});const empty=document.getElementById('search-empty');if(empty)empty.hidden=visible!==0;}
20023
20502
  async function clearDatabase(){if(!await askDanger())return;try{const data=await readJson(await fetch('/api/database/clear',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({confirmation:'CLEAR'})}));showToast(text('feedback.databaseCleared',{path:data.backupPath}));setTimeout(()=>location.reload(),1000)}catch(error){showToast(error.message,'error')}}
20024
20503
  async function confirmGatewayRestart(runningCount=0){const body=runningCount>0?text('dialog.restartGatewayRunningBody',{count:runningCount}):text('dialog.restartGatewayBody');return await ask(text('dialog.restartGateway'),body)}
@@ -20083,6 +20562,23 @@ function parsePositiveInteger(value) {
20083
20562
  function parseTaskStatus(value) {
20084
20563
  return TASK_STATUSES.has(value) ? value : null;
20085
20564
  }
20565
+ function listChildDirectories(path) {
20566
+ return readdirSync(path, { withFileTypes: true }).flatMap((entry) => {
20567
+ const entryPath = join4(path, entry.name);
20568
+ if (entry.isDirectory()) return [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }];
20569
+ if (!entry.isSymbolicLink()) return [];
20570
+ try {
20571
+ return statSync4(entryPath).isDirectory() ? [{ name: entry.name, path: entryPath, hidden: entry.name.startsWith(".") }] : [];
20572
+ } catch {
20573
+ return [];
20574
+ }
20575
+ }).sort((left, right) => {
20576
+ const leftHidden = left.name.startsWith(".");
20577
+ const rightHidden = right.name.startsWith(".");
20578
+ if (leftHidden !== rightHidden) return leftHidden ? 1 : -1;
20579
+ return left.name.localeCompare(right.name);
20580
+ });
20581
+ }
20086
20582
  function parseTaskPayload(value) {
20087
20583
  if (!value || typeof value !== "object" || Array.isArray(value)) {
20088
20584
  throw new Error("\u8BF7\u6C42\u5185\u5BB9\u5FC5\u987B\u662F\u5BF9\u8C61");
@@ -20260,6 +20756,33 @@ app.get("/health", (c) => {
20260
20756
  const health = getGatewayHealth();
20261
20757
  return c.json(health, health.status === "ok" ? 200 : 503);
20262
20758
  });
20759
+ app.get("/api/filesystem/directories", (c) => {
20760
+ const requestedPath = c.req.query("path")?.trim() || homedir4();
20761
+ try {
20762
+ validateTaskWorkingDirectory(requestedPath);
20763
+ return c.json({
20764
+ path: requestedPath,
20765
+ parent: dirname4(requestedPath),
20766
+ home: homedir4(),
20767
+ directories: listChildDirectories(requestedPath)
20768
+ });
20769
+ } catch (error) {
20770
+ return c.json({
20771
+ error: error instanceof Error ? error.message : String(error)
20772
+ }, 400);
20773
+ }
20774
+ });
20775
+ app.get("/api/opencode/catalog", async (c) => {
20776
+ const cwd = c.req.query("cwd")?.trim();
20777
+ if (!cwd) return c.json({ error: "cwd \u4E0D\u80FD\u4E3A\u7A7A" }, 400);
20778
+ try {
20779
+ return c.json(await loadOpenCodeCatalog(cwd));
20780
+ } catch (error) {
20781
+ return c.json({
20782
+ error: error instanceof Error ? error.message : String(error)
20783
+ }, 400);
20784
+ }
20785
+ });
20263
20786
  function formatDuration(startAt, endAt) {
20264
20787
  if (!startAt) return "\u2014";
20265
20788
  const start = new Date(startAt).getTime();
@@ -20270,6 +20793,66 @@ function formatDuration(startAt, endAt) {
20270
20793
  if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
20271
20794
  return `${Math.floor(seconds / 3600)}h ${Math.floor(seconds % 3600 / 60)}m`;
20272
20795
  }
20796
+ function recordValue(value) {
20797
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
20798
+ }
20799
+ function shellQuote(value) {
20800
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value;
20801
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
20802
+ }
20803
+ function presentRunLog(log) {
20804
+ let command = null;
20805
+ const textParts = [];
20806
+ const errors = [];
20807
+ const tools = [];
20808
+ for (const line of log.split("\n")) {
20809
+ const trimmed = line.trim();
20810
+ if (!trimmed) continue;
20811
+ let parsed = null;
20812
+ try {
20813
+ parsed = recordValue(JSON.parse(trimmed));
20814
+ } catch {
20815
+ errors.push(line);
20816
+ continue;
20817
+ }
20818
+ if (!parsed) continue;
20819
+ if (parsed.type === "supertask_command") {
20820
+ const executable = typeof parsed.executable === "string" ? parsed.executable : null;
20821
+ const cwd = typeof parsed.cwd === "string" ? parsed.cwd : null;
20822
+ const args = Array.isArray(parsed.args) && parsed.args.every((item) => typeof item === "string") ? parsed.args : null;
20823
+ if (executable && cwd && args) {
20824
+ command = {
20825
+ cwd,
20826
+ command: `cd ${shellQuote(cwd)} && ${[executable, ...args].map(shellQuote).join(" ")}`
20827
+ };
20828
+ }
20829
+ continue;
20830
+ }
20831
+ const part = recordValue(parsed.part);
20832
+ const eventType = typeof parsed.type === "string" ? parsed.type : "";
20833
+ const partType = typeof part?.type === "string" ? part.type : "";
20834
+ const text2 = typeof part?.text === "string" ? part.text : typeof parsed.text === "string" ? parsed.text : null;
20835
+ if (text2 && (eventType === "text" || partType === "text")) textParts.push(text2);
20836
+ const tool = typeof part?.tool === "string" ? part.tool : typeof parsed.tool === "string" ? parsed.tool : null;
20837
+ if (tool && (eventType === "tool_use" || partType === "tool")) tools.push(tool);
20838
+ const error = typeof parsed.error === "string" ? parsed.error : typeof part?.error === "string" ? part.error : null;
20839
+ if (error) errors.push(error);
20840
+ }
20841
+ return {
20842
+ command,
20843
+ text: textParts.join("\n").trim(),
20844
+ errors: [...new Set(errors)],
20845
+ tools
20846
+ };
20847
+ }
20848
+ function renderRunLog(runId, taskName, log, locale) {
20849
+ const presentation = presentRunLog(log);
20850
+ 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>` : "";
20851
+ const errors = presentation.errors.length > 0 ? `<div class="run-error"><strong>${t(locale, "logs.error")}</strong><pre>${esc(presentation.errors.join("\n"))}</pre></div>` : "";
20852
+ 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>` : "";
20853
+ const output = `<div class="run-output"><strong>${t(locale, "logs.output")}</strong><pre>${esc(presentation.text || t(locale, "logs.noText"))}</pre></div>`;
20854
+ return `<section class="panel log-panel"><div class="panel-head"><h3>Run #${runId} \xB7 ${esc(taskName)}</h3></div><div class="log-content">${command}${errors}${output}${tools}<details class="raw-log"><summary>${t(locale, "logs.raw")}</summary><div class="log-box">${esc(log)}</div></details></div></section>`;
20855
+ }
20273
20856
  function esc(value) {
20274
20857
  if (!value) return "";
20275
20858
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -20301,6 +20884,25 @@ function emptyState(title, hint, code = "") {
20301
20884
  return `<div class="empty-state"><div><div class="empty-icon">${icon("inbox")}</div>
20302
20885
  <h3>${title}</h3><p>${hint}</p>${code ? `<code>${code}</code>` : ""}</div></div>`;
20303
20886
  }
20887
+ function durationControl(locale, id, value, kind) {
20888
+ const units = [
20889
+ { value: "s", label: t(locale, "duration.seconds") },
20890
+ { value: "min", label: t(locale, "duration.minutes") },
20891
+ { value: "h", label: t(locale, "duration.hours") },
20892
+ { value: "d", label: t(locale, "duration.days") }
20893
+ ];
20894
+ const values = kind === "interval" ? [3e5, 9e5, 18e5, 36e5, 216e5, 432e5, 864e5] : kind === "retry" ? [0, 1e4, 3e4, 6e4, 3e5] : [3e5, 9e5, 18e5, 36e5, 72e5, 144e5];
20895
+ const presets = [
20896
+ ...kind === "timeout" ? [{ value: "", label: t(locale, "duration.systemDefault") }] : [],
20897
+ ...values.map((milliseconds) => ({
20898
+ value: String(milliseconds),
20899
+ label: milliseconds === 0 ? t(locale, "duration.immediate") : kind === "interval" ? t(locale, "duration.every", { duration: formatInterval(milliseconds, locale) }) : formatInterval(milliseconds, locale)
20900
+ })),
20901
+ { value: "custom", label: t(locale, "duration.custom") }
20902
+ ];
20903
+ const selected = value === null ? "" : String(value);
20904
+ 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>`;
20905
+ }
20304
20906
  function formatInterval(milliseconds, locale) {
20305
20907
  const formatter = new Intl.NumberFormat(locale === "en" ? "en-US" : "zh-CN", {
20306
20908
  maximumFractionDigits: 1
@@ -20465,13 +21067,14 @@ app.get("/", async (c) => {
20465
21067
  <div class="dialog-body">
20466
21068
  <div class="template-form-grid">
20467
21069
  <label class="form-field"><span>${t(locale, "template.name")}</span><input id="task-name" required maxlength="200" autocomplete="off"></label>
20468
- <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>
21070
+ <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>
20469
21071
  <datalist id="task-cwd-options">${projects.map((project) => `<option value="${esc(project.cwd)}"></option>`).join("")}</datalist>
20470
- <label class="form-field"><span>${t(locale, "template.agent")}</span><input id="task-agent" required autocomplete="off" value="build" placeholder="build"></label>
20471
- <label class="form-field"><span>${t(locale, "template.model")}</span><input id="task-model" required autocomplete="off" value="default" placeholder="default"></label>
21072
+ <label class="form-field"><span>${t(locale, "template.agent")}</span><select id="task-agent" required><option value="">${t(locale, "catalog.chooseProject")}</option></select><small>${t(locale, "catalog.agentHint")}</small></label>
21073
+ <label class="form-field"><span>${t(locale, "template.model")}</span><div class="model-selector"><select id="task-model-provider" aria-label="${t(locale, "catalog.provider")}" onchange="populateModelOptions('task')"><option value="">${t(locale, "catalog.defaultProvider")}</option></select><select id="task-model" required aria-label="${t(locale, "catalog.model")}" disabled><option value="default">${t(locale, "catalog.defaultModel")}</option></select></div><small>${t(locale, "catalog.modelHint")}</small></label>
20472
21074
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="task-prompt" rows="6" required></textarea></label>
20473
21075
  </div>
20474
21076
  <p id="task-project-status" class="form-note"></p>
21077
+ <p id="task-catalog-status" class="form-note catalog-status"></p>
20475
21078
  <details class="advanced-fields">
20476
21079
  <summary>${t(locale, "template.advanced")}</summary>
20477
21080
  <div class="template-form-grid">
@@ -20480,8 +21083,8 @@ app.get("/", async (c) => {
20480
21083
  <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>
20481
21084
  <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>
20482
21085
  <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>
20483
- <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>
20484
- <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>
21086
+ <label class="form-field"><span>${t(locale, "template.retryBackoff")}</span>${durationControl(locale, "task-retry-backoff", 3e4, "retry")}<small>${t(locale, "template.retryBackoffHint")}</small></label>
21087
+ <label class="form-field"><span>${t(locale, "template.timeout")}</span>${durationControl(locale, "task-timeout", null, "timeout")}<small>${t(locale, "template.timeoutHint")}</small></label>
20485
21088
  </div>
20486
21089
  </details>
20487
21090
  </div>
@@ -20541,15 +21144,16 @@ app.get("/templates", async (c) => {
20541
21144
  <div class="dialog-body">
20542
21145
  <div class="template-form-grid">
20543
21146
  <label class="form-field"><span>${t(locale, "template.name")}</span><input id="template-name" required maxlength="200" autocomplete="off"></label>
20544
- <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>
20545
- <label class="form-field"><span>${t(locale, "template.agent")}</span><input id="template-agent" required autocomplete="off" placeholder="build"></label>
20546
- <label class="form-field"><span>${t(locale, "template.model")}</span><input id="template-model" required autocomplete="off" value="default" placeholder="default"></label>
21147
+ <label class="form-field"><span>${t(locale, "template.cwd")}</span><div class="field-action"><input id="template-cwd" required autocomplete="off" placeholder="/path/to/project" oninput="scheduleCatalogLoad('template')"><button type="button" class="btn" onclick="openDirectoryPicker('template-cwd')">${icon("folder")}${t(locale, "action.chooseFolder")}</button></div><small>${t(locale, "template.cwdHint")}</small></label>
21148
+ <label class="form-field"><span>${t(locale, "template.agent")}</span><select id="template-agent" required><option value="">${t(locale, "catalog.chooseProject")}</option></select><small>${t(locale, "catalog.agentHint")}</small></label>
21149
+ <label class="form-field"><span>${t(locale, "template.model")}</span><div class="model-selector"><select id="template-model-provider" aria-label="${t(locale, "catalog.provider")}" onchange="populateModelOptions('template')"><option value="">${t(locale, "catalog.defaultProvider")}</option></select><select id="template-model" required aria-label="${t(locale, "catalog.model")}" disabled><option value="default">${t(locale, "catalog.defaultModel")}</option></select></div><small>${t(locale, "catalog.modelHint")}</small></label>
20547
21150
  <label class="form-field form-field-wide"><span>${t(locale, "template.prompt")}</span><textarea id="template-prompt" rows="6" required></textarea></label>
20548
21151
  <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>
20549
21152
  <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>
20550
- <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>
21153
+ <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>
20551
21154
  <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>
20552
21155
  </div>
21156
+ <p id="template-catalog-status" class="form-note catalog-status"></p>
20553
21157
  <details class="advanced-fields">
20554
21158
  <summary>${t(locale, "template.advanced")}</summary>
20555
21159
  <div class="template-form-grid">
@@ -20559,8 +21163,8 @@ app.get("/templates", async (c) => {
20559
21163
  <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>
20560
21164
  <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>
20561
21165
  <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>
20562
- <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>
20563
- <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>
21166
+ <label class="form-field"><span>${t(locale, "template.retryBackoff")}</span>${durationControl(locale, "template-retry-backoff", 3e4, "retry")}<small>${t(locale, "template.retryBackoffHint")}</small></label>
21167
+ <label class="form-field"><span>${t(locale, "template.timeout")}</span>${durationControl(locale, "template-timeout", null, "timeout")}<small>${t(locale, "template.timeoutHint")}</small></label>
20564
21168
  </div>
20565
21169
  </details>
20566
21170
  <p class="form-note">${t(locale, "template.futureOnly")}</p>
@@ -20597,14 +21201,11 @@ app.get("/runs", async (c) => {
20597
21201
  const total = Number(totalResult[0]?.count ?? 0);
20598
21202
  const totalPages = Math.max(1, Math.ceil(total / limit));
20599
21203
  if (page > totalPages) return c.redirect(`/runs?page=${totalPages}`);
20600
- const logs = [];
20601
21204
  const rows = runs.map((run) => {
20602
21205
  const status = safeStatus(run.status);
20603
21206
  const resumable = isValidSessionId(run.sessionId);
20604
- if (run.log) {
20605
- 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>`);
20606
- }
20607
- return `<tr>
21207
+ const log = run.log ? renderRunLog(run.id, run.taskName, run.log, locale) : "";
21208
+ return `<tr class="run-summary-row">
20608
21209
  <td class="faint" data-label="${t(locale, "table.run")}">#${run.id}</td>
20609
21210
  <td data-primary data-label="${t(locale, "table.task")}"><div class="task-name">${esc(run.taskName)} <span class="faint">#${run.taskId}</span></div>${run.model ? `<div style="margin-top:4px"><span class="tag">${esc(run.model)}</span></div>` : ""}</td>
20610
21211
  <td data-label="${t(locale, "table.agent")}"><span class="tag">${esc(run.taskAgent)}</span></td>
@@ -20615,7 +21216,7 @@ app.get("/runs", async (c) => {
20615
21216
  <td data-label="${t(locale, "table.actions")}"><div class="actions"><button type="button" class="btn" onclick="showRunDetail(${run.id})">${t(locale, "action.details")}</button>
20616
21217
  ${resumable ? `<button type="button" class="btn" onclick="copySessionCommand(${run.id})">${icon("copy")}${t(locale, "action.continueSession")}</button>` : ""}
20617
21218
  ${run.log ? `<button type="button" class="btn" aria-expanded="false" onclick="toggleLog(${run.id},this)">${t(locale, "action.logs")}</button>` : ""}</div></td>
20618
- </tr>`;
21219
+ </tr>${log ? `<tr id="log-${run.id}" class="run-log-row" hidden><td class="run-log-cell" colspan="8">${log}</td></tr>` : ""}`;
20619
21220
  }).join("");
20620
21221
  const body = `
20621
21222
  <div class="stats-grid">
@@ -20625,7 +21226,7 @@ app.get("/runs", async (c) => {
20625
21226
  ${statCard(runs.filter((run) => run.status === "running").length, t(locale, "stats.pageRunning"), "tone-blue", icon("activity"), "reveal-delay-2")}
20626
21227
  </div>
20627
21228
  <section class="panel reveal reveal-delay-2">${runs.length === 0 ? emptyState(t(locale, "empty.runs"), "") : `<div class="table-wrap"><table class="responsive-table"><thead><tr><th>${t(locale, "table.run")}</th><th>${t(locale, "table.task")}</th><th>${t(locale, "table.agent")}</th><th>${t(locale, "table.session")}</th><th>${t(locale, "table.status")}</th><th>${t(locale, "table.duration")}</th><th>${t(locale, "table.heartbeat")}</th><th>${t(locale, "table.actions")}</th></tr></thead><tbody>${rows}</tbody></table></div>`}</section>
20628
- ${logs.join("")}${pagination(locale, "/runs", page, totalPages, total)}`;
21229
+ ${pagination(locale, "/runs", page, totalPages, total)}`;
20629
21230
  return c.html(renderLayout({ locale, activeTab: "runs", body }));
20630
21231
  });
20631
21232
  app.get("/system", async (c) => {
@@ -20732,7 +21333,11 @@ app.get("/api/tasks/:id", async (c) => {
20732
21333
  const task = await TaskService.getById(id);
20733
21334
  if (!task) return c.json({ error: "not found" }, 404);
20734
21335
  const runs = await TaskRunService.listByTaskId(id);
20735
- return c.json({ ...task, _runs: runs });
21336
+ return c.json({
21337
+ ...task,
21338
+ _resultPresentation: task.resultLog ? presentRunLog(task.resultLog) : null,
21339
+ _runs: runs
21340
+ });
20736
21341
  });
20737
21342
  app.get("/api/runs/:id", async (c) => {
20738
21343
  const id = parsePositiveInteger(c.req.param("id"));
@@ -20929,6 +21534,7 @@ export {
20929
21534
  dashboardApp,
20930
21535
  web_default as default,
20931
21536
  isSafeDashboardRestartTarget,
21537
+ presentRunLog,
20932
21538
  resolveDashboardConfigState,
20933
21539
  setDashboardRuntimeConfig
20934
21540
  };