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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +13 -0
  3. data/CHANGELOG.md +26 -0
  4. data/Dockerfile +21 -1
  5. data/README.md +26 -7
  6. data/README_CN.md +26 -7
  7. data/README_JA.md +26 -7
  8. data/lib/clacky/agent/message_compressor.rb +4 -29
  9. data/lib/clacky/agent/message_compressor_helper.rb +13 -15
  10. data/lib/clacky/agent/session_serializer.rb +6 -0
  11. data/lib/clacky/agent.rb +13 -1
  12. data/lib/clacky/agent_config.rb +6 -3
  13. data/lib/clacky/brand_config.rb +32 -12
  14. data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +15 -0
  15. data/lib/clacky/default_extensions/ext-studio/api/handler.rb +1 -5
  16. data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +1 -1
  17. data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +5 -1
  18. data/lib/clacky/default_parsers/xlsx_parser.py +66 -0
  19. data/lib/clacky/extension/api_extension.rb +3 -2
  20. data/lib/clacky/message_format/open_ai.rb +26 -3
  21. data/lib/clacky/providers.rb +16 -8
  22. data/lib/clacky/server/http_server.rb +341 -24
  23. data/lib/clacky/server/project_manager.rb +150 -0
  24. data/lib/clacky/server/server_master.rb +7 -0
  25. data/lib/clacky/server/session_registry.rb +14 -1
  26. data/lib/clacky/session_manager.rb +5 -4
  27. data/lib/clacky/tools/file_reader.rb +2 -1
  28. data/lib/clacky/utils/model_pricing.rb +69 -18
  29. data/lib/clacky/utils/parser_manager.rb +115 -7
  30. data/lib/clacky/version.rb +1 -1
  31. data/lib/clacky/web/app.css +903 -135
  32. data/lib/clacky/web/app.js +11 -2
  33. data/lib/clacky/web/components/notify.js +139 -22
  34. data/lib/clacky/web/core/aside.js +21 -0
  35. data/lib/clacky/web/features/brand/store.js +3 -1
  36. data/lib/clacky/web/features/brand/view.js +29 -5
  37. data/lib/clacky/web/features/new-session/view.js +2 -2
  38. data/lib/clacky/web/features/trash/store.js +23 -9
  39. data/lib/clacky/web/features/trash/view.js +51 -0
  40. data/lib/clacky/web/i18n.js +95 -4
  41. data/lib/clacky/web/index.html +69 -15
  42. data/lib/clacky/web/projects.js +1143 -0
  43. data/lib/clacky/web/sessions.js +318 -537
  44. data/lib/clacky/web/settings.js +138 -5
  45. data/lib/clacky/web/ws-dispatcher.js +59 -1
  46. data/scripts/build/src/install_system_deps.sh.cc +11 -3
  47. data/scripts/install_system_deps.sh +11 -3
  48. metadata +5 -2
  49. 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
- if readme.strip.empty?
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
- await reload(); rebuild();
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) uses
199
- # :max_completion_tokens to cap the combined thinking + answer length.
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) ? :max_completion_tokens : :max_tokens
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
@@ -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-4-6",
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-4-8",
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", "base_url" => "https://api.minimaxi.com/v1", "region" => "cn" }.freeze,
258
- { "label" => "International", "label_key" => "settings.models.baseurl.variant.international", "base_url" => "https://api.minimax.io/v1", "region" => "intl" }.freeze
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.x does not support multimodal/vision input on this endpoint.
261
- # M3 (released 2026-06-01) is natively multimodal and accepts image
262
- # input, so it overrides the provider-level vision=false below.
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://www.minimaxi.com/user-center/basic-information/interface-key"
276
+ "website_url" => "https://platform.minimax.io/"
269
277
  }.freeze,
270
278
 
271
279
  "kimi" => {