dsh-taskboard 0.2.2 → 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 +44 -4
- package/lib/client.js +635 -23
- 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 +252 -4
- 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 +19 -3
- package/src/client/board/TaskBoard.tsx +73 -0
- package/src/client/board/TaskDetail.tsx +173 -1
- package/src/client/board/TaskFormModal.tsx +100 -1
- package/src/client/controller.ts +89 -6
- package/src/client/index.ts +18 -1
- package/src/client/styles.ts +45 -0
- package/src/host/execution.ts +291 -65
- package/src/host/git.ts +293 -0
- package/src/host/routes.ts +268 -5
- package/src/host/tools.ts +17 -0
- package/src/index.ts +24 -1
- package/src/shared/api.ts +35 -3
- package/src/shared/protocol.ts +64 -0
- package/src/shared/version.ts +1 -1
package/lib/client.js
CHANGED
|
@@ -37,8 +37,15 @@ window.__ModuleLoader__.load({
|
|
|
37
37
|
reject: (id, body) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/reject`, body),
|
|
38
38
|
comment: (id, bodyText) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/comment`, { body: bodyText }),
|
|
39
39
|
remove: (id, body) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/delete`, body),
|
|
40
|
-
run: (id) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/run`, {}),
|
|
40
|
+
run: (id, body) => post(`/dsh-taskboard/tasks/${encodeURIComponent(id)}/run`, body ?? {}),
|
|
41
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
|
+
}),
|
|
42
49
|
stream(onChange, onGap) {
|
|
43
50
|
const es = new EventSource("/dsh-taskboard/events");
|
|
44
51
|
let revision;
|
|
@@ -209,6 +216,22 @@ window.__ModuleLoader__.load({
|
|
|
209
216
|
//#region src/client/controller.ts
|
|
210
217
|
/** localStorage key for persisted view state (filters + sort). */
|
|
211
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
|
+
}
|
|
212
235
|
/** Load the persisted view state (never throws; fresh on any parse error). */
|
|
213
236
|
function loadView() {
|
|
214
237
|
try {
|
|
@@ -245,7 +268,8 @@ window.__ModuleLoader__.load({
|
|
|
245
268
|
search: "",
|
|
246
269
|
sortBy: view.sortBy,
|
|
247
270
|
composerOpen: false,
|
|
248
|
-
secondaryOpen: false
|
|
271
|
+
secondaryOpen: false,
|
|
272
|
+
diagOpen: false
|
|
249
273
|
};
|
|
250
274
|
}
|
|
251
275
|
/**
|
|
@@ -403,6 +427,11 @@ window.__ModuleLoader__.load({
|
|
|
403
427
|
toggleSecondary() {
|
|
404
428
|
this.setState({ secondaryOpen: !this.state.secondaryOpen });
|
|
405
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
|
+
}
|
|
406
435
|
/**
|
|
407
436
|
* Install the session-jump bridge (built from the runtime sessions service
|
|
408
437
|
* by the client entry). Without it openSession reports 'unavailable'.
|
|
@@ -517,10 +546,10 @@ window.__ModuleLoader__.load({
|
|
|
517
546
|
this.setState({ error: error instanceof Error ? error.message : String(error) });
|
|
518
547
|
}
|
|
519
548
|
}
|
|
520
|
-
/** Trigger a manual run (fresh in-project session, pinned model)
|
|
521
|
-
async run(id) {
|
|
549
|
+
/** Trigger a manual run (fresh in-project session, pinned model); `reuse` = 续跑. */
|
|
550
|
+
async run(id, reuse = false) {
|
|
522
551
|
try {
|
|
523
|
-
await this.client.run(id);
|
|
552
|
+
await this.client.run(id, reuse ? { reuse: true } : {});
|
|
524
553
|
await this.refresh();
|
|
525
554
|
} catch (error) {
|
|
526
555
|
this.setState({ error: error instanceof Error ? error.message : String(error) });
|
|
@@ -535,6 +564,66 @@ window.__ModuleLoader__.load({
|
|
|
535
564
|
this.setState({ error: error instanceof Error ? error.message : String(error) });
|
|
536
565
|
}
|
|
537
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
|
+
}
|
|
538
627
|
/** Soft-delete (agent parity) then optional purge. */
|
|
539
628
|
async remove(id, ifVersion, purge) {
|
|
540
629
|
try {
|
|
@@ -545,7 +634,7 @@ window.__ModuleLoader__.load({
|
|
|
545
634
|
this.setState({ error: error instanceof Error ? error.message : String(error) });
|
|
546
635
|
}
|
|
547
636
|
}
|
|
548
|
-
/** 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). */
|
|
549
638
|
async duplicate(task) {
|
|
550
639
|
try {
|
|
551
640
|
await this.client.create({
|
|
@@ -558,7 +647,9 @@ window.__ModuleLoader__.load({
|
|
|
558
647
|
mode: "scheduled",
|
|
559
648
|
cron: task.execution.cron
|
|
560
649
|
} : { mode: "claim" },
|
|
561
|
-
model: task.model
|
|
650
|
+
model: task.model,
|
|
651
|
+
isolation: task.isolation,
|
|
652
|
+
...task.presetId !== void 0 ? { presetId: task.presetId } : {}
|
|
562
653
|
});
|
|
563
654
|
await this.refresh();
|
|
564
655
|
} catch (error) {
|
|
@@ -1105,6 +1196,51 @@ window.__ModuleLoader__.load({
|
|
|
1105
1196
|
color: var(--dsw-alias-label-primary, inherit);
|
|
1106
1197
|
}
|
|
1107
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; }
|
|
1108
1244
|
`;
|
|
1109
1245
|
let injected = false;
|
|
1110
1246
|
/** Inject the stylesheet once (idempotent). */
|
|
@@ -1349,7 +1485,7 @@ window.__ModuleLoader__.load({
|
|
|
1349
1485
|
* @module dsh-taskboard/shared/version
|
|
1350
1486
|
*/
|
|
1351
1487
|
/** The package version (must equal package.json "version"). */
|
|
1352
|
-
const PLUGIN_VERSION = "0.
|
|
1488
|
+
const PLUGIN_VERSION = "0.3.3";
|
|
1353
1489
|
|
|
1354
1490
|
//#endregion
|
|
1355
1491
|
//#region src/client/board/TaskCard.tsx
|
|
@@ -1669,6 +1805,215 @@ window.__ModuleLoader__.load({
|
|
|
1669
1805
|
}), children]
|
|
1670
1806
|
});
|
|
1671
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
|
+
}
|
|
1672
2017
|
/**
|
|
1673
2018
|
* The detail view.
|
|
1674
2019
|
* @param task - the task record.
|
|
@@ -1726,6 +2071,10 @@ window.__ModuleLoader__.load({
|
|
|
1726
2071
|
icon: "✦",
|
|
1727
2072
|
children: task.model.model
|
|
1728
2073
|
}),
|
|
2074
|
+
task.presetId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
|
|
2075
|
+
icon: "🎛",
|
|
2076
|
+
children: task.presetId
|
|
2077
|
+
}),
|
|
1729
2078
|
task.execution.mode === "scheduled" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Chip, {
|
|
1730
2079
|
icon: "⏰",
|
|
1731
2080
|
children: [
|
|
@@ -1739,6 +2088,15 @@ window.__ModuleLoader__.load({
|
|
|
1739
2088
|
tone: "urgent",
|
|
1740
2089
|
children: "受阻"
|
|
1741
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
|
+
}),
|
|
1742
2100
|
holder !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Chip, {
|
|
1743
2101
|
icon: stale ? "⏱" : "🔑",
|
|
1744
2102
|
tone: stale ? "urgent" : void 0,
|
|
@@ -1782,6 +2140,13 @@ window.__ModuleLoader__.load({
|
|
|
1782
2140
|
onClick: () => void controller.duplicate(task),
|
|
1783
2141
|
children: "⧉ 复制"
|
|
1784
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
|
+
}),
|
|
1785
2150
|
canRun && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1786
2151
|
type: "button",
|
|
1787
2152
|
className: "dsh-atb-detail-run",
|
|
@@ -1852,6 +2217,10 @@ window.__ModuleLoader__.load({
|
|
|
1852
2217
|
children: task.prompt
|
|
1853
2218
|
})]
|
|
1854
2219
|
}),
|
|
2220
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IsolationBlock, {
|
|
2221
|
+
task,
|
|
2222
|
+
controller
|
|
2223
|
+
}),
|
|
1855
2224
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1856
2225
|
className: "dsh-atb-detail-actions",
|
|
1857
2226
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2154,6 +2523,10 @@ window.__ModuleLoader__.load({
|
|
|
2154
2523
|
const [cron, setCron] = (0, react.useState)(task?.execution.cron ?? "0 9 * * *");
|
|
2155
2524
|
const [catalog, setCatalog] = (0, react.useState)([]);
|
|
2156
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());
|
|
2157
2530
|
const titleRef = (0, react.useRef)(null);
|
|
2158
2531
|
(0, react.useEffect)(() => {
|
|
2159
2532
|
titleRef.current?.focus();
|
|
@@ -2168,14 +2541,36 @@ window.__ModuleLoader__.load({
|
|
|
2168
2541
|
if (face === void 0) return;
|
|
2169
2542
|
face().then(setCatalog).catch(() => setCatalog([]));
|
|
2170
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]);
|
|
2171
2553
|
const cronMatch = mode === "scheduled" ? parseCron(cron.trim()) : null;
|
|
2172
2554
|
const nextRun = cronMatch !== null ? nextCronTime(cronMatch, Date.now()) : null;
|
|
2173
2555
|
const cronBad = mode === "scheduled" && (cronMatch === null || nextRun === null);
|
|
2174
2556
|
const valid = title.trim().length > 0 && workspaceId !== "" && !cronBad;
|
|
2175
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;
|
|
2176
2569
|
const submit = () => {
|
|
2177
2570
|
if (!valid) return;
|
|
2178
2571
|
const picked = model !== "" ? JSON.parse(model) : void 0;
|
|
2572
|
+
const isolationOut = isolationPayload();
|
|
2573
|
+
const presetOut = presetPayload();
|
|
2179
2574
|
if (editing) controller.update(task.id, task.version, {
|
|
2180
2575
|
title,
|
|
2181
2576
|
description,
|
|
@@ -2186,7 +2581,9 @@ window.__ModuleLoader__.load({
|
|
|
2186
2581
|
mode,
|
|
2187
2582
|
cron: cron.trim()
|
|
2188
2583
|
} : { mode },
|
|
2189
|
-
model: picked ?? null
|
|
2584
|
+
model: picked ?? null,
|
|
2585
|
+
...isolationOut !== void 0 && !isolationLocked ? { isolation: isolationOut } : {},
|
|
2586
|
+
presetId: presetOut ?? null
|
|
2190
2587
|
});
|
|
2191
2588
|
else controller.create({
|
|
2192
2589
|
title,
|
|
@@ -2198,13 +2595,17 @@ window.__ModuleLoader__.load({
|
|
|
2198
2595
|
mode,
|
|
2199
2596
|
cron: cron.trim()
|
|
2200
2597
|
} : { mode },
|
|
2201
|
-
model: picked
|
|
2598
|
+
model: picked,
|
|
2599
|
+
...isolationOut !== void 0 ? { isolation: isolationOut } : {},
|
|
2600
|
+
...presetOut !== void 0 ? { presetId: presetOut } : {}
|
|
2202
2601
|
});
|
|
2203
2602
|
};
|
|
2204
2603
|
/** Save the form, then immediately trigger a manual run of the task. */
|
|
2205
2604
|
const submitAndRun = () => {
|
|
2206
2605
|
if (!valid || runBlocked) return;
|
|
2207
2606
|
const picked = model !== "" ? JSON.parse(model) : void 0;
|
|
2607
|
+
const isolationOut = isolationPayload();
|
|
2608
|
+
const presetOut = presetPayload();
|
|
2208
2609
|
if (editing) (async () => {
|
|
2209
2610
|
if (await controller.update(task.id, task.version, {
|
|
2210
2611
|
title,
|
|
@@ -2216,7 +2617,9 @@ window.__ModuleLoader__.load({
|
|
|
2216
2617
|
mode,
|
|
2217
2618
|
cron: cron.trim()
|
|
2218
2619
|
} : { mode },
|
|
2219
|
-
model: picked ?? null
|
|
2620
|
+
model: picked ?? null,
|
|
2621
|
+
...isolationOut !== void 0 && !isolationLocked ? { isolation: isolationOut } : {},
|
|
2622
|
+
presetId: presetOut ?? null
|
|
2220
2623
|
})) await controller.run(task.id);
|
|
2221
2624
|
})();
|
|
2222
2625
|
else (async () => {
|
|
@@ -2230,7 +2633,9 @@ window.__ModuleLoader__.load({
|
|
|
2230
2633
|
mode,
|
|
2231
2634
|
cron: cron.trim()
|
|
2232
2635
|
} : { mode },
|
|
2233
|
-
model: picked
|
|
2636
|
+
model: picked,
|
|
2637
|
+
...isolationOut !== void 0 ? { isolation: isolationOut } : {},
|
|
2638
|
+
...presetOut !== void 0 ? { presetId: presetOut } : {}
|
|
2234
2639
|
});
|
|
2235
2640
|
if (id !== void 0) await controller.run(id);
|
|
2236
2641
|
})();
|
|
@@ -2317,6 +2722,21 @@ window.__ModuleLoader__.load({
|
|
|
2317
2722
|
}, `${m.provider}/${m.model}`))]
|
|
2318
2723
|
})
|
|
2319
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
|
+
}),
|
|
2320
2740
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2321
2741
|
label: "紧急度",
|
|
2322
2742
|
full: true,
|
|
@@ -2414,6 +2834,46 @@ window.__ModuleLoader__.load({
|
|
|
2414
2834
|
children: ["下次 ", fmtTime(nextRun)]
|
|
2415
2835
|
})]
|
|
2416
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
|
+
})]
|
|
2417
2877
|
})
|
|
2418
2878
|
]
|
|
2419
2879
|
}),
|
|
@@ -2614,6 +3074,13 @@ window.__ModuleLoader__.load({
|
|
|
2614
3074
|
onClick: () => controller.toggleSecondary(),
|
|
2615
3075
|
children: state.secondaryOpen ? "返回看板" : "其它任务"
|
|
2616
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
|
+
}),
|
|
2617
3084
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2618
3085
|
type: "button",
|
|
2619
3086
|
className: "dsh-atb-btn",
|
|
@@ -2714,10 +3181,138 @@ window.__ModuleLoader__.load({
|
|
|
2714
3181
|
controller,
|
|
2715
3182
|
task: state.editingId === void 0 ? void 0 : state.ledger.tasks.find((t) => t.id === state.editingId)
|
|
2716
3183
|
}),
|
|
3184
|
+
state.diagOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DiagnosticsPanel, { controller }),
|
|
2717
3185
|
alertEl
|
|
2718
3186
|
]
|
|
2719
3187
|
});
|
|
2720
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
|
+
}
|
|
2721
3316
|
/** Secondary tab: tasks grouped into canceled / archived / trashed columns. */
|
|
2722
3317
|
function SecondaryTab({ controller, tasks }) {
|
|
2723
3318
|
const trashed = tasks.filter((t) => t.trashedAt !== void 0);
|
|
@@ -2920,17 +3515,34 @@ window.__ModuleLoader__.load({
|
|
|
2920
3515
|
injectStyles();
|
|
2921
3516
|
const controller = new BoardController(createClient());
|
|
2922
3517
|
const connection = ctx.get?.("connection");
|
|
2923
|
-
if (connection !== void 0)
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
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
|
+
}
|
|
2934
3546
|
controller.installSessionJumper(createSessionJumper({
|
|
2935
3547
|
getSessions: () => ctx.get?.("sessions"),
|
|
2936
3548
|
getWorkspaces: () => ctx.get?.("workspaces")
|