remote-coder 0.4.4__tar.gz → 0.5.0__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.
Files changed (161) hide show
  1. {remote_coder-0.4.4/remote_coder.egg-info → remote_coder-0.5.0}/PKG-INFO +7 -3
  2. {remote_coder-0.4.4 → remote_coder-0.5.0}/README.md +6 -2
  3. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/__init__.py +1 -1
  4. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/admin/advanced_settings.py +12 -20
  5. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/admin/router.py +75 -7
  6. remote_coder-0.5.0/app/admin/static/admin.css +1182 -0
  7. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/admin/static/i18n.js +55 -40
  8. remote_coder-0.5.0/app/admin/templates/admin.html +474 -0
  9. remote_coder-0.5.0/app/admin/templates/advanced.html +305 -0
  10. remote_coder-0.5.0/app/admin/templates/database.html +459 -0
  11. remote_coder-0.5.0/app/admin/templates/logs.html +335 -0
  12. remote_coder-0.5.0/app/admin/templates/projects.html +405 -0
  13. remote_coder-0.5.0/app/ai/base.py +242 -0
  14. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/ai/claude.py +10 -0
  15. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/ai/codex.py +11 -1
  16. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/ai/gemini.py +9 -0
  17. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/git/ai_commit.py +18 -2
  18. remote_coder-0.5.0/app/git/service.py +55 -0
  19. remote_coder-0.4.4/app/git/service.py → remote_coder-0.5.0/app/git/worktree_service.py +42 -14
  20. remote_coder-0.5.0/app/jobs/execution_pipeline.py +269 -0
  21. remote_coder-0.5.0/app/jobs/fix_pipeline.py +183 -0
  22. remote_coder-0.5.0/app/jobs/fix_support.py +70 -0
  23. remote_coder-0.5.0/app/jobs/heartbeat.py +40 -0
  24. remote_coder-0.5.0/app/jobs/manager.py +267 -0
  25. remote_coder-0.5.0/app/jobs/plan_decisions.py +147 -0
  26. remote_coder-0.5.0/app/jobs/result_writer.py +104 -0
  27. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/jobs/schemas.py +14 -0
  28. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/jobs/store.py +59 -0
  29. remote_coder-0.5.0/app/jobs/worktree_planner.py +95 -0
  30. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/main.py +35 -42
  31. remote_coder-0.5.0/app/monitoring/__init__.py +26 -0
  32. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/monitoring/memory.py +1 -0
  33. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/commands/__init__.py +4 -0
  34. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/commands/base.py +26 -6
  35. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/commands/branch.py +56 -11
  36. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/commands/clear_stop.py +7 -12
  37. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/commands/registry.py +6 -1
  38. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/commands/status.py +37 -6
  39. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/commands/system.py +26 -33
  40. remote_coder-0.5.0/app/telegram/conversation/__init__.py +21 -0
  41. remote_coder-0.5.0/app/telegram/conversation/collaborators.py +296 -0
  42. remote_coder-0.5.0/app/telegram/conversation/context.py +64 -0
  43. remote_coder-0.5.0/app/telegram/conversation/models.py +58 -0
  44. remote_coder-0.5.0/app/telegram/conversation/protocols.py +52 -0
  45. remote_coder-0.5.0/app/telegram/conversation/sqlite_rows.py +19 -0
  46. remote_coder-0.4.4/app/telegram/conversation.py → remote_coder-0.5.0/app/telegram/conversation/sqlite_store.py +124 -221
  47. remote_coder-0.5.0/app/telegram/conversation/store.py +25 -0
  48. remote_coder-0.5.0/app/telegram/formatting.py +90 -0
  49. remote_coder-0.5.0/app/telegram/handlers/__init__.py +2 -0
  50. remote_coder-0.5.0/app/telegram/handlers/callback_dispatcher.py +266 -0
  51. remote_coder-0.5.0/app/telegram/handlers/command_flow.py +51 -0
  52. remote_coder-0.5.0/app/telegram/handlers/fix_flow.py +202 -0
  53. remote_coder-0.5.0/app/telegram/handlers/job_submission.py +236 -0
  54. remote_coder-0.5.0/app/telegram/handlers/natural_flow.py +214 -0
  55. remote_coder-0.5.0/app/telegram/handlers/plan_flow.py +207 -0
  56. remote_coder-0.5.0/app/telegram/handlers/presenters.py +101 -0
  57. remote_coder-0.5.0/app/telegram/handlers/recent_updates.py +25 -0
  58. remote_coder-0.5.0/app/telegram/handlers/request.py +71 -0
  59. remote_coder-0.5.0/app/telegram/handlers/session_binding.py +45 -0
  60. remote_coder-0.5.0/app/telegram/handlers/update_handler.py +198 -0
  61. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/i18n.py +23 -7
  62. remote_coder-0.5.0/app/telegram/messages.py +199 -0
  63. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/notifier.py +145 -187
  64. remote_coder-0.5.0/app/telegram/notifier_protocol.py +42 -0
  65. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/parser.py +2 -6
  66. remote_coder-0.5.0/app/telegram/plan_decisions_flow.py +95 -0
  67. remote_coder-0.5.0/app/telegram/webhook.py +224 -0
  68. {remote_coder-0.4.4 → remote_coder-0.5.0}/pyproject.toml +2 -2
  69. {remote_coder-0.4.4 → remote_coder-0.5.0/remote_coder.egg-info}/PKG-INFO +7 -3
  70. {remote_coder-0.4.4 → remote_coder-0.5.0}/remote_coder.egg-info/SOURCES.txt +35 -1
  71. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_admin_router.py +174 -21
  72. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_ai_commit.py +22 -3
  73. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_claude_runner.py +52 -0
  74. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_cli.py +1 -1
  75. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_codex_runner.py +54 -0
  76. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_command_parser.py +62 -5
  77. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_commands.py +268 -19
  78. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_conversation_store.py +234 -6
  79. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_gemini_runner.py +31 -0
  80. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_git_service.py +110 -0
  81. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_i18n.py +7 -1
  82. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_job_manager.py +329 -1
  83. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_job_store.py +78 -0
  84. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_notifier.py +284 -0
  85. remote_coder-0.5.0/tests/test_plan_decisions.py +173 -0
  86. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_project_scoped_state.py +4 -1
  87. remote_coder-0.5.0/tests/test_telegram_formatting.py +154 -0
  88. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_webhook.py +995 -176
  89. remote_coder-0.4.4/app/admin/templates/admin.html +0 -633
  90. remote_coder-0.4.4/app/admin/templates/advanced.html +0 -684
  91. remote_coder-0.4.4/app/admin/templates/database.html +0 -880
  92. remote_coder-0.4.4/app/admin/templates/logs.html +0 -686
  93. remote_coder-0.4.4/app/admin/templates/projects.html +0 -872
  94. remote_coder-0.4.4/app/ai/base.py +0 -129
  95. remote_coder-0.4.4/app/jobs/manager.py +0 -762
  96. remote_coder-0.4.4/app/monitoring/__init__.py +0 -10
  97. remote_coder-0.4.4/app/telegram/webhook.py +0 -1113
  98. {remote_coder-0.4.4 → remote_coder-0.5.0}/LICENSE +0 -0
  99. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/admin/__init__.py +0 -0
  100. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/admin/database_browser.py +0 -0
  101. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/admin/static/icons/advanced.svg +0 -0
  102. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/admin/static/icons/database.svg +0 -0
  103. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/admin/static/icons/download.svg +0 -0
  104. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/admin/static/icons/home.svg +0 -0
  105. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/admin/static/icons/logs.svg +0 -0
  106. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/admin/static/icons/projects.svg +0 -0
  107. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/admin/static/summary.js +0 -0
  108. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/ai/__init__.py +0 -0
  109. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/ai/factory.py +0 -0
  110. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/ai/model_catalog.py +0 -0
  111. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/ai/usage.py +0 -0
  112. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/cli.py +0 -0
  113. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/config.py +0 -0
  114. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/diagnostics.py +0 -0
  115. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/git/__init__.py +0 -0
  116. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/git/branch_naming.py +0 -0
  117. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/git/commit_message.py +0 -0
  118. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/jobs/__init__.py +0 -0
  119. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/models.py +0 -0
  120. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/monitoring/code.py +0 -0
  121. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/monitoring/events.py +0 -0
  122. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/monitoring/git.py +0 -0
  123. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/monitoring/log_buffer.py +0 -0
  124. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/monitoring/model.py +0 -0
  125. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/projects/__init__.py +0 -0
  126. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/projects/registry.py +0 -0
  127. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/security/__init__.py +0 -0
  128. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/security/auth.py +0 -0
  129. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/system_startup.py +0 -0
  130. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/__init__.py +0 -0
  131. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/bot_instances.py +0 -0
  132. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/commands/fix.py +0 -0
  133. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/commands/model.py +0 -0
  134. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/commands/monitor.py +0 -0
  135. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/confirmations.py +0 -0
  136. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/model_preferences.py +0 -0
  137. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/telegram/webhook_registration.py +0 -0
  138. {remote_coder-0.4.4 → remote_coder-0.5.0}/app/tunnel.py +0 -0
  139. {remote_coder-0.4.4 → remote_coder-0.5.0}/remote_coder.egg-info/dependency_links.txt +0 -0
  140. {remote_coder-0.4.4 → remote_coder-0.5.0}/remote_coder.egg-info/entry_points.txt +0 -0
  141. {remote_coder-0.4.4 → remote_coder-0.5.0}/remote_coder.egg-info/requires.txt +0 -0
  142. {remote_coder-0.4.4 → remote_coder-0.5.0}/remote_coder.egg-info/top_level.txt +0 -0
  143. {remote_coder-0.4.4 → remote_coder-0.5.0}/setup.cfg +0 -0
  144. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_ai_base.py +0 -0
  145. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_ai_factory.py +0 -0
  146. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_auth.py +0 -0
  147. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_bot_instance_manager.py +0 -0
  148. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_branch_naming.py +0 -0
  149. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_commit_message_formatter.py +0 -0
  150. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_config.py +0 -0
  151. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_database_browser.py +0 -0
  152. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_diagnostics.py +0 -0
  153. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_event_logger.py +0 -0
  154. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_job_status.py +0 -0
  155. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_log_buffer.py +0 -0
  156. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_monitoring.py +0 -0
  157. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_project_registry.py +0 -0
  158. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_system_startup.py +0 -0
  159. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_tunnel.py +0 -0
  160. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_webhook_multibot.py +0 -0
  161. {remote_coder-0.4.4 → remote_coder-0.5.0}/tests/test_webhook_registration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remote-coder
3
- Version: 0.4.4
3
+ Version: 0.5.0
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
@@ -1,3 +1,3 @@
1
1
  """Remote AI Coder application package."""
2
2
 
3
- __version__ = "0.4.4"
3
+ __version__ = "0.5.0"
@@ -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
- data.pop("auto_pull_on_project_switch", None)
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
- _DEFAULT_NEW_PROJECT_WEBHOOK_SECRET = "optional-secret"
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 _DEFAULT_NEW_PROJECT_WEBHOOK_SECRET
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 lifecycle_notify=%s pull_startup=%s auto_merge=%s delete_rebased_branch=%s natural_confirm_buttons=%s status_limit=%d job_timeout=%s memory_limit=%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"