Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 0 additions & 26 deletions apps/decodex-app/Sources/DecodexApp/AccountPanelView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3132,7 +3132,6 @@ struct OperatorLanePopoverView: View {

return [
contextMetric(lifecycleMetrics),
toolsMetric(lifecycleMetrics),
trackerMetric(lifecycleMetrics),
protocolMetric(lifecycleMetrics),
].compactMap { $0 }
Expand Down Expand Up @@ -3166,31 +3165,6 @@ struct OperatorLanePopoverView: View {
)
}

private func toolsMetric(_ metrics: OperatorLifecycleMetrics) -> OperatorTotalMetric? {
var items = [OperatorLaneReadoutItem]()
if metrics.toolCallCount > 0 {
items.append(OperatorLaneReadoutItem(label: "tools", value: formatCompactCount(metrics.toolCallCount)))
}

if let largestOutput = metrics.largestToolOutputBytes, largestOutput > 0 {
items.append(
OperatorLaneReadoutItem(
label: "max output",
value: formatLargestOutput(bytes: largestOutput)
)
)
}

guard items.isEmpty == false else {
return nil
}

return OperatorTotalMetric(
title: "Tools",
items: items
)
}

private func trackerMetric(_ metrics: OperatorLifecycleMetrics) -> OperatorTotalMetric? {
guard let bucket = lifecycleBucket(named: "Tracker", in: metrics.buckets) else {
return nil
Expand Down
2 changes: 1 addition & 1 deletion apps/decodex-app/Sources/DecodexApp/AccountStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,12 @@ final class AccountStore: ObservableObject {
activeRunsComplete: payload.activeRunsComplete ?? true,
emittedAt: payload.emittedAt ?? Date()
)
liveRunActivity = activity
if let operatorSnapshot {
self.operatorSnapshot = activity.merging(into: operatorSnapshot)
} else {
operatorSnapshot = OperatorSnapshotResponse.activeRunsOnly(activeRuns)
}
liveRunActivity = activity.shouldPersistAsSnapshotOverlay ? activity : nil
operatorSnapshotUpdatedAt = activity.emittedAt
default:
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,10 @@ struct OperatorRunActivitySnapshot: Sendable {
let activeRunsComplete: Bool
let emittedAt: Date

var shouldPersistAsSnapshotOverlay: Bool {
activeRuns.isEmpty == false || activeRunsComplete == false
}

func merging(into snapshot: OperatorSnapshotResponse) -> OperatorSnapshotResponse {
snapshot.mergingRunActivity(activeRuns, activeRunsComplete: activeRunsComplete)
}
Expand Down
65 changes: 65 additions & 0 deletions apps/decodex-app/Tests/DecodexAppTests/AccountModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,71 @@ final class AccountModelTests: XCTestCase {
XCTAssertTrue(store.operatorSnapshot?.activeRuns(for: account).isEmpty ?? false)
}

@MainActor
func testCompleteEmptyRunActivityDoesNotKeepClearingNewSnapshots() throws {
let account = makeAccount(
status: "available",
email: "copy@example.com",
accountFingerprint: "...123456"
)
let store = AccountStore()

try store.applyOperatorDashboardEvent(dashboardEvent(
type: "snapshot",
payload: """
{
"snapshotPublishedAtUnixEpoch": 20,
"snapshot": {
"active_runs": [
{
"run_id": "run-live",
"issue_identifier": "XY-672",
"account": {
"email": "copy@example.com",
"account_fingerprint": "...123456"
}
}
]
}
}
"""
))
try store.applyOperatorDashboardEvent(dashboardEvent(
type: "runActivity",
payload: """
{
"emittedAtUnixEpoch": 30,
"activeRunsComplete": true,
"activeRuns": []
}
"""
))
XCTAssertTrue(store.operatorSnapshot?.activeRuns(for: account).isEmpty ?? false)

try store.applyOperatorDashboardEvent(dashboardEvent(
type: "snapshot",
payload: """
{
"snapshotPublishedAtUnixEpoch": 40,
"snapshot": {
"active_runs": [
{
"run_id": "run-returned",
"issue_identifier": "XY-934",
"account": {
"email": "copy@example.com",
"account_fingerprint": "...123456"
}
}
]
}
}
"""
))

XCTAssertEqual(store.operatorSnapshot?.activeRuns(for: account).map(\.runID), ["run-returned"])
}

func testOperatorSnapshotWarningSummaryUsesRawWarningToken() throws {
let payload = """
{
Expand Down
57 changes: 32 additions & 25 deletions apps/decodex/src/orchestrator/operator_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -8606,23 +8606,6 @@ <h2 id="recent-title">Run History</h2>
rows.push({ label: "Context", segments: contextSegments });
}

const toolCalls = lifecycleNumber(lifecycle?.tool_call_count);
const largestOutput = lifecycle?.largest_tool_output_bytes;
const toolSegments = [];
if (toolCalls > 0) {
appendLifecycleMetricSegment(toolSegments, formatCompactCount(toolCalls), "tools");
}
if (largestOutput != null) {
appendLifecycleMetricSegment(
toolSegments,
formatLargestOutputValue(largestOutput),
"max output",
);
}
if (toolSegments.length) {
rows.push({ label: "Tools", segments: toolSegments });
}

const tracker = lifecycleBucket(lifecycle, "Tracker");
if (tracker) {
const trackerSegments = [];
Expand Down Expand Up @@ -11321,17 +11304,37 @@ <h4>${escapeHtml(worktree.branch_name)}</h4>
};
}

function snapshotWithLiveRunActivity(snapshot) {
if (!snapshot || !dashboardSocketIsOpen()) {
return snapshot;
function dashboardLiveRunActivityHasOverlay({ includeCompletedEmpty = false } = {}) {
if (!dashboardLiveRunActivitySeen) {
return false;
}
if (dashboardLiveActiveRuns.length || !dashboardLiveActiveRunsComplete) {
return true;
}

return includeCompletedEmpty;
}

function clearDashboardLiveRunActivityOverlayIfCompleteEmpty() {
if (
!dashboardLiveRunActivitySeen &&
!dashboardLiveActiveRuns.length &&
!dashboardLiveAccounts &&
!dashboardLiveAccountControl
dashboardLiveRunActivitySeen &&
dashboardLiveActiveRunsComplete &&
!dashboardLiveActiveRuns.length
) {
dashboardLiveRunActivitySeen = false;
dashboardLiveActiveRuns = [];
dashboardLiveActiveRunsComplete = true;
dashboardLiveAccounts = null;
dashboardLiveAccountControl = null;
}
}

function snapshotWithLiveRunActivity(snapshot, options = {}) {
if (!snapshot || !dashboardSocketIsOpen()) {
return snapshot;
}

if (!dashboardLiveRunActivityHasOverlay(options)) {
return snapshot;
}

Expand Down Expand Up @@ -11373,13 +11376,16 @@ <h4>${escapeHtml(worktree.branch_name)}</h4>
error: false,
lastEventAt: new Date().toISOString(),
});
clearDashboardLiveRunActivityOverlayIfCompleteEmpty();

return;
}

lastDashboardRender = {
...lastDashboardRender,
snapshot: snapshotWithLiveRunActivity(lastDashboardRender.snapshot),
snapshot: snapshotWithLiveRunActivity(lastDashboardRender.snapshot, {
includeCompletedEmpty: true,
}),
};
updateDashboardStreamState(
{
Expand All @@ -11390,6 +11396,7 @@ <h4>${escapeHtml(worktree.branch_name)}</h4>
false,
);
renderDashboardState(lastDashboardRender);
clearDashboardLiveRunActivityOverlayIfCompleteEmpty();
}

function applyDashboardSnapshotEvent(payload) {
Expand Down
14 changes: 12 additions & 2 deletions apps/decodex/src/orchestrator/tests/operator/status/dashboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,6 @@ fn assert_child_lifecycle_contract(response: &str) {
"const header = [\"Stage\", \"attempts\", \"inference\", \"input\", \"output\", \"tools\", \"max output\"];",
"const alignRight = new Set([1, 2, 3, 4, 5, 6]);",
"width: fit-content;\n\t\t\t\tmax-width: 100%;",
"appendLifecycleMetricSegment(\n\t\t\t\t\t\t\ttoolSegments,\n\t\t\t\t\t\t\tformatLargestOutputValue(largestOutput),\n\t\t\t\t\t\t\t\"max output\",",
"\"tools\"",
"output bytes",
"field(\"Large outputs\", childAgentLargeOutputSummary(childAgentActivity(run)))",
Expand Down Expand Up @@ -470,6 +469,8 @@ fn assert_child_lifecycle_contract(response: &str) {
"const titleAttribute = segment.help ? ` title=\"${escapeHtml(segment.help)}\"` : \"\";",
"largestOutputHelp(largestOutput, lifecycle?.largest_tool_output_tool)",
"formatLargestOutputValue(largestOutput, lifecycle?.largest_tool_output_tool)",
"const toolSegments = [];",
"rows.push({ label: \"Tools\", segments: toolSegments });",
],
);
}
Expand Down Expand Up @@ -1834,7 +1835,12 @@ fn operator_dashboard_run_activity_preserves_snapshot_detail_fields() {
assert!(response.contains("let dashboardLiveActiveRunsComplete = true;"));
assert!(response.contains("let dashboardLiveAccounts = null;"));
assert!(response.contains("let dashboardLiveAccountControl = null;"));
assert!(response.contains("function snapshotWithLiveRunActivity(snapshot)"));
assert!(response
.contains("function dashboardLiveRunActivityHasOverlay({ includeCompletedEmpty = false } = {})"));
assert!(response.contains("function clearDashboardLiveRunActivityOverlayIfCompleteEmpty()"));
assert!(response.contains("return includeCompletedEmpty;"));
assert!(response.contains("function snapshotWithLiveRunActivity(snapshot, options = {})"));
assert!(response.contains("if (!dashboardLiveRunActivityHasOverlay(options))"));
assert!(response.contains("\"issue_identifier\""));
assert!(response.contains("\"title\""));
assert!(!response.contains("field(\"Author\","));
Expand All @@ -1858,6 +1864,10 @@ fn operator_dashboard_run_activity_preserves_snapshot_detail_fields() {
assert!(response.contains("dashboardLiveRunActivitySeen = true;"));
assert!(response.contains("dashboardLiveActiveRunsComplete ="));
assert!(response.contains("snapshot: snapshotWithLiveRunActivity(payload.snapshot),"));
assert!(response.contains(
"snapshot: snapshotWithLiveRunActivity(lastDashboardRender.snapshot, {\n\t\t\t\t\t\tincludeCompletedEmpty: true,\n\t\t\t\t\t}),"
));
assert!(response.contains("clearDashboardLiveRunActivityOverlayIfCompleteEmpty();"));
assert!(response.contains("account_control: accountControl,"));
assert!(response.contains("accounts,"));
assert!(response.contains("active_runs: mergedActiveRuns,"));
Expand Down