@goodandready/dsh-goal 0.2.30 → 0.2.32

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/lib/client.js CHANGED
@@ -1808,9 +1808,200 @@ window.__ModuleLoader__.load({
1808
1808
  background: var(--dsw-alias-bg-layer-2);
1809
1809
  font-family: monospace;
1810
1810
  }
1811
-
1811
+ .dsh-goal-tabs {
1812
+ display: flex;
1813
+ gap: 8px;
1814
+ border-bottom: 1px solid var(--dsw-alias-border-l1);
1815
+ margin: 10px 0 14px 0;
1816
+ padding-bottom: 6px;
1817
+ }
1818
+ .dsh-goal-tab-btn {
1819
+ background: transparent;
1820
+ border: none;
1821
+ font-size: 13px;
1822
+ font-weight: 500;
1823
+ color: var(--dsw-alias-label-secondary);
1824
+ cursor: pointer;
1825
+ padding: 6px 12px;
1826
+ border-radius: 6px;
1827
+ display: inline-flex;
1828
+ align-items: center;
1829
+ gap: 6px;
1830
+ transition: all 0.15s ease;
1831
+ }
1832
+ .dsh-goal-tab-btn:hover {
1833
+ color: var(--dsw-alias-label-primary);
1834
+ background: var(--dsw-alias-bg-layer-2);
1835
+ }
1836
+ .dsh-goal-tab-btn.active {
1837
+ color: var(--dsw-alias-brand-primary);
1838
+ background: var(--dsw-alias-bg-layer-2);
1839
+ font-weight: 600;
1840
+ }
1841
+ .dsh-goal-diff-box {
1842
+ display: flex;
1843
+ flex-direction: column;
1844
+ gap: 10px;
1845
+ }
1846
+ .dsh-goal-diff-summary {
1847
+ display: flex;
1848
+ align-items: center;
1849
+ justify-content: space-between;
1850
+ background: var(--dsw-alias-bg-sunken);
1851
+ border: 1px solid var(--dsw-alias-border-l1);
1852
+ border-radius: 6px;
1853
+ padding: 8px 12px;
1854
+ font-size: 12px;
1855
+ }
1856
+ .dsh-goal-diff-stat-pill {
1857
+ display: inline-flex;
1858
+ align-items: center;
1859
+ gap: 8px;
1860
+ font-weight: 600;
1861
+ }
1862
+ .dsh-goal-diff-ins {
1863
+ color: var(--dsw-alias-state-success-primary);
1864
+ }
1865
+ .dsh-goal-diff-del {
1866
+ color: var(--dsw-alias-state-danger-primary);
1867
+ }
1868
+ .dsh-goal-diff-files-list {
1869
+ display: flex;
1870
+ flex-wrap: wrap;
1871
+ gap: 6px;
1872
+ max-height: 100px;
1873
+ overflow-y: auto;
1874
+ }
1875
+ .dsh-goal-diff-file-chip {
1876
+ display: inline-flex;
1877
+ align-items: center;
1878
+ gap: 6px;
1879
+ background: var(--dsw-alias-bg-layer-2);
1880
+ border: 1px solid var(--dsw-alias-border-l1);
1881
+ border-radius: 4px;
1882
+ padding: 3px 8px;
1883
+ font-size: 11px;
1884
+ font-family: monospace;
1885
+ cursor: pointer;
1886
+ color: var(--dsw-alias-label-secondary);
1887
+ }
1888
+ .dsh-goal-diff-file-chip:hover,
1889
+ .dsh-goal-diff-file-chip.active {
1890
+ border-color: var(--dsw-alias-brand-primary);
1891
+ color: var(--dsw-alias-label-primary);
1892
+ }
1893
+ .dsh-goal-diff-viewer {
1894
+ background: var(--dsw-alias-bg-sunken);
1895
+ border: 1px solid var(--dsw-alias-border-l1);
1896
+ border-radius: 6px;
1897
+ padding: 10px;
1898
+ font-family: monospace;
1899
+ font-size: 12px;
1900
+ line-height: 1.45;
1901
+ max-height: 380px;
1902
+ overflow: auto;
1903
+ white-space: pre-wrap;
1904
+ word-break: break-all;
1905
+ }
1906
+ .dsh-goal-diff-line {
1907
+ display: block;
1908
+ padding: 1px 4px;
1909
+ border-radius: 2px;
1910
+ }
1911
+ .dsh-goal-diff-line-add {
1912
+ background: var(--dsw-alias-state-success-bg, var(--dsw-alias-bg-hover));
1913
+ color: var(--dsw-alias-state-success-primary);
1914
+ }
1915
+ .dsh-goal-diff-line-del {
1916
+ background: var(--dsw-alias-state-error-bg, var(--dsw-alias-bg-hover));
1917
+ color: var(--dsw-alias-state-danger-primary);
1918
+ }
1919
+ .dsh-goal-diff-line-hunk {
1920
+ background: var(--dsw-alias-bg-layer-2);
1921
+ color: var(--dsw-alias-state-info-primary);
1922
+ }
1923
+ .dsh-goal-diff-line-header {
1924
+ color: var(--dsw-alias-label-tertiary);
1925
+ font-weight: 600;
1926
+ }
1927
+ .dsh-goal-diff-empty {
1928
+ padding: 24px;
1929
+ text-align: center;
1930
+ color: var(--dsw-alias-label-secondary);
1931
+ font-size: 13px;
1932
+ }
1933
+ /* Sub-goals and Compaction (Issue 167 and Issue 170) */
1934
+ .dsh-goal-badge-subgoal {
1935
+ background: var(--dsw-alias-brand-bg, var(--dsw-alias-bg-hover));
1936
+ color: var(--dsw-alias-brand-primary, var(--dsw-alias-text-primary));
1937
+ border: 1px solid var(--dsw-alias-brand-border, var(--dsw-alias-border));
1938
+ }
1939
+ .dsh-goal-breadcrumbs {
1940
+ display: inline-flex;
1941
+ align-items: center;
1942
+ gap: 4px;
1943
+ font-size: 13px;
1944
+ color: var(--dsw-alias-label-secondary);
1945
+ }
1946
+ .dsh-goal-breadcrumb-item {
1947
+ color: var(--dsw-alias-label-secondary);
1948
+ }
1949
+ .dsh-goal-breadcrumb-item.active {
1950
+ color: var(--dsw-alias-text-primary);
1951
+ font-weight: 600;
1952
+ }
1953
+ .dsh-goal-crumb-sep {
1954
+ color: var(--dsw-alias-label-tertiary);
1955
+ margin: 0 2px;
1956
+ }
1957
+ .dsh-goal-subgoals-box {
1958
+ display: flex;
1959
+ flex-direction: column;
1960
+ gap: 10px;
1961
+ padding: 4px 0;
1962
+ }
1963
+ .dsh-goal-subgoals-list {
1964
+ display: flex;
1965
+ flex-direction: column;
1966
+ gap: 8px;
1967
+ }
1968
+ .dsh-goal-subgoal-card {
1969
+ background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-hover));
1970
+ border: 1px solid var(--dsw-alias-border);
1971
+ border-radius: 6px;
1972
+ padding: 10px 12px;
1973
+ display: flex;
1974
+ flex-direction: column;
1975
+ gap: 4px;
1976
+ }
1977
+ .dsh-goal-subgoal-header {
1978
+ display: flex;
1979
+ align-items: center;
1980
+ justify-content: space-between;
1981
+ gap: 8px;
1982
+ }
1983
+ .dsh-goal-subgoal-title {
1984
+ font-weight: 600;
1985
+ font-size: 13px;
1986
+ color: var(--dsw-alias-text-primary);
1987
+ }
1988
+ .dsh-goal-subgoal-summary {
1989
+ font-size: 12px;
1990
+ color: var(--dsw-alias-label-secondary);
1991
+ }
1992
+ .dsh-goal-compacted-pill {
1993
+ display: inline-flex;
1994
+ align-items: center;
1995
+ gap: 3px;
1996
+ font-size: 10px;
1997
+ padding: 1px 5px;
1998
+ border-radius: 3px;
1999
+ background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-hover));
2000
+ color: var(--dsw-alias-state-success-primary, var(--dsw-alias-text-secondary));
2001
+ border: 1px solid var(--dsw-alias-border);
2002
+ margin-left: 6px;
2003
+ }
1812
2004
  `;
1813
-
1814
2005
  function injectStylesOnce() {
1815
2006
 
1816
2007
  if (typeof document === 'undefined') return;
@@ -2103,6 +2294,193 @@ window.__ModuleLoader__.load({
2103
2294
  const [interveneText, setInterveneText] = useState('');
2104
2295
 
2105
2296
  const [interveneSent, setInterveneSent] = useState(false);
2297
+ const [activeTab, setActiveTab] = useState("overview");
2298
+ const [diffData, setDiffData] = useState(null);
2299
+ const [diffLoading, setDiffLoading] = useState(false);
2300
+ const [diffError, setDiffError] = useState(null);
2301
+ const [selectedFile, setSelectedFile] = useState(null);
2302
+
2303
+ const fetchDiff = useCallback(() => {
2304
+ setDiffLoading(true);
2305
+ setDiffError(null);
2306
+ const sid = state.sessionId || "default";
2307
+ const url = "/dsh-goal/diff?sessionId=" + encodeURIComponent(sid);
2308
+ if (typeof fetch === "function") {
2309
+ fetch(url)
2310
+ .then((res) => {
2311
+ if (!res.ok) throw new Error("HTTP " + res.status);
2312
+ return res.json();
2313
+ })
2314
+ .then((data) => {
2315
+ if (data && data.ok) {
2316
+ setDiffData(data);
2317
+ } else {
2318
+ setDiffError((data && data.error) || "Failed to load diff");
2319
+ }
2320
+ })
2321
+ .catch((err) => {
2322
+ setDiffError(err.message || "Network error");
2323
+ })
2324
+ .finally(() => {
2325
+ setDiffLoading(false);
2326
+ });
2327
+ } else {
2328
+ setDiffLoading(false);
2329
+ }
2330
+ }, [state.sessionId]);
2331
+
2332
+ useEffect(() => {
2333
+ if (activeTab === "diff") {
2334
+ fetchDiff();
2335
+ }
2336
+ }, [activeTab, fetchDiff]);
2337
+
2338
+ const renderDiffBox = () => {
2339
+ return React.createElement(
2340
+ "div",
2341
+ { className: "dsh-goal-diff-box" },
2342
+ React.createElement(
2343
+ "div",
2344
+ { className: "dsh-goal-diff-summary" },
2345
+ React.createElement(
2346
+ "div",
2347
+ { style: { display: "flex", alignItems: "center", gap: 8 } },
2348
+ React.createElement("span", null, (t("diffBaseCommit") || "Base:") + " ", React.createElement("code", null, (diffData && diffData.base ? diffData.base : (state.gitStartCommit || "main")).slice(0, 7))),
2349
+ React.createElement("span", null, "→"),
2350
+ React.createElement("span", null, (t("diffTargetBranch") || "Target:") + " ", React.createElement("code", null, (diffData && diffData.target) || state.gitBranch || state.branchName || "HEAD"))
2351
+ ),
2352
+ React.createElement(
2353
+ "div",
2354
+ { style: { display: "flex", alignItems: "center", gap: 8 } },
2355
+ diffData && diffData.stats ? React.createElement(
2356
+ "span",
2357
+ { className: "dsh-goal-diff-stat-pill" },
2358
+ React.createElement("span", { className: "dsh-goal-diff-ins" }, "+" + diffData.stats.insertions),
2359
+ React.createElement("span", { className: "dsh-goal-diff-del" }, "-" + diffData.stats.deletions)
2360
+ ) : null,
2361
+ React.createElement(
2362
+ "button",
2363
+ {
2364
+ type: "button",
2365
+ className: "dsh-goal-btn icon-only",
2366
+ style: { padding: "2px 8px", fontSize: 11 },
2367
+ onClick: fetchDiff,
2368
+ title: t("diffRefresh") || "Refresh Diff",
2369
+ },
2370
+ "🔄"
2371
+ )
2372
+ )
2373
+ ),
2374
+ diffData && diffData.files && diffData.files.length > 0 ? React.createElement(
2375
+ "div",
2376
+ { className: "dsh-goal-diff-files-list" },
2377
+ React.createElement(
2378
+ "span",
2379
+ {
2380
+ className: `dsh-goal-diff-file-chip${selectedFile === null ? " active" : ""}`,
2381
+ onClick: () => setSelectedFile(null),
2382
+ },
2383
+ (t("allFiles") || "All Files") + ` (${diffData.files.length})`
2384
+ ),
2385
+ diffData.files.map((f) => React.createElement(
2386
+ "span",
2387
+ {
2388
+ key: f.path,
2389
+ className: `dsh-goal-diff-file-chip${selectedFile === f.path ? " active" : ""}`,
2390
+ onClick: () => setSelectedFile(selectedFile === f.path ? null : f.path),
2391
+ title: `${f.path} (+${f.insertions} / -${f.deletions})`,
2392
+ },
2393
+ `${f.path.split("/").pop()} `,
2394
+ React.createElement("span", { className: "dsh-goal-diff-ins" }, "+" + f.insertions),
2395
+ "/",
2396
+ React.createElement("span", { className: "dsh-goal-diff-del" }, "-" + f.deletions)
2397
+ ))
2398
+ ) : null,
2399
+ diffLoading ? React.createElement("div", { className: "dsh-goal-diff-empty" }, t("diffLoading") || "Loading diff...")
2400
+ : diffError ? React.createElement("div", { className: "dsh-goal-diff-empty dsh-goal-gate-error" }, "Error: " + diffError)
2401
+ : !diffData || !diffData.diff || diffData.diff.trim().length === 0 ? React.createElement("div", { className: "dsh-goal-diff-empty" }, t("diffNoChanges") || "No git changes detected relative to base commit.")
2402
+ : React.createElement(
2403
+ "div",
2404
+ { className: "dsh-goal-diff-viewer" },
2405
+ (() => {
2406
+ const allLines = diffData.diff.split("\n");
2407
+ let linesToShow = allLines;
2408
+ if (selectedFile) {
2409
+ const fileChunks = [];
2410
+ let collecting = false;
2411
+ for (const l of allLines) {
2412
+ if (l.startsWith("diff --git ")) {
2413
+ const bPart = l.split(" b/")[1] || "";
2414
+ collecting = bPart === selectedFile;
2415
+ }
2416
+ if (collecting) fileChunks.push(l);
2417
+ }
2418
+ if (fileChunks.length > 0) linesToShow = fileChunks;
2419
+ }
2420
+ return linesToShow.map((line, idx) => {
2421
+ let lineClass = "dsh-goal-diff-line";
2422
+ if (line.startsWith("+") && !line.startsWith("+++")) {
2423
+ lineClass += " dsh-goal-diff-line-add";
2424
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
2425
+ lineClass += " dsh-goal-diff-line-del";
2426
+ } else if (line.startsWith("@@")) {
2427
+ lineClass += " dsh-goal-diff-line-hunk";
2428
+ } else if (line.startsWith("diff --git") || line.startsWith("index ") || line.startsWith("---") || line.startsWith("+++")) {
2429
+ lineClass += " dsh-goal-diff-line-header";
2430
+ }
2431
+ return React.createElement("span", { key: idx, className: lineClass }, line || " ");
2432
+ });
2433
+ })()
2434
+ )
2435
+ );
2436
+ };
2437
+
2438
+ const renderSubGoalsBox = () => {
2439
+ const subList = Array.isArray(state.subGoals) ? state.subGoals : [];
2440
+ return React.createElement(
2441
+ 'div',
2442
+ { className: 'dsh-goal-subgoals-box' },
2443
+ state.isSubGoal
2444
+ ? React.createElement(
2445
+ 'div',
2446
+ { className: 'dsh-goal-subgoal-card', style: { borderStyle: 'dashed' } },
2447
+ React.createElement('div', { style: { fontSize: 11, color: 'var(--dsw-alias-brand-primary)', fontWeight: 600 } }, '🌱 ' + (t('parentGoalBanner') || 'Child Sub-goal of:')),
2448
+ React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)' } }, state.parentGoalId || 'Parent Goal')
2449
+ )
2450
+ : null,
2451
+ subList.length === 0
2452
+ ? React.createElement('div', { className: 'dsh-goal-diff-empty' }, t('noSubGoals') || 'No sub-goals spawned yet.')
2453
+ : React.createElement(
2454
+ 'div',
2455
+ { className: 'dsh-goal-subgoals-list' },
2456
+ subList.map((sub, idx) =>
2457
+ React.createElement(
2458
+ 'div',
2459
+ { key: sub.id || sub.subGoalId || idx, className: 'dsh-goal-subgoal-card' },
2460
+ React.createElement(
2461
+ 'div',
2462
+ { className: 'dsh-goal-subgoal-header' },
2463
+ React.createElement('span', { className: 'dsh-goal-subgoal-title' }, sub.title),
2464
+ React.createElement(
2465
+ 'span',
2466
+ {
2467
+ className: 'dsh-goal-badge ' + (sub.state === 'COMPLETED' ? 'dsh-goal-badge-ok' : sub.state === 'RUNNING' ? 'dsh-goal-badge-plan' : ''),
2468
+ },
2469
+ sub.state || 'PENDING'
2470
+ )
2471
+ ),
2472
+ sub.resultSummary
2473
+ ? React.createElement(
2474
+ 'div',
2475
+ { className: 'dsh-goal-subgoal-summary' },
2476
+ sub.resultSummary
2477
+ )
2478
+ : null
2479
+ )
2480
+ )
2481
+ )
2482
+ );
2483
+ };
2106
2484
 
2107
2485
  const totalTokens = state.tokensUsage?.totalTokens || 0;
2108
2486
 
@@ -2222,7 +2600,46 @@ window.__ModuleLoader__.load({
2222
2600
 
2223
2601
  ),
2224
2602
 
2225
- isPlanMode && !isCompleted
2603
+ React.createElement(
2604
+ 'div',
2605
+ { className: 'dsh-goal-tabs' },
2606
+ React.createElement(
2607
+ 'button',
2608
+ {
2609
+ type: 'button',
2610
+ className: `dsh-goal-tab-btn${activeTab === "overview" ? " active" : ""}`,
2611
+ onClick: () => setActiveTab("overview"),
2612
+ },
2613
+ "📋 " + (t("tabOverview") || "Plan & Overview")
2614
+ ),
2615
+ React.createElement(
2616
+ 'button',
2617
+ {
2618
+ type: 'button',
2619
+ className: `dsh-goal-tab-btn${activeTab === "diff" ? " active" : ""}`,
2620
+ onClick: () => setActiveTab("diff"),
2621
+ },
2622
+ "🔍 " + (t("tabDiff") || "Changes / Diff") + (diffData && diffData.stats && diffData.stats.filesChanged ? ` (${diffData.stats.filesChanged})` : "")
2623
+ ),
2624
+ React.createElement(
2625
+ 'button',
2626
+ {
2627
+ type: 'button',
2628
+ className: `dsh-goal-tab-btn${activeTab === "subgoals" ? " active" : ""}`,
2629
+ onClick: () => setActiveTab("subgoals"),
2630
+ },
2631
+ "🌳 " + (t("tabSubGoals") || "Sub-goals") + (state.subGoals && state.subGoals.length > 0 ? ` (${state.subGoals.length})` : "")
2632
+ )
2633
+ ),
2634
+
2635
+ activeTab === 'diff'
2636
+ ? renderDiffBox()
2637
+ : activeTab === 'subgoals'
2638
+ ? renderSubGoalsBox()
2639
+ : React.createElement(
2640
+ React.Fragment,
2641
+ null,
2642
+ isPlanMode && !isCompleted
2226
2643
  ? React.createElement(
2227
2644
  'div',
2228
2645
  { className: 'dsh-goal-plan-banner' },
@@ -2547,6 +2964,13 @@ window.__ModuleLoader__.load({
2547
2964
  },
2548
2965
 
2549
2966
  `${i + 1}. ${m.title}`,
2967
+ m.compactionSummary
2968
+ ? React.createElement(
2969
+ 'span',
2970
+ { className: 'dsh-goal-compacted-pill', title: m.compactionSummary },
2971
+ '⚡ ' + (t('compactedBadge') || 'Compacted')
2972
+ )
2973
+ : null,
2550
2974
  m.verificationCommand
2551
2975
  ? React.createElement(
2552
2976
  'span',
@@ -2716,7 +3140,8 @@ window.__ModuleLoader__.load({
2716
3140
  )
2717
3141
  )
2718
3142
  )
2719
- : null,
3143
+ : null
3144
+ ),
2720
3145
 
2721
3146
  ),
2722
3147
 
@@ -4068,7 +4493,28 @@ window.__ModuleLoader__.load({
4068
4493
 
4069
4494
  ),
4070
4495
 
4071
- React.createElement('span', { className: 'dsh-goal-title', title: state.title }, state.title),
4496
+ state.isSubGoal
4497
+ ? React.createElement(
4498
+ 'span',
4499
+ { className: 'dsh-goal-badge dsh-goal-badge-subgoal', title: t('subGoalBadge') || 'Sub-goal' },
4500
+ '🌱 ' + (t('subGoalBadge') || 'Sub-goal')
4501
+ )
4502
+ : null,
4503
+
4504
+ state.breadcrumbs && state.breadcrumbs.length > 1
4505
+ ? React.createElement(
4506
+ 'span',
4507
+ { className: 'dsh-goal-breadcrumbs', title: state.title },
4508
+ state.breadcrumbs.map((crumb, idx) =>
4509
+ React.createElement(
4510
+ 'span',
4511
+ { key: crumb.sessionId || idx, className: 'dsh-goal-breadcrumb-item' + (crumb.isCurrent ? ' active' : '') },
4512
+ idx > 0 && React.createElement('span', { className: 'dsh-goal-crumb-sep' }, ' / '),
4513
+ crumb.title
4514
+ )
4515
+ )
4516
+ )
4517
+ : React.createElement('span', { className: 'dsh-goal-title', title: state.title }, state.title),
4072
4518
 
4073
4519
  ),
4074
4520
 
@@ -4078,6 +4524,19 @@ window.__ModuleLoader__.load({
4078
4524
 
4079
4525
  { className: 'dsh-goal-right' },
4080
4526
 
4527
+ state.subGoals && state.subGoals.length > 0
4528
+ ? React.createElement(
4529
+ 'span',
4530
+ {
4531
+ className: 'dsh-goal-stat-pill',
4532
+ style: { cursor: 'pointer' },
4533
+ title: t('subGoalsTitle') || 'Sub-goals',
4534
+ onClick: () => setIsModalOpen(true),
4535
+ },
4536
+ '🌿 ' + state.subGoals.filter((s) => s.state === 'COMPLETED').length + '/' + state.subGoals.length + ' ' + (t('subGoalsPill') || 'sub-goals')
4537
+ )
4538
+ : null,
4539
+
4081
4540
  React.createElement(
4082
4541
 
4083
4542
  'span',
@@ -5533,8 +5992,27 @@ window.__ModuleLoader__.load({
5533
5992
  planningPhase: 'Planning Phase (Read-Only)',
5534
5993
  planPhaseNotice: 'Agent is formulating the plan. File modifications are guarded until operator approval.',
5535
5994
  launchModeLabel: 'Launch Mode:',
5995
+ tabOverview: 'Plan & Overview',
5996
+ tabDiff: 'Changes / Diff',
5997
+ diffLoading: 'Loading diff...',
5998
+ diffNoChanges: 'No git changes detected relative to base commit.',
5999
+ diffFilesChanged: 'Files changed:',
6000
+ diffInsertions: 'insertions',
6001
+ diffDeletions: 'deletions',
6002
+ diffBaseCommit: 'Base:',
6003
+ diffTargetBranch: 'Target:',
6004
+ diffRefresh: 'Refresh Diff',
6005
+ allFiles: 'All Files',
5536
6006
  planModeDesc: 'Plan first, review milestones, then execute',
5537
6007
  actModeDesc: 'Direct execution right away',
6008
+ tabSubGoals: 'Sub-goals',
6009
+ subGoalsTitle: 'Hierarchical Sub-goals',
6010
+ subGoalBadge: 'Sub-goal',
6011
+ subGoalsPill: 'sub-goals',
6012
+ noSubGoals: 'No sub-goals spawned yet. Model can spawn sub-goals via goal_spawn_subgoal tool.',
6013
+ parentGoalBanner: 'Child Sub-goal of:',
6014
+ compactedBadge: 'Compacted',
6015
+ compactionTooltip: 'History compacted to reduce context footprint',
5538
6016
 
5539
6017
  },
5540
6018
 
@@ -5684,8 +6162,27 @@ window.__ModuleLoader__.load({
5684
6162
  planningPhase: '规划阶段(只读)',
5685
6163
  planPhaseNotice: '智能体正在制定方案与里程碑。在操作者批准前,禁止修改文件。',
5686
6164
  launchModeLabel: '启动模式:',
6165
+ tabOverview: '方案与概览',
6166
+ tabDiff: '代码变更 / Diff',
6167
+ diffLoading: '正在加载差异...',
6168
+ diffNoChanges: '未检测到相对于基线提交的代码变更。',
6169
+ diffFilesChanged: '变更文件数:',
6170
+ diffInsertions: '行新增',
6171
+ diffDeletions: '行删除',
6172
+ diffBaseCommit: '基线:',
6173
+ diffTargetBranch: '目标:',
6174
+ diffRefresh: '刷新 Diff',
6175
+ allFiles: '所有文件',
5687
6176
  planModeDesc: '先制定里程碑方案,人工确认后再执行',
5688
6177
  actModeDesc: '立即开始编码与任务执行',
6178
+ tabSubGoals: '子目标',
6179
+ subGoalsTitle: '分层子目标管理',
6180
+ subGoalBadge: '子目标',
6181
+ subGoalsPill: '个子目标',
6182
+ noSubGoals: '当前任务暂无衍生子目标。模型可通过 goal_spawn_subgoal 工具动态创建独立子目标。',
6183
+ parentGoalBanner: '衍生自主目标:',
6184
+ compactedBadge: '已压缩',
6185
+ compactionTooltip: '阶段历史已自动精简压缩以优化上下文窗口',
5689
6186
 
5690
6187
  },
5691
6188
 
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Milestone Context Compaction for DSH Goal Engine (Issue #167).
3
+ * Compresses verbose raw logs and full checklist traces of completed milestones
4
+ * into high-density summaries, cutting context token footprint by > 40%.
5
+ */
6
+
7
+ /**
8
+ * Compact a single milestone upon completion into a structured summary
9
+ * @param {object} milestone
10
+ * @param {object} [options]
11
+ * @returns {object}
12
+ */
13
+ export function compactMilestone(milestone, options = {}) {
14
+ if (!milestone) return null;
15
+
16
+ const id = milestone.id ? String(milestone.id).toUpperCase() : 'M';
17
+ const title = milestone.title || 'Milestone';
18
+ const notes = milestone.notes ? String(milestone.notes).trim() : '';
19
+
20
+ let checklistPart = '';
21
+ if (Array.isArray(milestone.checklist) && milestone.checklist.length > 0) {
22
+ const doneCount = milestone.checklist.filter((item) => item && item.done).length;
23
+ checklistPart = ` [${doneCount}/${milestone.checklist.length} subtasks]`;
24
+ }
25
+
26
+ let gatePart = '';
27
+ if (milestone.verificationCommand) {
28
+ gatePart = milestone.verificationPassed ? ' [Gate: verified]' : ' [Gate: passed]';
29
+ }
30
+
31
+ const outcomePart = notes ? ` (Outcome: ${notes})` : '';
32
+ const summaryLine = `[${id} COMPLETED] ${title}${outcomePart}${checklistPart}${gatePart}`;
33
+
34
+ milestone.compactionSummary = summaryLine;
35
+ milestone.compactedAt = milestone.completedAt || Date.now();
36
+
37
+ return {
38
+ id: milestone.id,
39
+ title,
40
+ notes,
41
+ summary: summaryLine,
42
+ completedAt: milestone.compactedAt,
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Build compacted milestones history block for active prompt injection
48
+ * @param {Array<object>} milestones
49
+ * @param {string} [lang='en']
50
+ * @returns {string}
51
+ */
52
+ export function buildCompactedMilestonesHistory(milestones = [], lang = 'en') {
53
+ if (!Array.isArray(milestones) || milestones.length === 0) return '';
54
+
55
+ const completed = milestones.filter((m) => m && m.status === 'completed');
56
+ if (completed.length === 0) return '';
57
+
58
+ const header = lang === 'zh'
59
+ ? '[已完成里程碑历史简报 / COMPACTED MILESTONES HISTORY]'
60
+ : '[COMPACTED MILESTONES HISTORY]';
61
+
62
+ const lines = completed.map((m) => {
63
+ if (m.compactionSummary) return `- ${m.compactionSummary}`;
64
+ const id = m.id ? String(m.id).toUpperCase() : 'M';
65
+ const outcome = m.notes ? ` (Outcome: ${m.notes})` : '';
66
+ return `- [${id} COMPLETED] ${m.title}${outcome}`;
67
+ });
68
+
69
+ return `${header}\n${lines.join('\n')}`;
70
+ }
71
+
72
+ /**
73
+ * Format milestone into raw uncompacted text (for baseline comparison & active milestones)
74
+ * @param {object} m
75
+ * @param {number} idx
76
+ * @returns {string}
77
+ */
78
+ export function formatRawMilestoneTrace(m, idx = 0) {
79
+ if (!m) return '';
80
+ const statusStr = (m.status || 'pending').toUpperCase();
81
+ let line = ` ${idx + 1}. [${statusStr}] ${m.title || ''}${m.notes ? ` (${m.notes})` : ''}`;
82
+ if (m.verificationCommand) {
83
+ line += `\n [verification_command: ${m.verificationCommand}]`;
84
+ }
85
+ if (Array.isArray(m.checklist) && m.checklist.length > 0) {
86
+ const checkLines = m.checklist
87
+ .map((item) => ` [${item && item.done ? 'x' : ' '}] ${item && item.text ? item.text : ''}`)
88
+ .join('\n');
89
+ line += `\n${checkLines}`;
90
+ }
91
+ return line;
92
+ }
93
+
94
+ /**
95
+ * Estimate context savings achieved by compaction on completed milestones
96
+ * @param {Array<object>} milestones
97
+ * @param {string} [lang='en']
98
+ * @returns {{ completedCount: number, uncompactedChars: number, compactedChars: number, savedChars: number, savingsRatio: number, savingsPercent: string }}
99
+ */
100
+ export function estimateCompactionSavings(milestones = [], lang = 'en') {
101
+ if (!Array.isArray(milestones) || milestones.length === 0) {
102
+ return { completedCount: 0, uncompactedChars: 0, compactedChars: 0, savedChars: 0, savingsRatio: 0, savingsPercent: '0%' };
103
+ }
104
+
105
+ const completed = milestones.filter((m) => m && m.status === 'completed');
106
+ if (completed.length === 0) {
107
+ return { completedCount: 0, uncompactedChars: 0, compactedChars: 0, savedChars: 0, savingsRatio: 0, savingsPercent: '0%' };
108
+ }
109
+
110
+ const uncompacted = completed.map((m, i) => formatRawMilestoneTrace(m, i)).join('\n');
111
+ const compacted = completed.map((m) => m.compactionSummary || `[${m.id ? String(m.id).toUpperCase() : 'M'} COMPLETED] ${m.title}`).join('\n');
112
+
113
+ const uncompactedChars = uncompacted.length;
114
+ const compactedChars = compacted.length;
115
+ const savedChars = Math.max(0, uncompactedChars - compactedChars);
116
+ const savingsRatio = uncompactedChars > 0 ? (savedChars / uncompactedChars) : 0;
117
+
118
+ return {
119
+ completedCount: completed.length,
120
+ uncompactedChars,
121
+ compactedChars,
122
+ savedChars,
123
+ savingsRatio,
124
+ savingsPercent: `${Math.round(savingsRatio * 100)}%`,
125
+ };
126
+ }