brindle 0.4.4__tar.gz → 0.4.5__tar.gz

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.
Files changed (163) hide show
  1. {brindle-0.4.4 → brindle-0.4.5}/PKG-INFO +1 -1
  2. {brindle-0.4.4 → brindle-0.4.5}/pyproject.toml +1 -1
  3. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/agents.py +11 -0
  4. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/sessions.py +16 -1
  5. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/sidebar_follow.py +55 -4
  6. {brindle-0.4.4 → brindle-0.4.5}/.gitignore +0 -0
  7. {brindle-0.4.4 → brindle-0.4.5}/LICENSE +0 -0
  8. {brindle-0.4.4 → brindle-0.4.5}/README.md +0 -0
  9. {brindle-0.4.4 → brindle-0.4.5}/SCHEDULE-A +0 -0
  10. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/__init__.py +0 -0
  11. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/__main__.py +0 -0
  12. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/account.py +0 -0
  13. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/airgap.py +0 -0
  14. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/antigravity.py +0 -0
  15. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/assembler.py +0 -0
  16. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/assets/braid_dark.png +0 -0
  17. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/assets/braid_light.png +0 -0
  18. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/autopilot.py +0 -0
  19. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/budget.py +0 -0
  20. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/__init__.py +0 -0
  21. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/backend-auditor.md +0 -0
  22. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/coordinator.md +0 -0
  23. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/developer-codex.md +0 -0
  24. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/developer-fable.md +0 -0
  25. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/developer-gemini.md +0 -0
  26. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/developer-heavy.md +0 -0
  27. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/developer-light.md +0 -0
  28. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/developer-local.md +0 -0
  29. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/developer-opencode.md +0 -0
  30. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/developer-sonnet.md +0 -0
  31. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/developer.md +0 -0
  32. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/qa.md +0 -0
  33. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/reviewer-codex.md +0 -0
  34. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/reviewer-gemini.md +0 -0
  35. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/reviewer-local.md +0 -0
  36. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/reviewer-opencode.md +0 -0
  37. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/reviewer.md +0 -0
  38. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/subagent.md +0 -0
  39. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_agents/supervisor.md +0 -0
  40. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_rules/__init__.py +0 -0
  41. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_rules/security/backend.md +0 -0
  42. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_rules/style/minimal-diff.md +0 -0
  43. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/builtin_rules/tests/only.md +0 -0
  44. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/ci_adapters.py +0 -0
  45. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/ci_client.py +0 -0
  46. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/ci_federation.py +0 -0
  47. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/ci_hosts.py +0 -0
  48. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/cli.py +0 -0
  49. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/cloud_credential.py +0 -0
  50. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/cloud_login.py +0 -0
  51. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/codemap.py +0 -0
  52. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/codemap_docs.py +0 -0
  53. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/codex_hook.py +0 -0
  54. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/company_identity.py +0 -0
  55. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/company_login.py +0 -0
  56. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/config.py +0 -0
  57. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/conflicts.py +0 -0
  58. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/coordinator.py +0 -0
  59. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/cost.py +0 -0
  60. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/cost_estimate.py +0 -0
  61. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/cull.py +0 -0
  62. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/db.py +0 -0
  63. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/demo.py +0 -0
  64. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/detect.py +0 -0
  65. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/doctor.py +0 -0
  66. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/escapes.py +0 -0
  67. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/events.py +0 -0
  68. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/gates.py +0 -0
  69. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/git.py +0 -0
  70. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/guardrails.py +0 -0
  71. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/history.py +0 -0
  72. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/identity_lock.py +0 -0
  73. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/inbox.py +0 -0
  74. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/kb.py +0 -0
  75. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/key_check.py +0 -0
  76. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/keys_cmds.py +0 -0
  77. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/keystore.py +0 -0
  78. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/knowledge.py +0 -0
  79. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/launch_health.py +0 -0
  80. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/learned_rules.py +0 -0
  81. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/learning.py +0 -0
  82. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/legacy.py +0 -0
  83. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/local_setup.py +0 -0
  84. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/login_pause.py +0 -0
  85. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/mcp_server.py +0 -0
  86. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/model_access.py +0 -0
  87. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/model_availability.py +0 -0
  88. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/native/__init__.py +0 -0
  89. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/native/catalog.py +0 -0
  90. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/native/client.py +0 -0
  91. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/native/loop.py +0 -0
  92. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/native/lsp.py +0 -0
  93. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/native/permissions.py +0 -0
  94. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/native/runner.py +0 -0
  95. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/native/serve.py +0 -0
  96. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/native/tools.py +0 -0
  97. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/opencode.py +0 -0
  98. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pane_auth.py +0 -0
  99. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/permissions.py +0 -0
  100. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/picker.py +0 -0
  101. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pipeline.py +0 -0
  102. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/plugins.py +0 -0
  103. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/policy.py +0 -0
  104. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pool.py +0 -0
  105. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pricing.py +0 -0
  106. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/__init__.py +0 -0
  107. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/_files.py +0 -0
  108. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/account.py +0 -0
  109. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/audit_chain.py +0 -0
  110. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/audit_export.py +0 -0
  111. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/auth.py +0 -0
  112. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/brief_learning.py +0 -0
  113. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/cost_centers.py +0 -0
  114. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/credentials.py +0 -0
  115. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/features.py +0 -0
  116. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/keys.py +0 -0
  117. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/learning.py +0 -0
  118. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/license.py +0 -0
  119. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/loopback.py +0 -0
  120. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/managed_models.py +0 -0
  121. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/org_profiles.py +0 -0
  122. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/orgkey.py +0 -0
  123. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/rollout.py +0 -0
  124. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/route_health.py +0 -0
  125. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/settings_sync.py +0 -0
  126. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/status.py +0 -0
  127. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/team_events.py +0 -0
  128. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/pro/team_policy.py +0 -0
  129. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/procs.py +0 -0
  130. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/profiles.py +0 -0
  131. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/prompt_answer.py +0 -0
  132. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/providers.py +0 -0
  133. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/quota.py +0 -0
  134. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/repo_cmds.py +0 -0
  135. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/repos.py +0 -0
  136. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/rewind.py +0 -0
  137. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/rule_checks.py +0 -0
  138. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/savings.py +0 -0
  139. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/scratch.py +0 -0
  140. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/secrets.py +0 -0
  141. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/self_serve.py +0 -0
  142. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/services.py +0 -0
  143. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/sidebar_logo.py +0 -0
  144. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/signin_pause.py +0 -0
  145. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/status_cache.py +0 -0
  146. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/tasks.py +0 -0
  147. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/tmux.py +0 -0
  148. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/updates.py +0 -0
  149. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/upgrade.py +0 -0
  150. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/usage.py +0 -0
  151. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/view.py +0 -0
  152. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/wake.py +0 -0
  153. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/watch.py +0 -0
  154. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/watch_costs.py +0 -0
  155. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/web/__init__.py +0 -0
  156. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/web/chat.py +0 -0
  157. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/web/dashboard.py +0 -0
  158. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/web/diff.py +0 -0
  159. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/web/server.py +0 -0
  160. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/web/static/app.js +0 -0
  161. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/web/static/index.html +0 -0
  162. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/worker_models.py +0 -0
  163. {brindle-0.4.4 → brindle-0.4.5}/src/brindle/workspaces.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brindle
3
- Version: 0.4.4
3
+ Version: 0.4.5
4
4
  Summary: Run coding agents in parallel, each on its own git branch, with a supervisor that delegates, reviews, and merges
5
5
  Project-URL: Homepage, https://pawdelta.com/brindle/
6
6
  Project-URL: Documentation, https://pawdelta.com/brindle/docs/recommended-use
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brindle"
3
- version = "0.4.4"
3
+ version = "0.4.5"
4
4
  description = "Run coding agents in parallel, each on its own git branch, with a supervisor that delegates, reviews, and merges"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -2184,12 +2184,17 @@ def close_removed(db: DB, agent_id: str) -> None:
2184
2184
  note.unlink()
2185
2185
  except OSError:
2186
2186
  session = ""
2187
+ root_id = root_of(db, agent_id) # before kill drops the record that links it
2187
2188
  try:
2188
2189
  kill(db, agent_id)
2189
2190
  except AgentError:
2190
2191
  pass
2191
2192
  if session and db.workspace_by_tmux_session(session) is None \
2192
2193
  and not tmux.session_attached(session):
2194
+ # A sidebar left in this session would go down with it.
2195
+ sidebar = db.get_sidebar_pane(root_id)
2196
+ if sidebar and sidebar != SIDEBAR_DISMISSED and tmux.pane_session(sidebar) == session:
2197
+ sidebar_come_home(db, root_id, sidebar)
2193
2198
  tmux.kill_session(session)
2194
2199
 
2195
2200
 
@@ -2213,6 +2218,12 @@ def _stop(db: DB, agent: Agent) -> None:
2213
2218
  # Only a window on the server brindle is using: another server's pane
2214
2219
  # ids mean other panes here (see same_server).
2215
2220
  window = agent.tmux_window if same_server(agent) and owns_pane(db, agent) else ""
2221
+ if window:
2222
+ from brindle.sidebar_follow import rescue_sidebar_from_window
2223
+
2224
+ # Before the pids are read: a moved pane keeps its process, and the
2225
+ # sidebar's would otherwise be stopped with the agent's.
2226
+ rescue_sidebar_from_window(db, agent)
2216
2227
  pane_pids = tmux.window_pids(window) if window else []
2217
2228
  if window:
2218
2229
  tmux.kill_window(window)
@@ -40,10 +40,22 @@ class Session:
40
40
 
41
41
 
42
42
  def paused(db: DB, repo_root: str) -> list[Session]:
43
- """Paused sessions whose chat ran in ``repo_root``, newest first."""
43
+ """Paused sessions whose chat ran in ``repo_root``, newest first. A
44
+ "failed" chat whose pane is still running isn't one: that status also
45
+ marks a live chat (a brief whose delivery wasn't confirmed, an auth error
46
+ on one turn), and dropping it (see enforce) would close it mid-session."""
44
47
  out = []
48
+ panes = owners = None
45
49
  for ws in db.find_workspaces(repo_root):
46
50
  for a in db.list_agents(ws.id):
51
+ if a.mode == "interactive" and a.status == "failed":
52
+ if panes is None:
53
+ panes = tmux.list_panes()
54
+ owners = agents.pane_owners(db, panes)
55
+ # On another server, its own server's panes say whose its pane is.
56
+ mine = owners if agents.same_server(a, panes) else None
57
+ if agents.is_alive(a, panes) and agents.owns_pane(db, a, mine):
58
+ continue
47
59
  if a.mode == "interactive" and a.status in ("paused", "failed"):
48
60
  members = agents.tree(db, a.id)
49
61
  branches = []
@@ -212,6 +224,9 @@ def _forget(db: DB, s: Session) -> None:
212
224
  # some other pane here (see agents.same_server).
213
225
  if (a.tmux_window and agents.same_server(a, panes) and agents.is_alive(a, panes)
214
226
  and agents.owns_pane(db, a, owners)):
227
+ from brindle.sidebar_follow import rescue_sidebar_from_window
228
+
229
+ rescue_sidebar_from_window(db, a)
215
230
  tmux.kill_window(a.tmux_window)
216
231
  ws = db.get_workspace(a.workspace_id)
217
232
  if ws and ws.kind == "worktree":
@@ -196,6 +196,54 @@ def rescue_sidebar(db: DB, ws: Workspace) -> None:
196
196
  pass
197
197
 
198
198
 
199
+ def _home_session(db: DB, root_id: str, sidebar: str) -> str | None:
200
+ """The brindle session whose agent window ``sidebar`` sits in. Not
201
+ ``tmux.pane_session``: a window the app linked into its own
202
+ ``brindle-view-*`` session reports that one, and that session being
203
+ attached says nothing about the person's terminal on the brindle session."""
204
+ window = tmux.pane_window(sidebar)
205
+ for agent in db.list_agents():
206
+ if (window and agent.tmux_window and root_of(db, agent.id) == root_id
207
+ and tmux.pane_window(agent.tmux_window) == window):
208
+ ws = db.get_workspace(agent.workspace_id)
209
+ if ws is not None:
210
+ return ws.tmux_session
211
+ return tmux.pane_session(sidebar)
212
+
213
+
214
+ def rescue_sidebar_from_window(db: DB, agent: Agent) -> None:
215
+ """Call before ``agent``'s tmux window is killed: if its root's sidebar is
216
+ a pane in that window, move it to a window of the root's session that
217
+ survives (the root's active one, else any other agent's there: a worker
218
+ sharing the root's workspace has its window in the same session). Never
219
+ raises."""
220
+ try:
221
+ if not agent.tmux_window:
222
+ return
223
+ root_id = root_of(db, agent.id)
224
+ root = db.get_agent(root_id)
225
+ root_ws = db.get_workspace(root.workspace_id) if root else None
226
+ if root is None or root_ws is None or agent.id == root_id:
227
+ return
228
+ with _sidebar_lock(root_id):
229
+ sidebar = db.get_sidebar_pane(root_id)
230
+ doomed = tmux.pane_window(agent.tmux_window)
231
+ if not (_valid_sidebar(sidebar, root_id) and doomed
232
+ and tmux.pane_window(sidebar) == doomed):
233
+ return
234
+ candidates = [tmux.active_window(root_ws.tmux_session)]
235
+ candidates += [tmux.pane_window(a.tmux_window) for a in db.list_agents(root_ws.id)
236
+ if a.tmux_window and a.status not in ("paused", "done")]
237
+ for window in candidates:
238
+ target_pane = window and window != doomed and tmux.agent_pane_in_window(window, sidebar)
239
+ if target_pane:
240
+ _note_sidebar_move(f"rescue to {root_ws.tmux_session}", sidebar, target_pane)
241
+ tmux.move_pane(sidebar, target_pane, SIDEBAR_COLUMNS, _sidebar_position(root_ws))
242
+ return
243
+ except Exception: # noqa: BLE001 - cleanup must not fail because of the sidebar
244
+ pass
245
+
246
+
199
247
  def sidebar_follow(db: DB, session: str) -> None:
200
248
  ws = db.workspace_by_tmux_session(session)
201
249
  if ws is None:
@@ -240,10 +288,13 @@ def sidebar_follow(db: DB, session: str) -> None:
240
288
  if tmux.pane_window(sidebar) == window:
241
289
  _repaint(sidebar)
242
290
  return
243
- if not tmux.session_attached(session):
244
- home = tmux.pane_session(sidebar)
245
- if home and home != session and tmux.session_attached(home):
246
- return
291
+ # Never take it out of another session someone still looks at: the
292
+ # person's own switch-client leaves that session first, so it reads
293
+ # unattached by now. A second client (the app's, attached to a
294
+ # worker's session) changing windows must not pull it from their terminal.
295
+ home = _home_session(db, root_id, sidebar)
296
+ if home and home != session and tmux.session_attached(home):
297
+ return
247
298
  target_pane = tmux.agent_pane_in_window(window, sidebar)
248
299
  if not target_pane:
249
300
  return
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes