coding-agent-harness 1.0.4 → 1.0.5
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/CHANGELOG.md +7 -0
- package/LICENSE +661 -21
- package/LICENSE-EXCEPTION.md +37 -0
- package/README.md +33 -1
- package/README.zh-CN.md +23 -1
- package/SKILL.md +9 -8
- package/docs-release/architecture/overview.md +1 -1
- package/docs-release/architecture/overview.zh-CN.md +1 -1
- package/docs-release/architecture/system-explainer/01-system-overview.md +217 -0
- package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
- package/docs-release/architecture/system-explainer/04-check-and-governance.md +239 -0
- package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +303 -0
- package/docs-release/architecture/system-explainer/README.md +67 -0
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +226 -0
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
- package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +250 -0
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +323 -0
- package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
- package/docs-release/guides/agent-installation.en-US.md +8 -7
- package/docs-release/guides/agent-installation.md +9 -7
- package/docs-release/guides/preset-development.md +26 -2
- package/docs-release/guides/task-state-machine.en-US.md +30 -13
- package/docs-release/guides/task-state-machine.md +30 -13
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/INDEX.md +60 -0
- package/package.json +3 -2
- package/references/harness-ledger.md +1 -1
- package/scripts/commands/migration-command.mjs +30 -0
- package/scripts/commands/task-command.mjs +26 -25
- package/scripts/harness.mjs +7 -3
- package/scripts/lib/capability-registry.mjs +17 -21
- package/scripts/lib/check-module-parallel.mjs +9 -16
- package/scripts/lib/check-profiles.mjs +35 -81
- package/scripts/lib/check-task-contracts.mjs +13 -5
- package/scripts/lib/core-shared.mjs +55 -2
- package/scripts/lib/dashboard-data.mjs +126 -18
- package/scripts/lib/dashboard-workbench.mjs +80 -1
- package/scripts/lib/dashboard-writer.mjs +6 -2
- package/scripts/lib/git-status-summary.mjs +1 -1
- package/scripts/lib/governance-sync.mjs +180 -83
- package/scripts/lib/harness-core.mjs +1 -0
- package/scripts/lib/markdown-utils.mjs +33 -0
- package/scripts/lib/migration-planner.mjs +4 -6
- package/scripts/lib/phase-kind.mjs +50 -0
- package/scripts/lib/preset-engine.mjs +5 -8
- package/scripts/lib/preset-registry.mjs +188 -39
- package/scripts/lib/review-confirm-git-gate.mjs +1 -1
- package/scripts/lib/status-builder.mjs +88 -0
- package/scripts/lib/status-dashboard-renderer.mjs +7 -4
- package/scripts/lib/task-audit-metadata.mjs +385 -0
- package/scripts/lib/task-audit-migration.mjs +350 -0
- package/scripts/lib/task-completion-consistency.mjs +11 -1
- package/scripts/lib/task-lifecycle/create-task-helpers.mjs +67 -0
- package/scripts/lib/task-lifecycle/phase-sync.mjs +88 -0
- package/scripts/lib/task-lifecycle/review-confirm.mjs +40 -29
- package/scripts/lib/task-lifecycle/review-gates.mjs +13 -10
- package/scripts/lib/task-lifecycle/review-submission.mjs +63 -0
- package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +49 -0
- package/scripts/lib/task-lifecycle/template-files.mjs +53 -0
- package/scripts/lib/task-lifecycle.mjs +114 -147
- package/scripts/lib/task-metadata.mjs +118 -0
- package/scripts/lib/task-review-model.mjs +54 -68
- package/scripts/lib/task-scanner.mjs +70 -143
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +11 -0
- package/templates/AGENTS.md.template +7 -5
- package/templates/dashboard/assets/app-src/00-state.js +12 -0
- package/templates/dashboard/assets/app-src/10-router.js +3 -0
- package/templates/dashboard/assets/app-src/20-overview.js +7 -3
- package/templates/dashboard/assets/app-src/35-task-detail.js +46 -6
- package/templates/dashboard/assets/app-src/55-presets.js +375 -0
- package/templates/dashboard/assets/app-src/60-shared.js +3 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +131 -0
- package/templates/dashboard/assets/app.css +583 -0
- package/templates/dashboard/assets/app.css.manifest.json +1 -0
- package/templates/dashboard/assets/app.js +578 -10
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +4 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +62 -0
- package/templates/dashboard/assets/css-src/45-presets.css +516 -0
- package/templates/dashboard/assets/i18n.js +140 -2
- package/templates/planning/INDEX.md +87 -0
- package/templates/planning/brief.md +1 -1
- package/templates/planning/module_session_prompt.md +1 -0
- package/templates/planning/review.md +0 -18
- package/templates/planning/task_plan.md +4 -43
- package/templates/planning/visual_map.md +13 -9
- package/templates/planning/visual_map.simple.md +52 -0
- package/templates/reference/execution-workflow-standard.md +29 -2
- package/templates-zh-CN/AGENTS.md.template +7 -5
- package/templates-zh-CN/planning/INDEX.md +87 -0
- package/templates-zh-CN/planning/brief.md +1 -1
- package/templates-zh-CN/planning/module_session_prompt.md +1 -0
- package/templates-zh-CN/planning/review.md +0 -18
- package/templates-zh-CN/planning/task_plan.md +3 -63
- package/templates-zh-CN/planning/visual_map.md +14 -7
- package/templates-zh-CN/planning/visual_map.simple.md +48 -0
- package/templates-zh-CN/reference/execution-workflow-standard.md +31 -6
|
@@ -12,6 +12,18 @@ const state = {
|
|
|
12
12
|
taskGroupPage: 1,
|
|
13
13
|
warningFilter: "all",
|
|
14
14
|
warningPage: 1,
|
|
15
|
+
presetQuery: "",
|
|
16
|
+
presetSourceFilter: "all",
|
|
17
|
+
selectedPresetKey: "",
|
|
18
|
+
selectedPresetId: "",
|
|
19
|
+
presetActionResult: null,
|
|
20
|
+
presetInstallSource: "",
|
|
21
|
+
presetInstallScope: "project",
|
|
22
|
+
presetInstallForce: false,
|
|
23
|
+
presetSeedScope: "project",
|
|
24
|
+
presetSeedForce: false,
|
|
25
|
+
presetUninstallScope: "project",
|
|
26
|
+
presetUninstallConfirm: "",
|
|
15
27
|
renderMode: "rendered",
|
|
16
28
|
theme: localStorage.getItem("harness.theme") || "system",
|
|
17
29
|
taskLayout: localStorage.getItem("harness.taskLayout") || "list",
|
|
@@ -72,6 +84,7 @@ function shell() {
|
|
|
72
84
|
${routeLink("#/tasks", t("taskIndex"), "tasks")}
|
|
73
85
|
${routeLink("#/review", t("reviewQueue"), "review")}
|
|
74
86
|
${routeLink("#/modules", t("moduleView"), "modules")}
|
|
87
|
+
${routeLink("#/presets", t("presetCatalog"), "presets")}
|
|
75
88
|
<button data-language-toggle>${locale === "zh" ? "EN" : "中文"}</button>
|
|
76
89
|
<button data-theme-toggle>${themeLabel()}</button>
|
|
77
90
|
</div>
|
|
@@ -98,6 +111,7 @@ function renderRoute() {
|
|
|
98
111
|
if (route.name === "reviewTask") return reviewWorkspace(route);
|
|
99
112
|
if (route.name === "review") return reviewQueue();
|
|
100
113
|
if (route.name === "modules") return modulesView(route.id);
|
|
114
|
+
if (route.name === "presets") return presetsView();
|
|
101
115
|
if (route.name === "tasks") return taskIndex();
|
|
102
116
|
return overview();
|
|
103
117
|
}
|
|
@@ -109,6 +123,7 @@ function currentRoute() {
|
|
|
109
123
|
if (parts[0] === "review" && parts[1]) return { name: "reviewTask", id: parts[1] };
|
|
110
124
|
if (parts[0] === "review") return { name: "review" };
|
|
111
125
|
if (parts[0] === "modules") return { name: "modules", id: parts[1] || "" };
|
|
126
|
+
if (parts[0] === "presets") return { name: "presets" };
|
|
112
127
|
if (parts[0] === "tasks") return { name: "tasks" };
|
|
113
128
|
return { name: "overview" };
|
|
114
129
|
}
|
|
@@ -137,6 +152,9 @@ function overview() {
|
|
|
137
152
|
|
|
138
153
|
function statusStrip() {
|
|
139
154
|
const status = bundle.status?.checkState?.status || "unknown";
|
|
155
|
+
const validationMode = bundle.status?.checkState?.validationMode || "validated";
|
|
156
|
+
const dataOnly = validationMode === "data-only";
|
|
157
|
+
const displayState = dataOnly ? "snapshot" : status;
|
|
140
158
|
const failures = bundle.status?.checkState?.failures || 0;
|
|
141
159
|
const warnings = bundle.status?.checkState?.warnings || 0;
|
|
142
160
|
const tasks = bundle.status?.tasks || [];
|
|
@@ -144,9 +162,9 @@ function statusStrip() {
|
|
|
144
162
|
const visual = summary.visualMapCoverage || {};
|
|
145
163
|
const withBrief = tasks.filter((task) => task.briefSource === "standalone").length;
|
|
146
164
|
return `<section class="status-card-group">
|
|
147
|
-
<div class="status-primary ${
|
|
148
|
-
<span>${t("readiness")}</span>
|
|
149
|
-
<strong>${label(status)}</strong>
|
|
165
|
+
<div class="status-primary ${displayState}">
|
|
166
|
+
<span>${dataOnly ? t("snapshotStatus") : t("readiness")}</span>
|
|
167
|
+
<strong>${dataOnly ? t("snapshot") : label(status)}</strong>
|
|
150
168
|
<p>${nextActionText()}</p>
|
|
151
169
|
</div>
|
|
152
170
|
<div class="metrics-grid">
|
|
@@ -167,6 +185,7 @@ function metric(labelText, value) {
|
|
|
167
185
|
}
|
|
168
186
|
|
|
169
187
|
function nextActionText() {
|
|
188
|
+
if ((bundle.status?.checkState?.validationMode || "validated") === "data-only") return t("snapshotNotValidated");
|
|
170
189
|
const failures = bundle.status?.checkState?.failures || 0;
|
|
171
190
|
if (failures > 0) return t("resolveBlockers");
|
|
172
191
|
const missingBriefs = (bundle.status?.tasks || []).filter((task) => task.briefSource !== "standalone").length;
|
|
@@ -845,17 +864,57 @@ function taskQueueReasonSummary(task) {
|
|
|
845
864
|
}
|
|
846
865
|
|
|
847
866
|
function phaseTimeline(task) {
|
|
867
|
+
const knownKinds = new Set(["init", "execution", "gate"]);
|
|
868
|
+
const groups = [
|
|
869
|
+
["init", "Init"],
|
|
870
|
+
["execution", "Execution"],
|
|
871
|
+
["gate", "Gate"],
|
|
872
|
+
["other", "Other / Invalid"],
|
|
873
|
+
];
|
|
874
|
+
const phases = task.phases || [];
|
|
875
|
+
const grouped = groups
|
|
876
|
+
.map(([kind, label]) => {
|
|
877
|
+
const items = kind === "other"
|
|
878
|
+
? phases.filter((phase) => !knownKinds.has(phase.kind || "execution"))
|
|
879
|
+
: phases.filter((phase) => (phase.kind || "execution") === kind);
|
|
880
|
+
if (!items.length) return "";
|
|
881
|
+
return `<div class="phase-kind-group ${escapeAttr(kind)}">
|
|
882
|
+
<h3>${escapeHtml(label)}</h3>
|
|
883
|
+
${items.map(phaseStep).join("")}
|
|
884
|
+
</div>`;
|
|
885
|
+
})
|
|
886
|
+
.join("");
|
|
848
887
|
return `<section class="phase-timeline">
|
|
849
888
|
<h2>${t("phaseTimeline")}</h2>
|
|
850
|
-
${
|
|
851
|
-
<strong>${escapeHtml(phase.id)}</strong>
|
|
852
|
-
<span>${phase.completion}%</span>
|
|
853
|
-
<p>${escapeHtml(phase.output || phase.blockingRisk || phase.state)}</p>
|
|
854
|
-
${progressBar(phase.completion)}
|
|
855
|
-
</div>`).join("") || emptyState(t("noPhaseData"))}
|
|
889
|
+
${grouped || emptyState(t("noPhaseData"))}
|
|
856
890
|
</section>`;
|
|
857
891
|
}
|
|
858
892
|
|
|
893
|
+
function phaseStep(phase) {
|
|
894
|
+
const kind = phase.kind || "execution";
|
|
895
|
+
const actor = phase.actor || "agent";
|
|
896
|
+
const knownKind = ["init", "execution", "gate"].includes(kind);
|
|
897
|
+
const kindLabel = knownKind ? escapeHtml(kind) : `<span class="tag warn">${escapeHtml(kind)}</span>`;
|
|
898
|
+
const phaseKindClass = knownKind ? kind : "other";
|
|
899
|
+
return `<div class="phase-step ${escapeAttr(phase.state)} ${escapeAttr(phaseKindClass)}">
|
|
900
|
+
<div class="phase-step-head">
|
|
901
|
+
<strong>${escapeHtml(phase.id)}</strong>
|
|
902
|
+
<span>${kindLabel} · ${phase.completion}%</span>
|
|
903
|
+
</div>
|
|
904
|
+
<p>${escapeHtml(phase.output || phase.blockingRisk || phase.state)}</p>
|
|
905
|
+
${progressBar(phase.completion)}
|
|
906
|
+
<div class="phase-meta">
|
|
907
|
+
${phaseMetaTag(actor)}
|
|
908
|
+
${tag(phase.evidenceStatus || "missing")}
|
|
909
|
+
</div>
|
|
910
|
+
${phase.exitCommand ? `<code class="phase-exit-command">${escapeHtml(phase.exitCommand)}</code>` : ""}
|
|
911
|
+
</div>`;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function phaseMetaTag(value) {
|
|
915
|
+
return `<span class="tag">${escapeHtml(String(value || "unknown").replaceAll("_", " "))}</span>`;
|
|
916
|
+
}
|
|
917
|
+
|
|
859
918
|
function taskDocSection(task, fileName, title, required) {
|
|
860
919
|
const doc = taskDocument(task, fileName);
|
|
861
920
|
if (!doc && !required) return "";
|
|
@@ -1609,6 +1668,382 @@ function healthPanel() {
|
|
|
1609
1668
|
</section>`;
|
|
1610
1669
|
}
|
|
1611
1670
|
|
|
1671
|
+
function presetsView() {
|
|
1672
|
+
ensurePresetState();
|
|
1673
|
+
const catalog = bundle.presetCatalog || { summary: {}, roots: [], presets: [] };
|
|
1674
|
+
let presets = filteredPresets();
|
|
1675
|
+
syncVisiblePresetSelection(presets);
|
|
1676
|
+
presets = filteredPresets();
|
|
1677
|
+
const selected = selectedPreset(presets);
|
|
1678
|
+
syncPresetUninstallScope(selected);
|
|
1679
|
+
return `<div class="presets-page stack">
|
|
1680
|
+
<section class="flow-panel preset-command-center">
|
|
1681
|
+
<div class="section-head">
|
|
1682
|
+
<div>
|
|
1683
|
+
<p class="eyebrow">${t("presetCatalog")}</p>
|
|
1684
|
+
<h2>${t("presetCatalog")}</h2>
|
|
1685
|
+
<p class="subtle">${t("presetCatalogSubtitle")}</p>
|
|
1686
|
+
</div>
|
|
1687
|
+
<span class="preset-count-pill">${presets.length}/${catalog.summary?.total || 0}</span>
|
|
1688
|
+
</div>
|
|
1689
|
+
<div class="preset-priority-strip" aria-label="${escapeAttr(t("presetPriorityTitle"))}">
|
|
1690
|
+
${presetPriorityStep("project", 1)}
|
|
1691
|
+
${presetPriorityStep("user", 2)}
|
|
1692
|
+
${presetPriorityStep("builtin", 3)}
|
|
1693
|
+
</div>
|
|
1694
|
+
<div class="preset-toolbar">
|
|
1695
|
+
<div class="input-group">
|
|
1696
|
+
<input data-preset-search value="${escapeAttr(state.presetQuery)}" placeholder="${escapeAttr(t("presetSearchPlaceholder"))}" aria-label="${escapeAttr(t("presetSearch"))}">
|
|
1697
|
+
</div>
|
|
1698
|
+
<div class="preset-source-tabs" role="tablist" aria-label="${escapeAttr(t("presetSourceFilter"))}">
|
|
1699
|
+
${presetSourceOptions().map((source) => presetSourceButton(source)).join("")}
|
|
1700
|
+
</div>
|
|
1701
|
+
</div>
|
|
1702
|
+
</section>
|
|
1703
|
+
<section class="preset-workspace">
|
|
1704
|
+
<div class="flow-panel preset-collection-panel">
|
|
1705
|
+
<div class="preset-panel-heading">
|
|
1706
|
+
<div>
|
|
1707
|
+
<h3>${t("presetCollection")}</h3>
|
|
1708
|
+
<p>${t("presetCollectionHint")}</p>
|
|
1709
|
+
</div>
|
|
1710
|
+
</div>
|
|
1711
|
+
<div class="preset-catalog-list">
|
|
1712
|
+
${presets.map((preset) => presetCard(preset, selected ? presetKey(selected) : "")).join("") || emptyState(t("noPresets"))}
|
|
1713
|
+
</div>
|
|
1714
|
+
</div>
|
|
1715
|
+
<div class="preset-detail-workspace stack">
|
|
1716
|
+
${presetDetailPanel(selected)}
|
|
1717
|
+
${presetLayerStackPanel(selected)}
|
|
1718
|
+
</div>
|
|
1719
|
+
<aside class="preset-context-actions stack">
|
|
1720
|
+
${presetActionPanel(selected)}
|
|
1721
|
+
${presetImportPanel()}
|
|
1722
|
+
${presetRestorePanel()}
|
|
1723
|
+
${presetSummaryPanel(catalog)}
|
|
1724
|
+
</aside>
|
|
1725
|
+
</section>
|
|
1726
|
+
</div>`;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
function ensurePresetState() {
|
|
1730
|
+
const presets = bundle.presetCatalog?.presets || [];
|
|
1731
|
+
if (!state.selectedPresetKey && state.selectedPresetId) {
|
|
1732
|
+
const legacySelection = presets.find((preset) => preset.id === state.selectedPresetId);
|
|
1733
|
+
if (legacySelection) state.selectedPresetKey = presetKey(legacySelection);
|
|
1734
|
+
}
|
|
1735
|
+
if (!state.selectedPresetKey && presets[0]) {
|
|
1736
|
+
state.selectedPresetKey = presetKey(presets[0]);
|
|
1737
|
+
state.presetUninstallConfirm = "";
|
|
1738
|
+
}
|
|
1739
|
+
if (state.selectedPresetKey && !presets.some((preset) => presetKey(preset) === state.selectedPresetKey) && presets[0]) {
|
|
1740
|
+
state.selectedPresetKey = presetKey(presets[0]);
|
|
1741
|
+
state.presetUninstallConfirm = "";
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function presetSourceOptions() {
|
|
1746
|
+
return [
|
|
1747
|
+
["all", t("allPresets")],
|
|
1748
|
+
["project", t("presetSourceProject")],
|
|
1749
|
+
["user", t("presetSourceUser")],
|
|
1750
|
+
["builtin", t("presetSourceBuiltin")],
|
|
1751
|
+
];
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
function presetSourceButton([source, labelText]) {
|
|
1755
|
+
const active = state.presetSourceFilter === source;
|
|
1756
|
+
const count = source === "all" ? (bundle.presetCatalog?.summary?.total || 0) : (bundle.presetCatalog?.summary?.[source] || 0);
|
|
1757
|
+
return `<button type="button" class="${active ? "active" : ""}" data-preset-source-filter="${escapeAttr(source)}" role="tab" aria-selected="${active ? "true" : "false"}">
|
|
1758
|
+
<span>${escapeHtml(labelText)}</span>
|
|
1759
|
+
<strong>${count}</strong>
|
|
1760
|
+
</button>`;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
function filteredPresets() {
|
|
1764
|
+
const query = String(state.presetQuery || "").trim().toLowerCase();
|
|
1765
|
+
return (bundle.presetCatalog?.presets || []).filter((preset) => {
|
|
1766
|
+
if (state.presetSourceFilter !== "all" && preset.source !== state.presetSourceFilter) return false;
|
|
1767
|
+
return presetMatchesQuery(preset, query);
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
function presetMatchesQuery(preset, query = state.presetQuery) {
|
|
1772
|
+
const normalizedQuery = String(query || "").trim().toLowerCase();
|
|
1773
|
+
if (!normalizedQuery) return true;
|
|
1774
|
+
return [
|
|
1775
|
+
preset.id,
|
|
1776
|
+
preset.source,
|
|
1777
|
+
preset.purpose,
|
|
1778
|
+
preset.taskKind,
|
|
1779
|
+
preset.manifestPath,
|
|
1780
|
+
preset.version,
|
|
1781
|
+
...(preset.compatibleBudgets || []),
|
|
1782
|
+
].some((value) => String(value || "").toLowerCase().includes(normalizedQuery));
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
function syncVisiblePresetSelection(visiblePresets) {
|
|
1786
|
+
if (!visiblePresets.length) {
|
|
1787
|
+
state.selectedPresetKey = "";
|
|
1788
|
+
state.presetUninstallConfirm = "";
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
if (!visiblePresets.some((preset) => presetKey(preset) === state.selectedPresetKey)) {
|
|
1792
|
+
state.selectedPresetKey = presetKey(visiblePresets[0]);
|
|
1793
|
+
state.presetUninstallConfirm = "";
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
function selectedPreset(visiblePresets = filteredPresets()) {
|
|
1798
|
+
return visiblePresets.find((preset) => presetKey(preset) === state.selectedPresetKey) || visiblePresets[0] || null;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
function presetCard(preset, selectedId) {
|
|
1802
|
+
const key = presetKey(preset);
|
|
1803
|
+
const selected = key === selectedId;
|
|
1804
|
+
return `<article class="preset-card ${selected ? "active" : ""} ${preset.effective ? "effective" : "shadowed"}">
|
|
1805
|
+
<div class="preset-card-topline">
|
|
1806
|
+
<button type="button" class="preset-card-select" data-preset-select="${escapeAttr(key)}" aria-pressed="${selected ? "true" : "false"}">
|
|
1807
|
+
<span class="card-id">${escapeHtml(preset.id)}</span>
|
|
1808
|
+
</button>
|
|
1809
|
+
<div class="preset-card-tools">
|
|
1810
|
+
${presetSourceBadge(preset.source)}
|
|
1811
|
+
${presetStatusBadge(preset)}
|
|
1812
|
+
<button type="button" class="copy-inline" data-copy-preset-id="${escapeAttr(preset.id)}" title="${escapeAttr(t("copyPresetId"))}">${t("copyIdShort")}</button>
|
|
1813
|
+
</div>
|
|
1814
|
+
</div>
|
|
1815
|
+
<button type="button" class="preset-card-body" data-preset-select="${escapeAttr(key)}">
|
|
1816
|
+
<span>${escapeHtml(preset.purpose || t("none"))}</span>
|
|
1817
|
+
</button>
|
|
1818
|
+
<div class="preset-card-meta">
|
|
1819
|
+
<span>${t("manifestVersion")}: ${escapeHtml(formatPresetVersion(preset))}</span>
|
|
1820
|
+
<span>${t("taskKind")}: ${escapeHtml(preset.taskKind || t("none"))}</span>
|
|
1821
|
+
<span>${t("budgets")}: ${escapeHtml((preset.compatibleBudgets || []).join(", ") || t("none"))}</span>
|
|
1822
|
+
</div>
|
|
1823
|
+
<code class="preset-manifest-path">${escapeHtml(preset.manifestPath || "")}</code>
|
|
1824
|
+
</article>`;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
function presetKey(preset) {
|
|
1828
|
+
return preset?.key || `${preset?.source || "unknown"}:${preset?.id || ""}`;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
function presetSourceRank(source) {
|
|
1832
|
+
return { project: 1, user: 2, builtin: 3 }[source] || 9;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
function presetLayersForId(id) {
|
|
1836
|
+
return (bundle.presetCatalog?.presets || [])
|
|
1837
|
+
.filter((preset) => preset.id === id)
|
|
1838
|
+
.sort((a, b) => presetSourceRank(a.source) - presetSourceRank(b.source));
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
function syncPresetUninstallScope(preset) {
|
|
1842
|
+
if (preset && ["project", "user"].includes(preset.source)) state.presetUninstallScope = preset.source;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
function presetPriorityStep(source, index) {
|
|
1846
|
+
return `<div class="preset-priority-step">
|
|
1847
|
+
<span>${index}</span>
|
|
1848
|
+
<strong>${escapeHtml(t(`presetSource_${source}`) || source)}</strong>
|
|
1849
|
+
</div>`;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
function presetSourceBadge(source) {
|
|
1853
|
+
const normalized = String(source || "unknown");
|
|
1854
|
+
return `<span class="tag preset-source-badge ${escapeAttr(normalized)}">${escapeHtml(t(`presetSource_${normalized}`) || normalized)}</span>`;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
function presetStatusBadge(preset) {
|
|
1858
|
+
return `<span class="tag ${preset.effective ? "pass" : "warn"}">${escapeHtml(preset.effective ? t("presetEffective") : t("presetShadowed"))}</span>`;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
function formatPresetVersion(preset) {
|
|
1862
|
+
return preset?.version ?? t("none");
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
function presetSummaryPanel(catalog) {
|
|
1866
|
+
const roots = catalog.roots || [];
|
|
1867
|
+
return `<section class="side-panel preset-summary-panel">
|
|
1868
|
+
<h3>${t("presetSources")}</h3>
|
|
1869
|
+
<p class="preset-helper">${t("presetSourcesHint")}</p>
|
|
1870
|
+
<div class="metrics-grid compact">
|
|
1871
|
+
${metric(t("presetSourceProject"), catalog.summary?.project || 0)}
|
|
1872
|
+
${metric(t("presetSourceUser"), catalog.summary?.user || 0)}
|
|
1873
|
+
${metric(t("presetSourceBuiltin"), catalog.summary?.builtin || 0)}
|
|
1874
|
+
</div>
|
|
1875
|
+
<div class="preset-roots">
|
|
1876
|
+
${roots.map((root) => `<div><strong>${escapeHtml(t(`presetSource_${root.source}`) || root.source)}</strong><code>${escapeHtml(root.path || "")}</code></div>`).join("")}
|
|
1877
|
+
</div>
|
|
1878
|
+
</section>`;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
function presetDetailPanel(preset) {
|
|
1882
|
+
if (!preset) return `<section class="flow-panel preset-detail-panel">${emptyState(t("noPresets"))}</section>`;
|
|
1883
|
+
const inspectCommand = `harness preset inspect ${preset.id} --json .`;
|
|
1884
|
+
const checkCommand = `harness preset check ${preset.id} --json .`;
|
|
1885
|
+
const commandRows = preset.effective
|
|
1886
|
+
? `${presetCommandRow(inspectCommand)}${presetCommandRow(checkCommand)}`
|
|
1887
|
+
: `<div class="preset-command-warning">${escapeHtml(t("presetCommandsEffectiveOnly"))}</div>`;
|
|
1888
|
+
return `<section class="flow-panel preset-detail-panel">
|
|
1889
|
+
<div class="preset-detail-hero">
|
|
1890
|
+
<div>
|
|
1891
|
+
<div class="preset-detail-title-row">
|
|
1892
|
+
<h3>${escapeHtml(preset.id)}</h3>
|
|
1893
|
+
<button type="button" class="copy-inline" data-copy-preset-id="${escapeAttr(preset.id)}">${t("copyPresetId")}</button>
|
|
1894
|
+
</div>
|
|
1895
|
+
<p>${escapeHtml(preset.purpose || "")}</p>
|
|
1896
|
+
</div>
|
|
1897
|
+
<div class="preset-detail-badges">
|
|
1898
|
+
${presetSourceBadge(preset.source)}
|
|
1899
|
+
${presetStatusBadge(preset)}
|
|
1900
|
+
</div>
|
|
1901
|
+
</div>
|
|
1902
|
+
<dl class="preset-detail-list">
|
|
1903
|
+
${presetDetailRow(t("manifestVersion"), formatPresetVersion(preset))}
|
|
1904
|
+
${presetDetailRow(t("source"), t(`presetSource_${preset.source}`) || preset.source)}
|
|
1905
|
+
${presetDetailRow(t("status"), preset.effective ? t("presetEffective") : t("presetShadowed"))}
|
|
1906
|
+
${presetDetailRow(t("taskKind"), preset.taskKind || t("none"))}
|
|
1907
|
+
${presetDetailRow(t("budgets"), (preset.compatibleBudgets || []).join(", ") || t("none"))}
|
|
1908
|
+
${presetDetailRow(t("inputs"), preset.inputCount || 0)}
|
|
1909
|
+
${presetDetailRow(t("references"), preset.referenceCount || 0)}
|
|
1910
|
+
${presetDetailRow(t("artifacts"), preset.artifactCount || 0)}
|
|
1911
|
+
${presetDetailRow(t("writeScopes"), preset.writeScopeCount || 0)}
|
|
1912
|
+
${presetDetailRow(t("requiredReads"), preset.requiredReadCount || 0)}
|
|
1913
|
+
</dl>
|
|
1914
|
+
<div class="preset-path-block">
|
|
1915
|
+
<span>${t("manifestPath")}</span>
|
|
1916
|
+
<code class="preset-manifest-path">${escapeHtml(preset.manifestPath || "")}</code>
|
|
1917
|
+
</div>
|
|
1918
|
+
<div class="preset-command-list">
|
|
1919
|
+
${commandRows}
|
|
1920
|
+
</div>
|
|
1921
|
+
</section>`;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
function presetDetailRow(labelText, value) {
|
|
1925
|
+
return `<div><dt>${escapeHtml(labelText)}</dt><dd>${escapeHtml(String(value ?? ""))}</dd></div>`;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
function presetCommandRow(command) {
|
|
1929
|
+
return `<div class="preset-command-row">
|
|
1930
|
+
<code>${escapeHtml(command)}</code>
|
|
1931
|
+
<button type="button" class="copy-inline" data-copy-preset-command="${escapeAttr(command)}">${t("copyCommand")}</button>
|
|
1932
|
+
</div>`;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
function presetLayerStackPanel(preset) {
|
|
1936
|
+
if (!preset) return "";
|
|
1937
|
+
const layers = presetLayersForId(preset.id);
|
|
1938
|
+
return `<section class="flow-panel preset-layer-panel">
|
|
1939
|
+
<div class="preset-panel-heading">
|
|
1940
|
+
<div>
|
|
1941
|
+
<h3>${t("presetLayerStack")}</h3>
|
|
1942
|
+
<p>${t("presetLayerStackHint")}</p>
|
|
1943
|
+
</div>
|
|
1944
|
+
</div>
|
|
1945
|
+
<div class="preset-layer-list">
|
|
1946
|
+
${layers.map((layer) => `<button type="button" class="preset-layer-row ${presetKey(layer) === presetKey(preset) ? "active" : ""}" data-preset-select="${escapeAttr(presetKey(layer))}">
|
|
1947
|
+
<span class="preset-layer-rank">${presetSourceRank(layer.source)}</span>
|
|
1948
|
+
<span>
|
|
1949
|
+
<strong>${escapeHtml(t(`presetSource_${layer.source}`) || layer.source)}</strong>
|
|
1950
|
+
<small>${t("manifestVersion")}: ${escapeHtml(formatPresetVersion(layer))}</small>
|
|
1951
|
+
</span>
|
|
1952
|
+
${presetStatusBadge(layer)}
|
|
1953
|
+
</button>`).join("")}
|
|
1954
|
+
</div>
|
|
1955
|
+
</section>`;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
function presetActionPanel(preset) {
|
|
1959
|
+
const staticNote = canUseWorkbenchAction("preset-install") ? "" : `<p class="lesson-action-note">${escapeHtml(t("presetWorkbenchRequired"))}</p>`;
|
|
1960
|
+
const lockedUninstallScope = preset && ["project", "user"].includes(preset.source) ? preset.source : "";
|
|
1961
|
+
const confirmMatches = Boolean(preset && state.presetUninstallConfirm.trim() === preset.id);
|
|
1962
|
+
const canCheck = canUseWorkbenchAction("preset-check") && preset && preset.effective;
|
|
1963
|
+
const canUninstall = canUseWorkbenchAction("preset-uninstall") && preset && preset.source !== "builtin" && confirmMatches;
|
|
1964
|
+
return `<section class="side-panel preset-action-panel">
|
|
1965
|
+
<div class="preset-panel-heading">
|
|
1966
|
+
<div>
|
|
1967
|
+
<h3>${t("presetContextActions")}</h3>
|
|
1968
|
+
<p>${preset ? escapeHtml(preset.id) : t("noPresets")}</p>
|
|
1969
|
+
</div>
|
|
1970
|
+
</div>
|
|
1971
|
+
${staticNote}
|
|
1972
|
+
${presetActionResult()}
|
|
1973
|
+
<div class="preset-action-group">
|
|
1974
|
+
<h4>${t("presetCheck")}</h4>
|
|
1975
|
+
<p>${preset?.effective ? t("presetCheckHint") : t("presetShadowedActionHint")}</p>
|
|
1976
|
+
<button data-preset-check="${escapeAttr(preset?.id || "")}" ${canCheck ? "" : "disabled"}>${t("presetCheckSelected")}</button>
|
|
1977
|
+
</div>
|
|
1978
|
+
<div class="preset-action-group danger">
|
|
1979
|
+
<h4>${t("presetUninstallSelected")}</h4>
|
|
1980
|
+
<p>${preset?.source === "builtin" ? t("presetBuiltinImmutable") : t("presetUninstallHint")}</p>
|
|
1981
|
+
<label>${t("scope")}<select data-preset-uninstall-scope ${lockedUninstallScope ? "disabled" : ""}>
|
|
1982
|
+
${presetScopeOptions(lockedUninstallScope || state.presetUninstallScope)}
|
|
1983
|
+
</select></label>
|
|
1984
|
+
<div class="preset-confirm-row">
|
|
1985
|
+
<label>${t("confirmPresetId")}<input data-preset-uninstall-confirm value="${escapeAttr(state.presetUninstallConfirm)}" placeholder="${escapeAttr(preset?.id || "")}"></label>
|
|
1986
|
+
<button type="button" data-preset-fill-confirm="${escapeAttr(preset?.id || "")}" ${preset && preset.source !== "builtin" ? "" : "disabled"}>${t("useSelectedId")}</button>
|
|
1987
|
+
</div>
|
|
1988
|
+
${preset && preset.source !== "builtin" && !confirmMatches ? `<p class="preset-confirm-warning">${escapeHtml(t("presetConfirmRequired"))}</p>` : ""}
|
|
1989
|
+
<button data-preset-uninstall="${escapeAttr(preset?.id || "")}" ${canUninstall ? "" : "disabled"}>${t("presetUninstallSelected")}</button>
|
|
1990
|
+
</div>
|
|
1991
|
+
</section>`;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
function presetImportPanel() {
|
|
1995
|
+
return `<section class="side-panel preset-action-panel">
|
|
1996
|
+
<div class="preset-panel-heading">
|
|
1997
|
+
<div>
|
|
1998
|
+
<h3>${t("presetImportTitle")}</h3>
|
|
1999
|
+
<p>${t("presetImportHint")}</p>
|
|
2000
|
+
</div>
|
|
2001
|
+
</div>
|
|
2002
|
+
<div class="preset-action-group">
|
|
2003
|
+
<label>${t("source")}<input data-preset-install-source value="${escapeAttr(state.presetInstallSource)}" placeholder="${escapeAttr(t("presetInstallSourcePlaceholder"))}"></label>
|
|
2004
|
+
<label>${t("scope")}<select data-preset-install-scope>
|
|
2005
|
+
${presetScopeOptions(state.presetInstallScope)}
|
|
2006
|
+
</select></label>
|
|
2007
|
+
<label class="check-row"><input type="checkbox" data-preset-install-force ${state.presetInstallForce ? "checked" : ""}> ${t("forceOverwrite")}</label>
|
|
2008
|
+
<button data-preset-install ${canUseWorkbenchAction("preset-install") ? "" : "disabled"}>${t("presetInstall")}</button>
|
|
2009
|
+
</div>
|
|
2010
|
+
</section>`;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
function presetRestorePanel() {
|
|
2014
|
+
return `<section class="side-panel preset-action-panel">
|
|
2015
|
+
<div class="preset-panel-heading">
|
|
2016
|
+
<div>
|
|
2017
|
+
<h3>${t("presetRestoreBundled")}</h3>
|
|
2018
|
+
<p>${t("presetRestoreBundledHint")}</p>
|
|
2019
|
+
</div>
|
|
2020
|
+
</div>
|
|
2021
|
+
<div class="preset-action-group">
|
|
2022
|
+
<label>${t("scope")}<select data-preset-seed-scope>
|
|
2023
|
+
${presetScopeOptions(state.presetSeedScope)}
|
|
2024
|
+
</select></label>
|
|
2025
|
+
<label class="check-row"><input type="checkbox" data-preset-seed-force ${state.presetSeedForce ? "checked" : ""}> ${t("forceOverwrite")}</label>
|
|
2026
|
+
<button data-preset-seed ${canUseWorkbenchAction("preset-seed") ? "" : "disabled"}>${t("presetRestoreBundled")}</button>
|
|
2027
|
+
</div>
|
|
2028
|
+
</section>`;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
function presetScopeOptions(current) {
|
|
2032
|
+
return [["project", t("presetSourceProject")], ["user", t("presetSourceUser")]]
|
|
2033
|
+
.map(([value, labelText]) => `<option value="${value}" ${current === value ? "selected" : ""}>${escapeHtml(labelText)}</option>`)
|
|
2034
|
+
.join("");
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
function presetActionResult() {
|
|
2038
|
+
const result = state.presetActionResult;
|
|
2039
|
+
if (!result) return "";
|
|
2040
|
+
const klass = result.ok ? "success" : "failed";
|
|
2041
|
+
return `<div class="workbench-action-result ${klass}">
|
|
2042
|
+
<strong>${escapeHtml(result.title || "")}</strong>
|
|
2043
|
+
<span>${escapeHtml(result.message || "")}</span>
|
|
2044
|
+
</div>`;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
1612
2047
|
function taskDocument(task, fileName) {
|
|
1613
2048
|
if (fileName === "__walkthrough__" && task.walkthroughPath) return findDocument(task.walkthroughPath);
|
|
1614
2049
|
return findDocument(`${task.path}/${fileName}`);
|
|
@@ -1639,7 +2074,9 @@ function tag(value) {
|
|
|
1639
2074
|
}
|
|
1640
2075
|
|
|
1641
2076
|
function label(value) {
|
|
1642
|
-
|
|
2077
|
+
const key = `state_${value}`;
|
|
2078
|
+
const translated = t(key);
|
|
2079
|
+
return translated === key ? String(value || "unknown").replaceAll("_", " ") : translated;
|
|
1643
2080
|
}
|
|
1644
2081
|
|
|
1645
2082
|
function list(items = []) {
|
|
@@ -1722,6 +2159,68 @@ function bind() {
|
|
|
1722
2159
|
state.warningPage = 1;
|
|
1723
2160
|
app();
|
|
1724
2161
|
}));
|
|
2162
|
+
document.querySelectorAll("[data-preset-search]").forEach((input) => input.addEventListener("input", () => {
|
|
2163
|
+
state.presetQuery = input.value;
|
|
2164
|
+
app();
|
|
2165
|
+
}));
|
|
2166
|
+
document.querySelectorAll("[data-preset-source-filter]").forEach((button) => button.addEventListener("click", () => {
|
|
2167
|
+
state.presetSourceFilter = button.dataset.presetSourceFilter || "all";
|
|
2168
|
+
state.selectedPresetKey = "";
|
|
2169
|
+
state.presetUninstallConfirm = "";
|
|
2170
|
+
app();
|
|
2171
|
+
}));
|
|
2172
|
+
document.querySelectorAll("[data-preset-select]").forEach((button) => button.addEventListener("click", () => {
|
|
2173
|
+
state.selectedPresetKey = button.dataset.presetSelect || "";
|
|
2174
|
+
state.selectedPresetId = "";
|
|
2175
|
+
const selectedPreset = (bundle.presetCatalog?.presets || []).find((preset) => presetKey(preset) === state.selectedPresetKey);
|
|
2176
|
+
if (selectedPreset && state.presetSourceFilter !== "all" && selectedPreset.source !== state.presetSourceFilter) {
|
|
2177
|
+
state.presetSourceFilter = selectedPreset.source;
|
|
2178
|
+
}
|
|
2179
|
+
if (selectedPreset && !presetMatchesQuery(selectedPreset)) state.presetQuery = "";
|
|
2180
|
+
if (selectedPreset && ["project", "user"].includes(selectedPreset.source)) state.presetUninstallScope = selectedPreset.source;
|
|
2181
|
+
state.presetUninstallConfirm = "";
|
|
2182
|
+
app();
|
|
2183
|
+
}));
|
|
2184
|
+
document.querySelectorAll("[data-preset-install-source]").forEach((input) => input.addEventListener("input", () => {
|
|
2185
|
+
state.presetInstallSource = input.value;
|
|
2186
|
+
}));
|
|
2187
|
+
document.querySelectorAll("[data-preset-install-scope]").forEach((select) => select.addEventListener("change", () => {
|
|
2188
|
+
state.presetInstallScope = select.value || "project";
|
|
2189
|
+
}));
|
|
2190
|
+
document.querySelectorAll("[data-preset-install-force]").forEach((input) => input.addEventListener("change", () => {
|
|
2191
|
+
state.presetInstallForce = input.checked;
|
|
2192
|
+
}));
|
|
2193
|
+
document.querySelectorAll("[data-preset-seed-scope]").forEach((select) => select.addEventListener("change", () => {
|
|
2194
|
+
state.presetSeedScope = select.value || "project";
|
|
2195
|
+
}));
|
|
2196
|
+
document.querySelectorAll("[data-preset-seed-force]").forEach((input) => input.addEventListener("change", () => {
|
|
2197
|
+
state.presetSeedForce = input.checked;
|
|
2198
|
+
}));
|
|
2199
|
+
document.querySelectorAll("[data-preset-uninstall-scope]").forEach((select) => select.addEventListener("change", () => {
|
|
2200
|
+
state.presetUninstallScope = select.value || "project";
|
|
2201
|
+
}));
|
|
2202
|
+
document.querySelectorAll("[data-preset-uninstall-confirm]").forEach((input) => input.addEventListener("input", () => {
|
|
2203
|
+
state.presetUninstallConfirm = input.value;
|
|
2204
|
+
}));
|
|
2205
|
+
document.querySelectorAll("[data-preset-fill-confirm]").forEach((button) => button.addEventListener("click", () => {
|
|
2206
|
+
state.presetUninstallConfirm = button.dataset.presetFillConfirm || "";
|
|
2207
|
+
app();
|
|
2208
|
+
}));
|
|
2209
|
+
document.querySelectorAll("[data-preset-check]").forEach((button) => button.addEventListener("click", () => runPresetAction("check", { id: button.dataset.presetCheck || "" })));
|
|
2210
|
+
document.querySelectorAll("[data-preset-install]").forEach((button) => button.addEventListener("click", () => runPresetAction("install", {
|
|
2211
|
+
source: state.presetInstallSource,
|
|
2212
|
+
scope: state.presetInstallScope,
|
|
2213
|
+
force: state.presetInstallForce,
|
|
2214
|
+
})));
|
|
2215
|
+
document.querySelectorAll("[data-preset-seed]").forEach((button) => button.addEventListener("click", () => runPresetAction("seed", {
|
|
2216
|
+
scope: state.presetSeedScope,
|
|
2217
|
+
force: state.presetSeedForce,
|
|
2218
|
+
})));
|
|
2219
|
+
document.querySelectorAll("[data-preset-uninstall]").forEach((button) => button.addEventListener("click", () => runPresetAction("uninstall", {
|
|
2220
|
+
id: button.dataset.presetUninstall || "",
|
|
2221
|
+
scope: state.presetUninstallScope,
|
|
2222
|
+
confirmText: state.presetUninstallConfirm,
|
|
2223
|
+
})));
|
|
1725
2224
|
document.querySelectorAll("[data-review-queue-tab]").forEach((button) => button.addEventListener("click", () => {
|
|
1726
2225
|
state.reviewQueueTab = button.dataset.reviewQueueTab || "review";
|
|
1727
2226
|
state.reviewQueuePage = 1;
|
|
@@ -1768,6 +2267,7 @@ function bind() {
|
|
|
1768
2267
|
openDrawer(taskId);
|
|
1769
2268
|
}));
|
|
1770
2269
|
bindCopyTaskNameButtons(document);
|
|
2270
|
+
bindPresetCopyButtons(document);
|
|
1771
2271
|
bindRepairPromptButtons(document);
|
|
1772
2272
|
bindLessonSedimentationButtons(document);
|
|
1773
2273
|
document.querySelectorAll("[data-open-lesson-drawer]").forEach((el) => el.addEventListener("click", (e) => {
|
|
@@ -1850,6 +2350,45 @@ async function completeReviewFromDashboard(taskId) {
|
|
|
1850
2350
|
}
|
|
1851
2351
|
}
|
|
1852
2352
|
|
|
2353
|
+
async function runPresetAction(action, body) {
|
|
2354
|
+
state.presetActionResult = { ok: true, title: t("presetActionRunning"), message: action };
|
|
2355
|
+
app();
|
|
2356
|
+
try {
|
|
2357
|
+
const response = await fetch(`/api/presets/${action}`, {
|
|
2358
|
+
method: "POST",
|
|
2359
|
+
headers: {
|
|
2360
|
+
"content-type": "application/json",
|
|
2361
|
+
"x-harness-csrf": state.runtime?.csrfToken || "",
|
|
2362
|
+
},
|
|
2363
|
+
body: JSON.stringify(body),
|
|
2364
|
+
});
|
|
2365
|
+
const payload = await response.json();
|
|
2366
|
+
if (!response.ok) throw payload;
|
|
2367
|
+
state.presetActionResult = {
|
|
2368
|
+
ok: true,
|
|
2369
|
+
title: t("presetActionSuccess"),
|
|
2370
|
+
message: presetActionMessage(action, payload),
|
|
2371
|
+
};
|
|
2372
|
+
app();
|
|
2373
|
+
if (["install", "seed", "uninstall"].includes(action)) setTimeout(() => window.location.reload(), 650);
|
|
2374
|
+
} catch (error) {
|
|
2375
|
+
state.presetActionResult = {
|
|
2376
|
+
ok: false,
|
|
2377
|
+
title: t("presetActionFailed"),
|
|
2378
|
+
message: error?.error || error?.message || String(error || action),
|
|
2379
|
+
};
|
|
2380
|
+
app();
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
function presetActionMessage(action, payload) {
|
|
2385
|
+
if (action === "check") return `${payload.id || ""} ${payload.status || ""}`.trim();
|
|
2386
|
+
if (action === "install") return `${payload.id || ""} -> ${payload.scope || ""}`.trim();
|
|
2387
|
+
if (action === "seed") return `${payload.created || 0} ${t("created")} · ${payload.skipped || 0} ${t("skipped")}`;
|
|
2388
|
+
if (action === "uninstall") return `${payload.id || ""} ${payload.removed ? t("removed") : t("notInstalled")}`.trim();
|
|
2389
|
+
return action;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
1853
2392
|
function renderDrawerContent(taskId) {
|
|
1854
2393
|
const task = (bundle.status?.tasks || []).find((item) => item.id === taskId);
|
|
1855
2394
|
if (!task) return `<div class="empty">${t("taskNotFound")}</div>`;
|
|
@@ -1929,6 +2468,35 @@ function bindCopyTaskNameButtons(root) {
|
|
|
1929
2468
|
}));
|
|
1930
2469
|
}
|
|
1931
2470
|
|
|
2471
|
+
function bindPresetCopyButtons(root) {
|
|
2472
|
+
root.querySelectorAll("[data-copy-preset-id]").forEach((button) => button.addEventListener("click", async (event) => {
|
|
2473
|
+
event.preventDefault();
|
|
2474
|
+
event.stopPropagation();
|
|
2475
|
+
const presetId = button.dataset.copyPresetId || "";
|
|
2476
|
+
const defaultText = button.textContent;
|
|
2477
|
+
try {
|
|
2478
|
+
await copyText(presetId);
|
|
2479
|
+
button.textContent = t("copyTaskNameSuccess");
|
|
2480
|
+
} catch {
|
|
2481
|
+
button.textContent = t("copyTaskNameFailed");
|
|
2482
|
+
}
|
|
2483
|
+
setTimeout(() => { button.textContent = defaultText; }, 1200);
|
|
2484
|
+
}));
|
|
2485
|
+
root.querySelectorAll("[data-copy-preset-command]").forEach((button) => button.addEventListener("click", async (event) => {
|
|
2486
|
+
event.preventDefault();
|
|
2487
|
+
event.stopPropagation();
|
|
2488
|
+
const command = button.dataset.copyPresetCommand || "";
|
|
2489
|
+
const defaultText = button.textContent;
|
|
2490
|
+
try {
|
|
2491
|
+
await copyText(command);
|
|
2492
|
+
button.textContent = t("copyTaskNameSuccess");
|
|
2493
|
+
} catch {
|
|
2494
|
+
button.textContent = t("copyTaskNameFailed");
|
|
2495
|
+
}
|
|
2496
|
+
setTimeout(() => { button.textContent = defaultText; }, 1200);
|
|
2497
|
+
}));
|
|
2498
|
+
}
|
|
2499
|
+
|
|
1932
2500
|
function bindRepairPromptButtons(root) {
|
|
1933
2501
|
root.querySelectorAll("[data-copy-repair-prompt]").forEach((button) => button.addEventListener("click", async (event) => {
|
|
1934
2502
|
event.preventDefault();
|