openclacky 1.5.20 → 1.5.21
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 +49 -0
- data/README.md +18 -0
- data/lib/clacky/agent/chunk_index.rb +8 -1
- data/lib/clacky/agent/feedback.rb +30 -0
- data/lib/clacky/agent/history_navigation.rb +17 -3
- data/lib/clacky/agent/hook_manager.rb +6 -1
- data/lib/clacky/agent/llm_caller.rb +1 -0
- data/lib/clacky/agent/message_compressor_helper.rb +28 -2
- data/lib/clacky/agent/session_serializer.rb +20 -2
- data/lib/clacky/agent.rb +56 -43
- data/lib/clacky/agent_config.rb +82 -4
- data/lib/clacky/billing/platform_billing.rb +5 -1
- data/lib/clacky/brand_config.rb +1 -1
- data/lib/clacky/cli.rb +3 -2
- data/lib/clacky/client.rb +5 -5
- data/lib/clacky/deepseek_web_tools.rb +156 -0
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +8 -0
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +21 -1
- data/lib/clacky/extension/loader.rb +5 -3
- data/lib/clacky/locales/en.rb +5 -5
- data/lib/clacky/locales/zh.rb +5 -5
- data/lib/clacky/message_format/anthropic.rb +20 -0
- data/lib/clacky/message_format/open_ai.rb +31 -14
- data/lib/clacky/message_history.rb +21 -3
- data/lib/clacky/platform_http_client.rb +6 -2
- data/lib/clacky/providers.rb +23 -14
- data/lib/clacky/search_config.rb +47 -13
- data/lib/clacky/server/browser_manager.rb +35 -5
- data/lib/clacky/server/channel/channel_manager.rb +6 -6
- data/lib/clacky/server/discover.rb +5 -4
- data/lib/clacky/server/http_server.rb +194 -47
- data/lib/clacky/server/model_prices.rb +5 -3
- data/lib/clacky/server/pid_files.rb +25 -0
- data/lib/clacky/server/server_master.rb +9 -3
- data/lib/clacky/server/session_registry.rb +19 -7
- data/lib/clacky/server/web_ui_controller.rb +61 -38
- data/lib/clacky/session_manager.rb +7 -1
- data/lib/clacky/skill.rb +1 -1
- data/lib/clacky/tools/ask_user.rb +4 -1
- data/lib/clacky/tools/base.rb +3 -2
- data/lib/clacky/tools/browser.rb +18 -2
- data/lib/clacky/tools/file_reader.rb +1 -1
- data/lib/clacky/tools/terminal/session_manager.rb +19 -1
- data/lib/clacky/tools/terminal.rb +65 -23
- data/lib/clacky/tools/trash_manager.rb +5 -3
- data/lib/clacky/tools/web_fetch.rb +81 -22
- data/lib/clacky/tools/web_search.rb +45 -4
- data/lib/clacky/ui2/ui_controller.rb +1 -1
- data/lib/clacky/ui_interface.rb +4 -3
- data/lib/clacky/utils/file_processor.rb +51 -0
- data/lib/clacky/utils/model_pricing.rb +132 -13
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +366 -105
- data/lib/clacky/web/components/chat-navigator.js +8 -1
- data/lib/clacky/web/components/mentions.js +2 -13
- data/lib/clacky/web/components/model-picker.js +3 -1
- data/lib/clacky/web/components/onboard.js +13 -10
- data/lib/clacky/web/features/archive/store.js +30 -16
- data/lib/clacky/web/features/archive/view.js +20 -10
- data/lib/clacky/web/features/backup/view.js +5 -5
- data/lib/clacky/web/features/new-session/store.js +3 -3
- data/lib/clacky/web/features/trash/store.js +10 -7
- data/lib/clacky/web/features/trash/view.js +9 -4
- data/lib/clacky/web/i18n.js +106 -30
- data/lib/clacky/web/index.html +90 -60
- data/lib/clacky/web/projects.js +56 -1
- data/lib/clacky/web/sessions.js +244 -131
- data/lib/clacky/web/settings.js +143 -8
- data/lib/clacky/web/ws-dispatcher.js +9 -13
- data/lib/clacky.rb +1 -0
- metadata +4 -1
|
@@ -15,7 +15,7 @@ module Clacky
|
|
|
15
15
|
subagent_instructions subagent_result subagent_transcript token_usage
|
|
16
16
|
compressed_summary chunk_path truncated transient
|
|
17
17
|
chunk_index chunk_count ext_events skill_command skill_command_display
|
|
18
|
-
display_files display_references
|
|
18
|
+
display_files display_references feedback_requests
|
|
19
19
|
].freeze
|
|
20
20
|
|
|
21
21
|
INTERNAL_CONTENT_BLOCK_FIELDS = %i[image_path image_name].freeze
|
|
@@ -37,7 +37,8 @@ module Clacky
|
|
|
37
37
|
# Write operations
|
|
38
38
|
# ─────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
# Append a single message hash to the history.
|
|
40
|
+
# Append a single message hash to the history. An optional block receives
|
|
41
|
+
# the stored, sanitized message so callers can retain its identity.
|
|
41
42
|
#
|
|
42
43
|
# When appending a user message, automatically drop any trailing assistant
|
|
43
44
|
# message that has unanswered tool_calls (no tool_result follows it).
|
|
@@ -48,7 +49,9 @@ module Clacky
|
|
|
48
49
|
if message[:role] == "user"
|
|
49
50
|
drop_dangling_tool_calls!
|
|
50
51
|
end
|
|
51
|
-
|
|
52
|
+
stored_message = deep_sanitize_utf8(message)
|
|
53
|
+
@messages << stored_message
|
|
54
|
+
yield stored_message if block_given?
|
|
52
55
|
self
|
|
53
56
|
end
|
|
54
57
|
|
|
@@ -111,6 +114,16 @@ module Clacky
|
|
|
111
114
|
self
|
|
112
115
|
end
|
|
113
116
|
|
|
117
|
+
# Save the effective request before waiting, including while no tool result
|
|
118
|
+
# exists yet. The original model-authored call remains unchanged.
|
|
119
|
+
def attach_feedback_request(message:, tool_call_id:, request:)
|
|
120
|
+
return false unless message
|
|
121
|
+
|
|
122
|
+
message[:feedback_requests] ||= {}
|
|
123
|
+
message[:feedback_requests][tool_call_id.to_s] = deep_sanitize_utf8(request)
|
|
124
|
+
true
|
|
125
|
+
end
|
|
126
|
+
|
|
114
127
|
# Settle an interrupted fan-out whose assistant.tool_calls turn is dangling
|
|
115
128
|
# because the batch was cancelled before any tool result was written. Mirrors
|
|
116
129
|
# repair_tool_call_pairing (same helpers, same scan) but PERSISTS the result:
|
|
@@ -315,6 +328,11 @@ module Clacky
|
|
|
315
328
|
@messages
|
|
316
329
|
end
|
|
317
330
|
msgs = source.map { |m| strip_for_api(m) }
|
|
331
|
+
# A tool message with a blank id answers no announced call, and one of
|
|
332
|
+
# them makes every later request fail (422 "invalid type: null"). Drop
|
|
333
|
+
# these before pairing: a blank id is not recognized as a tool result,
|
|
334
|
+
# so pairing would otherwise insert a placeholder and orphan the real one.
|
|
335
|
+
msgs.reject! { |m| m[:role] == "tool" && m[:tool_call_id].to_s.strip.empty? }
|
|
318
336
|
msgs = repair_tool_call_pairing(msgs)
|
|
319
337
|
ensure_reasoning_content_consistency(msgs, force: force_reasoning_content_pad)
|
|
320
338
|
end
|
|
@@ -50,9 +50,13 @@ module Clacky
|
|
|
50
50
|
# Auto-detects the platform endpoint:
|
|
51
51
|
# - When CLACKY_LICENSE_SERVER is set → that URL (dev override)
|
|
52
52
|
# - Otherwise → PRIMARY_HOST
|
|
53
|
+
def self.base_url
|
|
54
|
+
override = ENV["CLACKY_LICENSE_SERVER"]
|
|
55
|
+
override && !override.empty? ? override.chomp("/") : PRIMARY_HOST
|
|
56
|
+
end
|
|
57
|
+
|
|
53
58
|
def initialize
|
|
54
|
-
|
|
55
|
-
@base_url = override && !override.empty? ? override : PRIMARY_HOST
|
|
59
|
+
@base_url = self.class.base_url
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
# Send a POST request with a JSON body and return a normalised result hash.
|
data/lib/clacky/providers.rb
CHANGED
|
@@ -60,8 +60,7 @@ module Clacky
|
|
|
60
60
|
"abs-gpt-5.6-luna",
|
|
61
61
|
"or-gemini-4-argon",
|
|
62
62
|
"or-gemini-3-1-pro",
|
|
63
|
-
"or-gemini-3-8-flash"
|
|
64
|
-
"oc-grok-4.7"
|
|
63
|
+
"or-gemini-3-8-flash"
|
|
65
64
|
],
|
|
66
65
|
# Image generation models served by the openclacky platform
|
|
67
66
|
# gateway. The gateway exposes a standard OpenAI-compatible
|
|
@@ -146,7 +145,8 @@ module Clacky
|
|
|
146
145
|
# oc-glm-5.3 is likewise text-only: its upstream rejects any content
|
|
147
146
|
# block other than `type: "text"`, so images must never be sent
|
|
148
147
|
# inline. oc-kimi-k3 and oc-grok-4.7 are natively multimodal and keep
|
|
149
|
-
# vision=true.
|
|
148
|
+
# vision=true; oc-gpt-6.1-sol, oc-gpt-6-astra, oc-gpt-6-sol and
|
|
149
|
+
# oc-gpt-6-luna do too.
|
|
150
150
|
"model_capabilities" => {
|
|
151
151
|
"dsk-deepseek-flash" => { "vision" => true }.freeze,
|
|
152
152
|
"dsk-deepseek-v4-pro" => { "vision" => false }.freeze,
|
|
@@ -199,6 +199,7 @@ module Clacky
|
|
|
199
199
|
"endpoint_variants" => [
|
|
200
200
|
{ "label" => "Primary (Global)", "label_key" => "settings.models.baseurl.variant.openclacky_primary", "base_url" => "https://api.openclacky.com" }.freeze
|
|
201
201
|
].freeze,
|
|
202
|
+
"deepseek_web_tools" => true,
|
|
202
203
|
"website_url" => "https://www.openclacky.com/llm_keys"
|
|
203
204
|
}.freeze,
|
|
204
205
|
|
|
@@ -229,6 +230,7 @@ module Clacky
|
|
|
229
230
|
"model_capabilities" => {
|
|
230
231
|
"deepseek-flash" => { "vision" => true }.freeze
|
|
231
232
|
}.freeze,
|
|
233
|
+
"deepseek_web_tools" => true,
|
|
232
234
|
"website_url" => "https://platform.deepseek.com/api_keys"
|
|
233
235
|
}.freeze,
|
|
234
236
|
|
|
@@ -334,8 +336,8 @@ module Clacky
|
|
|
334
336
|
"name" => "Minimax",
|
|
335
337
|
"base_url" => "https://api.minimaxi.com/v1",
|
|
336
338
|
"api" => "openai-completions",
|
|
337
|
-
"default_model" => "MiniMax-M3",
|
|
338
|
-
"models" => ["MiniMax-M3
|
|
339
|
+
"default_model" => "MiniMax-M3.1-Flash",
|
|
340
|
+
"models" => ["MiniMax-M3.1-Flash", "MiniMax-M3"],
|
|
339
341
|
# MiniMax operates two regional endpoints with identical APIs & model
|
|
340
342
|
# lineup — mainland China (.com) and international (.io). Listing both
|
|
341
343
|
# lets find_by_base_url identify either one as provider "minimax",
|
|
@@ -345,16 +347,15 @@ module Clacky
|
|
|
345
347
|
{ "label" => "Mainland China", "label_key" => "settings.models.baseurl.variant.mainland_cn", "base_url" => "https://api.minimaxi.com/v1", "region" => "cn" }.freeze,
|
|
346
348
|
{ "label" => "International", "label_key" => "settings.models.baseurl.variant.international", "base_url" => "https://api.minimax.io/v1", "region" => "intl" }.freeze
|
|
347
349
|
].freeze,
|
|
348
|
-
#
|
|
349
|
-
#
|
|
350
|
-
#
|
|
351
|
-
# vision=false below. M3 exposes a 1,000,000-token context window
|
|
352
|
-
# (M2.7: 204,800; M2.5: 40,960).
|
|
350
|
+
# M3.1-Flash and M3 are natively multimodal (image_url content parts,
|
|
351
|
+
# 1M context) and override the provider-level vision=false, which
|
|
352
|
+
# stays as the safe default for retired text-only ids (M2.x).
|
|
353
353
|
"capabilities" => { "vision" => false }.freeze,
|
|
354
354
|
"model_capabilities" => {
|
|
355
|
-
"MiniMax-M3" => { "vision" => true }.freeze
|
|
355
|
+
"MiniMax-M3.1-Flash" => { "vision" => true }.freeze,
|
|
356
|
+
"MiniMax-M3" => { "vision" => true }.freeze
|
|
356
357
|
}.freeze,
|
|
357
|
-
"default_ocr_model" => "MiniMax-M3",
|
|
358
|
+
"default_ocr_model" => "MiniMax-M3.1-Flash",
|
|
358
359
|
"website_url" => "https://platform.minimax.io/"
|
|
359
360
|
}.freeze,
|
|
360
361
|
|
|
@@ -642,7 +643,6 @@ module Clacky
|
|
|
642
643
|
"glm-5.2",
|
|
643
644
|
"kimi-k3",
|
|
644
645
|
"glm-5.1",
|
|
645
|
-
"minimax-m2.7",
|
|
646
646
|
"minimax-m3",
|
|
647
647
|
"kimi-k2.7-code",
|
|
648
648
|
"kimi-k2.6",
|
|
@@ -664,7 +664,6 @@ module Clacky
|
|
|
664
664
|
"lite_models" => {
|
|
665
665
|
"glm-5.2" => "glm-5.1",
|
|
666
666
|
"kimi-k3" => "kimi-k2.6",
|
|
667
|
-
"minimax-m3" => "minimax-m2.7",
|
|
668
667
|
"kimi-k2.7-code" => "kimi-k2.6",
|
|
669
668
|
"deepseek-v4-pro" => "deepseek-v4-flash",
|
|
670
669
|
"gemini-3-flash-preview" => "deepseek-v4-flash"
|
|
@@ -1173,6 +1172,16 @@ module Clacky
|
|
|
1173
1172
|
return true unless caps.key?(key)
|
|
1174
1173
|
caps[key] != false
|
|
1175
1174
|
end
|
|
1175
|
+
|
|
1176
|
+
# True when the preset declares native DeepSeek web tools (search + fetch).
|
|
1177
|
+
# Currently DeepSeek direct ("deepseekv4") and the OpenClacky platform
|
|
1178
|
+
# gateway ("openclacky").
|
|
1179
|
+
def deepseek_web_tools?(provider_id)
|
|
1180
|
+
preset = PRESETS[provider_id]
|
|
1181
|
+
return false unless preset
|
|
1182
|
+
|
|
1183
|
+
preset["deepseek_web_tools"] == true
|
|
1184
|
+
end
|
|
1176
1185
|
end
|
|
1177
1186
|
end
|
|
1178
1187
|
end
|
data/lib/clacky/search_config.rb
CHANGED
|
@@ -2,19 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
require "fileutils"
|
|
5
|
+
require_relative "deepseek_web_tools"
|
|
5
6
|
|
|
6
7
|
module Clacky
|
|
7
8
|
# Search provider selection, backed by ~/.clacky/search.yml.
|
|
8
9
|
#
|
|
9
|
-
# The built-in Parallel/DuckDuckGo/Bing
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
10
|
+
# The built-in Parallel/DuckDuckGo/Bing chain is the default: it needs no key
|
|
11
|
+
# and no configuration, so a fresh install searches the web out of the box.
|
|
12
|
+
# DeepSeek's hosted web tools are opt-in — they render JS-heavy pages to
|
|
13
|
+
# Markdown but bill per call, so they only run once the user picks them.
|
|
13
14
|
#
|
|
14
15
|
# search.yml schema:
|
|
15
|
-
# provider: tavily —
|
|
16
|
-
# blank
|
|
17
|
-
#
|
|
16
|
+
# provider: tavily — "deepseek_web" for DeepSeek's hosted
|
|
17
|
+
# search/fetch, blank for built-in engines only,
|
|
18
|
+
# otherwise the basename of a script in ~/.clacky/searchers/
|
|
19
|
+
# key: tvly-xxx — credential for the selected provider (optional for
|
|
20
|
+
# deepseek_web, which can reuse a configured model key)
|
|
21
|
+
#
|
|
22
|
+
# A missing file, or one without a `provider` key, means "never chosen" and
|
|
23
|
+
# resolves to the free chain.
|
|
18
24
|
#
|
|
19
25
|
# The directory is the registry: dropping my_engine.rb into
|
|
20
26
|
# ~/.clacky/searchers/ and setting `provider: my_engine` is all it takes,
|
|
@@ -22,21 +28,49 @@ module Clacky
|
|
|
22
28
|
module SearchConfig
|
|
23
29
|
CONFIG_PATH = File.expand_path("~/.clacky/search.yml").freeze
|
|
24
30
|
|
|
31
|
+
# Hosted DeepSeek search + page fetch, billed per successful call upstream.
|
|
32
|
+
# It behaves like a provider but ships with the gem, so it has no script.
|
|
33
|
+
DEEPSEEK_WEB = "deepseek_web"
|
|
34
|
+
|
|
25
35
|
class << self
|
|
26
36
|
# @return [Hash] { "provider" => String, "key" => String }
|
|
27
37
|
def load
|
|
28
38
|
raw = File.exist?(CONFIG_PATH) ? YAML.load_file(CONFIG_PATH) : nil
|
|
29
|
-
return
|
|
39
|
+
return default_config unless raw.is_a?(Hash)
|
|
30
40
|
|
|
31
|
-
{
|
|
41
|
+
{
|
|
42
|
+
"provider" => raw["provider"].to_s.strip,
|
|
43
|
+
"key" => raw["key"].to_s.strip
|
|
44
|
+
}
|
|
32
45
|
rescue StandardError
|
|
33
|
-
|
|
46
|
+
default_config
|
|
34
47
|
end
|
|
35
48
|
|
|
36
|
-
def save(provider
|
|
49
|
+
def save(provider: nil, key: nil)
|
|
50
|
+
current = load
|
|
51
|
+
new_provider = provider.nil? ? current["provider"] : provider.to_s.strip
|
|
52
|
+
new_key = key.nil? ? current["key"] : key.to_s.strip
|
|
53
|
+
|
|
54
|
+
payload = {
|
|
55
|
+
"provider" => new_provider,
|
|
56
|
+
"key" => new_key
|
|
57
|
+
}
|
|
58
|
+
|
|
37
59
|
FileUtils.mkdir_p(File.dirname(CONFIG_PATH))
|
|
38
|
-
File.write(CONFIG_PATH,
|
|
60
|
+
File.write(CONFIG_PATH, payload.to_yaml)
|
|
39
61
|
File.chmod(0o600, CONFIG_PATH)
|
|
62
|
+
|
|
63
|
+
# A new provider or key invalidates an earlier failure, so the next
|
|
64
|
+
# search retries DeepSeek instead of sitting out the cooldown.
|
|
65
|
+
Clacky::DeepSeekWebTools.reset_cooldown!
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def deepseek_web?
|
|
69
|
+
load["provider"] == DEEPSEEK_WEB
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private def default_config
|
|
73
|
+
{ "provider" => "", "key" => "" }
|
|
40
74
|
end
|
|
41
75
|
|
|
42
76
|
# Absolute path of the configured searcher, or nil when unset/missing.
|
|
@@ -44,7 +78,7 @@ module Clacky
|
|
|
44
78
|
# web_search silently falls back instead of erroring on every search.
|
|
45
79
|
def script_path
|
|
46
80
|
name = load["provider"]
|
|
47
|
-
return nil if name.empty?
|
|
81
|
+
return nil if name.empty? || name == DEEPSEEK_WEB
|
|
48
82
|
|
|
49
83
|
Clacky::Utils::SearcherManager.path_for(name)
|
|
50
84
|
end
|
|
@@ -19,6 +19,7 @@ module Clacky
|
|
|
19
19
|
# enabled: true/false — whether the browser tool is active
|
|
20
20
|
# chrome_version: "146" — detected Chrome version (set by browser-setup skill)
|
|
21
21
|
# configured_at: date — when setup was last run
|
|
22
|
+
# screenshot_max_base64_bytes: Integer — screenshot size ceiling (settings UI)
|
|
22
23
|
#
|
|
23
24
|
# Liveness check strategy:
|
|
24
25
|
# process_alive? sends an MCP `ping` (standard in MCP spec 2024-11-05) and
|
|
@@ -116,27 +117,49 @@ module Clacky
|
|
|
116
117
|
enabled = cfg["enabled"] == true
|
|
117
118
|
running = @process && @process[:wait_thr]&.alive?
|
|
118
119
|
{
|
|
119
|
-
enabled:
|
|
120
|
-
daemon_running:
|
|
121
|
-
chrome_version:
|
|
120
|
+
enabled: enabled,
|
|
121
|
+
daemon_running: !!running,
|
|
122
|
+
chrome_version: cfg["chrome_version"],
|
|
123
|
+
screenshot_max_base64_bytes: effective_screenshot_limit(cfg)
|
|
122
124
|
}
|
|
123
125
|
end
|
|
124
126
|
|
|
125
127
|
# Write browser.yml with the given config and reload the daemon.
|
|
126
128
|
# Called by HttpServer POST /api/browser/configure.
|
|
129
|
+
# Existing keys are preserved so re-running browser-setup does not wipe
|
|
130
|
+
# user preferences such as the screenshot limits.
|
|
127
131
|
# @param chrome_version [String] detected Chrome major version
|
|
128
132
|
def configure(chrome_version:)
|
|
129
|
-
cfg =
|
|
133
|
+
cfg = load_config.merge(
|
|
130
134
|
"enabled" => true,
|
|
131
135
|
"browser" => "chrome",
|
|
132
136
|
"chrome_version" => chrome_version.to_s,
|
|
133
137
|
"configured_at" => Date.today.to_s
|
|
134
|
-
|
|
138
|
+
)
|
|
135
139
|
FileUtils.mkdir_p(File.dirname(BROWSER_CONFIG_PATH))
|
|
136
140
|
File.write(BROWSER_CONFIG_PATH, cfg.to_yaml)
|
|
137
141
|
reload
|
|
138
142
|
end
|
|
139
143
|
|
|
144
|
+
# Persist the screenshot preferences written by the settings UI.
|
|
145
|
+
# No daemon reload: the browser tool re-reads browser.yml on every screenshot.
|
|
146
|
+
# @param max_base64_bytes [Integer, nil] screenshot size ceiling in bytes
|
|
147
|
+
# @return [Hash] the effective settings after the update
|
|
148
|
+
def update_screenshot_settings(max_base64_bytes: nil)
|
|
149
|
+
raise "Browser not configured. Run /browser-setup first." unless File.exist?(BROWSER_CONFIG_PATH)
|
|
150
|
+
|
|
151
|
+
if !max_base64_bytes.nil? && !(max_base64_bytes.is_a?(Integer) && max_base64_bytes.positive?)
|
|
152
|
+
raise ArgumentError, "screenshot_max_base64_bytes must be a positive integer"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
cfg = load_config
|
|
156
|
+
cfg["screenshot_max_base64_bytes"] = max_base64_bytes unless max_base64_bytes.nil?
|
|
157
|
+
|
|
158
|
+
File.write(BROWSER_CONFIG_PATH, cfg.to_yaml)
|
|
159
|
+
@config = cfg
|
|
160
|
+
{ screenshot_max_base64_bytes: effective_screenshot_limit(cfg) }
|
|
161
|
+
end
|
|
162
|
+
|
|
140
163
|
# Toggle the browser tool on/off by flipping `enabled` in browser.yml.
|
|
141
164
|
# Raises if browser.yml doesn't exist (not yet set up).
|
|
142
165
|
# @return [Boolean] new enabled state
|
|
@@ -224,6 +247,13 @@ module Clacky
|
|
|
224
247
|
{}
|
|
225
248
|
end
|
|
226
249
|
|
|
250
|
+
private def effective_screenshot_limit(cfg)
|
|
251
|
+
limit = cfg["screenshot_max_base64_bytes"]
|
|
252
|
+
return limit if limit.is_a?(Integer) && limit.positive?
|
|
253
|
+
|
|
254
|
+
Clacky::Tools::Browser::SCREENSHOT_MAX_BASE64_BYTES
|
|
255
|
+
end
|
|
256
|
+
|
|
227
257
|
# Must be called inside @mutex
|
|
228
258
|
def ensure_process!
|
|
229
259
|
return if process_alive?
|
|
@@ -152,7 +152,7 @@ module Clacky
|
|
|
152
152
|
# receive_id for outbound messages), so we extract the chat portion.
|
|
153
153
|
prefix = "#{platform}:"
|
|
154
154
|
ids = []
|
|
155
|
-
@registry.list.each do |summary|
|
|
155
|
+
@registry.list(archived: nil).each do |summary|
|
|
156
156
|
@registry.with_session(summary[:id]) do |s|
|
|
157
157
|
(s[:channel_keys] || []).each do |key|
|
|
158
158
|
next unless key.start_with?(prefix)
|
|
@@ -463,7 +463,7 @@ module Clacky
|
|
|
463
463
|
when "/unbind"
|
|
464
464
|
unbound = false
|
|
465
465
|
cleared_agents = []
|
|
466
|
-
@registry.list.each do |summary|
|
|
466
|
+
@registry.list(archived: nil).each do |summary|
|
|
467
467
|
@registry.with_session(summary[:id]) do |s|
|
|
468
468
|
# Set#delete always returns self — use delete? so an unknown key
|
|
469
469
|
# reports "No binding found." instead of a false "Unbound.".
|
|
@@ -726,7 +726,7 @@ module Clacky
|
|
|
726
726
|
# /bind and /unbind keep agent.channel_info strictly in sync with channel_keys
|
|
727
727
|
# (see handle_bind / handle_unbind), so the two sources never disagree on the
|
|
728
728
|
# same key for two different sessions — a single pass is sufficient.
|
|
729
|
-
@registry.list.each do |summary|
|
|
729
|
+
@registry.list(archived: nil).each do |summary|
|
|
730
730
|
found = nil
|
|
731
731
|
@registry.with_session(summary[:id]) { |s| found = s[:channel_keys]&.include?(key) }
|
|
732
732
|
return summary[:id] if found
|
|
@@ -814,7 +814,7 @@ module Clacky
|
|
|
814
814
|
# The on-disk summary is authoritative here, so sessions evicted from
|
|
815
815
|
# memory are cleaned too — which is exactly the case /bind used to miss.
|
|
816
816
|
def clear_channel_info_for_key(key, keep_session_id: nil)
|
|
817
|
-
@registry.list(limit: nil).each do |summary|
|
|
817
|
+
@registry.list(limit: nil, archived: nil).each do |summary|
|
|
818
818
|
next if summary[:id] == keep_session_id
|
|
819
819
|
|
|
820
820
|
info = summary[:channel_info]
|
|
@@ -839,7 +839,7 @@ module Clacky
|
|
|
839
839
|
end
|
|
840
840
|
|
|
841
841
|
def bind_key_to_session(key, session_id)
|
|
842
|
-
@registry.list.each do |summary|
|
|
842
|
+
@registry.list(archived: nil).each do |summary|
|
|
843
843
|
@registry.with_session(summary[:id]) { |s| s[:channel_keys]&.delete(key) }
|
|
844
844
|
end
|
|
845
845
|
@registry.with_session(session_id) do |s|
|
|
@@ -932,7 +932,7 @@ module Clacky
|
|
|
932
932
|
def restore_channel_bindings
|
|
933
933
|
bound_keys = Set.new
|
|
934
934
|
restored_count = 0
|
|
935
|
-
@registry.list(limit: nil).each do |summary|
|
|
935
|
+
@registry.list(limit: nil, archived: nil).each do |summary|
|
|
936
936
|
info = summary[:channel_info]
|
|
937
937
|
next unless info.is_a?(Hash) && info[:platform] && info[:user_id] && info[:chat_id]
|
|
938
938
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative "pid_files"
|
|
4
4
|
require "socket"
|
|
5
5
|
|
|
6
6
|
module Clacky
|
|
7
7
|
module Server
|
|
8
8
|
# Discover locally-running Clacky server(s) by scanning PID files
|
|
9
|
-
# written by Master
|
|
9
|
+
# written by Master in the configured PID directory.
|
|
10
10
|
#
|
|
11
11
|
# Used by the CLI (bare `clacky agent` mode) to auto-detect a sibling
|
|
12
12
|
# server process, so skills that call back into the server (channels,
|
|
@@ -17,7 +17,6 @@ module Clacky
|
|
|
17
17
|
# Does NOT probe the TCP port (avoids false positives from stale files
|
|
18
18
|
# but also avoids noisy connection attempts).
|
|
19
19
|
module Discover
|
|
20
|
-
PID_FILE_GLOB = File.join(Dir.tmpdir, "clacky-master-*.pid").freeze
|
|
21
20
|
PID_FILE_REGEX = /clacky-master-(\d+)\.pid\z/.freeze
|
|
22
21
|
|
|
23
22
|
module_function
|
|
@@ -41,7 +40,9 @@ module Clacky
|
|
|
41
40
|
#
|
|
42
41
|
# @return [Array<Hash>] sorted by port ascending
|
|
43
42
|
def find_all_local
|
|
44
|
-
|
|
43
|
+
directory = PidFiles.directory
|
|
44
|
+
Dir.glob("clacky-master-*.pid", base: directory).filter_map do |filename|
|
|
45
|
+
path = File.join(directory, filename)
|
|
45
46
|
m = path.match(PID_FILE_REGEX)
|
|
46
47
|
next nil unless m
|
|
47
48
|
|