openclacky 0.9.0 → 0.9.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 732978cd6072f33b558e6260af317ffa188529f922913193d9b7505745f0bb2b
4
- data.tar.gz: 3a03afdbb194bbf3e52cd89d173a05186e01ce4cb8564410e294e0e5bb7db501
3
+ metadata.gz: 634937d9d7a20aa0a76b046ba079ef2d82c398ac81d59014968f7dbeb325726c
4
+ data.tar.gz: 7cd5c7eab980c54b5da38b6744dbc21dd3ac48af00c541a609d5537d6867ee70
5
5
  SHA512:
6
- metadata.gz: 0ad076fe8aa80fe40b8f9908cdf6bab87c08110c8940c76aa3d8fe5944886f565735b7dd06008e9f54fc0a13a3b1d5966800d3bb02bf1d7268cf99faab181dd4
7
- data.tar.gz: e29e2688839d08a271b105830df847517cfaab57ecae36d50787687180b1ba9a41e15a68f6e3afc21a800dd78b6eea6c7556c97c503aabdc46c2763bdf1c0bb8
6
+ metadata.gz: b874781caf58c502536666c33b8aff0c459076689909dd36bd82151f7092953d453d51b2e90259bbfdb6b2eb0ba389e5e56c083a1364118ca0a15edbef471e02
7
+ data.tar.gz: 1639031ccf11848ab3b8c2a18d3eb7d87c487dc1e13b856e4c0cdc8cb35e3ebf4c8001660ea607f013e64a40fa1ef09255915652af9d0bc44ad8ddf9c0b1dff2
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
+ ## [0.9.2] - 2026-03-15
11
+
12
+ ### Fixed
13
+ - **Version upgrade button now appears reliably**: the new version check now queries RubyGems directly instead of relying on local gem mirror sources (which often lag behind by hours or days), so the upgrade badge shows up promptly when a new version is available. Falls back to the local mirror if RubyGems is unreachable.
14
+ - **Edit confirmation diff output restored**: the file diff was not displaying correctly when the input area paused during an edit confirmation prompt; this is now fixed.
15
+
16
+ ## [0.9.1] - 2026-03-15
17
+
18
+ ### Added
19
+ - **Session context auto-injection**: the agent now automatically injects the current date and active model name into each conversation turn, so it always knows what day it is and which model it's running — helpful for time-sensitive tasks and multi-model setups
20
+ - **Kimi/Moonshot extended thinking support**: reasoning content is now preserved and echoed back correctly in message history, fixing HTTP 400 errors when using Kimi's extended thinking API
21
+
22
+ ### Improved
23
+ - **Browser tool install UX**: the `agent-browser` setup flow has been redesigned with a dedicated install script and clearer guidance, making first-time setup smoother
24
+
10
25
  ## [0.9.0] - 2026-03-14
11
26
 
12
27
  ### Added
data/lib/clacky/agent.rb CHANGED
@@ -182,6 +182,9 @@ module Clacky
182
182
  @messages << system_message
183
183
  end
184
184
 
185
+ # Inject session context (date + model) if not yet present or date has changed
186
+ inject_session_context_if_needed
187
+
185
188
  # Format user message with images and files if provided
186
189
  user_content = format_user_content(user_input, images, files)
187
190
  @messages << { role: "user", content: user_content, task_id: task_id, created_at: Time.now.to_f }
@@ -423,6 +426,9 @@ module Clacky
423
426
  end
424
427
  # Store token_usage in the message so replay_history can re-emit it
425
428
  msg[:token_usage] = response[:token_usage] if response[:token_usage]
429
+ # Preserve reasoning_content so it is echoed back to APIs that require it
430
+ # (e.g. Kimi/Moonshot extended thinking — omitting it causes HTTP 400)
431
+ msg[:reasoning_content] = response[:reasoning_content] if response[:reasoning_content]
426
432
  @messages << msg
427
433
 
428
434
  response
@@ -871,6 +877,33 @@ module Clacky
871
877
  content
872
878
  end
873
879
 
880
+ # Inject a session context message (date + model) into the conversation.
881
+ # Only injects when:
882
+ # 1. No context message exists yet in this session, OR
883
+ # 2. The existing context is from a previous day (cross-day session)
884
+ # Marked with system_injected: true so existing filters (replay_history,
885
+ # get_recent_user_messages, etc.) automatically skip it.
886
+ # Cache-safe: always inserted just before the current user message,
887
+ # so no historical cache entries are ever invalidated.
888
+ private def inject_session_context_if_needed
889
+ today = Time.now.strftime("%Y-%m-%d")
890
+
891
+ # Find the last injected context message
892
+ last_ctx = @messages.reverse.find { |m| m[:session_context] }
893
+
894
+ # Skip if we already have a context for today
895
+ return if last_ctx && last_ctx[:context_date] == today
896
+
897
+ content = "[Session context: Today is #{Time.now.strftime('%Y-%m-%d, %A')}. Current model: #{current_model}]"
898
+ @messages << {
899
+ role: "user",
900
+ content: content,
901
+ system_injected: true,
902
+ session_context: true,
903
+ context_date: today
904
+ }
905
+ end
906
+
874
907
  # Track modified files for Time Machine snapshots
875
908
  # @param tool_name [String] Name of the tool that was executed
876
909
  # @param args [Hash] Arguments passed to the tool
data/lib/clacky/client.rb CHANGED
@@ -659,13 +659,20 @@ module Clacky
659
659
  end
660
660
  end
661
661
 
662
- {
662
+ result = {
663
663
  content: message["content"],
664
664
  tool_calls: parse_tool_calls(message["tool_calls"]),
665
665
  finish_reason: data["choices"].first["finish_reason"],
666
666
  usage: usage_data,
667
667
  raw_api_usage: raw_api_usage
668
668
  }
669
+
670
+ # Preserve reasoning_content if present (e.g. Kimi/Moonshot extended thinking).
671
+ # The API requires this field to be echoed back in the message history on
672
+ # subsequent requests, otherwise it returns HTTP 400.
673
+ result[:reasoning_content] = message["reasoning_content"] if message["reasoning_content"]
674
+
675
+ result
669
676
  else
670
677
  raise_error(response)
671
678
  end
@@ -687,10 +687,38 @@ module Clacky
687
687
  latest
688
688
  end
689
689
 
690
- # Query the latest openclacky version via `gem list -r openclacky`.
691
- # Runs through login shell so gem source mirrors configured via rbenv/mise work correctly.
692
- # Output format: "openclacky (0.9.0)"
690
+ # Query the latest openclacky version.
691
+ # Strategy: try RubyGems official REST API first (most accurate, not affected by mirror lag),
692
+ # then fall back to `gem list -r` (respects user's configured gem source).
693
693
  private def fetch_latest_version_from_gem
694
+ fetch_latest_version_from_rubygems_api || fetch_latest_version_from_gem_command
695
+ end
696
+
697
+ # Try RubyGems official REST API — fast and always up-to-date.
698
+ # Returns nil if the request fails or times out.
699
+ private def fetch_latest_version_from_rubygems_api
700
+ require "net/http"
701
+ require "json"
702
+
703
+ uri = URI("https://rubygems.org/api/v1/gems/openclacky.json")
704
+ http = Net::HTTP.new(uri.host, uri.port)
705
+ http.use_ssl = true
706
+ http.open_timeout = 5
707
+ http.read_timeout = 8
708
+
709
+ res = http.get(uri.request_uri)
710
+ return nil unless res.is_a?(Net::HTTPSuccess)
711
+
712
+ data = JSON.parse(res.body)
713
+ data["version"].to_s.strip.then { |v| v.empty? ? nil : v }
714
+ rescue StandardError
715
+ nil
716
+ end
717
+
718
+ # Fall back to `gem list -r openclacky` via login shell.
719
+ # Respects the user's configured gem source (rbenv/mise mirrors, etc.).
720
+ # Output format: "openclacky (0.9.0)"
721
+ private def fetch_latest_version_from_gem_command
694
722
  shell = Clacky::Tools::Shell.new
695
723
  result = shell.execute(command: "gem list -r openclacky", soft_timeout: 15, hard_timeout: 30)
696
724
  return nil unless result[:exit_code] == 0
@@ -13,14 +13,24 @@ module Clacky
13
13
  self.tool_description = <<~DESC
14
14
  Browser automation for login-related operations (sign-in, OAuth, form submission requiring session). For simple page fetch or search, prefer web_fetch or web_search instead.
15
15
 
16
- isolated param: true = built-in browser (default, works immediately, login persists). false = user's Chrome (keeps cookies/login, needs one-time debug setup; opens URLs in new tab).
16
+ isolated: true = built-in browser (default, no setup, login persists). false = user's Chrome (keeps cookies/login, needs one-time debug setup; opens URLs in new tab).
17
17
 
18
- WORKFLOW:
19
- - Default: use isolated=true (built-in browser). No setup, works immediately.
20
- - If the user explicitly wants their Chrome (e.g. "use my Chrome"), set isolated=false. We auto-open chrome://inspect/#remote-debugging when needed guide user to enable the toggle. When opening URLs, we use a new tab to avoid replacing the user's current page.
18
+ SNAPSHOT — always run before interacting with a page. Refs (@e1, @e2...) expire after page changes, always re-snapshot before acting on a changed page:
19
+ - 'snapshot -i -C' interactive + cursor-clickable elements (recommended default)
20
+ - 'snapshot -i'interactive elements only (faster, for simple forms)
21
+ - 'snapshot' — full accessibility tree (when above miss elements)
21
22
 
22
- Commands: 'open <url>', 'snapshot -i', 'click @e1', 'fill @e2 "text"', etc.
23
- IMPORTANT: Always use 'snapshot -i' to inspect page state. NEVER use 'screenshot' without explicit user permission — it costs significantly more tokens. If snapshot is insufficient, tell the user and ask if they want a screenshot.
23
+ ELEMENT SELECTION prefer in this order:
24
+ 1. Refs: 'click @e1', 'fill @e2 "text"'
25
+ 2. Semantic find: 'find text "Submit" click', 'find role button "Login" click', 'find label "Email" fill "user@example.com"'
26
+ 3. CSS: 'click "#submit-btn"'
27
+
28
+ OTHER COMMANDS:
29
+ - 'open <url>', 'back', 'reload', 'press Enter', 'key Control+a'
30
+ - 'scroll down/up', 'scrollintoview @e1', 'wait @e1', 'wait --text "..."', 'wait --load networkidle'
31
+ - 'dialog accept/dismiss', 'tab new <url>', 'tab <n>'
32
+
33
+ SCREENSHOT: NEVER call on your own — costs far more tokens than snapshot. Last resort only. Ask user first: "Screenshots cost more tokens. Approve?" When approved: 'screenshot --screenshot-format jpeg --screenshot-quality 50'.
24
34
  DESC
25
35
  self.tool_category = "web"
26
36
  self.tool_parameters = {
@@ -47,11 +57,29 @@ module Clacky
47
57
  CHROME_DEBUG_PORT = 9222
48
58
  BROWSER_COMMAND_TIMEOUT = 30
49
59
  CHROME_DEBUG_PAGE = "chrome://inspect/#remote-debugging"
60
+ MIN_AGENT_BROWSER_VERSION = "0.20.0"
50
61
 
51
62
  def execute(command:, session: nil, isolated: nil, working_dir: nil)
52
- unless agent_browser_installed?
53
- install_result = auto_install_agent_browser
54
- return install_result if install_result[:error]
63
+ # Handle explicit install command
64
+ if command.strip == "install"
65
+ return do_install_agent_browser
66
+ end
67
+
68
+ if !agent_browser_installed?
69
+ return {
70
+ error: "agent-browser not installed",
71
+ message: "agent-browser is required for browser automation but is not installed.",
72
+ instructions: "Tell the user: 'agent-browser is not installed. It's required for browser automation. Run `browser(command: \"install\")` to install it — this may take a minute. Would you like me to install it now?' Wait for user confirmation before calling install."
73
+ }
74
+ end
75
+
76
+ if agent_browser_outdated?
77
+ current = `agent-browser --version 2>/dev/null`.strip.split.last
78
+ return {
79
+ error: "agent-browser version too old",
80
+ message: "agent-browser #{current} is installed but version >= #{MIN_AGENT_BROWSER_VERSION} is required.",
81
+ instructions: "Tell the user: 'agent-browser needs to be upgraded from #{current} to #{MIN_AGENT_BROWSER_VERSION}+. Run `browser(command: \"install\")` to upgrade — this may take a minute. Would you like me to upgrade it now?' Wait for user confirmation before calling install."
82
+ }
55
83
  end
56
84
 
57
85
  # Default to built-in browser (isolated=true). Only use user's Chrome when explicitly isolated=false.
@@ -94,12 +122,6 @@ module Clacky
94
122
  result = Shell.new.execute(command: full_command, hard_timeout: BROWSER_COMMAND_TIMEOUT, working_dir: working_dir)
95
123
  end
96
124
 
97
- if playwright_missing?(result)
98
- pw_result = install_playwright_chromium
99
- return pw_result if pw_result[:error]
100
- result = Shell.new.execute(command: full_command, hard_timeout: BROWSER_COMMAND_TIMEOUT, working_dir: working_dir)
101
- end
102
-
103
125
  if use_auto_connect && !result[:success] && connection_error?(result)
104
126
  if we_launched_chrome
105
127
  result = Shell.new.execute(command: full_command, hard_timeout: BROWSER_COMMAND_TIMEOUT, working_dir: working_dir)
@@ -363,52 +385,23 @@ module Clacky
363
385
  !!find_in_path(AGENT_BROWSER_BIN)
364
386
  end
365
387
 
366
- def auto_install_agent_browser
367
- npm = find_or_install_npm
368
- unless npm
369
- return {
370
- error: "agent-browser not installed",
371
- message: "agent-browser is required for browser automation but is not installed. " \
372
- "Node.js not found; tried to install via mise but failed.\n\n" \
373
- "Please run: mise install node@22 && mise use -g node@22"
374
- }
375
- end
376
-
377
- result = Shell.new.execute(command: "#{npm} install -g agent-browser", hard_timeout: 120)
378
- unless result[:success]
379
- return {
380
- error: "Failed to auto-install agent-browser",
381
- message: "npm install -g agent-browser failed: #{result[:stderr]}\n\nPlease run it manually."
382
- }
383
- end
384
-
385
- {}
386
- end
387
-
388
- def find_or_install_npm
389
- npm = find_in_path("npm")
390
- return npm if npm
391
-
392
- mise = find_mise_bin
393
- return nil unless mise
394
-
395
- path = `#{Shellwords.escape(mise)} which npm 2>/dev/null`.strip
396
- return path if path && !path.empty? && File.executable?(path)
397
- system(mise, "install", "node@22", out: File::NULL, err: File::NULL)
398
- system(mise, "use", "-g", "node@22", out: File::NULL, err: File::NULL)
399
-
400
- path = `#{Shellwords.escape(mise)} which npm 2>/dev/null`.strip
401
- return path if path && !path.empty? && File.executable?(path)
402
-
403
- nil
388
+ def agent_browser_outdated?
389
+ version = `agent-browser --version 2>/dev/null`.strip.split.last
390
+ return false if version.nil? || version.empty?
391
+ Gem::Version.new(version) < Gem::Version.new(MIN_AGENT_BROWSER_VERSION)
392
+ rescue StandardError
393
+ false
404
394
  end
405
395
 
406
- def find_mise_bin
407
- mise = find_in_path("mise")
408
- return mise if mise
409
-
410
- candidate = "#{Dir.home}/.local/bin/mise"
411
- File.executable?(candidate) ? candidate : nil
396
+ def do_install_agent_browser
397
+ script = File.expand_path("../../../../scripts/install_agent_browser.sh", __FILE__)
398
+ result = Shell.new.execute(command: "bash #{Shellwords.escape(script)}", hard_timeout: 180)
399
+ if result[:success]
400
+ version = `agent-browser --version 2>/dev/null`.strip.split.last
401
+ { success: true, message: "agent-browser #{version} installed successfully. You can now use browser commands." }
402
+ else
403
+ { error: "Failed to install agent-browser", message: result[:stdout].to_s.strip }
404
+ end
412
405
  end
413
406
 
414
407
  def command_name_for_temp(command)
@@ -445,25 +438,6 @@ module Clacky
445
438
  { content: first_part.join + notice, temp_file: temp_file }
446
439
  end
447
440
 
448
- def playwright_missing?(result)
449
- output = "#{result[:stdout]}#{result[:stderr]}"
450
- output.include?("Executable doesn't exist") ||
451
- output.include?("Please run the following command to download new browsers")
452
- end
453
-
454
- def install_playwright_chromium
455
- playwright = find_in_path("playwright")
456
- cmd = playwright ? "#{playwright} install chromium" : "npx playwright install chromium"
457
-
458
- result = Shell.new.execute(command: cmd, hard_timeout: 300)
459
- unless result[:success]
460
- return {
461
- error: "Failed to install Playwright Chromium",
462
- message: "Automatic browser installation failed. Please run manually:\n npx playwright install chromium"
463
- }
464
- end
465
- {}
466
- end
467
441
  end
468
442
  end
469
443
  end
@@ -59,8 +59,15 @@ module Clacky
59
59
  screen.clear_line
60
60
  end
61
61
 
62
- # Re-render fixed areas at new position
63
- render_fixed_areas
62
+ # When input is paused (InlineInput active), fixed_area_start_row has grown
63
+ # (input_area.required_height returns 0 while paused), so the cleared rows
64
+ # now belong to the output area. Re-render output from buffer to fill them in.
65
+ if input_area.paused?
66
+ render_output_from_buffer
67
+ else
68
+ # Re-render fixed areas at new position
69
+ render_fixed_areas
70
+ end
64
71
  screen.flush
65
72
  end
66
73
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clacky
4
- VERSION = "0.9.0"
4
+ VERSION = "0.9.2"
5
5
  end
data/scripts/install.sh CHANGED
@@ -590,40 +590,9 @@ main() {
590
590
  # Install agent-browser (browser automation tool)
591
591
  # This step is optional — failures are silently skipped with a hint.
592
592
  install_agent_browser() {
593
- print_step "Installing agent-browser (optional)..."
594
-
595
- # Try to find npm; if missing, attempt to install Node.js via mise
596
- if ! command_exists npm; then
597
- if command_exists mise || [ -x "$HOME/.local/bin/mise" ]; then
598
- print_info "Installing Node.js via mise..."
599
- "$HOME/.local/bin/mise" install node@22 > /dev/null 2>&1 || true
600
- "$HOME/.local/bin/mise" use -g node@22 > /dev/null 2>&1 || true
601
- # Reload mise so node/npm are on PATH
602
- eval "$("$HOME/.local/bin/mise" activate bash 2>/dev/null)" 2>/dev/null || true
603
- fi
604
- fi
605
-
606
- if ! command_exists npm; then
607
- print_warning "agent-browser skipped (Node.js/npm not found)."
608
- print_info "To enable browser automation later:"
609
- print_info " mise install node@22 && mise use -g node@22 && npm install -g agent-browser"
610
- return 0
611
- fi
612
-
613
- if command_exists agent-browser; then
614
- print_success "agent-browser already installed"
615
- return 0
616
- fi
617
-
618
- print_info "Running: npm install -g agent-browser"
619
- if npm install -g agent-browser > /dev/null 2>&1; then
620
- print_success "agent-browser installed"
621
- else
622
- print_warning "agent-browser installation failed — skipping."
623
- print_info "To install manually later: npm install -g agent-browser"
624
- fi
625
-
626
- return 0
593
+ local script_dir
594
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
595
+ bash "$script_dir/install_agent_browser.sh" || true
627
596
  }
628
597
 
629
598
  # Post-installation information
@@ -0,0 +1,67 @@
1
+ #!/bin/bash
2
+ # Install or upgrade agent-browser (browser automation tool)
3
+ # Can be sourced by install.sh or run standalone (e.g. from Ruby via shell)
4
+ #
5
+ # Exit codes:
6
+ # 0 — success (installed/upgraded)
7
+ # 1 — failure (npm not found or install failed)
8
+
9
+ set -e
10
+
11
+ RED='\033[0;31m'
12
+ GREEN='\033[0;32m'
13
+ YELLOW='\033[1;33m'
14
+ BLUE='\033[0;34m'
15
+ NC='\033[0m'
16
+
17
+ print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
18
+ print_success() { echo -e "${GREEN}✓${NC} $1"; }
19
+ print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
20
+ print_error() { echo -e "${RED}✗${NC} $1"; }
21
+ print_step() { echo -e "\n${BLUE}==>${NC} $1"; }
22
+
23
+ command_exists() { command -v "$1" >/dev/null 2>&1; }
24
+
25
+ print_step "Installing agent-browser..."
26
+
27
+ # Try to find npm; if missing, attempt to install Node.js via mise
28
+ if ! command_exists npm; then
29
+ mise_bin=""
30
+ if command_exists mise; then
31
+ mise_bin="mise"
32
+ elif [ -x "$HOME/.local/bin/mise" ]; then
33
+ mise_bin="$HOME/.local/bin/mise"
34
+ fi
35
+
36
+ if [ -n "$mise_bin" ]; then
37
+ print_info "Installing Node.js via mise..."
38
+ "$mise_bin" install node@22 > /dev/null 2>&1 || true
39
+ "$mise_bin" use -g node@22 > /dev/null 2>&1 || true
40
+ eval "$("$mise_bin" activate bash 2>/dev/null)" 2>/dev/null || true
41
+ fi
42
+ fi
43
+
44
+ if ! command_exists npm; then
45
+ print_error "agent-browser installation failed: Node.js/npm not found."
46
+ print_info "Please run: mise install node@22 && mise use -g node@22 && npm install -g agent-browser"
47
+ exit 1
48
+ fi
49
+
50
+ print_info "Running: npm install -g agent-browser"
51
+ if npm install -g agent-browser > /dev/null 2>&1; then
52
+ version=$(agent-browser --version 2>/dev/null | awk '{print $NF}')
53
+ print_success "agent-browser ${version} installed/updated"
54
+ else
55
+ print_error "agent-browser installation failed."
56
+ print_info "To install manually: npm install -g agent-browser"
57
+ exit 1
58
+ fi
59
+
60
+ print_info "Installing Playwright Chromium..."
61
+ if npx playwright install chromium > /dev/null 2>&1; then
62
+ print_success "Playwright Chromium installed"
63
+ else
64
+ print_error "Playwright Chromium installation failed."
65
+ print_info "To install manually: npx playwright install chromium"
66
+ exit 1
67
+ fi
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openclacky
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - windy
@@ -397,6 +397,7 @@ files:
397
397
  - lib/clacky/web/version.js
398
398
  - lib/clacky/web/ws.js
399
399
  - scripts/install.sh
400
+ - scripts/install_agent_browser.sh
400
401
  - scripts/uninstall.sh
401
402
  - sig/clacky.rbs
402
403
  homepage: https://github.com/yafeilee/clacky