taskplane 0.28.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 +70 -18
- package/dashboard/public/style.css +36 -0
- package/dashboard/server.cjs +2 -0
- package/extensions/taskplane/discovery.ts +4 -4
- package/extensions/taskplane/extension.ts +5135 -5125
- package/extensions/taskplane/persistence.ts +29 -0
- package/extensions/taskplane/types.ts +2 -0
- package/package.json +1 -1
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;
|
|
@@ -730,18 +737,32 @@ function renderLanesTasks(batch, sessions) {
|
|
|
730
737
|
<div class="task-progress-bar"><div class="task-progress-fill pct-0" style="width:0%"></div></div>
|
|
731
738
|
<span class="task-progress-text">0%</span>
|
|
732
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>`;
|
|
733
748
|
} else {
|
|
734
749
|
progressHtml = '<span style="color:var(--text-faint)">—</span>';
|
|
735
750
|
}
|
|
736
751
|
|
|
737
752
|
// Step cell
|
|
753
|
+
// TP-178: Prefer V2 snapshot currentStep (refreshed every sidecar poll) over
|
|
754
|
+
// server-parsed statusData which can lag behind (#488).
|
|
738
755
|
let stepHtml = "";
|
|
739
|
-
if (
|
|
740
|
-
|
|
741
|
-
if (sd.iteration > 0) stepHtml += `<span class="task-iter">i${sd.iteration}</span>`;
|
|
742
|
-
if (sd.reviews > 0) stepHtml += `<span class="task-iter">r${sd.reviews}</span>`;
|
|
743
|
-
} else if (task.status === "succeeded") {
|
|
756
|
+
if (task.status === "succeeded") {
|
|
757
|
+
// TP-178: Succeeded tasks always show "Complete" regardless of sidecar data (#491)
|
|
744
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>`;
|
|
745
766
|
} else if (task.status === "pending") {
|
|
746
767
|
stepHtml = '<span style="color:var(--text-faint)">Waiting</span>';
|
|
747
768
|
} else {
|
|
@@ -987,15 +1008,27 @@ function renderMergeAgents(batch, sessions) {
|
|
|
987
1008
|
const effectiveAlive = !!effectiveSession;
|
|
988
1009
|
if (effectiveSession) shownSessions.add(effectiveSession);
|
|
989
1010
|
|
|
990
|
-
// 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.
|
|
991
1015
|
let mergeTel = null;
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
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
|
+
}
|
|
995
1029
|
}
|
|
996
|
-
//
|
|
1030
|
+
// Priority 3: Effective session telemetry only (no catch-all fallback)
|
|
997
1031
|
if (!mergeTel && effectiveSession) mergeTel = telemetry[effectiveSession] || null;
|
|
998
|
-
if (!mergeTel) mergeTel = mergeSessions.reduce((found, ms) => found || telemetry[ms] || null, null);
|
|
999
1032
|
|
|
1000
1033
|
html += `<tr>`;
|
|
1001
1034
|
html += `<td class="merge-wave-cell">Wave ${mr.waveIndex + 1}</td>`;
|
|
@@ -1443,6 +1476,8 @@ function buildRecoveryTimeline(supervisor) {
|
|
|
1443
1476
|
target: a.target || a.lane || a.taskId || "",
|
|
1444
1477
|
outcome: a.outcome || a.result || "",
|
|
1445
1478
|
reason: a.reason || "",
|
|
1479
|
+
context: a.context || "",
|
|
1480
|
+
detail: a.detail || "",
|
|
1446
1481
|
source: "action"
|
|
1447
1482
|
}));
|
|
1448
1483
|
|
|
@@ -1456,6 +1491,8 @@ function buildRecoveryTimeline(supervisor) {
|
|
|
1456
1491
|
target: e.target || e.lane || e.taskId || "",
|
|
1457
1492
|
outcome: e.outcome || e.result || "",
|
|
1458
1493
|
reason: e.reason || e.message || "",
|
|
1494
|
+
context: e.context || "",
|
|
1495
|
+
detail: e.detail || "",
|
|
1459
1496
|
source: "event"
|
|
1460
1497
|
}));
|
|
1461
1498
|
|
|
@@ -1488,6 +1525,7 @@ function renderSupervisorActions(supervisor) {
|
|
|
1488
1525
|
const target = entry.target;
|
|
1489
1526
|
const outcome = entry.outcome;
|
|
1490
1527
|
const reason = entry.reason;
|
|
1528
|
+
const description = entry.context || entry.detail || "";
|
|
1491
1529
|
|
|
1492
1530
|
const outcomeCls = outcome === "success" || outcome === "recovered"
|
|
1493
1531
|
? "action-success"
|
|
@@ -1507,6 +1545,11 @@ function renderSupervisorActions(supervisor) {
|
|
|
1507
1545
|
if (target) html += `<span class="supervisor-action-target">${escapeHtml(target)}</span>`;
|
|
1508
1546
|
if (outcome) html += `<span class="supervisor-action-outcome ${outcomeCls}">${escapeHtml(outcome)}</span>`;
|
|
1509
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
|
+
}
|
|
1510
1553
|
if (reason) {
|
|
1511
1554
|
html += `<div class="supervisor-action-reason">${escapeHtml(reason)}</div>`;
|
|
1512
1555
|
}
|
|
@@ -1567,6 +1610,9 @@ function render(data) {
|
|
|
1567
1610
|
$lastUpdate.textContent = new Date().toLocaleTimeString();
|
|
1568
1611
|
|
|
1569
1612
|
if (!batch) {
|
|
1613
|
+
// TP-178: Clear viewer when batch disappears (#487)
|
|
1614
|
+
if (lastBatchId && viewerMode) closeViewer();
|
|
1615
|
+
lastBatchId = null;
|
|
1570
1616
|
renderHeader(null);
|
|
1571
1617
|
renderSummary(null);
|
|
1572
1618
|
renderSupervisor(data);
|
|
@@ -1576,6 +1622,12 @@ function render(data) {
|
|
|
1576
1622
|
return;
|
|
1577
1623
|
}
|
|
1578
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
|
+
|
|
1579
1631
|
// Live batch is running — hide history panel, reset viewing state
|
|
1580
1632
|
if (viewingHistoryId) {
|
|
1581
1633
|
viewingHistoryId = null;
|
|
@@ -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
|
}
|
|
@@ -659,11 +659,11 @@ export function parsePromptForOrchestrator(
|
|
|
659
659
|
|
|
660
660
|
// Check for "None" variants
|
|
661
661
|
if (!/\*?\*?None\*?\*?/i.test(depBody) && depBody.length > 0) {
|
|
662
|
-
// Pattern 1: "**Requires:** COMP-005 ..." or "**
|
|
663
|
-
const
|
|
664
|
-
/\*?\*?Requires:?\*?\*?\s*((?:[a-z0-9-]+\/)?[A-Z]+-\d+)/gi,
|
|
662
|
+
// Pattern 1: "**Requires:** COMP-005 ..." or "**Task:** TO-014 ..."
|
|
663
|
+
const labeledMatches = depBody.matchAll(
|
|
664
|
+
/\*?\*?(?:Requires|Task):?\*?\*?\s*((?:[a-z0-9-]+\/)?[A-Z]+-\d+)/gi,
|
|
665
665
|
);
|
|
666
|
-
for (const m of
|
|
666
|
+
for (const m of labeledMatches) {
|
|
667
667
|
const dep = normalizeDependencyReference(m[1]);
|
|
668
668
|
if (!dependencies.includes(dep)) dependencies.push(dep);
|
|
669
669
|
}
|