taskplane 0.27.0 → 0.28.1
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/dashboard/public/app.js +232 -31
- package/dashboard/public/style.css +36 -0
- package/dashboard/server.cjs +2 -0
- package/extensions/taskplane/discovery.ts +1818 -1508
- package/extensions/taskplane/execution.ts +33 -2
- package/extensions/taskplane/extension.ts +5135 -5125
- package/extensions/taskplane/lane-runner.ts +375 -44
- package/extensions/taskplane/persistence.ts +29 -0
- package/extensions/taskplane/types.ts +27 -1
- package/package.json +1 -1
- package/skills/create-taskplane-task/SKILL.md +58 -0
- package/skills/create-taskplane-task/references/prompt-template.md +39 -0
- package/templates/agents/task-worker-segment.md +44 -0
- package/templates/agents/task-worker.md +429 -429
package/dashboard/public/app.js
CHANGED
|
@@ -241,6 +241,7 @@ let viewingHistoryId = null; // batchId if viewing history, null if live
|
|
|
241
241
|
|
|
242
242
|
let viewerMode = null; // "conversation" | "status-md" | null
|
|
243
243
|
let viewerTarget = null; // session name (conversation) or taskId (status-md)
|
|
244
|
+
let lastBatchId = null; // TP-178: track batchId for stale viewer detection (#487)
|
|
244
245
|
|
|
245
246
|
// ─── Repo Helpers ───────────────────────────────────────────────────────────
|
|
246
247
|
|
|
@@ -515,14 +516,18 @@ function renderSummary(batch) {
|
|
|
515
516
|
const fillPct = ws.total > 0 ? (ws.checked / ws.total) * 100 : 0;
|
|
516
517
|
const checkboxDone = ws.checked === ws.total && ws.total > 0;
|
|
517
518
|
const pastWave = ws.waveIdx < currentWaveIdx;
|
|
518
|
-
const batchDone = batch.phase === "completed"
|
|
519
|
+
const batchDone = batch.phase === "completed";
|
|
520
|
+
// TP-178: During merging, only past waves are done; current wave is merging, future waves are pending (#493)
|
|
521
|
+
const isMerging = batch.phase === "merging";
|
|
519
522
|
const isDone = checkboxDone || pastWave || batchDone || ws.allSucceeded;
|
|
520
|
-
const
|
|
521
|
-
const
|
|
523
|
+
const isMergingWave = isMerging && ws.waveIdx === currentWaveIdx;
|
|
524
|
+
const isCurrent = ws.waveIdx === currentWaveIdx && (batch.phase === "executing" || isMerging);
|
|
525
|
+
const isFuture = ws.waveIdx > currentWaveIdx && (batch.phase === "executing" || isMerging);
|
|
522
526
|
|
|
523
527
|
const fillClass = isDone ? "pct-hi" : fillPct > 50 ? "pct-mid" : fillPct > 0 ? "pct-low" : "pct-0";
|
|
524
528
|
const fillWidth = isDone ? 100 : fillPct;
|
|
525
|
-
|
|
529
|
+
// TP-178: Add merging visual state for the wave currently being merged (#493)
|
|
530
|
+
const segClass = isMergingWave ? "wave-seg-current wave-seg-merging" : isCurrent ? "wave-seg-current" : isFuture ? "wave-seg-future" : "";
|
|
526
531
|
|
|
527
532
|
// TP-148: Use segment-aware labels in tooltip when available
|
|
528
533
|
const segLabels = waveSegmentLabels[ws.waveIdx] || new Map();
|
|
@@ -604,9 +609,11 @@ function renderSummary(batch) {
|
|
|
604
609
|
const waveIdx = batch.currentWaveIndex || 0;
|
|
605
610
|
let wavesHtml = '<span style="color:var(--text-muted); font-weight:600; margin-right:4px;">Waves</span>';
|
|
606
611
|
batch.wavePlan.forEach((taskIds, i) => {
|
|
607
|
-
|
|
608
|
-
const
|
|
609
|
-
const
|
|
612
|
+
// TP-178: During merging, only past waves are done; current wave shows merging state (#493)
|
|
613
|
+
const isDone = i < waveIdx || batch.phase === "completed";
|
|
614
|
+
const isCurrent = i === waveIdx && (batch.phase === "executing" || batch.phase === "merging");
|
|
615
|
+
const isMergingChip = i === waveIdx && batch.phase === "merging";
|
|
616
|
+
const cls = isDone ? "done" : isMergingChip ? "current merging" : isCurrent ? "current" : "";
|
|
610
617
|
wavesHtml += `<span class="wave-chip ${cls}">W${i + 1} [${taskIds.join(", ")}]</span>`;
|
|
611
618
|
});
|
|
612
619
|
$summaryWaves.innerHTML = wavesHtml;
|
|
@@ -699,21 +706,30 @@ function renderLanesTasks(batch, sessions) {
|
|
|
699
706
|
const showPacketHome = !!packetHomeRepo && packetHomeRepo !== (tRepo || lane.repoId || "");
|
|
700
707
|
|
|
701
708
|
// Progress cell
|
|
709
|
+
// TP-174: Prefer V2 snapshot progress (segment-scoped when available)
|
|
710
|
+
// over full STATUS.md counts when the task is actively running on this lane.
|
|
711
|
+
// TP-176: Succeeded tasks always show 100% regardless of sidecar/statusData (#491).
|
|
702
712
|
let progressHtml = "";
|
|
703
|
-
|
|
704
|
-
|
|
713
|
+
const v2p = ls && ls._v2Progress;
|
|
714
|
+
const useV2 = v2p && v2p.total > 0 && ls.taskId === task.taskId;
|
|
715
|
+
if (task.status === "succeeded") {
|
|
716
|
+
// #491 fix: succeeded tasks always show 100%
|
|
705
717
|
progressHtml = `
|
|
706
718
|
<div class="task-progress">
|
|
707
|
-
<div class="task-progress-bar">
|
|
708
|
-
|
|
709
|
-
</div>
|
|
710
|
-
<span class="task-progress-text">${sd.progress}% ${sd.checked}/${sd.total}</span>
|
|
719
|
+
<div class="task-progress-bar"><div class="task-progress-fill pct-hi" style="width:100%"></div></div>
|
|
720
|
+
<span class="task-progress-text">100%</span>
|
|
711
721
|
</div>`;
|
|
712
|
-
} else if (
|
|
722
|
+
} else if (sd || useV2) {
|
|
723
|
+
const displayChecked = useV2 ? v2p.checked : (sd ? sd.checked : 0);
|
|
724
|
+
const displayTotal = useV2 ? v2p.total : (sd ? sd.total : 0);
|
|
725
|
+
const displayProgress = displayTotal > 0 ? Math.round((displayChecked / displayTotal) * 100) : 0;
|
|
726
|
+
const fillClass = pctClass(displayProgress);
|
|
713
727
|
progressHtml = `
|
|
714
728
|
<div class="task-progress">
|
|
715
|
-
<div class="task-progress-bar"
|
|
716
|
-
|
|
729
|
+
<div class="task-progress-bar">
|
|
730
|
+
<div class="task-progress-fill ${fillClass}" style="width:${displayProgress}%"></div>
|
|
731
|
+
</div>
|
|
732
|
+
<span class="task-progress-text">${displayProgress}% ${displayChecked}/${displayTotal}</span>
|
|
717
733
|
</div>`;
|
|
718
734
|
} else if (task.status === "pending") {
|
|
719
735
|
progressHtml = `
|
|
@@ -721,18 +737,32 @@ function renderLanesTasks(batch, sessions) {
|
|
|
721
737
|
<div class="task-progress-bar"><div class="task-progress-fill pct-0" style="width:0%"></div></div>
|
|
722
738
|
<span class="task-progress-text">0%</span>
|
|
723
739
|
</div>`;
|
|
740
|
+
} else if (task.status === "running") {
|
|
741
|
+
// TP-178: Show executing indicator for running tasks without sidecar data (#494)
|
|
742
|
+
// This covers non-final segment execution where the sidecar hasn't started yet.
|
|
743
|
+
progressHtml = `
|
|
744
|
+
<div class="task-progress">
|
|
745
|
+
<div class="task-progress-bar"><div class="task-progress-fill pct-low task-progress-executing" style="width:100%"></div></div>
|
|
746
|
+
<span class="task-progress-text">executing…</span>
|
|
747
|
+
</div>`;
|
|
724
748
|
} else {
|
|
725
749
|
progressHtml = '<span style="color:var(--text-faint)">—</span>';
|
|
726
750
|
}
|
|
727
751
|
|
|
728
752
|
// Step cell
|
|
753
|
+
// TP-178: Prefer V2 snapshot currentStep (refreshed every sidecar poll) over
|
|
754
|
+
// server-parsed statusData which can lag behind (#488).
|
|
729
755
|
let stepHtml = "";
|
|
730
|
-
if (
|
|
731
|
-
|
|
732
|
-
if (sd.iteration > 0) stepHtml += `<span class="task-iter">i${sd.iteration}</span>`;
|
|
733
|
-
if (sd.reviews > 0) stepHtml += `<span class="task-iter">r${sd.reviews}</span>`;
|
|
734
|
-
} else if (task.status === "succeeded") {
|
|
756
|
+
if (task.status === "succeeded") {
|
|
757
|
+
// TP-178: Succeeded tasks always show "Complete" regardless of sidecar data (#491)
|
|
735
758
|
stepHtml = '<span style="color:var(--green)">Complete</span>';
|
|
759
|
+
} else if (sd || (useV2 && v2p)) {
|
|
760
|
+
const stepName = (useV2 && v2p && v2p.currentStep) ? v2p.currentStep : (sd ? sd.currentStep : "Unknown");
|
|
761
|
+
const iter = (useV2 && v2p && v2p.iteration != null) ? v2p.iteration : (sd ? sd.iteration : 0);
|
|
762
|
+
const revs = (useV2 && v2p && v2p.reviews != null) ? v2p.reviews : (sd ? sd.reviews : 0);
|
|
763
|
+
stepHtml = escapeHtml(stepName);
|
|
764
|
+
if (iter > 0) stepHtml += `<span class="task-iter">i${iter}</span>`;
|
|
765
|
+
if (revs > 0) stepHtml += `<span class="task-iter">r${revs}</span>`;
|
|
736
766
|
} else if (task.status === "pending") {
|
|
737
767
|
stepHtml = '<span style="color:var(--text-faint)">Waiting</span>';
|
|
738
768
|
} else {
|
|
@@ -978,15 +1008,27 @@ function renderMergeAgents(batch, sessions) {
|
|
|
978
1008
|
const effectiveAlive = !!effectiveSession;
|
|
979
1009
|
if (effectiveSession) shownSessions.add(effectiveSession);
|
|
980
1010
|
|
|
981
|
-
// Find merge telemetry
|
|
1011
|
+
// TP-178: Find merge telemetry precisely using waveIndex (#498).
|
|
1012
|
+
// First try matching by waveIndex from the telemetry entries (injected from merge snapshots).
|
|
1013
|
+
// Then fall back to session-based matching (lane numbers), but never use a
|
|
1014
|
+
// catch-all fallback that grabs any merge session's telemetry.
|
|
982
1015
|
let mergeTel = null;
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
if (
|
|
1016
|
+
// Priority 1: Match by waveIndex in telemetry entries
|
|
1017
|
+
for (const [telKey, tel] of Object.entries(telemetry)) {
|
|
1018
|
+
if (tel._source === "merge-snapshot" && tel.waveIndex === mr.waveIndex) {
|
|
1019
|
+
mergeTel = tel;
|
|
1020
|
+
break;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
// Priority 2: Match by lane number session
|
|
1024
|
+
if (!mergeTel) {
|
|
1025
|
+
for (const ln of waveLaneNums) {
|
|
1026
|
+
const candidate = getMergeSessionName(ln);
|
|
1027
|
+
if (telemetry[candidate]) { mergeTel = telemetry[candidate]; break; }
|
|
1028
|
+
}
|
|
986
1029
|
}
|
|
987
|
-
//
|
|
1030
|
+
// Priority 3: Effective session telemetry only (no catch-all fallback)
|
|
988
1031
|
if (!mergeTel && effectiveSession) mergeTel = telemetry[effectiveSession] || null;
|
|
989
|
-
if (!mergeTel) mergeTel = mergeSessions.reduce((found, ms) => found || telemetry[ms] || null, null);
|
|
990
1032
|
|
|
991
1033
|
html += `<tr>`;
|
|
992
1034
|
html += `<td class="merge-wave-cell">Wave ${mr.waveIndex + 1}</td>`;
|
|
@@ -1434,6 +1476,8 @@ function buildRecoveryTimeline(supervisor) {
|
|
|
1434
1476
|
target: a.target || a.lane || a.taskId || "",
|
|
1435
1477
|
outcome: a.outcome || a.result || "",
|
|
1436
1478
|
reason: a.reason || "",
|
|
1479
|
+
context: a.context || "",
|
|
1480
|
+
detail: a.detail || "",
|
|
1437
1481
|
source: "action"
|
|
1438
1482
|
}));
|
|
1439
1483
|
|
|
@@ -1447,6 +1491,8 @@ function buildRecoveryTimeline(supervisor) {
|
|
|
1447
1491
|
target: e.target || e.lane || e.taskId || "",
|
|
1448
1492
|
outcome: e.outcome || e.result || "",
|
|
1449
1493
|
reason: e.reason || e.message || "",
|
|
1494
|
+
context: e.context || "",
|
|
1495
|
+
detail: e.detail || "",
|
|
1450
1496
|
source: "event"
|
|
1451
1497
|
}));
|
|
1452
1498
|
|
|
@@ -1479,6 +1525,7 @@ function renderSupervisorActions(supervisor) {
|
|
|
1479
1525
|
const target = entry.target;
|
|
1480
1526
|
const outcome = entry.outcome;
|
|
1481
1527
|
const reason = entry.reason;
|
|
1528
|
+
const description = entry.context || entry.detail || "";
|
|
1482
1529
|
|
|
1483
1530
|
const outcomeCls = outcome === "success" || outcome === "recovered"
|
|
1484
1531
|
? "action-success"
|
|
@@ -1498,6 +1545,11 @@ function renderSupervisorActions(supervisor) {
|
|
|
1498
1545
|
if (target) html += `<span class="supervisor-action-target">${escapeHtml(target)}</span>`;
|
|
1499
1546
|
if (outcome) html += `<span class="supervisor-action-outcome ${outcomeCls}">${escapeHtml(outcome)}</span>`;
|
|
1500
1547
|
html += ` </div>`;
|
|
1548
|
+
if (description) {
|
|
1549
|
+
const fullDesc = escapeHtml(description);
|
|
1550
|
+
const truncated = description.length > 100 ? escapeHtml(description.slice(0, 100)) + "\u2026" : fullDesc;
|
|
1551
|
+
html += `<div class="supervisor-action-description" title="${fullDesc}">${truncated}</div>`;
|
|
1552
|
+
}
|
|
1501
1553
|
if (reason) {
|
|
1502
1554
|
html += `<div class="supervisor-action-reason">${escapeHtml(reason)}</div>`;
|
|
1503
1555
|
}
|
|
@@ -1558,6 +1610,9 @@ function render(data) {
|
|
|
1558
1610
|
$lastUpdate.textContent = new Date().toLocaleTimeString();
|
|
1559
1611
|
|
|
1560
1612
|
if (!batch) {
|
|
1613
|
+
// TP-178: Clear viewer when batch disappears (#487)
|
|
1614
|
+
if (lastBatchId && viewerMode) closeViewer();
|
|
1615
|
+
lastBatchId = null;
|
|
1561
1616
|
renderHeader(null);
|
|
1562
1617
|
renderSummary(null);
|
|
1563
1618
|
renderSupervisor(data);
|
|
@@ -1567,6 +1622,12 @@ function render(data) {
|
|
|
1567
1622
|
return;
|
|
1568
1623
|
}
|
|
1569
1624
|
|
|
1625
|
+
// TP-178: Detect batchId change — clear stale viewer state (#487)
|
|
1626
|
+
if (batch.batchId && lastBatchId && batch.batchId !== lastBatchId && viewerMode) {
|
|
1627
|
+
closeViewer();
|
|
1628
|
+
}
|
|
1629
|
+
lastBatchId = batch.batchId || null;
|
|
1630
|
+
|
|
1570
1631
|
// Live batch is running — hide history panel, reset viewing state
|
|
1571
1632
|
if (viewingHistoryId) {
|
|
1572
1633
|
viewingHistoryId = null;
|
|
@@ -1881,6 +1942,127 @@ function renderV2Event(evt) {
|
|
|
1881
1942
|
}
|
|
1882
1943
|
}
|
|
1883
1944
|
|
|
1945
|
+
// ── Segment-Scoped STATUS.md Helpers (TP-176) ──────────────────────────────
|
|
1946
|
+
|
|
1947
|
+
/**
|
|
1948
|
+
* Resolve the active segment repoId for a given task.
|
|
1949
|
+
* Uses runtimeLaneSnapshots (active segment) and falls back to
|
|
1950
|
+
* taskSegmentProgress (batch state).
|
|
1951
|
+
* Returns { repoId, segmentInfo } or null if single-segment / unresolvable.
|
|
1952
|
+
*/
|
|
1953
|
+
function resolveActiveSegmentForTask(taskId) {
|
|
1954
|
+
if (!currentData) return null;
|
|
1955
|
+
const batch = currentData.batch;
|
|
1956
|
+
if (!batch) return null;
|
|
1957
|
+
const task = (batch.tasks || []).find(t => t.taskId === taskId);
|
|
1958
|
+
if (!task) return null;
|
|
1959
|
+
const segmentIds = Array.isArray(task.segmentIds) ? task.segmentIds.filter(id => typeof id === "string") : [];
|
|
1960
|
+
if (segmentIds.length <= 1) return null; // single-segment or no segments
|
|
1961
|
+
|
|
1962
|
+
// Try to get active segment from runtime lane snapshots
|
|
1963
|
+
const v2Snapshots = currentData.runtimeLaneSnapshots || {};
|
|
1964
|
+
for (const snap of Object.values(v2Snapshots)) {
|
|
1965
|
+
if (snap && snap.taskId === taskId && snap.segmentId) {
|
|
1966
|
+
const parsed = parseSegmentId(snap.segmentId);
|
|
1967
|
+
if (parsed) {
|
|
1968
|
+
const idx = segmentIds.indexOf(snap.segmentId);
|
|
1969
|
+
return {
|
|
1970
|
+
repoId: parsed.repoId,
|
|
1971
|
+
segmentInfo: {
|
|
1972
|
+
index: idx >= 0 ? idx + 1 : null,
|
|
1973
|
+
total: segmentIds.length,
|
|
1974
|
+
repoId: parsed.repoId,
|
|
1975
|
+
segmentId: snap.segmentId,
|
|
1976
|
+
},
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
// Fallback: use taskSegmentProgress (batch state)
|
|
1983
|
+
const segmentStatusMap = buildSegmentStatusMap(batch);
|
|
1984
|
+
const info = taskSegmentProgress(task, segmentStatusMap, null);
|
|
1985
|
+
if (info && info.repoId) {
|
|
1986
|
+
return { repoId: info.repoId, segmentInfo: info };
|
|
1987
|
+
}
|
|
1988
|
+
return null;
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
/**
|
|
1992
|
+
* Filter STATUS.md content to show only the active segment's blocks.
|
|
1993
|
+
* Within each `### Step N:` section, removes `#### Segment: <otherRepo>` blocks
|
|
1994
|
+
* and keeps only the block matching `activeRepoId`.
|
|
1995
|
+
* Non-step content (metadata, notes, reviews, etc.) is preserved.
|
|
1996
|
+
*
|
|
1997
|
+
* Returns the filtered markdown string, or the original if no segment markers found.
|
|
1998
|
+
*/
|
|
1999
|
+
function filterStatusMdForSegment(markdown, activeRepoId) {
|
|
2000
|
+
if (!activeRepoId) return markdown;
|
|
2001
|
+
const lines = markdown.split('\n');
|
|
2002
|
+
const result = [];
|
|
2003
|
+
let inStep = false; // inside a ### Step section
|
|
2004
|
+
let inSegmentBlock = false; // inside a #### Segment: <repo> block
|
|
2005
|
+
let segmentMatch = false; // current segment block matches active repo
|
|
2006
|
+
let foundAnySegmentHeader = false;
|
|
2007
|
+
|
|
2008
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2009
|
+
const line = lines[i];
|
|
2010
|
+
|
|
2011
|
+
// Detect step headers: ### Step N: ...
|
|
2012
|
+
if (/^###\s+Step\s+\d+/.test(line)) {
|
|
2013
|
+
inStep = true;
|
|
2014
|
+
inSegmentBlock = false;
|
|
2015
|
+
segmentMatch = false;
|
|
2016
|
+
result.push(line);
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
// Detect non-step ### headers (e.g., ### Reviews, ### Notes)
|
|
2021
|
+
if (/^###\s+/.test(line) && !/^###\s+Step\s+\d+/.test(line)) {
|
|
2022
|
+
inStep = false;
|
|
2023
|
+
inSegmentBlock = false;
|
|
2024
|
+
segmentMatch = false;
|
|
2025
|
+
result.push(line);
|
|
2026
|
+
continue;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
// Inside a step section, detect #### Segment: <repoId> headers
|
|
2030
|
+
if (inStep && /^####\s+Segment:\s*/.test(line)) {
|
|
2031
|
+
foundAnySegmentHeader = true;
|
|
2032
|
+
const segRepo = line.replace(/^####\s+Segment:\s*/, '').trim();
|
|
2033
|
+
inSegmentBlock = true;
|
|
2034
|
+
segmentMatch = (segRepo === activeRepoId);
|
|
2035
|
+
if (segmentMatch) {
|
|
2036
|
+
result.push(line);
|
|
2037
|
+
}
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// Detect any other #### header (ends current segment block)
|
|
2042
|
+
if (/^####\s+/.test(line)) {
|
|
2043
|
+
inSegmentBlock = false;
|
|
2044
|
+
segmentMatch = false;
|
|
2045
|
+
result.push(line);
|
|
2046
|
+
continue;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// If we're in a segment block, only include matching lines
|
|
2050
|
+
if (inSegmentBlock) {
|
|
2051
|
+
if (segmentMatch) {
|
|
2052
|
+
result.push(line);
|
|
2053
|
+
}
|
|
2054
|
+
continue;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
// Outside segment blocks: keep the line
|
|
2058
|
+
result.push(line);
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
// If no segment headers were found, return original (fallback for single-segment)
|
|
2062
|
+
if (!foundAnySegmentHeader) return markdown;
|
|
2063
|
+
return result.join('\n');
|
|
2064
|
+
}
|
|
2065
|
+
|
|
1884
2066
|
// ── Open STATUS.md viewer ───────────────────────────────────────────────────
|
|
1885
2067
|
|
|
1886
2068
|
function viewStatusMd(taskId) {
|
|
@@ -1897,7 +2079,15 @@ function viewStatusMd(taskId) {
|
|
|
1897
2079
|
autoScrollOn = false;
|
|
1898
2080
|
lastStatusMdText = '';
|
|
1899
2081
|
|
|
1900
|
-
|
|
2082
|
+
// TP-176: Include segment context in title for multi-segment tasks
|
|
2083
|
+
const segData = resolveActiveSegmentForTask(taskId);
|
|
2084
|
+
if (segData && segData.segmentInfo) {
|
|
2085
|
+
const label = segmentProgressText(segData.segmentInfo);
|
|
2086
|
+
$terminalTitle.textContent = `STATUS.md — ${taskId} · ${label}`;
|
|
2087
|
+
} else {
|
|
2088
|
+
$terminalTitle.textContent = `STATUS.md — ${taskId}`;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
1901
2091
|
$autoScrollText.textContent = 'Track progress';
|
|
1902
2092
|
$autoScrollCheckbox.checked = false;
|
|
1903
2093
|
$terminalPanel.style.display = '';
|
|
@@ -1916,11 +2106,22 @@ function pollStatusMd() {
|
|
|
1916
2106
|
return r.text();
|
|
1917
2107
|
})
|
|
1918
2108
|
.then(text => {
|
|
2109
|
+
// TP-176: Apply segment-scoped filtering for multi-segment tasks.
|
|
2110
|
+
// Re-resolve on each poll since the active segment may change.
|
|
2111
|
+
const segData = resolveActiveSegmentForTask(viewerTarget);
|
|
2112
|
+
let displayText = text;
|
|
2113
|
+
if (segData && segData.repoId) {
|
|
2114
|
+
displayText = filterStatusMdForSegment(text, segData.repoId);
|
|
2115
|
+
// Update title with current segment context (may change between polls)
|
|
2116
|
+
const label = segmentProgressText(segData.segmentInfo);
|
|
2117
|
+
$terminalTitle.textContent = `STATUS.md \u2014 ${viewerTarget} \u00b7 ${label}`;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
1919
2120
|
// Diff-and-skip: no change, no DOM update
|
|
1920
|
-
if (
|
|
1921
|
-
lastStatusMdText =
|
|
2121
|
+
if (displayText === lastStatusMdText) return;
|
|
2122
|
+
lastStatusMdText = displayText;
|
|
1922
2123
|
|
|
1923
|
-
const { html, hasLastChecked } = renderStatusMd(
|
|
2124
|
+
const { html, hasLastChecked } = renderStatusMd(displayText);
|
|
1924
2125
|
$terminalBody.innerHTML = html;
|
|
1925
2126
|
|
|
1926
2127
|
// Update tracking highlight
|
|
@@ -415,6 +415,20 @@ body {
|
|
|
415
415
|
color: var(--green);
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
+
/* TP-178: Merging wave indicator — pulsing accent to distinguish from executing (#493) */
|
|
419
|
+
.wave-chip.merging {
|
|
420
|
+
animation: merge-pulse 1.5s ease-in-out infinite;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.wave-seg-merging {
|
|
424
|
+
animation: merge-pulse 1.5s ease-in-out infinite;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
@keyframes merge-pulse {
|
|
428
|
+
0%, 100% { opacity: 1; }
|
|
429
|
+
50% { opacity: 0.5; }
|
|
430
|
+
}
|
|
431
|
+
|
|
418
432
|
/* ─── Content ──────────────────────────────────────────────────────────── */
|
|
419
433
|
|
|
420
434
|
.content {
|
|
@@ -713,6 +727,16 @@ body {
|
|
|
713
727
|
.task-progress-fill.pct-mid { background: var(--cyan); }
|
|
714
728
|
.task-progress-fill.pct-hi { background: var(--green); }
|
|
715
729
|
|
|
730
|
+
/* TP-178: Pulsing indicator for executing tasks without sidecar progress (#494) */
|
|
731
|
+
.task-progress-executing {
|
|
732
|
+
opacity: 0.4;
|
|
733
|
+
animation: executing-pulse 2s ease-in-out infinite;
|
|
734
|
+
}
|
|
735
|
+
@keyframes executing-pulse {
|
|
736
|
+
0%, 100% { opacity: 0.25; }
|
|
737
|
+
50% { opacity: 0.55; }
|
|
738
|
+
}
|
|
739
|
+
|
|
716
740
|
.task-progress-text {
|
|
717
741
|
font-family: var(--font-mono);
|
|
718
742
|
font-size: 0.75rem;
|
|
@@ -1555,6 +1579,18 @@ body {
|
|
|
1555
1579
|
color: var(--yellow);
|
|
1556
1580
|
}
|
|
1557
1581
|
|
|
1582
|
+
.supervisor-action-description {
|
|
1583
|
+
font-size: 0.8rem;
|
|
1584
|
+
color: var(--text-secondary, #b0b0b0);
|
|
1585
|
+
margin-top: 3px;
|
|
1586
|
+
line-height: 1.4;
|
|
1587
|
+
overflow: hidden;
|
|
1588
|
+
text-overflow: ellipsis;
|
|
1589
|
+
white-space: nowrap;
|
|
1590
|
+
max-width: 600px;
|
|
1591
|
+
cursor: default;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1558
1594
|
.supervisor-action-reason {
|
|
1559
1595
|
font-size: 0.78rem;
|
|
1560
1596
|
color: var(--text-muted);
|
package/dashboard/server.cjs
CHANGED
|
@@ -1153,6 +1153,8 @@ function buildDashboardState() {
|
|
|
1153
1153
|
latestTotalTokens: (agent.inputTokens || 0) + (agent.outputTokens || 0),
|
|
1154
1154
|
_updatedAt: snap.updatedAt,
|
|
1155
1155
|
_source: "merge-snapshot",
|
|
1156
|
+
// TP-178: Include waveIndex for precise wave-telemetry association (#498)
|
|
1157
|
+
waveIndex: snap.waveIndex != null ? snap.waveIndex : undefined,
|
|
1156
1158
|
};
|
|
1157
1159
|
}
|
|
1158
1160
|
}
|