openclacky 1.2.8 → 1.2.10

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/lib/clacky/agent/llm_caller.rb +3 -0
  4. data/lib/clacky/agent/message_compressor_helper.rb +6 -5
  5. data/lib/clacky/agent/session_serializer.rb +4 -0
  6. data/lib/clacky/agent.rb +9 -0
  7. data/lib/clacky/agent_config.rb +111 -8
  8. data/lib/clacky/brand_config.rb +1 -0
  9. data/lib/clacky/cli.rb +49 -22
  10. data/lib/clacky/client.rb +6 -2
  11. data/lib/clacky/default_skills/channel-manager/SKILL.md +33 -110
  12. data/lib/clacky/default_skills/media-gen/SKILL.md +128 -0
  13. data/lib/clacky/idle_compression_timer.rb +38 -15
  14. data/lib/clacky/media/base.rb +68 -0
  15. data/lib/clacky/media/gemini.rb +36 -0
  16. data/lib/clacky/media/generator.rb +78 -0
  17. data/lib/clacky/media/openai_compat.rb +168 -0
  18. data/lib/clacky/providers.rb +89 -2
  19. data/lib/clacky/rich_ui_controller.rb +1549 -0
  20. data/lib/clacky/server/channel/adapters/weixin/adapter.rb +24 -2
  21. data/lib/clacky/server/channel/channel_manager.rb +89 -2
  22. data/lib/clacky/server/http_server.rb +334 -29
  23. data/lib/clacky/session_manager.rb +9 -8
  24. data/lib/clacky/telemetry.rb +26 -6
  25. data/lib/clacky/ui2/layout_manager.rb +11 -7
  26. data/lib/clacky/ui2/ui_controller.rb +2 -2
  27. data/lib/clacky/ui_interface.rb +1 -1
  28. data/lib/clacky/utils/model_pricing.rb +75 -53
  29. data/lib/clacky/version.rb +1 -1
  30. data/lib/clacky/web/app.css +393 -14
  31. data/lib/clacky/web/billing.js +1 -1
  32. data/lib/clacky/web/i18n.js +86 -4
  33. data/lib/clacky/web/index.html +23 -3
  34. data/lib/clacky/web/model-tester.js +58 -0
  35. data/lib/clacky/web/onboard.js +17 -30
  36. data/lib/clacky/web/sessions.js +443 -2
  37. data/lib/clacky/web/settings.js +372 -97
  38. data/lib/clacky/web/workspace.js +9 -1
  39. data/lib/clacky.rb +3 -0
  40. data/scripts/build/lib/network.sh +61 -30
  41. data/scripts/install.ps1 +16 -4
  42. data/scripts/install.sh +61 -30
  43. data/scripts/install_browser.sh +61 -30
  44. data/scripts/install_full.sh +61 -30
  45. data/scripts/install_rails_deps.sh +61 -30
  46. data/scripts/install_system_deps.sh +61 -30
  47. metadata +12 -3
  48. data/lib/clacky/default_skills/channel-manager/feishu_setup.rb +0 -574
@@ -179,16 +179,17 @@ module Clacky
179
179
  deleted
180
180
  end
181
181
 
182
- # Keep only the most recent N sessions by created_at; delete the rest.
183
- # Returns count of deleted sessions.
182
+ # Keep only the most recent N non-pinned sessions by created_at; the rest
183
+ # are soft-deleted (moved to the session trash, recoverable). Pinned
184
+ # sessions are never deleted and do not count toward the cap.
185
+ # Returns count of soft-deleted sessions.
184
186
  def cleanup_by_count(keep:)
185
- sessions = all_sessions # already sorted newest-first
186
- return 0 if sessions.size <= keep
187
+ non_pinned = all_sessions.reject { |s| s[:pinned] } # already sorted newest-first
188
+ return 0 if non_pinned.size <= keep
187
189
 
188
- sessions[keep..].each do |session|
189
- filepath = File.join(@sessions_dir, generate_filename(session[:session_id], session[:created_at]))
190
- _hard_delete_session_with_chunks(filepath) if File.exist?(filepath)
191
- end.size
190
+ victims = non_pinned[keep..]
191
+ victims.each { |session| soft_delete(session[:session_id]) }
192
+ victims.size
192
193
  end
193
194
 
194
195
  # ── Session trash (delegates to Tools::TrashManager) ──────────────
@@ -23,6 +23,11 @@ module Clacky
23
23
  # POST /api/v1/telemetry/startup
24
24
  # POST /api/v1/telemetry/task
25
25
  module Telemetry
26
+ LAUNCH_SOURCES = {
27
+ "installer" => "installer",
28
+ nil => "cli"
29
+ }.freeze
30
+
26
31
  class << self
27
32
  # Called on every CLI startup (agent and server mode).
28
33
  # No local dedup — the server deduplicates by device_hash for unique
@@ -32,11 +37,13 @@ module Clacky
32
37
 
33
38
  brand = Clacky::BrandConfig.load
34
39
  payload = {
35
- device_id: resolve_device_id(brand),
36
- version: Clacky::VERSION,
37
- os: RbConfig::CONFIG["host_os"],
38
- ruby_version: RUBY_VERSION,
39
- brand: brand.branded? ? brand.package_name : nil
40
+ device_id: resolve_device_id(brand),
41
+ version: Clacky::VERSION,
42
+ os: RbConfig::CONFIG["host_os"],
43
+ ruby_version: RUBY_VERSION,
44
+ brand: brand.branded? ? brand.package_name : nil,
45
+ launch_source: LAUNCH_SOURCES.fetch(ENV["CLACKY_LAUNCHED_BY"], "cli"),
46
+ container: detect_container
40
47
  }.compact
41
48
 
42
49
  fire_and_forget("/api/v1/telemetry/startup", payload)
@@ -56,7 +63,8 @@ module Clacky
56
63
  payload = {
57
64
  device_id: resolve_device_id(brand),
58
65
  version: Clacky::VERSION,
59
- brand: brand.branded? ? brand.package_name : nil
66
+ brand: brand.branded? ? brand.package_name : nil,
67
+ container: detect_container
60
68
  }
61
69
  payload.merge!(extract_task_metrics(result)) if result.is_a?(Hash)
62
70
 
@@ -74,6 +82,18 @@ module Clacky
74
82
  brand.device_id
75
83
  end
76
84
 
85
+ private def detect_container
86
+ return "docker" if File.exist?("/.dockerenv")
87
+
88
+ begin
89
+ cgroup = File.read("/proc/1/cgroup")
90
+ return "docker" if cgroup.include?("docker") || cgroup.include?("containerd")
91
+ rescue Errno::ENOENT, Errno::EACCES
92
+ nil
93
+ end
94
+ nil
95
+ end
96
+
77
97
  private def extract_task_metrics(result)
78
98
  cache = result[:cache_stats] || {}
79
99
  duration = result[:duration_seconds]
@@ -381,15 +381,19 @@ module Clacky
381
381
  render_all
382
382
  end
383
383
 
384
- def cleanup_screen
384
+ def cleanup_screen(clear_screen: false)
385
385
  @render_mutex.synchronize do
386
- fixed_start = fixed_area_start_row
387
- (fixed_start...screen.height).each do |row|
388
- screen.move_cursor(row, 0)
389
- screen.clear_line
386
+ if clear_screen
387
+ screen.clear_screen(mode: :reset)
388
+ else
389
+ fixed_start = fixed_area_start_row
390
+ (fixed_start...screen.height).each do |row|
391
+ screen.move_cursor(row, 0)
392
+ screen.clear_line
393
+ end
394
+ screen.move_cursor([@output_row, 0].max, 0)
395
+ print "\r"
390
396
  end
391
- screen.move_cursor([@output_row, 0].max, 0)
392
- print "\r"
393
397
  screen.show_cursor
394
398
  screen.flush
395
399
  end
@@ -150,9 +150,9 @@ module Clacky
150
150
  end
151
151
 
152
152
  # Stop the UI controller
153
- def stop
153
+ def stop(clear_screen: false)
154
154
  @running = false
155
- @layout.cleanup_screen
155
+ @layout.cleanup_screen(clear_screen: clear_screen)
156
156
  end
157
157
 
158
158
  # Clear the input area
@@ -135,6 +135,6 @@ module Clacky
135
135
 
136
136
  # === Path redaction (for encrypted brand skill tmpdirs) ===
137
137
  # === Lifecycle ===
138
- def stop; end
138
+ def stop(clear_screen: false); end
139
139
  end
140
140
  end
@@ -350,80 +350,104 @@ module Clacky
350
350
  cache: { write: 0.30, read: 0.03 }
351
351
  },
352
352
 
353
+ # M3 (released 2026-06-01) is MiniMax's multimodal flagship. Official
354
+ # pricing is tiered by context length (≤512K vs 512K–1M); per the
355
+ # project's "displayed ≤ actual" convention we record only the lowest
356
+ # (≤512K) tier as a flat rate — the global TIERED_PRICING_THRESHOLD is
357
+ # 200K, so applying the 512K–1M rate to the 200K–512K band would over-
358
+ # charge. Listed at original (non-promotional) prices: input $0.60,
359
+ # output $2.40, cache read $0.12 per 1M tokens.
360
+ "minimax-m3" => {
361
+ input: { default: 0.60, over_200k: 0.60 },
362
+ output: { default: 2.40, over_200k: 2.40 },
363
+ cache: { write: 0.60, read: 0.12 }
364
+ },
365
+
353
366
  "minimax-m2.7" => {
354
367
  input: { default: 0.30, over_200k: 0.30 },
355
368
  output: { default: 1.20, over_200k: 1.20 },
356
369
  cache: { write: 0.30, read: 0.06 }
357
370
  },
358
371
 
359
- # Qwen (Alibaba DashScope) USD per 1M tokens, Singapore region list price.
360
- # Source: Alibaba Cloud Model Studio international pricing.
361
- # Cache convention (mirrors DeepSeek/Kimi/GLM "displayed ≤ actual"):
362
- # - DashScope has two cache modes; implicit is auto-on, explicit is opt-in.
363
- # Implicit: write @ 100% input, read @ 20% input (no setup, no guarantee)
364
- # Explicit: write @ 125% input, read @ 10% input (cache_control marker)
365
- # - We bill writes at the regular input rate (matches implicit, and avoids
366
- # surprising users with the explicit 25% surcharge).
367
- # - We bill reads at 20% (implicit rate) — the conservative side; users on
368
- # explicit caching will see real bills slightly *lower* than displayed.
372
+ # Qwen (Alibaba DashScope) - USD per 1M tokens, international (Singapore) list price.
373
+ # Source: Alibaba Cloud Model Studio international console per-model pages.
374
+ #
375
+ # Pricing convention:
376
+ # - These rates are used for user-facing cost ESTIMATION, so we always use
377
+ # the standard LIST price and intentionally ignore any limited-time promo
378
+ # discounts. A promo lowers the user's actual bill, never raises it, so
379
+ # estimating at list price keeps the estimate a safe upper bound and avoids
380
+ # churn whenever a promo starts or ends.
381
+ # - We record the model's LOWEST context tier (e.g. input<=256k / <=128k) as a
382
+ # flat rate, since the global TIERED_PRICING_THRESHOLD is 200K and does not
383
+ # match Qwen's per-model breakpoints.
384
+ # - cache.write = official explicit-cache-create price.
385
+ # - cache.read = official explicit-cache-hit price.
386
+ # - When a model has NO published explicit-cache price (e.g. qwen3.6-27b,
387
+ # qwen-plus-latest), cache.write/read fall back to the input rate.
388
+ # qwen3.7-max: NOT tiered (single flat tier per Alibaba's definition).
389
+ # List price: input 2.5, output 7.5, explicit write 3.125, explicit read 0.25.
369
390
  "qwen3.7-max" => {
370
- input: { default: 1.20, over_200k: 1.20 },
371
- output: { default: 6.00, over_200k: 6.00 },
372
- cache: { write: 1.20, read: 0.24 }
391
+ input: { default: 2.5, over_200k: 2.5 },
392
+ output: { default: 7.5, over_200k: 7.5 },
393
+ cache: { write: 3.125, read: 0.25 }
373
394
  },
374
395
 
396
+ # qwen3.7-plus: list price (<=256k tier):
397
+ # input 0.4, output 1.6, explicit write 0.5, explicit read 0.04.
375
398
  "qwen3.7-plus" => {
376
- input: { default: 0.40, over_200k: 0.40 },
377
- output: { default: 2.40, over_200k: 2.40 },
378
- cache: { write: 0.40, read: 0.08 }
379
- },
380
-
381
- "qwen3.7-flash" => {
382
- input: { default: 0.15, over_200k: 0.15 },
383
- output: { default: 0.90, over_200k: 0.90 },
384
- cache: { write: 0.15, read: 0.03 }
399
+ input: { default: 0.4, over_200k: 0.4 },
400
+ output: { default: 1.6, over_200k: 1.6 },
401
+ cache: { write: 0.5, read: 0.04 }
385
402
  },
386
403
 
404
+ # qwen3.6-plus: list price (<=256k tier). Official explicit-cache prices.
405
+ # input 0.50, output 3.00, explicit write 0.625, explicit read 0.05
387
406
  "qwen3.6-plus" => {
388
- input: { default: 0.40, over_200k: 0.40 },
389
- output: { default: 2.40, over_200k: 2.40 },
390
- cache: { write: 0.40, read: 0.08 }
407
+ input: { default: 0.50, over_200k: 0.50 },
408
+ output: { default: 3.00, over_200k: 3.00 },
409
+ cache: { write: 0.625, read: 0.05 }
391
410
  },
392
411
 
412
+ # qwen3.6-max (qwen3.6-max-preview): list price (<=128k tier).
413
+ # input 1.30, output 7.80, explicit write 1.625, explicit read 0.13
393
414
  "qwen3.6-max" => {
394
- input: { default: 1.20, over_200k: 1.20 },
395
- output: { default: 6.00, over_200k: 6.00 },
396
- cache: { write: 1.20, read: 0.24 }
415
+ input: { default: 1.30, over_200k: 1.30 },
416
+ output: { default: 7.80, over_200k: 7.80 },
417
+ cache: { write: 1.625, read: 0.13 }
397
418
  },
398
419
 
420
+ # qwen3.6-27b: list price, no explicit-cache pricing published.
421
+ # Cache write/read fall back to the input rate (no cache discount).
399
422
  "qwen3.6-27b" => {
400
- input: { default: 0.20, over_200k: 0.20 },
401
- output: { default: 0.80, over_200k: 0.80 },
402
- cache: { write: 0.20, read: 0.04 }
423
+ input: { default: 0.60, over_200k: 0.60 },
424
+ output: { default: 3.60, over_200k: 3.60 },
425
+ cache: { write: 0.60, read: 0.60 }
403
426
  },
404
427
 
428
+ # qwen3.6-flash: list price (<=256k tier).
429
+ # input 0.25, output 1.50, explicit write 0.3125, explicit read 0.025
405
430
  "qwen3.6-flash" => {
406
- input: { default: 0.15, over_200k: 0.15 },
407
- output: { default: 0.90, over_200k: 0.90 },
408
- cache: { write: 0.15, read: 0.03 }
431
+ input: { default: 0.25, over_200k: 0.25 },
432
+ output: { default: 1.50, over_200k: 1.50 },
433
+ cache: { write: 0.3125, read: 0.025 }
409
434
  },
410
435
 
436
+ # qwen-plus-latest: list price (<=256k tier), no explicit-cache pricing.
437
+ # Cache write/read fall back to the input rate (no cache discount).
411
438
  "qwen-plus-latest" => {
412
439
  input: { default: 0.40, over_200k: 0.40 },
413
440
  output: { default: 1.20, over_200k: 1.20 },
414
- cache: { write: 0.40, read: 0.08 }
415
- },
416
-
417
- "qwen-vl-plus" => {
418
- input: { default: 0.14, over_200k: 0.14 },
419
- output: { default: 0.41, over_200k: 0.41 },
420
- cache: { write: 0.14, read: 0.028 }
441
+ cache: { write: 0.40, read: 0.40 }
421
442
  },
422
443
 
423
- "qwen-vl-max" => {
424
- input: { default: 0.52, over_200k: 0.52 },
425
- output: { default: 2.08, over_200k: 2.08 },
426
- cache: { write: 0.52, read: 0.104 }
444
+ # qwen3-vl-plus: replaces the retiring qwen-vl-plus. List price
445
+ # (128k<input<=256k tier). input 0.60, output 4.80,
446
+ # explicit write 0.75, explicit read 0.06.
447
+ "qwen3-vl-plus" => {
448
+ input: { default: 0.60, over_200k: 0.60 },
449
+ output: { default: 4.80, over_200k: 4.80 },
450
+ cache: { write: 0.75, read: 0.06 }
427
451
  },
428
452
 
429
453
  }.freeze
@@ -583,6 +607,8 @@ module Clacky
583
607
  "glm-4.7"
584
608
  # MiniMax — model ids in providers.rb use capitalised "MiniMax-M2.x"
585
609
  # but we match case-insensitively and map to the lowercased table key.
610
+ when /^minimax-m3$/i
611
+ "minimax-m3"
586
612
  when /^minimax-m2\.5$/i
587
613
  "minimax-m2.5"
588
614
  when /^minimax-m2\.7$/i
@@ -591,14 +617,12 @@ module Clacky
591
617
  # Qwen (Alibaba DashScope) — strict anchored match per registered
592
618
  # model id in providers.rb. qwen3.7-* is the latest flagship line;
593
619
  # qwen3.6-* are the previous generation; qwen-plus-latest is the
594
- # rolling alias for the latest Qwen-Plus release; qwen-vl-* are
595
- # the multimodal SKUs.
620
+ # rolling alias for the latest Qwen-Plus release; qwen3-vl-plus is
621
+ # the multimodal SKU (replaces the retired qwen-vl-plus/max).
596
622
  when /^qwen3\.7-max$/i
597
623
  "qwen3.7-max"
598
624
  when /^qwen3\.7-plus$/i
599
625
  "qwen3.7-plus"
600
- when /^qwen3\.7-flash$/i
601
- "qwen3.7-flash"
602
626
  when /^qwen3\.6-plus$/i
603
627
  "qwen3.6-plus"
604
628
  when /^qwen3\.6-max$/i
@@ -609,10 +633,8 @@ module Clacky
609
633
  "qwen3.6-flash"
610
634
  when /^qwen-plus-latest$/i
611
635
  "qwen-plus-latest"
612
- when /^qwen-vl-plus$/i
613
- "qwen-vl-plus"
614
- when /^qwen-vl-max$/i
615
- "qwen-vl-max"
636
+ when /^qwen3-vl-plus$/i
637
+ "qwen3-vl-plus"
616
638
 
617
639
  # OpenAI GPT-5.x models — match various dashed/dotted/compact forms
618
640
  # (e.g. "gpt-5.5", "gpt-5-5", "gpt5.5", "gpt55")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clacky
4
- VERSION = "1.2.8"
4
+ VERSION = "1.2.10"
5
5
  end