openclacky 0.9.28 → 0.9.30
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 +39 -0
- data/docs/deploy-architecture.md +619 -0
- data/lib/clacky/agent/llm_caller.rb +14 -2
- data/lib/clacky/agent/message_compressor.rb +24 -6
- data/lib/clacky/agent/message_compressor_helper.rb +17 -10
- data/lib/clacky/agent/session_serializer.rb +69 -0
- data/lib/clacky/agent/skill_manager.rb +2 -2
- data/lib/clacky/agent.rb +3 -0
- data/lib/clacky/brand_config.rb +29 -3
- data/lib/clacky/clacky_auth_client.rb +152 -0
- data/lib/clacky/clacky_cloud_config.rb +123 -0
- data/lib/clacky/cli.rb +13 -0
- data/lib/clacky/client.rb +21 -7
- data/lib/clacky/cloud_project_client.rb +169 -0
- data/lib/clacky/default_agents/base_prompt.md +1 -0
- data/lib/clacky/default_parsers/doc_parser.rb +9 -9
- data/lib/clacky/default_skills/browser-setup/SKILL.md +9 -0
- data/lib/clacky/default_skills/channel-setup/SKILL.md +21 -4
- data/lib/clacky/default_skills/channel-setup/feishu_setup.rb +8 -2
- data/lib/clacky/default_skills/deploy/SKILL.md +96 -5
- data/lib/clacky/default_skills/deploy/scripts/rails_deploy.rb +1268 -274
- data/lib/clacky/default_skills/deploy/tools/create_database_service.rb +341 -0
- data/lib/clacky/default_skills/deploy/tools/execute_deployment.rb +72 -147
- data/lib/clacky/default_skills/deploy/tools/fetch_runtime_logs.rb +60 -50
- data/lib/clacky/default_skills/deploy/tools/list_services.rb +47 -60
- data/lib/clacky/default_skills/deploy/tools/set_deploy_variables.rb +147 -96
- data/lib/clacky/default_skills/new/SKILL.md +117 -5
- data/lib/clacky/default_skills/new/scripts/cloud_project_init.sh +74 -0
- data/lib/clacky/default_skills/new/scripts/create_rails_project.sh +32 -0
- data/lib/clacky/deploy_api_client.rb +484 -0
- data/lib/clacky/json_ui_controller.rb +16 -10
- data/lib/clacky/message_format/bedrock.rb +3 -2
- data/lib/clacky/message_history.rb +8 -0
- data/lib/clacky/plain_ui_controller.rb +1 -6
- data/lib/clacky/providers.rb +23 -4
- data/lib/clacky/server/browser_manager.rb +3 -1
- data/lib/clacky/server/channel/adapters/feishu/ws_client.rb +2 -1
- data/lib/clacky/server/channel/adapters/wecom/ws_client.rb +3 -1
- data/lib/clacky/server/channel/adapters/weixin/adapter.rb +5 -5
- data/lib/clacky/server/http_server.rb +12 -2
- data/lib/clacky/server/server_master.rb +43 -7
- data/lib/clacky/server/web_ui_controller.rb +17 -9
- data/lib/clacky/skill.rb +6 -2
- data/lib/clacky/tools/run_project.rb +4 -1
- data/lib/clacky/tools/shell.rb +7 -1
- data/lib/clacky/ui2/ui_controller.rb +1 -5
- data/lib/clacky/ui_interface.rb +5 -7
- data/lib/clacky/utils/arguments_parser.rb +22 -5
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +45 -5
- data/lib/clacky/web/app.js +126 -19
- data/lib/clacky/web/i18n.js +57 -0
- data/lib/clacky/web/sessions.js +108 -39
- data/lib/clacky/web/skills.js +8 -2
- data/lib/clacky.rb +3 -0
- metadata +8 -1
|
@@ -417,11 +417,11 @@ module Clacky
|
|
|
417
417
|
r = text.dup
|
|
418
418
|
r.gsub!(/```[^\n]*\n?([\s\S]*?)```/) { Regexp.last_match(1).strip }
|
|
419
419
|
r.gsub!(/!\[[^\]]*\]\([^)]*\)/, "")
|
|
420
|
-
r.gsub!(/\[([^\]]+)\]\([^)]*\)/, '')
|
|
421
|
-
r.gsub!(/\*\*([^*]+)\*\*/, '')
|
|
422
|
-
r.gsub!(/\*([^*]+)\*/, '')
|
|
423
|
-
r.gsub!(/__([^_]+)__/, '')
|
|
424
|
-
r.gsub!(/_([^_]+)_/, '')
|
|
420
|
+
r.gsub!(/\[([^\]]+)\]\([^)]*\)/, '\\1')
|
|
421
|
+
r.gsub!(/\*\*([^*]+)\*\*/, '\\1')
|
|
422
|
+
r.gsub!(/\*([^*]+)\*/, '\\1')
|
|
423
|
+
r.gsub!(/__([^_]+)__/, '\\1')
|
|
424
|
+
r.gsub!(/_([^_]+)_/, '\\1')
|
|
425
425
|
r.gsub!(/^#+\s+/, "")
|
|
426
426
|
r.gsub!(/^[-*_]{3,}\s*$/, "")
|
|
427
427
|
r.strip
|
|
@@ -333,7 +333,13 @@ module Clacky
|
|
|
333
333
|
# hosts with OPEN_TIMEOUT=8s per attempt × 2 attempts = up to ~16s on the
|
|
334
334
|
# primary alone, before failing over to the fallback domain. Give them a
|
|
335
335
|
# generous 90s so retry + failover can complete without being cut short.
|
|
336
|
-
timeout_sec = path.start_with?("/api/brand")
|
|
336
|
+
timeout_sec = if path.start_with?("/api/brand")
|
|
337
|
+
90
|
|
338
|
+
elsif path == "/api/tool/browser"
|
|
339
|
+
30
|
|
340
|
+
else
|
|
341
|
+
10
|
|
342
|
+
end
|
|
337
343
|
Timeout.timeout(timeout_sec) do
|
|
338
344
|
_dispatch_rest(req, res)
|
|
339
345
|
end
|
|
@@ -723,6 +729,7 @@ module Clacky
|
|
|
723
729
|
local_skills = brand.installed_brand_skills.map do |name, meta|
|
|
724
730
|
{
|
|
725
731
|
"name" => meta["name"] || name,
|
|
732
|
+
"name_zh" => meta["name_zh"].to_s,
|
|
726
733
|
# Use locally cached description so it renders correctly offline
|
|
727
734
|
"description" => meta["description"].to_s,
|
|
728
735
|
"description_zh" => meta["description_zh"].to_s,
|
|
@@ -1385,7 +1392,9 @@ module Clacky
|
|
|
1385
1392
|
|
|
1386
1393
|
entry = {
|
|
1387
1394
|
name: skill.identifier,
|
|
1395
|
+
name_zh: skill.name_zh,
|
|
1388
1396
|
description: skill.context_description,
|
|
1397
|
+
description_zh: skill.description_zh,
|
|
1389
1398
|
source: source,
|
|
1390
1399
|
enabled: !skill.disabled?,
|
|
1391
1400
|
invalid: skill.invalid?,
|
|
@@ -1431,10 +1440,11 @@ module Clacky
|
|
|
1431
1440
|
loader = agent.skill_loader
|
|
1432
1441
|
loaded_from = loader.loaded_from
|
|
1433
1442
|
|
|
1434
|
-
|
|
1443
|
+
skill_data = skills.map do |skill|
|
|
1435
1444
|
source_type = loaded_from[skill.identifier]
|
|
1436
1445
|
{
|
|
1437
1446
|
name: skill.identifier,
|
|
1447
|
+
name_zh: skill.name_zh,
|
|
1438
1448
|
description: skill.description || skill.context_description,
|
|
1439
1449
|
description_zh: skill.description_zh,
|
|
1440
1450
|
encrypted: skill.encrypted?,
|
|
@@ -42,15 +42,30 @@ module Clacky
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def run
|
|
45
|
-
# 0.
|
|
46
|
-
print_banner
|
|
47
|
-
|
|
48
|
-
# 1. Kill any existing master on this port before binding.
|
|
45
|
+
# 0. Kill any existing master on this port before binding.
|
|
49
46
|
kill_existing_master
|
|
50
47
|
|
|
51
|
-
#
|
|
52
|
-
|
|
48
|
+
# 1. Try to bind the socket.
|
|
49
|
+
# If port is 7070 (default), try fallback ports 7071-7075 if occupied.
|
|
50
|
+
# If port is non-default (user-specified), only try that exact port.
|
|
51
|
+
original_port = @port
|
|
52
|
+
max_port = (@port == 7070) ? (@port + 5) : @port
|
|
53
|
+
@socket = bind_with_fallback(@host, @port, max_port: max_port)
|
|
54
|
+
|
|
55
|
+
if @socket.nil?
|
|
56
|
+
if @port == 7070
|
|
57
|
+
Clacky::Logger.error("[Master] No available ports in range 7070-7075")
|
|
58
|
+
else
|
|
59
|
+
Clacky::Logger.error("[Master] Port #{@port} is in use")
|
|
60
|
+
end
|
|
61
|
+
exit(1)
|
|
62
|
+
end
|
|
63
|
+
|
|
53
64
|
@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
|
65
|
+
@port = @socket.local_address.ip_port # Update to actual bound port
|
|
66
|
+
|
|
67
|
+
# 2. Print banner after port is determined
|
|
68
|
+
print_banner(port_changed: @port != original_port, original_port: original_port)
|
|
54
69
|
|
|
55
70
|
write_pid_file
|
|
56
71
|
|
|
@@ -221,12 +236,33 @@ module Clacky
|
|
|
221
236
|
end
|
|
222
237
|
end
|
|
223
238
|
|
|
224
|
-
|
|
239
|
+
# Try to bind to preferred_port, fall back to next ports if occupied.
|
|
240
|
+
# Returns the bound TCPServer, or nil if all ports in range are occupied.
|
|
241
|
+
def bind_with_fallback(host, preferred_port, max_port:)
|
|
242
|
+
(preferred_port..max_port).each do |port|
|
|
243
|
+
begin
|
|
244
|
+
server = TCPServer.new(host, port)
|
|
245
|
+
Clacky::Logger.info("[Master] Bound to port #{port}") if port != preferred_port
|
|
246
|
+
return server
|
|
247
|
+
rescue Errno::EADDRINUSE
|
|
248
|
+
next
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
nil
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def print_banner(port_changed: false, original_port: nil)
|
|
225
255
|
banner = Clacky::Banner.new
|
|
226
256
|
puts ""
|
|
227
257
|
puts banner.colored_cli_logo
|
|
228
258
|
puts banner.colored_tagline
|
|
229
259
|
puts ""
|
|
260
|
+
|
|
261
|
+
if port_changed
|
|
262
|
+
puts " [!] Port #{original_port} is in use, using #{@port} instead"
|
|
263
|
+
puts ""
|
|
264
|
+
end
|
|
265
|
+
|
|
230
266
|
puts " Web UI: #{banner.highlight("http://#{@host}:#{@port}")}"
|
|
231
267
|
puts " Version: #{Clacky::VERSION}"
|
|
232
268
|
puts " Press Ctrl-C to stop."
|
|
@@ -208,13 +208,25 @@ module Clacky
|
|
|
208
208
|
|
|
209
209
|
# === Progress ===
|
|
210
210
|
|
|
211
|
-
def show_progress(message = nil, prefix_newline: true,
|
|
212
|
-
@progress_start_time = Time.now
|
|
211
|
+
def show_progress(message = nil, prefix_newline: true, progress_type: "thinking", phase: "active", metadata: {})
|
|
212
|
+
@progress_start_time = Time.now if phase == "active"
|
|
213
213
|
@live_progress_message = message
|
|
214
214
|
# Reset stdout buffer for each new command so re-subscribe only replays current run
|
|
215
|
-
@live_stdout_buffer = []
|
|
216
|
-
|
|
215
|
+
@live_stdout_buffer = [] if phase == "active"
|
|
216
|
+
|
|
217
|
+
data = {
|
|
218
|
+
message: message,
|
|
219
|
+
progress_type: progress_type,
|
|
220
|
+
phase: phase,
|
|
221
|
+
status: phase == "active" ? "start" : "stop" # backward compat
|
|
222
|
+
}
|
|
223
|
+
data[:metadata] = metadata unless metadata.empty?
|
|
224
|
+
data[:elapsed] = (Time.now - @progress_start_time).round(1) if phase == "done" && @progress_start_time
|
|
225
|
+
|
|
226
|
+
emit("progress", **data)
|
|
217
227
|
forward_to_subscribers { |sub| sub.show_progress(message) }
|
|
228
|
+
|
|
229
|
+
@progress_start_time = nil if phase == "done"
|
|
218
230
|
end
|
|
219
231
|
|
|
220
232
|
# Stream shell stdout/stderr lines to the browser while a command is running.
|
|
@@ -230,14 +242,10 @@ module Clacky
|
|
|
230
242
|
end
|
|
231
243
|
|
|
232
244
|
def clear_progress
|
|
233
|
-
elapsed = @progress_start_time ? (Time.now - @progress_start_time).round(1) : 0
|
|
234
|
-
@progress_start_time = nil
|
|
235
|
-
@live_progress_message = nil
|
|
236
245
|
@live_tool_call = nil # command finished — nothing left to replay
|
|
237
246
|
# Keep @live_stdout_buffer intact — it will be reset on the next show_progress call.
|
|
238
247
|
# This allows a brief replay window even after the command finishes.
|
|
239
|
-
|
|
240
|
-
forward_to_subscribers { |sub| sub.clear_progress }
|
|
248
|
+
show_progress(progress_type: "thinking", phase: "done")
|
|
241
249
|
end
|
|
242
250
|
|
|
243
251
|
# Replay in-progress command state to a newly (re-)subscribing browser tab.
|
data/lib/clacky/skill.rb
CHANGED
|
@@ -12,6 +12,7 @@ module Clacky
|
|
|
12
12
|
# Frontmatter fields that are recognized
|
|
13
13
|
FRONTMATTER_FIELDS = %w[
|
|
14
14
|
name
|
|
15
|
+
name_zh
|
|
15
16
|
description
|
|
16
17
|
description_zh
|
|
17
18
|
disable-model-invocation
|
|
@@ -29,7 +30,7 @@ module Clacky
|
|
|
29
30
|
].freeze
|
|
30
31
|
|
|
31
32
|
attr_reader :directory, :frontmatter, :source_path
|
|
32
|
-
attr_reader :name, :description, :description_zh, :content
|
|
33
|
+
attr_reader :name, :description, :name_zh, :description_zh, :content
|
|
33
34
|
attr_reader :disable_model_invocation, :user_invocable
|
|
34
35
|
attr_reader :allowed_tools, :context, :agent_type, :argument_hint, :hooks
|
|
35
36
|
attr_reader :fork_agent, :model, :forbidden_tools, :auto_summarize
|
|
@@ -273,6 +274,7 @@ module Clacky
|
|
|
273
274
|
def to_h
|
|
274
275
|
{
|
|
275
276
|
name: identifier,
|
|
277
|
+
name_zh: @name_zh,
|
|
276
278
|
description: context_description,
|
|
277
279
|
directory: @directory.to_s,
|
|
278
280
|
source_path: @source_path.to_s,
|
|
@@ -386,6 +388,7 @@ module Clacky
|
|
|
386
388
|
if @cached_metadata
|
|
387
389
|
@frontmatter = {}
|
|
388
390
|
@name = @cached_metadata["name"]
|
|
391
|
+
@name_zh = @cached_metadata["name_zh"]
|
|
389
392
|
@description = @cached_metadata["description"]
|
|
390
393
|
@description_zh = @cached_metadata["description_zh"]
|
|
391
394
|
@content = plain ? plain_file.read.then { |raw| extract_content_only(raw) } : nil
|
|
@@ -451,6 +454,7 @@ module Clacky
|
|
|
451
454
|
# Pull known fields out of @frontmatter into instance variables.
|
|
452
455
|
private def extract_fields_from_frontmatter
|
|
453
456
|
@name = @frontmatter["name"]
|
|
457
|
+
@name_zh = @frontmatter["name_zh"]
|
|
454
458
|
@description = @frontmatter["description"]
|
|
455
459
|
@description_zh = @frontmatter["description_zh"]
|
|
456
460
|
@disable_model_invocation = @frontmatter["disable-model-invocation"]
|
|
@@ -471,7 +475,7 @@ module Clacky
|
|
|
471
475
|
# the skill is marked @invalid so the UI can display it greyed-out.
|
|
472
476
|
def sanitize_frontmatter
|
|
473
477
|
dir_slug = @directory.basename.to_s
|
|
474
|
-
valid_slug = ->(s) { s.to_s.match?(/\A[a-z0-9][a-z0-
|
|
478
|
+
valid_slug = ->(s) { s.to_s.match?(/\A[a-z0-9][a-z0-9_-]*\z/) }
|
|
475
479
|
|
|
476
480
|
# --- name ---
|
|
477
481
|
# Brand skills loaded via cached_metadata have their name pre-sanitized by
|
|
@@ -113,7 +113,10 @@ module Clacky
|
|
|
113
113
|
stop_existing_process if @@process_state
|
|
114
114
|
|
|
115
115
|
begin
|
|
116
|
-
|
|
116
|
+
# close_others: true prevents inheriting the server's listening socket (port 7070)
|
|
117
|
+
# when run_project is called from openclacky server. Without this, the spawned
|
|
118
|
+
# project server may inherit and hold the fd, causing port conflicts.
|
|
119
|
+
stdin, stdout, stderr, wait_thr = Open3.popen3(command, close_others: true)
|
|
117
120
|
|
|
118
121
|
@@process_state = {
|
|
119
122
|
stdin: stdin,
|
data/lib/clacky/tools/shell.rb
CHANGED
|
@@ -108,7 +108,13 @@ module Clacky
|
|
|
108
108
|
# acquiring /dev/tty as a controlling terminal, which triggers SIGTTIN
|
|
109
109
|
# when the process is not in the terminal's foreground group.
|
|
110
110
|
# Instead, wrap_with_shell sources the user's rc file explicitly.
|
|
111
|
-
|
|
111
|
+
#
|
|
112
|
+
# close_others: true prevents the child from inheriting file descriptors
|
|
113
|
+
# other than stdin/stdout/stderr. This is critical when running inside
|
|
114
|
+
# openclacky server — without it, user commands (rails s, npm run dev, etc.)
|
|
115
|
+
# inherit the server's listening socket (port 7070), causing port conflicts
|
|
116
|
+
# when the child process spawns its own server that persists after shell exit.
|
|
117
|
+
popen3_opts = { pgroup: 0, close_others: true }
|
|
112
118
|
popen3_opts[:chdir] = working_dir if working_dir && Dir.exist?(working_dir)
|
|
113
119
|
|
|
114
120
|
begin
|
|
@@ -466,8 +466,7 @@ module Clacky
|
|
|
466
466
|
# Show progress indicator with dynamic elapsed time
|
|
467
467
|
# @param message [String] Progress message (optional, will use random thinking verb if nil)
|
|
468
468
|
# @param prefix_newline [Boolean] Whether to add a blank line before progress (default: true)
|
|
469
|
-
|
|
470
|
-
def show_progress(message = nil, prefix_newline: true, output_buffer: nil)
|
|
469
|
+
def show_progress(message = nil, prefix_newline: true, progress_type: "thinking", phase: "active", metadata: {})
|
|
471
470
|
# Stop any existing progress thread
|
|
472
471
|
stop_progress_thread
|
|
473
472
|
|
|
@@ -476,7 +475,6 @@ module Clacky
|
|
|
476
475
|
|
|
477
476
|
@progress_message = message || Clacky::THINKING_VERBS.sample
|
|
478
477
|
@progress_start_time = Time.now
|
|
479
|
-
@progress_output_buffer = output_buffer
|
|
480
478
|
# Flag used by the progress thread to know when to stop gracefully.
|
|
481
479
|
# Using a flag + join is safe because Thread#kill can interrupt a thread
|
|
482
480
|
# while it holds @render_mutex, causing a permanent deadlock.
|
|
@@ -562,7 +560,6 @@ module Clacky
|
|
|
562
560
|
|
|
563
561
|
# Signal thread to stop without joining — it will exit on next loop tick
|
|
564
562
|
@progress_start_time = nil
|
|
565
|
-
@progress_output_buffer = nil
|
|
566
563
|
@stdout_lines = nil
|
|
567
564
|
@progress_thread_stop = true
|
|
568
565
|
# Detach: let the thread die on its own; we do NOT join here
|
|
@@ -594,7 +591,6 @@ module Clacky
|
|
|
594
591
|
# @render_mutex) and leave the mutex permanently locked.
|
|
595
592
|
def stop_progress_thread
|
|
596
593
|
@progress_start_time = nil
|
|
597
|
-
@progress_output_buffer = nil
|
|
598
594
|
@progress_thread_stop = true
|
|
599
595
|
if @progress_thread&.alive?
|
|
600
596
|
# Join with a short timeout; fall back to kill only as a last resort
|
data/lib/clacky/ui_interface.rb
CHANGED
|
@@ -30,7 +30,11 @@ module Clacky
|
|
|
30
30
|
def log(message, level: :info); end
|
|
31
31
|
|
|
32
32
|
# === Progress ===
|
|
33
|
-
|
|
33
|
+
# Unified progress indicator with type-based display customization.
|
|
34
|
+
# progress_type: "thinking" | "retrying" | "idle_compress" | custom
|
|
35
|
+
# phase: "active" | "done"
|
|
36
|
+
# metadata: extensible hash (e.g., {attempt: 3, total: 10} for retries)
|
|
37
|
+
def show_progress(message = nil, prefix_newline: true, progress_type: "thinking", phase: "active", metadata: {}); end
|
|
34
38
|
def clear_progress; end
|
|
35
39
|
|
|
36
40
|
# === State updates ===
|
|
@@ -39,12 +43,6 @@ module Clacky
|
|
|
39
43
|
def set_working_status; end
|
|
40
44
|
def set_idle_status; end
|
|
41
45
|
|
|
42
|
-
# === Idle compression status ===
|
|
43
|
-
# Emits a two-phase idle compression status update.
|
|
44
|
-
# phase: :start → show "Idle detected. Compressing..." (with spinner)
|
|
45
|
-
# phase: :end → update same element with final result (skipped / compressed)
|
|
46
|
-
def show_idle_status(phase:, message:); end
|
|
47
|
-
|
|
48
46
|
# === Blocking interaction ===
|
|
49
47
|
def request_confirmation(message, default: true); end
|
|
50
48
|
|
|
@@ -10,6 +10,16 @@ module Clacky
|
|
|
10
10
|
# 1. Try standard parsing
|
|
11
11
|
begin
|
|
12
12
|
args = JSON.parse(call[:arguments], symbolize_names: true)
|
|
13
|
+
|
|
14
|
+
# Check if any key contains XML tags (< or >) indicating contamination
|
|
15
|
+
# Even though JSON.parse succeeded, the keys might be malformed
|
|
16
|
+
has_xml_contamination = args.keys.any? { |k| k.to_s.include?('<') || k.to_s.include?('>') }
|
|
17
|
+
|
|
18
|
+
if has_xml_contamination
|
|
19
|
+
# Force repair even though JSON.parse succeeded
|
|
20
|
+
raise JSON::ParserError.new("Keys contain XML contamination")
|
|
21
|
+
end
|
|
22
|
+
|
|
13
23
|
return validate_required_params(call, args, tool_registry)
|
|
14
24
|
rescue JSON::ParserError => e
|
|
15
25
|
# Continue to repair
|
|
@@ -30,24 +40,31 @@ module Clacky
|
|
|
30
40
|
|
|
31
41
|
# Simple JSON repair: complete brackets and quotes, and remove XML contamination
|
|
32
42
|
def self.repair_json(json_str)
|
|
33
|
-
result = json_str.strip
|
|
34
43
|
|
|
44
|
+
result = json_str.strip
|
|
45
|
+
# Step 0: Convert literal \n (backslash+n) to real newlines
|
|
46
|
+
result = result.gsub(/\\n/, "\n")
|
|
47
|
+
# Step 0.5: Unescape quotes in JSON keys and values (\" -> ")
|
|
48
|
+
# This handles cases like {"end_line\":550 or name=\"path\"
|
|
49
|
+
result = result.gsub(/\\"/, '"')
|
|
35
50
|
# Step 1: Remove XML-style parameter tags that Claude might mix in
|
|
36
51
|
# Pattern 1: </parameter> closing tags - remove completely
|
|
37
52
|
result = result.gsub(/<\/parameter>/, '')
|
|
38
|
-
|
|
53
|
+
|
|
39
54
|
# Pattern 2: <parameter name="key"> or <parameter name="key": opening tags -> convert to JSON key
|
|
40
55
|
# Example: \n<parameter name="end_line"> 330 -> , "end_line": 330
|
|
41
56
|
# Also handles: \n<parameter name="end_line": 330 -> , "end_line": 330
|
|
42
|
-
result = result.gsub(/<parameter\s+name="([^"]+)":\s*/) { |match| ", \"#{$1}\": " }
|
|
43
|
-
result = result.gsub(/<parameter\s+name="([^"]+)">/) { |match| ", \"#{$1}\":" }
|
|
44
|
-
|
|
57
|
+
# result = result.gsub(/<parameter\s+name="([^"\\]+)":\s*/) { |match| ", \"#{$1}\": " }
|
|
58
|
+
# result = result.gsub(/<parameter\s+name="([^"\\]+)">/) { |match| ", \"#{$1}\":" }
|
|
59
|
+
result = result.gsub(/<parameter\s+name=\\?"([^"\\]+)\\?"[>:]?\s*/) { |match| ", \"#{$1}\": " }
|
|
60
|
+
|
|
45
61
|
# Pattern 3: Remove any remaining XML-like tags
|
|
46
62
|
result = result.gsub(/<[^>]+>/, '')
|
|
47
63
|
|
|
48
64
|
# Step 2: Clean up newlines with commas
|
|
49
65
|
# Example: 315\n, "end_line" -> 315, "end_line"
|
|
50
66
|
result = result.gsub(/\n\s*,/, ',')
|
|
67
|
+
result = result.gsub(/\n,/, ',')
|
|
51
68
|
result = result.gsub(/,\s*\n/, ',')
|
|
52
69
|
|
|
53
70
|
# Step 3: Clean up formatting issues
|
data/lib/clacky/version.rb
CHANGED
data/lib/clacky/web/app.css
CHANGED
|
@@ -793,13 +793,23 @@ body {
|
|
|
793
793
|
.session-item .session-name {
|
|
794
794
|
flex: 1;
|
|
795
795
|
min-width: 0;
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
796
|
+
display: flex;
|
|
797
|
+
align-items: center;
|
|
798
|
+
gap: 4px;
|
|
799
799
|
color: var(--color-text-secondary);
|
|
800
800
|
font-weight: 500;
|
|
801
801
|
transition: color .2s ease, font-weight .2s ease;
|
|
802
802
|
}
|
|
803
|
+
.session-item .session-name .session-name__text {
|
|
804
|
+
flex: 1;
|
|
805
|
+
min-width: 0;
|
|
806
|
+
overflow: hidden;
|
|
807
|
+
text-overflow: ellipsis;
|
|
808
|
+
white-space: nowrap;
|
|
809
|
+
}
|
|
810
|
+
.session-item .session-name .session-badge {
|
|
811
|
+
flex-shrink: 0;
|
|
812
|
+
}
|
|
803
813
|
/* Hover: subtle background overlay */
|
|
804
814
|
.session-item:hover {
|
|
805
815
|
background: var(--color-bg-hover);
|
|
@@ -863,8 +873,6 @@ body {
|
|
|
863
873
|
.session-name {
|
|
864
874
|
font-size: 12px;
|
|
865
875
|
white-space: nowrap;
|
|
866
|
-
overflow: hidden;
|
|
867
|
-
text-overflow: ellipsis;
|
|
868
876
|
}
|
|
869
877
|
/* While renaming, lift overflow so the input is fully visible */
|
|
870
878
|
.session-name.renaming {
|
|
@@ -2004,6 +2012,14 @@ body {
|
|
|
2004
2012
|
padding: 4px 0;
|
|
2005
2013
|
}
|
|
2006
2014
|
|
|
2015
|
+
.skill-ac-empty {
|
|
2016
|
+
padding: 14px 16px;
|
|
2017
|
+
font-size: 13px;
|
|
2018
|
+
color: var(--color-text-tertiary, #888);
|
|
2019
|
+
text-align: left;
|
|
2020
|
+
font-style: italic;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2007
2023
|
.skill-ac-item {
|
|
2008
2024
|
display: flex;
|
|
2009
2025
|
align-items: baseline;
|
|
@@ -2040,6 +2056,30 @@ body {
|
|
|
2040
2056
|
color: var(--color-accent-primary);
|
|
2041
2057
|
white-space: nowrap;
|
|
2042
2058
|
flex-shrink: 0;
|
|
2059
|
+
display: flex;
|
|
2060
|
+
align-items: center;
|
|
2061
|
+
gap: 6px;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
.skill-ac-name-zh {
|
|
2065
|
+
font-family: inherit;
|
|
2066
|
+
font-size: 13px;
|
|
2067
|
+
font-weight: 600;
|
|
2068
|
+
color: var(--color-accent-primary);
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
.skill-ac-name-id {
|
|
2072
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
|
2073
|
+
font-size: 11px;
|
|
2074
|
+
font-weight: 400;
|
|
2075
|
+
color: var(--color-text-secondary);
|
|
2076
|
+
opacity: 0.7;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
.skill-ac-highlight {
|
|
2080
|
+
background: rgba(255, 200, 0, 0.35);
|
|
2081
|
+
color: inherit;
|
|
2082
|
+
font-weight: inherit;
|
|
2043
2083
|
}
|
|
2044
2084
|
|
|
2045
2085
|
.skill-ac-desc {
|