openclacky 1.3.5 → 1.3.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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/lib/clacky/agent/message_compressor.rb +32 -8
  4. data/lib/clacky/agent/message_compressor_helper.rb +113 -12
  5. data/lib/clacky/agent.rb +8 -4
  6. data/lib/clacky/agent_profile.rb +121 -53
  7. data/lib/clacky/brand_config.rb +309 -6
  8. data/lib/clacky/cli.rb +4 -58
  9. data/lib/clacky/default_extensions/coding/ext.yml +18 -0
  10. data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +95 -0
  11. data/lib/clacky/default_extensions/ext-studio/api/handler.rb +254 -0
  12. data/lib/clacky/default_extensions/ext-studio/ext.yml +32 -0
  13. data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +1269 -0
  14. data/lib/clacky/default_extensions/ext-studio/skills/ext-debug/SKILL.md +71 -0
  15. data/lib/clacky/default_extensions/ext-studio/skills/ext-publish/SKILL.md +74 -0
  16. data/lib/clacky/default_extensions/ext-studio/skills/ext-scaffold/SKILL.md +65 -0
  17. data/lib/clacky/default_extensions/general/ext.yml +18 -0
  18. data/lib/clacky/default_extensions/git/ext.yml +12 -0
  19. data/lib/clacky/{default_agents/_panels/git/panel.js → default_extensions/git/panels/git/view.js} +1 -2
  20. data/lib/clacky/default_extensions/meeting/{handler.rb → api/handler.rb} +78 -26
  21. data/lib/clacky/default_extensions/meeting/ext.yml +15 -0
  22. data/lib/clacky/default_extensions/meeting/{meeting.js → panels/meeting/view.js} +266 -89
  23. data/lib/clacky/default_extensions/meeting/skills/meeting-summarizer/SKILL.md +2 -1
  24. data/lib/clacky/default_extensions/time_machine/ext.yml +12 -0
  25. data/lib/clacky/{default_agents/_panels/time_machine/panel.js → default_extensions/time_machine/panels/time_machine/view.js} +58 -47
  26. data/lib/clacky/default_skills/channel-manager/discord_setup.rb +1 -1
  27. data/lib/clacky/default_skills/extend-openclacky/SKILL.md +83 -18
  28. data/lib/clacky/{api_extension.rb → extension/api_extension.rb} +27 -11
  29. data/lib/clacky/extension/api_loader.rb +136 -0
  30. data/lib/clacky/extension/cli_commands.rb +226 -0
  31. data/lib/clacky/{server/api_extension_dispatcher.rb → extension/dispatcher.rb} +22 -9
  32. data/lib/clacky/extension/hook_loader.rb +77 -0
  33. data/lib/clacky/extension/loader.rb +483 -0
  34. data/lib/clacky/extension/packager.rb +226 -0
  35. data/lib/clacky/{patch_loader.rb → extension/patch_loader.rb} +45 -0
  36. data/lib/clacky/extension/scaffold/template_renderer.rb +64 -0
  37. data/lib/clacky/extension/scaffold/templates/full/README.md.erb +48 -0
  38. data/lib/clacky/extension/scaffold/templates/full/agents/designer.md +5 -0
  39. data/lib/clacky/extension/scaffold/templates/full/api/handler.rb.erb +16 -0
  40. data/lib/clacky/extension/scaffold/templates/full/channels/noop.rb.erb +40 -0
  41. data/lib/clacky/extension/scaffold/templates/full/ext.yml.erb +41 -0
  42. data/lib/clacky/extension/scaffold/templates/full/hooks/audit.rb +9 -0
  43. data/lib/clacky/extension/scaffold/templates/full/panels/dashboard/view.js.erb +194 -0
  44. data/lib/clacky/extension/scaffold/templates/full/patches/audit.rb +15 -0
  45. data/lib/clacky/extension/scaffold/templates/full/skills/__slug__-skill/SKILL.md.erb +8 -0
  46. data/lib/clacky/extension/scaffold/templates/hello/api/handler.rb.erb +9 -0
  47. data/lib/clacky/extension/scaffold/templates/hello/ext.yml.erb +12 -0
  48. data/lib/clacky/extension/scaffold/templates/hello/panels/hello/view.js.erb +31 -0
  49. data/lib/clacky/extension/scaffold.rb +55 -0
  50. data/lib/clacky/extension/verifier.rb +196 -0
  51. data/lib/clacky/identity.rb +70 -0
  52. data/lib/clacky/locales/en.rb +19 -1
  53. data/lib/clacky/locales/zh.rb +19 -1
  54. data/lib/clacky/platform_http_client.rb +18 -15
  55. data/lib/clacky/server/channel/extension_adapter_loader.rb +32 -0
  56. data/lib/clacky/server/channel.rb +6 -0
  57. data/lib/clacky/server/http_server.rb +435 -263
  58. data/lib/clacky/server/session_registry.rb +59 -3
  59. data/lib/clacky/session_manager.rb +35 -4
  60. data/lib/clacky/skill_loader.rb +28 -1
  61. data/lib/clacky/ui2/layout_manager.rb +0 -5
  62. data/lib/clacky/ui2/progress_handle.rb +0 -3
  63. data/lib/clacky/ui2/ui_controller.rb +9 -15
  64. data/lib/clacky/version.rb +1 -1
  65. data/lib/clacky/web/app.css +782 -554
  66. data/lib/clacky/web/app.js +63 -15
  67. data/lib/clacky/web/auth.js +2 -0
  68. data/lib/clacky/web/components/notify.js +2 -0
  69. data/lib/clacky/web/components/sidebar.js +2 -8
  70. data/lib/clacky/web/core/ext.js +316 -79
  71. data/lib/clacky/web/features/backup/store.js +24 -0
  72. data/lib/clacky/web/features/backup/view.js +49 -23
  73. data/lib/clacky/web/features/brand/view.js +13 -5
  74. data/lib/clacky/web/features/extensions/store.js +184 -0
  75. data/lib/clacky/web/features/extensions/view.js +389 -0
  76. data/lib/clacky/web/features/new-session/store.js +186 -0
  77. data/lib/clacky/web/features/new-session/view.js +319 -0
  78. data/lib/clacky/web/features/share/view.js +63 -14
  79. data/lib/clacky/web/features/skills/store.js +3 -1
  80. data/lib/clacky/web/features/skills/view.js +1 -1
  81. data/lib/clacky/web/features/tasks/view.js +88 -27
  82. data/lib/clacky/web/features/trash/store.js +24 -6
  83. data/lib/clacky/web/features/trash/view.js +46 -9
  84. data/lib/clacky/web/features/workspace/store.js +2 -0
  85. data/lib/clacky/web/features/workspace/view.js +0 -1
  86. data/lib/clacky/web/i18n.js +140 -108
  87. data/lib/clacky/web/index.html +190 -214
  88. data/lib/clacky/web/sessions.js +99 -304
  89. data/lib/clacky/web/settings.js +1 -1
  90. data/lib/clacky/web/skills.js +2 -0
  91. data/lib/clacky/web/theme.js +2 -0
  92. data/lib/clacky/web/ws-dispatcher.js +2 -1
  93. data/lib/clacky/web/ws.js +2 -0
  94. data/lib/clacky.rb +21 -10
  95. metadata +55 -24
  96. data/benchmark/runner.rb +0 -441
  97. data/lib/clacky/api_extension_loader.rb +0 -168
  98. data/lib/clacky/default_agents/SOUL.md +0 -3
  99. data/lib/clacky/default_agents/USER.md +0 -1
  100. data/lib/clacky/default_agents/coding/profile.yml +0 -5
  101. data/lib/clacky/default_agents/coding/webui/.gitkeep +0 -0
  102. data/lib/clacky/default_agents/general/profile.yml +0 -2
  103. data/lib/clacky/default_extensions/meeting/meta.yml +0 -3
  104. data/lib/clacky/web/features/creator/store.js +0 -81
  105. data/lib/clacky/web/features/creator/view.js +0 -380
  106. /data/lib/clacky/{default_agents → default_extensions/coding/agents}/coding/system_prompt.md +0 -0
  107. /data/lib/clacky/{default_agents → default_extensions/general/agents}/general/system_prompt.md +0 -0
  108. /data/lib/clacky/{default_agents/base_prompt.md → prompts/base.md} +0 -0
@@ -0,0 +1,254 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "tmpdir"
5
+ require "socket"
6
+ require "digest"
7
+
8
+ # Extension Studio — backend for the debug/publish panels. Mounted at
9
+ # /api/ext/ext-studio/. Reads the resolved extension tree, runs the verifier,
10
+ # packs and publishes local containers to the marketplace.
11
+ class ExtStudioExt < Clacky::ApiExtension
12
+ timeout 60
13
+
14
+ # GET /api/ext/ext-studio/extensions
15
+ # List every local-layer container with a units summary and verify status,
16
+ # so the debug panel can populate its picker and detail view.
17
+ get "/extensions" do
18
+ result = Clacky::ExtensionLoader.load_all(force: true)
19
+ issues = Clacky::ExtensionVerifier.verify(result)
20
+
21
+ exts = local_containers(result).map do |ext_id, container|
22
+ serialize_container(ext_id, container, result, issues)
23
+ end
24
+ exts.sort_by! { |e| -e[:mtime] }
25
+
26
+ json(extensions: exts)
27
+ end
28
+
29
+ # POST /api/ext/ext-studio/verify
30
+ # body: { ext_id? } — omit ext_id to verify the whole tree.
31
+ # Returns structured issues (same shape the CLI prints) plus resolved units.
32
+ post "/verify" do
33
+ ext_id = json_body["ext_id"].to_s.strip
34
+ result = Clacky::ExtensionLoader.load_all(force: true)
35
+ issues = Clacky::ExtensionVerifier.verify(result)
36
+
37
+ scoped = ext_id.empty? ? issues : issues.select { |i| i.ext == ext_id }
38
+ units = result.units
39
+ units = units.select { |u| u.ext_id == ext_id } unless ext_id.empty?
40
+
41
+ json(
42
+ ext_id: ext_id.empty? ? nil : ext_id,
43
+ units: units.map { |u| serialize_unit(u) },
44
+ issues: scoped.map { |i| serialize_issue(i) },
45
+ ok: scoped.none? { |i| i.level == :error }
46
+ )
47
+ end
48
+
49
+ # POST /api/ext/ext-studio/pack
50
+ # body: { ext_id }
51
+ # Packs a local container into a zip (into a temp dir) and reports its path.
52
+ post "/pack" do
53
+ ext_id = require_ext_id!
54
+ Dir.mktmpdir("clacky-ext-studio-pack") do |tmp|
55
+ res = Clacky::ExtensionPackager.pack(ext_id, out_dir: tmp)
56
+ json(ok: true, ext_id: res.ext_id, path: res.path)
57
+ end
58
+ rescue Clacky::ExtensionPackager::Error => e
59
+ error!(e.message, status: 422)
60
+ end
61
+
62
+ # POST /api/ext/ext-studio/publish
63
+ # body: { ext_id, force?, status?, changelog? }
64
+ # Packs then uploads to the marketplace. Requires the device to be bound to a
65
+ # platform account (device token). When unbound, returns a 428 with a hint so
66
+ # the UI can trigger the on-demand device-authorization flow.
67
+ post "/publish" do
68
+ ext_id = require_ext_id!
69
+
70
+ unless Clacky::Identity.load.bound?
71
+ error!("device not bound to a platform account; authorize this device to publish",
72
+ status: 428, needs_binding: true)
73
+ end
74
+
75
+ brand = Clacky::BrandConfig.load
76
+
77
+ Dir.mktmpdir("clacky-ext-studio-publish") do |tmp|
78
+ res = Clacky::ExtensionPackager.pack(ext_id, out_dir: tmp)
79
+ zip_data = File.binread(res.path)
80
+
81
+ result = brand.upload_extension!(
82
+ res.ext_id, zip_data,
83
+ force: json_body["force"] == true,
84
+ status: presence(json_body["status"]),
85
+ changelog: presence(json_body["changelog"])
86
+ )
87
+
88
+ if result[:success]
89
+ ext = result[:extension] || {}
90
+ ver = (ext["latest_version"] || {})["version"]
91
+ json(ok: true, ext_id: res.ext_id, version: ver, status: ext["status"])
92
+ elsif result[:already_exists]
93
+ json(ok: false, already_exists: true,
94
+ error: "#{res.ext_id} already published. Publish a new version with force.")
95
+ else
96
+ error!(result[:error] || "publish failed", status: 502)
97
+ end
98
+ end
99
+ rescue Clacky::ExtensionPackager::Error => e
100
+ error!(e.message, status: 422)
101
+ end
102
+
103
+ # GET /api/ext/ext-studio/published
104
+ # List the current user's published extensions from the marketplace.
105
+ get "/published" do
106
+ brand = Clacky::BrandConfig.load
107
+ result = brand.fetch_my_extensions!
108
+ error!(result[:error] || "failed to fetch published extensions", status: 502) unless result[:success]
109
+
110
+ exts = Array(result[:extensions]).map do |ext|
111
+ {
112
+ id: ext["name"] || ext["slug"] || ext["id"],
113
+ name: ext["name"],
114
+ version: (ext["latest_version"] || {})["version"] || ext["version"],
115
+ status: ext["status"],
116
+ units: ext["units"] || {}
117
+ }
118
+ end
119
+ json(extensions: exts)
120
+ end
121
+
122
+ # POST /api/ext/ext-studio/unpublish
123
+ # body: { ext_id }
124
+ post "/unpublish" do
125
+ ext_id = require_ext_id!
126
+ result = Clacky::BrandConfig.load.delete_extension!(ext_id)
127
+ error!(result[:error] || "unpublish failed", status: 502) unless result[:success]
128
+ json(ok: true, ext_id: ext_id)
129
+ end
130
+
131
+ # POST /api/ext/ext-studio/develop
132
+ # body: { idea? }
133
+ # Spawns a session bound to the ext-developer agent, optionally seeded with
134
+ # the user's idea as the first task — the "let AI build it for me" entry.
135
+ post "/develop" do
136
+ idea = presence(json_body["idea"])
137
+ name = idea ? "扩展开发: #{idea[0, 40]}" : "扩展开发"
138
+ sid = create_session(name: name, prompt: idea, profile: "ext-developer", source: :setup)
139
+ json(ok: true, session_id: sid)
140
+ end
141
+
142
+ # GET /api/ext/ext-studio/binding
143
+ # Reports whether this device is bound to a platform account, so the publish
144
+ # panel can decide up-front whether to run the binding flow.
145
+ get "/binding" do
146
+ identity = Clacky::Identity.load
147
+ json(bound: identity.bound?, user_id: identity.user_id)
148
+ end
149
+
150
+ # POST /api/ext/ext-studio/binding/start
151
+ # Kicks off an RFC 8628 device-authorization flow against the platform and
152
+ # returns the verification URL the panel opens plus the device_code to poll.
153
+ post "/binding/start" do
154
+ client = Clacky::PlatformHttpClient.new
155
+ result = client.post("/api/v1/device/authorize", {
156
+ device_id: binding_device_id,
157
+ device_info: { os: RUBY_PLATFORM, hostname: Socket.gethostname, app_version: Clacky::VERSION }
158
+ })
159
+
160
+ error!(result[:error] || "could not start authorization", status: 502) unless result[:success]
161
+
162
+ data = result[:data]
163
+ json(
164
+ ok: true,
165
+ device_code: data["device_code"],
166
+ user_code: data["user_code"],
167
+ verification_uri: data["verification_uri"],
168
+ verification_uri_complete: data["verification_uri_complete"],
169
+ interval: data["interval"] || 5
170
+ )
171
+ end
172
+
173
+ # POST /api/ext/ext-studio/binding/poll { device_code }
174
+ # Polls the platform once. On approval, binds the issued device token to the
175
+ # local Identity so subsequent publishes authenticate as the platform account.
176
+ post "/binding/poll" do
177
+ device_code = presence(json_body["device_code"])
178
+ error!("device_code required", status: 422) unless device_code
179
+
180
+ client = Clacky::PlatformHttpClient.new
181
+ result = client.post("/api/v1/device/token", { device_code: device_code })
182
+ data = result[:data] || {}
183
+ status = data["status"]
184
+
185
+ if result[:success] && status == "approved"
186
+ Clacky::Identity.load.bind!(
187
+ device_token: data["device_token"],
188
+ user_id: data["user_id"]
189
+ ) if data["device_token"]
190
+ json(ok: true, status: "approved")
191
+ elsif status == "pending"
192
+ json(ok: true, status: "pending")
193
+ else
194
+ json(ok: false, status: status || "error", error: result[:error])
195
+ end
196
+ end
197
+
198
+ private def local_containers(result)
199
+ Array(result.containers).select { |_id, c| c[:layer] == :local }
200
+ end
201
+
202
+ private def serialize_container(ext_id, container, result, issues)
203
+ raw = container[:raw] || {}
204
+ ext_issues = issues.select { |i| i.ext == ext_id }
205
+ dir = container[:dir]
206
+ {
207
+ id: ext_id,
208
+ name: raw["name"] || ext_id,
209
+ description: raw["description"],
210
+ version: raw["version"],
211
+ origin: container[:origin],
212
+ layer: container[:layer].to_s,
213
+ dir: dir,
214
+ mtime: File.mtime(File.join(dir, "ext.yml")).to_i,
215
+ units: result.units.select { |u| u.ext_id == ext_id }.map { |u| serialize_unit(u) },
216
+ error_count: ext_issues.count { |i| i.level == :error },
217
+ warning_count: ext_issues.count { |i| i.level == :warning }
218
+ }
219
+ end
220
+
221
+ private def serialize_unit(unit)
222
+ { kind: unit.kind.to_s, id: unit.id, layer: unit.layer.to_s }
223
+ end
224
+
225
+ private def serialize_issue(issue)
226
+ {
227
+ ext: issue.ext,
228
+ unit: issue.unit,
229
+ level: issue.level.to_s,
230
+ code: issue.code,
231
+ message: issue.message,
232
+ file: issue.file,
233
+ hint: issue.hint
234
+ }
235
+ end
236
+
237
+ private def require_ext_id!
238
+ id = json_body["ext_id"].to_s.strip
239
+ error!("ext_id required", status: 422) if id.empty?
240
+ id
241
+ end
242
+
243
+ private def presence(value)
244
+ str = value.to_s.strip
245
+ str.empty? ? nil : str
246
+ end
247
+
248
+ # Stable per-machine id for the device-authorization flow. Matches the
249
+ # onboarding device_id so binding reuses the same device row on the platform.
250
+ private def binding_device_id
251
+ components = [Socket.gethostname, ENV["USER"] || ENV["USERNAME"] || "", RUBY_PLATFORM]
252
+ Digest::SHA256.hexdigest(components.join(":"))
253
+ end
254
+ end
@@ -0,0 +1,32 @@
1
+ id: ext-studio
2
+ name: Extension Studio
3
+ description: AI-driven workspace for building, debugging, and publishing OpenClacky extensions
4
+ version: "0.1.0"
5
+ author: OpenClacky
6
+ homepage: https://www.openclacky.com
7
+ license: MIT
8
+ origin: self
9
+ contributes:
10
+ api: api/handler.rb
11
+ panels:
12
+ - id: studio
13
+ title: Extension & Creation
14
+ title_zh: 扩展与创作
15
+ description: Build, debug and publish extensions and skills
16
+ description_zh: 开发、调试、发布扩展与 skill
17
+ view: panels/studio/view.js
18
+ order: 210
19
+ agents:
20
+ - id: ext-developer
21
+ title: Extension Developer
22
+ title_zh: 扩展开发
23
+ description: AI expert that helps you build, debug, and publish OpenClacky extensions
24
+ description_zh: 帮你开发、调试、发布 OpenClacky 扩展的 AI 专家
25
+ order: 5
26
+ prompt: agents/ext-developer/system_prompt.md
27
+ panels: [studio]
28
+ skills: [ext-scaffold, ext-debug, ext-publish]
29
+ skills:
30
+ - id: ext-scaffold
31
+ - id: ext-debug
32
+ - id: ext-publish