@link-assistant/hive-mind 1.65.2 → 1.67.0

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.
@@ -0,0 +1,131 @@
1
+ zh
2
+ error "错误"
3
+ success "成功"
4
+ warning "警告"
5
+ failed "失败"
6
+ info "信息"
7
+ error.invalid_github_url "错误:GitHub URL 格式无效"
8
+ error.invalid_github_url_monitoring "错误:用于监控的 GitHub URL 无效"
9
+ error.missing_required_url "错误:缺少必需的 GitHub issue 或 pull request URL"
10
+ error.unable_determine_version "错误:无法确定版本"
11
+ error.invalid_url_type "错误:GitHub URL 格式无效"
12
+ error.url_type_not_supported "URL 类型 '{{type}}' 不受支持"
13
+ error.failed_to_get_current_user "错误:无法获取当前用户"
14
+ error.failed_to_initialize_repository "错误:无法初始化仓库"
15
+ error.failed_to_create_fork "多次重试后仍无法创建 fork"
16
+ error.fork_not_accessible "Fork 已存在但多次重试后仍无法访问"
17
+ error.failed_to_add_upstream_remote "无法添加 upstream 远程仓库"
18
+ error.failed_to_checkout "无法切换到 {{branch}}"
19
+ error.failed_to_sync "无法将 {{branch}} 与 upstream 同步"
20
+ error.failed_to_get_default_branch "无法获取默认分支名称"
21
+ error.failed_to_get_current_branch "无法获取当前分支"
22
+ error.failed_to_fetch_upstream "无法从 upstream 获取数据"
23
+ error.failed_to_add_pr_fork_remote "无法添加 pr-fork 远程仓库"
24
+ error.failed_to_fetch_from_pr_fork "无法从 pr-fork 获取数据"
25
+ error.pr_does_not_exist "错误:PR #{{prNumber}} 在 {{owner}}/{{repo}} 中不存在"
26
+ error.youtrack_url_detected_no_config "错误:检测到 YouTrack URL 但未找到 YouTrack 配置"
27
+ error.telegram_bot_token_not_set "错误:未设置 TELEGRAM_BOT_TOKEN 环境变量或 --token 选项"
28
+ error.invalid_github_url_solve "错误:solve 命令的 GitHub URL 无效"
29
+ error.invalid_language "无效的语言 '{{value}}'。支持的语言:{{supported}}"
30
+ success.readme_created "README.md 创建成功"
31
+ success.process_completed "进程已完成"
32
+ success.error_reported_to_sentry "错误已成功上报到 Sentry"
33
+ success.language_set "语言已设置为 {{language}}"
34
+ warning.session_log_not_found "警告:未找到会话 {{session}} 的日志,但将继续尝试恢复"
35
+ warning.failed_to_create_readme "失败:无法创建 README.md"
36
+ warning.could_not_determine_root_repository "无法确定根仓库"
37
+ warning.could_not_check_fork_status "警告:无法检查 fork 状态:{{message}}"
38
+ warning.no_linked_issue_found "警告:在 PR 内容中未找到关联的 issue"
39
+ warning.could_not_search_for_existing_prs "警告:无法搜索现有的 PR:{{message}}"
40
+ warning.could_not_get_current_user "警告:无法获取当前 GitHub 用户"
41
+ warning.could_not_check_github_permissions "警告:无法检查 GitHub 权限:{{message}}"
42
+ warning.could_not_determine_token_scopes "警告:无法从认证状态确定令牌范围"
43
+ warning.failed_to_fetch_branches "警告:无法从远程获取分支"
44
+ warning.error_during_auto_pr_creation "警告:自动创建 PR 时出错:{{message}}"
45
+ warning.could_not_convert_pr_to_draft "警告:无法将 PR 转换为草稿"
46
+ warning.could_not_check_convert_pr_draft_status "警告:无法检查/转换 PR 草稿状态"
47
+ warning.could_not_post_work_start_comment "警告:无法发布工作开始评论"
48
+ warning.could_not_post_work_end_comment "警告:无法发布工作结束评论"
49
+ warning.could_not_convert_pr_to_ready "警告:无法将 PR 转换为就绪状态"
50
+ warning.could_not_convert_pr_to_ready_status "警告:无法将 PR 状态转换为就绪"
51
+ warning.pr_created_but_no_url "警告:已创建 PR 但未返回 URL"
52
+ warning.could_not_assign_user "无法分配用户"
53
+ info.expected_github_url_format "预期格式:https://github.com/owner 或 https://github.com/owner/repo"
54
+ info.you_can_use_formats "您可以使用以下任意格式:"
55
+ info.format_https_owner "- https://github.com/owner"
56
+ info.format_https_owner_repo "- https://github.com/owner/repo"
57
+ info.format_http_owner "- http://github.com/owner(将转换为 https)"
58
+ info.format_github_owner "- github.com/owner(将添加 https://)"
59
+ info.format_owner "- owner(将转换为 https://github.com/owner)"
60
+ info.format_owner_repo "- owner/repo(将转换为 https://github.com/owner/repo)"
61
+ info.full_log_file "完整日志文件:{{path}}"
62
+ info.process_exited_with_code "进程退出代码:{{code}}"
63
+ info.current_configuration "当前配置:"
64
+ process.using_repository_fallback "使用按仓库回退方式处理 {{scope}}:{{owner}}"
65
+ process.fetching_repository_list "正在获取仓库列表……"
66
+ process.command "命令:{{command}}"
67
+ check.number "检查 #{{iteration}}:"
68
+ time.current "当前时间:{{time}}"
69
+ telegram.fetching_limits "🔄 正在获取使用限额……"
70
+ telegram.gathering_version "🔄 正在收集版本信息……"
71
+ telegram.usage_limits_title "📊 *使用限额*"
72
+ telegram.version_information_title "🤖 *版本信息*"
73
+ telegram.limits_only_in_groups "❌ /limits 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
74
+ telegram.version_only_in_groups "❌ /version 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
75
+ telegram.solve_only_in_groups "❌ {{commandDisplay}} 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
76
+ telegram.hive_only_in_groups "❌ /hive 命令仅在群聊中有效。请将本机器人加入群组并设为管理员。"
77
+ telegram.solve_disabled "❌ 此机器人实例已禁用 solve 命令。"
78
+ telegram.hive_disabled "❌ 此机器人实例已禁用 /hive 命令。"
79
+ telegram.no_github_link_in_reply "❌ 被回复的消息中未找到 GitHub issue/PR 链接。\n\n示例:使用 `/solve` 回复包含 GitHub issue 链接的消息\n\n或带选项:`/solve --model opus`"
80
+ telegram.invalid_options "❌ 无效选项:{{message}}\n\n使用 /help 查看可用选项"
81
+ telegram.invalid_isolation "❌ 无效的 --isolation 值 '{{value}}'。必须是:screen、tmux 或 docker"
82
+ telegram.invalid_locked_isolation "❌ 无效的锁定 --isolation 值 '{{value}}'。必须是:screen、tmux 或 docker"
83
+ telegram.option_syntax_check "请检查您的选项语法。"
84
+ telegram.url_status_active "❌ 此 URL 状态为 {{statusText}}。\n\nURL:{{url}}\n状态:{{status}}\n\n💡 使用 /solve_queue 查看队列状态。"
85
+ telegram.url_session_running "❌ 该 URL 已有正在运行的工作会话。\n\nURL:{{url}}\n会话:`{{session}}`\n\n💡 等待当前会话完成,或使用 /solve\\_stop 取消。"
86
+ telegram.solve_rejected "❌ Solve 命令被拒绝。\n\n{{infoBlock}}\n\n🚫 原因:{{reason}}"
87
+ telegram.language_invalid "❌ 语言无效。支持的语言:{{supported}}。\n用法:/language <代码>"
88
+ telegram.language_set "✅ 语言已设置为 *{{language}}*。"
89
+ telegram.language_current "🌐 当前语言:*{{language}}*。\n支持的语言:{{supported}}。\n用法:/language <代码>"
90
+ language.en "英语"
91
+ language.ru "俄语"
92
+ language.zh "中文"
93
+ language.hi "印地语"
94
+ prompt.user.issue_to_solve "要解决的问题:{{issueUrl}}"
95
+ prompt.user.issue_linked_to_pr "要解决的问题:与 PR #{{prNumber}} 关联的问题"
96
+ prompt.user.prepared_branch "为你准备的分支:{{branchName}}"
97
+ prompt.user.prepared_working_directory "为你准备的工作目录:{{tempDir}}"
98
+ prompt.user.prepared_tmp_directory "为你准备的临时目录(用于日志和下载):{{workspaceTmpDir}}"
99
+ prompt.user.prepared_pull_request "为你准备的 Pull Request:{{prUrl}}"
100
+ prompt.user.forked_repository "你的 fork 仓库:{{forkedRepo}}"
101
+ prompt.user.original_repository "原始仓库(upstream):{{owner}}/{{repo}}"
102
+ prompt.user.fork_actions_url "你 fork 上的 GitHub Actions:{{forkActionsUrl}}"
103
+ prompt.user.continue "继续。"
104
+ prompt.user.proceed "开始。"
105
+ prompt.system.intro '你是一个 AI 问题解决助手。当你研究问题时,优先进行根因分析。当你交流时,优先使用你亲自验证过的事实,或引用提供证据的来源(如引用代码或文档/网页链接)。当你不确定或基于假设时,自行验证或提出澄清问题。'
106
+ prompt.system.workspace_instructions '工作区临时目录。\n - 将 {{workspaceTmpDir}} 用于所有临时文件、日志和下载。\n - 将命令输出保存到文件时,保存到 {{workspaceTmpDir}}/command-output.log。\n - 下载 CI 日志时,保存到 {{workspaceTmpDir}}/ci-logs/。\n - 保存供审阅的 diff 时,保存到 {{workspaceTmpDir}}/diffs/。\n - 创建调试文件时,保存到 {{workspaceTmpDir}}/debug/。'
107
+ prompt.system.general_guidelines_header '通用准则。'
108
+ prompt.system.general_guidelines_body ' - 当你执行的命令输出变得很大时,将日志保存到文件以便复查。\n - 当你运行命令时,不要自行设置超时。让它们按需运行。默认 2 分钟超时通常足够,命令完成后再查看文件中的日志。\n - 当你运行 sudo 命令(尤其是 apt-get、yum 或 npm install 等包安装)时,请在后台运行以避免超时和需要终止进程时的权限错误。使用 run_in_background 参数或在命令末尾追加 &。'
109
+ prompt.system.issue_reporting ' - 当你在工作期间发现与主要任务无关的错误、bug 或小问题时,如果尚不存在则创建 issue 进行跟踪。Issue 应包含可复现的示例、最小可复现示例(如可能)、变通方法以及代码修复建议。对于当前仓库的 issue,使用 gh issue create --repo {{owner}}/{{repo}} --title "标题" --body "描述"。对于工作仓库使用的第三方 GitHub 仓库的 issue,使用 gh issue create --repo owner/repo --title "标题" --body "描述"。当可能存在类似 issue 时,先用 gh issue list --repo owner/repo --search "关键词" 查询以避免重复。如果已存在类似 issue,使用 gh issue comment <issue-number> --repo owner/repo --body "评论文本" 添加评论,描述你的具体情况,包括去除个人和敏感数据的匿名日志、复现步骤、最小可复现示例(如适用)、变通方法和修复建议。'
110
+ prompt.system.ci_investigation ' - 当 CI 失败或用户报告失败时,考虑在 todo 列表中加入详细调查协议:\n 第 1 步:使用以下命令列出近期运行(含时间戳):gh run list --repo {{owner}}/{{repo}} --branch {{branchName}} --limit 5 --json databaseId,conclusion,createdAt,headSha\n 第 2 步:通过检查时间戳和 SHA 验证运行是否在最新提交之后\n 第 3 步:对每个未通过的运行,下载日志以保留:gh run view {run-id} --repo {{owner}}/{{repo}} --log > ci-logs/{workflow}-{run-id}.log\n 第 4 步:使用 Read 工具读取每个下载的日志文件以了解实际失败\n 第 5 步:报告发现,包括日志中的具体错误和行号\n 当用户提到 CI 失败、要求查看日志、看到未通过状态或在收尾 PR 时,这种详细调查尤其有用。\n 注意:如果用户说"失败"但工具显示"通过",可能是数据陈旧——下载新日志并检查时间戳以解决差异。'
111
+ prompt.system.large_files " - 当代码或日志文件超过 1500 行时,按 1500 行为一段进行阅读。"
112
+ prompt.system.complex_problems " - 当面对复杂问题时,尽可能多地添加跟踪并打开所有详细模式。"
113
+ prompt.system.divide_and_conquer " - 当遇到极其困难的事情时,使用分而治之的方法。"
114
+ prompt.system.initial_research_header "初步研究。"
115
+ prompt.system.initial_research_body ' - 当你开始时,为自己制定详细计划,并按 todo 列表逐步执行。尽可能将本指南中相关的要点加入 todo 列表,以便清晰跟踪工作。\n - 当用户提到 CI 失败或要求调查日志时,考虑添加这些 todo:(1) 列出近期 CI 运行及时间戳;(2) 将失败运行的日志下载到 ci-logs/ 目录;(3) 分析错误信息并定位根因;(4) 实施修复;(5) 验证修复解决了日志中的具体错误。\n - 当你阅读 issue 时,仔细阅读所有细节和评论。\n - 当你在 issue 描述、PR 描述、评论或讨论中看到截图或图片时,先将图片下载为本地文件,然后用 Read 工具查看和分析。在用 Read 读取下载的图片之前,使用 file 等 CLI 工具确认它确实是图片而非 HTML。当损坏或非图片文件(例如保存为 .png 的 GitHub "Not Found" 页面)被读取时,可能引发 "Could not process image" 错误并导致 AI 解决器进程崩溃。当 file 命令显示 "HTML"、"text" 或 "ASCII text" 时,下载失败——不要对该文件调用 Read。改为:(1) 当图片来自 GitHub issue 或 PR(例如 URL 包含 "github.com/user-attachments"),重试:curl -L -H "Authorization: token $(gh auth token)" -o <文件名> "<url>";(2) 当重试仍失败,跳过该图片并注明不可用。\n - 当你需要 issue 详情时,使用 gh issue view https://github.com/{{owner}}/{{repo}}/issues/{{issueNumber}}。\n - 当你需要相关代码时,使用 gh search code --owner {{owner}} [关键词]。\n - 当你需要仓库上下文时,阅读你工作目录中的文件。'
116
+ prompt.system.explore_subagent " - 当你需要了解代码库结构、模式或工作原理时,使用 Task 工具并设置 subagent_type=Explore 来彻底探索代码库。"
117
+ prompt.system.check_sibling_prs " - 当你研究相关工作时,研究最近的相关 pull request。"
118
+ prompt.system.initial_research_tail ' - 当 issue 描述不够清晰时,写评论提出澄清问题。\n - 当访问 GitHub Gists(尤其是私有的)时,使用 gh gist view 命令而非直接 URL fetch,以确保正确的身份验证。\n - 当你修复 bug 时,先找出真正的根因并进行尽可能多的实验。\n - 当你修复 bug 而代码缺乏足够的跟踪或日志时,添加它们并保留在代码中(默认关闭)。\n - 当你需要 PR 上的评论时,请注意 GitHub 有三种不同的评论类型,每种都有不同的 API 端点:\n 1. PR 审阅评论(内联代码评论):gh api repos/{{owner}}/{{repo}}/pulls/{{prNumber}}/comments --paginate\n 2. PR 对话评论(一般讨论):gh api repos/{{owner}}/{{repo}}/issues/{{prNumber}}/comments --paginate\n 3. PR 审阅(批准/请求更改):gh api repos/{{owner}}/{{repo}}/pulls/{{prNumber}}/reviews --paginate\n 注意:命令 "gh pr view --json comments" 仅返回对话评论,会遗漏审阅评论。\n - 当你需要 issue 上的最新评论时,使用 gh api repos/{{owner}}/{{repo}}/issues/{{issueNumber}}/comments --paginate。'
119
+ prompt.system.general_purpose_subagent " - 当任务很大且需要处理许多文件或文件夹时,使用 `general-purpose` 子代理来委派工作。每个单独的文件或文件夹都可以委派给一个子代理以更高效地处理。"
120
+ prompt.system.case_studies " - 当你处理此 issue 时,在 ./docs/case-studies/issue-{{issueNumber}}/ 目录创建全面的案例研究。将与该 issue 相关的所有日志和数据下载到仓库。进行深入案例分析:在线搜索更多事实和数据,重建事件时间线,找出问题根因,并提出可能的解决方案。包含 README.md(执行摘要、问题陈述、时间线、根因)、TECHNICAL_SUMMARY.md(深度技术分析)、ANALYSIS.md(详细调查发现)、improvements.md(建议方案)和支持的日志/数据文件。"
121
+ prompt.system.solution_development '解决方案的开发与测试。\n - 当问题可解时,先创建复现该问题的测试,然后实施修复。\n - 当实现功能时,在代码库中搜索相似的现有实现,并将其作为示例,而不是从零开始重写一切。\n - 当编码时,将每个独立有用的原子步骤提交到 PR 分支,以便中断的工作仍保留在 PR 中。\n - 当你测试时:\n 从用单独脚本测试小函数开始;\n 使用带 mock 的单元测试快速起步。\n - 当你测试集成时,使用现有框架。\n - 当你测试解决方案草稿时,将自动检查纳入 PR。\n - 当你编写或修改测试时,考虑在测试、套件和 CI 作业级别设置合理的超时,使失败迅速暴露而非挂起。\n - 当你看到 CI 中重复的测试超时模式时,调查根因而不是增加超时。\n - 当 issue 不清楚时,写评论提出问题。\n - 当你遇到无法自行解决的问题且需要人类协助时,写评论到 PR 请求帮助。\n - 当你需要人类帮助时,使用 gh pr comment {{prNumber}} --body "你的消息" 在现有 PR 上发表评论。'
122
+ prompt.system.reproducible_testing "可复现测试。\n - 当修复 bug 时,在实施修复之前创建复现该问题的测试。如果无法复现问题,就无法验证修复。\n - 当遇到逻辑 bug 时,编写一个因 bug 而失败的自动化测试,然后实施修复使其通过。\n - 当遇到 UI bug 时,捕获显示问题状态的截图,然后在修复后创建视觉回归测试或人工验证截图。\n - 当创建测试时,优先使用最小可复现示例,即演示该问题的最简测试用例。\n - 当提交修复时,在 PR 描述中包含:(1) 如何复现问题;(2) 验证修复的自动化测试;(3) UI 问题的修复前/后截图。\n - 当 bug 修复没有复现测试时,将该修复视为不完整,因为以后可能在无人察觉时发生回归。"
123
+ prompt.system.preparing_pr '准备 pull request。\n - 当你编码时,遵循贡献指南。\n - 当你提交时,写清晰的提交消息。\n - 当你需要风格示例时,使用 gh pr list --repo {{owner}}/{{repo}} --state merged --search [关键词]。\n - 当你打开 PR 时,描述解决方案草稿并附带测试。\n - 当存在带版本号和自动发布 GitHub Actions 工作流的包时,在 PR 中更新版本号(或必要的发布触发条件)以为下次发布做准备。\n - 当你更新已有的 PR {{prNumber}} 时,使用 gh pr edit 修改标题和描述。\n - 当你即将提交或推送代码时,先在本地运行 CI 检查(如 ruff check、mypy、eslint 等),以便在推送前发现错误。\n - 当你完成 pull request 时:\n 在代码、标题和描述上沿用已合并 PR 的风格,\n 检查没有遗留与原始需求相对应的未提交更改,\n 检查默认分支已合并到 PR 分支,\n 在结束前检查所有 CI 检查(如有)已通过,\n 检查 issue 与 PR 上最新评论以确保未遗漏近期反馈,\n 仔细复核 PR 中所有变更确实满足 issue 的原始需求,\n 认真阅读 gh pr diff 以排查 PR 中新引入的 bug,\n 检查没有未经 issue 描述、issue 评论或 PR 评论明确请求而被移除的旧功能。\n - 当你完成实现时,使用 gh pr ready {{prNumber}}。'
124
+ prompt.system.workflow "工作流与协作。\n - 当你检查分支时,使用 git branch --show-current 验证。\n - 当你推送时,仅推送到分支 {{branchName}}。\n - 当你完成时,从分支 {{branchName}} 创建 PR。(注意:PR {{prNumber}} 已存在,请更新它)\n - 当你组织工作流时,使用 PR 而非直接合并到默认分支(main 或 master)。\n - 当你管理提交时,保留提交历史以便后续分析。\n - 当你贡献时,通过定期提交、推送和必要的回滚保持仓库历史向前推进。\n - 当你遇到自己无法解决的冲突时,请求帮助。\n - 当你协作时,遵守分支保护,仅在 {{branchName}} 上工作。\n - 当你提到结果时,附上 PR URL 或评论 URL。\n - 当你需要创建 PR 时,记住 PR {{prNumber}} 已存在于此分支。"
125
+ prompt.system.self_review_header "自我审阅。\n - 当你检查解决方案草稿时,在本地运行所有测试。\n - 当你检查解决方案草稿时,验证 git status 显示工作树干净,无未提交更改。\n - 当你与仓库风格比较时,使用 gh pr diff [编号]。\n - 当你完成时,确认代码、测试与描述一致。"
126
+ prompt.system.ensure_all_requirements " - 当未提供明确反馈或需求时,通过检查 issue 描述以及 issue 与 PR 上的所有评论,确保所有更改正确、一致、经过验证、测试、记录日志,并与所讨论的所有需求保持一致。检查所有 CI 或 CD 检查均已通过。"
127
+ prompt.system.github_cli_patterns 'GitHub CLI 命令模式。\n - 当从 GitHub API 获取列表时,使用 --paginate 标志确保返回所有结果(GitHub 默认每页最多 30 条)。\n - 当列出 PR 审阅评论(内联代码评论)时,使用 gh api repos/OWNER/REPO/pulls/NUMBER/comments --paginate。\n - 当列出 PR 对话评论时,使用 gh api repos/OWNER/REPO/issues/NUMBER/comments --paginate。\n - 当列出 PR 审阅时,使用 gh api repos/OWNER/REPO/pulls/NUMBER/reviews --paginate。\n - 当列出 issue 评论时,使用 gh api repos/OWNER/REPO/issues/NUMBER/comments --paginate。\n - 当添加 PR 评论时,使用 gh pr comment NUMBER --body "文本" --repo OWNER/REPO。\n - 当添加 issue 评论时,使用 gh issue comment NUMBER --body "文本" --repo OWNER/REPO。\n - 当查看 PR 详情时,使用 gh pr view NUMBER --repo OWNER/REPO。\n - 当用 jq 过滤时,使用 gh api repos/{{owner}}/{{repo}}/pulls/{{prNumber}}/comments --paginate --jq reverse-then-slice-first-five。'
128
+ prompt.system.playwright_mcp "Playwright MCP 用法(通过 mcp__playwright__* 工具进行浏览器自动化)。\n - 当你开发前端 Web 应用(HTML、CSS、JavaScript、React、Vue、Angular 等)时,使用 Playwright MCP 工具在真实浏览器中测试 UI。\n - 当 WebFetch 工具无法获取预期内容(例如返回空内容、JavaScript 渲染页面或受登录保护的页面)时,使用 Playwright MCP 工具(browser_navigate、browser_snapshot)作为浏览的备选方案。\n - 当 WebSearch 工具失败或结果不足时,使用 Playwright MCP 工具(browser_navigate、browser_snapshot)作为互联网搜索的备选方案。\n - 当你需要与需要执行 JavaScript 的动态网页交互时,使用 Playwright MCP 工具。\n - 当你需要可视化检查网页外观或截图时,使用 Playwright MCP 的 browser_take_screenshot。\n - 当你需要填表、点击按钮或在网页上执行用户操作时,使用 Playwright MCP 工具(browser_click、browser_type、browser_fill_form)。\n - 当你需要测试响应式设计或不同视口尺寸时,使用 Playwright MCP 的 browser_resize。\n - 当你完成浏览器操作时,调用 browser_close 释放资源。\n - 当复现 UI bug 时,使用 browser_take_screenshot 在实施修复之前捕获问题状态。\n - 当修复 UI bug 时,截取修复前/后的截图,为人工审阅提供视觉证据。\n - 当创建 UI 测试时,将基线截图保存到仓库以用于视觉回归测试。\n - 当验证 UI 修复时,比较截图以确保修复未引入意外的视觉变化。"
129
+ prompt.system.plan_subagent 'Plan 子代理用法。\n - 当你开始处理任务时,考虑使用 Plan 子代理研究代码库并制定实施计划。\n - 当使用 Plan 子代理时,可将其作为 todo 列表中的第一项。\n - 当你委托规划时,在开始实现工作前使用 Task 工具并设置 subagent_type="Plan"。'
130
+ prompt.system.visual_ui '视觉 UI 工作和截图。\n - 当你处理视觉 UI 更改(前端、CSS、HTML、设计)时,在 PR 描述中包含最终结果的渲染或截图。\n - 当你需要展示视觉结果时,截图并保存到仓库(例如 docs/screenshots/ 或 assets/ 文件夹)。\n - 当你将截图保存到仓库时,在 PR 描述 markdown 中使用永久链接(例如 https://github.com/{{screenshotRepoPath}}/blob/{{branchName}}/docs/screenshots/result.png?raw=true)。\n - 当你上传图片时,先将其提交到分支,然后使用带 ?raw=true 后缀的 GitHub blob URL 格式引用(适用于公开和私有仓库)。\n - 当视觉结果对审阅很重要时,在 PR 描述中以嵌入图片明确提及。\n - 当修复 UI bug 时,捕获"修复前"(问题)和"修复后"(已修复)的截图作为人工审阅证据。\n - 当报告 UI bug 时,附上问题状态的截图以便对修复进行视觉验证。\n - 当修复是视觉性的,在 PR 描述中并排或顺序对比修复前/后状态。\n - 当可能时,创建自动化视觉回归测试以防止 UI bug 复发。'
131
+ prompt.system.ci_examples "使用工作区临时目录的 CI 调查。\n - 当下载 CI 运行日志时:\n gh run view RUN_ID --repo {{owner}}/{{repo}} --log > {{workspaceTmpDir}}/ci-logs/run-RUN_ID.log\n - 当下载失败作业日志时:\n gh run view RUN_ID --repo {{owner}}/{{repo}} --log-failed > {{workspaceTmpDir}}/ci-logs/run-RUN_ID-failed.log\n - 当列出 CI 运行详情时:\n gh run list --repo {{owner}}/{{repo}} --branch {{branchName}} --limit 5 --json databaseId,conclusion,createdAt,headSha > {{workspaceTmpDir}}/ci-logs/recent-runs.json\n - 当保存 PR diff 用于审阅时:\n gh pr diff {{prNumber}} --repo {{owner}}/{{repo}} > {{workspaceTmpDir}}/diffs/pr-{{prNumber}}.diff\n - 当保存命令输出连同 stderr 时:\n npm test 2>&1 | tee {{workspaceTmpDir}}/test-output.log\n - 当调查 issue 详情时:\n gh issue view {{issueNumber}} --repo {{owner}}/{{repo}} --json body,comments > {{workspaceTmpDir}}/issue-{{issueNumber}}.json"
@@ -6,6 +6,7 @@
6
6
  import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mjs';
7
7
  import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.lib.mjs';
8
8
  import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
9
+ import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
9
10
 
10
11
  /**
11
12
  * Build the user prompt for OpenCode
@@ -258,7 +259,7 @@ Visual UI work and screenshots.
258
259
  - When the fix is visual, include side-by-side or sequential comparison of before/after states in the PR description.
259
260
  - When possible, create automated visual regression tests to prevent the UI bug from recurring.`
260
261
  : ''
261
- }${ciExamples}${getArchitectureCareSubPrompt(argv)}`;
262
+ }${ciExamples}${getArchitectureCareSubPrompt(argv)}${buildWorkLanguageDirective()}`;
262
263
  };
263
264
 
264
265
  // Export all functions as default object too
@@ -6,6 +6,7 @@
6
6
  import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mjs';
7
7
  import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.lib.mjs';
8
8
  import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
9
+ import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
9
10
 
10
11
  /**
11
12
  * Build the user prompt for Qwen Code
@@ -244,7 +245,7 @@ Visual UI work and screenshots.
244
245
  - When the fix is visual, include side-by-side or sequential comparison of before/after states in the PR description.
245
246
  - When possible, create automated visual regression tests to prevent the UI bug from recurring.`
246
247
  : ''
247
- }${ciExamples}${getArchitectureCareSubPrompt(argv)}`;
248
+ }${ciExamples}${getArchitectureCareSubPrompt(argv)}${buildWorkLanguageDirective()}`;
248
249
  };
249
250
 
250
251
  export default {
package/src/review.mjs CHANGED
@@ -101,6 +101,21 @@ const createReviewYargsConfig = yargsInstance =>
101
101
  description: 'Execute the AI tool using bunx (experimental, may improve speed and memory usage)',
102
102
  default: false,
103
103
  })
104
+ .option('language', {
105
+ type: 'string',
106
+ description: 'Default language for both --ui-language and --work-language (en, ru, zh, hi). Defaults to detected system locale.',
107
+ choices: ['en', 'ru', 'zh', 'hi'],
108
+ })
109
+ .option('ui-language', {
110
+ type: 'string',
111
+ description: 'Language for user-facing output (en, ru, zh, hi). Defaults to --language.',
112
+ choices: ['en', 'ru', 'zh', 'hi'],
113
+ })
114
+ .option('work-language', {
115
+ type: 'string',
116
+ description: 'Working language passed to the AI tool (en, ru, zh, hi). Defaults to --language.',
117
+ choices: ['en', 'ru', 'zh', 'hi'],
118
+ })
104
119
  .check(parsed => {
105
120
  if (!parsed['pr-url'] && !parsed.prUrl && !parsed._?.[0]) {
106
121
  throw new Error('The GitHub pull request URL is required');
@@ -128,6 +143,14 @@ const prUrl = argv['pr-url'] || argv.prUrl || argv._[0];
128
143
  // Set global verbose mode for log function
129
144
  global.verboseMode = argv.verbose;
130
145
 
146
+ // Initialize i18n based on --language / --ui-language / --work-language
147
+ const { initI18n } = await import('./i18n.lib.mjs');
148
+ await initI18n({
149
+ language: argv.language,
150
+ uiLanguage: argv.uiLanguage,
151
+ workLanguage: argv.workLanguage,
152
+ });
153
+
131
154
  // Create permanent log file immediately with timestamp
132
155
  const scriptDir = path.dirname(process.argv[1]);
133
156
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
@@ -552,6 +552,21 @@ export const SOLVE_OPTION_DEFINITIONS = {
552
552
  description: '[EXPERIMENTAL] Enable live progress monitoring. Accepts "comment" (default, updates a per-session PR comment) or "pr" (updates PR description). Plain --working-session-live-progress means "comment". Works with or without --interactive-mode.',
553
553
  default: false,
554
554
  },
555
+ language: {
556
+ type: 'string',
557
+ description: 'Default language for both --ui-language and --work-language (en, ru, zh, hi). Defaults to detected system locale. Each track can be overridden independently.',
558
+ choices: ['en', 'ru', 'zh', 'hi'],
559
+ },
560
+ 'ui-language': {
561
+ type: 'string',
562
+ description: 'Language for user-facing output (en, ru, zh, hi). Affects terminal status/error messages and bot-generated PR/issue comments. Defaults to --language.',
563
+ choices: ['en', 'ru', 'zh', 'hi'],
564
+ },
565
+ 'work-language': {
566
+ type: 'string',
567
+ description: "Working language passed to the AI tool (Claude/Codex/etc). Used as the tool's preferred language for translations and prompts. Defaults to --language.",
568
+ choices: ['en', 'ru', 'zh', 'hi'],
569
+ },
555
570
  };
556
571
 
557
572
  // Function to create yargs configuration - avoids duplication
package/src/solve.mjs CHANGED
@@ -84,6 +84,16 @@ try {
84
84
  }
85
85
  global.verboseMode = argv.verbose;
86
86
 
87
+ // Initialize i18n based on --language / --ui-language / --work-language
88
+ // (or detected system locale). --language sets both tracks by default;
89
+ // --ui-language and --work-language can override each track independently.
90
+ const { initI18n } = await import('./i18n.lib.mjs');
91
+ await initI18n({
92
+ language: argv.language,
93
+ uiLanguage: argv.uiLanguage,
94
+ workLanguage: argv.workLanguage,
95
+ });
96
+
87
97
  setupVerboseLogInterceptor(); // Issue #1466: capture [VERBOSE] output in log files
88
98
  setupStdioLogInterceptor(); // Issue #1549: capture ALL terminal output in log file
89
99
 
@@ -85,6 +85,21 @@ export const createYargsConfig = yargsInstance =>
85
85
  choices: ['text', 'json'],
86
86
  default: 'text',
87
87
  })
88
+ .option('language', {
89
+ type: 'string',
90
+ description: 'Default language for both --ui-language and --work-language (en, ru, zh, hi). Defaults to detected system locale.',
91
+ choices: ['en', 'ru', 'zh', 'hi'],
92
+ })
93
+ .option('ui-language', {
94
+ type: 'string',
95
+ description: 'Language for user-facing output (en, ru, zh, hi). Defaults to --language.',
96
+ choices: ['en', 'ru', 'zh', 'hi'],
97
+ })
98
+ .option('work-language', {
99
+ type: 'string',
100
+ description: 'Working language passed to the AI tool (en, ru, zh, hi). Defaults to --language.',
101
+ choices: ['en', 'ru', 'zh', 'hi'],
102
+ })
88
103
  .check(argv => {
89
104
  if (!argv['task-input'] && !argv._[0]) {
90
105
  throw new Error('Please provide a GitHub issue URL or task description');
package/src/task.mjs CHANGED
@@ -50,6 +50,14 @@ try {
50
50
  process.exit(1);
51
51
  }
52
52
 
53
+ // Initialize i18n based on --language / --ui-language / --work-language
54
+ const { initI18n } = await import('./i18n.lib.mjs');
55
+ await initI18n({
56
+ language: argv.language,
57
+ uiLanguage: argv.uiLanguage,
58
+ workLanguage: argv.workLanguage,
59
+ });
60
+
53
61
  const taskInput = argv['task-input'] || argv.taskInput || argv._[0];
54
62
  const selectedModel = argv.model || getDefaultTaskModel(argv.tool);
55
63
  const modelValidation = validateModelName(selectedModel, argv.tool);
@@ -297,6 +297,12 @@ await initializeSentry({
297
297
  environment: process.env.NODE_ENV || 'production',
298
298
  });
299
299
 
300
+ // Initialize i18n: pre-load every supported locale so per-user translations
301
+ // can resolve synchronously from the cache when handling Telegram updates.
302
+ const { initI18n, t, preloadAllLocales, resolveLocaleFromTelegramCtx } = await import('./i18n.lib.mjs');
303
+ await initI18n();
304
+ await preloadAllLocales();
305
+
300
306
  const telegrafModule = await use('telegraf');
301
307
  const { Telegraf } = telegrafModule;
302
308
 
@@ -410,6 +416,19 @@ function mergeArgsWithOverrides(userArgs, overrides) {
410
416
  return [...filteredArgs, ...overrides];
411
417
  }
412
418
 
419
+ // Inject --language LOCALE into spawn args if no language flag is already present.
420
+ // Issue #378: telegram bot resolves the user's effective locale and propagates
421
+ // it to spawned solve/hive sessions so the AI tool replies in the same language.
422
+ function injectLanguageIfMissing(args, locale) {
423
+ if (!locale || !args || !Array.isArray(args)) return args;
424
+ const langFlags = new Set(['--language', '--ui-language', '--work-language']);
425
+ for (const arg of args) {
426
+ const flag = arg.startsWith('--') ? arg.split('=')[0] : null;
427
+ if (flag && langFlags.has(flag)) return args;
428
+ }
429
+ return [...args, '--language', locale];
430
+ }
431
+
413
432
  /** Validate GitHub URL for Telegram bot commands. Returns { valid, error?, parsed?, normalizedUrl? } */
414
433
  async function getCommandUrlArg(args, createYargsConfig, positionalNames) {
415
434
  const parsedUrl = createYargsConfig ? await getFirstParsedPositionalArg(args, yargs, createYargsConfig, positionalNames) : null;
@@ -564,6 +583,7 @@ bot.command('help', async ctx => {
564
583
  message += '`/solve_queue` - Show solve queue status\n';
565
584
  message += '*/limits* - Show usage limits\n';
566
585
  message += '*/version* - Show bot and runtime versions\n';
586
+ message += '*/language* `[en|ru|zh|hi]` - Set or show your preferred reply language (in-memory only, per-user)\n';
567
587
  message += '`/accept_invites` - Accept all pending GitHub invitations\n';
568
588
  message += '*/merge* - Merge queue (experimental)\n';
569
589
  message += 'Usage: `/merge <github-repo-url>`\n';
@@ -621,11 +641,12 @@ bot.command('limits', async ctx => {
621
641
  return;
622
642
  }
623
643
 
644
+ const userLocale = resolveLocaleFromTelegramCtx(ctx);
624
645
  if (!_isGroupChat(ctx)) {
625
646
  if (VERBOSE) {
626
647
  console.log('[VERBOSE] /limits ignored: not a group chat');
627
648
  }
628
- await ctx.reply('❌ The /limits command only works in group chats. Please add this bot to a group and make it an admin.', { reply_to_message_id: ctx.message.message_id });
649
+ await ctx.reply(t('telegram.limits_only_in_groups', {}, { locale: userLocale }), { reply_to_message_id: ctx.message.message_id });
629
650
  return;
630
651
  }
631
652
 
@@ -638,7 +659,7 @@ bot.command('limits', async ctx => {
638
659
  }
639
660
 
640
661
  // Send "fetching" message to indicate work is in progress
641
- const fetchingMessage = await ctx.reply('🔄 Fetching usage limits...', {
662
+ const fetchingMessage = await ctx.reply(t('telegram.fetching_limits', {}, { locale: userLocale }), {
642
663
  reply_to_message_id: ctx.message.message_id,
643
664
  });
644
665
 
@@ -651,7 +672,7 @@ bot.command('limits', async ctx => {
651
672
  const solveQueue = getSolveQueue({ verbose: VERBOSE });
652
673
  const queueStatus = await solveQueue.formatStatus();
653
674
  const codexSection = formatCodexLimitsSection(limits.codex.success ? limits.codex : null, codexError);
654
- const message = '📊 *Usage Limits*\n\n' + formatUsageMessage(limits.claude.success ? limits.claude.usage : null, limits.disk.success ? limits.disk.diskSpace : null, limits.github.success ? limits.github.githubRateLimit : null, limits.cpu.success ? limits.cpu.cpuLoad : null, limits.memory.success ? limits.memory.memory : null, claudeError, [codexSection, queueStatus]);
675
+ const message = t('telegram.usage_limits_title', {}, { locale: userLocale }) + '\n\n' + formatUsageMessage(limits.claude.success ? limits.claude.usage : null, limits.disk.success ? limits.disk.diskSpace : null, limits.github.success ? limits.github.githubRateLimit : null, limits.cpu.success ? limits.cpu.cpuLoad : null, limits.memory.success ? limits.memory.memory : null, claudeError, [codexSection, queueStatus]);
655
676
  await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, message, { parse_mode: 'Markdown' });
656
677
  });
657
678
  bot.command('version', async ctx => {
@@ -663,16 +684,20 @@ bot.command('version', async ctx => {
663
684
  data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
664
685
  });
665
686
  if (isOldMessage(ctx) || isForwardedOrReply(ctx)) return;
666
- if (!_isGroupChat(ctx)) return await ctx.reply('❌ The /version command only works in group chats. Please add this bot to a group and make it an admin.', { reply_to_message_id: ctx.message.message_id });
687
+ const versionLocale = resolveLocaleFromTelegramCtx(ctx);
688
+ if (!_isGroupChat(ctx)) return await ctx.reply(t('telegram.version_only_in_groups', {}, { locale: versionLocale }), { reply_to_message_id: ctx.message.message_id });
667
689
  if (!isTopicAuthorized(ctx)) return await ctx.reply(buildAuthErrorMessage(ctx), { reply_to_message_id: ctx.message.message_id });
668
- const fetchingMessage = await ctx.reply('🔄 Gathering version information...', {
690
+ const fetchingMessage = await ctx.reply(t('telegram.gathering_version', {}, { locale: versionLocale }), {
669
691
  reply_to_message_id: ctx.message.message_id,
670
692
  });
671
693
  const result = await getVersionInfo(VERBOSE);
672
694
  if (!result.success) return await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, `❌ ${escapeMarkdownV2(result.error, { preserveCodeBlocks: true })}`, { parse_mode: 'MarkdownV2' });
673
- await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, '🤖 *Version Information*\n\n' + formatVersionMessage(result.versions), { parse_mode: 'Markdown' });
695
+ await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, t('telegram.version_information_title', {}, { locale: versionLocale }) + '\n\n' + formatVersionMessage(result.versions), { parse_mode: 'Markdown' });
674
696
  });
675
697
 
698
+ const { registerLanguageCommand } = await import('./telegram-language-command.lib.mjs');
699
+ registerLanguageCommand(bot, { VERBOSE, isOldMessage, isForwardedOrReply });
700
+
676
701
  const { registerAcceptInvitesCommand } = await import('./telegram-accept-invitations.lib.mjs');
677
702
  const sharedCommandOpts = { VERBOSE, isOldMessage, isForwardedOrReply, isGroupChat: _isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage, addBreadcrumb, isChatStopped, getStoppedChatRejectMessage };
678
703
  registerAcceptInvitesCommand(bot, sharedCommandOpts);
@@ -683,7 +708,7 @@ const { handleSolveQueueCommand } = registerSolveQueueCommand(bot, { ...sharedCo
683
708
  const { registerSubscribeCommands } = await import('./telegram-subscribers.lib.mjs'); // #1688
684
709
  registerSubscribeCommands(bot, sharedCommandOpts);
685
710
  const { registerTaskCommands } = await import('./telegram-task-command.lib.mjs');
686
- const { handleTaskCommand, TASK_COMMAND_NAMES } = registerTaskCommands(bot, { ...sharedCommandOpts, taskEnabled, safeReply, executeAndUpdateMessage });
711
+ const { handleTaskCommand, TASK_COMMAND_NAMES } = registerTaskCommands(bot, { ...sharedCommandOpts, taskEnabled, safeReply, executeAndUpdateMessage, resolveLocale: resolveLocaleFromTelegramCtx });
687
712
 
688
713
  // Named handler for /solve command - extracted for reuse by text-based fallback (issue #1207)
689
714
  async function handleSolveCommand(ctx) {
@@ -704,11 +729,12 @@ async function handleSolveCommand(ctx) {
704
729
  },
705
730
  });
706
731
 
732
+ const solveLocale = resolveLocaleFromTelegramCtx(ctx);
707
733
  if (!solveEnabled) {
708
734
  if (VERBOSE) {
709
735
  console.log(`[VERBOSE] ${solveCommandDisplay} ignored: command disabled`);
710
736
  }
711
- await ctx.reply(' The solve command is disabled on this bot instance.');
737
+ await ctx.reply(t('telegram.solve_disabled', {}, { locale: solveLocale }));
712
738
  return;
713
739
  }
714
740
 
@@ -737,7 +763,7 @@ async function handleSolveCommand(ctx) {
737
763
  if (VERBOSE) {
738
764
  console.log(`[VERBOSE] ${solveCommandDisplay} ignored: not a group chat`);
739
765
  }
740
- await ctx.reply(`❌ The ${solveCommandDisplay} command only works in group chats. Please add this bot to a group and make it an admin.`, { reply_to_message_id: ctx.message.message_id });
766
+ await ctx.reply(t('telegram.solve_only_in_groups', { commandDisplay: solveCommandDisplay }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
741
767
  return;
742
768
  }
743
769
 
@@ -802,7 +828,7 @@ async function handleSolveCommand(ctx) {
802
828
  if (VERBOSE) {
803
829
  console.log('[VERBOSE] No GitHub URL found in replied message');
804
830
  }
805
- await safeReply(ctx, ' No GitHub issue/PR link found in the replied message.\n\nExample: Reply to a message containing a GitHub issue link with `/solve`\n\nOr with options: `/solve --model opus`', { reply_to_message_id: ctx.message.message_id });
831
+ await safeReply(ctx, t('telegram.no_github_link_in_reply', {}, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
806
832
  return;
807
833
  }
808
834
  }
@@ -811,7 +837,7 @@ async function handleSolveCommand(ctx) {
811
837
 
812
838
  const { malformed, errors: malformedErrors } = detectMalformedFlags(userArgs);
813
839
  if (malformed.length > 0) {
814
- await safeReply(ctx, `❌ ${escapeMarkdown(malformedErrors.join('\n'))}\n\nPlease check your option syntax.`, { reply_to_message_id: ctx.message.message_id });
840
+ await safeReply(ctx, `❌ ${escapeMarkdown(malformedErrors.join('\n'))}\n\n${t('telegram.option_syntax_check', {}, { locale: solveLocale })}`, { reply_to_message_id: ctx.message.message_id });
815
841
  return;
816
842
  }
817
843
 
@@ -828,13 +854,13 @@ async function handleSolveCommand(ctx) {
828
854
  userArgs = moveArgumentToFront(userArgs, validation.normalizedUrl, cleanNonPrintableChars);
829
855
  const { backend: solvePerCommandIsolation, filteredArgs: userArgsWithoutIsolation } = extractIsolationFromArgs(userArgs); // issue #1534
830
856
  if (solvePerCommandIsolation && !isValidPerCommandIsolation(solvePerCommandIsolation)) {
831
- await safeReply(ctx, `❌ Invalid --isolation value '${escapeMarkdown(solvePerCommandIsolation)}'. Must be: screen, tmux, or docker`, { reply_to_message_id: ctx.message.message_id });
857
+ await safeReply(ctx, t('telegram.invalid_isolation', { value: escapeMarkdown(solvePerCommandIsolation) }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
832
858
  return;
833
859
  }
834
860
  const mergedSolveArgs = mergeArgsWithOverrides(userArgsWithoutIsolation, solveOverrides);
835
861
  const { backend: solveOverrideIsolation, filteredArgs: args } = extractIsolationFromArgs(mergedSolveArgs);
836
862
  if (solveOverrideIsolation && !isValidPerCommandIsolation(solveOverrideIsolation)) {
837
- await safeReply(ctx, `❌ Invalid locked --isolation value '${escapeMarkdown(solveOverrideIsolation)}'. Must be: screen, tmux, or docker`, { reply_to_message_id: ctx.message.message_id });
863
+ await safeReply(ctx, t('telegram.invalid_locked_isolation', { value: escapeMarkdown(solveOverrideIsolation) }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
838
864
  return;
839
865
  }
840
866
  const effectiveSolveIsolation = solveOverrideIsolation || solvePerCommandIsolation;
@@ -863,7 +889,7 @@ async function handleSolveCommand(ctx) {
863
889
  }
864
890
  const { malformed: mergedMalformed, errors: mergedMalformedErrors } = detectMalformedFlags(args);
865
891
  if (mergedMalformed.length > 0) {
866
- await safeReply(ctx, `❌ ${escapeMarkdown(mergedMalformedErrors.join('\n'))}\n\nPlease check your option syntax.`, { reply_to_message_id: ctx.message.message_id });
892
+ await safeReply(ctx, `❌ ${escapeMarkdown(mergedMalformedErrors.join('\n'))}\n\n${t('telegram.option_syntax_check', {}, { locale: solveLocale })}`, { reply_to_message_id: ctx.message.message_id });
867
893
  return;
868
894
  }
869
895
  // Validate merged arguments using solve's yargs config
@@ -871,7 +897,7 @@ async function handleSolveCommand(ctx) {
871
897
  try {
872
898
  parsedSolveArgs = await parseArgsWithYargs(args, yargs, createSolveYargsConfig);
873
899
  } catch (error) {
874
- await safeReply(ctx, `❌ Invalid options: ${escapeMarkdown(error.message || String(error))}\n\nUse /help to see available options`, {
900
+ await safeReply(ctx, t('telegram.invalid_options', { message: escapeMarkdown(error.message || String(error)) }, { locale: solveLocale }), {
875
901
  reply_to_message_id: ctx.message.message_id,
876
902
  });
877
903
  return;
@@ -909,20 +935,20 @@ async function handleSolveCommand(ctx) {
909
935
  const existingItem = solveQueue.findByUrl(normalizedUrl);
910
936
  if (existingItem) {
911
937
  const statusText = existingItem.status === 'starting' || existingItem.status === 'started' ? 'being processed' : 'already in the queue';
912
- await safeReply(ctx, `❌ This URL is ${statusText}.\n\nURL: ${escapeMarkdown(normalizedUrl)}\nStatus: ${existingItem.status}\n\n💡 Use /solve_queue to check the queue status.`, { reply_to_message_id: ctx.message.message_id });
938
+ await safeReply(ctx, t('telegram.url_status_active', { statusText, url: escapeMarkdown(normalizedUrl), status: existingItem.status }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
913
939
  return;
914
940
  }
915
941
  // Issue #1567: Prevent concurrent sessions on the same PR/issue
916
942
  const activeSession = await hasActiveSessionForUrlAsync(normalizedUrl, VERBOSE);
917
943
  if (activeSession.isActive) {
918
- await safeReply(ctx, `❌ A working session is already running for this URL.\n\nURL: ${escapeMarkdown(normalizedUrl)}\nSession: \`${activeSession.sessionName}\`\n\n💡 Wait for the current session to complete, or use /solve\\_stop to cancel it.`, { reply_to_message_id: ctx.message.message_id });
944
+ await safeReply(ctx, t('telegram.url_session_running', { url: escapeMarkdown(normalizedUrl), session: activeSession.sessionName }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
919
945
  return;
920
946
  }
921
947
  const check = await solveQueue.canStartCommand({ tool: solveTool }); // Skip Claude limits for agent (#1159)
922
948
  const queueStats = solveQueue.getStats();
923
949
  // Handle rejection: threshold strategy is 'reject' — fail immediately (issue #1267)
924
950
  if (check.rejected) {
925
- await safeReply(ctx, `❌ Solve command rejected.\n\n${infoBlock}\n\n🚫 Reason: ${escapeMarkdown(check.rejectReason || 'Unknown')}`, { reply_to_message_id: ctx.message.message_id });
951
+ await safeReply(ctx, t('telegram.solve_rejected', { infoBlock, reason: escapeMarkdown(check.rejectReason || 'Unknown') }, { locale: solveLocale }), { reply_to_message_id: ctx.message.message_id });
926
952
  return;
927
953
  }
928
954
 
@@ -930,11 +956,13 @@ async function handleSolveCommand(ctx) {
930
956
  const solveUrlContext = validation.parsed ? { owner: validation.parsed.owner, repo: validation.parsed.repo, number: validation.parsed.number, type: validation.parsed.type, normalized: validation.parsed.normalized || normalizedUrl } : null;
931
957
 
932
958
  const toolQueuedCount = queueStats.queuedByTool[solveTool] || 0; // tool-specific queue count (#1551)
959
+ // Issue #378: propagate user's effective Telegram locale to the spawned solve session.
960
+ const argsWithLocale = injectLanguageIfMissing(args, solveLocale);
933
961
  if (check.canStart && toolQueuedCount === 0) {
934
962
  const startingMessage = await safeReply(ctx, formatStartingWorkSessionMessage({ infoBlock }), { reply_to_message_id: ctx.message.message_id });
935
- await executeAndUpdateMessage(ctx, startingMessage, 'solve', args, infoBlock, effectiveSolveIsolation, solveTool, solveUrlContext);
963
+ await executeAndUpdateMessage(ctx, startingMessage, 'solve', argsWithLocale, infoBlock, effectiveSolveIsolation, solveTool, solveUrlContext);
936
964
  } else {
937
- const queueItem = solveQueue.enqueue({ url: normalizedUrl, args, ctx, requester, infoBlock, tool: solveTool, perCommandIsolation: effectiveSolveIsolation, urlContext: solveUrlContext });
965
+ const queueItem = solveQueue.enqueue({ url: normalizedUrl, args: argsWithLocale, ctx, requester, infoBlock, tool: solveTool, perCommandIsolation: effectiveSolveIsolation, urlContext: solveUrlContext });
938
966
  let queueMessage = `📋 Solve command queued (${solveTool} queue position #${toolQueuedCount + 1})\n\n${infoBlock}`; // tool-specific position (#1551)
939
967
  if (check.reason) queueMessage += `\n\n⏳ Waiting: ${escapeMarkdown(check.reason)}`;
940
968
  const queuedMessage = await safeReply(ctx, queueMessage, { reply_to_message_id: ctx.message.message_id });
@@ -970,11 +998,12 @@ async function handleHiveCommand(ctx) {
970
998
  },
971
999
  });
972
1000
 
1001
+ const hiveLocale = resolveLocaleFromTelegramCtx(ctx);
973
1002
  if (!hiveEnabled) {
974
1003
  if (VERBOSE) {
975
1004
  console.log('[VERBOSE] /hive ignored: command disabled');
976
1005
  }
977
- await ctx.reply(' The /hive command is disabled on this bot instance.');
1006
+ await ctx.reply(t('telegram.hive_disabled', {}, { locale: hiveLocale }));
978
1007
  return;
979
1008
  }
980
1009
 
@@ -998,7 +1027,7 @@ async function handleHiveCommand(ctx) {
998
1027
  if (VERBOSE) {
999
1028
  console.log('[VERBOSE] /hive ignored: not a group chat');
1000
1029
  }
1001
- await ctx.reply('❌ The /hive command only works in group chats. Please add this bot to a group and make it an admin.', { reply_to_message_id: ctx.message.message_id });
1030
+ await ctx.reply(t('telegram.hive_only_in_groups', {}, { locale: hiveLocale }), { reply_to_message_id: ctx.message.message_id });
1002
1031
  return;
1003
1032
  }
1004
1033
 
@@ -1041,13 +1070,13 @@ async function handleHiveCommand(ctx) {
1041
1070
 
1042
1071
  const { backend: hivePerCommandIsolation, filteredArgs: normalizedArgsWithoutIsolation } = extractIsolationFromArgs(normalizedArgs); // issue #1534
1043
1072
  if (hivePerCommandIsolation && !isValidPerCommandIsolation(hivePerCommandIsolation)) {
1044
- await safeReply(ctx, `❌ Invalid --isolation value '${escapeMarkdown(hivePerCommandIsolation)}'. Must be: screen, tmux, or docker`, { reply_to_message_id: ctx.message.message_id });
1073
+ await safeReply(ctx, t('telegram.invalid_isolation', { value: escapeMarkdown(hivePerCommandIsolation) }, { locale: hiveLocale }), { reply_to_message_id: ctx.message.message_id });
1045
1074
  return;
1046
1075
  }
1047
1076
  const mergedHiveArgs = mergeArgsWithOverrides(normalizedArgsWithoutIsolation, hiveOverrides);
1048
1077
  const { backend: hiveOverrideIsolation, filteredArgs: args } = extractIsolationFromArgs(mergedHiveArgs);
1049
1078
  if (hiveOverrideIsolation && !isValidPerCommandIsolation(hiveOverrideIsolation)) {
1050
- await safeReply(ctx, `❌ Invalid locked --isolation value '${escapeMarkdown(hiveOverrideIsolation)}'. Must be: screen, tmux, or docker`, { reply_to_message_id: ctx.message.message_id });
1079
+ await safeReply(ctx, t('telegram.invalid_locked_isolation', { value: escapeMarkdown(hiveOverrideIsolation) }, { locale: hiveLocale }), { reply_to_message_id: ctx.message.message_id });
1051
1080
  return;
1052
1081
  }
1053
1082
  const effectiveHiveIsolation = hiveOverrideIsolation || hivePerCommandIsolation;
@@ -1097,7 +1126,9 @@ async function handleHiveCommand(ctx) {
1097
1126
  }
1098
1127
 
1099
1128
  const startingMessage = await safeReply(ctx, formatStartingWorkSessionMessage({ infoBlock }), { reply_to_message_id: ctx.message.message_id });
1100
- await executeAndUpdateMessage(ctx, startingMessage, 'hive', args, infoBlock, effectiveHiveIsolation, hiveTool);
1129
+ // Issue #378: propagate user's effective Telegram locale to the spawned hive session.
1130
+ const hiveArgsWithLocale = injectLanguageIfMissing(args, hiveLocale);
1131
+ await executeAndUpdateMessage(ctx, startingMessage, 'hive', hiveArgsWithLocale, infoBlock, effectiveHiveIsolation, hiveTool);
1101
1132
  }
1102
1133
 
1103
1134
  bot.command(/^hive$/i, handleHiveCommand);