openclacky 1.5.14 → 1.5.16
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 +13 -3
- data/CHANGELOG.md +85 -0
- data/README.md +11 -0
- data/lib/clacky/agent/hook_manager.rb +7 -3
- data/lib/clacky/agent/llm_caller.rb +33 -7
- data/lib/clacky/agent/message_compressor_helper.rb +13 -7
- data/lib/clacky/agent/session_serializer.rb +70 -5
- data/lib/clacky/agent.rb +552 -166
- data/lib/clacky/agent_config.rb +4 -2
- data/lib/clacky/billing/platform_billing.rb +13 -2
- data/lib/clacky/brand_config.rb +14 -17
- data/lib/clacky/cli.rb +77 -12
- data/lib/clacky/cli_guidance.rb +48 -0
- data/lib/clacky/client.rb +36 -14
- data/lib/clacky/default_extensions/computer-use/ext.yml +15 -0
- data/lib/clacky/default_extensions/computer-use/lib/computer/geometry.rb +134 -0
- data/lib/clacky/default_extensions/computer-use/lib/computer/grid.rb +214 -0
- data/lib/clacky/default_extensions/computer-use/lib/computer/keycodes.rb +100 -0
- data/lib/clacky/default_extensions/computer-use/lib/computer/macos.rb +557 -0
- data/lib/clacky/default_extensions/computer-use/lib/computer/windows.rb +299 -0
- data/lib/clacky/default_extensions/computer-use/lib/computer/windows_agent.ps1 +436 -0
- data/lib/clacky/default_extensions/computer-use/lib/computer/windows_keycodes.rb +119 -0
- data/lib/clacky/default_extensions/computer-use/skills/computer-use/SKILL.md +141 -0
- data/lib/clacky/default_extensions/computer-use/skills/computer-use/bin/computer.rb +547 -0
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +4 -1
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +217 -94
- data/lib/clacky/extension/api_extension.rb +17 -5
- data/lib/clacky/extension/loader.rb +102 -25
- data/lib/clacky/extension/verifier.rb +1 -1
- data/lib/clacky/json_ui_controller.rb +4 -2
- data/lib/clacky/media/generator.rb +2 -2
- data/lib/clacky/message_format/open_ai.rb +50 -5
- data/lib/clacky/message_format/open_ai_responses.rb +7 -3
- data/lib/clacky/plain_ui_controller.rb +1 -1
- data/lib/clacky/providers.rb +135 -121
- data/lib/clacky/rich_ui/components/composer_guidance.rb +49 -0
- data/lib/clacky/rich_ui/rich_ui_controller.rb +23 -3
- data/lib/clacky/rich_ui/shell/rich_agent_shell.rb +13 -0
- data/lib/clacky/server/browser_manager.rb +14 -0
- data/lib/clacky/server/channel/adapters/base.rb +19 -0
- data/lib/clacky/server/channel/adapters/feishu/adapter.rb +23 -1
- data/lib/clacky/server/channel/adapters/feishu/bot.rb +267 -0
- data/lib/clacky/server/channel/channel_manager.rb +27 -5
- data/lib/clacky/server/channel/channel_ui_controller.rb +225 -9
- data/lib/clacky/server/dir_picker.rb +67 -4
- data/lib/clacky/server/http_server.rb +347 -138
- data/lib/clacky/server/server_master.rb +17 -7
- data/lib/clacky/server/session_registry.rb +4 -0
- data/lib/clacky/server/web_ui_controller.rb +10 -4
- data/lib/clacky/session_manager.rb +52 -1
- data/lib/clacky/shell_hook_loader.rb +3 -3
- data/lib/clacky/skill.rb +3 -0
- data/lib/clacky/skill_loader.rb +5 -1
- data/lib/clacky/tools/ask_user.rb +1 -2
- data/lib/clacky/tools/file_reader.rb +9 -4
- data/lib/clacky/tools/terminal/session_manager.rb +3 -1
- data/lib/clacky/tools/terminal.rb +12 -1
- data/lib/clacky/tools/web_search.rb +12 -0
- data/lib/clacky/ui2/components/command_suggestions.rb +1 -0
- data/lib/clacky/ui2/components/input_area.rb +15 -2
- data/lib/clacky/ui2/screen_buffer.rb +1 -0
- data/lib/clacky/ui2/ui_controller.rb +28 -5
- data/lib/clacky/ui_interface.rb +1 -1
- data/lib/clacky/utils/file_processor.rb +28 -3
- data/lib/clacky/utils/model_pricing.rb +101 -38
- data/lib/clacky/utils/resource_group.rb +67 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +1007 -485
- data/lib/clacky/web/app.js +132 -19
- data/lib/clacky/web/components/composer.js +3 -1
- data/lib/clacky/web/components/datepicker.js +19 -19
- data/lib/clacky/web/components/mentions.js +11 -14
- data/lib/clacky/web/components/model-picker.js +238 -55
- data/lib/clacky/web/components/quote-select.js +232 -89
- data/lib/clacky/web/features/billing/view.js +60 -15
- data/lib/clacky/web/features/extensions/store.js +207 -270
- data/lib/clacky/web/features/extensions/view.js +383 -261
- data/lib/clacky/web/features/model-tester/store.js +37 -4
- data/lib/clacky/web/features/new-session/store.js +4 -2
- data/lib/clacky/web/features/new-session/view.js +81 -5
- data/lib/clacky/web/features/share/view.js +500 -571
- data/lib/clacky/web/features/workspace/view.js +102 -15
- data/lib/clacky/web/i18n.js +172 -56
- data/lib/clacky/web/index.html +67 -64
- data/lib/clacky/web/sessions.js +499 -150
- data/lib/clacky/web/settings.js +163 -6
- data/lib/clacky/web/skills.js +47 -16
- data/lib/clacky/web/ws-dispatcher.js +153 -4
- data/lib/clacky.rb +13 -1
- metadata +14 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9939ab09ac4e473dc7b096dac6a14c67f6d6060fe7678d7bd0b2635c7cc924ed
|
|
4
|
+
data.tar.gz: 9e0e566a77267f73388a9761d101672c86fd35513dc7248355a3c3df7d8017f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 206657c6e08b40482a5029abf25fb8bd8579728ea469e2b2f7fba5b0f95dbbbcbfb8ca75778f73090bbbb34d7145cf58dab64c7d7462325d981a4e5106ced23e
|
|
7
|
+
data.tar.gz: 0b30e40beb4d544076f2063160d04d9fcc2099b43ce46c78e1694d35ec631859361ffc15b2521a8a53e0c0cd84fb52e157cebf4469557991f273770842808568
|
|
@@ -95,15 +95,25 @@ This is the one step the agent handles manually — the script does not write ch
|
|
|
95
95
|
```bash
|
|
96
96
|
gh api orgs/clacky-ai/members --paginate --jq '.[].login' | sort
|
|
97
97
|
```
|
|
98
|
-
- Then cross-reference PR authors in the release range
|
|
98
|
+
- Then cross-reference PR authors in the release range. Use `grep -oE`, not `-oP` —
|
|
99
|
+
BSD grep on macOS rejects `-P` with "invalid option", and the loop silently yields nothing:
|
|
99
100
|
```bash
|
|
100
|
-
git log <previous_tag>..HEAD --oneline | grep -
|
|
101
|
+
git log <previous_tag>..HEAD --oneline | grep -oE '#[0-9]+' | while read pr; do
|
|
101
102
|
num=${pr#\#}
|
|
102
103
|
gh pr view $num --json number,author --jq '"#\(.number) \(.author.login)"'
|
|
103
104
|
done
|
|
104
105
|
```
|
|
105
106
|
- Skip attribution for org members (internal contributors) - only call out external contributors
|
|
106
|
-
-
|
|
107
|
+
- Some cited numbers no longer resolve at all. A commit subject can name a superseded PR
|
|
108
|
+
next to the merged one (`feat: discover custom-endpoint models in the add-model dialog
|
|
109
|
+
(#559) (#560)`), and `gh pr view 559` answers "Could not resolve to a PullRequest".
|
|
110
|
+
When that happens, ask the commit which PR actually merged it:
|
|
111
|
+
```bash
|
|
112
|
+
gh api repos/clacky-ai/openclacky/commits/<sha>/pulls \
|
|
113
|
+
--jq '.[] | "PR #\(.number) | \(.user.login) | \(.state)"'
|
|
114
|
+
```
|
|
115
|
+
Attribute the bullet to the PR that resolves and carries a real author; a number that
|
|
116
|
+
resolves to nobody is not a contributor reference.
|
|
107
117
|
- Example: `- Todo panel with SVG icons (#437 - @shipinliang)`
|
|
108
118
|
|
|
109
119
|
6. Commit the changelog:
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,91 @@ 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.16] - 2026-09-24
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Add Claude Opus 5.5 and GPT-6 Sol/Luna to the model catalog
|
|
11
|
+
- Send task progress cards in Feishu, with collapsible process history (#582)
|
|
12
|
+
- Print a receipt-style usage scorecard from the billing page and share it as an image
|
|
13
|
+
- Understand video attachments through a sidecar model (#564)
|
|
14
|
+
- Open the app straight into a chat from an external clacky:// link, including the welcome view
|
|
15
|
+
- Route requests automatically: the auto alias picks a tier per request and reports which tier served it (#576)
|
|
16
|
+
- Drive the Windows desktop through WSL in computer-use
|
|
17
|
+
- Ship computer-use as a default-off extension
|
|
18
|
+
- Let on_start hooks veto a turn before the agent starts (#573 - @afumu)
|
|
19
|
+
- Cap the memory and CPU of a task's shells and browser daemons on Linux hosts with --task-cgroup
|
|
20
|
+
- Refuse to start on a taken port instead of silently moving to another one, with --strict-port
|
|
21
|
+
- Keep attachment paths when a compressed history chunk is replayed (#581 - @BoyYangzai)
|
|
22
|
+
- Point generated skills at their own directory through an inlined skill_dir
|
|
23
|
+
|
|
24
|
+
### Improved
|
|
25
|
+
- Restyle the web search result card (#575)
|
|
26
|
+
- Use official brand marks for model vendor badges (#566)
|
|
27
|
+
- Refresh the supported model catalogs and pricing (#571)
|
|
28
|
+
- Rename the extension group label User to Personal
|
|
29
|
+
- Adjust the session title size and unify the sidebar active highlight
|
|
30
|
+
- Soften the extension row action buttons and tighten the segmented control corners
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- Keep queued guidance attached to the message it was sent from, and allow cancelling it (#584, #578)
|
|
34
|
+
- Stop treating a pasted path as a slash command (#577)
|
|
35
|
+
- Restore structured tool result cards when a session history is replayed (#574)
|
|
36
|
+
- Stop duplicating user bubbles after an edit (#568)
|
|
37
|
+
- Show attachment badges in realtime user bubbles (#567)
|
|
38
|
+
- Transcribe audio attachments through the STT sidecar (#565)
|
|
39
|
+
- Preserve a quote selection while a live update streams in (#569)
|
|
40
|
+
- Load per-key platform usage in parallel in the billing view (#580)
|
|
41
|
+
- Prevent subagents from publishing input queues (#570 - @afumu)
|
|
42
|
+
- Block model switching while a session is running instead of racing the running turn
|
|
43
|
+
- Hand binary files back to the OS when they are reopened
|
|
44
|
+
- Report the doctor host before resolving the backend
|
|
45
|
+
- Stop the DeepSeek preset from showing a version suffix
|
|
46
|
+
|
|
47
|
+
## [1.5.15] - 2026-09-17
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
- Quote text selected in a message and send it as a reference badge
|
|
51
|
+
- Queue messages while the agent is working, with pending-message controls in the CLI and Web UI (#561)
|
|
52
|
+
- Steer a running turn with a queued message
|
|
53
|
+
- Discover custom-endpoint models from the add-model dialog (#560 - @kylezhang)
|
|
54
|
+
- Show vendor badges for each provider in the add-model dropdown
|
|
55
|
+
- Label every model with its provider in the model picker
|
|
56
|
+
- Open unviewable files in the system app and reveal them in the workspace tree
|
|
57
|
+
- Follow the browsed directory in the directory picker sidebar
|
|
58
|
+
- Show the extension and skill lists as segmented tabs in Extension Studio
|
|
59
|
+
- Add disclosure carets to the clickable status-bar fields
|
|
60
|
+
- Resolve redirected Windows shell folders in the WSL directory picker
|
|
61
|
+
- Show web search results as UI results in the chat
|
|
62
|
+
|
|
63
|
+
### Improved
|
|
64
|
+
- Swap the extension and channel management order in the sidebar
|
|
65
|
+
- Rename the Tasks sidebar entry to Scheduled Tasks
|
|
66
|
+
- Improve mobile folder navigation in the directory picker (#549)
|
|
67
|
+
- Improve the mobile layout of settings tabs and model controls (#548)
|
|
68
|
+
- Add a global focus ring for keyboard navigation (#544)
|
|
69
|
+
- Align the input behavior toggle with the send buttons
|
|
70
|
+
- Deepen the directory-picker breadcrumb field
|
|
71
|
+
- Keep the segmented control highlight dark on the dim and bluedark themes
|
|
72
|
+
- Retire the dsk-deepseek-v4-flash model ids
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
- Keep the mobile composer above the soft keyboard (#557)
|
|
76
|
+
- Install newly published brand skills when an agent starts
|
|
77
|
+
- Show the marketplace takedown state instead of an unusable unpublish button
|
|
78
|
+
- Show complete brand distribution for brand owners (#556)
|
|
79
|
+
- Focus brand owners on brand publishing in Extension Studio (#555)
|
|
80
|
+
- Hide the creator center from branded non-owners (#553)
|
|
81
|
+
- Forward brand marketplace sorting to the backend (#552)
|
|
82
|
+
- Complete the public session creation lifecycle (#551)
|
|
83
|
+
- Make the @ mention menu selectable on the new-session page (#558)
|
|
84
|
+
- Show a searched old session in the sidebar without a reload
|
|
85
|
+
- Stop session updated_at from rolling back on save
|
|
86
|
+
- Avoid a duplicated empty state in the sidebar search
|
|
87
|
+
- Align reasoning and image fields with the Responses API schema
|
|
88
|
+
- Use theme tokens for the badge colors
|
|
89
|
+
- Give the calendar popup its own class namespace
|
|
90
|
+
- Add the default user prompt for attachments
|
|
91
|
+
|
|
7
92
|
## [1.5.14] - 2026-09-10
|
|
8
93
|
|
|
9
94
|
### Added
|
data/README.md
CHANGED
|
@@ -185,6 +185,17 @@ openclacky server --port 8080 # custom port
|
|
|
185
185
|
openclacky server --host 0.0.0.0 # listen on all interfaces (remote access)
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
+
Linux sandbox hosts can opt into task limits with
|
|
189
|
+
`openclacky server --task-cgroup /sys/fs/cgroup/tasks`. The host must first create
|
|
190
|
+
that cgroup, configure its aggregate memory/CPU limits, and enable the `memory`
|
|
191
|
+
and `cpu` subtree controllers. Terminal shells and browser MCP daemons enter
|
|
192
|
+
child groups before exec; the server remains outside. Terminal results include
|
|
193
|
+
`resource_exhausted: "memory"` and an error when their group records an OOM kill,
|
|
194
|
+
even if the shell masks the child's failure. A separately managed Chrome must
|
|
195
|
+
also be placed under the same parent by its launcher. This option is off by
|
|
196
|
+
default and does not establish a security boundary against privileged commands.
|
|
197
|
+
|
|
198
|
+
|
|
188
199
|
## Configuration
|
|
189
200
|
|
|
190
201
|
```bash
|
|
@@ -25,7 +25,11 @@ module Clacky
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# @return [Hash] `{action: :allow}`, `{action: :deny, reason:}`, or
|
|
28
|
-
# `{action: :handled, result:}` when a hook fulfilled the
|
|
28
|
+
# `{action: :handled, result:}` when a hook fulfilled the event itself.
|
|
29
|
+
# on_start receives (user_input, agent) after task/history initialization.
|
|
30
|
+
# Its handled result is returned unchanged from Agent#run; the extension
|
|
31
|
+
# supplies the run-result status and any queue/feedback flags it needs.
|
|
32
|
+
# on_start is not fired for steering input inside an already running turn.
|
|
29
33
|
# Extra trailing arg: the agent that owns this hook chain, so ext hooks can
|
|
30
34
|
# call `agent.emit_event(...)`. Blocks are procs — those declaring fewer
|
|
31
35
|
# params (`|call|`, `|call, result|`) silently ignore it.
|
|
@@ -43,8 +47,8 @@ module Clacky
|
|
|
43
47
|
# place (chained rewrite), so for non-deny results there's nothing to
|
|
44
48
|
# merge — we just keep going.
|
|
45
49
|
#
|
|
46
|
-
# :handled short-circuits the same way
|
|
47
|
-
# the
|
|
50
|
+
# :handled short-circuits the same way: the hook has already fulfilled
|
|
51
|
+
# the event, so later hooks have nothing left to act on.
|
|
48
52
|
if hook_result[:action] == :deny || hook_result[:action] == :handled
|
|
49
53
|
result = hook_result
|
|
50
54
|
break
|
|
@@ -45,7 +45,7 @@ module Clacky
|
|
|
45
45
|
# Inside call_llm we only *update in place* during retries, so the
|
|
46
46
|
# already-live progress slot shows meaningful transient status
|
|
47
47
|
# ("Network failed… attempt 2/10", etc.).
|
|
48
|
-
private def call_llm
|
|
48
|
+
private def call_llm(agent_upstream_fails: nil, agent_upgrade_fails: nil)
|
|
49
49
|
# Transition :fallback_active → :probing if cooling-off has expired.
|
|
50
50
|
@config.maybe_start_probing
|
|
51
51
|
|
|
@@ -54,6 +54,12 @@ module Clacky
|
|
|
54
54
|
max_retries = 10
|
|
55
55
|
retry_delay = 5
|
|
56
56
|
retries = 0
|
|
57
|
+
# Per-call tier attribution for auto routing: each retryable failure is
|
|
58
|
+
# charged to the tier the gateway actually routed that call to (echoed
|
|
59
|
+
# in X-Clacky-Routed-Tier). Failures without a tier (network layer,
|
|
60
|
+
# timeouts — the gateway never answered) are not attributed to either.
|
|
61
|
+
floor_fails = 0
|
|
62
|
+
upgrade_fails = 0
|
|
57
63
|
|
|
58
64
|
# Track whether any of the retry/fallback branches below opened a
|
|
59
65
|
# "retrying" progress slot via show_progress(progress_type:
|
|
@@ -111,7 +117,9 @@ module Clacky
|
|
|
111
117
|
max_tokens: @config.max_tokens,
|
|
112
118
|
enable_caching: @config.enable_prompt_caching,
|
|
113
119
|
reasoning_effort: @reasoning_effort,
|
|
114
|
-
on_chunk: build_progress_on_chunk
|
|
120
|
+
on_chunk: build_progress_on_chunk,
|
|
121
|
+
agent_upstream_fails: (agent_upstream_fails || 0) + floor_fails,
|
|
122
|
+
agent_upgrade_fails: (agent_upgrade_fails || 0) + upgrade_fails
|
|
115
123
|
)
|
|
116
124
|
|
|
117
125
|
# Successful response — if we were probing, confirm primary is healthy.
|
|
@@ -142,7 +150,7 @@ module Clacky
|
|
|
142
150
|
finish_reason: response[:finish_reason].to_s,
|
|
143
151
|
completion_tokens: response.dig(:token_usage, :completion_tokens)
|
|
144
152
|
)
|
|
145
|
-
raise RetryableError
|
|
153
|
+
raise RetryableError.new("[LLM] Model returned empty response (no content, no tool_calls), retrying...", routed_tier: response[:routed_tier])
|
|
146
154
|
end
|
|
147
155
|
|
|
148
156
|
# Thinking-mode silent response detector. DeepSeek V4 / Kimi K2 /
|
|
@@ -164,7 +172,7 @@ module Clacky
|
|
|
164
172
|
reasoning_tail: reasoning_str[-200, 200] || reasoning_str,
|
|
165
173
|
completion_tokens: response.dig(:token_usage, :completion_tokens)
|
|
166
174
|
)
|
|
167
|
-
raise RetryableError
|
|
175
|
+
raise RetryableError.new("[LLM] Thinking-mode model produced reasoning but empty content/tool_calls, retrying...", routed_tier: response[:routed_tier])
|
|
168
176
|
end
|
|
169
177
|
|
|
170
178
|
rescue Faraday::TimeoutError => e
|
|
@@ -275,6 +283,11 @@ module Clacky
|
|
|
275
283
|
Clacky::Shutdown.checkpoint!
|
|
276
284
|
retries += 1
|
|
277
285
|
|
|
286
|
+
case e.routed_tier
|
|
287
|
+
when "floor" then floor_fails += 1
|
|
288
|
+
when "upgrade" then upgrade_fails += 1
|
|
289
|
+
end
|
|
290
|
+
|
|
278
291
|
# Probing failure: primary still down — renew cooling-off and retry with fallback.
|
|
279
292
|
if @config.probing?
|
|
280
293
|
handle_probe_failure
|
|
@@ -398,10 +411,21 @@ module Clacky
|
|
|
398
411
|
raise
|
|
399
412
|
end
|
|
400
413
|
|
|
414
|
+
# Fold this call's tier-attributed failures into the task-level tallies
|
|
415
|
+
# so later calls in the same task keep routing away from the failing
|
|
416
|
+
# lane — the per-call counters above reset to 0 once this call
|
|
417
|
+
# succeeds. A floor failure proves the floor lane is shaky for this
|
|
418
|
+
# task (route up); an upgrade failure proves the upgrade lane is shaky
|
|
419
|
+
# (route back down and stay on the floor until the task boundary).
|
|
420
|
+
@task_upstream_fails = (@task_upstream_fails || 0) + floor_fails if floor_fails > 0
|
|
421
|
+
@task_upgrade_fails = (@task_upgrade_fails || 0) + upgrade_fails if upgrade_fails > 0
|
|
422
|
+
|
|
401
423
|
# Track cost and collect token usage data.
|
|
402
424
|
# Pass the model name captured at API call time to ensure accurate billing
|
|
403
425
|
# even if the user switched models during the (potentially long) API call.
|
|
404
|
-
|
|
426
|
+
# For the "auto" alias the gateway reports the concrete model it routed
|
|
427
|
+
# to via X-Clacky-Routed-Model — bill under that so pricing resolves.
|
|
428
|
+
token_data = track_cost(response[:usage], raw_api_usage: response[:raw_api_usage], model: response[:routed_model] || api_call_model)
|
|
405
429
|
response[:token_usage] = token_data
|
|
406
430
|
|
|
407
431
|
# [DIAG] Log raw client response shape. Only emit when we see the
|
|
@@ -721,9 +745,11 @@ module Clacky
|
|
|
721
745
|
# and the hint just sits in history without affecting behaviour.
|
|
722
746
|
inject_upstream_truncation_hint_if_first(truncated)
|
|
723
747
|
|
|
724
|
-
raise Clacky::UpstreamTruncatedError
|
|
748
|
+
raise Clacky::UpstreamTruncatedError.new(
|
|
725
749
|
"[LLM] Upstream truncated tool_call `#{truncated[:name]}` " \
|
|
726
|
-
"(args=#{args_str[0, 40].inspect}). Retrying..."
|
|
750
|
+
"(args=#{args_str[0, 40].inspect}). Retrying...",
|
|
751
|
+
routed_tier: response[:routed_tier]
|
|
752
|
+
)
|
|
727
753
|
end
|
|
728
754
|
|
|
729
755
|
# True when a tool_call's arguments field is unusable — either empty
|
|
@@ -678,9 +678,8 @@ module Clacky
|
|
|
678
678
|
lines
|
|
679
679
|
end
|
|
680
680
|
|
|
681
|
-
# Serialize
|
|
682
|
-
#
|
|
683
|
-
# contents deliberately stay out of the chunk archive.
|
|
681
|
+
# Serialize lightweight UI metadata for file badges after compression.
|
|
682
|
+
# Includes path/preview_path when set; file contents and sizes stay out of the archive.
|
|
684
683
|
def display_files_for_archive(msg)
|
|
685
684
|
files = Array(msg[:display_files]).dup
|
|
686
685
|
Array(msg[:content]).each do |block|
|
|
@@ -688,12 +687,14 @@ module Clacky
|
|
|
688
687
|
next unless %w[image image_url].include?((block[:type] || block["type"]).to_s)
|
|
689
688
|
|
|
690
689
|
name = block[:image_name] || block["image_name"]
|
|
690
|
+
path = block[:image_path] || block["image_path"]
|
|
691
691
|
if name.nil? || name.to_s.strip.empty?
|
|
692
|
-
path = block[:image_path] || block["image_path"]
|
|
693
692
|
name = File.basename(path.to_s).sub(/\A[0-9a-f]{16}_/, "") unless path.to_s.empty?
|
|
694
693
|
end
|
|
695
694
|
name = "image" if name.nil? || name.to_s.strip.empty?
|
|
696
|
-
|
|
695
|
+
entry = { name: name, type: "image" }
|
|
696
|
+
entry[:path] = path.to_s unless path.nil? || path.to_s.strip.empty?
|
|
697
|
+
files << entry
|
|
697
698
|
end
|
|
698
699
|
|
|
699
700
|
files.filter_map do |file|
|
|
@@ -704,8 +705,13 @@ module Clacky
|
|
|
704
705
|
|
|
705
706
|
type = file[:type] || file["type"] || "file"
|
|
706
707
|
type = "file" if type.to_s.strip.empty?
|
|
707
|
-
{ name: name.to_s, type: type.to_s }
|
|
708
|
-
|
|
708
|
+
entry = { name: name.to_s, type: type.to_s }
|
|
709
|
+
path = file[:path] || file["path"]
|
|
710
|
+
entry[:path] = path.to_s unless path.nil? || path.to_s.strip.empty?
|
|
711
|
+
preview = file[:preview_path] || file["preview_path"]
|
|
712
|
+
entry[:preview_path] = preview.to_s unless preview.nil? || preview.to_s.strip.empty?
|
|
713
|
+
entry
|
|
714
|
+
end.uniq { |file| [file[:name], file[:type], file[:path]] }
|
|
709
715
|
end
|
|
710
716
|
|
|
711
717
|
# Once an image has a badge in the archive, omit the generic [image_url]
|
|
@@ -18,6 +18,10 @@ module Clacky
|
|
|
18
18
|
@name = session_data[:name] || ""
|
|
19
19
|
@pinned = session_data[:pinned] || false
|
|
20
20
|
@history = MessageHistory.new(session_data[:messages] || [])
|
|
21
|
+
@input_queue = (session_data[:pending_inputs] || []).map do |entry|
|
|
22
|
+
entry.reject { |key, _| key == :steer_target }.merge(delivery: "queue")
|
|
23
|
+
end
|
|
24
|
+
@accepting_steering = false
|
|
21
25
|
@todos = session_data[:todos] || [] # Restore todos from session
|
|
22
26
|
@iterations = session_data.dig(:stats, :total_iterations) || 0
|
|
23
27
|
@total_cost = session_data.dig(:stats, :total_cost_usd) || 0.0
|
|
@@ -227,15 +231,25 @@ module Clacky
|
|
|
227
231
|
stats_data[:last_error] = error_message if status == :error && error_message
|
|
228
232
|
stats_data[:last_error_raw] = raw_message if status == :error && raw_message
|
|
229
233
|
|
|
234
|
+
# Saves that carry no user-visible activity (idle compression, channel
|
|
235
|
+
# bookkeeping) pass no timestamp and reuse the last one this agent
|
|
236
|
+
# wrote. Keeping it in memory rather than falling back to the value
|
|
237
|
+
# loaded at restore time is what stops those saves from dragging an
|
|
238
|
+
# actively used session back to the moment it was restored.
|
|
239
|
+
stamp = updated_at || @persisted_updated_at || Time.now
|
|
240
|
+
stamp = stamp.iso8601 if stamp.respond_to?(:iso8601)
|
|
241
|
+
@persisted_updated_at = stamp
|
|
242
|
+
|
|
230
243
|
{
|
|
231
244
|
session_id: @session_id,
|
|
232
245
|
name: @name,
|
|
233
246
|
pinned: @pinned,
|
|
234
247
|
created_at: @created_at,
|
|
235
|
-
updated_at:
|
|
248
|
+
updated_at: stamp,
|
|
236
249
|
working_dir: @working_dir,
|
|
237
250
|
source: @source.to_s, # "manual" | "cron" | "channel" | "setup"
|
|
238
251
|
agent_profile: @agent_profile&.name || "", # "general" | "coding" | custom
|
|
252
|
+
pending_inputs: pending_inputs,
|
|
239
253
|
todos: @todos, # Include todos in session data
|
|
240
254
|
time_machine: { # Include Time Machine state
|
|
241
255
|
task_parents: @task_parents || {},
|
|
@@ -562,7 +576,7 @@ module Clacky
|
|
|
562
576
|
|
|
563
577
|
# Pull lightweight attachment badge metadata out of a chunk section.
|
|
564
578
|
# The marker is internal archive data and must not appear in replayed text.
|
|
565
|
-
#
|
|
579
|
+
# Restores name, type, and path/preview_path when present in the chunk marker.
|
|
566
580
|
def extract_display_files_from_text(text)
|
|
567
581
|
return [text, []] unless text.include?("_Display files:")
|
|
568
582
|
|
|
@@ -580,7 +594,12 @@ module Clacky
|
|
|
580
594
|
|
|
581
595
|
type = file["type"] || file[:type] || "file"
|
|
582
596
|
type = "file" if type.to_s.strip.empty?
|
|
583
|
-
|
|
597
|
+
entry = { name: name.to_s, type: type.to_s }
|
|
598
|
+
path = file["path"] || file[:path]
|
|
599
|
+
entry[:path] = path.to_s unless path.nil? || path.to_s.strip.empty?
|
|
600
|
+
preview = file["preview_path"] || file[:preview_path]
|
|
601
|
+
entry[:preview_path] = preview.to_s unless preview.nil? || preview.to_s.strip.empty?
|
|
602
|
+
files << entry
|
|
584
603
|
end
|
|
585
604
|
true
|
|
586
605
|
end
|
|
@@ -736,6 +755,7 @@ module Clacky
|
|
|
736
755
|
questions: questions)
|
|
737
756
|
end
|
|
738
757
|
else
|
|
758
|
+
remember_replayed_tool_name(tc[:id], name)
|
|
739
759
|
ui.show_tool_call(name, args)
|
|
740
760
|
end
|
|
741
761
|
end
|
|
@@ -754,20 +774,65 @@ module Clacky
|
|
|
754
774
|
msg[:content].each do |blk|
|
|
755
775
|
next unless blk.is_a?(Hash) && blk[:type] == "tool_result"
|
|
756
776
|
|
|
757
|
-
|
|
777
|
+
content = blk[:content].to_s
|
|
778
|
+
emit_replayed_tool_result(ui, content, blk[:tool_use_id])
|
|
758
779
|
end
|
|
759
780
|
replay_subagent_transcript(msg, ui)
|
|
760
781
|
end
|
|
761
782
|
|
|
762
783
|
when "tool"
|
|
763
784
|
# OpenAI-format tool result
|
|
764
|
-
ui
|
|
785
|
+
emit_replayed_tool_result(ui, msg[:content].to_s, msg[:tool_call_id])
|
|
765
786
|
replay_subagent_transcript(msg, ui)
|
|
766
787
|
end
|
|
767
788
|
|
|
768
789
|
replay_ext_events(msg, ui)
|
|
769
790
|
end
|
|
770
791
|
|
|
792
|
+
# Tool results persist as the raw JSON the tool returned, so replay can
|
|
793
|
+
# rebuild the same structured `ui:` payload the live path sent instead of
|
|
794
|
+
# degrading the card to plain text (C-5797).
|
|
795
|
+
private def emit_replayed_tool_result(ui, content, tool_call_id)
|
|
796
|
+
ui_payload = replayed_tool_ui(content, tool_call_id)
|
|
797
|
+
if ui_payload
|
|
798
|
+
ui.show_tool_result(content, ui: ui_payload)
|
|
799
|
+
else
|
|
800
|
+
ui.show_tool_result(content)
|
|
801
|
+
end
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
# Tool names live on the preceding assistant message, so remember them by
|
|
805
|
+
# call id while walking the round.
|
|
806
|
+
private def remember_replayed_tool_name(call_id, name)
|
|
807
|
+
return if call_id.nil? || name.to_s.empty?
|
|
808
|
+
|
|
809
|
+
@replayed_tool_names ||= {}
|
|
810
|
+
@replayed_tool_names[call_id.to_s] = name
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
private def replayed_tool_ui(content, tool_call_id)
|
|
814
|
+
return nil if tool_call_id.nil?
|
|
815
|
+
|
|
816
|
+
name = (@replayed_tool_names || {})[tool_call_id.to_s]
|
|
817
|
+
return nil if name.nil?
|
|
818
|
+
|
|
819
|
+
text = content.to_s
|
|
820
|
+
return nil unless text.start_with?("{")
|
|
821
|
+
|
|
822
|
+
resolved = @tool_registry.resolve(name)
|
|
823
|
+
return nil if resolved.nil?
|
|
824
|
+
|
|
825
|
+
tool = @tool_registry.get(resolved)
|
|
826
|
+
return nil unless tool.respond_to?(:ui_result)
|
|
827
|
+
|
|
828
|
+
parsed = JSON.parse(text, symbolize_names: true)
|
|
829
|
+
return nil unless parsed.is_a?(Hash)
|
|
830
|
+
|
|
831
|
+
tool.ui_result(parsed)
|
|
832
|
+
rescue StandardError
|
|
833
|
+
nil
|
|
834
|
+
end
|
|
835
|
+
|
|
771
836
|
# Re-emit custom extension events recorded on a message so extension
|
|
772
837
|
# panels can rebuild their state after a page reload. Mirrors what
|
|
773
838
|
# Agent#emit_event pushed live. No-op when the message carries none.
|