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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -0
- data/docs/rich_ui_guide.md +277 -0
- data/docs/rich_ui_refactor_plan.md +396 -0
- data/lib/clacky/agent/fake_tool_call_detector.rb +52 -0
- data/lib/clacky/agent/llm_caller.rb +10 -4
- data/lib/clacky/agent/session_serializer.rb +5 -3
- data/lib/clacky/agent/tool_executor.rb +0 -12
- data/lib/clacky/agent.rb +77 -11
- data/lib/clacky/agent_config.rb +2 -14
- data/lib/clacky/api_extension.rb +343 -0
- data/lib/clacky/api_extension_loader.rb +168 -0
- data/lib/clacky/cli.rb +93 -3
- data/lib/clacky/client.rb +47 -25
- data/lib/clacky/default_agents/_panels/git/panel.js +1 -1
- data/lib/clacky/default_agents/_panels/time_machine/panel.js +23 -1
- data/lib/clacky/default_agents/base_prompt.md +1 -0
- data/lib/clacky/default_extensions/meeting/handler.rb +331 -0
- data/lib/clacky/default_extensions/meeting/meeting.js +790 -0
- data/lib/clacky/default_extensions/meeting/meta.yml +3 -0
- data/lib/clacky/default_extensions/meeting/skills/meeting-summarizer/SKILL.md +44 -0
- data/lib/clacky/default_skills/media-gen/SKILL.md +72 -6
- data/lib/clacky/default_skills/media-gen/scripts/video_seq.sh +114 -0
- data/lib/clacky/idle_compression_timer.rb +3 -1
- data/lib/clacky/json_ui_controller.rb +1 -1
- data/lib/clacky/locales/en.rb +26 -0
- data/lib/clacky/locales/i18n.rb +26 -0
- data/lib/clacky/locales/zh.rb +26 -0
- data/lib/clacky/media/base.rb +60 -0
- data/lib/clacky/media/dashscope.rb +385 -21
- data/lib/clacky/media/gemini.rb +9 -0
- data/lib/clacky/media/generator.rb +52 -0
- data/lib/clacky/media/openai_compat.rb +166 -0
- data/lib/clacky/null_ui_controller.rb +13 -0
- data/lib/clacky/plain_ui_controller.rb +1 -1
- data/lib/clacky/providers.rb +50 -2
- data/lib/clacky/rich_ui/components/base_component.rb +50 -0
- data/lib/clacky/rich_ui/components/dialogs/approval_dialog.rb +142 -0
- data/lib/clacky/rich_ui/components/dialogs/config_menu_dialog.rb +106 -0
- data/lib/clacky/rich_ui/components/dialogs/form_dialog.rb +128 -0
- data/lib/clacky/rich_ui/components/sidebar.rb +119 -0
- data/lib/clacky/rich_ui/components/sidebar_panels.rb +134 -0
- data/lib/clacky/rich_ui/components/status_view.rb +58 -0
- data/lib/clacky/rich_ui/components/thinking_live_view.rb +79 -0
- data/lib/clacky/rich_ui/entry_tracker.rb +56 -0
- data/lib/clacky/rich_ui/layout_adapter.rb +16 -0
- data/lib/clacky/rich_ui/progress_handle_adapter.rb +24 -0
- data/lib/clacky/rich_ui/rich_ui_controller.rb +868 -0
- data/lib/clacky/rich_ui/shell/rich_agent_shell.rb +184 -0
- data/lib/clacky/rich_ui/view_renderer.rb +291 -0
- data/lib/clacky/rich_ui.rb +57 -0
- data/lib/clacky/rich_ui_controller.rb +3 -1549
- data/lib/clacky/server/api_extension_dispatcher.rb +120 -0
- data/lib/clacky/server/channel/channel_ui_controller.rb +1 -1
- data/lib/clacky/server/http_server.rb +291 -109
- data/lib/clacky/server/session_registry.rb +5 -3
- data/lib/clacky/server/web_ui_controller.rb +3 -2
- data/lib/clacky/shell_hook_loader.rb +1 -1
- data/lib/clacky/skill_loader.rb +14 -2
- data/lib/clacky/tools/edit.rb +14 -2
- data/lib/clacky/tools/terminal/output_cleaner.rb +1 -3
- data/lib/clacky/tools/terminal.rb +0 -43
- data/lib/clacky/ui2/components/modal_component.rb +1 -1
- data/lib/clacky/ui2/ui_controller.rb +147 -31
- data/lib/clacky/ui_interface.rb +10 -1
- data/lib/clacky/utils/encoding.rb +25 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +201 -81
- data/lib/clacky/web/app.js +65 -7
- data/lib/clacky/web/components/onboard.js +19 -16
- data/lib/clacky/web/core/aside.js +8 -3
- data/lib/clacky/web/core/ext.js +1 -1
- data/lib/clacky/web/features/brand/view.js +8 -5
- data/lib/clacky/web/features/channels/store.js +1 -20
- data/lib/clacky/web/features/mcp/store.js +1 -20
- data/lib/clacky/web/features/profile/store.js +1 -13
- data/lib/clacky/web/features/profile/view.js +16 -4
- data/lib/clacky/web/features/skills/store.js +36 -23
- data/lib/clacky/web/features/skills/view.js +32 -1
- data/lib/clacky/web/features/version/store.js +2 -0
- data/lib/clacky/web/features/workspace/view.js +1 -1
- data/lib/clacky/web/i18n.js +48 -13
- data/lib/clacky/web/index.html +24 -17
- data/lib/clacky/web/sessions.js +427 -79
- data/lib/clacky/web/settings.js +143 -113
- data/lib/clacky/web/ws-dispatcher.js +18 -6
- data/lib/clacky.rb +27 -5
- metadata +45 -2
- 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)
|