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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 10e414552c9ae0e3bb7a0b547f2e4da0709c83e0a0925f3b708820848e1a4cf3
|
|
4
|
+
data.tar.gz: 6caa29ccd3048e95912500be99000ae7ca883c9a5ad473ebc1bb2f243aef5cc3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 26f220973c6821a411362df35ca281ef53ae58e6ab36d26651db393b31713340ca9bcf131f69bf4e6aa391eec3f4f21fc7d0d12cb4bbbdc368b6fe8068fa2dfa
|
|
7
|
+
data.tar.gz: 84faf59b60486d29a9168f847c97d7fd5670dac293f967bd713c08049612adab7f947fa38af3a7e89c4a5acc2cc8c460e9b8ebbf53c189c19a52bf7e89a44388
|
data/.dockerignore
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ 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
|
+
|
|
7
33
|
## [1.5.3] - 2026-07-28
|
|
8
34
|
|
|
9
35
|
### 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
|
-
|
|
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
|
-
|
|
113
|
+
#### Pre-built image (GHCR)
|
|
114
|
+
|
|
115
|
+
Images are published to GitHub Container Registry on version tags (`v*`).
|
|
114
116
|
|
|
115
117
|
```bash
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
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
|
|
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:
|
|
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>
|
|
196
|
-
|
|
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
|
|
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
|
-
#
|
|
269
|
-
#
|
|
270
|
-
#
|
|
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 &&
|
|
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 << "
|
|
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
|
data/lib/clacky/agent_config.rb
CHANGED
|
@@ -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
|
data/lib/clacky/brand_config.rb
CHANGED
|
@@ -383,16 +383,16 @@ module Clacky
|
|
|
383
383
|
|
|
384
384
|
# Returns true when a public distribution refresh is due.
|
|
385
385
|
#
|
|
386
|
-
# Refresh is needed
|
|
387
|
-
#
|
|
388
|
-
# distribution data via #heartbeat
|
|
386
|
+
# Refresh is needed for unactivated branded installs and for private
|
|
387
|
+
# platform sources that have not supplied their deployment brand yet.
|
|
388
|
+
# Activated licenses already get fresh distribution data via #heartbeat!.
|
|
389
389
|
#
|
|
390
390
|
# Rate limit: once every HEARTBEAT_INTERVAL (24h), measured from the last
|
|
391
391
|
# SUCCESSFUL refresh. A failed refresh does not advance the clock so we'll
|
|
392
392
|
# keep trying on subsequent startups / status polls.
|
|
393
393
|
def distribution_refresh_due?
|
|
394
|
-
return false unless branded?
|
|
395
394
|
return false if activated?
|
|
395
|
+
return false unless branded? || private_platform_source?
|
|
396
396
|
return true if @distribution_last_refreshed_at.nil?
|
|
397
397
|
|
|
398
398
|
elapsed = Time.now.utc - @distribution_last_refreshed_at
|
|
@@ -400,7 +400,8 @@ module Clacky
|
|
|
400
400
|
end
|
|
401
401
|
|
|
402
402
|
# Refresh public brand assets (logo, theme, homepage_url, support_*) for
|
|
403
|
-
# installs
|
|
403
|
+
# unactivated installs. Package installs use an exact package lookup;
|
|
404
|
+
# private platform sources without a local brand use the source default.
|
|
404
405
|
#
|
|
405
406
|
# Motivation: `install.sh --brand-name=X --command=X` only writes
|
|
406
407
|
# product_name + package_name to brand.yml. The rest of the distribution
|
|
@@ -409,32 +410,47 @@ module Clacky
|
|
|
409
410
|
# anonymous public lookup endpoint.
|
|
410
411
|
#
|
|
411
412
|
# Behaviour:
|
|
412
|
-
# * No-op
|
|
413
|
-
#
|
|
413
|
+
# * No-op when already activated, or when neither a package_name nor a
|
|
414
|
+
# private platform source is available.
|
|
414
415
|
# * On success: apply_distribution + save + stamp
|
|
415
416
|
# @distribution_last_refreshed_at.
|
|
417
|
+
# * When a block is provided, discard the response unless it still
|
|
418
|
+
# confirms that the platform source is current.
|
|
416
419
|
# * On failure: log and return without touching the timestamp (so we
|
|
417
420
|
# retry on next trigger).
|
|
418
421
|
#
|
|
419
422
|
# Returns { success: Boolean, message: String }.
|
|
420
423
|
def refresh_distribution!
|
|
421
|
-
|
|
424
|
+
source_brand_lookup = private_platform_source? &&
|
|
425
|
+
(@package_name.nil? || @package_name.strip.empty?)
|
|
426
|
+
unless branded? || source_brand_lookup
|
|
422
427
|
return { success: false, message: "Not branded" }
|
|
423
428
|
end
|
|
424
429
|
if activated?
|
|
425
430
|
return { success: false, message: "License activated — use heartbeat! instead" }
|
|
426
431
|
end
|
|
427
|
-
if @package_name.nil? || @package_name.strip.empty?
|
|
432
|
+
if !source_brand_lookup && (@package_name.nil? || @package_name.strip.empty?)
|
|
428
433
|
return { success: false, message: "package_name not configured" }
|
|
429
434
|
end
|
|
430
435
|
|
|
431
|
-
|
|
432
|
-
|
|
436
|
+
if source_brand_lookup
|
|
437
|
+
path = "/api/v1/distributions/lookup"
|
|
438
|
+
else
|
|
439
|
+
encoded_pkg = URI.encode_www_form_component(@package_name.strip)
|
|
440
|
+
path = "/api/v1/distributions/lookup?package_name=#{encoded_pkg}"
|
|
441
|
+
end
|
|
433
442
|
|
|
434
|
-
Clacky::Logger.info(
|
|
443
|
+
Clacky::Logger.info(
|
|
444
|
+
"[Brand] refresh_distribution! fetching — package_name=#{@package_name || "source-default"}"
|
|
445
|
+
)
|
|
435
446
|
response = platform_client.get(path)
|
|
436
447
|
|
|
437
448
|
if response[:success] && response[:data].is_a?(Hash) && response[:data]["distribution"].is_a?(Hash)
|
|
449
|
+
if block_given? && !yield
|
|
450
|
+
Clacky::Logger.info("[Brand] refresh_distribution! discarded — platform source changed")
|
|
451
|
+
return { success: false, message: "Platform source changed during refresh" }
|
|
452
|
+
end
|
|
453
|
+
|
|
438
454
|
apply_distribution(response[:data]["distribution"])
|
|
439
455
|
@distribution_last_refreshed_at = Time.now.utc
|
|
440
456
|
save
|
|
@@ -1762,6 +1778,10 @@ module Clacky
|
|
|
1762
1778
|
@homepage_url = dist["homepage_url"] if dist.key?("homepage_url")
|
|
1763
1779
|
end
|
|
1764
1780
|
|
|
1781
|
+
private def private_platform_source?
|
|
1782
|
+
!ENV["CLACKY_LICENSE_SERVER"].to_s.strip.empty?
|
|
1783
|
+
end
|
|
1784
|
+
|
|
1765
1785
|
# Download a remote URL to a local file path.
|
|
1766
1786
|
#
|
|
1767
1787
|
# Deprecated: this method now delegates to
|
|
@@ -143,3 +143,18 @@ main product, and apply every rule below whenever you propose or write code:
|
|
|
143
143
|
Ruby and carry supply-chain risk.
|
|
144
144
|
- After editing `view.js`, `handler.rb`, or a `SKILL.md`, tell the user to reload the
|
|
145
145
|
WebUI page — hot reload is per-request, no restart needed.
|
|
146
|
+
- After you finish editing extension files, call this once at the very end to trigger
|
|
147
|
+
a reload button in the UI (if it doesn't appear, the user can manually refresh the browser):
|
|
148
|
+
```
|
|
149
|
+
curl -s --noproxy '*' -X POST "http://${CLACKY_SERVER_HOST}:${CLACKY_SERVER_PORT}/api/ui/show_ext_refresh" \
|
|
150
|
+
-H "Content-Type: application/json" \
|
|
151
|
+
-d "{\"session_id\": \"${CLACKY_SESSION_ID}\"}"
|
|
152
|
+
```
|
|
153
|
+
(`--noproxy '*'` prevents shell proxy env vars from silently intercepting this loopback call.)
|
|
154
|
+
- If the extension contributes a `session.aside` panel, also call this right after the above
|
|
155
|
+
to open the panel automatically:
|
|
156
|
+
```
|
|
157
|
+
curl -s --noproxy '*' -X POST "http://${CLACKY_SERVER_HOST}:${CLACKY_SERVER_PORT}/api/ui/open_aside" \
|
|
158
|
+
-H "Content-Type: application/json" \
|
|
159
|
+
-d "{\"session_id\": \"${CLACKY_SESSION_ID}\"}"
|
|
160
|
+
```
|