openclacky 1.5.2 → 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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +13 -0
  3. data/CHANGELOG.md +50 -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 +42 -11
  13. data/lib/clacky/brand_config.rb +32 -12
  14. data/lib/clacky/client.rb +7 -0
  15. data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +15 -0
  16. data/lib/clacky/default_extensions/ext-studio/api/handler.rb +2 -6
  17. data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +11 -4
  18. data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +5 -1
  19. data/lib/clacky/default_parsers/xlsx_parser.py +66 -0
  20. data/lib/clacky/extension/api_extension.rb +3 -2
  21. data/lib/clacky/message_format/anthropic.rb +27 -1
  22. data/lib/clacky/message_format/open_ai.rb +92 -4
  23. data/lib/clacky/providers.rb +67 -14
  24. data/lib/clacky/server/http_server.rb +361 -36
  25. data/lib/clacky/server/project_manager.rb +150 -0
  26. data/lib/clacky/server/server_master.rb +7 -0
  27. data/lib/clacky/server/session_registry.rb +14 -1
  28. data/lib/clacky/session_manager.rb +5 -4
  29. data/lib/clacky/tools/file_reader.rb +2 -1
  30. data/lib/clacky/utils/model_pricing.rb +77 -18
  31. data/lib/clacky/utils/parser_manager.rb +115 -7
  32. data/lib/clacky/version.rb +1 -1
  33. data/lib/clacky/web/app.css +1053 -143
  34. data/lib/clacky/web/app.js +19 -5
  35. data/lib/clacky/web/components/code-editor.js +50 -12
  36. data/lib/clacky/web/components/notify.js +139 -22
  37. data/lib/clacky/web/core/aside.js +21 -0
  38. data/lib/clacky/web/features/billing/view.js +2 -1
  39. data/lib/clacky/web/features/brand/store.js +3 -1
  40. data/lib/clacky/web/features/brand/view.js +29 -5
  41. data/lib/clacky/web/features/extensions/store.js +15 -6
  42. data/lib/clacky/web/features/extensions/view.js +8 -4
  43. data/lib/clacky/web/features/new-session/view.js +2 -2
  44. data/lib/clacky/web/features/trash/store.js +23 -9
  45. data/lib/clacky/web/features/trash/view.js +51 -0
  46. data/lib/clacky/web/features/workspace/store.js +9 -0
  47. data/lib/clacky/web/features/workspace/view.js +8 -1
  48. data/lib/clacky/web/i18n.js +99 -4
  49. data/lib/clacky/web/index.html +70 -17
  50. data/lib/clacky/web/projects.js +1143 -0
  51. data/lib/clacky/web/sessions.js +318 -537
  52. data/lib/clacky/web/settings.js +146 -7
  53. data/lib/clacky/web/ws-dispatcher.js +59 -1
  54. data/scripts/build/src/install_system_deps.sh.cc +11 -3
  55. data/scripts/install_system_deps.sh +11 -3
  56. metadata +5 -2
  57. data/lib/clacky/default_parsers/xlsx_parser.rb +0 -121
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb5f68cdd24e5d619fa22b79c643953e6c00f54400d7e89d95a9c99373328a55
4
- data.tar.gz: 5bc17c114482d78e13d59d5f6b194caffbf9456015bce6082d3bbc63bdf951a2
3
+ metadata.gz: 10e414552c9ae0e3bb7a0b547f2e4da0709c83e0a0925f3b708820848e1a4cf3
4
+ data.tar.gz: 6caa29ccd3048e95912500be99000ae7ca883c9a5ad473ebc1bb2f243aef5cc3
5
5
  SHA512:
6
- metadata.gz: 55044dd2a193d5ed5e0b64aefee284fec18cb036369482d55936e7c3c01d8740aecee16e0ca75f1e5d1cc643a4d70d66005f1f5eb61d3bfc02a6152ef3b4155d
7
- data.tar.gz: 86de30d32d826921245ea60abd5229665eb82445b68f68e6a68f90b519d4b0dd900b077a738c82b65a1d78a3bc974b3d938986445e9149f074b94095ead044c9
6
+ metadata.gz: 26f220973c6821a411362df35ca281ef53ae58e6ab36d26651db393b31713340ca9bcf131f69bf4e6aa391eec3f4f21fc7d0d12cb4bbbdc368b6fe8068fa2dfa
7
+ data.tar.gz: 84faf59b60486d29a9168f847c97d7fd5670dac293f967bd713c08049612adab7f947fa38af3a7e89c4a5acc2cc8c460e9b8ebbf53c189c19a52bf7e89a44388
data/.dockerignore ADDED
@@ -0,0 +1,13 @@
1
+ .git
2
+ .github
3
+ .clacky
4
+ vendor
5
+ spec
6
+ docs
7
+ tmp
8
+ coverage
9
+ pkg
10
+ *.gem
11
+ .rspec_status
12
+ AGENTS.md
13
+ CLAUDE.md
data/CHANGELOG.md CHANGED
@@ -4,6 +4,56 @@ 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.4] - 2026-08-01
8
+
9
+ ### Added
10
+ - Project management: create, manage, and switch between projects
11
+ - Claude Sonnet 5 and Opus 5 models with pricing
12
+ - Desktop notifications for background task completion
13
+ - Configurable platform source and branding restoration
14
+ - Hidden flag on create_session API (#403)
15
+ - MiniMax preset refreshed with Anthropic endpoints, M3 thinking, and current lineup (#402)
16
+
17
+ ### Improved
18
+ - File upload size limit increased from 32 MB to 300 MB
19
+ - Dir picker UX: breadcrumb overflow scroll, row spacing, and padding
20
+ - Session-sidebar collapse button appearance matches opener button
21
+
22
+ ### Fixed
23
+ - XLSX parser switched to Python/openpyxl with 60s timeout kill for reliable parsing (C-5656)
24
+ - Panel agents map missing general profile on first load
25
+ - Stale result on first load due to container extension script execution order
26
+ - Page now reloads after deleting local extension in ext-studio
27
+ - Accent color used for extensions filter tab active underline
28
+ - Missing pricing for Claude Sonnet 5 and Opus 5
29
+ - Docker image builds from source and publishes to GHCR on version tags
30
+ - Session list page size reduced from 20 to 15 for better performance
31
+ - MiniMax-M2.5 restored, default base_url reverted to mainland
32
+
33
+ ## [1.5.3] - 2026-07-28
34
+
35
+ ### Added
36
+ - Per-model max output token limits (MODEL_MAX_OUTPUT) applied automatically when sending requests
37
+ - GLM-5.2 added to preset model lineup with pricing table
38
+ - MiMo-V2.5 thinking mode support with max_completion_tokens
39
+ - reasoning_effort mapped to GLM thinking mode and Kimi-K3 thinking levels
40
+ - File-tree save support added to CodeEditor with markdown split-preview panel
41
+ - Unlisted badge and source=brand routing for brand-private extensions
42
+
43
+ ### Improved
44
+ - Markdown preview styles refined: hr, inline images, tables, modal sizing
45
+ - OpenAI client refactored with token_field_for and apply_reasoning_params helpers
46
+
47
+ ### Fixed
48
+ - URL fallback state (base URL) now resets correctly when switching models in quick switch
49
+ - Kimi K3 now uses correct thinking:{type:'enabled'} format for Anthropic client
50
+ - MiMo preset aligned with V2.5 lineup and Token Plan endpoint
51
+ - ext-studio sessions no longer incorrectly tagged as setup sessions (C-5722)
52
+ - localhost URLs no longer trigger OpenClacky failover (find_by_base_url guard)
53
+ - API key toggle icon default and click states corrected
54
+ - Share button on billing page now uses SVG icon instead of emoji
55
+ - Share button hover border color uses accent-primary token
56
+
7
57
  ## [1.5.2] - 2026-07-27
8
58
 
9
59
  ### Added
data/Dockerfile CHANGED
@@ -1,14 +1,34 @@
1
1
  FROM ruby:3.4.4-slim AS builder
2
2
 
3
+ ARG VERSION
4
+
3
5
  RUN apt-get update && apt-get install -y \
4
6
  build-essential \
5
7
  curl \
8
+ git \
6
9
  && rm -rf /var/lib/apt/lists/*
7
10
 
8
- RUN gem install openclacky --no-document
11
+ # Build from repository source so the image matches this checkout (not a published gem).
12
+ # gemspec uses `git ls-files`; seed a temporary git index because .git is dockerignored.
13
+ COPY . /src
14
+ WORKDIR /src
15
+ RUN git init \
16
+ && git config user.email "docker@local" \
17
+ && git config user.name "docker" \
18
+ && git add -A \
19
+ && git commit -m "docker build" \
20
+ && gem build openclacky.gemspec \
21
+ && gem install ./openclacky-*.gem --no-document \
22
+ && ruby -e 'require "clacky"; abort "bad version" unless Clacky::VERSION'
9
23
 
10
24
  FROM ruby:3.4.4-slim
11
25
 
26
+ ARG VERSION
27
+ LABEL org.opencontainers.image.title="openclacky" \
28
+ org.opencontainers.image.description="OpenClacky AI agent CLI and Web UI" \
29
+ org.opencontainers.image.source="https://github.com/clacky-ai/openclacky" \
30
+ org.opencontainers.image.version="${VERSION}"
31
+
12
32
  RUN apt-get update && apt-get install -y \
13
33
  git \
14
34
  curl \
data/README.md CHANGED
@@ -110,18 +110,24 @@ see more: https://www.openclacky.com/docs/installation
110
110
 
111
111
  ### Docker
112
112
 
113
- Build:
113
+ #### Pre-built image (GHCR)
114
+
115
+ Images are published to GitHub Container Registry on version tags (`v*`).
114
116
 
115
117
  ```bash
116
- git clone https://github.com/clacky-ai/openclacky.git
117
- cd openclacky
118
- docker build -t openclacky .
118
+ # Replace <owner> with the repo owner (e.g. clacky-ai or your fork)
119
+ docker pull ghcr.io/<owner>/openclacky:latest
120
+ # or pin a release:
121
+ # docker pull ghcr.io/<owner>/openclacky:1.5.3
119
122
  ```
120
123
 
121
124
  **Linux:**
122
125
 
123
126
  ```bash
124
- docker run -d --network=host -e CLACKY_ACCESS_KEY="" openclacky
127
+ docker run -d --name openclacky --network=host \
128
+ -e CLACKY_ACCESS_KEY="" \
129
+ -v openclacky-data:/root/.clacky \
130
+ ghcr.io/<owner>/openclacky:latest
125
131
  ```
126
132
 
127
133
  `--network=host` is required so the agent inside the container can reach Chrome's remote debugging port running on the host.
@@ -129,18 +135,31 @@ docker run -d --network=host -e CLACKY_ACCESS_KEY="" openclacky
129
135
  **macOS / Windows:**
130
136
 
131
137
  ```bash
132
- docker run -d -p 7070:7070 -e CLACKY_ACCESS_KEY="" openclacky
138
+ docker run -d --name openclacky -p 7070:7070 \
139
+ -e CLACKY_ACCESS_KEY="" \
140
+ -v openclacky-data:/root/.clacky \
141
+ ghcr.io/<owner>/openclacky:latest
133
142
  ```
134
143
 
135
144
  > **Note:** On macOS/Windows, `--network=host` is not supported — browser automation may be limited.
136
145
 
137
146
  Open **http://localhost:7070** after starting.
138
147
 
148
+ #### Build from source
149
+
150
+ ```bash
151
+ git clone https://github.com/clacky-ai/openclacky.git
152
+ cd openclacky
153
+ # optional: set the OCI image version label
154
+ docker build --build-arg VERSION=1.5.3 -t openclacky .
155
+ docker run -d -p 7070:7070 -e CLACKY_ACCESS_KEY="" openclacky
156
+ ```
157
+
139
158
  Environment variables:
140
159
 
141
160
  | Variable | Description |
142
161
  |---|---|
143
- | `CLACKY_ACCESS_KEY` | Protect the Web UI with an access key (empty = public mode) |
162
+ | `CLACKY_ACCESS_KEY` | Protect the Web UI with an access key (empty = public mode; env must be present when binding `0.0.0.0`) |
144
163
 
145
164
 
146
165
  ## Quick Start
data/README_CN.md CHANGED
@@ -106,18 +106,24 @@ gem install openclacky
106
106
 
107
107
  ### Docker
108
108
 
109
- 构建:
109
+ #### 预构建镜像(GHCR)
110
+
111
+ 镜像在版本 tag(`v*`)时自动发布到 GitHub Container Registry。
110
112
 
111
113
  ```bash
112
- git clone https://github.com/clacky-ai/openclacky.git
113
- cd openclacky
114
- docker build -t openclacky .
114
+ # <owner> 替换为仓库所有者(如 clacky-ai 或你的 fork)
115
+ docker pull ghcr.io/<owner>/openclacky:latest
116
+ # 或固定某个版本:
117
+ # docker pull ghcr.io/<owner>/openclacky:1.5.3
115
118
  ```
116
119
 
117
120
  **Linux:**
118
121
 
119
122
  ```bash
120
- docker run -d --network=host -e CLACKY_ACCESS_KEY="" openclacky
123
+ docker run -d --name openclacky --network=host \
124
+ -e CLACKY_ACCESS_KEY="" \
125
+ -v openclacky-data:/root/.clacky \
126
+ ghcr.io/<owner>/openclacky:latest
121
127
  ```
122
128
 
123
129
  `--network=host` 使容器与宿主机共享网络栈,Agent 可直接访问宿主机上运行的 Chrome 远程调试端口。
@@ -125,18 +131,31 @@ docker run -d --network=host -e CLACKY_ACCESS_KEY="" openclacky
125
131
  **macOS / Windows:**
126
132
 
127
133
  ```bash
128
- docker run -d -p 7070:7070 -e CLACKY_ACCESS_KEY="" openclacky
134
+ docker run -d --name openclacky -p 7070:7070 \
135
+ -e CLACKY_ACCESS_KEY="" \
136
+ -v openclacky-data:/root/.clacky \
137
+ ghcr.io/<owner>/openclacky:latest
129
138
  ```
130
139
 
131
140
  > **注意:** macOS/Windows 不支持 `--network=host`,浏览器自动化功能可能受限。
132
141
 
133
142
  启动后访问 **http://localhost:7070**。
134
143
 
144
+ #### 从源码构建
145
+
146
+ ```bash
147
+ git clone https://github.com/clacky-ai/openclacky.git
148
+ cd openclacky
149
+ # 可选:设置 OCI 镜像版本标签
150
+ docker build --build-arg VERSION=1.5.3 -t openclacky .
151
+ docker run -d -p 7070:7070 -e CLACKY_ACCESS_KEY="" openclacky
152
+ ```
153
+
135
154
  环境变量:
136
155
 
137
156
  | 变量 | 说明 |
138
157
  |---|---|
139
- | `CLACKY_ACCESS_KEY` | 设置访问密钥保护 Web UI(留空 = 公开模式) |
158
+ | `CLACKY_ACCESS_KEY` | 设置访问密钥保护 Web UI(留空 = 公开模式;绑定 `0.0.0.0` 时必须设置该环境变量) |
140
159
 
141
160
  ## 快速开始
142
161
 
data/README_JA.md CHANGED
@@ -110,18 +110,24 @@ gem install openclacky
110
110
 
111
111
  ### Docker
112
112
 
113
- ビルド:
113
+ #### ビルド済みイメージ(GHCR)
114
+
115
+ バージョンタグ(`v*`)が付くと GitHub Container Registry にイメージが自動公開されます。
114
116
 
115
117
  ```bash
116
- git clone https://github.com/clacky-ai/openclacky.git
117
- cd openclacky
118
- docker build -t openclacky .
118
+ # <owner> をリポジトリオーナーに置き換えてください(例: clacky-ai またはあなたのフォーク)
119
+ docker pull ghcr.io/<owner>/openclacky:latest
120
+ # またはバージョンを固定する場合:
121
+ # docker pull ghcr.io/<owner>/openclacky:1.5.3
119
122
  ```
120
123
 
121
124
  **Linux:**
122
125
 
123
126
  ```bash
124
- docker run -d --network=host -e CLACKY_ACCESS_KEY="" openclacky
127
+ docker run -d --name openclacky --network=host \
128
+ -e CLACKY_ACCESS_KEY="" \
129
+ -v openclacky-data:/root/.clacky \
130
+ ghcr.io/<owner>/openclacky:latest
125
131
  ```
126
132
 
127
133
  `--network=host` は、コンテナ内のエージェントがホスト上で動作する Chrome のリモートデバッグポートに到達するために必要です。
@@ -129,18 +135,31 @@ docker run -d --network=host -e CLACKY_ACCESS_KEY="" openclacky
129
135
  **macOS / Windows:**
130
136
 
131
137
  ```bash
132
- docker run -d -p 7070:7070 -e CLACKY_ACCESS_KEY="" openclacky
138
+ docker run -d --name openclacky -p 7070:7070 \
139
+ -e CLACKY_ACCESS_KEY="" \
140
+ -v openclacky-data:/root/.clacky \
141
+ ghcr.io/<owner>/openclacky:latest
133
142
  ```
134
143
 
135
144
  > **注意:** macOS/Windows では `--network=host` がサポートされていないため、ブラウザの自動化が制限される場合があります。
136
145
 
137
146
  起動後、**http://localhost:7070** を開いてください。
138
147
 
148
+ #### ソースからビルド
149
+
150
+ ```bash
151
+ git clone https://github.com/clacky-ai/openclacky.git
152
+ cd openclacky
153
+ # オプション: OCI イメージバージョンラベルを設定
154
+ docker build --build-arg VERSION=1.5.3 -t openclacky .
155
+ docker run -d -p 7070:7070 -e CLACKY_ACCESS_KEY="" openclacky
156
+ ```
157
+
139
158
  環境変数:
140
159
 
141
160
  | 変数 | 説明 |
142
161
  |---|---|
143
- | `CLACKY_ACCESS_KEY` | アクセスキーで Web UI を保護します(空の場合はパブリックモード) |
162
+ | `CLACKY_ACCESS_KEY` | アクセスキーで Web UI を保護します(空の場合はパブリックモード;`0.0.0.0` にバインドする場合は必須) |
144
163
 
145
164
 
146
165
  ## クイックスタート
@@ -39,14 +39,10 @@ module Clacky
39
39
 
40
40
  REQUIRED RESPONSE FORMAT:
41
41
  First output a <topics> line listing 3-6 key topic phrases (comma-separated, concise).
42
- Then output a <continues_previous> line: "true" if this conversation is a direct
43
- continuation of the SAME task/topic as the PREVIOUS chunk shown below, "false" if it
44
- has moved on to a different task or topic. If there is no previous chunk, output "false".
45
42
  Then output the full summary wrapped in <summary> tags.
46
43
 
47
44
  Example format:
48
45
  <topics>Rails setup, database config, deploy pipeline, Tailwind CSS</topics>
49
- <continues_previous>false</continues_previous>
50
46
  <summary>
51
47
  ...full summary text...
52
48
  </summary>
@@ -58,8 +54,7 @@ module Clacky
58
54
  - Errors encountered and fixes applied
59
55
  - Current work status and pending tasks
60
56
 
61
- Begin your response NOW. Remember: PURE TEXT only, starting with <topics> then
62
- <continues_previous> then <summary>.
57
+ Begin your response NOW. Remember: PURE TEXT only, starting with <topics> then <summary>.
63
58
  PROMPT
64
59
 
65
60
  def initialize(client, model: nil)
@@ -77,9 +72,6 @@ module Clacky
77
72
  #
78
73
  # @param messages [Array<Hash>] Original conversation messages
79
74
  # @param recent_messages [Array<Hash>] Recent messages to keep uncompressed (optional)
80
- # @param previous_topics [String, nil] Topics of the most recent chunk on disk,
81
- # shown to the LLM so it can decide whether the current conversation is a
82
- # continuation (drives the <continues_previous> output for chunk merging).
83
75
  # @return [Hash] Compression instruction message to insert, or nil if nothing to compress
84
76
  def build_compression_message(messages, recent_messages: [], previous_topics: nil)
85
77
  # Get messages to compress (exclude system message and recent messages)
@@ -88,14 +80,9 @@ module Clacky
88
80
  # If nothing to compress, return nil
89
81
  return nil if messages_to_compress.empty?
90
82
 
91
- content = COMPRESSION_PROMPT
92
- if previous_topics && !previous_topics.strip.empty?
93
- content = "#{COMPRESSION_PROMPT}\n\nPREVIOUS CHUNK TOPICS (for <continues_previous> judgement): #{previous_topics}"
94
- end
95
-
96
83
  {
97
84
  role: "user",
98
- content: content,
85
+ content: COMPRESSION_PROMPT,
99
86
  system_injected: true
100
87
  }
101
88
  end
@@ -154,15 +141,6 @@ module Clacky
154
141
  m ? m[1].strip : nil
155
142
  end
156
143
 
157
- # Parse the <continues_previous> tag. Returns true only when the LLM
158
- # explicitly says "true"; missing tag or any other value → false.
159
- # This conservative default ensures we never merge unless the model is sure.
160
- def parse_continues_previous(content)
161
- return false if content.nil? || content.to_s.empty?
162
- m = content.to_s.match(/<continues_previous>(.*?)<\/continues_previous>/m)
163
- m ? m[1].strip.downcase == "true" : false
164
- end
165
-
166
144
  def parse_compressed_result(result, chunk_path: nil, topics: nil, previous_chunks: [])
167
145
  # Return the compressed result as a single user message (role: "user").
168
146
  #
@@ -192,11 +170,8 @@ module Clacky
192
170
  if content.empty?
193
171
  []
194
172
  else
195
- # Strip out the <topics> and <continues_previous> blocks they're
196
- # metadata for chunk handling, not for AI context.
197
- content_without_topics = content.gsub(/<topics>.*?<\/topics>\n*/m, "")
198
- .gsub(/<continues_previous>.*?<\/continues_previous>\n*/m, "")
199
- .strip
173
+ # Strip out the <topics> block it's metadata for chunk handling, not for AI context.
174
+ content_without_topics = content.gsub(/<topics>.*?<\/topics>\n*/m, "").strip
200
175
 
201
176
  # Build previous chunks index section — links to older chunk files so the AI
202
177
  # can find earlier conversations without keeping all prior compressed_summary
@@ -200,21 +200,19 @@ module Clacky
200
200
  recent_messages = get_recent_messages_with_tool_pairs(all_messages, target_recent_count)
201
201
  recent_messages = [] if recent_messages.nil?
202
202
 
203
- # Surface the most recent chunk's topics so the compression LLM can judge
204
- # whether this conversation continues the same task (drives chunk merging).
205
- previous_topics = nil
206
- if @session_id && @created_at
207
- latest = session_manager.chunks_for_current(@session_id, @created_at).last
208
- previous_topics = latest && latest[:topics]
209
- end
210
-
211
203
  # Build compression instruction message (to be inserted into conversation)
212
204
  compression_message = @message_compressor.build_compression_message(
213
- all_messages, recent_messages: recent_messages, previous_topics: previous_topics
205
+ all_messages, recent_messages: recent_messages
214
206
  )
215
207
 
216
208
  return nil if compression_message.nil?
217
209
 
210
+ # Active (non-forced) compression means we've accumulated enough
211
+ # conversation to hit the normal thresholds — always create a new chunk.
212
+ # Idle / context-overflow compressions (force: true) always merge into
213
+ # the previous chunk to avoid fragmenting small batches.
214
+ force_new_chunk = !force
215
+
218
216
  # Return compression context for agent to handle
219
217
  {
220
218
  compression_message: compression_message,
@@ -222,7 +220,8 @@ module Clacky
222
220
  pulled_back_messages: pulled_back_messages,
223
221
  original_token_count: total_tokens,
224
222
  original_message_count: @history.size,
225
- compression_level: @compression_level
223
+ compression_level: @compression_level,
224
+ force_new_chunk: force_new_chunk
226
225
  }
227
226
  end
228
227
 
@@ -265,12 +264,11 @@ module Clacky
265
264
  topics = @message_compressor.parse_topics(compressed_content)
266
265
 
267
266
  # Decide whether to MERGE into the previous chunk or create a NEW one.
268
- # The LLM judges (via <continues_previous>) whether this conversation is a
269
- # direct continuation of the previous chunk's task. Merging avoids tiny
270
- # fragmented chunks (e.g. a long task compressed mid-flight into 2-message
271
- # chunks) that pollute the topics index and degrade recall.
267
+ # Active compression (force_new_chunk: true) always creates a new chunk.
268
+ # Idle / overflow compression always merges into the previous chunk if
269
+ # one exists, avoiding tiny fragmented chunks that pollute the topics index.
272
270
  latest_chunk = existing_chunks.last
273
- continues = latest_chunk && @message_compressor.parse_continues_previous(compressed_content)
271
+ continues = latest_chunk && !compression_context[:force_new_chunk]
274
272
 
275
273
  if continues
276
274
  chunk_path = merge_into_previous_chunk(
@@ -11,6 +11,7 @@ module Clacky
11
11
  @session_id = session_data[:session_id]
12
12
  @name = session_data[:name] || ""
13
13
  @pinned = session_data[:pinned] || false
14
+ @hidden = session_data[:hidden] || false
14
15
  @history = MessageHistory.new(session_data[:messages] || [])
15
16
  @todos = session_data[:todos] || [] # Restore todos from session
16
17
  @iterations = session_data.dig(:stats, :total_iterations) || 0
@@ -29,6 +30,9 @@ module Clacky
29
30
  # Restore channel info for IM platform sessions
30
31
  @channel_info = session_data[:channel_info]
31
32
 
33
+ # Restore project association (nil = no project)
34
+ @project_id = session_data[:project_id]
35
+
32
36
  # Restore cache statistics if available
33
37
  @cache_stats = session_data.dig(:stats, :cache_stats) || {
34
38
  cache_creation_input_tokens: 0,
@@ -200,6 +204,7 @@ module Clacky
200
204
  session_id: @session_id,
201
205
  name: @name,
202
206
  pinned: @pinned,
207
+ hidden: @hidden,
203
208
  created_at: @created_at,
204
209
  updated_at: (updated_at || (preserve_updated_at && @persisted_updated_at) || Time.now.iso8601).then { |v| v.is_a?(String) ? v : v.iso8601 },
205
210
  working_dir: @working_dir,
@@ -234,6 +239,7 @@ module Clacky
234
239
  sub_model: @config.session_model_overlay_name
235
240
  },
236
241
  channel_info: @channel_info,
242
+ project_id: @project_id,
237
243
  stats: stats_data,
238
244
  messages: @history.to_a
239
245
  }
data/lib/clacky/agent.rb CHANGED
@@ -49,7 +49,9 @@ module Clacky
49
49
  :latest_latency, # Hash of latency metrics from the most recent LLM call (see Client#send_messages_with_tools)
50
50
  :reasoning_effort
51
51
  attr_accessor :pinned
52
+ attr_accessor :hidden
52
53
  attr_accessor :channel_info
54
+ attr_accessor :project_id
53
55
 
54
56
  REASONING_EFFORTS = %w[low medium high xhigh].freeze
55
57
 
@@ -82,6 +84,7 @@ module Clacky
82
84
  @session_id = session_id
83
85
  @name = ""
84
86
  @pinned = false
87
+ @hidden = false
85
88
  @history = MessageHistory.new
86
89
  @todos = [] # Store todos in memory
87
90
  @iterations = 0
@@ -435,8 +438,9 @@ module Clacky
435
438
  lines << "Parse failed: #{parse_error}"
436
439
  if parser_path
437
440
  expected_preview = "#{path}.preview.md"
441
+ interp = Utils::ParserManager.interpreter_for(File.basename(parser_path))
438
442
  lines << "Action required: fix the parser at #{parser_path}, then run:"
439
- lines << " ruby #{parser_path} #{path} > #{expected_preview}"
443
+ lines << " #{interp} #{parser_path} #{path} > #{expected_preview}"
440
444
  lines << "Once done, read #{expected_preview} to continue helping the user."
441
445
  end
442
446
  end
@@ -1005,6 +1009,14 @@ module Clacky
1005
1009
  # Inject working_dir so tools don't rely on Dir.chdir global state
1006
1010
  args[:working_dir] = @working_dir if @working_dir
1007
1011
 
1012
+ # Inject CLACKY_SESSION_ID into every terminal invocation so skills/system
1013
+ # prompts can reliably curl our own HTTP API (e.g. POST /api/ui/show_ext_refresh)
1014
+ # without the AI needing to know or guess the session id. Merges with (and never
1015
+ # overrides) any env the AI explicitly passed.
1016
+ if call[:name] == "terminal" && @session_id
1017
+ args[:env] = { "CLACKY_SESSION_ID" => @session_id.to_s }.merge(args[:env] || {})
1018
+ end
1019
+
1008
1020
  # For terminal: stream live stdout chunks to the UI as they arrive,
1009
1021
  # so the user sees real-time output (e.g. build logs) instead of a
1010
1022
  # blank spinner. The UI buffers lines for Ctrl+O fullscreen view
@@ -165,7 +165,8 @@ module Clacky
165
165
  :memory_update_enabled, :skill_evolution,
166
166
  :max_running_agents, :max_idle_agents,
167
167
  :default_working_dir,
168
- :proxy_url
168
+ :proxy_url,
169
+ :clacky_license_server
169
170
 
170
171
  def initialize(options = {})
171
172
  @permission_mode = validate_permission_mode(options[:permission_mode])
@@ -222,6 +223,7 @@ module Clacky
222
223
  # ignored — set proxy_url here to route Clacky's outbound HTTP through
223
224
  # a proxy. Leave nil to go direct.
224
225
  @proxy_url = options[:proxy_url]
226
+ @clacky_license_server = options[:clacky_license_server]
225
227
 
226
228
  # Per-session virtual model overlay.
227
229
  # When set, #current_model returns a *merged* hash (the resolved @models
@@ -414,7 +416,7 @@ module Clacky
414
416
  memory_update_enabled
415
417
  skill_evolution max_running_agents max_idle_agents
416
418
  default_working_dir
417
- proxy_url
419
+ proxy_url clacky_license_server
418
420
  ].freeze
419
421
 
420
422
  # Serialize the current agent configuration to YAML.
@@ -434,7 +436,8 @@ module Clacky
434
436
  "max_running_agents" => @max_running_agents,
435
437
  "max_idle_agents" => @max_idle_agents,
436
438
  "default_working_dir" => @default_working_dir,
437
- "proxy_url" => @proxy_url
439
+ "proxy_url" => @proxy_url,
440
+ "clacky_license_server" => @clacky_license_server
438
441
  }
439
442
  YAML.dump("settings" => settings, "models" => persistable_models)
440
443
  end
@@ -476,7 +479,13 @@ module Clacky
476
479
  @current_model_id = id
477
480
  @current_model_index = index
478
481
 
479
- @session_model_overlay = nil if previous_id != id
482
+ if previous_id != id
483
+ @session_model_overlay = nil
484
+ # Reset URL fallback when switching models — the new model has its own
485
+ # base_url and the old fallback endpoint is irrelevant (or even wrong).
486
+ @url_fallback_active = false
487
+ @url_fallback_base_url = nil
488
+ end
480
489
 
481
490
  true
482
491
  end
@@ -492,9 +501,16 @@ module Clacky
492
501
  index = @models.find_index { |m| m["model"].to_s.downcase == name_str }
493
502
  return false if index.nil?
494
503
 
504
+ previous_id = @current_model_id
495
505
  @current_model_id = @models[index]["id"]
496
506
  @current_model_index = index
497
507
 
508
+ if previous_id != @current_model_id
509
+ # Reset URL fallback when switching models — same rationale as switch_model_by_id.
510
+ @url_fallback_active = false
511
+ @url_fallback_base_url = nil
512
+ end
513
+
498
514
  true
499
515
  end
500
516
 
@@ -1061,19 +1077,34 @@ module Clacky
1061
1077
  # probe or reset it automatically, keeping the logic simple.
1062
1078
 
1063
1079
  # Look up the fallback base URL for the current model's provider.
1064
- # Returns nil if the provider has no secondary gateway configured.
1080
+ # Returns nil if:
1081
+ # - provider cannot be determined
1082
+ # - provider is not openclacky (URL failover is only supported for the
1083
+ # OpenClacky gateway; other providers have no secondary endpoint)
1084
+ # - current base_url is already the fallback URL (user picked the
1085
+ # Secondary/China node directly — switching to the same URL does nothing)
1065
1086
  # @return [String, nil]
1066
1087
  def fallback_base_url_for_current_provider
1067
1088
  m = current_model
1068
1089
  return nil unless m
1069
1090
 
1070
- provider_id = Clacky::Providers.resolve_provider(
1071
- base_url: m["base_url"],
1072
- api_key: m["api_key"]
1073
- )
1074
- return nil unless provider_id
1091
+ # Use strict URL matching to identify the provider — no inference.
1092
+ # resolve_provider would incorrectly return "openclacky" for localhost
1093
+ # addresses and clacky-* api keys, triggering unwanted failover.
1094
+ # find_by_base_url only matches registered preset URLs, so localhost
1095
+ # and other non-openclacky endpoints correctly return nil.
1096
+ provider_id = Clacky::Providers.find_by_base_url(m["base_url"])
1097
+ return nil unless provider_id == "openclacky"
1098
+
1099
+ fallback_url = Clacky::Providers.fallback_base_url(provider_id)
1100
+ return nil unless fallback_url
1101
+
1102
+ # Don't offer a fallback when the current base_url is already the
1103
+ # fallback endpoint — switching to the same URL accomplishes nothing.
1104
+ current_base = m["base_url"].to_s.chomp("/")
1105
+ return nil if current_base == fallback_url.to_s.chomp("/")
1075
1106
 
1076
- Clacky::Providers.fallback_base_url(provider_id)
1107
+ fallback_url
1077
1108
  end
1078
1109
 
1079
1110
  # Activate the URL fallback: override base_url with the secondary gateway.