remote-coder 0.4.4__tar.gz → 0.5.1__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.
- {remote_coder-0.4.4/remote_coder.egg-info → remote_coder-0.5.1}/PKG-INFO +7 -3
- {remote_coder-0.4.4 → remote_coder-0.5.1}/README.md +6 -2
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/__init__.py +1 -1
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/admin/advanced_settings.py +12 -20
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/admin/router.py +75 -7
- remote_coder-0.5.1/app/admin/static/admin.css +1182 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/admin/static/i18n.js +55 -40
- remote_coder-0.5.1/app/admin/templates/admin.html +474 -0
- remote_coder-0.5.1/app/admin/templates/advanced.html +305 -0
- remote_coder-0.5.1/app/admin/templates/database.html +459 -0
- remote_coder-0.5.1/app/admin/templates/logs.html +335 -0
- remote_coder-0.5.1/app/admin/templates/projects.html +405 -0
- remote_coder-0.5.1/app/ai/base.py +243 -0
- remote_coder-0.5.1/app/ai/claude.py +33 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/ai/codex.py +11 -1
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/ai/gemini.py +9 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/git/ai_commit.py +18 -2
- remote_coder-0.5.1/app/git/service.py +55 -0
- remote_coder-0.4.4/app/git/service.py → remote_coder-0.5.1/app/git/worktree_service.py +42 -14
- remote_coder-0.5.1/app/jobs/execution_pipeline.py +270 -0
- remote_coder-0.5.1/app/jobs/fix_pipeline.py +184 -0
- remote_coder-0.5.1/app/jobs/fix_support.py +70 -0
- remote_coder-0.5.1/app/jobs/heartbeat.py +49 -0
- remote_coder-0.5.1/app/jobs/manager.py +297 -0
- remote_coder-0.5.1/app/jobs/plan_decisions.py +147 -0
- remote_coder-0.5.1/app/jobs/result_writer.py +104 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/jobs/schemas.py +14 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/jobs/store.py +59 -0
- remote_coder-0.5.1/app/jobs/worktree_planner.py +95 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/main.py +60 -49
- remote_coder-0.5.1/app/monitoring/__init__.py +26 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/monitoring/git.py +34 -16
- remote_coder-0.5.1/app/monitoring/memory.py +30 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/commands/__init__.py +4 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/commands/base.py +59 -30
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/commands/branch.py +56 -11
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/commands/clear_stop.py +7 -12
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/commands/registry.py +6 -1
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/commands/status.py +37 -6
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/commands/system.py +38 -59
- remote_coder-0.5.1/app/telegram/conversation/__init__.py +21 -0
- remote_coder-0.5.1/app/telegram/conversation/collaborators.py +296 -0
- remote_coder-0.5.1/app/telegram/conversation/context.py +64 -0
- remote_coder-0.5.1/app/telegram/conversation/models.py +58 -0
- remote_coder-0.5.1/app/telegram/conversation/protocols.py +52 -0
- remote_coder-0.5.1/app/telegram/conversation/sqlite_rows.py +19 -0
- remote_coder-0.4.4/app/telegram/conversation.py → remote_coder-0.5.1/app/telegram/conversation/sqlite_store.py +124 -221
- remote_coder-0.5.1/app/telegram/conversation/store.py +25 -0
- remote_coder-0.5.1/app/telegram/formatting.py +120 -0
- remote_coder-0.5.1/app/telegram/handlers/__init__.py +2 -0
- remote_coder-0.5.1/app/telegram/handlers/callback_dispatcher.py +266 -0
- remote_coder-0.5.1/app/telegram/handlers/command_flow.py +51 -0
- remote_coder-0.5.1/app/telegram/handlers/fix_flow.py +202 -0
- remote_coder-0.5.1/app/telegram/handlers/job_submission.py +236 -0
- remote_coder-0.5.1/app/telegram/handlers/natural_flow.py +214 -0
- remote_coder-0.5.1/app/telegram/handlers/plan_flow.py +207 -0
- remote_coder-0.5.1/app/telegram/handlers/presenters.py +101 -0
- remote_coder-0.5.1/app/telegram/handlers/recent_updates.py +25 -0
- remote_coder-0.5.1/app/telegram/handlers/request.py +71 -0
- remote_coder-0.5.1/app/telegram/handlers/session_binding.py +45 -0
- remote_coder-0.5.1/app/telegram/handlers/update_handler.py +198 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/i18n.py +82 -88
- remote_coder-0.5.1/app/telegram/lists.py +20 -0
- remote_coder-0.5.1/app/telegram/messages.py +220 -0
- remote_coder-0.5.1/app/telegram/notifier.py +390 -0
- remote_coder-0.5.1/app/telegram/notifier_protocol.py +44 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/parser.py +2 -6
- remote_coder-0.5.1/app/telegram/plan_decisions_flow.py +96 -0
- remote_coder-0.5.1/app/telegram/webhook.py +224 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/webhook_registration.py +32 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/pyproject.toml +2 -2
- {remote_coder-0.4.4 → remote_coder-0.5.1/remote_coder.egg-info}/PKG-INFO +7 -3
- {remote_coder-0.4.4 → remote_coder-0.5.1}/remote_coder.egg-info/SOURCES.txt +38 -1
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_admin_router.py +174 -21
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_ai_commit.py +22 -3
- remote_coder-0.5.1/tests/test_claude_runner.py +153 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_cli.py +1 -1
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_codex_runner.py +54 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_command_parser.py +62 -5
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_commands.py +304 -31
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_conversation_store.py +234 -6
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_gemini_runner.py +31 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_git_service.py +110 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_i18n.py +32 -1
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_job_manager.py +438 -2
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_job_store.py +78 -0
- remote_coder-0.5.1/tests/test_lists.py +26 -0
- remote_coder-0.5.1/tests/test_main_lifespan.py +67 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_monitoring.py +26 -0
- remote_coder-0.5.1/tests/test_notifier.py +925 -0
- remote_coder-0.5.1/tests/test_plan_decisions.py +173 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_project_scoped_state.py +4 -1
- remote_coder-0.5.1/tests/test_telegram_formatting.py +184 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_webhook.py +994 -178
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_webhook_registration.py +35 -0
- remote_coder-0.4.4/app/admin/templates/admin.html +0 -633
- remote_coder-0.4.4/app/admin/templates/advanced.html +0 -684
- remote_coder-0.4.4/app/admin/templates/database.html +0 -880
- remote_coder-0.4.4/app/admin/templates/logs.html +0 -686
- remote_coder-0.4.4/app/admin/templates/projects.html +0 -872
- remote_coder-0.4.4/app/ai/base.py +0 -129
- remote_coder-0.4.4/app/ai/claude.py +0 -16
- remote_coder-0.4.4/app/jobs/manager.py +0 -762
- remote_coder-0.4.4/app/monitoring/__init__.py +0 -10
- remote_coder-0.4.4/app/monitoring/memory.py +0 -19
- remote_coder-0.4.4/app/telegram/notifier.py +0 -387
- remote_coder-0.4.4/app/telegram/webhook.py +0 -1113
- remote_coder-0.4.4/tests/test_claude_runner.py +0 -75
- remote_coder-0.4.4/tests/test_notifier.py +0 -484
- {remote_coder-0.4.4 → remote_coder-0.5.1}/LICENSE +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/admin/__init__.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/admin/database_browser.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/admin/static/icons/advanced.svg +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/admin/static/icons/database.svg +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/admin/static/icons/download.svg +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/admin/static/icons/home.svg +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/admin/static/icons/logs.svg +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/admin/static/icons/projects.svg +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/admin/static/summary.js +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/ai/__init__.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/ai/factory.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/ai/model_catalog.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/ai/usage.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/cli.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/config.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/diagnostics.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/git/__init__.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/git/branch_naming.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/git/commit_message.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/jobs/__init__.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/models.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/monitoring/code.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/monitoring/events.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/monitoring/log_buffer.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/monitoring/model.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/projects/__init__.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/projects/registry.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/security/__init__.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/security/auth.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/system_startup.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/__init__.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/bot_instances.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/commands/fix.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/commands/model.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/commands/monitor.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/confirmations.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/telegram/model_preferences.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/app/tunnel.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/remote_coder.egg-info/dependency_links.txt +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/remote_coder.egg-info/entry_points.txt +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/remote_coder.egg-info/requires.txt +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/remote_coder.egg-info/top_level.txt +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/setup.cfg +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_ai_base.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_ai_factory.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_auth.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_bot_instance_manager.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_branch_naming.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_commit_message_formatter.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_config.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_database_browser.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_diagnostics.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_event_logger.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_job_status.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_log_buffer.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_project_registry.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_system_startup.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_tunnel.py +0 -0
- {remote_coder-0.4.4 → remote_coder-0.5.1}/tests/test_webhook_multibot.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remote-coder
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Telegram-based remote AI coding automation server
|
|
5
5
|
Author: Remote AI Coder contributors
|
|
6
6
|
License: Apache License
|
|
@@ -234,8 +234,9 @@ Run Claude Code, Codex, or Gemini on your local development machine by sending a
|
|
|
234
234
|
- One Telegram bot per registered project, with project-scoped allowlists and settings.
|
|
235
235
|
- Request-specific Git worktrees, branch creation, commit, push, and result notifications.
|
|
236
236
|
- Claude, Codex, and Gemini runners behind the same job flow.
|
|
237
|
+
- Reply-linked jobs continue the same AI CLI session, so follow-ups build on prior context.
|
|
237
238
|
- Local admin UI for project setup, advanced settings, logs, and conversation memory.
|
|
238
|
-
- Read-only `plan:` and `ask:` modes when you want analysis without commits.
|
|
239
|
+
- Read-only `plan:` and `ask:` modes when you want analysis without commits. PLAN mode asks open decisions through inline buttons first, then finalizes the plan from your answers.
|
|
239
240
|
|
|
240
241
|
## Quick Start
|
|
241
242
|
|
|
@@ -268,6 +269,7 @@ Open `http://127.0.0.1:8000/`, add your first project, and message the project b
|
|
|
268
269
|
- Allowed Telegram Chat IDs, and optionally User IDs
|
|
269
270
|
- At least one local AI CLI: `claude`, `codex`, or `gemini`
|
|
270
271
|
- A local Git repository to automate
|
|
272
|
+
- GitHub CLI (`gh`) authenticated with `gh auth login` when using `/pr`
|
|
271
273
|
|
|
272
274
|
## How It Works
|
|
273
275
|
|
|
@@ -294,13 +296,15 @@ Each project uses its own bot. The webhook path contains the first 16 hex charac
|
|
|
294
296
|
| `/branch [name]` | Show or switch the bound project's local branch |
|
|
295
297
|
| `/pull` | Fetch remotes and pull the current branch |
|
|
296
298
|
| `/rebase [branch]` | Rebase and fast-forward a completed branch into `main` or `master` |
|
|
297
|
-
| `/pr [branch]` | Create a GitHub PR with `gh` |
|
|
299
|
+
| `/pr [branch]` | Create a GitHub PR from a succeeded Job branch with `gh` |
|
|
298
300
|
| `/fix ...` | Rework a previous job's commit message or source |
|
|
299
301
|
| `/monitor ...` | Inspect model, memory, branch, worktree, code, or project status |
|
|
300
302
|
| `/clear ...` | Clean managed branches, worktrees, or conversation memory |
|
|
301
303
|
| `/stop [job_id]` | Cancel a queued or running job |
|
|
302
304
|
| `/init` | Reset chat-local model and pending confirmations |
|
|
303
305
|
|
|
306
|
+
Calling `/pr` without a branch lists only remote branches produced by succeeded Jobs in the current project and Telegram chat. Direct `/pr <branch>` calls enforce the same ownership check and verify that the branch still exists on the configured Git remote. Install the [GitHub CLI](https://cli.github.com/) and run `gh auth login` before using this command.
|
|
307
|
+
|
|
304
308
|
Natural-language examples:
|
|
305
309
|
|
|
306
310
|
```text
|
|
@@ -13,8 +13,9 @@ Run Claude Code, Codex, or Gemini on your local development machine by sending a
|
|
|
13
13
|
- One Telegram bot per registered project, with project-scoped allowlists and settings.
|
|
14
14
|
- Request-specific Git worktrees, branch creation, commit, push, and result notifications.
|
|
15
15
|
- Claude, Codex, and Gemini runners behind the same job flow.
|
|
16
|
+
- Reply-linked jobs continue the same AI CLI session, so follow-ups build on prior context.
|
|
16
17
|
- Local admin UI for project setup, advanced settings, logs, and conversation memory.
|
|
17
|
-
- Read-only `plan:` and `ask:` modes when you want analysis without commits.
|
|
18
|
+
- Read-only `plan:` and `ask:` modes when you want analysis without commits. PLAN mode asks open decisions through inline buttons first, then finalizes the plan from your answers.
|
|
18
19
|
|
|
19
20
|
## Quick Start
|
|
20
21
|
|
|
@@ -47,6 +48,7 @@ Open `http://127.0.0.1:8000/`, add your first project, and message the project b
|
|
|
47
48
|
- Allowed Telegram Chat IDs, and optionally User IDs
|
|
48
49
|
- At least one local AI CLI: `claude`, `codex`, or `gemini`
|
|
49
50
|
- A local Git repository to automate
|
|
51
|
+
- GitHub CLI (`gh`) authenticated with `gh auth login` when using `/pr`
|
|
50
52
|
|
|
51
53
|
## How It Works
|
|
52
54
|
|
|
@@ -73,13 +75,15 @@ Each project uses its own bot. The webhook path contains the first 16 hex charac
|
|
|
73
75
|
| `/branch [name]` | Show or switch the bound project's local branch |
|
|
74
76
|
| `/pull` | Fetch remotes and pull the current branch |
|
|
75
77
|
| `/rebase [branch]` | Rebase and fast-forward a completed branch into `main` or `master` |
|
|
76
|
-
| `/pr [branch]` | Create a GitHub PR with `gh` |
|
|
78
|
+
| `/pr [branch]` | Create a GitHub PR from a succeeded Job branch with `gh` |
|
|
77
79
|
| `/fix ...` | Rework a previous job's commit message or source |
|
|
78
80
|
| `/monitor ...` | Inspect model, memory, branch, worktree, code, or project status |
|
|
79
81
|
| `/clear ...` | Clean managed branches, worktrees, or conversation memory |
|
|
80
82
|
| `/stop [job_id]` | Cancel a queued or running job |
|
|
81
83
|
| `/init` | Reset chat-local model and pending confirmations |
|
|
82
84
|
|
|
85
|
+
Calling `/pr` without a branch lists only remote branches produced by succeeded Jobs in the current project and Telegram chat. Direct `/pr <branch>` calls enforce the same ownership check and verify that the branch still exists on the configured Git remote. Install the [GitHub CLI](https://cli.github.com/) and run `gh auth login` before using this command.
|
|
86
|
+
|
|
83
87
|
Natural-language examples:
|
|
84
88
|
|
|
85
89
|
```text
|
|
@@ -14,26 +14,31 @@ CONVERSATION_REPLY_SNIPPET_MAX_CHARS_DEFAULT = 3000
|
|
|
14
14
|
CONVERSATION_REPLY_SNIPPET_MAX_CHARS_MIN = 200
|
|
15
15
|
CONVERSATION_REPLY_SNIPPET_MAX_CHARS_MAX = 20000
|
|
16
16
|
|
|
17
|
+
# Settings retired for usability; popped on load so old config files still validate.
|
|
18
|
+
_REMOVED_SETTING_KEYS = (
|
|
19
|
+
"auto_pull_on_project_switch",
|
|
20
|
+
"server_lifecycle_notify_enabled",
|
|
21
|
+
"natural_job_confirmation_buttons_enabled",
|
|
22
|
+
"status_recent_job_limit",
|
|
23
|
+
"conversation_recent_limit",
|
|
24
|
+
"conversation_reply_snippet_max_chars",
|
|
25
|
+
)
|
|
26
|
+
|
|
17
27
|
|
|
18
28
|
class AdvancedSettings(BaseModel):
|
|
19
29
|
model_config = {"extra": "forbid"}
|
|
20
30
|
|
|
21
31
|
ui_language: UiLanguage = UiLanguage.ENGLISH
|
|
22
|
-
server_lifecycle_notify_enabled: bool = True
|
|
23
32
|
pull_projects_on_server_startup_enabled: bool = False
|
|
24
33
|
auto_merge_to_main_enabled: bool = False
|
|
25
34
|
delete_rebased_branch_enabled: bool = True
|
|
26
|
-
natural_job_confirmation_buttons_enabled: bool = False
|
|
27
35
|
conversation_memory_limit_enabled: bool = False
|
|
28
36
|
conversation_memory_max_rows: int | None = None
|
|
29
37
|
conversation_memory_max_bytes: int | None = None
|
|
30
|
-
status_recent_job_limit: int = 10
|
|
31
38
|
job_timeout_seconds: int = 1800
|
|
32
39
|
git_remote_name: str = "origin"
|
|
33
40
|
keep_worktree_on_success: bool = True
|
|
34
41
|
codex_sandbox: CodexSandboxMode = CodexSandboxMode.WORKSPACE_WRITE
|
|
35
|
-
conversation_recent_limit: int = 10
|
|
36
|
-
conversation_reply_snippet_max_chars: int = CONVERSATION_REPLY_SNIPPET_MAX_CHARS_DEFAULT
|
|
37
42
|
|
|
38
43
|
@model_validator(mode="after")
|
|
39
44
|
def _validate_memory_limits(self) -> Self:
|
|
@@ -51,22 +56,8 @@ class AdvancedSettings(BaseModel):
|
|
|
51
56
|
raise ValueError("conversation_memory_max_rows must be positive or blank.")
|
|
52
57
|
if self.conversation_memory_max_bytes is not None and self.conversation_memory_max_bytes <= 0:
|
|
53
58
|
raise ValueError("conversation_memory_max_bytes must be positive or blank.")
|
|
54
|
-
if self.status_recent_job_limit < 1:
|
|
55
|
-
raise ValueError("status_recent_job_limit must be at least 1.")
|
|
56
59
|
if self.job_timeout_seconds <= 0:
|
|
57
60
|
raise ValueError("job_timeout_seconds must be positive.")
|
|
58
|
-
if self.conversation_recent_limit < 1:
|
|
59
|
-
raise ValueError("conversation_recent_limit must be at least 1.")
|
|
60
|
-
if not (
|
|
61
|
-
CONVERSATION_REPLY_SNIPPET_MAX_CHARS_MIN
|
|
62
|
-
<= self.conversation_reply_snippet_max_chars
|
|
63
|
-
<= CONVERSATION_REPLY_SNIPPET_MAX_CHARS_MAX
|
|
64
|
-
):
|
|
65
|
-
raise ValueError(
|
|
66
|
-
"conversation_reply_snippet_max_chars must be between "
|
|
67
|
-
f"{CONVERSATION_REPLY_SNIPPET_MAX_CHARS_MIN} and "
|
|
68
|
-
f"{CONVERSATION_REPLY_SNIPPET_MAX_CHARS_MAX}.",
|
|
69
|
-
)
|
|
70
61
|
return self
|
|
71
62
|
|
|
72
63
|
|
|
@@ -92,7 +83,8 @@ class FileAdvancedSettingsStore:
|
|
|
92
83
|
raw = self._path.read_text(encoding="utf-8")
|
|
93
84
|
data = json.loads(raw) if raw.strip() else {}
|
|
94
85
|
if isinstance(data, dict):
|
|
95
|
-
|
|
86
|
+
for removed_key in _REMOVED_SETTING_KEYS:
|
|
87
|
+
data.pop(removed_key, None)
|
|
96
88
|
self._cached = AdvancedSettings.model_validate(data)
|
|
97
89
|
return self._cached.model_copy(deep=True)
|
|
98
90
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import secrets
|
|
3
4
|
from functools import lru_cache
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Annotated, Callable
|
|
6
7
|
|
|
8
|
+
import httpx
|
|
7
9
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
|
8
10
|
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, StreamingResponse
|
|
9
11
|
from pydantic import BaseModel, Field, SecretStr
|
|
@@ -43,7 +45,7 @@ def require_localhost(request: Request) -> None:
|
|
|
43
45
|
|
|
44
46
|
LocalhostOnly = Annotated[None, Depends(require_localhost)]
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
_WEBHOOK_SECRET_BYTES = 32
|
|
47
49
|
|
|
48
50
|
|
|
49
51
|
class ProjectUpsertBody(BaseModel):
|
|
@@ -61,6 +63,25 @@ class DefaultProjectBody(BaseModel):
|
|
|
61
63
|
name: str = Field(min_length=1)
|
|
62
64
|
|
|
63
65
|
|
|
66
|
+
class SetupTokenBody(BaseModel):
|
|
67
|
+
bot_token: str = Field(min_length=1)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _telegram_get(token: str, method: str, params: dict | None = None) -> dict:
|
|
71
|
+
url = f"https://api.telegram.org/bot{token}/{method}"
|
|
72
|
+
try:
|
|
73
|
+
response = httpx.get(url, params=params, timeout=httpx.Timeout(10.0, connect=5.0))
|
|
74
|
+
except httpx.HTTPError as exc:
|
|
75
|
+
raise HTTPException(status_code=502, detail="Could not reach Telegram.") from exc
|
|
76
|
+
try:
|
|
77
|
+
data = response.json()
|
|
78
|
+
except ValueError:
|
|
79
|
+
data = {}
|
|
80
|
+
if not isinstance(data, dict):
|
|
81
|
+
data = {}
|
|
82
|
+
return data
|
|
83
|
+
|
|
84
|
+
|
|
64
85
|
_ADMIN_ICON_NAMES = frozenset(
|
|
65
86
|
{"home.svg", "projects.svg", "advanced.svg", "logs.svg", "database.svg", "download.svg"}
|
|
66
87
|
)
|
|
@@ -336,7 +357,6 @@ def create_admin_router(
|
|
|
336
357
|
"job_timeout_seconds": adv.job_timeout_seconds,
|
|
337
358
|
"git_remote_name": adv.git_remote_name,
|
|
338
359
|
"codex_sandbox": adv.codex_sandbox.value,
|
|
339
|
-
"conversation_recent_limit": adv.conversation_recent_limit,
|
|
340
360
|
"keep_worktree_on_success": adv.keep_worktree_on_success,
|
|
341
361
|
"webhook_token_hash_prefix_length": WEBHOOK_TOKEN_HASH_PREFIX_LENGTH,
|
|
342
362
|
"webhook_route_template": "/telegram/webhook/{token_hash_prefix}",
|
|
@@ -364,6 +384,50 @@ def create_admin_router(
|
|
|
364
384
|
)
|
|
365
385
|
return report.model_dump()
|
|
366
386
|
|
|
387
|
+
@router.post("/api/setup/validate-token")
|
|
388
|
+
def api_setup_validate_token(body: SetupTokenBody, _: LocalhostOnly) -> dict:
|
|
389
|
+
token = body.bot_token.strip()
|
|
390
|
+
if not token:
|
|
391
|
+
raise HTTPException(status_code=400, detail="bot_token is required")
|
|
392
|
+
data = _telegram_get(token, "getMe")
|
|
393
|
+
if not data.get("ok"):
|
|
394
|
+
_adminlog.warning("setup token validation rejected by Telegram")
|
|
395
|
+
raise HTTPException(status_code=400, detail="Telegram rejected the bot token.")
|
|
396
|
+
result = data.get("result") or {}
|
|
397
|
+
_adminlog.info("setup token validated bot=%s", result.get("username") or "-")
|
|
398
|
+
return {
|
|
399
|
+
"ok": True,
|
|
400
|
+
"bot_username": result.get("username"),
|
|
401
|
+
"bot_name": result.get("first_name"),
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
@router.post("/api/setup/detect-chat")
|
|
405
|
+
def api_setup_detect_chat(body: SetupTokenBody, _: LocalhostOnly) -> dict:
|
|
406
|
+
token = body.bot_token.strip()
|
|
407
|
+
if not token:
|
|
408
|
+
raise HTTPException(status_code=400, detail="bot_token is required")
|
|
409
|
+
data = _telegram_get(token, "getUpdates", {"timeout": 0, "limit": 100})
|
|
410
|
+
if not data.get("ok"):
|
|
411
|
+
_adminlog.warning("setup chat detection failed (getUpdates not ok)")
|
|
412
|
+
raise HTTPException(
|
|
413
|
+
status_code=409,
|
|
414
|
+
detail="Could not read updates for this bot (a webhook may already be set). "
|
|
415
|
+
"Enter the chat ID manually.",
|
|
416
|
+
)
|
|
417
|
+
for update in reversed(data.get("result") or []):
|
|
418
|
+
message = update.get("message") if isinstance(update, dict) else None
|
|
419
|
+
chat = (message or {}).get("chat") if isinstance(message, dict) else None
|
|
420
|
+
if isinstance(chat, dict) and chat.get("id") is not None:
|
|
421
|
+
sender = message.get("from") or {}
|
|
422
|
+
_adminlog.info("setup chat detected chat_id=%s", chat.get("id"))
|
|
423
|
+
return {
|
|
424
|
+
"found": True,
|
|
425
|
+
"chat_id": chat.get("id"),
|
|
426
|
+
"chat_name": chat.get("title") or chat.get("username") or chat.get("first_name"),
|
|
427
|
+
"user_id": sender.get("id"),
|
|
428
|
+
}
|
|
429
|
+
return {"found": False}
|
|
430
|
+
|
|
367
431
|
@router.get("/api/projects")
|
|
368
432
|
def api_projects_get(_: LocalhostOnly) -> JSONResponse:
|
|
369
433
|
_adminlog.info("projects queried count=%d", len(registry.list_projects()))
|
|
@@ -377,7 +441,7 @@ def create_admin_router(
|
|
|
377
441
|
raise HTTPException(status_code=400, detail="allowed_chat_ids must have at least one entry")
|
|
378
442
|
wh_stripped = (body.webhook_secret or "").strip()
|
|
379
443
|
webhook_secret = SecretStr(
|
|
380
|
-
wh_stripped if wh_stripped else
|
|
444
|
+
wh_stripped if wh_stripped else secrets.token_urlsafe(_WEBHOOK_SECRET_BYTES)
|
|
381
445
|
)
|
|
382
446
|
record = ProjectRecord(
|
|
383
447
|
name=body.name,
|
|
@@ -492,19 +556,23 @@ def create_admin_router(
|
|
|
492
556
|
for project in registry.list_projects():
|
|
493
557
|
webhook_registrar.sync_project_commands(project)
|
|
494
558
|
_adminlog.info(
|
|
495
|
-
"advanced settings updated ui_language=%s
|
|
559
|
+
"advanced settings updated ui_language=%s pull_startup=%s auto_merge=%s delete_rebased_branch=%s job_timeout=%s memory_limit=%s",
|
|
496
560
|
saved.ui_language.value,
|
|
497
|
-
saved.server_lifecycle_notify_enabled,
|
|
498
561
|
saved.pull_projects_on_server_startup_enabled,
|
|
499
562
|
saved.auto_merge_to_main_enabled,
|
|
500
563
|
saved.delete_rebased_branch_enabled,
|
|
501
|
-
saved.natural_job_confirmation_buttons_enabled,
|
|
502
|
-
saved.status_recent_job_limit,
|
|
503
564
|
saved.job_timeout_seconds or "-",
|
|
504
565
|
saved.conversation_memory_limit_enabled,
|
|
505
566
|
)
|
|
506
567
|
return saved.model_dump(mode="json")
|
|
507
568
|
|
|
569
|
+
@router.get("/admin-static/admin.css")
|
|
570
|
+
def admin_css(_: LocalhostOnly) -> FileResponse:
|
|
571
|
+
path = Path(__file__).parent / "static" / "admin.css"
|
|
572
|
+
if not path.is_file():
|
|
573
|
+
raise HTTPException(status_code=404, detail="not found")
|
|
574
|
+
return FileResponse(path, media_type="text/css; charset=utf-8")
|
|
575
|
+
|
|
508
576
|
@router.get("/admin-static/i18n.js")
|
|
509
577
|
def admin_i18n_js(_: LocalhostOnly) -> FileResponse:
|
|
510
578
|
path = Path(__file__).parent / "static" / "i18n.js"
|