codex-telegram-bridge 0.6.2__tar.gz → 0.6.4__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.
- {codex_telegram_bridge-0.6.2/codex_telegram_bridge.egg-info → codex_telegram_bridge-0.6.4}/PKG-INFO +13 -9
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/README.md +12 -8
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/bridge_setup.py +153 -52
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/bridge_watchdog.py +128 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4/codex_telegram_bridge.egg-info}/PKG-INFO +13 -9
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/pyproject.toml +1 -1
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/tests/test_setup_wizard.py +60 -16
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/LICENSE +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/agent_runtime/__init__.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/agent_runtime/adapters/__init__.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/agent_runtime/adapters/base.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/agent_runtime/adapters/codex_repl.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/agent_runtime/approvals.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/agent_runtime/capabilities.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/agent_runtime/locks.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/agent_runtime/transport.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/agent_runtime/types.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/codex_audio_transcribe.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/codex_repl_bridge.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/codex_telegram_bridge.egg-info/SOURCES.txt +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/codex_telegram_bridge.egg-info/dependency_links.txt +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/codex_telegram_bridge.egg-info/entry_points.txt +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/codex_telegram_bridge.egg-info/requires.txt +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/codex_telegram_bridge.egg-info/top_level.txt +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/setup.cfg +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/telegram_agent_bridge.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/tests/test_agent_runtime.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/tests/test_bridge.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/tests/test_public_export.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/tests/test_repl_bridge.py +0 -0
- {codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/tests/test_watchdog.py +0 -0
{codex_telegram_bridge-0.6.2/codex_telegram_bridge.egg-info → codex_telegram_bridge-0.6.4}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-telegram-bridge
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
4
4
|
Summary: Control a live Codex CLI session from Telegram.
|
|
5
5
|
Author: Kang Daejong
|
|
6
6
|
License: MIT
|
|
@@ -132,7 +132,7 @@ Answer media attachments
|
|
|
132
132
|
-> send the actual media with sendPhoto/sendVideo/sendVoice/sendAudio
|
|
133
133
|
|
|
134
134
|
Service restart
|
|
135
|
-
-> run a local watchdog every 60 seconds
|
|
135
|
+
-> run a local watchdog every 60 seconds
|
|
136
136
|
-> restart or kickstart the bridge if the user service is inactive
|
|
137
137
|
-> load a persistent JSONL cursor and final-answer dedup ring
|
|
138
138
|
-> resume from the cursor when it still matches the current session file
|
|
@@ -219,8 +219,8 @@ The wizard will:
|
|
|
219
219
|
- install `~/.local/bin/telegram-agent-bridge-run`
|
|
220
220
|
- install and start a user service with systemd on Linux/WSL, launchd on macOS,
|
|
221
221
|
or a per-user Windows Startup launcher
|
|
222
|
-
- install a watchdog timer/LaunchAgent
|
|
223
|
-
|
|
222
|
+
- install a watchdog timer/LaunchAgent/Scheduled Task that recovers an inactive
|
|
223
|
+
bridge service
|
|
224
224
|
- send a setup-complete test message
|
|
225
225
|
|
|
226
226
|
Default setup mode is `repl`, which supports the visible Codex CLI transcript,
|
|
@@ -504,8 +504,8 @@ service was restarting.
|
|
|
504
504
|
|
|
505
505
|
## Service Watchdog
|
|
506
506
|
|
|
507
|
-
When the setup wizard installs a background service
|
|
508
|
-
|
|
507
|
+
When the setup wizard installs a background service, it also installs a small
|
|
508
|
+
watchdog.
|
|
509
509
|
|
|
510
510
|
On Windows, the wizard writes a per-user Startup launcher at
|
|
511
511
|
`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\telegram-agent-bridge.bat`.
|
|
@@ -513,9 +513,13 @@ This does not require administrator rights. Setup also starts that launcher once
|
|
|
513
513
|
immediately so you can send `/ping` without logging out and back in. If an older
|
|
514
514
|
per-user `telegram-agent-bridge` Scheduled Task exists, `doctor` still reports
|
|
515
515
|
its `schtasks /query` status; otherwise it reports whether the Startup launcher
|
|
516
|
-
is installed.
|
|
517
|
-
|
|
518
|
-
|
|
516
|
+
is installed.
|
|
517
|
+
|
|
518
|
+
The Windows watchdog is a separate per-user Scheduled Task named
|
|
519
|
+
`telegram-agent-bridge-watchdog`. It runs `bridge_watchdog.py` every minute
|
|
520
|
+
without administrator rights. If the bridge process is missing, it first restarts
|
|
521
|
+
an older service Scheduled Task when present, otherwise it re-runs the Startup
|
|
522
|
+
launcher and writes the same status file.
|
|
519
523
|
|
|
520
524
|
On Linux and WSL, the wizard writes:
|
|
521
525
|
|
|
@@ -105,7 +105,7 @@ Answer media attachments
|
|
|
105
105
|
-> send the actual media with sendPhoto/sendVideo/sendVoice/sendAudio
|
|
106
106
|
|
|
107
107
|
Service restart
|
|
108
|
-
-> run a local watchdog every 60 seconds
|
|
108
|
+
-> run a local watchdog every 60 seconds
|
|
109
109
|
-> restart or kickstart the bridge if the user service is inactive
|
|
110
110
|
-> load a persistent JSONL cursor and final-answer dedup ring
|
|
111
111
|
-> resume from the cursor when it still matches the current session file
|
|
@@ -192,8 +192,8 @@ The wizard will:
|
|
|
192
192
|
- install `~/.local/bin/telegram-agent-bridge-run`
|
|
193
193
|
- install and start a user service with systemd on Linux/WSL, launchd on macOS,
|
|
194
194
|
or a per-user Windows Startup launcher
|
|
195
|
-
- install a watchdog timer/LaunchAgent
|
|
196
|
-
|
|
195
|
+
- install a watchdog timer/LaunchAgent/Scheduled Task that recovers an inactive
|
|
196
|
+
bridge service
|
|
197
197
|
- send a setup-complete test message
|
|
198
198
|
|
|
199
199
|
Default setup mode is `repl`, which supports the visible Codex CLI transcript,
|
|
@@ -477,8 +477,8 @@ service was restarting.
|
|
|
477
477
|
|
|
478
478
|
## Service Watchdog
|
|
479
479
|
|
|
480
|
-
When the setup wizard installs a background service
|
|
481
|
-
|
|
480
|
+
When the setup wizard installs a background service, it also installs a small
|
|
481
|
+
watchdog.
|
|
482
482
|
|
|
483
483
|
On Windows, the wizard writes a per-user Startup launcher at
|
|
484
484
|
`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\telegram-agent-bridge.bat`.
|
|
@@ -486,9 +486,13 @@ This does not require administrator rights. Setup also starts that launcher once
|
|
|
486
486
|
immediately so you can send `/ping` without logging out and back in. If an older
|
|
487
487
|
per-user `telegram-agent-bridge` Scheduled Task exists, `doctor` still reports
|
|
488
488
|
its `schtasks /query` status; otherwise it reports whether the Startup launcher
|
|
489
|
-
is installed.
|
|
490
|
-
|
|
491
|
-
|
|
489
|
+
is installed.
|
|
490
|
+
|
|
491
|
+
The Windows watchdog is a separate per-user Scheduled Task named
|
|
492
|
+
`telegram-agent-bridge-watchdog`. It runs `bridge_watchdog.py` every minute
|
|
493
|
+
without administrator rights. If the bridge process is missing, it first restarts
|
|
494
|
+
an older service Scheduled Task when present, otherwise it re-runs the Startup
|
|
495
|
+
launcher and writes the same status file.
|
|
492
496
|
|
|
493
497
|
On Linux and WSL, the wizard writes:
|
|
494
498
|
|
|
@@ -27,6 +27,7 @@ APP_NAME = "telegram-agent-bridge"
|
|
|
27
27
|
SERVICE_NAME = "telegram-agent-bridge.service"
|
|
28
28
|
LAUNCHD_LABEL = "com.user.telegram-agent-bridge"
|
|
29
29
|
WINDOWS_TASK_NAME = APP_NAME
|
|
30
|
+
WINDOWS_WATCHDOG_TASK_NAME = f"{APP_NAME}-watchdog"
|
|
30
31
|
WATCHDOG_SERVICE_NAME = "telegram-agent-bridge-watchdog.service"
|
|
31
32
|
WATCHDOG_TIMER_NAME = "telegram-agent-bridge-watchdog.timer"
|
|
32
33
|
WATCHDOG_LAUNCHD_LABEL = "com.user.telegram-agent-bridge-watchdog"
|
|
@@ -148,7 +149,7 @@ def setup_command(command: str) -> None:
|
|
|
148
149
|
def doctor_command_hint() -> str:
|
|
149
150
|
invoked = Path(sys.argv[0]).name
|
|
150
151
|
if invoked == "bridge_setup.py":
|
|
151
|
-
python_cmd = "python" if platform.system() == "Windows" else "python3"
|
|
152
|
+
python_cmd = windows_command_quote(sys.executable or "python") if platform.system() == "Windows" else "python3"
|
|
152
153
|
return f"{python_cmd} bridge_setup.py doctor"
|
|
153
154
|
if invoked in {"codex-telegram-bridge", "telegram-agent-bridge"}:
|
|
154
155
|
return f"{invoked} doctor"
|
|
@@ -159,6 +160,8 @@ def setup_command_hint(*, mode: str | None = None) -> str:
|
|
|
159
160
|
invoked = Path(sys.argv[0]).name
|
|
160
161
|
if invoked in {"codex-telegram-bridge", "telegram-agent-bridge"}:
|
|
161
162
|
command = f"{invoked} setup"
|
|
163
|
+
elif platform.system() == "Windows":
|
|
164
|
+
command = f"{windows_command_quote(sys.executable or 'python')} bridge_setup.py setup"
|
|
162
165
|
else:
|
|
163
166
|
command = "python bridge_setup.py setup"
|
|
164
167
|
if mode:
|
|
@@ -187,7 +190,7 @@ def chmod_private(path: Path) -> None:
|
|
|
187
190
|
def write_text_atomic(path: Path, value: str, mode: int | None = None) -> None:
|
|
188
191
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
189
192
|
tmp = path.with_name(path.name + ".tmp")
|
|
190
|
-
tmp.write_text(value, encoding="utf-8")
|
|
193
|
+
tmp.write_text(value, encoding="utf-8", newline="\n")
|
|
191
194
|
if mode is not None:
|
|
192
195
|
tmp.chmod(mode)
|
|
193
196
|
tmp.replace(path)
|
|
@@ -320,7 +323,7 @@ def wait_for_chat_id(
|
|
|
320
323
|
|
|
321
324
|
|
|
322
325
|
def service_is_running(status: str | None) -> bool:
|
|
323
|
-
return (status or "").strip().lower() in {"active", "running"
|
|
326
|
+
return (status or "").strip().lower() in {"active", "running"}
|
|
324
327
|
|
|
325
328
|
|
|
326
329
|
def send_test_message(
|
|
@@ -331,12 +334,7 @@ def send_test_message(
|
|
|
331
334
|
service_status_text: str | None = None,
|
|
332
335
|
start_command: str | None = None,
|
|
333
336
|
) -> bool:
|
|
334
|
-
if
|
|
335
|
-
text = (
|
|
336
|
-
"telegram-agent-bridge setup complete. Windows Startup autostart is installed "
|
|
337
|
-
"and was launched. Send /ping to test the bridge."
|
|
338
|
-
)
|
|
339
|
-
elif service_status_text and service_is_running(service_status_text):
|
|
337
|
+
if service_status_text and service_is_running(service_status_text):
|
|
340
338
|
text = "telegram-agent-bridge setup complete. Background service is running. Send /ping to test the bridge."
|
|
341
339
|
elif start_command:
|
|
342
340
|
status = service_status_text or "unknown"
|
|
@@ -420,7 +418,61 @@ def write_env_config(
|
|
|
420
418
|
write_text_atomic(path, content, mode=0o600)
|
|
421
419
|
|
|
422
420
|
|
|
423
|
-
def
|
|
421
|
+
def python_runner_content(env_file: Path) -> str:
|
|
422
|
+
return "\n".join(
|
|
423
|
+
[
|
|
424
|
+
"#!/usr/bin/env python3",
|
|
425
|
+
"import os",
|
|
426
|
+
"import shlex",
|
|
427
|
+
"import subprocess",
|
|
428
|
+
"import sys",
|
|
429
|
+
"from pathlib import Path",
|
|
430
|
+
"",
|
|
431
|
+
f"ENV_FILE = Path({str(env_file)!r})",
|
|
432
|
+
f"REPL_SCRIPT = Path({str(REPL_BRIDGE_SCRIPT)!r})",
|
|
433
|
+
f"EXEC_SCRIPT = Path({str(EXEC_BRIDGE_SCRIPT)!r})",
|
|
434
|
+
"",
|
|
435
|
+
"if not ENV_FILE.exists():",
|
|
436
|
+
" print(f\"config file not found: {ENV_FILE}\", file=sys.stderr)",
|
|
437
|
+
" raise SystemExit(2)",
|
|
438
|
+
"",
|
|
439
|
+
"for raw_line in ENV_FILE.read_text(encoding='utf-8').splitlines():",
|
|
440
|
+
" line = raw_line.strip()",
|
|
441
|
+
" if not line or line.startswith('#') or '=' not in line:",
|
|
442
|
+
" continue",
|
|
443
|
+
" key, raw_value = line.split('=', 1)",
|
|
444
|
+
" key = key.strip()",
|
|
445
|
+
" raw_value = raw_value.strip()",
|
|
446
|
+
" try:",
|
|
447
|
+
" parts = shlex.split(raw_value)",
|
|
448
|
+
" value = parts[0] if parts else ''",
|
|
449
|
+
" except ValueError:",
|
|
450
|
+
" value = raw_value.strip(\"'\\\"\")",
|
|
451
|
+
" if key:",
|
|
452
|
+
" os.environ[key] = value",
|
|
453
|
+
"",
|
|
454
|
+
"mode = os.environ.get('TAB_BRIDGE_MODE', 'repl')",
|
|
455
|
+
"if mode == 'repl':",
|
|
456
|
+
" script = REPL_SCRIPT",
|
|
457
|
+
"elif mode == 'exec':",
|
|
458
|
+
" script = EXEC_SCRIPT",
|
|
459
|
+
"else:",
|
|
460
|
+
" print(f\"unknown TAB_BRIDGE_MODE: {mode}\", file=sys.stderr)",
|
|
461
|
+
" raise SystemExit(2)",
|
|
462
|
+
"",
|
|
463
|
+
"os.environ.setdefault('PYTHONUNBUFFERED', '1')",
|
|
464
|
+
"raise SystemExit(subprocess.call([sys.executable, str(script)]))",
|
|
465
|
+
"",
|
|
466
|
+
]
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def install_runner(runner_file: Path, env_file: Path, *, os_name: str | None = None) -> None:
|
|
471
|
+
os_name = os_name or platform.system()
|
|
472
|
+
if os_name == "Windows":
|
|
473
|
+
write_text_atomic(runner_file, python_runner_content(env_file), mode=0o755)
|
|
474
|
+
return
|
|
475
|
+
|
|
424
476
|
runner = "\n".join(
|
|
425
477
|
[
|
|
426
478
|
"#!/usr/bin/env bash",
|
|
@@ -768,6 +820,14 @@ def windows_command_quote(value: str) -> str:
|
|
|
768
820
|
return value
|
|
769
821
|
|
|
770
822
|
|
|
823
|
+
def windows_python_executable() -> str:
|
|
824
|
+
return sys.executable or shutil.which("python") or "python"
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
def windows_python_runner_command(runner_file: Path) -> tuple[str, list[str]]:
|
|
828
|
+
return windows_python_executable(), [str(runner_file)]
|
|
829
|
+
|
|
830
|
+
|
|
771
831
|
def windows_manual_start_command(host: WindowsBashHost, runner_file: Path) -> str:
|
|
772
832
|
if host.kind == "wsl":
|
|
773
833
|
args: list[str] = []
|
|
@@ -789,10 +849,8 @@ def manual_start_command_for_runner(
|
|
|
789
849
|
) -> str:
|
|
790
850
|
os_name = os_name or platform.system()
|
|
791
851
|
if os_name == "Windows":
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
except SetupError:
|
|
795
|
-
return str(runner_file)
|
|
852
|
+
executable, args = windows_python_runner_command(runner_file)
|
|
853
|
+
return " ".join(windows_command_quote(part) for part in [executable, *args])
|
|
796
854
|
return str(runner_file)
|
|
797
855
|
|
|
798
856
|
|
|
@@ -834,32 +892,55 @@ def install_windows_scheduled_task(
|
|
|
834
892
|
return f"Scheduled Task: {task_name}"
|
|
835
893
|
|
|
836
894
|
|
|
895
|
+
def windows_watchdog_task_action() -> str:
|
|
896
|
+
python_bin = sys.executable or shutil.which("python") or shutil.which("python3") or "python"
|
|
897
|
+
return " ".join(windows_command_quote(part) for part in [str(python_bin), str(WATCHDOG_SCRIPT)])
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def install_windows_watchdog_task(
|
|
901
|
+
*,
|
|
902
|
+
start: bool,
|
|
903
|
+
task_name: str,
|
|
904
|
+
run: Callable[..., subprocess.CompletedProcess[str]],
|
|
905
|
+
) -> str | None:
|
|
906
|
+
create = run(
|
|
907
|
+
[
|
|
908
|
+
"schtasks",
|
|
909
|
+
"/Create",
|
|
910
|
+
"/TN",
|
|
911
|
+
task_name,
|
|
912
|
+
"/SC",
|
|
913
|
+
"MINUTE",
|
|
914
|
+
"/MO",
|
|
915
|
+
"1",
|
|
916
|
+
"/TR",
|
|
917
|
+
windows_watchdog_task_action(),
|
|
918
|
+
"/F",
|
|
919
|
+
]
|
|
920
|
+
)
|
|
921
|
+
if create.returncode != 0:
|
|
922
|
+
detail = command_output(create) or "unknown error"
|
|
923
|
+
warn(f"failed to create Windows watchdog Scheduled Task {task_name}: {detail}")
|
|
924
|
+
return None
|
|
925
|
+
|
|
926
|
+
if start:
|
|
927
|
+
started = run(["schtasks", "/Run", "/TN", task_name])
|
|
928
|
+
if started.returncode != 0:
|
|
929
|
+
warn(f"could not start Windows watchdog Scheduled Task {task_name}: {command_output(started)}")
|
|
930
|
+
return f"Scheduled Task: {task_name}"
|
|
931
|
+
|
|
932
|
+
|
|
837
933
|
def powershell_invoke_command(executable: str, args: list[str]) -> str:
|
|
838
934
|
return " ".join(["&", powershell_single_quote(executable), *[powershell_single_quote(arg) for arg in args]])
|
|
839
935
|
|
|
840
936
|
|
|
841
|
-
def windows_startup_launcher_command(
|
|
842
|
-
executable, args =
|
|
843
|
-
|
|
844
|
-
nested_args = [
|
|
845
|
-
"-NoProfile",
|
|
846
|
-
"-ExecutionPolicy",
|
|
847
|
-
"Bypass",
|
|
848
|
-
"-Command",
|
|
849
|
-
inner,
|
|
850
|
-
]
|
|
851
|
-
quoted_args = ", ".join(powershell_single_quote(arg) for arg in nested_args)
|
|
852
|
-
script = (
|
|
853
|
-
"Start-Process -WindowStyle Hidden "
|
|
854
|
-
f"-FilePath {powershell_single_quote('powershell.exe')} "
|
|
855
|
-
f"-ArgumentList @({quoted_args})"
|
|
856
|
-
)
|
|
857
|
-
script_arg = '"' + script.replace('"', '`"') + '"'
|
|
858
|
-
return "powershell.exe", f"-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command {script_arg}"
|
|
937
|
+
def windows_startup_launcher_command(runner_file: Path) -> tuple[str, str]:
|
|
938
|
+
executable, args = windows_python_runner_command(runner_file)
|
|
939
|
+
return windows_scheduled_task_exec(executable, args)
|
|
859
940
|
|
|
860
941
|
|
|
861
|
-
def windows_startup_launcher_content(
|
|
862
|
-
command, arguments = windows_startup_launcher_command(
|
|
942
|
+
def windows_startup_launcher_content(runner_file: Path) -> str:
|
|
943
|
+
command, arguments = windows_startup_launcher_command(runner_file)
|
|
863
944
|
return "\r\n".join(
|
|
864
945
|
[
|
|
865
946
|
"@echo off",
|
|
@@ -879,16 +960,12 @@ def install_windows_startup_launcher(
|
|
|
879
960
|
startup_dir: Path | None = None,
|
|
880
961
|
app_name: str = APP_NAME,
|
|
881
962
|
) -> Path | None:
|
|
882
|
-
|
|
883
|
-
host = bash_host or detect_windows_bash_host(run=run)
|
|
884
|
-
except SetupError as exc:
|
|
885
|
-
warn(str(exc))
|
|
886
|
-
return None
|
|
963
|
+
_ = bash_host
|
|
887
964
|
|
|
888
965
|
launcher_file = default_windows_startup_launcher_file(startup_dir, app_name=app_name)
|
|
889
966
|
write_text_atomic(
|
|
890
967
|
launcher_file,
|
|
891
|
-
windows_startup_launcher_content(
|
|
968
|
+
windows_startup_launcher_content(runner_file),
|
|
892
969
|
mode=0o644,
|
|
893
970
|
)
|
|
894
971
|
if start:
|
|
@@ -919,6 +996,19 @@ def windows_scheduled_task_status(
|
|
|
919
996
|
return parse_windows_scheduled_task_status(proc.stdout or proc.stderr or "")
|
|
920
997
|
|
|
921
998
|
|
|
999
|
+
def windows_bridge_process_running(run: Callable[..., subprocess.CompletedProcess[str]]) -> bool:
|
|
1000
|
+
script = (
|
|
1001
|
+
"$ErrorActionPreference='SilentlyContinue'; "
|
|
1002
|
+
"$p = Get-CimInstance Win32_Process | Where-Object { "
|
|
1003
|
+
"$_.ProcessId -ne $PID -and $_.CommandLine -and "
|
|
1004
|
+
"$_.CommandLine -match 'telegram_agent_bridge\\.py|codex_repl_bridge\\.py|telegram-agent-bridge-run' "
|
|
1005
|
+
"} | Select-Object -First 1; "
|
|
1006
|
+
"if ($p) { 'running'; exit 0 } else { 'missing'; exit 1 }"
|
|
1007
|
+
)
|
|
1008
|
+
proc = run(["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script])
|
|
1009
|
+
return proc.returncode == 0
|
|
1010
|
+
|
|
1011
|
+
|
|
922
1012
|
def windows_startup_launcher_status(
|
|
923
1013
|
startup_dir: Path | None = None,
|
|
924
1014
|
*,
|
|
@@ -1035,11 +1125,11 @@ def install_watchdog(
|
|
|
1035
1125
|
return plist_file
|
|
1036
1126
|
|
|
1037
1127
|
if os_name == "Windows":
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1128
|
+
return install_windows_watchdog_task(
|
|
1129
|
+
start=start,
|
|
1130
|
+
task_name=WINDOWS_WATCHDOG_TASK_NAME,
|
|
1131
|
+
run=run,
|
|
1041
1132
|
)
|
|
1042
|
-
return None
|
|
1043
1133
|
|
|
1044
1134
|
warn(f"watchdog install is not automated for {os_name}")
|
|
1045
1135
|
return None
|
|
@@ -1107,6 +1197,11 @@ def uninstall_watchdog(
|
|
|
1107
1197
|
pass
|
|
1108
1198
|
return
|
|
1109
1199
|
|
|
1200
|
+
if os_name == "Windows":
|
|
1201
|
+
run(["schtasks", "/End", "/TN", WINDOWS_WATCHDOG_TASK_NAME])
|
|
1202
|
+
run(["schtasks", "/Delete", "/TN", WINDOWS_WATCHDOG_TASK_NAME, "/F"])
|
|
1203
|
+
return
|
|
1204
|
+
|
|
1110
1205
|
warn(f"watchdog uninstall is not automated for {os_name}")
|
|
1111
1206
|
|
|
1112
1207
|
|
|
@@ -1133,6 +1228,8 @@ def service_status(
|
|
|
1133
1228
|
task_status = windows_scheduled_task_status(task_name=task_name, run=run)
|
|
1134
1229
|
if task_status != "not-installed":
|
|
1135
1230
|
return task_status
|
|
1231
|
+
if windows_bridge_process_running(run):
|
|
1232
|
+
return "running"
|
|
1136
1233
|
return windows_startup_launcher_status(windows_startup_dir, app_name=task_name)
|
|
1137
1234
|
return "manual"
|
|
1138
1235
|
|
|
@@ -1141,7 +1238,7 @@ def watchdog_status(
|
|
|
1141
1238
|
*,
|
|
1142
1239
|
os_name: str | None = None,
|
|
1143
1240
|
run: Callable[..., subprocess.CompletedProcess[str]] = run_command,
|
|
1144
|
-
task_name: str =
|
|
1241
|
+
task_name: str = WINDOWS_WATCHDOG_TASK_NAME,
|
|
1145
1242
|
) -> str:
|
|
1146
1243
|
os_name = os_name or platform.system()
|
|
1147
1244
|
if os_name == "Linux":
|
|
@@ -1323,13 +1420,13 @@ def setup_bridge(options: SetupOptions, api_call: ApiCall = telegram_call) -> in
|
|
|
1323
1420
|
ok(f"wrote private config: {options.config_file}")
|
|
1324
1421
|
setup_note("The config file is chmod 600 and should stay out of git.")
|
|
1325
1422
|
|
|
1326
|
-
|
|
1423
|
+
current_os = platform.system()
|
|
1424
|
+
install_runner(options.runner_file, options.config_file, os_name=current_os)
|
|
1327
1425
|
ok(f"installed runner: {options.runner_file}")
|
|
1328
1426
|
|
|
1329
1427
|
setup_step(5, "Install the background service")
|
|
1330
1428
|
service_state: str | None = None
|
|
1331
1429
|
start_command: str | None = None
|
|
1332
|
-
current_os = platform.system()
|
|
1333
1430
|
if options.install_service:
|
|
1334
1431
|
installed = install_service(options.runner_file, start=options.start_service)
|
|
1335
1432
|
if installed:
|
|
@@ -1337,7 +1434,12 @@ def setup_bridge(options: SetupOptions, api_call: ApiCall = telegram_call) -> in
|
|
|
1337
1434
|
ok(f"installed service: {installed}")
|
|
1338
1435
|
service_state = service_status()
|
|
1339
1436
|
if options.start_service:
|
|
1340
|
-
|
|
1437
|
+
if service_is_running(service_state):
|
|
1438
|
+
ok(f"service status: {service_state}")
|
|
1439
|
+
else:
|
|
1440
|
+
warn(f"service status: {service_state}")
|
|
1441
|
+
if current_os == "Windows" and service_state == "startup-installed":
|
|
1442
|
+
setup_note("Windows Startup autostart exists, but the bridge process is not confirmed running.")
|
|
1341
1443
|
else:
|
|
1342
1444
|
start_command = manual_start_command_for_runner(options.runner_file, os_name=current_os)
|
|
1343
1445
|
warn("service install did not complete; run the bridge manually")
|
|
@@ -1500,6 +1602,8 @@ def doctor(config_file: Path, runner_file: Path, api_call: ApiCall = telegram_ca
|
|
|
1500
1602
|
add_next_step(f"Install Windows Startup autostart: {setup_command_hint()}")
|
|
1501
1603
|
else:
|
|
1502
1604
|
add_next_step(f"Run setup again to install service/watchdog: {setup_command_hint()}")
|
|
1605
|
+
elif current_os == "Windows" and status == "startup-installed":
|
|
1606
|
+
add_next_step(f"Start Windows Startup launcher: {service_start_command(os_name=current_os, runner_file=runner_file)}")
|
|
1503
1607
|
warnings += 1
|
|
1504
1608
|
|
|
1505
1609
|
wd_status = watchdog_status()
|
|
@@ -1509,10 +1613,7 @@ def doctor(config_file: Path, runner_file: Path, api_call: ApiCall = telegram_ca
|
|
|
1509
1613
|
warn(f"watchdog status: {wd_status}")
|
|
1510
1614
|
if wd_status == "not-installed":
|
|
1511
1615
|
if current_os == "Windows":
|
|
1512
|
-
|
|
1513
|
-
"Windows watchdog install is not automated; "
|
|
1514
|
-
"the Startup launcher covers logon autostart but not periodic recovery."
|
|
1515
|
-
)
|
|
1616
|
+
add_next_step(f"Run setup again to install Windows watchdog: {setup_command_hint()}")
|
|
1516
1617
|
else:
|
|
1517
1618
|
add_next_step(f"Run setup again to install service/watchdog: {setup_command_hint()}")
|
|
1518
1619
|
warnings += 1
|
|
@@ -15,6 +15,7 @@ from typing import Callable
|
|
|
15
15
|
APP_NAME = "telegram-agent-bridge"
|
|
16
16
|
SERVICE_NAME = "telegram-agent-bridge.service"
|
|
17
17
|
LAUNCHD_LABEL = "com.user.telegram-agent-bridge"
|
|
18
|
+
WINDOWS_TASK_NAME = APP_NAME
|
|
18
19
|
|
|
19
20
|
RunCommand = Callable[[list[str]], subprocess.CompletedProcess[str]]
|
|
20
21
|
|
|
@@ -41,6 +42,83 @@ def status_text(proc: subprocess.CompletedProcess[str]) -> str:
|
|
|
41
42
|
return (proc.stdout or proc.stderr or "").strip()
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
def default_windows_startup_dir() -> Path:
|
|
46
|
+
appdata = os.environ.get("APPDATA")
|
|
47
|
+
if appdata:
|
|
48
|
+
return (
|
|
49
|
+
Path(appdata)
|
|
50
|
+
/ "Microsoft"
|
|
51
|
+
/ "Windows"
|
|
52
|
+
/ "Start Menu"
|
|
53
|
+
/ "Programs"
|
|
54
|
+
/ "Startup"
|
|
55
|
+
)
|
|
56
|
+
return (
|
|
57
|
+
Path.home()
|
|
58
|
+
/ "AppData"
|
|
59
|
+
/ "Roaming"
|
|
60
|
+
/ "Microsoft"
|
|
61
|
+
/ "Windows"
|
|
62
|
+
/ "Start Menu"
|
|
63
|
+
/ "Programs"
|
|
64
|
+
/ "Startup"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def default_windows_startup_launcher_file(
|
|
69
|
+
startup_dir: Path | None = None,
|
|
70
|
+
*,
|
|
71
|
+
app_name: str = APP_NAME,
|
|
72
|
+
) -> Path:
|
|
73
|
+
return (startup_dir or default_windows_startup_dir()) / f"{app_name}.bat"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def powershell_single_quote(value: str | Path) -> str:
|
|
77
|
+
return "'" + str(value).replace("'", "''") + "'"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def windows_startup_launcher_start_command(launcher_file: Path) -> list[str]:
|
|
81
|
+
script = f"Start-Process -WindowStyle Hidden -FilePath {powershell_single_quote(launcher_file)}"
|
|
82
|
+
return [
|
|
83
|
+
"powershell.exe",
|
|
84
|
+
"-NoProfile",
|
|
85
|
+
"-ExecutionPolicy",
|
|
86
|
+
"Bypass",
|
|
87
|
+
"-Command",
|
|
88
|
+
script,
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def parse_windows_scheduled_task_status(output: str) -> str:
|
|
93
|
+
for line in output.replace("\r\n", "\n").splitlines():
|
|
94
|
+
if ":" not in line:
|
|
95
|
+
continue
|
|
96
|
+
key, value = line.split(":", 1)
|
|
97
|
+
if key.strip().lower() == "status":
|
|
98
|
+
return value.strip() or "unknown"
|
|
99
|
+
return "unknown"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def windows_scheduled_task_status(*, task_name: str, run: RunCommand) -> str:
|
|
103
|
+
proc = run(["schtasks", "/Query", "/TN", task_name, "/FO", "LIST", "/V"])
|
|
104
|
+
if proc.returncode != 0:
|
|
105
|
+
return "not-installed"
|
|
106
|
+
return parse_windows_scheduled_task_status(proc.stdout or proc.stderr or "")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def windows_bridge_process_running(run: RunCommand) -> bool:
|
|
110
|
+
script = (
|
|
111
|
+
"$ErrorActionPreference='SilentlyContinue'; "
|
|
112
|
+
"$p = Get-CimInstance Win32_Process | Where-Object { "
|
|
113
|
+
"$_.ProcessId -ne $PID -and $_.CommandLine -and "
|
|
114
|
+
"$_.CommandLine -match 'telegram_agent_bridge\\.py|codex_repl_bridge\\.py|telegram-agent-bridge-run' "
|
|
115
|
+
"}; "
|
|
116
|
+
"if ($p) { 'running'; exit 0 } else { 'missing'; exit 1 }"
|
|
117
|
+
)
|
|
118
|
+
proc = run(["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script])
|
|
119
|
+
return proc.returncode == 0
|
|
120
|
+
|
|
121
|
+
|
|
44
122
|
def launchd_state(output: str) -> str:
|
|
45
123
|
for line in output.splitlines():
|
|
46
124
|
stripped = line.strip()
|
|
@@ -130,12 +208,54 @@ def watch_macos(
|
|
|
130
208
|
return 1
|
|
131
209
|
|
|
132
210
|
|
|
211
|
+
def watch_windows(
|
|
212
|
+
*,
|
|
213
|
+
service_task_name: str,
|
|
214
|
+
startup_launcher: Path | None,
|
|
215
|
+
status_file: Path,
|
|
216
|
+
run: RunCommand,
|
|
217
|
+
settle_seconds: float,
|
|
218
|
+
) -> int:
|
|
219
|
+
if windows_bridge_process_running(run):
|
|
220
|
+
write_status(status_file, "active", "windows-process")
|
|
221
|
+
return 0
|
|
222
|
+
|
|
223
|
+
task_status = windows_scheduled_task_status(task_name=service_task_name, run=run)
|
|
224
|
+
if task_status != "not-installed":
|
|
225
|
+
start = run(["schtasks", "/Run", "/TN", service_task_name])
|
|
226
|
+
if settle_seconds > 0:
|
|
227
|
+
time.sleep(settle_seconds)
|
|
228
|
+
if windows_bridge_process_running(run):
|
|
229
|
+
write_status(status_file, "recovered", f"windows-task:{service_task_name}")
|
|
230
|
+
return 0
|
|
231
|
+
detail = status_text(start) or f"windows-task:{service_task_name}:{task_status}:start-failed"
|
|
232
|
+
write_status(status_file, "failed", detail)
|
|
233
|
+
return 1
|
|
234
|
+
|
|
235
|
+
launcher = startup_launcher or default_windows_startup_launcher_file()
|
|
236
|
+
if launcher.exists():
|
|
237
|
+
start = run(windows_startup_launcher_start_command(launcher))
|
|
238
|
+
if settle_seconds > 0:
|
|
239
|
+
time.sleep(settle_seconds)
|
|
240
|
+
if windows_bridge_process_running(run):
|
|
241
|
+
write_status(status_file, "recovered", f"windows-startup:{launcher}")
|
|
242
|
+
return 0
|
|
243
|
+
detail = status_text(start) or f"windows-startup:{launcher}:process-not-detected"
|
|
244
|
+
write_status(status_file, "failed", detail)
|
|
245
|
+
return 1
|
|
246
|
+
|
|
247
|
+
write_status(status_file, "failed", "windows:not-installed")
|
|
248
|
+
return 1
|
|
249
|
+
|
|
250
|
+
|
|
133
251
|
def watch_once(
|
|
134
252
|
*,
|
|
135
253
|
os_name: str | None = None,
|
|
136
254
|
service_name: str = SERVICE_NAME,
|
|
137
255
|
launchd_label: str = LAUNCHD_LABEL,
|
|
138
256
|
launchd_plist: Path | None = None,
|
|
257
|
+
windows_service_task_name: str = WINDOWS_TASK_NAME,
|
|
258
|
+
windows_startup_launcher: Path | None = None,
|
|
139
259
|
status_file: Path | None = None,
|
|
140
260
|
run: RunCommand = run_command,
|
|
141
261
|
settle_seconds: float = 2.0,
|
|
@@ -158,6 +278,14 @@ def watch_once(
|
|
|
158
278
|
run=run,
|
|
159
279
|
settle_seconds=settle_seconds,
|
|
160
280
|
)
|
|
281
|
+
if os_name == "Windows":
|
|
282
|
+
return watch_windows(
|
|
283
|
+
service_task_name=windows_service_task_name,
|
|
284
|
+
startup_launcher=windows_startup_launcher,
|
|
285
|
+
status_file=status_file,
|
|
286
|
+
run=run,
|
|
287
|
+
settle_seconds=settle_seconds,
|
|
288
|
+
)
|
|
161
289
|
|
|
162
290
|
write_status(status_file, "unsupported", os_name)
|
|
163
291
|
return 0
|
{codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4/codex_telegram_bridge.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-telegram-bridge
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
4
4
|
Summary: Control a live Codex CLI session from Telegram.
|
|
5
5
|
Author: Kang Daejong
|
|
6
6
|
License: MIT
|
|
@@ -132,7 +132,7 @@ Answer media attachments
|
|
|
132
132
|
-> send the actual media with sendPhoto/sendVideo/sendVoice/sendAudio
|
|
133
133
|
|
|
134
134
|
Service restart
|
|
135
|
-
-> run a local watchdog every 60 seconds
|
|
135
|
+
-> run a local watchdog every 60 seconds
|
|
136
136
|
-> restart or kickstart the bridge if the user service is inactive
|
|
137
137
|
-> load a persistent JSONL cursor and final-answer dedup ring
|
|
138
138
|
-> resume from the cursor when it still matches the current session file
|
|
@@ -219,8 +219,8 @@ The wizard will:
|
|
|
219
219
|
- install `~/.local/bin/telegram-agent-bridge-run`
|
|
220
220
|
- install and start a user service with systemd on Linux/WSL, launchd on macOS,
|
|
221
221
|
or a per-user Windows Startup launcher
|
|
222
|
-
- install a watchdog timer/LaunchAgent
|
|
223
|
-
|
|
222
|
+
- install a watchdog timer/LaunchAgent/Scheduled Task that recovers an inactive
|
|
223
|
+
bridge service
|
|
224
224
|
- send a setup-complete test message
|
|
225
225
|
|
|
226
226
|
Default setup mode is `repl`, which supports the visible Codex CLI transcript,
|
|
@@ -504,8 +504,8 @@ service was restarting.
|
|
|
504
504
|
|
|
505
505
|
## Service Watchdog
|
|
506
506
|
|
|
507
|
-
When the setup wizard installs a background service
|
|
508
|
-
|
|
507
|
+
When the setup wizard installs a background service, it also installs a small
|
|
508
|
+
watchdog.
|
|
509
509
|
|
|
510
510
|
On Windows, the wizard writes a per-user Startup launcher at
|
|
511
511
|
`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\telegram-agent-bridge.bat`.
|
|
@@ -513,9 +513,13 @@ This does not require administrator rights. Setup also starts that launcher once
|
|
|
513
513
|
immediately so you can send `/ping` without logging out and back in. If an older
|
|
514
514
|
per-user `telegram-agent-bridge` Scheduled Task exists, `doctor` still reports
|
|
515
515
|
its `schtasks /query` status; otherwise it reports whether the Startup launcher
|
|
516
|
-
is installed.
|
|
517
|
-
|
|
518
|
-
|
|
516
|
+
is installed.
|
|
517
|
+
|
|
518
|
+
The Windows watchdog is a separate per-user Scheduled Task named
|
|
519
|
+
`telegram-agent-bridge-watchdog`. It runs `bridge_watchdog.py` every minute
|
|
520
|
+
without administrator rights. If the bridge process is missing, it first restarts
|
|
521
|
+
an older service Scheduled Task when present, otherwise it re-runs the Startup
|
|
522
|
+
launcher and writes the same status file.
|
|
519
523
|
|
|
520
524
|
On Linux and WSL, the wizard writes:
|
|
521
525
|
|
|
@@ -5,6 +5,7 @@ import subprocess
|
|
|
5
5
|
import tempfile
|
|
6
6
|
import unittest
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from unittest import mock
|
|
8
9
|
|
|
9
10
|
import bridge_setup as setup
|
|
10
11
|
|
|
@@ -174,7 +175,23 @@ class SetupWizardTests(unittest.TestCase):
|
|
|
174
175
|
self.assertIn("/mnt/c/Users/Ada/very-long-profile-segment", xml_text)
|
|
175
176
|
self.assertIn(("schtasks", "/Run", "/TN", "tab-test-autostart"), commands)
|
|
176
177
|
|
|
177
|
-
def
|
|
178
|
+
def test_install_runner_windows_writes_python_runner_with_lf(self):
|
|
179
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
180
|
+
runner = Path(tmpdir) / "telegram-agent-bridge-run"
|
|
181
|
+
env_file = Path(r"C:\Users\gb\.config\telegram-agent-bridge.env")
|
|
182
|
+
|
|
183
|
+
setup.install_runner(runner, env_file, os_name="Windows")
|
|
184
|
+
|
|
185
|
+
raw = runner.read_bytes()
|
|
186
|
+
text = raw.decode("utf-8")
|
|
187
|
+
self.assertNotIn(b"\r", raw)
|
|
188
|
+
self.assertIn("import os", text)
|
|
189
|
+
self.assertIn("shlex.split", text)
|
|
190
|
+
self.assertIn(repr(str(env_file)), text)
|
|
191
|
+
self.assertIn("telegram_agent_bridge.py", text)
|
|
192
|
+
self.assertNotIn("set -euo pipefail", text)
|
|
193
|
+
|
|
194
|
+
def test_install_service_windows_installs_native_startup_launcher_without_bash(self):
|
|
178
195
|
commands = []
|
|
179
196
|
|
|
180
197
|
def run(cmd, check=False):
|
|
@@ -183,22 +200,26 @@ class SetupWizardTests(unittest.TestCase):
|
|
|
183
200
|
|
|
184
201
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
185
202
|
startup_dir = Path(tmpdir) / "Startup"
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
203
|
+
with mock.patch.object(setup.sys, "executable", r"C:\Python312\python.exe"):
|
|
204
|
+
installed = setup.install_service(
|
|
205
|
+
Path(r"C:\Users\gb\.local\bin\telegram-agent-bridge-run"),
|
|
206
|
+
os_name="Windows",
|
|
207
|
+
run=run,
|
|
208
|
+
task_name="telegram-agent-bridge",
|
|
209
|
+
bash_host=setup.WindowsBashHost(kind="git-bash", executable=r"C:\Program Files\Git\bin\bash.exe"),
|
|
210
|
+
windows_startup_dir=startup_dir,
|
|
211
|
+
)
|
|
194
212
|
|
|
195
213
|
launcher = startup_dir / "telegram-agent-bridge.bat"
|
|
196
214
|
self.assertEqual(installed, launcher)
|
|
197
215
|
self.assertTrue(launcher.exists())
|
|
198
216
|
launcher_text = launcher.read_text(encoding="utf-8")
|
|
199
217
|
self.assertIn("Start-Process", launcher_text)
|
|
200
|
-
self.assertIn("C
|
|
201
|
-
self.assertIn("
|
|
218
|
+
self.assertIn(r"C:\Python312\python.exe", launcher_text)
|
|
219
|
+
self.assertIn(r"C:\Users\gb\.local\bin\telegram-agent-bridge-run", launcher_text)
|
|
220
|
+
self.assertNotIn("bash.exe", launcher_text)
|
|
221
|
+
self.assertNotIn("wsl.exe", launcher_text)
|
|
222
|
+
self.assertNotIn("/c/Users/gb", launcher_text)
|
|
202
223
|
self.assertIn(">NUL 2>NUL", launcher_text)
|
|
203
224
|
|
|
204
225
|
self.assertNotIn(("schtasks", "/Create"), [cmd[:2] for cmd in commands])
|
|
@@ -215,8 +236,11 @@ class SetupWizardTests(unittest.TestCase):
|
|
|
215
236
|
launcher.write_text("@echo off\n", encoding="utf-8")
|
|
216
237
|
|
|
217
238
|
def missing_task(cmd, check=False):
|
|
218
|
-
|
|
219
|
-
|
|
239
|
+
if cmd[:4] == ["schtasks", "/Query", "/TN", "telegram-agent-bridge"]:
|
|
240
|
+
return completed(cmd, returncode=1, stderr="ERROR: Access is denied.\r\n")
|
|
241
|
+
if cmd[0] == "powershell.exe" and "Get-CimInstance" in cmd[-1]:
|
|
242
|
+
return completed(cmd, returncode=1, stdout="missing")
|
|
243
|
+
self.fail(f"unexpected command: {cmd}")
|
|
220
244
|
|
|
221
245
|
self.assertEqual(
|
|
222
246
|
setup.service_status(
|
|
@@ -228,6 +252,26 @@ class SetupWizardTests(unittest.TestCase):
|
|
|
228
252
|
"startup-installed",
|
|
229
253
|
)
|
|
230
254
|
|
|
255
|
+
def running_process(cmd, check=False):
|
|
256
|
+
if cmd[:4] == ["schtasks", "/Query", "/TN", "telegram-agent-bridge"]:
|
|
257
|
+
return completed(cmd, returncode=1, stderr="ERROR: Access is denied.\r\n")
|
|
258
|
+
if cmd[0] == "powershell.exe" and "Get-CimInstance" in cmd[-1]:
|
|
259
|
+
return completed(cmd, returncode=0, stdout="running")
|
|
260
|
+
self.fail(f"unexpected command: {cmd}")
|
|
261
|
+
|
|
262
|
+
self.assertEqual(
|
|
263
|
+
setup.service_status(
|
|
264
|
+
os_name="Windows",
|
|
265
|
+
run=running_process,
|
|
266
|
+
task_name="telegram-agent-bridge",
|
|
267
|
+
windows_startup_dir=startup_dir,
|
|
268
|
+
),
|
|
269
|
+
"running",
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def test_windows_startup_installed_is_not_running(self):
|
|
273
|
+
self.assertFalse(setup.service_is_running("startup-installed"))
|
|
274
|
+
|
|
231
275
|
def test_windows_service_status_parses_schtasks_query(self):
|
|
232
276
|
def running(cmd, check=False):
|
|
233
277
|
self.assertEqual(cmd[:4], ["schtasks", "/Query", "/TN", "tab-test-autostart"])
|
|
@@ -276,7 +320,7 @@ class SetupWizardTests(unittest.TestCase):
|
|
|
276
320
|
self.assertIn("/ping", params["text"])
|
|
277
321
|
self.assertIn("schtasks /Run /TN telegram-agent-bridge", params["text"])
|
|
278
322
|
|
|
279
|
-
def
|
|
323
|
+
def test_setup_complete_message_treats_windows_startup_launcher_as_not_confirmed_running(self):
|
|
280
324
|
api = FakeApi()
|
|
281
325
|
|
|
282
326
|
self.assertTrue(
|
|
@@ -291,9 +335,9 @@ class SetupWizardTests(unittest.TestCase):
|
|
|
291
335
|
|
|
292
336
|
_token, method, _timeout, params = api.calls[-1]
|
|
293
337
|
self.assertEqual(method, "sendMessage")
|
|
294
|
-
self.assertIn("
|
|
338
|
+
self.assertIn("Background service is not running (startup-installed)", params["text"])
|
|
295
339
|
self.assertIn("/ping", params["text"])
|
|
296
|
-
self.
|
|
340
|
+
self.assertIn("Start it with", params["text"])
|
|
297
341
|
|
|
298
342
|
def test_noninteractive_setup_writes_config_and_runner(self):
|
|
299
343
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
File without changes
|
|
File without changes
|
{codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/agent_runtime/adapters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{codex_telegram_bridge-0.6.2 → codex_telegram_bridge-0.6.4}/agent_runtime/adapters/codex_repl.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|