clauderipple 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.es.md +1 -0
  3. package/README.ja.md +1 -0
  4. package/README.ko.md +1 -0
  5. package/README.md +1 -0
  6. package/README.pt-BR.md +1 -0
  7. package/README.zh-CN.md +1 -0
  8. package/dist/app/package.json +1 -1
  9. package/dist/cli/src/index.js +2 -1
  10. package/dist/mod/hooks/i18n.ts +42 -12
  11. package/dist/mod/hooks/ladder.js +5 -5
  12. package/dist/mod/hooks/ladder.ts +5 -5
  13. package/dist/mod/hooks/register.tsx +58 -15
  14. package/dist/mod/hooks/session.ts +37 -3
  15. package/dist/mod/hooks/settings.js +13 -11
  16. package/dist/mod/hooks/settings.ts +13 -11
  17. package/dist/mod/hooks/ui/alerts.tsx +17 -4
  18. package/dist/mod/hooks/ui/art.ts +2 -2
  19. package/dist/mod/hooks/ui/band.tsx +1 -1
  20. package/dist/mod/hooks/ui/kit.tsx +15 -2
  21. package/dist/mod/hooks/ui/view.ts +4 -1
  22. package/dist/mod/hooks/workers.ts +8 -8
  23. package/dist/mod/tests/register.test.tsx +63 -0
  24. package/dist/mod/tests/ui.test.tsx +10 -6
  25. package/dist/mod/types/index.d.ts +10 -1
  26. package/dist/router/src/admin.js +76 -5
  27. package/dist/router/src/agents.js +4 -0
  28. package/dist/router/src/config.js +24 -0
  29. package/dist/router/src/ingress/server.js +2 -2
  30. package/dist/router/src/providers/grok/auth.js +289 -0
  31. package/dist/router/src/providers/grok/catalog.js +52 -0
  32. package/dist/router/src/providers/grok/index.js +94 -0
  33. package/dist/router/src/providers/openai/index.js +69 -17
  34. package/dist/router/src/providers/openai/translate.js +6 -1
  35. package/dist/router/src/proxy.js +43 -0
  36. package/dist/router/src/redact.js +7 -2
  37. package/dist/router/src/version.js +1 -1
  38. package/dist/ui/app.js +115 -6
  39. package/dist/ui/i18n.js +6 -6
  40. package/docs/ARCHITECTURE.md +78 -0
  41. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.3 — 2026-10-14
4
+
5
+ ### Fixed
6
+
7
+ - **Claude workers offer X-High.** Claude's effort ladder stopped at high and max, so X-High could
8
+ not be picked for Claude in the mod or the dashboard though GPT had it. Opus, Sonnet and Haiku 5.5
9
+ all take it (#56).
10
+ - **A model ticked after the mod's worker list was saved is usable.** It used to stay off and show
11
+ as unavailable in the mod (GPT-6 Astra in #56). A worker the list has not seen now starts on.
12
+ Settings saved before this keep the old reading until the list is changed once.
13
+ - **The plan limit shows in the band on Claude chats.** The desktop Code tab left the mod's rate
14
+ limits empty, so the limit never appeared even with it switched on. The mod now falls back to the
15
+ router's figure for the same Claude login, and hides it when that figure is over 15 minutes old.
16
+ - `/ripple-bar` no longer says the band is shown above the prompt when the layout is Minimal, where
17
+ only the short line under the prompt is drawn (#56).
18
+ - The close button lights as one disc under the pointer instead of drawing a second box around it.
19
+
20
+ ## 0.9.2 — 2026-10-14
21
+
22
+ ### Added
23
+
24
+ - **The mod shows when keep-warm held your cache.** A successful ping used to be silent, so it was
25
+ easy to think keep-warm never ran. When you come back, a card now says how many pings kept how
26
+ many tokens in cache and how long is left. It goes when you send a prompt or close it, and the
27
+ cold-cache card takes over if the cache expires after all.
28
+ - **Keep-warm per chat.** The band's flame now turns keep-warm on or off for that chat only: a full
29
+ flame where it is on, an empty one where it is off. Settings is what a new chat starts with.
30
+ After a restart or resume, a chat follows Settings again.
31
+
32
+ - **Grok through a Grok subscription.** A new "Grok subscription" provider uses Grok 4.7, 4.7
33
+ Fast, 4.6 and 4.5 with the Grok CLI's own sign-in (`grok login`), with no API key. It reads the
34
+ CLI's session and, when the session is about to expire, has the CLI refresh it. Tools,
35
+ thinking and effort (low…xhigh, per model) work as on the other translated providers. This
36
+ is the channel the Grok CLI itself talks to, not a published xAI API, so a CLI update can
37
+ change it. Pay-per-token access with an xAI API key stays under the xAI preset. Thanks @nakjiya (#52).
38
+
39
+ ### Fixed
40
+
41
+ - An OpenAI-compatible provider's error that echoed the bare token, without its `Bearer` scheme,
42
+ could show it in the error message. It is now redacted like the full header value (#52).
43
+ - On a two-line mod card, 리플이 sat on the title's line; it now sits at the card's middle.
44
+
3
45
  ## 0.9.1 — 2026-10-14
4
46
 
5
47
  ### Added
package/README.es.md CHANGED
@@ -552,6 +552,7 @@ compatible con Anthropic que hayas configurado está disponible de la misma form
552
552
  |---|---|---|---|---|
553
553
  | Suscripción de ChatGPT | Backend de Codex | varios inicios de sesión, con cambio cuando uno se agota (también reutiliza el de Codex) | leída de tu suscripción (GPT-6 Sol, Luna, Astra, GPT-5.6 …) | esfuerzo low…max (Luna: ultra), caché de prompts 94–99 % |
554
554
  | Google Gemini | API de Gemini | clave de API de AI Studio o inicio de sesión con cuenta de Google (Antigravity, consulta las preguntas frecuentes) | descubierta | traducido (Messages ⇄ generateContent), las firmas de pensamiento se conservan entre turnos |
555
+ | Suscripción de Grok | proxy de chat de la CLI de Grok | el inicio de sesión de la CLI de Grok (`grok login`), renovado por la CLI | leída de tu suscripción (Grok 4.7, 4.7 Fast, 4.6, 4.5) | traducido (Chat Completions), esfuerzo low…xhigh; canal propio de la CLI, no una API publicada ([§4e](docs/ARCHITECTURE.md#4e-grok-providers-implemented-2026-10-08)) |
555
556
  | OpenRouter | Compatible con Anthropic | clave de API | más de 400, descubierta | compatibilidad de esfuerzo por modelo leída de la API |
556
557
  | DeepSeek, Kimi, Z.ai GLM, MiniMax, Qwen (intl / cn) | Compatible con Anthropic | clave de API | predefinida | verificado con la documentación del proveedor |
557
558
  | xAI Grok, Mistral, Groq, Together, Fireworks | Compatible con OpenAI | clave de API | descubierta | traducido (Chat Completions / Responses) |
package/README.ja.md CHANGED
@@ -321,6 +321,7 @@ ClaudeのモデルはCodexのモデル一覧にそれぞれの名前で表示さ
321
321
  |---|---|---|---|---|
322
322
  | ChatGPTサブスクリプション | Codexバックエンド | 複数ログイン、上限で自動切り替え(Codexのログインも再利用) | サブスクリプションから取得(GPT-6 Sol、Luna、Astra、GPT-5.6 …) | 推論強度 low…max(Lunaはultra)、プロンプトキャッシュ 94–99 % |
323
323
  | Google Gemini | Gemini API | AI StudioのAPIキー、またはGoogleアカウントでのログイン(Antigravity、FAQ参照) | 自動検出 | 変換(Messages ⇄ generateContent)、thought signatureをターン間で保持 |
324
+ | Grokサブスクリプション | Grok CLIのチャットプロキシ | Grok CLIのログイン(`grok login`)、更新はCLIが行う | サブスクリプションから取得(Grok 4.7、4.7 Fast、4.6、4.5) | 変換(Chat Completions)、推論強度 low…xhigh。公開APIではなくCLI専用の経路([§4e](docs/ARCHITECTURE.md#4e-grok-providers-implemented-2026-10-08)) |
324
325
  | OpenRouter | Anthropic互換 | APIキー | 400以上、自動検出 | モデルごとの推論強度への対応をAPIから取得 |
325
326
  | DeepSeek、Kimi、Z.ai GLM、MiniMax、Qwen(国際版 / 中国版) | Anthropic互換 | APIキー | プリセット | ベンダーのドキュメントで確認済み |
326
327
  | xAI Grok、Mistral、Groq、Together、Fireworks | OpenAI互換 | APIキー | 自動検出 | 変換(Chat Completions / Responses) |
package/README.ko.md CHANGED
@@ -449,6 +449,7 @@ Anthropic API 키로 갑니다. 구독 로그인 재사용은 Anthropic 약관
449
449
  |---|---|---|---|---|
450
450
  | ChatGPT 구독 | Codex 백엔드 | 로그인 여러 개, 한도 차면 자동 전환(Codex 로그인도 재사용) | 구독에서 자동으로 읽음(GPT-6 Sol·Luna·Astra, GPT-5.6 …) | 추론 강도 low…max(Luna는 ultra), 프롬프트 캐시 94~99 % |
451
451
  | Google Gemini | Gemini API | AI Studio API 키, 또는 Google 계정 로그인(Antigravity, 위 FAQ 참고) | 자동 검색 | 번역(Messages ⇄ generateContent), 턴 사이 thought signature 유지 |
452
+ | Grok 구독 | Grok CLI 채팅 프록시 | Grok CLI 로그인(`grok login`), 갱신은 CLI가 함 | 구독에서 자동으로 읽음(Grok 4.7, 4.7 Fast, 4.6, 4.5) | 번역(Chat Completions), 추론 강도 low…xhigh. 공개 API가 아닌 CLI 전용 채널([§4e](docs/ARCHITECTURE.md#4e-grok-providers-implemented-2026-10-08)) |
452
453
  | OpenRouter | Anthropic 호환 | API 키 | 400+, 자동 검색 | 모델별 추론 강도 지원을 API에서 읽음 |
453
454
  | DeepSeek, Kimi, Z.ai GLM, MiniMax, Qwen(국제/중국) | Anthropic 호환 | API 키 | 프리셋 | 벤더 공식 문서로 확인 |
454
455
  | xAI Grok, Mistral, Groq, Together, Fireworks | OpenAI 호환 | API 키 | 자동 검색 | 번역(Chat Completions / Responses) |
package/README.md CHANGED
@@ -527,6 +527,7 @@ provider you configured is available the same way.
527
527
  |---|---|---|---|---|
528
528
  | ChatGPT subscription | Codex backend | several sign-ins, switching when one runs out (Codex login reused too) | read from your subscription (GPT-6 Sol, Luna, Astra, GPT-5.6 …) | effort low…max (Luna: ultra), prompt cache 94–99 % |
529
529
  | Google Gemini | Gemini API | AI Studio API key, or Google account sign-in (Antigravity, see the FAQ) | discovered | translated (Messages ⇄ generateContent), thought signatures kept across turns |
530
+ | Grok subscription | Grok CLI chat proxy | the Grok CLI's sign-in (`grok login`), refreshed through the CLI | read from your subscription (Grok 4.7, 4.7 Fast, 4.6, 4.5) | translated (Chat Completions), effort low…xhigh; the CLI's own channel, not a published API ([§4e](docs/ARCHITECTURE.md#4e-grok-providers-implemented-2026-10-08)) |
530
531
  | OpenRouter | Anthropic-compatible | API key | 400+, discovered | per-model effort support read from the API |
531
532
  | DeepSeek, Kimi, Z.ai GLM, MiniMax, Qwen (intl / cn) | Anthropic-compatible | API key | preset | verified against vendor docs |
532
533
  | xAI Grok, Mistral, Groq, Together, Fireworks | OpenAI-compatible | API key | discovered | translated (Chat Completions / Responses) |
package/README.pt-BR.md CHANGED
@@ -539,6 +539,7 @@ que você tenha configurado fica disponível da mesma forma.
539
539
  |---|---|---|---|---|
540
540
  | Assinatura do ChatGPT | backend do Codex | vários logins, trocando quando um se esgota (o login do Codex também é reaproveitado) | lida da sua assinatura (GPT-6 Sol, Luna, Astra, GPT-5.6 …) | esforço low…max (Luna: ultra), cache de prompt 94–99% |
541
541
  | Google Gemini | API Gemini | chave de API do AI Studio, ou login com conta do Google (Antigravity, veja as Perguntas frequentes) | descoberta automática | traduzido (Messages ⇄ generateContent), thought signatures mantidas entre turnos |
542
+ | Assinatura do Grok | proxy de chat da CLI do Grok | o login da CLI do Grok (`grok login`), renovado pela CLI | lida da sua assinatura (Grok 4.7, 4.7 Fast, 4.6, 4.5) | traduzido (Chat Completions), esforço low…xhigh; canal próprio da CLI, não uma API publicada ([§4e](docs/ARCHITECTURE.md#4e-grok-providers-implemented-2026-10-08)) |
542
543
  | OpenRouter | compatível com Anthropic | chave de API | mais de 400, descoberta automática | suporte a esforço por modelo lido da API |
543
544
  | DeepSeek, Kimi, Z.ai GLM, MiniMax, Qwen (intl / cn) | compatível com Anthropic | chave de API | preset | conferido com a documentação do fornecedor |
544
545
  | xAI Grok, Mistral, Groq, Together, Fireworks | compatível com OpenAI | chave de API | descoberta automática | traduzido (Chat Completions / Responses) |
package/README.zh-CN.md CHANGED
@@ -322,6 +322,7 @@ Claude 模型会以各自的名称出现在 Codex 的模型列表中(ClaudeRip
322
322
  |---|---|---|---|---|
323
323
  | ChatGPT 订阅 | Codex 后端 | 可登录多个账号,一个用尽自动切换(也会复用 Codex 登录) | 从你的订阅读取(GPT-6 Sol、Luna、Astra、GPT-5.6 …) | 推理强度 low…max(Luna:ultra),提示缓存 94–99 % |
324
324
  | Google Gemini | Gemini API | AI Studio API 密钥,或 Google 账号登录(Antigravity,见[常见问题](#通过-google-账号使用-gemini-呢)) | 自动发现 | 格式转换(Messages ⇄ generateContent),跨轮次保留思维签名(thought signatures) |
325
+ | Grok 订阅 | Grok CLI 聊天代理 | Grok CLI 的登录(`grok login`),由 CLI 刷新 | 从你的订阅读取(Grok 4.7、4.7 Fast、4.6、4.5) | 格式转换(Chat Completions),推理强度 low…xhigh;CLI 自己的通道,不是公开 API([§4e](docs/ARCHITECTURE.md#4e-grok-providers-implemented-2026-10-08)) |
325
326
  | OpenRouter | Anthropic 兼容 | API 密钥 | 400+,自动发现 | 从 API 读取各模型对推理强度的支持情况 |
326
327
  | DeepSeek、Kimi、Z.ai GLM、MiniMax、Qwen(国际版 / 国内版) | Anthropic 兼容 | API 密钥 | 预设 | 已对照厂商文档验证 |
327
328
  | xAI Grok、Mistral、Groq、Together、Fireworks | OpenAI 兼容 | API 密钥 | 自动发现 | 格式转换(Chat Completions / Responses) |
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "private": true,
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "type": "commonjs"
5
5
  }
@@ -20,6 +20,7 @@ import { applyAppProxy, caTrusted, currentAppProxy, nssDb, removeAppProxy, trust
20
20
  import { runtime } from "./runtime.js";
21
21
  import { codexOff, codexOn } from "./codex.js";
22
22
  import { ingressModels } from "../../router/src/ingress/models.js";
23
+ import { GROK_DEFAULT_URL } from "../../router/src/providers/grok/catalog.js";
23
24
  import { claudeLogin, claudeLogout, desktopClaudeCodeDirs } from "./claude-auth.js";
24
25
  import { openBrowser } from "./browser.js";
25
26
  import { electronPath, installTrayRuntime, startTray } from "./tray.js";
@@ -315,7 +316,7 @@ async function status() {
315
316
  const p = await probe({ host: cfg.listen.host, port: cfg.listen.port, caPem: caPath, upstream: cfg.upstream });
316
317
  rows.push(["probe", `${p.ok ? "ok" : "FAIL"}: ${p.detail} (${p.ms}ms)`]);
317
318
  for (const [name, p2] of Object.entries(cfg.providers)) {
318
- const providerUrl = p2.type === "chatgpt" ? (p2.url ?? "https://chatgpt.com") : p2.type === "anthropic" ? "https://api.anthropic.com" : p2.type === "google" ? (p2.url ?? "https://generativelanguage.googleapis.com") : p2.url;
319
+ const providerUrl = p2.type === "chatgpt" ? (p2.url ?? "https://chatgpt.com") : p2.type === "anthropic" ? "https://api.anthropic.com" : p2.type === "google" ? (p2.url ?? "https://generativelanguage.googleapis.com") : p2.type === "grok" ? (p2.url ?? GROK_DEFAULT_URL) : p2.url;
319
320
  const u = new URL(providerUrl);
320
321
  rows.push([`provider ${name}`, await tcpCheck(u.hostname, Number(u.port) || (u.protocol === "https:" ? 443 : 80))]);
321
322
  }
@@ -70,6 +70,7 @@ const EN = {
70
70
  'band.hit': 'hit {percent}%',
71
71
  'band.effortAutoAlt': 'Effort Auto {state}',
72
72
  'band.warmAlt': 'Keep cache warm {state}',
73
+ 'band.warmHere': 'this chat only; new chats follow Settings',
73
74
  'band.handoffAdvised': 'Handoff (advised: a fresh chat pays now)',
74
75
  'band.closeLog': 'Close the request log',
75
76
  'band.openLog': 'Open the request log',
@@ -90,6 +91,9 @@ const EN = {
90
91
  // Alerts.
91
92
  'alerts.coldTitle': 'The cache went cold',
92
93
  'alerts.coldBody': 'It sat {minutes} min, so the next prompt re-reads all {tokens} tokens at full price.',
94
+ 'alerts.keptTitle': 'Cache kept warm while you were away',
95
+ 'alerts.keptBody': '{pings} ping(s) held {tokens} tokens in cache, so your next prompt reads them at the cached rate. {minutes} min left.',
96
+ 'alerts.gotIt': 'Got it',
93
97
  'alerts.swampTitle': 'Context {percent}% full',
94
98
  'alerts.swampBody': '{tokens} tokens ride along with every prompt: slower, dearer, blurrier.',
95
99
  'alerts.hotTitle': '{window} limit at {percent}%',
@@ -347,16 +351,17 @@ const EN = {
347
351
  'toasts.workerMissing': '{name} is not installed',
348
352
  // Commands (stats and debug labels included).
349
353
  'commands.description': 'ClaudeRipple effort, workers, cache and session tools',
350
- 'commands.argumentHint': 'settings|setup|auto|warm|save|handoff [full]|stats|debug|agents|log|bar|cold|swamp|hot|down',
354
+ 'commands.argumentHint': 'settings|setup|auto|warm|save|handoff [full]|stats|debug|agents|log|bar|cold|swamp|hot|down|kept',
351
355
  'commands.logDescription': 'Open or close the ClaudeRipple request log',
352
356
  'commands.barDescription': 'Show or hide the ClaudeRipple band',
353
357
  'commands.logTitle': 'ClaudeRipple',
354
358
  'commands.agentsTitle': 'ClaudeRipple Agents',
355
- 'commands.help': 'ClaudeRipple: settings, setup, auto, warm, save, handoff [full], stats, debug, agents, log, bar, cold, swamp, hot, down.',
359
+ 'commands.help': 'ClaudeRipple: settings, setup, auto, warm, save, handoff [full], stats, debug, agents, log, bar, cold, swamp, hot, down, kept.',
356
360
  'commands.done': 'ClaudeRipple: {command}.',
357
361
  'commands.logOpened': 'ClaudeRipple log opened.',
358
362
  'commands.logClosed': 'ClaudeRipple log closed.',
359
363
  'commands.barShown': 'ClaudeRipple line shown above the prompt.',
364
+ 'commands.barShownMinimal': 'The layout is Minimal, so ClaudeRipple shows as a short line under the prompt. For the full band above the prompt, set Layout to Dashboard in /ripple settings.',
360
365
  'commands.barHidden': 'ClaudeRipple line hidden. /ripple-bar shows it again.',
361
366
  'commands.statsAuto': 'Auto: {prompts} prompts, {requests} requests; {total} weighted tokens',
362
367
  'commands.statsTokens': 'Tokens: input {input}, write {write}, read {read}, output {output}',
@@ -515,6 +520,7 @@ const KO: Record<Key, string> = {
515
520
  'band.hit': '적중 {percent}%',
516
521
  'band.effortAutoAlt': '추론 강도 자동 {state}',
517
522
  'band.warmAlt': '캐시 유지 {state}',
523
+ 'band.warmHere': '이 세션만 적용, 새 세션은 설정을 따릅니다',
518
524
  'band.handoffAdvised': '인수인계 (권장: 지금은 새 대화가 더 효율적입니다)',
519
525
  'band.closeLog': '요청 로그 닫기',
520
526
  'band.openLog': '요청 로그 열기',
@@ -535,6 +541,9 @@ const KO: Record<Key, string> = {
535
541
  // Alerts.
536
542
  'alerts.coldTitle': '캐시가 만료되었습니다',
537
543
  'alerts.coldBody': '{minutes}분 동안 대기하여 캐시가 만료되었습니다. 다음 프롬프트는 {tokens} 토큰 전체를 새로 읽어 비용이 발생합니다.',
544
+ 'alerts.keptTitle': '자리를 비운 동안 캐시를 유지했습니다',
545
+ 'alerts.keptBody': '핑 {pings}회로 {tokens} 토큰을 캐시에 붙잡아 두었습니다. 다음 프롬프트도 캐시로 읽습니다. 만료까지 {minutes}분 남았습니다.',
546
+ 'alerts.gotIt': '확인',
538
547
  'alerts.swampTitle': '컨텍스트 {percent}% 사용',
539
548
  'alerts.swampBody': '프롬프트마다 {tokens} 토큰이 함께 전송되어 응답 속도와 정확도, 비용에 영향을 줍니다.',
540
549
  'alerts.hotTitle': '{window} 한도 {percent}% 도달',
@@ -792,16 +801,17 @@ const KO: Record<Key, string> = {
792
801
  'toasts.workerMissing': '{name} 에이전트 미설치',
793
802
  // Commands (stats and debug labels included).
794
803
  'commands.description': 'ClaudeRipple 추론 강도, 워커, 캐시 및 세션 도구',
795
- 'commands.argumentHint': 'settings|setup|auto|warm|save|handoff [full]|stats|debug|agents|log|bar|cold|swamp|hot|down',
804
+ 'commands.argumentHint': 'settings|setup|auto|warm|save|handoff [full]|stats|debug|agents|log|bar|cold|swamp|hot|down|kept',
796
805
  'commands.logDescription': 'ClaudeRipple 요청 로그 열기/닫기',
797
806
  'commands.barDescription': 'ClaudeRipple 상태 바 표시/숨기기',
798
807
  'commands.logTitle': 'ClaudeRipple',
799
808
  'commands.agentsTitle': 'ClaudeRipple 에이전트',
800
- 'commands.help': 'ClaudeRipple 하위 명령: settings, setup, auto, warm, save, handoff [full], stats, debug, agents, log, bar, cold, swamp, hot, down.',
809
+ 'commands.help': 'ClaudeRipple 하위 명령: settings, setup, auto, warm, save, handoff [full], stats, debug, agents, log, bar, cold, swamp, hot, down, kept.',
801
810
  'commands.done': 'ClaudeRipple: {command} 완료.',
802
811
  'commands.logOpened': 'ClaudeRipple 로그를 열었습니다.',
803
812
  'commands.logClosed': 'ClaudeRipple 로그를 닫았습니다.',
804
813
  'commands.barShown': '프롬프트 상단에 ClaudeRipple 상태 바를 표시합니다.',
814
+ 'commands.barShownMinimal': '레이아웃이 최소화로 되어 있어 ClaudeRipple은 프롬프트 아래 한 줄로만 표시됩니다. 프롬프트 위에 상태 바를 띄우려면 /ripple settings에서 레이아웃을 대시보드로 바꾸십시오.',
805
815
  'commands.barHidden': 'ClaudeRipple 상태 바를 숨겼습니다. /ripple-bar로 다시 표시할 수 있습니다.',
806
816
  'commands.statsAuto': '자동: 프롬프트 {prompts}개, 요청 {requests}개, 가중 토큰 {total}',
807
817
  'commands.statsTokens': '토큰: 입력 {input}, 쓰기 {write}, 읽기 {read}, 출력 {output}',
@@ -958,6 +968,7 @@ const ZH: Record<Key, string> = {
958
968
  'band.hit': '命中 {percent}%',
959
969
  'band.effortAutoAlt': '推理强度自动{state}',
960
970
  'band.warmAlt': '缓存保温{state}',
971
+ 'band.warmHere': '仅限本会话,新会话按设置',
961
972
  'band.handoffAdvised': '交接(建议:现在换新对话更划算)',
962
973
  'band.closeLog': '关闭请求日志',
963
974
  'band.openLog': '打开请求日志',
@@ -978,6 +989,9 @@ const ZH: Record<Key, string> = {
978
989
  // Alerts.
979
990
  'alerts.coldTitle': '缓存已过期',
980
991
  'alerts.coldBody': '闲置了 {minutes} 分钟,下一条提示词会按全价重读全部 {tokens} token。',
992
+ 'alerts.keptTitle': '您离开期间缓存已保持温热',
993
+ 'alerts.keptBody': '保温 ping {pings} 次,把 {tokens} token 留在了缓存里,下一条提示词仍按缓存价读取。还有 {minutes} 分钟过期。',
994
+ 'alerts.gotIt': '知道了',
981
995
  'alerts.swampTitle': '上下文已用 {percent}%',
982
996
  'alerts.swampBody': '每条提示词都带着 {tokens} token:更慢、更贵、更模糊。',
983
997
  'alerts.hotTitle': '{window}限额已达 {percent}%',
@@ -1235,16 +1249,17 @@ const ZH: Record<Key, string> = {
1235
1249
  'toasts.workerMissing': '未安装 {name}',
1236
1250
  // Commands (stats and debug labels included).
1237
1251
  'commands.description': 'ClaudeRipple 推理强度、Worker、缓存与会话工具',
1238
- 'commands.argumentHint': 'settings|setup|auto|warm|save|handoff [full]|stats|debug|agents|log|bar|cold|swamp|hot|down',
1252
+ 'commands.argumentHint': 'settings|setup|auto|warm|save|handoff [full]|stats|debug|agents|log|bar|cold|swamp|hot|down|kept',
1239
1253
  'commands.logDescription': '打开或关闭 ClaudeRipple 请求日志',
1240
1254
  'commands.barDescription': '显示或隐藏 ClaudeRipple 状态栏',
1241
1255
  'commands.logTitle': 'ClaudeRipple',
1242
1256
  'commands.agentsTitle': 'ClaudeRipple 代理',
1243
- 'commands.help': 'ClaudeRipple 子命令:settings, setup, auto, warm, save, handoff [full], stats, debug, agents, log, bar, cold, swamp, hot, down.',
1257
+ 'commands.help': 'ClaudeRipple 子命令:settings, setup, auto, warm, save, handoff [full], stats, debug, agents, log, bar, cold, swamp, hot, down, kept.',
1244
1258
  'commands.done': 'ClaudeRipple: {command}.',
1245
1259
  'commands.logOpened': '已打开 ClaudeRipple 日志。',
1246
1260
  'commands.logClosed': '已关闭 ClaudeRipple 日志。',
1247
1261
  'commands.barShown': 'ClaudeRipple 状态栏已显示在提示词上方。',
1262
+ 'commands.barShownMinimal': '布局是“极简”,所以 ClaudeRipple 只在提示词下方显示一行。要在提示词上方显示完整状态栏,请在 /ripple settings 里把布局改为“仪表盘”。',
1248
1263
  'commands.barHidden': 'ClaudeRipple 状态栏已隐藏,/ripple-bar 可重新显示。',
1249
1264
  'commands.statsAuto': '自动:{prompts} 条提示词,{requests} 次请求;加权 token {total}',
1250
1265
  'commands.statsTokens': 'Token:输入 {input},写入 {write},读取 {read},输出 {output}',
@@ -1401,6 +1416,7 @@ const JA: Record<Key, string> = {
1401
1416
  'band.hit': 'ヒット {percent}%',
1402
1417
  'band.effortAutoAlt': '推論強度の自動 {state}',
1403
1418
  'band.warmAlt': 'キャッシュ保持 {state}',
1419
+ 'band.warmHere': 'このチャットだけ。新しいチャットは設定に従います',
1404
1420
  'band.handoffAdvised': '引き継ぎ(推奨: 今は新しい会話のほうが得です)',
1405
1421
  'band.closeLog': 'リクエストログを閉じる',
1406
1422
  'band.openLog': 'リクエストログを開く',
@@ -1421,6 +1437,9 @@ const JA: Record<Key, string> = {
1421
1437
  // Alerts.
1422
1438
  'alerts.coldTitle': 'キャッシュの期限が切れました',
1423
1439
  'alerts.coldBody': '{minutes} 分止まっていたので、次のプロンプトで {tokens} トークンを定価で読み直します。',
1440
+ 'alerts.keptTitle': '不在中もキャッシュを温かいまま保ちました',
1441
+ 'alerts.keptBody': 'キープウォームが {pings} 回、{tokens} トークンをキャッシュに保持しました。次のプロンプトもキャッシュで読めます。期限まで {minutes} 分です。',
1442
+ 'alerts.gotIt': '了解',
1424
1443
  'alerts.swampTitle': 'コンテキスト {percent}% 使用中',
1425
1444
  'alerts.swampBody': 'プロンプトごとに {tokens} トークンが付いていき、遅く・高く・曖昧になります。',
1426
1445
  'alerts.hotTitle': '{window}上限の {percent}% に到達',
@@ -1678,16 +1697,17 @@ const JA: Record<Key, string> = {
1678
1697
  'toasts.workerMissing': '{name} がインストールされていません',
1679
1698
  // Commands (stats and debug labels included).
1680
1699
  'commands.description': 'ClaudeRipple の推論強度、ワーカー、キャッシュ、セッションツール',
1681
- 'commands.argumentHint': 'settings|setup|auto|warm|save|handoff [full]|stats|debug|agents|log|bar|cold|swamp|hot|down',
1700
+ 'commands.argumentHint': 'settings|setup|auto|warm|save|handoff [full]|stats|debug|agents|log|bar|cold|swamp|hot|down|kept',
1682
1701
  'commands.logDescription': 'ClaudeRipple のリクエストログを開閉',
1683
1702
  'commands.barDescription': 'ClaudeRipple のバーを表示・非表示',
1684
1703
  'commands.logTitle': 'ClaudeRipple',
1685
1704
  'commands.agentsTitle': 'ClaudeRipple エージェント',
1686
- 'commands.help': 'ClaudeRipple のサブコマンド: settings, setup, auto, warm, save, handoff [full], stats, debug, agents, log, bar, cold, swamp, hot, down.',
1705
+ 'commands.help': 'ClaudeRipple のサブコマンド: settings, setup, auto, warm, save, handoff [full], stats, debug, agents, log, bar, cold, swamp, hot, down, kept.',
1687
1706
  'commands.done': 'ClaudeRipple: {command}.',
1688
1707
  'commands.logOpened': 'ClaudeRipple のログを開きました。',
1689
1708
  'commands.logClosed': 'ClaudeRipple のログを閉じました。',
1690
1709
  'commands.barShown': 'ClaudeRipple のバーをプロンプトの上に表示しました。',
1710
+ 'commands.barShownMinimal': 'レイアウトがミニマルなので、ClaudeRipple はプロンプトの下に 1 行で表示されます。プロンプトの上にバーを出すには、/ripple settings でレイアウトをダッシュボードにしてください。',
1691
1711
  'commands.barHidden': 'ClaudeRipple のバーを非表示にしました。/ripple-bar で再表示できます。',
1692
1712
  'commands.statsAuto': '自動: プロンプト {prompts} 件、リクエスト {requests} 件、加重トークン {total}',
1693
1713
  'commands.statsTokens': 'トークン: 入力 {input}、書き込み {write}、読み取り {read}、出力 {output}',
@@ -1844,6 +1864,7 @@ const ES: Record<Key, string> = {
1844
1864
  'band.hit': 'acierto {percent}%',
1845
1865
  'band.effortAutoAlt': 'Esfuerzo auto {state}',
1846
1866
  'band.warmAlt': 'Mantener la caché caliente {state}',
1867
+ 'band.warmHere': 'solo este chat; los nuevos siguen los Ajustes',
1847
1868
  'band.handoffAdvised': 'Traspaso (recomendado: ahora conviene una conversación nueva)',
1848
1869
  'band.closeLog': 'Cerrar el registro de solicitudes',
1849
1870
  'band.openLog': 'Abrir el registro de solicitudes',
@@ -1864,6 +1885,9 @@ const ES: Record<Key, string> = {
1864
1885
  // Alerts.
1865
1886
  'alerts.coldTitle': 'La caché se enfrió',
1866
1887
  'alerts.coldBody': 'Estuvo {minutes} min parada, así que el próximo prompt relee sus {tokens} tokens a precio completo.',
1888
+ 'alerts.keptTitle': 'La caché se mantuvo caliente mientras no estabas',
1889
+ 'alerts.keptBody': '{pings} ping(s) mantuvieron {tokens} tokens en caché, así que tu próximo prompt los lee al precio en caché. Quedan {minutes} min.',
1890
+ 'alerts.gotIt': 'Entendido',
1867
1891
  'alerts.swampTitle': 'Contexto al {percent}%',
1868
1892
  'alerts.swampBody': '{tokens} tokens viajan con cada prompt: más lento, más caro, más difuso.',
1869
1893
  'alerts.hotTitle': 'Límite {window} al {percent}%',
@@ -2121,16 +2145,17 @@ const ES: Record<Key, string> = {
2121
2145
  'toasts.workerMissing': '{name} no está instalado',
2122
2146
  // Commands (stats and debug labels included).
2123
2147
  'commands.description': 'Esfuerzo, workers, caché y herramientas de sesión de ClaudeRipple',
2124
- 'commands.argumentHint': 'settings|setup|auto|warm|save|handoff [full]|stats|debug|agents|log|bar|cold|swamp|hot|down',
2148
+ 'commands.argumentHint': 'settings|setup|auto|warm|save|handoff [full]|stats|debug|agents|log|bar|cold|swamp|hot|down|kept',
2125
2149
  'commands.logDescription': 'Abre o cierra el registro de solicitudes de ClaudeRipple',
2126
2150
  'commands.barDescription': 'Muestra u oculta la barra de ClaudeRipple',
2127
2151
  'commands.logTitle': 'ClaudeRipple',
2128
2152
  'commands.agentsTitle': 'Agentes de ClaudeRipple',
2129
- 'commands.help': 'Subcomandos de ClaudeRipple: settings, setup, auto, warm, save, handoff [full], stats, debug, agents, log, bar, cold, swamp, hot, down.',
2153
+ 'commands.help': 'Subcomandos de ClaudeRipple: settings, setup, auto, warm, save, handoff [full], stats, debug, agents, log, bar, cold, swamp, hot, down, kept.',
2130
2154
  'commands.done': 'ClaudeRipple: {command}.',
2131
2155
  'commands.logOpened': 'Registro de ClaudeRipple abierto.',
2132
2156
  'commands.logClosed': 'Registro de ClaudeRipple cerrado.',
2133
2157
  'commands.barShown': 'Barra de ClaudeRipple visible sobre el prompt.',
2158
+ 'commands.barShownMinimal': 'La disposición es Mínimo, así que ClaudeRipple aparece como una línea corta bajo el prompt. Para la barra completa sobre el prompt, elige Panel en Disposición en /ripple settings.',
2134
2159
  'commands.barHidden': 'Barra de ClaudeRipple oculta. /ripple-bar la vuelve a mostrar.',
2135
2160
  'commands.statsAuto': 'Auto: {prompts} prompts, {requests} solicitudes; {total} tokens ponderados',
2136
2161
  'commands.statsTokens': 'Tokens: entrada {input}, escritura {write}, lectura {read}, salida {output}',
@@ -2287,6 +2312,7 @@ const PT: Record<Key, string> = {
2287
2312
  'band.hit': 'acerto {percent}%',
2288
2313
  'band.effortAutoAlt': 'Esforço auto {state}',
2289
2314
  'band.warmAlt': 'Manter o cache quente {state}',
2315
+ 'band.warmHere': 'só esta conversa; as novas seguem as Configurações',
2290
2316
  'band.handoffAdvised': 'Repasse (recomendado: agora uma conversa nova compensa)',
2291
2317
  'band.closeLog': 'Fechar o log de requisições',
2292
2318
  'band.openLog': 'Abrir o log de requisições',
@@ -2307,6 +2333,9 @@ const PT: Record<Key, string> = {
2307
2333
  // Alerts.
2308
2334
  'alerts.coldTitle': 'O cache esfriou',
2309
2335
  'alerts.coldBody': 'Ficou parada {minutes} min, então o próximo prompt relê os {tokens} tokens pelo preço cheio.',
2336
+ 'alerts.keptTitle': 'O cache ficou quente enquanto você esteve fora',
2337
+ 'alerts.keptBody': '{pings} ping(s) seguraram {tokens} tokens no cache, então o próximo prompt os lê pelo preço em cache. Faltam {minutes} min.',
2338
+ 'alerts.gotIt': 'Entendi',
2310
2339
  'alerts.swampTitle': 'Contexto {percent}% cheio',
2311
2340
  'alerts.swampBody': '{tokens} tokens vão junto com cada prompt: mais lento, mais caro, mais confuso.',
2312
2341
  'alerts.hotTitle': 'Limite {window} em {percent}%',
@@ -2564,16 +2593,17 @@ const PT: Record<Key, string> = {
2564
2593
  'toasts.workerMissing': '{name} não está instalado',
2565
2594
  // Commands (stats and debug labels included).
2566
2595
  'commands.description': 'Esforço, workers, cache e ferramentas de sessão do ClaudeRipple',
2567
- 'commands.argumentHint': 'settings|setup|auto|warm|save|handoff [full]|stats|debug|agents|log|bar|cold|swamp|hot|down',
2596
+ 'commands.argumentHint': 'settings|setup|auto|warm|save|handoff [full]|stats|debug|agents|log|bar|cold|swamp|hot|down|kept',
2568
2597
  'commands.logDescription': 'Abre ou fecha o log de requisições do ClaudeRipple',
2569
2598
  'commands.barDescription': 'Mostra ou oculta a barra do ClaudeRipple',
2570
2599
  'commands.logTitle': 'ClaudeRipple',
2571
2600
  'commands.agentsTitle': 'Agentes do ClaudeRipple',
2572
- 'commands.help': 'Subcomandos do ClaudeRipple: settings, setup, auto, warm, save, handoff [full], stats, debug, agents, log, bar, cold, swamp, hot, down.',
2601
+ 'commands.help': 'Subcomandos do ClaudeRipple: settings, setup, auto, warm, save, handoff [full], stats, debug, agents, log, bar, cold, swamp, hot, down, kept.',
2573
2602
  'commands.done': 'ClaudeRipple: {command}.',
2574
2603
  'commands.logOpened': 'Log do ClaudeRipple aberto.',
2575
2604
  'commands.logClosed': 'Log do ClaudeRipple fechado.',
2576
2605
  'commands.barShown': 'Barra do ClaudeRipple visível acima do prompt.',
2606
+ 'commands.barShownMinimal': 'O layout está em Mínimo, então o ClaudeRipple aparece como uma linha curta abaixo do prompt. Para a barra completa acima do prompt, escolha Painel em Layout no /ripple settings.',
2577
2607
  'commands.barHidden': 'Barra do ClaudeRipple oculta. /ripple-bar mostra de novo.',
2578
2608
  'commands.statsAuto': 'Auto: {prompts} prompts, {requests} requisições; {total} tokens ponderados',
2579
2609
  'commands.statsTokens': 'Tokens: entrada {input}, gravação {write}, leitura {read}, saída {output}',
@@ -150,9 +150,9 @@ export function preset(id, choices) {
150
150
  }
151
151
  return { rungs, substituted };
152
152
  }
153
- /** The choices a person ticked; an empty list ticks every one. */
154
- export function tickedChoices(choices, allow) {
155
- return choices.filter(choice => isAllowed(candidateKey(choice.target), choice.provider, allow));
153
+ /** The choices a person ticked; an empty list ticks every one, and a key absent from `known` ticks on too. */
154
+ export function tickedChoices(choices, allow, known = []) {
155
+ return choices.filter(choice => isAllowed(candidateKey(choice.target), choice.provider, allow, known));
156
156
  }
157
157
  /** Only empty lists need filling; unticked entries stay in order and are skipped at dispatch. */
158
158
  export function ladderNeedsFill(rungs, _allow = []) {
@@ -162,8 +162,8 @@ export function ladderNeedsFill(rungs, _allow = []) {
162
162
  * Fill empty lists from the ticked, connected choices. Existing ordered lists are never rewritten
163
163
  * for temporary availability or shared allow-list changes; dispatch explains each skipped entry.
164
164
  */
165
- export function fillLadder(rungs, allow, choices) {
166
- const pick = preset('claudeGpt', tickedChoices(choices, allow));
165
+ export function fillLadder(rungs, allow, choices, known = []) {
166
+ const pick = preset('claudeGpt', tickedChoices(choices, allow, known));
167
167
  const out = {};
168
168
  const filled = [];
169
169
  for (const rung of RUNGS) {
@@ -146,9 +146,9 @@ export function preset(id: PresetId, choices: readonly LadderChoice[]): Preset {
146
146
  return { rungs, substituted }
147
147
  }
148
148
 
149
- /** The choices a person ticked; an empty list ticks every one. */
150
- export function tickedChoices(choices: readonly LadderChoice[], allow: readonly string[]): LadderChoice[] {
151
- return choices.filter(choice => isAllowed(candidateKey(choice.target), choice.provider, allow))
149
+ /** The choices a person ticked; an empty list ticks every one, and a key absent from `known` ticks on too. */
150
+ export function tickedChoices(choices: readonly LadderChoice[], allow: readonly string[], known: readonly string[] = []): LadderChoice[] {
151
+ return choices.filter(choice => isAllowed(candidateKey(choice.target), choice.provider, allow, known))
152
152
  }
153
153
 
154
154
  /** Only empty lists need filling; unticked entries stay in order and are skipped at dispatch. */
@@ -160,8 +160,8 @@ export function ladderNeedsFill(rungs: Rungs, _allow: readonly string[] = []): b
160
160
  * Fill empty lists from the ticked, connected choices. Existing ordered lists are never rewritten
161
161
  * for temporary availability or shared allow-list changes; dispatch explains each skipped entry.
162
162
  */
163
- export function fillLadder(rungs: Rungs, allow: readonly string[], choices: readonly LadderChoice[]): { rungs: Record<Rung, Step[]>; filled: Rung[] } {
164
- const pick = preset('claudeGpt', tickedChoices(choices, allow))
163
+ export function fillLadder(rungs: Rungs, allow: readonly string[], choices: readonly LadderChoice[], known: readonly string[] = []): { rungs: Record<Rung, Step[]>; filled: Rung[] } {
164
+ const pick = preset('claudeGpt', tickedChoices(choices, allow, known))
165
165
  const out = {} as Record<Rung, Step[]>
166
166
  const filled: Rung[] = []
167
167
  for (const rung of RUNGS) {