@linxin666/dsh-client-ui-task-board 0.4.2 → 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.
- package/README.i18n.yaml +2 -2
- package/README.md +21 -13
- package/README.zh.md +21 -13
- package/lib/client.js +1566 -929
- package/lib/client.js.map +1 -1
- package/lib/index.js +934 -176
- package/lib/types/client/board/TaskForm.d.ts +19 -1
- package/lib/types/client/board/preset-label.d.ts +22 -0
- package/lib/types/client/host-api.d.ts +24 -1
- package/lib/types/client/index.d.ts +15 -1
- package/lib/types/client/locales.d.ts +38 -2
- package/lib/types/client/native-panel.d.ts +62 -0
- package/lib/types/client/schedule-zone.d.ts +47 -0
- package/lib/types/client/settings-entry-form.d.ts +49 -0
- package/lib/types/core/controller.d.ts +42 -2
- package/lib/types/core/degraded-reason.d.ts +46 -0
- package/lib/types/core/package-name.d.ts +9 -0
- package/lib/types/core/schedule.d.ts +95 -20
- package/lib/types/core/subtask.d.ts +30 -11
- package/lib/types/core/tasks.d.ts +26 -2
- package/lib/types/core/use-cases/task-create.d.ts +2 -1
- package/lib/types/core/use-cases/task-schedule.d.ts +8 -4
- package/lib/types/core/use-cases/task-update.d.ts +1 -1
- package/lib/types/host-ledger.d.ts +65 -4
- package/lib/types/host-runner.d.ts +76 -3
- package/lib/types/host-service.d.ts +77 -5
- package/lib/types/index.d.ts +11 -1
- package/lib/types/mount-once.d.ts +38 -7
- package/lib/types/protocol.d.ts +9 -3
- package/package.json +26 -23
- package/src/client/board/LinkSubtaskModal.tsx +21 -11
- package/src/client/board/NewTaskModal.tsx +271 -150
- package/src/client/board/TaskCard.tsx +24 -5
- package/src/client/board/TaskDetail.tsx +83 -33
- package/src/client/board/TaskForm.tsx +48 -3
- package/src/client/board/preset-label.ts +44 -0
- package/src/client/board.module.css +130 -136
- package/src/client/host-api.ts +109 -6
- package/src/client/index.ts +50 -18
- package/src/client/locales.ts +82 -8
- package/src/client/native-panel.tsx +124 -0
- package/src/client/schedule-zone.ts +135 -0
- package/src/client/settings-entry-form.ts +142 -0
- package/src/core/controller.ts +66 -4
- package/src/core/degraded-reason.ts +81 -0
- package/src/core/package-name.ts +9 -0
- package/src/core/schedule.ts +211 -55
- package/src/core/store.ts +10 -1
- package/src/core/subtask.ts +77 -22
- package/src/core/tasks.ts +38 -4
- package/src/core/use-cases/task-create.ts +14 -2
- package/src/core/use-cases/task-schedule.ts +17 -6
- package/src/core/use-cases/task-update.ts +4 -1
- package/src/host/agent-tools.ts +40 -8
- package/src/host-ledger.ts +223 -23
- package/src/host-routes.ts +37 -1
- package/src/host-runner.ts +164 -14
- package/src/host-service.ts +232 -39
- package/src/index.ts +23 -1
- package/src/mount-once.ts +107 -13
- package/src/protocol.ts +27 -8
- package/lib/types/client/board-mount.d.ts +0 -23
- package/lib/types/client/body-mutations.d.ts +0 -45
- package/lib/types/client/panel-mount-core.d.ts +0 -56
- package/lib/types/client/sidebar-entry-core.d.ts +0 -55
- package/lib/types/client/sidebar-entry.d.ts +0 -29
- package/lib/types/host/run-guarded.d.ts +0 -39
- package/src/client/board-mount.tsx +0 -43
- package/src/client/body-mutations.ts +0 -133
- package/src/client/panel-mount-core.ts +0 -155
- package/src/client/sidebar-entry-core.ts +0 -237
- package/src/client/sidebar-entry.ts +0 -53
- package/src/host/run-guarded.ts +0 -76
package/lib/client.js
CHANGED
|
@@ -6,7 +6,6 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
-
let react_dom_client = require("react-dom/client");
|
|
10
9
|
/**
|
|
11
10
|
* Repair a persisted tag list: keep the well-formed entries, trim, drop
|
|
12
11
|
* blanks and repeats, cap the count, and collapse a blank prompt line to
|
|
@@ -160,6 +159,7 @@ window.__ModuleLoader__.load({
|
|
|
160
159
|
model: normalizeTargetId(input.model),
|
|
161
160
|
reuseSession: input.reuseSession === true ? true : void 0,
|
|
162
161
|
teamRun: input.teamRun === true ? true : void 0,
|
|
162
|
+
goalRun: input.goalRun === false ? false : void 0,
|
|
163
163
|
...input.freeze === void 0 ? {} : { freeze: freezeOf(input.freeze, now) },
|
|
164
164
|
...input.handover === void 0 ? {} : { handover: {
|
|
165
165
|
...input.handover,
|
|
@@ -187,11 +187,14 @@ window.__ModuleLoader__.load({
|
|
|
187
187
|
const schedule = {
|
|
188
188
|
enabled: current?.enabled ?? false,
|
|
189
189
|
cron: current?.cron ?? "",
|
|
190
|
+
...current?.timeZone === void 0 ? {} : { timeZone: current.timeZone },
|
|
190
191
|
nextRunAt: current?.nextRunAt,
|
|
191
192
|
lastTriggeredAt: current?.lastTriggeredAt
|
|
192
193
|
};
|
|
193
194
|
if ("enabled" in patch) schedule.enabled = patch.enabled ?? false;
|
|
194
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;
|
|
195
198
|
if ("nextRunAt" in patch) schedule.nextRunAt = patch.nextRunAt;
|
|
196
199
|
if ("lastTriggeredAt" in patch) schedule.lastTriggeredAt = patch.lastTriggeredAt;
|
|
197
200
|
return {
|
|
@@ -223,6 +226,22 @@ window.__ModuleLoader__.load({
|
|
|
223
226
|
function isExecutionOutcome(value) {
|
|
224
227
|
return value === "succeeded" || value === "failed" || value === "cancelled";
|
|
225
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
|
+
}
|
|
226
245
|
function indexOf(tasks) {
|
|
227
246
|
return new Map(tasks.map((task) => [task.id, task]));
|
|
228
247
|
}
|
|
@@ -232,8 +251,8 @@ window.__ModuleLoader__.load({
|
|
|
232
251
|
* under a larger limit survives that limit being lowered, and restore and
|
|
233
252
|
* inheritance must still see all of it.
|
|
234
253
|
*/
|
|
235
|
-
function ancestorChain(tasks, task) {
|
|
236
|
-
const index = indexOf(tasks);
|
|
254
|
+
function ancestorChain(tasks, task, lineage) {
|
|
255
|
+
const index = lineage?.byId ?? indexOf(tasks);
|
|
237
256
|
const chain = [];
|
|
238
257
|
const seen = /* @__PURE__ */ new Set([task.id]);
|
|
239
258
|
let current = task.parentId === void 0 ? void 0 : index.get(task.parentId);
|
|
@@ -246,29 +265,33 @@ window.__ModuleLoader__.load({
|
|
|
246
265
|
return chain;
|
|
247
266
|
}
|
|
248
267
|
/** Root-to-task depth: a root task is 0, its subtask 1, and so on. */
|
|
249
|
-
function taskDepth(tasks, id) {
|
|
250
|
-
const task = indexOf(tasks).get(id);
|
|
251
|
-
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;
|
|
252
271
|
}
|
|
253
272
|
/** Direct subtasks of a task, in ledger order. */
|
|
254
|
-
function directSubtasks(tasks, id) {
|
|
273
|
+
function directSubtasks(tasks, id, lineage) {
|
|
274
|
+
if (lineage !== void 0) return [...lineage.childrenByParent.get(id) ?? []];
|
|
255
275
|
return tasks.filter((task) => task.parentId === id);
|
|
256
276
|
}
|
|
257
277
|
/**
|
|
258
278
|
* Descendants of a task in breadth-first order, bounded by maxSubtaskDepth
|
|
259
279
|
* total depth. The visited set keeps a malformed ledger from looping.
|
|
260
280
|
*/
|
|
261
|
-
function descendantTasks(tasks, id, maxSubtaskDepth) {
|
|
281
|
+
function descendantTasks(tasks, id, maxSubtaskDepth, lineage) {
|
|
262
282
|
const visited = /* @__PURE__ */ new Set([id]);
|
|
263
283
|
const found = [];
|
|
264
284
|
let frontier = [id];
|
|
265
285
|
for (let depth = 1; depth <= maxSubtaskDepth && frontier.length > 0; depth += 1) {
|
|
266
286
|
const next = [];
|
|
267
|
-
for (const parentId of frontier)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
+
}
|
|
272
295
|
}
|
|
273
296
|
frontier = next;
|
|
274
297
|
}
|
|
@@ -279,13 +302,13 @@ window.__ModuleLoader__.load({
|
|
|
279
302
|
* The visited set makes the walk terminate on a cyclic hand-edited ledger
|
|
280
303
|
* instead of recursing until the stack overflows.
|
|
281
304
|
*/
|
|
282
|
-
function subtreeHeight(tasks, id, visited = /* @__PURE__ */ new Set()) {
|
|
305
|
+
function subtreeHeight(tasks, id, visited = /* @__PURE__ */ new Set(), lineage) {
|
|
283
306
|
if (visited.has(id)) return 0;
|
|
284
307
|
const seen = /* @__PURE__ */ new Set([...visited, id]);
|
|
285
|
-
const children = directSubtasks(tasks, id);
|
|
308
|
+
const children = directSubtasks(tasks, id, lineage);
|
|
286
309
|
if (children.length === 0) return 0;
|
|
287
310
|
let height = 0;
|
|
288
|
-
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));
|
|
289
312
|
return Math.min(height, 3);
|
|
290
313
|
}
|
|
291
314
|
/**
|
|
@@ -295,8 +318,8 @@ window.__ModuleLoader__.load({
|
|
|
295
318
|
* and the resulting depth (parent depth + 1 + the child own subtree height)
|
|
296
319
|
* must stay within maxSubtaskDepth.
|
|
297
320
|
*/
|
|
298
|
-
function checkParentLink(tasks, childId, parentId, maxSubtaskDepth) {
|
|
299
|
-
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 {
|
|
300
323
|
ok: false,
|
|
301
324
|
reason: "unknown-task"
|
|
302
325
|
};
|
|
@@ -305,7 +328,7 @@ window.__ModuleLoader__.load({
|
|
|
305
328
|
ok: false,
|
|
306
329
|
reason: "self-parent"
|
|
307
330
|
};
|
|
308
|
-
const parent = tasks.find((task) => task.id === parentId);
|
|
331
|
+
const parent = lineage === void 0 ? tasks.find((task) => task.id === parentId) : lineage.byId.get(parentId);
|
|
309
332
|
if (parent === void 0) return {
|
|
310
333
|
ok: false,
|
|
311
334
|
reason: "unknown-parent"
|
|
@@ -314,11 +337,11 @@ window.__ModuleLoader__.load({
|
|
|
314
337
|
ok: false,
|
|
315
338
|
reason: "archived-parent"
|
|
316
339
|
};
|
|
317
|
-
if (descendantTasks(tasks, childId, 3).some((task) => task.id === parentId)) return {
|
|
340
|
+
if (descendantTasks(tasks, childId, 3, lineage).some((task) => task.id === parentId)) return {
|
|
318
341
|
ok: false,
|
|
319
342
|
reason: "cycle"
|
|
320
343
|
};
|
|
321
|
-
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 {
|
|
322
345
|
ok: false,
|
|
323
346
|
reason: "depth-exceeded"
|
|
324
347
|
};
|
|
@@ -425,6 +448,112 @@ window.__ModuleLoader__.load({
|
|
|
425
448
|
[1, 12],
|
|
426
449
|
[0, 7]
|
|
427
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
|
+
}
|
|
428
557
|
/**
|
|
429
558
|
* Parse a 5-field cron expression.
|
|
430
559
|
* @returns the match sets, or null when the expression is invalid.
|
|
@@ -447,8 +576,8 @@ window.__ModuleLoader__.load({
|
|
|
447
576
|
days: sets[2],
|
|
448
577
|
months: sets[3],
|
|
449
578
|
weekdays,
|
|
450
|
-
|
|
451
|
-
|
|
579
|
+
dayStarred: fields[2].startsWith("*"),
|
|
580
|
+
weekdayStarred: fields[4].startsWith("*")
|
|
452
581
|
};
|
|
453
582
|
}
|
|
454
583
|
/** Whether the expression parses. */
|
|
@@ -456,52 +585,57 @@ window.__ModuleLoader__.load({
|
|
|
456
585
|
return parseCron(expr) !== null;
|
|
457
586
|
}
|
|
458
587
|
/**
|
|
459
|
-
* Compute the next matching instant after `fromMs`
|
|
460
|
-
*
|
|
461
|
-
* of the matching minute's start, or undefined
|
|
462
|
-
* can never match (for example `0 0 30 2 *`).
|
|
463
|
-
* a full leap cycle, so a valid February 29
|
|
464
|
-
* 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.
|
|
465
594
|
*
|
|
466
595
|
* Walks candidate year/month/day/hour/minute values straight from the parsed
|
|
467
596
|
* field sets instead of scanning every minute: a sparse expression such as
|
|
468
597
|
* `0 0 29 2 *` used to iterate ~1.5M wall-clock minutes before reaching the
|
|
469
|
-
* next leap day.
|
|
470
|
-
*
|
|
471
|
-
*
|
|
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.
|
|
472
605
|
*/
|
|
473
|
-
function nextRunAtMs(expr, fromMs) {
|
|
606
|
+
function nextRunAtMs(expr, fromMs, timeZone) {
|
|
474
607
|
const schedule = parseCron(expr);
|
|
475
608
|
if (schedule === null) return void 0;
|
|
476
609
|
if (!hasPossibleCalendarDay(schedule)) return void 0;
|
|
477
|
-
const
|
|
610
|
+
const zone = timeZone ?? resolveHostTimeZone();
|
|
611
|
+
const start = partsInZone(zone, fromMs);
|
|
478
612
|
const limitMs = fromMs + 5 * 366 * 24 * 60 * 60 * 1e3;
|
|
479
613
|
const sortedMinutes = [...schedule.minutes].sort((a, b) => a - b);
|
|
480
614
|
const sortedHours = [...schedule.hours].sort((a, b) => a - b);
|
|
481
615
|
const sortedMonths = [...schedule.months].sort((a, b) => a - b);
|
|
482
|
-
let year =
|
|
483
|
-
let month =
|
|
484
|
-
let day =
|
|
485
|
-
let hour =
|
|
486
|
-
let minute =
|
|
487
|
-
while (
|
|
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) {
|
|
488
622
|
for (const candidateMonth of sortedMonths) {
|
|
489
623
|
if (candidateMonth < month) continue;
|
|
490
|
-
const daysInMonth = new Date(year, candidateMonth, 0).
|
|
624
|
+
const daysInMonth = new Date(Date.UTC(year, candidateMonth, 0)).getUTCDate();
|
|
491
625
|
const dayStart = candidateMonth === month ? day : 1;
|
|
492
626
|
for (let candidateDay = dayStart; candidateDay <= daysInMonth; candidateDay += 1) {
|
|
493
|
-
if (!dayCandidate(schedule,
|
|
627
|
+
if (!dayCandidate(schedule, year, candidateMonth, candidateDay)) continue;
|
|
494
628
|
const hourStart = candidateMonth === month && candidateDay === day ? hour : 0;
|
|
495
629
|
for (const candidateHour of sortedHours) {
|
|
496
630
|
if (candidateHour < hourStart) continue;
|
|
497
631
|
const minuteStart = candidateMonth === month && candidateDay === day && candidateHour === hour ? minute : 0;
|
|
498
632
|
for (const candidateMinute of sortedMinutes) {
|
|
499
633
|
if (candidateMinute < minuteStart) continue;
|
|
500
|
-
const
|
|
501
|
-
|
|
634
|
+
const time = zonedEpoch(zone, year, candidateMonth, candidateDay, candidateHour, candidateMinute);
|
|
635
|
+
if (time === void 0) continue;
|
|
502
636
|
if (time <= fromMs) continue;
|
|
503
637
|
if (time > limitMs) return void 0;
|
|
504
|
-
|
|
638
|
+
return time;
|
|
505
639
|
}
|
|
506
640
|
}
|
|
507
641
|
}
|
|
@@ -513,17 +647,20 @@ window.__ModuleLoader__.load({
|
|
|
513
647
|
minute = 0;
|
|
514
648
|
}
|
|
515
649
|
}
|
|
516
|
-
/**
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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;
|
|
523
660
|
}
|
|
524
661
|
/** Reject impossible month/day pairs without spending the multi-year scan. */
|
|
525
662
|
function hasPossibleCalendarDay(schedule) {
|
|
526
|
-
if (schedule.
|
|
663
|
+
if (schedule.dayStarred || !schedule.weekdayStarred) return true;
|
|
527
664
|
const maximumDay = /* @__PURE__ */ new Map([
|
|
528
665
|
[1, 31],
|
|
529
666
|
[2, 29],
|
|
@@ -574,13 +711,6 @@ window.__ModuleLoader__.load({
|
|
|
574
711
|
}
|
|
575
712
|
return true;
|
|
576
713
|
}
|
|
577
|
-
/** Day/weekday OR semantics: a restricted day field alone gates, and vice versa. */
|
|
578
|
-
function matches(schedule, date) {
|
|
579
|
-
if (!schedule.minutes.has(date.getMinutes())) return false;
|
|
580
|
-
if (!schedule.hours.has(date.getHours())) return false;
|
|
581
|
-
if (!schedule.months.has(date.getMonth() + 1)) return false;
|
|
582
|
-
return dayCandidate(schedule, date);
|
|
583
|
-
}
|
|
584
714
|
function isDigits(value) {
|
|
585
715
|
return /^\d+$/.test(value);
|
|
586
716
|
}
|
|
@@ -606,8 +736,9 @@ window.__ModuleLoader__.load({
|
|
|
606
736
|
* @param now - clock instant (ms epoch).
|
|
607
737
|
* @param id - minted task id.
|
|
608
738
|
* @param maxSubtaskDepth - deployment subtask depth limit.
|
|
739
|
+
* @param hostTimeZone - zone a rule that stores no zone of its own follows.
|
|
609
740
|
*/
|
|
610
|
-
function applyCreateTask(tasks, input, now, id, maxSubtaskDepth = 1) {
|
|
741
|
+
function applyCreateTask(tasks, input, now, id, maxSubtaskDepth = 1, hostTimeZone = resolveHostTimeZone()) {
|
|
611
742
|
if (input.title.trim() === "") return {
|
|
612
743
|
task: void 0,
|
|
613
744
|
tasks,
|
|
@@ -642,10 +773,18 @@ window.__ModuleLoader__.load({
|
|
|
642
773
|
const requested = input.schedule;
|
|
643
774
|
if (requested?.enabled === true && requested.cron.trim() !== "" && isValidCron(requested.cron)) {
|
|
644
775
|
const cron = requested.cron.trim();
|
|
645
|
-
|
|
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, {
|
|
646
784
|
enabled: true,
|
|
647
785
|
cron,
|
|
648
|
-
|
|
786
|
+
timeZone: requestedZone,
|
|
787
|
+
nextRunAt
|
|
649
788
|
}, now);
|
|
650
789
|
}
|
|
651
790
|
return {
|
|
@@ -683,15 +822,17 @@ window.__ModuleLoader__.load({
|
|
|
683
822
|
* the core cron parser (schedule.ts) and the withSchedule transition.
|
|
684
823
|
*/
|
|
685
824
|
/**
|
|
686
|
-
* Set an on-board task's schedule rule. A blank or invalid cron, or
|
|
687
|
-
* archived task, is rejected (state untouched); an enabled
|
|
688
|
-
* next run instant immediately
|
|
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.
|
|
689
829
|
* @param tasks - current ledger.
|
|
690
830
|
* @param id - the task to schedule.
|
|
691
831
|
* @param patch - rule fields to change (absent fields keep their current value).
|
|
692
832
|
* @param now - clock instant (ms epoch).
|
|
833
|
+
* @param hostTimeZone - zone a rule with no stored zone follows.
|
|
693
834
|
*/
|
|
694
|
-
function applySetSchedule(tasks, id, patch, now) {
|
|
835
|
+
function applySetSchedule(tasks, id, patch, now, hostTimeZone) {
|
|
695
836
|
const task = tasks.find((candidate) => candidate.id === id);
|
|
696
837
|
if (task === void 0 || task.archivedAt !== void 0) return {
|
|
697
838
|
tasks,
|
|
@@ -703,8 +844,13 @@ window.__ModuleLoader__.load({
|
|
|
703
844
|
tasks,
|
|
704
845
|
applied: false
|
|
705
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
|
+
};
|
|
706
852
|
const enabled = patch.enabled ?? current?.enabled ?? false;
|
|
707
|
-
const nextRunAt = enabled ? nextRunAtMs(cron, now) : void 0;
|
|
853
|
+
const nextRunAt = enabled ? nextRunAtMs(cron, now, requestedZone ?? hostTimeZone) : void 0;
|
|
708
854
|
if (enabled && nextRunAt === void 0) return {
|
|
709
855
|
tasks,
|
|
710
856
|
applied: false
|
|
@@ -713,6 +859,7 @@ window.__ModuleLoader__.load({
|
|
|
713
859
|
tasks: tasks.map((candidate) => candidate.id === id ? withSchedule(candidate, {
|
|
714
860
|
enabled,
|
|
715
861
|
cron,
|
|
862
|
+
timeZone: requestedZone ?? void 0,
|
|
716
863
|
nextRunAt
|
|
717
864
|
}, now) : candidate),
|
|
718
865
|
applied: true
|
|
@@ -868,6 +1015,7 @@ window.__ModuleLoader__.load({
|
|
|
868
1015
|
if ("permission" in patch && patch.permission !== void 0 && patch.permission !== task.permission || "handover" in patch) next.permissionConfirmedAt = void 0;
|
|
869
1016
|
if ("reuseSession" in patch) next.reuseSession = patch.reuseSession === true ? true : void 0;
|
|
870
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;
|
|
871
1019
|
if (workspaceId !== void 0 || "workspaceId" in patch) next.workspaceId = workspaceId;
|
|
872
1020
|
if (mode !== void 0 || "mode" in patch) next.mode = mode;
|
|
873
1021
|
if (permission !== void 0 || "permission" in patch) next.permission = permission;
|
|
@@ -880,6 +1028,8 @@ window.__ModuleLoader__.load({
|
|
|
880
1028
|
function currentOf(sessions) {
|
|
881
1029
|
return sessions?.current();
|
|
882
1030
|
}
|
|
1031
|
+
/** Stable id shared by the board's sidebar panel row and its main-slot page. */
|
|
1032
|
+
const TASK_BOARD_PANEL_ID = "task-board";
|
|
883
1033
|
/** The selected task (resolved from the ledger), or undefined. */
|
|
884
1034
|
function selectedTaskOf(snapshot) {
|
|
885
1035
|
if (snapshot.selectedTaskId === void 0) return void 0;
|
|
@@ -977,20 +1127,55 @@ window.__ModuleLoader__.load({
|
|
|
977
1127
|
async retryHostSync() {
|
|
978
1128
|
return await this.initializeRemote();
|
|
979
1129
|
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Show the board. The layout owns which panel the center column renders, so
|
|
1132
|
+
* the state flip and the panel selection travel together here; a composition
|
|
1133
|
+
* with no layout face (tests, a shell-less host) still flips the state.
|
|
1134
|
+
*
|
|
1135
|
+
* The selection is requested AFTER the snapshot flips so a subscriber
|
|
1136
|
+
* rendering against `boardOpen` never observes "open" while the shell still
|
|
1137
|
+
* shows the conversation.
|
|
1138
|
+
*/
|
|
980
1139
|
openBoard() {
|
|
981
1140
|
if (this.boardOpen) return;
|
|
982
1141
|
this.boardOpen = true;
|
|
983
1142
|
this.notify();
|
|
1143
|
+
this.selectPanel(TASK_BOARD_PANEL_ID);
|
|
984
1144
|
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Return the center column to the conversation. The layout's own selection is
|
|
1147
|
+
* the source of truth for what the column renders, so this asks for the
|
|
1148
|
+
* conversation explicitly rather than only clearing local state.
|
|
1149
|
+
*/
|
|
985
1150
|
closeBoard() {
|
|
986
1151
|
this.boardOpen = false;
|
|
987
1152
|
this.notify();
|
|
1153
|
+
this.selectPanel(null);
|
|
988
1154
|
}
|
|
989
1155
|
toggleBoard() {
|
|
990
1156
|
if (this.boardOpen) this.closeBoard();
|
|
991
1157
|
else this.openBoard();
|
|
992
1158
|
}
|
|
993
1159
|
/**
|
|
1160
|
+
* Reflect a panel selection that came from OUTSIDE this controller (the user
|
|
1161
|
+
* clicked another sidebar row, or the layout dropped the panel id). Keeps
|
|
1162
|
+
* `boardOpen` aligned with what the column actually shows without asking the
|
|
1163
|
+
* layout to select anything back.
|
|
1164
|
+
* @param panelId - the layout's current panel id, or null for the conversation.
|
|
1165
|
+
*/
|
|
1166
|
+
syncPanelSelection(panelId) {
|
|
1167
|
+
const open = panelId === TASK_BOARD_PANEL_ID;
|
|
1168
|
+
if (open === this.boardOpen) return;
|
|
1169
|
+
this.boardOpen = open;
|
|
1170
|
+
this.notify();
|
|
1171
|
+
}
|
|
1172
|
+
/** Ask the layout to select a panel; a shell that serves no layout face is a no-op. */
|
|
1173
|
+
selectPanel(panelId) {
|
|
1174
|
+
try {
|
|
1175
|
+
this.deps.panel?.select(panelId);
|
|
1176
|
+
} catch {}
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
994
1179
|
* Switch between the kanban columns and the archive view. Leaving the
|
|
995
1180
|
* archive view with an archived task still selected closes the selection —
|
|
996
1181
|
* the detail overlay must not linger over a task that is off-board.
|
|
@@ -1181,7 +1366,8 @@ window.__ModuleLoader__.load({
|
|
|
1181
1366
|
* @returns true when applied, false when rejected (invalid cron / unknown task).
|
|
1182
1367
|
*/
|
|
1183
1368
|
setSchedule(id, patch) {
|
|
1184
|
-
const
|
|
1369
|
+
const hostTimeZone = this.hostState?.scheduler.timeZone ?? resolveHostTimeZone();
|
|
1370
|
+
const { tasks, applied } = applySetSchedule(this.tasks, id, patch, this.now(), hostTimeZone);
|
|
1185
1371
|
if (!applied) return false;
|
|
1186
1372
|
if (this.deps.transport !== void 0) {
|
|
1187
1373
|
this.commitRemote({
|
|
@@ -1270,8 +1456,8 @@ window.__ModuleLoader__.load({
|
|
|
1270
1456
|
* (background navigation, the Host runner creating and selecting a fresh
|
|
1271
1457
|
* execution session, settlement, other plugins), so closing on `current`
|
|
1272
1458
|
* changes would evict the board without the user asking. The board closes
|
|
1273
|
-
* only on explicit user navigation:
|
|
1274
|
-
*
|
|
1459
|
+
* only on explicit user navigation: selecting another panel (the layout owns
|
|
1460
|
+
* selection, and `syncPanelSelection` follows it) or the board's own actions
|
|
1275
1461
|
* (openSession / close). Keeping the hook preserves the subscription
|
|
1276
1462
|
* contract for future listeners.
|
|
1277
1463
|
*/
|
|
@@ -1708,6 +1894,7 @@ window.__ModuleLoader__.load({
|
|
|
1708
1894
|
if (record.mode !== void 0 && typeof record.mode !== "string") return false;
|
|
1709
1895
|
if (record.permission !== void 0 && typeof record.permission !== "string") return false;
|
|
1710
1896
|
if (record.reuseSession !== void 0 && typeof record.reuseSession !== "boolean") return false;
|
|
1897
|
+
if (record.goalRun !== void 0 && typeof record.goalRun !== "boolean") return false;
|
|
1711
1898
|
if (!Array.isArray(record.executions)) return false;
|
|
1712
1899
|
for (const execution of record.executions) {
|
|
1713
1900
|
if (typeof execution !== "object" || execution === null) return false;
|
|
@@ -1738,9 +1925,11 @@ window.__ModuleLoader__.load({
|
|
|
1738
1925
|
const rule = schedule;
|
|
1739
1926
|
if (typeof rule.cron !== "string") return void 0;
|
|
1740
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;
|
|
1741
1929
|
return {
|
|
1742
1930
|
enabled: rule.enabled === true,
|
|
1743
1931
|
cron: rule.cron,
|
|
1932
|
+
...timeZone === void 0 ? {} : { timeZone },
|
|
1744
1933
|
nextRunAt: typeof rule.nextRunAt === "number" ? rule.nextRunAt : void 0,
|
|
1745
1934
|
lastTriggeredAt: typeof rule.lastTriggeredAt === "number" ? rule.lastTriggeredAt : void 0
|
|
1746
1935
|
};
|
|
@@ -1825,6 +2014,7 @@ window.__ModuleLoader__.load({
|
|
|
1825
2014
|
task.archivedAt = typeof row.archivedAt === "number" && Number.isFinite(row.archivedAt) ? row.archivedAt : void 0;
|
|
1826
2015
|
task.permission = isTaskPermission(row.permission) ? row.permission : void 0;
|
|
1827
2016
|
task.reuseSession = row.reuseSession === true ? true : void 0;
|
|
2017
|
+
task.goalRun = row.goalRun === false ? false : void 0;
|
|
1828
2018
|
task.freeze = normalizeFreeze(row.freeze);
|
|
1829
2019
|
task.handover = normalizeHandover(row.handover);
|
|
1830
2020
|
task.tags = normalizeTags(row.tags);
|
|
@@ -1942,8 +2132,11 @@ window.__ModuleLoader__.load({
|
|
|
1942
2132
|
"board.updated": "更新于",
|
|
1943
2133
|
"board.created": "创建于",
|
|
1944
2134
|
"board.hostError": "Host 操作失败:{error}",
|
|
1945
|
-
"board.hostError.notMounted": "
|
|
2135
|
+
"board.hostError.notMounted": "任务看板的后台接口没有挂载:宿主没有加载这个插件(可能未安装、未启用,或该行启动失败)。重启 DSH 服务后再试",
|
|
2136
|
+
"board.hostError.ledgerLocked": "任务看板的账本已被另一个 DSH 进程占用(占用者 pid {pid}),本实例没有注册后台接口:关闭那个 DSH 实例,或等它退出后再点重试——反复重启本实例无效(宿主原因:{detail})",
|
|
2137
|
+
"board.hostError.degraded": "任务看板这一行在启动时降级,后台接口没有注册(宿主原因:{detail})",
|
|
1946
2138
|
"board.hostError.unauthorized": "登录状态已失效,请刷新页面后重试",
|
|
2139
|
+
"board.hostError.forbidden": "当前环境未通过同源校验:桌面外壳或反向代理可能未传递浏览器标记,也没有携带宿主认证凭据",
|
|
1947
2140
|
"board.hostError.locked": "任务看板的后台数据被占用:{detail}",
|
|
1948
2141
|
"board.hostError.timeout": "后台 {seconds} 秒内没有响应,请稍后重试",
|
|
1949
2142
|
"board.hostError.unreachable": "连接不上本机 DSH 服务,请确认它仍在运行",
|
|
@@ -1968,6 +2161,19 @@ window.__ModuleLoader__.load({
|
|
|
1968
2161
|
"new.aiParseFailed": "解析失败:{error}",
|
|
1969
2162
|
"board.retryHost": "重试连接 Host",
|
|
1970
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": "单轮",
|
|
1971
2177
|
"new.title": "标题",
|
|
1972
2178
|
"new.titlePlaceholder": "一句话描述要做什么",
|
|
1973
2179
|
"new.description": "描述",
|
|
@@ -2059,13 +2265,31 @@ window.__ModuleLoader__.load({
|
|
|
2059
2265
|
"detail.schedule.invalid": "Cron 表达式无效",
|
|
2060
2266
|
"detail.schedule.notScheduled": "尚未排程",
|
|
2061
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} 秒",
|
|
2062
2279
|
"card.scheduled": "定时",
|
|
2063
2280
|
"new.workspace": "工作区",
|
|
2064
|
-
"new.
|
|
2281
|
+
"new.agentPreset": "Agent 预设",
|
|
2065
2282
|
"new.permission": "权限",
|
|
2066
2283
|
"new.model": "模型",
|
|
2067
2284
|
"exec.workspace.recent": "最近使用(默认)",
|
|
2068
|
-
"exec.mode.
|
|
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",
|
|
2069
2293
|
"exec.mode.defaultSuffix": "(默认)",
|
|
2070
2294
|
"exec.mode.brokenSuffix": "(不可用)",
|
|
2071
2295
|
"exec.mode.removed": "(已移除)",
|
|
@@ -2081,8 +2305,10 @@ window.__ModuleLoader__.load({
|
|
|
2081
2305
|
"exec.teamRunHint": "开启后:执行本任务只启动一个 Leader 会话,每个子任务由它派生为 teammate 并在该会话内并行工作(子任务自身的权限钉住不适用)。关闭则每个成员各开独立会话。",
|
|
2082
2306
|
"exec.teamRunUnavailable": "当前部署未提供 Agent Teams 服务,无法启用。",
|
|
2083
2307
|
"exec.reuseSessionHint": "开启后,本任务的后续执行在上一次会话里继续(该会话空闲且仍存在时),不再每次新建对话;每次复用时都会重新应用上面钉住的权限与模型。",
|
|
2308
|
+
"exec.goalRun": "以 dsh 内置的 /goal 开始执行任务",
|
|
2309
|
+
"exec.goalRunHint": "默认开启:执行时把任务目标作为持久目标交给 dsh 内置的 /goal,会话会自动续跑多轮,直到 agent 标记目标完成;看板在该目标真正结束后才结算本次执行。关闭则只执行一轮普通对话。",
|
|
2084
2310
|
"detail.executionSettings": "执行设置",
|
|
2085
|
-
"exec.hint": "
|
|
2311
|
+
"exec.hint": "执行时生效:工作区决定执行会话落在哪个工作区;Agent 预设决定会话的组合(工具面与提示词段);权限经 /permission 命令应用到会话。留空则继承部署默认。",
|
|
2086
2312
|
"settings.title": "任务看板",
|
|
2087
2313
|
"settings.description": "控制 Host 任务看板、agent 播报与运行期间的系统空闲睡眠保护。",
|
|
2088
2314
|
"settings.enabled": "启用任务看板",
|
|
@@ -2164,8 +2390,11 @@ window.__ModuleLoader__.load({
|
|
|
2164
2390
|
"board.updated": "Updated",
|
|
2165
2391
|
"board.created": "Created",
|
|
2166
2392
|
"board.hostError": "Host action failed: {error}",
|
|
2167
|
-
"board.hostError.notMounted": "The task board Host API is not mounted: the Host
|
|
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}",
|
|
2168
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",
|
|
2169
2398
|
"board.hostError.locked": "The task board ledger is locked: {detail}",
|
|
2170
2399
|
"board.hostError.timeout": "The Host did not answer within {seconds}s; try again shortly",
|
|
2171
2400
|
"board.hostError.unreachable": "Cannot reach the local DSH service; make sure it is still running",
|
|
@@ -2190,6 +2419,19 @@ window.__ModuleLoader__.load({
|
|
|
2190
2419
|
"new.aiParseFailed": "Parsing failed: {error}",
|
|
2191
2420
|
"board.retryHost": "Retry Host connection",
|
|
2192
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",
|
|
2193
2435
|
"new.title": "Title",
|
|
2194
2436
|
"new.titlePlaceholder": "What should be done, in one line",
|
|
2195
2437
|
"new.description": "Description",
|
|
@@ -2281,13 +2523,31 @@ window.__ModuleLoader__.load({
|
|
|
2281
2523
|
"detail.schedule.invalid": "Invalid cron expression",
|
|
2282
2524
|
"detail.schedule.notScheduled": "Not scheduled yet",
|
|
2283
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",
|
|
2284
2537
|
"card.scheduled": "scheduled",
|
|
2285
2538
|
"new.workspace": "Workspace",
|
|
2286
|
-
"new.
|
|
2539
|
+
"new.agentPreset": "Agent preset",
|
|
2287
2540
|
"new.permission": "Permission",
|
|
2288
2541
|
"new.model": "Model",
|
|
2289
2542
|
"exec.workspace.recent": "Most recent (default)",
|
|
2290
|
-
"exec.mode.
|
|
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",
|
|
2291
2551
|
"exec.mode.defaultSuffix": " (default)",
|
|
2292
2552
|
"exec.mode.brokenSuffix": " (unavailable)",
|
|
2293
2553
|
"exec.mode.removed": " (removed)",
|
|
@@ -2303,8 +2563,10 @@ window.__ModuleLoader__.load({
|
|
|
2303
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.",
|
|
2304
2564
|
"exec.teamRunUnavailable": "This deployment serves no Agent Teams service, so team runs cannot be enabled.",
|
|
2305
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.",
|
|
2306
2568
|
"detail.executionSettings": "Execution Settings",
|
|
2307
|
-
"exec.hint": "Applied when the task runs: the workspace decides where the execution session lands; the
|
|
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.",
|
|
2308
2570
|
"settings.title": "Task Board",
|
|
2309
2571
|
"settings.description": "Configure the Host task board, agent announcement, and idle-system-sleep protection while work is pending.",
|
|
2310
2572
|
"settings.enabled": "Enable the task board",
|
|
@@ -2387,7 +2649,7 @@ window.__ModuleLoader__.load({
|
|
|
2387
2649
|
}
|
|
2388
2650
|
//#endregion
|
|
2389
2651
|
//#region \0dsh-css:packages/dsh-task-board/src/client/board.module.css.mjs
|
|
2390
|
-
const css$1 = "[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-taskboard-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0;container:_7D6uKa_task-board-view/inline-size}html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-dsh-taskboard-view]{display:block}html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-pane=conversation]>:not([data-dsh-taskboard-view]),html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [class*=centerCol]>:not([data-dsh-taskboard-view]){display:none!important}._7D6uKa_entry{box-sizing:border-box;min-height:36px;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;text-align:left;white-space:nowrap;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;margin:0 2px;padding:7px 8px;font-size:14px;line-height:22px;display:flex}._7D6uKa_entry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_entry[data-active]{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:600}._7D6uKa_entryIcon{flex:none;justify-content:center;align-items:center;width:16px;height:16px;display:inline-flex}._7D6uKa_entryIcon svg{width:16px;height:16px;display:block}._7D6uKa_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entry,[data-sidebar-collapsed] ._7D6uKa_entry{border-radius:12px;justify-content:center;width:36px;height:36px;margin:0 auto 12px;padding:0}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entryIcon,[data-sidebar-collapsed] ._7D6uKa_entryIcon,[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entryIcon svg,[data-sidebar-collapsed] ._7D6uKa_entryIcon svg{width:18px;height:18px}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entryLabel,[data-sidebar-collapsed] ._7D6uKa_entryLabel{display:none}._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_entry,._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_entry:focus-visible,._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_entry,._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_entry:active,._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_entry[data-active]:hover{background:var(--dsw-specific-sidebar-nav-item-active)}._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_entry,._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}";
|
|
2391
2653
|
const tagId$1 = "@linxin666/dsh-client-ui-task-board/packages/dsh-task-board/src/client/board.module.css";
|
|
2392
2654
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
2393
2655
|
const tag = document.createElement("style");
|
|
@@ -2434,9 +2696,6 @@ window.__ModuleLoader__.load({
|
|
|
2434
2696
|
"detailText": "_7D6uKa_detailText",
|
|
2435
2697
|
"detailTitle": "_7D6uKa_detailTitle",
|
|
2436
2698
|
"dshTbSpin": "_7D6uKa_dshTbSpin",
|
|
2437
|
-
"entry": "_7D6uKa_entry",
|
|
2438
|
-
"entryIcon": "_7D6uKa_entryIcon",
|
|
2439
|
-
"entryLabel": "_7D6uKa_entryLabel",
|
|
2440
2699
|
"executionBadge": "_7D6uKa_executionBadge",
|
|
2441
2700
|
"executionError": "_7D6uKa_executionError",
|
|
2442
2701
|
"executionList": "_7D6uKa_executionList",
|
|
@@ -2446,12 +2705,19 @@ window.__ModuleLoader__.load({
|
|
|
2446
2705
|
"fieldHint": "_7D6uKa_fieldHint",
|
|
2447
2706
|
"fieldLabel": "_7D6uKa_fieldLabel",
|
|
2448
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",
|
|
2449
2714
|
"ghostButton": "_7D6uKa_ghostButton",
|
|
2450
2715
|
"iconButton": "_7D6uKa_iconButton",
|
|
2451
2716
|
"input": "_7D6uKa_input",
|
|
2452
2717
|
"linkButton": "_7D6uKa_linkButton",
|
|
2453
2718
|
"modal": "_7D6uKa_modal",
|
|
2454
2719
|
"modalBackdrop": "_7D6uKa_modalBackdrop",
|
|
2720
|
+
"modalBody": "_7D6uKa_modalBody",
|
|
2455
2721
|
"modalFooter": "_7D6uKa_modalFooter",
|
|
2456
2722
|
"modalTitle": "_7D6uKa_modalTitle",
|
|
2457
2723
|
"moveRow": "_7D6uKa_moveRow",
|
|
@@ -2470,6 +2736,7 @@ window.__ModuleLoader__.load({
|
|
|
2470
2736
|
"schedulePreset": "_7D6uKa_schedulePreset",
|
|
2471
2737
|
"scheduleRow": "_7D6uKa_scheduleRow",
|
|
2472
2738
|
"scheduleToggle": "_7D6uKa_scheduleToggle",
|
|
2739
|
+
"scheduleZone": "_7D6uKa_scheduleZone",
|
|
2473
2740
|
"search": "_7D6uKa_search",
|
|
2474
2741
|
"select": "_7D6uKa_select",
|
|
2475
2742
|
"statusBadge": "_7D6uKa_statusBadge",
|
|
@@ -2506,7 +2773,128 @@ window.__ModuleLoader__.load({
|
|
|
2506
2773
|
}
|
|
2507
2774
|
];
|
|
2508
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
|
|
2509
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
|
+
*/
|
|
2510
2898
|
/** DOM id shared by the tag-name inputs and their datalist (one board at a time). */
|
|
2511
2899
|
const TAG_NAME_LIST_ID = "dsh-task-board-tag-names";
|
|
2512
2900
|
/** Modal overlay: closes on backdrop press, submits through the form. */
|
|
@@ -2529,7 +2917,10 @@ window.__ModuleLoader__.load({
|
|
|
2529
2917
|
className: board_module_css_default.modalTitle,
|
|
2530
2918
|
children: title
|
|
2531
2919
|
}),
|
|
2532
|
-
|
|
2920
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2921
|
+
className: board_module_css_default.modalBody,
|
|
2922
|
+
children
|
|
2923
|
+
}),
|
|
2533
2924
|
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2534
2925
|
className: board_module_css_default.formError,
|
|
2535
2926
|
children: error
|
|
@@ -2562,6 +2953,48 @@ window.__ModuleLoader__.load({
|
|
|
2562
2953
|
})
|
|
2563
2954
|
});
|
|
2564
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
|
+
}
|
|
2565
2998
|
/** Title + description + prompt fields shared by the new and edit task forms. */
|
|
2566
2999
|
function TaskContentFields({ title, description, prompt, onTitleChange, onDescriptionChange, onPromptChange }) {
|
|
2567
3000
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
@@ -2585,7 +3018,7 @@ window.__ModuleLoader__.load({
|
|
|
2585
3018
|
children: t("new.description")
|
|
2586
3019
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
2587
3020
|
className: board_module_css_default.input,
|
|
2588
|
-
rows:
|
|
3021
|
+
rows: 2,
|
|
2589
3022
|
value: description,
|
|
2590
3023
|
placeholder: t("new.descriptionPlaceholder"),
|
|
2591
3024
|
onChange: (event) => onDescriptionChange(event.target.value)
|
|
@@ -2695,6 +3128,166 @@ window.__ModuleLoader__.load({
|
|
|
2695
3128
|
return normalizeTags(tags) ?? [];
|
|
2696
3129
|
}
|
|
2697
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
|
|
2698
3291
|
//#region src/client/board/parse-model-pref.ts
|
|
2699
3292
|
/**
|
|
2700
3293
|
* The model the user last picked for AI parsing, remembered per browser so the
|
|
@@ -2726,6 +3319,35 @@ window.__ModuleLoader__.load({
|
|
|
2726
3319
|
} catch {}
|
|
2727
3320
|
}
|
|
2728
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
|
|
2729
3351
|
//#region src/client/board/NewTaskModal.tsx
|
|
2730
3352
|
/**
|
|
2731
3353
|
* New-task modal: title + description + the prompt that execution will send.
|
|
@@ -2743,8 +3365,10 @@ window.__ModuleLoader__.load({
|
|
|
2743
3365
|
const [model, setModel] = (0, react.useState)(initialTask?.model ?? parentTask?.model ?? "");
|
|
2744
3366
|
const inheritedPermission = parentTask === void 0 ? void 0 : effectiveTaskPermission(parentTask);
|
|
2745
3367
|
const [reuseSession, setReuseSession] = (0, react.useState)(initialTask?.reuseSession ?? false);
|
|
3368
|
+
const [goalRun, setGoalRun] = (0, react.useState)(initialTask?.goalRun ?? true);
|
|
2746
3369
|
const [scheduleEnabled, setScheduleEnabled] = (0, react.useState)(initialTask?.schedule?.enabled ?? false);
|
|
2747
3370
|
const [scheduleCron, setScheduleCron] = (0, react.useState)(initialTask?.schedule?.cron ?? "");
|
|
3371
|
+
const [scheduleZone, setScheduleZone] = (0, react.useState)(initialTask?.schedule?.timeZone ?? "");
|
|
2748
3372
|
const [scheduleError, setScheduleError] = (0, react.useState)(void 0);
|
|
2749
3373
|
const [freezeText, setFreezeText] = (0, react.useState)("");
|
|
2750
3374
|
const [freezeError, setFreezeError] = (0, react.useState)(void 0);
|
|
@@ -2843,10 +3467,12 @@ window.__ModuleLoader__.load({
|
|
|
2843
3467
|
permission: permission === "" ? void 0 : permission,
|
|
2844
3468
|
model: model === "" ? void 0 : model,
|
|
2845
3469
|
...reuseSession ? { reuseSession: true } : {},
|
|
3470
|
+
...goalRun ? {} : { goalRun: false },
|
|
2846
3471
|
...tagList.length > 0 ? { tags: tagList } : {},
|
|
2847
3472
|
schedule: scheduleEnabled ? {
|
|
2848
3473
|
enabled: true,
|
|
2849
|
-
cron: scheduleCron.trim()
|
|
3474
|
+
cron: scheduleCron.trim(),
|
|
3475
|
+
...scheduleZone === "" ? {} : { timeZone: scheduleZone }
|
|
2850
3476
|
} : void 0
|
|
2851
3477
|
});
|
|
2852
3478
|
if (task === void 0) {
|
|
@@ -2861,9 +3487,30 @@ window.__ModuleLoader__.load({
|
|
|
2861
3487
|
}
|
|
2862
3488
|
onClose();
|
|
2863
3489
|
};
|
|
2864
|
-
|
|
2865
|
-
const
|
|
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;
|
|
2866
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");
|
|
2867
3514
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ModalShell, {
|
|
2868
3515
|
ariaLabel: modalTitle,
|
|
2869
3516
|
title: modalTitle,
|
|
@@ -2881,68 +3528,73 @@ window.__ModuleLoader__.load({
|
|
|
2881
3528
|
}
|
|
2882
3529
|
},
|
|
2883
3530
|
children: [
|
|
2884
|
-
canParse && /* @__PURE__ */ (0, react_jsx_runtime.
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
}),
|
|
2907
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2908
|
-
className: board_module_css_default.aiParseRow,
|
|
2909
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
2910
|
-
className: board_module_css_default.select,
|
|
2911
|
-
value: parseModel,
|
|
2912
|
-
"aria-label": t("new.aiParseModel"),
|
|
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,
|
|
2913
3553
|
onChange: (event) => {
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
}
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
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
|
+
})
|
|
2946
3598
|
}),
|
|
2947
3599
|
parentTask !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2948
3600
|
className: board_module_css_default.detailText,
|
|
@@ -2955,409 +3607,333 @@ window.__ModuleLoader__.load({
|
|
|
2955
3607
|
t("new.subtaskInherit")
|
|
2956
3608
|
]
|
|
2957
3609
|
}),
|
|
2958
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2959
|
-
title,
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
className: board_module_css_default.input,
|
|
2981
|
-
rows: 4,
|
|
2982
|
-
value: freezeText,
|
|
2983
|
-
placeholder: t("new.freezePlaceholder"),
|
|
2984
|
-
spellCheck: false,
|
|
2985
|
-
onChange: (event) => {
|
|
2986
|
-
setFreezeText(event.target.value);
|
|
2987
|
-
setFreezeError(void 0);
|
|
2988
|
-
}
|
|
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);
|
|
3620
|
+
},
|
|
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") })]
|
|
2989
3632
|
})]
|
|
2990
3633
|
}),
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
children: t("new.handover")
|
|
3000
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
3001
|
-
className: board_module_css_default.input,
|
|
3002
|
-
rows: 3,
|
|
3003
|
-
value: handoverText,
|
|
3004
|
-
placeholder: t("new.handoverPlaceholder"),
|
|
3005
|
-
spellCheck: false,
|
|
3006
|
-
onChange: (event) => {
|
|
3007
|
-
setHandoverText(event.target.value);
|
|
3008
|
-
}
|
|
3009
|
-
})]
|
|
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
|
+
})
|
|
3010
3642
|
}),
|
|
3011
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
children: [
|
|
3023
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3024
|
-
value: "",
|
|
3025
|
-
children: t("exec.workspace.recent")
|
|
3026
|
-
}),
|
|
3027
|
-
!workspaceKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
3643
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
|
|
3644
|
+
title: t("new.section.execution"),
|
|
3645
|
+
summary: executionSummary,
|
|
3646
|
+
children: [
|
|
3647
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
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,
|
|
3028
3654
|
value: workspaceId,
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
3051
|
-
|
|
3052
|
-
children: t("
|
|
3053
|
-
}),
|
|
3054
|
-
|
|
3655
|
+
onChange: (event) => {
|
|
3656
|
+
setWorkspaceId(event.target.value);
|
|
3657
|
+
},
|
|
3658
|
+
children: [
|
|
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))
|
|
3671
|
+
]
|
|
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,
|
|
3055
3681
|
value: mode,
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
value: preset.id,
|
|
3060
|
-
disabled: preset.broken !== void 0,
|
|
3682
|
+
onChange: (event) => {
|
|
3683
|
+
setMode(event.target.value);
|
|
3684
|
+
},
|
|
3061
3685
|
children: [
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
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
|
+
})
|
|
3065
3718
|
]
|
|
3066
|
-
}
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
},
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
3096
|
-
className: board_module_css_default.select,
|
|
3097
|
-
value: model,
|
|
3098
|
-
onChange: (event) => {
|
|
3099
|
-
setModel(event.target.value);
|
|
3100
|
-
},
|
|
3101
|
-
children: [
|
|
3102
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3103
|
-
value: "",
|
|
3104
|
-
children: t("exec.model.default")
|
|
3105
|
-
}),
|
|
3106
|
-
!modelKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
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,
|
|
3107
3748
|
value: model,
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
className: board_module_css_default.detailText,
|
|
3129
|
-
children: t("exec.reuseSessionHint")
|
|
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
|
+
]
|
|
3130
3769
|
}),
|
|
3131
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
3132
|
-
|
|
3770
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
|
|
3771
|
+
title: t("new.section.run"),
|
|
3772
|
+
summary: runSummary,
|
|
3133
3773
|
children: [
|
|
3134
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.schedule") }),
|
|
3135
3774
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3136
3775
|
className: board_module_css_default.scheduleToggle,
|
|
3137
3776
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3138
3777
|
type: "checkbox",
|
|
3139
|
-
checked:
|
|
3778
|
+
checked: reuseSession,
|
|
3140
3779
|
onChange: (event) => {
|
|
3141
|
-
|
|
3142
|
-
if (!event.target.checked) setScheduleError(void 0);
|
|
3780
|
+
setReuseSession(event.target.checked);
|
|
3143
3781
|
}
|
|
3144
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("
|
|
3782
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("exec.reuseSession") })]
|
|
3145
3783
|
}),
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
if (event.target.value === "") return;
|
|
3165
|
-
setScheduleCron(event.target.value);
|
|
3166
|
-
setScheduleError(void 0);
|
|
3167
|
-
},
|
|
3168
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
3169
|
-
value: "",
|
|
3170
|
-
children: [t("detail.schedule.presets"), "…"]
|
|
3171
|
-
}), SCHEDULE_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3172
|
-
value: preset.cron,
|
|
3173
|
-
children: t(preset.label)
|
|
3174
|
-
}, preset.cron))]
|
|
3175
|
-
})]
|
|
3176
|
-
}),
|
|
3177
|
-
scheduleError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3178
|
-
className: board_module_css_default.formError,
|
|
3179
|
-
children: scheduleError
|
|
3180
|
-
}),
|
|
3181
|
-
scheduleError === void 0 && scheduleNextRun !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
3182
|
-
className: board_module_css_default.scheduleMeta,
|
|
3183
|
-
children: [
|
|
3184
|
-
t("detail.schedule.nextRun"),
|
|
3185
|
-
" ",
|
|
3186
|
-
new Date(scheduleNextRun).toLocaleString()
|
|
3187
|
-
]
|
|
3188
|
-
})
|
|
3189
|
-
] })
|
|
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
|
+
})
|
|
3190
3802
|
]
|
|
3191
3803
|
}),
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
type: "checkbox",
|
|
3197
|
-
checked: archiveOriginal,
|
|
3198
|
-
onChange: (event) => {
|
|
3199
|
-
setArchiveOriginal(event.target.checked);
|
|
3200
|
-
}
|
|
3201
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("new.archiveOriginal") })]
|
|
3202
|
-
})
|
|
3203
|
-
]
|
|
3204
|
-
});
|
|
3205
|
-
}
|
|
3206
|
-
//#endregion
|
|
3207
|
-
//#region src/client/board/status-key.ts
|
|
3208
|
-
/** Task status → locale key (board column titles and the detail badge). */
|
|
3209
|
-
const STATUS_KEY = {
|
|
3210
|
-
backlog: "board.status.backlog",
|
|
3211
|
-
todo: "board.status.todo",
|
|
3212
|
-
running: "board.status.running",
|
|
3213
|
-
done: "board.status.done",
|
|
3214
|
-
failed: "board.status.failed"
|
|
3215
|
-
};
|
|
3216
|
-
//#endregion
|
|
3217
|
-
//#region src/client/board/TaskCard.tsx
|
|
3218
|
-
/**
|
|
3219
|
-
* Task card: the board's column item. Clicking opens the task detail — it
|
|
3220
|
-
* never executes anything directly (detail holds the Run button).
|
|
3221
|
-
*
|
|
3222
|
-
* Memoized: the card re-renders only when its own task record changes, so a
|
|
3223
|
-
* status/filter update on one card (or scrolling) never re-renders every
|
|
3224
|
-
* card on the board. The per-card onClick is built with a stable task reference
|
|
3225
|
-
* by the board, so the memo boundary is effective.
|
|
3226
|
-
*/
|
|
3227
|
-
/** Compact relative/absolute time label. */
|
|
3228
|
-
function formatHostTimestamp(ms, timeZone) {
|
|
3229
|
-
try {
|
|
3230
|
-
return new Intl.DateTimeFormat(void 0, {
|
|
3231
|
-
dateStyle: "medium",
|
|
3232
|
-
timeStyle: "medium",
|
|
3233
|
-
...timeZone === void 0 ? {} : { timeZone }
|
|
3234
|
-
}).format(new Date(ms));
|
|
3235
|
-
} catch {
|
|
3236
|
-
return new Date(ms).toISOString();
|
|
3237
|
-
}
|
|
3238
|
-
}
|
|
3239
|
-
function formatTime(ms, timeZone) {
|
|
3240
|
-
const date = new Date(ms);
|
|
3241
|
-
const minutes = Math.floor((Date.now() - ms) / 6e4);
|
|
3242
|
-
if (minutes < 1) return t("time.justNow");
|
|
3243
|
-
if (minutes < 60) return `${minutes}m`;
|
|
3244
|
-
if (minutes < 1440) return `${Math.floor(minutes / 60)}h`;
|
|
3245
|
-
if (timeZone !== void 0) return formatHostTimestamp(ms, timeZone);
|
|
3246
|
-
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
3247
|
-
}
|
|
3248
|
-
function TaskCardInner({ task, pending, timeZone, onClick, subtaskCount = 0, isSubtask = false, subtasksDone = 0, subtasksRunning = 0, subtasksFailed = 0 }) {
|
|
3249
|
-
const latest = task.executions[task.executions.length - 1];
|
|
3250
|
-
const runs = task.executions.length;
|
|
3251
|
-
const archived = task.archivedAt !== void 0;
|
|
3252
|
-
const isDraggable = !archived && task.status !== "running" && !pending;
|
|
3253
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3254
|
-
type: "button",
|
|
3255
|
-
className: board_module_css_default.card,
|
|
3256
|
-
"data-status": archived ? "archived" : task.status,
|
|
3257
|
-
"data-dsh-part": "card",
|
|
3258
|
-
"data-pending": pending || void 0,
|
|
3259
|
-
draggable: isDraggable,
|
|
3260
|
-
onDragStart: isDraggable ? (event) => {
|
|
3261
|
-
event.dataTransfer.setData("text/plain", task.id);
|
|
3262
|
-
event.dataTransfer.effectAllowed = "move";
|
|
3263
|
-
} : void 0,
|
|
3264
|
-
onClick,
|
|
3265
|
-
title: task.description !== "" ? task.description : task.title,
|
|
3266
|
-
children: [
|
|
3267
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3268
|
-
className: board_module_css_default.cardTitle,
|
|
3269
|
-
children: task.title
|
|
3270
|
-
}),
|
|
3271
|
-
task.tags !== void 0 && task.tags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3272
|
-
className: board_module_css_default.cardTags,
|
|
3273
|
-
children: task.tags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3274
|
-
className: board_module_css_default.cardTag,
|
|
3275
|
-
"data-tag-tone": tagTone(tag.name),
|
|
3276
|
-
"data-dsh-part": "tag-badge",
|
|
3277
|
-
"data-tag-hint": tag.promptPrefix === void 0 ? void 0 : tag.promptPrefix,
|
|
3278
|
-
title: tag.promptPrefix === void 0 ? tag.name : tag.promptPrefix,
|
|
3279
|
-
children: tag.name
|
|
3280
|
-
}, tag.name))
|
|
3281
|
-
}),
|
|
3282
|
-
task.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3283
|
-
className: board_module_css_default.cardExcerpt,
|
|
3284
|
-
children: task.description
|
|
3285
|
-
}),
|
|
3286
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3287
|
-
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,
|
|
3288
3808
|
children: [
|
|
3289
|
-
|
|
3290
|
-
className: board_module_css_default.
|
|
3291
|
-
|
|
3292
|
-
|
|
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
|
+
})]
|
|
3293
3825
|
}),
|
|
3294
|
-
|
|
3295
|
-
className: board_module_css_default.
|
|
3296
|
-
|
|
3297
|
-
"data-tone": subtasksFailed > 0 ? "failed" : subtasksRunning > 0 ? "running" : subtasksDone === subtaskCount ? "done" : void 0,
|
|
3298
|
-
title: t("card.subtasksBreakdown", {
|
|
3299
|
-
total: String(subtaskCount),
|
|
3300
|
-
done: String(subtasksDone),
|
|
3301
|
-
running: String(subtasksRunning),
|
|
3302
|
-
failed: String(subtasksFailed)
|
|
3303
|
-
}),
|
|
3304
|
-
children: [
|
|
3305
|
-
t("card.subtasks", { count: String(subtaskCount) }),
|
|
3306
|
-
subtasksFailed > 0 ? " · " + t("card.subtasksFailed", { count: String(subtasksFailed) }) : "",
|
|
3307
|
-
subtasksFailed === 0 && subtasksRunning > 0 ? " · " + t("card.subtasksRunning", { count: String(subtasksRunning) }) : ""
|
|
3308
|
-
]
|
|
3826
|
+
freezeError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3827
|
+
className: board_module_css_default.formError,
|
|
3828
|
+
children: freezeError
|
|
3309
3829
|
}),
|
|
3310
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
3311
|
-
className: board_module_css_default.
|
|
3312
|
-
children: [
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
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
|
+
})]
|
|
3317
3892
|
}),
|
|
3318
|
-
|
|
3319
|
-
className: board_module_css_default.
|
|
3320
|
-
|
|
3321
|
-
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
|
|
3322
3896
|
}),
|
|
3323
|
-
|
|
3324
|
-
className: board_module_css_default.
|
|
3325
|
-
|
|
3326
|
-
|
|
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
|
+
})]
|
|
3327
3913
|
}),
|
|
3328
|
-
|
|
3329
|
-
className: board_module_css_default.
|
|
3330
|
-
"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,
|
|
3331
3916
|
children: [
|
|
3332
|
-
|
|
3917
|
+
t("detail.schedule.nextRun"),
|
|
3333
3918
|
" ",
|
|
3334
|
-
|
|
3919
|
+
nextRunLabel(scheduleNextRun, scheduleTimeZone, formatHostTimestamp, Date.now())
|
|
3335
3920
|
]
|
|
3336
|
-
}),
|
|
3337
|
-
latest?.sessionId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3338
|
-
className: board_module_css_default.cardSession,
|
|
3339
|
-
title: latest.sessionId,
|
|
3340
|
-
children: "⌁"
|
|
3341
|
-
}),
|
|
3342
|
-
!archived && (task.status === "running" || pending) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3343
|
-
className: board_module_css_default.cardSpinner,
|
|
3344
|
-
"aria-hidden": "true"
|
|
3345
3921
|
})
|
|
3346
|
-
]
|
|
3347
|
-
}),
|
|
3348
|
-
!archived && pending && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3349
|
-
className: board_module_css_default.cardRunningLabel,
|
|
3350
|
-
children: [t("board.pending"), "…"]
|
|
3351
|
-
}),
|
|
3352
|
-
!archived && latest !== void 0 && executionLabel(latest) === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3353
|
-
className: board_module_css_default.cardRunningLabel,
|
|
3354
|
-
children: [latest.ownResult === void 0 ? t("detail.result.running") : t("detail.subtasks.waiting"), "…"]
|
|
3922
|
+
] })]
|
|
3355
3923
|
})
|
|
3356
3924
|
]
|
|
3357
3925
|
});
|
|
3358
3926
|
}
|
|
3359
|
-
|
|
3360
|
-
|
|
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
|
+
};
|
|
3361
3937
|
//#endregion
|
|
3362
3938
|
//#region src/client/board/ConfirmDialog.tsx
|
|
3363
3939
|
/**
|
|
@@ -3512,7 +4088,15 @@ window.__ModuleLoader__.load({
|
|
|
3512
4088
|
const [error, setError] = (0, react.useState)(void 0);
|
|
3513
4089
|
const snapshot = controller.getSnapshot();
|
|
3514
4090
|
const limit = snapshot.host?.maxSubtaskDepth ?? 1;
|
|
3515
|
-
const
|
|
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
|
+
]);
|
|
3516
4100
|
const link = async (taskId) => {
|
|
3517
4101
|
setPending(true);
|
|
3518
4102
|
setError(void 0);
|
|
@@ -3644,15 +4228,8 @@ window.__ModuleLoader__.load({
|
|
|
3644
4228
|
});
|
|
3645
4229
|
}
|
|
3646
4230
|
/** The execution-target editor: workspace / mode / permission pickers. */
|
|
3647
|
-
function ExecutionSettingsSection({ controller, task, pending }) {
|
|
3648
|
-
const
|
|
3649
|
-
const teamRunOffered = () => controller.getSnapshot().host?.teamRunAvailable === true;
|
|
3650
|
-
const [teamRunAvailable, setTeamRunAvailable] = (0, react.useState)(teamRunOffered());
|
|
3651
|
-
(0, react.useEffect)(() => controller.subscribe(() => {
|
|
3652
|
-
const snapshot = controller.getSnapshot();
|
|
3653
|
-
setOptions(snapshot.executionOptions);
|
|
3654
|
-
setTeamRunAvailable(snapshot.host?.teamRunAvailable === true);
|
|
3655
|
-
}), [controller]);
|
|
4231
|
+
function ExecutionSettingsSection({ controller, task, pending, executionOptions, teamRunAvailable }) {
|
|
4232
|
+
const options = executionOptions;
|
|
3656
4233
|
const workspaceId = task.workspaceId ?? "";
|
|
3657
4234
|
const mode = task.mode ?? "";
|
|
3658
4235
|
const permission = task.permission ?? "";
|
|
@@ -3700,7 +4277,7 @@ window.__ModuleLoader__.load({
|
|
|
3700
4277
|
className: board_module_css_default.field,
|
|
3701
4278
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3702
4279
|
className: board_module_css_default.fieldLabel,
|
|
3703
|
-
children: t("new.
|
|
4280
|
+
children: t("new.agentPreset")
|
|
3704
4281
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
3705
4282
|
className: board_module_css_default.select,
|
|
3706
4283
|
value: mode,
|
|
@@ -3711,7 +4288,7 @@ window.__ModuleLoader__.load({
|
|
|
3711
4288
|
children: [
|
|
3712
4289
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3713
4290
|
value: "",
|
|
3714
|
-
children:
|
|
4291
|
+
children: inheritPresetLabel(options.presets)
|
|
3715
4292
|
}),
|
|
3716
4293
|
!modeKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
3717
4294
|
value: mode,
|
|
@@ -3721,7 +4298,7 @@ window.__ModuleLoader__.load({
|
|
|
3721
4298
|
value: preset.id,
|
|
3722
4299
|
disabled: preset.broken !== void 0,
|
|
3723
4300
|
children: [
|
|
3724
|
-
preset
|
|
4301
|
+
presetLabel(preset),
|
|
3725
4302
|
preset.isDefault ? t("exec.mode.defaultSuffix") : "",
|
|
3726
4303
|
preset.broken !== void 0 ? t("exec.mode.brokenSuffix") : ""
|
|
3727
4304
|
]
|
|
@@ -3793,6 +4370,21 @@ window.__ModuleLoader__.load({
|
|
|
3793
4370
|
className: board_module_css_default.detailText,
|
|
3794
4371
|
children: t("exec.reuseSessionHint")
|
|
3795
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
|
+
}),
|
|
3796
4388
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3797
4389
|
className: board_module_css_default.scheduleToggle,
|
|
3798
4390
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -3811,7 +4403,7 @@ window.__ModuleLoader__.load({
|
|
|
3811
4403
|
]
|
|
3812
4404
|
});
|
|
3813
4405
|
}
|
|
3814
|
-
/** 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. */
|
|
3815
4407
|
function ScheduleSection({ controller, task, pending }) {
|
|
3816
4408
|
const schedule = task.schedule;
|
|
3817
4409
|
const [cron, setCron] = (0, react.useState)(schedule?.cron ?? "0 9 * * *");
|
|
@@ -3819,17 +4411,21 @@ window.__ModuleLoader__.load({
|
|
|
3819
4411
|
const [nextRunAt, setNextRunAt] = (0, react.useState)(schedule?.nextRunAt);
|
|
3820
4412
|
const [lastTriggeredAt, setLastTriggeredAt] = (0, react.useState)(schedule?.lastTriggeredAt);
|
|
3821
4413
|
const [error, setError] = (0, react.useState)(void 0);
|
|
3822
|
-
const
|
|
4414
|
+
const hostTimeZone = controller.getSnapshot().host?.scheduler.timeZone;
|
|
4415
|
+
const [zone, setZone] = (0, react.useState)(schedule?.timeZone ?? "");
|
|
4416
|
+
const now = Date.now();
|
|
3823
4417
|
(0, react.useEffect)(() => {
|
|
3824
4418
|
setCron(schedule?.cron ?? "0 9 * * *");
|
|
3825
4419
|
setEnabled(schedule?.enabled ?? false);
|
|
3826
4420
|
setNextRunAt(schedule?.nextRunAt);
|
|
3827
4421
|
setLastTriggeredAt(schedule?.lastTriggeredAt);
|
|
4422
|
+
setZone(schedule?.timeZone ?? "");
|
|
3828
4423
|
setError(void 0);
|
|
3829
4424
|
}, [
|
|
3830
4425
|
task.id,
|
|
3831
4426
|
schedule?.enabled,
|
|
3832
4427
|
schedule?.cron,
|
|
4428
|
+
schedule?.timeZone,
|
|
3833
4429
|
schedule?.nextRunAt,
|
|
3834
4430
|
schedule?.lastTriggeredAt
|
|
3835
4431
|
]);
|
|
@@ -3844,6 +4440,15 @@ window.__ModuleLoader__.load({
|
|
|
3844
4440
|
setError(void 0);
|
|
3845
4441
|
controller.setSchedule(task.id, { cron: trimmed });
|
|
3846
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
|
+
};
|
|
3847
4452
|
/** Arm/disarm the schedule (arming first persists the edited cron). */
|
|
3848
4453
|
const toggleEnabled = (next) => {
|
|
3849
4454
|
const trimmed = cron.trim();
|
|
@@ -3863,8 +4468,10 @@ window.__ModuleLoader__.load({
|
|
|
3863
4468
|
setError(void 0);
|
|
3864
4469
|
controller.setSchedule(task.id, { cron: preset });
|
|
3865
4470
|
};
|
|
3866
|
-
const
|
|
3867
|
-
const
|
|
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);
|
|
3868
4475
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3869
4476
|
className: board_module_css_default.detailSection,
|
|
3870
4477
|
children: [
|
|
@@ -3916,6 +4523,23 @@ window.__ModuleLoader__.load({
|
|
|
3916
4523
|
}, preset.cron))]
|
|
3917
4524
|
})]
|
|
3918
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
|
+
}),
|
|
3919
4543
|
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3920
4544
|
className: board_module_css_default.formError,
|
|
3921
4545
|
children: error
|
|
@@ -3940,9 +4564,7 @@ window.__ModuleLoader__.load({
|
|
|
3940
4564
|
* and the actions that grow or prune the tree. Every gate here mirrors the
|
|
3941
4565
|
* Host lineage gate for affordance only; the Host re-checks the action.
|
|
3942
4566
|
*/
|
|
3943
|
-
function SubtaskSection({ controller, task, pending, archived }) {
|
|
3944
|
-
const [snapshot, setSnapshot] = (0, react.useState)(controller.getSnapshot());
|
|
3945
|
-
(0, react.useEffect)(() => controller.subscribe(() => setSnapshot(controller.getSnapshot())), [controller]);
|
|
4567
|
+
function SubtaskSection({ controller, task, pending, archived, snapshot }) {
|
|
3946
4568
|
const [showAdd, setShowAdd] = (0, react.useState)(false);
|
|
3947
4569
|
const [showLink, setShowLink] = (0, react.useState)(false);
|
|
3948
4570
|
const tasks = snapshot.tasks;
|
|
@@ -4141,7 +4763,8 @@ window.__ModuleLoader__.load({
|
|
|
4141
4763
|
controller,
|
|
4142
4764
|
task: current,
|
|
4143
4765
|
pending,
|
|
4144
|
-
archived
|
|
4766
|
+
archived,
|
|
4767
|
+
snapshot
|
|
4145
4768
|
}),
|
|
4146
4769
|
current.tags !== void 0 && current.tags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4147
4770
|
className: board_module_css_default.detailSection,
|
|
@@ -4213,9 +4836,9 @@ window.__ModuleLoader__.load({
|
|
|
4213
4836
|
": ",
|
|
4214
4837
|
current.handover.workspaceId ?? t("exec.workspace.recent"),
|
|
4215
4838
|
" · ",
|
|
4216
|
-
t("new.
|
|
4839
|
+
t("new.agentPreset"),
|
|
4217
4840
|
": ",
|
|
4218
|
-
current.handover.mode ?? t("exec.mode.
|
|
4841
|
+
current.handover.mode ?? t("exec.mode.inherit"),
|
|
4219
4842
|
" · ",
|
|
4220
4843
|
t("new.permission"),
|
|
4221
4844
|
": ",
|
|
@@ -4269,7 +4892,9 @@ window.__ModuleLoader__.load({
|
|
|
4269
4892
|
!archived && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExecutionSettingsSection, {
|
|
4270
4893
|
controller,
|
|
4271
4894
|
task: current,
|
|
4272
|
-
pending
|
|
4895
|
+
pending,
|
|
4896
|
+
executionOptions: snapshot.executionOptions,
|
|
4897
|
+
teamRunAvailable: snapshot.host?.teamRunAvailable === true
|
|
4273
4898
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScheduleSection, {
|
|
4274
4899
|
controller,
|
|
4275
4900
|
task: current,
|
|
@@ -4842,390 +5467,89 @@ window.__ModuleLoader__.load({
|
|
|
4842
5467
|
});
|
|
4843
5468
|
}
|
|
4844
5469
|
//#endregion
|
|
4845
|
-
//#region src/client/
|
|
4846
|
-
/**
|
|
4847
|
-
const
|
|
4848
|
-
const INVALIDATION_ONLY = Symbol.for("dsh-web.body-mutation-invalidation");
|
|
4849
|
-
function needsRecords(subscribers) {
|
|
4850
|
-
for (const listener of subscribers) if (!listener[INVALIDATION_ONLY]) return true;
|
|
4851
|
-
return false;
|
|
4852
|
-
}
|
|
4853
|
-
/**
|
|
4854
|
-
* Subscribe to a coalesced DOM re-check without retaining mutation records.
|
|
4855
|
-
* The marked wrapper also works with an older hub, which delivers records
|
|
4856
|
-
* that it simply ignores until a page reload picks up the updated hub.
|
|
4857
|
-
*/
|
|
4858
|
-
function subscribeBodyInvalidations(subscriber) {
|
|
4859
|
-
const listener = () => {
|
|
4860
|
-
subscriber();
|
|
4861
|
-
};
|
|
4862
|
-
listener[INVALIDATION_ONLY] = true;
|
|
4863
|
-
return subscribeBodyMutations(listener);
|
|
4864
|
-
}
|
|
4865
|
-
/**
|
|
4866
|
-
* Subscribe to body-level childList mutations.
|
|
4867
|
-
* @param subscriber - called at most once per animation frame with the records
|
|
4868
|
-
* collected since the previous flush; must be safe to run repeatedly.
|
|
4869
|
-
* @returns the disposer removing this subscriber (and the observer when it was
|
|
4870
|
-
* the last one).
|
|
4871
|
-
*/
|
|
4872
|
-
function subscribeBodyMutations(subscriber) {
|
|
4873
|
-
if (typeof globalThis === "undefined" || typeof document === "undefined") return () => {};
|
|
4874
|
-
if (typeof MutationObserver !== "function") return () => {};
|
|
4875
|
-
const registry = globalThis;
|
|
4876
|
-
let hub = registry[HUB_KEY];
|
|
4877
|
-
if (hub === void 0) {
|
|
4878
|
-
const subscribers = /* @__PURE__ */ new Set();
|
|
4879
|
-
const created = {
|
|
4880
|
-
observer: void 0,
|
|
4881
|
-
subscribers,
|
|
4882
|
-
pending: [],
|
|
4883
|
-
scheduled: false
|
|
4884
|
-
};
|
|
4885
|
-
const flush = () => {
|
|
4886
|
-
created.frame = void 0;
|
|
4887
|
-
created.scheduled = false;
|
|
4888
|
-
const batch = created.pending;
|
|
4889
|
-
created.pending = [];
|
|
4890
|
-
for (const listener of [...subscribers]) {
|
|
4891
|
-
if (!subscribers.has(listener)) continue;
|
|
4892
|
-
try {
|
|
4893
|
-
listener(batch);
|
|
4894
|
-
} catch {}
|
|
4895
|
-
}
|
|
4896
|
-
};
|
|
4897
|
-
const schedule = () => {
|
|
4898
|
-
if (created.scheduled) return;
|
|
4899
|
-
created.scheduled = true;
|
|
4900
|
-
if (typeof requestAnimationFrame === "function") created.frame = requestAnimationFrame(flush);
|
|
4901
|
-
else flush();
|
|
4902
|
-
};
|
|
4903
|
-
created.observer = new MutationObserver((records) => {
|
|
4904
|
-
if (needsRecords(subscribers)) for (const record of records) created.pending.push(record);
|
|
4905
|
-
schedule();
|
|
4906
|
-
});
|
|
4907
|
-
created.observer.observe(document.body ?? document.documentElement, {
|
|
4908
|
-
childList: true,
|
|
4909
|
-
subtree: true
|
|
4910
|
-
});
|
|
4911
|
-
registry[HUB_KEY] = created;
|
|
4912
|
-
hub = created;
|
|
4913
|
-
}
|
|
4914
|
-
const active = hub;
|
|
4915
|
-
active.subscribers.add(subscriber);
|
|
4916
|
-
let subscribed = true;
|
|
4917
|
-
return () => {
|
|
4918
|
-
if (!subscribed) return;
|
|
4919
|
-
subscribed = false;
|
|
4920
|
-
active.subscribers.delete(subscriber);
|
|
4921
|
-
if (!needsRecords(active.subscribers)) active.pending = [];
|
|
4922
|
-
if (active.subscribers.size === 0 && registry[HUB_KEY] === active) {
|
|
4923
|
-
active.observer.disconnect();
|
|
4924
|
-
if (active.frame !== void 0 && typeof cancelAnimationFrame === "function") cancelAnimationFrame(active.frame);
|
|
4925
|
-
active.frame = void 0;
|
|
4926
|
-
active.pending = [];
|
|
4927
|
-
active.scheduled = false;
|
|
4928
|
-
delete registry[HUB_KEY];
|
|
4929
|
-
}
|
|
4930
|
-
};
|
|
4931
|
-
}
|
|
4932
|
-
//#endregion
|
|
4933
|
-
//#region src/client/panel-mount-core.ts
|
|
5470
|
+
//#region src/client/native-panel.tsx
|
|
5471
|
+
/** Row order among the shell's global panel rows (Plugins is 0, Schedule 10). */
|
|
5472
|
+
const PANEL_ORDER = 20;
|
|
4934
5473
|
/**
|
|
4935
|
-
*
|
|
4936
|
-
*
|
|
4937
|
-
*
|
|
4938
|
-
* plugins cannot declare slots, so a family panel takes over the center
|
|
4939
|
-
* column at the DOM level: a container is appended inside the center column
|
|
4940
|
-
* (`[class*="centerCol"]`, the 0.1.0-rc.6+ AppFrame layout; previously
|
|
4941
|
-
* `[data-pane="conversation"]` on older shells — the mount selector keeps
|
|
4942
|
-
* both, ssh #243 / task-board #107) as an extra trailing child React never
|
|
4943
|
-
* manages, and a stylesheet rule hides the conversation content while the
|
|
4944
|
-
* panel is active. Toggling is a data attribute on <html> — no React
|
|
4945
|
-
* involvement, so the conversation subtree underneath stays mounted and
|
|
4946
|
-
* stateful.
|
|
5474
|
+
* The sidebar row glyph the shell asks for at its own size and active state.
|
|
5475
|
+
* The shell owns the button, label, tooltip and rail geometry; this component
|
|
5476
|
+
* draws only the glyph, like every other panel row.
|
|
4947
5477
|
*
|
|
4948
|
-
*
|
|
4949
|
-
*
|
|
4950
|
-
*
|
|
4951
|
-
*
|
|
4952
|
-
*
|
|
5478
|
+
* The glyph carries `data-dsh-panel-entry` because it is the only DOM the
|
|
5479
|
+
* panel's own code owns inside that shell-owned row: the L2 contract (skins)
|
|
5480
|
+
* resolves which row belongs to which plugin through it, since the shell
|
|
5481
|
+
* stamps no per-entry hook of its own (see contracts/semantic-attrs-v1.md).
|
|
5482
|
+
* @param props - the shell's icon share: square edge and selection state.
|
|
5483
|
+
* @returns the decorative board glyph.
|
|
4953
5484
|
*/
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
if (root !== void 0) options.render(root);
|
|
4974
|
-
});
|
|
4975
|
-
} catch {}
|
|
4976
|
-
const ensure = () => {
|
|
4977
|
-
if (container !== void 0 && !container.isConnected) {
|
|
4978
|
-
root?.unmount();
|
|
4979
|
-
root = void 0;
|
|
4980
|
-
container.remove();
|
|
4981
|
-
container = void 0;
|
|
4982
|
-
}
|
|
4983
|
-
if (container === void 0) {
|
|
4984
|
-
const column = conversationColumn();
|
|
4985
|
-
if (column === void 0) return;
|
|
4986
|
-
container = document.createElement("div");
|
|
4987
|
-
container.dataset[options.viewDatasetKey] = "";
|
|
4988
|
-
container.dataset.dshPlugin = options.pluginName;
|
|
4989
|
-
container.className = options.viewClassName;
|
|
4990
|
-
column.appendChild(container);
|
|
4991
|
-
}
|
|
4992
|
-
if (root !== void 0 || !options.isOpen()) return;
|
|
4993
|
-
root = (0, react_dom_client.createRoot)(container);
|
|
4994
|
-
options.render(root);
|
|
4995
|
-
};
|
|
4996
|
-
const unsubscribeBody = subscribeBodyInvalidations(() => {
|
|
4997
|
-
ensure();
|
|
5485
|
+
function TaskBoardPanelIcon({ size }) {
|
|
5486
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
5487
|
+
"data-dsh-panel-entry": TASK_BOARD_PANEL_ID,
|
|
5488
|
+
viewBox: "0 0 16 16",
|
|
5489
|
+
width: size,
|
|
5490
|
+
height: size,
|
|
5491
|
+
fill: "none",
|
|
5492
|
+
stroke: "currentColor",
|
|
5493
|
+
strokeWidth: "1.3",
|
|
5494
|
+
strokeLinecap: "round",
|
|
5495
|
+
strokeLinejoin: "round",
|
|
5496
|
+
"aria-hidden": "true",
|
|
5497
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
5498
|
+
x: "2",
|
|
5499
|
+
y: "2.5",
|
|
5500
|
+
width: "12",
|
|
5501
|
+
height: "11",
|
|
5502
|
+
rx: "1.5"
|
|
5503
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 6.5h12M6.5 6.5v7" })]
|
|
4998
5504
|
});
|
|
4999
|
-
const applyActive = () => {
|
|
5000
|
-
if (options.isOpen()) {
|
|
5001
|
-
ensure();
|
|
5002
|
-
document.documentElement.removeAttribute(options.siblingActiveAttribute);
|
|
5003
|
-
document.documentElement.setAttribute(options.activeAttribute, "");
|
|
5004
|
-
document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: options.panelName }));
|
|
5005
|
-
} else document.documentElement.removeAttribute(options.activeAttribute);
|
|
5006
|
-
};
|
|
5007
|
-
const onOtherActivate = (event) => {
|
|
5008
|
-
if (event.detail === options.siblingPanelName && options.isOpen()) options.close();
|
|
5009
|
-
};
|
|
5010
|
-
const onClickSidebarRow = (event) => {
|
|
5011
|
-
if (!options.isOpen()) return;
|
|
5012
|
-
const target = event.target;
|
|
5013
|
-
if (target === null) return;
|
|
5014
|
-
if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) options.close();
|
|
5015
|
-
};
|
|
5016
|
-
document.addEventListener("click", onClickSidebarRow, true);
|
|
5017
|
-
document.addEventListener(ACTIVATE_EVENT, onOtherActivate);
|
|
5018
|
-
const unsubscribe = options.subscribe(applyActive);
|
|
5019
|
-
applyActive();
|
|
5020
|
-
ensure();
|
|
5021
|
-
return () => {
|
|
5022
|
-
document.removeEventListener("click", onClickSidebarRow, true);
|
|
5023
|
-
document.removeEventListener(ACTIVATE_EVENT, onOtherActivate);
|
|
5024
|
-
unsubscribeBody();
|
|
5025
|
-
unsubscribe();
|
|
5026
|
-
unsubscribeLocale?.();
|
|
5027
|
-
document.documentElement.removeAttribute(options.activeAttribute);
|
|
5028
|
-
root?.unmount();
|
|
5029
|
-
root = void 0;
|
|
5030
|
-
container?.remove();
|
|
5031
|
-
container = void 0;
|
|
5032
|
-
};
|
|
5033
5505
|
}
|
|
5034
|
-
//#endregion
|
|
5035
|
-
//#region src/client/board-mount.tsx
|
|
5036
5506
|
/**
|
|
5037
|
-
*
|
|
5038
|
-
*
|
|
5039
|
-
*
|
|
5040
|
-
*
|
|
5041
|
-
*
|
|
5042
|
-
* @
|
|
5507
|
+
* The main-slot page. The layout mounts it only while the board is the selected
|
|
5508
|
+
* panel, so the conversation keeps the center column untouched the rest of the
|
|
5509
|
+
* time; the wrapper carries the pinned `data-dsh-taskboard-view` semantic
|
|
5510
|
+
* anchor (L2 contract, skins) and the container query context the board's
|
|
5511
|
+
* responsive rules read.
|
|
5512
|
+
* @param props - the framework main-slot share plus this entry's injected face.
|
|
5513
|
+
* @returns the board page.
|
|
5043
5514
|
*/
|
|
5044
|
-
function
|
|
5045
|
-
return
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
activeAttribute: "data-dsh-taskboard-active",
|
|
5051
|
-
siblingActiveAttribute: "data-dsh-ssh-active",
|
|
5052
|
-
panelName: "taskboard",
|
|
5053
|
-
siblingPanelName: "ssh",
|
|
5054
|
-
isOpen: () => controller.getSnapshot().boardOpen,
|
|
5055
|
-
close: () => controller.closeBoard(),
|
|
5056
|
-
subscribe: (listener) => controller.subscribe(listener),
|
|
5057
|
-
locale
|
|
5515
|
+
function TaskBoardPanel({ controller }) {
|
|
5516
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5517
|
+
className: board_module_css_default.panel,
|
|
5518
|
+
"data-dsh-taskboard-view": "",
|
|
5519
|
+
"data-dsh-plugin": "task-board",
|
|
5520
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskBoard, { controller })
|
|
5058
5521
|
});
|
|
5059
5522
|
}
|
|
5060
|
-
//#endregion
|
|
5061
|
-
//#region src/client/sidebar-entry-core.ts
|
|
5062
5523
|
/**
|
|
5063
|
-
*
|
|
5064
|
-
*
|
|
5065
|
-
* dsh's sidebar shell exposes no slot an external plugin can register into,
|
|
5066
|
-
* so the entry row is injected between the shell's New Session button and the
|
|
5067
|
-
* workspace browser. The injection self-heals: a MutationObserver watches the
|
|
5068
|
-
* sidebar root and re-inserts the row whenever a React re-render displaces it
|
|
5069
|
-
* (re-insertion happens in the same frame, before paint, so no flicker).
|
|
5070
|
-
*
|
|
5071
|
-
* The row is plain DOM (no React tree) so it can never disturb the shell's
|
|
5072
|
-
* reconciliation; the view it toggles is a separate root owned by the caller.
|
|
5524
|
+
* Register the board's sidebar row and center-column page.
|
|
5073
5525
|
*
|
|
5074
|
-
*
|
|
5075
|
-
*
|
|
5526
|
+
* Both seats are declared by shell plugins this package does not depend on at
|
|
5527
|
+
* runtime, so each registration is wrapped in `ctx.slots.inject`: the callback
|
|
5528
|
+
* runs only after the owning entry declares the seat, and a shell that never
|
|
5529
|
+
* declares it leaves the board simply absent instead of failing boot.
|
|
5530
|
+
* @param ctx - client root context (services: slots).
|
|
5531
|
+
* @param controller - the board controller the page and the row drive.
|
|
5532
|
+
* @returns disposer releasing both registrations.
|
|
5076
5533
|
*/
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
const
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
const entry = document.createElement("button");
|
|
5092
|
-
entry.type = "button";
|
|
5093
|
-
entry.setAttribute(options.rowAttribute, "");
|
|
5094
|
-
if (options.plugin !== void 0) {
|
|
5095
|
-
entry.setAttribute("data-dsh-plugin", options.plugin);
|
|
5096
|
-
entry.setAttribute("data-dsh-part", "sidebar-entry");
|
|
5097
|
-
}
|
|
5098
|
-
entry.className = options.css["entry"] ?? "";
|
|
5099
|
-
const labelSpan = document.createElement("span");
|
|
5100
|
-
labelSpan.className = options.css["entryLabel"] ?? "";
|
|
5101
|
-
const iconSpan = document.createElement("span");
|
|
5102
|
-
iconSpan.className = options.css["entryIcon"] ?? "";
|
|
5103
|
-
iconSpan.innerHTML = options.icon;
|
|
5104
|
-
entry.append(iconSpan, labelSpan);
|
|
5105
|
-
const applyLabel = () => {
|
|
5106
|
-
entry.setAttribute("aria-label", options.label());
|
|
5107
|
-
if (options.tooltip !== void 0) entry.setAttribute("title", options.tooltip());
|
|
5108
|
-
labelSpan.textContent = options.label();
|
|
5109
|
-
};
|
|
5110
|
-
applyLabel();
|
|
5111
|
-
entry.addEventListener("click", options.onToggle);
|
|
5112
|
-
return {
|
|
5113
|
-
entry,
|
|
5114
|
-
applyLabel
|
|
5115
|
-
};
|
|
5116
|
-
}
|
|
5117
|
-
/** Re-insert the entry after the New Session row (before the browser region). */
|
|
5118
|
-
function placeEntry(root, entry, options) {
|
|
5119
|
-
const button = newSessionButton(root);
|
|
5120
|
-
if (button === void 0) return false;
|
|
5121
|
-
if (entry.parentElement !== root) {
|
|
5122
|
-
const row = button.closest("[class*=\"logoRow\"]");
|
|
5123
|
-
const base = row !== null && row.parentElement === root ? row : button;
|
|
5124
|
-
const family = Array.from(root.children).filter((el) => el instanceof HTMLElement && el.matches(options.familySelectors.join(", ")));
|
|
5125
|
-
const anchor = options.position === "before" ? family.length > 0 ? family[0] : base.nextElementSibling : family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling;
|
|
5126
|
-
root.insertBefore(entry, anchor);
|
|
5127
|
-
}
|
|
5128
|
-
return true;
|
|
5129
|
-
}
|
|
5130
|
-
/**
|
|
5131
|
-
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
|
5132
|
-
* on later React re-renders.
|
|
5133
|
-
* @param options - the row's attribute/icon/copy/action/ordering configuration.
|
|
5134
|
-
* @returns disposer removing the entry and its observers.
|
|
5135
|
-
*/
|
|
5136
|
-
function mountSidebarEntry$1(options) {
|
|
5137
|
-
if (typeof document !== "undefined" && document.querySelector(options.rowSelector) !== null) return () => {};
|
|
5138
|
-
const { entry, applyLabel } = createEntry(options);
|
|
5139
|
-
let root;
|
|
5140
|
-
let placed = false;
|
|
5141
|
-
let unsubscribeRefresh;
|
|
5142
|
-
if (options.refresh !== void 0) try {
|
|
5143
|
-
unsubscribeRefresh = options.refresh.subscribe(applyLabel);
|
|
5144
|
-
} catch {}
|
|
5145
|
-
const tryPlace = () => {
|
|
5146
|
-
if (root !== void 0 && !root.isConnected) {
|
|
5147
|
-
rootObserver.disconnect();
|
|
5148
|
-
root = void 0;
|
|
5149
|
-
placed = false;
|
|
5150
|
-
}
|
|
5151
|
-
if (placed) {
|
|
5152
|
-
if (document.body.contains(entry)) return;
|
|
5153
|
-
rootObserver.disconnect();
|
|
5154
|
-
root = void 0;
|
|
5155
|
-
placed = false;
|
|
5156
|
-
}
|
|
5157
|
-
root ??= sidebarRoot();
|
|
5158
|
-
if (root === void 0) return;
|
|
5159
|
-
placed = placeEntry(root, entry, options);
|
|
5160
|
-
if (placed) rootObserver.observe(root, {
|
|
5161
|
-
childList: true,
|
|
5162
|
-
subtree: true
|
|
5163
|
-
});
|
|
5164
|
-
};
|
|
5165
|
-
const unsubscribeBody = subscribeBodyInvalidations(() => {
|
|
5166
|
-
tryPlace();
|
|
5167
|
-
});
|
|
5168
|
-
const rootObserver = new MutationObserver(() => {
|
|
5169
|
-
if (root === void 0 || !root.isConnected) {
|
|
5170
|
-
placed = false;
|
|
5171
|
-
tryPlace();
|
|
5172
|
-
return;
|
|
5173
|
-
}
|
|
5174
|
-
if (!root.contains(entry)) placed = placeEntry(root, entry, options);
|
|
5175
|
-
});
|
|
5176
|
-
const unsubscribeActive = options.active === void 0 ? void 0 : (() => {
|
|
5177
|
-
const syncActive = () => {
|
|
5178
|
-
if (options.active.isOpen()) entry.dataset.active = "true";
|
|
5179
|
-
else delete entry.dataset.active;
|
|
5180
|
-
};
|
|
5181
|
-
const unsubscribe = options.active.subscribe(syncActive);
|
|
5182
|
-
syncActive();
|
|
5183
|
-
return unsubscribe;
|
|
5184
|
-
})();
|
|
5185
|
-
tryPlace();
|
|
5534
|
+
function registerTaskBoardPanel(ctx, controller) {
|
|
5535
|
+
const slots = ctx.slots;
|
|
5536
|
+
const disposers = [];
|
|
5537
|
+
disposers.push(slots.inject("sidebar.panellist", () => slots.register({
|
|
5538
|
+
name: "sidebar.panellist",
|
|
5539
|
+
id: TASK_BOARD_PANEL_ID,
|
|
5540
|
+
order: PANEL_ORDER,
|
|
5541
|
+
label: () => t("entry.label")
|
|
5542
|
+
}, TaskBoardPanelIcon)));
|
|
5543
|
+
disposers.push(slots.inject("main", () => slots.register({
|
|
5544
|
+
name: "main",
|
|
5545
|
+
key: TASK_BOARD_PANEL_ID,
|
|
5546
|
+
inject: () => ({ controller })
|
|
5547
|
+
}, TaskBoardPanel)));
|
|
5186
5548
|
return () => {
|
|
5187
|
-
|
|
5188
|
-
rootObserver.disconnect();
|
|
5189
|
-
unsubscribeRefresh?.();
|
|
5190
|
-
unsubscribeActive?.();
|
|
5191
|
-
entry.remove();
|
|
5549
|
+
for (const dispose of disposers.splice(0)) dispose();
|
|
5192
5550
|
};
|
|
5193
5551
|
}
|
|
5194
5552
|
//#endregion
|
|
5195
|
-
//#region src/client/sidebar-entry.ts
|
|
5196
|
-
/** Stable data attribute identifying the injected entry row. */
|
|
5197
|
-
const ENTRY_SELECTOR = "[data-dsh-taskboard-entry]";
|
|
5198
|
-
/** Inline icon normalized to the shell's 16px navigation glyph size. */
|
|
5199
|
-
const ICON = "<svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2\" y=\"2.5\" width=\"12\" height=\"11\" rx=\"1.5\"/><path d=\"M2 6.5h12M6.5 6.5v7\"/></svg>";
|
|
5200
|
-
/**
|
|
5201
|
-
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
|
5202
|
-
* on later React re-renders.
|
|
5203
|
-
* @param controller - the board controller the entry toggles.
|
|
5204
|
-
* @param locale - locale-change source; when given, re-applies the label on
|
|
5205
|
-
* a Language switch (the plain-DOM row otherwise keeps the mount-time copy).
|
|
5206
|
-
* @returns disposer removing the entry and its observers.
|
|
5207
|
-
*/
|
|
5208
|
-
function mountSidebarEntry(controller, locale) {
|
|
5209
|
-
return mountSidebarEntry$1({
|
|
5210
|
-
rowAttribute: "data-dsh-taskboard-entry",
|
|
5211
|
-
rowSelector: ENTRY_SELECTOR,
|
|
5212
|
-
plugin: "task-board",
|
|
5213
|
-
icon: ICON,
|
|
5214
|
-
css: board_module_css_default,
|
|
5215
|
-
label: () => t("entry.label"),
|
|
5216
|
-
refresh: locale === void 0 ? void 0 : { subscribe: (listener) => locale.subscribe(listener) },
|
|
5217
|
-
onToggle: () => {
|
|
5218
|
-
controller.toggleBoard();
|
|
5219
|
-
},
|
|
5220
|
-
position: "before",
|
|
5221
|
-
familySelectors: ["[data-dsh-taskboard-entry]", "[data-dsh-ssh-entry]"],
|
|
5222
|
-
active: {
|
|
5223
|
-
subscribe: (listener) => controller.subscribe(listener),
|
|
5224
|
-
isOpen: () => controller.getSnapshot().boardOpen
|
|
5225
|
-
}
|
|
5226
|
-
});
|
|
5227
|
-
}
|
|
5228
|
-
//#endregion
|
|
5229
5553
|
//#region \0dsh-css:packages/dsh-task-board/src/client/settings-card.module.css.mjs
|
|
5230
5554
|
const css = ".Jh0q7G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Jh0q7G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_header{appearance:none;box-sizing:border-box;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Jh0q7G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Jh0q7G_headerStatic{box-sizing:border-box;border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.Jh0q7G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Jh0q7G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Jh0q7G_description{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5}.Jh0q7G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Jh0q7G_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Jh0q7G_chevronOpen{transform:rotate(180deg)}.Jh0q7G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.Jh0q7G_readOnly{color:var(--dsw-alias-label-secondary);margin:12px 0 0;font-size:12px;line-height:1.5}.Jh0q7G_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.Jh0q7G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.Jh0q7G_failed{min-width:0;color:var(--dsw-alias-state-error-primary,#b42318);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.Jh0q7G_discard,.Jh0q7G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Jh0q7G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Jh0q7G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Jh0q7G_discard:disabled,.Jh0q7G_save:disabled{opacity:.4;cursor:default}.Jh0q7G_discard:focus-visible,.Jh0q7G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Jh0q7G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.Jh0q7G_field+.Jh0q7G_field{border-top:1px solid var(--dsw-alias-border-l2)}.Jh0q7G_head{align-items:center;gap:8px;display:flex}.Jh0q7G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.Jh0q7G_badges{align-items:center;gap:8px;display:inline-flex}.Jh0q7G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Jh0q7G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.Jh0q7G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.Jh0q7G_reset:disabled{cursor:default}.Jh0q7G_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.Jh0q7G_input,.Jh0q7G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Jh0q7G_input:focus-visible,.Jh0q7G_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.Jh0q7G_input:disabled,.Jh0q7G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Jh0q7G_inputInvalid{border:1px solid var(--dsw-alias-state-error-primary,#b42318);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Jh0q7G_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-state-error-primary,#b42318);outline-offset:1px;border-color:var(--dsw-alias-state-error-primary,#b42318)}.Jh0q7G_selectWrap{position:relative}.Jh0q7G_selectButton{appearance:none;text-align:left;cursor:pointer;justify-content:space-between;align-items:center;gap:8px;width:100%;display:flex}.Jh0q7G_selectLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.Jh0q7G_selectChevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Jh0q7G_selectChevronOpen{transform:rotate(180deg)}.Jh0q7G_selectPopup{z-index:40;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);max-height:240px;box-shadow:0 8px 24px var(--dsw-alias-bg-mask-2);opacity:0;border-radius:8px;flex-direction:column;padding:4px;transition:opacity .1s,transform .1s;display:flex;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow-y:auto;transform:translateY(-4px)}.Jh0q7G_selectPopupOpen{opacity:1;transform:none}.Jh0q7G_selectPopupClose{opacity:0;pointer-events:none;transform:translateY(-4px)}.Jh0q7G_selectOption{color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-overflow:ellipsis;border-radius:6px;flex-shrink:0;padding:6px 10px;font-size:13px;line-height:1.5;overflow:hidden}.Jh0q7G_selectOption:hover,.Jh0q7G_selectOptionActive{background:var(--dsw-alias-interactive-bg-hover)}.Jh0q7G_selectOptionSelected{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 10%, transparent);font-weight:500}.Jh0q7G_invalid{color:var(--dsw-alias-state-error-primary,#b42318);margin:0;font-size:12px;line-height:1.5}.Jh0q7G_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.Jh0q7G_card,.Jh0q7G_header,.Jh0q7G_chevron,.Jh0q7G_chevronOpen,.Jh0q7G_discard,.Jh0q7G_save,.Jh0q7G_selectChevron,.Jh0q7G_selectChevronOpen,.Jh0q7G_selectPopup{transition:none}}";
|
|
5231
5555
|
const tagId = "@linxin666/dsh-client-ui-task-board/packages/dsh-task-board/src/client/settings-card.module.css";
|
|
@@ -6144,6 +6468,71 @@ window.__ModuleLoader__.load({
|
|
|
6144
6468
|
});
|
|
6145
6469
|
}
|
|
6146
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
|
|
6147
6536
|
//#region src/protocol.ts
|
|
6148
6537
|
const TASK_BOARD_API_PREFIX = "/api/task-board";
|
|
6149
6538
|
/** Whether a decoded reply carries the three draft strings the form accepts. */
|
|
@@ -6182,6 +6571,90 @@ window.__ModuleLoader__.load({
|
|
|
6182
6571
|
this.name = "HostApiError";
|
|
6183
6572
|
}
|
|
6184
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
|
+
}
|
|
6185
6658
|
function uuid() {
|
|
6186
6659
|
return globalThis.crypto?.randomUUID?.() ?? `browser-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
6187
6660
|
}
|
|
@@ -6206,12 +6679,13 @@ window.__ModuleLoader__.load({
|
|
|
6206
6679
|
return parsed;
|
|
6207
6680
|
}
|
|
6208
6681
|
if (hostError !== void 0) {
|
|
6209
|
-
if (hostError === "forbidden") throw new HostApiError("
|
|
6682
|
+
if (hostError === "forbidden") throw new HostApiError("forbidden", t("board.hostError.forbidden"), response.status);
|
|
6210
6683
|
if (response.status === 503 || /lock/i.test(hostError)) throw new HostApiError("locked", t("board.hostError.locked", { detail: hostError }), response.status);
|
|
6211
6684
|
throw new HostApiError("rejected", hostError, response.status);
|
|
6212
6685
|
}
|
|
6213
|
-
if (response.status === 404) throw
|
|
6214
|
-
if (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);
|
|
6215
6689
|
throw new HostApiError("unexpected", t("board.hostError.unexpected", { status: String(response.status) }), response.status);
|
|
6216
6690
|
}
|
|
6217
6691
|
var HttpTaskBoardHostTransport = class {
|
|
@@ -6310,8 +6784,9 @@ window.__ModuleLoader__.load({
|
|
|
6310
6784
|
if (record !== void 0 && isTaskParseDraft(record.draft)) return record.draft;
|
|
6311
6785
|
throw new HostApiError("unexpected", t("new.aiParseFailed", { error: t("board.hostError.unexpected", { status: String(response.status) }) }), response.status);
|
|
6312
6786
|
}
|
|
6313
|
-
if (response.status === 404) throw
|
|
6314
|
-
if (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);
|
|
6315
6790
|
const code = typeof record?.code === "string" ? record.code : void 0;
|
|
6316
6791
|
if (code === "no-model") throw new HostApiError("rejected", t("new.aiParseNoModel"), response.status);
|
|
6317
6792
|
if (code === "timeout") throw new HostApiError("timeout", t("new.aiParseTimeout", { seconds: String(TASK_PARSE_TIMEOUT_SECONDS) }), response.status);
|
|
@@ -6353,7 +6828,7 @@ window.__ModuleLoader__.load({
|
|
|
6353
6828
|
/** The building package's version, when the bundle carries it. */
|
|
6354
6829
|
function bakedVersion() {
|
|
6355
6830
|
try {
|
|
6356
|
-
return "0.4.
|
|
6831
|
+
return "0.4.4";
|
|
6357
6832
|
} catch {
|
|
6358
6833
|
return;
|
|
6359
6834
|
}
|
|
@@ -6541,6 +7016,96 @@ window.__ModuleLoader__.load({
|
|
|
6541
7016
|
reconcile();
|
|
6542
7017
|
}
|
|
6543
7018
|
//#endregion
|
|
7019
|
+
//#region src/client/settings-entry-form.ts
|
|
7020
|
+
/** The snapshot a form reports before the Host has answered with this entry. */
|
|
7021
|
+
function pendingSnapshot() {
|
|
7022
|
+
return {
|
|
7023
|
+
status: "loading",
|
|
7024
|
+
value: void 0,
|
|
7025
|
+
base: void 0,
|
|
7026
|
+
user: void 0,
|
|
7027
|
+
revision: void 0,
|
|
7028
|
+
writable: false,
|
|
7029
|
+
mode: "host"
|
|
7030
|
+
};
|
|
7031
|
+
}
|
|
7032
|
+
/**
|
|
7033
|
+
* Create a settings form bound to the profile entry id this Host serves, and
|
|
7034
|
+
* rebound whenever the shared describe mirror names a different one of this
|
|
7035
|
+
* package's rows.
|
|
7036
|
+
* @param options - the shared forms service and this package's candidate row ids.
|
|
7037
|
+
* @returns a form delegating to the currently resolved entry's own form.
|
|
7038
|
+
*/
|
|
7039
|
+
function createServedEntryForm(options) {
|
|
7040
|
+
const { forms, entryIds } = options;
|
|
7041
|
+
const fallbackId = entryIds[0];
|
|
7042
|
+
const namespaceId = entryIds[entryIds.length - 1];
|
|
7043
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
7044
|
+
let boundId;
|
|
7045
|
+
let bound;
|
|
7046
|
+
let offBound;
|
|
7047
|
+
let snapshot = pendingSnapshot();
|
|
7048
|
+
/** Republish: the delegated snapshot when one is bound, the pending one otherwise. */
|
|
7049
|
+
const publish = () => {
|
|
7050
|
+
if (bound !== void 0) snapshot = bound.getSnapshot();
|
|
7051
|
+
for (const listener of [...listeners]) listener();
|
|
7052
|
+
};
|
|
7053
|
+
/**
|
|
7054
|
+
* The entry id the mirror currently justifies. An unanswered or empty mirror
|
|
7055
|
+
* is not evidence of absence, so it keeps the first candidate; a mirror that
|
|
7056
|
+
* answers without any of this package's rows leaves the namespace itself.
|
|
7057
|
+
* @returns the entry id to bind.
|
|
7058
|
+
*/
|
|
7059
|
+
const resolve = () => {
|
|
7060
|
+
let served;
|
|
7061
|
+
try {
|
|
7062
|
+
served = forms.describe().getSnapshot().view?.namespaces.map((row) => row.ns);
|
|
7063
|
+
} catch {
|
|
7064
|
+
served = void 0;
|
|
7065
|
+
}
|
|
7066
|
+
if (served === void 0 || served.length === 0) return fallbackId;
|
|
7067
|
+
return entryIds.find((id) => served.includes(id)) ?? namespaceId;
|
|
7068
|
+
};
|
|
7069
|
+
/** Bind (or rebind) the resolved entry's form; a no-op while it is unchanged. */
|
|
7070
|
+
const bind = () => {
|
|
7071
|
+
const target = resolve();
|
|
7072
|
+
if (target === boundId) return;
|
|
7073
|
+
offBound?.();
|
|
7074
|
+
offBound = void 0;
|
|
7075
|
+
let form;
|
|
7076
|
+
try {
|
|
7077
|
+
form = forms.get(target);
|
|
7078
|
+
} catch {
|
|
7079
|
+
boundId = void 0;
|
|
7080
|
+
return;
|
|
7081
|
+
}
|
|
7082
|
+
boundId = target;
|
|
7083
|
+
bound = form;
|
|
7084
|
+
offBound = form.subscribe(() => {
|
|
7085
|
+
publish();
|
|
7086
|
+
});
|
|
7087
|
+
publish();
|
|
7088
|
+
};
|
|
7089
|
+
try {
|
|
7090
|
+
forms.describe().subscribe(() => {
|
|
7091
|
+
bind();
|
|
7092
|
+
});
|
|
7093
|
+
} catch {}
|
|
7094
|
+
bind();
|
|
7095
|
+
return {
|
|
7096
|
+
getSnapshot: () => snapshot,
|
|
7097
|
+
subscribe: (listener) => {
|
|
7098
|
+
listeners.add(listener);
|
|
7099
|
+
return () => {
|
|
7100
|
+
listeners.delete(listener);
|
|
7101
|
+
};
|
|
7102
|
+
},
|
|
7103
|
+
set: (field, value) => bound?.set(field, value) ?? Promise.resolve(false),
|
|
7104
|
+
unset: (field) => bound?.unset(field) ?? Promise.resolve(false),
|
|
7105
|
+
mutate: (ops, expectedRevision) => bound?.mutate(ops, expectedRevision) ?? Promise.resolve(false)
|
|
7106
|
+
};
|
|
7107
|
+
}
|
|
7108
|
+
//#endregion
|
|
6544
7109
|
//#region src/client/index.ts
|
|
6545
7110
|
/** Locale namespace this plugin owns. */
|
|
6546
7111
|
const NS = "task-board";
|
|
@@ -6581,9 +7146,48 @@ window.__ModuleLoader__.load({
|
|
|
6581
7146
|
"locale",
|
|
6582
7147
|
"remote",
|
|
6583
7148
|
"remote.session",
|
|
6584
|
-
"uiWorkspace"
|
|
7149
|
+
"uiWorkspace",
|
|
7150
|
+
"layout"
|
|
6585
7151
|
];
|
|
6586
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
|
+
/**
|
|
6587
7191
|
* Mount the task board.
|
|
6588
7192
|
* @param ctx - client root context (services: sessions, workspaces).
|
|
6589
7193
|
*/
|
|
@@ -6626,6 +7230,9 @@ window.__ModuleLoader__.load({
|
|
|
6626
7230
|
const controller = new BoardController({
|
|
6627
7231
|
store: new LocalStorageTaskStore(),
|
|
6628
7232
|
transport: new HttpTaskBoardHostTransport(),
|
|
7233
|
+
panel: { select: (panelId) => {
|
|
7234
|
+
ctx.get("layout")?.selectPanel?.(panelId);
|
|
7235
|
+
} },
|
|
6629
7236
|
sessions: {
|
|
6630
7237
|
current: () => mainViewSessionId(sessions.list.getSnapshot().byId),
|
|
6631
7238
|
open: (id) => ctx.uiWorkspace.openSession(id),
|
|
@@ -6646,6 +7253,21 @@ window.__ModuleLoader__.load({
|
|
|
6646
7253
|
controller.setWorkspaceCreator(async (path) => {
|
|
6647
7254
|
return { workspaceId: (await workspaces.create({ path })).workspaceId };
|
|
6648
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
|
+
};
|
|
6649
7271
|
const pushModelOptions = async () => {
|
|
6650
7272
|
try {
|
|
6651
7273
|
let models = [];
|
|
@@ -6701,15 +7323,25 @@ window.__ModuleLoader__.load({
|
|
|
6701
7323
|
console.error("[dsh-task-board] model options read failed", error);
|
|
6702
7324
|
}
|
|
6703
7325
|
};
|
|
7326
|
+
pushPresetOptions();
|
|
6704
7327
|
pushModelOptions();
|
|
6705
7328
|
disposers.push(ctx.on("connection/reset", () => {
|
|
7329
|
+
pushPresetOptions();
|
|
6706
7330
|
pushModelOptions();
|
|
6707
7331
|
}));
|
|
6708
7332
|
try {
|
|
6709
|
-
disposers.push(
|
|
6710
|
-
|
|
7333
|
+
disposers.push(registerTaskBoardPanel(ctx, controller));
|
|
7334
|
+
const layoutFace = ctx.get("layout");
|
|
7335
|
+
if (layoutFace?.panelInfo !== void 0) {
|
|
7336
|
+
const sync = () => {
|
|
7337
|
+
const active = layoutFace.panelInfo.getSnapshot().activePanelId;
|
|
7338
|
+
controller.syncPanelSelection(active === "task-board" ? TASK_BOARD_PANEL_ID : null);
|
|
7339
|
+
};
|
|
7340
|
+
sync();
|
|
7341
|
+
disposers.push(layoutFace.panelInfo.subscribe(sync));
|
|
7342
|
+
}
|
|
6711
7343
|
} catch (error) {
|
|
6712
|
-
console.error("[dsh-task-board]
|
|
7344
|
+
console.error("[dsh-task-board] panel registration failed:", error);
|
|
6713
7345
|
}
|
|
6714
7346
|
uiDisposer = () => {
|
|
6715
7347
|
for (const dispose of disposers.splice(0)) dispose();
|
|
@@ -6744,19 +7376,23 @@ window.__ModuleLoader__.load({
|
|
|
6744
7376
|
function bindSettingsForm(ctx) {
|
|
6745
7377
|
const binder = ctx.get("webUiSettings");
|
|
6746
7378
|
if (binder !== void 0 && typeof binder.bind === "function") return binder.bind({ namespace: TASK_BOARD_NS });
|
|
6747
|
-
return
|
|
7379
|
+
return createServedEntryForm({
|
|
7380
|
+
forms: ctx.configForms,
|
|
7381
|
+
entryIds: TASK_BOARD_ENTRY_IDS
|
|
7382
|
+
});
|
|
6748
7383
|
}
|
|
6749
7384
|
/**
|
|
6750
|
-
* The profile entry id this package's own row carries
|
|
7385
|
+
* The profile entry id this package's own row carries, for a page that serves
|
|
7386
|
+
* no family binder.
|
|
6751
7387
|
*
|
|
6752
7388
|
* The shared describe mirror is the only local evidence of which row id this
|
|
6753
|
-
* profile actually serves, but it answers asynchronously: at plugin
|
|
6754
|
-
*
|
|
6755
|
-
*
|
|
6756
|
-
*
|
|
6757
|
-
*
|
|
7389
|
+
* profile actually serves, but it answers asynchronously: at plugin activation
|
|
7390
|
+
* it usually holds nothing yet. An unanswered mirror therefore binds the
|
|
7391
|
+
* aggregate row id rather than guessing among the candidates, and the binding
|
|
7392
|
+
* is re-resolved once the mirror answers — see
|
|
7393
|
+
* {@link createServedEntryForm}, which owns that rebinding.
|
|
6758
7394
|
* @param forms - the shared configuration forms service.
|
|
6759
|
-
* @returns the entry id to bind.
|
|
7395
|
+
* @returns the entry id to bind before the mirror answers.
|
|
6760
7396
|
*/
|
|
6761
7397
|
function servedEntryId(forms) {
|
|
6762
7398
|
let served;
|
|
@@ -6772,6 +7408,7 @@ window.__ModuleLoader__.load({
|
|
|
6772
7408
|
exports.apply = apply;
|
|
6773
7409
|
exports.bindSettingsForm = bindSettingsForm;
|
|
6774
7410
|
exports.inject = inject;
|
|
7411
|
+
exports.servedEntryId = servedEntryId;
|
|
6775
7412
|
return module.exports;
|
|
6776
7413
|
}
|
|
6777
7414
|
});
|