openclacky 1.5.3 → 1.5.4
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/.dockerignore +13 -0
- data/CHANGELOG.md +26 -0
- data/Dockerfile +21 -1
- data/README.md +26 -7
- data/README_CN.md +26 -7
- data/README_JA.md +26 -7
- data/lib/clacky/agent/message_compressor.rb +4 -29
- data/lib/clacky/agent/message_compressor_helper.rb +13 -15
- data/lib/clacky/agent/session_serializer.rb +6 -0
- data/lib/clacky/agent.rb +13 -1
- data/lib/clacky/agent_config.rb +6 -3
- data/lib/clacky/brand_config.rb +32 -12
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +15 -0
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +1 -5
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +1 -1
- data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +5 -1
- data/lib/clacky/default_parsers/xlsx_parser.py +66 -0
- data/lib/clacky/extension/api_extension.rb +3 -2
- data/lib/clacky/message_format/open_ai.rb +26 -3
- data/lib/clacky/providers.rb +16 -8
- data/lib/clacky/server/http_server.rb +341 -24
- data/lib/clacky/server/project_manager.rb +150 -0
- data/lib/clacky/server/server_master.rb +7 -0
- data/lib/clacky/server/session_registry.rb +14 -1
- data/lib/clacky/session_manager.rb +5 -4
- data/lib/clacky/tools/file_reader.rb +2 -1
- data/lib/clacky/utils/model_pricing.rb +69 -18
- data/lib/clacky/utils/parser_manager.rb +115 -7
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +903 -135
- data/lib/clacky/web/app.js +11 -2
- data/lib/clacky/web/components/notify.js +139 -22
- data/lib/clacky/web/core/aside.js +21 -0
- data/lib/clacky/web/features/brand/store.js +3 -1
- data/lib/clacky/web/features/brand/view.js +29 -5
- data/lib/clacky/web/features/new-session/view.js +2 -2
- data/lib/clacky/web/features/trash/store.js +23 -9
- data/lib/clacky/web/features/trash/view.js +51 -0
- data/lib/clacky/web/i18n.js +95 -4
- data/lib/clacky/web/index.html +69 -15
- data/lib/clacky/web/projects.js +1143 -0
- data/lib/clacky/web/sessions.js +318 -537
- data/lib/clacky/web/settings.js +138 -5
- data/lib/clacky/web/ws-dispatcher.js +59 -1
- data/scripts/build/src/install_system_deps.sh.cc +11 -3
- data/scripts/install_system_deps.sh +11 -3
- metadata +5 -2
- data/lib/clacky/default_parsers/xlsx_parser.rb +0 -121
|
@@ -201,11 +201,7 @@ class ExtStudioExt < Clacky::ApiExtension
|
|
|
201
201
|
error!("extension not found: #{ext_id}", status: 404) unless container
|
|
202
202
|
|
|
203
203
|
readme_path = File.join(container[:dir], "README.md")
|
|
204
|
-
|
|
205
|
-
File.delete(readme_path) if File.exist?(readme_path)
|
|
206
|
-
else
|
|
207
|
-
File.write(readme_path, readme, encoding: "utf-8")
|
|
208
|
-
end
|
|
204
|
+
File.write(readme_path, readme, encoding: "utf-8")
|
|
209
205
|
end
|
|
210
206
|
|
|
211
207
|
json(ok: true, ext_id: ext_id, saved_to: published ? "platform" : "local")
|
|
@@ -1539,7 +1539,7 @@
|
|
|
1539
1539
|
let data = {};
|
|
1540
1540
|
try { data = await res.json(); } catch (_e) {}
|
|
1541
1541
|
if (!res.ok) throw new Error(data?.error || `Error ${res.status}`);
|
|
1542
|
-
|
|
1542
|
+
location.reload();
|
|
1543
1543
|
} catch (e) {
|
|
1544
1544
|
alert(t("err.generic", { msg: e.message }));
|
|
1545
1545
|
btn.disabled = false;
|
|
@@ -204,6 +204,10 @@ Response helpers: `json` / `text(str)` / `send_data(bytes, content_type:, filena
|
|
|
204
204
|
- Drive sessions from the backend: `create_session(prompt:, profile:, …)`,
|
|
205
205
|
`submit_task(session_id, prompt)`, `dispatch_to_session(session_id, prompt)` (runs a
|
|
206
206
|
side task on a fork and returns its reply without touching the conversation).
|
|
207
|
+
- `create_session(hidden: true)` creates a session the openclacky session list hides
|
|
208
|
+
and the 200-session cleanup skips - use it for dedicated extension sessions you
|
|
209
|
+
manage yourself. Still reachable by `session_id` via `submit_task` /
|
|
210
|
+
`dispatch_to_session` / `registry.with_session`; forking resets `hidden` to false.
|
|
207
211
|
- Public (no-auth) endpoints: call `public_endpoint("/path")` in the class **and** set
|
|
208
212
|
`public: true` at ext.yml top level — both are required.
|
|
209
213
|
|
|
@@ -294,7 +298,7 @@ contributes:
|
|
|
294
298
|
classes; swap the body for the real UI, POST to your own route:
|
|
295
299
|
|
|
296
300
|
```js
|
|
297
|
-
Clacky.ext.ui.mount("session.aside", function (ctx) {
|
|
301
|
+
Clacky.ext.ui.mount("session.aside", function (container, ctx) {
|
|
298
302
|
var el = document.createElement("div");
|
|
299
303
|
el.style.padding = "16px";
|
|
300
304
|
var input = document.createElement("input");
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""xlsx_parser.py — extract an XLSX/XLS workbook to Markdown tables.
|
|
3
|
+
|
|
4
|
+
Usage: python3 xlsx_parser.py <file_path>
|
|
5
|
+
Output: stdout — Markdown tables (UTF-8), one section per non-empty sheet
|
|
6
|
+
stderr — error messages
|
|
7
|
+
exit 0 success / 1 failure / 2 dependency missing
|
|
8
|
+
|
|
9
|
+
Uses openpyxl in read_only mode: a streaming reader that keeps memory
|
|
10
|
+
constant regardless of sheet size, so multi-megabyte worksheets parse in
|
|
11
|
+
seconds instead of exhausting RAM/CPU like a full DOM parse would.
|
|
12
|
+
"""
|
|
13
|
+
# VERSION: 2
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_table(rows):
|
|
18
|
+
width = max(len(r) for r in rows)
|
|
19
|
+
lines = []
|
|
20
|
+
for i, r in enumerate(rows):
|
|
21
|
+
padded = r + [""] * (width - len(r))
|
|
22
|
+
lines.append("| " + " | ".join(padded) + " |")
|
|
23
|
+
if i == 0:
|
|
24
|
+
lines.append("|" + " --- |" * width)
|
|
25
|
+
return "\n".join(lines)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main():
|
|
29
|
+
if len(sys.argv) < 2:
|
|
30
|
+
sys.stderr.write("Usage: xlsx_parser.py <file_path>\n")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
path = sys.argv[1]
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
from openpyxl import load_workbook
|
|
36
|
+
except ImportError as e:
|
|
37
|
+
sys.stderr.write(f"openpyxl not installed: {e}\n")
|
|
38
|
+
sys.stderr.write("Install with: pip3 install --user openpyxl\n")
|
|
39
|
+
sys.exit(2)
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
wb = load_workbook(path, read_only=True, data_only=True)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
sys.stderr.write(f"Failed to open workbook: {e}\n")
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
47
|
+
sections = []
|
|
48
|
+
for ws in wb.worksheets:
|
|
49
|
+
rows = []
|
|
50
|
+
for row in ws.iter_rows(values_only=True):
|
|
51
|
+
cells = ["" if v is None else str(v) for v in row]
|
|
52
|
+
if any(c != "" for c in cells):
|
|
53
|
+
rows.append(cells)
|
|
54
|
+
if rows:
|
|
55
|
+
sections.append(f"### {ws.title}\n\n{build_table(rows)}")
|
|
56
|
+
wb.close()
|
|
57
|
+
|
|
58
|
+
if not sections:
|
|
59
|
+
sys.stderr.write("Spreadsheet appears to be empty\n")
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
sys.stdout.write("\n\n".join(sections))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
main()
|
|
@@ -263,7 +263,7 @@ module Clacky
|
|
|
263
263
|
# submitted immediately (the session starts running); display_message
|
|
264
264
|
# controls the user-facing bubble shown in place of the raw prompt.
|
|
265
265
|
def create_session(name: nil, prompt: nil, working_dir: nil, profile: "general",
|
|
266
|
-
source: :manual, display_message: nil)
|
|
266
|
+
source: :manual, display_message: nil, hidden: false)
|
|
267
267
|
error!("server not ready", status: 503) unless @http_server
|
|
268
268
|
|
|
269
269
|
session_id = @http_server.send(
|
|
@@ -271,7 +271,8 @@ module Clacky
|
|
|
271
271
|
name: name,
|
|
272
272
|
working_dir: working_dir,
|
|
273
273
|
profile: profile,
|
|
274
|
-
source: source
|
|
274
|
+
source: source,
|
|
275
|
+
hidden: hidden
|
|
275
276
|
)
|
|
276
277
|
|
|
277
278
|
submit_task(session_id, prompt, display_message: display_message) if prompt && !prompt.strip.empty?
|
|
@@ -195,10 +195,16 @@ module Clacky
|
|
|
195
195
|
# ── Private helpers ───────────────────────────────────────────────────────
|
|
196
196
|
|
|
197
197
|
# Returns the token-limit field name for the given model.
|
|
198
|
-
# Most OpenAI-compatible APIs use :max_tokens; MiMo-V2.5 (Xiaomi)
|
|
199
|
-
# :max_completion_tokens to cap the combined thinking +
|
|
198
|
+
# Most OpenAI-compatible APIs use :max_tokens; MiMo-V2.5 (Xiaomi) and
|
|
199
|
+
# MiniMax-M3 use :max_completion_tokens to cap the combined thinking +
|
|
200
|
+
# answer length (M3 documents max_completion_tokens over the legacy
|
|
201
|
+
# max_tokens field).
|
|
200
202
|
private_class_method def self.token_field_for(model)
|
|
201
|
-
model.to_s.match?(/^mimo-v2/i) ?
|
|
203
|
+
if model.to_s.match?(/^mimo-v2/i) || model.to_s.match?(/^minimax-m3$/i)
|
|
204
|
+
:max_completion_tokens
|
|
205
|
+
else
|
|
206
|
+
:max_tokens
|
|
207
|
+
end
|
|
202
208
|
end
|
|
203
209
|
|
|
204
210
|
# Apply model-specific reasoning / thinking parameters to the request body.
|
|
@@ -256,6 +262,23 @@ module Clacky
|
|
|
256
262
|
elsif !effort_str.empty?
|
|
257
263
|
body[:thinking] = { type: "enabled" }
|
|
258
264
|
end
|
|
265
|
+
elsif model.to_s.match?(/^minimax-m3$/i)
|
|
266
|
+
# MiniMax-M3 controls thinking via a top-level "thinking" field with
|
|
267
|
+
# { type: "adaptive" | "disabled" } — the same envelope shape MiMo
|
|
268
|
+
# uses, but with MiniMax-specific values. Adaptive thinking is the
|
|
269
|
+
# server default when the field is omitted, so we only send the
|
|
270
|
+
# field to explicitly switch modes:
|
|
271
|
+
# - reasoning_effort "off"/"disabled" → thinking disabled
|
|
272
|
+
# (M3 answers directly; M2.7 keeps thinking on regardless).
|
|
273
|
+
# - any other effort → adaptive thinking (M3
|
|
274
|
+
# decides per request); we let the server pick the intensity.
|
|
275
|
+
# M2.7 cannot disable thinking and ignores this field, so sending
|
|
276
|
+
# it is harmless for the rest of the MiniMax lineup.
|
|
277
|
+
if %w[off nothink disabled].include?(effort_str)
|
|
278
|
+
body[:thinking] = { type: "disabled" }
|
|
279
|
+
elsif !effort_str.empty?
|
|
280
|
+
body[:thinking] = { type: "adaptive" }
|
|
281
|
+
end
|
|
259
282
|
elsif reasoning_effort && !effort_str.empty?
|
|
260
283
|
body[:reasoning_effort] = effort_str
|
|
261
284
|
end
|
data/lib/clacky/providers.rb
CHANGED
|
@@ -29,12 +29,14 @@ module Clacky
|
|
|
29
29
|
"name" => "OpenClacky",
|
|
30
30
|
"base_url" => "https://api.openclacky.com",
|
|
31
31
|
"api" => "bedrock",
|
|
32
|
-
"default_model" => "abs-claude-sonnet-
|
|
32
|
+
"default_model" => "abs-claude-sonnet-5",
|
|
33
33
|
"models" => [
|
|
34
34
|
"abs-claude-fable-5",
|
|
35
|
+
"abs-claude-opus-5",
|
|
35
36
|
"abs-claude-opus-4-8",
|
|
36
37
|
"abs-claude-opus-4-7",
|
|
37
38
|
"abs-claude-opus-4-6",
|
|
39
|
+
"abs-claude-sonnet-5",
|
|
38
40
|
"abs-claude-sonnet-4-6",
|
|
39
41
|
"abs-claude-sonnet-4-5",
|
|
40
42
|
"abs-claude-haiku-4-5",
|
|
@@ -134,9 +136,11 @@ module Clacky
|
|
|
134
136
|
# no injection happens when the default model is already lite-class.
|
|
135
137
|
"lite_models" => {
|
|
136
138
|
"abs-claude-fable-5" => "abs-claude-haiku-4-5",
|
|
139
|
+
"abs-claude-opus-5" => "abs-claude-haiku-4-5",
|
|
137
140
|
"abs-claude-opus-4-8" => "abs-claude-haiku-4-5",
|
|
138
141
|
"abs-claude-opus-4-7" => "abs-claude-haiku-4-5",
|
|
139
142
|
"abs-claude-opus-4-6" => "abs-claude-haiku-4-5",
|
|
143
|
+
"abs-claude-sonnet-5" => "abs-claude-haiku-4-5",
|
|
140
144
|
"abs-claude-sonnet-4-6" => "abs-claude-haiku-4-5",
|
|
141
145
|
"abs-claude-sonnet-4-5" => "abs-claude-haiku-4-5",
|
|
142
146
|
"dsk-deepseek-v4-pro" => "dsk-deepseek-v4-flash",
|
|
@@ -145,7 +149,9 @@ module Clacky
|
|
|
145
149
|
# Fallback chain: if a model is unavailable, try the next one in order.
|
|
146
150
|
# Keys are primary model names; values are the fallback model to use instead.
|
|
147
151
|
"fallback_models" => {
|
|
148
|
-
"abs-claude-fable-5" => "abs-claude-opus-
|
|
152
|
+
"abs-claude-fable-5" => "abs-claude-opus-5",
|
|
153
|
+
"abs-claude-opus-5" => "abs-claude-opus-4-8",
|
|
154
|
+
"abs-claude-sonnet-5" => "abs-claude-sonnet-4-6",
|
|
149
155
|
"abs-claude-sonnet-4-6" => "abs-claude-sonnet-4-5"
|
|
150
156
|
},
|
|
151
157
|
# Secondary gateway URL used when the primary base_url is unreachable
|
|
@@ -254,18 +260,20 @@ module Clacky
|
|
|
254
260
|
# so capability checks (vision=false) fire correctly regardless of
|
|
255
261
|
# which endpoint the user configured.
|
|
256
262
|
"endpoint_variants" => [
|
|
257
|
-
{ "label" => "Mainland China", "label_key" => "settings.models.baseurl.variant.mainland_cn",
|
|
258
|
-
{ "label" => "International", "label_key" => "settings.models.baseurl.variant.international",
|
|
263
|
+
{ "label" => "Mainland China", "label_key" => "settings.models.baseurl.variant.mainland_cn", "base_url" => "https://api.minimaxi.com/v1", "region" => "cn" }.freeze,
|
|
264
|
+
{ "label" => "International", "label_key" => "settings.models.baseurl.variant.international", "base_url" => "https://api.minimax.io/v1", "region" => "intl" }.freeze
|
|
259
265
|
].freeze,
|
|
260
|
-
# MiniMax M2.
|
|
261
|
-
#
|
|
262
|
-
#
|
|
266
|
+
# MiniMax M2.5/M2.7 are text-only on this endpoint. M3 (released 2026-06-01)
|
|
267
|
+
# is natively multimodal — it accepts image input via OpenAI-style
|
|
268
|
+
# image_url content parts — so it overrides the provider-level
|
|
269
|
+
# vision=false below. M3 exposes a 1,000,000-token context window
|
|
270
|
+
# (M2.7: 204,800; M2.5: 40,960).
|
|
263
271
|
"capabilities" => { "vision" => false }.freeze,
|
|
264
272
|
"model_capabilities" => {
|
|
265
273
|
"MiniMax-M3" => { "vision" => true }.freeze
|
|
266
274
|
}.freeze,
|
|
267
275
|
"default_ocr_model" => "MiniMax-M3",
|
|
268
|
-
"website_url" => "https://
|
|
276
|
+
"website_url" => "https://platform.minimax.io/"
|
|
269
277
|
}.freeze,
|
|
270
278
|
|
|
271
279
|
"kimi" => {
|