openclacky 1.0.0.beta.2 → 1.0.0.beta.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/lib/clacky/agent/cost_tracker.rb +14 -7
- data/lib/clacky/agent/session_serializer.rb +5 -0
- data/lib/clacky/agent.rb +1 -0
- data/lib/clacky/agent_config.rb +44 -3
- data/lib/clacky/cli.rb +21 -0
- data/lib/clacky/json_ui_controller.rb +1 -1
- data/lib/clacky/message_format/bedrock.rb +18 -6
- data/lib/clacky/plain_ui_controller.rb +1 -1
- data/lib/clacky/providers.rb +13 -6
- data/lib/clacky/server/channel/channel_ui_controller.rb +6 -2
- data/lib/clacky/server/http_server.rb +182 -81
- data/lib/clacky/server/session_registry.rb +5 -1
- data/lib/clacky/server/web_ui_controller.rb +4 -2
- data/lib/clacky/skill_loader.rb +6 -12
- data/lib/clacky/tools/terminal.rb +19 -3
- data/lib/clacky/ui2/ui_controller.rb +1 -1
- data/lib/clacky/ui_interface.rb +1 -1
- data/lib/clacky/utils/file_processor.rb +188 -15
- data/lib/clacky/utils/model_pricing.rb +18 -31
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.js +88 -15
- data/lib/clacky/web/index.html +2 -2
- data/lib/clacky/web/sessions.js +23 -10
- data/lib/clacky/web/settings.js +128 -49
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b145934170a510f46e3263c3fbce94acf618cdd416c93e17a7758984361c02b7
|
|
4
|
+
data.tar.gz: 618fdf4917ce68514e0332ad44c522afa061a21884d6e95511f564da985f8433
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 50a63fc4087f97c9a3c3242e2e379da95de6e73cdac3bb75ab11ad67bc03eb151afaf47e3a229bd769a4790f57e3b116309c0908807f35618ae64222feb30575
|
|
7
|
+
data.tar.gz: f492569e101a0b6af312c65cffa244b29a5c47d79201129214e66ac62db7181c29678d64c9ddec88e6dc61525cf2442b0f22647e8dcdb430e0bbc87ca9f1a370
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.0.beta.3] - 2026-04-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Gemini 2.5 Pro support.** The new `gemini2.5-pro` model is now available as a selectable option, giving you access to Google's latest flagship model.
|
|
14
|
+
- **File attachments now support Markdown, plain text, and `.tar.gz` archives.** When you attach `.md`, `.txt`, or `.tar.gz` files to a session, the agent can read and reason over their contents directly.
|
|
15
|
+
- **Image type auto-detection.** Image files are now correctly identified by their binary content (magic bytes), not just their file extension — preventing misclassified images from causing upload or vision errors.
|
|
16
|
+
|
|
17
|
+
### Improved
|
|
18
|
+
- **Settings page fully revamped.** The Web UI Settings panel now saves configuration correctly and exposes a richer set of options for managing providers, models, and API keys.
|
|
19
|
+
- **Skills no longer have a 50-item cap.** The skill loader previously limited the results list to 50 entries; that cap has been removed so all available skills show up.
|
|
20
|
+
- **Cost tracking no longer requires a hard-coded price list.** Model pricing is now resolved dynamically, so new models show real cost figures instead of falling back to a default.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- **Terminal tool no longer crashes on non-UTF-8 output.** Commands that emit binary or non-UTF-8 bytes (e.g. compiled output, legacy scripts) no longer raise an encoding error in the terminal tool.
|
|
24
|
+
|
|
10
25
|
## [1.0.0.beta.2] - 2026-04-27
|
|
11
26
|
|
|
12
27
|
### Added
|
|
@@ -24,15 +24,22 @@ module Clacky
|
|
|
24
24
|
cost = result[:cost]
|
|
25
25
|
pricing_source = result[:source]
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
# Only accumulate cost when the model has known pricing.
|
|
28
|
+
# Unknown models return nil — display N/A, don't add to total.
|
|
29
|
+
if cost
|
|
30
|
+
@total_cost += cost
|
|
31
|
+
iteration_cost = cost
|
|
32
|
+
@cost_source = pricing_source
|
|
33
|
+
@task_cost_source = pricing_source
|
|
34
|
+
end
|
|
32
35
|
|
|
33
36
|
if @config.verbose
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
if cost
|
|
38
|
+
source_label = pricing_source == :price ? "model pricing" : "default pricing"
|
|
39
|
+
@ui&.log("Calculated cost for #{@config.model_name} using #{source_label}: $#{cost.round(6)}", level: :debug)
|
|
40
|
+
else
|
|
41
|
+
@ui&.log("No pricing data available for #{@config.model_name} — cost is unknown", level: :debug)
|
|
42
|
+
end
|
|
36
43
|
@ui&.log("Usage breakdown: prompt=#{usage[:prompt_tokens]}, completion=#{usage[:completion_tokens]}, cache_write=#{usage[:cache_creation_input_tokens] || 0}, cache_read=#{usage[:cache_read_input_tokens] || 0}", level: :debug)
|
|
37
44
|
end
|
|
38
45
|
end
|
|
@@ -18,6 +18,10 @@ module Clacky
|
|
|
18
18
|
@working_dir = session_data[:working_dir]
|
|
19
19
|
@created_at = session_data[:created_at]
|
|
20
20
|
@total_tasks = session_data.dig(:stats, :total_tasks) || 0
|
|
21
|
+
# Restore cost_source so frontend knows if cost is reliable
|
|
22
|
+
cost_src = session_data.dig(:stats, :cost_source)
|
|
23
|
+
@cost_source = (cost_src && cost_src.to_sym) || :estimated
|
|
24
|
+
@task_cost_source = :estimated
|
|
21
25
|
# Restore source; fall back to :manual for sessions saved before this field existed
|
|
22
26
|
@source = (session_data[:source] || "manual").to_sym
|
|
23
27
|
|
|
@@ -65,6 +69,7 @@ module Clacky
|
|
|
65
69
|
total_tasks: @total_tasks,
|
|
66
70
|
total_iterations: @iterations,
|
|
67
71
|
total_cost_usd: @total_cost.round(4),
|
|
72
|
+
cost_source: @cost_source.to_s,
|
|
68
73
|
duration_seconds: @start_time ? (Time.now - @start_time).round(2) : 0,
|
|
69
74
|
last_status: status.to_s,
|
|
70
75
|
cache_stats: @cache_stats,
|
data/lib/clacky/agent.rb
CHANGED
|
@@ -477,6 +477,7 @@ module Clacky
|
|
|
477
477
|
@ui&.show_complete(
|
|
478
478
|
iterations: result[:iterations],
|
|
479
479
|
cost: result[:total_cost_usd],
|
|
480
|
+
cost_source: result[:cost_source],
|
|
480
481
|
duration: result[:duration_seconds],
|
|
481
482
|
cache_stats: result[:cache_stats],
|
|
482
483
|
awaiting_user_feedback: awaiting_user_feedback
|
data/lib/clacky/agent_config.rb
CHANGED
|
@@ -190,6 +190,10 @@ module Clacky
|
|
|
190
190
|
auto_create_threshold: 12,
|
|
191
191
|
reflection_mode: "llm_analysis"
|
|
192
192
|
}
|
|
193
|
+
# Deep-symbolize keys — YAML-loaded hashes come with string keys,
|
|
194
|
+
# but the rest of the codebase accesses with symbols.
|
|
195
|
+
@skill_evolution = @skill_evolution.transform_keys(&:to_sym)
|
|
196
|
+
@skill_evolution.transform_values! { |v| v.is_a?(Hash) ? v.transform_keys(&:to_sym) : v }
|
|
193
197
|
|
|
194
198
|
# Per-session virtual model overlay.
|
|
195
199
|
# When set, #current_model returns a *merged* hash (the resolved @models
|
|
@@ -211,6 +215,13 @@ module Clacky
|
|
|
211
215
|
data = nil
|
|
212
216
|
end
|
|
213
217
|
|
|
218
|
+
# Extract settings from hash-format config (new format).
|
|
219
|
+
# Old flat-array configs have no settings section — all defaults.
|
|
220
|
+
loaded_settings = {}
|
|
221
|
+
if data.is_a?(Hash) && data["settings"].is_a?(Hash)
|
|
222
|
+
loaded_settings = data["settings"]
|
|
223
|
+
end
|
|
224
|
+
|
|
214
225
|
# Parse models from config
|
|
215
226
|
models = parse_models(data)
|
|
216
227
|
|
|
@@ -278,7 +289,21 @@ module Clacky
|
|
|
278
289
|
default_index = models.find_index { |m| m["type"] == "default" } || 0
|
|
279
290
|
default_id = models[default_index] && models[default_index]["id"]
|
|
280
291
|
|
|
281
|
-
|
|
292
|
+
# Build constructor args from loaded settings (new hash-format config)
|
|
293
|
+
# plus the parsed models. Only pass settings that have explicit values;
|
|
294
|
+
# omitted keys get their default from AgentConfig#initialize.
|
|
295
|
+
constructor_args = {
|
|
296
|
+
models: models,
|
|
297
|
+
current_model_index: default_index,
|
|
298
|
+
current_model_id: default_id
|
|
299
|
+
}
|
|
300
|
+
CONFIG_SETTINGS_KEYS.each do |key|
|
|
301
|
+
if loaded_settings.key?(key)
|
|
302
|
+
constructor_args[key.to_sym] = loaded_settings[key]
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
new(**constructor_args)
|
|
282
307
|
end
|
|
283
308
|
|
|
284
309
|
# Auto-injection of provider-preset lite models into @models has been
|
|
@@ -339,11 +364,27 @@ module Clacky
|
|
|
339
364
|
# config.yml remains backward compatible with users on older versions.
|
|
340
365
|
RUNTIME_ONLY_FIELDS = %w[id auto_injected].freeze
|
|
341
366
|
|
|
367
|
+
# Settings keys that are persisted to config.yml.
|
|
368
|
+
# These map directly to AgentConfig accessors.
|
|
369
|
+
CONFIG_SETTINGS_KEYS = %w[
|
|
370
|
+
enable_compression enable_prompt_caching memory_update_enabled
|
|
371
|
+
skill_evolution
|
|
372
|
+
].freeze
|
|
373
|
+
|
|
374
|
+
# Serialize the current agent configuration to YAML.
|
|
375
|
+
# Outputs a hash with "settings" and "models" keys (new format).
|
|
376
|
+
# Backward compatibility: old flat-array format is still readable by .load.
|
|
342
377
|
def to_yaml
|
|
343
|
-
|
|
378
|
+
persistable_models = @models.reject { |m| m["auto_injected"] }.map do |m|
|
|
344
379
|
m.reject { |k, _| RUNTIME_ONLY_FIELDS.include?(k) }
|
|
345
380
|
end
|
|
346
|
-
|
|
381
|
+
settings = {
|
|
382
|
+
"enable_compression" => @enable_compression,
|
|
383
|
+
"enable_prompt_caching" => @enable_prompt_caching,
|
|
384
|
+
"memory_update_enabled" => @memory_update_enabled,
|
|
385
|
+
"skill_evolution" => @skill_evolution
|
|
386
|
+
}
|
|
387
|
+
YAML.dump("settings" => settings, "models" => persistable_models)
|
|
347
388
|
end
|
|
348
389
|
|
|
349
390
|
# Check if any model is configured
|
data/lib/clacky/cli.rb
CHANGED
|
@@ -905,6 +905,14 @@ module Clacky
|
|
|
905
905
|
option :port, type: :numeric, default: 7070, desc: "Listen port (default: 7070)"
|
|
906
906
|
option :brand_test, type: :boolean, default: false,
|
|
907
907
|
desc: "Enable brand test mode: mock license activation without calling remote API"
|
|
908
|
+
option :no_compression, type: :boolean, default: false,
|
|
909
|
+
desc: "Disable message compression (saves tokens but may lose context)"
|
|
910
|
+
option :no_memory, type: :boolean, default: false,
|
|
911
|
+
desc: "Disable automatic memory updates"
|
|
912
|
+
option :no_caching, type: :boolean, default: false,
|
|
913
|
+
desc: "Disable prompt caching"
|
|
914
|
+
option :no_skill_evolution, type: :boolean, default: false,
|
|
915
|
+
desc: "Disable automatic skill evolution"
|
|
908
916
|
def server
|
|
909
917
|
# ── Security gate ──────────────────────────────────────────────────────
|
|
910
918
|
# Binding to 0.0.0.0 exposes the server to the public network.
|
|
@@ -948,6 +956,15 @@ module Clacky
|
|
|
948
956
|
agent_config = Clacky::AgentConfig.load
|
|
949
957
|
agent_config.permission_mode = :confirm_all
|
|
950
958
|
|
|
959
|
+
# Apply CLI overrides to agent config (--no-compression etc.)
|
|
960
|
+
# These override whatever is stored in config.yml.
|
|
961
|
+
agent_config.enable_compression = false if options[:no_compression]
|
|
962
|
+
agent_config.memory_update_enabled = false if options[:no_memory]
|
|
963
|
+
agent_config.enable_prompt_caching = false if options[:no_caching]
|
|
964
|
+
if options[:no_skill_evolution]
|
|
965
|
+
agent_config.skill_evolution[:enabled] = false
|
|
966
|
+
end
|
|
967
|
+
|
|
951
968
|
client_factory = lambda do
|
|
952
969
|
Clacky::Client.new(
|
|
953
970
|
agent_config.api_key,
|
|
@@ -987,6 +1004,10 @@ module Clacky
|
|
|
987
1004
|
|
|
988
1005
|
extra_flags = []
|
|
989
1006
|
extra_flags << "--brand-test" if options[:brand_test]
|
|
1007
|
+
extra_flags << "--no-compression" if options[:no_compression]
|
|
1008
|
+
extra_flags << "--no-memory" if options[:no_memory]
|
|
1009
|
+
extra_flags << "--no-caching" if options[:no_caching]
|
|
1010
|
+
extra_flags << "--no-skill-evolution" if options[:no_skill_evolution]
|
|
990
1011
|
|
|
991
1012
|
Clacky::Logger.console = true
|
|
992
1013
|
|
|
@@ -78,7 +78,7 @@ module Clacky
|
|
|
78
78
|
emit("token_usage", **token_data)
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false)
|
|
81
|
+
def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false, cost_source: nil)
|
|
82
82
|
data = { iterations: iterations, cost: cost }
|
|
83
83
|
data[:duration] = duration if duration
|
|
84
84
|
data[:cache_stats] = cache_stats if cache_stats
|
|
@@ -19,14 +19,26 @@ module Clacky
|
|
|
19
19
|
# Detect if the request should use the Bedrock Converse API.
|
|
20
20
|
# Matches any of:
|
|
21
21
|
# - API key with "ABSK" prefix (native AWS Bedrock)
|
|
22
|
-
# - API key with "clacky-" prefix (Clacky workspace key, proxied via Bedrock Converse)
|
|
23
22
|
# - Model ID with "abs-" prefix (Clacky AI proxy that speaks Bedrock Converse)
|
|
23
|
+
#
|
|
24
|
+
# A bare "clacky-" key is NOT enough: that same workspace key is also
|
|
25
|
+
# used for dsk-*, or-*, and other OpenAI-compatible aliases served by
|
|
26
|
+
# the same Clacky proxy on a different endpoint. The *model prefix* is
|
|
27
|
+
# the source of truth for which upstream format the proxy expects:
|
|
28
|
+
#
|
|
29
|
+
# abs-* → Bedrock Converse (POST /model/{id}/converse)
|
|
30
|
+
# dsk-* → OpenAI-compatible (POST /chat/completions)
|
|
31
|
+
# or-* → OpenAI-compatible (POST /chat/completions)
|
|
32
|
+
# other → depends on base_url + explicit anthropic_format flag
|
|
33
|
+
#
|
|
34
|
+
# Historically this method also returned true for any "clacky-" key,
|
|
35
|
+
# which forced non-abs aliases into the Bedrock endpoint and produced
|
|
36
|
+
# `unknown model "..."` errors. Keep the explicit-prefix rule: if you
|
|
37
|
+
# add a new OpenAI-compatible alias family on the Clacky proxy, it
|
|
38
|
+
# will route correctly without touching this file.
|
|
24
39
|
def self.bedrock_api_key?(api_key, model)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return false if model.to_s.start_with?("dsk-")
|
|
28
|
-
|
|
29
|
-
api_key.to_s.start_with?("ABSK", "clacky-") || model.to_s.start_with?("abs-")
|
|
40
|
+
return true if api_key.to_s.start_with?("ABSK")
|
|
41
|
+
model.to_s.start_with?("abs-")
|
|
30
42
|
end
|
|
31
43
|
|
|
32
44
|
module_function
|
|
@@ -97,7 +97,7 @@ module Clacky
|
|
|
97
97
|
puts_line("[shell] #{command}")
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false)
|
|
100
|
+
def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false, cost_source: nil)
|
|
101
101
|
parts = ["[done] iterations=#{iterations}", "cost=$#{cost.round(4)}"]
|
|
102
102
|
parts << "duration=#{duration.round(1)}s" if duration
|
|
103
103
|
puts_line(parts.join(" "))
|
data/lib/clacky/providers.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Clacky
|
|
|
22
22
|
"name" => "OpenClacky",
|
|
23
23
|
"base_url" => "https://api.openclacky.com",
|
|
24
24
|
"api" => "bedrock",
|
|
25
|
-
"default_model" => "abs-claude-sonnet-4-
|
|
25
|
+
"default_model" => "abs-claude-sonnet-4-6",
|
|
26
26
|
"models" => [
|
|
27
27
|
"abs-claude-opus-4-7",
|
|
28
28
|
"abs-claude-opus-4-6",
|
|
@@ -30,12 +30,15 @@ module Clacky
|
|
|
30
30
|
"abs-claude-sonnet-4-5",
|
|
31
31
|
"abs-claude-haiku-4-5",
|
|
32
32
|
"dsk-deepseek-v4-pro",
|
|
33
|
-
"dsk-deepseek-v4-flash"
|
|
33
|
+
"dsk-deepseek-v4-flash",
|
|
34
|
+
"or-gemini-3-1-pro"
|
|
34
35
|
],
|
|
35
36
|
# Provider-level default: the Claude family served here is vision-capable.
|
|
36
37
|
"capabilities" => { "vision" => true }.freeze,
|
|
37
38
|
# Model-level overrides: DeepSeek models routed through this provider
|
|
38
39
|
# are text-only; images uploaded for them must be downgraded to disk refs.
|
|
40
|
+
# Gemini 3.1 Pro keeps the provider-default vision=true (it accepts
|
|
41
|
+
# image/audio/video input natively via OpenRouter).
|
|
39
42
|
"model_capabilities" => {
|
|
40
43
|
"dsk-deepseek-v4-pro" => { "vision" => false }.freeze,
|
|
41
44
|
"dsk-deepseek-v4-flash" => { "vision" => false }.freeze
|
|
@@ -46,6 +49,10 @@ module Clacky
|
|
|
46
49
|
# weak models (haiku / v4-flash) ARE the lite tier themselves, so
|
|
47
50
|
# they're intentionally not listed here — no injection happens when
|
|
48
51
|
# the default model is already lite-class.
|
|
52
|
+
#
|
|
53
|
+
# or-gemini-3-1-pro is intentionally absent: Gemini has no lite
|
|
54
|
+
# sibling wired up (yet) on this provider; subagents using the
|
|
55
|
+
# Gemini default will just reuse it for lite work until we add one.
|
|
49
56
|
"lite_models" => {
|
|
50
57
|
"abs-claude-opus-4-7" => "abs-claude-haiku-4-5",
|
|
51
58
|
"abs-claude-opus-4-6" => "abs-claude-haiku-4-5",
|
|
@@ -86,8 +93,6 @@ module Clacky
|
|
|
86
93
|
"models" => [
|
|
87
94
|
"deepseek-v4-flash",
|
|
88
95
|
"deepseek-v4-pro",
|
|
89
|
-
"deepseek-chat",
|
|
90
|
-
"deepseek-reasoner"
|
|
91
96
|
],
|
|
92
97
|
# DeepSeek V4 API does not accept image inputs — text-only across all models.
|
|
93
98
|
"capabilities" => { "vision" => false }.freeze,
|
|
@@ -137,9 +142,11 @@ module Clacky
|
|
|
137
142
|
"abs-claude-sonnet-4-5",
|
|
138
143
|
"abs-claude-haiku-4-5",
|
|
139
144
|
"dsk-deepseek-v4-pro",
|
|
140
|
-
"dsk-deepseek-v4-flash"
|
|
145
|
+
"dsk-deepseek-v4-flash",
|
|
146
|
+
"or-gemini-3-1-pro"
|
|
141
147
|
],
|
|
142
|
-
# Same lineup as openclacky — Claude is vision, DeepSeek is text-only
|
|
148
|
+
# Same lineup as openclacky — Claude is vision, DeepSeek is text-only,
|
|
149
|
+
# Gemini inherits the provider-default vision=true.
|
|
143
150
|
"capabilities" => { "vision" => true }.freeze,
|
|
144
151
|
"model_capabilities" => {
|
|
145
152
|
"dsk-deepseek-v4-pro" => { "vision" => false }.freeze,
|
|
@@ -104,10 +104,14 @@ module Clacky
|
|
|
104
104
|
# Suppress
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false)
|
|
107
|
+
def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false, cost_source: nil)
|
|
108
108
|
flush_buffer
|
|
109
109
|
parts = ["Done", "#{iterations} step#{"s" if iterations != 1}"]
|
|
110
|
-
|
|
110
|
+
# Only show cost when pricing source is known (model matched pricing table).
|
|
111
|
+
# Unknown models return nil — skip to avoid misleading numbers.
|
|
112
|
+
if cost && cost > 0 && cost_source
|
|
113
|
+
parts << "$#{cost.round(4)}"
|
|
114
|
+
end
|
|
111
115
|
parts << "#{duration.round(1)}s" if duration
|
|
112
116
|
send_text(parts.join(" · "))
|
|
113
117
|
end
|