openclacky 1.2.5 → 1.2.7

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -0
  3. data/README.md +34 -0
  4. data/README_CN.md +34 -0
  5. data/lib/clacky/agent/cost_tracker.rb +24 -10
  6. data/lib/clacky/agent/llm_caller.rb +25 -3
  7. data/lib/clacky/agent/message_compressor.rb +2 -1
  8. data/lib/clacky/agent/message_compressor_helper.rb +6 -2
  9. data/lib/clacky/agent/session_serializer.rb +23 -4
  10. data/lib/clacky/agent/tool_executor.rb +14 -0
  11. data/lib/clacky/agent/tool_registry.rb +0 -7
  12. data/lib/clacky/agent.rb +43 -10
  13. data/lib/clacky/agent_config.rb +54 -6
  14. data/lib/clacky/billing/billing_store.rb +62 -4
  15. data/lib/clacky/brand_config.rb +5 -0
  16. data/lib/clacky/cli.rb +76 -24
  17. data/lib/clacky/client.rb +59 -4
  18. data/lib/clacky/default_parsers/wps_parser.rb +82 -0
  19. data/lib/clacky/default_skills/onboard/SKILL.md +2 -2
  20. data/lib/clacky/json_ui_controller.rb +5 -2
  21. data/lib/clacky/message_format/anthropic.rb +13 -3
  22. data/lib/clacky/message_format/bedrock.rb +2 -2
  23. data/lib/clacky/plain_ui_controller.rb +1 -1
  24. data/lib/clacky/platform_http_client.rb +28 -1
  25. data/lib/clacky/providers.rb +11 -29
  26. data/lib/clacky/server/channel/channel_manager.rb +148 -12
  27. data/lib/clacky/server/channel/channel_ui_controller.rb +4 -2
  28. data/lib/clacky/server/http_server.rb +133 -13
  29. data/lib/clacky/server/session_registry.rb +30 -4
  30. data/lib/clacky/server/web_ui_controller.rb +6 -3
  31. data/lib/clacky/tools/browser.rb +4 -13
  32. data/lib/clacky/tools/terminal.rb +23 -27
  33. data/lib/clacky/ui2/ui_controller.rb +1 -1
  34. data/lib/clacky/ui_interface.rb +1 -1
  35. data/lib/clacky/utils/file_processor.rb +3 -0
  36. data/lib/clacky/utils/parser_manager.rb +3 -0
  37. data/lib/clacky/version.rb +1 -1
  38. data/lib/clacky/web/app.css +659 -75
  39. data/lib/clacky/web/app.js +0 -1
  40. data/lib/clacky/web/billing.js +371 -99
  41. data/lib/clacky/web/i18n.js +48 -2
  42. data/lib/clacky/web/index.html +34 -1
  43. data/lib/clacky/web/sessions.js +213 -82
  44. data/lib/clacky/web/settings.js +59 -17
  45. data/lib/clacky/web/workspace.js +204 -0
  46. data/lib/clacky/web/ws-dispatcher.js +19 -3
  47. data/lib/clacky.rb +9 -3
  48. metadata +4 -5
  49. data/lib/clacky/tools/list_tasks.rb +0 -54
  50. data/lib/clacky/tools/redo_task.rb +0 -41
  51. data/lib/clacky/tools/undo_task.rb +0 -35
@@ -35,7 +35,7 @@ module Clacky
35
35
  Workflow: open → snapshot(interactive:true) → act(ref=...). New tab from `open` is auto-selected; only use `focus` to switch back to a previously-opened tab.
36
36
  snapshot: returns hierarchical a11y tree truncated to ~8KB. Use query="text" to seek, or offset=N to page.
37
37
  act kinds: click | dblclick | type | fill | press | hover | scroll | drag | select | wait | evaluate | click_at
38
- evaluate: `js` is a function body, e.g. `return document.title` or `const x=...; return x;` result is JSON-encoded.
38
+ evaluate: `js` is a JS expression, e.g. "document.title". For multi-line / async logic use an IIFE: "(async () => { const r = await fetch(...); return r.status })()". Result is JSON-encoded.
39
39
  screenshot: expensive — pass `ref` to capture one element instead of the whole page.
40
40
  DESC
41
41
  self.tool_category = "web"
@@ -58,7 +58,7 @@ module Clacky
58
58
  amount: { type: "integer", description: "act scroll pixels (default 300)" },
59
59
  ms: { type: "integer", description: "act wait ms" },
60
60
  selector: { type: "string", description: "act wait: text or CSS selector" },
61
- js: { type: "string", description: "act evaluate: JS function body (use return)" },
61
+ js: { type: "string", description: "act evaluate: JS expression (use IIFE for multi-line/async)" },
62
62
  target_ref: { type: "string", description: "act drag destination ref" },
63
63
  values: { type: "array", items: { type: "string" }, description: "act select options" },
64
64
  x: { type: "number", description: "click_at x px" },
@@ -264,7 +264,7 @@ module Clacky
264
264
  url = require_url(opts)
265
265
  return url if url.is_a?(Hash)
266
266
  invalidate_page_cache!
267
- mcp_call("navigate_page", { type: "url", url: url })
267
+ mcp_call("navigate_page", with_page({ type: "url", url: url }))
268
268
  wait_for_page_ready
269
269
  { action: "navigate", success: true, profile: "user", url: url, output: "Navigated to: #{url}" }
270
270
 
@@ -385,19 +385,10 @@ module Clacky
385
385
  pid ? args.merge(pageId: pid) : args
386
386
  end
387
387
 
388
- # Wrap user-supplied JS as a Chrome-DevTools-MCP `function` argument.
389
- # We treat `js` as a function body so users can write `const x = ...; return x;`
390
- # naturally. For pure expressions ("document.title"), we auto-prepend `return`
391
- # so the result still flows back. Detection is conservative — the presence of
392
- # `return` or any top-level statement keyword skips the auto-return.
393
388
  private def build_evaluate_function(js)
394
389
  body = js.to_s.strip
395
390
  return "() => {}" if body.empty?
396
-
397
- looks_like_statement = body.match?(/(^|[\s;{])(return|const|let|var|if|for|while|throw|try|switch|function|class|do|await|async)\b/) ||
398
- body.include?(";")
399
- body = "return (#{body})" unless looks_like_statement
400
- "() => { #{body} }"
391
+ "() => (#{body})"
401
392
  end
402
393
 
403
394
  SCREENSHOT_MAX_WIDTH = 800
@@ -340,12 +340,6 @@ module Clacky
340
340
  project_root: cwd || Dir.pwd
341
341
  )
342
342
 
343
- # WSL interop fix: Windows .exe processes inherit the PTY's stdin fd
344
- # and attempt to use it as a Windows Console, causing them to hang
345
- # indefinitely. Redirect stdin from /dev/null for any .exe invocation
346
- # that doesn't already have an explicit stdin redirect.
347
- safe_command = redirect_exe_stdin(safe_command)
348
-
349
343
  # PowerShell 5 on Chinese Windows emits CP936/GBK by default; force
350
344
  # UTF-8 so our PTY (which decodes as UTF-8) doesn't see ??? bytes.
351
345
  safe_command = force_powershell_utf8(safe_command)
@@ -988,10 +982,31 @@ module Clacky
988
982
  # exit codes are also swallowed so the *user* command's $? is what
989
983
  # lands in `__clacky_ec`.
990
984
  hooks_line = with_hooks ? hooks_prefix_for(session) : ""
991
- line = %Q|#{hooks_line}{ #{command}\n}; __clacky_ec=$?; printf "\n__CLACKY_DONE_#{token}_%s__\n" "$__clacky_ec"\n|
985
+ # WSL interop fix: Windows .exe processes inherit the PTY slave fd
986
+ # as their stdin and treat it like a Windows Console — they sit
987
+ # there waiting for input nobody will ever send, hanging the whole
988
+ # session. Wrapping the user command's group with `</dev/null` gives
989
+ # every process inside it (including .exe interop children) an
990
+ # immediate EOF on stdin, so they exit cleanly.
991
+ #
992
+ # We only do this on WSL when the command actually mentions `.exe`,
993
+ # so Linux interactive commands like `read -p` / `python` REPL on
994
+ # non-WSL hosts (and on WSL when not invoking Windows binaries)
995
+ # keep their PTY stdin and continue to behave as before.
996
+ stdin_redirect = exe_needs_stdin_isolation?(command) ? " </dev/null" : ""
997
+ line = %Q|#{hooks_line}{ #{command}\n}#{stdin_redirect}; __clacky_ec=$?; printf "\n__CLACKY_DONE_#{token}_%s__\n" "$__clacky_ec"\n|
992
998
  session.mutex.synchronize { session.writer.write(line) }
993
999
  end
994
1000
 
1001
+ # True when the command should run with stdin redirected from
1002
+ # /dev/null. Currently only triggers on WSL when the command string
1003
+ # mentions a Windows `.exe` binary — see write_user_command for the
1004
+ # full rationale.
1005
+ private def exe_needs_stdin_isolation?(command)
1006
+ return false unless Clacky::Utils::EnvironmentDetector.wsl?
1007
+ command.to_s =~ /\.exe\b/i ? true : false
1008
+ end
1009
+
995
1010
  # Build the "run hooks" prefix line. Empty string for shells where
996
1011
  # we don't know how to introspect hook registries.
997
1012
  private def hooks_prefix_for(session)
@@ -1508,31 +1523,12 @@ module Clacky
1508
1523
  lines.last(DISPLAY_TAIL_LINES).join("\n")
1509
1524
  end
1510
1525
 
1511
- # WSL interop fix: Windows .exe processes inherit the PTY stdin fd
1512
- # and try to use it as a Windows Console, which hangs indefinitely.
1513
- # Detect .exe invocations and redirect stdin from /dev/null unless
1514
- # the command already has an explicit stdin redirect.
1515
- private def redirect_exe_stdin(command)
1516
- return command unless command =~ /\.exe\b/i
1517
- return command if command =~ /<\s*[^\s|&;]/
1518
-
1519
- # If the command has a shell-level pipe, insert </dev/null before
1520
- # the first pipe so only the .exe segment gets its stdin redirected,
1521
- # rather than starving a downstream pipe reader (e.g. `tr`, `grep`).
1522
- if command =~ /\|/
1523
- command.sub(/\s*\|/, ' </dev/null |')
1524
- else
1525
- "#{command} </dev/null"
1526
- end
1527
- end
1528
-
1529
1526
  # PowerShell 5 on Chinese Windows defaults [Console]::OutputEncoding
1530
1527
  # to CP936/GBK; our PTY decodes as UTF-8 so non-ASCII output becomes
1531
1528
  # `???`. Inject UTF-8 setup into the user's PowerShell command so the
1532
1529
  # shell emits UTF-8 bytes regardless of host locale.
1533
1530
  POWERSHELL_PREAMBLE =
1534
- "[Console]::OutputEncoding=[Text.Encoding]::UTF8;" \
1535
- "$OutputEncoding=[Text.Encoding]::UTF8;"
1531
+ "[Console]::OutputEncoding=[Text.Encoding]::UTF8;"
1536
1532
 
1537
1533
  # Only rewrites simple `powershell[.exe]` / `pwsh[.exe]` invocations.
1538
1534
  # Skips -File / -EncodedCommand / commands already handling encoding /
@@ -861,7 +861,7 @@ module Clacky
861
861
 
862
862
  # Show error message
863
863
  # @param message [String] Error message
864
- def show_error(message)
864
+ def show_error(message, code: nil, top_up_url: nil)
865
865
  output = @renderer.render_error(message)
866
866
  append_output(output)
867
867
  end
@@ -26,7 +26,7 @@ module Clacky
26
26
  # === Status messages ===
27
27
  def show_info(message, prefix_newline: true); end
28
28
  def show_warning(message); end
29
- def show_error(message); end
29
+ def show_error(message, code: nil, top_up_url: nil); end
30
30
  def show_success(message); end
31
31
  def log(message, level: :info); end
32
32
 
@@ -43,10 +43,12 @@ module Clacky
43
43
  .mp3 .mp4 .avi .mov .mkv .wav .flac
44
44
  .ttf .otf .woff .woff2
45
45
  .db .sqlite .bin .dat
46
+ .wps .et .dps
46
47
  ].freeze
47
48
 
48
49
  GLOB_ALLOWED_BINARY_EXTENSIONS = %w[
49
50
  .pdf .doc .docx .ppt .pptx .xls .xlsx .odt .odp .ods
51
+ .wps .et .dps
50
52
  ].freeze
51
53
 
52
54
  LLM_BINARY_EXTENSIONS = %w[.png .jpg .jpeg .gif .webp .pdf].freeze
@@ -64,6 +66,7 @@ module Clacky
64
66
  ".docx" => :document, ".doc" => :document,
65
67
  ".xlsx" => :spreadsheet, ".xls" => :spreadsheet,
66
68
  ".pptx" => :presentation, ".ppt" => :presentation,
69
+ ".wps" => :document, ".et" => :spreadsheet, ".dps" => :presentation,
67
70
  ".pdf" => :pdf,
68
71
  ".zip" => :zip, ".gz" => :zip, ".tgz" => :zip, ".tar" => :zip, ".rar" => :zip, ".7z" => :zip,
69
72
  ".png" => :image, ".jpg" => :image, ".jpeg" => :image,
@@ -30,6 +30,9 @@ module Clacky
30
30
  ".xls" => "xlsx_parser.rb",
31
31
  ".pptx" => "pptx_parser.rb",
32
32
  ".ppt" => "pptx_parser.rb",
33
+ ".wps" => "wps_parser.rb",
34
+ ".et" => "wps_parser.rb",
35
+ ".dps" => "wps_parser.rb",
33
36
  }.freeze
34
37
 
35
38
  # Ensure ~/.clacky/parsers/ exists and all default parsers are present.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clacky
4
- VERSION = "1.2.5"
4
+ VERSION = "1.2.7"
5
5
  end