openclacky 1.5.1 → 1.5.2
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 +24 -0
- data/lib/clacky/agent/llm_caller.rb +48 -0
- data/lib/clacky/agent/message_compressor_helper.rb +7 -0
- data/lib/clacky/agent/session_serializer.rb +42 -0
- data/lib/clacky/agent/skill_manager.rb +7 -0
- data/lib/clacky/agent.rb +58 -1
- data/lib/clacky/agent_config.rb +42 -0
- data/lib/clacky/brand_config.rb +88 -59
- data/lib/clacky/client.rb +6 -2
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +5 -0
- data/lib/clacky/extension/packager.rb +17 -0
- data/lib/clacky/locales/en.rb +4 -2
- data/lib/clacky/locales/zh.rb +5 -3
- data/lib/clacky/message_format/anthropic.rb +29 -0
- data/lib/clacky/message_history.rb +15 -1
- data/lib/clacky/providers.rb +22 -0
- data/lib/clacky/server/http_server.rb +41 -17
- data/lib/clacky/server/session_registry.rb +4 -2
- data/lib/clacky/skill.rb +4 -0
- data/lib/clacky/tools/invoke_skill.rb +21 -0
- data/lib/clacky/tools/security.rb +11 -13
- data/lib/clacky/ui_interface.rb +2 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +61 -14
- data/lib/clacky/web/features/billing/view.js +5 -0
- data/lib/clacky/web/features/extensions/store.js +9 -2
- data/lib/clacky/web/features/new-session/view.js +61 -0
- data/lib/clacky/web/i18n.js +10 -0
- data/lib/clacky/web/index.html +3 -0
- data/lib/clacky/web/settings.js +41 -12
- metadata +2 -2
|
@@ -139,6 +139,67 @@ const NewSessionView = (() => {
|
|
|
139
139
|
|
|
140
140
|
_updatePlaceholder();
|
|
141
141
|
_renderAgentPanels();
|
|
142
|
+
_renderStarterPrompts();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Starter prompts ────────────────────────────────────────────────────
|
|
146
|
+
// Hardcoded UI hints per agent id. These are purely presentational and
|
|
147
|
+
// help users discover what to ask without needing to think from scratch.
|
|
148
|
+
const STARTER_PROMPTS = {
|
|
149
|
+
"ext-developer": [
|
|
150
|
+
{
|
|
151
|
+
en: "Help me build a Xiaohongshu content publishing extension, with a custom panel in the right sidebar",
|
|
152
|
+
zh: "帮我开发一个小红书内容发布管理扩展,在会话右侧边栏添加一个自定义面板",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
en: "I want to build a GitHub PR review assistant extension that auto-analyzes code changes, with an entry in the bottom-left sidebar",
|
|
156
|
+
zh: "我想做一个 GitHub PR 审查助手扩展,自动分析代码变更,入口放在左底部侧边栏",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
en: "Help me build a Pomodoro + task tracking extension with a left sidebar entry and custom Agent",
|
|
160
|
+
zh: "帮我开发一个番茄钟 + 任务追踪扩展,带左侧边栏访问入口和自定义 Agent",
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
function _renderStarterPrompts() {
|
|
166
|
+
const container = $("new-session-starter-prompts");
|
|
167
|
+
if (!container) return;
|
|
168
|
+
const agent = NewSessionStore.currentAgent();
|
|
169
|
+
const prompts = agent && STARTER_PROMPTS[agent.id];
|
|
170
|
+
if (!prompts || !prompts.length) {
|
|
171
|
+
container.style.display = "none";
|
|
172
|
+
container.replaceChildren();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const zh = _isZh();
|
|
177
|
+
container.replaceChildren();
|
|
178
|
+
|
|
179
|
+
const label = document.createElement("div");
|
|
180
|
+
label.className = "ns-starter-label";
|
|
181
|
+
label.textContent = I18n.t("sessions.new.tryAsking");
|
|
182
|
+
container.appendChild(label);
|
|
183
|
+
|
|
184
|
+
const list = document.createElement("div");
|
|
185
|
+
list.className = "ns-starter-list";
|
|
186
|
+
prompts.forEach((p) => {
|
|
187
|
+
const btn = document.createElement("button");
|
|
188
|
+
btn.type = "button";
|
|
189
|
+
btn.className = "ns-starter-item";
|
|
190
|
+
const text = zh ? p.zh : p.en;
|
|
191
|
+
btn.textContent = "\u201c" + text + "\u201d";
|
|
192
|
+
btn.addEventListener("click", () => {
|
|
193
|
+
const input = $("new-session-input");
|
|
194
|
+
if (!input) return;
|
|
195
|
+
input.value = zh ? p.zh : p.en;
|
|
196
|
+
input.focus();
|
|
197
|
+
_updateSendButton();
|
|
198
|
+
});
|
|
199
|
+
list.appendChild(btn);
|
|
200
|
+
});
|
|
201
|
+
container.appendChild(list);
|
|
202
|
+
container.style.display = "block";
|
|
142
203
|
}
|
|
143
204
|
|
|
144
205
|
function _renderAgentPanels() {
|
data/lib/clacky/web/i18n.js
CHANGED
|
@@ -33,6 +33,7 @@ const I18n = (() => {
|
|
|
33
33
|
"sessions.new.subtitle": "Pick who you want to chat with, then type your first message.",
|
|
34
34
|
"sessions.new.placeholder": "Type your first message to {{agent}}…",
|
|
35
35
|
"sessions.new.moreOptions": "More options",
|
|
36
|
+
"sessions.new.tryAsking": "Try asking",
|
|
36
37
|
"sessions.new.send": "Send",
|
|
37
38
|
|
|
38
39
|
// ── Chat panel ──
|
|
@@ -666,6 +667,8 @@ const I18n = (() => {
|
|
|
666
667
|
"settings.models.baseurl.variant.ark_payg": "Pay-as-you-go",
|
|
667
668
|
"settings.models.baseurl.variant.ark_coding": "Coding Plan",
|
|
668
669
|
"settings.models.baseurl.variant.ark_agent": "Agent Plan",
|
|
670
|
+
"settings.models.baseurl.variant.openclacky_primary": "Primary (Global)",
|
|
671
|
+
"settings.models.baseurl.variant.openclacky_secondary": "Secondary (China)",
|
|
669
672
|
"settings.models.btn.save": "Save",
|
|
670
673
|
"settings.models.btn.saving": "Saving…",
|
|
671
674
|
"settings.models.btn.saved": "Saved ✓",
|
|
@@ -753,6 +756,7 @@ const I18n = (() => {
|
|
|
753
756
|
"settings.brand.badge.active": "Active",
|
|
754
757
|
"settings.brand.badge.warning": "Expiring Soon",
|
|
755
758
|
"settings.brand.badge.expired": "Expired",
|
|
759
|
+
"settings.brand.badge.unreachable": "Connection Issue",
|
|
756
760
|
"settings.brand.desc": "Have a serial number from a brand partner? Enter it below to activate branded mode.",
|
|
757
761
|
"settings.brand.descNamed": "Enter your {{name}} serial number to activate branded mode.",
|
|
758
762
|
"settings.brand.btn.getSerial": "Get a Serial Number",
|
|
@@ -1004,6 +1008,7 @@ const I18n = (() => {
|
|
|
1004
1008
|
"billing.headerHit": "Hit",
|
|
1005
1009
|
"billing.headerMiss": "Miss",
|
|
1006
1010
|
"billing.headerOutput": "Output",
|
|
1011
|
+
"billing.disclaimer": "Costs are estimated based on token usage and may differ from your provider's actual bill.",
|
|
1007
1012
|
},
|
|
1008
1013
|
zh: {
|
|
1009
1014
|
// ── Sidebar ──
|
|
@@ -1026,6 +1031,7 @@ const I18n = (() => {
|
|
|
1026
1031
|
"sessions.new.subtitle": "选一个 Agent,然后输入第一条消息。",
|
|
1027
1032
|
"sessions.new.placeholder": "输入第一条消息发给 {{agent}}…",
|
|
1028
1033
|
"sessions.new.moreOptions": "更多选项",
|
|
1034
|
+
"sessions.new.tryAsking": "试试这样问我",
|
|
1029
1035
|
"sessions.new.send": "发送",
|
|
1030
1036
|
|
|
1031
1037
|
// ── Chat panel ──
|
|
@@ -1663,6 +1669,8 @@ const I18n = (() => {
|
|
|
1663
1669
|
"settings.models.baseurl.variant.ark_payg": "按量付费",
|
|
1664
1670
|
"settings.models.baseurl.variant.ark_coding": "Coding Plan",
|
|
1665
1671
|
"settings.models.baseurl.variant.ark_agent": "Agent Plan",
|
|
1672
|
+
"settings.models.baseurl.variant.openclacky_primary": "主节点(全球)",
|
|
1673
|
+
"settings.models.baseurl.variant.openclacky_secondary": "备用节点(中国)",
|
|
1666
1674
|
"settings.models.btn.save": "保存",
|
|
1667
1675
|
"settings.models.btn.saving": "保存中…",
|
|
1668
1676
|
"settings.models.btn.saved": "已保存 ✓",
|
|
@@ -1750,6 +1758,7 @@ const I18n = (() => {
|
|
|
1750
1758
|
"settings.brand.badge.active": "已激活",
|
|
1751
1759
|
"settings.brand.badge.warning": "即将过期",
|
|
1752
1760
|
"settings.brand.badge.expired": "已过期",
|
|
1761
|
+
"settings.brand.badge.unreachable": "连接异常",
|
|
1753
1762
|
"settings.brand.desc": "有品牌合作伙伴的序列号?在下方输入以激活品牌模式。",
|
|
1754
1763
|
"settings.brand.descNamed": "请输入 {{name}} 的序列号以激活品牌模式。",
|
|
1755
1764
|
"settings.brand.btn.getSerial": "获取序列号",
|
|
@@ -2001,6 +2010,7 @@ const I18n = (() => {
|
|
|
2001
2010
|
"billing.headerHit": "命中",
|
|
2002
2011
|
"billing.headerMiss": "未命中",
|
|
2003
2012
|
"billing.headerOutput": "输出",
|
|
2013
|
+
"billing.disclaimer": "费用为估算值,基于 Token 用量和模型定价计算,可能与 API 提供商实际账单有所差异。",
|
|
2004
2014
|
}
|
|
2005
2015
|
};
|
|
2006
2016
|
// ── State ──────────────────────────────────────────────────────────────────
|
data/lib/clacky/web/index.html
CHANGED
|
@@ -335,6 +335,9 @@
|
|
|
335
335
|
|
|
336
336
|
<div id="new-session-agents" class="new-session-agents"></div>
|
|
337
337
|
|
|
338
|
+
<!-- Starter prompts shown when selected agent provides suggestions -->
|
|
339
|
+
<div id="new-session-starter-prompts" class="new-session-starter-prompts" style="display:none"></div>
|
|
340
|
+
|
|
338
341
|
<!-- Panels the selected agent exposes (filled when an agent is picked) -->
|
|
339
342
|
<div id="new-session-agent-panels" class="new-session-agent-panels" style="display:none"></div>
|
|
340
343
|
|
data/lib/clacky/web/settings.js
CHANGED
|
@@ -66,7 +66,11 @@ const Settings = (() => {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function _getProviderName(model) {
|
|
69
|
-
|
|
69
|
+
// Prefer the saved provider_id (survives custom base_url edits) over
|
|
70
|
+
// a reverse base_url lookup so the card always shows what the user picked.
|
|
71
|
+
const p = (model.provider_id
|
|
72
|
+
? _providers.find(pr => pr.id === model.provider_id) || null
|
|
73
|
+
: null) || _findProviderByBaseUrl(model.base_url);
|
|
70
74
|
return _providerDisplayName(p);
|
|
71
75
|
}
|
|
72
76
|
|
|
@@ -87,7 +91,11 @@ const Settings = (() => {
|
|
|
87
91
|
function _renderCard(container, model, index) {
|
|
88
92
|
const isDefault = model.type === "default";
|
|
89
93
|
const isLite = model.type === "lite";
|
|
90
|
-
|
|
94
|
+
// Prefer saved provider_id over base_url reverse-lookup so the card
|
|
95
|
+
// shows the correct provider even when the user has customised base_url.
|
|
96
|
+
const provider = (model.provider_id
|
|
97
|
+
? _providers.find(p => p.id === model.provider_id) || null
|
|
98
|
+
: null) || _findProviderByBaseUrl(model.base_url);
|
|
91
99
|
const providerName = _providerDisplayName(provider);
|
|
92
100
|
const websiteUrl = provider && provider.website_url;
|
|
93
101
|
const displayName = model.model || I18n.t("settings.models.unnamed");
|
|
@@ -190,12 +198,21 @@ const Settings = (() => {
|
|
|
190
198
|
setDefaultCb.checked = isOnlyModel ? true : (model.type === "default");
|
|
191
199
|
setDefaultCb.disabled = isOnlyModel;
|
|
192
200
|
|
|
193
|
-
// Set provider dropdown value
|
|
201
|
+
// Set provider dropdown value.
|
|
202
|
+
// Priority: 1) saved provider_id on the model record (explicit user choice,
|
|
203
|
+
// survives base_url edits); 2) reverse-lookup from base_url (legacy records
|
|
204
|
+
// without provider_id); 3) anthropic_format flag (self-hosted proxy hint).
|
|
194
205
|
const matched = _findProviderByBaseUrl(model.base_url);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
const savedProviderId = model.provider_id || null;
|
|
207
|
+
const resolvedProviderId = savedProviderId ||
|
|
208
|
+
(matched ? matched.id : (model.anthropic_format ? "anthropic" : null));
|
|
209
|
+
_modalSelectedProviderId = resolvedProviderId;
|
|
210
|
+
const resolvedProvider = resolvedProviderId
|
|
211
|
+
? _providers.find(p => p.id === resolvedProviderId) || null
|
|
212
|
+
: null;
|
|
213
|
+
const providerName = resolvedProvider
|
|
214
|
+
? _providerDisplayName(resolvedProvider)
|
|
215
|
+
: I18n.t("settings.models.provider.custom");
|
|
199
216
|
const providerValue = document.getElementById("model-modal-provider-value");
|
|
200
217
|
providerValue.textContent = providerName;
|
|
201
218
|
providerValue.classList.remove("placeholder");
|
|
@@ -398,7 +415,7 @@ const Settings = (() => {
|
|
|
398
415
|
|
|
399
416
|
const hasId = !!existingId;
|
|
400
417
|
|
|
401
|
-
const payload = { model, base_url, anthropic_format };
|
|
418
|
+
const payload = { model, base_url, anthropic_format, provider_id: _modalSelectedProviderId || null };
|
|
402
419
|
const setDefault = document.getElementById("model-modal-set-default").checked;
|
|
403
420
|
payload.type = setDefault ? "default" : null;
|
|
404
421
|
if (setDefault) {
|
|
@@ -1389,10 +1406,22 @@ const Settings = (() => {
|
|
|
1389
1406
|
|
|
1390
1407
|
const badge = document.getElementById("brand-status-badge");
|
|
1391
1408
|
if (data.warning) {
|
|
1392
|
-
//
|
|
1393
|
-
const
|
|
1394
|
-
|
|
1395
|
-
|
|
1409
|
+
// Use warning_type from backend for accurate badge display
|
|
1410
|
+
const wt = data.warning_type;
|
|
1411
|
+
let text, cls;
|
|
1412
|
+
if (wt === "expired") {
|
|
1413
|
+
text = I18n.t("settings.brand.badge.expired");
|
|
1414
|
+
cls = "badge-expired";
|
|
1415
|
+
} else if (wt === "unreachable") {
|
|
1416
|
+
text = I18n.t("settings.brand.badge.unreachable");
|
|
1417
|
+
cls = "badge-unreachable";
|
|
1418
|
+
} else {
|
|
1419
|
+
// "expiring" or legacy fallback
|
|
1420
|
+
text = I18n.t("settings.brand.badge.warning");
|
|
1421
|
+
cls = "badge-expiring";
|
|
1422
|
+
}
|
|
1423
|
+
badge.textContent = text;
|
|
1424
|
+
badge.className = "brand-status-value " + cls;
|
|
1396
1425
|
} else {
|
|
1397
1426
|
badge.textContent = I18n.t("settings.brand.badge.active");
|
|
1398
1427
|
badge.className = "brand-status-value badge-active";
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openclacky
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.5.
|
|
4
|
+
version: 1.5.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- windy
|
|
@@ -745,7 +745,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
745
745
|
- !ruby/object:Gem::Version
|
|
746
746
|
version: '0'
|
|
747
747
|
requirements: []
|
|
748
|
-
rubygems_version: 3.6.
|
|
748
|
+
rubygems_version: 3.6.8
|
|
749
749
|
specification_version: 4
|
|
750
750
|
summary: The most Token-efficient open-source AI Agent — BYOK, Skill-driven, IM-integrated.
|
|
751
751
|
test_files: []
|