dsh-taskboard 0.2.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -4
- package/lib/client.js +818 -77
- package/lib/host/execution.js +194 -54
- package/lib/host/execution.js.map +1 -1
- package/lib/host/git.js +234 -0
- package/lib/host/git.js.map +1 -0
- package/lib/host/routes.js +282 -5
- package/lib/host/routes.js.map +1 -1
- package/lib/host/tools.js +16 -2
- package/lib/host/tools.js.map +1 -1
- package/lib/index.js +17 -2
- package/lib/index.js.map +1 -1
- package/lib/shared/api.js.map +1 -1
- package/lib/shared/protocol.js +10 -1
- package/lib/shared/protocol.js.map +1 -1
- package/package.json +74 -74
- package/src/client/api.ts +23 -3
- package/src/client/board/TaskBoard.tsx +73 -0
- package/src/client/board/TaskCard.tsx +73 -5
- package/src/client/board/TaskDetail.tsx +173 -1
- package/src/client/board/TaskFormModal.tsx +100 -1
- package/src/client/controller.ts +108 -6
- package/src/client/index.ts +18 -1
- package/src/client/styles.ts +60 -0
- package/src/host/execution.ts +291 -65
- package/src/host/git.ts +293 -0
- package/src/host/routes.ts +294 -6
- package/src/host/tools.ts +17 -0
- package/src/index.ts +24 -1
- package/src/shared/api.ts +42 -3
- package/src/shared/protocol.ts +64 -0
- package/src/shared/version.ts +1 -1
package/lib/client.js
CHANGED
|
@@ -34,10 +34,18 @@ window.__ModuleLoader__.load({
|
|
|
34
34
|
get: (id) => unwrap(fetch(`/dsh-taskboard/tasks/${encodeURIComponent(id)}`)),
|
|
35
35
|
update: (id, body) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/update`, body),
|
|
36
36
|
move: (id, body) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/move`, body),
|
|
37
|
+
reject: (id, body) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/reject`, body),
|
|
37
38
|
comment: (id, bodyText) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/comment`, { body: bodyText }),
|
|
38
39
|
remove: (id, body) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/delete`, body),
|
|
39
|
-
run: (id) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/run`, {}),
|
|
40
|
+
run: (id, body) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/run`, body ?? {}),
|
|
40
41
|
cancel: (id) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/cancel`, {}),
|
|
42
|
+
mergeBranch: (id) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/merge`, {}),
|
|
43
|
+
worktreeRemove: (id, body) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/worktree-remove`, body),
|
|
44
|
+
diagnostics: () => unwrap(fetch("/dsh-taskboard/diagnostics")),
|
|
45
|
+
worktreeCleanup: (workspaceId, taskId) => post("/dsh-taskboard/worktree-cleanup", {
|
|
46
|
+
workspaceId,
|
|
47
|
+
taskId
|
|
48
|
+
}),
|
|
41
49
|
stream(onChange, onGap) {
|
|
42
50
|
const es = new EventSource("/dsh-taskboard/events");
|
|
43
51
|
let revision;
|
|
@@ -208,6 +216,22 @@ window.__ModuleLoader__.load({
|
|
|
208
216
|
//#region src/client/controller.ts
|
|
209
217
|
/** localStorage key for persisted view state (filters + sort). */
|
|
210
218
|
const VIEW_KEY = "dsh-taskboard-view-v1";
|
|
219
|
+
/** localStorage key for the remembered isolation toggle choice (0.3.0). */
|
|
220
|
+
const ISOLATION_KEY = "dsh-taskboard-isolation-v1";
|
|
221
|
+
/** Load the remembered default isolation (worktree unless explicitly turned off). */
|
|
222
|
+
function loadDefaultIsolation() {
|
|
223
|
+
try {
|
|
224
|
+
return localStorage.getItem(ISOLATION_KEY) === "none" ? "none" : "worktree";
|
|
225
|
+
} catch {
|
|
226
|
+
return "worktree";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** Remember the isolation toggle choice across forms (best effort). */
|
|
230
|
+
function saveDefaultIsolation(mode) {
|
|
231
|
+
try {
|
|
232
|
+
localStorage.setItem(ISOLATION_KEY, mode);
|
|
233
|
+
} catch {}
|
|
234
|
+
}
|
|
211
235
|
/** Load the persisted view state (never throws; fresh on any parse error). */
|
|
212
236
|
function loadView() {
|
|
213
237
|
try {
|
|
@@ -244,7 +268,8 @@ window.__ModuleLoader__.load({
|
|
|
244
268
|
search: "",
|
|
245
269
|
sortBy: view.sortBy,
|
|
246
270
|
composerOpen: false,
|
|
247
|
-
secondaryOpen: false
|
|
271
|
+
secondaryOpen: false,
|
|
272
|
+
diagOpen: false
|
|
248
273
|
};
|
|
249
274
|
}
|
|
250
275
|
/**
|
|
@@ -402,6 +427,11 @@ window.__ModuleLoader__.load({
|
|
|
402
427
|
toggleSecondary() {
|
|
403
428
|
this.setState({ secondaryOpen: !this.state.secondaryOpen });
|
|
404
429
|
}
|
|
430
|
+
/** Whether a workspace passed git detection (form toggle enablement). */
|
|
431
|
+
gitAvailable(workspaceId) {
|
|
432
|
+
if (workspaceId === void 0) return true;
|
|
433
|
+
return this.state.workspaces.find((w) => w.id === workspaceId)?.gitAvailable === true;
|
|
434
|
+
}
|
|
405
435
|
/**
|
|
406
436
|
* Install the session-jump bridge (built from the runtime sessions service
|
|
407
437
|
* by the client entry). Without it openSession reports 'unavailable'.
|
|
@@ -474,6 +504,27 @@ window.__ModuleLoader__.load({
|
|
|
474
504
|
this.setState({ error: error instanceof Error ? error.message : String(error) });
|
|
475
505
|
}
|
|
476
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* Quick-reject (card ✗ button): move back to todo with an optional user
|
|
509
|
+
* comment, committed atomically host-side. Returns whether the task moved.
|
|
510
|
+
* @param id - task id.
|
|
511
|
+
* @param ifVersion - optimistic version (captured at click time).
|
|
512
|
+
* @param comment - optional comment text; blank = move only.
|
|
513
|
+
*/
|
|
514
|
+
async reject(id, ifVersion, comment) {
|
|
515
|
+
const body = comment !== void 0 && comment.trim().length > 0 ? comment.trim() : void 0;
|
|
516
|
+
try {
|
|
517
|
+
await this.client.reject(id, body === void 0 ? { ifVersion } : {
|
|
518
|
+
ifVersion,
|
|
519
|
+
body
|
|
520
|
+
});
|
|
521
|
+
await this.refresh();
|
|
522
|
+
return true;
|
|
523
|
+
} catch (error) {
|
|
524
|
+
this.setState({ error: error instanceof Error ? error.message : String(error) });
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
477
528
|
/** Toggle the blocked marker. */
|
|
478
529
|
async toggleBlocked(task) {
|
|
479
530
|
try {
|
|
@@ -495,10 +546,10 @@ window.__ModuleLoader__.load({
|
|
|
495
546
|
this.setState({ error: error instanceof Error ? error.message : String(error) });
|
|
496
547
|
}
|
|
497
548
|
}
|
|
498
|
-
/** Trigger a manual run (fresh in-project session, pinned model)
|
|
499
|
-
async run(id) {
|
|
549
|
+
/** Trigger a manual run (fresh in-project session, pinned model); `reuse` = 续跑. */
|
|
550
|
+
async run(id, reuse = false) {
|
|
500
551
|
try {
|
|
501
|
-
await this.client.run(id);
|
|
552
|
+
await this.client.run(id, reuse ? { reuse: true } : {});
|
|
502
553
|
await this.refresh();
|
|
503
554
|
} catch (error) {
|
|
504
555
|
this.setState({ error: error instanceof Error ? error.message : String(error) });
|
|
@@ -513,6 +564,66 @@ window.__ModuleLoader__.load({
|
|
|
513
564
|
this.setState({ error: error instanceof Error ? error.message : String(error) });
|
|
514
565
|
}
|
|
515
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* ⇥ 合并 (detail page): merge the task branch into the main worktree.
|
|
569
|
+
* @returns the outcome; `noop` means the branch had no new commits (nothing merged).
|
|
570
|
+
*/
|
|
571
|
+
async mergeBranch(id) {
|
|
572
|
+
try {
|
|
573
|
+
const value = await this.client.mergeBranch(id);
|
|
574
|
+
await this.refresh();
|
|
575
|
+
return value.noop === true ? {
|
|
576
|
+
ok: true,
|
|
577
|
+
noop: true
|
|
578
|
+
} : { ok: true };
|
|
579
|
+
} catch (error) {
|
|
580
|
+
return {
|
|
581
|
+
ok: false,
|
|
582
|
+
error: error instanceof Error ? error.message : String(error)
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* 🗑 删除 worktree (detail page), optionally deleting the task branch too.
|
|
588
|
+
* @returns the outcome; failures carry the git message for an alert.
|
|
589
|
+
*/
|
|
590
|
+
async removeWorktree(id, deleteBranch) {
|
|
591
|
+
try {
|
|
592
|
+
const value = await this.client.worktreeRemove(id, { deleteBranch });
|
|
593
|
+
await this.refresh();
|
|
594
|
+
return value.branchError !== void 0 ? {
|
|
595
|
+
ok: true,
|
|
596
|
+
branchError: value.branchError
|
|
597
|
+
} : { ok: true };
|
|
598
|
+
} catch (error) {
|
|
599
|
+
return {
|
|
600
|
+
ok: false,
|
|
601
|
+
error: error instanceof Error ? error.message : String(error)
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/** Open the ⚙ diagnostics panel and fetch a fresh snapshot. */
|
|
606
|
+
openDiagnostics() {
|
|
607
|
+
this.setState({ diagOpen: true });
|
|
608
|
+
this.client.diagnostics().then((diagnostics) => this.setState({ diagnostics })).catch((error) => this.setState({ error: error instanceof Error ? error.message : String(error) }));
|
|
609
|
+
}
|
|
610
|
+
/** Close the ⚙ diagnostics panel. */
|
|
611
|
+
closeDiagnostics() {
|
|
612
|
+
this.setState({ diagOpen: false });
|
|
613
|
+
}
|
|
614
|
+
/** Clean one orphan worktree (⚙ panel); refreshes the diagnostics payload. */
|
|
615
|
+
async cleanupOrphan(workspaceId, taskId) {
|
|
616
|
+
try {
|
|
617
|
+
await this.client.worktreeCleanup(workspaceId, taskId);
|
|
618
|
+
const diagnostics = await this.client.diagnostics();
|
|
619
|
+
this.setState({
|
|
620
|
+
diagnostics,
|
|
621
|
+
error: void 0
|
|
622
|
+
});
|
|
623
|
+
} catch (error) {
|
|
624
|
+
this.setState({ error: error instanceof Error ? error.message : String(error) });
|
|
625
|
+
}
|
|
626
|
+
}
|
|
516
627
|
/** Soft-delete (agent parity) then optional purge. */
|
|
517
628
|
async remove(id, ifVersion, purge) {
|
|
518
629
|
try {
|
|
@@ -523,7 +634,7 @@ window.__ModuleLoader__.load({
|
|
|
523
634
|
this.setState({ error: error instanceof Error ? error.message : String(error) });
|
|
524
635
|
}
|
|
525
636
|
}
|
|
526
|
-
/** Duplicate a task into a fresh todo card (same project/urgency/prompt/execution/model). */
|
|
637
|
+
/** Duplicate a task into a fresh todo card (same project/urgency/prompt/execution/model/isolation). */
|
|
527
638
|
async duplicate(task) {
|
|
528
639
|
try {
|
|
529
640
|
await this.client.create({
|
|
@@ -536,7 +647,9 @@ window.__ModuleLoader__.load({
|
|
|
536
647
|
mode: "scheduled",
|
|
537
648
|
cron: task.execution.cron
|
|
538
649
|
} : { mode: "claim" },
|
|
539
|
-
model: task.model
|
|
650
|
+
model: task.model,
|
|
651
|
+
isolation: task.isolation,
|
|
652
|
+
...task.presetId !== void 0 ? { presetId: task.presetId } : {}
|
|
540
653
|
});
|
|
541
654
|
await this.refresh();
|
|
542
655
|
} catch (error) {
|
|
@@ -763,6 +876,21 @@ window.__ModuleLoader__.load({
|
|
|
763
876
|
.dsh-atb-badge[data-kind="done"] { background: rgba(46,160,67,.16); color: #2ea043; }
|
|
764
877
|
.dsh-atb-badge[data-kind="running"] { background: rgba(229,152,42,.16); color: #e69842; }
|
|
765
878
|
|
|
879
|
+
/* ---------- card quick review (in_review column) ---------- */
|
|
880
|
+
.dsh-atb-quick { display: flex; gap: 6px; margin-top: 7px; }
|
|
881
|
+
.dsh-atb-quickbtn {
|
|
882
|
+
flex: 1; font-size: 11.5px; padding: 3px 8px; border-radius: 6px; cursor: pointer;
|
|
883
|
+
border: 1px solid var(--dsw-border, rgba(128,128,128,.3));
|
|
884
|
+
background: var(--dsw-bg-elevated, rgba(128,128,128,.12)); color: inherit;
|
|
885
|
+
}
|
|
886
|
+
.dsh-atb-quickbtn:hover { border-color: var(--dsw-border-strong, rgba(128,128,128,.6)); }
|
|
887
|
+
.dsh-atb-quickbtn[data-act="done"] { background: rgba(46,160,67,.14); color: #2ea043; border-color: rgba(46,160,67,.4); }
|
|
888
|
+
.dsh-atb-quickbtn[data-act="done"]:hover { background: rgba(46,160,67,.22); }
|
|
889
|
+
.dsh-atb-quickbtn[data-act="reject"] { background: rgba(229,152,42,.12); color: #d9822b; border-color: rgba(229,152,42,.4); }
|
|
890
|
+
.dsh-atb-quickbtn[data-act="reject"]:hover { background: rgba(229,152,42,.2); }
|
|
891
|
+
.dsh-atb-quick-reject { display: flex; gap: 6px; margin-top: 7px; align-items: stretch; }
|
|
892
|
+
.dsh-atb-quick-note { flex: 1; min-width: 0; font-size: 11.5px; padding: 3px 8px; }
|
|
893
|
+
|
|
766
894
|
.dsh-atb-error { font-size: 12px; color: #e5484d; padding: 4px 8px; border-radius: 6px; background: rgba(229,72,77,.1); }
|
|
767
895
|
.dsh-atb-empty { font-size: 12px; color: var(--dsw-text-secondary, gray); padding: 10px 4px; }
|
|
768
896
|
|
|
@@ -1068,6 +1196,51 @@ window.__ModuleLoader__.load({
|
|
|
1068
1196
|
color: var(--dsw-alias-label-primary, inherit);
|
|
1069
1197
|
}
|
|
1070
1198
|
.dsh-atb-alert .dsh-atb-btn { padding: 6px 28px; font-size: 13px; }
|
|
1199
|
+
|
|
1200
|
+
/* ---------- 0.3.0 isolation ---------- */
|
|
1201
|
+
.dsh-atb-isolation-note { display: block; margin-top: 6px; font-size: 11px; color: var(--dsw-alias-label-tertiary, gray); }
|
|
1202
|
+
.dsh-atb-mode-picker[data-disabled="true"] .dsh-atb-mode-opt { cursor: not-allowed; opacity: .55; }
|
|
1203
|
+
.dsh-atb-iso-none { font-size: 12.5px; color: var(--dsw-alias-label-secondary, inherit); }
|
|
1204
|
+
.dsh-atb-iso-facts { display: flex; flex-wrap: wrap; gap: 6px 12px; margin-bottom: 8px; }
|
|
1205
|
+
.dsh-atb-iso-fact { font-size: 11.5px; color: var(--dsw-alias-label-secondary, inherit); }
|
|
1206
|
+
.dsh-atb-iso-fact b { font-weight: 600; color: var(--dsw-alias-state-business-primary, #3e63dd); }
|
|
1207
|
+
.dsh-atb-iso-commits { display: flex; flex-direction: column; gap: 3px; margin-bottom: 8px; }
|
|
1208
|
+
.dsh-atb-iso-commit { display: flex; gap: 8px; font-size: 11.5px; align-items: baseline; }
|
|
1209
|
+
.dsh-atb-iso-commit code {
|
|
1210
|
+
font-family: ui-monospace, Consolas, monospace; font-size: 10.5px;
|
|
1211
|
+
color: var(--dsh-alias-state-business-primary, #3e63dd); flex-shrink: 0;
|
|
1212
|
+
}
|
|
1213
|
+
.dsh-atb-iso-commit span { word-break: break-all; color: var(--dsw-alias-label-secondary, inherit); }
|
|
1214
|
+
.dsh-atb-iso-more { font-size: 11px; color: var(--dsw-alias-label-tertiary, gray); }
|
|
1215
|
+
.dsh-atb-iso-nocommit { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, gray); margin-bottom: 8px; }
|
|
1216
|
+
.dsh-atb-iso-dirty {
|
|
1217
|
+
font-size: 11.5px; color: var(--dsw-alias-state-error-primary, #e5484d);
|
|
1218
|
+
background: rgba(229,72,77,.09); border: 1px solid rgba(229,72,77,.35);
|
|
1219
|
+
border-radius: 8px; padding: 6px 10px; margin-bottom: 8px;
|
|
1220
|
+
}
|
|
1221
|
+
.dsh-atb-iso-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
1222
|
+
.dsh-atb-iso-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary, gray); }
|
|
1223
|
+
|
|
1224
|
+
/* ---------- 0.3.0 diagnostics ---------- */
|
|
1225
|
+
.dsh-atb-diag { max-width: 520px; width: min(520px, 92vw); }
|
|
1226
|
+
.dsh-atb-diag-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 14px; }
|
|
1227
|
+
.dsh-atb-diag-item {
|
|
1228
|
+
display: flex; flex-direction: column; align-items: center; gap: 2px;
|
|
1229
|
+
padding: 10px 6px; border-radius: 10px;
|
|
1230
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.25));
|
|
1231
|
+
background: var(--dsw-alias-bg-layer-1, rgba(128,128,128,.04));
|
|
1232
|
+
}
|
|
1233
|
+
.dsh-atb-diag-item b { font-size: 18px; font-weight: 700; }
|
|
1234
|
+
.dsh-atb-diag-item span { font-size: 10.5px; color: var(--dsw-alias-label-tertiary, gray); }
|
|
1235
|
+
.dsh-atb-diag-item[data-bad="true"] b { color: var(--dsw-alias-state-error-primary, #e5484d); }
|
|
1236
|
+
.dsh-atb-diag-sec h4 { margin: 0 0 8px; font-size: 12.5px; }
|
|
1237
|
+
.dsh-atb-diag-orphans { display: flex; flex-direction: column; gap: 6px; }
|
|
1238
|
+
.dsh-atb-diag-orphan {
|
|
1239
|
+
display: flex; align-items: center; gap: 10px; justify-content: space-between;
|
|
1240
|
+
padding: 7px 10px; border-radius: 8px;
|
|
1241
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.25));
|
|
1242
|
+
}
|
|
1243
|
+
.dsh-atb-diag-orphan-path { font-size: 11.5px; font-family: ui-monospace, Consolas, monospace; word-break: break-all; }
|
|
1071
1244
|
`;
|
|
1072
1245
|
let injected = false;
|
|
1073
1246
|
/** Inject the stylesheet once (idempotent). */
|
|
@@ -1312,10 +1485,23 @@ window.__ModuleLoader__.load({
|
|
|
1312
1485
|
* @module dsh-taskboard/shared/version
|
|
1313
1486
|
*/
|
|
1314
1487
|
/** The package version (must equal package.json "version"). */
|
|
1315
|
-
const PLUGIN_VERSION = "0.
|
|
1488
|
+
const PLUGIN_VERSION = "0.3.3";
|
|
1316
1489
|
|
|
1317
1490
|
//#endregion
|
|
1318
1491
|
//#region src/client/board/TaskCard.tsx
|
|
1492
|
+
/**
|
|
1493
|
+
* One board card: urgency edge, title, project/urgency/model/schedule/
|
|
1494
|
+
* blocked/trashed badges, comment count, and the last execution outcome.
|
|
1495
|
+
* Click opens the detail pane; cards in the backlog/todo columns are
|
|
1496
|
+
* draggable between those two columns (HTML5 drag & drop). Cards sitting
|
|
1497
|
+
* in the in_review column also carry quick-review actions (✓ complete /
|
|
1498
|
+
* ✗ send back with an optional note).
|
|
1499
|
+
*
|
|
1500
|
+
* The root is a div[role=button] (not a <button>) so the quick actions can
|
|
1501
|
+
* be real nested buttons — valid HTML and native keyboard activation.
|
|
1502
|
+
*
|
|
1503
|
+
* @module dsh-taskboard/client/board/TaskCard
|
|
1504
|
+
*/
|
|
1319
1505
|
const URGENCY_LABEL$1 = {
|
|
1320
1506
|
urgent: "紧急",
|
|
1321
1507
|
normal: "一般",
|
|
@@ -1338,14 +1524,27 @@ window.__ModuleLoader__.load({
|
|
|
1338
1524
|
* @param onAlert - show an alert message (replaces native alert).
|
|
1339
1525
|
*/
|
|
1340
1526
|
function TaskCard({ task, controller, draggable = false, now, onAlert }) {
|
|
1527
|
+
const [rejectOpen, setRejectOpen] = (0, react.useState)(false);
|
|
1528
|
+
const [note, setNote] = (0, react.useState)("");
|
|
1341
1529
|
const last = task.executions.length > 0 ? task.executions[task.executions.length - 1] : void 0;
|
|
1342
1530
|
const running = task.executions.find((ex) => ex.outcome === "running");
|
|
1343
1531
|
const stale = now !== void 0 && isStaleClaim(task, now);
|
|
1344
|
-
|
|
1345
|
-
|
|
1532
|
+
const reviewing = task.status === "in_review" && task.trashedAt === void 0;
|
|
1533
|
+
/** Submit the quick-reject: one atomic route (move + optional note). */
|
|
1534
|
+
const submitReject = () => {
|
|
1535
|
+
controller.reject(task.id, task.version, note).then((ok) => {
|
|
1536
|
+
if (ok) {
|
|
1537
|
+
setRejectOpen(false);
|
|
1538
|
+
setNote("");
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
};
|
|
1542
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1543
|
+
role: "button",
|
|
1544
|
+
tabIndex: 0,
|
|
1346
1545
|
className: "dsh-atb-card",
|
|
1347
1546
|
"data-urgency": task.urgency,
|
|
1348
|
-
draggable,
|
|
1547
|
+
draggable: draggable && !rejectOpen,
|
|
1349
1548
|
onDragStart: (e) => {
|
|
1350
1549
|
if (running !== void 0) {
|
|
1351
1550
|
e.preventDefault();
|
|
@@ -1362,57 +1561,123 @@ window.__ModuleLoader__.load({
|
|
|
1362
1561
|
delete e.currentTarget.dataset.dragging;
|
|
1363
1562
|
},
|
|
1364
1563
|
onClick: () => controller.select(task.id),
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
task.
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1564
|
+
onKeyDown: (e) => {
|
|
1565
|
+
if (e.target !== e.currentTarget) return;
|
|
1566
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1567
|
+
e.preventDefault();
|
|
1568
|
+
controller.select(task.id);
|
|
1569
|
+
}
|
|
1570
|
+
},
|
|
1571
|
+
children: [
|
|
1572
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1573
|
+
className: "dsh-atb-card-title",
|
|
1574
|
+
children: task.title
|
|
1575
|
+
}),
|
|
1576
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1577
|
+
className: "dsh-atb-card-meta",
|
|
1578
|
+
children: [
|
|
1579
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1580
|
+
className: "dsh-atb-badge",
|
|
1581
|
+
children: URGENCY_LABEL$1[task.urgency]
|
|
1582
|
+
}),
|
|
1583
|
+
task.blocked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1584
|
+
className: "dsh-atb-badge",
|
|
1585
|
+
"data-kind": "blocked",
|
|
1586
|
+
children: "受阻"
|
|
1587
|
+
}),
|
|
1588
|
+
stale && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1589
|
+
className: "dsh-atb-badge",
|
|
1590
|
+
"data-kind": "stale",
|
|
1591
|
+
children: "⏱ 认领超时"
|
|
1592
|
+
}),
|
|
1593
|
+
task.execution.mode === "scheduled" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1594
|
+
className: "dsh-atb-badge",
|
|
1595
|
+
"data-kind": "scheduled",
|
|
1596
|
+
children: ["⏰ ", fmtTime(task.execution.nextRunAt)]
|
|
1597
|
+
}),
|
|
1598
|
+
task.model !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1599
|
+
className: "dsh-atb-badge",
|
|
1600
|
+
children: task.model.model
|
|
1601
|
+
}),
|
|
1602
|
+
task.status === "done" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1603
|
+
className: "dsh-atb-badge",
|
|
1604
|
+
"data-kind": "done",
|
|
1605
|
+
children: "完成"
|
|
1606
|
+
}),
|
|
1607
|
+
last !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1608
|
+
className: "dsh-atb-badge",
|
|
1609
|
+
"data-kind": last.outcome === "running" ? "running" : last.outcome,
|
|
1610
|
+
children: OUTCOME_LABEL$1[last.outcome] ?? last.outcome
|
|
1611
|
+
}),
|
|
1612
|
+
task.comments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["💬 ", task.comments.length] }),
|
|
1613
|
+
task.trashedAt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1614
|
+
className: "dsh-atb-badge",
|
|
1615
|
+
"data-kind": "trashed",
|
|
1616
|
+
children: "待清除"
|
|
1617
|
+
}),
|
|
1618
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1619
|
+
style: { marginLeft: "auto" },
|
|
1620
|
+
children: fmtTime(task.updatedAt)
|
|
1621
|
+
})
|
|
1622
|
+
]
|
|
1623
|
+
}),
|
|
1624
|
+
reviewing && (rejectOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1625
|
+
className: "dsh-atb-quick-reject",
|
|
1626
|
+
onClick: (e) => e.stopPropagation(),
|
|
1627
|
+
children: [
|
|
1628
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1629
|
+
className: "dsh-atb-input dsh-atb-quick-note",
|
|
1630
|
+
value: note,
|
|
1631
|
+
placeholder: "退回原因(可选,agent 开工前会读)…",
|
|
1632
|
+
autoFocus: true,
|
|
1633
|
+
spellCheck: false,
|
|
1634
|
+
onChange: (e) => setNote(e.target.value),
|
|
1635
|
+
onKeyDown: (e) => {
|
|
1636
|
+
if (e.key === "Enter") submitReject();
|
|
1637
|
+
else if (e.key === "Escape") {
|
|
1638
|
+
setRejectOpen(false);
|
|
1639
|
+
setNote("");
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}),
|
|
1643
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1644
|
+
type: "button",
|
|
1645
|
+
className: "dsh-atb-quickbtn",
|
|
1646
|
+
"data-act": "reject-confirm",
|
|
1647
|
+
onClick: submitReject,
|
|
1648
|
+
children: "退回待办"
|
|
1649
|
+
}),
|
|
1650
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1651
|
+
type: "button",
|
|
1652
|
+
className: "dsh-atb-quickbtn",
|
|
1653
|
+
"data-act": "reject-cancel",
|
|
1654
|
+
onClick: () => {
|
|
1655
|
+
setRejectOpen(false);
|
|
1656
|
+
setNote("");
|
|
1657
|
+
},
|
|
1658
|
+
children: "取消"
|
|
1659
|
+
})
|
|
1660
|
+
]
|
|
1661
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1662
|
+
className: "dsh-atb-quick",
|
|
1663
|
+
onClick: (e) => e.stopPropagation(),
|
|
1664
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1665
|
+
type: "button",
|
|
1666
|
+
className: "dsh-atb-quickbtn",
|
|
1667
|
+
"data-act": "done",
|
|
1668
|
+
title: "验收完成:移至已完成",
|
|
1669
|
+
onClick: () => void controller.move(task.id, task.version, "done"),
|
|
1670
|
+
children: "✓ 完成"
|
|
1671
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1672
|
+
type: "button",
|
|
1673
|
+
className: "dsh-atb-quickbtn",
|
|
1674
|
+
"data-act": "reject",
|
|
1675
|
+
title: "退回待办,可附退回原因",
|
|
1676
|
+
onClick: () => setRejectOpen(true),
|
|
1677
|
+
children: "✗ 退回"
|
|
1678
|
+
})]
|
|
1679
|
+
}))
|
|
1680
|
+
]
|
|
1416
1681
|
});
|
|
1417
1682
|
}
|
|
1418
1683
|
|
|
@@ -1540,6 +1805,215 @@ window.__ModuleLoader__.load({
|
|
|
1540
1805
|
}), children]
|
|
1541
1806
|
});
|
|
1542
1807
|
}
|
|
1808
|
+
/** The most recent execution carrying isolation facts, newest first. */
|
|
1809
|
+
function latestIsolated(task) {
|
|
1810
|
+
return [...task.executions].reverse().find((e) => e.isolation !== void 0 || e.worktreePath !== void 0 || e.isolationNote !== void 0);
|
|
1811
|
+
}
|
|
1812
|
+
/** Short commit hash for display. */
|
|
1813
|
+
function shortHash(hash) {
|
|
1814
|
+
return hash === void 0 ? "" : hash.slice(0, 8);
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* The 0.3.0 isolation block: branch / baseline→head commits / change stats /
|
|
1818
|
+
* uncommitted-changes warning, plus the user-only git actions (merge /
|
|
1819
|
+
* remove worktree — plan §3.3).
|
|
1820
|
+
*/
|
|
1821
|
+
function IsolationBlock({ task, controller }) {
|
|
1822
|
+
const { alert: showAlert, el: alertEl } = useAlert();
|
|
1823
|
+
const [confirmMerge, setConfirmMerge] = (0, react.useState)(false);
|
|
1824
|
+
const [confirmRemove, setConfirmRemove] = (0, react.useState)(null);
|
|
1825
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
1826
|
+
const execution = latestIsolated(task);
|
|
1827
|
+
const running = task.executions.some((e) => e.outcome === "running");
|
|
1828
|
+
if (execution === void 0) return null;
|
|
1829
|
+
const doMerge = () => {
|
|
1830
|
+
setBusy(true);
|
|
1831
|
+
controller.mergeBranch(task.id).then((result) => {
|
|
1832
|
+
setBusy(false);
|
|
1833
|
+
setConfirmMerge(false);
|
|
1834
|
+
if (!result.ok) showAlert(`合并失败:${result.error}`);
|
|
1835
|
+
else if (result.noop === true) showAlert("该分支没有领先主工作区的新提交,无需合并(可退回续跑或直接清理)");
|
|
1836
|
+
});
|
|
1837
|
+
};
|
|
1838
|
+
const doRemove = (deleteBranch) => {
|
|
1839
|
+
setBusy(true);
|
|
1840
|
+
controller.removeWorktree(task.id, deleteBranch).then((result) => {
|
|
1841
|
+
setBusy(false);
|
|
1842
|
+
setConfirmRemove(null);
|
|
1843
|
+
if (!result.ok) showAlert(`删除失败:${result.error}`);
|
|
1844
|
+
else if (result.branchError !== void 0) showAlert(`worktree 已删除,但分支删除失败:${result.branchError}`);
|
|
1845
|
+
});
|
|
1846
|
+
};
|
|
1847
|
+
if (execution.isolation !== "worktree" || execution.worktreePath === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1848
|
+
className: "dsh-atb-fieldcard",
|
|
1849
|
+
"data-kind": "isolation",
|
|
1850
|
+
children: [
|
|
1851
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1852
|
+
className: "dsh-atb-fieldcard-label",
|
|
1853
|
+
children: "执行隔离"
|
|
1854
|
+
}),
|
|
1855
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1856
|
+
className: "dsh-atb-iso-none",
|
|
1857
|
+
children: ["📁 原目录执行", execution.isolationNote !== void 0 ? ` · ${execution.isolationNote}` : ""]
|
|
1858
|
+
}),
|
|
1859
|
+
alertEl
|
|
1860
|
+
]
|
|
1861
|
+
});
|
|
1862
|
+
const commits = execution.commits ?? [];
|
|
1863
|
+
const commitTotal = execution.commitsTotal ?? commits.length;
|
|
1864
|
+
const dirty = execution.dirtyFiles ?? [];
|
|
1865
|
+
const dirtyTotal = execution.dirtyFilesTotal ?? dirty.length;
|
|
1866
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1867
|
+
className: "dsh-atb-fieldcard",
|
|
1868
|
+
"data-kind": "isolation",
|
|
1869
|
+
children: [
|
|
1870
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1871
|
+
className: "dsh-atb-fieldcard-label",
|
|
1872
|
+
children: "执行隔离 · Worktree"
|
|
1873
|
+
}),
|
|
1874
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1875
|
+
className: "dsh-atb-iso-facts",
|
|
1876
|
+
children: [
|
|
1877
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1878
|
+
className: "dsh-atb-iso-fact",
|
|
1879
|
+
title: execution.worktreePath,
|
|
1880
|
+
children: ["🌿 分支 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: execution.branch ?? task.branch })]
|
|
1881
|
+
}),
|
|
1882
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1883
|
+
className: "dsh-atb-iso-fact",
|
|
1884
|
+
children: [
|
|
1885
|
+
"基线 ",
|
|
1886
|
+
shortHash(execution.baseCommit),
|
|
1887
|
+
" → ",
|
|
1888
|
+
shortHash(execution.headCommit)
|
|
1889
|
+
]
|
|
1890
|
+
}),
|
|
1891
|
+
execution.changedFiles !== void 0 && execution.changedFiles > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1892
|
+
className: "dsh-atb-iso-fact",
|
|
1893
|
+
children: [
|
|
1894
|
+
"改动 ",
|
|
1895
|
+
execution.changedFiles,
|
|
1896
|
+
" 个文件"
|
|
1897
|
+
]
|
|
1898
|
+
}),
|
|
1899
|
+
execution.diffStat !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1900
|
+
className: "dsh-atb-iso-fact",
|
|
1901
|
+
title: execution.diffStat,
|
|
1902
|
+
children: execution.diffStat
|
|
1903
|
+
})
|
|
1904
|
+
]
|
|
1905
|
+
}),
|
|
1906
|
+
commits.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1907
|
+
className: "dsh-atb-iso-commits",
|
|
1908
|
+
children: [commits.slice(0, 10).map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1909
|
+
className: "dsh-atb-iso-commit",
|
|
1910
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: shortHash(c.hash) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: c.subject })]
|
|
1911
|
+
}, c.hash)), commitTotal > 10 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1912
|
+
className: "dsh-atb-iso-more",
|
|
1913
|
+
children: [
|
|
1914
|
+
"… 共 ",
|
|
1915
|
+
commitTotal,
|
|
1916
|
+
" 个提交"
|
|
1917
|
+
]
|
|
1918
|
+
})]
|
|
1919
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1920
|
+
className: "dsh-atb-iso-nocommit",
|
|
1921
|
+
children: "该次执行没有产生提交(改动可能未提交,见下方警告)"
|
|
1922
|
+
}),
|
|
1923
|
+
dirtyTotal > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1924
|
+
className: "dsh-atb-iso-dirty",
|
|
1925
|
+
title: dirty.join("\n"),
|
|
1926
|
+
children: [
|
|
1927
|
+
"⚠ 有 ",
|
|
1928
|
+
dirtyTotal,
|
|
1929
|
+
" 处未提交修改(合并前请让 agent 提交,或手动处理)"
|
|
1930
|
+
]
|
|
1931
|
+
}),
|
|
1932
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1933
|
+
className: "dsh-atb-iso-actions",
|
|
1934
|
+
children: [
|
|
1935
|
+
running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1936
|
+
className: "dsh-atb-iso-hint",
|
|
1937
|
+
children: "执行中 — 结束后可合并或清理"
|
|
1938
|
+
}) : confirmMerge ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1939
|
+
className: "dsh-atb-confirm",
|
|
1940
|
+
children: [
|
|
1941
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1942
|
+
className: "dsh-atb-confirm-label",
|
|
1943
|
+
children: "将分支以 --no-ff 合并到主工作区?"
|
|
1944
|
+
}),
|
|
1945
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1946
|
+
type: "button",
|
|
1947
|
+
className: "dsh-atb-btn",
|
|
1948
|
+
"data-primary": "true",
|
|
1949
|
+
disabled: busy,
|
|
1950
|
+
onClick: doMerge,
|
|
1951
|
+
children: "确认合并"
|
|
1952
|
+
}),
|
|
1953
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1954
|
+
type: "button",
|
|
1955
|
+
className: "dsh-atb-btn",
|
|
1956
|
+
onClick: () => setConfirmMerge(false),
|
|
1957
|
+
children: "取消"
|
|
1958
|
+
})
|
|
1959
|
+
]
|
|
1960
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1961
|
+
type: "button",
|
|
1962
|
+
className: "dsh-atb-btn",
|
|
1963
|
+
disabled: busy,
|
|
1964
|
+
title: "在主工作区 git merge --no-ff 该任务分支(要求主区干净;冲突会原样报告)",
|
|
1965
|
+
onClick: () => setConfirmMerge(true),
|
|
1966
|
+
children: "⇥ 合并到主工作区"
|
|
1967
|
+
}),
|
|
1968
|
+
!running && (confirmRemove === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1969
|
+
type: "button",
|
|
1970
|
+
className: "dsh-atb-btn",
|
|
1971
|
+
"data-danger": "true",
|
|
1972
|
+
disabled: busy,
|
|
1973
|
+
title: "git worktree remove(有未提交修改时拒绝)",
|
|
1974
|
+
onClick: () => setConfirmRemove("wt"),
|
|
1975
|
+
children: "🗑 删除 worktree"
|
|
1976
|
+
}), task.branch !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1977
|
+
type: "button",
|
|
1978
|
+
className: "dsh-atb-btn",
|
|
1979
|
+
"data-danger": "true",
|
|
1980
|
+
disabled: busy,
|
|
1981
|
+
title: "删除 worktree 并删除任务分支(有未提交修改时拒绝)",
|
|
1982
|
+
onClick: () => setConfirmRemove("wtb"),
|
|
1983
|
+
children: "🗑 删 worktree + 分支"
|
|
1984
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1985
|
+
className: "dsh-atb-confirm",
|
|
1986
|
+
children: [
|
|
1987
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1988
|
+
className: "dsh-atb-confirm-label",
|
|
1989
|
+
children: confirmRemove === "wtb" ? "删除 worktree 并删除分支?" : "删除 worktree 目录?"
|
|
1990
|
+
}),
|
|
1991
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1992
|
+
type: "button",
|
|
1993
|
+
className: "dsh-atb-btn",
|
|
1994
|
+
"data-danger": "true",
|
|
1995
|
+
disabled: busy,
|
|
1996
|
+
onClick: () => doRemove(confirmRemove === "wtb"),
|
|
1997
|
+
children: "确认删除"
|
|
1998
|
+
}),
|
|
1999
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2000
|
+
type: "button",
|
|
2001
|
+
className: "dsh-atb-btn",
|
|
2002
|
+
onClick: () => setConfirmRemove(null),
|
|
2003
|
+
children: "取消"
|
|
2004
|
+
})
|
|
2005
|
+
]
|
|
2006
|
+
})),
|
|
2007
|
+
!running && confirmRemove === null && !confirmMerge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2008
|
+
className: "dsh-atb-iso-hint",
|
|
2009
|
+
children: "分支与 worktree 保留中 — 可退回继续修改"
|
|
2010
|
+
})
|
|
2011
|
+
]
|
|
2012
|
+
}),
|
|
2013
|
+
alertEl
|
|
2014
|
+
]
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
1543
2017
|
/**
|
|
1544
2018
|
* The detail view.
|
|
1545
2019
|
* @param task - the task record.
|
|
@@ -1597,6 +2071,10 @@ window.__ModuleLoader__.load({
|
|
|
1597
2071
|
icon: "✦",
|
|
1598
2072
|
children: task.model.model
|
|
1599
2073
|
}),
|
|
2074
|
+
task.presetId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
|
|
2075
|
+
icon: "🎛",
|
|
2076
|
+
children: task.presetId
|
|
2077
|
+
}),
|
|
1600
2078
|
task.execution.mode === "scheduled" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Chip, {
|
|
1601
2079
|
icon: "⏰",
|
|
1602
2080
|
children: [
|
|
@@ -1610,6 +2088,15 @@ window.__ModuleLoader__.load({
|
|
|
1610
2088
|
tone: "urgent",
|
|
1611
2089
|
children: "受阻"
|
|
1612
2090
|
}),
|
|
2091
|
+
task.branch !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Chip, {
|
|
2092
|
+
icon: "🌿",
|
|
2093
|
+
tone: void 0,
|
|
2094
|
+
children: ["Worktree · ", task.branch.length > 28 ? `${task.branch.slice(0, 28)}…` : task.branch]
|
|
2095
|
+
}),
|
|
2096
|
+
(task.isolation === void 0 || task.isolation === "worktree") && task.branch === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
|
|
2097
|
+
icon: "🌿",
|
|
2098
|
+
children: "Worktree 隔离"
|
|
2099
|
+
}),
|
|
1613
2100
|
holder !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Chip, {
|
|
1614
2101
|
icon: stale ? "⏱" : "🔑",
|
|
1615
2102
|
tone: stale ? "urgent" : void 0,
|
|
@@ -1653,6 +2140,13 @@ window.__ModuleLoader__.load({
|
|
|
1653
2140
|
onClick: () => void controller.duplicate(task),
|
|
1654
2141
|
children: "⧉ 复制"
|
|
1655
2142
|
}),
|
|
2143
|
+
canRun && task.branch !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2144
|
+
type: "button",
|
|
2145
|
+
className: "dsh-atb-detail-run",
|
|
2146
|
+
title: "续跑:保留现有 worktree 与分支(上次的改动和提交都在原处),在其上继续执行;默认「立即执行」会重置为全新基线",
|
|
2147
|
+
onClick: () => void controller.run(task.id, true),
|
|
2148
|
+
children: "↻ 续跑"
|
|
2149
|
+
}),
|
|
1656
2150
|
canRun && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1657
2151
|
type: "button",
|
|
1658
2152
|
className: "dsh-atb-detail-run",
|
|
@@ -1723,6 +2217,10 @@ window.__ModuleLoader__.load({
|
|
|
1723
2217
|
children: task.prompt
|
|
1724
2218
|
})]
|
|
1725
2219
|
}),
|
|
2220
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IsolationBlock, {
|
|
2221
|
+
task,
|
|
2222
|
+
controller
|
|
2223
|
+
}),
|
|
1726
2224
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1727
2225
|
className: "dsh-atb-detail-actions",
|
|
1728
2226
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2025,6 +2523,10 @@ window.__ModuleLoader__.load({
|
|
|
2025
2523
|
const [cron, setCron] = (0, react.useState)(task?.execution.cron ?? "0 9 * * *");
|
|
2026
2524
|
const [catalog, setCatalog] = (0, react.useState)([]);
|
|
2027
2525
|
const [model, setModel] = (0, react.useState)(task?.model !== void 0 ? JSON.stringify(task.model) : "");
|
|
2526
|
+
const [presetId, setPresetId] = (0, react.useState)(task?.presetId ?? "");
|
|
2527
|
+
const [presets, setPresets] = (0, react.useState)([]);
|
|
2528
|
+
const [presetDefault, setPresetDefault] = (0, react.useState)(void 0);
|
|
2529
|
+
const [isolation, setIsolation] = (0, react.useState)(task?.isolation ?? loadDefaultIsolation());
|
|
2028
2530
|
const titleRef = (0, react.useRef)(null);
|
|
2029
2531
|
(0, react.useEffect)(() => {
|
|
2030
2532
|
titleRef.current?.focus();
|
|
@@ -2039,14 +2541,36 @@ window.__ModuleLoader__.load({
|
|
|
2039
2541
|
if (face === void 0) return;
|
|
2040
2542
|
face().then(setCatalog).catch(() => setCatalog([]));
|
|
2041
2543
|
}, [controller]);
|
|
2544
|
+
(0, react.useEffect)(() => {
|
|
2545
|
+
const face = controller.presetCatalog;
|
|
2546
|
+
if (face === void 0) return;
|
|
2547
|
+
face().then((roster) => {
|
|
2548
|
+
setPresets(roster.presets);
|
|
2549
|
+
setPresetDefault(roster.defaultId);
|
|
2550
|
+
if (task?.presetId === void 0 && roster.defaultId !== void 0) setPresetId(roster.defaultId);
|
|
2551
|
+
}).catch(() => setPresets([]));
|
|
2552
|
+
}, [controller, task?.presetId]);
|
|
2042
2553
|
const cronMatch = mode === "scheduled" ? parseCron(cron.trim()) : null;
|
|
2043
2554
|
const nextRun = cronMatch !== null ? nextCronTime(cronMatch, Date.now()) : null;
|
|
2044
2555
|
const cronBad = mode === "scheduled" && (cronMatch === null || nextRun === null);
|
|
2045
2556
|
const valid = title.trim().length > 0 && workspaceId !== "" && !cronBad;
|
|
2046
2557
|
const runBlocked = editing && task.status === "in_progress";
|
|
2558
|
+
const isolationLocked = editing && ((task.executions?.length ?? 0) > 0 || task.status === "in_progress");
|
|
2559
|
+
const gitOk = controller.gitAvailable(workspaceId);
|
|
2560
|
+
const isolationDisabled = isolationLocked || !gitOk;
|
|
2561
|
+
/** Isolation payload for submit: undefined keeps the default (degrades naturally). */
|
|
2562
|
+
const isolationPayload = () => {
|
|
2563
|
+
if (!gitOk) return void 0;
|
|
2564
|
+
if (!editing) saveDefaultIsolation(isolation);
|
|
2565
|
+
return isolation;
|
|
2566
|
+
};
|
|
2567
|
+
/** Preset payload: '' = follow the deployment default (submit omits). */
|
|
2568
|
+
const presetPayload = () => presetId.trim().length > 0 ? presetId.trim() : void 0;
|
|
2047
2569
|
const submit = () => {
|
|
2048
2570
|
if (!valid) return;
|
|
2049
2571
|
const picked = model !== "" ? JSON.parse(model) : void 0;
|
|
2572
|
+
const isolationOut = isolationPayload();
|
|
2573
|
+
const presetOut = presetPayload();
|
|
2050
2574
|
if (editing) controller.update(task.id, task.version, {
|
|
2051
2575
|
title,
|
|
2052
2576
|
description,
|
|
@@ -2057,7 +2581,9 @@ window.__ModuleLoader__.load({
|
|
|
2057
2581
|
mode,
|
|
2058
2582
|
cron: cron.trim()
|
|
2059
2583
|
} : { mode },
|
|
2060
|
-
model: picked ?? null
|
|
2584
|
+
model: picked ?? null,
|
|
2585
|
+
...isolationOut !== void 0 && !isolationLocked ? { isolation: isolationOut } : {},
|
|
2586
|
+
presetId: presetOut ?? null
|
|
2061
2587
|
});
|
|
2062
2588
|
else controller.create({
|
|
2063
2589
|
title,
|
|
@@ -2069,13 +2595,17 @@ window.__ModuleLoader__.load({
|
|
|
2069
2595
|
mode,
|
|
2070
2596
|
cron: cron.trim()
|
|
2071
2597
|
} : { mode },
|
|
2072
|
-
model: picked
|
|
2598
|
+
model: picked,
|
|
2599
|
+
...isolationOut !== void 0 ? { isolation: isolationOut } : {},
|
|
2600
|
+
...presetOut !== void 0 ? { presetId: presetOut } : {}
|
|
2073
2601
|
});
|
|
2074
2602
|
};
|
|
2075
2603
|
/** Save the form, then immediately trigger a manual run of the task. */
|
|
2076
2604
|
const submitAndRun = () => {
|
|
2077
2605
|
if (!valid || runBlocked) return;
|
|
2078
2606
|
const picked = model !== "" ? JSON.parse(model) : void 0;
|
|
2607
|
+
const isolationOut = isolationPayload();
|
|
2608
|
+
const presetOut = presetPayload();
|
|
2079
2609
|
if (editing) (async () => {
|
|
2080
2610
|
if (await controller.update(task.id, task.version, {
|
|
2081
2611
|
title,
|
|
@@ -2087,7 +2617,9 @@ window.__ModuleLoader__.load({
|
|
|
2087
2617
|
mode,
|
|
2088
2618
|
cron: cron.trim()
|
|
2089
2619
|
} : { mode },
|
|
2090
|
-
model: picked ?? null
|
|
2620
|
+
model: picked ?? null,
|
|
2621
|
+
...isolationOut !== void 0 && !isolationLocked ? { isolation: isolationOut } : {},
|
|
2622
|
+
presetId: presetOut ?? null
|
|
2091
2623
|
})) await controller.run(task.id);
|
|
2092
2624
|
})();
|
|
2093
2625
|
else (async () => {
|
|
@@ -2101,7 +2633,9 @@ window.__ModuleLoader__.load({
|
|
|
2101
2633
|
mode,
|
|
2102
2634
|
cron: cron.trim()
|
|
2103
2635
|
} : { mode },
|
|
2104
|
-
model: picked
|
|
2636
|
+
model: picked,
|
|
2637
|
+
...isolationOut !== void 0 ? { isolation: isolationOut } : {},
|
|
2638
|
+
...presetOut !== void 0 ? { presetId: presetOut } : {}
|
|
2105
2639
|
});
|
|
2106
2640
|
if (id !== void 0) await controller.run(id);
|
|
2107
2641
|
})();
|
|
@@ -2188,6 +2722,21 @@ window.__ModuleLoader__.load({
|
|
|
2188
2722
|
}, `${m.provider}/${m.model}`))]
|
|
2189
2723
|
})
|
|
2190
2724
|
}),
|
|
2725
|
+
presets.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2726
|
+
label: "执行模式(preset)",
|
|
2727
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
2728
|
+
value: presetId,
|
|
2729
|
+
onChange: (e) => setPresetId(e.target.value),
|
|
2730
|
+
title: "执行会话按该 preset 组合(决定工具集与人设);默认 = 部署默认 preset",
|
|
2731
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
2732
|
+
value: "",
|
|
2733
|
+
children: ["跟随部署默认", presetDefault !== void 0 ? `(当前:${presets.find((p) => p.id === presetDefault)?.name ?? presetDefault})` : ""]
|
|
2734
|
+
}), presets.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
2735
|
+
value: p.id,
|
|
2736
|
+
children: [p.name ?? p.id, p.id === presetDefault ? "(部署默认)" : ""]
|
|
2737
|
+
}, p.id))]
|
|
2738
|
+
})
|
|
2739
|
+
}),
|
|
2191
2740
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2192
2741
|
label: "紧急度",
|
|
2193
2742
|
full: true,
|
|
@@ -2285,6 +2834,46 @@ window.__ModuleLoader__.load({
|
|
|
2285
2834
|
children: ["下次 ", fmtTime(nextRun)]
|
|
2286
2835
|
})]
|
|
2287
2836
|
})]
|
|
2837
|
+
}),
|
|
2838
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
|
|
2839
|
+
label: "执行隔离",
|
|
2840
|
+
full: true,
|
|
2841
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2842
|
+
className: "dsh-atb-mode-picker",
|
|
2843
|
+
"data-disabled": isolationDisabled ? "true" : void 0,
|
|
2844
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2845
|
+
type: "button",
|
|
2846
|
+
className: "dsh-atb-mode-opt",
|
|
2847
|
+
"data-on": isolation === "worktree",
|
|
2848
|
+
disabled: isolationDisabled,
|
|
2849
|
+
title: isolationLocked ? "任务已有执行记录,隔离方式已锁定" : !gitOk ? "当前项目非 git 仓库" : "每次执行在独立 worktree 分支上进行",
|
|
2850
|
+
onClick: () => setIsolation("worktree"),
|
|
2851
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2852
|
+
className: "dsh-atb-mode-name",
|
|
2853
|
+
children: "🌿 Worktree 隔离"
|
|
2854
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2855
|
+
className: "dsh-atb-mode-hint",
|
|
2856
|
+
children: isolationLocked ? "已锁定(执行开始后不可更改)" : !gitOk ? "当前项目非 git 仓库" : "独立分支 task/标题+ID,互不污染"
|
|
2857
|
+
})]
|
|
2858
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2859
|
+
type: "button",
|
|
2860
|
+
className: "dsh-atb-mode-opt",
|
|
2861
|
+
"data-on": isolation === "none",
|
|
2862
|
+
disabled: isolationDisabled,
|
|
2863
|
+
title: isolationLocked ? "任务已有执行记录,隔离方式已锁定" : "直接在项目目录执行(不使用 git)",
|
|
2864
|
+
onClick: () => setIsolation("none"),
|
|
2865
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2866
|
+
className: "dsh-atb-mode-name",
|
|
2867
|
+
children: "📁 原目录执行"
|
|
2868
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2869
|
+
className: "dsh-atb-mode-hint",
|
|
2870
|
+
children: isolationLocked ? "已锁定(执行开始后不可更改)" : !gitOk ? "当前项目非 git 仓库,将在原目录执行" : "不使用 git,直接在项目目录工作"
|
|
2871
|
+
})]
|
|
2872
|
+
})]
|
|
2873
|
+
}), !gitOk && !isolationLocked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2874
|
+
className: "dsh-atb-isolation-note",
|
|
2875
|
+
children: "当前项目非 git 仓库,将在原目录执行(任务仍按默认配置创建,运行时自动降级)"
|
|
2876
|
+
})]
|
|
2288
2877
|
})
|
|
2289
2878
|
]
|
|
2290
2879
|
}),
|
|
@@ -2485,6 +3074,13 @@ window.__ModuleLoader__.load({
|
|
|
2485
3074
|
onClick: () => controller.toggleSecondary(),
|
|
2486
3075
|
children: state.secondaryOpen ? "返回看板" : "其它任务"
|
|
2487
3076
|
}),
|
|
3077
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3078
|
+
type: "button",
|
|
3079
|
+
className: "dsh-atb-btn",
|
|
3080
|
+
title: "健康诊断:遗留 worktree、台账基本项",
|
|
3081
|
+
onClick: () => controller.openDiagnostics(),
|
|
3082
|
+
children: "⚙ 诊断"
|
|
3083
|
+
}),
|
|
2488
3084
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2489
3085
|
type: "button",
|
|
2490
3086
|
className: "dsh-atb-btn",
|
|
@@ -2585,10 +3181,138 @@ window.__ModuleLoader__.load({
|
|
|
2585
3181
|
controller,
|
|
2586
3182
|
task: state.editingId === void 0 ? void 0 : state.ledger.tasks.find((t) => t.id === state.editingId)
|
|
2587
3183
|
}),
|
|
3184
|
+
state.diagOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DiagnosticsPanel, { controller }),
|
|
2588
3185
|
alertEl
|
|
2589
3186
|
]
|
|
2590
3187
|
});
|
|
2591
3188
|
}
|
|
3189
|
+
/** ⚙ Health-diagnostics panel (plan §3.6): ledger basics + orphan worktrees + one-click cleanup. */
|
|
3190
|
+
function DiagnosticsPanel({ controller }) {
|
|
3191
|
+
const state = controller.getSnapshot();
|
|
3192
|
+
const diag = state.diagnostics;
|
|
3193
|
+
const wsName = (id) => {
|
|
3194
|
+
const ws = state.workspaces.find((w) => w.id === id);
|
|
3195
|
+
return ws?.title ?? ws?.path ?? id.slice(0, 8);
|
|
3196
|
+
};
|
|
3197
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3198
|
+
className: "dsh-atb-modal-backdrop",
|
|
3199
|
+
onClick: (e) => {
|
|
3200
|
+
if (e.target === e.currentTarget) controller.closeDiagnostics();
|
|
3201
|
+
},
|
|
3202
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3203
|
+
className: "dsh-atb-modal dsh-atb-diag",
|
|
3204
|
+
role: "dialog",
|
|
3205
|
+
"aria-modal": "true",
|
|
3206
|
+
"aria-label": "健康诊断",
|
|
3207
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3208
|
+
className: "dsh-atb-modal-head",
|
|
3209
|
+
children: [
|
|
3210
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3211
|
+
className: "dsh-atb-modal-headicon",
|
|
3212
|
+
children: "⚙"
|
|
3213
|
+
}),
|
|
3214
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3215
|
+
className: "dsh-atb-modal-headtext",
|
|
3216
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "健康诊断" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "台账基本项与 worktree 遗留清理" })]
|
|
3217
|
+
}),
|
|
3218
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3219
|
+
type: "button",
|
|
3220
|
+
className: "dsh-atb-modal-close",
|
|
3221
|
+
"aria-label": "关闭",
|
|
3222
|
+
onClick: () => controller.closeDiagnostics(),
|
|
3223
|
+
children: "✕"
|
|
3224
|
+
})
|
|
3225
|
+
]
|
|
3226
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3227
|
+
className: "dsh-atb-modal-body",
|
|
3228
|
+
children: diag === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3229
|
+
className: "dsh-atb-empty2",
|
|
3230
|
+
children: "读取中…"
|
|
3231
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3232
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3233
|
+
className: "dsh-atb-diag-grid",
|
|
3234
|
+
children: [
|
|
3235
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3236
|
+
className: "dsh-atb-diag-item",
|
|
3237
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: diag.revision }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "台账修订号" })]
|
|
3238
|
+
}),
|
|
3239
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3240
|
+
className: "dsh-atb-diag-item",
|
|
3241
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: diag.tasks }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "任务总数" })]
|
|
3242
|
+
}),
|
|
3243
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3244
|
+
className: "dsh-atb-diag-item",
|
|
3245
|
+
"data-bad": diag.staleRunning > 0 ? "true" : void 0,
|
|
3246
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: diag.staleRunning }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "执行中" })]
|
|
3247
|
+
}),
|
|
3248
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3249
|
+
className: "dsh-atb-diag-item",
|
|
3250
|
+
"data-bad": diag.orphanWorktrees.length > 0 ? "true" : void 0,
|
|
3251
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: diag.orphanWorktrees.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "遗留 worktree" })]
|
|
3252
|
+
})
|
|
3253
|
+
]
|
|
3254
|
+
}),
|
|
3255
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3256
|
+
className: "dsh-atb-diag-sec",
|
|
3257
|
+
children: [
|
|
3258
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: "遗留 worktree(台账无主但目录存在)" }),
|
|
3259
|
+
diag.orphanWorktrees.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3260
|
+
className: "dsh-atb-empty2",
|
|
3261
|
+
children: "无遗留 — 各项目 .dsh-worktrees 目录干净"
|
|
3262
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3263
|
+
className: "dsh-atb-diag-orphans",
|
|
3264
|
+
children: diag.orphanWorktrees.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3265
|
+
className: "dsh-atb-diag-orphan",
|
|
3266
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3267
|
+
className: "dsh-atb-diag-orphan-path",
|
|
3268
|
+
title: o.path,
|
|
3269
|
+
children: [
|
|
3270
|
+
wsName(o.workspaceId),
|
|
3271
|
+
" · ",
|
|
3272
|
+
o.taskId
|
|
3273
|
+
]
|
|
3274
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3275
|
+
type: "button",
|
|
3276
|
+
className: "dsh-atb-btn",
|
|
3277
|
+
"data-danger": "true",
|
|
3278
|
+
onClick: () => void controller.cleanupOrphan(o.workspaceId, o.taskId),
|
|
3279
|
+
children: "清理"
|
|
3280
|
+
})]
|
|
3281
|
+
}, o.path))
|
|
3282
|
+
}),
|
|
3283
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3284
|
+
className: "dsh-atb-empty2",
|
|
3285
|
+
children: "提示:有未提交修改的遗留目录会被拒绝清理,请先手动处理其内容。live 任务的 worktree 请在任务详情页删除。"
|
|
3286
|
+
})
|
|
3287
|
+
]
|
|
3288
|
+
}),
|
|
3289
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3290
|
+
className: "dsh-atb-diag-sec",
|
|
3291
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: "gitignore 建议" }), (diag.gitIgnoreSuggestions ?? []).length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3292
|
+
className: "dsh-atb-empty2",
|
|
3293
|
+
children: "无待办 — 各 git 项目已忽略 .dsh-worktrees 目录"
|
|
3294
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3295
|
+
className: "dsh-atb-diag-orphans",
|
|
3296
|
+
children: diag.gitIgnoreSuggestions.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3297
|
+
className: "dsh-atb-diag-orphan",
|
|
3298
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3299
|
+
className: "dsh-atb-diag-orphan-path",
|
|
3300
|
+
title: s.workspacePath,
|
|
3301
|
+
children: [
|
|
3302
|
+
wsName(s.workspaceId),
|
|
3303
|
+
" · 建议在 .gitignore 加入一行 ",
|
|
3304
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: ".dsh-worktrees/" }),
|
|
3305
|
+
"(不会自动修改)"
|
|
3306
|
+
]
|
|
3307
|
+
})
|
|
3308
|
+
}, s.workspaceId))
|
|
3309
|
+
})]
|
|
3310
|
+
})
|
|
3311
|
+
] })
|
|
3312
|
+
})]
|
|
3313
|
+
})
|
|
3314
|
+
});
|
|
3315
|
+
}
|
|
2592
3316
|
/** Secondary tab: tasks grouped into canceled / archived / trashed columns. */
|
|
2593
3317
|
function SecondaryTab({ controller, tasks }) {
|
|
2594
3318
|
const trashed = tasks.filter((t) => t.trashedAt !== void 0);
|
|
@@ -2791,17 +3515,34 @@ window.__ModuleLoader__.load({
|
|
|
2791
3515
|
injectStyles();
|
|
2792
3516
|
const controller = new BoardController(createClient());
|
|
2793
3517
|
const connection = ctx.get?.("connection");
|
|
2794
|
-
if (connection !== void 0)
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
3518
|
+
if (connection !== void 0) {
|
|
3519
|
+
controller.modelCatalog = async () => {
|
|
3520
|
+
const response = await connection.api.llm.models({});
|
|
3521
|
+
if (!response.result.ok) return [];
|
|
3522
|
+
const out = [];
|
|
3523
|
+
for (const group of response.result.value.groups) for (const model of group.models) out.push({
|
|
3524
|
+
provider: group.id,
|
|
3525
|
+
model: model.id,
|
|
3526
|
+
name: model.name
|
|
3527
|
+
});
|
|
3528
|
+
return out;
|
|
3529
|
+
};
|
|
3530
|
+
controller.presetCatalog = async () => {
|
|
3531
|
+
const list = connection.api.agentPresets;
|
|
3532
|
+
if (list === void 0) return { presets: [] };
|
|
3533
|
+
const response = await list.list({});
|
|
3534
|
+
if (!response.result.ok) return { presets: [] };
|
|
3535
|
+
const presets = response.result.value.presets.map((p) => ({
|
|
3536
|
+
id: p.id,
|
|
3537
|
+
name: p.name
|
|
3538
|
+
}));
|
|
3539
|
+
const def = response.result.value.presets.find((p) => p.isDefault);
|
|
3540
|
+
return {
|
|
3541
|
+
presets,
|
|
3542
|
+
...def !== void 0 ? { defaultId: def.id } : {}
|
|
3543
|
+
};
|
|
3544
|
+
};
|
|
3545
|
+
}
|
|
2805
3546
|
controller.installSessionJumper(createSessionJumper({
|
|
2806
3547
|
getSessions: () => ctx.get?.("sessions"),
|
|
2807
3548
|
getWorkspaces: () => ctx.get?.("workspaces")
|