@linxin666/dsh-client-ui-task-board 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +11 -7
  3. package/README.zh.md +11 -7
  4. package/lib/client.js +821 -145
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +1 -1
  7. package/lib/types/client/PluginSettingsCard.d.ts +11 -0
  8. package/lib/types/client/locales.d.ts +21 -0
  9. package/lib/types/client/schedule-presets.d.ts +10 -0
  10. package/lib/types/core/controller.d.ts +57 -1
  11. package/lib/types/core/execution.d.ts +65 -4
  12. package/lib/types/core/scheduler.d.ts +7 -0
  13. package/lib/types/core/store.d.ts +1 -1
  14. package/lib/types/core/tasks.d.ts +43 -0
  15. package/lib/types/core/use-cases/task-archive.d.ts +22 -0
  16. package/lib/types/core/use-cases/task-create.d.ts +1 -6
  17. package/lib/types/core/use-cases/task-update.d.ts +9 -4
  18. package/lib/types/index.d.ts +1 -1
  19. package/package.json +1 -1
  20. package/src/client/PluginSettingsCard.tsx +50 -47
  21. package/src/client/board/NewTaskModal.tsx +136 -3
  22. package/src/client/board/TaskBoard.tsx +48 -19
  23. package/src/client/board/TaskDetail.tsx +97 -8
  24. package/src/client/board.module.css +16 -0
  25. package/src/client/index.ts +71 -2
  26. package/src/client/locales.ts +42 -0
  27. package/src/client/schedule-presets.ts +13 -0
  28. package/src/client/settings-card.module.css +9 -0
  29. package/src/core/controller.ts +92 -2
  30. package/src/core/execution.ts +179 -12
  31. package/src/core/scheduler.ts +15 -0
  32. package/src/core/store.ts +16 -2
  33. package/src/core/tasks.ts +56 -0
  34. package/src/core/use-cases/task-archive.ts +48 -0
  35. package/src/core/use-cases/task-create.ts +12 -3
  36. package/src/core/use-cases/task-update.ts +34 -5
  37. package/src/index.ts +1 -1
package/lib/client.js CHANGED
@@ -9,6 +9,18 @@ window.__ModuleLoader__.load({
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
10
  let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
11
11
  //#region src/core/tasks.ts
12
+ /** Statuses a settled task may be archived from. */
13
+ const ARCHIVABLE_STATUSES = ["done", "failed"];
14
+ /** Permission presets a task may pin on its execution session (the `/permission <id>` ids). */
15
+ const TASK_PERMISSIONS = [
16
+ "read-only",
17
+ "workspace-write",
18
+ "danger-full-access"
19
+ ];
20
+ /** Whether an unknown value is a known permission preset id. */
21
+ function isTaskPermission(value) {
22
+ return typeof value === "string" && TASK_PERMISSIONS.includes(value);
23
+ }
12
24
  /** The five kanban columns, in display order. */
13
25
  const COLUMNS = [
14
26
  {
@@ -46,6 +58,11 @@ window.__ModuleLoader__.load({
46
58
  function isTaskStatus(value) {
47
59
  return typeof value === "string" && ALL_STATUSES.includes(value);
48
60
  }
61
+ /** Normalize one optional execution-target string: trim; blank collapses to undefined. */
62
+ function normalizeTargetId$2(value) {
63
+ const trimmed = value?.trim();
64
+ return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
65
+ }
49
66
  /** Create a task from user input. */
50
67
  function createTask(input, now, id) {
51
68
  return {
@@ -56,7 +73,10 @@ window.__ModuleLoader__.load({
56
73
  status: "todo",
57
74
  createdAt: now,
58
75
  updatedAt: now,
59
- executions: []
76
+ executions: [],
77
+ workspaceId: normalizeTargetId$2(input.workspaceId),
78
+ mode: normalizeTargetId$2(input.mode),
79
+ permission: isTaskPermission(input.permission) ? input.permission : void 0
60
80
  };
61
81
  }
62
82
  /** Clone a task with an updated status and a fresh updatedAt. */
@@ -148,44 +168,42 @@ window.__ModuleLoader__.load({
148
168
  return "running";
149
169
  }
150
170
  //#endregion
151
- //#region src/core/use-cases/task-create.ts
171
+ //#region src/core/use-cases/task-archive.ts
152
172
  /**
153
- * Create-task use case: mint a new task from user input, rejecting a blank
154
- * title. Pure ledger transition (no persistence or notify — the controller
155
- * orchestrates those), so it is unit-testable without any runtime face.
156
- */
157
- /**
158
- * Apply a create against the current ledger. Returns the new task and the
159
- * appended ledger, or the unchanged ledger when the title is blank.
160
- * @param tasks - current ledger.
161
- * @param input - raw user input (title/description/prompt).
162
- * @param now - clock instant (ms epoch).
163
- * @param id - minted task id.
173
+ * Archive one task: only settled statuses (done/failed) can be archived;
174
+ * a running or not-yet-settled task stays on the board (its runner still
175
+ * owns its lifecycle). Already-archived tasks are a no-op.
164
176
  */
165
- function applyCreateTask(tasks, input, now, id) {
166
- if (input.title.trim() === "") return {
167
- task: void 0,
168
- tasks
169
- };
170
- const task = createTask(input, now, id);
177
+ function applyArchiveTask(tasks, id, now) {
178
+ let applied = false;
171
179
  return {
172
- task,
173
- tasks: [...tasks, task]
180
+ tasks: tasks.map((task) => {
181
+ if (task.id !== id || task.archivedAt !== void 0) return task;
182
+ if (!ARCHIVABLE_STATUSES.includes(task.status)) return task;
183
+ applied = true;
184
+ return {
185
+ ...task,
186
+ archivedAt: now,
187
+ updatedAt: now
188
+ };
189
+ }),
190
+ archived: applied
174
191
  };
175
192
  }
176
- //#endregion
177
- //#region src/core/use-cases/task-delete.ts
178
- /**
179
- * Apply a delete across the ledger. The selection (a task id) is cleared when
180
- * it matches the removed task, so the UI never lingers on a vanished detail.
181
- * @param tasks - current ledger.
182
- * @param selectedTaskId - the currently selected task id (may be undefined).
183
- * @param id - the task to remove.
184
- */
185
- function applyDeleteTask(tasks, selectedTaskId, id) {
193
+ /** Restore one task back onto the main board (clears the archive marker). */
194
+ function applyRestoreTask(tasks, id, now) {
195
+ let applied = false;
186
196
  return {
187
- tasks: tasks.filter((task) => task.id !== id),
188
- selectionCleared: selectedTaskId === id
197
+ tasks: tasks.map((task) => {
198
+ if (task.id !== id || task.archivedAt === void 0) return task;
199
+ applied = true;
200
+ const { archivedAt: _archived, ...rest } = task;
201
+ return {
202
+ ...rest,
203
+ updatedAt: now
204
+ };
205
+ }),
206
+ archived: applied
189
207
  };
190
208
  }
191
209
  //#endregion
@@ -290,6 +308,56 @@ window.__ModuleLoader__.load({
290
308
  return /^\d+$/.test(value);
291
309
  }
292
310
  //#endregion
311
+ //#region src/core/use-cases/task-create.ts
312
+ /**
313
+ * Create-task use case: mint a new task from user input, rejecting a blank
314
+ * title. Pure ledger transition (no persistence or notify — the controller
315
+ * orchestrates those), so it is unit-testable without any runtime face.
316
+ */
317
+ /**
318
+ * Apply a create against the current ledger. Returns the new task and the
319
+ * appended ledger, or the unchanged ledger when the title is blank.
320
+ * @param tasks - current ledger.
321
+ * @param input - raw user input (title/description/prompt + optional schedule).
322
+ * @param now - clock instant (ms epoch).
323
+ * @param id - minted task id.
324
+ */
325
+ function applyCreateTask(tasks, input, now, id) {
326
+ if (input.title.trim() === "") return {
327
+ task: void 0,
328
+ tasks
329
+ };
330
+ let task = createTask(input, now, id);
331
+ const requested = input.schedule;
332
+ if (requested?.enabled === true && requested.cron.trim() !== "" && isValidCron(requested.cron)) {
333
+ const cron = requested.cron.trim();
334
+ task = withSchedule(task, {
335
+ enabled: true,
336
+ cron,
337
+ nextRunAt: nextRunAtMs(cron, now)
338
+ }, now);
339
+ }
340
+ return {
341
+ task,
342
+ tasks: [...tasks, task]
343
+ };
344
+ }
345
+ //#endregion
346
+ //#region src/core/use-cases/task-delete.ts
347
+ /**
348
+ * Apply a delete across the ledger. The selection (a task id) is cleared when
349
+ * it matches the removed task, so the UI never lingers on a vanished detail.
350
+ * @param tasks - current ledger.
351
+ * @param selectedTaskId - the currently selected task id (may be undefined).
352
+ * @param id - the task to remove.
353
+ */
354
+ function applyDeleteTask(tasks, selectedTaskId, id) {
355
+ return {
356
+ tasks: tasks.filter((task) => task.id !== id),
357
+ selectionCleared: selectedTaskId === id
358
+ };
359
+ }
360
+ //#endregion
293
361
  //#region src/core/use-cases/task-schedule.ts
294
362
  /**
295
363
  * Schedule use case: arm/disarm a task's cron rule and roll a rule forward.
@@ -348,6 +416,25 @@ window.__ModuleLoader__.load({
348
416
  //#endregion
349
417
  //#region src/core/use-cases/task-update.ts
350
418
  /**
419
+ * Update-task use case: apply an editable-field patch (title/description/
420
+ * prompt plus the execution targets workspaceId/mode/permission) with a
421
+ * fresh updatedAt. Pure ledger transition (no persistence or notify — the
422
+ * controller orchestrates those).
423
+ *
424
+ * An explicit `undefined` in the patch clears the field (the task falls
425
+ * back to the runtime default); an unknown permission string is ignored so
426
+ * stale UI can never persist a value the execution service rejects.
427
+ */
428
+ /** Collapse a blank target string to undefined (clears the pin). */
429
+ function normalizeTargetId$1(value) {
430
+ return value !== void 0 && value.trim() === "" ? void 0 : value;
431
+ }
432
+ /** Keep an unknown permission string from entering the ledger. */
433
+ function normalizePermission(current, value) {
434
+ if (value === void 0) return void 0;
435
+ return isTaskPermission(value) ? value : current;
436
+ }
437
+ /**
351
438
  * Apply an update across the ledger. Tasks that do not match the id are left
352
439
  * untouched; the matched task receives the patch plus a fresh updatedAt.
353
440
  * @param tasks - current ledger.
@@ -356,11 +443,21 @@ window.__ModuleLoader__.load({
356
443
  * @param now - clock instant (ms epoch).
357
444
  */
358
445
  function applyUpdateTask(tasks, id, patch, now) {
359
- return tasks.map((task) => task.id === id ? {
360
- ...task,
361
- ...patch,
362
- updatedAt: now
363
- } : task);
446
+ return tasks.map((task) => {
447
+ if (task.id !== id) return task;
448
+ const workspaceId = "workspaceId" in patch ? normalizeTargetId$1(patch.workspaceId) : void 0;
449
+ const mode = "mode" in patch ? normalizeTargetId$1(patch.mode) : void 0;
450
+ const permission = "permission" in patch ? normalizePermission(task.permission, patch.permission) : void 0;
451
+ const next = {
452
+ ...task,
453
+ ...patch,
454
+ updatedAt: now
455
+ };
456
+ if (workspaceId !== void 0 || "workspaceId" in patch) next.workspaceId = workspaceId;
457
+ if (mode !== void 0 || "mode" in patch) next.mode = mode;
458
+ if (permission !== void 0 || "permission" in patch) next.permission = permission;
459
+ return next;
460
+ });
364
461
  }
365
462
  //#endregion
366
463
  //#region src/core/controller.ts
@@ -389,7 +486,12 @@ window.__ModuleLoader__.load({
389
486
  deps;
390
487
  tasks = [];
391
488
  boardOpen = false;
489
+ archiveView = false;
392
490
  selectedTaskId;
491
+ executionOptions = {
492
+ workspaces: [],
493
+ presets: []
494
+ };
393
495
  listeners = /* @__PURE__ */ new Set();
394
496
  disposers = [];
395
497
  now;
@@ -425,7 +527,9 @@ window.__ModuleLoader__.load({
425
527
  return {
426
528
  tasks: this.tasks,
427
529
  boardOpen: this.boardOpen,
428
- selectedTaskId: this.selectedTaskId
530
+ archiveView: this.archiveView,
531
+ selectedTaskId: this.selectedTaskId,
532
+ executionOptions: this.executionOptions
429
533
  };
430
534
  }
431
535
  subscribe(fn) {
@@ -449,6 +553,18 @@ window.__ModuleLoader__.load({
449
553
  if (this.boardOpen) this.closeBoard();
450
554
  else this.openBoard();
451
555
  }
556
+ /**
557
+ * Switch between the kanban columns and the archive view. Leaving the
558
+ * archive view with an archived task still selected closes the selection —
559
+ * the detail overlay must not linger over a task that is off-board.
560
+ */
561
+ toggleArchiveView() {
562
+ this.archiveView = !this.archiveView;
563
+ if (!this.archiveView && this.selectedTaskId !== void 0) {
564
+ if (this.tasks.find((task) => task.id === this.selectedTaskId)?.archivedAt !== void 0) this.selectedTaskId = void 0;
565
+ }
566
+ this.notify();
567
+ }
452
568
  openTask(id) {
453
569
  if (this.tasks.some((task) => task.id === id)) {
454
570
  this.selectedTaskId = id;
@@ -471,6 +587,17 @@ window.__ModuleLoader__.load({
471
587
  this.tasks = [...applyUpdateTask(this.tasks, id, patch, this.now())];
472
588
  this.persistAndNotify();
473
589
  }
590
+ /**
591
+ * Replace (a part of) the picker option sets the UI feeds (workspace list
592
+ * and agent-preset roster come from the runtime, not the ledger).
593
+ */
594
+ setExecutionOptions(patch) {
595
+ this.executionOptions = {
596
+ ...this.executionOptions,
597
+ ...patch
598
+ };
599
+ this.notify();
600
+ }
474
601
  moveTask(id, status) {
475
602
  this.tasks = this.tasks.map((task) => task.id === id ? withStatus(task, status, this.now()) : task);
476
603
  this.persistAndNotify();
@@ -482,6 +609,27 @@ window.__ModuleLoader__.load({
482
609
  this.persistAndNotify();
483
610
  }
484
611
  /**
612
+ * Archive a settled task (done/failed). Running or on-board-unsettled
613
+ * tasks are refused so the runner keeps exclusive ownership of their
614
+ * lifecycle.
615
+ * @returns true when applied.
616
+ */
617
+ archiveTask(id) {
618
+ const { tasks, archived } = applyArchiveTask(this.tasks, id, this.now());
619
+ if (!archived) return false;
620
+ this.tasks = [...tasks];
621
+ this.persistAndNotify();
622
+ return true;
623
+ }
624
+ /** Restore an archived task back onto the board (same status column). */
625
+ restoreTask(id) {
626
+ const { tasks, archived } = applyRestoreTask(this.tasks, id, this.now());
627
+ if (!archived) return false;
628
+ this.tasks = [...tasks];
629
+ this.persistAndNotify();
630
+ return true;
631
+ }
632
+ /**
485
633
  * Update a task's schedule rule. A blank or invalid cron expression is
486
634
  * rejected (returns false, state untouched). When the rule ends up enabled
487
635
  * the next run instant is computed immediately; a disabled rule carries no
@@ -508,6 +656,17 @@ window.__ModuleLoader__.load({
508
656
  this.persistAndNotify();
509
657
  }
510
658
  /**
659
+ * Reload the ledger from the persisted store without notifying subscribers.
660
+ * The scheduler calls this before every tick so a task deleted in another
661
+ * tab (or a stale in-memory copy) can never be fired from this tab: the
662
+ * fire decision and the subsequent roll-forward both run on the freshest
663
+ * persisted truth. Deliberately silent — same-origin external changes still
664
+ * reach subscribers through the storage-event subscription.
665
+ */
666
+ reloadFromStore() {
667
+ this.tasks = this.deps.store.load();
668
+ }
669
+ /**
511
670
  * Jump to an execution's session transcript. Selecting the session changes
512
671
  * `current`, which closes the board (the conversation view takes over).
513
672
  * @param sessionId - the execution session to open.
@@ -637,6 +796,18 @@ window.__ModuleLoader__.load({
637
796
  if (error instanceof Error) return error.message;
638
797
  return String(error);
639
798
  }
799
+ /**
800
+ * Whether a rejected preset switch actually means "the session already runs
801
+ * this preset" (the host's agent-preset-conflict with a matching
802
+ * existingPreset). A blank-session reuse race can produce this even though
803
+ * the requested composition is in place.
804
+ */
805
+ function presetAlreadyRuns(error, mode) {
806
+ if (typeof error !== "object" || error === null) return false;
807
+ const details = error.details;
808
+ if (typeof details !== "object" || details === null) return false;
809
+ return details.existingPreset === mode;
810
+ }
640
811
  /** Whether a `turn/end` payload closed the turn with an error reason. */
641
812
  function isErrorTurnEnd(data) {
642
813
  if (typeof data !== "object" || data === null) return false;
@@ -654,13 +825,31 @@ window.__ModuleLoader__.load({
654
825
  */
655
826
  var ExecutionService = class {
656
827
  env;
828
+ /**
829
+ * In-flight preset switches keyed by `sessionId\u0000mode`. The session
830
+ * list mirror can lag behind a just-applied switch, so concurrent runs
831
+ * against one blank session would otherwise each issue the same select
832
+ * RPC (an amplified storm); sharing one in-flight call keeps the wire to
833
+ * a single request. The settled entry is forgotten so a later run can
834
+ * switch again.
835
+ */
836
+ presetSwitches = /* @__PURE__ */ new Map();
657
837
  /** @param env - the runtime faces (real or fake). */
658
838
  constructor(env) {
659
839
  this.env = env;
660
840
  }
661
841
  async run(task, execution, onEvent) {
842
+ const settleFailed = (error) => {
843
+ onEvent({
844
+ kind: "settled",
845
+ taskId: task.id,
846
+ executionId: execution.id,
847
+ outcome: "failed",
848
+ error
849
+ });
850
+ };
662
851
  try {
663
- const sessionId = await this.connectSession();
852
+ const sessionId = await this.connectSession(task.workspaceId);
664
853
  onEvent({
665
854
  kind: "started",
666
855
  taskId: task.id,
@@ -669,38 +858,100 @@ window.__ModuleLoader__.load({
669
858
  });
670
859
  const driver = this.driverOf(sessionId);
671
860
  if (driver === void 0) {
672
- onEvent({
673
- kind: "settled",
674
- taskId: task.id,
675
- executionId: execution.id,
676
- outcome: "failed",
677
- error: "execution session is not ready"
678
- });
861
+ settleFailed("execution session is not ready");
679
862
  return;
680
863
  }
864
+ if (!await this.applyMode(driver, task, sessionId, settleFailed)) return;
865
+ if (!await this.applyPermission(driver, task, settleFailed)) return;
681
866
  await driver.rename(task.title).catch(() => {});
682
867
  const baseline = driver.getSnapshot().turnEnds.size;
683
868
  const accepted = await this.sendPrompt(driver, task);
684
869
  if (!accepted.ok) {
685
- onEvent({
686
- kind: "settled",
687
- taskId: task.id,
688
- executionId: execution.id,
689
- outcome: "failed",
690
- error: messageOf(accepted.error)
691
- });
870
+ settleFailed(messageOf(accepted.error));
692
871
  return;
693
872
  }
694
873
  this.watchForSettlement(driver, task.id, execution.id, onEvent, baseline);
695
874
  } catch (error) {
696
- onEvent({
697
- kind: "settled",
698
- taskId: task.id,
699
- executionId: execution.id,
700
- outcome: "failed",
701
- error: messageOf(error)
702
- });
875
+ settleFailed(messageOf(error));
876
+ }
877
+ }
878
+ /**
879
+ * Recompose the execution session's agent from the task-pinned preset.
880
+ * No-op when the task pins none or the session already runs it; fails the
881
+ * run when the session is no longer blank, the preset face is missing, or
882
+ * the wire refuses.
883
+ */
884
+ async applyMode(driver, task, sessionId, settleFailed) {
885
+ const mode = task.mode;
886
+ if (mode === void 0 || mode === "") return true;
887
+ const summary = this.env.sessions.list.getSnapshot().byId[sessionId];
888
+ if (summary?.blank === false) {
889
+ settleFailed(`cannot switch agent preset to ${mode}: the execution session is not blank`);
890
+ return false;
891
+ }
892
+ if (summary?.agentPreset === mode) return true;
893
+ const presets = this.env.presets;
894
+ if (presets === void 0) {
895
+ settleFailed(`this deployment does not support agent presets (task asks for ${mode})`);
896
+ return false;
897
+ }
898
+ try {
899
+ const result = await this.switchPreset(presets, sessionId, mode);
900
+ if (!result.ok) {
901
+ if (presetAlreadyRuns(result.error, mode)) {
902
+ this.env.sessions.noteAgentPreset?.(sessionId, mode);
903
+ return true;
904
+ }
905
+ settleFailed(`agent preset switch to ${mode} rejected: ${messageOf(result.error)}`);
906
+ return false;
907
+ }
908
+ } catch (error) {
909
+ settleFailed(`agent preset switch to ${mode} failed: ${messageOf(error)}`);
910
+ return false;
703
911
  }
912
+ this.env.sessions.noteAgentPreset?.(sessionId, mode);
913
+ return true;
914
+ }
915
+ /**
916
+ * One in-flight `select` per (session, preset): concurrent runs against
917
+ * the same blank session share the same wire call instead of each issuing
918
+ * a duplicate RPC. The entry is removed once the call settles, so a later
919
+ * run (after a shared failure, say) issues a fresh switch.
920
+ */
921
+ switchPreset(presets, sessionId, mode) {
922
+ const key = `${sessionId}\u0000${mode}`;
923
+ const inflight = this.presetSwitches.get(key);
924
+ if (inflight !== void 0) return inflight;
925
+ const attempt = presets.select(sessionId, mode).finally(() => {
926
+ if (this.presetSwitches.get(key) === attempt) this.presetSwitches.delete(key);
927
+ });
928
+ this.presetSwitches.set(key, attempt);
929
+ return attempt;
930
+ }
931
+ /**
932
+ * Apply the task-pinned permission preset through the `/permission <id>`
933
+ * slash command. No-op when the task pins none; fails the run when the
934
+ * admission is rejected or no command claimed the line.
935
+ */
936
+ async applyPermission(driver, task, settleFailed) {
937
+ const permission = task.permission;
938
+ if (permission === void 0) return true;
939
+ const line = `/permission ${permission}`;
940
+ try {
941
+ const result = await driver.command(line);
942
+ if (!result.ok) {
943
+ settleFailed(`permission command rejected: ${messageOf(result.error)}`);
944
+ return false;
945
+ }
946
+ if (!result.matched) {
947
+ settleFailed(`permission command not recognized: ${line}`);
948
+ return false;
949
+ }
950
+ } catch (error) {
951
+ settleFailed(`permission command failed: ${messageOf(error)}`);
952
+ return false;
953
+ }
954
+ return true;
704
955
  }
705
956
  /**
706
957
  * Inspect a reloaded/background task that was left 'running' and emit a
@@ -773,8 +1024,12 @@ window.__ModuleLoader__.load({
773
1024
  return false;
774
1025
  }
775
1026
  }
776
- async connectSession() {
1027
+ async connectSession(taskWorkspaceId) {
777
1028
  const workspace = this.env.workspaces.list.getSnapshot();
1029
+ if (taskWorkspaceId !== void 0 && taskWorkspaceId !== "") {
1030
+ if (!workspace.items.some((item) => item.workspaceId === taskWorkspaceId)) throw new Error(`task workspace is not available: ${taskWorkspaceId}`);
1031
+ return this.env.workspaces.connectWorkspace(taskWorkspaceId);
1032
+ }
778
1033
  const workspaceId = workspace.recentWorkspaceId ?? workspace.items[0]?.workspaceId;
779
1034
  if (workspaceId === void 0) throw new Error("no workspace available to run the task in");
780
1035
  return this.env.workspaces.connectWorkspace(workspaceId);
@@ -836,6 +1091,11 @@ window.__ModuleLoader__.load({
836
1091
  * a task still running at its due instant is skipped by the controller's
837
1092
  * runTask guard and simply waits for the next cron match.
838
1093
  *
1094
+ * A deleted task can never fire again from a stale in-memory copy: every
1095
+ * tick first re-reads the persisted ledger through the optional `refresh`
1096
+ * hook (the controller reloads its store), so the fire decision and the
1097
+ * subsequent roll-forward both run on the freshest persisted truth.
1098
+ *
839
1099
  * The ticker is controlled: `start` arms a single interval guarded by an
840
1100
  * idempotence check (a second start while running is a no-op, so wiring can
841
1101
  * never leak a duplicate timer), and `stop`/`dispose` clear the timer and
@@ -910,6 +1170,7 @@ window.__ModuleLoader__.load({
910
1170
  async tick() {
911
1171
  if (this.disposed) return;
912
1172
  if (this.deps.ready !== void 0 && !this.deps.ready()) return;
1173
+ this.deps.refresh?.();
913
1174
  const now = this.deps.now();
914
1175
  for (const task of this.deps.tasks()) {
915
1176
  const schedule = task.schedule;
@@ -960,6 +1221,9 @@ window.__ModuleLoader__.load({
960
1221
  if (typeof record.prompt !== "string") return false;
961
1222
  if (typeof record.createdAt !== "number") return false;
962
1223
  if (typeof record.updatedAt !== "number") return false;
1224
+ if (record.workspaceId !== void 0 && typeof record.workspaceId !== "string") return false;
1225
+ if (record.mode !== void 0 && typeof record.mode !== "string") return false;
1226
+ if (record.permission !== void 0 && typeof record.permission !== "string") return false;
963
1227
  if (!Array.isArray(record.executions)) return false;
964
1228
  for (const execution of record.executions) {
965
1229
  if (typeof execution !== "object" || execution === null) return false;
@@ -973,6 +1237,10 @@ window.__ModuleLoader__.load({
973
1237
  }
974
1238
  return true;
975
1239
  }
1240
+ /** Collapse a blank persisted target string to undefined (clears the pin). */
1241
+ function normalizeTargetId(value) {
1242
+ return value !== void 0 && value.trim() === "" ? void 0 : value;
1243
+ }
976
1244
  /** Normalize an unknown persisted status back into the closed status union. */
977
1245
  function normalizeStatus(status) {
978
1246
  return isTaskStatus(status) ? status : "todo";
@@ -1019,6 +1287,10 @@ window.__ModuleLoader__.load({
1019
1287
  status: normalizeStatus(row.status)
1020
1288
  };
1021
1289
  task.schedule = normalizeSchedule(row.schedule);
1290
+ task.workspaceId = normalizeTargetId(row.workspaceId);
1291
+ task.mode = normalizeTargetId(row.mode);
1292
+ task.archivedAt = typeof row.archivedAt === "number" && Number.isFinite(row.archivedAt) ? row.archivedAt : void 0;
1293
+ task.permission = isTaskPermission(row.permission) ? row.permission : void 0;
1022
1294
  tasks.push(task);
1023
1295
  }
1024
1296
  return tasks;
@@ -1117,6 +1389,10 @@ window.__ModuleLoader__.load({
1117
1389
  "board.search": "筛选任务…",
1118
1390
  "board.empty": "这个状态还没有任务",
1119
1391
  "board.filterAll": "全部",
1392
+ "board.archive": "归档",
1393
+ "board.archiveView": "归档 ({count})",
1394
+ "board.backToBoard": "返回看板",
1395
+ "archive.empty": "没有已归档的任务",
1120
1396
  "board.status": "状态",
1121
1397
  "board.status.backlog": "待规划",
1122
1398
  "board.status.todo": "待办",
@@ -1144,6 +1420,9 @@ window.__ModuleLoader__.load({
1144
1420
  "detail.run": "执行",
1145
1421
  "detail.rerun": "重新执行",
1146
1422
  "detail.delete": "删除",
1423
+ "detail.archive": "归档",
1424
+ "detail.restore": "恢复",
1425
+ "detail.archivedAt": "已归档 · {time}",
1147
1426
  "detail.viewSession": "查看会话",
1148
1427
  "detail.noSession": "暂无会话",
1149
1428
  "detail.executionStarted": "已启动",
@@ -1176,6 +1455,20 @@ window.__ModuleLoader__.load({
1176
1455
  "detail.schedule.notScheduled": "尚未排程",
1177
1456
  "detail.schedule.dueSoon": "即将运行",
1178
1457
  "card.scheduled": "定时",
1458
+ "new.workspace": "工作区",
1459
+ "new.mode": "模式",
1460
+ "new.permission": "权限",
1461
+ "exec.workspace.recent": "最近使用(默认)",
1462
+ "exec.mode.default": "部署默认",
1463
+ "exec.mode.defaultSuffix": "(默认)",
1464
+ "exec.mode.brokenSuffix": "(不可用)",
1465
+ "exec.mode.removed": "(已移除)",
1466
+ "exec.permission.default": "会话默认",
1467
+ "exec.permission.read-only": "只读",
1468
+ "exec.permission.workspace-write": "工作区可写",
1469
+ "exec.permission.danger-full-access": "完全访问",
1470
+ "detail.executionSettings": "执行设置",
1471
+ "exec.hint": "执行时生效:工作区决定执行会话落在哪个工作区;模式决定会话的 agent 预设;权限经 /permission 命令应用到会话。留空则使用运行时默认。",
1179
1472
  "settings.title": "任务看板",
1180
1473
  "settings.description": "控制看板在 agent 系统提示中的播报行为。",
1181
1474
  "settings.enabled": "启用任务看板",
@@ -1207,6 +1500,10 @@ window.__ModuleLoader__.load({
1207
1500
  "board.search": "Filter tasks…",
1208
1501
  "board.empty": "No tasks in this column",
1209
1502
  "board.filterAll": "All",
1503
+ "board.archive": "Archive",
1504
+ "board.archiveView": "Archived ({count})",
1505
+ "board.backToBoard": "Back to board",
1506
+ "archive.empty": "No archived tasks",
1210
1507
  "board.status": "Status",
1211
1508
  "board.status.backlog": "Backlog",
1212
1509
  "board.status.todo": "To Do",
@@ -1234,6 +1531,9 @@ window.__ModuleLoader__.load({
1234
1531
  "detail.run": "Run",
1235
1532
  "detail.rerun": "Run Again",
1236
1533
  "detail.delete": "Delete",
1534
+ "detail.archive": "Archive",
1535
+ "detail.restore": "Restore",
1536
+ "detail.archivedAt": "Archived · {time}",
1237
1537
  "detail.viewSession": "View Session",
1238
1538
  "detail.noSession": "No session",
1239
1539
  "detail.executionStarted": "Started",
@@ -1266,6 +1566,20 @@ window.__ModuleLoader__.load({
1266
1566
  "detail.schedule.notScheduled": "Not scheduled yet",
1267
1567
  "detail.schedule.dueSoon": "Due soon",
1268
1568
  "card.scheduled": "scheduled",
1569
+ "new.workspace": "Workspace",
1570
+ "new.mode": "Mode",
1571
+ "new.permission": "Permission",
1572
+ "exec.workspace.recent": "Most recent (default)",
1573
+ "exec.mode.default": "Deployment default",
1574
+ "exec.mode.defaultSuffix": " (default)",
1575
+ "exec.mode.brokenSuffix": " (unavailable)",
1576
+ "exec.mode.removed": " (removed)",
1577
+ "exec.permission.default": "Session default",
1578
+ "exec.permission.read-only": "Read-only",
1579
+ "exec.permission.workspace-write": "Workspace Write",
1580
+ "exec.permission.danger-full-access": "Full Access",
1581
+ "detail.executionSettings": "Execution Settings",
1582
+ "exec.hint": "Applied when the task runs: the workspace decides where the execution session lands; the mode composes the session's agent preset; the permission is applied through the /permission command. Blank = runtime default.",
1269
1583
  "settings.title": "Task Board",
1270
1584
  "settings.description": "How the board announces itself in each agent system prompt.",
1271
1585
  "settings.enabled": "Enable the task board",
@@ -1300,7 +1614,7 @@ window.__ModuleLoader__.load({
1300
1614
  }
1301
1615
  //#endregion
1302
1616
  //#region \0dsh-css:packages/dsh-task-board/src/client/board.module.css.mjs
1303
- 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}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{width:100%;height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:0 12px;font-size:13px;display:flex}._7D6uKa_entry:hover{background:var(--dsw-specific-sidebar-nav-item-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_entry[data-active]{background:var(--dsw-specific-sidebar-nav-item-active);color:var(--dsw-alias-label-primary);font-weight:600}._7D6uKa_entryIcon{flex:none;justify-content:center;align-items:center;display:inline-flex}._7D6uKa_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entry{justify-content:center;width:100%;padding:0}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entryLabel{display:none}._7D6uKa_board{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_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{flex:1;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px;min-height:0;display:grid}._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_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}._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_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_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_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_schedulePreset,._7D6uKa_scheduleToggle input{transition:none}._7D6uKa_cardSpinner{animation:none}}";
1617
+ 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}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{width:100%;height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:0 12px;font-size:13px;display:flex}._7D6uKa_entry:hover{background:var(--dsw-specific-sidebar-nav-item-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_entry[data-active]{background:var(--dsw-specific-sidebar-nav-item-active);color:var(--dsw-alias-label-primary);font-weight:600}._7D6uKa_entryIcon{flex:none;justify-content:center;align-items:center;display:inline-flex}._7D6uKa_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entry{justify-content:center;width:100%;padding:0}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entryLabel{display:none}._7D6uKa_board{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_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{flex:1;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px;min-height:0;display:grid}._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}._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}}";
1304
1618
  const tagId$1 = "@linxin666/dsh-client-ui-task-board/board.module.css";
1305
1619
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
1306
1620
  const tag = document.createElement("style");
@@ -1370,10 +1684,32 @@ window.__ModuleLoader__.load({
1370
1684
  "scheduleRow": "_7D6uKa_scheduleRow",
1371
1685
  "scheduleToggle": "_7D6uKa_scheduleToggle",
1372
1686
  "search": "_7D6uKa_search",
1687
+ "select": "_7D6uKa_select",
1373
1688
  "statusBadge": "_7D6uKa_statusBadge",
1374
1689
  "statusDot": "_7D6uKa_statusDot"
1375
1690
  };
1376
1691
  //#endregion
1692
+ //#region src/client/schedule-presets.ts
1693
+ /** Common scheduled-run presets (cron → locale label). */
1694
+ const SCHEDULE_PRESETS = [
1695
+ {
1696
+ cron: "0 9 * * *",
1697
+ label: "detail.schedule.preset.daily9"
1698
+ },
1699
+ {
1700
+ cron: "0 * * * *",
1701
+ label: "detail.schedule.preset.hourly"
1702
+ },
1703
+ {
1704
+ cron: "*/10 * * * *",
1705
+ label: "detail.schedule.preset.tenMin"
1706
+ },
1707
+ {
1708
+ cron: "0 9 * * 1",
1709
+ label: "detail.schedule.preset.weeklyMon9"
1710
+ }
1711
+ ];
1712
+ //#endregion
1377
1713
  //#region src/client/board/NewTaskModal.tsx
1378
1714
  /**
1379
1715
  * New-task modal: title + description + the prompt that execution will send.
@@ -1384,18 +1720,42 @@ window.__ModuleLoader__.load({
1384
1720
  const [title, setTitle] = (0, react.useState)("");
1385
1721
  const [description, setDescription] = (0, react.useState)("");
1386
1722
  const [prompt, setPrompt] = (0, react.useState)("");
1723
+ const [workspaceId, setWorkspaceId] = (0, react.useState)("");
1724
+ const [mode, setMode] = (0, react.useState)("");
1725
+ const [permission, setPermission] = (0, react.useState)("");
1726
+ const [scheduleEnabled, setScheduleEnabled] = (0, react.useState)(false);
1727
+ const [scheduleCron, setScheduleCron] = (0, react.useState)("");
1728
+ const [scheduleError, setScheduleError] = (0, react.useState)(void 0);
1387
1729
  const [error, setError] = (0, react.useState)(void 0);
1730
+ const [options, setOptions] = (0, react.useState)(controller.getSnapshot().executionOptions);
1731
+ (0, react.useEffect)(() => controller.subscribe(() => setOptions(controller.getSnapshot().executionOptions)), [controller]);
1388
1732
  const submit = () => {
1733
+ if (scheduleEnabled) {
1734
+ const cron = scheduleCron.trim();
1735
+ if (cron === "" || !isValidCron(cron)) {
1736
+ setScheduleError(t("detail.schedule.invalid"));
1737
+ return;
1738
+ }
1739
+ }
1389
1740
  if (controller.createTask({
1390
1741
  title,
1391
1742
  description,
1392
- prompt
1743
+ prompt,
1744
+ workspaceId: workspaceId === "" ? void 0 : workspaceId,
1745
+ mode: mode === "" ? void 0 : mode,
1746
+ permission: permission === "" ? void 0 : permission,
1747
+ schedule: scheduleEnabled ? {
1748
+ enabled: true,
1749
+ cron: scheduleCron.trim()
1750
+ } : void 0
1393
1751
  }) === void 0) {
1394
1752
  setError(t("new.required"));
1395
1753
  return;
1396
1754
  }
1397
1755
  onClose();
1398
1756
  };
1757
+ /** Next-run preview for a valid armed cron (creation-time only). */
1758
+ const scheduleNextRun = scheduleEnabled && scheduleCron.trim() !== "" && isValidCron(scheduleCron) ? nextRunAtMs(scheduleCron, Date.now()) : void 0;
1399
1759
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1400
1760
  className: board_module_css_default.modalBackdrop,
1401
1761
  onMouseDown: (event) => {
@@ -1460,6 +1820,132 @@ window.__ModuleLoader__.load({
1460
1820
  }
1461
1821
  })]
1462
1822
  }),
1823
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1824
+ className: board_module_css_default.field,
1825
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1826
+ className: board_module_css_default.fieldLabel,
1827
+ children: t("new.workspace")
1828
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1829
+ className: board_module_css_default.select,
1830
+ value: workspaceId,
1831
+ onChange: (event) => {
1832
+ setWorkspaceId(event.target.value);
1833
+ },
1834
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1835
+ value: "",
1836
+ children: t("exec.workspace.recent")
1837
+ }), options.workspaces.map((workspace) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1838
+ value: workspace.workspaceId,
1839
+ children: workspace.title
1840
+ }, workspace.workspaceId))]
1841
+ })]
1842
+ }),
1843
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1844
+ className: board_module_css_default.field,
1845
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1846
+ className: board_module_css_default.fieldLabel,
1847
+ children: t("new.mode")
1848
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1849
+ className: board_module_css_default.select,
1850
+ value: mode,
1851
+ onChange: (event) => {
1852
+ setMode(event.target.value);
1853
+ },
1854
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1855
+ value: "",
1856
+ children: t("exec.mode.default")
1857
+ }), options.presets.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
1858
+ value: preset.id,
1859
+ disabled: preset.broken !== void 0,
1860
+ children: [
1861
+ preset.name ?? preset.id,
1862
+ preset.isDefault ? t("exec.mode.defaultSuffix") : "",
1863
+ preset.broken !== void 0 ? t("exec.mode.brokenSuffix") : ""
1864
+ ]
1865
+ }, preset.id))]
1866
+ })]
1867
+ }),
1868
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1869
+ className: board_module_css_default.field,
1870
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1871
+ className: board_module_css_default.fieldLabel,
1872
+ children: t("new.permission")
1873
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1874
+ className: board_module_css_default.select,
1875
+ value: permission,
1876
+ onChange: (event) => {
1877
+ setPermission(event.target.value);
1878
+ },
1879
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1880
+ value: "",
1881
+ children: t("exec.permission.default")
1882
+ }), TASK_PERMISSIONS.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1883
+ value: id,
1884
+ children: t(`exec.permission.${id}`)
1885
+ }, id))]
1886
+ })]
1887
+ }),
1888
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1889
+ className: board_module_css_default.detailSection,
1890
+ children: [
1891
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.schedule") }),
1892
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1893
+ className: board_module_css_default.scheduleToggle,
1894
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1895
+ type: "checkbox",
1896
+ checked: scheduleEnabled,
1897
+ onChange: (event) => {
1898
+ setScheduleEnabled(event.target.checked);
1899
+ if (!event.target.checked) setScheduleError(void 0);
1900
+ }
1901
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.enable") })]
1902
+ }),
1903
+ scheduleEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1904
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1905
+ className: board_module_css_default.scheduleRow,
1906
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1907
+ className: `${board_module_css_default.input} ${board_module_css_default.scheduleInput}${scheduleError !== void 0 ? ` ${board_module_css_default.scheduleInputInvalid}` : ""}`,
1908
+ value: scheduleCron,
1909
+ placeholder: "0 9 * * *",
1910
+ spellCheck: false,
1911
+ "aria-label": t("detail.schedule.cron"),
1912
+ onChange: (event) => {
1913
+ setScheduleCron(event.target.value);
1914
+ setScheduleError(void 0);
1915
+ }
1916
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1917
+ className: board_module_css_default.schedulePreset,
1918
+ value: "",
1919
+ "aria-label": t("detail.schedule.presets"),
1920
+ onChange: (event) => {
1921
+ if (event.target.value === "") return;
1922
+ setScheduleCron(event.target.value);
1923
+ setScheduleError(void 0);
1924
+ },
1925
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
1926
+ value: "",
1927
+ children: [t("detail.schedule.presets"), "…"]
1928
+ }), SCHEDULE_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1929
+ value: preset.cron,
1930
+ children: t(preset.label)
1931
+ }, preset.cron))]
1932
+ })]
1933
+ }),
1934
+ scheduleError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1935
+ className: board_module_css_default.formError,
1936
+ children: scheduleError
1937
+ }),
1938
+ scheduleError === void 0 && scheduleNextRun !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1939
+ className: board_module_css_default.scheduleMeta,
1940
+ children: [
1941
+ t("detail.schedule.nextRun"),
1942
+ " ",
1943
+ new Date(scheduleNextRun).toLocaleString()
1944
+ ]
1945
+ })
1946
+ ] })
1947
+ ]
1948
+ }),
1463
1949
  error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1464
1950
  className: board_module_css_default.formError,
1465
1951
  children: error
@@ -1667,25 +2153,105 @@ window.__ModuleLoader__.load({
1667
2153
  ]
1668
2154
  });
1669
2155
  }
1670
- /** Common scheduled-run presets (cron → locale label). */
1671
- const SCHEDULE_PRESETS = [
1672
- {
1673
- cron: "0 9 * * *",
1674
- label: "detail.schedule.preset.daily9"
1675
- },
1676
- {
1677
- cron: "0 * * * *",
1678
- label: "detail.schedule.preset.hourly"
1679
- },
1680
- {
1681
- cron: "*/10 * * * *",
1682
- label: "detail.schedule.preset.tenMin"
1683
- },
1684
- {
1685
- cron: "0 9 * * 1",
1686
- label: "detail.schedule.preset.weeklyMon9"
1687
- }
1688
- ];
2156
+ /** The execution-target editor: workspace / mode / permission pickers. */
2157
+ function ExecutionSettingsSection({ controller, task }) {
2158
+ const [options, setOptions] = (0, react.useState)(controller.getSnapshot().executionOptions);
2159
+ (0, react.useEffect)(() => controller.subscribe(() => setOptions(controller.getSnapshot().executionOptions)), [controller]);
2160
+ const workspaceId = task.workspaceId ?? "";
2161
+ const mode = task.mode ?? "";
2162
+ const permission = task.permission ?? "";
2163
+ const workspaceKnown = workspaceId === "" || options.workspaces.some((item) => item.workspaceId === workspaceId);
2164
+ const modeKnown = mode === "" || options.presets.some((item) => item.id === mode);
2165
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2166
+ className: board_module_css_default.detailSection,
2167
+ children: [
2168
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.executionSettings") }),
2169
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2170
+ className: board_module_css_default.detailText,
2171
+ children: t("exec.hint")
2172
+ }),
2173
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2174
+ className: board_module_css_default.field,
2175
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2176
+ className: board_module_css_default.fieldLabel,
2177
+ children: t("new.workspace")
2178
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2179
+ className: board_module_css_default.select,
2180
+ value: workspaceId,
2181
+ onChange: (event) => {
2182
+ controller.updateTask(task.id, { workspaceId: event.target.value });
2183
+ },
2184
+ children: [
2185
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2186
+ value: "",
2187
+ children: t("exec.workspace.recent")
2188
+ }),
2189
+ !workspaceKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
2190
+ value: workspaceId,
2191
+ children: [workspaceId, t("exec.mode.removed")]
2192
+ }),
2193
+ options.workspaces.map((workspace) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2194
+ value: workspace.workspaceId,
2195
+ children: workspace.title
2196
+ }, workspace.workspaceId))
2197
+ ]
2198
+ })]
2199
+ }),
2200
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2201
+ className: board_module_css_default.field,
2202
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2203
+ className: board_module_css_default.fieldLabel,
2204
+ children: t("new.mode")
2205
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2206
+ className: board_module_css_default.select,
2207
+ value: mode,
2208
+ onChange: (event) => {
2209
+ controller.updateTask(task.id, { mode: event.target.value });
2210
+ },
2211
+ children: [
2212
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2213
+ value: "",
2214
+ children: t("exec.mode.default")
2215
+ }),
2216
+ !modeKnown && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
2217
+ value: mode,
2218
+ children: [mode, t("exec.mode.removed")]
2219
+ }),
2220
+ options.presets.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
2221
+ value: preset.id,
2222
+ disabled: preset.broken !== void 0,
2223
+ children: [
2224
+ preset.name ?? preset.id,
2225
+ preset.isDefault ? t("exec.mode.defaultSuffix") : "",
2226
+ preset.broken !== void 0 ? t("exec.mode.brokenSuffix") : ""
2227
+ ]
2228
+ }, preset.id))
2229
+ ]
2230
+ })]
2231
+ }),
2232
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2233
+ className: board_module_css_default.field,
2234
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2235
+ className: board_module_css_default.fieldLabel,
2236
+ children: t("new.permission")
2237
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2238
+ className: board_module_css_default.select,
2239
+ value: permission,
2240
+ onChange: (event) => {
2241
+ controller.updateTask(task.id, { permission: event.target.value === "" ? void 0 : event.target.value });
2242
+ },
2243
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2244
+ value: "",
2245
+ children: t("exec.permission.default")
2246
+ }), TASK_PERMISSIONS.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2247
+ value: id,
2248
+ children: t(`exec.permission.${id}`)
2249
+ }, id))]
2250
+ })]
2251
+ })
2252
+ ]
2253
+ });
2254
+ }
1689
2255
  /** The scheduled-runs editor: enable toggle, cron input + presets, next-run info. */
1690
2256
  function ScheduleSection({ controller, task }) {
1691
2257
  const schedule = task.schedule;
@@ -1863,6 +2429,10 @@ window.__ModuleLoader__.load({
1863
2429
  children: current.prompt !== "" ? current.prompt : current.title
1864
2430
  })]
1865
2431
  }),
2432
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExecutionSettingsSection, {
2433
+ controller,
2434
+ task: current
2435
+ }),
1866
2436
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScheduleSection, {
1867
2437
  controller,
1868
2438
  task: current
@@ -1912,6 +2482,23 @@ window.__ModuleLoader__.load({
1912
2482
  },
1913
2483
  children: current.executions.length === 0 ? t("detail.run") : t("detail.rerun")
1914
2484
  }),
2485
+ current.archivedAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2486
+ type: "button",
2487
+ className: board_module_css_default.primaryButton,
2488
+ onClick: () => {
2489
+ controller.restoreTask(current.id);
2490
+ controller.closeTask();
2491
+ },
2492
+ children: t("detail.restore")
2493
+ }) : (current.status === "done" || current.status === "failed") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2494
+ type: "button",
2495
+ className: board_module_css_default.ghostButton,
2496
+ onClick: () => {
2497
+ controller.archiveTask(current.id);
2498
+ controller.closeTask();
2499
+ },
2500
+ children: t("detail.archive")
2501
+ }),
1915
2502
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1916
2503
  type: "button",
1917
2504
  className: board_module_css_default.dangerButton,
@@ -1925,7 +2512,8 @@ window.__ModuleLoader__.load({
1925
2512
  children: [
1926
2513
  t("board.created"),
1927
2514
  " ",
1928
- formatTime(current.createdAt)
2515
+ formatTime(current.createdAt),
2516
+ current.archivedAt !== void 0 && ` · ${t("detail.archivedAt", { time: formatTime(current.archivedAt) })}`
1929
2517
  ]
1930
2518
  })
1931
2519
  ]
@@ -1989,7 +2577,8 @@ window.__ModuleLoader__.load({
1989
2577
  const [filter, setFilter] = (0, react.useState)("");
1990
2578
  const [showNew, setShowNew] = (0, react.useState)(false);
1991
2579
  const selected = selectedTaskOf(snapshot);
1992
- const visible = snapshot.tasks.filter((task) => matchesFilter(task, filter));
2580
+ const archiveView = snapshot.archiveView;
2581
+ const visible = snapshot.tasks.filter((task) => (archiveView ? task.archivedAt !== void 0 : task.archivedAt === void 0) && matchesFilter(task, filter));
1993
2582
  const openTask = (0, react.useCallback)((id) => {
1994
2583
  controller.openTask(id);
1995
2584
  }, [controller]);
@@ -2014,6 +2603,14 @@ window.__ModuleLoader__.load({
2014
2603
  },
2015
2604
  "aria-label": t("board.search")
2016
2605
  }),
2606
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2607
+ type: "button",
2608
+ className: archiveView ? board_module_css_default.primaryButton : board_module_css_default.ghostButton,
2609
+ onClick: () => {
2610
+ controller.toggleArchiveView();
2611
+ },
2612
+ children: archiveView ? t("board.backToBoard") : t("board.archiveView", { count: String(snapshot.tasks.filter((task) => task.archivedAt !== void 0).length) })
2613
+ }),
2017
2614
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2018
2615
  type: "button",
2019
2616
  className: board_module_css_default.primaryButton,
@@ -2034,7 +2631,29 @@ window.__ModuleLoader__.load({
2034
2631
  }),
2035
2632
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2036
2633
  className: board_module_css_default.columns,
2037
- children: COLUMNS.map((column) => {
2634
+ children: archiveView ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2635
+ className: board_module_css_default.column,
2636
+ "data-status": "archived",
2637
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2638
+ className: board_module_css_default.columnHeader,
2639
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2640
+ className: board_module_css_default.columnTitle,
2641
+ children: t("board.archive")
2642
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2643
+ className: board_module_css_default.columnCount,
2644
+ children: visible.length
2645
+ })]
2646
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2647
+ className: board_module_css_default.cards,
2648
+ children: [visible.map((task) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoTaskCard, {
2649
+ task,
2650
+ onOpen: openTask
2651
+ }, task.id)), visible.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2652
+ className: board_module_css_default.columnEmpty,
2653
+ children: t("archive.empty")
2654
+ })]
2655
+ })]
2656
+ }) : COLUMNS.map((column) => {
2038
2657
  const tasks = visible.filter((task) => task.status === column.status);
2039
2658
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2040
2659
  className: board_module_css_default.column,
@@ -2274,7 +2893,7 @@ window.__ModuleLoader__.load({
2274
2893
  }
2275
2894
  //#endregion
2276
2895
  //#region \0dsh-css:packages/dsh-task-board/src/client/settings-card.module.css.mjs
2277
- 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;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_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-tertiary);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-tertiary);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-label-error);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-label-error);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-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.Jh0q7G_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.Jh0q7G_hint{color:var(--dsw-alias-label-tertiary);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{transition:none}}";
2896
+ 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;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{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-tertiary);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-tertiary);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-label-error);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-label-error);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-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.Jh0q7G_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.Jh0q7G_hint{color:var(--dsw-alias-label-tertiary);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{transition:none}}";
2278
2897
  const tagId = "@linxin666/dsh-client-ui-task-board/settings-card.module.css";
2279
2898
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2280
2899
  const tag = document.createElement("style");
@@ -2299,6 +2918,7 @@ window.__ModuleLoader__.load({
2299
2918
  "head": "Jh0q7G_head",
2300
2919
  "headText": "Jh0q7G_headText",
2301
2920
  "header": "Jh0q7G_header",
2921
+ "headerStatic": "Jh0q7G_headerStatic",
2302
2922
  "hint": "Jh0q7G_hint",
2303
2923
  "input": "Jh0q7G_input",
2304
2924
  "inputInvalid": "Jh0q7G_inputInvalid",
@@ -2328,24 +2948,42 @@ window.__ModuleLoader__.load({
2328
2948
  * @returns the card, or nothing while the namespace is still loading.
2329
2949
  */
2330
2950
  function PluginSettingsCard(props) {
2331
- const [open, setOpen] = (0, react.useState)(false);
2332
- const { state } = props;
2951
+ const [open, setOpen] = (0, react.useState)(props.defaultOpen ?? true);
2952
+ const { state, alwaysOpen } = props;
2333
2953
  if (!state.available) return null;
2334
2954
  const title = props.t(props.titleKey);
2335
2955
  const description = props.t(props.descriptionKey);
2336
2956
  const blocked = !state.dirty || state.invalid || state.saving;
2337
- const cardClass = open ? `${settings_card_module_css_default.cardOpen} ${settings_card_module_css_default.card}` : settings_card_module_css_default.card;
2338
- if (!state.exposed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2339
- className: cardClass,
2340
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2341
- type: "button",
2342
- className: settings_card_module_css_default.header,
2343
- "aria-expanded": open,
2344
- "aria-label": `${props.t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
2345
- onClick: () => {
2346
- setOpen(!open);
2347
- },
2348
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2957
+ const expanded = alwaysOpen === true || open;
2958
+ const cardClass = expanded ? `${settings_card_module_css_default.cardOpen} ${settings_card_module_css_default.card}` : settings_card_module_css_default.card;
2959
+ const header = alwaysOpen === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2960
+ className: settings_card_module_css_default.headerStatic,
2961
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2962
+ className: settings_card_module_css_default.headText,
2963
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2964
+ className: settings_card_module_css_default.name,
2965
+ title,
2966
+ children: title
2967
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2968
+ className: settings_card_module_css_default.description,
2969
+ title: description,
2970
+ children: description
2971
+ })]
2972
+ }), state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2973
+ className: settings_card_module_css_default.pending,
2974
+ title: props.t("settings.unsaved"),
2975
+ children: props.t("settings.unsaved")
2976
+ }) : null]
2977
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2978
+ type: "button",
2979
+ className: settings_card_module_css_default.header,
2980
+ "aria-expanded": open,
2981
+ "aria-label": `${props.t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
2982
+ onClick: () => {
2983
+ setOpen(!open);
2984
+ },
2985
+ children: [
2986
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2349
2987
  className: settings_card_module_css_default.headText,
2350
2988
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2351
2989
  className: settings_card_module_css_default.name,
@@ -2356,7 +2994,13 @@ window.__ModuleLoader__.load({
2356
2994
  title: description,
2357
2995
  children: description
2358
2996
  })]
2359
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2997
+ }),
2998
+ state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2999
+ className: settings_card_module_css_default.pending,
3000
+ title: props.t("settings.unsaved"),
3001
+ children: props.t("settings.unsaved")
3002
+ }) : null,
3003
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2360
3004
  width: "14",
2361
3005
  height: "14",
2362
3006
  viewBox: "0 0 14 14",
@@ -2367,8 +3011,12 @@ window.__ModuleLoader__.load({
2367
3011
  d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
2368
3012
  fill: "currentColor"
2369
3013
  })
2370
- })]
2371
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3014
+ })
3015
+ ]
3016
+ });
3017
+ if (!state.exposed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
3018
+ className: cardClass,
3019
+ children: [header, expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2372
3020
  className: settings_card_module_css_default.body,
2373
3021
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2374
3022
  className: settings_card_module_css_default.notExposed,
@@ -2379,46 +3027,7 @@ window.__ModuleLoader__.load({
2379
3027
  });
2380
3028
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2381
3029
  className: cardClass,
2382
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2383
- type: "button",
2384
- className: settings_card_module_css_default.header,
2385
- "aria-expanded": open,
2386
- "aria-label": `${props.t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
2387
- onClick: () => {
2388
- setOpen(!open);
2389
- },
2390
- children: [
2391
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2392
- className: settings_card_module_css_default.headText,
2393
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2394
- className: settings_card_module_css_default.name,
2395
- title,
2396
- children: title
2397
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2398
- className: settings_card_module_css_default.description,
2399
- title: description,
2400
- children: description
2401
- })]
2402
- }),
2403
- state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2404
- className: settings_card_module_css_default.pending,
2405
- title: props.t("settings.unsaved"),
2406
- children: props.t("settings.unsaved")
2407
- }) : null,
2408
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2409
- width: "14",
2410
- height: "14",
2411
- viewBox: "0 0 14 14",
2412
- fill: "none",
2413
- xmlns: "http://www.w3.org/2000/svg",
2414
- className: open ? `${settings_card_module_css_default.chevron} ${settings_card_module_css_default.chevronOpen}` : settings_card_module_css_default.chevron,
2415
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2416
- d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
2417
- fill: "currentColor"
2418
- })
2419
- })
2420
- ]
2421
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3030
+ children: [header, expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2422
3031
  className: settings_card_module_css_default.body,
2423
3032
  children: [
2424
3033
  !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -2883,12 +3492,50 @@ window.__ModuleLoader__.load({
2883
3492
  exec: new ExecutionService({
2884
3493
  sessions: {
2885
3494
  list: sessions.list,
2886
- binding: (id) => sessions.binding(id)
3495
+ binding: (id) => {
3496
+ const binding = sessions.binding(id);
3497
+ if (binding === void 0) return void 0;
3498
+ const { session } = binding;
3499
+ return { session: {
3500
+ rename: (title) => session.rename(title),
3501
+ prompt: (content, mode) => session.prompt(content, mode).then((result) => result.ok ? { ok: true } : {
3502
+ ok: false,
3503
+ error: result.error
3504
+ }),
3505
+ command: (line) => session.command(line).then((result) => result.ok ? {
3506
+ ok: true,
3507
+ matched: result.value.matched
3508
+ } : {
3509
+ ok: false,
3510
+ error: result.error
3511
+ }),
3512
+ getSnapshot: () => session.getSnapshot(),
3513
+ subscribe: (fn) => session.subscribe(fn)
3514
+ } };
3515
+ },
3516
+ noteAgentPreset: (sessionId, agentPreset) => sessions.noteAgentPreset(sessionId, agentPreset)
2887
3517
  },
2888
3518
  workspaces: {
2889
3519
  list: workspaces.list,
2890
3520
  connectWorkspace: (id) => workspaces.connectWorkspace(id)
2891
3521
  },
3522
+ presets: { select: async (sessionId, agentPreset) => {
3523
+ try {
3524
+ const response = await connection.api.agentPresets.select({
3525
+ sessionId,
3526
+ agentPreset
3527
+ });
3528
+ return response.result.ok ? { ok: true } : {
3529
+ ok: false,
3530
+ error: response.result.error
3531
+ };
3532
+ } catch (error) {
3533
+ return {
3534
+ ok: false,
3535
+ error
3536
+ };
3537
+ }
3538
+ } },
2892
3539
  history: { loadTail: async (sessionId) => {
2893
3540
  const response = await connection.api.sessions.history({
2894
3541
  sessionId,
@@ -2905,6 +3552,7 @@ window.__ModuleLoader__.load({
2905
3552
  controller.start();
2906
3553
  const scheduler = new SchedulerService({
2907
3554
  tasks: () => controller.getSnapshot().tasks,
3555
+ refresh: () => controller.reloadFromStore(),
2908
3556
  now: () => Date.now(),
2909
3557
  runTask: (id) => controller.runTask(id),
2910
3558
  applySchedule: (id, nextRunAt, lastTriggeredAt) => controller.applyScheduleNextRun(id, nextRunAt, lastTriggeredAt),
@@ -2916,6 +3564,34 @@ window.__ModuleLoader__.load({
2916
3564
  });
2917
3565
  scheduler.start();
2918
3566
  const disposers = [];
3567
+ const pushWorkspaceOptions = () => {
3568
+ const snapshot = workspaces.list.getSnapshot();
3569
+ controller.setExecutionOptions({ workspaces: snapshot.items.map((item) => ({
3570
+ workspaceId: item.workspaceId,
3571
+ title: item.title !== "" ? item.title : item.path
3572
+ })) });
3573
+ };
3574
+ pushWorkspaceOptions();
3575
+ disposers.push(workspaces.list.subscribe(pushWorkspaceOptions));
3576
+ const pushPresetOptions = async () => {
3577
+ try {
3578
+ const response = await connection.api.agentPresets.list({});
3579
+ if (!response.result.ok) return;
3580
+ controller.setExecutionOptions({ presets: response.result.value.presets.map((preset) => ({
3581
+ id: preset.id,
3582
+ name: preset.name,
3583
+ description: preset.description,
3584
+ broken: preset.broken,
3585
+ isDefault: preset.isDefault
3586
+ })) });
3587
+ } catch (error) {
3588
+ console.error("[dsh-task-board] agent preset roster read failed", error);
3589
+ }
3590
+ };
3591
+ pushPresetOptions();
3592
+ disposers.push(ctx.on("connection/reset", () => {
3593
+ pushPresetOptions();
3594
+ }));
2919
3595
  try {
2920
3596
  disposers.push(mountSidebarEntry(controller));
2921
3597
  disposers.push(mountBoard(controller));