atomcode-core 4.23.1

Open-source terminal AI coding agent
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
use std::borrow::Cow;
use super::messages::Msg;

pub(super) fn zh_cn(msg: Msg<'_>) -> Cow<'static, str> {
    match msg {
        Msg::WelcomeBannerLine1 =>
            "欢迎使用 AtomCode,请选择一项开始:".into(),
        Msg::WelcomeBannerLine2 =>
            "(↑↓ 切换,Enter 确认,Esc 跳过)".into(),
        Msg::WelcomeOptionCodingPlan => "配置 CodingPlan".into(),
        Msg::WelcomeOptionCodingPlanHint => "免费额度 · 推荐".into(),
        Msg::WelcomeOptionConfigureManually => "手动配置".into(),
        Msg::WelcomeOptionConfigureManuallyHint => "使用 API key".into(),
        Msg::WelcomeOptionSkip => "暂时跳过".into(),
        Msg::WelcomeOptionSkipHint => "稍后再说".into(),

        // ── /codingplan ──
        Msg::CodingPlanSetupFailed { error } =>
            format!("CodingPlan 设置失败:{error}").into(),
        Msg::CpReauthAfter401 =>
            "  ⚠ 登录凭证已失效 — 正在重新登录...\n".into(),
        Msg::ChatAuthExpired =>
            "认证已过期,请执行 /login 重新登录".into(),
        Msg::CpSetupHeader =>
            "  AtomCode CodingPlan 配置:\n\n".into(),
        Msg::CpLoggedIn { who, username, email } =>
            format!("  ✓ 已登录:{} ({}{})\n", who, username, email).into(),
        Msg::CpStepSkipped { reason } =>
            format!("{}\n", reason).into(),
        Msg::CpLoginFailed { error } =>
            format!("  ✗ 登录失败 — {}\n", error).into(),
        Msg::CpClaimed { message, plan_type } =>
            format!("  ✓ CodingPlan 已领取 — {}(CodingPlan {}\n", message, plan_type).into(),
        Msg::CpClaimSuccessFallback => "成功".into(),
        Msg::CpAlreadyClaimed { reason } =>
            format!("  ✓ CodingPlan 已领取 — {}\n", reason).into(),
        Msg::CpClaimFailed { error } =>
            format!("  ✗ CodingPlan 领取失败 — {}\n", error).into(),
        Msg::CpClaimTierSucceeded { tier } =>
            format!("  ✓ CodingPlan {} 领取成功\n", tier).into(),
        Msg::CpClaimTierAlreadyHeld { tier } =>
            format!("  ✓ CodingPlan {} 已领取\n", tier).into(),
        Msg::CpClaimTierFailed { tier, reason } =>
            format!("  ✗ CodingPlan {} 领取失败 — {}\n", tier, reason).into(),
        Msg::CpAddedProviders { count, plural_s: _ } =>
            format!("  ✓ 已添加 {} 个 Provider:\n", count).into(),
        Msg::CpLocked { name } =>
            // SGR 31 / 39 = 标准红前景 + 默认色重置。用标准色(不
            // 是亮色)让终端按当前主题映射 —— Solarized / Dracula /
            // 浅色模式都会落到各自的「红」上,不会被一个写死的 RGB
            // 锁住。retained 渲染器走严格 sanitizer 会把 SGR 剥光,
            // 但 `✗ … (需要升级成 Pro 以上套餐)` 文本本身仍能传达含义。
            format!("      \x1b[31m✗ {}  (需要升级成 Pro 以上套餐)\x1b[39m\n", name).into(),
        Msg::CpProviderRow { provider, model, default_suffix } =>
            format!("{}{}{}\n", provider, model, default_suffix).into(),
        Msg::CpDefaultSuffix => "  (默认)".into(),
        Msg::CpVisionAuto { kind } =>
            format!("  ✓ 视觉预处理器 → {}  (自动检测)\n", kind).into(),
        Msg::CpVisionUserSupplied { kind } =>
            format!("  ✓ 视觉预处理器 → {}  (保留用户设置)\n", kind).into(),
        Msg::CpVisionCleared =>
            "  ⚠ 视觉预处理器已清除 — 当前模型列表中没有可用的 VL/OCR 模型\n".into(),
        Msg::CpModelsSkipped { reason } =>
            format!("  ✓ 模型步骤已跳过 — {}\n", reason).into(),
        Msg::CpModelsFailed { error } =>
            format!("  ✗ 模型步骤失败 — {}\n", error).into(),
        Msg::CpStatusHeader =>
            "  ✓ CodingPlan 状态:\n".into(),
        Msg::CpPlanPending { plan } =>
            format!("      套餐:{}  ·  正在激活\n", plan).into(),
        Msg::CpPlanActive { plan, expires_at, remaining_days, total_days } =>
            format!(
                "      套餐:{}  ·  到期时间 {}(剩余 {}d / 共 {}d)\n",
                plan, expires_at, remaining_days, total_days,
            ).into(),
        Msg::CpUsageLine { usage, reset_at, duration } =>
            format!("      用量:{}  ·  重置于 {}{} 后)\n", usage, reset_at, duration).into(),
        Msg::CpWindowQuotaExhausted =>
            "      ⚠ 当前窗口配额已耗尽\n".into(),
        Msg::CpWindowQuotaHint { hint } =>
            format!("{}\n", hint).into(),
        Msg::CpStatusFetchSkipped { reason } =>
            format!("  ⚠ 状态获取已跳过 — {}\n", reason).into(),
        Msg::CpStatusFetchFailed { error } =>
            format!("  ⚠ 状态获取失败(非致命) — {}\n", error).into(),
        Msg::CpOfficialBuildRequired => Cow::Borrowed(
            "此功能需要官方 AtomCode 构建,请前往 \
             https://atomgit.com/atomgit_atomcode/atomcode/releases 下载安装。",
        ),
        Msg::CpAuthRequired => Cow::Borrowed(
            "未登录 AtomCode CodingPlan。请运行 /codingplan 完成登录后再发送请求。",
        ),
        Msg::CpSignStaleClockSkew => Cow::Borrowed(
            "请求被服务端拒绝:签名时间戳已过期。请校准本地系统时间(NTP 同步)后重试。",
        ),
        Msg::CpSignReplayPersisted => Cow::Borrowed(
            "请求多次被识别为重放,请重新运行命令。",
        ),
        Msg::CpSignVersionTooOld => Cow::Borrowed(
            "当前 AtomCode 版本过旧,已不兼容 CodingPlan。请升级 AtomCode 后继续使用。",
        ),
        Msg::CpUpgradeRequired => Cow::Borrowed(
            "需要升级才能继续使用 CodingPlan,请前往官方发布页安装最新版 AtomCode。",
        ),

        Msg::ErrUnsupportedLocale { input } =>
            format!("不支持的语言:{input}").into(),

        // ── 状态栏 ──
        Msg::StatusNoProvider =>
            "未配置 Provider · 使用 /provider 配置".into(),
        Msg::StatusUpgradeHint { version } =>
            format!("{version} 可用 · 使用 /upgrade 升级").into(),
        Msg::StatusModelNotConfigured =>
            "(未配置)".into(),
        Msg::StatusClipboardImageHint =>
            "剪贴板有图片 · ctrl+v 粘贴".into(),
        Msg::StatusClipboardImageHintSlash =>
            "剪贴板有图片 · /paste 粘贴".into(),

        // ── /status 命令主体 ──
        Msg::StatusBody { model, dir, config, tokens } =>
            format!(
                "  模型:    {}\n  目录:    {}\n  配置文件:{}\n  Token:   {}\n",
                model, dir, config, tokens,
            ).into(),
        Msg::StatusCpNotSignedIn =>
            "  CodingPlan:(未登录 — 运行 /codingplan 进行配置)\n".into(),
        Msg::StatusCpFetchFailed { error } =>
            format!("  CodingPlan:(状态获取失败 — {}\n", error).into(),
        Msg::StatusCpNoActive =>
            "  CodingPlan:(无激活套餐 — 运行 /codingplan)\n".into(),
        Msg::StatusCpLine { plan, expires_at, remaining_days, total_days } =>
            format!(
                "  CodingPlan:{}  ·  到期 {}{}d / 共 {}d)\n",
                plan, expires_at, remaining_days, total_days,
            ).into(),
        Msg::StatusCpUsage { usage, reset_at, seconds } =>
            format!("  用量:{}  ·  重置于 {}{}s 后)\n", usage, reset_at, seconds).into(),
        Msg::StatusCpWindowExhausted =>
            "  ⚠ 当前窗口配额已耗尽\n".into(),
        Msg::StatusCpWindowHint { hint } =>
            format!("{}\n", hint).into(),
        Msg::StatusInstructionFilesHeader =>
            "  指令文件:\n".into(),
        Msg::StatusInstructionPresent { path, label } =>
            format!("{} ({})\n", path, label).into(),
        Msg::StatusInstructionMissing { label } =>
            format!("{} — 未找到\n", label).into(),

        // ── /login 完成提示 ──
        Msg::LoginSignedInWithCpHint { name, username } =>
            format!(
                "  已登录:{}{})。现在可以开始对话;运行 /codingplan 同步最新的模型权限。\n",
                name, username,
            ).into(),

        // ── 帮助 ──
        Msg::HelpAvailableCommands =>
            "  可用命令:\n".into(),
        Msg::KeybindingsHelp => r#"  键盘快捷键

  ── 输入 ──
    Enter                            发送消息
    Ctrl+J                           插入换行(所有终端通用)
    \ 后接 Enter                     插入换行(atomcode 兜底,所有终端通用)
    Alt+Enter                        插入换行 *
    Shift+Enter                      插入换行 **
    /                                打开斜杠命令菜单
    Tab                              自动补全
    Backspace / Ctrl+H               删除上一个字符
    Delete / Ctrl+?                  删除下一个字符
    Ctrl+W                           删除前一个单词
    Ctrl+U                           清空当前行
    Ctrl+K                           删除到行尾
    Ctrl+A / Home                    跳到行首
    Ctrl+E / End                     跳到行尾
    Left / Right                     光标左右移动

  ── 历史 ──
    Up                               上一条输入
    Down                             下一条输入

  ── 会话 ──
    Ctrl+C                           取消当前轮 / 关闭弹层
    Ctrl+D                           退出 atomcode
    Ctrl+L                           清屏
    Ctrl+O                           切换工具实时输出
    Ctrl+V                           粘贴(文本 + 图片)

  ── 斜杠菜单 / 弹层导航 ──
    Up / Down                        移动选择
    Enter                            确认
    Esc                              取消 / 关闭弹层
    Tab                              插入当前高亮命令

  * Alt+Enter 在多数终端可用;macOS Apple Terminal 需在
    Settings → Profiles → Keyboard 启用 "Use Option as Meta key"
    才会发送换行。
  ** Shift+Enter 需要终端区分该按键,目前已知支持的有:
     Kitty / WezTerm / iTerm2(启用 Report Modifiers)/
     Windows Terminal / Ghostty / Warp。其他终端(包括 macOS
     Apple Terminal、默认 xterm、GNOME Terminal、VS Code 集成
     终端)不区分 Shift+Enter 与 Enter,请用 Ctrl+J 或 \ + Enter。

  提示:输入 /help 查看完整斜杠命令列表。
"#.into(),

        // ── Provider 向导 ──
        Msg::ProviderWizardHeader =>
            "  Provider 管理 — 添加 / 编辑 / 删除 / 设为默认。按 Esc 取消。\n".into(),
        Msg::ProviderWizardCancelled =>
            "(已取消)".into(),
        Msg::ProviderMenuAdd => "添加".into(),
        Msg::ProviderMenuAddDesc => "添加新 Provider".into(),
        Msg::ProviderMenuEdit => "编辑".into(),
        Msg::ProviderMenuEditDesc => "编辑已有 Provider".into(),
        Msg::ProviderMenuDelete => "删除".into(),
        Msg::ProviderMenuDeleteDesc => "移除 Provider".into(),
        Msg::ProviderMenuSetDefault => "设为默认".into(),
        Msg::ProviderMenuSetDefaultDesc => "切换默认 Provider".into(),
        Msg::ProviderNoProviders =>
            "尚未配置任何 Provider。".into(),
        Msg::ProviderDeleteConfirm { name } =>
            format!("删除 \"{name}\"?[y/N]").into(),
        Msg::ProviderDeleted { name } =>
            format!("已移除 \"{name}\"").into(),
        Msg::ProviderDeleteKept => "(已保留)".into(),
        Msg::ProviderDefaultSet { name } =>
            format!("默认已设为 {name}").into(),
        Msg::ProviderAdded { name, model } =>
            format!("已添加 Provider \"{name}\",并切换到 {name} · {model}").into(),
        Msg::ProviderUpdated { name } =>
            format!("已更新 \"{name}\"").into(),
        Msg::ProviderStepName => "Provider 名称?".into(),
        Msg::ProviderStepType => "类型?(openai / claude / ollama)".into(),
        Msg::ProviderStepTypeWithHint { current } =>
            format!("类型?[{current}](openai / claude / ollama,留空保持不变)").into(),
        Msg::ProviderStepBaseUrl =>
            "Base URL?(留空使用默认值)".into(),
        Msg::ProviderStepBaseUrlWithHint { current } =>
            format!("Base URL?[{current}](留空保持不变)").into(),
        Msg::ProviderDefaultHint => "Provider 默认值".into(),
        Msg::ProviderStepApiKey =>
            "API 密钥?(留空不设置)".into(),
        Msg::ProviderStepApiKeyWithHint { hint } =>
            format!("API 密钥?[{hint}]").into(),
        Msg::ProviderStepApiKeySet => "已设置 — 留空保持不变".into(),
        Msg::ProviderStepApiKeyUnset => "未设置".into(),
        Msg::ProviderStepModel => "模型?".into(),
        Msg::ProviderStepModelWithHint { current } =>
            format!("模型?[{current}](留空保持不变)").into(),
        Msg::ProviderNameEmpty => "名称不能为空。".into(),
        Msg::ProviderUnknownType =>
            "未知类型。请选择 openai / claude / ollama。".into(),
        Msg::ProviderUnknownTypeEdit =>
            "未知类型。请选择 openai / claude / ollama 或留空。".into(),
        Msg::ProviderModelEmpty => "模型不能为空。".into(),
        Msg::ProviderEditKeep => "(保持不变)".into(),

        // ── Model 选择器 ──
        Msg::ModelSwitched { provider, model } =>
            format!("  已切换到 {provider} · {model}\n").into(),

        // ── 会话选择器 ──
        Msg::SessionLoadFailed { error } =>
            format!("加载会话失败:{error}").into(),
        Msg::SessionResumedLabel { name } =>
            format!("已恢复:{name}").into(),
        Msg::SessionTimeJustNow => "刚刚".into(),
        Msg::SessionTimeMinAgo { n } => format!("{n}分钟前").into(),
        Msg::SessionTimeHourAgo { n } => format!("{n}小时前").into(),
        Msg::SessionTimeDayAgo { n } => format!("{n}天前").into(),
        Msg::SessionMsgCount { count } =>
            format!("{count} 条消息").into(),
        Msg::SessionNameEmpty =>
            "会话名不能为空".into(),
        Msg::SessionNameTooLong { max } =>
            format!("会话名过长(最多 {max} 个字符)").into(),
        Msg::SessionNameControlChars =>
            "会话名不能包含控制字符".into(),
        Msg::SessionListFailed { error } =>
            format!("列出会话失败:{error}").into(),
        Msg::SessionRenamed { old, new } =>
            format!("  已重命名:'{old}' -> '{new}'").into(),
        Msg::SessionSaveFailed { error } =>
            format!("保存会话失败:{error}。未持久化新名称。").into(),
        Msg::SessionNoneSelected =>
            "未选中会话".into(),
        Msg::SessionRenameEditing { buffer } =>
            format!("> {buffer}_  [Enter: 确认, Esc: 取消]").into(),

        // ── 目录选择器 ──
        Msg::DirCurrent => "当前".into(),
        Msg::DirNotExists { path } =>
            format!("目录已不存在:{path}").into(),
        Msg::DirChanged { path } =>
            format!("  已切换到:{path}\n").into(),
        Msg::DirNotADirectory { path } =>
            format!("不是目录:{path}").into(),

        // ── Issue 向导 ──
        Msg::IssueCancelled => "(已取消)".into(),
        Msg::IssueNewOn { owner, repo } =>
            format!("在 atomgit.com/{owner}/{repo} 创建 Issue").into(),
        Msg::IssueStep1 =>
            "步骤 1/2 — 输入标题(必填,按 Esc 取消):".into(),
        Msg::IssueStep2 =>
            "步骤 2/2 — 输入描述(Shift+Enter 换行,Enter 提交,Esc 取消):".into(),
        Msg::IssueTitleConfirmed { title } =>
            format!("✓ 标题:{title}").into(),
        Msg::IssueCreated { number, title, url } =>
            format!("  [issue] ✓ 已创建 #{number}{title}\n  {url}\n").into(),
        Msg::IssueCreateFailed { error } =>
            format!("  [issue] ✗ 创建失败:{error}\n").into(),
        Msg::IssueRequiredField { field } =>
            format!("(必填 — 请输入 {field},或按 Esc 取消)").into(),

        // ── 语言 ──
        Msg::LanguageSwitched { label, locale } =>
            format!("  ✓ 已切换语言为 {label}{locale})。\n").into(),

        // ── 空闲/引导提示 ──
        Msg::IdleHintPrefix =>
            "输入内容,或按 ".into(),
        Msg::IdleHintSlash => "/".into(),
        Msg::IdleHintSuffix =>
            " 浏览命令".into(),
        Msg::IdleHintFull =>
            "输入内容,或按 / 浏览命令".into(),
        Msg::IdleHintProvider => "/provider".into(),
        Msg::IdleHintProviderSuffix =>
            "添加自定义模型".into(),
        Msg::IdleHintProviderFull =>
            "使用 /provider 添加自定义模型".into(),
        Msg::IdleHintCodingplan => "/codingplan".into(),
        Msg::IdleHintCodingplanSuffix =>
            "领取免费 Token 额度".into(),
        Msg::IdleHintCodingplanFull =>
            "使用 /codingplan 领取免费 Token 额度".into(),

        // ── 斜杠命令 ──
        Msg::CmdSwitchedPlanMode =>
            "  已切换到 Plan 模式(只读探索)。\n".into(),
        Msg::CmdSwitchedBuildMode =>
            "  已切换到 Build 模式(完整执行)。\n".into(),
        Msg::CmdNewSession =>
            "  新会话已开始。\n".into(),
        Msg::CmdNoProviders =>
            "  未配置任何 Provider。\n".into(),
        Msg::CmdNoSessions =>
            "  未找到历史会话。请先开始一段对话。\n".into(),
        Msg::CmdUnknownCommand { name } =>
            format!("未知命令:/{name}").into(),
        Msg::CmdLoginFailed { error } =>
            format!("登录失败:{error}").into(),
        Msg::CmdLogoutDone =>
            "  已退出 AtomGit 登录。权限已刷新。\n".into(),
        Msg::CmdLogoutFailed { error } =>
            format!("退出登录失败:{error}").into(),
        Msg::CmdWhoamiNotSignedIn =>
            "  尚未登录。使用 /login 进行认证。\n".into(),
        Msg::CmdReloadDone { provider, model } =>
            format!("  配置已重载。当前:{provider} · {model}\n").into(),
        Msg::CmdReloadFailed { error } =>
            format!("重载失败:{error}(保留先前配置)").into(),
        Msg::CmdUndoNotSupported =>
            "  撤销功能暂不支持。\n".into(),
        Msg::CmdNoChanges =>
            "  (无变更)\n".into(),
        Msg::CmdCheckingUpdate =>
            "  正在检查更新...\n".into(),
        Msg::CmdNoActiveProvider =>
            "未配置活跃的 Provider。使用 /provider 添加一个。".into(),

        // ── 审批提示 ──
        Msg::ApprovalPromptAlt { tool, detail } =>
            format!("允许 {}{})?[Y]是 / [N]否 / [A]总是", tool, detail).into(),
        Msg::ApprovalWaitingLabel =>
            "▶ 等待审批:".into(),
        Msg::ApprovalAllow => " 允许  ".into(),
        Msg::ApprovalAlways => " 总是  ".into(),
        Msg::ApprovalDeny => " 拒绝".into(),

        // ── 取消 / 错误前缀 ──
        Msg::Cancelled => "(已取消)".into(),
        Msg::ErrorPrefix { msg } =>
            format!("[错误:{msg}]").into(),

        // ── 升级 ──
        Msg::UpgradeSuccess { from, to } =>
            format!("  ✓ 已升级 {}{}\n", from, to).into(),
        Msg::UpgradeManifestFetched { version } =>
            format!("  最新版本: {}\n", version).into(),
        Msg::UpgradeDownloading { pct, bytes, total } =>
            format!("  下载中 {}% ({} / {} bytes)\n", pct, bytes, total).into(),
        Msg::UpgradeVerifying =>
            "  正在校验 SHA256\n".into(),
        Msg::UpgradeReplacing =>
            "  正在替换二进制文件\n".into(),
        Msg::UpgradeDone { version, backup } =>
            format!("\n✓ 已升级到 {}(旧版本保留为 {}\n  正在重启新版本...\n", version, backup).into(),
        Msg::UpgradeAlreadyLatest { current, latest } =>
            format!(
                "  ✓ 已是最新版本,无需更新(当前 {},远端最新 {})。如需重装请加 --force。\n",
                current, latest
            ).into(),
        Msg::UpgradeFailed { error } =>
            format!("升级失败: {}", error).into(),
        Msg::UpgradeRolledBack { exe, backup } =>
            format!("\n✓ 已回滚。当前二进制: {};另一版本保存在 {}\n  正在重启回滚版本...\n", exe, backup).into(),

        // ── 终端键盘提示 ──
        Msg::KbdHintMacos =>
            "  ⚠ 终端不支持增强键盘协议。\n    请使用 Ctrl+Enter 插入换行(Shift+Enter 不可用)。\n\n".into(),
        Msg::KbdHintOther =>
            "  ⚠ 终端不支持增强键盘协议。\n    请使用 Alt+Enter 或 Ctrl+Enter 插入换行(Shift+Enter 不可用)。\n\n".into(),

        // ── JediTerm / conhost 回退 ──
        Msg::JediTermFallback =>
            "  ⓘ 检测到 JetBrains IDE 终端 — 运行在备用屏幕模式下。\n    \
            使用鼠标滚轮、PageUp/PageDown 或 Shift+Up/Down 滚动历史。\n    \
            AtomCode 运行期间无法使用宿主终端的原生回滚;\n    \
            退出后宿主终端将恢复到 AtomCode 之前的状态。\n    \
            设置 ATOMCODE_PLAIN=1 使用基础 CI 风格输出,或\n    \
            设置 ATOMCODE_RETAIN=1 绕过此回退(可能导致对齐问题)。\n\n".into(),
        Msg::LegacyConhostFallback =>
            "  ⓘ 检测到旧版 Windows 控制台 — 运行在备用屏幕模式下。\n    \
            使用鼠标滚轮、PageUp/PageDown 或 Shift+Up/Down 滚动历史。\n    \
            AtomCode 运行期间无法使用宿主终端的原生回滚。\n    \
            要获得完整的宿主终端回滚支持,请安装 Windows Terminal\n    \
            (免费,Microsoft Store)、ConEmu、Alacritty 或 WezTerm。\n    \
            设置 ATOMCODE_PLAIN=1 使用基础输出,或设置 ATOMCODE_RETAIN=1\n    \
            绕过此回退(滚动时可能出现重复内容)。\n\n".into(),

        // ── 后台任务 ──
        Msg::BackgroundComplete { turns } =>
            format!("  后台任务完成({} 轮):\n", turns).into(),
        Msg::BackgroundFailed { turns } =>
            format!("  后台任务失败,共 {} 轮:\n", turns).into(),
        Msg::BackgroundFilesEdited =>
            "  已编辑的文件:\n".into(),

        // ── /config ──
        Msg::ConfigProviderLabel { provider, path } =>
            format!("  Provider:{}\n  配置文件:{}\n\n", provider, path).into(),

        // ── /cost ──
        Msg::CostReport { prompt, completion, cached, cache_rate, total, cost } =>
            format!(
                "  提示 Token:       {}\n  补全 Token:       {}\n  缓存 Token:       {}{}% 命中率)\n  Token 总计:       {}\n  预估费用:         {}\n",
                prompt, completion, cached, cache_rate, total, cost
            ).into(),

        // ── /think ──
        Msg::ThinkStatus { status, budget, provider } =>
            format!(
                "  深度思考:{}\n  预算:{} Token\n  Provider:{}\n\n  用法:/think on | off | budget <N>\n",
                status, budget, provider
            ).into(),
        Msg::ThinkEnabled { budget } =>
            format!("  深度思考已启用(预算:{} Token)。\n", budget).into(),
        Msg::ThinkDisabled =>
            "  深度思考已禁用。\n".into(),
        Msg::ThinkBudgetSet { n } =>
            format!("  思考预算已设为 {} Token。\n", n).into(),
        Msg::ThinkBudgetTooSmall { n } =>
            format!("预算必须 >= 1024(当前 {}", n).into(),
        Msg::ThinkBudgetUsage =>
            "用法:/think budget <数字>".into(),
        Msg::ThinkUsage =>
            "  用法:/think [on | off | budget <N>]\n".into(),

        // ── /remember, /forget ──
        Msg::RememberUsage =>
            "用法:/remember <要记住的内容>(--global 为全局范围)".into(),
        Msg::ForgetUsage =>
            "用法:/forget <关键词>".into(),

        // ── /background ──
        Msg::BackgroundUsage =>
            "  用法:/background <任务描述>\n".into(),

        // ── /init ──
        Msg::InitAlreadyExists { path } =>
            format!("  {} 已存在。使用 `/init --force` 覆盖。\n", path).into(),
        Msg::InitWrote { path, bytes } =>
            format!("  已写入 {}{} 字节)。编辑以自定义;下一条消息生效。\n", path, bytes).into(),
        Msg::InitFailed { error } =>
            format!("  /init 失败:{}\n", error).into(),

        // ── /cd ──
        Msg::CdWorkingDir { cwd } =>
            format!("  工作目录:{}\n  无最近项目。使用 `/cd <路径>` 切换。\n", cwd).into(),

        // ── /diff ──
        Msg::DiffFailed { error } =>
            format!("git diff 失败:{}", error).into(),

        // ── /upgrade ──
        Msg::UpgradeUnknownArg { arg } =>
            format!("未知的 /upgrade 参数:{}\n  用法:/upgrade [rollback|--force]", arg).into(),

        // ── /skills ──
        Msg::SkillsNone =>
            "  没有可调用的技能。\n".into(),
        Msg::SkillsAvailable =>
            "  可用技能:\n".into(),
        Msg::SkillUnknown { name } =>
            format!("未知技能:{}(输入 /skills 查看列表)", name).into(),

        // ── /mcp ──
        Msg::McpReloading { count } =>
            format!("  正在重载 MCP 服务器...({} 个已配置)\n", count).into(),
        Msg::McpConnecting =>
            "  正在连接:\n".into(),
        Msg::McpConnectingServer { name } =>
            format!("    - {}  连接中...\n", name).into(),
        Msg::McpNoServersConfigured =>
            "  未配置 MCP 服务器。\n".into(),
        Msg::McpClearedReconnecting { removed } =>
            format!("  ✓ 已清除 {} 个 MCP 工具。正在后台重新连接...\n", removed).into(),
        Msg::McpClearedNoServers { removed } =>
            format!("  ✓ 已清除 {} 个 MCP 工具。无需连接。\n", removed).into(),
        Msg::McpToolsUsage =>
            "  用法:/mcp tools <服务器名>\n  示例:/mcp tools filesystem\n".into(),
        Msg::McpToolsListing { server } =>
            format!("  正在列出 '{}' 的 MCP 工具...\n", server).into(),
        Msg::McpNoRegistry =>
            "  MCP 注册表未加载。请先运行 /mcp reload。\n".into(),
        Msg::McpServersHeader =>
            "  MCP 服务器:\n".into(),
        Msg::McpReloadFailed { error } =>
            format!("MCP 重载失败:无法加载 .mcp.json / $ATOMCODE_HOME/mcp.json:{:#}", error).into(),
        // /mcp login / logout
        Msg::McpOAuthLoginUsage =>
            "  用法:/mcp login <服务名>\n  示例:/mcp login github\n".into(),
        Msg::McpOAuthLogoutUsage =>
            "  用法:/mcp logout <服务名>\n  示例:/mcp logout github\n".into(),
        Msg::McpOAuthLoadConfigFailed { error } =>
            format!("  MCP OAuth 登录失败:无法加载配置:{error}\n").into(),
        Msg::McpOAuthServerNotFound { server } =>
            format!("  MCP OAuth 登录失败:配置中未找到服务 '{server}'。\n").into(),
        Msg::McpOAuthStarting { server } =>
            format!("  正在浏览器中启动 '{server}' 的 MCP OAuth 流程...\n").into(),
        Msg::McpOAuthSaved { provider, server } =>
            format!("  已保存 MCP 服务 '{server}' 的 {provider} OAuth Token。运行 /mcp reload 完成连接。\n").into(),
        Msg::McpOAuthFailed { error } =>
            format!("  MCP OAuth 失败:{error}\n").into(),
        Msg::McpOAuthTokenRemoved { server } =>
            format!("  已移除 MCP 服务 '{server}' 保存的 OAuth Token。\n").into(),
        Msg::McpOAuthNoToken { server } =>
            format!("  未找到 MCP 服务 '{server}' 保存的 OAuth Token。\n").into(),
        Msg::McpOAuthLogoutFailed { error } =>
            format!("  MCP OAuth 登出失败:{error}\n").into(),
        // MCP / LSP 服务连接反馈
        Msg::McpServerConnected { name } =>
            format!("✓ MCP 服务 '{name}' 已连接").into(),
        Msg::McpServerFailed { name, error } =>
            format!("✗ MCP 服务 '{name}' 失败:{error}").into(),
        Msg::LspServerStarted { name, ext } =>
            format!("✓ LSP 服务 '{name}' 已为 .{ext} 启动").into(),
        Msg::LspServerFailed { name, ext, error } =>
            format!("✗ LSP 服务 '{name}'(.{ext})失败:{error}").into(),

        // ── /worktree ──
        Msg::WorktreeUsage =>
            "  用法:\n    /worktree create <分支> [基准]   创建工作树并切换\n    /worktree list                    列出所有工作树\n    /worktree done                    切回原始目录\n    /worktree cleanup <分支>          清理工作树\n".into(),
        Msg::WorktreeCreateUsage =>
            "  用法:/worktree create <分支> [基准]\n  示例:/worktree create fix-bug main\n".into(),
        Msg::WorktreeCreated { branch, base, path } =>
            format!("  ✓ 工作树已创建\n    分支:{}(基于 {}\n    路径:{}\n    工作目录已切换\n", branch, base, path).into(),
        Msg::WorktreeCreateFailed { error } =>
            format!("工作树创建失败:{}", error).into(),
        Msg::WorktreeNoActive =>
            "  没有活跃的工作树。\n".into(),
        Msg::WorktreeListFailed { error } =>
            format!("工作树列表失败:{}", error).into(),
        Msg::WorktreeActiveHeader =>
            "  活跃工作树:\n".into(),
        Msg::WorktreeHasChanges => "(有变更)".into(),
        Msg::WorktreeClean => "(无变更)".into(),
        Msg::WorktreeCurrent => " ← 当前".into(),
        Msg::WorktreeDoneBack { path } =>
            format!("  ✓ 工作目录已切回:{}\n", path).into(),
        Msg::WorktreeDoneMergeHint { branch } =>
            format!("  提示:使用 'git merge {}' 或创建 PR 合入主分支\n", branch).into(),
        Msg::WorktreeNoSession =>
            "  没有活跃的工作树会话。先使用 /worktree create 创建一个。\n".into(),
        Msg::WorktreeCleanupUsage =>
            "  用法:/worktree cleanup <分支> [--force]\n".into(),
        Msg::WorktreeCleaned { branch } =>
            format!("  ✓ 工作树 '{}' 已清理\n", branch).into(),
        Msg::WorktreeCleanedSwitched { path } =>
            format!("  工作目录已切回:{}\n", path).into(),
        Msg::WorktreeCleanupUncommitted { branch } =>
            format!("  ⚠ 工作树 '{}' 有未提交的变更。\n  使用 /worktree cleanup {} --force 强制清理\n", branch, branch).into(),
        Msg::WorktreeCleanupFailed { error } =>
            format!("工作树清理失败:{}", error).into(),

        // ── /help commands(自定义命令) ──
        Msg::HelpCustomCommandsHeader =>
            "  自定义命令:\n".into(),
        Msg::HelpCustomNone =>
            "    (无)\n\n".into(),
        Msg::HelpCustomCreateHint =>
            "  创建方式:~/.atomcode/commands/<名称>.md 或 .atomcode/commands/<名称>.md\n".into(),
        Msg::HelpSourceGlobal => "全局".into(),
        Msg::HelpSourceProject => "项目".into(),

        // ── /setup ──
        Msg::SetupHeader { installed, skipped, failed, duration_ms } =>
            format!("\n✅ Setup 完成 — {} 装好, {} 跳过, {} 失败  · 耗时 {}ms\n\n", installed, skipped, failed, duration_ms).into(),
        Msg::SetupInstalledLabel =>
            "已安装:\n".into(),
        Msg::SetupSkippedLabel =>
            "\n跳过:\n".into(),
        Msg::SetupFailedLabel =>
            "\n失败:\n".into(),
        Msg::SetupInstalledRow { kind, slug, path } =>
            format!("{}:{}{}\n", kind, slug, path).into(),
        Msg::SetupSkippedRow { kind, slug, reason } =>
            format!("  - {}:{} ({:?})\n", kind, slug, reason).into(),
        Msg::SetupFailedRow { kind, slug, error } =>
            format!("{}:{}{}\n", kind, slug, error).into(),
        Msg::CmdSetupTip =>
            "\u{1f4a1} 提示:运行 \x1b[1;96m/setup\x1b[0m 可自动为该项目配置 hooks、skills 和 MCP。".into(),
        Msg::CmdSetupRunning =>
            "正在运行 atomcode setup...".into(),
        Msg::CmdSetupSkillsReloaded { count } =>
            format!("  🔄 Skills 已重载 — {} 个可用", count).into(),
        Msg::CmdSetupError { error } =>
            format!("setup 错误:{error}").into(),
        Msg::CmdSetupRunningSkill =>
            "  🚀 正在运行 setup skill — 分析项目并生成推荐...".into(),
        Msg::CmdSetupSkillMissing =>
            "setup skill 未找到 — 请重新运行 /setup 以重新安装".into(),

        // ── /plugin ──
        Msg::PluginUsage =>
            "用法:/plugin [marketplace add|remove|update|list | install <p>@<m> | uninstall <p>@<m> | list]".into(),
        Msg::PluginMarketplaceUsage =>
            "用法:/plugin marketplace [add|remove|update|list] <参数>".into(),
        Msg::PluginInstallUsage =>
            "用法:/plugin install <插件>@<市场>".into(),
        Msg::PluginUninstallUsage =>
            "用法:/plugin uninstall <插件>@<市场>".into(),
        Msg::PluginNoMarketplaces =>
            "未注册任何市场".into(),
        Msg::PluginMarketplacesHeader =>
            "已注册的市场:".into(),
        Msg::PluginNoInstalled =>
            "未安装任何插件".into(),
        Msg::PluginInstalledHeader =>
            "已安装的插件:".into(),
        Msg::PluginMarketplaceCloning { url } =>
            format!("正在从 {url} 克隆 marketplace…").into(),
        Msg::PluginMarketplaceRemoved { name } =>
            format!("已移除 marketplace `{name}`").into(),
        Msg::PluginMarketplaceRemoveFailed { error } =>
            format!("移除 marketplace 失败:{error}").into(),
        Msg::PluginMarketplaceUpdating { name } =>
            format!("正在更新 marketplace `{name}`…").into(),
        Msg::PluginMarketplaceListFailed { error } =>
            format!("列出 marketplace 失败:{error}").into(),
        Msg::PluginInstalling { plugin, marketplace } =>
            format!("正在安装 `{plugin}@{marketplace}`…").into(),
        Msg::PluginUninstalled { plugin, marketplace } =>
            format!("已卸载 `{plugin}@{marketplace}`").into(),
        Msg::PluginUninstallFailed { error } =>
            format!("卸载失败:{error}").into(),
        Msg::PluginListFailed { error } =>
            format!("列出插件失败:{error}").into(),

        // ── 命令描述 ──
Msg::CmdDescSetup =>
"扫描项目、安装种子文件并运行 setup skill [hooks|mcp|skills|all]".into(),
        Msg::CmdDescCodingplan =>
            "领取 CodingPlan 并从计划的模型列表中配置模型".into(),
        Msg::CmdDescResume => "恢复上次会话".into(),
        Msg::CmdDescRename => "重命名当前会话".into(),
        Msg::CmdDescLogin => "使用 AtomGit OAuth 登录".into(),
        Msg::CmdDescLogout => "退出 AtomGit 登录".into(),
        Msg::CmdDescWhoami => "显示当前登录用户".into(),
        Msg::CmdDescModel => "切换 Provider / 模型".into(),
        Msg::CmdDescProvider => "管理 Provider(添加 / 编辑 / 删除)".into(),
        Msg::CmdDescStatus => "显示会话状态".into(),
        Msg::CmdDescConfig => "显示配置文件路径".into(),
        Msg::CmdDescReload => "从磁盘重新加载 $ATOMCODE_HOME/config.toml".into(),
        Msg::CmdDescCd => "切换工作目录".into(),
Msg::CmdDescInit => "从工作目录生成 .atomcode.md 项目指令".into(),
Msg::CmdDescBg => "后台会话:/bg、/bg list、/bg <N>、/bg drop <N>".into(),
Msg::CmdDescBackground => "在隔离的后台上下文中运行一次性任务(只读工具子集)".into(),
        Msg::CmdDescDiff => "显示 git diff".into(),
        Msg::CmdDescClear => "清屏".into(),
        Msg::CmdDescSession => "开始新会话(清除对话)".into(),
        Msg::CmdDescCost => "显示 Token 费用".into(),
        Msg::CmdDescContext => "显示上下文预算明细".into(),
        Msg::CmdDescCompact => "压缩对话历史".into(),
        Msg::CmdDescRemember => "保存记忆(/remember --global 为全局)".into(),
        Msg::CmdDescForget => "删除匹配的记忆".into(),
        Msg::CmdDescMemory => "显示所有已保存的记忆".into(),
        Msg::CmdDescMcp => "显示 MCP 服务器状态(子命令:reload)".into(),
        Msg::CmdDescUndo => "撤销上次变更(暂不支持)".into(),
        Msg::CmdDescWorktree => "Git 工作树隔离(create/list/done/cleanup)".into(),
        Msg::CmdDescUpgrade => "升级到最新版本(子命令:rollback)".into(),
        Msg::CmdDescIssue => "为 AtomCode 报告 Bug / 提出功能建议(交互式向导)".into(),
        Msg::CmdDescPlan => "切换到 Plan 模式(只读探索)".into(),
        Msg::CmdDescBuild => "切换到 Build 模式(完整执行)".into(),
        Msg::CmdDescThink => "深度思考控制(on/off/budget N)".into(),
        Msg::CmdDescHelp => "显示帮助".into(),
        Msg::CmdDescKeys => "显示键盘快捷键".into(),
        Msg::CmdDescLanguage => "切换显示语言".into(),
        Msg::CmdDescQuit => "退出 AtomCode".into(),
        Msg::CmdDescSkills => "浏览已加载的技能".into(),
        Msg::CmdDescPlugin => "插件市场(子命令:marketplace, install, uninstall, list)".into(),
        Msg::CmdDescPaste => "从剪贴板粘贴图片(Windows 下 Ctrl+V 被终端拦截时的备用入口)".into(),
        Msg::CmdPasteNoImage => "剪贴板中没有图片。".into(),

        // ── 配置保存失败 ──
        Msg::ConfigSaveFailed { error } =>
            format!("配置保存失败:{}", error).into(),

        // ── OnboardingWizard ──
        Msg::OnboardingStepHeaderWelcome => "第 1/3 步 · 欢迎".into(),
        Msg::OnboardingStepHeaderLanguage => "第 2/3 步 · 语言".into(),
        Msg::OnboardingStepHeaderSetup => "第 3/3 步 · 配置".into(),
        Msg::OnboardingPanelTitle => "AtomCode".into(),
        Msg::OnboardingIntroVersionLine { v } =>
            format!("版本 {v}  ·  在终端里运行的 AI 编程代理").into(),
        Msg::OnboardingIntroBullet1 =>
            "• 多步骤 agent loop · 内置代码图工具".into(),
        Msg::OnboardingIntroBullet2 =>
            "• 兼容所有 OpenAI 风格 API".into(),
        Msg::OnboardingIntroBullet3 =>
            "• 通过 CodingPlan 获取免费额度".into(),
        Msg::OnboardingIntroPressEnter => "按 Enter 继续。".into(),
        Msg::OnboardingIntroCtrlC => "Ctrl+C 可随时退出。".into(),
        Msg::OnboardingIntroCompactTagline =>
            "在终端里运行的 AI 编程代理。".into(),
        Msg::OnboardingLanguageTitleBilingual =>
            "Choose your language / 选择语言".into(),
        Msg::OnboardingLanguagePrompt =>
            "选择界面语言。任何时候都可以用 `/language` 修改。".into(),
        Msg::OnboardingLanguageOptionAuto =>
            "自动检测 (LC_ALL / LANG)".into(),
        Msg::OnboardingLanguageOptionEn => "English".into(),
        Msg::OnboardingLanguageOptionZhCn => "简体中文 (Simplified Chinese)".into(),
        Msg::OnboardingSetupTitle => "想怎么开始?".into(),
        Msg::OnboardingNavHint =>
            "1-3 选择 · Enter 确认 · ← 返回 · Esc 跳过".into(),
        Msg::OnboardingConfirmClear =>
            "/welcome 会清屏。是否继续?[y/N]".into(),
        Msg::CmdWelcomeDescription => "重新运行 onboarding 向导".into(),
        Msg::VisionPreprocessSuccess { char_count } =>
            format!("✓ VL 识别图片成功,返回 {char_count} chars").into(),
        Msg::TurnSummary { done, turn_count, tool_call_count, duration, total_tokens } =>
            format!("{done} · {turn_count} 轮 · {tool_call_count} 工具 · {duration} · {total_tokens} tokens").into(),
        Msg::LoginQrHeader =>
            "  登录 AtomGit — 使用微信扫描下方二维码:\n\n".into(),
        Msg::LoginUrlAfterQr =>
            "\n\n  或在浏览器打开下方链接:\n  ".into(),
        Msg::LoginNoQrNoUrl =>
            "  当前终端无法渲染二维码,\n  \
             且该平台不支持基于 URL 的登录。\n  \
             请改用支持 Unicode 的终端以显示二维码。".into(),
        Msg::LoginUrlOnly =>
            "  在浏览器中打开此链接以登录 AtomGit:\n  ".into(),
        Msg::LoginCancelHint => "\n\n  按 ESC 取消\n".into(),
        Msg::CtxUsageHeader => "上下文用量".into(),
        Msg::CtxUsageNoTurns => "(请至少完成一轮对话 — 统计在每轮结束时记录)".into(),
        Msg::CtxUsageWaiting => "(等待首轮完成 — 当前仅为部分统计)".into(),
        Msg::CtxProvider => "Provider".into(),
        Msg::CtxCtxName => "ctx".into(),
        Msg::CtxLabelSystemPrompt => "系统提示".into(),
        Msg::CtxLabelToolDefs => "工具定义".into(),
        Msg::CtxLabelColdZone => "冷区".into(),
        Msg::CtxLabelMessages => "消息".into(),
        Msg::CtxLabelFree => "空闲".into(),
        Msg::CtxMessagesInWindow { n } => format!("窗口内消息数:{n}").into(),
        Msg::CtxSystemPromptHeader => "=== 系统提示 ===".into(),
        Msg::CtxSystemPromptEmpty => "(为空 — 完成一轮对话后捕获)".into(),
        Msg::CtxTokensSuffix => "tokens".into(),
        Msg::CompactNothingShort => "(无需压缩 — 当前对话较短)\n".into(),
        Msg::CompactStarting => "(正在使用 LLM 摘要进行压缩...)\n".into(),
        Msg::CompactNothingNoSavings { before, after } =>
            format!("(无需压缩 — 压缩后不会节省 token:{}{}\n", before, after).into(),
        Msg::CompactDropped { messages, before, after } =>
            format!("(已压缩 — 丢弃 {} 条消息,{}{} tokens)\n", messages, before, after).into(),
        Msg::ModelNoImageSupport { model } => format!(
            "当前模型 \"{}\" 不支持图片输入,且未配置 vision_preprocessor_provider。\
             请用 /model 切换到支持视觉的模型,或在配置中设置 vision_preprocessor_provider。",
            model
        )
        .into(),
        Msg::CtrlCAgainToExit => "  (再次按 Ctrl+C 退出)\n".into(),
        Msg::HintMultiLineInput =>
            "  \u{24d8} 多行输入:在行尾加 `\\` 再按 Enter。\n    \
            所有终端均可用。(Shift / Alt / Ctrl + Enter 在部分终端也支持,\n    \
            取决于该终端的键盘协议 — 可以试试看。)\n\n"
                .into(),

        // ── /bg(后台会话)──
        Msg::BgHelp =>
            "  /bg                 将当前会话放到后台,打开新的前台会话\n  /bg list            列出后台会话\n  /bg <N>             恢复第 N 号后台会话\n  /bg drop <N>        丢弃第 N 号后台会话\n  /bg help            显示此帮助\n".into(),
        Msg::BgListEmpty => "  没有后台会话。\n".into(),
        Msg::BgListHeader => "  #   ID        状态       创建时间   摘要\n".into(),
        Msg::BgListRow { slot, short_id, state, age, summary } =>
            format!("  {:<3} {:<8}  {:<9}  {:<8}  {}\n", slot, short_id, state, age, summary).into(),
        Msg::BgStateRunning => "运行中".into(),
        Msg::BgStateIdle => "空闲".into(),
        Msg::BgStateDone => "已完成".into(),
        Msg::BgStateCancelled => "已取消".into(),
        Msg::BgStateError => "错误".into(),
        Msg::BgAgeNow => "刚刚".into(),
        Msg::BgAgeMinutes { n } => format!("{n} 分钟").into(),
        Msg::BgAgeHours { n } => format!("{n} 小时").into(),
        Msg::BgAgeDays { n } => format!("{n}").into(),
        Msg::BgSlotLimitReached { max } =>
            format!("后台槽位已达上限({max}").into(),
        Msg::BgBackgroundCurrent { new_id, slot, old_id, state } =>
            format!("  新前台会话 [{new_id}]\n  后台:[#{slot}] {old_id}(状态:{state}\n").into(),
        Msg::BgInvalidSlot { slot, available } =>
            format!("无效的后台槽位 {slot}(可用:{available}").into(),
        Msg::BgNoRuntimeClient => "后台槽位没有运行时客户端".into(),
        Msg::BgResumed { slot, short_id } =>
            format!("  已恢复后台 [#{slot}] {short_id}\n").into(),
        Msg::BgPreviousForegroundMoved { slot } =>
            format!("  原前台会话已移至 [#{slot}]\n").into(),
        Msg::BgDropped { slot, short_id } =>
            format!("  已丢弃后台 [#{slot}] {short_id}\n").into(),
        Msg::BgTaskStarted { slot, short_id } =>
            format!("  后台:[#{slot}] {short_id}(状态:运行中)\n").into(),
        Msg::BgTaskTimedOut { secs } =>
            format!("后台任务超时({secs} 秒)。").into(),
        Msg::BgTaskError { error } =>
            format!("错误:{error}").into(),
        Msg::BgTaskCancelled => "已取消。".into(),
        Msg::BgTaskNoSummary => "任务完成(无摘要文本)。".into(),
    }
}

#[cfg(test)]
mod codingplan_crypto_tests {
    use super::*;
    use crate::i18n::Msg;

    #[test]
    fn zh_official_build_required_mentions_official_and_releases() {
        let s = zh_cn(Msg::CpOfficialBuildRequired);
        assert!(s.contains("官方"));
        assert!(s.contains("releases") || s.contains("发布"));
    }

    #[test]
    fn zh_stale_clock_mentions_time() {
        let s = zh_cn(Msg::CpSignStaleClockSkew);
        assert!(s.contains("时间") || s.contains("时钟"));
    }

    #[test]
    fn zh_replay_persisted_is_non_empty() {
        let s = zh_cn(Msg::CpSignReplayPersisted);
        assert!(!s.is_empty());
    }

    #[test]
    fn zh_version_too_old_mentions_upgrade() {
        let s = zh_cn(Msg::CpSignVersionTooOld);
        assert!(s.contains("升级") || s.contains("更新"));
    }

    #[test]
    fn zh_upgrade_required_is_non_empty() {
        let s = zh_cn(Msg::CpUpgradeRequired);
        assert!(!s.is_empty());
    }
}