vibe-remote 2.1.6__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.
- config/__init__.py +37 -0
- config/paths.py +56 -0
- config/v2_compat.py +74 -0
- config/v2_config.py +206 -0
- config/v2_sessions.py +73 -0
- config/v2_settings.py +115 -0
- core/__init__.py +0 -0
- core/controller.py +736 -0
- core/handlers/__init__.py +13 -0
- core/handlers/command_handlers.py +342 -0
- core/handlers/message_handler.py +365 -0
- core/handlers/session_handler.py +233 -0
- core/handlers/settings_handler.py +362 -0
- modules/__init__.py +0 -0
- modules/agent_router.py +58 -0
- modules/agents/__init__.py +38 -0
- modules/agents/base.py +91 -0
- modules/agents/claude_agent.py +344 -0
- modules/agents/codex_agent.py +368 -0
- modules/agents/opencode_agent.py +2155 -0
- modules/agents/service.py +41 -0
- modules/agents/subagent_router.py +136 -0
- modules/claude_client.py +154 -0
- modules/im/__init__.py +63 -0
- modules/im/base.py +323 -0
- modules/im/factory.py +60 -0
- modules/im/formatters/__init__.py +4 -0
- modules/im/formatters/base_formatter.py +639 -0
- modules/im/formatters/slack_formatter.py +127 -0
- modules/im/slack.py +2091 -0
- modules/session_manager.py +138 -0
- modules/settings_manager.py +587 -0
- vibe/__init__.py +6 -0
- vibe/__main__.py +12 -0
- vibe/_version.py +34 -0
- vibe/api.py +412 -0
- vibe/cli.py +637 -0
- vibe/runtime.py +213 -0
- vibe/service_main.py +101 -0
- vibe/templates/slack_manifest.json +65 -0
- vibe/ui/dist/assets/index-8g3mNwMK.js +35 -0
- vibe/ui/dist/assets/index-M55aMB5R.css +1 -0
- vibe/ui/dist/assets/logo-BzryTZ7u.png +0 -0
- vibe/ui/dist/index.html +17 -0
- vibe/ui/dist/logo.png +0 -0
- vibe/ui/dist/vite.svg +1 -0
- vibe/ui_server.py +346 -0
- vibe_remote-2.1.6.dist-info/METADATA +295 -0
- vibe_remote-2.1.6.dist-info/RECORD +52 -0
- vibe_remote-2.1.6.dist-info/WHEEL +4 -0
- vibe_remote-2.1.6.dist-info/entry_points.txt +2 -0
- vibe_remote-2.1.6.dist-info/licenses/LICENSE +21 -0
vibe/runtime.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import signal
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from config import paths
|
|
10
|
+
from config.v2_config import (
|
|
11
|
+
AgentsConfig,
|
|
12
|
+
ClaudeConfig,
|
|
13
|
+
CodexConfig,
|
|
14
|
+
OpenCodeConfig,
|
|
15
|
+
RuntimeConfig,
|
|
16
|
+
SlackConfig,
|
|
17
|
+
V2Config,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_package_root() -> Path:
|
|
22
|
+
"""Get the root directory of the vibe package."""
|
|
23
|
+
return Path(__file__).resolve().parent
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_project_root() -> Path:
|
|
27
|
+
"""Get the project root directory (for development mode)."""
|
|
28
|
+
return Path(__file__).resolve().parents[1]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_ui_dist_path() -> Path:
|
|
32
|
+
"""Get the path to UI dist directory."""
|
|
33
|
+
# First check if we're in development mode (ui/dist exists at project root)
|
|
34
|
+
project_root = get_project_root()
|
|
35
|
+
dev_ui_path = project_root / "ui" / "dist"
|
|
36
|
+
if dev_ui_path.exists():
|
|
37
|
+
return dev_ui_path
|
|
38
|
+
|
|
39
|
+
# Then check if UI is bundled with the package
|
|
40
|
+
package_ui_path = get_package_root() / "ui" / "dist"
|
|
41
|
+
if package_ui_path.exists():
|
|
42
|
+
return package_ui_path
|
|
43
|
+
|
|
44
|
+
# Fallback to development path
|
|
45
|
+
return dev_ui_path
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_service_main_path() -> Path:
|
|
49
|
+
"""Get the path to the main service entry point."""
|
|
50
|
+
# First check if we're in development mode (main.py exists at project root)
|
|
51
|
+
project_root = get_project_root()
|
|
52
|
+
dev_main_path = project_root / "main.py"
|
|
53
|
+
if dev_main_path.exists():
|
|
54
|
+
return dev_main_path
|
|
55
|
+
|
|
56
|
+
# Then check if service_main.py is bundled with the package
|
|
57
|
+
package_main_path = get_package_root() / "service_main.py"
|
|
58
|
+
if package_main_path.exists():
|
|
59
|
+
return package_main_path
|
|
60
|
+
|
|
61
|
+
# Fallback to development path
|
|
62
|
+
return dev_main_path
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_working_dir() -> Path:
|
|
66
|
+
"""Get the working directory for subprocess execution."""
|
|
67
|
+
# In development mode, use project root
|
|
68
|
+
project_root = get_project_root()
|
|
69
|
+
if (project_root / "main.py").exists():
|
|
70
|
+
return project_root
|
|
71
|
+
|
|
72
|
+
# In installed mode, use package root
|
|
73
|
+
return get_package_root()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
ROOT_DIR = get_project_root() # For backward compatibility
|
|
77
|
+
MAIN_PATH = get_service_main_path()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def ensure_dirs():
|
|
81
|
+
paths.ensure_data_dirs()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def default_config():
|
|
85
|
+
return V2Config(
|
|
86
|
+
mode="self_host",
|
|
87
|
+
version="v2",
|
|
88
|
+
slack=SlackConfig(bot_token="", app_token=""),
|
|
89
|
+
runtime=RuntimeConfig(default_cwd=str(Path.cwd())),
|
|
90
|
+
agents=AgentsConfig(
|
|
91
|
+
default_backend="opencode",
|
|
92
|
+
opencode=OpenCodeConfig(enabled=True, cli_path="opencode"),
|
|
93
|
+
claude=ClaudeConfig(enabled=True, cli_path="claude"),
|
|
94
|
+
codex=CodexConfig(enabled=False, cli_path="codex"),
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def ensure_config():
|
|
100
|
+
config_path = paths.get_config_path()
|
|
101
|
+
if not config_path.exists():
|
|
102
|
+
default = default_config()
|
|
103
|
+
default.save(config_path)
|
|
104
|
+
return V2Config.load(config_path)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def write_json(path, payload):
|
|
108
|
+
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def read_json(path):
|
|
112
|
+
if not path.exists():
|
|
113
|
+
return None
|
|
114
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def pid_alive(pid):
|
|
118
|
+
try:
|
|
119
|
+
os.kill(pid, 0)
|
|
120
|
+
return True
|
|
121
|
+
except OSError:
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _log_path(name: str) -> Path:
|
|
126
|
+
return paths.get_runtime_dir() / name
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def spawn_background(args, pid_path, stdout_name: str, stderr_name: str):
|
|
130
|
+
stdout_path = _log_path(stdout_name)
|
|
131
|
+
stderr_path = _log_path(stderr_name)
|
|
132
|
+
stdout_path.parent.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
stdout = stdout_path.open("ab")
|
|
134
|
+
stderr = stderr_path.open("ab")
|
|
135
|
+
process = subprocess.Popen(
|
|
136
|
+
args,
|
|
137
|
+
stdout=stdout,
|
|
138
|
+
stderr=stderr,
|
|
139
|
+
start_new_session=True,
|
|
140
|
+
cwd=str(get_working_dir()),
|
|
141
|
+
close_fds=True,
|
|
142
|
+
)
|
|
143
|
+
stdout.close()
|
|
144
|
+
stderr.close()
|
|
145
|
+
pid_path.write_text(str(process.pid), encoding="utf-8")
|
|
146
|
+
return process.pid
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def stop_process(pid_path):
|
|
150
|
+
if not pid_path.exists():
|
|
151
|
+
return False
|
|
152
|
+
pid = int(pid_path.read_text(encoding="utf-8").strip())
|
|
153
|
+
if not pid_alive(pid):
|
|
154
|
+
pid_path.unlink(missing_ok=True)
|
|
155
|
+
return False
|
|
156
|
+
os.kill(pid, signal.SIGTERM)
|
|
157
|
+
pid_path.unlink(missing_ok=True)
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def write_status(state, detail=None, service_pid=None, ui_pid=None):
|
|
162
|
+
payload = {
|
|
163
|
+
"state": state,
|
|
164
|
+
"detail": detail,
|
|
165
|
+
"service_pid": service_pid,
|
|
166
|
+
"ui_pid": ui_pid,
|
|
167
|
+
"updated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
168
|
+
}
|
|
169
|
+
write_json(paths.get_runtime_status_path(), payload)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def read_status():
|
|
173
|
+
return read_json(paths.get_runtime_status_path()) or {}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def render_status():
|
|
177
|
+
status = read_status()
|
|
178
|
+
pid_path = paths.get_runtime_pid_path()
|
|
179
|
+
pid = pid_path.read_text(encoding="utf-8").strip() if pid_path.exists() else None
|
|
180
|
+
running = bool(pid and pid.isdigit() and pid_alive(int(pid)))
|
|
181
|
+
status["running"] = running
|
|
182
|
+
status["pid"] = int(pid) if pid and pid.isdigit() else None
|
|
183
|
+
return json.dumps(status, indent=2)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def start_service():
|
|
187
|
+
main_path = get_service_main_path()
|
|
188
|
+
return spawn_background(
|
|
189
|
+
[sys.executable, str(main_path)],
|
|
190
|
+
paths.get_runtime_pid_path(),
|
|
191
|
+
"service_stdout.log",
|
|
192
|
+
"service_stderr.log",
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def start_ui(host, port):
|
|
197
|
+
command = "from vibe.ui_server import run_ui_server; run_ui_server('{}', {})".format(
|
|
198
|
+
host, port
|
|
199
|
+
)
|
|
200
|
+
return spawn_background(
|
|
201
|
+
[sys.executable, "-c", command],
|
|
202
|
+
paths.get_runtime_ui_pid_path(),
|
|
203
|
+
"ui_stdout.log",
|
|
204
|
+
"ui_stderr.log",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def stop_service():
|
|
209
|
+
return stop_process(paths.get_runtime_pid_path())
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def stop_ui():
|
|
213
|
+
return stop_process(paths.get_runtime_ui_pid_path())
|
vibe/service_main.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import os
|
|
3
|
+
import signal
|
|
4
|
+
import sys
|
|
5
|
+
import logging
|
|
6
|
+
import asyncio
|
|
7
|
+
from config.paths import ensure_data_dirs, get_logs_dir
|
|
8
|
+
from config.v2_config import V2Config
|
|
9
|
+
from core.controller import Controller
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def setup_logging(level: str = "INFO"):
|
|
13
|
+
"""Setup logging configuration with file location and line numbers"""
|
|
14
|
+
# Create a custom formatter with file location
|
|
15
|
+
log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(funcName)s() - %(message)s'
|
|
16
|
+
|
|
17
|
+
# For development, you can use this more detailed format:
|
|
18
|
+
# log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(pathname)s:%(lineno)d] - %(funcName)s() - %(message)s'
|
|
19
|
+
|
|
20
|
+
ensure_data_dirs()
|
|
21
|
+
logs_dir = str(get_logs_dir())
|
|
22
|
+
|
|
23
|
+
logging.basicConfig(
|
|
24
|
+
level=getattr(logging, level.upper()),
|
|
25
|
+
format=log_format,
|
|
26
|
+
handlers=[
|
|
27
|
+
logging.StreamHandler(sys.stdout),
|
|
28
|
+
logging.FileHandler(f"{logs_dir}/vibe_remote.log"),
|
|
29
|
+
],
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def apply_claude_sdk_patches():
|
|
34
|
+
"""Apply runtime patches for third-party SDK limits."""
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
try:
|
|
37
|
+
from claude_code_sdk._internal.transport import subprocess_cli
|
|
38
|
+
except Exception as exc:
|
|
39
|
+
logger.warning(f"Claude SDK patch skipped: {exc}")
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
buffer_size = 16 * 1024 * 1024
|
|
43
|
+
previous = getattr(subprocess_cli, "_MAX_BUFFER_SIZE", None)
|
|
44
|
+
subprocess_cli._MAX_BUFFER_SIZE = buffer_size
|
|
45
|
+
if previous != buffer_size:
|
|
46
|
+
logger.info(
|
|
47
|
+
"Patched claude_code_sdk _MAX_BUFFER_SIZE from %s to %s bytes",
|
|
48
|
+
previous,
|
|
49
|
+
buffer_size,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def main():
|
|
54
|
+
"""Main entry point"""
|
|
55
|
+
try:
|
|
56
|
+
# Load configuration
|
|
57
|
+
config = V2Config.load()
|
|
58
|
+
|
|
59
|
+
# Setup logging
|
|
60
|
+
setup_logging(config.runtime.log_level)
|
|
61
|
+
logger = logging.getLogger(__name__)
|
|
62
|
+
|
|
63
|
+
apply_claude_sdk_patches()
|
|
64
|
+
|
|
65
|
+
logger.info("Starting vibe-remote service...")
|
|
66
|
+
logger.info(f"Working directory: {config.runtime.default_cwd}")
|
|
67
|
+
|
|
68
|
+
# Create and run controller
|
|
69
|
+
from config.v2_compat import to_app_config
|
|
70
|
+
|
|
71
|
+
controller = Controller(to_app_config(config))
|
|
72
|
+
|
|
73
|
+
shutdown_initiated = False
|
|
74
|
+
|
|
75
|
+
def _handle_shutdown(signum, frame):
|
|
76
|
+
nonlocal shutdown_initiated
|
|
77
|
+
if shutdown_initiated:
|
|
78
|
+
return
|
|
79
|
+
shutdown_initiated = True
|
|
80
|
+
try:
|
|
81
|
+
logger.info(f"Received signal {signum}, shutting down...")
|
|
82
|
+
except Exception:
|
|
83
|
+
pass
|
|
84
|
+
try:
|
|
85
|
+
controller.cleanup_sync()
|
|
86
|
+
except Exception as cleanup_err:
|
|
87
|
+
logger.error(f"Cleanup failed: {cleanup_err}")
|
|
88
|
+
raise SystemExit(0)
|
|
89
|
+
|
|
90
|
+
signal.signal(signal.SIGTERM, _handle_shutdown)
|
|
91
|
+
signal.signal(signal.SIGINT, _handle_shutdown)
|
|
92
|
+
|
|
93
|
+
controller.run()
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logging.error(f"Failed to start: {e}")
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
main()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_metadata": {
|
|
3
|
+
"major_version": 1,
|
|
4
|
+
"minor_version": 1
|
|
5
|
+
},
|
|
6
|
+
"display_information": {
|
|
7
|
+
"name": "Vibe Remote",
|
|
8
|
+
"description": "AI coding agent runtime for Slack",
|
|
9
|
+
"background_color": "#262626"
|
|
10
|
+
},
|
|
11
|
+
"features": {
|
|
12
|
+
"bot_user": {
|
|
13
|
+
"display_name": "Vibe Remote",
|
|
14
|
+
"always_online": true
|
|
15
|
+
},
|
|
16
|
+
"app_home": {
|
|
17
|
+
"home_tab_enabled": true,
|
|
18
|
+
"messages_tab_enabled": true,
|
|
19
|
+
"messages_tab_read_only_enabled": false
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"oauth_config": {
|
|
23
|
+
"scopes": {
|
|
24
|
+
"bot": [
|
|
25
|
+
"channels:history",
|
|
26
|
+
"channels:read",
|
|
27
|
+
"chat:write",
|
|
28
|
+
"app_mentions:read",
|
|
29
|
+
"users:read",
|
|
30
|
+
"commands",
|
|
31
|
+
"groups:read",
|
|
32
|
+
"groups:history",
|
|
33
|
+
"im:history",
|
|
34
|
+
"im:read",
|
|
35
|
+
"im:write",
|
|
36
|
+
"mpim:history",
|
|
37
|
+
"mpim:read",
|
|
38
|
+
"mpim:write",
|
|
39
|
+
"files:read",
|
|
40
|
+
"files:write",
|
|
41
|
+
"reactions:read",
|
|
42
|
+
"reactions:write"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"settings": {
|
|
47
|
+
"event_subscriptions": {
|
|
48
|
+
"bot_events": [
|
|
49
|
+
"message.channels",
|
|
50
|
+
"message.groups",
|
|
51
|
+
"message.im",
|
|
52
|
+
"message.mpim",
|
|
53
|
+
"app_mention",
|
|
54
|
+
"reaction_added",
|
|
55
|
+
"reaction_removed"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"interactivity": {
|
|
59
|
+
"is_enabled": true
|
|
60
|
+
},
|
|
61
|
+
"org_deploy_enabled": true,
|
|
62
|
+
"socket_mode_enabled": true,
|
|
63
|
+
"token_rotation_enabled": false
|
|
64
|
+
}
|
|
65
|
+
}
|