openclacky 1.0.4 → 1.1.0
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/.clacky/skills/gem-release/SKILL.md +99 -356
- data/.clacky/skills/gem-release/scripts/release.sh +304 -0
- data/CHANGELOG.md +42 -0
- data/docs/system-skill-authoring-guide.md +1 -1
- data/lib/clacky/agent/tool_executor.rb +3 -1
- data/lib/clacky/agent.rb +12 -7
- data/lib/clacky/agent_config.rb +9 -3
- data/lib/clacky/brand_config.rb +19 -4
- data/lib/clacky/cli.rb +1 -1
- data/lib/clacky/default_skills/{channel-setup → channel-manager}/SKILL.md +180 -18
- data/lib/clacky/default_skills/channel-manager/dingtalk_setup.rb +191 -0
- data/lib/clacky/default_skills/channel-manager/discord_setup.rb +199 -0
- data/lib/clacky/default_skills/channel-manager/install_feishu_skills.rb +105 -0
- data/lib/clacky/default_skills/onboard/SKILL.md +2 -2
- data/lib/clacky/default_skills/onboard/scripts/import_external_skills.rb +2 -4
- data/lib/clacky/default_skills/onboard/scripts/install_builtin_skills.rb +18 -96
- data/lib/clacky/default_skills/product-help/SKILL.md +10 -2
- data/lib/clacky/message_history.rb +26 -1
- data/lib/clacky/providers.rb +29 -4
- data/lib/clacky/server/channel/adapters/dingtalk/adapter.rb +177 -0
- data/lib/clacky/server/channel/adapters/dingtalk/api_client.rb +82 -0
- data/lib/clacky/server/channel/adapters/dingtalk/stream_client.rb +205 -0
- data/lib/clacky/server/channel/adapters/discord/adapter.rb +229 -0
- data/lib/clacky/server/channel/adapters/discord/api_client.rb +108 -0
- data/lib/clacky/server/channel/adapters/discord/gateway_client.rb +272 -0
- data/lib/clacky/server/channel/adapters/telegram/adapter.rb +375 -0
- data/lib/clacky/server/channel/adapters/telegram/api_client.rb +205 -0
- data/lib/clacky/server/channel/channel_config.rb +26 -0
- data/lib/clacky/server/channel.rb +3 -0
- data/lib/clacky/server/http_server.rb +75 -4
- data/lib/clacky/server/server_master.rb +35 -13
- data/lib/clacky/server/session_registry.rb +54 -3
- data/lib/clacky/server/web_ui_controller.rb +7 -1
- data/lib/clacky/telemetry.rb +1 -16
- data/lib/clacky/tools/browser.rb +8 -5
- data/lib/clacky/tools/glob.rb +11 -38
- data/lib/clacky/tools/grep.rb +7 -16
- data/lib/clacky/ui2/markdown_renderer.rb +1 -1
- data/lib/clacky/ui2/ui_controller.rb +2 -1
- data/lib/clacky/utils/file_ignore_helper.rb +49 -0
- data/lib/clacky/utils/gitignore_parser.rb +27 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +248 -31
- data/lib/clacky/web/app.js +51 -1
- data/lib/clacky/web/channels.js +98 -28
- data/lib/clacky/web/datepicker.js +205 -0
- data/lib/clacky/web/i18n.js +48 -9
- data/lib/clacky/web/index.html +33 -6
- data/lib/clacky/web/onboard.js +46 -4
- data/lib/clacky/web/sessions.js +33 -72
- data/lib/clacky/web/settings.js +42 -4
- data/lib/clacky/web/version.js +52 -1
- metadata +21 -10
- data/docs/proposals/2026-05-11-system-prompt-alignment.md +0 -325
- data/docs/proposals/2026-05-12-memory-mechanism-optimization.md +0 -89
- /data/lib/clacky/default_skills/{channel-setup → channel-manager}/feishu_setup.rb +0 -0
- /data/lib/clacky/default_skills/{channel-setup → channel-manager}/import_lark_skills.rb +0 -0
- /data/lib/clacky/default_skills/{channel-setup → channel-manager}/weixin_setup.rb +0 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# release.sh — openclacky gem release automation
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# bash release.sh <version> [--prerelease] [--update-latest]
|
|
6
|
+
#
|
|
7
|
+
# Examples:
|
|
8
|
+
# bash release.sh 1.0.6 # stable release
|
|
9
|
+
# bash release.sh 1.0.6 --dry-run # preview without executing
|
|
10
|
+
# bash release.sh 2.0.0.beta.1 --prerelease # pre-release, skip latest.txt
|
|
11
|
+
# bash release.sh 2.0.0.rc1 --prerelease --update-latest # pre-release, update latest.txt
|
|
12
|
+
#
|
|
13
|
+
# Prerequisites:
|
|
14
|
+
# - gh CLI installed and authenticated
|
|
15
|
+
# - coscli installed at /usr/local/bin/coscli with ~/.cos.yaml
|
|
16
|
+
# - RubyGems credentials configured (gem push)
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
# ── Colors ──────────────────────────────────────────────────────────────
|
|
21
|
+
RED='\033[0;31m'
|
|
22
|
+
GREEN='\033[0;32m'
|
|
23
|
+
YELLOW='\033[1;33m'
|
|
24
|
+
BLUE='\033[0;34m'
|
|
25
|
+
CYAN='\033[0;36m'
|
|
26
|
+
NC='\033[0m'
|
|
27
|
+
|
|
28
|
+
info() { echo -e "${BLUE}ℹ${NC} $1"; }
|
|
29
|
+
success() { echo -e "${GREEN}✓${NC} $1"; }
|
|
30
|
+
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
|
|
31
|
+
error() { echo -e "${RED}✗${NC} $1" >&2; }
|
|
32
|
+
step() { echo -e "\n${CYAN}▶ Step $1:${NC} $2\n"; }
|
|
33
|
+
die() { error "$1"; exit 1; }
|
|
34
|
+
|
|
35
|
+
# ── Parse args ──────────────────────────────────────────────────────────
|
|
36
|
+
VERSION=""
|
|
37
|
+
PRERELEASE=false
|
|
38
|
+
UPDATE_LATEST=true
|
|
39
|
+
DRY_RUN=false
|
|
40
|
+
|
|
41
|
+
while [[ $# -gt 0 ]]; do
|
|
42
|
+
case "$1" in
|
|
43
|
+
--prerelease) PRERELEASE=true; UPDATE_LATEST=false ;;
|
|
44
|
+
--update-latest) UPDATE_LATEST=true ;;
|
|
45
|
+
--dry-run) DRY_RUN=true ;;
|
|
46
|
+
--help|-h)
|
|
47
|
+
echo "Usage: bash release.sh <version> [--prerelease] [--update-latest] [--dry-run]"
|
|
48
|
+
exit 0
|
|
49
|
+
;;
|
|
50
|
+
-*) die "Unknown option: $1" ;;
|
|
51
|
+
*) VERSION="$1" ;;
|
|
52
|
+
esac
|
|
53
|
+
shift
|
|
54
|
+
done
|
|
55
|
+
|
|
56
|
+
[[ -z "$VERSION" ]] && die "Version argument required. Usage: bash release.sh <version>"
|
|
57
|
+
|
|
58
|
+
# ── Resolve paths ───────────────────────────────────────────────────────
|
|
59
|
+
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || die "Not inside a git repository"
|
|
60
|
+
cd "$REPO_ROOT"
|
|
61
|
+
|
|
62
|
+
VERSION_FILE="lib/clacky/version.rb"
|
|
63
|
+
GEMSPEC="openclacky.gemspec"
|
|
64
|
+
CHANGELOG="CHANGELOG.md"
|
|
65
|
+
GEM_FILE="openclacky-${VERSION}.gem"
|
|
66
|
+
OSS_BUCKET="cos://clackyai-1258723534"
|
|
67
|
+
|
|
68
|
+
[[ -f "$VERSION_FILE" ]] || die "Version file not found: $VERSION_FILE"
|
|
69
|
+
[[ -f "$GEMSPEC" ]] || die "Gemspec not found: $GEMSPEC"
|
|
70
|
+
|
|
71
|
+
CURRENT_VERSION=$(ruby -ne 'puts $1 if /VERSION\s*=\s*"([^"]+)"/' "$VERSION_FILE")
|
|
72
|
+
info "Current version: ${CURRENT_VERSION}"
|
|
73
|
+
info "Target version: ${VERSION}"
|
|
74
|
+
info "Pre-release: ${PRERELEASE}"
|
|
75
|
+
info "Update latest: ${UPDATE_LATEST}"
|
|
76
|
+
info "Dry run: ${DRY_RUN}"
|
|
77
|
+
echo ""
|
|
78
|
+
|
|
79
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
80
|
+
warn "DRY RUN mode — no changes will be made"
|
|
81
|
+
echo ""
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# ── Helper: run or preview ──────────────────────────────────────────────
|
|
85
|
+
run() {
|
|
86
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
87
|
+
echo -e " ${YELLOW}[dry-run]${NC} $*"
|
|
88
|
+
else
|
|
89
|
+
"$@"
|
|
90
|
+
fi
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
94
|
+
# Step 1: Pre-release checks
|
|
95
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
96
|
+
step 1 "Pre-release checks"
|
|
97
|
+
|
|
98
|
+
if [[ -n "$(git status --porcelain)" ]]; then
|
|
99
|
+
die "Working directory is not clean. Commit or stash changes first."
|
|
100
|
+
fi
|
|
101
|
+
success "Working directory is clean"
|
|
102
|
+
|
|
103
|
+
BRANCH=$(git branch --show-current)
|
|
104
|
+
if [[ "$BRANCH" != "main" ]]; then
|
|
105
|
+
warn "Not on main branch (currently on '${BRANCH}')"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
command -v gh >/dev/null 2>&1 || die "gh CLI not found. Install with: brew install gh"
|
|
109
|
+
command -v coscli >/dev/null 2>&1 || die "coscli not found. Install at /usr/local/bin/coscli"
|
|
110
|
+
success "Required tools available (gh, coscli)"
|
|
111
|
+
|
|
112
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
113
|
+
# Step 2: Run tests
|
|
114
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
115
|
+
step 2 "Running test suite"
|
|
116
|
+
|
|
117
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
118
|
+
echo -e " ${YELLOW}[dry-run]${NC} bundle exec rspec"
|
|
119
|
+
else
|
|
120
|
+
bundle exec rspec || die "Tests failed — aborting release"
|
|
121
|
+
fi
|
|
122
|
+
success "All tests passed"
|
|
123
|
+
|
|
124
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
125
|
+
# Step 3: Bump version
|
|
126
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
127
|
+
step 3 "Bumping version to ${VERSION}"
|
|
128
|
+
|
|
129
|
+
run sed -i '' "s/VERSION = \"${CURRENT_VERSION}\"/VERSION = \"${VERSION}\"/" "$VERSION_FILE"
|
|
130
|
+
|
|
131
|
+
if [[ "$DRY_RUN" != true ]]; then
|
|
132
|
+
grep -q "VERSION = \"${VERSION}\"" "$VERSION_FILE" || die "Version bump failed"
|
|
133
|
+
fi
|
|
134
|
+
success "Updated ${VERSION_FILE}"
|
|
135
|
+
|
|
136
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
137
|
+
# Step 4: Update Gemfile.lock
|
|
138
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
139
|
+
step 4 "Updating Gemfile.lock"
|
|
140
|
+
|
|
141
|
+
run bundle install --quiet
|
|
142
|
+
success "Gemfile.lock updated"
|
|
143
|
+
|
|
144
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
145
|
+
# Step 5: Commit version bump
|
|
146
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
147
|
+
step 5 "Committing version bump"
|
|
148
|
+
|
|
149
|
+
run git add "$VERSION_FILE" Gemfile.lock
|
|
150
|
+
run git commit -m "chore: bump version to ${VERSION}"
|
|
151
|
+
success "Version bump committed"
|
|
152
|
+
|
|
153
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
154
|
+
# Step 6: Push and wait for CI
|
|
155
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
156
|
+
step 6 "Pushing to origin and checking CI"
|
|
157
|
+
|
|
158
|
+
run git push origin "$BRANCH"
|
|
159
|
+
success "Pushed to origin/${BRANCH}"
|
|
160
|
+
|
|
161
|
+
if [[ "$DRY_RUN" != true ]]; then
|
|
162
|
+
info "Waiting for CI to complete (this may take a few minutes)..."
|
|
163
|
+
if gh run list --branch "$BRANCH" --limit 1 --json status -q '.[0].status' 2>/dev/null | grep -q "completed"; then
|
|
164
|
+
success "CI already completed"
|
|
165
|
+
else
|
|
166
|
+
gh run watch --exit-status 2>/dev/null || warn "Could not watch CI run — verify manually at GitHub Actions"
|
|
167
|
+
fi
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
171
|
+
# Step 7: Build gem
|
|
172
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
173
|
+
step 7 "Building gem"
|
|
174
|
+
|
|
175
|
+
run gem build "$GEMSPEC"
|
|
176
|
+
|
|
177
|
+
if [[ "$DRY_RUN" != true ]]; then
|
|
178
|
+
[[ -f "$GEM_FILE" ]] || die "Gem file not found: $GEM_FILE"
|
|
179
|
+
fi
|
|
180
|
+
success "Built ${GEM_FILE}"
|
|
181
|
+
|
|
182
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
183
|
+
# Step 8: Publish to RubyGems
|
|
184
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
185
|
+
step 8 "Publishing to RubyGems"
|
|
186
|
+
|
|
187
|
+
run gem push "$GEM_FILE"
|
|
188
|
+
success "Published to RubyGems"
|
|
189
|
+
|
|
190
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
191
|
+
# Step 9: Git tag
|
|
192
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
193
|
+
step 9 "Creating git tag v${VERSION}"
|
|
194
|
+
|
|
195
|
+
run git tag "v${VERSION}"
|
|
196
|
+
run git push origin --tags
|
|
197
|
+
success "Tag v${VERSION} pushed"
|
|
198
|
+
|
|
199
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
200
|
+
# Step 10: GitHub Release
|
|
201
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
202
|
+
step 10 "Creating GitHub Release"
|
|
203
|
+
|
|
204
|
+
RELEASE_NOTES_FILE="/tmp/release_notes_${VERSION}.md"
|
|
205
|
+
|
|
206
|
+
if [[ "$DRY_RUN" != true ]]; then
|
|
207
|
+
if [[ -f "$CHANGELOG" ]]; then
|
|
208
|
+
# Extract section for this version from CHANGELOG
|
|
209
|
+
awk "/^## \\[${VERSION}\\]/{found=1; next} /^## \\[/{if(found) exit} found{print}" "$CHANGELOG" > "$RELEASE_NOTES_FILE"
|
|
210
|
+
if [[ ! -s "$RELEASE_NOTES_FILE" ]]; then
|
|
211
|
+
echo "Release v${VERSION}" > "$RELEASE_NOTES_FILE"
|
|
212
|
+
warn "No CHANGELOG entry found for ${VERSION} — using placeholder"
|
|
213
|
+
fi
|
|
214
|
+
else
|
|
215
|
+
echo "Release v${VERSION}" > "$RELEASE_NOTES_FILE"
|
|
216
|
+
warn "CHANGELOG.md not found — using placeholder"
|
|
217
|
+
fi
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
if [[ "$PRERELEASE" == true ]]; then
|
|
221
|
+
run gh release create "v${VERSION}" \
|
|
222
|
+
--title "v${VERSION}" \
|
|
223
|
+
--notes-file "$RELEASE_NOTES_FILE" \
|
|
224
|
+
--prerelease \
|
|
225
|
+
"$GEM_FILE"
|
|
226
|
+
else
|
|
227
|
+
run gh release create "v${VERSION}" \
|
|
228
|
+
--title "v${VERSION}" \
|
|
229
|
+
--notes-file "$RELEASE_NOTES_FILE" \
|
|
230
|
+
--latest \
|
|
231
|
+
"$GEM_FILE"
|
|
232
|
+
fi
|
|
233
|
+
success "GitHub Release created"
|
|
234
|
+
|
|
235
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
236
|
+
# Step 11: Upload to OSS CDN
|
|
237
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
238
|
+
step 11 "Syncing to Tencent Cloud OSS"
|
|
239
|
+
|
|
240
|
+
run coscli cp "$GEM_FILE" "${OSS_BUCKET}/openclacky/${GEM_FILE}"
|
|
241
|
+
success "Uploaded ${GEM_FILE} to OSS"
|
|
242
|
+
|
|
243
|
+
if [[ "$UPDATE_LATEST" == true ]]; then
|
|
244
|
+
if [[ "$DRY_RUN" != true ]]; then
|
|
245
|
+
echo "${VERSION}" > /tmp/latest.txt
|
|
246
|
+
fi
|
|
247
|
+
run coscli cp /tmp/latest.txt "${OSS_BUCKET}/openclacky/latest.txt"
|
|
248
|
+
success "Updated latest.txt → ${VERSION}"
|
|
249
|
+
|
|
250
|
+
if [[ "$DRY_RUN" != true ]]; then
|
|
251
|
+
VERIFY=$(curl -fsSL https://oss.1024code.com/openclacky/latest.txt 2>/dev/null || echo "FAILED")
|
|
252
|
+
if [[ "$VERIFY" == "$VERSION" ]]; then
|
|
253
|
+
success "Verified latest.txt = ${VERSION}"
|
|
254
|
+
else
|
|
255
|
+
warn "latest.txt verification returned: ${VERIFY}"
|
|
256
|
+
fi
|
|
257
|
+
fi
|
|
258
|
+
else
|
|
259
|
+
info "Skipping latest.txt update (pre-release or not requested)"
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
263
|
+
# Step 12: Sync scripts to OSS
|
|
264
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
265
|
+
step 12 "Rebuilding and syncing scripts to OSS"
|
|
266
|
+
|
|
267
|
+
run bash scripts/build/build.sh
|
|
268
|
+
|
|
269
|
+
if [[ "$DRY_RUN" != true ]]; then
|
|
270
|
+
for script in scripts/*.sh scripts/*.ps1; do
|
|
271
|
+
[[ -f "$script" ]] || continue
|
|
272
|
+
coscli cp "$script" "${OSS_BUCKET}/clacky-ai/openclacky/main/scripts/$(basename "$script")"
|
|
273
|
+
success "Uploaded $(basename "$script")"
|
|
274
|
+
done
|
|
275
|
+
else
|
|
276
|
+
echo -e " ${YELLOW}[dry-run]${NC} Upload scripts/*.sh and scripts/*.ps1 to OSS"
|
|
277
|
+
fi
|
|
278
|
+
success "Scripts synced to OSS"
|
|
279
|
+
|
|
280
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
281
|
+
# Step 13: Cleanup
|
|
282
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
283
|
+
step 13 "Cleanup"
|
|
284
|
+
|
|
285
|
+
[[ -f "$GEM_FILE" ]] && rm -f "$GEM_FILE" && info "Removed ${GEM_FILE}"
|
|
286
|
+
[[ -f "$RELEASE_NOTES_FILE" ]] && rm -f "$RELEASE_NOTES_FILE"
|
|
287
|
+
[[ -f "/tmp/latest.txt" ]] && rm -f /tmp/latest.txt
|
|
288
|
+
|
|
289
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
290
|
+
# Done
|
|
291
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
292
|
+
echo ""
|
|
293
|
+
echo "╔═══════════════════════════════════════════════════════════╗"
|
|
294
|
+
echo "║ ║"
|
|
295
|
+
echo -e "║ ${GREEN}🎉 v${VERSION} released successfully!${NC} ║"
|
|
296
|
+
echo "║ ║"
|
|
297
|
+
echo "╠═══════════════════════════════════════════════════════════╣"
|
|
298
|
+
echo "║ ║"
|
|
299
|
+
echo "║ 📦 RubyGems: rubygems.org/gems/openclacky ║"
|
|
300
|
+
echo "║ 🏷️ GitHub: github.com/clacky-ai/openclacky/releases ║"
|
|
301
|
+
echo "║ ☁️ OSS CDN: oss.1024code.com/openclacky/ ║"
|
|
302
|
+
echo "║ ║"
|
|
303
|
+
echo "╚═══════════════════════════════════════════════════════════╝"
|
|
304
|
+
echo ""
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,48 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.0] - 2026-05-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **DingTalk channel adapter.** New IM channel adapter connects openclacky to DingTalk via Stream Mode WebSocket. Includes DingTalk API client for text/markdown messages, Device Flow QR setup script, and full Web UI integration with channel config, HTTP server routes, and i18n strings. (#112)
|
|
12
|
+
- **Feishu channel-manager skill setup & onboard improvements.** Channel-manager now includes a dedicated Feishu skills installation flow (`install_feishu_skills.rb`) and updated setup instructions. Skill installation is serialized for reliability. (#122)
|
|
13
|
+
- **Custom datepicker component with i18n support.** New reusable datepicker component with CSS variable theming and full English/Chinese localization, replacing browser-native date inputs. (#119)
|
|
14
|
+
- **Rename sessions via modal dialog.** Session rename now uses a proper modal dialog with i18n support instead of inline editing, for a cleaner UX. (#113)
|
|
15
|
+
- **Channel enable/disable toggle.** Configured channels can now be individually enabled or disabled from the Channels page without removing credentials. Distinguishes "disabled" from "not configured" in badge and hint text. (#108)
|
|
16
|
+
- **Provider promo hint for OpenClacky.** When OpenClacky is selected as provider, a contextual promo hint appears below the dropdown on both settings and onboarding pages, with dark mode support and localized copy. (#109)
|
|
17
|
+
- **Running config for concurrent agent limits.** New `AgentConfig` running configuration and `SessionRegistry` concurrency controls to limit the number of simultaneously active agents, preventing resource exhaustion on busy servers.
|
|
18
|
+
|
|
19
|
+
### Improved
|
|
20
|
+
- **Channel page and sidebar nav polish.** Visual refinements to the Channels page layout and sidebar navigation styling.
|
|
21
|
+
- **Telegram group chat skill guidance.** Channel-setup skill now clarifies Privacy Mode requirements for Telegram group chats, preventing common misconfiguration. (#117)
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **Channel skill trigger matching.** Renamed `channel-setup` to `channel-manager` so the agent's send-message intent matches the correct skill more reliably. (C-5584, #120)
|
|
25
|
+
- **Markdown image overflow in chat bubbles.** Images in assistant messages are now width-constrained to fit within the message bubble instead of overflowing. (C-5585, #118)
|
|
26
|
+
- **Channel image rewriting scoped to Web UI.** Local image URL rewriting is now applied only in the Web UI context; IM channel messages use the file basename as attachment name instead. (C-5590, #115)
|
|
27
|
+
- **Discord file upload.** Added multipart middleware to the Discord Faraday connection so file attachments upload correctly. (C-5589, #116)
|
|
28
|
+
- **File walk respects ignore patterns.** Fixed glob/walk to apply ignore patterns before traversal, resolving cases where ignored files were still visited. (#102)
|
|
29
|
+
- **Server restart kills stale PIDs.** Improved process cleanup on restart with better PID management and user-facing hints when restart fails.
|
|
30
|
+
- **Device ID persistence.** Device ID is now persisted in `BrandConfig` instead of being regenerated, ensuring stable telemetry identity across restarts.
|
|
31
|
+
- **Terminal markdown rendering on Ruby 4.0.** Fixed compatibility issue with Ruby 4.0's stricter method dispatch that broke terminal markdown output. (#99)
|
|
32
|
+
|
|
33
|
+
## [1.0.5] - 2026-05-12
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- **Telegram channel adapter.** New IM channel adapter that connects openclacky to Telegram via the Bot API. Setup is just a bot token from @BotFather — no browser automation, no QR. Mirrors the existing Feishu / WeCom / Weixin contract: HTTPS long-poll inbound, `sendMessage` / `sendPhoto` / `sendDocument` outbound, photo + document download routed through the standard FileProcessor + vision pipeline, group `@-mention` filtering and `allowed_users` whitelist. `base_url` is configurable to support self-hosted Bot API servers (https://github.com/tdlib/telegram-bot-api) for networks where `api.telegram.org` is unreachable. Frontend Channels panel, `channel-setup` skill, English/Chinese i18n, and `app.css` logo class added. 32 new specs in `spec/clacky/server/channel/adapters/telegram/`.
|
|
37
|
+
- **Discord channel adapter.** Full Discord integration via REST API + Gateway (WebSocket), with channel-setup support, Web UI Channels panel entry, and i18n strings. Connect Clacky to Discord servers for bot interactions through slash commands and message events.
|
|
38
|
+
- **OpenRouter curated model list.** The OpenRouter provider now ships with a curated dropdown of mainstream Claude and GPT models (Sonnet, Opus, Haiku, GPT-5.5/5.4), so users can pick from the list instead of typing model IDs manually. Full catalogue still accessible by typing any model ID.
|
|
39
|
+
- **OpenRouter lite model pairing.** Subagents on OpenRouter now automatically get a sensible cheap/fast sidekick — Claude family pairs with Haiku, GPT family pairs with the mini variant — matching the behavior already available on the native OpenAI and OpenClacky providers.
|
|
40
|
+
- **MiMo 2.5 Pro (Xiaomi) model support.** Added `mimo-v2.5-pro` to the MiMo provider preset alongside existing MiMo models.
|
|
41
|
+
- **AI key setup guide link.** New users and those configuring API keys now see a "New to AI keys? See the guide →" link on both onboarding and settings pages, pointing to the official documentation.
|
|
42
|
+
|
|
43
|
+
### Improved
|
|
44
|
+
- **Default model upgraded to claude-sonnet-4-6.** The OpenClacky provider now defaults to the latest Claude Sonnet model for better performance out of the box.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
- **Linux server restart stability.** Fixed an inherited socket cleanup bug where WEBrick's shutdown would propagate `SHUT_RDWR` to the shared kernel socket, breaking subsequent `accept()` calls on Linux. The server now detaches inherited sockets before shutdown so worker restarts work reliably.
|
|
48
|
+
- **Upgrade failure recovery UI.** When an in-app upgrade restart fails, the UI now shows both tray icon and CLI recovery paths (`gem update ...`) instead of leaving users stranded. Also added branded CLI command info to the version check API for white-label builds.
|
|
49
|
+
|
|
8
50
|
## [1.0.4] - 2026-05-11
|
|
9
51
|
|
|
10
52
|
### Added
|
|
@@ -23,7 +23,7 @@ Skills must not read local config files directly.
|
|
|
23
23
|
- ❌ `cat ~/.clacky/browser.yml`
|
|
24
24
|
- ✅ `curl http://${CLACKY_SERVER_HOST}:${CLACKY_SERVER_PORT}/api/browser/status`
|
|
25
25
|
|
|
26
|
-
Exception: lightweight `enable` / `disable` operations may read/write yml directly (see `channel-
|
|
26
|
+
Exception: lightweight `enable` / `disable` operations may read/write yml directly (see `channel-manager`).
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -178,6 +178,9 @@ module Clacky
|
|
|
178
178
|
if formatted_result.is_a?(Hash) && formatted_result[:image_inject]
|
|
179
179
|
image_inject = formatted_result[:image_inject]
|
|
180
180
|
formatted_result = formatted_result.reject { |k, _| k == :image_inject }
|
|
181
|
+
if formatted_result[:content_string]
|
|
182
|
+
formatted_result = formatted_result[:content_string]
|
|
183
|
+
end
|
|
181
184
|
end
|
|
182
185
|
|
|
183
186
|
# If the tool returned a plain string, use it directly (avoids double-escaping).
|
|
@@ -187,7 +190,6 @@ module Clacky
|
|
|
187
190
|
content = if formatted_result.is_a?(String)
|
|
188
191
|
formatted_result
|
|
189
192
|
elsif formatted_result.is_a?(Array)
|
|
190
|
-
# Multipart content (e.g. screenshot image blocks) — keep as Array
|
|
191
193
|
formatted_result
|
|
192
194
|
else
|
|
193
195
|
JSON.generate(formatted_result)
|
data/lib/clacky/agent.rb
CHANGED
|
@@ -979,9 +979,12 @@ module Clacky
|
|
|
979
979
|
next unless mime_type && base64_data
|
|
980
980
|
|
|
981
981
|
data_url = "data:#{mime_type};base64,#{base64_data}"
|
|
982
|
+
label = path ? File.basename(path.to_s) : "image"
|
|
983
|
+
image_block = { type: "image_url", image_url: { url: data_url } }
|
|
984
|
+
image_block[:image_path] = path if path
|
|
982
985
|
image_content = [
|
|
983
|
-
{ type: "text",
|
|
984
|
-
|
|
986
|
+
{ type: "text", text: "[Image: #{label}]" },
|
|
987
|
+
image_block
|
|
985
988
|
]
|
|
986
989
|
@history.append({
|
|
987
990
|
role: "user",
|
|
@@ -1514,7 +1517,7 @@ module Clacky
|
|
|
1514
1517
|
inline = $1 == "!"
|
|
1515
1518
|
# URL-decode percent-encoded characters (e.g. Chinese filenames encoded by AI)
|
|
1516
1519
|
raw_path = CGI.unescape($3)
|
|
1517
|
-
name =
|
|
1520
|
+
name = File.basename(raw_path)
|
|
1518
1521
|
path = File.expand_path(raw_path)
|
|
1519
1522
|
Clacky::Logger.info("[parse_file_links] raw=#{$3.inspect} expanded=#{path.inspect} exist=#{File.exist?(path)}")
|
|
1520
1523
|
files << { name: name, path: path, inline: inline }
|
|
@@ -1523,13 +1526,15 @@ module Clacky
|
|
|
1523
1526
|
end
|
|
1524
1527
|
|
|
1525
1528
|
# Emit assistant message to UI, parsing any embedded file:// links first.
|
|
1529
|
+
#
|
|
1530
|
+
# Local image URL rewriting (file:// → /api/local-image) is intentionally
|
|
1531
|
+
# NOT done here. It is browser-specific (the Web UI runs on http://localhost
|
|
1532
|
+
# and cannot load file:// directly) and must stay scoped to the Web UI
|
|
1533
|
+
# controller. IM channel subscribers need the original file:// markdown so
|
|
1534
|
+
# parse_file_links can extract paths and deliver images as native attachments.
|
|
1526
1535
|
private def emit_assistant_message(content)
|
|
1527
1536
|
return if content.nil? || content.empty?
|
|
1528
1537
|
|
|
1529
|
-
# Rewrite local image paths (file:// and bare absolute) to /api/local-image proxy URLs
|
|
1530
|
-
# so the browser can render them without file:// security blocks.
|
|
1531
|
-
content = Clacky::Utils::FileProcessor.rewrite_local_image_urls(content)
|
|
1532
|
-
|
|
1533
1538
|
parsed = parse_file_links(content)
|
|
1534
1539
|
@ui&.show_assistant_message(parsed[:text], files: parsed[:files])
|
|
1535
1540
|
end
|
data/lib/clacky/agent_config.rb
CHANGED
|
@@ -154,7 +154,8 @@ module Clacky
|
|
|
154
154
|
attr_accessor :permission_mode, :max_tokens, :verbose,
|
|
155
155
|
:enable_compression, :enable_prompt_caching,
|
|
156
156
|
:models, :current_model_index, :current_model_id,
|
|
157
|
-
:memory_update_enabled, :skill_evolution
|
|
157
|
+
:memory_update_enabled, :skill_evolution,
|
|
158
|
+
:max_running_agents, :max_idle_agents
|
|
158
159
|
|
|
159
160
|
def initialize(options = {})
|
|
160
161
|
@permission_mode = validate_permission_mode(options[:permission_mode])
|
|
@@ -195,6 +196,9 @@ module Clacky
|
|
|
195
196
|
@skill_evolution = @skill_evolution.transform_keys(&:to_sym)
|
|
196
197
|
@skill_evolution.transform_values! { |v| v.is_a?(Hash) ? v.transform_keys(&:to_sym) : v }
|
|
197
198
|
|
|
199
|
+
@max_running_agents = options[:max_running_agents] || 10
|
|
200
|
+
@max_idle_agents = options[:max_idle_agents] || 10
|
|
201
|
+
|
|
198
202
|
# Per-session virtual model overlay.
|
|
199
203
|
# When set, #current_model returns a *merged* hash (the resolved @models
|
|
200
204
|
# entry merged with this overlay) without mutating the shared @models
|
|
@@ -368,7 +372,7 @@ module Clacky
|
|
|
368
372
|
# These map directly to AgentConfig accessors.
|
|
369
373
|
CONFIG_SETTINGS_KEYS = %w[
|
|
370
374
|
enable_compression enable_prompt_caching memory_update_enabled
|
|
371
|
-
skill_evolution
|
|
375
|
+
skill_evolution max_running_agents max_idle_agents
|
|
372
376
|
].freeze
|
|
373
377
|
|
|
374
378
|
# Serialize the current agent configuration to YAML.
|
|
@@ -382,7 +386,9 @@ module Clacky
|
|
|
382
386
|
"enable_compression" => @enable_compression,
|
|
383
387
|
"enable_prompt_caching" => @enable_prompt_caching,
|
|
384
388
|
"memory_update_enabled" => @memory_update_enabled,
|
|
385
|
-
"skill_evolution" => @skill_evolution
|
|
389
|
+
"skill_evolution" => @skill_evolution,
|
|
390
|
+
"max_running_agents" => @max_running_agents,
|
|
391
|
+
"max_idle_agents" => @max_idle_agents
|
|
386
392
|
}
|
|
387
393
|
YAML.dump("settings" => settings, "models" => persistable_models)
|
|
388
394
|
end
|
data/lib/clacky/brand_config.rb
CHANGED
|
@@ -76,13 +76,28 @@ module Clacky
|
|
|
76
76
|
|
|
77
77
|
# Load brand configuration from ~/.clacky/brand.yml.
|
|
78
78
|
# Returns an empty BrandConfig (no brand) if the file does not exist.
|
|
79
|
+
# Always ensures a stable device_id is present and persisted.
|
|
79
80
|
def self.load
|
|
80
|
-
|
|
81
|
+
if File.exist?(BRAND_FILE)
|
|
82
|
+
data = YAML.safe_load(File.read(BRAND_FILE)) || {}
|
|
83
|
+
else
|
|
84
|
+
data = {}
|
|
85
|
+
end
|
|
81
86
|
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
instance = new(data)
|
|
88
|
+
instance.ensure_device_id!
|
|
89
|
+
instance
|
|
84
90
|
rescue StandardError
|
|
85
|
-
new({})
|
|
91
|
+
instance = new({})
|
|
92
|
+
instance.ensure_device_id!
|
|
93
|
+
instance
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def ensure_device_id!
|
|
97
|
+
return if @device_id && !@device_id.strip.empty?
|
|
98
|
+
|
|
99
|
+
@device_id = generate_device_id
|
|
100
|
+
save
|
|
86
101
|
end
|
|
87
102
|
|
|
88
103
|
# Returns true when this installation has a product name configured.
|
data/lib/clacky/cli.rb
CHANGED
|
@@ -172,7 +172,7 @@ module Clacky
|
|
|
172
172
|
# is booted by this process), and only when the user hasn't already set
|
|
173
173
|
# CLACKY_SERVER_HOST / CLACKY_SERVER_PORT explicitly.
|
|
174
174
|
#
|
|
175
|
-
# Why: skills like `channel-
|
|
175
|
+
# Why: skills like `channel-manager` and `browser-setup` call back into
|
|
176
176
|
# http://${CLACKY_SERVER_HOST}:${CLACKY_SERVER_PORT}/api/*. In server
|
|
177
177
|
# mode those vars are injected by HTTPServer#start. In CLI mode they
|
|
178
178
|
# would be blank, so the skill templates expand to an unreachable URL.
|