overcode 0.5.4__tar.gz → 0.5.6__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.5.4/src/overcode.egg-info → overcode-0.5.6}/PKG-INFO +2 -2
- {overcode-0.5.4 → overcode-0.5.6}/README.md +1 -1
- {overcode-0.5.4 → overcode-0.5.6}/pyproject.toml +1 -1
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/opencode.py +4 -1
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/_shared.py +18 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/agent.py +90 -10
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/budget.py +6 -6
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/focal.py +2 -2
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/jobs.py +2 -2
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/monitoring.py +6 -6
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/split.py +63 -30
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/tags.py +4 -4
- overcode-0.5.6/src/overcode/command_palette.py +538 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/config.py +65 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/data_export.py +5 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/exceptions.py +9 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/follow_mode.py +63 -16
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/history_reader.py +274 -112
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/hook_handler.py +100 -10
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/hook_status_detector.py +74 -7
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/implementations.py +37 -1
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/launcher.py +254 -27
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/mocks.py +45 -2
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/monitor_daemon.py +506 -107
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/monitor_daemon_core.py +16 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/monitor_daemon_state.py +92 -15
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/opencode_plugin/overcode-telemetry-v2/overcode-telemetry-core.mjs +62 -15
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/opencode_plugin/overcode-telemetry.js +161 -21
- overcode-0.5.6/src/overcode/pane_capture_gate.py +140 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/presence_logger.py +85 -14
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/protocols.py +26 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/session_manager.py +696 -136
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/settings.py +63 -2
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/sister_controller.py +0 -8
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/standing_instructions.py +0 -21
- overcode-0.5.6/src/overcode/stat_gate.py +123 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/stats_reader.py +44 -14
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/status_detector.py +14 -1
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/status_detector_factory.py +14 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/status_history.py +316 -48
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/summarizer_component.py +23 -4
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/summary_columns.py +222 -5
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/supervisor_daemon.py +4 -2
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tmux_manager.py +19 -7
- overcode-0.5.6/src/overcode/tmux_utils.py +573 -0
- overcode-0.5.6/src/overcode/transcript_index.py +557 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui.py +891 -308
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui.tcss +25 -10
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_actions/session.py +32 -69
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_actions/view.py +31 -7
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_logic.py +245 -62
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/__init__.py +7 -2
- overcode-0.5.6/src/overcode/tui_widgets/column_header.py +99 -0
- overcode-0.5.6/src/overcode/tui_widgets/command_palette.py +609 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/daemon_status_bar.py +23 -6
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/help_overlay.py +6 -4
- overcode-0.5.6/src/overcode/tui_widgets/jump_modal.py +40 -0
- overcode-0.5.6/src/overcode/tui_widgets/rename_agent_modal.py +188 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/status_timeline.py +7 -35
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/summary_config_modal.py +23 -1
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/web_api.py +19 -7
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/web_control_api.py +3 -31
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/web_server.py +8 -1
- overcode-0.5.6/src/overcode/worker_guard.py +163 -0
- {overcode-0.5.4 → overcode-0.5.6/src/overcode.egg-info}/PKG-INFO +2 -2
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode.egg-info/SOURCES.txt +8 -0
- overcode-0.5.4/src/overcode/tmux_utils.py +0 -285
- overcode-0.5.4/src/overcode/tui_widgets/jump_modal.py +0 -163
- {overcode-0.5.4 → overcode-0.5.6}/LICENSE +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/MANIFEST.in +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/setup.cfg +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/__init__.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/agent_scanner.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/__init__.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/base.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/claude_code.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/codex.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/codex_stats.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/grok.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/grok_stats.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/hermes.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/hermes_stats.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/opencode2.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/opencode2_patterns.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/opencode2_plugin_install.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/opencode2_stats.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/backends/opencode_stats.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/bundled_skills.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/claude_config.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/claude_pid.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/__init__.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/__main__.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/config.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/daemon.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/doctor.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/hooks.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/models.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/parallelism.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/perms.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/sister.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/skills.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/cli/wrappers.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/daemon_claude_skill.md +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/daemon_logging.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/daemon_utils.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/data/model_metadata.json +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/dependency_check.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/doctor.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/duration.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/entrypoint.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/hermes_plugin/__init__.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/hermes_plugin/plugin.yaml +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/interfaces.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/job_launcher.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/job_manager.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/logging_config.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/model_metadata.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/notifier.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/opencode_plugin/overcode-telemetry-v2/index.js +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/opencode_plugin/overcode-telemetry-v2/tui.js +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/pid_utils.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/pricing.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/process_resources.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/sandbox_detect.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/sister_poller.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/ssh_provisioner.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/status_constants.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/status_patterns.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/summarizer_client.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/summary_groups.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/supervisor_daemon_core.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/supervisor_layout.sh +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/testing/__init__.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/testing/renderer.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/testing/tmux_driver.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/testing/tui_eye.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/testing/tui_eye_skill.md +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/time_context.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_actions/__init__.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_actions/daemon.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_actions/input.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_actions/navigation.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_helpers.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_render.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/agent_select_modal.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/command_bar.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/daemon_panel.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/fullscreen_preview.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/instruction_history_modal.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/job_summary.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/modal_base.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/new_agent_defaults_modal.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/new_agent_modal.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/passthru_config_modal.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/preview_pane.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/session_summary.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/sister_selection_modal.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/tmux_config_modal.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/tui_widgets/tui_log_panel.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/usage_monitor.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/web/__init__.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/web/templates/analytics.html +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/web/templates/dashboard.html +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/web_chartjs.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/web_server_runner.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/web_templates.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode/wrapper.py +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode.egg-info/dependency_links.txt +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode.egg-info/entry_points.txt +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode.egg-info/requires.txt +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/src/overcode.egg-info/top_level.txt +0 -0
- {overcode-0.5.4 → overcode-0.5.6}/tests/test_e2e_multi_agent_jokes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: overcode
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.6
|
|
4
4
|
Summary: A supervisor for managing multiple Claude Code instances in tmux
|
|
5
5
|
Author: Mike Bond
|
|
6
6
|
Project-URL: Homepage, https://github.com/mkb23/overcode
|
|
@@ -176,7 +176,7 @@ See the [TUI Guide](docs/tui-guide.md) for all keyboard shortcuts.
|
|
|
176
176
|
- [TUI Guide](docs/tui-guide.md) - Keyboard shortcuts and display modes
|
|
177
177
|
- [Configuration](docs/configuration.md) - Config file and environment variables
|
|
178
178
|
- [Wrappers](docs/wrappers.md) - Run agents in containers and custom environments
|
|
179
|
-
- [Advanced Features](docs/advanced-features.md) - Sleep mode,
|
|
179
|
+
- [Advanced Features](docs/advanced-features.md) - Sleep mode, remote monitoring
|
|
180
180
|
|
|
181
181
|
## Testing
|
|
182
182
|
|
|
@@ -134,7 +134,7 @@ See the [TUI Guide](docs/tui-guide.md) for all keyboard shortcuts.
|
|
|
134
134
|
- [TUI Guide](docs/tui-guide.md) - Keyboard shortcuts and display modes
|
|
135
135
|
- [Configuration](docs/configuration.md) - Config file and environment variables
|
|
136
136
|
- [Wrappers](docs/wrappers.md) - Run agents in containers and custom environments
|
|
137
|
-
- [Advanced Features](docs/advanced-features.md) - Sleep mode,
|
|
137
|
+
- [Advanced Features](docs/advanced-features.md) - Sleep mode, remote monitoring
|
|
138
138
|
|
|
139
139
|
## Testing
|
|
140
140
|
|
|
@@ -14,7 +14,10 @@ remains the fallback for a launch where the plugin could not be installed.
|
|
|
14
14
|
Everything below the flag table was captured from a real opencode
|
|
15
15
|
v1.18.19 session; the pane corpus lives in
|
|
16
16
|
``tests/fixtures_opencode_panes/`` and is replayed by
|
|
17
|
-
``tests/unit/test_status_detector_opencode.py``.
|
|
17
|
+
``tests/unit/test_status_detector_opencode.py``. Re-verified unchanged
|
|
18
|
+
against v1.18.29 (Sep 2026, #474): a second pane corpus under
|
|
19
|
+
``tests/fixtures_opencode_panes/v1.18.29/`` and the plugin's event-stream
|
|
20
|
+
corpus in ``tests/fixtures_opencode_events/``.
|
|
18
21
|
"""
|
|
19
22
|
|
|
20
23
|
import json
|
|
@@ -109,6 +109,24 @@ app.add_typer(models_app, name="models")
|
|
|
109
109
|
|
|
110
110
|
# Console for rich output
|
|
111
111
|
console = Console()
|
|
112
|
+
# Notices that must not mix into output a script may parse
|
|
113
|
+
err_console = Console(stderr=True)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def find_agent(manager, name: str):
|
|
117
|
+
"""Look up the agent ``name`` addresses, following renames (#478).
|
|
118
|
+
|
|
119
|
+
``manager`` is a SessionManager. When ``name`` is an agent's old name,
|
|
120
|
+
the agent is returned and a one-line notice goes to stderr, so a person
|
|
121
|
+
or script still using the old name learns the new one.
|
|
122
|
+
"""
|
|
123
|
+
from ..session_manager import rename_notice
|
|
124
|
+
|
|
125
|
+
session = manager.resolve_session_name(name)
|
|
126
|
+
notice = rename_notice(name, session)
|
|
127
|
+
if notice:
|
|
128
|
+
err_console.print(f"[yellow]{notice}[/yellow]")
|
|
129
|
+
return session
|
|
112
130
|
|
|
113
131
|
# Global session option (hidden advanced usage)
|
|
114
132
|
SessionOption = Annotated[
|
|
@@ -9,7 +9,7 @@ import typer
|
|
|
9
9
|
from rich import print as rprint
|
|
10
10
|
|
|
11
11
|
from ..launcher import AgentLauncher
|
|
12
|
-
from ._shared import app, SessionOption, _parse_duration
|
|
12
|
+
from ._shared import app, SessionOption, _parse_duration, find_agent
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def _parse_oversight_policy(on_stuck: Optional[str], oversight_timeout: Optional[str]) -> tuple[str, float]:
|
|
@@ -427,7 +427,7 @@ def fork(
|
|
|
427
427
|
from ..backends import BackendCapability, get_backend, supports
|
|
428
428
|
|
|
429
429
|
sm = SessionManager()
|
|
430
|
-
source_session = sm
|
|
430
|
+
source_session = find_agent(sm, source)
|
|
431
431
|
if not source_session:
|
|
432
432
|
rprint(f"[red]Error: Agent '{source}' not found[/red]")
|
|
433
433
|
raise typer.Exit(code=1)
|
|
@@ -546,7 +546,7 @@ def list_agents(
|
|
|
546
546
|
|
|
547
547
|
# Filter to specific agent + descendants if name given (#244)
|
|
548
548
|
if name:
|
|
549
|
-
root = launcher.sessions
|
|
549
|
+
root = find_agent(launcher.sessions, name)
|
|
550
550
|
if not root:
|
|
551
551
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
552
552
|
raise typer.Exit(code=1)
|
|
@@ -842,7 +842,7 @@ def restart(
|
|
|
842
842
|
from ..launcher import AgentLauncher
|
|
843
843
|
|
|
844
844
|
launcher = AgentLauncher(session)
|
|
845
|
-
sess = launcher.sessions
|
|
845
|
+
sess = find_agent(launcher.sessions, name)
|
|
846
846
|
if not sess:
|
|
847
847
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
848
848
|
raise typer.Exit(code=1)
|
|
@@ -879,6 +879,83 @@ def follow(
|
|
|
879
879
|
raise typer.Exit(code=exit_code)
|
|
880
880
|
|
|
881
881
|
|
|
882
|
+
@app.command()
|
|
883
|
+
def rename(
|
|
884
|
+
name: Annotated[str, typer.Argument(help="Current name of agent")],
|
|
885
|
+
new_name: Annotated[str, typer.Argument(help="New name for the agent")],
|
|
886
|
+
force: Annotated[
|
|
887
|
+
bool,
|
|
888
|
+
typer.Option(
|
|
889
|
+
"--force",
|
|
890
|
+
help="Rename even if the agent is mid-turn (its turn is cancelled)",
|
|
891
|
+
),
|
|
892
|
+
] = False,
|
|
893
|
+
session: SessionOption = "agents",
|
|
894
|
+
):
|
|
895
|
+
"""Rename an agent, keeping its conversation and history.
|
|
896
|
+
|
|
897
|
+
A live agent is briefly stopped and resumed under the new name, in the
|
|
898
|
+
same window with the same conversation; it is told its new name with
|
|
899
|
+
its next prompt. The old name keeps working as an alias — `send`,
|
|
900
|
+
`follow`, `kill` and the rest reach the agent and say it was renamed —
|
|
901
|
+
so a parent or script still using it is not broken.
|
|
902
|
+
|
|
903
|
+
An agent that is busy (working, or showing a permission dialog) is not
|
|
904
|
+
renamed, because stopping it would cancel its turn; wait until it is
|
|
905
|
+
idle, or pass --force. A terminated agent just has its record renamed.
|
|
906
|
+
|
|
907
|
+
Examples:
|
|
908
|
+
overcode rename bugfix-1 auth-refactor
|
|
909
|
+
"""
|
|
910
|
+
from ..exceptions import AgentBusyError, InvalidSessionNameError
|
|
911
|
+
from ..launcher import AgentLauncher
|
|
912
|
+
|
|
913
|
+
launcher = AgentLauncher(session)
|
|
914
|
+
sess = find_agent(launcher.sessions, name)
|
|
915
|
+
if not sess:
|
|
916
|
+
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
917
|
+
raise typer.Exit(code=1)
|
|
918
|
+
name = sess.name
|
|
919
|
+
|
|
920
|
+
try:
|
|
921
|
+
renamed = launcher.rename(sess, new_name, force=force)
|
|
922
|
+
except InvalidSessionNameError:
|
|
923
|
+
rprint(
|
|
924
|
+
f"[red]Error: '{new_name}' is not a valid agent name "
|
|
925
|
+
f"(letters, digits, - and _ only, max 64 chars)[/red]"
|
|
926
|
+
)
|
|
927
|
+
raise typer.Exit(code=1)
|
|
928
|
+
except AgentBusyError as exc:
|
|
929
|
+
state = (
|
|
930
|
+
"could not be checked" if exc.status == "unknown" else f"is busy ({exc.status})"
|
|
931
|
+
)
|
|
932
|
+
rprint(
|
|
933
|
+
f"[yellow]Agent '{name}' {state}.[/yellow] Renaming restarts it, "
|
|
934
|
+
"which would cancel a turn in progress. Try again when it is idle, "
|
|
935
|
+
"or use --force."
|
|
936
|
+
)
|
|
937
|
+
raise typer.Exit(code=1)
|
|
938
|
+
except ValueError as exc:
|
|
939
|
+
rprint(f"[red]Error: {exc}[/red]")
|
|
940
|
+
raise typer.Exit(code=1)
|
|
941
|
+
|
|
942
|
+
if not renamed:
|
|
943
|
+
current = launcher.sessions.get_session(sess.id)
|
|
944
|
+
if current is not None and current.name == new_name:
|
|
945
|
+
rprint(
|
|
946
|
+
f"[yellow]Renamed '{name}' → '{new_name}', but the relaunch could "
|
|
947
|
+
f"not be sent.[/yellow] Run: overcode restart {new_name}"
|
|
948
|
+
)
|
|
949
|
+
else:
|
|
950
|
+
rprint(
|
|
951
|
+
f"[red]Error: tmux refused to rename the window; '{name}' was "
|
|
952
|
+
"restarted under its old name.[/red]"
|
|
953
|
+
)
|
|
954
|
+
raise typer.Exit(code=1)
|
|
955
|
+
|
|
956
|
+
rprint(f"[green]Renamed agent: {name} → {new_name} (conversation preserved)[/green]")
|
|
957
|
+
|
|
958
|
+
|
|
882
959
|
@app.command()
|
|
883
960
|
def report(
|
|
884
961
|
status: Annotated[
|
|
@@ -930,7 +1007,7 @@ def report(
|
|
|
930
1007
|
# Also update session fields for persistence
|
|
931
1008
|
from ..session_manager import SessionManager
|
|
932
1009
|
sm = SessionManager()
|
|
933
|
-
session = sm
|
|
1010
|
+
session = find_agent(sm, agent_name)
|
|
934
1011
|
if session:
|
|
935
1012
|
sm.update_session(
|
|
936
1013
|
session.id,
|
|
@@ -996,7 +1073,7 @@ def set_value(
|
|
|
996
1073
|
from ..session_manager import SessionManager
|
|
997
1074
|
|
|
998
1075
|
manager = SessionManager()
|
|
999
|
-
agent = manager
|
|
1076
|
+
agent = find_agent(manager, name)
|
|
1000
1077
|
if not agent:
|
|
1001
1078
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
1002
1079
|
raise typer.Exit(code=1)
|
|
@@ -1018,7 +1095,7 @@ def set_budget(
|
|
|
1018
1095
|
from ..session_manager import SessionManager
|
|
1019
1096
|
|
|
1020
1097
|
manager = SessionManager()
|
|
1021
|
-
agent = manager
|
|
1098
|
+
agent = find_agent(manager, name)
|
|
1022
1099
|
if not agent:
|
|
1023
1100
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
1024
1101
|
raise typer.Exit(code=1)
|
|
@@ -1055,7 +1132,7 @@ def annotate(
|
|
|
1055
1132
|
from ..session_manager import SessionManager
|
|
1056
1133
|
|
|
1057
1134
|
manager = SessionManager()
|
|
1058
|
-
agent = manager
|
|
1135
|
+
agent = find_agent(manager, name)
|
|
1059
1136
|
if not agent:
|
|
1060
1137
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
1061
1138
|
raise typer.Exit(code=1)
|
|
@@ -1099,7 +1176,9 @@ def send(
|
|
|
1099
1176
|
sm = SessionManager()
|
|
1100
1177
|
|
|
1101
1178
|
# Auto-wake sleeping agent (#168)
|
|
1102
|
-
agent_session = sm
|
|
1179
|
+
agent_session = find_agent(sm, name)
|
|
1180
|
+
if agent_session:
|
|
1181
|
+
name = agent_session.name # an old name resolved; say the current one
|
|
1103
1182
|
if agent_session and agent_session.is_asleep:
|
|
1104
1183
|
sm.update_session(agent_session.id, is_asleep=False)
|
|
1105
1184
|
rprint(f"[dim]Woke agent '{name}' to send command[/dim]")
|
|
@@ -1149,10 +1228,11 @@ def show(
|
|
|
1149
1228
|
launcher = AgentLauncher(session)
|
|
1150
1229
|
|
|
1151
1230
|
# Get the Session object
|
|
1152
|
-
sess = launcher.sessions
|
|
1231
|
+
sess = find_agent(launcher.sessions, name)
|
|
1153
1232
|
if sess is None:
|
|
1154
1233
|
rprint(f"[red]✗[/red] Agent '[bold]{name}[/bold]' not found")
|
|
1155
1234
|
raise typer.Exit(1)
|
|
1235
|
+
name = sess.name # an old name resolved; show under the current one
|
|
1156
1236
|
|
|
1157
1237
|
# Read daemon state for status/activity (single source of truth)
|
|
1158
1238
|
daemon_state = get_monitor_daemon_state(session)
|
|
@@ -7,7 +7,7 @@ from typing import Annotated, Optional
|
|
|
7
7
|
import typer
|
|
8
8
|
from rich import print as rprint
|
|
9
9
|
|
|
10
|
-
from ._shared import budget_app, SessionOption
|
|
10
|
+
from ._shared import budget_app, SessionOption, find_agent
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@budget_app.command("set")
|
|
@@ -28,7 +28,7 @@ def budget_set(
|
|
|
28
28
|
from ..session_manager import SessionManager
|
|
29
29
|
|
|
30
30
|
manager = SessionManager()
|
|
31
|
-
agent = manager
|
|
31
|
+
agent = find_agent(manager, name)
|
|
32
32
|
if not agent:
|
|
33
33
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
34
34
|
raise typer.Exit(code=1)
|
|
@@ -73,12 +73,12 @@ def budget_transfer(
|
|
|
73
73
|
|
|
74
74
|
manager = SessionManager()
|
|
75
75
|
|
|
76
|
-
source_agent = manager
|
|
76
|
+
source_agent = find_agent(manager, source)
|
|
77
77
|
if not source_agent:
|
|
78
78
|
rprint(f"[red]Error: Source agent '{source}' not found[/red]")
|
|
79
79
|
raise typer.Exit(code=1)
|
|
80
80
|
|
|
81
|
-
target_agent = manager
|
|
81
|
+
target_agent = find_agent(manager, target)
|
|
82
82
|
if not target_agent:
|
|
83
83
|
rprint(f"[red]Error: Target agent '{target}' not found[/red]")
|
|
84
84
|
raise typer.Exit(code=1)
|
|
@@ -118,7 +118,7 @@ def budget_reclaim(
|
|
|
118
118
|
from ..session_manager import SessionManager
|
|
119
119
|
|
|
120
120
|
manager = SessionManager()
|
|
121
|
-
agent = manager
|
|
121
|
+
agent = find_agent(manager, name)
|
|
122
122
|
if not agent:
|
|
123
123
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
124
124
|
raise typer.Exit(code=1)
|
|
@@ -162,7 +162,7 @@ def budget_show(
|
|
|
162
162
|
|
|
163
163
|
if name:
|
|
164
164
|
agents = []
|
|
165
|
-
agent = manager
|
|
165
|
+
agent = find_agent(manager, name)
|
|
166
166
|
if not agent:
|
|
167
167
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
168
168
|
raise typer.Exit(code=1)
|
|
@@ -7,7 +7,7 @@ from typing import Annotated, Optional
|
|
|
7
7
|
import typer
|
|
8
8
|
from rich import print as rprint
|
|
9
9
|
|
|
10
|
-
from ._shared import app, SessionOption
|
|
10
|
+
from ._shared import app, SessionOption, find_agent
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@app.command("focal-repo")
|
|
@@ -40,7 +40,7 @@ def focal_repo(
|
|
|
40
40
|
from ..session_manager import SessionManager
|
|
41
41
|
|
|
42
42
|
manager = SessionManager()
|
|
43
|
-
agent = manager
|
|
43
|
+
agent = find_agent(manager, name)
|
|
44
44
|
if not agent:
|
|
45
45
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
46
46
|
raise typer.Exit(code=1)
|
|
@@ -7,7 +7,7 @@ from typing import Annotated, Optional
|
|
|
7
7
|
import typer
|
|
8
8
|
from rich import print as rprint
|
|
9
9
|
|
|
10
|
-
from ._shared import app, jobs_app
|
|
10
|
+
from ._shared import app, jobs_app, find_agent
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@app.command()
|
|
@@ -32,7 +32,7 @@ def bash(
|
|
|
32
32
|
agent = os.environ.get("OVERCODE_SESSION_NAME")
|
|
33
33
|
if agent:
|
|
34
34
|
sm = SessionManager()
|
|
35
|
-
sess = sm
|
|
35
|
+
sess = find_agent(sm, agent)
|
|
36
36
|
if sess:
|
|
37
37
|
agent_session_id = sess.id
|
|
38
38
|
agent_name = sess.name
|
|
@@ -9,7 +9,7 @@ from typing import Annotated, Optional, List
|
|
|
9
9
|
import typer
|
|
10
10
|
from rich import print as rprint
|
|
11
11
|
|
|
12
|
-
from ._shared import app, SessionOption
|
|
12
|
+
from ._shared import app, SessionOption, find_agent
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@app.command("hook-handler", hidden=True)
|
|
@@ -67,7 +67,7 @@ def instruct(
|
|
|
67
67
|
raise typer.Exit(1)
|
|
68
68
|
|
|
69
69
|
sessions = SessionManager()
|
|
70
|
-
sess = sessions
|
|
70
|
+
sess = find_agent(sessions, name)
|
|
71
71
|
|
|
72
72
|
if sess is None:
|
|
73
73
|
rprint(f"[red]✗[/red] Agent '[bold]{name}[/bold]' not found")
|
|
@@ -150,7 +150,7 @@ def heartbeat(
|
|
|
150
150
|
from ..tui_helpers import format_duration
|
|
151
151
|
|
|
152
152
|
manager = SessionManager()
|
|
153
|
-
agent = manager
|
|
153
|
+
agent = find_agent(manager, name)
|
|
154
154
|
if not agent:
|
|
155
155
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
156
156
|
raise typer.Exit(code=1)
|
|
@@ -463,9 +463,9 @@ def history(
|
|
|
463
463
|
sessions = SessionManager()
|
|
464
464
|
|
|
465
465
|
if name:
|
|
466
|
-
# Show specific archived session
|
|
467
|
-
|
|
468
|
-
session = next((s for s in
|
|
466
|
+
# Show specific archived session: stream the archive (one record
|
|
467
|
+
# at a time, file order) instead of materialising every entry.
|
|
468
|
+
session = next((s for s in sessions.iter_archived_sessions() if s.name == name), None)
|
|
469
469
|
if not session:
|
|
470
470
|
rprint(f"[red]✗[/red] No archived session named '[bold]{name}[/bold]'")
|
|
471
471
|
raise typer.Exit(1)
|
|
@@ -72,9 +72,40 @@ def _acquire_setup_lock() -> bool:
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def _release_setup_lock() -> None:
|
|
75
|
-
"""Release the setup lock.
|
|
75
|
+
"""Release the setup lock, but only if this process still owns it.
|
|
76
|
+
|
|
77
|
+
Release is ownership-aware because it can legitimately run twice: once in
|
|
78
|
+
`_exec_tmux_attach` just before exec, and again from the outer `finally:`
|
|
79
|
+
in `_tmux_layout` if that exec raises. Between the two, another
|
|
80
|
+
`overcode tmux` may have acquired the lock; unlinking unconditionally
|
|
81
|
+
would delete *its* lock and let a third invocation run setup concurrently.
|
|
82
|
+
"""
|
|
76
83
|
lock_path = Path.home() / ".overcode" / "tmux-setup.lock"
|
|
77
|
-
|
|
84
|
+
try:
|
|
85
|
+
holder = lock_path.read_text().strip()
|
|
86
|
+
except FileNotFoundError:
|
|
87
|
+
return # already released, or never acquired
|
|
88
|
+
if holder == str(os.getpid()):
|
|
89
|
+
lock_path.unlink(missing_ok=True)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _exec_tmux_attach(oc_session: str) -> None:
|
|
93
|
+
"""Release the setup lock, then replace this process with `tmux attach`.
|
|
94
|
+
|
|
95
|
+
`os.execvp` never returns, so any `finally:` that would normally release
|
|
96
|
+
the lock is skipped. Worse, the PID recorded in the lock file becomes the
|
|
97
|
+
long-lived tmux client, so the stale-lock check treats it as a genuine
|
|
98
|
+
holder for as long as the client stays attached and every later
|
|
99
|
+
`overcode tmux` is refused. Always go through this helper to exec.
|
|
100
|
+
|
|
101
|
+
If exec itself fails, the caller's `finally:` releases again; that is
|
|
102
|
+
safe because `_release_setup_lock` only removes a lock this PID owns.
|
|
103
|
+
"""
|
|
104
|
+
from ..tmux_utils import _build_tmux_cmd
|
|
105
|
+
|
|
106
|
+
_release_setup_lock()
|
|
107
|
+
_cmd = [*_build_tmux_cmd(), "attach-session", "-t", oc_session]
|
|
108
|
+
os.execvp("tmux", _cmd)
|
|
78
109
|
|
|
79
110
|
|
|
80
111
|
def _find_overcode_cmd() -> str:
|
|
@@ -785,22 +816,22 @@ def _tmux_layout_locked(session: str, ratio: int, rprint, *, restart: bool = Fal
|
|
|
785
816
|
if existing:
|
|
786
817
|
if _is_split_window_healthy(existing):
|
|
787
818
|
if in_tmux:
|
|
788
|
-
#
|
|
789
|
-
#
|
|
790
|
-
#
|
|
791
|
-
# accidentally move the bottom pane's nested client from
|
|
792
|
-
# oc-view-agents to overcode, creating a recursive display
|
|
819
|
+
# Move the caller's own client to the split window — but
|
|
820
|
+
# never the bottom pane's nested client. Switching that one
|
|
821
|
+
# from oc-view-agents to overcode creates a recursive display
|
|
793
822
|
# that collapses the window.
|
|
794
823
|
#
|
|
795
824
|
# Detect nested clients by comparing client TTYs against
|
|
796
825
|
# pane TTYs in the split window (#387). A nested tmux
|
|
797
826
|
# client's TTY matches one of the pane TTYs.
|
|
798
|
-
pane_ttys =
|
|
799
|
-
|
|
827
|
+
pane_ttys = {
|
|
828
|
+
tty.strip()
|
|
829
|
+
for tty in _tmux_output(
|
|
800
830
|
"list-panes", "-t", f"{oc_session}:{SPLIT_WINDOW_NAME}",
|
|
801
831
|
"-F", "#{pane_tty}",
|
|
802
832
|
).splitlines()
|
|
803
|
-
|
|
833
|
+
if tty.strip()
|
|
834
|
+
}
|
|
804
835
|
oc_clients = _tmux_output(
|
|
805
836
|
"list-clients", "-t", oc_session,
|
|
806
837
|
"-F", "#{client_tty}",
|
|
@@ -810,29 +841,33 @@ def _tmux_layout_locked(session: str, ratio: int, rprint, *, restart: bool = Fal
|
|
|
810
841
|
for tty in oc_clients.splitlines()
|
|
811
842
|
if tty.strip()
|
|
812
843
|
)
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
844
|
+
client_tty = _tmux_output(
|
|
845
|
+
"display-message", "-p", "#{client_tty}",
|
|
846
|
+
).strip()
|
|
847
|
+
if client_tty and client_tty not in pane_ttys:
|
|
848
|
+
# The caller may already be attached to the overcode
|
|
849
|
+
# session but sitting on another window (e.g. an agent
|
|
850
|
+
# window opened there), so always target the split
|
|
851
|
+
# window explicitly rather than just the session.
|
|
852
|
+
_tmux("switch-client", "-c", client_tty,
|
|
853
|
+
"-t", f"{oc_session}:{SPLIT_WINDOW_NAME}")
|
|
854
|
+
elif client_tty:
|
|
855
|
+
# Caller is inside the nested bottom pane — it's already
|
|
856
|
+
# looking at the split; leave the client alone.
|
|
857
|
+
pass
|
|
858
|
+
elif not has_real_client:
|
|
859
|
+
# Can't determine client — tell user how to get there
|
|
860
|
+
rprint(f"[green]Split layout is running.[/green] Switch to it with:")
|
|
861
|
+
rprint(f" tmux switch-client -t {oc_session}")
|
|
862
|
+
return
|
|
826
863
|
else:
|
|
827
|
-
# Respawn before attach —
|
|
864
|
+
# Respawn before attach — exec replaces this process
|
|
828
865
|
_tmux("respawn-pane", "-k",
|
|
829
866
|
"-t", f"{oc_session}:{SPLIT_WINDOW_NAME}.{get_pane_base_index()}",
|
|
830
867
|
monitor_cmd)
|
|
831
868
|
rprint(f"[green]Attaching to existing {SPLIT_WINDOW_NAME} window (monitor restarted)...[/green]")
|
|
832
869
|
time.sleep(0.2)
|
|
833
|
-
|
|
834
|
-
_cmd = [*_build_tmux_cmd(), "attach-session", "-t", oc_session]
|
|
835
|
-
os.execvp("tmux", _cmd)
|
|
870
|
+
_exec_tmux_attach(oc_session)
|
|
836
871
|
# Always restart the monitor so code changes take effect
|
|
837
872
|
_tmux("respawn-pane", "-k",
|
|
838
873
|
"-t", f"{oc_session}:{SPLIT_WINDOW_NAME}.{get_pane_base_index()}",
|
|
@@ -1004,6 +1039,4 @@ def _tmux_layout_locked(session: str, ratio: int, rprint, *, restart: bool = Fal
|
|
|
1004
1039
|
# Attach to the session (replaces this process)
|
|
1005
1040
|
rprint(f"[green]Attaching to split layout...[/green]")
|
|
1006
1041
|
time.sleep(0.2)
|
|
1007
|
-
|
|
1008
|
-
_cmd = [*_build_tmux_cmd(), "attach-session", "-t", oc_session]
|
|
1009
|
-
os.execvp("tmux", _cmd)
|
|
1042
|
+
_exec_tmux_attach(oc_session)
|
|
@@ -11,7 +11,7 @@ import typer
|
|
|
11
11
|
from rich import print as rprint
|
|
12
12
|
from rich.table import Table
|
|
13
13
|
|
|
14
|
-
from ._shared import app, SessionOption
|
|
14
|
+
from ._shared import app, SessionOption, find_agent
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@app.command("tag")
|
|
@@ -32,7 +32,7 @@ def tag_add(
|
|
|
32
32
|
from ..session_manager import SessionManager
|
|
33
33
|
|
|
34
34
|
manager = SessionManager()
|
|
35
|
-
agent = manager
|
|
35
|
+
agent = find_agent(manager, name)
|
|
36
36
|
if not agent:
|
|
37
37
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
38
38
|
raise typer.Exit(code=1)
|
|
@@ -66,7 +66,7 @@ def tag_remove(
|
|
|
66
66
|
from ..session_manager import SessionManager
|
|
67
67
|
|
|
68
68
|
manager = SessionManager()
|
|
69
|
-
agent = manager
|
|
69
|
+
agent = find_agent(manager, name)
|
|
70
70
|
if not agent:
|
|
71
71
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
72
72
|
raise typer.Exit(code=1)
|
|
@@ -95,7 +95,7 @@ def tags_list(
|
|
|
95
95
|
|
|
96
96
|
manager = SessionManager()
|
|
97
97
|
if name:
|
|
98
|
-
agent = manager
|
|
98
|
+
agent = find_agent(manager, name)
|
|
99
99
|
if not agent:
|
|
100
100
|
rprint(f"[red]Error: Agent '{name}' not found[/red]")
|
|
101
101
|
raise typer.Exit(code=1)
|