remote-coder 0.5.1__tar.gz → 0.5.2__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.5.1/remote_coder.egg-info → remote_coder-0.5.2}/PKG-INFO +3 -2
- {remote_coder-0.5.1 → remote_coder-0.5.2}/README.md +2 -1
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/__init__.py +1 -1
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/router.py +2 -1
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/static/i18n.js +5 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/templates/admin.html +4 -1
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/ai/base.py +14 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/ai/codex.py +3 -3
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/ai/gemini.py +2 -2
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/cli.py +5 -1
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/diagnostics.py +12 -1
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/jobs/execution_pipeline.py +3 -5
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/jobs/schemas.py +8 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/jobs/worktree_planner.py +2 -2
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/commands/base.py +21 -4
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/commands/branch.py +24 -4
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/commands/registry.py +8 -1
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/commands/system.py +13 -1
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/handlers/job_submission.py +2 -2
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/handlers/natural_flow.py +6 -1
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/handlers/presenters.py +9 -6
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/handlers/update_handler.py +7 -2
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/i18n.py +21 -10
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/messages.py +7 -4
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/parser.py +14 -12
- {remote_coder-0.5.1 → remote_coder-0.5.2}/pyproject.toml +1 -1
- {remote_coder-0.5.1 → remote_coder-0.5.2/remote_coder.egg-info}/PKG-INFO +3 -2
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_admin_router.py +2 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_ai_base.py +8 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_claude_runner.py +24 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_cli.py +2 -1
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_codex_runner.py +23 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_command_parser.py +25 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_commands.py +59 -4
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_diagnostics.py +14 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_gemini_runner.py +26 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_job_manager.py +47 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_notifier.py +36 -5
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_webhook.py +108 -1
- {remote_coder-0.5.1 → remote_coder-0.5.2}/LICENSE +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/__init__.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/advanced_settings.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/database_browser.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/static/admin.css +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/static/icons/advanced.svg +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/static/icons/database.svg +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/static/icons/download.svg +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/static/icons/home.svg +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/static/icons/logs.svg +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/static/icons/projects.svg +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/static/summary.js +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/templates/advanced.html +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/templates/database.html +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/templates/logs.html +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/admin/templates/projects.html +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/ai/__init__.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/ai/claude.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/ai/factory.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/ai/model_catalog.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/ai/usage.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/config.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/git/__init__.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/git/ai_commit.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/git/branch_naming.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/git/commit_message.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/git/service.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/git/worktree_service.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/jobs/__init__.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/jobs/fix_pipeline.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/jobs/fix_support.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/jobs/heartbeat.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/jobs/manager.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/jobs/plan_decisions.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/jobs/result_writer.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/jobs/store.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/main.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/models.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/monitoring/__init__.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/monitoring/code.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/monitoring/events.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/monitoring/git.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/monitoring/log_buffer.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/monitoring/memory.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/monitoring/model.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/projects/__init__.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/projects/registry.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/security/__init__.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/security/auth.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/system_startup.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/__init__.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/bot_instances.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/commands/__init__.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/commands/clear_stop.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/commands/fix.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/commands/model.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/commands/monitor.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/commands/status.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/confirmations.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/conversation/__init__.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/conversation/collaborators.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/conversation/context.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/conversation/models.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/conversation/protocols.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/conversation/sqlite_rows.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/conversation/sqlite_store.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/conversation/store.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/formatting.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/handlers/__init__.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/handlers/callback_dispatcher.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/handlers/command_flow.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/handlers/fix_flow.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/handlers/plan_flow.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/handlers/recent_updates.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/handlers/request.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/handlers/session_binding.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/lists.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/model_preferences.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/notifier.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/notifier_protocol.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/plan_decisions_flow.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/webhook.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/telegram/webhook_registration.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/app/tunnel.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/remote_coder.egg-info/SOURCES.txt +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/remote_coder.egg-info/dependency_links.txt +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/remote_coder.egg-info/entry_points.txt +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/remote_coder.egg-info/requires.txt +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/remote_coder.egg-info/top_level.txt +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/setup.cfg +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_ai_commit.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_ai_factory.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_auth.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_bot_instance_manager.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_branch_naming.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_commit_message_formatter.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_config.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_conversation_store.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_database_browser.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_event_logger.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_git_service.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_i18n.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_job_status.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_job_store.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_lists.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_log_buffer.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_main_lifespan.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_monitoring.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_plan_decisions.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_project_registry.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_project_scoped_state.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_system_startup.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_telegram_formatting.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_tunnel.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/tests/test_webhook_multibot.py +0 -0
- {remote_coder-0.5.1 → remote_coder-0.5.2}/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.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Telegram-based remote AI coding automation server
|
|
5
5
|
Author: Remote AI Coder contributors
|
|
6
6
|
License: Apache License
|
|
@@ -236,7 +236,7 @@ Run Claude Code, Codex, or Gemini on your local development machine by sending a
|
|
|
236
236
|
- Claude, Codex, and Gemini runners behind the same job flow.
|
|
237
237
|
- Reply-linked jobs continue the same AI CLI session, so follow-ups build on prior context.
|
|
238
238
|
- Local admin UI for project setup, advanced settings, logs, and conversation memory.
|
|
239
|
-
- Read-only `plan
|
|
239
|
+
- Read-only `plan:`, `ask:`, and `research:` modes when you want analysis without commits. PLAN mode asks open decisions through inline buttons first, then finalizes the plan from your answers; RESEARCH mode asks the selected AI CLI to use internet search when useful.
|
|
240
240
|
|
|
241
241
|
## Quick Start
|
|
242
242
|
|
|
@@ -311,6 +311,7 @@ Natural-language examples:
|
|
|
311
311
|
Fix the login validation bug with model: codex
|
|
312
312
|
plan: outline the migration before changing code
|
|
313
313
|
/ask what test command does this repo use?
|
|
314
|
+
/research compare current Telegram webhook security guidance
|
|
314
315
|
수정: 방금 작업에서 README 문구만 더 간결하게 바꿔줘
|
|
315
316
|
```
|
|
316
317
|
|
|
@@ -15,7 +15,7 @@ Run Claude Code, Codex, or Gemini on your local development machine by sending a
|
|
|
15
15
|
- Claude, Codex, and Gemini runners behind the same job flow.
|
|
16
16
|
- Reply-linked jobs continue the same AI CLI session, so follow-ups build on prior context.
|
|
17
17
|
- Local admin UI for project setup, advanced settings, logs, and conversation memory.
|
|
18
|
-
- Read-only `plan
|
|
18
|
+
- Read-only `plan:`, `ask:`, and `research:` modes when you want analysis without commits. PLAN mode asks open decisions through inline buttons first, then finalizes the plan from your answers; RESEARCH mode asks the selected AI CLI to use internet search when useful.
|
|
19
19
|
|
|
20
20
|
## Quick Start
|
|
21
21
|
|
|
@@ -90,6 +90,7 @@ Natural-language examples:
|
|
|
90
90
|
Fix the login validation bug with model: codex
|
|
91
91
|
plan: outline the migration before changing code
|
|
92
92
|
/ask what test command does this repo use?
|
|
93
|
+
/research compare current Telegram webhook security guidance
|
|
93
94
|
수정: 방금 작업에서 README 문구만 더 간결하게 바꿔줘
|
|
94
95
|
```
|
|
95
96
|
|
|
@@ -378,9 +378,10 @@ def create_admin_router(
|
|
|
378
378
|
report = check_prerequisites()
|
|
379
379
|
installed = [cli.name for cli in report.ai_clis if cli.installed]
|
|
380
380
|
_adminlog.info(
|
|
381
|
-
"prerequisites queried ngrok_ok=%s ai_clis=%s",
|
|
381
|
+
"prerequisites queried ngrok_ok=%s ai_clis=%s github_cli=%s",
|
|
382
382
|
report.ngrok_ok,
|
|
383
383
|
",".join(installed) or "-",
|
|
384
|
+
report.github_cli.installed,
|
|
384
385
|
)
|
|
385
386
|
return report.model_dump()
|
|
386
387
|
|
|
@@ -146,6 +146,11 @@
|
|
|
146
146
|
en: "None found (install at least one: claude / codex / gemini)",
|
|
147
147
|
ko: "설치된 것 없음 (claude / codex / gemini 중 최소 1개 설치)",
|
|
148
148
|
},
|
|
149
|
+
"setup.githubCli": { en: "GitHub CLI", ko: "GitHub CLI" },
|
|
150
|
+
"setup.githubCliNone": {
|
|
151
|
+
en: "Not found (install gh before using /pr)",
|
|
152
|
+
ko: "설치되지 않음 (/pr 사용 전 gh 설치 필요)",
|
|
153
|
+
},
|
|
149
154
|
"setup.checking": { en: "Checking…", ko: "확인 중…" },
|
|
150
155
|
"setup.recheck": { en: "Re-check", ko: "다시 확인" },
|
|
151
156
|
"setup.cta": { en: "Add your first project", ko: "첫 프로젝트 추가" },
|
|
@@ -248,9 +248,12 @@
|
|
|
248
248
|
const installed = (report.ai_clis || []).filter((c) => c.installed).map((c) => c.name);
|
|
249
249
|
const aiOk = installed.length > 0;
|
|
250
250
|
const aiDetail = aiOk ? installed.join(", ") : i18n.t("setup.aiCliNone");
|
|
251
|
+
const ghOk = !!(report.github_cli && report.github_cli.installed);
|
|
252
|
+
const ghDetail = ghOk ? "gh" : i18n.t("setup.githubCliNone");
|
|
251
253
|
$("#setup-prereq").innerHTML =
|
|
252
254
|
renderPrereqRow(!!report.ngrok_ok, i18n.t("setup.ngrok"), ngrokDetail) +
|
|
253
|
-
renderPrereqRow(aiOk, i18n.t("setup.aiCli"), aiDetail)
|
|
255
|
+
renderPrereqRow(aiOk, i18n.t("setup.aiCli"), aiDetail) +
|
|
256
|
+
renderPrereqRow(ghOk, i18n.t("setup.githubCli"), ghDetail);
|
|
254
257
|
}
|
|
255
258
|
|
|
256
259
|
async function loadPrerequisites() {
|
|
@@ -57,6 +57,18 @@ _PLAN_DECISIONS_INSTRUCTION = (
|
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
_RESEARCH_INSTRUCTION = (
|
|
61
|
+
"You are in RESEARCH mode. Read the repository context and answer the user's research "
|
|
62
|
+
"question. Do not modify files.\n\n"
|
|
63
|
+
"Use internet search when it is useful or necessary for the question, similar to a deep "
|
|
64
|
+
"research workflow. Compare multiple perspectives or sources when possible, and clearly "
|
|
65
|
+
"separate repository-derived facts from external findings. Include citations or source "
|
|
66
|
+
"links for external claims, call out uncertainty or limitations, and finish with a direct "
|
|
67
|
+
"answer to the user's problem.\n\n"
|
|
68
|
+
"User research request:\n"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
60
72
|
def instruction_for_runner_mode(instruction: str, mode: JobMode) -> str:
|
|
61
73
|
if mode == JobMode.PLAN:
|
|
62
74
|
return f"{_PLAN_DECISIONS_INSTRUCTION}{instruction}"
|
|
@@ -66,6 +78,8 @@ def instruction_for_runner_mode(instruction: str, mode: JobMode) -> str:
|
|
|
66
78
|
"Do not modify files.\n\n"
|
|
67
79
|
f"User question:\n{instruction}"
|
|
68
80
|
)
|
|
81
|
+
if mode == JobMode.RESEARCH:
|
|
82
|
+
return f"{_RESEARCH_INSTRUCTION}{instruction}"
|
|
69
83
|
return instruction
|
|
70
84
|
|
|
71
85
|
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
from app.ai.base import BaseCliRunner, RunnerInput, instruction_for_runner_mode
|
|
6
|
-
from app.jobs.schemas import
|
|
6
|
+
from app.jobs.schemas import is_read_only_job_mode
|
|
7
7
|
from app.models import CodexSandboxMode
|
|
8
8
|
from app.monitoring.events import EventLogger
|
|
9
9
|
|
|
@@ -16,7 +16,7 @@ class CodexRunner(BaseCliRunner):
|
|
|
16
16
|
self._sandbox = sandbox
|
|
17
17
|
|
|
18
18
|
def _resolve_sandbox(self, runner_input: RunnerInput) -> CodexSandboxMode:
|
|
19
|
-
if runner_input.mode
|
|
19
|
+
if is_read_only_job_mode(runner_input.mode):
|
|
20
20
|
return CodexSandboxMode.READ_ONLY
|
|
21
21
|
return self._sandbox
|
|
22
22
|
|
|
@@ -29,7 +29,7 @@ class CodexRunner(BaseCliRunner):
|
|
|
29
29
|
|
|
30
30
|
def build_argv(self, runner_input: RunnerInput) -> list[str]:
|
|
31
31
|
sandbox = self._resolve_sandbox(runner_input)
|
|
32
|
-
if runner_input.mode
|
|
32
|
+
if is_read_only_job_mode(runner_input.mode):
|
|
33
33
|
instruction = instruction_for_runner_mode(runner_input.instruction, runner_input.mode)
|
|
34
34
|
else:
|
|
35
35
|
instruction = runner_input.instruction
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
from app.ai.base import BaseCliRunner, RunnerInput, instruction_for_runner_mode
|
|
6
|
-
from app.jobs.schemas import
|
|
6
|
+
from app.jobs.schemas import is_read_only_job_mode
|
|
7
7
|
from app.monitoring.events import EventLogger
|
|
8
8
|
|
|
9
9
|
|
|
@@ -17,7 +17,7 @@ class GeminiRunner(BaseCliRunner):
|
|
|
17
17
|
return Path.home() / ".gemini" / "sessions"
|
|
18
18
|
|
|
19
19
|
def build_argv(self, runner_input: RunnerInput) -> list[str]:
|
|
20
|
-
if runner_input.mode
|
|
20
|
+
if is_read_only_job_mode(runner_input.mode):
|
|
21
21
|
prompt = instruction_for_runner_mode(runner_input.instruction, runner_input.mode)
|
|
22
22
|
argv = ["gemini", "--skip-trust", "-p", prompt]
|
|
23
23
|
else:
|
|
@@ -32,7 +32,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
32
32
|
help="Run the server only (skip ngrok and webhook registration)",
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
-
subparsers.add_parser("doctor", help="Check prerequisites (ngrok, AI CLIs)")
|
|
35
|
+
subparsers.add_parser("doctor", help="Check prerequisites (ngrok, AI CLIs, GitHub CLI)")
|
|
36
36
|
return parser
|
|
37
37
|
|
|
38
38
|
|
|
@@ -128,6 +128,10 @@ def run_doctor() -> None:
|
|
|
128
128
|
" ⚠️ AI CLI (claude/codex/gemini) not found. Install at least one. "
|
|
129
129
|
"(e.g. npm install -g @anthropic-ai/claude-code)"
|
|
130
130
|
)
|
|
131
|
+
if report.github_cli.installed:
|
|
132
|
+
print(" ✅ GitHub CLI: gh")
|
|
133
|
+
else:
|
|
134
|
+
print(" ⚠️ GitHub CLI (gh) not found. Install it before using /pr.")
|
|
131
135
|
|
|
132
136
|
|
|
133
137
|
if __name__ == "__main__":
|
|
@@ -7,6 +7,7 @@ from pydantic import BaseModel
|
|
|
7
7
|
from app import tunnel
|
|
8
8
|
|
|
9
9
|
AI_CLI_TOOLS = ("claude", "codex", "gemini")
|
|
10
|
+
GITHUB_CLI_TOOL = "gh"
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class AiCliStatus(BaseModel):
|
|
@@ -18,6 +19,7 @@ class PrerequisitesReport(BaseModel):
|
|
|
18
19
|
ngrok_ok: bool
|
|
19
20
|
ngrok_detail: str
|
|
20
21
|
ai_clis: list[AiCliStatus]
|
|
22
|
+
github_cli: AiCliStatus
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
def check_prerequisites() -> PrerequisitesReport:
|
|
@@ -34,4 +36,13 @@ def check_prerequisites() -> PrerequisitesReport:
|
|
|
34
36
|
AiCliStatus(name=tool, installed=shutil.which(tool) is not None)
|
|
35
37
|
for tool in AI_CLI_TOOLS
|
|
36
38
|
]
|
|
37
|
-
|
|
39
|
+
github_cli = AiCliStatus(
|
|
40
|
+
name=GITHUB_CLI_TOOL,
|
|
41
|
+
installed=shutil.which(GITHUB_CLI_TOOL) is not None,
|
|
42
|
+
)
|
|
43
|
+
return PrerequisitesReport(
|
|
44
|
+
ngrok_ok=ngrok_ok,
|
|
45
|
+
ngrok_detail=ngrok_detail,
|
|
46
|
+
ai_clis=ai_clis,
|
|
47
|
+
github_cli=github_cli,
|
|
48
|
+
)
|
|
@@ -6,7 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from app.ai.base import RunnerInput
|
|
7
7
|
from app.git.commit_message import CommitMessageFormatter
|
|
8
8
|
from app.jobs.plan_decisions import PlanDecisionQuestion, parse_plan_decisions
|
|
9
|
-
from app.jobs.schemas import Job, JobMode
|
|
9
|
+
from app.jobs.schemas import Job, JobMode, is_read_only_job_mode
|
|
10
10
|
from app.monitoring.events import EventLogger
|
|
11
11
|
|
|
12
12
|
_joblog = EventLogger("app.jobs.lifecycle", "job.lifecycle")
|
|
@@ -50,7 +50,7 @@ def run_job(manager, job_id: str) -> Job:
|
|
|
50
50
|
created_worktree_for_job = False
|
|
51
51
|
failed_stage: str | None = None
|
|
52
52
|
remote = manager._effective_git_remote_name()
|
|
53
|
-
read_only_job = job.request.mode
|
|
53
|
+
read_only_job = is_read_only_job_mode(job.request.mode)
|
|
54
54
|
plan_decision_questions: list[PlanDecisionQuestion] | None = None
|
|
55
55
|
try:
|
|
56
56
|
job.mark_running()
|
|
@@ -238,9 +238,7 @@ def run_job(manager, job_id: str) -> Job:
|
|
|
238
238
|
finally:
|
|
239
239
|
manager._cancel_events.pop(job_id, None)
|
|
240
240
|
manager._cancelled_job_ids.discard(job_id)
|
|
241
|
-
read_only_succeeded = (
|
|
242
|
-
job.request.mode in (JobMode.PLAN, JobMode.ASK) and job.status.value == "succeeded"
|
|
243
|
-
)
|
|
241
|
+
read_only_succeeded = is_read_only_job_mode(job.request.mode) and job.status.value == "succeeded"
|
|
244
242
|
cleanup_on_success = read_only_succeeded or not manager._effective_keep_worktree_on_success()
|
|
245
243
|
_joblog.info(
|
|
246
244
|
"job finalizing status=%s created_worktree=%s cleanup_on_success=%s",
|
|
@@ -26,9 +26,17 @@ class JobMode(StrEnum):
|
|
|
26
26
|
AGENT = "agent"
|
|
27
27
|
PLAN = "plan"
|
|
28
28
|
ASK = "ask"
|
|
29
|
+
RESEARCH = "research"
|
|
29
30
|
AGENT_FIX = "agent_fix"
|
|
30
31
|
|
|
31
32
|
|
|
33
|
+
READ_ONLY_JOB_MODES = frozenset({JobMode.PLAN, JobMode.ASK, JobMode.RESEARCH})
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def is_read_only_job_mode(mode: JobMode) -> bool:
|
|
37
|
+
return mode in READ_ONLY_JOB_MODES
|
|
38
|
+
|
|
39
|
+
|
|
32
40
|
class FixKind(StrEnum):
|
|
33
41
|
SOURCE = "source"
|
|
34
42
|
|
|
@@ -4,7 +4,7 @@ from dataclasses import dataclass
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
from app.git.service import GitWorktreeService
|
|
7
|
-
from app.jobs.schemas import Job,
|
|
7
|
+
from app.jobs.schemas import Job, is_read_only_job_mode
|
|
8
8
|
from app.monitoring.events import EventLogger
|
|
9
9
|
|
|
10
10
|
_joblog = EventLogger("app.jobs.lifecycle", "job.lifecycle")
|
|
@@ -27,7 +27,7 @@ def prepare_worktree_plan(
|
|
|
27
27
|
job_ctx: dict[str, object],
|
|
28
28
|
) -> WorktreePlan:
|
|
29
29
|
requested_branch = job.request.branch
|
|
30
|
-
if job.request.mode
|
|
30
|
+
if is_read_only_job_mode(job.request.mode):
|
|
31
31
|
path = git_service.prepare_detached_worktree(
|
|
32
32
|
project_path, job.id, worktree_base_dir=worktree_base
|
|
33
33
|
)
|
|
@@ -100,8 +100,9 @@ HELP_TEXT = "\n".join(
|
|
|
100
100
|
"- no commit",
|
|
101
101
|
"- plan: <natural language> or /plan <natural language> - plan mode (plan only; no code changes)",
|
|
102
102
|
"- ask: <natural language> or /ask <natural language> - ask mode (analysis and answers; no code edits)",
|
|
103
|
+
"- research: <natural language> or /research <natural language> - research mode (internet-backed answers; no code edits)",
|
|
103
104
|
"- fix: <natural language> or /fix - fix mode (reply to a job result; amends that commit)",
|
|
104
|
-
"- Korean aliases 계획:, 질문:, and 수정: instead of plan:/ask:/fix: (colons `:` or full-width `:` allowed)",
|
|
105
|
+
"- Korean aliases 계획:, 질문:, 조사:, and 수정: instead of plan:/ask:/research:/fix: (colons `:` or full-width `:` allowed)",
|
|
105
106
|
"",
|
|
106
107
|
"📋 Commands",
|
|
107
108
|
render_command_list(_HELP_COMMAND_ROWS),
|
|
@@ -122,7 +123,7 @@ HELP_AGENT_TOPIC = "\n".join(
|
|
|
122
123
|
"- model: codex branch: remote-auth strengthen tests",
|
|
123
124
|
"- no commit just verify the doc wording",
|
|
124
125
|
"",
|
|
125
|
-
"A job is accepted after project/branch/model checks
|
|
126
|
+
"A job is accepted after project/branch/model checks with inline Yes/No buttons.",
|
|
126
127
|
]
|
|
127
128
|
)
|
|
128
129
|
|
|
@@ -131,7 +132,7 @@ HELP_PLAN_TOPIC = "\n".join(
|
|
|
131
132
|
"📐 Plan mode (plan)",
|
|
132
133
|
"",
|
|
133
134
|
"Receive change plans only; no code edits. Like agent mode, a job is accepted after confirmation "
|
|
134
|
-
"
|
|
135
|
+
"with inline Yes/No buttons.",
|
|
135
136
|
"",
|
|
136
137
|
"💡 Examples",
|
|
137
138
|
"- plan: summarize the login validation flow",
|
|
@@ -147,7 +148,7 @@ HELP_ASK_TOPIC = "\n".join(
|
|
|
147
148
|
"❓ Ask mode (ask)",
|
|
148
149
|
"",
|
|
149
150
|
"Answer questions using the repository; no code edits, commits, or pushes. Jobs are accepted like "
|
|
150
|
-
"agent mode after confirmation
|
|
151
|
+
"agent mode after confirmation with inline Yes/No buttons.",
|
|
151
152
|
"",
|
|
152
153
|
"💡 Examples",
|
|
153
154
|
"- ask: how do I run pytest in this project?",
|
|
@@ -158,6 +159,22 @@ HELP_ASK_TOPIC = "\n".join(
|
|
|
158
159
|
]
|
|
159
160
|
)
|
|
160
161
|
|
|
162
|
+
HELP_RESEARCH_TOPIC = "\n".join(
|
|
163
|
+
[
|
|
164
|
+
"🔎 Research mode (research)",
|
|
165
|
+
"",
|
|
166
|
+
"Answer research questions using repository context and internet search when useful; no code edits, "
|
|
167
|
+
"commits, or pushes. Jobs are accepted like agent mode after confirmation with inline Yes/No buttons.",
|
|
168
|
+
"",
|
|
169
|
+
"💡 Examples",
|
|
170
|
+
"- research: compare webhook retry strategies for this service",
|
|
171
|
+
"- /research model: codex find current FastAPI deployment guidance",
|
|
172
|
+
"- 조사:Telegram webhook 보안 권장사항 조사",
|
|
173
|
+
"",
|
|
174
|
+
"See /help for more options.",
|
|
175
|
+
]
|
|
176
|
+
)
|
|
177
|
+
|
|
161
178
|
HELP_FIX_TOPIC = "\n".join(
|
|
162
179
|
[
|
|
163
180
|
"🔧 Fix mode (fix)",
|
|
@@ -17,6 +17,26 @@ from app.telegram.commands.base import (
|
|
|
17
17
|
from app.telegram.i18n import ui_message
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
_MISSING_GH_ERROR = "GitHub CLI (gh) is not installed or not available on PATH."
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _format_pr_error(exc: RuntimeError) -> str:
|
|
24
|
+
message = str(exc)
|
|
25
|
+
if _MISSING_GH_ERROR in message:
|
|
26
|
+
return ui_message(
|
|
27
|
+
"pr.gh_missing",
|
|
28
|
+
"/pr failed: GitHub CLI (gh) is not installed or not available on PATH.\n\n"
|
|
29
|
+
"To create PRs from Telegram:\n"
|
|
30
|
+
"1. Install GitHub CLI:\n"
|
|
31
|
+
" - macOS: `brew install gh`\n"
|
|
32
|
+
" - Windows: `winget install --id GitHub.cli`\n"
|
|
33
|
+
" - Ubuntu/Debian: `sudo apt install gh`\n"
|
|
34
|
+
"2. Sign in: `gh auth login`\n"
|
|
35
|
+
"3. Restart Remote AI Coder: `remote-coder up`",
|
|
36
|
+
)
|
|
37
|
+
return f"/pr failed: {message}"
|
|
38
|
+
|
|
39
|
+
|
|
20
40
|
class BranchCommand(TelegramCommand):
|
|
21
41
|
name = "/branch"
|
|
22
42
|
description = "Show the current branch or switch to a local branch"
|
|
@@ -261,7 +281,7 @@ class PrCommand(TelegramCommand):
|
|
|
261
281
|
try:
|
|
262
282
|
branches = self._load_pr_candidates(message, ctx)
|
|
263
283
|
except RuntimeError as exc:
|
|
264
|
-
return
|
|
284
|
+
return _format_pr_error(exc)
|
|
265
285
|
if not branches:
|
|
266
286
|
return ui_message(
|
|
267
287
|
"pr.no_candidates",
|
|
@@ -301,7 +321,7 @@ class PrCommand(TelegramCommand):
|
|
|
301
321
|
entry.root_path, remote, ""
|
|
302
322
|
)
|
|
303
323
|
except RuntimeError as exc:
|
|
304
|
-
return
|
|
324
|
+
return _format_pr_error(exc)
|
|
305
325
|
if branch not in remote_branches:
|
|
306
326
|
return ui_message(
|
|
307
327
|
"pr.remote_branch_missing",
|
|
@@ -314,7 +334,7 @@ class PrCommand(TelegramCommand):
|
|
|
314
334
|
try:
|
|
315
335
|
base_branch = ctx.git_service.resolve_integrate_branch(entry.root_path)
|
|
316
336
|
except RuntimeError as exc:
|
|
317
|
-
return
|
|
337
|
+
return _format_pr_error(exc)
|
|
318
338
|
|
|
319
339
|
title, body = self._build_pr_content(branch, project_name, message.chat_id, ctx)
|
|
320
340
|
|
|
@@ -327,7 +347,7 @@ class PrCommand(TelegramCommand):
|
|
|
327
347
|
body,
|
|
328
348
|
)
|
|
329
349
|
except RuntimeError as exc:
|
|
330
|
-
return
|
|
350
|
+
return _format_pr_error(exc)
|
|
331
351
|
|
|
332
352
|
return f"PR created:\n{pr_url}"
|
|
333
353
|
|
|
@@ -37,7 +37,7 @@ class CommandRegistry:
|
|
|
37
37
|
ctx.confirmation_store.pop(scope_project, message.chat_id)
|
|
38
38
|
return init_cmd.execute(message, ctx)
|
|
39
39
|
|
|
40
|
-
if head in {"/plan", "/ask", "/fix"}:
|
|
40
|
+
if head in {"/plan", "/ask", "/research", "/fix"}:
|
|
41
41
|
return None
|
|
42
42
|
|
|
43
43
|
pending = ctx.confirmation_store.get(scope_project, message.chat_id)
|
|
@@ -89,6 +89,13 @@ class CommandRegistry:
|
|
|
89
89
|
"command": "ask",
|
|
90
90
|
"description": translate_text("ask mode message (example: /ask explain the JobManager role)", language),
|
|
91
91
|
},
|
|
92
|
+
{
|
|
93
|
+
"command": "research",
|
|
94
|
+
"description": translate_text(
|
|
95
|
+
"research mode message (example: /research compare webhook retry strategies)",
|
|
96
|
+
language,
|
|
97
|
+
),
|
|
98
|
+
},
|
|
92
99
|
]
|
|
93
100
|
|
|
94
101
|
|
|
@@ -6,6 +6,7 @@ from app.telegram.commands.base import (
|
|
|
6
6
|
HELP_ASK_TOPIC,
|
|
7
7
|
HELP_FIX_TOPIC,
|
|
8
8
|
HELP_PLAN_TOPIC,
|
|
9
|
+
HELP_RESEARCH_TOPIC,
|
|
9
10
|
HELP_TEXT,
|
|
10
11
|
CommandContext,
|
|
11
12
|
InlineButton,
|
|
@@ -98,6 +99,9 @@ class StartCommand(TelegramCommand):
|
|
|
98
99
|
InlineButton("AGENTS mode", "/help agent"),
|
|
99
100
|
InlineButton("PLAN mode", "/help plan"),
|
|
100
101
|
InlineButton("ASK mode", "/help ask"),
|
|
102
|
+
],
|
|
103
|
+
[
|
|
104
|
+
InlineButton("RESEARCH mode", "/help research"),
|
|
101
105
|
InlineButton("FIX mode", "/help fix"),
|
|
102
106
|
],
|
|
103
107
|
],
|
|
@@ -121,7 +125,13 @@ class HelpCommand(TelegramCommand):
|
|
|
121
125
|
tokens = message.text.strip().split()
|
|
122
126
|
if len(tokens) >= 2:
|
|
123
127
|
raw = tokens[1]
|
|
124
|
-
topic_aliases = {
|
|
128
|
+
topic_aliases = {
|
|
129
|
+
"에이전트": "agent",
|
|
130
|
+
"계획": "plan",
|
|
131
|
+
"질문": "ask",
|
|
132
|
+
"조사": "research",
|
|
133
|
+
"수정": "fix",
|
|
134
|
+
}
|
|
125
135
|
topic = topic_aliases.get(raw, raw.lower())
|
|
126
136
|
if topic in ("agent", "agents"):
|
|
127
137
|
return HELP_AGENT_TOPIC
|
|
@@ -129,6 +139,8 @@ class HelpCommand(TelegramCommand):
|
|
|
129
139
|
return HELP_PLAN_TOPIC
|
|
130
140
|
if topic == "ask":
|
|
131
141
|
return HELP_ASK_TOPIC
|
|
142
|
+
if topic == "research":
|
|
143
|
+
return HELP_RESEARCH_TOPIC
|
|
132
144
|
if topic == "fix":
|
|
133
145
|
return HELP_FIX_TOPIC
|
|
134
146
|
if len(tokens) >= 2 and self._registry is not None:
|
|
@@ -7,7 +7,7 @@ from fastapi import BackgroundTasks
|
|
|
7
7
|
from app.ai.model_catalog import format_model_selection
|
|
8
8
|
from app.ai.usage import format_token_usage
|
|
9
9
|
from app.jobs.manager import JobManager
|
|
10
|
-
from app.jobs.schemas import Job,
|
|
10
|
+
from app.jobs.schemas import Job, JobRequest, is_read_only_job_mode
|
|
11
11
|
from app.monitoring.events import EventLogger
|
|
12
12
|
from app.telegram.conversation import SQLiteConversationStore
|
|
13
13
|
|
|
@@ -26,7 +26,7 @@ def format_job_result_memory_summary(final_job: Job) -> str:
|
|
|
26
26
|
token_usage = format_token_usage(final_job.runner_token_usage)
|
|
27
27
|
if token_usage:
|
|
28
28
|
summary += f" tokens={token_usage}"
|
|
29
|
-
if final_job.request.mode
|
|
29
|
+
if is_read_only_job_mode(final_job.request.mode) and final_job.runner_stdout_summary:
|
|
30
30
|
preview = final_job.runner_stdout_summary[:_JOB_RESULT_MEMORY_READ_ONLY_STDOUT_PREVIEW]
|
|
31
31
|
summary += f" stdout_preview={preview}"
|
|
32
32
|
return summary
|
|
@@ -177,7 +177,12 @@ class NaturalFlow:
|
|
|
177
177
|
) -> dict[str, str]:
|
|
178
178
|
cc = req.command_context
|
|
179
179
|
cc.confirmation_store.pop(req.scope_project, req.chat_id)
|
|
180
|
-
|
|
180
|
+
mode_prefixes = {
|
|
181
|
+
JobMode.PLAN.value: "/plan",
|
|
182
|
+
JobMode.ASK.value: "/ask",
|
|
183
|
+
JobMode.RESEARCH.value: "/research",
|
|
184
|
+
}
|
|
185
|
+
mode_prefix = mode_prefixes.get(pending.action, "/ask")
|
|
181
186
|
try:
|
|
182
187
|
parsed_request = self._parser.parse_natural(
|
|
183
188
|
f"{mode_prefix} {req.message.text}",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from app.ai.model_catalog import format_model_selection
|
|
4
|
-
from app.jobs.schemas import JobMode, JobRequest
|
|
4
|
+
from app.jobs.schemas import JobMode, JobRequest, is_read_only_job_mode
|
|
5
5
|
from app.telegram.commands import InlineButton
|
|
6
6
|
from app.telegram.commands import NAV_CLOSE_CALLBACK
|
|
7
7
|
|
|
@@ -24,10 +24,8 @@ def format_natural_job_confirmation(
|
|
|
24
24
|
f"- Work branch: {current_branch}",
|
|
25
25
|
f"- Model: {format_model_selection(request.model, request.model_id)}",
|
|
26
26
|
]
|
|
27
|
-
if request.mode
|
|
28
|
-
lines.append("- Mode:
|
|
29
|
-
elif request.mode is JobMode.ASK:
|
|
30
|
-
lines.append("- Mode: ask (read-only, no commit/push)")
|
|
27
|
+
if is_read_only_job_mode(request.mode):
|
|
28
|
+
lines.append(f"- Mode: {request.mode.value} (read-only, no commit/push)")
|
|
31
29
|
else:
|
|
32
30
|
lines.append("- Mode: agent (may edit code, commit, and push)")
|
|
33
31
|
if request.branch:
|
|
@@ -81,6 +79,12 @@ def format_mode_input_prompt(mode: JobMode) -> str:
|
|
|
81
79
|
"Example: Explain the JobManager flow\n"
|
|
82
80
|
"Example: model: codex How do I run pytest?"
|
|
83
81
|
)
|
|
82
|
+
if mode is JobMode.RESEARCH:
|
|
83
|
+
return (
|
|
84
|
+
"Send the research question to run in research mode.\n\n"
|
|
85
|
+
"Example: Compare FastAPI deployment options for this project\n"
|
|
86
|
+
"Example: model: codex Research the safest webhook retry strategy"
|
|
87
|
+
)
|
|
84
88
|
raise AssertionError(mode)
|
|
85
89
|
|
|
86
90
|
|
|
@@ -98,4 +102,3 @@ def format_fix_requires_reply_message() -> str:
|
|
|
98
102
|
"Example: reply to a job result, then send /fix\n"
|
|
99
103
|
"Example: reply to a job result with fix: add missing tests"
|
|
100
104
|
)
|
|
101
|
-
|
|
@@ -180,8 +180,13 @@ class WebhookUpdateHandler:
|
|
|
180
180
|
if pending_result is not None:
|
|
181
181
|
return pending_result
|
|
182
182
|
|
|
183
|
-
if message_head_lower in {"/plan", "/ask"} and len(message_tokens) == 1:
|
|
184
|
-
|
|
183
|
+
if message_head_lower in {"/plan", "/ask", "/research"} and len(message_tokens) == 1:
|
|
184
|
+
mode_by_command = {
|
|
185
|
+
"/plan": JobMode.PLAN,
|
|
186
|
+
"/ask": JobMode.ASK,
|
|
187
|
+
"/research": JobMode.RESEARCH,
|
|
188
|
+
}
|
|
189
|
+
mode = mode_by_command[message_head_lower]
|
|
185
190
|
return self._natural_flow_factory().prompt_for_mode_instruction(req, mode)
|
|
186
191
|
|
|
187
192
|
if message_head_lower == "/fix" and len(message_tokens) == 1:
|
|
@@ -120,12 +120,14 @@ def command_parse_error_empty_instruction_plan_ask(language: UiLanguage) -> str:
|
|
|
120
120
|
return (
|
|
121
121
|
"작업 지시문이 비어 있습니다.\n\n"
|
|
122
122
|
"예: plan: 로그인 수정 계획 세워줘\n"
|
|
123
|
-
"예: /ask JobManager 흐름
|
|
123
|
+
"예: /ask JobManager 흐름 설명해줘\n"
|
|
124
|
+
"예: /research webhook 보안 권장사항 조사"
|
|
124
125
|
)
|
|
125
126
|
return (
|
|
126
127
|
"The work instruction is empty.\n\n"
|
|
127
128
|
"Example: plan: outline the login refactor\n"
|
|
128
|
-
"Example: /ask explain JobManager routing"
|
|
129
|
+
"Example: /ask explain JobManager routing\n"
|
|
130
|
+
"Example: /research compare webhook security guidance"
|
|
129
131
|
)
|
|
130
132
|
|
|
131
133
|
|
|
@@ -191,6 +193,7 @@ _TEXT_REPLACEMENTS_KO_TO_EN_RAW: tuple[tuple[str, str], ...] = (
|
|
|
191
193
|
("옵션", "Options"),
|
|
192
194
|
("계획 모드", "plan mode"),
|
|
193
195
|
("질문 모드", "ask mode"),
|
|
196
|
+
("조사 모드", "research mode"),
|
|
194
197
|
("명령어 목록:", "Commands:"),
|
|
195
198
|
("메뉴와 프로젝트 상태를 확인합니다", "Show the menu and project status"),
|
|
196
199
|
("사용 가능한 명령어를 확인합니다", "Show available commands"),
|
|
@@ -233,6 +236,7 @@ _TEXT_REPLACEMENTS_KO_TO_EN_RAW: tuple[tuple[str, str], ...] = (
|
|
|
233
236
|
),
|
|
234
237
|
("계획 모드 메시지", "plan mode message"),
|
|
235
238
|
("질문 모드 메시지", "ask mode message"),
|
|
239
|
+
("조사 모드 메시지", "research mode message"),
|
|
236
240
|
("로그인 흐름 검토", "review login flow"),
|
|
237
241
|
("역할 설명", "explain the role"),
|
|
238
242
|
("기본 모델 변경", "Change the default model"),
|
|
@@ -305,6 +309,7 @@ _TEXT_REPLACEMENTS_KO_TO_EN_RAW: tuple[tuple[str, str], ...] = (
|
|
|
305
309
|
("🤖 AGENTS 모드 (agent)", "🤖 AGENTS mode (agent)"),
|
|
306
310
|
("📐 Plan 모드 (plan)", "📐 Plan mode (plan)"),
|
|
307
311
|
("❓ Ask 모드 (ask)", "❓ Ask mode (ask)"),
|
|
312
|
+
("🔎 Research 모드 (research)", "🔎 Research mode (research)"),
|
|
308
313
|
("🔧 Fix 모드 (fix)", "🔧 Fix mode (fix)"),
|
|
309
314
|
("💡 팁: 작업 결과에 reply 후 `fix: ...`를 보내면 그 커밋을 보완합니다.", "💡 Tip: Reply to a job result and send `fix: ...` to amend that commit."),
|
|
310
315
|
("등록 정보 없음", "not registered"),
|
|
@@ -376,14 +381,8 @@ _TEXT_REPLACEMENTS_KO_TO_EN_RAW: tuple[tuple[str, str], ...] = (
|
|
|
376
381
|
("기억 저장소가 설정되지 않았습니다.", "Memory storage is not configured."),
|
|
377
382
|
("현재 할 작업을 확인하세요.", "Confirm the work to run."),
|
|
378
383
|
("실행 여부를 선택하세요.", "Choose whether to run it."),
|
|
379
|
-
(
|
|
380
|
-
|
|
381
|
-
"새 자연어 요청으로 이 확인을 바꿀 수 있습니다. "
|
|
382
|
-
"파싱되지 않는 입력은 대기 작업이 취소됩니다.",
|
|
383
|
-
"Send `y` or `Y` to run. Another natural-language request replaces this confirmation. "
|
|
384
|
-
"Unparseable input cancels the pending request.",
|
|
385
|
-
),
|
|
386
|
-
("실행하려면 `y` 또는 `Y`를 입력하세요. 그 외 응답은 취소됩니다.", "Send `y` or `Y` to run it. Any other response cancels it."),
|
|
384
|
+
("인라인 Yes/No 버튼으로 확인한 뒤 작업이 접수됩니다.", "A job is accepted after confirmation with inline Yes/No buttons."),
|
|
385
|
+
("인라인 Yes/No 버튼으로 작업 실행 여부를 선택하세요.", "Choose whether to run with inline Yes/No buttons."),
|
|
387
386
|
("작업 요청을 취소했습니다.", "Cancelled the work request."),
|
|
388
387
|
("알 수 없는 clear 작업입니다.", "Unknown clear action."),
|
|
389
388
|
("봇에 연결된 프로젝트가 없거나 레지스트리에서 찾을 수 없습니다.", "No project is bound to this bot or found in the registry."),
|
|
@@ -413,6 +412,7 @@ _TEXT_REPLACEMENTS_KO_TO_EN_RAW: tuple[tuple[str, str], ...] = (
|
|
|
413
412
|
("로컬 브랜치가 없습니다:", "No local branch:"),
|
|
414
413
|
("plan 모드로 실행할 작업 지시문을 보내주세요.", "Send the instruction to run in plan mode."),
|
|
415
414
|
("ask 모드로 실행할 질문을 보내주세요.", "Send the question to run in ask mode."),
|
|
415
|
+
("research 모드로 실행할 조사 질문을 보내주세요.", "Send the research question to run in research mode."),
|
|
416
416
|
("읽기 전용 · 커밋·push 없음", "read-only — no commit/push"),
|
|
417
417
|
("코드 수정·커밋·push 가능", "allows edit, commit, and push"),
|
|
418
418
|
("요청 브랜치", "Requested branch"),
|
|
@@ -512,6 +512,16 @@ _MESSAGE_CATALOG_KO: dict[str, str] = {
|
|
|
512
512
|
"원격 브랜치 `{branch}`를 `{remote}`에서 찾을 수 없습니다. "
|
|
513
513
|
"Job 완료 후 삭제되었을 수 있습니다."
|
|
514
514
|
),
|
|
515
|
+
"pr.gh_missing": (
|
|
516
|
+
"/pr 실패: GitHub CLI(gh)가 설치되어 있지 않거나 PATH에서 찾을 수 없습니다.\n\n"
|
|
517
|
+
"Telegram에서 PR을 만들려면 다음 순서로 준비하세요.\n"
|
|
518
|
+
"1. GitHub CLI 설치:\n"
|
|
519
|
+
" - macOS: `brew install gh`\n"
|
|
520
|
+
" - Windows: `winget install --id GitHub.cli`\n"
|
|
521
|
+
" - Ubuntu/Debian: `sudo apt install gh`\n"
|
|
522
|
+
"2. GitHub 로그인: `gh auth login`\n"
|
|
523
|
+
"3. Remote AI Coder 재시작: `remote-coder up`"
|
|
524
|
+
),
|
|
515
525
|
"job.heartbeat": "{accepted}\n\n⏳ 실행 중 ({minutes}분 경과)",
|
|
516
526
|
"job.cancelled": "{mode_prefix}⛔ 작업 중단됨\n\n- Job ID: {job_id}{session_line}\n- 프로젝트: {project}",
|
|
517
527
|
"job.readonly_completed": (
|
|
@@ -634,6 +644,7 @@ _BUTTON_LABELS = {
|
|
|
634
644
|
"AGENTS mode": "AGENTS 모드",
|
|
635
645
|
"PLAN mode": "PLAN 모드",
|
|
636
646
|
"ASK mode": "ASK 모드",
|
|
647
|
+
"RESEARCH mode": "RESEARCH 모드",
|
|
637
648
|
}
|
|
638
649
|
|
|
639
650
|
|