openclacky 1.5.16 → 1.5.17
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 +39 -0
- data/README.md +1 -1
- data/README_CN.md +1 -1
- data/README_JA.md +1 -1
- data/lib/clacky/agent/history_navigation.rb +1 -1
- data/lib/clacky/agent/llm_caller.rb +3 -51
- data/lib/clacky/agent/session_serializer.rb +12 -2
- data/lib/clacky/agent.rb +1 -1
- data/lib/clacky/agent_config.rb +21 -73
- data/lib/clacky/client.rb +18 -11
- data/lib/clacky/default_extensions/advisor/panels/advisor/view.js +1 -1
- data/lib/clacky/default_extensions/computer-use/skills/computer-use/SKILL.md +2 -0
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +13 -19
- data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +2 -0
- data/lib/clacky/default_extensions/meeting/ext.yml +1 -0
- data/lib/clacky/default_extensions/meeting/skills/meeting-summarizer/SKILL.md +2 -0
- data/lib/clacky/default_skills/browser-setup/SKILL.md +2 -0
- data/lib/clacky/default_skills/channel-manager/SKILL.md +2 -0
- data/lib/clacky/default_skills/channel-manager/install_feishu_skills.rb +1 -2
- data/lib/clacky/default_skills/code-explorer/SKILL.md +2 -0
- data/lib/clacky/default_skills/cron-task-creator/SKILL.md +2 -0
- data/lib/clacky/default_skills/deploy/SKILL.md +3 -1
- data/lib/clacky/default_skills/mcp-manager/SKILL.md +2 -0
- data/lib/clacky/default_skills/media-gen/SKILL.md +2 -0
- data/lib/clacky/default_skills/new/SKILL.md +2 -0
- data/lib/clacky/default_skills/onboard/SKILL.md +2 -0
- data/lib/clacky/default_skills/onboard/scripts/install_builtin_skills.rb +1 -2
- data/lib/clacky/default_skills/persist-memory/SKILL.md +2 -0
- data/lib/clacky/default_skills/personal-website/SKILL.md +2 -0
- data/lib/clacky/default_skills/personal-website/publish.rb +6 -10
- data/lib/clacky/default_skills/product-help/SKILL.md +2 -0
- data/lib/clacky/default_skills/recall-memory/SKILL.md +2 -0
- data/lib/clacky/default_skills/search-skills/SKILL.md +2 -0
- data/lib/clacky/default_skills/skill-add/SKILL.md +2 -0
- data/lib/clacky/default_skills/skill-creator/SKILL.md +2 -0
- data/lib/clacky/extension/packager.rb +3 -20
- data/lib/clacky/locales/en.rb +35 -3
- data/lib/clacky/locales/i18n.rb +16 -2
- data/lib/clacky/locales/zh.rb +36 -4
- data/lib/clacky/media/base.rb +2 -2
- data/lib/clacky/platform_http_client.rb +52 -117
- data/lib/clacky/providers.rb +62 -38
- data/lib/clacky/rich_ui/view_renderer.rb +3 -2
- data/lib/clacky/server/channel/adapters/feishu/adapter.rb +53 -1
- data/lib/clacky/server/channel/adapters/feishu/bot.rb +354 -75
- data/lib/clacky/server/channel/adapters/feishu/ws_client.rb +48 -13
- data/lib/clacky/server/channel/channel_config.rb +21 -4
- data/lib/clacky/server/channel/channel_manager.rb +5 -27
- data/lib/clacky/server/channel/channel_ui_controller.rb +65 -34
- data/lib/clacky/server/http_server.rb +59 -19
- data/lib/clacky/server/scheduler.rb +117 -2
- data/lib/clacky/server/session_registry.rb +0 -4
- data/lib/clacky/tools/ask_user.rb +2 -10
- data/lib/clacky/ui2/ui_controller.rb +5 -5
- data/lib/clacky/utils/channel_prompt.rb +33 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +1277 -1344
- data/lib/clacky/web/app.js +10 -2
- data/lib/clacky/web/components/composer.js +130 -6
- data/lib/clacky/web/components/datepicker.js +11 -4
- data/lib/clacky/web/components/model-picker.js +10 -4
- data/lib/clacky/web/components/onboard.js +14 -22
- data/lib/clacky/web/core/ext.js +39 -1
- data/lib/clacky/web/features/billing/view.js +6 -5
- data/lib/clacky/web/features/channels/store.js +16 -1
- data/lib/clacky/web/features/channels/view.js +158 -148
- data/lib/clacky/web/features/extensions/view.js +10 -18
- data/lib/clacky/web/features/mcp/store.js +1 -1
- data/lib/clacky/web/features/mcp/view.js +112 -119
- data/lib/clacky/web/features/new-session/view.js +14 -12
- data/lib/clacky/web/features/profile/view.js +9 -9
- data/lib/clacky/web/features/skills/store.js +16 -8
- data/lib/clacky/web/features/skills/view.js +568 -215
- data/lib/clacky/web/features/tasks/store.js +28 -6
- data/lib/clacky/web/features/tasks/view.js +314 -102
- data/lib/clacky/web/features/trash/view.js +4 -6
- data/lib/clacky/web/features/workspace/view.js +24 -0
- data/lib/clacky/web/i18n.js +94 -32
- data/lib/clacky/web/index.html +157 -95
- data/lib/clacky/web/projects.js +10 -3
- data/lib/clacky/web/sessions.js +150 -44
- data/lib/clacky/web/utils.js +20 -0
- data/lib/clacky/web/ws-dispatcher.js +8 -4
- data/lib/clacky.rb +3 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 23761f95ba9b1fa52c2acc37020fd4e903a92d8f49c0c97668d5cfbb6e572329
|
|
4
|
+
data.tar.gz: 16838f20ba7d650e15952c738c35b93ca418a8df81baaeb6f9fd0263c5754e65
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d0a29e5ab8a0e6b21e1f2a2f36657bcb8043cfc9d904671acdead8157f082ac032c2d1f7f0a0cee16c0f72a7fdbb939020b3abfe52145a9eea722f4f4856bac2
|
|
7
|
+
data.tar.gz: '050389221f4f71ab6e54a6038606f0d1f48f1a6571116c5b052493362158a850fe0f1a90a4d520b35fff875315fd1292610bab104366251626d5d9fdcb5b7f54'
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,45 @@ 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.17] - 2026-09-30
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Show a top-up action when a free-trial model rejects the request
|
|
11
|
+
- Add Requesty as a named provider preset (#587 - @Thibaultjaigu)
|
|
12
|
+
- Redesign the skills page around cards and a detail view
|
|
13
|
+
- Redesign the scheduled tasks page and keep a run history per task
|
|
14
|
+
- Redesign the channels page as a compact two-column list
|
|
15
|
+
- Rework the MCP page layout, its row menu and the remove confirmation
|
|
16
|
+
- Add "Add to chat" to the workspace file context menu
|
|
17
|
+
- Add a global switch for IM progress cards (#599)
|
|
18
|
+
- Show the vendor logo on the model chip of a new session
|
|
19
|
+
- Use Chinese names for the built-in skills
|
|
20
|
+
|
|
21
|
+
### Improved
|
|
22
|
+
- Optimize the Feishu card styling (#597)
|
|
23
|
+
- Improve drag-and-drop for session references and files (#600)
|
|
24
|
+
- Restyle the channel status badge, cron rows, message settings and search boxes
|
|
25
|
+
- Polish the assistant memory page, onboarding page, billing header and trash toolbar
|
|
26
|
+
- Improve the discoverability of overflowing aside tabs (#601)
|
|
27
|
+
- Show skill, MCP and task errors as in-app toasts instead of browser alerts
|
|
28
|
+
- Localize the default extension icon letter
|
|
29
|
+
- Ship meeting as a default-off extension
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- Make an IM message interrupt the running task instead of queueing silently (#586)
|
|
33
|
+
- Hide the sidecar models from the model switchers (#598)
|
|
34
|
+
- Show the "View process" panel in Feishu only when there is process history (#596)
|
|
35
|
+
- Keep a subagent phase running when a message is queued (#594)
|
|
36
|
+
- Fix the skill install click and the extension installed state
|
|
37
|
+
- Strip channel-only prompt prefixes when replaying history (#585)
|
|
38
|
+
- Use the in-app dialog for delete confirmations instead of the browser prompt
|
|
39
|
+
- Toggle a sidebar ··· menu on a repeated click (#595)
|
|
40
|
+
- Hide the auto tag and routing tooltip once the alias is pinned to a model
|
|
41
|
+
|
|
42
|
+
### More
|
|
43
|
+
- Retire Claude 4.5 / Opus 4.6 and Gemini 3.5 / 3.6 Flash from the catalogs
|
|
44
|
+
- Remove legacy fallback domains and dead failover paths
|
|
45
|
+
|
|
7
46
|
## [1.5.16] - 2026-09-24
|
|
8
47
|
|
|
9
48
|
### Added
|
data/README.md
CHANGED
|
@@ -205,7 +205,7 @@ $ openclacky
|
|
|
205
205
|
|
|
206
206
|
Set your **API Key**, **Model**, and **Base URL** (any OpenAI-compatible provider).
|
|
207
207
|
|
|
208
|
-
Supported out of the box: **Claude (Anthropic) · GPT (OpenAI) · DeepSeek · Kimi (Moonshot) · MiniMax · OpenRouter · OrcaRouter** — or any custom endpoint.
|
|
208
|
+
Supported out of the box: **Claude (Anthropic) · GPT (OpenAI) · DeepSeek · Kimi (Moonshot) · MiniMax · OpenRouter · OrcaRouter · Requesty** — or any custom endpoint.
|
|
209
209
|
|
|
210
210
|
## Coding use case
|
|
211
211
|
|
data/README_CN.md
CHANGED
|
@@ -189,7 +189,7 @@ $ openclacky
|
|
|
189
189
|
|
|
190
190
|
设置你的 **API Key**、**模型**和 **Base URL**(任意 OpenAI 兼容提供商)。
|
|
191
191
|
|
|
192
|
-
开箱即支持:**Claude (Anthropic) · GPT (OpenAI) · DeepSeek · Kimi (Moonshot) · MiniMax · OpenRouter · OrcaRouter**,或任意自定义端点。
|
|
192
|
+
开箱即支持:**Claude (Anthropic) · GPT (OpenAI) · DeepSeek · Kimi (Moonshot) · MiniMax · OpenRouter · OrcaRouter · Requesty**,或任意自定义端点。
|
|
193
193
|
|
|
194
194
|
## 代码开发场景
|
|
195
195
|
|
data/README_JA.md
CHANGED
|
@@ -194,7 +194,7 @@ $ openclacky
|
|
|
194
194
|
|
|
195
195
|
**API Key**、**Model**、**Base URL**(任意の OpenAI 互換プロバイダー)を設定します。
|
|
196
196
|
|
|
197
|
-
標準でサポート: **Claude (Anthropic) · GPT (OpenAI) · DeepSeek · Kimi (Moonshot) · MiniMax · OpenRouter · OrcaRouter** — または任意のカスタムエンドポイント。
|
|
197
|
+
標準でサポート: **Claude (Anthropic) · GPT (OpenAI) · DeepSeek · Kimi (Moonshot) · MiniMax · OpenRouter · OrcaRouter · Requesty** — または任意のカスタムエンドポイント。
|
|
198
198
|
|
|
199
199
|
## コーディングのユースケース
|
|
200
200
|
|
|
@@ -208,7 +208,7 @@ module Clacky
|
|
|
208
208
|
|
|
209
209
|
private def navigation_entry(round)
|
|
210
210
|
msg = round[:user_msg]
|
|
211
|
-
user = navigation_preview(msg[:display_text] || extract_text_from_content(msg[:content]))
|
|
211
|
+
user = navigation_preview(msg[:display_text] || strip_channel_prompt_prefix(extract_text_from_content(msg[:content])))
|
|
212
212
|
if user.empty?
|
|
213
213
|
names = Array(msg[:display_files]).map { |file| file[:name] || file["name"] }
|
|
214
214
|
Array(msg[:content]).each do |block|
|
|
@@ -211,21 +211,7 @@ module Clacky
|
|
|
211
211
|
Clacky::Shutdown.sleep(retry_delay)
|
|
212
212
|
retry
|
|
213
213
|
else
|
|
214
|
-
#
|
|
215
|
-
if !@config.url_fallback_active?
|
|
216
|
-
fallback_url = @config.fallback_base_url_for_current_provider
|
|
217
|
-
if fallback_url
|
|
218
|
-
@config.activate_url_fallback!(fallback_url)
|
|
219
|
-
rebuild_client_for_current_model!
|
|
220
|
-
retries = 0
|
|
221
|
-
@ui&.show_warning(
|
|
222
|
-
I18n.t("llm.warn.switching_to_fallback_url", url: fallback_url)
|
|
223
|
-
)
|
|
224
|
-
retry
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
raise AgentError.new("[LLM] #{I18n.t("llm.error.request_timeout", retries: max_retries)}", raw_message: e.message)
|
|
214
|
+
raise AgentError.new("#{I18n.t("llm.error.request_timeout", retries: max_retries)}", raw_message: e.message)
|
|
229
215
|
end
|
|
230
216
|
|
|
231
217
|
rescue Faraday::ConnectionFailed, Faraday::SSLError, Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::EPIPE => e
|
|
@@ -259,24 +245,9 @@ module Clacky
|
|
|
259
245
|
Clacky::Shutdown.sleep(retry_delay)
|
|
260
246
|
retry
|
|
261
247
|
else
|
|
262
|
-
# All retries on the primary endpoint exhausted for a network-level
|
|
263
|
-
# failure (DNS/TCP/TLS). Try the secondary gateway URL if available.
|
|
264
|
-
if !@config.url_fallback_active?
|
|
265
|
-
fallback_url = @config.fallback_base_url_for_current_provider
|
|
266
|
-
if fallback_url
|
|
267
|
-
@config.activate_url_fallback!(fallback_url)
|
|
268
|
-
rebuild_client_for_current_model!
|
|
269
|
-
retries = 0
|
|
270
|
-
@ui&.show_warning(
|
|
271
|
-
I18n.t("llm.warn.switching_to_fallback_url", url: fallback_url)
|
|
272
|
-
)
|
|
273
|
-
retry
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
|
|
277
248
|
# Don't show_error here — let the outer rescue block handle it to avoid duplicates.
|
|
278
249
|
# Progress cleanup is the caller's responsibility (via its own ensure block).
|
|
279
|
-
raise AgentError.new("
|
|
250
|
+
raise AgentError.new("#{I18n.t("llm.error.network_failed", retries: max_retries)}", raw_message: e.message)
|
|
280
251
|
end
|
|
281
252
|
|
|
282
253
|
rescue RetryableError => e
|
|
@@ -318,28 +289,9 @@ module Clacky
|
|
|
318
289
|
Clacky::Shutdown.sleep(retry_delay)
|
|
319
290
|
retry
|
|
320
291
|
else
|
|
321
|
-
# All retries on the current endpoint exhausted.
|
|
322
|
-
# If a secondary gateway URL is configured and not yet tried, switch
|
|
323
|
-
# to it now, rebuild the client, and reset the retry counter so the
|
|
324
|
-
# same request gets a fresh MAX_RETRIES_ON_FALLBACK budget on the
|
|
325
|
-
# new endpoint. This is a one-shot switch — url_fallback_active?
|
|
326
|
-
# prevents a second switch if the fallback URL also fails.
|
|
327
|
-
if !@config.url_fallback_active?
|
|
328
|
-
fallback_url = @config.fallback_base_url_for_current_provider
|
|
329
|
-
if fallback_url
|
|
330
|
-
@config.activate_url_fallback!(fallback_url)
|
|
331
|
-
rebuild_client_for_current_model!
|
|
332
|
-
retries = 0
|
|
333
|
-
@ui&.show_warning(
|
|
334
|
-
I18n.t("llm.warn.switching_to_fallback_url", url: fallback_url)
|
|
335
|
-
)
|
|
336
|
-
retry
|
|
337
|
-
end
|
|
338
|
-
end
|
|
339
|
-
|
|
340
292
|
# Don't show_error here — let the outer rescue block handle it to avoid duplicates.
|
|
341
293
|
# Progress cleanup is the caller's responsibility (via its own ensure block).
|
|
342
|
-
raise AgentError.new("
|
|
294
|
+
raise AgentError.new("#{I18n.t("llm.error.service_unavailable", retries: current_max)}", raw_message: e.message)
|
|
343
295
|
end
|
|
344
296
|
|
|
345
297
|
rescue Clacky::BadRequestError => e
|
|
@@ -332,10 +332,20 @@ module Clacky
|
|
|
332
332
|
{ has_more: has_more }
|
|
333
333
|
end
|
|
334
334
|
|
|
335
|
+
# ChannelManager prefixes channel prompts with agent-only context for
|
|
336
|
+
# the LLM; the clean original rides in display_text. Replay paths that
|
|
337
|
+
# fall back to raw content (pre-display_text sessions, chunk MD archives)
|
|
338
|
+
# must strip that block for UI parity with live rendering. Gated on
|
|
339
|
+
# channel_info so a normal user literally typing these lines keeps them.
|
|
340
|
+
private def strip_channel_prompt_prefix(text)
|
|
341
|
+
return text unless @channel_info
|
|
342
|
+
Clacky::Utils::ChannelPrompt.strip(text)
|
|
343
|
+
end
|
|
344
|
+
|
|
335
345
|
private def replay_rounds(ui, page)
|
|
336
346
|
page.each do |round|
|
|
337
347
|
msg = round[:user_msg]
|
|
338
|
-
raw_text = msg[:display_text] || extract_text_from_content(msg[:content])
|
|
348
|
+
raw_text = msg[:display_text] || strip_channel_prompt_prefix(extract_text_from_content(msg[:content]))
|
|
339
349
|
# Images: recovered from inline image_url blocks in content (carry data_url for <img> rendering)
|
|
340
350
|
image_files = extract_image_files_from_content(msg[:content])
|
|
341
351
|
# Disk files (PDF, doc, etc.): stored in display_files on the user message at send time
|
|
@@ -507,7 +517,7 @@ module Clacky
|
|
|
507
517
|
synthetic_ts = base_time - ((index ? index[:section_count] : sections.size) - round_offset - round_index) * 1.0
|
|
508
518
|
user_msg = {
|
|
509
519
|
role: "user",
|
|
510
|
-
content: text,
|
|
520
|
+
content: strip_channel_prompt_prefix(text),
|
|
511
521
|
created_at: synthetic_ts,
|
|
512
522
|
ext_events: sec_ext_events,
|
|
513
523
|
_from_chunk: true
|
data/lib/clacky/agent.rb
CHANGED
|
@@ -238,7 +238,7 @@ module Clacky
|
|
|
238
238
|
entry = @config.current_model
|
|
239
239
|
@client = Clacky::Client.new(
|
|
240
240
|
@config.api_key,
|
|
241
|
-
base_url: @config.
|
|
241
|
+
base_url: @config.base_url,
|
|
242
242
|
model: @config.model_name,
|
|
243
243
|
anthropic_format: @config.anthropic_format?,
|
|
244
244
|
api_format: @config.api_format,
|
data/lib/clacky/agent_config.rb
CHANGED
|
@@ -523,13 +523,7 @@ module Clacky
|
|
|
523
523
|
@current_model_id = id
|
|
524
524
|
@current_model_index = index
|
|
525
525
|
|
|
526
|
-
if previous_id != id
|
|
527
|
-
@session_model_overlay = nil
|
|
528
|
-
# Reset URL fallback when switching models — the new model has its own
|
|
529
|
-
# base_url and the old fallback endpoint is irrelevant (or even wrong).
|
|
530
|
-
@url_fallback_active = false
|
|
531
|
-
@url_fallback_base_url = nil
|
|
532
|
-
end
|
|
526
|
+
@session_model_overlay = nil if previous_id != id
|
|
533
527
|
|
|
534
528
|
true
|
|
535
529
|
end
|
|
@@ -545,16 +539,9 @@ module Clacky
|
|
|
545
539
|
index = @models.find_index { |m| m["model"].to_s.downcase == name_str }
|
|
546
540
|
return false if index.nil?
|
|
547
541
|
|
|
548
|
-
previous_id = @current_model_id
|
|
549
542
|
@current_model_id = @models[index]["id"]
|
|
550
543
|
@current_model_index = index
|
|
551
544
|
|
|
552
|
-
if previous_id != @current_model_id
|
|
553
|
-
# Reset URL fallback when switching models — same rationale as switch_model_by_id.
|
|
554
|
-
@url_fallback_active = false
|
|
555
|
-
@url_fallback_base_url = nil
|
|
556
|
-
end
|
|
557
|
-
|
|
558
545
|
true
|
|
559
546
|
end
|
|
560
547
|
|
|
@@ -601,9 +588,27 @@ module Clacky
|
|
|
601
588
|
true
|
|
602
589
|
end
|
|
603
590
|
|
|
604
|
-
#
|
|
591
|
+
# Model entries the user can actually chat with.
|
|
592
|
+
#
|
|
593
|
+
# `type` doubles as a record discriminator: `default`/`lite` tag a chat
|
|
594
|
+
# model's role, while the media/ocr kinds mark sidecar capability configs
|
|
595
|
+
# that only the dedicated media UI should surface. Every model switcher
|
|
596
|
+
# (CLI, IM, Web UI) must go through here so the rule lives in one place.
|
|
597
|
+
def chat_models
|
|
598
|
+
@models.select { |m| self.class.chat_model?(m) }
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
# @param entry [Hash, nil]
|
|
602
|
+
def self.chat_model?(entry)
|
|
603
|
+
return false unless entry
|
|
604
|
+
return false if entry["auto_injected"]
|
|
605
|
+
|
|
606
|
+
!Clacky::Providers::SIDECAR_KINDS.include?(entry["type"].to_s)
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# List all chat model names
|
|
605
610
|
def model_names
|
|
606
|
-
|
|
611
|
+
chat_models.map { |m| m["model"] }
|
|
607
612
|
end
|
|
608
613
|
|
|
609
614
|
# Get API key for current model
|
|
@@ -1138,63 +1143,6 @@ module Clacky
|
|
|
1138
1143
|
end
|
|
1139
1144
|
end
|
|
1140
1145
|
|
|
1141
|
-
# ── URL-level fallback ─────────────────────────────────────────────────
|
|
1142
|
-
# Independent from the model-name fallback above. When all max_retries
|
|
1143
|
-
# on the primary endpoint are exhausted, the caller may switch to a
|
|
1144
|
-
# secondary gateway URL (same model, different host) via these methods.
|
|
1145
|
-
# The URL fallback is intentionally one-shot per session — we do not
|
|
1146
|
-
# probe or reset it automatically, keeping the logic simple.
|
|
1147
|
-
|
|
1148
|
-
# Look up the fallback base URL for the current model's provider.
|
|
1149
|
-
# Returns nil if:
|
|
1150
|
-
# - provider cannot be determined
|
|
1151
|
-
# - provider is not openclacky (URL failover is only supported for the
|
|
1152
|
-
# OpenClacky gateway; other providers have no secondary endpoint)
|
|
1153
|
-
# - current base_url is already the fallback URL (user picked the
|
|
1154
|
-
# Secondary/China node directly — switching to the same URL does nothing)
|
|
1155
|
-
# @return [String, nil]
|
|
1156
|
-
def fallback_base_url_for_current_provider
|
|
1157
|
-
m = current_model
|
|
1158
|
-
return nil unless m
|
|
1159
|
-
|
|
1160
|
-
# Use strict URL matching to identify the provider — no inference.
|
|
1161
|
-
# resolve_provider would incorrectly return "openclacky" for localhost
|
|
1162
|
-
# addresses and clacky-* api keys, triggering unwanted failover.
|
|
1163
|
-
# find_by_base_url only matches registered preset URLs, so localhost
|
|
1164
|
-
# and other non-openclacky endpoints correctly return nil.
|
|
1165
|
-
provider_id = Clacky::Providers.find_by_base_url(m["base_url"])
|
|
1166
|
-
return nil unless provider_id == Clacky::Providers::OPENCLACKY_ID
|
|
1167
|
-
|
|
1168
|
-
fallback_url = Clacky::Providers.fallback_base_url(provider_id)
|
|
1169
|
-
return nil unless fallback_url
|
|
1170
|
-
|
|
1171
|
-
# Don't offer a fallback when the current base_url is already the
|
|
1172
|
-
# fallback endpoint — switching to the same URL accomplishes nothing.
|
|
1173
|
-
current_base = m["base_url"].to_s.chomp("/")
|
|
1174
|
-
return nil if current_base == fallback_url.to_s.chomp("/")
|
|
1175
|
-
|
|
1176
|
-
fallback_url
|
|
1177
|
-
end
|
|
1178
|
-
|
|
1179
|
-
# Activate the URL fallback: override base_url with the secondary gateway.
|
|
1180
|
-
# Idempotent — safe to call multiple times.
|
|
1181
|
-
# @param fallback_url [String] the secondary gateway URL
|
|
1182
|
-
def activate_url_fallback!(fallback_url)
|
|
1183
|
-
@url_fallback_active = true
|
|
1184
|
-
@url_fallback_base_url = fallback_url
|
|
1185
|
-
end
|
|
1186
|
-
|
|
1187
|
-
# Returns true when the secondary gateway URL is in use.
|
|
1188
|
-
def url_fallback_active?
|
|
1189
|
-
@url_fallback_active == true
|
|
1190
|
-
end
|
|
1191
|
-
|
|
1192
|
-
# The effective base URL for API calls.
|
|
1193
|
-
# Returns the URL-fallback override when active, otherwise the model's configured base_url.
|
|
1194
|
-
def effective_base_url
|
|
1195
|
-
@url_fallback_active ? @url_fallback_base_url : base_url
|
|
1196
|
-
end
|
|
1197
|
-
|
|
1198
1146
|
# Get current model configuration.
|
|
1199
1147
|
#
|
|
1200
1148
|
# Resolution order:
|
data/lib/clacky/client.rb
CHANGED
|
@@ -817,7 +817,7 @@ module Clacky
|
|
|
817
817
|
|
|
818
818
|
if error_code == "insufficient_credit" || response.status == 402
|
|
819
819
|
raise InsufficientCreditError.new(
|
|
820
|
-
"
|
|
820
|
+
"#{I18n.t("llm.error.insufficient_credit")}",
|
|
821
821
|
error_code: "insufficient_credit",
|
|
822
822
|
provider_id: @provider_id,
|
|
823
823
|
raw_message: error_message
|
|
@@ -827,30 +827,37 @@ module Clacky
|
|
|
827
827
|
case response.status
|
|
828
828
|
when 400
|
|
829
829
|
if error_message.match?(/ThrottlingException|unavailable|quota/i)
|
|
830
|
-
raise RetryableError.new("
|
|
830
|
+
raise RetryableError.new("#{I18n.t("llm.error.rate_limit_400")}", routed_tier: routed_tier)
|
|
831
831
|
end
|
|
832
832
|
|
|
833
833
|
raise BadRequestError.new(
|
|
834
834
|
"[LLM] Client request error: #{error_message}",
|
|
835
|
-
display_message: "
|
|
835
|
+
display_message: "#{I18n.t("llm.error.bad_request")}",
|
|
836
836
|
raw_message: error_message
|
|
837
837
|
)
|
|
838
838
|
when 401
|
|
839
|
-
raise AgentError.new("
|
|
839
|
+
raise AgentError.new("#{I18n.t("llm.error.invalid_api_key")}", raw_message: error_message)
|
|
840
840
|
when 403
|
|
841
841
|
i18n_key = "llm.error.403.#{error_code}"
|
|
842
842
|
translated = I18n.t(i18n_key)
|
|
843
843
|
translated = I18n.t("llm.error.403.default") if translated == i18n_key
|
|
844
|
-
raise AgentError.new(
|
|
844
|
+
raise AgentError.new(translated, raw_message: error_message) unless error_code == "model_not_allowed"
|
|
845
|
+
|
|
846
|
+
raise ModelNotAllowedError.new(
|
|
847
|
+
translated,
|
|
848
|
+
error_code: error_code,
|
|
849
|
+
provider_id: @provider_id,
|
|
850
|
+
raw_message: error_message
|
|
851
|
+
)
|
|
845
852
|
when 404
|
|
846
|
-
raise AgentError.new("
|
|
853
|
+
raise AgentError.new("#{I18n.t("llm.error.endpoint_not_found")}", raw_message: error_message)
|
|
847
854
|
when 429
|
|
848
855
|
if error_code == "quota_exceeded"
|
|
849
|
-
raise AgentError.new("
|
|
856
|
+
raise AgentError.new("#{I18n.t("llm.error.quota_exhausted")}", raw_message: error_message)
|
|
850
857
|
end
|
|
851
|
-
raise RetryableError.new("
|
|
852
|
-
when 500..599 then raise RetryableError.new("
|
|
853
|
-
else raise AgentError.new("
|
|
858
|
+
raise RetryableError.new("#{I18n.t("llm.error.rate_limit_429")}", routed_tier: routed_tier)
|
|
859
|
+
when 500..599 then raise RetryableError.new("#{I18n.t("llm.error.server_error", status: response.status)}", routed_tier: routed_tier)
|
|
860
|
+
else raise AgentError.new("#{I18n.t("llm.error.unexpected", status: response.status)}", raw_message: error_message)
|
|
854
861
|
end
|
|
855
862
|
end
|
|
856
863
|
|
|
@@ -858,7 +865,7 @@ module Clacky
|
|
|
858
865
|
def check_html_response(response)
|
|
859
866
|
body = response.body.to_s.lstrip
|
|
860
867
|
if body.start_with?("<!DOCTYPE", "<!doctype", "<html", "<HTML")
|
|
861
|
-
raise RetryableError, "
|
|
868
|
+
raise RetryableError, "#{I18n.t("llm.error.html_response")}"
|
|
862
869
|
end
|
|
863
870
|
end
|
|
864
871
|
|
|
@@ -366,7 +366,7 @@
|
|
|
366
366
|
|
|
367
367
|
function settingsTab() {
|
|
368
368
|
const btn = document.createElement("button");
|
|
369
|
-
btn.className = "settings-tab";
|
|
369
|
+
btn.className = "settings-tab page-tab";
|
|
370
370
|
btn.dataset.tab = "advisor";
|
|
371
371
|
btn.textContent = t("advisor.settingsTab", "工作建议");
|
|
372
372
|
return btn;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: computer-use
|
|
3
|
+
name_zh: 电脑操控
|
|
4
|
+
description_zh: 操控桌面上的原生应用、弹窗、菜单栏和文件选择器,处理终端和浏览器够不着的操作,支持 macOS 和 WSL 下的 Windows。
|
|
3
5
|
description: Drive the desktop — macOS, or the Windows desktop through WSL — when a task needs a native app, dialog, menu bar, file picker or anything the terminal and browser tools cannot reach. Screenshot first, then click and type by coordinates read from that image.
|
|
4
6
|
---
|
|
5
7
|
|
|
@@ -1643,7 +1643,11 @@
|
|
|
1643
1643
|
}
|
|
1644
1644
|
|
|
1645
1645
|
function _skeletonHtml() {
|
|
1646
|
-
|
|
1646
|
+
const bar = `
|
|
1647
|
+
<div class="studio-seg-bar" style="pointer-events:none">
|
|
1648
|
+
<span class="skel" style="display:inline-block;height:2rem;width:12rem;border-radius:8px"></span>
|
|
1649
|
+
</div>`;
|
|
1650
|
+
return bar + Array.from({ length: 3 }).map(() => `
|
|
1647
1651
|
<div class="studio-skill-card" style="pointer-events:none">
|
|
1648
1652
|
<div class="studio-skill-head">
|
|
1649
1653
|
<span class="skel" style="display:inline-block;height:1rem;width:40%"></span>
|
|
@@ -2078,16 +2082,10 @@
|
|
|
2078
2082
|
headerActions.appendChild(newBtn);
|
|
2079
2083
|
}
|
|
2080
2084
|
|
|
2081
|
-
if (locked) {
|
|
2082
|
-
const lockHint = el("p", { class: "studio-empty", text: t("skills.locked") });
|
|
2083
|
-
lockHint.style.marginBottom = "12px";
|
|
2084
|
-
container.appendChild(lockHint);
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
2085
|
const sections = [{
|
|
2088
2086
|
id: "local",
|
|
2089
2087
|
label: t("skills.section.local"),
|
|
2090
|
-
hint: t("skills.section.localHint"),
|
|
2088
|
+
hint: locked ? t("skills.locked") : t("skills.section.localHint"),
|
|
2091
2089
|
items: local,
|
|
2092
2090
|
cardFn: localCard,
|
|
2093
2091
|
emptyKey: "skills.local.empty",
|
|
@@ -2122,12 +2120,12 @@
|
|
|
2122
2120
|
root.appendChild(page);
|
|
2123
2121
|
|
|
2124
2122
|
const header = el("div", { class: "studio-page-head" });
|
|
2125
|
-
header.appendChild(el("h2", { class: "studio-page-title", text: t("ws.title") }));
|
|
2123
|
+
header.appendChild(el("h2", { class: "studio-page-title page-title", text: t("ws.title") }));
|
|
2126
2124
|
const headerActions = el("div", { class: "studio-page-head-actions" });
|
|
2127
2125
|
header.appendChild(headerActions);
|
|
2128
2126
|
page.appendChild(header);
|
|
2129
2127
|
|
|
2130
|
-
const tabsBar = el("div", { class: "studio-tabs" });
|
|
2128
|
+
const tabsBar = el("div", { class: "studio-tabs page-tabs" });
|
|
2131
2129
|
const body = el("div", { class: "studio-tab-body" });
|
|
2132
2130
|
page.appendChild(tabsBar);
|
|
2133
2131
|
page.appendChild(body);
|
|
@@ -2140,7 +2138,7 @@
|
|
|
2140
2138
|
|
|
2141
2139
|
function select(id) {
|
|
2142
2140
|
active = id;
|
|
2143
|
-
Array.from(tabsBar.children).forEach((b) => b.classList.toggle("
|
|
2141
|
+
Array.from(tabsBar.children).forEach((b) => b.classList.toggle("active", b.dataset.tab === id));
|
|
2144
2142
|
body.innerHTML = "";
|
|
2145
2143
|
headerActions.innerHTML = "";
|
|
2146
2144
|
const tab = tabs.find((t2) => t2.id === id);
|
|
@@ -2148,7 +2146,7 @@
|
|
|
2148
2146
|
}
|
|
2149
2147
|
|
|
2150
2148
|
tabs.forEach((tab) => {
|
|
2151
|
-
const btn = el("button", { class: "studio-tab", text: tab.label });
|
|
2149
|
+
const btn = el("button", { class: "studio-tab page-tab", text: tab.label });
|
|
2152
2150
|
btn.dataset.tab = tab.id;
|
|
2153
2151
|
btn.addEventListener("click", () => select(tab.id));
|
|
2154
2152
|
tabsBar.appendChild(btn);
|
|
@@ -2282,16 +2280,12 @@
|
|
|
2282
2280
|
.studio-published-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 0; font-size: 12px; border-bottom: 1px solid var(--color-border-primary); }
|
|
2283
2281
|
.studio-published-name { display: flex; align-items: center; gap: 6px; color: var(--color-text-primary); }
|
|
2284
2282
|
.studio-page { width: 100%; }
|
|
2285
|
-
.studio-page-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
|
|
2283
|
+
.studio-page-head { display: flex; align-items: center; justify-content: space-between; min-height: 36px; margin-bottom: 20px; }
|
|
2286
2284
|
.studio-page-head-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
|
|
2287
|
-
.studio-page-title { margin: 0; font-size: 22px; font-weight: 600; color: var(--color-text-primary); }
|
|
2288
2285
|
.studio-btn-sm { padding: 5px 12px; font-size: 12px; }
|
|
2289
|
-
.studio-seg-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
|
|
2286
|
+
.studio-seg-bar { display: flex; align-items: center; gap: 12px; min-height: 36px; margin-bottom: 14px; }
|
|
2290
2287
|
.studio-seg-hint { font-size: 11px; color: var(--color-text-muted); }
|
|
2291
|
-
.studio-tabs {
|
|
2292
|
-
.studio-tab { padding: 8px 16px; border: none; background: transparent; color: var(--color-text-tertiary); cursor: pointer; font-size: 14px; font-weight: 500; border-bottom: 2px solid transparent; margin-bottom: -1px; }
|
|
2293
|
-
.studio-tab:hover { color: var(--color-text-primary); }
|
|
2294
|
-
.studio-tab-active { color: var(--color-text-primary); border-bottom-color: var(--color-accent-primary); }
|
|
2288
|
+
.studio-tabs { margin-bottom: 24px; }
|
|
2295
2289
|
.studio-ext-block { margin-bottom: 20px; }
|
|
2296
2290
|
.studio-block-title { font-size: 13px; font-weight: 600; color: var(--color-text-secondary); padding: 8px 0 0; }
|
|
2297
2291
|
.studio-skill-card { border: 1px solid var(--color-border-primary); border-radius: var(--radius-md, 8px); padding: 12px; margin: 0 0 10px; background: var(--color-bg-secondary); }
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
# After context compression, previously inspected host files may be
|
|
4
4
|
# mistaken for extension edit targets.
|
|
5
5
|
name: ext-develop
|
|
6
|
+
name_zh: 扩展开发
|
|
7
|
+
description_zh: 开发、调试和发布扩展:从想法搭出新扩展,修复加载失败或不显示的问题,或发布到扩展市场。
|
|
6
8
|
description: Build, debug, or publish an OpenClacky extension — scaffold a new one from an idea, fix a broken/invisible panel/api/skill/agent, or ship it to the marketplace. Trigger on create/start extension, plugin, panel, ext verify error, "won't load", "not showing up", publish/ship/unpublish an extension.
|
|
7
9
|
agent: ext-developer
|
|
8
10
|
---
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: meeting-summarizer
|
|
3
|
+
name_zh: 会议纪要
|
|
4
|
+
description_zh: 根据会议转写内容生成结构化纪要,包含关键决策、待办事项和讨论要点,会议结束后自动触发。
|
|
3
5
|
description: Summarize a completed meeting from its transcript. Produces a structured summary with key decisions, action items, and discussion highlights. Triggered automatically when a meeting ends.
|
|
4
6
|
user-invocable: false
|
|
5
7
|
auto_summarize: false
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: browser-setup
|
|
3
|
+
name_zh: 浏览器配置
|
|
4
|
+
description_zh: 配置 Chrome 或 Edge 浏览器工具并检测连接,支持 macOS、Linux 和 WSL。
|
|
3
5
|
description: |
|
|
4
6
|
Configure the browser tool for Clacky. Guides the user through Chrome or Edge setup,
|
|
5
7
|
verifies the connection, and writes ~/.clacky/browser.yml.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: channel-manager
|
|
3
|
+
name_zh: 渠道管理
|
|
4
|
+
description_zh: 接入飞书、企业微信、微信、钉钉、Discord、Telegram 等 IM 渠道,支持配置、启停、诊断和发消息。
|
|
3
5
|
description: |
|
|
4
6
|
Configure IM platform channels (Feishu, WeCom, Weixin, Discord, Telegram, DingTalk) for openclacky.
|
|
5
7
|
Uses browser automation for navigation; guides the user to paste credentials and perform UI steps.
|
|
@@ -23,8 +23,7 @@ require_relative '../skill-add/scripts/install_from_zip'
|
|
|
23
23
|
|
|
24
24
|
class FeishuSkillsInstaller
|
|
25
25
|
PRIMARY_HOST = ENV.fetch('CLACKY_LICENSE_SERVER', 'https://www.openclacky.com')
|
|
26
|
-
|
|
27
|
-
API_HOSTS = ENV['CLACKY_LICENSE_SERVER'] ? [PRIMARY_HOST] : [PRIMARY_HOST, FALLBACK_HOST]
|
|
26
|
+
API_HOSTS = [PRIMARY_HOST]
|
|
28
27
|
API_PATH = '/api/v1/skills/feishu'
|
|
29
28
|
API_OPEN_TIMEOUT = 5
|
|
30
29
|
API_READ_TIMEOUT = 10
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-explorer
|
|
3
|
+
name_zh: 代码探索
|
|
4
|
+
description_zh: 分析项目结构,梳理代码逻辑,回答「这个功能是怎么实现的」。
|
|
3
5
|
description: Use this skill when exploring, analyzing, or understanding project/code structure. Required for tasks like "analyze project", "explore codebase", "understand how X works".
|
|
4
6
|
agent: coding
|
|
5
7
|
fork_agent: true
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cron-task-creator
|
|
3
|
+
name_zh: 定时任务
|
|
4
|
+
description_zh: 创建和管理定时自动任务,支持按天、周、小时执行,可随时编辑、启停或删除。
|
|
3
5
|
description: 'Create, manage, and run scheduled automated tasks (cron jobs) in Clacky. Use this skill whenever the user wants to create a new automated task or cron job, set up recurring automation, schedule something to run daily/weekly/hourly, view all scheduled tasks, edit an existing task prompt or cron schedule, enable or disable a task, delete a task, check task run history or logs, or run a task immediately via the WebUI. Trigger on phrases like cron, scheduled task, run every day, automate this; 定时任务, 每天自动, 定时执行.'
|
|
4
6
|
disable-model-invocation: false
|
|
5
7
|
user-invocable: true
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: deploy
|
|
3
|
-
|
|
3
|
+
name_zh: 一键部署
|
|
4
|
+
description_zh: 把 Rails 应用部署到 Railway,首次配置和后续更新都能自动完成。
|
|
5
|
+
description: 'Deploy Rails applications to Railway. Handles first-time setup and re-deploys idempotently using Railway CLI. Trigger on: "deploy", "deploy to railway", "railway deploy", "发布", "部署", "上线".'
|
|
4
6
|
agent: coding
|
|
5
7
|
disable-model-invocation: false
|
|
6
8
|
---
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mcp-manager
|
|
3
|
+
name_zh: MCP 管理
|
|
4
|
+
description_zh: 添加、查看、检测、删除和重新配置 MCP 服务,无需手动编辑 JSON。
|
|
3
5
|
description: |
|
|
4
6
|
Manage MCP (Model Context Protocol) servers for openclacky: add, list, probe, remove,
|
|
5
7
|
reconfigure. Edits ~/.clacky/mcp.json so the user never writes JSON by hand.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: media-gen
|
|
3
|
+
name_zh: 媒体生成
|
|
4
|
+
description_zh: 生成或编辑图片、视频,以及文字转语音,适用于封面、海报、插画、配音等。
|
|
3
5
|
description: 'Generate or edit images, videos, or audio (text-to-speech) in the current task. Use whenever the user asks to create/generate/produce or edit/modify a picture / image / illustration / cover / poster / icon / artwork, a video / clip / animation, or speech / voiceover / narration / TTS — e.g. generate image, draw, design a cover, edit this image, change the background, text-to-video, generate speech; 画一张, 配图, 编辑图片, 改图, 换背景, 做个视频, 配音, 文字转语音. Also use when a document (slides, poster, README hero) needs an inline image.'
|
|
4
6
|
disable-model-invocation: false
|
|
5
7
|
user-invocable: true
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: onboard
|
|
3
|
+
name_zh: 初始设置
|
|
4
|
+
description_zh: 首次使用引导:设置助手名字、性格和你的个人档案,也可单独调整其中一项。
|
|
3
5
|
description: Onboard a new user OR curate a single piece of the assistant's inner state. Without arguments, runs the full first-run ceremony (AI name, personality, user profile, SOUL.md + USER.md, optional browser + personal website). With `scope:soul` or `scope:user`, runs a quick chat to update just that one profile file. With `path:<abs>`, runs a quick chat to update / keep / delete one memory file under ~/.clacky/memories/.
|
|
4
6
|
disable-model-invocation: true
|
|
5
7
|
user-invocable: true
|