overcode 0.3.4__tar.gz → 0.3.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.
- {overcode-0.3.4/src/overcode.egg-info → overcode-0.3.5}/PKG-INFO +1 -1
- {overcode-0.3.4 → overcode-0.3.5}/pyproject.toml +1 -1
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/bundled_skills.py +5 -2
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/agent.py +82 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/jobs.py +104 -1
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/config.py +20 -0
- overcode-0.3.5/src/overcode/daemon_claude_skill.md +68 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/history_reader.py +21 -2
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/hook_handler.py +47 -1
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/hook_status_detector.py +2 -1
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/launcher.py +41 -2
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/monitor_daemon.py +75 -5
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/monitor_daemon_state.py +2 -1
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/session_manager.py +3 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/sister_poller.py +2 -1
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/summarizer_client.py +63 -6
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/summary_columns.py +24 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/supervisor_daemon.py +53 -2
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/supervisor_daemon_core.py +14 -7
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tmux_manager.py +7 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui.py +17 -7
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui.tcss +2 -2
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/command_bar.py +36 -6
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/session_summary.py +5 -1
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/web_api.py +1 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/web_control_api.py +6 -6
- {overcode-0.3.4 → overcode-0.3.5/src/overcode.egg-info}/PKG-INFO +1 -1
- overcode-0.3.4/src/overcode/daemon_claude_skill.md +0 -183
- {overcode-0.3.4 → overcode-0.3.5}/LICENSE +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/MANIFEST.in +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/README.md +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/setup.cfg +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/__init__.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/agent_scanner.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/claude_config.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/claude_pid.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/__init__.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/__main__.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/_shared.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/budget.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/config.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/daemon.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/hooks.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/monitoring.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/perms.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/sister.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/skills.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/cli/split.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/daemon_logging.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/daemon_utils.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/data_export.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/dependency_check.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/duration.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/exceptions.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/follow_mode.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/implementations.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/interfaces.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/job_launcher.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/job_manager.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/logging_config.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/mocks.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/monitor_daemon_core.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/notifier.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/pid_utils.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/presence_logger.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/protocols.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/settings.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/sister_controller.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/ssh_provisioner.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/standing_instructions.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/status_constants.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/status_detector.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/status_detector_factory.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/status_history.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/status_patterns.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/summarizer_component.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/summary_groups.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/supervisor_layout.sh +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/testing/__init__.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/testing/renderer.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/testing/tmux_driver.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/testing/tui_eye.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/testing/tui_eye_skill.md +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/time_context.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tmux_utils.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_actions/__init__.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_actions/daemon.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_actions/input.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_actions/navigation.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_actions/session.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_actions/view.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_helpers.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_logic.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_render.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/__init__.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/agent_select_modal.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/daemon_panel.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/daemon_status_bar.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/fullscreen_preview.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/help_overlay.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/instruction_history_modal.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/job_summary.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/new_agent_defaults_modal.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/preview_pane.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/sister_selection_modal.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/status_timeline.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/summary_config_modal.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/tui_widgets/tui_log_panel.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/usage_monitor.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/web/__init__.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/web/templates/analytics.html +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/web/templates/dashboard.html +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/web_chartjs.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/web_server.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/web_server_runner.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode/web_templates.py +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode.egg-info/SOURCES.txt +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode.egg-info/dependency_links.txt +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode.egg-info/entry_points.txt +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode.egg-info/requires.txt +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/src/overcode.egg-info/top_level.txt +0 -0
- {overcode-0.3.4 → overcode-0.3.5}/tests/test_e2e_multi_agent_jokes.py +0 -0
|
@@ -94,8 +94,10 @@ overcode bash "make deploy-staging" --agent my-agent # Link to an agent
|
|
|
94
94
|
|
|
95
95
|
# Manage jobs
|
|
96
96
|
overcode jobs list [--all] # List running (or all) jobs
|
|
97
|
+
overcode jobs tail <name> # Stream output (works without TTY)
|
|
98
|
+
overcode jobs tail <name> -n 50 # Last 50 lines and exit
|
|
97
99
|
overcode jobs kill <name> # Kill a running job
|
|
98
|
-
overcode jobs attach <name> # Attach to job's tmux window
|
|
100
|
+
overcode jobs attach <name> # Attach to job's tmux window (needs TTY)
|
|
99
101
|
overcode jobs clear # Remove completed/failed/killed jobs
|
|
100
102
|
|
|
101
103
|
# TUI: press J to toggle jobs view, j/k to navigate, x to kill, c to clear
|
|
@@ -227,7 +229,8 @@ overcode bash "npm run build" --name frontend-build --agent my-agent
|
|
|
227
229
|
|
|
228
230
|
# Check on it later
|
|
229
231
|
overcode jobs list
|
|
230
|
-
overcode jobs
|
|
232
|
+
overcode jobs tail full-tests # Stream output (no TTY needed)
|
|
233
|
+
overcode jobs tail full-tests -n 50 # Last 50 lines snapshot
|
|
231
234
|
overcode jobs kill full-tests # Kill if needed
|
|
232
235
|
```
|
|
233
236
|
|
|
@@ -212,6 +212,10 @@ def launch(
|
|
|
212
212
|
bool,
|
|
213
213
|
typer.Option("--teams", help="Enable Claude Code agent teams (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS)"),
|
|
214
214
|
] = False,
|
|
215
|
+
provider: Annotated[
|
|
216
|
+
Optional[str],
|
|
217
|
+
typer.Option("--provider", "-P", help="API provider: 'web' (Claude.ai OAuth) or 'bedrock' (AWS Bedrock)"),
|
|
218
|
+
] = None,
|
|
215
219
|
sister: Annotated[
|
|
216
220
|
Optional[str],
|
|
217
221
|
typer.Option("--sister", "-S", help="Launch on a remote sister machine (by name from config)"),
|
|
@@ -270,6 +274,15 @@ def launch(
|
|
|
270
274
|
# Parse oversight policy
|
|
271
275
|
oversight_policy, oversight_timeout_seconds = _parse_oversight_policy(on_stuck, oversight_timeout)
|
|
272
276
|
|
|
277
|
+
# Resolve provider: CLI flag > config default > "web"
|
|
278
|
+
resolved_provider = provider
|
|
279
|
+
if resolved_provider is None:
|
|
280
|
+
from ..config import get_new_agent_defaults
|
|
281
|
+
resolved_provider = get_new_agent_defaults().get("provider", "web")
|
|
282
|
+
if resolved_provider not in ("web", "bedrock"):
|
|
283
|
+
rprint(f"[red]Error: Invalid provider '{resolved_provider}'. Use: web, bedrock[/red]")
|
|
284
|
+
raise typer.Exit(code=1)
|
|
285
|
+
|
|
273
286
|
# Default to current directory if not specified
|
|
274
287
|
working_dir = directory if directory else os.getcwd()
|
|
275
288
|
|
|
@@ -288,6 +301,7 @@ def launch(
|
|
|
288
301
|
budget_usd=budget,
|
|
289
302
|
claude_agent=agent,
|
|
290
303
|
model=model,
|
|
304
|
+
provider=resolved_provider,
|
|
291
305
|
)
|
|
292
306
|
|
|
293
307
|
if result:
|
|
@@ -304,6 +318,8 @@ def launch(
|
|
|
304
318
|
rprint(f" Agent: {agent}")
|
|
305
319
|
if teams:
|
|
306
320
|
rprint(" Agent teams: enabled")
|
|
321
|
+
if resolved_provider != "web":
|
|
322
|
+
rprint(f" Provider: {resolved_provider}")
|
|
307
323
|
if budget is not None and budget > 0:
|
|
308
324
|
rprint(f" Budget: ${budget:.2f}")
|
|
309
325
|
|
|
@@ -479,6 +495,8 @@ def list_agents(
|
|
|
479
495
|
|
|
480
496
|
# Pre-compute: any agent with budget, column alignment widths
|
|
481
497
|
any_has_budget = any(s.cost_budget_usd > 0 for s in sessions)
|
|
498
|
+
any_has_provider = any(getattr(s, 'provider', 'web') not in ('web', None, '') for s in sessions)
|
|
499
|
+
any_has_model = any(getattr(s, 'model', None) for s in sessions)
|
|
482
500
|
max_name_len = max((len(s.name) for s in sessions), default=10)
|
|
483
501
|
name_width = min(max(max_name_len, 10), 20)
|
|
484
502
|
max_repo_width = max((len(s.repo_name or "n/a") for s in sessions), default=5)
|
|
@@ -585,6 +603,8 @@ def list_agents(
|
|
|
585
603
|
oversight_deadline=oversight_deadline,
|
|
586
604
|
pr_number=getattr(sess, 'pr_number', None),
|
|
587
605
|
any_has_pr=any_has_pr,
|
|
606
|
+
any_has_model=any_has_model,
|
|
607
|
+
any_has_provider=any_has_provider,
|
|
588
608
|
monochrome=False,
|
|
589
609
|
summary_detail=detail,
|
|
590
610
|
has_sisters=has_sisters,
|
|
@@ -673,6 +693,59 @@ def kill(
|
|
|
673
693
|
launcher.kill_session(name, cascade=not no_cascade)
|
|
674
694
|
|
|
675
695
|
|
|
696
|
+
@app.command()
|
|
697
|
+
def restart(
|
|
698
|
+
name: Annotated[str, typer.Argument(help="Name of agent to restart")],
|
|
699
|
+
session: SessionOption = "agents",
|
|
700
|
+
):
|
|
701
|
+
"""Restart a running agent with the same configuration.
|
|
702
|
+
|
|
703
|
+
Gracefully exits Claude (Ctrl-C + /exit), then relaunches with the
|
|
704
|
+
same permissions mode. Useful when MCP server configs change.
|
|
705
|
+
"""
|
|
706
|
+
import os
|
|
707
|
+
import time
|
|
708
|
+
from ..session_manager import SessionManager
|
|
709
|
+
from ..tmux_manager import TmuxManager
|
|
710
|
+
from ..tmux_utils import tmux_window_target, _build_tmux_cmd
|
|
711
|
+
|
|
712
|
+
sm = SessionManager()
|
|
713
|
+
sess = sm.get_session_by_name(name)
|
|
714
|
+
if not sess:
|
|
715
|
+
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
716
|
+
raise typer.Exit(code=1)
|
|
717
|
+
|
|
718
|
+
tmux = TmuxManager(session)
|
|
719
|
+
if not tmux.window_exists(sess.tmux_window):
|
|
720
|
+
rprint(f"[red]Error: Tmux window for '{name}' no longer exists[/red]")
|
|
721
|
+
raise typer.Exit(code=1)
|
|
722
|
+
|
|
723
|
+
# Build the claude command based on permissiveness mode
|
|
724
|
+
claude_command = os.environ.get("CLAUDE_COMMAND", "claude")
|
|
725
|
+
cmd_parts = [claude_command]
|
|
726
|
+
if sess.permissiveness_mode == "bypass":
|
|
727
|
+
cmd_parts.append("--dangerously-skip-permissions")
|
|
728
|
+
elif sess.permissiveness_mode == "permissive":
|
|
729
|
+
cmd_parts.extend(["--permission-mode", "dontAsk"])
|
|
730
|
+
cmd_str = " ".join(cmd_parts)
|
|
731
|
+
|
|
732
|
+
# Gracefully exit Claude: Ctrl-C + /exit
|
|
733
|
+
rprint(f"[dim]Stopping '{name}'...[/dim]")
|
|
734
|
+
tmux.send_keys(sess.tmux_window, "C-c", enter=False)
|
|
735
|
+
time.sleep(0.5)
|
|
736
|
+
tmux.send_keys(sess.tmux_window, "/exit", enter=True)
|
|
737
|
+
time.sleep(3.0)
|
|
738
|
+
|
|
739
|
+
# Relaunch
|
|
740
|
+
if tmux.send_keys(sess.tmux_window, cmd_str, enter=True):
|
|
741
|
+
sm.update_stats(sess.id, current_task="Restarting...")
|
|
742
|
+
sm.update_session(sess.id, claude_session_ids=[])
|
|
743
|
+
rprint(f"[green]Restarted agent: {name}[/green]")
|
|
744
|
+
else:
|
|
745
|
+
rprint(f"[red]Failed to restart agent: {name}[/red]")
|
|
746
|
+
raise typer.Exit(code=1)
|
|
747
|
+
|
|
748
|
+
|
|
676
749
|
@app.command()
|
|
677
750
|
def follow(
|
|
678
751
|
name: Annotated[str, typer.Argument(help="Name of agent to follow")],
|
|
@@ -903,6 +976,15 @@ def send(
|
|
|
903
976
|
overcode send my-agent escape # Press Escape (reject)
|
|
904
977
|
overcode send my-agent --no-enter "y" # Send "y" without Enter
|
|
905
978
|
"""
|
|
979
|
+
from ..session_manager import SessionManager
|
|
980
|
+
sm = SessionManager()
|
|
981
|
+
|
|
982
|
+
# Auto-wake sleeping agent (#168)
|
|
983
|
+
agent_session = sm.get_session_by_name(name)
|
|
984
|
+
if agent_session and agent_session.is_asleep:
|
|
985
|
+
sm.update_session(agent_session.id, is_asleep=False)
|
|
986
|
+
rprint(f"[dim]Woke agent '{name}' to send command[/dim]")
|
|
987
|
+
|
|
906
988
|
launcher = ClaudeLauncher(session)
|
|
907
989
|
|
|
908
990
|
# Join all text parts if multiple were given
|
|
@@ -55,7 +55,10 @@ def bash(
|
|
|
55
55
|
rprint(f" Linked to agent: {agent_name}")
|
|
56
56
|
|
|
57
57
|
if follow:
|
|
58
|
-
|
|
58
|
+
if os.isatty(0):
|
|
59
|
+
launcher.attach(job.name)
|
|
60
|
+
else:
|
|
61
|
+
rprint(f" [dim]No TTY — use 'overcode jobs tail {job.name}' to stream output[/dim]")
|
|
59
62
|
|
|
60
63
|
|
|
61
64
|
@jobs_app.command("list")
|
|
@@ -144,6 +147,106 @@ def clear_completed():
|
|
|
144
147
|
rprint("[green]✓[/green] Cleared completed jobs")
|
|
145
148
|
|
|
146
149
|
|
|
150
|
+
@jobs_app.command("tail")
|
|
151
|
+
def tail_job(
|
|
152
|
+
name: Annotated[str, typer.Argument(help="Job name to tail")],
|
|
153
|
+
lines: Annotated[Optional[int], typer.Option("--lines", "-n", help="Show last N lines and exit")] = None,
|
|
154
|
+
follow: Annotated[bool, typer.Option("--follow/--no-follow", "-f", help="Stream output until job completes")] = True,
|
|
155
|
+
poll_interval: Annotated[float, typer.Option("--poll", hidden=True)] = 0.5,
|
|
156
|
+
):
|
|
157
|
+
"""Stream a job's output (like tail -f). Works without a TTY.
|
|
158
|
+
|
|
159
|
+
Default: streams output until the job completes or Ctrl-C.
|
|
160
|
+
With --lines N: shows last N lines and exits.
|
|
161
|
+
|
|
162
|
+
Examples:
|
|
163
|
+
overcode jobs tail my-job
|
|
164
|
+
overcode jobs tail my-job --lines 50
|
|
165
|
+
overcode jobs tail my-job --no-follow
|
|
166
|
+
"""
|
|
167
|
+
import signal
|
|
168
|
+
import sys
|
|
169
|
+
import time
|
|
170
|
+
from collections import deque
|
|
171
|
+
from ..follow_mode import _capture_pane, _find_dedup_start
|
|
172
|
+
from ..job_manager import JobManager
|
|
173
|
+
from ..status_patterns import strip_ansi
|
|
174
|
+
|
|
175
|
+
manager = JobManager()
|
|
176
|
+
job = manager.get_job_by_name(name)
|
|
177
|
+
if not job:
|
|
178
|
+
rprint(f"[red]Error: Job '{name}' not found[/red]")
|
|
179
|
+
raise typer.Exit(1)
|
|
180
|
+
|
|
181
|
+
tmux_session = job.tmux_session
|
|
182
|
+
window_name = job.tmux_window
|
|
183
|
+
|
|
184
|
+
# One-shot: capture and print last N lines
|
|
185
|
+
if lines is not None:
|
|
186
|
+
raw = _capture_pane(tmux_session, window_name, lines=lines)
|
|
187
|
+
if raw:
|
|
188
|
+
for line in raw.rstrip().split('\n'):
|
|
189
|
+
cleaned = strip_ansi(line).strip()
|
|
190
|
+
if cleaned:
|
|
191
|
+
print(cleaned)
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
# Streaming mode
|
|
195
|
+
interrupted = False
|
|
196
|
+
|
|
197
|
+
def _sigint(sig, frame):
|
|
198
|
+
nonlocal interrupted
|
|
199
|
+
interrupted = True
|
|
200
|
+
|
|
201
|
+
old_handler = signal.signal(signal.SIGINT, _sigint)
|
|
202
|
+
|
|
203
|
+
recent_lines: deque = deque(maxlen=50)
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
while not interrupted:
|
|
207
|
+
raw = _capture_pane(tmux_session, window_name)
|
|
208
|
+
if raw:
|
|
209
|
+
new_lines = []
|
|
210
|
+
for line in raw.rstrip().split('\n'):
|
|
211
|
+
cleaned = strip_ansi(line).strip()
|
|
212
|
+
new_lines.append(cleaned)
|
|
213
|
+
|
|
214
|
+
output_start = _find_dedup_start(new_lines, recent_lines)
|
|
215
|
+
if output_start < len(new_lines):
|
|
216
|
+
for line in new_lines[output_start:]:
|
|
217
|
+
if line:
|
|
218
|
+
print(line)
|
|
219
|
+
recent_lines.append(line)
|
|
220
|
+
|
|
221
|
+
# Check if job is done
|
|
222
|
+
if follow:
|
|
223
|
+
job = manager.get_job_by_name(name)
|
|
224
|
+
if job and job.status in ("completed", "failed", "killed"):
|
|
225
|
+
# Final capture
|
|
226
|
+
time.sleep(poll_interval)
|
|
227
|
+
raw = _capture_pane(tmux_session, window_name)
|
|
228
|
+
if raw:
|
|
229
|
+
new_lines = [strip_ansi(l).strip() for l in raw.rstrip().split('\n')]
|
|
230
|
+
output_start = _find_dedup_start(new_lines, recent_lines)
|
|
231
|
+
for line in new_lines[output_start:]:
|
|
232
|
+
if line:
|
|
233
|
+
print(line)
|
|
234
|
+
recent_lines.append(line)
|
|
235
|
+
status_msg = f"completed (exit {job.exit_code})" if job.exit_code is not None else job.status
|
|
236
|
+
print(f"\n[tail] Job '{name}' {status_msg}", file=sys.stderr)
|
|
237
|
+
raise typer.Exit(0 if job.status == "completed" else 1)
|
|
238
|
+
else:
|
|
239
|
+
# --no-follow: one capture cycle then exit
|
|
240
|
+
break
|
|
241
|
+
|
|
242
|
+
time.sleep(poll_interval)
|
|
243
|
+
finally:
|
|
244
|
+
signal.signal(signal.SIGINT, old_handler)
|
|
245
|
+
|
|
246
|
+
if interrupted:
|
|
247
|
+
raise typer.Exit(130)
|
|
248
|
+
|
|
249
|
+
|
|
147
250
|
@jobs_app.command("_complete", hidden=True)
|
|
148
251
|
def mark_complete(
|
|
149
252
|
job_id: Annotated[str, typer.Argument(help="Job ID")],
|
|
@@ -118,8 +118,10 @@ def get_summarizer_config() -> dict:
|
|
|
118
118
|
default_api_url = "https://api.openai.com/v1/chat/completions"
|
|
119
119
|
default_model = "gpt-4o-mini"
|
|
120
120
|
default_api_key_var = "OPENAI_API_KEY"
|
|
121
|
+
default_api_type = "openai"
|
|
121
122
|
|
|
122
123
|
# Config file takes precedence, env vars are fallback
|
|
124
|
+
api_type = _get_config_value("summarizer.api_type") or os.environ.get("OVERCODE_SUMMARIZER_API_TYPE") or default_api_type
|
|
123
125
|
api_url = _get_config_value("summarizer.api_url") or os.environ.get("OVERCODE_SUMMARIZER_API_URL") or default_api_url
|
|
124
126
|
model = _get_config_value("summarizer.model") or os.environ.get("OVERCODE_SUMMARIZER_MODEL") or default_model
|
|
125
127
|
api_key_var = _get_config_value("summarizer.api_key_var") or os.environ.get("OVERCODE_SUMMARIZER_API_KEY_VAR") or default_api_key_var
|
|
@@ -128,6 +130,7 @@ def get_summarizer_config() -> dict:
|
|
|
128
130
|
api_key = os.environ.get(api_key_var)
|
|
129
131
|
|
|
130
132
|
return {
|
|
133
|
+
"api_type": api_type,
|
|
131
134
|
"api_url": api_url,
|
|
132
135
|
"model": model,
|
|
133
136
|
"api_key": api_key,
|
|
@@ -340,6 +343,7 @@ def get_new_agent_defaults() -> dict:
|
|
|
340
343
|
return {
|
|
341
344
|
"bypass_permissions": bool(defaults.get("bypass_permissions", False)),
|
|
342
345
|
"agent_teams": bool(defaults.get("agent_teams", False)),
|
|
346
|
+
"provider": defaults.get("provider", "web"),
|
|
343
347
|
}
|
|
344
348
|
|
|
345
349
|
|
|
@@ -354,6 +358,22 @@ def save_new_agent_defaults(defaults: dict) -> None:
|
|
|
354
358
|
save_config(config)
|
|
355
359
|
|
|
356
360
|
|
|
361
|
+
def get_bedrock_config() -> dict:
|
|
362
|
+
"""Get AWS Bedrock configuration.
|
|
363
|
+
|
|
364
|
+
Config format in ~/.overcode/config.yaml:
|
|
365
|
+
bedrock:
|
|
366
|
+
region: us-east-1
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
Dict with region (str).
|
|
370
|
+
"""
|
|
371
|
+
bedrock = _get_config_value("bedrock", {})
|
|
372
|
+
return {
|
|
373
|
+
"region": bedrock.get("region", "us-east-1"),
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
|
|
357
377
|
def get_jobs_retention_hours() -> float:
|
|
358
378
|
"""Get job retention period in hours.
|
|
359
379
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Overcode Supervisor Skill
|
|
2
|
+
|
|
3
|
+
You are the Overcode supervisor agent. Your mission: **Unblock each non-green session once, then exit**.
|
|
4
|
+
|
|
5
|
+
## Status Guide
|
|
6
|
+
|
|
7
|
+
- ORANGE (`waiting_approval`) -- Agent blocked on a permission prompt. This is your PRIMARY target. Approve or reject based on standing instructions and approval rules.
|
|
8
|
+
- RED (`waiting_user`) -- Agent waiting for human input at the prompt. If it has standing instructions, send guidance. If not, skip it.
|
|
9
|
+
- YELLOW (`busy_sleeping`) -- Agent is sleeping. Usually skip.
|
|
10
|
+
- PURPLE (`error`) -- API error. Usually skip.
|
|
11
|
+
|
|
12
|
+
## Critical: Act Fast, Don't Investigate
|
|
13
|
+
|
|
14
|
+
You have LIMITED TIME. Do NOT waste it on `overcode list` or reading sessions.json -- the context below already tells you which sessions need help and their standing instructions.
|
|
15
|
+
|
|
16
|
+
**For each non-green session in order:**
|
|
17
|
+
|
|
18
|
+
1. Run `overcode show <name>` to see what it's stuck on
|
|
19
|
+
2. Immediately act: `overcode send <name> enter` (approve) or `overcode send <name> escape` (reject)
|
|
20
|
+
3. Move to the next session -- do NOT check if it worked
|
|
21
|
+
|
|
22
|
+
## How to Unblock
|
|
23
|
+
|
|
24
|
+
# Approve a permission request (ORANGE sessions)
|
|
25
|
+
overcode send my-agent enter
|
|
26
|
+
|
|
27
|
+
# Reject a permission request
|
|
28
|
+
overcode send my-agent escape
|
|
29
|
+
|
|
30
|
+
# Send text response (RED sessions with instructions)
|
|
31
|
+
overcode send my-agent "your guidance here"
|
|
32
|
+
|
|
33
|
+
## Approval Rules
|
|
34
|
+
|
|
35
|
+
Follow the session's **standing instructions** first. Then apply these defaults:
|
|
36
|
+
|
|
37
|
+
### Auto-Approve
|
|
38
|
+
- File reads/writes/edits, Grep, Glob
|
|
39
|
+
- Shell commands: ls, cat, head, tail, find, grep, mkdir, touch, wc, sort, diff
|
|
40
|
+
- git add, git commit, git status, git diff, git log, git branch
|
|
41
|
+
- Running tests, linters, builds
|
|
42
|
+
- WebFetch, web searches
|
|
43
|
+
- pip/npm/uv install
|
|
44
|
+
|
|
45
|
+
### Use Judgment
|
|
46
|
+
- git push (only if tests pass)
|
|
47
|
+
- Operations outside the project directory
|
|
48
|
+
- Destructive operations (rm, git reset)
|
|
49
|
+
|
|
50
|
+
### Reject
|
|
51
|
+
- rm -rf on large directories
|
|
52
|
+
- Operations on system files
|
|
53
|
+
- Network writes to external services (unless in standing instructions)
|
|
54
|
+
|
|
55
|
+
## Your Process
|
|
56
|
+
|
|
57
|
+
For EACH non-green session listed in the context below:
|
|
58
|
+
1. `overcode show <name>` -- see what it needs
|
|
59
|
+
2. Decide and act immediately
|
|
60
|
+
3. Move on
|
|
61
|
+
|
|
62
|
+
After attempting ALL sessions once, run `exit 0`. The daemon will call you again if needed.
|
|
63
|
+
|
|
64
|
+
**Do NOT:**
|
|
65
|
+
- Run `overcode list` (you already have the list)
|
|
66
|
+
- Read sessions.json (you already have the context)
|
|
67
|
+
- Loop back to check results
|
|
68
|
+
- Make multiple attempts on the same session
|
|
@@ -650,8 +650,15 @@ def get_session_stats(
|
|
|
650
650
|
interactions = hf.get_interactions_for_session(session)
|
|
651
651
|
interaction_count = len(interactions)
|
|
652
652
|
|
|
653
|
-
# Derive Claude sessionIds
|
|
653
|
+
# Derive Claude sessionIds and their project paths from interactions.
|
|
654
|
+
# Claude Code may store session files under a different project path
|
|
655
|
+
# than start_directory (e.g., when the directory doesn't exist or Claude
|
|
656
|
+
# chooses a different project root).
|
|
654
657
|
session_ids = {e.session_id for e in interactions if e.session_id}
|
|
658
|
+
sid_to_project: Dict[str, str] = {}
|
|
659
|
+
for e in interactions:
|
|
660
|
+
if e.session_id and e.project:
|
|
661
|
+
sid_to_project[e.session_id] = e.project
|
|
655
662
|
|
|
656
663
|
# Active session ID for context window after /clear (#116)
|
|
657
664
|
active_session_id = getattr(session, 'active_claude_session_id', None)
|
|
@@ -673,6 +680,16 @@ def get_session_stats(
|
|
|
673
680
|
session_file = get_session_file_path(
|
|
674
681
|
session.start_directory, sid, projects_path
|
|
675
682
|
)
|
|
683
|
+
# Fall back to the project path from history entries if the session
|
|
684
|
+
# file doesn't exist at the expected start_directory path. Claude
|
|
685
|
+
# Code may use a different project root (e.g. home dir) when the
|
|
686
|
+
# launch directory no longer exists.
|
|
687
|
+
if not session_file.exists():
|
|
688
|
+
alt_project = sid_to_project.get(sid)
|
|
689
|
+
if alt_project:
|
|
690
|
+
session_file = get_session_file_path(
|
|
691
|
+
alt_project, sid, projects_path
|
|
692
|
+
)
|
|
676
693
|
usage, work_times = read_session_file_stats(session_file, since=session_start)
|
|
677
694
|
total_input += usage["input_tokens"]
|
|
678
695
|
total_output += usage["output_tokens"]
|
|
@@ -695,7 +712,9 @@ def get_session_stats(
|
|
|
695
712
|
all_work_times.extend(work_times)
|
|
696
713
|
|
|
697
714
|
# Check for subagent files in {sessionId}/subagents/
|
|
698
|
-
|
|
715
|
+
# Use the actual project path where the session file was found.
|
|
716
|
+
actual_project = sid_to_project.get(sid, session.start_directory)
|
|
717
|
+
encoded = encode_project_path(actual_project)
|
|
699
718
|
subagents_dir = projects_path / encoded / sid / "subagents"
|
|
700
719
|
if subagents_dir.exists():
|
|
701
720
|
for subagent_file in subagents_dir.glob("agent-*.jsonl"):
|
|
@@ -15,6 +15,7 @@ Hook registrations (all use the same command):
|
|
|
15
15
|
import json
|
|
16
16
|
import logging
|
|
17
17
|
import os
|
|
18
|
+
import subprocess
|
|
18
19
|
import sys
|
|
19
20
|
import time
|
|
20
21
|
from pathlib import Path
|
|
@@ -35,6 +36,47 @@ OVERCODE_HOOKS: list[tuple[str, str]] = [
|
|
|
35
36
|
]
|
|
36
37
|
|
|
37
38
|
|
|
39
|
+
def _detect_from_tmux_pane() -> tuple[str | None, str | None]:
|
|
40
|
+
"""Detect agent name and tmux session from the current tmux pane.
|
|
41
|
+
|
|
42
|
+
Fallback for when OVERCODE_SESSION_NAME / OVERCODE_TMUX_SESSION env vars
|
|
43
|
+
are missing (e.g. after a manual session restart with --session-id).
|
|
44
|
+
|
|
45
|
+
Returns (session_name, tmux_session) or (None, None) if detection fails.
|
|
46
|
+
"""
|
|
47
|
+
pane_id = os.environ.get("TMUX_PANE")
|
|
48
|
+
if not pane_id:
|
|
49
|
+
return None, None
|
|
50
|
+
try:
|
|
51
|
+
window_name = subprocess.run(
|
|
52
|
+
["tmux", "display-message", "-p", "-t", pane_id, "#{window_name}"],
|
|
53
|
+
capture_output=True, text=True, timeout=2,
|
|
54
|
+
).stdout.strip()
|
|
55
|
+
tmux_session = subprocess.run(
|
|
56
|
+
["tmux", "display-message", "-p", "-t", pane_id, "#{session_name}"],
|
|
57
|
+
capture_output=True, text=True, timeout=2,
|
|
58
|
+
).stdout.strip()
|
|
59
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
60
|
+
return None, None
|
|
61
|
+
|
|
62
|
+
if not window_name or not tmux_session:
|
|
63
|
+
return None, None
|
|
64
|
+
|
|
65
|
+
# Strip oc-view- prefix from split-view session names
|
|
66
|
+
if tmux_session.startswith("oc-view-"):
|
|
67
|
+
tmux_session = tmux_session[len("oc-view-"):]
|
|
68
|
+
|
|
69
|
+
# Window names are "agentname-XXXX" where XXXX is a UUID prefix
|
|
70
|
+
# Strip the last "-XXXX" suffix to get the agent name
|
|
71
|
+
dash_idx = window_name.rfind("-")
|
|
72
|
+
if dash_idx > 0:
|
|
73
|
+
session_name = window_name[:dash_idx]
|
|
74
|
+
else:
|
|
75
|
+
session_name = window_name
|
|
76
|
+
|
|
77
|
+
return session_name, tmux_session
|
|
78
|
+
|
|
79
|
+
|
|
38
80
|
def _get_hook_state_path(tmux_session: str, session_name: str) -> Path:
|
|
39
81
|
"""Get the path for a hook state file.
|
|
40
82
|
|
|
@@ -88,7 +130,11 @@ def handle_hook_event() -> None:
|
|
|
88
130
|
tmux_session = os.environ.get("OVERCODE_TMUX_SESSION")
|
|
89
131
|
|
|
90
132
|
if not session_name or not tmux_session:
|
|
91
|
-
|
|
133
|
+
# Fallback: detect from tmux pane when env vars are missing
|
|
134
|
+
# (e.g. after manual session restart with --session-id)
|
|
135
|
+
session_name, tmux_session = _detect_from_tmux_pane()
|
|
136
|
+
if not session_name or not tmux_session:
|
|
137
|
+
return
|
|
92
138
|
|
|
93
139
|
# Read stdin JSON
|
|
94
140
|
try:
|
|
@@ -24,6 +24,7 @@ from .status_constants import (
|
|
|
24
24
|
STATUS_CAPTURE_LINES,
|
|
25
25
|
STATUS_RUNNING,
|
|
26
26
|
STATUS_BUSY_SLEEPING,
|
|
27
|
+
STATUS_WAITING_APPROVAL,
|
|
27
28
|
STATUS_WAITING_USER,
|
|
28
29
|
STATUS_WAITING_OVERSIGHT,
|
|
29
30
|
STATUS_TERMINATED,
|
|
@@ -51,7 +52,7 @@ _HOOK_STATUS_MAP = {
|
|
|
51
52
|
"PostToolUseFailure": STATUS_RUNNING, # Tool failed but agent is still working
|
|
52
53
|
"Stop": STATUS_WAITING_USER,
|
|
53
54
|
"StopFailure": STATUS_ERROR, # API error ended the turn (purple indicator)
|
|
54
|
-
"PermissionRequest":
|
|
55
|
+
"PermissionRequest": STATUS_WAITING_APPROVAL,
|
|
55
56
|
"SessionEnd": STATUS_TERMINATED,
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -141,6 +141,7 @@ class ClaudeLauncher:
|
|
|
141
141
|
agent_teams: bool = False,
|
|
142
142
|
claude_agent: Optional[str] = None,
|
|
143
143
|
model: Optional[str] = None,
|
|
144
|
+
provider: str = "web",
|
|
144
145
|
) -> dict:
|
|
145
146
|
"""Build the kwargs dict for SessionManager.create_session.
|
|
146
147
|
|
|
@@ -160,6 +161,7 @@ class ClaudeLauncher:
|
|
|
160
161
|
agent_teams=agent_teams,
|
|
161
162
|
claude_agent=claude_agent,
|
|
162
163
|
model=model,
|
|
164
|
+
provider=provider,
|
|
163
165
|
session_id=session_id,
|
|
164
166
|
)
|
|
165
167
|
|
|
@@ -188,6 +190,13 @@ class ClaudeLauncher:
|
|
|
188
190
|
if metadata.get('agent_teams'):
|
|
189
191
|
env_prefix += " CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"
|
|
190
192
|
|
|
193
|
+
if metadata.get('provider') == 'bedrock':
|
|
194
|
+
env_prefix += " CLAUDE_CODE_USE_BEDROCK=1"
|
|
195
|
+
# Use configured region or default to us-east-1
|
|
196
|
+
from .config import get_bedrock_config
|
|
197
|
+
bedrock_cfg = get_bedrock_config()
|
|
198
|
+
env_prefix += f" AWS_REGION={bedrock_cfg['region']}"
|
|
199
|
+
|
|
191
200
|
mock_scenario = os.environ.get("MOCK_SCENARIO")
|
|
192
201
|
if mock_scenario:
|
|
193
202
|
cmd_str = f"MOCK_SCENARIO={mock_scenario} {env_prefix} python {shlex.join(claude_cmd)}"
|
|
@@ -221,6 +230,7 @@ class ClaudeLauncher:
|
|
|
221
230
|
budget_usd: Optional[float] = None,
|
|
222
231
|
claude_agent: Optional[str] = None,
|
|
223
232
|
model: Optional[str] = None,
|
|
233
|
+
provider: str = "web",
|
|
224
234
|
) -> Optional[Session]:
|
|
225
235
|
"""
|
|
226
236
|
Launch an interactive Claude Code session in a tmux window.
|
|
@@ -236,6 +246,7 @@ class ClaudeLauncher:
|
|
|
236
246
|
If not set, auto-detects from OVERCODE_SESSION_NAME env var.
|
|
237
247
|
allowed_tools: Comma-separated tool list for --allowedTools
|
|
238
248
|
extra_claude_args: Extra Claude CLI flags (each a space-separated string)
|
|
249
|
+
provider: API provider — "web" (Claude.ai OAuth) or "bedrock" (AWS Bedrock)
|
|
239
250
|
|
|
240
251
|
Returns:
|
|
241
252
|
Session object if successful, None otherwise
|
|
@@ -329,7 +340,7 @@ class ClaudeLauncher:
|
|
|
329
340
|
standing_instructions=default_instructions,
|
|
330
341
|
permissiveness_mode=perm_mode, allowed_tools=allowed_tools,
|
|
331
342
|
extra_claude_args=extra_claude_args, agent_teams=agent_teams,
|
|
332
|
-
claude_agent=claude_agent, model=model,
|
|
343
|
+
claude_agent=claude_agent, model=model, provider=provider,
|
|
333
344
|
)
|
|
334
345
|
|
|
335
346
|
session = self._prepare_and_launch(
|
|
@@ -377,16 +388,42 @@ class ClaudeLauncher:
|
|
|
377
388
|
) -> bool:
|
|
378
389
|
"""Poll pane content until Claude's input prompt appears.
|
|
379
390
|
|
|
391
|
+
Automatically handles startup dialogs:
|
|
392
|
+
1. Workspace trust dialog -- presses Enter to accept default
|
|
393
|
+
2. Bypass permissions warning -- selects "Yes, I accept" (Down + Enter)
|
|
394
|
+
|
|
380
395
|
Returns True if prompt detected, False on timeout.
|
|
381
396
|
"""
|
|
382
397
|
from .status_patterns import strip_ansi
|
|
398
|
+
from .tmux_utils import _build_tmux_cmd
|
|
399
|
+
|
|
400
|
+
target = tmux_window_target(self.tmux.session_name, window_name)
|
|
401
|
+
tmux_cmd = _build_tmux_cmd()
|
|
402
|
+
trust_accepted = False
|
|
403
|
+
perms_accepted = False
|
|
383
404
|
|
|
384
405
|
deadline = time.time() + timeout
|
|
385
406
|
while time.time() < deadline:
|
|
386
407
|
content = get_tmux_pane_content(
|
|
387
|
-
self.tmux.session_name, window_name, lines=
|
|
408
|
+
self.tmux.session_name, window_name, lines=20
|
|
388
409
|
)
|
|
389
410
|
if content:
|
|
411
|
+
# Trust prompt: default is accept, just Enter
|
|
412
|
+
if not trust_accepted and "I trust this folder" in content:
|
|
413
|
+
subprocess.run(tmux_cmd + ['send-keys', '-t', target, '', 'Enter'], timeout=5)
|
|
414
|
+
trust_accepted = True
|
|
415
|
+
time.sleep(1.5)
|
|
416
|
+
continue
|
|
417
|
+
|
|
418
|
+
# Permissions warning: navigate Down to "Yes, I accept", then Enter
|
|
419
|
+
if not perms_accepted and "Yes, I accept" in content:
|
|
420
|
+
subprocess.run(tmux_cmd + ['send-keys', '-t', target, 'Down'], timeout=5)
|
|
421
|
+
time.sleep(0.3)
|
|
422
|
+
subprocess.run(tmux_cmd + ['send-keys', '-t', target, '', 'Enter'], timeout=5)
|
|
423
|
+
perms_accepted = True
|
|
424
|
+
time.sleep(2.0)
|
|
425
|
+
continue
|
|
426
|
+
|
|
390
427
|
for line in content.split('\n'):
|
|
391
428
|
cleaned = strip_ansi(line).strip()
|
|
392
429
|
if cleaned in self.PROMPT_READY_CHARS:
|
|
@@ -501,6 +538,7 @@ class ClaudeLauncher:
|
|
|
501
538
|
extra_claude_args=source_session.extra_claude_args,
|
|
502
539
|
resume_session_id=source_session.active_claude_session_id,
|
|
503
540
|
fork=True,
|
|
541
|
+
model=source_session.model,
|
|
504
542
|
)
|
|
505
543
|
|
|
506
544
|
perm_mode = source_session.permissiveness_mode or "normal"
|
|
@@ -512,6 +550,7 @@ class ClaudeLauncher:
|
|
|
512
550
|
permissiveness_mode=perm_mode, allowed_tools=source_session.allowed_tools,
|
|
513
551
|
extra_claude_args=source_session.extra_claude_args,
|
|
514
552
|
agent_teams=source_session.agent_teams, claude_agent=source_session.claude_agent,
|
|
553
|
+
model=source_session.model, provider=source_session.provider,
|
|
515
554
|
)
|
|
516
555
|
|
|
517
556
|
session = self._prepare_and_launch(
|