Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
4b2ae3d
Add failing surface hibernation lifecycle policy tests
austinywang Jun 10, 2026
46f2606
Hibernate idle background terminal surfaces behind an LRU cap
austinywang Jun 10, 2026
8838919
Merge remote-tracking branch 'origin/main' into issue-5731-surface-hi…
austinywang Jun 10, 2026
e617b49
Document why the agent-panel test helper marks surfaces busy
austinywang Jun 10, 2026
db742b8
Track test helper comment in file length budget
austinywang Jun 10, 2026
d30745d
Localize surface hibernation schema descriptions
austinywang Jun 10, 2026
9838586
Merge remote-tracking branch 'origin/main' into issue-5731-surface-hi…
austinywang Jun 10, 2026
8083a3b
Track AgentLaunchSanitizer length from main in budget
austinywang Jun 10, 2026
22b11e6
Harden surface hibernation busy gate, settings UI, and idle-tick cost
austinywang Jun 10, 2026
707e695
Persist hibernated scrollback on every autosave; drop input throttle
austinywang Jun 10, 2026
4b0ab2f
Merge remote-tracking branch 'origin/main' into issue-5731-surface-hi…
austinywang Jun 10, 2026
5d40d1e
Persist hibernated working directory in session snapshots
austinywang Jun 10, 2026
874b1c6
True up budget for hibernated-directory snapshot fix
austinywang Jun 10, 2026
80c604e
Protect visible panels, pending command lines, and main's ghostty poi…
austinywang Jun 10, 2026
e54ca51
Merge remote-tracking branch 'origin/main' into issue-5731-surface-hi…
austinywang Jun 10, 2026
caf6c41
Keep command line pending when text trails the last settling character
austinywang Jun 10, 2026
a825af8
Merge remote-tracking branch 'origin/main' into issue-5731-surface-hi…
austinywang Jun 10, 2026
f700d46
Clear pending shell input only on observed prompt transitions
austinywang Jun 10, 2026
1f28564
Record input synchronously and restore rendered hibernated panels
austinywang Jun 10, 2026
a9167eb
Include shell-integration command state in the hibernation busy gate
austinywang Jun 10, 2026
eb462de
Re-read hibernation settings after async work; route mobile input via…
austinywang Jun 10, 2026
68b1b7a
Merge remote-tracking branch 'origin/main' into issue-5731-surface-hi…
austinywang Jun 10, 2026
3e9a576
Convert policy suite to Swift Testing; harden re-enable and cwd staging
austinywang Jun 10, 2026
73f9106
Fix nested shell-state type and make round-trip test hermetic
austinywang Jun 10, 2026
96eab78
Merge remote-tracking branch 'origin/main' into issue-5731-surface-hi…
austinywang Jun 10, 2026
73eec3b
Honor the documented hidden-workspace window on first tail sample
austinywang Jun 10, 2026
b5db675
Replay restored scrollback in the fish integration
austinywang Jun 10, 2026
5afca77
Abort hibernation on failed scrollback capture; count prompt transiti…
austinywang Jun 10, 2026
683dc21
Merge branch 'main' of https://github.com/manaflow-ai/cmux into issue…
austinywang Jun 10, 2026
e1b2c61
Keep batched trailing text pending across the next prompt transition
austinywang Jun 10, 2026
bfb83e5
Stage hibernated working directories without touching the disk
austinywang Jun 10, 2026
7c095bb
Quarantine stale surfaces in busy probe; cover paste batches; reapply…
austinywang Jun 10, 2026
ead282b
Bound unmounted-idle hibernation batches and stale tail-sampling pres…
austinywang Jun 10, 2026
988908f
Survive one prompt transition per batched command before clearing pen…
austinywang Jun 10, 2026
189d5b0
Restore main-side content reverted by merge mishaps
austinywang Jun 10, 2026
c57ce56
True budget entries after restoring main-side content
austinywang Jun 10, 2026
f8b094a
Bound global-cap drains, seed pending before evaluation, gate restart…
austinywang Jun 10, 2026
85753cc
Wake hibernated terminals for mobile reads; only text-bearing input a…
austinywang Jun 10, 2026
b43f598
Discount prior selections before computing global-cap overflow
austinywang Jun 10, 2026
8299f48
Gate restart on current integration setting; arm pending for text nam…
austinywang Jun 10, 2026
5c1cb8b
Merge remote-tracking branch 'origin/main' into issue-5731-surface-hi…
austinywang Jun 10, 2026
fe24526
Key replay capability off the shell name; treat editing controls as n…
austinywang Jun 10, 2026
732b6a4
Write replay file at hibernate time; seed snapshot fallback; cover sp…
austinywang Jun 10, 2026
dfc052e
Scope tail-fingerprint sampling to the opt-in agent cap
austinywang Jun 10, 2026
dcc6946
Abort hibernation when the replay file cannot be written; clean up ab…
austinywang Jun 10, 2026
7c73096
Purge stale scrollback replay files once per process
austinywang Jun 10, 2026
0f3e304
Scope replay purge per bundle; keep last scrollback for surface-less …
austinywang Jun 10, 2026
05a1ce0
Document the new surface hibernation catalog keys
austinywang Jun 10, 2026
8a0d352
Harden shell-restart gates: non-shell foregrounds and disabled-integr…
austinywang Jun 10, 2026
9646e1f
Fail closed on missing foreground PID and pre-filter process syscalls
austinywang Jun 10, 2026
d464421
merge: resolve conflicts with main
austinywang Jun 10, 2026
dffa233
Keep the replay-file purge off the calling thread
austinywang Jun 10, 2026
ec21726
merge: resolve length-budget conflict with main
austinywang Jun 10, 2026
e47ba95
Make pending survivals additive and key replay support off installed …
austinywang Jun 10, 2026
65df0ac
Clear the scrollback fallback when hibernation captures an empty term…
austinywang Jun 11, 2026
722d3a1
Record key input only when it reaches the terminal; reconcile seeded …
austinywang Jun 11, 2026
44ad65b
Own the staged replay file through the whole restore window
austinywang Jun 11, 2026
b6523fe
Forward duplicate shell reports to hibernation; repair browser-CLI me…
austinywang Jun 11, 2026
b7cab15
Bound per-tick foreground verification to an LRU window
austinywang Jun 11, 2026
668b10d
merge: resolve length-budget conflict with main
austinywang Jun 11, 2026
889a4b9
Cache verified-busy foregrounds and stop arming pending on editing co…
austinywang Jun 11, 2026
21fc212
Requalify seeded shells on bare Enter; keep budget for evictable cand…
austinywang Jun 11, 2026
b9acc1a
merge: resolve length-budget conflict with main
austinywang Jun 11, 2026
40c8368
Write replay files off-actor; fix CRLF classification; restore socket…
austinywang Jun 11, 2026
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
2 changes: 2 additions & 0 deletions .github/swift-file-length-budget.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
575 cmuxTests/FeedCoordinatorTests.swift
574 Sources/Feed/FeedTextEditorDebugWindowController.swift
568 Packages/CMUXMobileCore/Sources/CMUXMobileCore/MobileTerminalRenderGrid.swift
562 Sources/App/WorkspaceRuntimeSettings.swift
562 cmuxTests/AgentExecutableResolverTests.swift
560 cmuxTests/GhosttyConfigPathResolverTests.swift
558 Packages/CmuxGit/Sources/CmuxGit/Parsing/GitMetadataService+Config.swift
Expand All @@ -174,6 +175,7 @@
519 Packages/CmuxSwiftRender/Tests/CmuxSwiftRenderTests/Corpus/stress-two-column-cockpit-sidebar.swift
518 Packages/CmuxSwiftRender/Tests/CmuxSwiftRenderTests/Corpus/stress-git-review-queue-command-deck.swift
516 Sources/CmuxConfigExecutor.swift
514 cmuxTests/SurfaceHibernationPolicyTests.swift
514 cmuxUITests/UpdatePillUITests.swift
509 Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentLaunchSanitizerAdditionalPolicies.swift
507 Sources/TerminalControllerTopSupport.swift
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,23 @@ jobs:
-only-testing:cmuxTests/AppearanceSettingsTests/testSplitGhosttyThemeUsesSystemDarkWhenAppAppearanceIsStaleLight \
test

# The full unit-test pass below tolerates app-host crashes by classifying
# the surviving summary lines, so suites that sort after an early crash
# can be skipped without failing the job. Surface hibernation policy
# tests gate memory reclamation (issue 5731) and must always execute.
- name: Run surface hibernation policy regression
run: |
set -euo pipefail
SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages"
xcodebuild -project cmux.xcodeproj -scheme cmux-unit -configuration Debug \
-derivedDataPath "$CMUX_DERIVED_DATA_PATH" \
-clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \
-disableAutomaticPackageResolution \
-destination "platform=macOS" \
CMUX_SKIP_ZIG_BUILD=1 \
-only-testing:cmuxTests/SurfaceHibernationPolicyTests \
test

- name: Run unit tests
run: |
set -euo pipefail
Expand Down
113 changes: 113 additions & 0 deletions Sources/App/SurfaceHibernationPolicy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import Foundation

/// How a live Ghostty runtime surface can be reclaimed by hibernation.
enum SurfaceHibernationMechanism: Equatable, Sendable {
/// The panel runs a restorable coding agent. Hibernation terminates the
/// scoped agent processes and frees the runtime surface; the agent resumes
/// with its saved session when the panel is visited again.
case agentResume
/// The panel is a plain shell whose recreation has no side effects.
/// Hibernation captures scrollback, frees the runtime surface (which ends
/// the shell), and starts a fresh shell in the captured working directory
/// with the scrollback replayed when the panel is visited again.
case shellRestart
}

/// One terminal panel as seen by the surface-lifecycle planner.
struct SurfaceHibernationPlannerInput: Sendable {
let key: AgentHibernationPanelKey
/// nil when the panel must never be hibernated (deferred startup work,
/// remote PTY attach, tmux-bound, or queued input that suspension would drop).
let mechanism: SurfaceHibernationMechanism?
/// Whether the panel currently owns a live Ghostty runtime surface (or, for
/// agent panels, a live scoped process). Every live input counts toward the
/// global cap census even when it is exempt from eviction.
let isLive: Bool
/// Visible in the selected workspace's rendered layout.
let isProtected: Bool
/// The surface reports it is not safely at a shell prompt
/// (`needsConfirmClose`), e.g. a foreground process is running.
let isBusy: Bool
/// Agent lifecycle reported by hooks; `.unknown` for plain shells.
let lifecycle: AgentHibernationLifecycleState
let hasUnconfirmedTerminalInput: Bool
let lastActivityAt: TimeInterval
/// When the owning workspace last stopped rendering (unmounted), or nil
/// while it is mounted.
let workspaceUnmountedAt: TimeInterval?

init(
key: AgentHibernationPanelKey,
mechanism: SurfaceHibernationMechanism?,
isLive: Bool,
isProtected: Bool,
isBusy: Bool = false,
lifecycle: AgentHibernationLifecycleState = .unknown,
hasUnconfirmedTerminalInput: Bool = false,
lastActivityAt: TimeInterval,
workspaceUnmountedAt: TimeInterval? = nil
) {
self.key = key
self.mechanism = mechanism
self.isLive = isLive
self.isProtected = isProtected
self.isBusy = isBusy
self.lifecycle = lifecycle
self.hasUnconfirmedTerminalInput = hasUnconfirmedTerminalInput
self.lastActivityAt = lastActivityAt
self.workspaceUnmountedAt = workspaceUnmountedAt
}
}

/// Pure selection policy for which live terminal surfaces to hibernate.
///
/// Three rules compose, and the result is their union:
/// - Agent cap: live restorable-agent terminals above
/// `AgentHibernationSettings.maxLiveTerminals`, oldest first.
/// - Global LRU cap: live surfaces of any kind above
/// `SurfaceHibernationSettings.maxLiveSurfaces`, oldest first. The census
/// counts every live surface — including ones materialized by background
/// priming or queued socket input — while only eligible ones are evicted.
/// - Unmounted idle: surfaces whose workspace has been unmounted longer than
/// `SurfaceHibernationSettings.unmountedIdleSeconds`, regardless of cap
/// pressure.
enum SurfaceHibernationPlanner {
static func selectedPanelKeys(
inputs: [SurfaceHibernationPlannerInput],
agentSettings: AgentHibernationSettings.Values,
surfaceSettings: SurfaceHibernationSettings.Values,
now: TimeInterval
) -> Set<AgentHibernationPanelKey> {
// This mirrors the shipped AgentHibernationPlanner policy: only
// restorable-agent terminals are counted or evicted, and only under
// agent-cap pressure. Plain-shell surfaces are invisible to it, the
// global cap census does not exist, and workspaces unmounted for long
// periods keep every runtime surface alive
// (https://github.com/manaflow-ai/cmux/issues/5731).
guard agentSettings.enabled else { return [] }
let liveRestorable = inputs.filter { $0.mechanism == .agentResume && $0.isLive }
let excess = liveRestorable.count - agentSettings.maxLiveTerminals
guard excess > 0 else { return [] }

let eligible = liveRestorable
.filter { input in
!input.isProtected &&
input.lifecycle.allowsHibernation &&
!input.hasUnconfirmedTerminalInput &&
now - input.lastActivityAt >= agentSettings.idleSeconds
}
.sorted(by: Self.leastRecentlyUsedFirst)

return Set(eligible.prefix(excess).map(\.key))
}
Comment thread
austinywang marked this conversation as resolved.
Resolved
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

static func leastRecentlyUsedFirst(
_ lhs: SurfaceHibernationPlannerInput,
_ rhs: SurfaceHibernationPlannerInput
) -> Bool {
if lhs.lastActivityAt == rhs.lastActivityAt {
return lhs.key.panelId.uuidString < rhs.key.panelId.uuidString
}
return lhs.lastActivityAt < rhs.lastActivityAt
}
}
129 changes: 129 additions & 0 deletions Sources/App/WorkspaceRuntimeSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,135 @@ enum AgentHibernationSettings {
}
}

enum SurfaceHibernationSettings {
struct Values: Equatable, Sendable {
var enabled: Bool
var idleSeconds: TimeInterval
var unmountedIdleSeconds: TimeInterval
var maxLiveSurfaces: Int
var confirmationSeconds: TimeInterval
}

static let enabledKey = "terminal.surfaceHibernation.enabled"
static let idleSecondsKey = "terminal.surfaceHibernation.idleSeconds"
static let unmountedIdleSecondsKey = "terminal.surfaceHibernation.unmountedIdleSeconds"
static let maxLiveSurfacesKey = "terminal.surfaceHibernation.maxLiveSurfaces"
static let confirmationSecondsKey = "terminal.surfaceHibernation.confirmationSeconds"

// Surface hibernation is on by default: it only reclaims surfaces that are
// safely at a shell prompt, restores scrollback and working directory on
// the next visit, and is the P0 backstop against unbounded per-workspace
// surface accumulation (https://github.com/manaflow-ai/cmux/issues/5731).
static let defaultEnabled = true
static let defaultIdleSeconds: TimeInterval = 300
static let defaultUnmountedIdleSeconds: TimeInterval = 1800
static let defaultMaxLiveSurfaces = 12
static let defaultConfirmationSeconds: TimeInterval = 60
static let didChangeNotification = Notification.Name("cmux.surfaceHibernationSettingsDidChange")

static func values(defaults: UserDefaults = .standard) -> Values {
Values(
enabled: isEnabled(defaults: defaults),
idleSeconds: idleSeconds(defaults: defaults),
unmountedIdleSeconds: unmountedIdleSeconds(defaults: defaults),
maxLiveSurfaces: maxLiveSurfaces(defaults: defaults),
confirmationSeconds: confirmationSeconds(defaults: defaults)
)
}

static func isEnabled(defaults: UserDefaults = .standard) -> Bool {
guard defaults.object(forKey: enabledKey) != nil else { return defaultEnabled }
return defaults.bool(forKey: enabledKey)
}

static func idleSeconds(defaults: UserDefaults = .standard) -> TimeInterval {
guard defaults.object(forKey: idleSecondsKey) != nil else { return defaultIdleSeconds }
return sanitizedIdleSeconds(defaults.double(forKey: idleSecondsKey))
}

static func unmountedIdleSeconds(defaults: UserDefaults = .standard) -> TimeInterval {
guard defaults.object(forKey: unmountedIdleSecondsKey) != nil else { return defaultUnmountedIdleSeconds }
return sanitizedUnmountedIdleSeconds(defaults.double(forKey: unmountedIdleSecondsKey))
}

static func maxLiveSurfaces(defaults: UserDefaults = .standard) -> Int {
guard defaults.object(forKey: maxLiveSurfacesKey) != nil else { return defaultMaxLiveSurfaces }
return sanitizedMaxLiveSurfaces(defaults.integer(forKey: maxLiveSurfacesKey))
}

static func confirmationSeconds(defaults: UserDefaults = .standard) -> TimeInterval {
guard defaults.object(forKey: confirmationSecondsKey) != nil else { return defaultConfirmationSeconds }
return sanitizedConfirmationSeconds(defaults.double(forKey: confirmationSecondsKey))
}

static func sanitizedIdleSeconds(_ value: TimeInterval) -> TimeInterval {
guard value.isFinite else { return defaultIdleSeconds }
return min(max(value.rounded(), 30), 7 * 24 * 60 * 60)
}

static func sanitizedUnmountedIdleSeconds(_ value: TimeInterval) -> TimeInterval {
guard value.isFinite else { return defaultUnmountedIdleSeconds }
return min(max(value.rounded(), 60), 30 * 24 * 60 * 60)
}

static func sanitizedMaxLiveSurfaces(_ value: Int) -> Int {
min(max(value, 1), 256)
}

static func sanitizedConfirmationSeconds(_ value: TimeInterval) -> TimeInterval {
guard value.isFinite else { return defaultConfirmationSeconds }
return min(max(value.rounded(), 5), 60 * 60)
}

static func setValues(
enabled: Bool? = nil,
idleSeconds: TimeInterval? = nil,
unmountedIdleSeconds: TimeInterval? = nil,
maxLiveSurfaces: Int? = nil,
defaults: UserDefaults = .standard,
notificationCenter: NotificationCenter = .default
) {
let oldValues = values(defaults: defaults)
if let enabled {
defaults.set(enabled, forKey: enabledKey)
}
if let idleSeconds {
defaults.set(sanitizedIdleSeconds(idleSeconds), forKey: idleSecondsKey)
}
if let unmountedIdleSeconds {
defaults.set(sanitizedUnmountedIdleSeconds(unmountedIdleSeconds), forKey: unmountedIdleSecondsKey)
}
if let maxLiveSurfaces {
defaults.set(sanitizedMaxLiveSurfaces(maxLiveSurfaces), forKey: maxLiveSurfacesKey)
}
if oldValues != values(defaults: defaults) {
notifyDidChange(notificationCenter: notificationCenter)
}
}

@discardableResult
static func reset(
defaults: UserDefaults = .standard,
notificationCenter: NotificationCenter = .default
) -> Bool {
let oldValues = values(defaults: defaults)
defaults.removeObject(forKey: enabledKey)
defaults.removeObject(forKey: idleSecondsKey)
defaults.removeObject(forKey: unmountedIdleSecondsKey)
defaults.removeObject(forKey: maxLiveSurfacesKey)
defaults.removeObject(forKey: confirmationSecondsKey)
let didChange = oldValues != values(defaults: defaults)
if didChange {
notifyDidChange(notificationCenter: notificationCenter)
}
return didChange
}

static func notifyDidChange(notificationCenter: NotificationCenter = .default) {
notificationCenter.post(name: didChangeNotification, object: nil)
}
}

enum AgentHibernationTrackingGate {
private static let lock = NSLock()
private static var enabled = AgentHibernationSettings.isEnabled()
Expand Down
8 changes: 8 additions & 0 deletions cmux.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,8 @@
F6355600A1B2C3D4E5F60718 /* SSHStartupSignalLifecycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6355601A1B2C3D4E5F60718 /* SSHStartupSignalLifecycleTests.swift */; };
F20F85FC5900550685FA33AD /* StackAuth in Frameworks */ = {isa = PBXBuildFile; productRef = A8BD195031FC4B82B4354297 /* StackAuth */; };
D35B71010000000000000001 /* StartupBreadcrumbLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = D35B71010000000000000002 /* StartupBreadcrumbLog.swift */; };
D36A00050000000000000001 /* SurfaceHibernationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36A00050000000000000002 /* SurfaceHibernationPolicy.swift */; };
D36A00060000000000000001 /* SurfaceHibernationPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36A00060000000000000002 /* SurfaceHibernationPolicyTests.swift */; };
A5001303 /* SurfaceSearchOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001301 /* SurfaceSearchOverlay.swift */; };
C9A5720BC9A5720BC9A5720B /* TabItemView+WorkspaceGroups.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A5720CC9A5720CC9A5720C /* TabItemView+WorkspaceGroups.swift */; };
C3677003000000000000001 /* TabManager+CompatibilityTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3677003000000000000002 /* TabManager+CompatibilityTypes.swift */; };
Expand Down Expand Up @@ -1310,6 +1312,8 @@
E3309A0E /* SplitEqualizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitEqualizer.swift; sourceTree = "<group>"; };
F6355601A1B2C3D4E5F60718 /* SSHStartupSignalLifecycleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHStartupSignalLifecycleTests.swift; sourceTree = "<group>"; };
D35B71010000000000000002 /* StartupBreadcrumbLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/StartupBreadcrumbLog.swift; sourceTree = "<group>"; };
D36A00050000000000000002 /* SurfaceHibernationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/SurfaceHibernationPolicy.swift; sourceTree = "<group>"; };
D36A00060000000000000002 /* SurfaceHibernationPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceHibernationPolicyTests.swift; sourceTree = "<group>"; };
A5001301 /* SurfaceSearchOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Find/SurfaceSearchOverlay.swift; sourceTree = "<group>"; };
C9A5720CC9A5720CC9A5720C /* TabItemView+WorkspaceGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TabItemView+WorkspaceGroups.swift"; sourceTree = "<group>"; };
C3677003000000000000002 /* TabManager+CompatibilityTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TabManager+CompatibilityTypes.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1699,6 +1703,7 @@
C0DE32470000000000000002 /* ContentViewIdentifierCopyCommands.swift */,
9960CC3992F3C44489E7627C /* WorkspaceRuntimeSettings.swift */,
D36A00010000000000000002 /* AgentHibernationController.swift */,
D36A00050000000000000002 /* SurfaceHibernationPolicy.swift */,
243632BA1DBBA36FD46E0610 /* SidebarAppearanceSupport.swift */,
D50D9BCE46701FBA91C2EFB6 /* SidebarWorkspaceSnapshotRefreshPolicy.swift */,
D35450020000000000000002 /* SidebarWorkspaceRowHoverTracker.swift */,
Expand Down Expand Up @@ -2107,6 +2112,7 @@
A9E050000000000000000001 /* AgentSessionWebRendererTests.swift */,
A9E040000000000000000001 /* CodexAppServerSessionTests.swift */,
D36A00020000000000000002 /* AgentHibernationTests.swift */,
D36A00060000000000000002 /* SurfaceHibernationPolicyTests.swift */,
F5410001A1B2C3D4E5F60718 /* RestorableAgentHookProviderResumeTests.swift */,
F5410005A1B2C3D4E5F60718 /* RestorableAgentHookProviderHermesTests.swift */,
F5410003A1B2C3D4E5F60718 /* RestorableAgentNonInteractiveTests.swift */,
Expand Down Expand Up @@ -2974,6 +2980,7 @@
A5001226 /* SocketControlMode+Display.swift in Sources */,
E3309A0D /* SplitEqualizer.swift in Sources */,
D35B71010000000000000001 /* StartupBreadcrumbLog.swift in Sources */,
D36A00050000000000000001 /* SurfaceHibernationPolicy.swift in Sources */,
A5001303 /* SurfaceSearchOverlay.swift in Sources */,
C9A5720BC9A5720BC9A5720B /* TabItemView+WorkspaceGroups.swift in Sources */,
C3677003000000000000001 /* TabManager+CompatibilityTypes.swift in Sources */,
Expand Down Expand Up @@ -3295,6 +3302,7 @@
62270F3DCECB4787D789CCE3 /* SidebarWorkspaceSnapshotRefreshPolicyTests.swift in Sources */,
F8000000A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift in Sources */,
F6355600A1B2C3D4E5F60718 /* SSHStartupSignalLifecycleTests.swift in Sources */,
D36A00060000000000000001 /* SurfaceHibernationPolicyTests.swift in Sources */,
2BB56A710BB1FC50367E5BCF /* TabManagerSessionSnapshotTests.swift in Sources */,
B6BF3DC98DB1495E57900199 /* TabManagerUnitTests.swift in Sources */,
C7A507000000000000000002 /* TaskManagerResourcesTests.swift in Sources */,
Expand Down
Loading
Loading