@linxin666/dsh-client-ui-task-board 0.4.3 → 0.4.4

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.
Files changed (53) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +17 -10
  3. package/README.zh.md +17 -10
  4. package/lib/client.js +1350 -552
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +655 -134
  7. package/lib/types/client/board/TaskForm.d.ts +19 -1
  8. package/lib/types/client/board/preset-label.d.ts +22 -0
  9. package/lib/types/client/host-api.d.ts +24 -1
  10. package/lib/types/client/locales.d.ts +38 -2
  11. package/lib/types/client/schedule-zone.d.ts +47 -0
  12. package/lib/types/core/controller.d.ts +1 -0
  13. package/lib/types/core/degraded-reason.d.ts +46 -0
  14. package/lib/types/core/package-name.d.ts +9 -0
  15. package/lib/types/core/schedule.d.ts +95 -20
  16. package/lib/types/core/subtask.d.ts +30 -11
  17. package/lib/types/core/tasks.d.ts +26 -2
  18. package/lib/types/core/use-cases/task-create.d.ts +2 -1
  19. package/lib/types/core/use-cases/task-schedule.d.ts +8 -4
  20. package/lib/types/core/use-cases/task-update.d.ts +1 -1
  21. package/lib/types/host-ledger.d.ts +56 -4
  22. package/lib/types/host-runner.d.ts +76 -3
  23. package/lib/types/host-service.d.ts +26 -0
  24. package/lib/types/protocol.d.ts +9 -3
  25. package/package.json +25 -25
  26. package/src/client/board/LinkSubtaskModal.tsx +21 -11
  27. package/src/client/board/NewTaskModal.tsx +271 -150
  28. package/src/client/board/TaskCard.tsx +24 -5
  29. package/src/client/board/TaskDetail.tsx +83 -33
  30. package/src/client/board/TaskForm.tsx +48 -3
  31. package/src/client/board/preset-label.ts +44 -0
  32. package/src/client/board.module.css +114 -5
  33. package/src/client/host-api.ts +109 -6
  34. package/src/client/index.ts +5 -1
  35. package/src/client/locales.ts +82 -8
  36. package/src/client/schedule-zone.ts +135 -0
  37. package/src/core/controller.ts +7 -2
  38. package/src/core/degraded-reason.ts +81 -0
  39. package/src/core/package-name.ts +9 -0
  40. package/src/core/schedule.ts +211 -55
  41. package/src/core/store.ts +10 -1
  42. package/src/core/subtask.ts +77 -22
  43. package/src/core/tasks.ts +38 -4
  44. package/src/core/use-cases/task-create.ts +14 -2
  45. package/src/core/use-cases/task-schedule.ts +17 -6
  46. package/src/core/use-cases/task-update.ts +4 -1
  47. package/src/host/agent-tools.ts +40 -8
  48. package/src/host-ledger.ts +203 -23
  49. package/src/host-runner.ts +164 -14
  50. package/src/host-service.ts +94 -17
  51. package/src/protocol.ts +27 -8
  52. package/lib/types/host/run-guarded.d.ts +0 -39
  53. package/src/host/run-guarded.ts +0 -76
package/lib/client.js CHANGED
@@ -159,6 +159,7 @@ window.__ModuleLoader__.load({
159
159
  model: normalizeTargetId(input.model),
160
160
  reuseSession: input.reuseSession === true ? true : void 0,
161
161
  teamRun: input.teamRun === true ? true : void 0,
162
+ goalRun: input.goalRun === false ? false : void 0,
162
163
  ...input.freeze === void 0 ? {} : { freeze: freezeOf(input.freeze, now) },
163
164
  ...input.handover === void 0 ? {} : { handover: {
164
165
  ...input.handover,
@@ -186,11 +187,14 @@ window.__ModuleLoader__.load({
186
187
  const schedule = {
187
188
  enabled: current?.enabled ?? false,
188
189
  cron: current?.cron ?? "",
190
+ ...current?.timeZone === void 0 ? {} : { timeZone: current.timeZone },
189
191
  nextRunAt: current?.nextRunAt,
190
192
  lastTriggeredAt: current?.lastTriggeredAt
191
193
  };
192
194
  if ("enabled" in patch) schedule.enabled = patch.enabled ?? false;
193
195
  if ("cron" in patch) schedule.cron = patch.cron ?? "";
196
+ if ("timeZone" in patch) if (patch.timeZone === void 0) delete schedule.timeZone;
197
+ else schedule.timeZone = patch.timeZone;
194
198
  if ("nextRunAt" in patch) schedule.nextRunAt = patch.nextRunAt;
195
199
  if ("lastTriggeredAt" in patch) schedule.lastTriggeredAt = patch.lastTriggeredAt;
196
200
  return {
@@ -222,6 +226,22 @@ window.__ModuleLoader__.load({
222
226
  function isExecutionOutcome(value) {
223
227
  return value === "succeeded" || value === "failed" || value === "cancelled";
224
228
  }
229
+ /** Build the reusable index for a task list (ledger order preserved). */
230
+ function buildLineageIndex(tasks) {
231
+ const byId = /* @__PURE__ */ new Map();
232
+ const childrenByParent = /* @__PURE__ */ new Map();
233
+ for (const task of tasks) {
234
+ byId.set(task.id, task);
235
+ if (task.parentId === void 0) continue;
236
+ const siblings = childrenByParent.get(task.parentId);
237
+ if (siblings === void 0) childrenByParent.set(task.parentId, [task]);
238
+ else siblings.push(task);
239
+ }
240
+ return {
241
+ byId,
242
+ childrenByParent
243
+ };
244
+ }
225
245
  function indexOf(tasks) {
226
246
  return new Map(tasks.map((task) => [task.id, task]));
227
247
  }
@@ -231,8 +251,8 @@ window.__ModuleLoader__.load({
231
251
  * under a larger limit survives that limit being lowered, and restore and
232
252
  * inheritance must still see all of it.
233
253
  */
234
- function ancestorChain(tasks, task) {
235
- const index = indexOf(tasks);
254
+ function ancestorChain(tasks, task, lineage) {
255
+ const index = lineage?.byId ?? indexOf(tasks);
236
256
  const chain = [];
237
257
  const seen = /* @__PURE__ */ new Set([task.id]);
238
258
  let current = task.parentId === void 0 ? void 0 : index.get(task.parentId);
@@ -245,29 +265,33 @@ window.__ModuleLoader__.load({
245
265
  return chain;
246
266
  }
247
267
  /** Root-to-task depth: a root task is 0, its subtask 1, and so on. */
248
- function taskDepth(tasks, id) {
249
- const task = indexOf(tasks).get(id);
250
- return task === void 0 ? 0 : ancestorChain(tasks, task).length;
268
+ function taskDepth(tasks, id, lineage) {
269
+ const task = (lineage?.byId ?? indexOf(tasks)).get(id);
270
+ return task === void 0 ? 0 : ancestorChain(tasks, task, lineage).length;
251
271
  }
252
272
  /** Direct subtasks of a task, in ledger order. */
253
- function directSubtasks(tasks, id) {
273
+ function directSubtasks(tasks, id, lineage) {
274
+ if (lineage !== void 0) return [...lineage.childrenByParent.get(id) ?? []];
254
275
  return tasks.filter((task) => task.parentId === id);
255
276
  }
256
277
  /**
257
278
  * Descendants of a task in breadth-first order, bounded by maxSubtaskDepth
258
279
  * total depth. The visited set keeps a malformed ledger from looping.
259
280
  */
260
- function descendantTasks(tasks, id, maxSubtaskDepth) {
281
+ function descendantTasks(tasks, id, maxSubtaskDepth, lineage) {
261
282
  const visited = /* @__PURE__ */ new Set([id]);
262
283
  const found = [];
263
284
  let frontier = [id];
264
285
  for (let depth = 1; depth <= maxSubtaskDepth && frontier.length > 0; depth += 1) {
265
286
  const next = [];
266
- for (const parentId of frontier) for (const task of tasks) {
267
- if (task.parentId !== parentId || visited.has(task.id)) continue;
268
- visited.add(task.id);
269
- found.push(task);
270
- next.push(task.id);
287
+ for (const parentId of frontier) {
288
+ const children = lineage === void 0 ? tasks.filter((task) => task.parentId === parentId) : lineage.childrenByParent.get(parentId) ?? [];
289
+ for (const task of children) {
290
+ if (visited.has(task.id)) continue;
291
+ visited.add(task.id);
292
+ found.push(task);
293
+ next.push(task.id);
294
+ }
271
295
  }
272
296
  frontier = next;
273
297
  }
@@ -278,13 +302,13 @@ window.__ModuleLoader__.load({
278
302
  * The visited set makes the walk terminate on a cyclic hand-edited ledger
279
303
  * instead of recursing until the stack overflows.
280
304
  */
281
- function subtreeHeight(tasks, id, visited = /* @__PURE__ */ new Set()) {
305
+ function subtreeHeight(tasks, id, visited = /* @__PURE__ */ new Set(), lineage) {
282
306
  if (visited.has(id)) return 0;
283
307
  const seen = /* @__PURE__ */ new Set([...visited, id]);
284
- const children = directSubtasks(tasks, id);
308
+ const children = directSubtasks(tasks, id, lineage);
285
309
  if (children.length === 0) return 0;
286
310
  let height = 0;
287
- for (const child of children) height = Math.max(height, 1 + subtreeHeight(tasks, child.id, seen));
311
+ for (const child of children) height = Math.max(height, 1 + subtreeHeight(tasks, child.id, seen, lineage));
288
312
  return Math.min(height, 3);
289
313
  }
290
314
  /**
@@ -294,8 +318,8 @@ window.__ModuleLoader__.load({
294
318
  * and the resulting depth (parent depth + 1 + the child own subtree height)
295
319
  * must stay within maxSubtaskDepth.
296
320
  */
297
- function checkParentLink(tasks, childId, parentId, maxSubtaskDepth) {
298
- if (tasks.find((task) => task.id === childId) === void 0) return {
321
+ function checkParentLink(tasks, childId, parentId, maxSubtaskDepth, lineage) {
322
+ if ((lineage === void 0 ? tasks.find((task) => task.id === childId) : lineage.byId.get(childId)) === void 0) return {
299
323
  ok: false,
300
324
  reason: "unknown-task"
301
325
  };
@@ -304,7 +328,7 @@ window.__ModuleLoader__.load({
304
328
  ok: false,
305
329
  reason: "self-parent"
306
330
  };
307
- const parent = tasks.find((task) => task.id === parentId);
331
+ const parent = lineage === void 0 ? tasks.find((task) => task.id === parentId) : lineage.byId.get(parentId);
308
332
  if (parent === void 0) return {
309
333
  ok: false,
310
334
  reason: "unknown-parent"
@@ -313,11 +337,11 @@ window.__ModuleLoader__.load({
313
337
  ok: false,
314
338
  reason: "archived-parent"
315
339
  };
316
- if (descendantTasks(tasks, childId, 3).some((task) => task.id === parentId)) return {
340
+ if (descendantTasks(tasks, childId, 3, lineage).some((task) => task.id === parentId)) return {
317
341
  ok: false,
318
342
  reason: "cycle"
319
343
  };
320
- if (taskDepth(tasks, parentId) + 1 + subtreeHeight(tasks, childId) > maxSubtaskDepth) return {
344
+ if (taskDepth(tasks, parentId, lineage) + 1 + subtreeHeight(tasks, childId, /* @__PURE__ */ new Set(), lineage) > maxSubtaskDepth) return {
321
345
  ok: false,
322
346
  reason: "depth-exceeded"
323
347
  };
@@ -424,6 +448,112 @@ window.__ModuleLoader__.load({
424
448
  [1, 12],
425
449
  [0, 7]
426
450
  ];
451
+ /** IANA `Area/Location`, plus the bare `UTC` the platform reports. */
452
+ const IANA_ZONE = /^[A-Za-z][A-Za-z0-9_+.-]*(?:\/[A-Za-z0-9_+.-]+)+$/;
453
+ /** Cached formatters: constructing an Intl formatter dominates the cost of a resolve. */
454
+ const FORMATTERS = /* @__PURE__ */ new Map();
455
+ function zoneFormatter(timeZone) {
456
+ let formatter = FORMATTERS.get(timeZone);
457
+ if (formatter === void 0) {
458
+ formatter = new Intl.DateTimeFormat("en-US", {
459
+ timeZone,
460
+ hourCycle: "h23",
461
+ year: "numeric",
462
+ month: "2-digit",
463
+ day: "2-digit",
464
+ hour: "2-digit",
465
+ minute: "2-digit"
466
+ });
467
+ FORMATTERS.set(timeZone, formatter);
468
+ }
469
+ return formatter;
470
+ }
471
+ /**
472
+ * The wall-clock reading of one instant in an explicit zone. Reading through
473
+ * Intl (rather than the process `Date` accessors) is what makes the result
474
+ * independent of the Host's own TZ.
475
+ * @param timeZone - explicit IANA zone.
476
+ * @param epochMs - instant to read.
477
+ * @returns the zone-local calendar and clock fields.
478
+ */
479
+ function partsInZone(timeZone, epochMs) {
480
+ const fields = {};
481
+ for (const part of zoneFormatter(timeZone).formatToParts(new Date(epochMs))) if (part.type !== "literal") fields[part.type] = part.value;
482
+ return {
483
+ year: Number(fields.year),
484
+ month: Number(fields.month),
485
+ day: Number(fields.day),
486
+ hour: Number(fields.hour),
487
+ minute: Number(fields.minute)
488
+ };
489
+ }
490
+ /** The zone's UTC offset in effect at one instant, in milliseconds. */
491
+ function offsetAt(timeZone, epochMs) {
492
+ const parts = partsInZone(timeZone, epochMs);
493
+ return Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute) - epochMs;
494
+ }
495
+ /**
496
+ * Resolve a wall clock in an explicit zone to its instant.
497
+ *
498
+ * The offset is probed a day either side of the naive guess so both DST
499
+ * transitions are covered, then every candidate offset whose inverse maps back
500
+ * to the requested wall clock is kept. No survivor means the wall clock does
501
+ * not exist (a spring-forward gap); several means it is ambiguous (a fall-back
502
+ * overlap), and the earliest instant wins so the repeated clock is taken once.
503
+ * @param timeZone - explicit IANA zone.
504
+ * @param year - full calendar year.
505
+ * @param month - 1-12.
506
+ * @param day - day of month.
507
+ * @param hour - 0-23.
508
+ * @param minute - 0-59.
509
+ * @returns the instant in ms epoch, or undefined when the wall clock is skipped.
510
+ */
511
+ function zonedEpoch(timeZone, year, month, day, hour, minute) {
512
+ const naive = Date.UTC(year, month - 1, day, hour, minute);
513
+ const offsets = /* @__PURE__ */ new Set([
514
+ offsetAt(timeZone, naive - 864e5),
515
+ offsetAt(timeZone, naive),
516
+ offsetAt(timeZone, naive + 864e5)
517
+ ]);
518
+ let earliest;
519
+ for (const offset of offsets) {
520
+ const candidate = naive - offset;
521
+ const parts = partsInZone(timeZone, candidate);
522
+ if (parts.year !== year || parts.month !== month || parts.day !== day) continue;
523
+ if (parts.hour !== hour || parts.minute !== minute) continue;
524
+ if (earliest === void 0 || candidate < earliest) earliest = candidate;
525
+ }
526
+ return earliest;
527
+ }
528
+ /**
529
+ * The Host process's IANA zone, used for schedules that store no zone of their
530
+ * own (a ledger written before zones were persisted).
531
+ * @returns a zone name Intl accepts, never empty.
532
+ */
533
+ function resolveHostTimeZone() {
534
+ try {
535
+ const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
536
+ return typeof zone === "string" && zone !== "" ? zone : "UTC";
537
+ } catch {
538
+ return "UTC";
539
+ }
540
+ }
541
+ /**
542
+ * Whether a value names a zone this runtime can actually resolve. A stored
543
+ * zone is trusted only after it round-trips through Intl, so a typo or a zone
544
+ * that this ICU build does not know can never arm a schedule.
545
+ * @param value - candidate zone name.
546
+ * @returns true when the name is usable.
547
+ */
548
+ function isValidTimeZone(value) {
549
+ if (value.trim() !== value || value === "") return false;
550
+ if (value !== "UTC" && !IANA_ZONE.test(value)) return false;
551
+ try {
552
+ return new Intl.DateTimeFormat("en-US", { timeZone: value }).resolvedOptions().timeZone !== void 0;
553
+ } catch {
554
+ return false;
555
+ }
556
+ }
427
557
  /**
428
558
  * Parse a 5-field cron expression.
429
559
  * @returns the match sets, or null when the expression is invalid.
@@ -446,8 +576,8 @@ window.__ModuleLoader__.load({
446
576
  days: sets[2],
447
577
  months: sets[3],
448
578
  weekdays,
449
- dayWildcard: fields[2] === "*",
450
- weekdayWildcard: fields[4] === "*"
579
+ dayStarred: fields[2].startsWith("*"),
580
+ weekdayStarred: fields[4].startsWith("*")
451
581
  };
452
582
  }
453
583
  /** Whether the expression parses. */
@@ -455,52 +585,57 @@ window.__ModuleLoader__.load({
455
585
  return parseCron(expr) !== null;
456
586
  }
457
587
  /**
458
- * Compute the next matching instant after `fromMs` (ms epoch), in local time,
459
- * at minute granularity, strictly greater than `fromMs`. Returns the ms epoch
460
- * of the matching minute's start, or undefined when the calendar constraint
461
- * can never match (for example `0 0 30 2 *`). The five-year horizon includes
462
- * a full leap cycle, so a valid February 29 schedule remains reachable from
463
- * every non-leap year.
588
+ * Compute the next matching instant after `fromMs` strictly greater than it,
589
+ * interpreting the expression's wall clock in `timeZone` (the Host zone when
590
+ * omitted). Returns the ms epoch of the matching minute's start, or undefined
591
+ * when the calendar constraint can never match (for example `0 0 30 2 *`).
592
+ * The five-year horizon includes a full leap cycle, so a valid February 29
593
+ * schedule remains reachable from every non-leap year.
464
594
  *
465
595
  * Walks candidate year/month/day/hour/minute values straight from the parsed
466
596
  * field sets instead of scanning every minute: a sparse expression such as
467
597
  * `0 0 29 2 *` used to iterate ~1.5M wall-clock minutes before reaching the
468
- * next leap day. Wall-clock field construction + the final `matches` re-check
469
- * preserve the old minute scan's DST semantics exactly (nonexistent spring
470
- * minutes normalize forward and the repeated fall-back hour is never visited).
598
+ * next leap day. A candidate wall clock that the zone skips (spring-forward
599
+ * gap) is passed over, which is what makes the gap semantics fall out of the
600
+ * candidate walk rather than needing a separate rule.
601
+ * @param expr - 5-field cron expression.
602
+ * @param fromMs - exclusive lower bound, ms epoch.
603
+ * @param timeZone - explicit IANA zone; defaults to the Host zone.
604
+ * @returns the next trigger instant, or undefined when unreachable.
471
605
  */
472
- function nextRunAtMs(expr, fromMs) {
606
+ function nextRunAtMs(expr, fromMs, timeZone) {
473
607
  const schedule = parseCron(expr);
474
608
  if (schedule === null) return void 0;
475
609
  if (!hasPossibleCalendarDay(schedule)) return void 0;
476
- const from = new Date(fromMs);
610
+ const zone = timeZone ?? resolveHostTimeZone();
611
+ const start = partsInZone(zone, fromMs);
477
612
  const limitMs = fromMs + 5 * 366 * 24 * 60 * 60 * 1e3;
478
613
  const sortedMinutes = [...schedule.minutes].sort((a, b) => a - b);
479
614
  const sortedHours = [...schedule.hours].sort((a, b) => a - b);
480
615
  const sortedMonths = [...schedule.months].sort((a, b) => a - b);
481
- let year = from.getFullYear();
482
- let month = from.getMonth() + 1;
483
- let day = from.getDate();
484
- let hour = from.getHours();
485
- let minute = from.getMinutes() + 1;
486
- while (new Date(year, month - 1, 1, 0, 0, 0, 0).getTime() <= limitMs) {
616
+ let year = start.year;
617
+ let month = start.month;
618
+ let day = start.day;
619
+ let hour = start.hour;
620
+ let minute = start.minute + 1;
621
+ while (Date.UTC(year, month - 1, 1) <= limitMs + 864e5) {
487
622
  for (const candidateMonth of sortedMonths) {
488
623
  if (candidateMonth < month) continue;
489
- const daysInMonth = new Date(year, candidateMonth, 0).getDate();
624
+ const daysInMonth = new Date(Date.UTC(year, candidateMonth, 0)).getUTCDate();
490
625
  const dayStart = candidateMonth === month ? day : 1;
491
626
  for (let candidateDay = dayStart; candidateDay <= daysInMonth; candidateDay += 1) {
492
- if (!dayCandidate(schedule, new Date(year, candidateMonth - 1, candidateDay, 0, 0, 0, 0))) continue;
627
+ if (!dayCandidate(schedule, year, candidateMonth, candidateDay)) continue;
493
628
  const hourStart = candidateMonth === month && candidateDay === day ? hour : 0;
494
629
  for (const candidateHour of sortedHours) {
495
630
  if (candidateHour < hourStart) continue;
496
631
  const minuteStart = candidateMonth === month && candidateDay === day && candidateHour === hour ? minute : 0;
497
632
  for (const candidateMinute of sortedMinutes) {
498
633
  if (candidateMinute < minuteStart) continue;
499
- const candidate = new Date(year, candidateMonth - 1, candidateDay, candidateHour, candidateMinute, 0, 0);
500
- const time = candidate.getTime();
634
+ const time = zonedEpoch(zone, year, candidateMonth, candidateDay, candidateHour, candidateMinute);
635
+ if (time === void 0) continue;
501
636
  if (time <= fromMs) continue;
502
637
  if (time > limitMs) return void 0;
503
- if (matches(schedule, candidate)) return time;
638
+ return time;
504
639
  }
505
640
  }
506
641
  }
@@ -512,17 +647,20 @@ window.__ModuleLoader__.load({
512
647
  minute = 0;
513
648
  }
514
649
  }
515
- /** Day/weekday OR gate shared by {@link matches} and the candidate scan. */
516
- function dayCandidate(schedule, date) {
517
- const dayMatches = schedule.days.has(date.getDate());
518
- const weekdayMatches = schedule.weekdays.has(date.getDay());
519
- if (schedule.dayWildcard) return weekdayMatches;
520
- if (schedule.weekdayWildcard) return dayMatches;
521
- return dayMatches || weekdayMatches;
650
+ /**
651
+ * The Vixie day gate shared by the candidate walk. Both fields restricted is
652
+ * OR; any other combination is AND, because a starred field matches every
653
+ * value and so cannot widen the result.
654
+ */
655
+ function dayCandidate(schedule, year, month, day) {
656
+ const dayMatches = schedule.days.has(day);
657
+ const weekdayMatches = schedule.weekdays.has(new Date(Date.UTC(year, month - 1, day)).getUTCDay());
658
+ if (!schedule.dayStarred && !schedule.weekdayStarred) return dayMatches || weekdayMatches;
659
+ return dayMatches && weekdayMatches;
522
660
  }
523
661
  /** Reject impossible month/day pairs without spending the multi-year scan. */
524
662
  function hasPossibleCalendarDay(schedule) {
525
- if (schedule.dayWildcard || !schedule.weekdayWildcard) return true;
663
+ if (schedule.dayStarred || !schedule.weekdayStarred) return true;
526
664
  const maximumDay = /* @__PURE__ */ new Map([
527
665
  [1, 31],
528
666
  [2, 29],
@@ -573,13 +711,6 @@ window.__ModuleLoader__.load({
573
711
  }
574
712
  return true;
575
713
  }
576
- /** Day/weekday OR semantics: a restricted day field alone gates, and vice versa. */
577
- function matches(schedule, date) {
578
- if (!schedule.minutes.has(date.getMinutes())) return false;
579
- if (!schedule.hours.has(date.getHours())) return false;
580
- if (!schedule.months.has(date.getMonth() + 1)) return false;
581
- return dayCandidate(schedule, date);
582
- }
583
714
  function isDigits(value) {
584
715
  return /^\d+$/.test(value);
585
716
  }
@@ -605,8 +736,9 @@ window.__ModuleLoader__.load({
605
736
  * @param now - clock instant (ms epoch).
606
737
  * @param id - minted task id.
607
738
  * @param maxSubtaskDepth - deployment subtask depth limit.
739
+ * @param hostTimeZone - zone a rule that stores no zone of its own follows.
608
740
  */
609
- function applyCreateTask(tasks, input, now, id, maxSubtaskDepth = 1) {
741
+ function applyCreateTask(tasks, input, now, id, maxSubtaskDepth = 1, hostTimeZone = resolveHostTimeZone()) {
610
742
  if (input.title.trim() === "") return {
611
743
  task: void 0,
612
744
  tasks,
@@ -641,10 +773,18 @@ window.__ModuleLoader__.load({
641
773
  const requested = input.schedule;
642
774
  if (requested?.enabled === true && requested.cron.trim() !== "" && isValidCron(requested.cron)) {
643
775
  const cron = requested.cron.trim();
644
- task = withSchedule(task, {
776
+ const requestedZone = requested.timeZone;
777
+ if (requestedZone !== void 0 && !isValidTimeZone(requestedZone)) return {
778
+ task: void 0,
779
+ tasks,
780
+ error: "invalid schedule time zone"
781
+ };
782
+ const nextRunAt = nextRunAtMs(cron, now, requestedZone ?? hostTimeZone);
783
+ if (nextRunAt !== void 0) task = withSchedule(task, {
645
784
  enabled: true,
646
785
  cron,
647
- nextRunAt: nextRunAtMs(cron, now)
786
+ timeZone: requestedZone,
787
+ nextRunAt
648
788
  }, now);
649
789
  }
650
790
  return {
@@ -682,15 +822,17 @@ window.__ModuleLoader__.load({
682
822
  * the core cron parser (schedule.ts) and the withSchedule transition.
683
823
  */
684
824
  /**
685
- * Set an on-board task's schedule rule. A blank or invalid cron, or an
686
- * archived task, is rejected (state untouched); an enabled rule computes the
687
- * next run instant immediately, a disabled one carries no next-run instant.
825
+ * Set an on-board task's schedule rule. A blank or invalid cron, an unknown or
826
+ * archived task, or an unusable zone is rejected (state untouched); an enabled
827
+ * rule computes the next run instant immediately in the rule's own zone, a
828
+ * disabled one carries no next-run instant.
688
829
  * @param tasks - current ledger.
689
830
  * @param id - the task to schedule.
690
831
  * @param patch - rule fields to change (absent fields keep their current value).
691
832
  * @param now - clock instant (ms epoch).
833
+ * @param hostTimeZone - zone a rule with no stored zone follows.
692
834
  */
693
- function applySetSchedule(tasks, id, patch, now) {
835
+ function applySetSchedule(tasks, id, patch, now, hostTimeZone) {
694
836
  const task = tasks.find((candidate) => candidate.id === id);
695
837
  if (task === void 0 || task.archivedAt !== void 0) return {
696
838
  tasks,
@@ -702,8 +844,13 @@ window.__ModuleLoader__.load({
702
844
  tasks,
703
845
  applied: false
704
846
  };
847
+ const requestedZone = patch.timeZone === void 0 ? current?.timeZone : patch.timeZone ?? void 0;
848
+ if (requestedZone !== void 0 && !isValidTimeZone(requestedZone)) return {
849
+ tasks,
850
+ applied: false
851
+ };
705
852
  const enabled = patch.enabled ?? current?.enabled ?? false;
706
- const nextRunAt = enabled ? nextRunAtMs(cron, now) : void 0;
853
+ const nextRunAt = enabled ? nextRunAtMs(cron, now, requestedZone ?? hostTimeZone) : void 0;
707
854
  if (enabled && nextRunAt === void 0) return {
708
855
  tasks,
709
856
  applied: false
@@ -712,6 +859,7 @@ window.__ModuleLoader__.load({
712
859
  tasks: tasks.map((candidate) => candidate.id === id ? withSchedule(candidate, {
713
860
  enabled,
714
861
  cron,
862
+ timeZone: requestedZone ?? void 0,
715
863
  nextRunAt
716
864
  }, now) : candidate),
717
865
  applied: true
@@ -867,6 +1015,7 @@ window.__ModuleLoader__.load({
867
1015
  if ("permission" in patch && patch.permission !== void 0 && patch.permission !== task.permission || "handover" in patch) next.permissionConfirmedAt = void 0;
868
1016
  if ("reuseSession" in patch) next.reuseSession = patch.reuseSession === true ? true : void 0;
869
1017
  if ("teamRun" in patch) next.teamRun = patch.teamRun === true ? true : void 0;
1018
+ if ("goalRun" in patch) next.goalRun = patch.goalRun === false ? false : void 0;
870
1019
  if (workspaceId !== void 0 || "workspaceId" in patch) next.workspaceId = workspaceId;
871
1020
  if (mode !== void 0 || "mode" in patch) next.mode = mode;
872
1021
  if (permission !== void 0 || "permission" in patch) next.permission = permission;
@@ -1217,7 +1366,8 @@ window.__ModuleLoader__.load({
1217
1366
  * @returns true when applied, false when rejected (invalid cron / unknown task).
1218
1367
  */
1219
1368
  setSchedule(id, patch) {
1220
- const { tasks, applied } = applySetSchedule(this.tasks, id, patch, this.now());
1369
+ const hostTimeZone = this.hostState?.scheduler.timeZone ?? resolveHostTimeZone();
1370
+ const { tasks, applied } = applySetSchedule(this.tasks, id, patch, this.now(), hostTimeZone);
1221
1371
  if (!applied) return false;
1222
1372
  if (this.deps.transport !== void 0) {
1223
1373
  this.commitRemote({
@@ -1744,6 +1894,7 @@ window.__ModuleLoader__.load({
1744
1894
  if (record.mode !== void 0 && typeof record.mode !== "string") return false;
1745
1895
  if (record.permission !== void 0 && typeof record.permission !== "string") return false;
1746
1896
  if (record.reuseSession !== void 0 && typeof record.reuseSession !== "boolean") return false;
1897
+ if (record.goalRun !== void 0 && typeof record.goalRun !== "boolean") return false;
1747
1898
  if (!Array.isArray(record.executions)) return false;
1748
1899
  for (const execution of record.executions) {
1749
1900
  if (typeof execution !== "object" || execution === null) return false;
@@ -1774,9 +1925,11 @@ window.__ModuleLoader__.load({
1774
1925
  const rule = schedule;
1775
1926
  if (typeof rule.cron !== "string") return void 0;
1776
1927
  if (rule.cron.trim() === "" || !isValidCron(rule.cron)) return void 0;
1928
+ const timeZone = typeof rule.timeZone === "string" && isValidTimeZone(rule.timeZone) ? rule.timeZone : void 0;
1777
1929
  return {
1778
1930
  enabled: rule.enabled === true,
1779
1931
  cron: rule.cron,
1932
+ ...timeZone === void 0 ? {} : { timeZone },
1780
1933
  nextRunAt: typeof rule.nextRunAt === "number" ? rule.nextRunAt : void 0,
1781
1934
  lastTriggeredAt: typeof rule.lastTriggeredAt === "number" ? rule.lastTriggeredAt : void 0
1782
1935
  };
@@ -1861,6 +2014,7 @@ window.__ModuleLoader__.load({
1861
2014
  task.archivedAt = typeof row.archivedAt === "number" && Number.isFinite(row.archivedAt) ? row.archivedAt : void 0;
1862
2015
  task.permission = isTaskPermission(row.permission) ? row.permission : void 0;
1863
2016
  task.reuseSession = row.reuseSession === true ? true : void 0;
2017
+ task.goalRun = row.goalRun === false ? false : void 0;
1864
2018
  task.freeze = normalizeFreeze(row.freeze);
1865
2019
  task.handover = normalizeHandover(row.handover);
1866
2020
  task.tags = normalizeTags(row.tags);
@@ -1978,8 +2132,11 @@ window.__ModuleLoader__.load({
1978
2132
  "board.updated": "更新于",
1979
2133
  "board.created": "创建于",
1980
2134
  "board.hostError": "Host 操作失败:{error}",
1981
- "board.hostError.notMounted": "任务看板的后台接口没有挂载:宿主可能没有加载该插件,也可能后台数据正被另一个 DSH 实例占用。重启 DSH 服务后再试",
2135
+ "board.hostError.notMounted": "任务看板的后台接口没有挂载:宿主没有加载这个插件(可能未安装、未启用,或该行启动失败)。重启 DSH 服务后再试",
2136
+ "board.hostError.ledgerLocked": "任务看板的账本已被另一个 DSH 进程占用(占用者 pid {pid}),本实例没有注册后台接口:关闭那个 DSH 实例,或等它退出后再点重试——反复重启本实例无效(宿主原因:{detail})",
2137
+ "board.hostError.degraded": "任务看板这一行在启动时降级,后台接口没有注册(宿主原因:{detail})",
1982
2138
  "board.hostError.unauthorized": "登录状态已失效,请刷新页面后重试",
2139
+ "board.hostError.forbidden": "当前环境未通过同源校验:桌面外壳或反向代理可能未传递浏览器标记,也没有携带宿主认证凭据",
1983
2140
  "board.hostError.locked": "任务看板的后台数据被占用:{detail}",
1984
2141
  "board.hostError.timeout": "后台 {seconds} 秒内没有响应,请稍后重试",
1985
2142
  "board.hostError.unreachable": "连接不上本机 DSH 服务,请确认它仍在运行",
@@ -2004,6 +2161,19 @@ window.__ModuleLoader__.load({
2004
2161
  "new.aiParseFailed": "解析失败:{error}",
2005
2162
  "board.retryHost": "重试连接 Host",
2006
2163
  "board.hostMeta": "Host 时区 {timeZone} · revision {revision}",
2164
+ "new.section.parse": "AI 解析",
2165
+ "new.section.content": "任务内容",
2166
+ "new.section.labels": "标签",
2167
+ "new.section.execution": "执行设置",
2168
+ "new.section.run": "运行方式",
2169
+ "new.section.handover": "交接与续跑",
2170
+ "new.section.schedule": "定时运行",
2171
+ "new.summary.none": "未设置",
2172
+ "new.summary.labelCount": "{count} 个标签",
2173
+ "new.summary.filled": "已填写",
2174
+ "new.summary.scheduleOff": "未启用",
2175
+ "new.summary.multiRound": "多轮 /goal",
2176
+ "new.summary.singleRound": "单轮",
2007
2177
  "new.title": "标题",
2008
2178
  "new.titlePlaceholder": "一句话描述要做什么",
2009
2179
  "new.description": "描述",
@@ -2095,13 +2265,31 @@ window.__ModuleLoader__.load({
2095
2265
  "detail.schedule.invalid": "Cron 表达式无效",
2096
2266
  "detail.schedule.notScheduled": "尚未排程",
2097
2267
  "detail.schedule.dueSoon": "即将运行",
2268
+ "detail.schedule.timeZone": "时区",
2269
+ "detail.schedule.timeZoneHost": "Host 时区({timeZone})",
2270
+ "detail.schedule.timeZoneHostUnknown": "Host 时区(未知)",
2271
+ "detail.schedule.timeZoneHint": "Cron 的墙上时间按此时区解读;夏令时跳变会跳过不存在的时刻,重复的时刻只触发一次。",
2272
+ "detail.schedule.timeZoneSearch": "搜索时区…",
2273
+ "detail.schedule.timeZoneNone": "没有匹配的时区",
2274
+ "detail.schedule.nextRunOverdue": "已逾期 {duration}",
2275
+ "detail.schedule.duration.days": "{count} 天",
2276
+ "detail.schedule.duration.hours": "{count} 小时",
2277
+ "detail.schedule.duration.minutes": "{count} 分钟",
2278
+ "detail.schedule.duration.seconds": "{count} 秒",
2098
2279
  "card.scheduled": "定时",
2099
2280
  "new.workspace": "工作区",
2100
- "new.mode": "模式",
2281
+ "new.agentPreset": "Agent 预设",
2101
2282
  "new.permission": "权限",
2102
2283
  "new.model": "模型",
2103
2284
  "exec.workspace.recent": "最近使用(默认)",
2104
- "exec.mode.default": "部署默认",
2285
+ "exec.mode.inherit": "继承(部署默认)",
2286
+ "exec.mode.inheritWith": "继承(部署默认:{preset})",
2287
+ "exec.mode.builtinGroup": "内置预设",
2288
+ "exec.mode.customGroup": "自定义预设",
2289
+ "preset.builtin.standard": "标准",
2290
+ "preset.builtin.ptc": "PTC(程序化工具调用)",
2291
+ "preset.builtin.minimal": "极简",
2292
+ "preset.builtin.cordis": "Cordis",
2105
2293
  "exec.mode.defaultSuffix": "(默认)",
2106
2294
  "exec.mode.brokenSuffix": "(不可用)",
2107
2295
  "exec.mode.removed": "(已移除)",
@@ -2117,8 +2305,10 @@ window.__ModuleLoader__.load({
2117
2305
  "exec.teamRunHint": "开启后:执行本任务只启动一个 Leader 会话,每个子任务由它派生为 teammate 并在该会话内并行工作(子任务自身的权限钉住不适用)。关闭则每个成员各开独立会话。",
2118
2306
  "exec.teamRunUnavailable": "当前部署未提供 Agent Teams 服务,无法启用。",
2119
2307
  "exec.reuseSessionHint": "开启后,本任务的后续执行在上一次会话里继续(该会话空闲且仍存在时),不再每次新建对话;每次复用时都会重新应用上面钉住的权限与模型。",
2308
+ "exec.goalRun": "以 dsh 内置的 /goal 开始执行任务",
2309
+ "exec.goalRunHint": "默认开启:执行时把任务目标作为持久目标交给 dsh 内置的 /goal,会话会自动续跑多轮,直到 agent 标记目标完成;看板在该目标真正结束后才结算本次执行。关闭则只执行一轮普通对话。",
2120
2310
  "detail.executionSettings": "执行设置",
2121
- "exec.hint": "执行时生效:工作区决定执行会话落在哪个工作区;模式决定会话的 agent 预设;权限经 /permission 命令应用到会话。留空则使用运行时默认。",
2311
+ "exec.hint": "执行时生效:工作区决定执行会话落在哪个工作区;Agent 预设决定会话的组合(工具面与提示词段);权限经 /permission 命令应用到会话。留空则继承部署默认。",
2122
2312
  "settings.title": "任务看板",
2123
2313
  "settings.description": "控制 Host 任务看板、agent 播报与运行期间的系统空闲睡眠保护。",
2124
2314
  "settings.enabled": "启用任务看板",
@@ -2200,8 +2390,11 @@ window.__ModuleLoader__.load({
2200
2390
  "board.updated": "Updated",
2201
2391
  "board.created": "Created",
2202
2392
  "board.hostError": "Host action failed: {error}",
2203
- "board.hostError.notMounted": "The task board Host API is not mounted: the Host may not have loaded the plugin, or another DSH instance holds the ledger. Restart the DSH service and try again",
2393
+ "board.hostError.notMounted": "The task board Host API is not mounted: the Host did not load this plugin (not installed, disabled, or its row failed to start). Restart the DSH service and try again",
2394
+ "board.hostError.ledgerLocked": "Another DSH process (pid {pid}) holds the task board ledger, so this instance registered no Host API: close that DSH instance, or wait for it to exit and retry. Restarting this instance cannot help (reason: {detail})",
2395
+ "board.hostError.degraded": "The task board row degraded during start, so its Host API was not registered: {detail}",
2204
2396
  "board.hostError.unauthorized": "This session is no longer signed in; reload the page and try again",
2397
+ "board.hostError.forbidden": "The request did not pass the same-origin fence: the desktop shell or reverse proxy may not have forwarded browser credentials",
2205
2398
  "board.hostError.locked": "The task board ledger is locked: {detail}",
2206
2399
  "board.hostError.timeout": "The Host did not answer within {seconds}s; try again shortly",
2207
2400
  "board.hostError.unreachable": "Cannot reach the local DSH service; make sure it is still running",
@@ -2226,6 +2419,19 @@ window.__ModuleLoader__.load({
2226
2419
  "new.aiParseFailed": "Parsing failed: {error}",
2227
2420
  "board.retryHost": "Retry Host connection",
2228
2421
  "board.hostMeta": "Host time zone {timeZone} · revision {revision}",
2422
+ "new.section.parse": "AI Parse",
2423
+ "new.section.content": "Task Content",
2424
+ "new.section.labels": "Tags",
2425
+ "new.section.execution": "Execution Settings",
2426
+ "new.section.run": "Run Mode",
2427
+ "new.section.handover": "Handover",
2428
+ "new.section.schedule": "Scheduled Runs",
2429
+ "new.summary.none": "Not set",
2430
+ "new.summary.labelCount": "{count} tags",
2431
+ "new.summary.filled": "Filled",
2432
+ "new.summary.scheduleOff": "Off",
2433
+ "new.summary.multiRound": "multi-round /goal",
2434
+ "new.summary.singleRound": "single round",
2229
2435
  "new.title": "Title",
2230
2436
  "new.titlePlaceholder": "What should be done, in one line",
2231
2437
  "new.description": "Description",
@@ -2317,13 +2523,31 @@ window.__ModuleLoader__.load({
2317
2523
  "detail.schedule.invalid": "Invalid cron expression",
2318
2524
  "detail.schedule.notScheduled": "Not scheduled yet",
2319
2525
  "detail.schedule.dueSoon": "Due soon",
2526
+ "detail.schedule.timeZone": "Time zone",
2527
+ "detail.schedule.timeZoneHost": "Host time zone ({timeZone})",
2528
+ "detail.schedule.timeZoneHostUnknown": "Host time zone (unknown)",
2529
+ "detail.schedule.timeZoneHint": "The cron wall clock is read in this zone; daylight-saving gaps are skipped and a repeated time fires once.",
2530
+ "detail.schedule.timeZoneSearch": "Search time zones…",
2531
+ "detail.schedule.timeZoneNone": "No matching time zone",
2532
+ "detail.schedule.nextRunOverdue": "Overdue by {duration}",
2533
+ "detail.schedule.duration.days": "{count}d",
2534
+ "detail.schedule.duration.hours": "{count}h",
2535
+ "detail.schedule.duration.minutes": "{count}m",
2536
+ "detail.schedule.duration.seconds": "{count}s",
2320
2537
  "card.scheduled": "scheduled",
2321
2538
  "new.workspace": "Workspace",
2322
- "new.mode": "Mode",
2539
+ "new.agentPreset": "Agent preset",
2323
2540
  "new.permission": "Permission",
2324
2541
  "new.model": "Model",
2325
2542
  "exec.workspace.recent": "Most recent (default)",
2326
- "exec.mode.default": "Deployment default",
2543
+ "exec.mode.inherit": "Inherit (deployment default)",
2544
+ "exec.mode.inheritWith": "Inherit (deployment default: {preset})",
2545
+ "exec.mode.builtinGroup": "Built-in presets",
2546
+ "exec.mode.customGroup": "Custom presets",
2547
+ "preset.builtin.standard": "Standard",
2548
+ "preset.builtin.ptc": "PTC (programmatic tool calling)",
2549
+ "preset.builtin.minimal": "Minimal",
2550
+ "preset.builtin.cordis": "Cordis",
2327
2551
  "exec.mode.defaultSuffix": " (default)",
2328
2552
  "exec.mode.brokenSuffix": " (unavailable)",
2329
2553
  "exec.mode.removed": " (removed)",
@@ -2339,8 +2563,10 @@ window.__ModuleLoader__.load({
2339
2563
  "exec.teamRunHint": "On: running this task starts one Lead session and spawns a teammate per subtask inside it, working in parallel (a subtask permission pin does not apply). Off: every member gets its own independent session.",
2340
2564
  "exec.teamRunUnavailable": "This deployment serves no Agent Teams service, so team runs cannot be enabled.",
2341
2565
  "exec.reuseSessionHint": "When on, later runs continue in the previous session (when that session is idle and still exists) instead of starting a new conversation each time; the pinned permission and model above are re-applied on every reuse.",
2566
+ "exec.goalRun": "Start the run with dsh's built-in /goal",
2567
+ "exec.goalRunHint": "On by default: the run arms dsh built-in /goal with the task objective, so the session keeps working automatic continuation rounds until the agent marks the goal complete, and the board settles the execution only when that goal really ends. Off: one plain turn.",
2342
2568
  "detail.executionSettings": "Execution Settings",
2343
- "exec.hint": "Applied when the task runs: the workspace decides where the execution session lands; the mode composes the session's agent preset; the permission is applied through the /permission command. Blank = runtime default.",
2569
+ "exec.hint": "Applied when the task runs: the workspace decides where the execution session lands; the agent preset composes the session (tool face and prompt sections); the permission is applied through the /permission command. Blank inherits the deployment default.",
2344
2570
  "settings.title": "Task Board",
2345
2571
  "settings.description": "Configure the Host task board, agent announcement, and idle-system-sleep protection while work is pending.",
2346
2572
  "settings.enabled": "Enable the task board",
@@ -2423,7 +2649,7 @@ window.__ModuleLoader__.load({
2423
2649
  }
2424
2650
  //#endregion
2425
2651
  //#region \0dsh-css:packages/dsh-task-board/src/client/board.module.css.mjs
2426
- const css$1 = "[data-dsh-taskboard-view]{box-sizing:border-box;background:var(--dsw-alias-bg-base);flex-direction:column;width:100%;min-width:0;height:100%;min-height:0;display:flex;container:_7D6uKa_task-board-view/inline-size}._7D6uKa_board{box-sizing:border-box;background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:12px;padding:14px 16px 16px;display:flex}._7D6uKa_boardHeader{flex:none;align-items:center;gap:10px;display:flex}._7D6uKa_boardTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:16px;font-weight:700}._7D6uKa_backButton{align-items:center;gap:4px;display:inline-flex}._7D6uKa_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}._7D6uKa_search::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_columns{overscroll-behavior-inline:contain;scrollbar-color:var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);scrollbar-width:thin;flex:1;grid-auto-columns:minmax(220px,1fr);grid-auto-flow:column;gap:12px;min-height:0;padding-bottom:6px;display:grid;overflow:auto hidden}._7D6uKa_columns::-webkit-scrollbar{height:10px}._7D6uKa_columns::-webkit-scrollbar-track{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);background-clip:content-box;border:2px solid #0000;border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-border-l4);background-clip:content-box}._7D6uKa_column{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;min-height:0;display:flex;overflow:hidden}._7D6uKa_columnHeader{flex:none;align-items:center;gap:6px;padding:10px 12px;display:flex}._7D6uKa_columnTitle{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:13px;font-weight:700;overflow:hidden}._7D6uKa_columnCount{min-width:0;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:1px 8px;font-size:12px}._7D6uKa_statusDot{border-radius:50%;flex:none;width:8px;height:8px}._7D6uKa_statusDot[data-status=backlog]{background:var(--dsw-alias-label-tertiary)}._7D6uKa_statusDot[data-status=todo]{background:var(--dsw-alias-state-business-primary)}._7D6uKa_statusDot[data-status=running]{background:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusDot[data-status=done]{background:var(--dsw-alias-state-success-primary)}._7D6uKa_statusDot[data-status=failed]{background:var(--dsw-alias-state-error-primary)}._7D6uKa_cards{flex-direction:column;flex:1;gap:8px;min-height:0;padding:2px 8px 10px;display:flex;overflow-y:auto}._7D6uKa_columnEmpty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:24px 8px;font-size:12px}._7D6uKa_card{text-align:left;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;color:var(--dsw-alias-label-primary);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;font-family:inherit;transition:box-shadow .12s,border-color .12s,transform .12s;display:flex}._7D6uKa_card:hover{box-shadow:var(--dsw-shadow-lv2);border-color:var(--dsw-alias-border-l3);transform:translateY(-1px)}._7D6uKa_card[data-status=running]{border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_cardTitle{-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:600;line-height:1.35;display:-webkit-box;overflow:hidden}._7D6uKa_cardExcerpt{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:1.4;display:-webkit-box;overflow:hidden}._7D6uKa_cardMeta{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:11px;display:flex}._7D6uKa_cardTime{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}._7D6uKa_cardSchedule{white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:2px 6px;font-size:12px;line-height:1}._7D6uKa_cardRun{flex:none}._7D6uKa_cardRun[data-result=failed]{color:var(--dsw-alias-state-error-primary)}._7D6uKa_cardRun[data-result=succeeded]{color:var(--dsw-alias-state-success-primary)}._7D6uKa_cardSession{color:var(--dsw-alias-state-business-primary);flex:none}._7D6uKa_cardRunningLabel{color:var(--dsw-alias-state-warn-primary);font-size:11px}._7D6uKa_cardSpinner{border:2px solid var(--dsw-alias-state-warn-primary);border-top-color:#0000;border-radius:50%;flex:none;width:10px;height:10px;animation:.8s linear infinite _7D6uKa_dshTbSpin}@keyframes _7D6uKa_dshTbSpin{to{transform:rotate(360deg)}}._7D6uKa_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}._7D6uKa_primaryButton:disabled{opacity:.5;cursor:default}._7D6uKa_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}._7D6uKa_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7D6uKa_ghostButton:disabled{opacity:.45;cursor:default}._7D6uKa_dangerButton{color:#fff;background:var(--dsw-alias-state-error-primary);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_dangerButton:hover:not(:disabled){filter:brightness(1.08)}._7D6uKa_dangerButton:active:not(:disabled){filter:brightness(.94)}._7D6uKa_dangerButton:disabled{opacity:.5;cursor:default}._7D6uKa_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}._7D6uKa_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}._7D6uKa_linkButton:hover{text-decoration:underline}._7D6uKa_modalBackdrop{z-index:1300;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._7D6uKa_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(520px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:12px;padding:18px;display:flex;overflow-y:auto}._7D6uKa_modalTitle{margin:0;font-size:15px;font-weight:700}._7D6uKa_confirmMessage{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.5}._7D6uKa_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}._7D6uKa_field{flex-direction:column;gap:5px;display:flex}._7D6uKa_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}._7D6uKa_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_select{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;max-width:100%;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}._7D6uKa_detail{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(640px,100vw - 48px);max-height:calc(100vh - 80px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;display:flex;overflow:hidden}._7D6uKa_detailHeader{border-bottom:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:14px 18px;display:flex}._7D6uKa_detailTitle{overflow-wrap:anywhere;flex:1;margin:0;font-size:15px;font-weight:700}._7D6uKa_statusBadge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:2px 10px;font-size:12px}._7D6uKa_statusBadge[data-status=running]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusBadge[data-status=done]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._7D6uKa_statusBadge[data-status=failed]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_detailBody{flex-direction:column;flex:1;gap:16px;padding:14px 18px;display:flex;overflow-y:auto}._7D6uKa_detailSection{flex-direction:column;gap:6px;display:flex}._7D6uKa_detailSection h4{color:var(--dsw-alias-label-tertiary);text-transform:none;margin:0;font-size:12px;font-weight:700}._7D6uKa_detailText{color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.55}._7D6uKa_scheduleToggle{color:var(--dsw-alias-label-primary);cursor:pointer;user-select:none;align-items:center;gap:8px;font-size:13px;display:flex}._7D6uKa_scheduleToggle input{accent-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleRow{align-items:center;gap:8px;display:flex}._7D6uKa_scheduleInput{min-width:0;font-family:var(--dsw-font-markdown-code-block-small);flex:1;font-size:12.5px}._7D6uKa_scheduleInputInvalid,._7D6uKa_scheduleInputInvalid:focus{border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_schedulePreset{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:none;padding:7px 8px;font-size:12.5px}._7D6uKa_scheduleMeta{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;margin:0;font-size:12px}._7D6uKa_promptBlock{font-size:12.5px;line-height:1.5;font-family:var(--dsw-font-markdown-code-block-small);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-markdown-code-block);border:1px solid var(--dsw-alias-border-l1);white-space:pre-wrap;overflow-wrap:anywhere;border-radius:8px;max-height:240px;margin:0;padding:10px 12px;overflow-y:auto}._7D6uKa_executionList{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}._7D6uKa_executionRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-wrap:wrap;align-items:center;gap:10px;padding:8px 10px;display:flex}._7D6uKa_executionBadge{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-state-warn-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:600}._7D6uKa_executionBadge[data-result=succeeded]{color:var(--dsw-alias-state-success-primary);background:0 0}._7D6uKa_executionBadge[data-result=failed]{color:var(--dsw-alias-state-error-primary);background:0 0}._7D6uKa_executionBadge[data-result=cancelled]{color:var(--dsw-alias-label-tertiary);background:0 0}._7D6uKa_executionTimes{color:var(--dsw-alias-label-secondary);font-size:12px}._7D6uKa_executionError{width:100%;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;font-size:12px}._7D6uKa_moveRow{flex-wrap:wrap;gap:8px;display:flex}._7D6uKa_detailFooter{border-top:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:12px 18px;display:flex}._7D6uKa_detailMeta{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:11px}@container _7D6uKa_task-board-view (width<=768px){._7D6uKa_board{gap:10px;padding:10px}._7D6uKa_boardHeader{flex-wrap:wrap;align-items:center;gap:8px}._7D6uKa_backButton{flex:none;order:1}._7D6uKa_boardTitle{flex:auto;order:2}._7D6uKa_boardHeader>._7D6uKa_detailMeta{flex:1 0 100%;order:3;margin-left:0}._7D6uKa_search{flex:1 0 100%;order:4;min-width:0}._7D6uKa_boardHeader>button:not(._7D6uKa_backButton){flex:1 1 0;order:5;min-width:0}._7D6uKa_columns{scroll-snap-type:inline mandatory;scrollbar-width:none;-webkit-overflow-scrolling:touch;grid-auto-columns:86cqw;gap:10px;padding-inline:2px 14cqw;scroll-padding-inline:2px}._7D6uKa_columns::-webkit-scrollbar{display:none}._7D6uKa_column{scroll-snap-align:start;scroll-snap-stop:always}}@container _7D6uKa_task-board-view (width<=720px){._7D6uKa_boardHeader>._7D6uKa_detailMeta{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}}@container _7D6uKa_task-board-view (width<=600px){._7D6uKa_board{padding-inline:8px}}@media (width<=768px){[data-dsh-taskboard-view]{height:100dvh}._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle{min-height:44px}._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset{box-sizing:border-box;font-size:16px}._7D6uKa_modalBackdrop{justify-content:stretch;align-items:stretch;width:100vw;height:100dvh}._7D6uKa_modal,._7D6uKa_detail{box-sizing:border-box;border:0;border-radius:0;width:100vw;height:100dvh;max-height:none}._7D6uKa_modal{padding-top:max(16px, env(safe-area-inset-top));padding-right:max(16px, env(safe-area-inset-right));padding-bottom:max(16px, env(safe-area-inset-bottom));padding-left:max(16px, env(safe-area-inset-left))}._7D6uKa_modalFooter{z-index:1;background:var(--dsw-alias-bg-base);flex-wrap:wrap;padding-top:8px;position:sticky;bottom:0}._7D6uKa_modalFooter>button{flex:120px}._7D6uKa_detailHeader{padding-top:max(12px, env(safe-area-inset-top));padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailTitle{min-width:0}._7D6uKa_detailBody{overscroll-behavior-y:contain;padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left))}._7D6uKa_detailFooter{padding-right:max(14px, env(safe-area-inset-right));padding-bottom:max(12px, env(safe-area-inset-bottom));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailFooter>button{flex:96px}._7D6uKa_detailFooter>._7D6uKa_detailMeta{text-align:end;flex:1 0 100%;margin-left:0}._7D6uKa_scheduleRow{flex-direction:column;align-items:stretch}._7D6uKa_schedulePreset{width:100%}}._7D6uKa_card:focus-visible,._7D6uKa_primaryButton:focus-visible,._7D6uKa_ghostButton:focus-visible,._7D6uKa_dangerButton:focus-visible,._7D6uKa_iconButton:focus-visible,._7D6uKa_linkButton:focus-visible,._7D6uKa_search:focus-visible,._7D6uKa_input:focus-visible,._7D6uKa_select:focus-visible,._7D6uKa_schedulePreset:focus-visible,._7D6uKa_scheduleToggle input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:background-color .12s,color .12s,border-color .12s,outline-color .12s,box-shadow .12s,transform .12s}._7D6uKa_card:active{box-shadow:var(--dsw-shadow-lv1);transform:translateY(0)}._7D6uKa_primaryButton:active:not(:disabled),._7D6uKa_ghostButton:active:not(:disabled),._7D6uKa_dangerButton:active:not(:disabled),._7D6uKa_iconButton:active:not(:disabled),._7D6uKa_linkButton:active:not(:disabled){transform:translateY(1px)}._7D6uKa_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton:hover:not(:disabled){text-decoration:underline}._7D6uKa_iconButton:disabled,._7D6uKa_linkButton:disabled{opacity:.45;cursor:default}._7D6uKa_search:focus,._7D6uKa_select:focus,._7D6uKa_schedulePreset:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleToggle input{margin:0}@media (prefers-reduced-motion:reduce){._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:none}._7D6uKa_cardSpinner{animation:none}}._7D6uKa_cardTags{flex-wrap:wrap;gap:4px;display:flex}._7D6uKa_cardTag{border:1px solid var(--dsh-task-tag-border);background:var(--dsh-task-tag-fill);max-width:100%;color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;border-radius:999px;padding:0 7px;font-size:10px;line-height:16px;overflow:hidden}._7D6uKa_tagFilter{flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 10px;display:flex}._7D6uKa_tagFilterLabel{color:var(--dsw-alias-label-tertiary);font-size:11px}._7D6uKa_tagChip{border:1px solid var(--dsh-task-tag-border);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:999px;padding:1px 9px;font-family:inherit;font-size:11px;line-height:18px}._7D6uKa_tagChip[data-active=true]{background:var(--dsh-task-tag-fill);color:var(--dsw-alias-label-primary)}._7D6uKa_cardTag[data-tag-tone=\"0\"],._7D6uKa_tagChip[data-tag-tone=\"0\"]{--dsh-task-tag-fill:#4e93e82e;--dsh-task-tag-border:#4e93e866}._7D6uKa_cardTag[data-tag-tone=\"1\"],._7D6uKa_tagChip[data-tag-tone=\"1\"]{--dsh-task-tag-fill:#2ea36a2e;--dsh-task-tag-border:#2ea36a66}._7D6uKa_cardTag[data-tag-tone=\"2\"],._7D6uKa_tagChip[data-tag-tone=\"2\"]{--dsh-task-tag-fill:#d08a2a2e;--dsh-task-tag-border:#d08a2a66}._7D6uKa_cardTag[data-tag-tone=\"3\"],._7D6uKa_tagChip[data-tag-tone=\"3\"]{--dsh-task-tag-fill:#b456c82e;--dsh-task-tag-border:#b456c866}._7D6uKa_cardTag[data-tag-tone=\"4\"],._7D6uKa_tagChip[data-tag-tone=\"4\"]{--dsh-task-tag-fill:#cf5f7a2e;--dsh-task-tag-border:#cf5f7a66}._7D6uKa_cardTag[data-tag-tone=\"5\"],._7D6uKa_tagChip[data-tag-tone=\"5\"]{--dsh-task-tag-fill:#4a9fb52e;--dsh-task-tag-border:#4a9fb566}._7D6uKa_fieldHint{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.4}._7D6uKa_tagRow{align-items:center;gap:6px;display:flex}._7D6uKa_tagRow ._7D6uKa_input{flex:1 1 0;min-width:0}._7D6uKa_tagRow ._7D6uKa_ghostButton{flex:none}._7D6uKa_tagAddButton{align-self:flex-start}._7D6uKa_projectFilter{flex:none;align-items:center;gap:6px;display:flex}._7D6uKa_projectFilterLabel{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:12px}._7D6uKa_projectDialog{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;flex-direction:column;flex:none;gap:8px;margin-bottom:8px;padding:10px 12px;display:flex}._7D6uKa_projectDialogActions{justify-content:flex-end;gap:8px;display:flex}._7D6uKa_aiParse{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._7D6uKa_aiParseRow{align-items:center;gap:8px;display:flex}._7D6uKa_aiParseRow ._7D6uKa_select{flex:1 1 0;min-width:0}._7D6uKa_aiParseRow ._7D6uKa_ghostButton,._7D6uKa_aiParseRow ._7D6uKa_primaryButton{flex:none}._7D6uKa_cardSubtask{border:1px solid var(--dsw-alias-border-l2);max-width:100%;color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;background:0 0;border-radius:999px;padding:0 7px;font-size:10px;line-height:16px;overflow:hidden}._7D6uKa_cardSubtask[data-tone=failed]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_cardSubtask[data-tone=running]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_cardSubtask[data-tone=done]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._7D6uKa_subtaskList{flex-direction:column;gap:6px;margin:0;padding:0;list-style:none;display:flex}._7D6uKa_subtaskRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-wrap:wrap;align-items:center;gap:10px;padding:6px 10px;display:flex}._7D6uKa_subtaskRow ._7D6uKa_linkButton:first-child{text-align:left;overflow-wrap:anywhere;flex:auto;min-width:0}._7D6uKa_subtaskAddRow{flex-wrap:wrap;gap:8px;display:flex}._7D6uKa_pickList{flex-direction:column;gap:6px;max-height:320px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}._7D6uKa_pickRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;align-items:center;gap:10px;padding:6px 10px;display:flex}._7D6uKa_pickTitle{min-width:0;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;flex:auto;font-size:13px}";
2652
+ const css$1 = "[data-dsh-taskboard-view]{box-sizing:border-box;background:var(--dsw-alias-bg-base);flex-direction:column;width:100%;min-width:0;height:100%;min-height:0;display:flex;container:_7D6uKa_task-board-view/inline-size}._7D6uKa_board{box-sizing:border-box;background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:12px;padding:14px 16px 16px;display:flex}._7D6uKa_boardHeader{flex:none;align-items:center;gap:10px;display:flex}._7D6uKa_boardTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:16px;font-weight:700}._7D6uKa_backButton{align-items:center;gap:4px;display:inline-flex}._7D6uKa_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}._7D6uKa_search::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_columns{overscroll-behavior-inline:contain;scrollbar-color:var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);scrollbar-width:thin;flex:1;grid-auto-columns:minmax(220px,1fr);grid-auto-flow:column;gap:12px;min-height:0;padding-bottom:6px;display:grid;overflow:auto hidden}._7D6uKa_columns::-webkit-scrollbar{height:10px}._7D6uKa_columns::-webkit-scrollbar-track{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);background-clip:content-box;border:2px solid #0000;border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-border-l4);background-clip:content-box}._7D6uKa_column{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;min-height:0;display:flex;overflow:hidden}._7D6uKa_columnHeader{flex:none;align-items:center;gap:6px;padding:10px 12px;display:flex}._7D6uKa_columnTitle{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:13px;font-weight:700;overflow:hidden}._7D6uKa_columnCount{min-width:0;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:1px 8px;font-size:12px}._7D6uKa_statusDot{border-radius:50%;flex:none;width:8px;height:8px}._7D6uKa_statusDot[data-status=backlog]{background:var(--dsw-alias-label-tertiary)}._7D6uKa_statusDot[data-status=todo]{background:var(--dsw-alias-state-business-primary)}._7D6uKa_statusDot[data-status=running]{background:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusDot[data-status=done]{background:var(--dsw-alias-state-success-primary)}._7D6uKa_statusDot[data-status=failed]{background:var(--dsw-alias-state-error-primary)}._7D6uKa_cards{flex-direction:column;flex:1;gap:8px;min-height:0;padding:2px 8px 10px;display:flex;overflow-y:auto}._7D6uKa_columnEmpty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:24px 8px;font-size:12px}._7D6uKa_card{text-align:left;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;color:var(--dsw-alias-label-primary);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;font-family:inherit;transition:box-shadow .12s,border-color .12s,transform .12s;display:flex}._7D6uKa_card:hover{box-shadow:var(--dsw-shadow-lv2);border-color:var(--dsw-alias-border-l3);transform:translateY(-1px)}._7D6uKa_card[data-status=running]{border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_cardTitle{-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:600;line-height:1.35;display:-webkit-box;overflow:hidden}._7D6uKa_cardExcerpt{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:1.4;display:-webkit-box;overflow:hidden}._7D6uKa_cardMeta{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:11px;display:flex}._7D6uKa_cardTime{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}._7D6uKa_cardSchedule{white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:2px 6px;font-size:12px;line-height:1}._7D6uKa_cardRun{flex:none}._7D6uKa_cardRun[data-result=failed]{color:var(--dsw-alias-state-error-primary)}._7D6uKa_cardRun[data-result=succeeded]{color:var(--dsw-alias-state-success-primary)}._7D6uKa_cardSession{color:var(--dsw-alias-state-business-primary);flex:none}._7D6uKa_cardRunningLabel{color:var(--dsw-alias-state-warn-primary);font-size:11px}._7D6uKa_cardSpinner{border:2px solid var(--dsw-alias-state-warn-primary);border-top-color:#0000;border-radius:50%;flex:none;width:10px;height:10px;animation:.8s linear infinite _7D6uKa_dshTbSpin}@keyframes _7D6uKa_dshTbSpin{to{transform:rotate(360deg)}}._7D6uKa_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}._7D6uKa_primaryButton:disabled{opacity:.5;cursor:default}._7D6uKa_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}._7D6uKa_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7D6uKa_ghostButton:disabled{opacity:.45;cursor:default}._7D6uKa_dangerButton{color:#fff;background:var(--dsw-alias-state-error-primary);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_dangerButton:hover:not(:disabled){filter:brightness(1.08)}._7D6uKa_dangerButton:active:not(:disabled){filter:brightness(.94)}._7D6uKa_dangerButton:disabled{opacity:.5;cursor:default}._7D6uKa_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}._7D6uKa_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}._7D6uKa_linkButton:hover{text-decoration:underline}._7D6uKa_modalBackdrop{z-index:1300;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._7D6uKa_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(520px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:10px;padding:18px;display:flex;overflow:hidden}._7D6uKa_modalTitle{margin:0;font-size:15px;font-weight:700}._7D6uKa_confirmMessage{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.5}._7D6uKa_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}._7D6uKa_modalBody{flex-direction:column;gap:8px;min-height:0;display:flex;overflow-y:auto}._7D6uKa_modalBody>*{flex:none}._7D6uKa_formSection{border:1px solid var(--dsw-alias-separator-primary);border-radius:10px;flex-direction:column;display:flex;overflow:hidden}._7D6uKa_formSectionHeader{width:100%;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;gap:8px;padding:7px 10px;font-family:inherit;font-size:12.5px;font-weight:600;display:flex}._7D6uKa_formSectionHeader:hover{background:var(--dsw-alias-bg-mask-1)}._7D6uKa_formSectionChevron{border-top:4px solid #0000;border-bottom:4px solid #0000;border-left:5px solid var(--dsw-alias-label-tertiary);flex:none;width:0;height:0;margin-left:1px;transition:transform .12s}._7D6uKa_formSection[data-open=true] ._7D6uKa_formSectionChevron{transform:rotate(90deg)}._7D6uKa_formSectionTitle{flex:none}._7D6uKa_formSectionSummary{min-width:0;color:var(--dsw-alias-label-tertiary);text-align:right;text-overflow:ellipsis;white-space:nowrap;flex:auto;font-weight:400;overflow:hidden}._7D6uKa_formSectionBody{border-top:1px solid var(--dsw-alias-separator-primary);flex-direction:column;gap:8px;padding:8px 10px 10px;display:flex}._7D6uKa_field{flex-direction:column;gap:5px;display:flex}._7D6uKa_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}._7D6uKa_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_select{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;max-width:100%;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}._7D6uKa_detail{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(640px,100vw - 48px);max-height:calc(100vh - 80px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;display:flex;overflow:hidden}._7D6uKa_detailHeader{border-bottom:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:14px 18px;display:flex}._7D6uKa_detailTitle{overflow-wrap:anywhere;flex:1;margin:0;font-size:15px;font-weight:700}._7D6uKa_statusBadge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:2px 10px;font-size:12px}._7D6uKa_statusBadge[data-status=running]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusBadge[data-status=done]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._7D6uKa_statusBadge[data-status=failed]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_detailBody{flex-direction:column;flex:1;gap:16px;padding:14px 18px;display:flex;overflow-y:auto}._7D6uKa_detailSection{flex-direction:column;gap:6px;display:flex}._7D6uKa_detailSection h4{color:var(--dsw-alias-label-tertiary);text-transform:none;margin:0;font-size:12px;font-weight:700}._7D6uKa_detailText{color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.55}._7D6uKa_scheduleToggle{color:var(--dsw-alias-label-primary);cursor:pointer;user-select:none;align-items:center;gap:8px;font-size:13px;display:flex}._7D6uKa_scheduleToggle input{accent-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleRow{align-items:center;gap:8px;display:flex}._7D6uKa_scheduleInput{min-width:0;font-family:var(--dsw-font-markdown-code-block-small);flex:1;font-size:12.5px}._7D6uKa_scheduleInputInvalid,._7D6uKa_scheduleInputInvalid:focus{border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_schedulePreset{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:none;padding:7px 8px;font-size:12.5px}._7D6uKa_scheduleZone{color:var(--dsw-alias-label-secondary);align-items:center;gap:8px;font-size:12.5px;display:flex}._7D6uKa_scheduleZone select{flex:1;min-width:0}._7D6uKa_scheduleMeta{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;margin:0;font-size:12px}._7D6uKa_promptBlock{font-size:12.5px;line-height:1.5;font-family:var(--dsw-font-markdown-code-block-small);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-markdown-code-block);border:1px solid var(--dsw-alias-border-l1);white-space:pre-wrap;overflow-wrap:anywhere;border-radius:8px;max-height:240px;margin:0;padding:10px 12px;overflow-y:auto}._7D6uKa_executionList{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}._7D6uKa_executionRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-wrap:wrap;align-items:center;gap:10px;padding:8px 10px;display:flex}._7D6uKa_executionBadge{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-state-warn-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:600}._7D6uKa_executionBadge[data-result=succeeded]{color:var(--dsw-alias-state-success-primary);background:0 0}._7D6uKa_executionBadge[data-result=failed]{color:var(--dsw-alias-state-error-primary);background:0 0}._7D6uKa_executionBadge[data-result=cancelled]{color:var(--dsw-alias-label-tertiary);background:0 0}._7D6uKa_executionTimes{color:var(--dsw-alias-label-secondary);font-size:12px}._7D6uKa_executionError{width:100%;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;font-size:12px}._7D6uKa_moveRow{flex-wrap:wrap;gap:8px;display:flex}._7D6uKa_detailFooter{border-top:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:12px 18px;display:flex}._7D6uKa_detailMeta{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:11px}@container _7D6uKa_task-board-view (width<=768px){._7D6uKa_board{gap:10px;padding:10px}._7D6uKa_boardHeader{flex-wrap:wrap;align-items:center;gap:8px}._7D6uKa_backButton{flex:none;order:1}._7D6uKa_boardTitle{flex:auto;order:2}._7D6uKa_boardHeader>._7D6uKa_detailMeta{flex:1 0 100%;order:3;margin-left:0}._7D6uKa_search{flex:1 0 100%;order:4;min-width:0}._7D6uKa_boardHeader>button:not(._7D6uKa_backButton){flex:1 1 0;order:5;min-width:0}._7D6uKa_columns{scroll-snap-type:inline mandatory;scrollbar-width:none;-webkit-overflow-scrolling:touch;grid-auto-columns:86cqw;gap:10px;padding-inline:2px 14cqw;scroll-padding-inline:2px}._7D6uKa_columns::-webkit-scrollbar{display:none}._7D6uKa_column{scroll-snap-align:start;scroll-snap-stop:always}}@container _7D6uKa_task-board-view (width<=720px){._7D6uKa_boardHeader>._7D6uKa_detailMeta{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}}@container _7D6uKa_task-board-view (width<=600px){._7D6uKa_board{padding-inline:8px}}@media (width<=768px){[data-dsh-taskboard-view]{height:100dvh}._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle{min-height:44px}._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset{box-sizing:border-box;font-size:16px}._7D6uKa_modalBackdrop{justify-content:stretch;align-items:stretch;width:100vw;height:100dvh}._7D6uKa_modal,._7D6uKa_detail{box-sizing:border-box;border:0;border-radius:0;width:100vw;height:100dvh;max-height:none}._7D6uKa_modal{padding-top:max(16px, env(safe-area-inset-top));padding-right:max(16px, env(safe-area-inset-right));padding-bottom:max(16px, env(safe-area-inset-bottom));padding-left:max(16px, env(safe-area-inset-left))}._7D6uKa_modalFooter{z-index:1;background:var(--dsw-alias-bg-base);flex-wrap:wrap;padding-top:8px;position:sticky;bottom:0}._7D6uKa_modalFooter>button{flex:120px}._7D6uKa_detailHeader{padding-top:max(12px, env(safe-area-inset-top));padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailTitle{min-width:0}._7D6uKa_detailBody{overscroll-behavior-y:contain;padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left))}._7D6uKa_detailFooter{padding-right:max(14px, env(safe-area-inset-right));padding-bottom:max(12px, env(safe-area-inset-bottom));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailFooter>button{flex:96px}._7D6uKa_detailFooter>._7D6uKa_detailMeta{text-align:end;flex:1 0 100%;margin-left:0}._7D6uKa_scheduleRow{flex-direction:column;align-items:stretch}._7D6uKa_schedulePreset{width:100%}}._7D6uKa_card:focus-visible,._7D6uKa_primaryButton:focus-visible,._7D6uKa_ghostButton:focus-visible,._7D6uKa_dangerButton:focus-visible,._7D6uKa_iconButton:focus-visible,._7D6uKa_linkButton:focus-visible,._7D6uKa_search:focus-visible,._7D6uKa_input:focus-visible,._7D6uKa_select:focus-visible,._7D6uKa_schedulePreset:focus-visible,._7D6uKa_scheduleToggle input:focus-visible,._7D6uKa_formSectionHeader:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input,._7D6uKa_formSectionHeader,._7D6uKa_formSectionChevron{transition:background-color .12s,color .12s,border-color .12s,outline-color .12s,box-shadow .12s,transform .12s}._7D6uKa_card:active{box-shadow:var(--dsw-shadow-lv1);transform:translateY(0)}._7D6uKa_primaryButton:active:not(:disabled),._7D6uKa_ghostButton:active:not(:disabled),._7D6uKa_dangerButton:active:not(:disabled),._7D6uKa_iconButton:active:not(:disabled),._7D6uKa_linkButton:active:not(:disabled){transform:translateY(1px)}._7D6uKa_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton:hover:not(:disabled){text-decoration:underline}._7D6uKa_iconButton:disabled,._7D6uKa_linkButton:disabled{opacity:.45;cursor:default}._7D6uKa_search:focus,._7D6uKa_select:focus,._7D6uKa_schedulePreset:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleToggle input{margin:0}@media (prefers-reduced-motion:reduce){._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input,._7D6uKa_formSectionHeader,._7D6uKa_formSectionChevron{transition:none}._7D6uKa_cardSpinner{animation:none}}._7D6uKa_cardTags{flex-wrap:wrap;gap:4px;display:flex}._7D6uKa_cardTag{border:1px solid var(--dsh-task-tag-border);background:var(--dsh-task-tag-fill);max-width:100%;color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;border-radius:999px;padding:0 7px;font-size:10px;line-height:16px;overflow:hidden}._7D6uKa_tagFilter{flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 10px;display:flex}._7D6uKa_tagFilterLabel{color:var(--dsw-alias-label-tertiary);font-size:11px}._7D6uKa_tagChip{border:1px solid var(--dsh-task-tag-border);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:999px;padding:1px 9px;font-family:inherit;font-size:11px;line-height:18px}._7D6uKa_tagChip[data-active=true]{background:var(--dsh-task-tag-fill);color:var(--dsw-alias-label-primary)}._7D6uKa_cardTag[data-tag-tone=\"0\"],._7D6uKa_tagChip[data-tag-tone=\"0\"]{--dsh-task-tag-fill:#4e93e82e;--dsh-task-tag-border:#4e93e866}._7D6uKa_cardTag[data-tag-tone=\"1\"],._7D6uKa_tagChip[data-tag-tone=\"1\"]{--dsh-task-tag-fill:#2ea36a2e;--dsh-task-tag-border:#2ea36a66}._7D6uKa_cardTag[data-tag-tone=\"2\"],._7D6uKa_tagChip[data-tag-tone=\"2\"]{--dsh-task-tag-fill:#d08a2a2e;--dsh-task-tag-border:#d08a2a66}._7D6uKa_cardTag[data-tag-tone=\"3\"],._7D6uKa_tagChip[data-tag-tone=\"3\"]{--dsh-task-tag-fill:#b456c82e;--dsh-task-tag-border:#b456c866}._7D6uKa_cardTag[data-tag-tone=\"4\"],._7D6uKa_tagChip[data-tag-tone=\"4\"]{--dsh-task-tag-fill:#cf5f7a2e;--dsh-task-tag-border:#cf5f7a66}._7D6uKa_cardTag[data-tag-tone=\"5\"],._7D6uKa_tagChip[data-tag-tone=\"5\"]{--dsh-task-tag-fill:#4a9fb52e;--dsh-task-tag-border:#4a9fb566}._7D6uKa_fieldHint{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.4}._7D6uKa_tagRow{align-items:center;gap:6px;display:flex}._7D6uKa_tagRow ._7D6uKa_input{flex:1 1 0;min-width:0}._7D6uKa_tagRow ._7D6uKa_ghostButton{flex:none}._7D6uKa_tagAddButton{align-self:flex-start}._7D6uKa_projectFilter{flex:none;align-items:center;gap:6px;display:flex}._7D6uKa_projectFilterLabel{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:12px}._7D6uKa_projectDialog{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;flex-direction:column;flex:none;gap:8px;margin-bottom:8px;padding:10px 12px;display:flex}._7D6uKa_projectDialogActions{justify-content:flex-end;gap:8px;display:flex}._7D6uKa_aiParse{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._7D6uKa_aiParseRow{align-items:center;gap:8px;display:flex}._7D6uKa_aiParseRow ._7D6uKa_select{flex:1 1 0;min-width:0}._7D6uKa_aiParseRow ._7D6uKa_ghostButton,._7D6uKa_aiParseRow ._7D6uKa_primaryButton{flex:none}._7D6uKa_cardSubtask{border:1px solid var(--dsw-alias-border-l2);max-width:100%;color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;background:0 0;border-radius:999px;padding:0 7px;font-size:10px;line-height:16px;overflow:hidden}._7D6uKa_cardSubtask[data-tone=failed]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_cardSubtask[data-tone=running]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_cardSubtask[data-tone=done]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._7D6uKa_subtaskList{flex-direction:column;gap:6px;margin:0;padding:0;list-style:none;display:flex}._7D6uKa_subtaskRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-wrap:wrap;align-items:center;gap:10px;padding:6px 10px;display:flex}._7D6uKa_subtaskRow ._7D6uKa_linkButton:first-child{text-align:left;overflow-wrap:anywhere;flex:auto;min-width:0}._7D6uKa_subtaskAddRow{flex-wrap:wrap;gap:8px;display:flex}._7D6uKa_pickList{flex-direction:column;gap:6px;max-height:320px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}._7D6uKa_pickRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;align-items:center;gap:10px;padding:6px 10px;display:flex}._7D6uKa_pickTitle{min-width:0;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;flex:auto;font-size:13px}";
2427
2653
  const tagId$1 = "@linxin666/dsh-client-ui-task-board/packages/dsh-task-board/src/client/board.module.css";
2428
2654
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
2429
2655
  const tag = document.createElement("style");
@@ -2479,12 +2705,19 @@ window.__ModuleLoader__.load({
2479
2705
  "fieldHint": "_7D6uKa_fieldHint",
2480
2706
  "fieldLabel": "_7D6uKa_fieldLabel",
2481
2707
  "formError": "_7D6uKa_formError",
2708
+ "formSection": "_7D6uKa_formSection",
2709
+ "formSectionBody": "_7D6uKa_formSectionBody",
2710
+ "formSectionChevron": "_7D6uKa_formSectionChevron",
2711
+ "formSectionHeader": "_7D6uKa_formSectionHeader",
2712
+ "formSectionSummary": "_7D6uKa_formSectionSummary",
2713
+ "formSectionTitle": "_7D6uKa_formSectionTitle",
2482
2714
  "ghostButton": "_7D6uKa_ghostButton",
2483
2715
  "iconButton": "_7D6uKa_iconButton",
2484
2716
  "input": "_7D6uKa_input",
2485
2717
  "linkButton": "_7D6uKa_linkButton",
2486
2718
  "modal": "_7D6uKa_modal",
2487
2719
  "modalBackdrop": "_7D6uKa_modalBackdrop",
2720
+ "modalBody": "_7D6uKa_modalBody",
2488
2721
  "modalFooter": "_7D6uKa_modalFooter",
2489
2722
  "modalTitle": "_7D6uKa_modalTitle",
2490
2723
  "moveRow": "_7D6uKa_moveRow",
@@ -2503,6 +2736,7 @@ window.__ModuleLoader__.load({
2503
2736
  "schedulePreset": "_7D6uKa_schedulePreset",
2504
2737
  "scheduleRow": "_7D6uKa_scheduleRow",
2505
2738
  "scheduleToggle": "_7D6uKa_scheduleToggle",
2739
+ "scheduleZone": "_7D6uKa_scheduleZone",
2506
2740
  "search": "_7D6uKa_search",
2507
2741
  "select": "_7D6uKa_select",
2508
2742
  "statusBadge": "_7D6uKa_statusBadge",
@@ -2539,7 +2773,128 @@ window.__ModuleLoader__.load({
2539
2773
  }
2540
2774
  ];
2541
2775
  //#endregion
2776
+ //#region src/client/schedule-zone.ts
2777
+ /**
2778
+ * Shared scheduling helpers for the board's schedule editors (the new-task
2779
+ * dialog and the task detail panel): the time-zone picker inventory, the
2780
+ * relative next-run wording, and the combined absolute + relative label.
2781
+ *
2782
+ * Everything here is browser-side presentation built on the same pure engine
2783
+ * the Host schedules with (`core/schedule.ts`), so the preview a user sees is
2784
+ * computed by the code that will actually arm the rule.
2785
+ */
2786
+ /**
2787
+ * The runtime's zone inventory, read once. `Intl.supportedValuesOf` is not
2788
+ * available in every engine, so a missing inventory degrades to `UTC` rather
2789
+ * than offering a list that cannot resolve.
2790
+ *
2791
+ * The inventory is deliberately NOT truncated. A capped list silently omits
2792
+ * whole regions, and because a `<select>` whose value matches no option falls
2793
+ * back to its first option, an omitted zone would make the control show — and
2794
+ * then save — a different zone than the rule actually stores.
2795
+ */
2796
+ let inventoryCache;
2797
+ function zoneInventory() {
2798
+ if (inventoryCache !== void 0) return inventoryCache;
2799
+ let inventory = [];
2800
+ try {
2801
+ inventory = typeof Intl.supportedValuesOf === "function" ? Intl.supportedValuesOf("timeZone") : [];
2802
+ } catch {
2803
+ inventory = [];
2804
+ }
2805
+ inventoryCache = inventory.length > 0 ? inventory : ["UTC"];
2806
+ return inventoryCache;
2807
+ }
2808
+ /**
2809
+ * Zones offered by the picker: the Host zone first (the default, and the entry
2810
+ * a rule with no stored zone follows), then the runtime's own inventory.
2811
+ *
2812
+ * The Host entry is always present, even before the Host snapshot reports a
2813
+ * zone: it is the entry that stores no zone, so dropping it would leave a rule
2814
+ * unable to express "follow the Host" and would let the control silently
2815
+ * display an unrelated zone.
2816
+ *
2817
+ * A stored zone the inventory does not list (a rule written on another machine
2818
+ * or under a different ICU build) is appended, so opening the editor can never
2819
+ * silently rewrite the rule's zone.
2820
+ * @param hostTimeZone - zone the Host reported for this deployment, when known.
2821
+ * @param storedTimeZone - zone the edited rule already stores, when any.
2822
+ * @returns the de-duplicated choice list.
2823
+ */
2824
+ function zoneChoices(hostTimeZone, storedTimeZone) {
2825
+ const host = hostTimeZone !== void 0 && isValidTimeZone(hostTimeZone) ? hostTimeZone : void 0;
2826
+ const choices = [];
2827
+ const seen = /* @__PURE__ */ new Set();
2828
+ choices.push({
2829
+ id: "",
2830
+ label: host === void 0 ? t("detail.schedule.timeZoneHostUnknown") : t("detail.schedule.timeZoneHost", { timeZone: host })
2831
+ });
2832
+ if (host !== void 0) seen.add(host);
2833
+ for (const zone of zoneInventory()) {
2834
+ if (seen.has(zone) || !isValidTimeZone(zone)) continue;
2835
+ seen.add(zone);
2836
+ choices.push({
2837
+ id: zone,
2838
+ label: zone
2839
+ });
2840
+ }
2841
+ if (storedTimeZone !== void 0 && !seen.has(storedTimeZone) && isValidTimeZone(storedTimeZone)) choices.push({
2842
+ id: storedTimeZone,
2843
+ label: storedTimeZone
2844
+ });
2845
+ return choices;
2846
+ }
2847
+ /** Compact duration parts, largest unit first, with at most two units kept. */
2848
+ function durationParts(ms) {
2849
+ const total = Math.max(0, Math.floor(ms / 1e3));
2850
+ const days = Math.floor(total / 86400);
2851
+ const hours = Math.floor(total % 86400 / 3600);
2852
+ const minutes = Math.floor(total % 3600 / 60);
2853
+ const seconds = total % 60;
2854
+ const parts = [];
2855
+ if (days > 0) parts.push(t("detail.schedule.duration.days", { count: String(days) }));
2856
+ if (hours > 0) parts.push(t("detail.schedule.duration.hours", { count: String(hours) }));
2857
+ if (minutes > 0) parts.push(t("detail.schedule.duration.minutes", { count: String(minutes) }));
2858
+ if (parts.length < 2 && (seconds > 0 || parts.length === 0)) parts.push(t("detail.schedule.duration.seconds", { count: String(seconds) }));
2859
+ return parts.slice(0, 2).join(" ");
2860
+ }
2861
+ /**
2862
+ * The relative half of a next-run label: how far away the instant is, or how
2863
+ * far past it the board is running. `now` is passed in rather than sampled so
2864
+ * callers can share one clock reading across a row, and tests stay
2865
+ * deterministic.
2866
+ * @param targetMs - the instant being described.
2867
+ * @param now - the reference instant.
2868
+ * @returns localized relative wording.
2869
+ */
2870
+ function relativeTimeLabel(targetMs, now) {
2871
+ const delta = targetMs - now;
2872
+ if (delta < 0) return t("detail.schedule.nextRunOverdue", { duration: durationParts(-delta) });
2873
+ return `${durationParts(delta)}`;
2874
+ }
2875
+ /**
2876
+ * The full next-run label: the absolute wall clock in the schedule's own zone,
2877
+ * plus the relative distance in parentheses. Showing both matters because a
2878
+ * rule's zone can differ from the reader's, so the wall clock alone is
2879
+ * ambiguous and the distance alone loses the actual moment.
2880
+ * @param targetMs - the instant being described.
2881
+ * @param timeZone - zone to render the absolute half in.
2882
+ * @param formatAbsolute - formatter for the absolute half (keeps Intl caching in the caller).
2883
+ * @param now - the reference instant.
2884
+ * @param translate - translate function, bound so this module stays dependency-light.
2885
+ * @returns the combined label.
2886
+ */
2887
+ function nextRunLabel(targetMs, timeZone, formatAbsolute, now) {
2888
+ return `${formatAbsolute(targetMs, timeZone)} (${relativeTimeLabel(targetMs, now)})`;
2889
+ }
2890
+ //#endregion
2542
2891
  //#region src/client/board/TaskForm.tsx
2892
+ /**
2893
+ * Shared task-modal pieces: the overlay shell (backdrop, form, title, error,
2894
+ * footer) and the title/description/prompt field trio used by both the
2895
+ * NewTaskModal and the EditTaskModal. State stays in the owning modal; these
2896
+ * are controlled components.
2897
+ */
2543
2898
  /** DOM id shared by the tag-name inputs and their datalist (one board at a time). */
2544
2899
  const TAG_NAME_LIST_ID = "dsh-task-board-tag-names";
2545
2900
  /** Modal overlay: closes on backdrop press, submits through the form. */
@@ -2562,7 +2917,10 @@ window.__ModuleLoader__.load({
2562
2917
  className: board_module_css_default.modalTitle,
2563
2918
  children: title
2564
2919
  }),
2565
- children,
2920
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2921
+ className: board_module_css_default.modalBody,
2922
+ children
2923
+ }),
2566
2924
  error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2567
2925
  className: board_module_css_default.formError,
2568
2926
  children: error
@@ -2595,6 +2953,48 @@ window.__ModuleLoader__.load({
2595
2953
  })
2596
2954
  });
2597
2955
  }
2956
+ /**
2957
+ * One collapsible region of a task form (create/edit dialog UX): the form
2958
+ * groups its configuration into a few regions instead of stacking every field
2959
+ * in one long column, so a normal-sized dialog needs no scrollbar. A collapsed
2960
+ * region keeps a one-line summary of the values it holds, so nothing is hidden
2961
+ * without a trace; a region holding a blocking error is forced open by its
2962
+ * owner (forceOpen) rather than reporting the error out of sight.
2963
+ */
2964
+ function CollapsibleSection({ title, summary, defaultOpen = false, forceOpen = false, children }) {
2965
+ const [open, setOpen] = (0, react.useState)(defaultOpen);
2966
+ const expanded = forceOpen || open;
2967
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2968
+ className: board_module_css_default.formSection,
2969
+ "data-dsh-part": "form-section",
2970
+ "data-open": expanded ? "true" : void 0,
2971
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2972
+ type: "button",
2973
+ className: board_module_css_default.formSectionHeader,
2974
+ "aria-expanded": expanded,
2975
+ onClick: () => {
2976
+ setOpen((value) => !value);
2977
+ },
2978
+ children: [
2979
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2980
+ className: board_module_css_default.formSectionChevron,
2981
+ "aria-hidden": "true"
2982
+ }),
2983
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2984
+ className: board_module_css_default.formSectionTitle,
2985
+ children: title
2986
+ }),
2987
+ !expanded && summary !== void 0 && summary !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2988
+ className: board_module_css_default.formSectionSummary,
2989
+ children: summary
2990
+ })
2991
+ ]
2992
+ }), expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2993
+ className: board_module_css_default.formSectionBody,
2994
+ children
2995
+ })]
2996
+ });
2997
+ }
2598
2998
  /** Title + description + prompt fields shared by the new and edit task forms. */
2599
2999
  function TaskContentFields({ title, description, prompt, onTitleChange, onDescriptionChange, onPromptChange }) {
2600
3000
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
@@ -2618,7 +3018,7 @@ window.__ModuleLoader__.load({
2618
3018
  children: t("new.description")
2619
3019
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
2620
3020
  className: board_module_css_default.input,
2621
- rows: 3,
3021
+ rows: 2,
2622
3022
  value: description,
2623
3023
  placeholder: t("new.descriptionPlaceholder"),
2624
3024
  onChange: (event) => onDescriptionChange(event.target.value)
@@ -2728,6 +3128,166 @@ window.__ModuleLoader__.load({
2728
3128
  return normalizeTags(tags) ?? [];
2729
3129
  }
2730
3130
  //#endregion
3131
+ //#region src/client/board/TaskCard.tsx
3132
+ /**
3133
+ * Task card: the board's column item. Clicking opens the task detail — it
3134
+ * never executes anything directly (detail holds the Run button).
3135
+ *
3136
+ * Memoized: the card re-renders only when its own task record changes, so a
3137
+ * status/filter update on one card (or scrolling) never re-renders every
3138
+ * card on the board. The per-card onClick is built with a stable task reference
3139
+ * by the board, so the memo boundary is effective.
3140
+ */
3141
+ /**
3142
+ * Built formatters, keyed by time zone ('' = the browser's own). Constructing
3143
+ * an Intl.DateTimeFormat is orders of magnitude more expensive than formatting
3144
+ * with one, and the board renders dozens of timestamps per SSE frame; the
3145
+ * cached formatter keeps that cost off the render path.
3146
+ */
3147
+ const HOST_TIMESTAMP_FORMATS = /* @__PURE__ */ new Map();
3148
+ function hostTimestampFormat(timeZone) {
3149
+ const key = timeZone ?? "";
3150
+ const cached = HOST_TIMESTAMP_FORMATS.get(key);
3151
+ if (cached !== void 0) return cached;
3152
+ const format = new Intl.DateTimeFormat(void 0, {
3153
+ dateStyle: "medium",
3154
+ timeStyle: "medium",
3155
+ ...timeZone === void 0 ? {} : { timeZone }
3156
+ });
3157
+ HOST_TIMESTAMP_FORMATS.set(key, format);
3158
+ return format;
3159
+ }
3160
+ /** Compact relative/absolute time label. */
3161
+ function formatHostTimestamp(ms, timeZone) {
3162
+ try {
3163
+ return hostTimestampFormat(timeZone).format(new Date(ms));
3164
+ } catch {
3165
+ return new Date(ms).toISOString();
3166
+ }
3167
+ }
3168
+ function formatTime(ms, timeZone) {
3169
+ const date = new Date(ms);
3170
+ const minutes = Math.floor((Date.now() - ms) / 6e4);
3171
+ if (minutes < 1) return t("time.justNow");
3172
+ if (minutes < 60) return `${minutes}m`;
3173
+ if (minutes < 1440) return `${Math.floor(minutes / 60)}h`;
3174
+ if (timeZone !== void 0) return formatHostTimestamp(ms, timeZone);
3175
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
3176
+ }
3177
+ function TaskCardInner({ task, pending, timeZone, onClick, subtaskCount = 0, isSubtask = false, subtasksDone = 0, subtasksRunning = 0, subtasksFailed = 0 }) {
3178
+ const latest = task.executions[task.executions.length - 1];
3179
+ const runs = task.executions.length;
3180
+ const archived = task.archivedAt !== void 0;
3181
+ const isDraggable = !archived && task.status !== "running" && !pending;
3182
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3183
+ type: "button",
3184
+ className: board_module_css_default.card,
3185
+ "data-status": archived ? "archived" : task.status,
3186
+ "data-dsh-part": "card",
3187
+ "data-pending": pending || void 0,
3188
+ draggable: isDraggable,
3189
+ onDragStart: isDraggable ? (event) => {
3190
+ event.dataTransfer.setData("text/plain", task.id);
3191
+ event.dataTransfer.effectAllowed = "move";
3192
+ } : void 0,
3193
+ onClick,
3194
+ title: task.description !== "" ? task.description : task.title,
3195
+ children: [
3196
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3197
+ className: board_module_css_default.cardTitle,
3198
+ children: task.title
3199
+ }),
3200
+ task.tags !== void 0 && task.tags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3201
+ className: board_module_css_default.cardTags,
3202
+ children: task.tags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3203
+ className: board_module_css_default.cardTag,
3204
+ "data-tag-tone": tagTone(tag.name),
3205
+ "data-dsh-part": "tag-badge",
3206
+ "data-tag-hint": tag.promptPrefix === void 0 ? void 0 : tag.promptPrefix,
3207
+ title: tag.promptPrefix === void 0 ? tag.name : tag.promptPrefix,
3208
+ children: tag.name
3209
+ }, tag.name))
3210
+ }),
3211
+ task.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3212
+ className: board_module_css_default.cardExcerpt,
3213
+ children: task.description
3214
+ }),
3215
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3216
+ className: board_module_css_default.cardMeta,
3217
+ children: [
3218
+ isSubtask && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3219
+ className: board_module_css_default.cardSubtask,
3220
+ "data-dsh-part": "subtask-badge",
3221
+ children: t("card.subtask")
3222
+ }),
3223
+ subtaskCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3224
+ className: board_module_css_default.cardSubtask,
3225
+ "data-dsh-part": "subtask-count",
3226
+ "data-tone": subtasksFailed > 0 ? "failed" : subtasksRunning > 0 ? "running" : subtasksDone === subtaskCount ? "done" : void 0,
3227
+ title: t("card.subtasksBreakdown", {
3228
+ total: String(subtaskCount),
3229
+ done: String(subtasksDone),
3230
+ running: String(subtasksRunning),
3231
+ failed: String(subtasksFailed)
3232
+ }),
3233
+ children: [
3234
+ t("card.subtasks", { count: String(subtaskCount) }),
3235
+ subtasksFailed > 0 ? " · " + t("card.subtasksFailed", { count: String(subtasksFailed) }) : "",
3236
+ subtasksFailed === 0 && subtasksRunning > 0 ? " · " + t("card.subtasksRunning", { count: String(subtasksRunning) }) : ""
3237
+ ]
3238
+ }),
3239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3240
+ className: board_module_css_default.cardTime,
3241
+ children: [
3242
+ t("board.updated"),
3243
+ " ",
3244
+ formatTime(task.updatedAt)
3245
+ ]
3246
+ }),
3247
+ task.freeze !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3248
+ className: board_module_css_default.cardSchedule,
3249
+ title: task.freeze.goal,
3250
+ children: t("card.frozen")
3251
+ }),
3252
+ !archived && task.schedule?.enabled === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3253
+ className: board_module_css_default.cardSchedule,
3254
+ title: task.schedule.nextRunAt !== void 0 ? `${t("card.scheduled")} · ${formatHostTimestamp(task.schedule.nextRunAt, timeZone)}` : t("card.scheduled"),
3255
+ children: t("card.scheduled")
3256
+ }),
3257
+ latest !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3258
+ className: board_module_css_default.cardRun,
3259
+ "data-result": archived ? void 0 : latest.result,
3260
+ children: [
3261
+ runs,
3262
+ " ",
3263
+ t("board.runs")
3264
+ ]
3265
+ }),
3266
+ latest?.sessionId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3267
+ className: board_module_css_default.cardSession,
3268
+ title: latest.sessionId,
3269
+ children: "⌁"
3270
+ }),
3271
+ !archived && (task.status === "running" || pending) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3272
+ className: board_module_css_default.cardSpinner,
3273
+ "aria-hidden": "true"
3274
+ })
3275
+ ]
3276
+ }),
3277
+ !archived && pending && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3278
+ className: board_module_css_default.cardRunningLabel,
3279
+ children: [t("board.pending"), "…"]
3280
+ }),
3281
+ !archived && latest !== void 0 && executionLabel(latest) === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3282
+ className: board_module_css_default.cardRunningLabel,
3283
+ children: [latest.ownResult === void 0 ? t("detail.result.running") : t("detail.subtasks.waiting"), "…"]
3284
+ })
3285
+ ]
3286
+ });
3287
+ }
3288
+ /** Memoized card: re-renders only when the card's own task record changes. */
3289
+ const TaskCard = (0, react.memo)(TaskCardInner);
3290
+ //#endregion
2731
3291
  //#region src/client/board/parse-model-pref.ts
2732
3292
  /**
2733
3293
  * The model the user last picked for AI parsing, remembered per browser so the
@@ -2759,6 +3319,35 @@ window.__ModuleLoader__.load({
2759
3319
  } catch {}
2760
3320
  }
2761
3321
  //#endregion
3322
+ //#region src/client/board/preset-label.ts
3323
+ /** Built-in preset id -> display-name key (display sugar only). */
3324
+ const BUILTIN_PRESET_LABELS = {
3325
+ standard: "preset.builtin.standard",
3326
+ ptc: "preset.builtin.ptc",
3327
+ minimal: "preset.builtin.minimal",
3328
+ cordis: "preset.builtin.cordis"
3329
+ };
3330
+ /** Whether one roster row is a shipped built-in preset. */
3331
+ function isBuiltinPreset(id) {
3332
+ return BUILTIN_PRESET_LABELS[id] !== void 0;
3333
+ }
3334
+ /** Display label of one preset row: built-in label, roster name, or raw id. */
3335
+ function presetLabel(preset) {
3336
+ const builtin = BUILTIN_PRESET_LABELS[preset.id];
3337
+ if (builtin !== void 0 && (preset.name ?? "").trim() === "") return t(builtin);
3338
+ return preset.name ?? preset.id;
3339
+ }
3340
+ /**
3341
+ * Label of the empty ("inherit") choice: names the preset a run actually lands
3342
+ * on when the card pins nothing, which is the deployment default the roster
3343
+ * marks with `isDefault`.
3344
+ */
3345
+ function inheritPresetLabel(presets) {
3346
+ const fallback = presets.find((preset) => preset.isDefault);
3347
+ if (fallback === void 0) return t("exec.mode.inherit");
3348
+ return t("exec.mode.inheritWith", { preset: presetLabel(fallback) });
3349
+ }
3350
+ //#endregion
2762
3351
  //#region src/client/board/NewTaskModal.tsx
2763
3352
  /**
2764
3353
  * New-task modal: title + description + the prompt that execution will send.
@@ -2776,8 +3365,10 @@ window.__ModuleLoader__.load({
2776
3365
  const [model, setModel] = (0, react.useState)(initialTask?.model ?? parentTask?.model ?? "");
2777
3366
  const inheritedPermission = parentTask === void 0 ? void 0 : effectiveTaskPermission(parentTask);
2778
3367
  const [reuseSession, setReuseSession] = (0, react.useState)(initialTask?.reuseSession ?? false);
3368
+ const [goalRun, setGoalRun] = (0, react.useState)(initialTask?.goalRun ?? true);
2779
3369
  const [scheduleEnabled, setScheduleEnabled] = (0, react.useState)(initialTask?.schedule?.enabled ?? false);
2780
3370
  const [scheduleCron, setScheduleCron] = (0, react.useState)(initialTask?.schedule?.cron ?? "");
3371
+ const [scheduleZone, setScheduleZone] = (0, react.useState)(initialTask?.schedule?.timeZone ?? "");
2781
3372
  const [scheduleError, setScheduleError] = (0, react.useState)(void 0);
2782
3373
  const [freezeText, setFreezeText] = (0, react.useState)("");
2783
3374
  const [freezeError, setFreezeError] = (0, react.useState)(void 0);
@@ -2876,10 +3467,12 @@ window.__ModuleLoader__.load({
2876
3467
  permission: permission === "" ? void 0 : permission,
2877
3468
  model: model === "" ? void 0 : model,
2878
3469
  ...reuseSession ? { reuseSession: true } : {},
3470
+ ...goalRun ? {} : { goalRun: false },
2879
3471
  ...tagList.length > 0 ? { tags: tagList } : {},
2880
3472
  schedule: scheduleEnabled ? {
2881
3473
  enabled: true,
2882
- cron: scheduleCron.trim()
3474
+ cron: scheduleCron.trim(),
3475
+ ...scheduleZone === "" ? {} : { timeZone: scheduleZone }
2883
3476
  } : void 0
2884
3477
  });
2885
3478
  if (task === void 0) {
@@ -2894,9 +3487,30 @@ window.__ModuleLoader__.load({
2894
3487
  }
2895
3488
  onClose();
2896
3489
  };
2897
- /** Next-run preview for a valid armed cron (creation-time only). */
2898
- const scheduleNextRun = scheduleEnabled && scheduleCron.trim() !== "" && isValidCron(scheduleCron) ? nextRunAtMs(scheduleCron, Date.now()) : void 0;
3490
+ const hostTimeZone = controller.getSnapshot().host?.scheduler.timeZone;
3491
+ const scheduleTimeZone = scheduleZone === "" ? hostTimeZone : scheduleZone;
3492
+ const scheduleNextRun = scheduleEnabled && scheduleCron.trim() !== "" && isValidCron(scheduleCron) ? nextRunAtMs(scheduleCron, Date.now(), scheduleTimeZone) : void 0;
2899
3493
  const modalTitle = parentTask !== void 0 ? t("new.subtaskTitle") : isDuplicate ? t("new.duplicateTitle") : t("board.new");
3494
+ const builtinPresets = options.presets.filter((preset) => isBuiltinPreset(preset.id));
3495
+ const customPresets = options.presets.filter((preset) => !isBuiltinPreset(preset.id));
3496
+ const inheritLabel = inheritPresetLabel(options.presets);
3497
+ const selectedPreset = options.presets.find((preset) => preset.id === mode);
3498
+ const modeLabel = mode === "" ? inheritLabel : selectedPreset === void 0 ? mode : presetLabel(selectedPreset);
3499
+ const workspaceLabel = workspaceId === "" ? t("exec.workspace.recent") : options.workspaces.find((item) => item.workspaceId === workspaceId)?.title ?? workspaceId;
3500
+ const permissionLabel = permission === "" ? inheritedPermission === void 0 ? t("exec.permission.default") : t("exec.permission.inheritParent", { permission: t(`exec.permission.${inheritedPermission}`) }) : t(`exec.permission.${permission}`);
3501
+ const modelLabel = model === "" ? t("exec.model.default") : (options.models ?? []).find((item) => item.id === model)?.name ?? model;
3502
+ const executionSummary = [
3503
+ workspaceLabel,
3504
+ modeLabel,
3505
+ permissionLabel,
3506
+ ...model === "" ? [] : [modelLabel]
3507
+ ].join(" · ");
3508
+ const tagCount = cleanTags(tags).length;
3509
+ const labelsSummary = tagCount === 0 ? t("new.summary.none") : t("new.summary.labelCount", { count: String(tagCount) });
3510
+ const runSummary = [goalRun ? t("new.summary.multiRound") : t("new.summary.singleRound"), ...reuseSession ? [t("exec.reuseSession")] : []].join(" · ");
3511
+ const handoverSummary = freezeText.trim() !== "" || handoverText.trim() !== "" ? t("new.summary.filled") : t("new.summary.none");
3512
+ const scheduleSummary = !scheduleEnabled ? t("new.summary.scheduleOff") : scheduleCron.trim() === "" ? t("new.summary.none") : scheduleCron.trim();
3513
+ const parseSummary = parseText.trim() === "" ? t("new.summary.none") : t("new.summary.filled");
2900
3514
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ModalShell, {
2901
3515
  ariaLabel: modalTitle,
2902
3516
  title: modalTitle,
@@ -2914,68 +3528,73 @@ window.__ModuleLoader__.load({
2914
3528
  }
2915
3529
  },
2916
3530
  children: [
2917
- canParse && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2918
- className: board_module_css_default.aiParse,
2919
- "data-dsh-part": "ai-parse",
2920
- children: [
2921
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2922
- className: board_module_css_default.fieldLabel,
2923
- children: t("new.aiParse")
2924
- }),
2925
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2926
- className: board_module_css_default.fieldHint,
2927
- children: t("new.aiParseHint")
2928
- }),
2929
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
2930
- className: board_module_css_default.input,
2931
- rows: 3,
2932
- value: parseText,
2933
- placeholder: t("new.aiParsePlaceholder"),
2934
- spellCheck: false,
2935
- onChange: (event) => {
2936
- setParseText(event.target.value);
2937
- setParseError(void 0);
2938
- }
2939
- }),
2940
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2941
- className: board_module_css_default.aiParseRow,
2942
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2943
- className: board_module_css_default.select,
2944
- value: parseModel,
2945
- "aria-label": t("new.aiParseModel"),
2946
- onChange: (event) => {
2947
- setParseModel(event.target.value);
2948
- writeParseModelPreference(event.target.value);
2949
- },
2950
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2951
- value: "",
2952
- children: t("exec.model.default")
2953
- }), parseModels.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2954
- value: option.id,
2955
- children: option.name ?? option.id
2956
- }, option.id))]
2957
- }), parsePending ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2958
- type: "button",
2959
- className: board_module_css_default.ghostButton,
2960
- onClick: () => {
2961
- parseAbort.current?.abort();
2962
- },
2963
- children: t("new.aiParseCancel")
2964
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2965
- type: "button",
2966
- className: board_module_css_default.primaryButton,
2967
- disabled: parseText.trim() === "",
2968
- onClick: () => {
2969
- runParse();
2970
- },
2971
- children: t("new.aiParseRun")
2972
- })]
2973
- }),
2974
- parseError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2975
- className: board_module_css_default.formError,
2976
- children: parseError
2977
- })
2978
- ]
3531
+ canParse && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
3532
+ title: t("new.section.parse"),
3533
+ summary: parseSummary,
3534
+ forceOpen: parseError !== void 0,
3535
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3536
+ className: board_module_css_default.aiParse,
3537
+ "data-dsh-part": "ai-parse",
3538
+ children: [
3539
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3540
+ className: board_module_css_default.fieldLabel,
3541
+ children: t("new.aiParse")
3542
+ }),
3543
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3544
+ className: board_module_css_default.fieldHint,
3545
+ children: t("new.aiParseHint")
3546
+ }),
3547
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3548
+ className: board_module_css_default.input,
3549
+ rows: 3,
3550
+ value: parseText,
3551
+ placeholder: t("new.aiParsePlaceholder"),
3552
+ spellCheck: false,
3553
+ onChange: (event) => {
3554
+ setParseText(event.target.value);
3555
+ setParseError(void 0);
3556
+ }
3557
+ }),
3558
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3559
+ className: board_module_css_default.aiParseRow,
3560
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3561
+ className: board_module_css_default.select,
3562
+ value: parseModel,
3563
+ "aria-label": t("new.aiParseModel"),
3564
+ onChange: (event) => {
3565
+ setParseModel(event.target.value);
3566
+ writeParseModelPreference(event.target.value);
3567
+ },
3568
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3569
+ value: "",
3570
+ children: t("exec.model.default")
3571
+ }), parseModels.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3572
+ value: option.id,
3573
+ children: option.name ?? option.id
3574
+ }, option.id))]
3575
+ }), parsePending ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3576
+ type: "button",
3577
+ className: board_module_css_default.ghostButton,
3578
+ onClick: () => {
3579
+ parseAbort.current?.abort();
3580
+ },
3581
+ children: t("new.aiParseCancel")
3582
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3583
+ type: "button",
3584
+ className: board_module_css_default.primaryButton,
3585
+ disabled: parseText.trim() === "",
3586
+ onClick: () => {
3587
+ runParse();
3588
+ },
3589
+ children: t("new.aiParseRun")
3590
+ })]
3591
+ }),
3592
+ parseError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3593
+ className: board_module_css_default.formError,
3594
+ children: parseError
3595
+ })
3596
+ ]
3597
+ })
2979
3598
  }),
2980
3599
  parentTask !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2981
3600
  className: board_module_css_default.detailText,
@@ -2988,409 +3607,333 @@ window.__ModuleLoader__.load({
2988
3607
  t("new.subtaskInherit")
2989
3608
  ]
2990
3609
  }),
2991
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskContentFields, {
2992
- title,
2993
- description,
2994
- prompt,
2995
- onTitleChange: (value) => {
2996
- setTitle(value);
2997
- setError(void 0);
2998
- },
2999
- onDescriptionChange: setDescription,
3000
- onPromptChange: setPrompt
3001
- }),
3002
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskTagFields, {
3003
- tags,
3004
- knownTags: collectKnownTags(controller.getSnapshot().tasks),
3005
- onChange: setTags
3006
- }),
3007
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3008
- className: board_module_css_default.field,
3009
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3010
- className: board_module_css_default.fieldLabel,
3011
- children: t("new.freeze")
3012
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3013
- className: board_module_css_default.input,
3014
- rows: 4,
3015
- value: freezeText,
3016
- placeholder: t("new.freezePlaceholder"),
3017
- spellCheck: false,
3018
- onChange: (event) => {
3019
- setFreezeText(event.target.value);
3020
- setFreezeError(void 0);
3021
- }
3022
- })]
3023
- }),
3024
- freezeError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3025
- className: board_module_css_default.formError,
3026
- children: freezeError
3027
- }),
3028
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3029
- className: board_module_css_default.field,
3030
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3031
- className: board_module_css_default.fieldLabel,
3032
- children: t("new.handover")
3033
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3034
- className: board_module_css_default.input,
3035
- rows: 3,
3036
- value: handoverText,
3037
- placeholder: t("new.handoverPlaceholder"),
3038
- spellCheck: false,
3039
- onChange: (event) => {
3040
- setHandoverText(event.target.value);
3041
- }
3042
- })]
3043
- }),
3044
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3045
- className: board_module_css_default.field,
3046
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3047
- className: board_module_css_default.fieldLabel,
3048
- children: t("new.workspace")
3049
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3050
- className: board_module_css_default.select,
3051
- value: workspaceId,
3052
- onChange: (event) => {
3053
- setWorkspaceId(event.target.value);
3054
- },
3055
- children: [
3056
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3057
- value: "",
3058
- children: t("exec.workspace.recent")
3059
- }),
3060
- !workspaceKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3061
- value: workspaceId,
3062
- children: [workspaceId, t("exec.mode.removed")]
3063
- }),
3064
- options.workspaces.map((workspace) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3065
- value: workspace.workspaceId,
3066
- children: workspace.title
3067
- }, workspace.workspaceId))
3068
- ]
3069
- })]
3070
- }),
3071
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3072
- className: board_module_css_default.field,
3073
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3074
- className: board_module_css_default.fieldLabel,
3075
- children: t("new.mode")
3076
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3077
- className: board_module_css_default.select,
3078
- value: mode,
3079
- onChange: (event) => {
3080
- setMode(event.target.value);
3081
- },
3082
- children: [
3083
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3084
- value: "",
3085
- children: t("exec.mode.default")
3086
- }),
3087
- !modeKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3088
- value: mode,
3089
- children: [mode, t("exec.mode.removed")]
3090
- }),
3091
- options.presets.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3092
- value: preset.id,
3093
- disabled: preset.broken !== void 0,
3094
- children: [
3095
- preset.name ?? preset.id,
3096
- preset.isDefault ? t("exec.mode.defaultSuffix") : "",
3097
- preset.broken !== void 0 ? t("exec.mode.brokenSuffix") : ""
3098
- ]
3099
- }, preset.id))
3100
- ]
3101
- })]
3102
- }),
3103
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3104
- className: board_module_css_default.field,
3105
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3106
- className: board_module_css_default.fieldLabel,
3107
- children: t("new.permission")
3108
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3109
- className: board_module_css_default.select,
3110
- value: permission,
3111
- onChange: (event) => {
3112
- setPermission(event.target.value);
3113
- },
3114
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3115
- value: "",
3116
- children: inheritedPermission === void 0 ? t("exec.permission.default") : t("exec.permission.inheritParent", { permission: t(`exec.permission.${inheritedPermission}`) })
3117
- }), TASK_PERMISSIONS.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3118
- value: id,
3119
- children: t(`exec.permission.${id}`)
3120
- }, id))]
3121
- })]
3122
- }),
3123
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3124
- className: board_module_css_default.field,
3125
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3126
- className: board_module_css_default.fieldLabel,
3127
- children: t("new.model")
3128
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3129
- className: board_module_css_default.select,
3130
- value: model,
3131
- onChange: (event) => {
3132
- setModel(event.target.value);
3610
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
3611
+ title: t("new.section.content"),
3612
+ defaultOpen: true,
3613
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskContentFields, {
3614
+ title,
3615
+ description,
3616
+ prompt,
3617
+ onTitleChange: (value) => {
3618
+ setTitle(value);
3619
+ setError(void 0);
3133
3620
  },
3134
- children: [
3135
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3136
- value: "",
3137
- children: t("exec.model.default")
3138
- }),
3139
- !modelKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3140
- value: model,
3141
- children: [model, t("exec.model.unknown")]
3142
- }),
3143
- options.models?.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3144
- value: item.id,
3145
- children: item.name ?? item.id
3146
- }, item.id))
3147
- ]
3621
+ onDescriptionChange: setDescription,
3622
+ onPromptChange: setPrompt
3623
+ }), isDuplicate && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3624
+ className: board_module_css_default.checkboxLabel,
3625
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3626
+ type: "checkbox",
3627
+ checked: archiveOriginal,
3628
+ onChange: (event) => {
3629
+ setArchiveOriginal(event.target.checked);
3630
+ }
3631
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("new.archiveOriginal") })]
3148
3632
  })]
3149
3633
  }),
3150
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3151
- className: board_module_css_default.scheduleToggle,
3152
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3153
- type: "checkbox",
3154
- checked: reuseSession,
3155
- onChange: (event) => {
3156
- setReuseSession(event.target.checked);
3157
- }
3158
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("exec.reuseSession") })]
3159
- }),
3160
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3161
- className: board_module_css_default.detailText,
3162
- children: t("exec.reuseSessionHint")
3634
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
3635
+ title: t("new.section.labels"),
3636
+ summary: labelsSummary,
3637
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskTagFields, {
3638
+ tags,
3639
+ knownTags: collectKnownTags(controller.getSnapshot().tasks),
3640
+ onChange: setTags
3641
+ })
3163
3642
  }),
3164
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3165
- className: board_module_css_default.detailSection,
3643
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
3644
+ title: t("new.section.execution"),
3645
+ summary: executionSummary,
3166
3646
  children: [
3167
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.schedule") }),
3168
3647
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3169
- className: board_module_css_default.scheduleToggle,
3170
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3171
- type: "checkbox",
3172
- checked: scheduleEnabled,
3648
+ className: board_module_css_default.field,
3649
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3650
+ className: board_module_css_default.fieldLabel,
3651
+ children: t("new.workspace")
3652
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3653
+ className: board_module_css_default.select,
3654
+ value: workspaceId,
3173
3655
  onChange: (event) => {
3174
- setScheduleEnabled(event.target.checked);
3175
- if (!event.target.checked) setScheduleError(void 0);
3176
- }
3177
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.enable") })]
3178
- }),
3179
- scheduleEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3180
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3181
- className: board_module_css_default.scheduleRow,
3182
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3183
- className: `${board_module_css_default.input} ${board_module_css_default.scheduleInput}${scheduleError !== void 0 ? ` ${board_module_css_default.scheduleInputInvalid}` : ""}`,
3184
- value: scheduleCron,
3185
- placeholder: "0 9 * * *",
3186
- spellCheck: false,
3187
- "aria-label": t("detail.schedule.cron"),
3188
- onChange: (event) => {
3189
- setScheduleCron(event.target.value);
3190
- setScheduleError(void 0);
3191
- }
3192
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3193
- className: board_module_css_default.schedulePreset,
3194
- value: "",
3195
- "aria-label": t("detail.schedule.presets"),
3196
- onChange: (event) => {
3197
- if (event.target.value === "") return;
3198
- setScheduleCron(event.target.value);
3199
- setScheduleError(void 0);
3200
- },
3201
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3202
- value: "",
3203
- children: [t("detail.schedule.presets"), "…"]
3204
- }), SCHEDULE_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3205
- value: preset.cron,
3206
- children: t(preset.label)
3207
- }, preset.cron))]
3208
- })]
3209
- }),
3210
- scheduleError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3211
- className: board_module_css_default.formError,
3212
- children: scheduleError
3213
- }),
3214
- scheduleError === void 0 && scheduleNextRun !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3215
- className: board_module_css_default.scheduleMeta,
3656
+ setWorkspaceId(event.target.value);
3657
+ },
3216
3658
  children: [
3217
- t("detail.schedule.nextRun"),
3218
- " ",
3219
- new Date(scheduleNextRun).toLocaleString()
3659
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3660
+ value: "",
3661
+ children: t("exec.workspace.recent")
3662
+ }),
3663
+ !workspaceKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3664
+ value: workspaceId,
3665
+ children: [workspaceId, t("exec.mode.removed")]
3666
+ }),
3667
+ options.workspaces.map((workspace) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3668
+ value: workspace.workspaceId,
3669
+ children: workspace.title
3670
+ }, workspace.workspaceId))
3220
3671
  ]
3221
- })
3222
- ] })
3223
- ]
3224
- }),
3225
- isDuplicate && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3226
- className: board_module_css_default.checkboxLabel,
3227
- style: { marginTop: "12px" },
3228
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3229
- type: "checkbox",
3230
- checked: archiveOriginal,
3231
- onChange: (event) => {
3232
- setArchiveOriginal(event.target.checked);
3233
- }
3234
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("new.archiveOriginal") })]
3235
- })
3236
- ]
3237
- });
3238
- }
3239
- //#endregion
3240
- //#region src/client/board/status-key.ts
3241
- /** Task status → locale key (board column titles and the detail badge). */
3242
- const STATUS_KEY = {
3243
- backlog: "board.status.backlog",
3244
- todo: "board.status.todo",
3245
- running: "board.status.running",
3246
- done: "board.status.done",
3247
- failed: "board.status.failed"
3248
- };
3249
- //#endregion
3250
- //#region src/client/board/TaskCard.tsx
3251
- /**
3252
- * Task card: the board's column item. Clicking opens the task detail — it
3253
- * never executes anything directly (detail holds the Run button).
3254
- *
3255
- * Memoized: the card re-renders only when its own task record changes, so a
3256
- * status/filter update on one card (or scrolling) never re-renders every
3257
- * card on the board. The per-card onClick is built with a stable task reference
3258
- * by the board, so the memo boundary is effective.
3259
- */
3260
- /** Compact relative/absolute time label. */
3261
- function formatHostTimestamp(ms, timeZone) {
3262
- try {
3263
- return new Intl.DateTimeFormat(void 0, {
3264
- dateStyle: "medium",
3265
- timeStyle: "medium",
3266
- ...timeZone === void 0 ? {} : { timeZone }
3267
- }).format(new Date(ms));
3268
- } catch {
3269
- return new Date(ms).toISOString();
3270
- }
3271
- }
3272
- function formatTime(ms, timeZone) {
3273
- const date = new Date(ms);
3274
- const minutes = Math.floor((Date.now() - ms) / 6e4);
3275
- if (minutes < 1) return t("time.justNow");
3276
- if (minutes < 60) return `${minutes}m`;
3277
- if (minutes < 1440) return `${Math.floor(minutes / 60)}h`;
3278
- if (timeZone !== void 0) return formatHostTimestamp(ms, timeZone);
3279
- return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
3280
- }
3281
- function TaskCardInner({ task, pending, timeZone, onClick, subtaskCount = 0, isSubtask = false, subtasksDone = 0, subtasksRunning = 0, subtasksFailed = 0 }) {
3282
- const latest = task.executions[task.executions.length - 1];
3283
- const runs = task.executions.length;
3284
- const archived = task.archivedAt !== void 0;
3285
- const isDraggable = !archived && task.status !== "running" && !pending;
3286
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3287
- type: "button",
3288
- className: board_module_css_default.card,
3289
- "data-status": archived ? "archived" : task.status,
3290
- "data-dsh-part": "card",
3291
- "data-pending": pending || void 0,
3292
- draggable: isDraggable,
3293
- onDragStart: isDraggable ? (event) => {
3294
- event.dataTransfer.setData("text/plain", task.id);
3295
- event.dataTransfer.effectAllowed = "move";
3296
- } : void 0,
3297
- onClick,
3298
- title: task.description !== "" ? task.description : task.title,
3299
- children: [
3300
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3301
- className: board_module_css_default.cardTitle,
3302
- children: task.title
3303
- }),
3304
- task.tags !== void 0 && task.tags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3305
- className: board_module_css_default.cardTags,
3306
- children: task.tags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3307
- className: board_module_css_default.cardTag,
3308
- "data-tag-tone": tagTone(tag.name),
3309
- "data-dsh-part": "tag-badge",
3310
- "data-tag-hint": tag.promptPrefix === void 0 ? void 0 : tag.promptPrefix,
3311
- title: tag.promptPrefix === void 0 ? tag.name : tag.promptPrefix,
3312
- children: tag.name
3313
- }, tag.name))
3672
+ })]
3673
+ }),
3674
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3675
+ className: board_module_css_default.field,
3676
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3677
+ className: board_module_css_default.fieldLabel,
3678
+ children: t("new.agentPreset")
3679
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3680
+ className: board_module_css_default.select,
3681
+ value: mode,
3682
+ onChange: (event) => {
3683
+ setMode(event.target.value);
3684
+ },
3685
+ children: [
3686
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3687
+ value: "",
3688
+ children: inheritLabel
3689
+ }),
3690
+ !modeKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3691
+ value: mode,
3692
+ children: [mode, t("exec.mode.removed")]
3693
+ }),
3694
+ builtinPresets.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("optgroup", {
3695
+ label: t("exec.mode.builtinGroup"),
3696
+ children: builtinPresets.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3697
+ value: preset.id,
3698
+ disabled: preset.broken !== void 0,
3699
+ children: [
3700
+ presetLabel(preset),
3701
+ preset.isDefault ? t("exec.mode.defaultSuffix") : "",
3702
+ preset.broken !== void 0 ? t("exec.mode.brokenSuffix") : ""
3703
+ ]
3704
+ }, preset.id))
3705
+ }),
3706
+ customPresets.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("optgroup", {
3707
+ label: t("exec.mode.customGroup"),
3708
+ children: customPresets.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3709
+ value: preset.id,
3710
+ disabled: preset.broken !== void 0,
3711
+ children: [
3712
+ presetLabel(preset),
3713
+ preset.isDefault ? t("exec.mode.defaultSuffix") : "",
3714
+ preset.broken !== void 0 ? t("exec.mode.brokenSuffix") : ""
3715
+ ]
3716
+ }, preset.id))
3717
+ })
3718
+ ]
3719
+ })]
3720
+ }),
3721
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3722
+ className: board_module_css_default.field,
3723
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3724
+ className: board_module_css_default.fieldLabel,
3725
+ children: t("new.permission")
3726
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3727
+ className: board_module_css_default.select,
3728
+ value: permission,
3729
+ onChange: (event) => {
3730
+ setPermission(event.target.value);
3731
+ },
3732
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3733
+ value: "",
3734
+ children: inheritedPermission === void 0 ? t("exec.permission.default") : t("exec.permission.inheritParent", { permission: t(`exec.permission.${inheritedPermission}`) })
3735
+ }), TASK_PERMISSIONS.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3736
+ value: id,
3737
+ children: t(`exec.permission.${id}`)
3738
+ }, id))]
3739
+ })]
3740
+ }),
3741
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3742
+ className: board_module_css_default.field,
3743
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3744
+ className: board_module_css_default.fieldLabel,
3745
+ children: t("new.model")
3746
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3747
+ className: board_module_css_default.select,
3748
+ value: model,
3749
+ onChange: (event) => {
3750
+ setModel(event.target.value);
3751
+ },
3752
+ children: [
3753
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3754
+ value: "",
3755
+ children: t("exec.model.default")
3756
+ }),
3757
+ !modelKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3758
+ value: model,
3759
+ children: [model, t("exec.model.unknown")]
3760
+ }),
3761
+ options.models?.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3762
+ value: item.id,
3763
+ children: item.name ?? item.id
3764
+ }, item.id))
3765
+ ]
3766
+ })]
3767
+ })
3768
+ ]
3314
3769
  }),
3315
- task.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3316
- className: board_module_css_default.cardExcerpt,
3317
- children: task.description
3770
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
3771
+ title: t("new.section.run"),
3772
+ summary: runSummary,
3773
+ children: [
3774
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3775
+ className: board_module_css_default.scheduleToggle,
3776
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3777
+ type: "checkbox",
3778
+ checked: reuseSession,
3779
+ onChange: (event) => {
3780
+ setReuseSession(event.target.checked);
3781
+ }
3782
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("exec.reuseSession") })]
3783
+ }),
3784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3785
+ className: board_module_css_default.detailText,
3786
+ children: t("exec.reuseSessionHint")
3787
+ }),
3788
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3789
+ className: board_module_css_default.scheduleToggle,
3790
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3791
+ type: "checkbox",
3792
+ checked: goalRun,
3793
+ onChange: (event) => {
3794
+ setGoalRun(event.target.checked);
3795
+ }
3796
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("exec.goalRun") })]
3797
+ }),
3798
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3799
+ className: board_module_css_default.detailText,
3800
+ children: t("exec.goalRunHint")
3801
+ })
3802
+ ]
3318
3803
  }),
3319
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3320
- className: board_module_css_default.cardMeta,
3804
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
3805
+ title: t("new.section.handover"),
3806
+ summary: handoverSummary,
3807
+ forceOpen: freezeError !== void 0,
3321
3808
  children: [
3322
- isSubtask && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3323
- className: board_module_css_default.cardSubtask,
3324
- "data-dsh-part": "subtask-badge",
3325
- children: t("card.subtask")
3809
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3810
+ className: board_module_css_default.field,
3811
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3812
+ className: board_module_css_default.fieldLabel,
3813
+ children: t("new.freeze")
3814
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3815
+ className: board_module_css_default.input,
3816
+ rows: 4,
3817
+ value: freezeText,
3818
+ placeholder: t("new.freezePlaceholder"),
3819
+ spellCheck: false,
3820
+ onChange: (event) => {
3821
+ setFreezeText(event.target.value);
3822
+ setFreezeError(void 0);
3823
+ }
3824
+ })]
3326
3825
  }),
3327
- subtaskCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3328
- className: board_module_css_default.cardSubtask,
3329
- "data-dsh-part": "subtask-count",
3330
- "data-tone": subtasksFailed > 0 ? "failed" : subtasksRunning > 0 ? "running" : subtasksDone === subtaskCount ? "done" : void 0,
3331
- title: t("card.subtasksBreakdown", {
3332
- total: String(subtaskCount),
3333
- done: String(subtasksDone),
3334
- running: String(subtasksRunning),
3335
- failed: String(subtasksFailed)
3336
- }),
3337
- children: [
3338
- t("card.subtasks", { count: String(subtaskCount) }),
3339
- subtasksFailed > 0 ? " · " + t("card.subtasksFailed", { count: String(subtasksFailed) }) : "",
3340
- subtasksFailed === 0 && subtasksRunning > 0 ? " · " + t("card.subtasksRunning", { count: String(subtasksRunning) }) : ""
3341
- ]
3826
+ freezeError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3827
+ className: board_module_css_default.formError,
3828
+ children: freezeError
3342
3829
  }),
3343
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3344
- className: board_module_css_default.cardTime,
3345
- children: [
3346
- t("board.updated"),
3347
- " ",
3348
- formatTime(task.updatedAt)
3349
- ]
3830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3831
+ className: board_module_css_default.field,
3832
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3833
+ className: board_module_css_default.fieldLabel,
3834
+ children: t("new.handover")
3835
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3836
+ className: board_module_css_default.input,
3837
+ rows: 3,
3838
+ value: handoverText,
3839
+ placeholder: t("new.handoverPlaceholder"),
3840
+ spellCheck: false,
3841
+ onChange: (event) => {
3842
+ setHandoverText(event.target.value);
3843
+ }
3844
+ })]
3845
+ })
3846
+ ]
3847
+ }),
3848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
3849
+ title: t("new.section.schedule"),
3850
+ summary: scheduleSummary,
3851
+ forceOpen: scheduleError !== void 0,
3852
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3853
+ className: board_module_css_default.scheduleToggle,
3854
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3855
+ type: "checkbox",
3856
+ checked: scheduleEnabled,
3857
+ onChange: (event) => {
3858
+ setScheduleEnabled(event.target.checked);
3859
+ if (!event.target.checked) setScheduleError(void 0);
3860
+ }
3861
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.enable") })]
3862
+ }), scheduleEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3863
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3864
+ className: board_module_css_default.scheduleRow,
3865
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3866
+ className: `${board_module_css_default.input} ${board_module_css_default.scheduleInput}${scheduleError !== void 0 ? ` ${board_module_css_default.scheduleInputInvalid}` : ""}`,
3867
+ value: scheduleCron,
3868
+ placeholder: "0 9 * * *",
3869
+ spellCheck: false,
3870
+ "aria-label": t("detail.schedule.cron"),
3871
+ onChange: (event) => {
3872
+ setScheduleCron(event.target.value);
3873
+ setScheduleError(void 0);
3874
+ }
3875
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3876
+ className: board_module_css_default.schedulePreset,
3877
+ value: "",
3878
+ "aria-label": t("detail.schedule.presets"),
3879
+ onChange: (event) => {
3880
+ if (event.target.value === "") return;
3881
+ setScheduleCron(event.target.value);
3882
+ setScheduleError(void 0);
3883
+ },
3884
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3885
+ value: "",
3886
+ children: [t("detail.schedule.presets"), "…"]
3887
+ }), SCHEDULE_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3888
+ value: preset.cron,
3889
+ children: t(preset.label)
3890
+ }, preset.cron))]
3891
+ })]
3350
3892
  }),
3351
- task.freeze !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3352
- className: board_module_css_default.cardSchedule,
3353
- title: task.freeze.goal,
3354
- children: t("card.frozen")
3893
+ scheduleError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3894
+ className: board_module_css_default.formError,
3895
+ children: scheduleError
3355
3896
  }),
3356
- !archived && task.schedule?.enabled === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3357
- className: board_module_css_default.cardSchedule,
3358
- title: task.schedule.nextRunAt !== void 0 ? `${t("card.scheduled")} · ${formatHostTimestamp(task.schedule.nextRunAt, timeZone)}` : t("card.scheduled"),
3359
- children: t("card.scheduled")
3897
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3898
+ className: board_module_css_default.scheduleZone,
3899
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.timeZone") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
3900
+ className: board_module_css_default.schedulePreset,
3901
+ value: scheduleZone,
3902
+ "aria-label": t("detail.schedule.timeZone"),
3903
+ title: t("detail.schedule.timeZoneHint"),
3904
+ onChange: (event) => {
3905
+ setScheduleZone(event.target.value);
3906
+ setScheduleError(void 0);
3907
+ },
3908
+ children: zoneChoices(hostTimeZone, initialTask?.schedule?.timeZone).map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3909
+ value: choice.id,
3910
+ children: choice.label
3911
+ }, choice.id === "" ? "__host" : choice.id))
3912
+ })]
3360
3913
  }),
3361
- latest !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3362
- className: board_module_css_default.cardRun,
3363
- "data-result": archived ? void 0 : latest.result,
3914
+ scheduleError === void 0 && scheduleNextRun !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3915
+ className: board_module_css_default.scheduleMeta,
3364
3916
  children: [
3365
- runs,
3917
+ t("detail.schedule.nextRun"),
3366
3918
  " ",
3367
- t("board.runs")
3919
+ nextRunLabel(scheduleNextRun, scheduleTimeZone, formatHostTimestamp, Date.now())
3368
3920
  ]
3369
- }),
3370
- latest?.sessionId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3371
- className: board_module_css_default.cardSession,
3372
- title: latest.sessionId,
3373
- children: "⌁"
3374
- }),
3375
- !archived && (task.status === "running" || pending) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3376
- className: board_module_css_default.cardSpinner,
3377
- "aria-hidden": "true"
3378
3921
  })
3379
- ]
3380
- }),
3381
- !archived && pending && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3382
- className: board_module_css_default.cardRunningLabel,
3383
- children: [t("board.pending"), "…"]
3384
- }),
3385
- !archived && latest !== void 0 && executionLabel(latest) === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3386
- className: board_module_css_default.cardRunningLabel,
3387
- children: [latest.ownResult === void 0 ? t("detail.result.running") : t("detail.subtasks.waiting"), "…"]
3922
+ ] })]
3388
3923
  })
3389
3924
  ]
3390
3925
  });
3391
3926
  }
3392
- /** Memoized card: re-renders only when the card's own task record changes. */
3393
- const TaskCard = (0, react.memo)(TaskCardInner);
3927
+ //#endregion
3928
+ //#region src/client/board/status-key.ts
3929
+ /** Task status → locale key (board column titles and the detail badge). */
3930
+ const STATUS_KEY = {
3931
+ backlog: "board.status.backlog",
3932
+ todo: "board.status.todo",
3933
+ running: "board.status.running",
3934
+ done: "board.status.done",
3935
+ failed: "board.status.failed"
3936
+ };
3394
3937
  //#endregion
3395
3938
  //#region src/client/board/ConfirmDialog.tsx
3396
3939
  /**
@@ -3545,7 +4088,15 @@ window.__ModuleLoader__.load({
3545
4088
  const [error, setError] = (0, react.useState)(void 0);
3546
4089
  const snapshot = controller.getSnapshot();
3547
4090
  const limit = snapshot.host?.maxSubtaskDepth ?? 1;
3548
- const candidates = snapshot.tasks.filter((task) => task.archivedAt === void 0 && task.status !== "running" && task.parentId === void 0 && task.id !== parent.id && checkParentLink(snapshot.tasks, task.id, parent.id, limit).ok);
4091
+ const tasks = snapshot.tasks;
4092
+ const candidates = (0, react.useMemo)(() => {
4093
+ const lineage = buildLineageIndex(tasks);
4094
+ return tasks.filter((task) => task.archivedAt === void 0 && task.status !== "running" && task.parentId === void 0 && task.id !== parent.id && checkParentLink(tasks, task.id, parent.id, limit, lineage).ok);
4095
+ }, [
4096
+ tasks,
4097
+ parent.id,
4098
+ limit
4099
+ ]);
3549
4100
  const link = async (taskId) => {
3550
4101
  setPending(true);
3551
4102
  setError(void 0);
@@ -3677,15 +4228,8 @@ window.__ModuleLoader__.load({
3677
4228
  });
3678
4229
  }
3679
4230
  /** The execution-target editor: workspace / mode / permission pickers. */
3680
- function ExecutionSettingsSection({ controller, task, pending }) {
3681
- const [options, setOptions] = (0, react.useState)(controller.getSnapshot().executionOptions);
3682
- const teamRunOffered = () => controller.getSnapshot().host?.teamRunAvailable === true;
3683
- const [teamRunAvailable, setTeamRunAvailable] = (0, react.useState)(teamRunOffered());
3684
- (0, react.useEffect)(() => controller.subscribe(() => {
3685
- const snapshot = controller.getSnapshot();
3686
- setOptions(snapshot.executionOptions);
3687
- setTeamRunAvailable(snapshot.host?.teamRunAvailable === true);
3688
- }), [controller]);
4231
+ function ExecutionSettingsSection({ controller, task, pending, executionOptions, teamRunAvailable }) {
4232
+ const options = executionOptions;
3689
4233
  const workspaceId = task.workspaceId ?? "";
3690
4234
  const mode = task.mode ?? "";
3691
4235
  const permission = task.permission ?? "";
@@ -3733,7 +4277,7 @@ window.__ModuleLoader__.load({
3733
4277
  className: board_module_css_default.field,
3734
4278
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3735
4279
  className: board_module_css_default.fieldLabel,
3736
- children: t("new.mode")
4280
+ children: t("new.agentPreset")
3737
4281
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3738
4282
  className: board_module_css_default.select,
3739
4283
  value: mode,
@@ -3744,7 +4288,7 @@ window.__ModuleLoader__.load({
3744
4288
  children: [
3745
4289
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3746
4290
  value: "",
3747
- children: t("exec.mode.default")
4291
+ children: inheritPresetLabel(options.presets)
3748
4292
  }),
3749
4293
  !modeKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3750
4294
  value: mode,
@@ -3754,7 +4298,7 @@ window.__ModuleLoader__.load({
3754
4298
  value: preset.id,
3755
4299
  disabled: preset.broken !== void 0,
3756
4300
  children: [
3757
- preset.name ?? preset.id,
4301
+ presetLabel(preset),
3758
4302
  preset.isDefault ? t("exec.mode.defaultSuffix") : "",
3759
4303
  preset.broken !== void 0 ? t("exec.mode.brokenSuffix") : ""
3760
4304
  ]
@@ -3826,6 +4370,21 @@ window.__ModuleLoader__.load({
3826
4370
  className: board_module_css_default.detailText,
3827
4371
  children: t("exec.reuseSessionHint")
3828
4372
  }),
4373
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4374
+ className: board_module_css_default.scheduleToggle,
4375
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4376
+ type: "checkbox",
4377
+ checked: task.goalRun !== false,
4378
+ disabled: pending,
4379
+ onChange: (event) => {
4380
+ controller.updateTask(task.id, { goalRun: event.target.checked });
4381
+ }
4382
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("exec.goalRun") })]
4383
+ }),
4384
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4385
+ className: board_module_css_default.detailText,
4386
+ children: t("exec.goalRunHint")
4387
+ }),
3829
4388
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3830
4389
  className: board_module_css_default.scheduleToggle,
3831
4390
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -3844,7 +4403,7 @@ window.__ModuleLoader__.load({
3844
4403
  ]
3845
4404
  });
3846
4405
  }
3847
- /** The scheduled-runs editor: enable toggle, cron input + presets, next-run info. */
4406
+ /** The scheduled-runs editor: enable toggle, cron input + presets, zone, next-run info. */
3848
4407
  function ScheduleSection({ controller, task, pending }) {
3849
4408
  const schedule = task.schedule;
3850
4409
  const [cron, setCron] = (0, react.useState)(schedule?.cron ?? "0 9 * * *");
@@ -3852,17 +4411,21 @@ window.__ModuleLoader__.load({
3852
4411
  const [nextRunAt, setNextRunAt] = (0, react.useState)(schedule?.nextRunAt);
3853
4412
  const [lastTriggeredAt, setLastTriggeredAt] = (0, react.useState)(schedule?.lastTriggeredAt);
3854
4413
  const [error, setError] = (0, react.useState)(void 0);
3855
- const timeZone = controller.getSnapshot().host?.scheduler.timeZone;
4414
+ const hostTimeZone = controller.getSnapshot().host?.scheduler.timeZone;
4415
+ const [zone, setZone] = (0, react.useState)(schedule?.timeZone ?? "");
4416
+ const now = Date.now();
3856
4417
  (0, react.useEffect)(() => {
3857
4418
  setCron(schedule?.cron ?? "0 9 * * *");
3858
4419
  setEnabled(schedule?.enabled ?? false);
3859
4420
  setNextRunAt(schedule?.nextRunAt);
3860
4421
  setLastTriggeredAt(schedule?.lastTriggeredAt);
4422
+ setZone(schedule?.timeZone ?? "");
3861
4423
  setError(void 0);
3862
4424
  }, [
3863
4425
  task.id,
3864
4426
  schedule?.enabled,
3865
4427
  schedule?.cron,
4428
+ schedule?.timeZone,
3866
4429
  schedule?.nextRunAt,
3867
4430
  schedule?.lastTriggeredAt
3868
4431
  ]);
@@ -3877,6 +4440,15 @@ window.__ModuleLoader__.load({
3877
4440
  setError(void 0);
3878
4441
  controller.setSchedule(task.id, { cron: trimmed });
3879
4442
  };
4443
+ /**
4444
+ * Change the rule's zone. `''` clears the stored zone, which is how a user
4445
+ * returns a rule to following the Host zone.
4446
+ */
4447
+ const changeZone = (value) => {
4448
+ setZone(value);
4449
+ setError(void 0);
4450
+ controller.setSchedule(task.id, { timeZone: value === "" ? null : value });
4451
+ };
3880
4452
  /** Arm/disarm the schedule (arming first persists the edited cron). */
3881
4453
  const toggleEnabled = (next) => {
3882
4454
  const trimmed = cron.trim();
@@ -3896,8 +4468,10 @@ window.__ModuleLoader__.load({
3896
4468
  setError(void 0);
3897
4469
  controller.setSchedule(task.id, { cron: preset });
3898
4470
  };
3899
- const nextLabel = !enabled || nextRunAt === void 0 ? t("detail.schedule.notScheduled") : nextRunAt <= Date.now() ? t("detail.schedule.dueSoon") : formatHostTimestamp(nextRunAt, timeZone);
3900
- const lastLabel = lastTriggeredAt === void 0 ? "—" : formatHostTimestamp(lastTriggeredAt, timeZone);
4471
+ const effectiveZone = zone === "" ? hostTimeZone : zone;
4472
+ const nextLabel = !enabled || nextRunAt === void 0 ? t("detail.schedule.notScheduled") : nextRunAt <= now ? t("detail.schedule.dueSoon") : nextRunLabel(nextRunAt, effectiveZone, formatHostTimestamp, now);
4473
+ const lastLabel = lastTriggeredAt === void 0 ? "—" : formatHostTimestamp(lastTriggeredAt, effectiveZone);
4474
+ const zones = zoneChoices(hostTimeZone, schedule?.timeZone);
3901
4475
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3902
4476
  className: board_module_css_default.detailSection,
3903
4477
  children: [
@@ -3949,6 +4523,23 @@ window.__ModuleLoader__.load({
3949
4523
  }, preset.cron))]
3950
4524
  })]
3951
4525
  }),
4526
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4527
+ className: board_module_css_default.scheduleZone,
4528
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.timeZone") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
4529
+ className: board_module_css_default.schedulePreset,
4530
+ value: zone,
4531
+ disabled: pending,
4532
+ "aria-label": t("detail.schedule.timeZone"),
4533
+ title: t("detail.schedule.timeZoneHint"),
4534
+ onChange: (event) => {
4535
+ changeZone(event.target.value);
4536
+ },
4537
+ children: zones.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4538
+ value: choice.id,
4539
+ children: choice.label
4540
+ }, choice.id === "" ? "__host" : choice.id))
4541
+ })]
4542
+ }),
3952
4543
  error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3953
4544
  className: board_module_css_default.formError,
3954
4545
  children: error
@@ -3973,9 +4564,7 @@ window.__ModuleLoader__.load({
3973
4564
  * and the actions that grow or prune the tree. Every gate here mirrors the
3974
4565
  * Host lineage gate for affordance only; the Host re-checks the action.
3975
4566
  */
3976
- function SubtaskSection({ controller, task, pending, archived }) {
3977
- const [snapshot, setSnapshot] = (0, react.useState)(controller.getSnapshot());
3978
- (0, react.useEffect)(() => controller.subscribe(() => setSnapshot(controller.getSnapshot())), [controller]);
4567
+ function SubtaskSection({ controller, task, pending, archived, snapshot }) {
3979
4568
  const [showAdd, setShowAdd] = (0, react.useState)(false);
3980
4569
  const [showLink, setShowLink] = (0, react.useState)(false);
3981
4570
  const tasks = snapshot.tasks;
@@ -4174,7 +4763,8 @@ window.__ModuleLoader__.load({
4174
4763
  controller,
4175
4764
  task: current,
4176
4765
  pending,
4177
- archived
4766
+ archived,
4767
+ snapshot
4178
4768
  }),
4179
4769
  current.tags !== void 0 && current.tags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4180
4770
  className: board_module_css_default.detailSection,
@@ -4246,9 +4836,9 @@ window.__ModuleLoader__.load({
4246
4836
  ": ",
4247
4837
  current.handover.workspaceId ?? t("exec.workspace.recent"),
4248
4838
  " · ",
4249
- t("new.mode"),
4839
+ t("new.agentPreset"),
4250
4840
  ": ",
4251
- current.handover.mode ?? t("exec.mode.default"),
4841
+ current.handover.mode ?? t("exec.mode.inherit"),
4252
4842
  " · ",
4253
4843
  t("new.permission"),
4254
4844
  ": ",
@@ -4302,7 +4892,9 @@ window.__ModuleLoader__.load({
4302
4892
  !archived && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExecutionSettingsSection, {
4303
4893
  controller,
4304
4894
  task: current,
4305
- pending
4895
+ pending,
4896
+ executionOptions: snapshot.executionOptions,
4897
+ teamRunAvailable: snapshot.host?.teamRunAvailable === true
4306
4898
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScheduleSection, {
4307
4899
  controller,
4308
4900
  task: current,
@@ -5876,6 +6468,71 @@ window.__ModuleLoader__.load({
5876
6468
  });
5877
6469
  }
5878
6470
  //#endregion
6471
+ //#region src/core/degraded-reason.ts
6472
+ /**
6473
+ * The one degraded record naming `plugin`, or undefined when that row is not
6474
+ * degraded (or the payload does not come from the shell's own shape). A
6475
+ * plugin may appear once: the shell keys the ledger by package name and
6476
+ * refreshes the record on every new failure.
6477
+ * @param body - parsed JSON body of the health route.
6478
+ * @param plugin - the real plugin package name to look for.
6479
+ * @returns the degraded row, or undefined when the ledger does not name it.
6480
+ */
6481
+ function degradedRowOf(body, plugin) {
6482
+ if (typeof body !== "object" || body === null) return void 0;
6483
+ const ledger = body;
6484
+ if (ledger.ok !== true || !Array.isArray(ledger.degraded)) return void 0;
6485
+ for (const entry of ledger.degraded) {
6486
+ if (typeof entry !== "object" || entry === null) continue;
6487
+ const record = entry;
6488
+ if (record.plugin !== plugin) continue;
6489
+ const stage = record.stage === "import" || record.stage === "shape" || record.stage === "start" ? record.stage : void 0;
6490
+ if (stage === void 0) continue;
6491
+ return {
6492
+ plugin,
6493
+ stage,
6494
+ reason: typeof record.reason === "string" ? record.reason : ""
6495
+ };
6496
+ }
6497
+ }
6498
+ /** The ledger owner pid a task-board lock reason names, when it names one. */
6499
+ const OWNED_BY_PROCESS_RE = /already owned by process\s+(\d+)/;
6500
+ /**
6501
+ * The marker the ledger appends when it could NOT confirm the recorded PID
6502
+ * belongs to the lock's real owner: the PID was reused by an unrelated process
6503
+ * after a crash, and the reason then carries the manual-removal step instead.
6504
+ * That reason must be rendered verbatim — telling the user to close "the other
6505
+ * DSH instance" would send them after a process that never touched the board.
6506
+ */
6507
+ const UNCONFIRMED_OWNER_MARKER = "reused after a crash";
6508
+ /**
6509
+ * Extract the pid of the LIVE process a ledger-lock failure blames, when the
6510
+ * reason identifies one. The task board's lock refusal is the one degraded
6511
+ * reason that carries an owner the user can act on, so the panel renders the
6512
+ * pid in its own sentence and adds the guidance that actually applies.
6513
+ * @param reason - the shell's one-line failure reason.
6514
+ * @returns the confirmed owner pid, or undefined when the reason names none or
6515
+ * warns that the PID may have been reused (its own text owns the recovery).
6516
+ */
6517
+ function ledgerOwnerPid(reason) {
6518
+ if (reason.includes(UNCONFIRMED_OWNER_MARKER)) return void 0;
6519
+ const match = OWNED_BY_PROCESS_RE.exec(reason);
6520
+ if (match === null) return void 0;
6521
+ const pid = Number(match[1]);
6522
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
6523
+ }
6524
+ //#endregion
6525
+ //#region src/core/package-name.ts
6526
+ /**
6527
+ * npm identity of this plugin. The dsh-web-all degraded ledger is keyed by the
6528
+ * real plugin package name of a family row, and this plugin's browser half
6529
+ * must name its OWN row there to learn why its Host API is missing (issue
6530
+ * #1730): the served bundle cannot derive the name, because a family row
6531
+ * mounts the package from a versioned profile path. The host half spells the
6532
+ * same name in its single-mount guard (src/index.ts); the two move together.
6533
+ */
6534
+ const TASK_BOARD_PACKAGE = "@linxin666/dsh-client-ui-task-board";
6535
+ //#endregion
5879
6536
  //#region src/protocol.ts
5880
6537
  const TASK_BOARD_API_PREFIX = "/api/task-board";
5881
6538
  /** Whether a decoded reply carries the three draft strings the form accepts. */
@@ -5914,6 +6571,90 @@ window.__ModuleLoader__.load({
5914
6571
  this.name = "HostApiError";
5915
6572
  }
5916
6573
  };
6574
+ /**
6575
+ * Same-origin family health route served by the dsh-web-all shell: the
6576
+ * degraded ledger, in which a family row that failed during apply appears WITH
6577
+ * its reason. DOCUMENT-RELATIVE for the same reason as the board's own routes
6578
+ * (issue #1707). A 404 here means the family shell is absent or older than the
6579
+ * route, which is not itself a failure of the board.
6580
+ */
6581
+ const FAMILY_DEGRADED_ROUTE = "api/dsh-web-all/degraded";
6582
+ /** Health-route ceiling: an unresponsive family shell must not hold the board's first paint. */
6583
+ const DEGRADED_LOOKUP_TIMEOUT_MS = 2e3;
6584
+ /**
6585
+ * The failure text for a row this browser knows failed during start.
6586
+ *
6587
+ * A ledger lock is the one class with an owner the user can act on, so its
6588
+ * reason is unpacked into the pid plus what to do about it; every other
6589
+ * degraded row is reported with the shell's own reason verbatim. The raw
6590
+ * reason rides along either way, because it is the only text that names the
6591
+ * lock file or the recovery step when the pid is not the whole story.
6592
+ * @param reason - the shell's one-line failure reason (never empty).
6593
+ */
6594
+ function degradedFailureMessage(reason) {
6595
+ const pid = ledgerOwnerPid(reason);
6596
+ if (pid === void 0) return {
6597
+ failure: "degraded",
6598
+ message: t("board.hostError.degraded", { detail: reason })
6599
+ };
6600
+ return {
6601
+ failure: "ledger-locked",
6602
+ message: t("board.hostError.ledgerLocked", {
6603
+ pid: String(pid),
6604
+ detail: reason
6605
+ })
6606
+ };
6607
+ }
6608
+ /**
6609
+ * The degraded record the family shell holds for THIS board, or undefined when
6610
+ * the row is healthy, the shell is absent, or the answer is unusable. The
6611
+ * lookup is best-effort by contract: any uncertainty keeps the caller's own
6612
+ * failure text instead of replacing it with a weaker one.
6613
+ * @param fetchImpl - fetch implementation (injected for tests).
6614
+ * @returns the ledger's reason for this row, or undefined when it names none.
6615
+ */
6616
+ async function fetchDegradedReason(fetchImpl) {
6617
+ const controller = new AbortController();
6618
+ const timer = globalThis.setTimeout(() => {
6619
+ controller.abort();
6620
+ }, DEGRADED_LOOKUP_TIMEOUT_MS);
6621
+ try {
6622
+ const response = await fetchImpl(FAMILY_DEGRADED_ROUTE, {
6623
+ signal: controller.signal,
6624
+ headers: { accept: "application/json" },
6625
+ cache: "no-store"
6626
+ });
6627
+ if (!response.ok) return void 0;
6628
+ return degradedRowOf(await response.json(), TASK_BOARD_PACKAGE)?.reason;
6629
+ } catch {
6630
+ return;
6631
+ } finally {
6632
+ globalThis.clearTimeout(timer);
6633
+ }
6634
+ }
6635
+ /**
6636
+ * Name the real reason this board's routes are missing (issue #1730).
6637
+ *
6638
+ * The core webserver answers an unmounted route with a bare 404, which cannot
6639
+ * distinguish "this deployment never loaded the plugin" from "this row
6640
+ * degraded during start" — a second DSH process holding `ledger-v2.lock` is
6641
+ * the case that matters, because the panel's advice ("restart the service")
6642
+ * is then false: the lock belongs to the other process, and restarting cannot
6643
+ * win it. The family health route knows the difference, so the 404 is
6644
+ * disambiguated there before anything is shown.
6645
+ * @param fallback - copy for the plain "routes are not there" failure, which
6646
+ * is route-specific (the board's state route and the AI-parse route phrase
6647
+ * their own absence); it is also what is shown when the ledger names nothing.
6648
+ * @param fetchImpl - fetch implementation (injected for tests).
6649
+ * @returns the HostApiError to throw for a missing board route.
6650
+ */
6651
+ async function missingRouteFailure(fallback = t("board.hostError.notMounted"), fetchImpl = fetch) {
6652
+ const absent = () => new HostApiError("not-mounted", fallback, 404);
6653
+ const reason = await fetchDegradedReason(fetchImpl);
6654
+ if (reason === void 0 || reason === "") return absent();
6655
+ const described = degradedFailureMessage(reason);
6656
+ return new HostApiError(described.failure, described.message, 404);
6657
+ }
5917
6658
  function uuid() {
5918
6659
  return globalThis.crypto?.randomUUID?.() ?? `browser-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
5919
6660
  }
@@ -5938,12 +6679,13 @@ window.__ModuleLoader__.load({
5938
6679
  return parsed;
5939
6680
  }
5940
6681
  if (hostError !== void 0) {
5941
- if (hostError === "forbidden") throw new HostApiError("unauthorized", t("board.hostError.unauthorized"), response.status);
6682
+ if (hostError === "forbidden") throw new HostApiError("forbidden", t("board.hostError.forbidden"), response.status);
5942
6683
  if (response.status === 503 || /lock/i.test(hostError)) throw new HostApiError("locked", t("board.hostError.locked", { detail: hostError }), response.status);
5943
6684
  throw new HostApiError("rejected", hostError, response.status);
5944
6685
  }
5945
- if (response.status === 404) throw new HostApiError("not-mounted", t("board.hostError.notMounted"), 404);
5946
- if (response.status === 401 || response.status === 403) throw new HostApiError("unauthorized", t("board.hostError.unauthorized"), response.status);
6686
+ if (response.status === 404) throw await missingRouteFailure();
6687
+ if (response.status === 403) throw new HostApiError("forbidden", t("board.hostError.forbidden"), 403);
6688
+ if (response.status === 401) throw new HostApiError("unauthorized", t("board.hostError.unauthorized"), 401);
5947
6689
  throw new HostApiError("unexpected", t("board.hostError.unexpected", { status: String(response.status) }), response.status);
5948
6690
  }
5949
6691
  var HttpTaskBoardHostTransport = class {
@@ -6042,8 +6784,9 @@ window.__ModuleLoader__.load({
6042
6784
  if (record !== void 0 && isTaskParseDraft(record.draft)) return record.draft;
6043
6785
  throw new HostApiError("unexpected", t("new.aiParseFailed", { error: t("board.hostError.unexpected", { status: String(response.status) }) }), response.status);
6044
6786
  }
6045
- if (response.status === 404) throw new HostApiError("not-mounted", t("new.aiParseUnavailable"), 404);
6046
- if (response.status === 401 || response.status === 403) throw new HostApiError("unauthorized", t("board.hostError.unauthorized"), response.status);
6787
+ if (response.status === 404) throw await missingRouteFailure(t("new.aiParseUnavailable"));
6788
+ if (response.status === 403) throw new HostApiError("forbidden", t("board.hostError.forbidden"), 403);
6789
+ if (response.status === 401) throw new HostApiError("unauthorized", t("board.hostError.unauthorized"), 401);
6047
6790
  const code = typeof record?.code === "string" ? record.code : void 0;
6048
6791
  if (code === "no-model") throw new HostApiError("rejected", t("new.aiParseNoModel"), response.status);
6049
6792
  if (code === "timeout") throw new HostApiError("timeout", t("new.aiParseTimeout", { seconds: String(TASK_PARSE_TIMEOUT_SECONDS) }), response.status);
@@ -6085,7 +6828,7 @@ window.__ModuleLoader__.load({
6085
6828
  /** The building package's version, when the bundle carries it. */
6086
6829
  function bakedVersion() {
6087
6830
  try {
6088
- return "0.4.3";
6831
+ return "0.4.4";
6089
6832
  } catch {
6090
6833
  return;
6091
6834
  }
@@ -6407,6 +7150,44 @@ window.__ModuleLoader__.load({
6407
7150
  "layout"
6408
7151
  ];
6409
7152
  /**
7153
+ * Read the agent-preset roster through whichever face the running host
7154
+ * serves: the generated api-remotes face (`remote.agentPresets`,
7155
+ * 0.1.2-alpha.2) or the connection RPC face
7156
+ * (`connection.api.agentPresets`, hosts below that cohort). Answers
7157
+ * undefined when the host serves neither, so the caller leaves the picker
7158
+ * options untouched instead of erroring.
7159
+ */
7160
+ async function readPresetRoster(ctx, remote) {
7161
+ let remotes;
7162
+ try {
7163
+ remotes = remote.agentPresets;
7164
+ } catch {
7165
+ remotes = void 0;
7166
+ }
7167
+ if (remotes !== void 0) {
7168
+ const response = await remotes.list();
7169
+ if (!response.ok) return {
7170
+ ok: false,
7171
+ presets: []
7172
+ };
7173
+ return {
7174
+ ok: true,
7175
+ presets: response.value.presets
7176
+ };
7177
+ }
7178
+ const legacy = ctx.get("connection").api?.agentPresets;
7179
+ if (legacy === void 0) return void 0;
7180
+ const response = await legacy.list({});
7181
+ if (!response.result.ok || response.result.value === void 0) return {
7182
+ ok: false,
7183
+ presets: []
7184
+ };
7185
+ return {
7186
+ ok: true,
7187
+ presets: response.result.value.presets ?? []
7188
+ };
7189
+ }
7190
+ /**
6410
7191
  * Mount the task board.
6411
7192
  * @param ctx - client root context (services: sessions, workspaces).
6412
7193
  */
@@ -6472,6 +7253,21 @@ window.__ModuleLoader__.load({
6472
7253
  controller.setWorkspaceCreator(async (path) => {
6473
7254
  return { workspaceId: (await workspaces.create({ path })).workspaceId };
6474
7255
  });
7256
+ const pushPresetOptions = async () => {
7257
+ try {
7258
+ const roster = await readPresetRoster(ctx, remote);
7259
+ if (roster === void 0 || !roster.ok) return;
7260
+ controller.setExecutionOptions({ presets: roster.presets.map((preset) => ({
7261
+ id: preset.id,
7262
+ name: preset.name,
7263
+ description: preset.description,
7264
+ broken: preset.broken,
7265
+ isDefault: preset.isDefault
7266
+ })) });
7267
+ } catch (error) {
7268
+ console.error("[dsh-task-board] agent preset roster read failed", error);
7269
+ }
7270
+ };
6475
7271
  const pushModelOptions = async () => {
6476
7272
  try {
6477
7273
  let models = [];
@@ -6527,8 +7323,10 @@ window.__ModuleLoader__.load({
6527
7323
  console.error("[dsh-task-board] model options read failed", error);
6528
7324
  }
6529
7325
  };
7326
+ pushPresetOptions();
6530
7327
  pushModelOptions();
6531
7328
  disposers.push(ctx.on("connection/reset", () => {
7329
+ pushPresetOptions();
6532
7330
  pushModelOptions();
6533
7331
  }));
6534
7332
  try {