taskplane 0.28.0 → 0.28.2
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 +81 -23
- 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 +17 -7
- 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,20 @@ 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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
519
|
+
const batchDone = batch.phase === "completed";
|
|
520
|
+
// TP-178: During merging, only past waves are truly done. The current wave's
|
|
521
|
+
// checkboxDone/allSucceeded can be true (tasks finished) but the wave itself
|
|
522
|
+
// isn't done until the merge completes. (#493)
|
|
523
|
+
const isMerging = batch.phase === "merging";
|
|
524
|
+
const isDone = batchDone || pastWave || (!isMerging && (checkboxDone || ws.allSucceeded));
|
|
525
|
+
const isMergingWave = isMerging && ws.waveIdx === currentWaveIdx;
|
|
526
|
+
const isCurrent = ws.waveIdx === currentWaveIdx && (batch.phase === "executing" || isMerging);
|
|
527
|
+
const isFuture = ws.waveIdx > currentWaveIdx && (batch.phase === "executing" || isMerging);
|
|
522
528
|
|
|
523
529
|
const fillClass = isDone ? "pct-hi" : fillPct > 50 ? "pct-mid" : fillPct > 0 ? "pct-low" : "pct-0";
|
|
524
530
|
const fillWidth = isDone ? 100 : fillPct;
|
|
525
|
-
|
|
531
|
+
// TP-178: Add merging visual state for the wave currently being merged (#493)
|
|
532
|
+
const segClass = isMergingWave ? "wave-seg-current wave-seg-merging" : isCurrent ? "wave-seg-current" : isFuture ? "wave-seg-future" : "";
|
|
526
533
|
|
|
527
534
|
// TP-148: Use segment-aware labels in tooltip when available
|
|
528
535
|
const segLabels = waveSegmentLabels[ws.waveIdx] || new Map();
|
|
@@ -604,9 +611,11 @@ function renderSummary(batch) {
|
|
|
604
611
|
const waveIdx = batch.currentWaveIndex || 0;
|
|
605
612
|
let wavesHtml = '<span style="color:var(--text-muted); font-weight:600; margin-right:4px;">Waves</span>';
|
|
606
613
|
batch.wavePlan.forEach((taskIds, i) => {
|
|
607
|
-
|
|
608
|
-
const
|
|
609
|
-
const
|
|
614
|
+
// TP-178: During merging, only past waves are done; current wave shows merging state (#493)
|
|
615
|
+
const isDone = i < waveIdx || batch.phase === "completed";
|
|
616
|
+
const isCurrent = i === waveIdx && (batch.phase === "executing" || batch.phase === "merging");
|
|
617
|
+
const isMergingChip = i === waveIdx && batch.phase === "merging";
|
|
618
|
+
const cls = isDone ? "done" : isMergingChip ? "current merging" : isCurrent ? "current" : "";
|
|
610
619
|
wavesHtml += `<span class="wave-chip ${cls}">W${i + 1} [${taskIds.join(", ")}]</span>`;
|
|
611
620
|
});
|
|
612
621
|
$summaryWaves.innerHTML = wavesHtml;
|
|
@@ -704,7 +713,10 @@ function renderLanesTasks(batch, sessions) {
|
|
|
704
713
|
// TP-176: Succeeded tasks always show 100% regardless of sidecar/statusData (#491).
|
|
705
714
|
let progressHtml = "";
|
|
706
715
|
const v2p = ls && ls._v2Progress;
|
|
707
|
-
const
|
|
716
|
+
const taskMatch = v2p && ls.taskId === task.taskId;
|
|
717
|
+
// Split V2 usage: progress needs totals > 0, but step/iter can be used whenever present
|
|
718
|
+
const useV2Progress = taskMatch && v2p.total > 0;
|
|
719
|
+
const useV2Step = taskMatch && !!v2p.currentStep;
|
|
708
720
|
if (task.status === "succeeded") {
|
|
709
721
|
// #491 fix: succeeded tasks always show 100%
|
|
710
722
|
progressHtml = `
|
|
@@ -712,9 +724,9 @@ function renderLanesTasks(batch, sessions) {
|
|
|
712
724
|
<div class="task-progress-bar"><div class="task-progress-fill pct-hi" style="width:100%"></div></div>
|
|
713
725
|
<span class="task-progress-text">100%</span>
|
|
714
726
|
</div>`;
|
|
715
|
-
} else if (
|
|
716
|
-
const displayChecked =
|
|
717
|
-
const displayTotal =
|
|
727
|
+
} else if (useV2Progress || (sd && sd.total > 0)) {
|
|
728
|
+
const displayChecked = useV2Progress ? v2p.checked : sd.checked;
|
|
729
|
+
const displayTotal = useV2Progress ? v2p.total : sd.total;
|
|
718
730
|
const displayProgress = displayTotal > 0 ? Math.round((displayChecked / displayTotal) * 100) : 0;
|
|
719
731
|
const fillClass = pctClass(displayProgress);
|
|
720
732
|
progressHtml = `
|
|
@@ -724,6 +736,14 @@ function renderLanesTasks(batch, sessions) {
|
|
|
724
736
|
</div>
|
|
725
737
|
<span class="task-progress-text">${displayProgress}% ${displayChecked}/${displayTotal}</span>
|
|
726
738
|
</div>`;
|
|
739
|
+
} else if (task.status === "running") {
|
|
740
|
+
// #494 fix: running tasks without meaningful totals show executing indicator
|
|
741
|
+
// This covers non-final segments, early execution before sidecar captures, and stale 0/0 data
|
|
742
|
+
progressHtml = `
|
|
743
|
+
<div class="task-progress">
|
|
744
|
+
<div class="task-progress-bar"><div class="task-progress-fill pct-low task-progress-executing" style="width:100%"></div></div>
|
|
745
|
+
<span class="task-progress-text">executing…</span>
|
|
746
|
+
</div>`;
|
|
727
747
|
} else if (task.status === "pending") {
|
|
728
748
|
progressHtml = `
|
|
729
749
|
<div class="task-progress">
|
|
@@ -735,13 +755,20 @@ function renderLanesTasks(batch, sessions) {
|
|
|
735
755
|
}
|
|
736
756
|
|
|
737
757
|
// Step cell
|
|
758
|
+
// TP-178: Prefer V2 snapshot currentStep (refreshed every sidecar poll) over
|
|
759
|
+
// server-parsed statusData which can lag behind (#488).
|
|
738
760
|
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") {
|
|
761
|
+
if (task.status === "succeeded") {
|
|
762
|
+
// TP-178: Succeeded tasks always show "Complete" regardless of sidecar data (#491)
|
|
744
763
|
stepHtml = '<span style="color:var(--green)">Complete</span>';
|
|
764
|
+
} else if (sd || useV2Step) {
|
|
765
|
+
// #488 fix: prefer V2 step name whenever present (even if totals are 0)
|
|
766
|
+
const stepName = useV2Step ? v2p.currentStep : (sd ? sd.currentStep : "Unknown");
|
|
767
|
+
const iter = (useV2Step && v2p.iteration != null) ? v2p.iteration : (sd ? sd.iteration : 0);
|
|
768
|
+
const revs = (useV2Step && v2p.reviews != null) ? v2p.reviews : (sd ? sd.reviews : 0);
|
|
769
|
+
stepHtml = escapeHtml(stepName);
|
|
770
|
+
if (iter > 0) stepHtml += `<span class="task-iter">i${iter}</span>`;
|
|
771
|
+
if (revs > 0) stepHtml += `<span class="task-iter">r${revs}</span>`;
|
|
745
772
|
} else if (task.status === "pending") {
|
|
746
773
|
stepHtml = '<span style="color:var(--text-faint)">Waiting</span>';
|
|
747
774
|
} else {
|
|
@@ -987,15 +1014,27 @@ function renderMergeAgents(batch, sessions) {
|
|
|
987
1014
|
const effectiveAlive = !!effectiveSession;
|
|
988
1015
|
if (effectiveSession) shownSessions.add(effectiveSession);
|
|
989
1016
|
|
|
990
|
-
// Find merge telemetry
|
|
1017
|
+
// TP-178: Find merge telemetry precisely using waveIndex (#498).
|
|
1018
|
+
// First try matching by waveIndex from the telemetry entries (injected from merge snapshots).
|
|
1019
|
+
// Then fall back to session-based matching (lane numbers), but never use a
|
|
1020
|
+
// catch-all fallback that grabs any merge session's telemetry.
|
|
991
1021
|
let mergeTel = null;
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
if (
|
|
1022
|
+
// Priority 1: Match by waveIndex in telemetry entries
|
|
1023
|
+
for (const [telKey, tel] of Object.entries(telemetry)) {
|
|
1024
|
+
if (tel._source === "merge-snapshot" && tel.waveIndex === mr.waveIndex) {
|
|
1025
|
+
mergeTel = tel;
|
|
1026
|
+
break;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
// Priority 2: Match by lane number session
|
|
1030
|
+
if (!mergeTel) {
|
|
1031
|
+
for (const ln of waveLaneNums) {
|
|
1032
|
+
const candidate = getMergeSessionName(ln);
|
|
1033
|
+
if (telemetry[candidate]) { mergeTel = telemetry[candidate]; break; }
|
|
1034
|
+
}
|
|
995
1035
|
}
|
|
996
|
-
//
|
|
1036
|
+
// Priority 3: Effective session telemetry only (no catch-all fallback)
|
|
997
1037
|
if (!mergeTel && effectiveSession) mergeTel = telemetry[effectiveSession] || null;
|
|
998
|
-
if (!mergeTel) mergeTel = mergeSessions.reduce((found, ms) => found || telemetry[ms] || null, null);
|
|
999
1038
|
|
|
1000
1039
|
html += `<tr>`;
|
|
1001
1040
|
html += `<td class="merge-wave-cell">Wave ${mr.waveIndex + 1}</td>`;
|
|
@@ -1443,6 +1482,8 @@ function buildRecoveryTimeline(supervisor) {
|
|
|
1443
1482
|
target: a.target || a.lane || a.taskId || "",
|
|
1444
1483
|
outcome: a.outcome || a.result || "",
|
|
1445
1484
|
reason: a.reason || "",
|
|
1485
|
+
context: a.context || "",
|
|
1486
|
+
detail: a.detail || "",
|
|
1446
1487
|
source: "action"
|
|
1447
1488
|
}));
|
|
1448
1489
|
|
|
@@ -1456,6 +1497,8 @@ function buildRecoveryTimeline(supervisor) {
|
|
|
1456
1497
|
target: e.target || e.lane || e.taskId || "",
|
|
1457
1498
|
outcome: e.outcome || e.result || "",
|
|
1458
1499
|
reason: e.reason || e.message || "",
|
|
1500
|
+
context: e.context || "",
|
|
1501
|
+
detail: e.detail || "",
|
|
1459
1502
|
source: "event"
|
|
1460
1503
|
}));
|
|
1461
1504
|
|
|
@@ -1488,6 +1531,7 @@ function renderSupervisorActions(supervisor) {
|
|
|
1488
1531
|
const target = entry.target;
|
|
1489
1532
|
const outcome = entry.outcome;
|
|
1490
1533
|
const reason = entry.reason;
|
|
1534
|
+
const description = entry.context || entry.detail || "";
|
|
1491
1535
|
|
|
1492
1536
|
const outcomeCls = outcome === "success" || outcome === "recovered"
|
|
1493
1537
|
? "action-success"
|
|
@@ -1507,6 +1551,11 @@ function renderSupervisorActions(supervisor) {
|
|
|
1507
1551
|
if (target) html += `<span class="supervisor-action-target">${escapeHtml(target)}</span>`;
|
|
1508
1552
|
if (outcome) html += `<span class="supervisor-action-outcome ${outcomeCls}">${escapeHtml(outcome)}</span>`;
|
|
1509
1553
|
html += ` </div>`;
|
|
1554
|
+
if (description) {
|
|
1555
|
+
const fullDesc = escapeHtml(description);
|
|
1556
|
+
const truncated = description.length > 100 ? escapeHtml(description.slice(0, 100)) + "\u2026" : fullDesc;
|
|
1557
|
+
html += `<div class="supervisor-action-description" title="${fullDesc}">${truncated}</div>`;
|
|
1558
|
+
}
|
|
1510
1559
|
if (reason) {
|
|
1511
1560
|
html += `<div class="supervisor-action-reason">${escapeHtml(reason)}</div>`;
|
|
1512
1561
|
}
|
|
@@ -1567,6 +1616,9 @@ function render(data) {
|
|
|
1567
1616
|
$lastUpdate.textContent = new Date().toLocaleTimeString();
|
|
1568
1617
|
|
|
1569
1618
|
if (!batch) {
|
|
1619
|
+
// TP-178: Clear viewer when batch disappears (#487)
|
|
1620
|
+
if (lastBatchId && viewerMode) closeViewer();
|
|
1621
|
+
lastBatchId = null;
|
|
1570
1622
|
renderHeader(null);
|
|
1571
1623
|
renderSummary(null);
|
|
1572
1624
|
renderSupervisor(data);
|
|
@@ -1576,6 +1628,12 @@ function render(data) {
|
|
|
1576
1628
|
return;
|
|
1577
1629
|
}
|
|
1578
1630
|
|
|
1631
|
+
// TP-178: Detect batchId change — clear stale viewer state (#487)
|
|
1632
|
+
if (batch.batchId && lastBatchId && batch.batchId !== lastBatchId && viewerMode) {
|
|
1633
|
+
closeViewer();
|
|
1634
|
+
}
|
|
1635
|
+
lastBatchId = batch.batchId || null;
|
|
1636
|
+
|
|
1579
1637
|
// Live batch is running — hide history panel, reset viewing state
|
|
1580
1638
|
if (viewingHistoryId) {
|
|
1581
1639
|
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
|
}
|
|
@@ -15,7 +15,7 @@ import { ORCH_MESSAGES, computeIntegrateCleanupResult } from "./messages.ts";
|
|
|
15
15
|
import type { IntegrateCleanupRepoFindings } from "./messages.ts";
|
|
16
16
|
import { computeWaveAssignments } from "./waves.ts";
|
|
17
17
|
import { createOrchWidget, formatDependencyGraph, formatWavePlan } from "./formatting.ts";
|
|
18
|
-
import { deleteBatchState, loadBatchState, saveBatchState, detectOrphanSessions } from "./persistence.ts";
|
|
18
|
+
import { deleteBatchState, loadBatchState, saveBatchState, detectOrphanSessions, updateBatchHistoryIntegration } from "./persistence.ts";
|
|
19
19
|
import { deleteStaleBranches, listWorktrees, resolveWorktreeBasePath, formatPreflightResults, runPreflight } from "./worktree.ts";
|
|
20
20
|
import { computeTransitiveDependents, resolveCanonicalTaskPaths } from "./execution.ts";
|
|
21
21
|
import { executeOrchBatch } from "./engine.ts";
|
|
@@ -1385,7 +1385,7 @@ export function buildIntegrationExecutor(repoRoot: string, opId?: string, stateR
|
|
|
1385
1385
|
}
|
|
1386
1386
|
},
|
|
1387
1387
|
deleteBatchState: () => {
|
|
1388
|
-
try { deleteBatchState(repoRoot); } catch { /* best effort */ }
|
|
1388
|
+
try { deleteBatchState(stateRoot ?? repoRoot); } catch { /* best effort */ }
|
|
1389
1389
|
},
|
|
1390
1390
|
};
|
|
1391
1391
|
|
|
@@ -1411,6 +1411,11 @@ export function buildIntegrationExecutor(repoRoot: string, opId?: string, stateR
|
|
|
1411
1411
|
try {
|
|
1412
1412
|
cleanupPostIntegrate(stateRoot ?? repoRoot, context.batchId);
|
|
1413
1413
|
} catch { /* best effort — don't fail integration for cleanup errors */ }
|
|
1414
|
+
|
|
1415
|
+
// TP-179: Write integratedAt to batch history before state is gone
|
|
1416
|
+
try {
|
|
1417
|
+
updateBatchHistoryIntegration(stateRoot ?? repoRoot, context.batchId, Date.now());
|
|
1418
|
+
} catch { /* best effort */ }
|
|
1414
1419
|
}
|
|
1415
1420
|
|
|
1416
1421
|
return result;
|
|
@@ -1428,7 +1433,7 @@ export function buildIntegrationExecutor(repoRoot: string, opId?: string, stateR
|
|
|
1428
1433
|
*
|
|
1429
1434
|
* @since TP-043
|
|
1430
1435
|
*/
|
|
1431
|
-
export function buildCiDeps(repoRoot: string): CiDeps {
|
|
1436
|
+
export function buildCiDeps(repoRoot: string, stateRoot?: string): CiDeps {
|
|
1432
1437
|
return {
|
|
1433
1438
|
runCommand: (cmd: string, cmdArgs: string[]) => {
|
|
1434
1439
|
try {
|
|
@@ -1450,7 +1455,7 @@ export function buildCiDeps(repoRoot: string): CiDeps {
|
|
|
1450
1455
|
},
|
|
1451
1456
|
runGit: (gitArgs: string[]) => runGit(gitArgs, repoRoot),
|
|
1452
1457
|
deleteBatchState: () => {
|
|
1453
|
-
try { deleteBatchState(repoRoot); } catch { /* best effort */ }
|
|
1458
|
+
try { deleteBatchState(stateRoot ?? repoRoot); } catch { /* best effort */ }
|
|
1454
1459
|
},
|
|
1455
1460
|
};
|
|
1456
1461
|
}
|
|
@@ -2149,7 +2154,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2149
2154
|
mode,
|
|
2150
2155
|
repoRoot,
|
|
2151
2156
|
buildIntegrationExecutor(repoRoot, opId, execCtx!.workspaceRoot),
|
|
2152
|
-
buildCiDeps(repoRoot),
|
|
2157
|
+
buildCiDeps(repoRoot, execCtx!.workspaceRoot),
|
|
2153
2158
|
sDeps,
|
|
2154
2159
|
);
|
|
2155
2160
|
return;
|
|
@@ -2486,7 +2491,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2486
2491
|
mode,
|
|
2487
2492
|
execCtx!.repoRoot,
|
|
2488
2493
|
buildIntegrationExecutor(execCtx!.repoRoot, opId, execCtx!.workspaceRoot),
|
|
2489
|
-
buildCiDeps(execCtx!.repoRoot),
|
|
2494
|
+
buildCiDeps(execCtx!.repoRoot, execCtx!.workspaceRoot),
|
|
2490
2495
|
sDeps,
|
|
2491
2496
|
);
|
|
2492
2497
|
return;
|
|
@@ -3320,7 +3325,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
3320
3325
|
hasWarning = true;
|
|
3321
3326
|
}
|
|
3322
3327
|
|
|
3323
|
-
|
|
3328
|
+
// TP-179: Write integratedAt to batch history before deleting state
|
|
3329
|
+
if (batchId) {
|
|
3330
|
+
try { updateBatchHistoryIntegration(stateRoot, batchId, Date.now()); } catch { /* best effort */ }
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
try { deleteBatchState(stateRoot); } catch { /* best effort */ }
|
|
3324
3334
|
|
|
3325
3335
|
// ── TP-065: Post-integrate artifact cleanup (Layer 1) ────
|
|
3326
3336
|
// Delete batch-specific telemetry and merge result files.
|
|
@@ -1876,6 +1876,35 @@ export function saveBatchHistory(repoRoot: string, summary: BatchHistorySummary)
|
|
|
1876
1876
|
}
|
|
1877
1877
|
}
|
|
1878
1878
|
|
|
1879
|
+
/**
|
|
1880
|
+
* Update an existing batch history entry with the integration timestamp.
|
|
1881
|
+
*
|
|
1882
|
+
* Sets `integratedAt` on the matching entry (by batchId). If no entry
|
|
1883
|
+
* is found, this is a no-op — the batch may predate the history feature.
|
|
1884
|
+
*
|
|
1885
|
+
* @since TP-179
|
|
1886
|
+
*/
|
|
1887
|
+
export function updateBatchHistoryIntegration(repoRoot: string, batchId: string, integratedAt: number): void {
|
|
1888
|
+
const filePath = batchHistoryPath(repoRoot);
|
|
1889
|
+
try {
|
|
1890
|
+
const history = loadBatchHistory(repoRoot);
|
|
1891
|
+
const entry = history.find(e => e.batchId === batchId);
|
|
1892
|
+
if (!entry) {
|
|
1893
|
+
execLog("batch", "history", `no history entry found for batchId=${batchId}, skipping integratedAt update`);
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
entry.integratedAt = integratedAt;
|
|
1897
|
+
const dir = dirname(filePath);
|
|
1898
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
1899
|
+
const tmpPath = filePath + ".tmp";
|
|
1900
|
+
writeFileSync(tmpPath, JSON.stringify(history, null, 2));
|
|
1901
|
+
renameSync(tmpPath, filePath);
|
|
1902
|
+
execLog("batch", "history", `updated integratedAt for batchId=${batchId}`);
|
|
1903
|
+
} catch (err) {
|
|
1904
|
+
execLog("batch", "history", `failed to update integratedAt: ${err}`);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1879
1908
|
|
|
1880
1909
|
// ── Tier 0 Supervisor Event Logging (TP-039 Step 2) ─────────────────
|
|
1881
1910
|
|
|
@@ -3295,6 +3295,8 @@ export interface BatchHistorySummary {
|
|
|
3295
3295
|
tokens: TokenCounts;
|
|
3296
3296
|
tasks: BatchTaskSummary[];
|
|
3297
3297
|
waves: BatchWaveSummary[];
|
|
3298
|
+
/** Timestamp (ms since epoch) when the batch was integrated. Set by orch-integrate. */
|
|
3299
|
+
integratedAt?: number;
|
|
3298
3300
|
}
|
|
3299
3301
|
|
|
3300
3302
|
/** Max number of batch history entries to retain. */
|