openclacky 1.5.9 → 1.5.11

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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/.clackyrules +1 -0
  3. data/CHANGELOG.md +60 -0
  4. data/README.md +4 -4
  5. data/README_CN.md +4 -4
  6. data/README_JA.md +4 -4
  7. data/lib/clacky/agent/llm_caller.rb +7 -3
  8. data/lib/clacky/agent/memory_updater.rb +1 -43
  9. data/lib/clacky/agent/session_serializer.rb +26 -14
  10. data/lib/clacky/agent/skill_auto_creator.rb +12 -9
  11. data/lib/clacky/agent/skill_evolution.rb +4 -3
  12. data/lib/clacky/agent/skill_manager.rb +11 -14
  13. data/lib/clacky/agent/skill_reflector.rb +2 -2
  14. data/lib/clacky/agent/tool_executor.rb +1 -1
  15. data/lib/clacky/agent/tool_registry.rb +6 -4
  16. data/lib/clacky/agent.rb +240 -76
  17. data/lib/clacky/agent_config.rb +66 -39
  18. data/lib/clacky/agent_profile.rb +36 -2
  19. data/lib/clacky/brand_config.rb +15 -17
  20. data/lib/clacky/cli.rb +26 -7
  21. data/lib/clacky/client.rb +52 -14
  22. data/lib/clacky/default_extensions/advisor/advisors/general.md +48 -0
  23. data/lib/clacky/default_extensions/advisor/api/handler.rb +27 -0
  24. data/lib/clacky/default_extensions/advisor/ext.yml +35 -0
  25. data/lib/clacky/default_extensions/advisor/hooks/advisor.rb +291 -0
  26. data/lib/clacky/default_extensions/advisor/hooks/advisor_complete.rb +11 -0
  27. data/lib/clacky/default_extensions/advisor/hooks/advisor_tool_use.rb +12 -0
  28. data/lib/clacky/default_extensions/advisor/panels/advisor/view.js +462 -0
  29. data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +6 -19
  30. data/lib/clacky/default_extensions/ext-studio/api/handler.rb +1 -0
  31. data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +92 -57
  32. data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +64 -11
  33. data/lib/clacky/default_extensions/meeting/api/handler.rb +1 -1
  34. data/lib/clacky/default_skills/channel-manager/SKILL.md +4 -4
  35. data/lib/clacky/default_skills/media-gen/SKILL.md +18 -17
  36. data/lib/clacky/default_skills/new/SKILL.md +1 -1
  37. data/lib/clacky/default_skills/onboard/SKILL.md +2 -2
  38. data/lib/clacky/default_skills/skill-creator/SKILL.md +8 -7
  39. data/lib/clacky/extension/cli_commands.rb +1 -1
  40. data/lib/clacky/extension/hook_loader.rb +2 -0
  41. data/lib/clacky/extension/loader.rb +36 -3
  42. data/lib/clacky/extension/packager.rb +45 -10
  43. data/lib/clacky/extension/scaffold/templates/full/README.md.erb +3 -1
  44. data/lib/clacky/extension/scaffold/templates/full/ext.yml.erb +8 -1
  45. data/lib/clacky/extension/scaffold/templates/full/panels/dashboard/view.js.erb +10 -22
  46. data/lib/clacky/extension/scaffold/templates/full/tools/hello.rb +28 -0
  47. data/lib/clacky/extension/scaffold/templates/hello/panels/hello/view.js.erb +3 -3
  48. data/lib/clacky/extension/scaffold.rb +3 -2
  49. data/lib/clacky/extension/verifier.rb +4 -2
  50. data/lib/clacky/fanout.rb +1 -1
  51. data/lib/clacky/idle_compression_timer.rb +3 -5
  52. data/lib/clacky/json_ui_controller.rb +1 -1
  53. data/lib/clacky/locales/en.rb +1 -1
  54. data/lib/clacky/locales/zh.rb +1 -1
  55. data/lib/clacky/mcp/http_transport.rb +29 -13
  56. data/lib/clacky/mcp/registry.rb +2 -3
  57. data/lib/clacky/mcp/stdio_transport.rb +21 -17
  58. data/lib/clacky/media/base.rb +22 -13
  59. data/lib/clacky/message_format/open_ai.rb +17 -1
  60. data/lib/clacky/message_history.rb +1 -1
  61. data/lib/clacky/plain_ui_controller.rb +19 -16
  62. data/lib/clacky/platform_http_client.rb +14 -14
  63. data/lib/clacky/providers.rb +55 -15
  64. data/lib/clacky/rich_ui/rich_ui_controller.rb +6 -7
  65. data/lib/clacky/server/browser_manager.rb +4 -7
  66. data/lib/clacky/server/channel/adapters/dingtalk/stream_client.rb +2 -1
  67. data/lib/clacky/server/channel/adapters/discord/gateway_client.rb +7 -5
  68. data/lib/clacky/server/channel/adapters/feishu/bot.rb +42 -20
  69. data/lib/clacky/server/channel/adapters/feishu/ws_client.rb +4 -3
  70. data/lib/clacky/server/channel/adapters/telegram/adapter.rb +3 -2
  71. data/lib/clacky/server/channel/adapters/telegram/api_client.rb +5 -3
  72. data/lib/clacky/server/channel/adapters/wecom/ws_client.rb +6 -5
  73. data/lib/clacky/server/channel/adapters/weixin/adapter.rb +13 -11
  74. data/lib/clacky/server/channel/adapters/weixin/api_client.rb +7 -2
  75. data/lib/clacky/server/channel/channel_config.rb +36 -6
  76. data/lib/clacky/server/channel/channel_manager.rb +24 -11
  77. data/lib/clacky/server/channel/channel_ui_controller.rb +40 -8
  78. data/lib/clacky/server/http_server.rb +323 -81
  79. data/lib/clacky/server/model_prices.rb +39 -0
  80. data/lib/clacky/server/project_manager.rb +4 -1
  81. data/lib/clacky/server/scheduler.rb +4 -5
  82. data/lib/clacky/server/server_master.rb +27 -4
  83. data/lib/clacky/server/session_registry.rb +7 -2
  84. data/lib/clacky/server/web_ui_controller.rb +16 -15
  85. data/lib/clacky/session_manager.rb +11 -4
  86. data/lib/clacky/shell_hook_loader.rb +3 -3
  87. data/lib/clacky/shutdown.rb +71 -0
  88. data/lib/clacky/skill.rb +9 -0
  89. data/lib/clacky/skill_loader.rb +8 -3
  90. data/lib/clacky/telemetry.rb +1 -1
  91. data/lib/clacky/thread_registry.rb +102 -0
  92. data/lib/clacky/tools/ask_user.rb +212 -0
  93. data/lib/clacky/tools/base.rb +9 -0
  94. data/lib/clacky/tools/terminal.rb +1 -1
  95. data/lib/clacky/tools/trash_manager.rb +16 -0
  96. data/lib/clacky/tools/web_search.rb +25 -12
  97. data/lib/clacky/ui2/components/command_suggestions.rb +1 -2
  98. data/lib/clacky/ui2/components/input_area.rb +2 -2
  99. data/lib/clacky/ui2/progress_handle.rb +1 -2
  100. data/lib/clacky/ui2/progress_indicator.rb +1 -1
  101. data/lib/clacky/ui2/ui_controller.rb +118 -18
  102. data/lib/clacky/ui_interface.rb +3 -3
  103. data/lib/clacky/utils/arguments_parser.rb +31 -4
  104. data/lib/clacky/utils/logger.rb +3 -1
  105. data/lib/clacky/utils/model_pricing.rb +114 -8
  106. data/lib/clacky/utils/parser_manager.rb +2 -2
  107. data/lib/clacky/version.rb +1 -1
  108. data/lib/clacky/web/app.css +413 -66
  109. data/lib/clacky/web/components/notify.js +9 -7
  110. data/lib/clacky/web/core/ext.js +4 -6
  111. data/lib/clacky/web/features/channels/store.js +32 -2
  112. data/lib/clacky/web/features/channels/view.js +76 -0
  113. data/lib/clacky/web/features/extensions/store.js +25 -0
  114. data/lib/clacky/web/features/extensions/view.js +24 -0
  115. data/lib/clacky/web/features/model-tester/store.js +2 -1
  116. data/lib/clacky/web/i18n.js +96 -2
  117. data/lib/clacky/web/index.html +47 -8
  118. data/lib/clacky/web/projects.js +29 -1
  119. data/lib/clacky/web/sessions.js +463 -101
  120. data/lib/clacky/web/settings.js +206 -22
  121. data/lib/clacky/web/skills.js +86 -10
  122. data/lib/clacky/web/ws-dispatcher.js +43 -7
  123. data/lib/clacky.rb +3 -1
  124. metadata +13 -2
  125. data/lib/clacky/tools/request_user_feedback.rb +0 -84
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 565adf42059330fb821c57cb3b1200035fb866a1f2190ca87e093bc34de9edc0
4
- data.tar.gz: 89db4dd987f39d4b77acd52121aa31bef54858f81d614557ac7b9984a1066ba0
3
+ metadata.gz: 1cef597eb5d6c066192ba6c2c587e0dcd62fdf2dd9ed422161f71718acbb7f25
4
+ data.tar.gz: 03bcd29f23eacd7b49a37db0494f7964757391ac52e8f3ef9787c49a6b2f8efb
5
5
  SHA512:
6
- metadata.gz: e3bbb8988d833e3c6e6ce4cd13da64111e2ee15b400c2eb34b78bca9f51ae80525336baf923ba18cdbf85b5320d542f8050567792e7d5a437b28e2d09d29729d
7
- data.tar.gz: ae508f3bdb44a35b8edd0c89ebcc53a64d3f7340214ded6c8dd47d0c757c66f5f99700f466c86278e8ee0a38ab838b8509c80ba8af2f253653f967b91f52a814
6
+ metadata.gz: 7543c4f518bf5a2e20778472e60909b5eda8f727015b66aad37c69b2d256b53ba3cd5126d2b385a06456a45a6531fe4fdaad7c6d7f8b247aeb2a76bf1446a9f1
7
+ data.tar.gz: d6ceb974a12d29096ae65bcbed3d6c6467880005101664919357572201846792faf4f89956ff5ca568519d74960674e214a39c61333e527741a9eb38083fcbf1
data/.clackyrules CHANGED
@@ -70,6 +70,7 @@ the CI matrix - a green local `bundle exec rspec` on Ruby 3.x proves nothing abo
70
70
  - **IMPORTANT**: When developing new features, write RSpec tests as needed and ensure they pass
71
71
  - **DO NOT** write custom test scripts unless explicitly requested by the user
72
72
  - **DO NOT** create markdown documentation unless explicitly requested by the user
73
+ - **DO NOT** run RuboCop for verification. `bundle exec rspec` is the only required check; the codebase has many pre-existing offenses, so RuboCop output is noise. Only run it when the user explicitly asks
73
74
  - **IMPORTANT**: When testing clacky commands or debugging, always use `bundle exec ruby bin/clacky` instead of the global `clacky` command. The global command loads the system-installed gem version (e.g., `openclacky-0.7.0`), not your local development code
74
75
 
75
76
  ### Tool Development
data/CHANGELOG.md CHANGED
@@ -4,6 +4,66 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
 
7
+ ## [1.5.11] - 2026-08-23
8
+
9
+ ### Added
10
+ - Creator Studio: separate Marketplace and Brand sections for published extensions
11
+ - Advisor extension for next-job suggestions
12
+ - ask_user tool with multi-question support (replaces feedback_user)
13
+ - Sidebar states for awaiting-feedback and just-finished sessions
14
+ - Auto-purge file-trash entries older than 8 days
15
+ - DeepSeek V4 flash vision exp model preset
16
+ - IM channel toggle to mute tool-call narration and previews
17
+
18
+ ### Improved
19
+ - Unified internal thread management with graceful shutdown
20
+
21
+ ### Fixed
22
+ - Skill decrypt retry now picks up a renewed license key
23
+ - DeepSeek weekends billed entirely at off-peak rates
24
+ - Skill and extension zip downloads fall back to a secondary CDN host
25
+ - Session status dot aligned with name and channel badge
26
+ - Project session tag top margin
27
+ - Unified modal close button and ext-studio modal semantics
28
+ - Unified checkbox accent and distinct verified badge
29
+ - Edit-model modal overflow on small screens
30
+ - Subagent UI fixes
31
+ - Star History charts migrated to star-history.dera.page (#491 - @CrustyMozarella)
32
+
33
+ ## [1.5.10] - 2026-08-19
34
+
35
+ ### Added
36
+ - Import extensions from a local zip file
37
+ - Per-model API format override (#484 - @kylezhang)
38
+ - Project pinning (pin to top)
39
+ - Copy Session ID action in session menu
40
+ - Price ratio on model switcher panel
41
+ - Model search filter and provider filter dropdown in settings
42
+ - Remark field for model configs
43
+ - Custom vision model config
44
+ - GPT-5.6 Sol/Terra/Luna presets and pricing
45
+ - GLM-5.3 model support
46
+ - Global IM status-messages toggle (default off)
47
+ - Extension-defined tools support; extensions can disable skills
48
+ - Server restart state and HTTP restart flag
49
+ - Actionable web search provider errors
50
+ - Localized display names for broadcast skill commands
51
+
52
+ ### Improved
53
+ - Skill autocomplete dropdown UI with localized source labels
54
+ - Skill auto-creation and evolution reporting refinements
55
+
56
+ ### Fixed
57
+ - MCP: preserve CRLF pairs split across SSE chunk boundaries (#489 - @MohammedAlkindi)
58
+ - Feishu: auto-refresh token and retry when revoked (99991663) (#483 - @chengoak)
59
+ - Preserve head+tail for oversized terminal output (#455 - @shipinliang)
60
+ - openclacky start failed when no TTY
61
+ - Capture master/worker crash logs under LaunchAgent
62
+ - ReferenceError in model modal _syncRemarkField
63
+ - Remark field always visible in model modal
64
+ - api_format field only shown for custom providers and validated before mutating model config
65
+ - Media gen output dir handling
66
+
7
67
  ## [1.5.9] - 2026-08-14
8
68
 
9
69
  ### Added
data/README.md CHANGED
@@ -209,11 +209,11 @@ $ openclacky
209
209
 
210
210
  ## Star History
211
211
 
212
- <a href="https://www.star-history.com/?repos=clacky-ai%2Fopenclacky&type=date&legend=top-left">
212
+ <a href="https://star-history.dera.page/#clacky-ai/openclacky&type=date&legend=top-left">
213
213
  <picture>
214
- <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=clacky-ai/openclacky&type=date&theme=dark&legend=top-left" />
215
- <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=clacky-ai/openclacky&type=date&legend=top-left" />
216
- <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=clacky-ai/openclacky&type=date&legend=top-left" />
214
+ <source media="(prefers-color-scheme: dark)" srcset="https://star-history.dera.page/svg?repos=clacky-ai/openclacky&type=date&theme=dark&legend=top-left" />
215
+ <source media="(prefers-color-scheme: light)" srcset="https://star-history.dera.page/svg?repos=clacky-ai/openclacky&type=date&legend=top-left" />
216
+ <img alt="Star History Chart" src="https://star-history.dera.page/svg?repos=clacky-ai/openclacky&type=date&legend=top-left" />
217
217
  </picture>
218
218
  </a>
219
219
 
data/README_CN.md CHANGED
@@ -204,11 +204,11 @@ $ openclacky
204
204
 
205
205
  ## Star 历史
206
206
 
207
- <a href="https://www.star-history.com/?repos=clacky-ai%2Fopenclacky&type=date&legend=top-left">
207
+ <a href="https://star-history.dera.page/#clacky-ai/openclacky&type=date&legend=top-left">
208
208
  <picture>
209
- <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=clacky-ai/openclacky&type=date&theme=dark&legend=top-left" />
210
- <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=clacky-ai/openclacky&type=date&legend=top-left" />
211
- <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=clacky-ai/openclacky&type=date&legend=top-left" />
209
+ <source media="(prefers-color-scheme: dark)" srcset="https://star-history.dera.page/svg?repos=clacky-ai/openclacky&type=date&theme=dark&legend=top-left" />
210
+ <source media="(prefers-color-scheme: light)" srcset="https://star-history.dera.page/svg?repos=clacky-ai/openclacky&type=date&legend=top-left" />
211
+ <img alt="Star History Chart" src="https://star-history.dera.page/svg?repos=clacky-ai/openclacky&type=date&legend=top-left" />
212
212
  </picture>
213
213
  </a>
214
214
 
data/README_JA.md CHANGED
@@ -209,11 +209,11 @@ $ openclacky
209
209
 
210
210
  ## Star History
211
211
 
212
- <a href="https://www.star-history.com/?repos=clacky-ai%2Fopenclacky&type=date&legend=top-left">
212
+ <a href="https://star-history.dera.page/#clacky-ai/openclacky&type=date&legend=top-left">
213
213
  <picture>
214
- <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=clacky-ai/openclacky&type=date&theme=dark&legend=top-left" />
215
- <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=clacky-ai/openclacky&type=date&legend=top-left" />
216
- <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=clacky-ai/openclacky&type=date&legend=top-left" />
214
+ <source media="(prefers-color-scheme: dark)" srcset="https://star-history.dera.page/svg?repos=clacky-ai/openclacky&type=date&theme=dark&legend=top-left" />
215
+ <source media="(prefers-color-scheme: light)" srcset="https://star-history.dera.page/svg?repos=clacky-ai/openclacky&type=date&legend=top-left" />
216
+ <img alt="Star History Chart" src="https://star-history.dera.page/svg?repos=clacky-ai/openclacky&type=date&legend=top-left" />
217
217
  </picture>
218
218
  </a>
219
219
 
@@ -95,6 +95,7 @@ module Clacky
95
95
 
96
96
  begin
97
97
  begin
98
+ Clacky::Shutdown.checkpoint!
98
99
  # Use active_messages (Time Machine) when undone, otherwise send full history.
99
100
  # to_api strips internal fields and handles orphaned tool_calls.
100
101
  messages_to_send = if respond_to?(:active_messages)
@@ -167,6 +168,7 @@ module Clacky
167
168
  end
168
169
 
169
170
  rescue Faraday::TimeoutError => e
171
+ Clacky::Shutdown.checkpoint!
170
172
  # Faraday::TimeoutError on our non-streaming POST almost always means
171
173
  # the *response* took longer than the 300s read-timeout to come back —
172
174
  # i.e. the model is trying to produce a huge output in one shot
@@ -198,7 +200,7 @@ module Clacky
198
200
  metadata: { attempt: retries, total: max_retries }
199
201
  )
200
202
  retrying_progress_opened = true
201
- sleep retry_delay
203
+ Clacky::Shutdown.sleep(retry_delay)
202
204
  retry
203
205
  else
204
206
  # All retries exhausted for a timeout. Try the secondary gateway URL if available.
@@ -219,6 +221,7 @@ module Clacky
219
221
  end
220
222
 
221
223
  rescue Faraday::ConnectionFailed, Faraday::SSLError, Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::EPIPE => e
224
+ Clacky::Shutdown.checkpoint!
222
225
  retries += 1
223
226
 
224
227
  # Errno::EPIPE means the underlying TCP socket is dead (server closed the
@@ -245,7 +248,7 @@ module Clacky
245
248
  metadata: { attempt: retries, total: max_retries }
246
249
  )
247
250
  retrying_progress_opened = true
248
- sleep retry_delay
251
+ Clacky::Shutdown.sleep(retry_delay)
249
252
  retry
250
253
  else
251
254
  # All retries on the primary endpoint exhausted for a network-level
@@ -269,6 +272,7 @@ module Clacky
269
272
  end
270
273
 
271
274
  rescue RetryableError => e
275
+ Clacky::Shutdown.checkpoint!
272
276
  retries += 1
273
277
 
274
278
  # Probing failure: primary still down — renew cooling-off and retry with fallback.
@@ -298,7 +302,7 @@ module Clacky
298
302
  metadata: { attempt: retries, total: current_max }
299
303
  )
300
304
  retrying_progress_opened = true
301
- sleep retry_delay
305
+ Clacky::Shutdown.sleep(retry_delay)
302
306
  retry
303
307
  else
304
308
  # All retries on the current endpoint exhausted.
@@ -74,7 +74,7 @@ module Clacky
74
74
  private def with_memory_update_phase
75
75
  return yield unless @ui.respond_to?(:with_phase)
76
76
 
77
- pid = @ui.phase_start(kind: "memory_update", label: "Updating long-term memory")
77
+ pid = @ui.phase_start(kind: "memory_update", label: Clacky::I18n.t("phase.memory_update"))
78
78
  summary = nil
79
79
  begin
80
80
  summary = yield
@@ -127,11 +127,6 @@ module Clacky
127
127
  # still reflects ONLY the user's task, not the memory update.
128
128
  subagent_cost = absorb_subagent_cost(result)
129
129
 
130
- # A summary is only worth showing if the subagent actually wrote
131
- # something. The common "No memory updates needed." path returns nil
132
- # so the phase leaves no trace at all.
133
- return unless subagent_wrote_memory?(subagent)
134
-
135
130
  "#{result[:iterations]} steps · $#{subagent_cost.round(4)}"
136
131
  end
137
132
 
@@ -142,43 +137,6 @@ module Clacky
142
137
  @config.memory_update_enabled != false
143
138
  end
144
139
 
145
- # Inspect the subagent's history for a successful write/edit tool
146
- # call targeting a memory file. Used to decide whether to surface a
147
- # "Memory updated" info line (option C — silent when nothing changed).
148
- # @param subagent [Clacky::Agent]
149
- # @return [Boolean]
150
- private def subagent_wrote_memory?(subagent)
151
- return false unless subagent.respond_to?(:history) && subagent.history
152
-
153
- subagent.history.to_a.any? do |msg|
154
- next false unless msg.is_a?(Hash)
155
-
156
- # Match OpenAI-style tool_calls on assistant messages …
157
- tool_calls = msg[:tool_calls] || msg["tool_calls"]
158
- if tool_calls.is_a?(Array) && tool_calls.any?
159
- next true if tool_calls.any? do |tc|
160
- name = tc.dig(:function, :name) || tc.dig("function", "name") || tc[:name] || tc["name"]
161
- %w[write edit].include?(name.to_s)
162
- end
163
- end
164
-
165
- # … and Anthropic-style content blocks with type=tool_use.
166
- content = msg[:content] || msg["content"]
167
- if content.is_a?(Array)
168
- next true if content.any? do |block|
169
- block.is_a?(Hash) &&
170
- (block[:type] == "tool_use" || block["type"] == "tool_use") &&
171
- %w[write edit].include?((block[:name] || block["name"]).to_s)
172
- end
173
- end
174
-
175
- false
176
- end
177
- rescue StandardError
178
- # Defensive: never let introspection errors break memory update.
179
- false
180
- end
181
-
182
140
  # Build the memory update prompt for the forked subagent.
183
141
  #
184
142
  # Architecture:
@@ -392,7 +392,9 @@ module Clacky
392
392
  }
393
393
  all_files = image_files + disk_files
394
394
  ui.show_user_message(raw_text, created_at: msg[:created_at], files: all_files,
395
- editable: round[:editable] != false)
395
+ editable: round[:editable] != false,
396
+ skill_command: msg[:skill_command],
397
+ skill_command_display: msg[:skill_command_display])
396
398
  replay_ext_events(msg, ui)
397
399
 
398
400
  round[:events].each do |ev|
@@ -740,16 +742,17 @@ module Clacky
740
742
  args_raw = tc[:arguments] || tc.dig(:function, :arguments) || {}
741
743
  args = args_raw.is_a?(String) ? (JSON.parse(args_raw) rescue args_raw) : args_raw
742
744
 
743
- # Special handling: request_user_feedback question is shown as an
744
- # assistant message (matching real-time behavior), not as a tool call.
745
- # Reconstruct the full formatted message including options (mirrors RequestUserFeedback#execute).
746
- if name == "request_user_feedback"
747
- question = args.is_a?(Hash) ? (args[:question] || args["question"]).to_s : ""
748
- context = args.is_a?(Hash) ? (args[:context] || args["context"]).to_s : ""
749
- options = args.is_a?(Hash) ? (args[:options] || args["options"]) : nil
750
- options = Array(options) if options && !options.is_a?(Array)
745
+ # Special handling: the ask_user question is shown as an assistant
746
+ # message (matching real-time behavior), not as a tool call.
747
+ if Clacky::Tools::AskUser.feedback_tool?(name)
748
+ questions = Clacky::Tools::AskUser.normalize_questions(args)
749
+ context = Clacky::Tools::AskUser.fetch_key(args, :context).to_s
750
+ first = questions.first
751
751
 
752
- ui.show_feedback_request(question, context, options || []) unless question.empty?
752
+ unless first.nil?
753
+ ui.show_feedback_request(first[:question], context, first[:options],
754
+ questions: questions)
755
+ end
753
756
  else
754
757
  ui.show_tool_call(name, args)
755
758
  end
@@ -794,14 +797,23 @@ module Clacky
794
797
  end
795
798
  end
796
799
 
797
- # Replay a subagent transcript stored on a tool result message. Emits a
800
+ # Replay subagent transcripts stored on a tool result message. Emits a
798
801
  # bracketed sequence of UI events the frontend can render as a collapsible
799
802
  # sub-process block: subagent_start → (assistant_message / tool_call /
800
- # tool_result)* → subagent_end. No-op when the message carries no transcript.
803
+ # tool_result)* → subagent_end. A fan-out batch renders one block per job,
804
+ # in the caller's original job order.
805
+ #
806
+ # Sessions saved before fan-out support stored a bare Hash here.
801
807
  def replay_subagent_transcript(msg, ui)
802
- transcript = msg[:subagent_transcript]
803
- return unless transcript.is_a?(Hash)
808
+ stored = msg[:subagent_transcript]
809
+ Array(stored.is_a?(Hash) ? [stored] : stored).each do |transcript|
810
+ next unless transcript.is_a?(Hash)
811
+
812
+ replay_one_subagent_transcript(transcript, ui)
813
+ end
814
+ end
804
815
 
816
+ def replay_one_subagent_transcript(transcript, ui)
805
817
  events = transcript[:events] || transcript["events"] || []
806
818
  return if events.empty?
807
819
 
@@ -22,8 +22,10 @@ module Clacky
22
22
 
23
23
  # Fork an isolated subagent to evaluate + create — does NOT touch main history
24
24
  subagent = fork_subagent
25
- subagent.run(build_skill_creation_prompt)
26
- nil
25
+ result = subagent.run(build_skill_creation_prompt)
26
+ cost = absorb_subagent_cost(result)
27
+
28
+ "$#{cost.round(4)}"
27
29
  end
28
30
 
29
31
  # Determine if this task is a candidate for skill auto-creation
@@ -84,19 +86,20 @@ module Clacky
84
86
  ## Action
85
87
 
86
88
  If **ALL** criteria are met:
87
- → Call invoke_skill with:
89
+ → Call invoke_skill with EXACTLY these two parameters:
88
90
  - skill_name: "skill-creator"
89
- - task: A clear description of what to automate and how (be specific)
90
- - mode: "quick" (enables fast auto-creation without user interviews)
91
- - suggested_name: A descriptive identifier (lowercase, hyphens OK)
91
+ - task: A clear description of what to automate and how (be specific).
92
+ Start it with "Quick mode:" and state the suggested skill name
93
+ (lowercase, hyphens OK) inside this text.
94
+
95
+ invoke_skill accepts ONLY skill_name and task. Passing any other
96
+ parameter raises an ArgumentError and the creation fails.
92
97
 
93
98
  Example invocation:
94
99
  ```
95
100
  invoke_skill(
96
101
  skill_name: "skill-creator",
97
- task: "Create a skill to extract and summarize content from URLs. The skill should: 1) fetch the URL content, 2) parse the main text, 3) generate a concise summary. Expected input: URL. Expected output: markdown summary.",
98
- mode: "quick",
99
- suggested_name: "url-summarizer"
102
+ task: "Quick mode: create a skill named 'url-summarizer' (no user interviews). It should extract and summarize content from URLs: 1) fetch the URL content, 2) parse the main text, 3) generate a concise summary. Expected input: URL. Expected output: markdown summary."
100
103
  )
101
104
  ```
102
105
 
@@ -26,11 +26,12 @@ module Clacky
26
26
  def run_skill_evolution_hooks
27
27
  return unless skill_evolution_enabled?
28
28
  return if @is_subagent
29
+ # Preconditions unmet (short task, default skill, ...): the evolution
30
+ # never ran, so there is nothing to report — stay completely silent.
31
+ return unless skill_evolution_has_work?
29
32
 
30
33
  with_skill_evolution_phase do
31
- if !skill_evolution_has_work?
32
- Clacky::I18n.t("phase.skipped")
33
- elsif @skill_execution_context
34
+ if @skill_execution_context
34
35
  maybe_reflect_on_skill
35
36
  else
36
37
  maybe_create_skill_from_task
@@ -82,7 +82,7 @@ module Clacky
82
82
  return { matched: true, found: false, skill_name: skill_name, reason: :not_user_invocable, skill: skill }
83
83
  end
84
84
 
85
- if @agent_profile && !skill.allowed_for_agent?(@agent_profile.name)
85
+ if @agent_profile && !@agent_profile.skill_allowed?(skill)
86
86
  return { matched: true, found: false, skill_name: skill_name, reason: :agent_not_allowed, skill: skill }
87
87
  end
88
88
 
@@ -266,22 +266,20 @@ module Clacky
266
266
  # is injected instructing the LLM to inform the user in their own language and
267
267
  # suggest similar skills — no error is raised, the LLM handles the reply.
268
268
  #
269
- # @param user_input [String] Raw user input
269
+ # @param skill_command [Hash] Result of parse_skill_command for the user input
270
270
  # @param task_id [Integer] Current task ID (for message tagging)
271
271
  # @return [void]
272
- def inject_skill_command_as_assistant_message(user_input, task_id)
273
- result = parse_skill_command(user_input)
274
-
272
+ def inject_skill_command_as_assistant_message(skill_command, task_id)
275
273
  # Not a slash command at all — nothing to do
276
- return unless result[:matched]
274
+ return unless skill_command[:matched]
277
275
 
278
- skill_name = result[:skill_name]
276
+ skill_name = skill_command[:skill_name]
279
277
 
280
278
  # Slash command recognised but skill could not be dispatched — inject an
281
279
  # LLM-facing notice so the model explains the situation to the user in
282
280
  # their own language instead of silently ignoring the command.
283
- unless result[:found]
284
- notice = case result[:reason]
281
+ unless skill_command[:found]
282
+ notice = case skill_command[:reason]
285
283
  when :not_found
286
284
  suggestions = suggest_similar_skills(skill_name)
287
285
  msg = "[SYSTEM] The user entered the slash command /#{skill_name} but no matching skill was found. " \
@@ -302,8 +300,8 @@ module Clacky
302
300
  return
303
301
  end
304
302
 
305
- skill = result[:skill]
306
- arguments = result[:arguments]
303
+ skill = skill_command[:skill]
304
+ arguments = skill_command[:arguments]
307
305
 
308
306
  # fork_agent skills run in an isolated subagent
309
307
  if skill.fork_agent?
@@ -443,7 +441,7 @@ module Clacky
443
441
  def filter_skills_by_profile(skills)
444
442
  return skills unless @agent_profile
445
443
 
446
- skills.select { |skill| skill.allowed_for_agent?(@agent_profile.name) }
444
+ skills.select { |skill| @agent_profile.skill_allowed?(skill) }
447
445
  end
448
446
 
449
447
  # Build template context for skill content expansion.
@@ -653,8 +651,7 @@ module Clacky
653
651
  # observe() attaches each trail to its own tool result message.
654
652
  # Kept out of the LLM payload via INTERNAL_FIELDS.
655
653
  if tool_call_id
656
- @pending_subagent_transcripts[tool_call_id] =
657
- extract_subagent_transcript(subagent, skill.identifier)
654
+ record_subagent_transcript(tool_call_id, subagent, skill.identifier)
658
655
  end
659
656
 
660
657
  # Mutate the subagent_instructions message in-place to become the result summary
@@ -26,11 +26,11 @@ module Clacky
26
26
  subagent = fork_subagent
27
27
  result = subagent.run(build_skill_reflection_prompt(skill_name))
28
28
 
29
- cost = absorb_subagent_cost(result) if result
29
+ cost = absorb_subagent_cost(result)
30
30
 
31
31
  @skill_execution_context = nil
32
32
 
33
- "#{skill_name} · $#{cost.round(4)}" if cost
33
+ "#{skill_name} · $#{cost.round(4)}"
34
34
  end
35
35
 
36
36
  private def should_reflect_on_skill?
@@ -13,7 +13,7 @@ module Clacky
13
13
  case @config.permission_mode
14
14
  when :auto_approve, :confirm_all
15
15
  # Both modes auto-execute all file/shell tools without confirmation.
16
- # The difference is only in request_user_feedback handling:
16
+ # The difference is only in ask_user handling:
17
17
  # auto_approve → no human present, inject auto_reply
18
18
  # confirm_all → human present, truly wait for user input
19
19
  true
@@ -55,10 +55,12 @@ module Clacky
55
55
  # todo_manager aliases
56
56
  "todo" => "todo_manager",
57
57
  "task_manager" => "todo_manager",
58
- # request_user_feedback aliases
59
- "ask_user" => "request_user_feedback",
60
- "user_feedback" => "request_user_feedback",
61
- "ask" => "request_user_feedback",
58
+ # ask_user aliases
59
+ "request_user_feedback" => "ask_user",
60
+ "user_feedback" => "ask_user",
61
+ "ask" => "ask_user",
62
+ "ask_question" => "ask_user",
63
+ "clarify" => "ask_user",
62
64
  # trash_manager aliases
63
65
  "trash" => "trash_manager",
64
66
  "delete" => "trash_manager",