openclacky 1.3.3 → 1.3.5

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -0
  3. data/docs/rich_ui_guide.md +277 -0
  4. data/docs/rich_ui_refactor_plan.md +396 -0
  5. data/lib/clacky/agent/fake_tool_call_detector.rb +52 -0
  6. data/lib/clacky/agent/llm_caller.rb +10 -4
  7. data/lib/clacky/agent/session_serializer.rb +5 -3
  8. data/lib/clacky/agent/tool_executor.rb +0 -12
  9. data/lib/clacky/agent.rb +77 -11
  10. data/lib/clacky/agent_config.rb +2 -14
  11. data/lib/clacky/api_extension.rb +343 -0
  12. data/lib/clacky/api_extension_loader.rb +168 -0
  13. data/lib/clacky/cli.rb +93 -3
  14. data/lib/clacky/client.rb +47 -25
  15. data/lib/clacky/default_agents/_panels/git/panel.js +1 -1
  16. data/lib/clacky/default_agents/_panels/time_machine/panel.js +23 -1
  17. data/lib/clacky/default_agents/base_prompt.md +1 -0
  18. data/lib/clacky/default_extensions/meeting/handler.rb +331 -0
  19. data/lib/clacky/default_extensions/meeting/meeting.js +790 -0
  20. data/lib/clacky/default_extensions/meeting/meta.yml +3 -0
  21. data/lib/clacky/default_extensions/meeting/skills/meeting-summarizer/SKILL.md +44 -0
  22. data/lib/clacky/default_skills/media-gen/SKILL.md +72 -6
  23. data/lib/clacky/default_skills/media-gen/scripts/video_seq.sh +114 -0
  24. data/lib/clacky/idle_compression_timer.rb +3 -1
  25. data/lib/clacky/json_ui_controller.rb +1 -1
  26. data/lib/clacky/locales/en.rb +26 -0
  27. data/lib/clacky/locales/i18n.rb +26 -0
  28. data/lib/clacky/locales/zh.rb +26 -0
  29. data/lib/clacky/media/base.rb +60 -0
  30. data/lib/clacky/media/dashscope.rb +385 -21
  31. data/lib/clacky/media/gemini.rb +9 -0
  32. data/lib/clacky/media/generator.rb +52 -0
  33. data/lib/clacky/media/openai_compat.rb +166 -0
  34. data/lib/clacky/null_ui_controller.rb +13 -0
  35. data/lib/clacky/plain_ui_controller.rb +1 -1
  36. data/lib/clacky/providers.rb +50 -2
  37. data/lib/clacky/rich_ui/components/base_component.rb +50 -0
  38. data/lib/clacky/rich_ui/components/dialogs/approval_dialog.rb +142 -0
  39. data/lib/clacky/rich_ui/components/dialogs/config_menu_dialog.rb +106 -0
  40. data/lib/clacky/rich_ui/components/dialogs/form_dialog.rb +128 -0
  41. data/lib/clacky/rich_ui/components/sidebar.rb +119 -0
  42. data/lib/clacky/rich_ui/components/sidebar_panels.rb +134 -0
  43. data/lib/clacky/rich_ui/components/status_view.rb +58 -0
  44. data/lib/clacky/rich_ui/components/thinking_live_view.rb +79 -0
  45. data/lib/clacky/rich_ui/entry_tracker.rb +56 -0
  46. data/lib/clacky/rich_ui/layout_adapter.rb +16 -0
  47. data/lib/clacky/rich_ui/progress_handle_adapter.rb +24 -0
  48. data/lib/clacky/rich_ui/rich_ui_controller.rb +868 -0
  49. data/lib/clacky/rich_ui/shell/rich_agent_shell.rb +184 -0
  50. data/lib/clacky/rich_ui/view_renderer.rb +291 -0
  51. data/lib/clacky/rich_ui.rb +57 -0
  52. data/lib/clacky/rich_ui_controller.rb +3 -1549
  53. data/lib/clacky/server/api_extension_dispatcher.rb +120 -0
  54. data/lib/clacky/server/channel/channel_ui_controller.rb +1 -1
  55. data/lib/clacky/server/http_server.rb +291 -109
  56. data/lib/clacky/server/session_registry.rb +5 -3
  57. data/lib/clacky/server/web_ui_controller.rb +3 -2
  58. data/lib/clacky/shell_hook_loader.rb +1 -1
  59. data/lib/clacky/skill_loader.rb +14 -2
  60. data/lib/clacky/tools/edit.rb +14 -2
  61. data/lib/clacky/tools/terminal/output_cleaner.rb +1 -3
  62. data/lib/clacky/tools/terminal.rb +0 -43
  63. data/lib/clacky/ui2/components/modal_component.rb +1 -1
  64. data/lib/clacky/ui2/ui_controller.rb +147 -31
  65. data/lib/clacky/ui_interface.rb +10 -1
  66. data/lib/clacky/utils/encoding.rb +25 -0
  67. data/lib/clacky/version.rb +1 -1
  68. data/lib/clacky/web/app.css +201 -81
  69. data/lib/clacky/web/app.js +65 -7
  70. data/lib/clacky/web/components/onboard.js +19 -16
  71. data/lib/clacky/web/core/aside.js +8 -3
  72. data/lib/clacky/web/core/ext.js +1 -1
  73. data/lib/clacky/web/features/brand/view.js +8 -5
  74. data/lib/clacky/web/features/channels/store.js +1 -20
  75. data/lib/clacky/web/features/mcp/store.js +1 -20
  76. data/lib/clacky/web/features/profile/store.js +1 -13
  77. data/lib/clacky/web/features/profile/view.js +16 -4
  78. data/lib/clacky/web/features/skills/store.js +36 -23
  79. data/lib/clacky/web/features/skills/view.js +32 -1
  80. data/lib/clacky/web/features/version/store.js +2 -0
  81. data/lib/clacky/web/features/workspace/view.js +1 -1
  82. data/lib/clacky/web/i18n.js +48 -13
  83. data/lib/clacky/web/index.html +24 -17
  84. data/lib/clacky/web/sessions.js +427 -79
  85. data/lib/clacky/web/settings.js +143 -113
  86. data/lib/clacky/web/ws-dispatcher.js +18 -6
  87. data/lib/clacky.rb +27 -5
  88. metadata +45 -2
  89. data/lib/clacky/media/output_dir.rb +0 -43
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "timeout"
5
+ require "uri"
6
+
7
+ module Clacky
8
+ module Server
9
+ # Routes /api/ext/<name>/<sub-path> requests to the matching ApiExtension
10
+ # subclass. Wraps each handler invocation with a timeout and a unified
11
+ # JSON error envelope so a misbehaving extension cannot break neighboring
12
+ # extensions or the host process.
13
+ module ApiExtensionDispatcher
14
+ MOUNT_PREFIX = "/api/ext/"
15
+
16
+ class << self
17
+ # Public entry called from HttpServer#_dispatch_rest.
18
+ # Returns true to indicate the request was handled (200/4xx/5xx).
19
+ def handle(req, res, http_server:)
20
+ ext_id, sub_path = parse_path(req.path)
21
+ return not_found(res, "extension id missing") unless ext_id
22
+
23
+ klass = Clacky::ApiExtension.registry[ext_id]
24
+ return not_found(res, "extension '#{ext_id}' not found") unless klass
25
+
26
+ method = req.request_method.to_s.downcase.to_sym
27
+ route, params = find_route(klass, method, sub_path)
28
+ return not_found(res, "no route for #{req.request_method} #{req.path}") unless route
29
+
30
+ # Public-endpoint check is done at HttpServer level (it owns access-key
31
+ # logic); by the time we get here, auth has already been resolved.
32
+
33
+ timeout_sec = route.options[:timeout] || klass.class_timeout || Clacky::ApiExtension::DEFAULT_TIMEOUT
34
+ invoke_route(klass, route, params, req, res, http_server, timeout_sec)
35
+ true
36
+ end
37
+
38
+ # Tells HttpServer whether a given /api/ext/... path can skip access-key
39
+ # auth, so the host can keep its single-source-of-truth auth logic.
40
+ def public_path?(path, method)
41
+ ext_id, sub_path = parse_path(path)
42
+ return false unless ext_id
43
+
44
+ klass = Clacky::ApiExtension.registry[ext_id]
45
+ return false unless klass
46
+ return false if klass.public_paths.empty?
47
+
48
+ route, _params = find_route(klass, method.to_s.downcase.to_sym, sub_path)
49
+ return false unless route
50
+
51
+ klass.public_paths.include?(route.pattern)
52
+ end
53
+
54
+ private def parse_path(path)
55
+ return [nil, nil] unless path.to_s.start_with?(MOUNT_PREFIX)
56
+
57
+ tail = path[MOUNT_PREFIX.length..]
58
+ slash = tail.index("/")
59
+ if slash
60
+ [tail[0...slash], tail[slash..]]
61
+ else
62
+ [tail, "/"]
63
+ end
64
+ end
65
+
66
+ private def find_route(klass, method, sub_path)
67
+ klass.routes.each do |route|
68
+ next unless route.method == method
69
+ next unless (m = route.regex.match(sub_path))
70
+
71
+ params = {}
72
+ route.param_names.each_with_index do |name, i|
73
+ params[name] = URI.decode_www_form_component(m[i + 1].to_s)
74
+ end
75
+ return [route, params]
76
+ end
77
+ [nil, nil]
78
+ end
79
+
80
+ private def invoke_route(klass, route, params, req, res, http_server, timeout_sec)
81
+ instance = klass.new(req: req, res: res, route: route, params: params, http_server: http_server)
82
+ Timeout.timeout(timeout_sec) { instance.invoke }
83
+
84
+ # Handler exited without writing — empty 204
85
+ empty_response(res)
86
+ rescue Clacky::ApiExtension::Halt => halt
87
+ write_response(res, halt.status, halt.payload, halt.content_type)
88
+ rescue Timeout::Error
89
+ Clacky::Logger.warn("[api_ext:#{klass.ext_id}] Timed out after #{timeout_sec}s on #{route.method.upcase} #{route.pattern}")
90
+ write_json(res, 503, error: "request timed out")
91
+ rescue StandardError => e
92
+ Clacky::Logger.warn("[api_ext:#{klass.ext_id}] #{e.class}: #{e.message}\n#{e.backtrace.first(5).join("\n")}")
93
+ write_json(res, 500, error: e.message)
94
+ end
95
+
96
+ private def write_response(res, status, body, content_type)
97
+ res.status = status
98
+ res.content_type = content_type
99
+ res["Access-Control-Allow-Origin"] = "*"
100
+ res.body = body
101
+ end
102
+
103
+ private def write_json(res, status, payload)
104
+ write_response(res, status, JSON.generate(payload), "application/json; charset=utf-8")
105
+ end
106
+
107
+ private def empty_response(res)
108
+ res.status = 204
109
+ res["Access-Control-Allow-Origin"] = "*"
110
+ res.body = ""
111
+ end
112
+
113
+ private def not_found(res, message)
114
+ write_json(res, 404, error: message)
115
+ true
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -145,7 +145,7 @@ module Clacky
145
145
  send_text("Warning: #{message}")
146
146
  end
147
147
 
148
- def show_error(message, code: nil, top_up_url: nil)
148
+ def show_error(message, code: nil, top_up_url: nil, raw_message: nil)
149
149
  text = "Error: #{message}"
150
150
  text += "\n#{top_up_url}" if top_up_url
151
151
  send_text(text)