overcode 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl
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.
- overcode/__init__.py +1 -1
- overcode/cli.py +154 -51
- overcode/config.py +66 -0
- overcode/daemon_claude_skill.md +36 -33
- overcode/history_reader.py +69 -8
- overcode/implementations.py +178 -87
- overcode/monitor_daemon.py +87 -97
- overcode/monitor_daemon_core.py +261 -0
- overcode/monitor_daemon_state.py +24 -15
- overcode/pid_utils.py +17 -3
- overcode/session_manager.py +54 -0
- overcode/settings.py +34 -0
- overcode/status_constants.py +1 -1
- overcode/status_detector.py +8 -2
- overcode/status_patterns.py +19 -0
- overcode/summarizer_client.py +72 -27
- overcode/summarizer_component.py +87 -107
- overcode/supervisor_daemon.py +55 -38
- overcode/supervisor_daemon_core.py +210 -0
- overcode/testing/__init__.py +6 -0
- overcode/testing/renderer.py +268 -0
- overcode/testing/tmux_driver.py +223 -0
- overcode/testing/tui_eye.py +185 -0
- overcode/testing/tui_eye_skill.md +187 -0
- overcode/tmux_manager.py +117 -93
- overcode/tui.py +399 -1969
- overcode/tui_actions/__init__.py +20 -0
- overcode/tui_actions/daemon.py +201 -0
- overcode/tui_actions/input.py +128 -0
- overcode/tui_actions/navigation.py +117 -0
- overcode/tui_actions/session.py +428 -0
- overcode/tui_actions/view.py +357 -0
- overcode/tui_helpers.py +42 -9
- overcode/tui_logic.py +347 -0
- overcode/tui_render.py +414 -0
- overcode/tui_widgets/__init__.py +24 -0
- overcode/tui_widgets/command_bar.py +399 -0
- overcode/tui_widgets/daemon_panel.py +153 -0
- overcode/tui_widgets/daemon_status_bar.py +245 -0
- overcode/tui_widgets/help_overlay.py +71 -0
- overcode/tui_widgets/preview_pane.py +69 -0
- overcode/tui_widgets/session_summary.py +514 -0
- overcode/tui_widgets/status_timeline.py +253 -0
- {overcode-0.1.2.dist-info → overcode-0.1.4.dist-info}/METADATA +4 -1
- overcode-0.1.4.dist-info/RECORD +68 -0
- {overcode-0.1.2.dist-info → overcode-0.1.4.dist-info}/WHEEL +1 -1
- {overcode-0.1.2.dist-info → overcode-0.1.4.dist-info}/entry_points.txt +1 -0
- overcode-0.1.2.dist-info/RECORD +0 -45
- {overcode-0.1.2.dist-info → overcode-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {overcode-0.1.2.dist-info → overcode-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session action methods for TUI.
|
|
3
|
+
|
|
4
|
+
Handles agent/session operations like kill, new, sleep, command bar focus.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, List
|
|
10
|
+
|
|
11
|
+
from textual.css.query import NoMatches
|
|
12
|
+
from textual.widgets import Input
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ..tui_widgets import SessionSummary, CommandBar, PreviewPane
|
|
16
|
+
from ..session_manager import Session
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SessionActionsMixin:
|
|
20
|
+
"""Mixin providing session/agent actions for SupervisorTUI."""
|
|
21
|
+
|
|
22
|
+
def action_toggle_focused(self) -> None:
|
|
23
|
+
"""Toggle expansion of focused session (only in tree mode)."""
|
|
24
|
+
from ..tui_widgets import SessionSummary
|
|
25
|
+
if self.view_mode == "list_preview":
|
|
26
|
+
return # Don't toggle in list mode
|
|
27
|
+
focused = self.focused
|
|
28
|
+
if isinstance(focused, SessionSummary):
|
|
29
|
+
focused.expanded = not focused.expanded
|
|
30
|
+
|
|
31
|
+
def action_toggle_sleep(self) -> None:
|
|
32
|
+
"""Toggle sleep mode for the focused agent.
|
|
33
|
+
|
|
34
|
+
Sleep mode marks an agent as 'asleep' (human doesn't want it to do anything).
|
|
35
|
+
Sleeping agents are excluded from stats calculations.
|
|
36
|
+
Press z again to wake the agent.
|
|
37
|
+
|
|
38
|
+
Note: Cannot put a running agent to sleep (#158).
|
|
39
|
+
"""
|
|
40
|
+
from ..tui_widgets import SessionSummary
|
|
41
|
+
from ..status_constants import STATUS_RUNNING
|
|
42
|
+
focused = self.focused
|
|
43
|
+
if not isinstance(focused, SessionSummary):
|
|
44
|
+
self.notify("No agent focused", severity="warning")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
session = focused.session
|
|
48
|
+
new_asleep_state = not session.is_asleep
|
|
49
|
+
|
|
50
|
+
# Prevent putting a running agent to sleep (#158)
|
|
51
|
+
if new_asleep_state and focused.detected_status == STATUS_RUNNING:
|
|
52
|
+
self.notify("Cannot put a running agent to sleep", severity="warning")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
# Update the session in the session manager
|
|
56
|
+
self.session_manager.update_session(session.id, is_asleep=new_asleep_state)
|
|
57
|
+
|
|
58
|
+
# Update the local session object
|
|
59
|
+
session.is_asleep = new_asleep_state
|
|
60
|
+
|
|
61
|
+
# Update the widget's display status if sleeping
|
|
62
|
+
if new_asleep_state:
|
|
63
|
+
focused.detected_status = "asleep"
|
|
64
|
+
self.notify(f"Agent '{session.name}' is now asleep (excluded from stats)", severity="information")
|
|
65
|
+
else:
|
|
66
|
+
# Wake up - status will be refreshed on next update cycle
|
|
67
|
+
self.notify(f"Agent '{session.name}' is now awake", severity="information")
|
|
68
|
+
|
|
69
|
+
# Force a refresh
|
|
70
|
+
focused.refresh()
|
|
71
|
+
|
|
72
|
+
def action_kill_focused(self) -> None:
|
|
73
|
+
"""Kill the currently focused agent (requires confirmation)."""
|
|
74
|
+
from ..tui_widgets import SessionSummary
|
|
75
|
+
focused = self.focused
|
|
76
|
+
if not isinstance(focused, SessionSummary):
|
|
77
|
+
self.notify("No agent focused", severity="warning")
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
session_name = focused.session.name
|
|
81
|
+
session_id = focused.session.id
|
|
82
|
+
now = time.time()
|
|
83
|
+
|
|
84
|
+
# Check if this is a confirmation of a pending kill
|
|
85
|
+
if self._pending_kill:
|
|
86
|
+
pending_name, pending_time = self._pending_kill
|
|
87
|
+
# Confirm if same session and within 3 second window
|
|
88
|
+
if pending_name == session_name and (now - pending_time) < 3.0:
|
|
89
|
+
self._pending_kill = None # Clear pending state
|
|
90
|
+
self._execute_kill(focused, session_name, session_id)
|
|
91
|
+
return
|
|
92
|
+
else:
|
|
93
|
+
# Different session or expired - start new confirmation
|
|
94
|
+
self._pending_kill = None
|
|
95
|
+
|
|
96
|
+
# First press - request confirmation
|
|
97
|
+
self._pending_kill = (session_name, now)
|
|
98
|
+
self.notify(
|
|
99
|
+
f"Press x again to kill '{session_name}'",
|
|
100
|
+
severity="warning",
|
|
101
|
+
timeout=3
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def action_restart_focused(self) -> None:
|
|
105
|
+
"""Restart the currently focused agent (requires confirmation).
|
|
106
|
+
|
|
107
|
+
Sends Ctrl-C to kill the current Claude process, then restarts it
|
|
108
|
+
with the same configuration (directory, permissions).
|
|
109
|
+
"""
|
|
110
|
+
from ..tui_widgets import SessionSummary
|
|
111
|
+
focused = self.focused
|
|
112
|
+
if not isinstance(focused, SessionSummary):
|
|
113
|
+
self.notify("No agent focused", severity="warning")
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
session_name = focused.session.name
|
|
117
|
+
now = time.time()
|
|
118
|
+
|
|
119
|
+
# Check if this is a confirmation of a pending restart
|
|
120
|
+
if self._pending_restart:
|
|
121
|
+
pending_name, pending_time = self._pending_restart
|
|
122
|
+
# Confirm if same session and within 3 second window
|
|
123
|
+
if pending_name == session_name and (now - pending_time) < 3.0:
|
|
124
|
+
self._pending_restart = None # Clear pending state
|
|
125
|
+
self._execute_restart(focused)
|
|
126
|
+
return
|
|
127
|
+
else:
|
|
128
|
+
# Different session or expired - start new confirmation
|
|
129
|
+
self._pending_restart = None
|
|
130
|
+
|
|
131
|
+
# First press - request confirmation
|
|
132
|
+
self._pending_restart = (session_name, now)
|
|
133
|
+
self.notify(
|
|
134
|
+
f"Press R again to restart '{session_name}'",
|
|
135
|
+
severity="warning",
|
|
136
|
+
timeout=3
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def action_sync_to_main_and_clear(self) -> None:
|
|
140
|
+
"""Switch to main branch, pull, and clear agent context (requires confirmation).
|
|
141
|
+
|
|
142
|
+
This action:
|
|
143
|
+
1. Runs git checkout main && git pull via Claude's bash command
|
|
144
|
+
2. Sends /clear to reset the conversation context
|
|
145
|
+
"""
|
|
146
|
+
from ..tui_widgets import SessionSummary
|
|
147
|
+
|
|
148
|
+
focused = self.focused
|
|
149
|
+
if not isinstance(focused, SessionSummary):
|
|
150
|
+
self.notify("No agent focused", severity="warning")
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
session_name = focused.session.name
|
|
154
|
+
now = time.time()
|
|
155
|
+
|
|
156
|
+
# Check if this is a confirmation of a pending sync
|
|
157
|
+
if self._pending_sync:
|
|
158
|
+
pending_name, pending_time = self._pending_sync
|
|
159
|
+
# Confirm if same session and within 3 second window
|
|
160
|
+
if pending_name == session_name and (now - pending_time) < 3.0:
|
|
161
|
+
self._pending_sync = None # Clear pending state
|
|
162
|
+
self._execute_sync(focused)
|
|
163
|
+
return
|
|
164
|
+
else:
|
|
165
|
+
# Different session or expired - start new confirmation
|
|
166
|
+
self._pending_sync = None
|
|
167
|
+
|
|
168
|
+
# First press - request confirmation
|
|
169
|
+
self._pending_sync = (session_name, now)
|
|
170
|
+
self.notify(
|
|
171
|
+
f"Press c again to sync '{session_name}' to main",
|
|
172
|
+
severity="warning",
|
|
173
|
+
timeout=3
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def _execute_sync(self, widget: "SessionSummary") -> None:
|
|
177
|
+
"""Execute the actual sync operation after confirmation."""
|
|
178
|
+
from ..tmux_manager import TmuxManager
|
|
179
|
+
|
|
180
|
+
session = widget.session
|
|
181
|
+
session_name = session.name
|
|
182
|
+
tmux = TmuxManager(self.tmux_session)
|
|
183
|
+
|
|
184
|
+
self.notify(f"Syncing '{session_name}' to main...", severity="information")
|
|
185
|
+
|
|
186
|
+
# Send git commands - Claude will execute and return to prompt
|
|
187
|
+
git_commands = "!git checkout main && git pull"
|
|
188
|
+
if not tmux.send_keys(session.tmux_window, git_commands, enter=True):
|
|
189
|
+
self.notify(f"Failed to send git commands to '{session_name}'", severity="error")
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
# Send /clear - tmux queues this, Claude processes after git completes
|
|
193
|
+
if tmux.send_keys(session.tmux_window, "/clear", enter=True):
|
|
194
|
+
self.notify(f"Synced '{session_name}' to main with fresh context", severity="information")
|
|
195
|
+
# Reset session stats for fresh start
|
|
196
|
+
self.session_manager.update_stats(
|
|
197
|
+
session.id,
|
|
198
|
+
current_task="Synced to main"
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
self.notify(f"Failed to send /clear to '{session_name}'", severity="error")
|
|
202
|
+
|
|
203
|
+
def action_new_agent(self) -> None:
|
|
204
|
+
"""Prompt for directory and name to create a new agent.
|
|
205
|
+
|
|
206
|
+
Two-step flow:
|
|
207
|
+
1. Enter working directory (or press Enter for current directory)
|
|
208
|
+
2. Enter agent name (defaults to directory basename)
|
|
209
|
+
"""
|
|
210
|
+
from ..tui_widgets import CommandBar
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
command_bar = self.query_one("#command-bar", CommandBar)
|
|
214
|
+
command_bar.add_class("visible") # Must show the command bar first
|
|
215
|
+
command_bar.set_mode("new_agent_dir")
|
|
216
|
+
# Pre-fill with current working directory
|
|
217
|
+
input_widget = command_bar.query_one("#cmd-input", Input)
|
|
218
|
+
input_widget.value = str(Path.cwd())
|
|
219
|
+
command_bar.focus_input()
|
|
220
|
+
except NoMatches:
|
|
221
|
+
self.notify("Command bar not found", severity="error")
|
|
222
|
+
|
|
223
|
+
def action_focus_command_bar(self) -> None:
|
|
224
|
+
"""Focus the command bar for input."""
|
|
225
|
+
from ..tui_widgets import SessionSummary, CommandBar
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
cmd_bar = self.query_one("#command-bar", CommandBar)
|
|
229
|
+
|
|
230
|
+
# Show the command bar
|
|
231
|
+
cmd_bar.add_class("visible")
|
|
232
|
+
|
|
233
|
+
# Get the currently focused session (if any)
|
|
234
|
+
focused = self.focused
|
|
235
|
+
if isinstance(focused, SessionSummary):
|
|
236
|
+
cmd_bar.set_target(focused.session.name)
|
|
237
|
+
elif not cmd_bar.target_session and self.sessions:
|
|
238
|
+
# Default to first session if none focused
|
|
239
|
+
cmd_bar.set_target(self.sessions[0].name)
|
|
240
|
+
|
|
241
|
+
# Enable and focus the input
|
|
242
|
+
cmd_input = cmd_bar.query_one("#cmd-input", Input)
|
|
243
|
+
cmd_input.disabled = False
|
|
244
|
+
cmd_input.focus()
|
|
245
|
+
except NoMatches:
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
def action_focus_standing_orders(self) -> None:
|
|
249
|
+
"""Focus the command bar for editing standing orders."""
|
|
250
|
+
from ..tui_widgets import SessionSummary, CommandBar
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
cmd_bar = self.query_one("#command-bar", CommandBar)
|
|
254
|
+
|
|
255
|
+
# Show the command bar
|
|
256
|
+
cmd_bar.add_class("visible")
|
|
257
|
+
|
|
258
|
+
# Get the currently focused session (if any)
|
|
259
|
+
focused = self.focused
|
|
260
|
+
if isinstance(focused, SessionSummary):
|
|
261
|
+
cmd_bar.set_target(focused.session.name)
|
|
262
|
+
# Pre-fill with existing standing orders
|
|
263
|
+
cmd_input = cmd_bar.query_one("#cmd-input", Input)
|
|
264
|
+
cmd_input.value = focused.session.standing_instructions or ""
|
|
265
|
+
elif not cmd_bar.target_session and self.sessions:
|
|
266
|
+
# Default to first session if none focused
|
|
267
|
+
cmd_bar.set_target(self.sessions[0].name)
|
|
268
|
+
|
|
269
|
+
# Set mode to standing_orders
|
|
270
|
+
cmd_bar.set_mode("standing_orders")
|
|
271
|
+
|
|
272
|
+
# Enable and focus the input
|
|
273
|
+
cmd_input = cmd_bar.query_one("#cmd-input", Input)
|
|
274
|
+
cmd_input.disabled = False
|
|
275
|
+
cmd_input.focus()
|
|
276
|
+
except NoMatches:
|
|
277
|
+
pass
|
|
278
|
+
|
|
279
|
+
def action_focus_human_annotation(self) -> None:
|
|
280
|
+
"""Focus input for editing human annotation (#74)."""
|
|
281
|
+
from ..tui_widgets import SessionSummary, CommandBar
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
cmd_bar = self.query_one("#command-bar", CommandBar)
|
|
285
|
+
|
|
286
|
+
# Show the command bar
|
|
287
|
+
cmd_bar.add_class("visible")
|
|
288
|
+
|
|
289
|
+
# Get the currently focused session (if any)
|
|
290
|
+
focused = self.focused
|
|
291
|
+
if isinstance(focused, SessionSummary):
|
|
292
|
+
cmd_bar.set_target(focused.session.name)
|
|
293
|
+
# Pre-fill with existing annotation
|
|
294
|
+
cmd_input = cmd_bar.query_one("#cmd-input", Input)
|
|
295
|
+
cmd_input.value = focused.session.human_annotation or ""
|
|
296
|
+
elif not cmd_bar.target_session and self.sessions:
|
|
297
|
+
# Default to first session if none focused
|
|
298
|
+
cmd_bar.set_target(self.sessions[0].name)
|
|
299
|
+
|
|
300
|
+
# Set mode to annotation editing
|
|
301
|
+
cmd_bar.set_mode("annotation")
|
|
302
|
+
|
|
303
|
+
# Enable and focus the input
|
|
304
|
+
cmd_input = cmd_bar.query_one("#cmd-input", Input)
|
|
305
|
+
cmd_input.disabled = False
|
|
306
|
+
cmd_input.focus()
|
|
307
|
+
except NoMatches:
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
def action_edit_agent_value(self) -> None:
|
|
311
|
+
"""Focus the command bar for editing agent value (#61)."""
|
|
312
|
+
from ..tui_widgets import SessionSummary, CommandBar
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
cmd_bar = self.query_one("#command-bar", CommandBar)
|
|
316
|
+
|
|
317
|
+
# Show the command bar
|
|
318
|
+
cmd_bar.add_class("visible")
|
|
319
|
+
|
|
320
|
+
# Get the currently focused session (if any)
|
|
321
|
+
focused = self.focused
|
|
322
|
+
if isinstance(focused, SessionSummary):
|
|
323
|
+
cmd_bar.set_target(focused.session.name)
|
|
324
|
+
# Pre-fill with existing value
|
|
325
|
+
cmd_input = cmd_bar.query_one("#cmd-input", Input)
|
|
326
|
+
cmd_input.value = str(focused.session.agent_value)
|
|
327
|
+
elif not cmd_bar.target_session and self.sessions:
|
|
328
|
+
# Default to first session if none focused
|
|
329
|
+
cmd_bar.set_target(self.sessions[0].name)
|
|
330
|
+
cmd_input = cmd_bar.query_one("#cmd-input", Input)
|
|
331
|
+
cmd_input.value = "1000"
|
|
332
|
+
|
|
333
|
+
# Set mode to value
|
|
334
|
+
cmd_bar.set_mode("value")
|
|
335
|
+
|
|
336
|
+
# Enable and focus the input
|
|
337
|
+
cmd_input = cmd_bar.query_one("#cmd-input", Input)
|
|
338
|
+
cmd_input.disabled = False
|
|
339
|
+
cmd_input.focus()
|
|
340
|
+
except NoMatches:
|
|
341
|
+
pass
|
|
342
|
+
|
|
343
|
+
def action_transport_all(self) -> None:
|
|
344
|
+
"""Prepare all sessions for transport/handover (requires double-press confirmation).
|
|
345
|
+
|
|
346
|
+
Sends instructions to all active (non-sleeping) agents to:
|
|
347
|
+
- Create a new branch if on main/master
|
|
348
|
+
- Commit their current changes
|
|
349
|
+
- Push to their branch
|
|
350
|
+
- Create a draft PR if none exists
|
|
351
|
+
- Post handover summary as a PR comment
|
|
352
|
+
|
|
353
|
+
Sleeping agents are excluded from handover.
|
|
354
|
+
"""
|
|
355
|
+
now = time.time()
|
|
356
|
+
|
|
357
|
+
# Get active sessions (exclude terminated and sleeping)
|
|
358
|
+
active_sessions = [
|
|
359
|
+
s for s in self.sessions
|
|
360
|
+
if s.status != "terminated" and not s.is_asleep
|
|
361
|
+
]
|
|
362
|
+
if not active_sessions:
|
|
363
|
+
self.notify("No active sessions to prepare (sleeping sessions excluded)", severity="warning")
|
|
364
|
+
return
|
|
365
|
+
|
|
366
|
+
# Check if this is a confirmation of a pending transport
|
|
367
|
+
if self._pending_transport is not None:
|
|
368
|
+
if (now - self._pending_transport) < 3.0:
|
|
369
|
+
self._pending_transport = None # Clear pending state
|
|
370
|
+
self._execute_transport_all(active_sessions)
|
|
371
|
+
return
|
|
372
|
+
else:
|
|
373
|
+
# Expired - start new confirmation
|
|
374
|
+
self._pending_transport = None
|
|
375
|
+
|
|
376
|
+
# First press - request confirmation
|
|
377
|
+
self._pending_transport = now
|
|
378
|
+
count = len(active_sessions)
|
|
379
|
+
self.notify(
|
|
380
|
+
f"Press H again to send handover instructions to {count} agent(s)",
|
|
381
|
+
severity="warning",
|
|
382
|
+
timeout=3
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
def _execute_transport_all(self, sessions: List["Session"]) -> None:
|
|
386
|
+
"""Execute transport/handover instructions to all sessions."""
|
|
387
|
+
from ..launcher import ClaudeLauncher
|
|
388
|
+
|
|
389
|
+
# The handover instruction to send to each agent
|
|
390
|
+
handover_instruction = (
|
|
391
|
+
"Please prepare for handover. Follow these steps in order:\n\n"
|
|
392
|
+
"1. Check your current branch with `git branch --show-current`\n"
|
|
393
|
+
" - If on main or master, create and switch to a new branch:\n"
|
|
394
|
+
" `git checkout -b handover/<brief-task-description>`\n"
|
|
395
|
+
" - Never push directly to main/master\n\n"
|
|
396
|
+
"2. Commit all your current changes with a descriptive commit message\n\n"
|
|
397
|
+
"3. Push to your branch: `git push -u origin <branch-name>`\n\n"
|
|
398
|
+
"4. Check if a PR exists: `gh pr list --head $(git branch --show-current)`\n"
|
|
399
|
+
" - If no PR exists, create a draft PR:\n"
|
|
400
|
+
" `gh pr create --draft --title '<brief title>' --body 'WIP'`\n\n"
|
|
401
|
+
"5. Post a handover comment on the PR using `gh pr comment` with:\n"
|
|
402
|
+
" - What you've accomplished\n"
|
|
403
|
+
" - Current state of the work\n"
|
|
404
|
+
" - Any pending tasks or next steps\n"
|
|
405
|
+
" - Known issues or blockers"
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
launcher = ClaudeLauncher(
|
|
409
|
+
tmux_session=self.tmux_session,
|
|
410
|
+
session_manager=self.session_manager
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
success_count = 0
|
|
414
|
+
for session in sessions:
|
|
415
|
+
if launcher.send_to_session(session.name, handover_instruction):
|
|
416
|
+
success_count += 1
|
|
417
|
+
|
|
418
|
+
if success_count == len(sessions):
|
|
419
|
+
self.notify(
|
|
420
|
+
f"Sent handover instructions to {success_count} agent(s)",
|
|
421
|
+
severity="information"
|
|
422
|
+
)
|
|
423
|
+
else:
|
|
424
|
+
failed = len(sessions) - success_count
|
|
425
|
+
self.notify(
|
|
426
|
+
f"Sent to {success_count}, failed {failed} agent(s)",
|
|
427
|
+
severity="warning"
|
|
428
|
+
)
|