clauderipple 0.8.10 → 0.9.1

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 (69) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.es.md +21 -15
  3. package/README.ja.md +13 -9
  4. package/README.ko.md +23 -9
  5. package/README.md +26 -11
  6. package/README.pt-BR.md +21 -15
  7. package/README.zh-CN.md +13 -9
  8. package/dist/app/package.json +1 -1
  9. package/dist/cli/src/codex.js +3 -1
  10. package/dist/cli/src/settings.js +5 -2
  11. package/dist/mod/.claude-plugin/plugin.json +9 -1
  12. package/dist/mod/hooks/cache.ts +60 -0
  13. package/dist/mod/hooks/compact.ts +40 -0
  14. package/dist/mod/hooks/handoff.ts +43 -0
  15. package/dist/mod/hooks/i18n.ts +2716 -0
  16. package/dist/mod/hooks/judge.ts +242 -0
  17. package/dist/mod/hooks/ladder.js +179 -0
  18. package/dist/mod/hooks/ladder.ts +174 -0
  19. package/dist/mod/hooks/modes.ts +93 -0
  20. package/dist/mod/hooks/register.tsx +1458 -256
  21. package/dist/mod/hooks/session.ts +62 -0
  22. package/dist/mod/hooks/settings.js +342 -0
  23. package/dist/mod/hooks/settings.ts +321 -0
  24. package/dist/mod/hooks/stats.ts +59 -0
  25. package/dist/mod/hooks/ui/agents.tsx +138 -0
  26. package/dist/mod/hooks/ui/alerts.tsx +373 -0
  27. package/dist/mod/hooks/ui/art.ts +628 -0
  28. package/dist/mod/hooks/ui/band.tsx +836 -0
  29. package/dist/mod/hooks/ui/kit.tsx +258 -0
  30. package/dist/mod/hooks/ui/log.tsx +109 -0
  31. package/dist/mod/hooks/ui/parts.tsx +217 -0
  32. package/dist/mod/hooks/ui/ripple.ts +614 -0
  33. package/dist/mod/hooks/ui/settings.tsx +376 -0
  34. package/dist/mod/hooks/ui/setup.tsx +148 -0
  35. package/dist/mod/hooks/ui/theme.ts +217 -0
  36. package/dist/mod/hooks/ui/view.ts +371 -0
  37. package/dist/mod/hooks/worker.ts +1 -1
  38. package/dist/mod/hooks/workers.ts +217 -0
  39. package/dist/mod/tests/register.test.tsx +1730 -0
  40. package/dist/mod/tests/ui.test.tsx +853 -0
  41. package/dist/mod/tsconfig.json +2 -1
  42. package/dist/mod/types/index.d.ts +320 -1
  43. package/dist/router/src/admin.js +170 -52
  44. package/dist/router/src/agents.js +24 -5
  45. package/dist/router/src/bootstrap.js +4 -2
  46. package/dist/router/src/config.js +3 -0
  47. package/dist/router/src/health.js +33 -0
  48. package/dist/router/src/index.js +1 -0
  49. package/dist/router/src/ingress/models.js +4 -1
  50. package/dist/router/src/mod-settings.js +120 -0
  51. package/dist/router/src/model-policy.js +6 -0
  52. package/dist/router/src/model-scores.js +159 -0
  53. package/dist/router/src/picker.js +7 -0
  54. package/dist/router/src/providers/chatgpt/index.js +23 -5
  55. package/dist/router/src/providers/chatgpt/translate.js +4 -1
  56. package/dist/router/src/providers/google/index.js +8 -3
  57. package/dist/router/src/providers/openai/index.js +6 -1
  58. package/dist/router/src/providers/openai/translate.js +6 -1
  59. package/dist/router/src/proxy.js +62 -24
  60. package/dist/router/src/requestlog.js +78 -20
  61. package/dist/router/src/version.js +1 -1
  62. package/dist/ui/app.js +908 -209
  63. package/dist/ui/favicon-32.png +0 -0
  64. package/dist/ui/favicon.svg +12 -0
  65. package/dist/ui/i18n.js +919 -233
  66. package/dist/ui/index.html +159 -87
  67. package/dist/ui/style.css +789 -410
  68. package/docs/ARCHITECTURE.md +141 -0
  69. package/package.json +7 -3
@@ -0,0 +1,93 @@
1
+ // Operating modes in the session (docs/specs/2026-10-12-worker-tiers.md "Operating modes"): which
2
+ // mode is on, when it ends by itself, and the eager-delegation note. Pure; register.tsx wires them.
3
+
4
+ import type { Hot, Mode, Settings } from '../types'
5
+
6
+ /** The mode that is on, if the settings name one that exists. */
7
+ export function activeModeOf(s: Pick<Settings, 'modes' | 'activeMode'>): Mode | undefined {
8
+ return s.activeMode ? s.modes.find(m => m.id === s.activeMode) : undefined
9
+ }
10
+
11
+ /** One turning-on of a mode: the same mode turned off and on again is a new one. */
12
+ export function modeKey(s: Pick<Settings, 'activeMode' | 'activeSince'>): string | null {
13
+ return s.activeMode ? `${s.activeMode}@${s.activeSince ?? 0}` : null
14
+ }
15
+
16
+ /** What the main chat reads beside the prompt while an eager mode is on. */
17
+ export function modeNote(name: string): string {
18
+ return `ClaudeRipple mode ${name}: delegate everything except judgment and final review to ripple:auto.`
19
+ }
20
+
21
+ /**
22
+ * The eager note for this prompt: sent on the first prompt after the mode turned on, and again on
23
+ * the first after a compaction (which clears `noted`), never on the turns between.
24
+ */
25
+ export function eagerNote(s: Pick<Settings, 'modes' | 'activeMode' | 'activeSince'>, noted: string | null): { note?: string; noted: string | null } {
26
+ const mode = activeModeOf(s)
27
+ const key = modeKey(s)
28
+ if (!mode || mode.delegation !== 'eager') return { noted }
29
+ return noted === key ? { noted } : { note: modeNote(mode.name), noted: key }
30
+ }
31
+
32
+ /** Modes replace the base worker set, without changing fallback, allow-list or provider policy. */
33
+ export function routingSettings(s: Settings): Settings {
34
+ const mode = activeModeOf(s)
35
+ return mode ? { ...s, rules: { general: { mode: 'ladder', rungs: mode.rungs } } } : s
36
+ }
37
+
38
+ /** The provider whose quota reset ends the mode: 'anthropic' is Claude, any other a ChatGPT provider. */
39
+ export function resetProvider(mode: Pick<Mode, 'until'>): string | undefined {
40
+ return typeof mode.until === 'object' ? mode.until.reset : undefined
41
+ }
42
+
43
+ /** Same binding limit as the band: highest percent used, input order breaking ties. */
44
+ export function claudeWindow(limit: Hot | null): QuotaWindow[] {
45
+ const at = limit?.resetsAt ? Date.parse(limit.resetsAt) : NaN
46
+ return Number.isFinite(at) ? [{ at }] : []
47
+ }
48
+
49
+ /** One account's quota window: when it resets next, and how long it is when known. */
50
+ export type QuotaWindow = { at: number; windowMs?: number }
51
+
52
+ function record(value: unknown): Record<string, unknown> {
53
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
54
+ }
55
+
56
+ /**
57
+ * Each signed-in, unpaused ChatGPT account's known windows, from GET
58
+ * /api/chatgpt-accounts: `quota.rate_limits.{primary,secondary}` with `reset_at` (epoch seconds,
59
+ * or ms) or `reset_after_seconds` from the snapshot's `at`, and `window_minutes`
60
+ * (packages/router/src/providers/chatgpt/index.ts rateLimitsFromHeaders/rateLimitsFromUsage).
61
+ * An account whose reset is unknown is left out.
62
+ */
63
+ export function quotaWindows(body: unknown): QuotaWindow[] {
64
+ const accounts = record(body).accounts
65
+ if (!Array.isArray(accounts)) return []
66
+ return accounts.flatMap(value => {
67
+ const account = record(value)
68
+ if (account.state === 'paused' || account.state === 'needs-login') return []
69
+ const quota = record(account.quota)
70
+ const limits = record(quota.rate_limits)
71
+ return ['primary', 'secondary'].flatMap(key => {
72
+ const window = record(limits[key])
73
+ const resetAt = typeof window.reset_at === 'number' ? (window.reset_at > 1e12 ? window.reset_at : window.reset_at * 1000)
74
+ : typeof window.reset_after_seconds === 'number' && typeof quota.at === 'number' ? quota.at + window.reset_after_seconds * 1000 : undefined
75
+ if (resetAt === undefined || !Number.isFinite(resetAt)) return []
76
+ const minutes = window.window_minutes
77
+ return [{ at: resetAt, ...(typeof minutes === 'number' && minutes > 0 ? { windowMs: minutes * 60_000 } : {}) }]
78
+ })
79
+ })
80
+ }
81
+
82
+ /**
83
+ * End at the first known reset after activation of the mode's chosen provider (`windows` are only
84
+ * that provider's), not after every account resets. ChatGPT's fixed window length also identifies a
85
+ * reset when a fresh snapshot has rolled forward. Claude windows are not extrapolated: the
86
+ * five-hour window can start lazily after idle.
87
+ */
88
+ export function modeOver(mode: Pick<Mode, 'until'>, since: number | undefined, now: number, windows: readonly QuotaWindow[] = []): boolean {
89
+ if (typeof mode.until === 'number') return now >= mode.until
90
+ if (!resetProvider(mode) || since === undefined || !windows.length) return false
91
+ const passed = (at: number) => at > since && at <= now
92
+ return windows.some(w => passed(w.at) || (w.windowMs !== undefined && passed(w.at - w.windowMs)))
93
+ }