openclacky 1.5.6 → 1.5.7
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 +18 -1
- data/.clackyrules +25 -1
- data/CHANGELOG.md +26 -0
- data/CONTRIBUTING.md +9 -0
- data/Rakefile +15 -0
- data/docs/design-emit-and-background-skills.md +253 -0
- data/docs/mcp-architecture.md +48 -9
- data/docs/mcp.example.json +9 -0
- data/docs/superpowers/plans/2026-08-09-chatcut-extension-bridge.md +191 -0
- data/docs/superpowers/plans/2026-08-09-native-mcp-oauth.md +243 -0
- data/docs/superpowers/specs/2026-08-09-mcp-oauth-extension-fallback-design.md +115 -0
- data/lib/clacky/agent/cost_tracker.rb +17 -2
- data/lib/clacky/agent/hook_manager.rb +14 -3
- data/lib/clacky/agent/memory_updater.rb +17 -18
- data/lib/clacky/agent/message_compressor_helper.rb +23 -0
- data/lib/clacky/agent/session_serializer.rb +136 -22
- data/lib/clacky/agent/skill_auto_creator.rb +1 -2
- data/lib/clacky/agent/skill_evolution.rb +11 -7
- data/lib/clacky/agent/skill_manager.rb +20 -18
- data/lib/clacky/agent/skill_reflector.rb +3 -6
- data/lib/clacky/agent.rb +111 -20
- data/lib/clacky/anthropic_stream_aggregator.rb +6 -1
- data/lib/clacky/cli.rb +4 -0
- data/lib/clacky/client.rb +1 -1
- data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +13 -5
- data/lib/clacky/extension/api_extension.rb +11 -3
- data/lib/clacky/fanout.rb +100 -0
- data/lib/clacky/locales/en.rb +2 -0
- data/lib/clacky/locales/zh.rb +2 -0
- data/lib/clacky/mcp/cli_commands.rb +61 -0
- data/lib/clacky/mcp/client.rb +18 -2
- data/lib/clacky/mcp/connection_manager.rb +85 -0
- data/lib/clacky/mcp/http_transport.rb +25 -9
- data/lib/clacky/mcp/oauth/authorization_manager.rb +257 -0
- data/lib/clacky/mcp/oauth/config.rb +51 -0
- data/lib/clacky/mcp/oauth/credential_store.rb +82 -0
- data/lib/clacky/mcp/oauth/session.rb +88 -0
- data/lib/clacky/message_format/anthropic.rb +27 -13
- data/lib/clacky/message_history.rb +32 -1
- data/lib/clacky/providers.rb +11 -5
- data/lib/clacky/server/http_server.rb +42 -22
- data/lib/clacky/server/session_registry.rb +11 -15
- data/lib/clacky/server/web_ui_controller.rb +1 -1
- data/lib/clacky/session_manager.rb +13 -11
- data/lib/clacky/shell_hook_loader.rb +14 -8
- data/lib/clacky/tools/invoke_skill.rb +3 -2
- data/lib/clacky/tools/terminal.rb +11 -4
- data/lib/clacky/ui2/progress_handle.rb +27 -3
- data/lib/clacky/ui2/ui_controller.rb +206 -6
- data/lib/clacky/ui_interface.rb +13 -3
- data/lib/clacky/utils/arguments_parser.rb +73 -4
- data/lib/clacky/utils/model_pricing.rb +21 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +10 -3
- data/lib/clacky/web/features/new-session/view.js +19 -2
- data/lib/clacky/web/i18n.js +22 -8
- data/lib/clacky/web/index.html +7 -11
- data/lib/clacky/web/projects.js +26 -0
- data/lib/clacky/web/sessions.js +169 -119
- data/lib/clacky/web/settings.js +1 -0
- data/lib/clacky/web/ws-dispatcher.js +61 -34
- data/lib/clacky.rb +2 -0
- metadata +12 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c045233115364b3165e5ca43d7b45b898250996f952c2db8baacb4435404cc0
|
|
4
|
+
data.tar.gz: 3617f770edb88eaf6361bb0e6398f2ab6519f950ce234c89d0e9bce4fed4d486
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e5943dfb3599650349c8e09359e3fc1467e7fa6126f78ccf4840d2d9eaa02e5c06b088dc02bbeb789d53e8ae5fc128656a6e193dd385951a627f2349222262d
|
|
7
|
+
data.tar.gz: 8565520e6fef3cf12bc8580c537b04499a0f3b6068b2ff88d495dbec51f2feb70ef2e45713d0c1d1a06af0b1e4fffbdf4e3d4a2f414649943515b83d7a7f2946
|
|
@@ -89,7 +89,24 @@ This is the one step the agent handles manually — the script does not write ch
|
|
|
89
89
|
- Skip trivial commits (typos, minor formatting)
|
|
90
90
|
- Sanity check: count `### Added` bullets vs `feat:` commits — if commits > bullets, you likely merged too aggressively
|
|
91
91
|
|
|
92
|
-
5.
|
|
92
|
+
5. External contributor attribution:
|
|
93
|
+
- For PRs from **non-clacky-ai org members**, append `(#PR_NUMBER - @username)` to the bullet
|
|
94
|
+
- Query org members to distinguish external contributors:
|
|
95
|
+
```bash
|
|
96
|
+
gh api orgs/clacky-ai/members --paginate --jq '.[].login' | sort
|
|
97
|
+
```
|
|
98
|
+
- Then cross-reference PR authors in the release range:
|
|
99
|
+
```bash
|
|
100
|
+
git log <previous_tag>..HEAD --oneline | grep -oP '#\d+' | while read pr; do
|
|
101
|
+
num=${pr#\#}
|
|
102
|
+
gh pr view $num --json number,author --jq '"#\(.number) \(.author.login)"'
|
|
103
|
+
done
|
|
104
|
+
```
|
|
105
|
+
- Skip attribution for org members (internal contributors) - only call out external contributors
|
|
106
|
+
- If a PR was closed and resubmitted, use the merged PR number
|
|
107
|
+
- Example: `- Todo panel with SVG icons (#437 - @shipinliang)`
|
|
108
|
+
|
|
109
|
+
6. Commit the changelog:
|
|
93
110
|
```bash
|
|
94
111
|
git add CHANGELOG.md
|
|
95
112
|
git commit -m "docs: update CHANGELOG for v<version>"
|
data/.clackyrules
CHANGED
|
@@ -28,6 +28,30 @@ It provides chat functionality and autonomous AI agent capabilities with tool us
|
|
|
28
28
|
- **IMPORTANT**: Always use inline `private` with instance method definitions (e.g., `private def method_name`). Do NOT use standalone `private` keyword
|
|
29
29
|
- **IMPORTANT**: For class methods (`def self.method_name`), do NOT use `private`. Class methods should be public
|
|
30
30
|
|
|
31
|
+
### Ruby 2.6 Compatibility (IMPORTANT)
|
|
32
|
+
The gem must install and run on Ruby 2.6 (macOS system Ruby) through Ruby 4.0.
|
|
33
|
+
`openclacky.gemspec` declares `required_ruby_version = ">= 2.6.0", "< 4.1"`, and CI
|
|
34
|
+
(`.github/workflows/main.yml`) runs the suite on 2.6.8 / 3.3.4 / 4.0.5 with separate lockfiles.
|
|
35
|
+
Note `.rubocop.yml` uses `TargetRubyVersion: 3.1`, so RuboCop will NOT catch 2.6 violations.
|
|
36
|
+
|
|
37
|
+
Do NOT use syntax newer than Ruby 2.6 in `lib/`:
|
|
38
|
+
- No endless methods (`def foo = bar`) - Ruby 3.0
|
|
39
|
+
- No hash shorthand (`{ name:, age: }`) - Ruby 3.1
|
|
40
|
+
- No pattern matching (`case/in`) - Ruby 2.7
|
|
41
|
+
- No numbered block params (`_1`, `it`) - Ruby 2.7
|
|
42
|
+
- No rightward assignment, no argument forwarding (`...`) - Ruby 2.7+
|
|
43
|
+
- No `Hash#except`, `Enumerable#tally`, `Enumerable#filter_map` (unless using the polyfill),
|
|
44
|
+
`Array#intersect?`, `Data.define`, `Struct` keyword_init defaults
|
|
45
|
+
|
|
46
|
+
Existing polyfills live at the top of `lib/clacky.rb` (`filter_map`, `File.absolute_path?`,
|
|
47
|
+
`URI.encode_uri_component`, `YAMLCompat.safe_load`). Reuse them instead of writing new ones.
|
|
48
|
+
`Clacky::AesGcm` exists because macOS Ruby 2.6 links against LibreSSL without native AES-GCM.
|
|
49
|
+
|
|
50
|
+
When adding a runtime dependency, cap its version so RubyGems resolves a 2.6-compatible
|
|
51
|
+
release on old Ruby (see the `faraday < 2.9`, `rubyzip ~> 2.4.1`, `rouge < 5.0` comments in
|
|
52
|
+
the gemspec). Verify with `ruby -c` under a 2.6 interpreter when available, otherwise rely on
|
|
53
|
+
the CI matrix - a green local `bundle exec rspec` on Ruby 3.x proves nothing about 2.6.
|
|
54
|
+
|
|
31
55
|
### Architecture Patterns
|
|
32
56
|
- Tools inherit from `Clacky::Tools::Base`
|
|
33
57
|
- Each tool defines: `tool_name`, `tool_description`, `tool_category`, `tool_parameters`
|
|
@@ -77,7 +101,7 @@ Skills are the preferred way to add new high-level capabilities. A skill is a Ma
|
|
|
77
101
|
- Support different permission modes (confirm_all, confirm_edits, auto_approve, plan_only)
|
|
78
102
|
|
|
79
103
|
## Dependencies
|
|
80
|
-
- Ruby >=
|
|
104
|
+
- Ruby >= 2.6.0, < 4.1 (see "Ruby 2.6 Compatibility" above)
|
|
81
105
|
- faraday (~> 2.0) - HTTP client
|
|
82
106
|
- thor (~> 1.3) - CLI framework
|
|
83
107
|
- tty-prompt (~> 0.23) - Interactive prompts
|
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.7] - 2026-08-10
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Gemini 3.6 Flash model support
|
|
11
|
+
- OAuth for remote MCP HTTP servers (#456 - @sengmitnick)
|
|
12
|
+
- Parallel subagents, extension event bus, and collapsible phase UI
|
|
13
|
+
- Delete sessions action in project menu
|
|
14
|
+
- Extension sessions grouped by source instead of hidden flag (#448)
|
|
15
|
+
- Hook agent
|
|
16
|
+
|
|
17
|
+
### Improved
|
|
18
|
+
- Hide todo panel when only single task exists
|
|
19
|
+
- Simplify CLI memory updater output
|
|
20
|
+
- Redesign web search settings UI with unified dropdown icons
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- New session inherits project working directory instead of falling back to global default (#468 - @kylezhang)
|
|
24
|
+
- Client retry: replace undefined Clacky::Error with RetryableError on retry (#460 - @shipinliang)
|
|
25
|
+
- Guard against nil tool parameters when formatting definitions (#461 - @shipinliang)
|
|
26
|
+
- Double-serialization of tool call arguments with schema-aware fix (#457 - @shipinliang)
|
|
27
|
+
- Project-scoped cron sessions now show in project area, not cron group
|
|
28
|
+
- Project sidebar now shows newly created session immediately
|
|
29
|
+
- Ruby 2.6 compatibility: replace 3.0+ syntax and pin parallel_tests
|
|
30
|
+
- Token provider Anthropic usage reporting fixes
|
|
31
|
+
- Fix chunk JSON handling
|
|
32
|
+
|
|
7
33
|
## [1.5.6] - 2026-08-08
|
|
8
34
|
|
|
9
35
|
### Added
|
data/CONTRIBUTING.md
CHANGED
|
@@ -48,6 +48,15 @@ PR description.
|
|
|
48
48
|
- All tests **must pass** before a PR can be merged.
|
|
49
49
|
- **Coverage must not drop.** New code needs new tests.
|
|
50
50
|
|
|
51
|
+
```bash
|
|
52
|
+
bundle exec rspec # serial, ~45s
|
|
53
|
+
bundle exec rake spec:parallel # across all cores, ~22s
|
|
54
|
+
bundle exec rake coverage # parallel + merged HTML report at coverage/index.html
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Coverage is opt-in (`COVERAGE=1`), so a normal run pays nothing for it.
|
|
58
|
+
Set `COVERAGE_MIN_LINE=<pct>` to fail the run below a line-coverage floor.
|
|
59
|
+
|
|
51
60
|
### Commits & PRs
|
|
52
61
|
|
|
53
62
|
- **Write commit messages and PR titles/descriptions in English.** This applies
|
data/Rakefile
CHANGED
|
@@ -5,6 +5,21 @@ require "rspec/core/rake_task"
|
|
|
5
5
|
|
|
6
6
|
RSpec::Core::RakeTask.new(:spec)
|
|
7
7
|
|
|
8
|
+
namespace :spec do
|
|
9
|
+
desc "Run the suite in parallel across CPU cores"
|
|
10
|
+
task :parallel do
|
|
11
|
+
sh "bundle exec parallel_rspec spec"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc "Run the suite with coverage (HTML report in coverage/index.html)"
|
|
16
|
+
task :coverage do
|
|
17
|
+
ENV["COVERAGE"] = "1"
|
|
18
|
+
rm_rf "coverage"
|
|
19
|
+
sh "bundle exec parallel_rspec spec"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
8
23
|
namespace :build do
|
|
9
24
|
desc "Build both openclacky and clacky gems"
|
|
10
25
|
task :all do
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# 设计草案:通用事件通道(emit)与 background 技能调用
|
|
2
|
+
|
|
3
|
+
状态:**草案,待 review**。本文只描述设计,不含实现。
|
|
4
|
+
|
|
5
|
+
调研基线:`VERSION = 1.5.5`,工作副本 `~/workspace/openclacky-all/openclacky`。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目录
|
|
10
|
+
|
|
11
|
+
- [背景](#背景)
|
|
12
|
+
- [提案一:UIInterface#emit 通用事件通道](#提案一uiinterfaceemit-通用事件通道)
|
|
13
|
+
- [提案二:invoke_skill background](#提案二invoke_skill-background)
|
|
14
|
+
- [两者的依赖关系](#两者的依赖关系)
|
|
15
|
+
- [风险与回滚](#风险与回滚)
|
|
16
|
+
- [待决问题](#待决问题)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 背景
|
|
21
|
+
|
|
22
|
+
两个需求:
|
|
23
|
+
|
|
24
|
+
1. 让 `invoke_skill` 原生支持 `background`(不阻塞主循环,后台跑子 agent)。
|
|
25
|
+
2. 让扩展能够发出自定义事件。
|
|
26
|
+
|
|
27
|
+
调研后结论:**需求 2 是需求 1 的前置**。后台子 agent 若无法向外发事件,用户在它运行期间只能干等,background 的价值大打折扣。因此建议拆成两个 PR,提案一先行。
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 提案一:UIInterface#emit 通用事件通道
|
|
32
|
+
|
|
33
|
+
### 现状问题
|
|
34
|
+
|
|
35
|
+
`lib/clacky/ui_interface.rb` 定义的是**固定方法签名接口**,每类事件一个具名方法:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
def show_assistant_message(content, files:); end
|
|
39
|
+
def show_subagent_start(skill: nil, iterations: nil, cost_usd: nil); end
|
|
40
|
+
def show_tool_call(name, args); end
|
|
41
|
+
def show_progress(...); end
|
|
42
|
+
# ...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
后果:扩展想新增一种事件类型,必须修改 `UIInterface` 并同步全部实现。当前有 **8 个 UI 实现**:
|
|
46
|
+
|
|
47
|
+
| 文件 | 定义 phase_start | 定义 emit |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `lib/clacky/server/web_ui_controller.rb` | ✅ | ✅ |
|
|
50
|
+
| `lib/clacky/json_ui_controller.rb` | — | ✅ |
|
|
51
|
+
| `lib/clacky/ui2/ui_controller.rb` | ✅ | — |
|
|
52
|
+
| `lib/clacky/null_ui_controller.rb` | — | — |
|
|
53
|
+
| `lib/clacky/plain_ui_controller.rb` | — | — |
|
|
54
|
+
| `lib/clacky/rich_ui/rich_ui_controller.rb` | — | — |
|
|
55
|
+
| `lib/clacky/rich_ui_controller.rb` | — | — |
|
|
56
|
+
| `lib/clacky/server/channel/channel_ui_controller.rb` | — | — |
|
|
57
|
+
|
|
58
|
+
对扩展而言这是一个封闭接口。
|
|
59
|
+
|
|
60
|
+
### 关键依据:抽象已自发出现两次,且签名一致
|
|
61
|
+
|
|
62
|
+
`web_ui_controller.rb:446`
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
def emit(type, **data)
|
|
66
|
+
event = { type: type, session_id: @session_id }.merge(data)
|
|
67
|
+
if (pid = Thread.current[:clacky_phase_id]) && !data.key?(:phase_id)
|
|
68
|
+
event[:phase_id] = pid
|
|
69
|
+
end
|
|
70
|
+
@broadcaster.call(@session_id, event)
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`json_ui_controller.rb:21`
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
def emit(type, **data)
|
|
78
|
+
event = { type: type }.merge(data)
|
|
79
|
+
@mutex.synchronize do
|
|
80
|
+
@output.puts(JSON.generate(event))
|
|
81
|
+
@output.flush
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
两处独立演化出**完全相同的签名** `emit(type, **data)`,只是投递目标不同(broadcaster / stdout)。这是应当提炼为公共契约的强信号。目前 `emit` 未出现在 `UIInterface` 中,扩展无法依赖它。
|
|
87
|
+
|
|
88
|
+
### 设计
|
|
89
|
+
|
|
90
|
+
在 `UIInterface` 增加默认空实现:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
# Emit a structured event. Unknown event types are silently dropped by UIs
|
|
94
|
+
# that don't understand them — this is what makes the channel extensible.
|
|
95
|
+
def emit(type, **data); end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
要点:
|
|
99
|
+
|
|
100
|
+
- **零破坏**。`UIInterface` 全部方法本就是空实现默认值,未实现 `emit` 的 6 个 UI 自动获得 no-op,静默丢弃未知事件——正是前向兼容所需语义。
|
|
101
|
+
- **两处已有实现自然满足契约**,无需改写行为,只需确认签名一致。
|
|
102
|
+
- **命名空间约定**:建议扩展事件统一带前缀(如 `ext.<name>.<event>`),避免与内置类型冲突。此约定需写入文档,不做运行时强制。
|
|
103
|
+
|
|
104
|
+
### 附带发现:phase 分组是线程局部的
|
|
105
|
+
|
|
106
|
+
`web_ui_controller.rb:228`
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
def phase_start(kind:, label: nil)
|
|
110
|
+
pid = SecureRandom.uuid
|
|
111
|
+
Thread.current[:clacky_phase_id] = pid
|
|
112
|
+
# ...
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
phase_id 存于 `Thread.current`,而 `emit` 会自动将其注入事件(`:448-450`)。
|
|
117
|
+
|
|
118
|
+
含义:**并行子 agent 各占一个线程时,其发出的事件会自动携带各自的分组 ID,无需手动透传 `subagent_id`。** 这为提案二的 UI 分栏提供了现成载体。`UIInterface#phase_start` 已有默认实现(`ui_interface.rb:166`,返回一个 UUID),未实现的 UI 同样安全。
|
|
119
|
+
|
|
120
|
+
### 影响面
|
|
121
|
+
|
|
122
|
+
新增一个默认 no-op 方法;可选地为两处已有实现补充注释说明其契约来源。不改变任何现有行为。
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 提案二:invoke_skill background
|
|
127
|
+
|
|
128
|
+
### 入口现状
|
|
129
|
+
|
|
130
|
+
`lib/clacky/tools/invoke_skill.rb` 共 117 行,参数仅 `skill_name`、`task`。执行时按技能配置分叉:
|
|
131
|
+
|
|
132
|
+
- `fork_agent?` 为真 → 走 subagent(`agent/skill_manager.rb:580 execute_skill_with_subagent`)
|
|
133
|
+
- 否则 → `enqueue_injection`(`agent.rb:1442`)延迟注入
|
|
134
|
+
|
|
135
|
+
新增一个 `background: false` 布尔参数,入口改动面很小。
|
|
136
|
+
|
|
137
|
+
**当前执行模型是彻底串行的**:`agent.rb:1117` 的工具循环为 `tool_calls.each_with_index`,即使 LLM 单轮返回多个工具调用,也是逐个执行、前一个完成才开始下一个。加上 `run_detached` 的同步阻塞(见下文),**当前不存在任何形式的后台或并发子 agent**。
|
|
138
|
+
|
|
139
|
+
### 硬约束:tool_call 必须配对 tool_result
|
|
140
|
+
|
|
141
|
+
LLM 的工具调用是请求-响应配对的,每个 `tool_call` 必须有对应的 `tool_result` 才能继续对话;Bedrock / Anthropic 对消息顺序尤其严格(`invoke_skill.rb` 内已有注释专门警告此点)。
|
|
142
|
+
|
|
143
|
+
因此 `background: true` **必须立即返回一个 tool_result**(形如「已启动,handle=xxx」),真实结果稍后通过其他通道回到对话。这就需要明确定义**收割机制**。
|
|
144
|
+
|
|
145
|
+
### 收割机制的两个候选
|
|
146
|
+
|
|
147
|
+
**方案 A:下一轮 turn 自动注入**
|
|
148
|
+
|
|
149
|
+
复用现有 `@pending_injections`(`agent.rb:1442-1444`)。后台任务完成后把结果排入队列,下一轮 turn 自动注入上下文。
|
|
150
|
+
|
|
151
|
+
- 优点:复用成熟通道;对 LLM 透明,无需新工具;不增加模型的认知负担。
|
|
152
|
+
- 缺点:LLM 无法主动等待——若它下一步就需要结果,只能空转一轮。
|
|
153
|
+
|
|
154
|
+
**方案 B:新增 `wait_for` 工具**
|
|
155
|
+
|
|
156
|
+
后台任务返回 handle,LLM 显式调用 `wait_for(handle)` 阻塞取结果。
|
|
157
|
+
|
|
158
|
+
- 优点:控制力强,可表达「先并发启动 N 个,再一起等」的模式,这是真并行的关键。
|
|
159
|
+
- 缺点:多一个工具;LLM 可能忘记调用,导致任务结果永远无人收割(需兜底:turn 结束时强制收割未取结果)。
|
|
160
|
+
|
|
161
|
+
倾向:**若目标是真并行,方案 B 更契合**;方案 A 更适合「发射后不管」的通知类任务。两者不互斥,可先做 A 再加 B。
|
|
162
|
+
|
|
163
|
+
### 必须先修的并发缺陷
|
|
164
|
+
|
|
165
|
+
**`@pending_subagent_transcript` 是单数槽位**(`agent.rb:113`),消费点 `agent.rb:1404`:
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
private def attach_pending_subagent_transcript(response)
|
|
169
|
+
transcript = @pending_subagent_transcript
|
|
170
|
+
return unless transcript
|
|
171
|
+
@pending_subagent_transcript = nil
|
|
172
|
+
|
|
173
|
+
skill_call = Array(response[:tool_calls]).find { |tc| (tc[:name] || tc.dig(:function, :name)) == "invoke_skill" }
|
|
174
|
+
target_id = skill_call && skill_call[:id]
|
|
175
|
+
return unless target_id
|
|
176
|
+
|
|
177
|
+
@history.attach_to_tool_result(target_id, :subagent_transcript, transcript)
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
两个缺陷:
|
|
182
|
+
|
|
183
|
+
1. 单槽位——多个后台子 agent 先后完成会互相覆盖,先完成者的轨迹丢失。
|
|
184
|
+
2. `find { ... }` 只取**第一个** `invoke_skill` 调用——同一轮内有多个 `invoke_skill` 时必然张冠李戴,把 A 的轨迹挂到 B 的结果上。
|
|
185
|
+
|
|
186
|
+
修法:改为按 `tool_call_id` 索引的 map,写入时携带发起调用的 id,消费时精确匹配。此缺陷在当前串行场景下也存在(同一轮多次 `invoke_skill` 即可触发),**可作为独立 bugfix 先行提交**。
|
|
187
|
+
|
|
188
|
+
### 其余并发注意点(已核实)
|
|
189
|
+
|
|
190
|
+
| 项 | 结论 |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `check_stale!`(`agent.rb:1103`) | **不是障碍**。`@task_thread`(`time_machine.rb:81`)是实例变量,子 agent 在自己线程 `run` 时认领自身线程,子之间不互杀。**铁律:子线程绝不能回写父 history** |
|
|
193
|
+
| `ToolRegistry`(`agent.rb:84`)、`@todos`(`agent.rb:91`) | 每实例自建,天然隔离 |
|
|
194
|
+
| `Tools::Terminal::SessionManager`(`session_manager.rb:51`) | 唯一类级单例,自带 `@mutex` |
|
|
195
|
+
| 线程模型 | LLM 调用是 Faraday 同步阻塞 IO,GVL 在 IO 等待时释放 → `Thread.new` 即可真并行,**不需要 async 重构**。`web_search.rb:175` 已有 `Thread.new + Queue` fan-out 先例 |
|
|
196
|
+
| 工作目录 | 工具走 `working_dir` 注入,无 `Dir.chdir` 全局污染(`agent.rb:1205` 有注释说明) |
|
|
197
|
+
| 成本累加 | `skill_manager.rb:660` 的 `@total_cost += subagent_cost` 是非原子读-改-写,并行下需加锁 |
|
|
198
|
+
| UI 混流 | `fork_subagent` 默认继承父 `@ui`(`agent.rb:1668`);多个后台 agent 同时输出会交错,需靠提案一的 phase 分组隔离,或改用 `NullUIController` |
|
|
199
|
+
|
|
200
|
+
### 现成原型(注意:不含并发)
|
|
201
|
+
|
|
202
|
+
`agent.rb:1577 run_detached` 提供了「fork + NullUI + run + 只回收结果与成本、完全不碰父 history」的**执行体**原型,可作为 background 执行体的基础。
|
|
203
|
+
|
|
204
|
+
**但必须澄清:`run_detached` 目前完全是同步阻塞的,不存在任何后台执行能力。**
|
|
205
|
+
|
|
206
|
+
- `agent.rb:1589` 的 `result = subagent.run(task)` 会阻塞调用线程直到子 agent 跑完,无 `Thread.new`、无队列。
|
|
207
|
+
- 全仓唯一调用方 `extension/api_extension.rb:372` 写作 `{ text: agent.run_detached(...) }`,直接取返回值作文本使用,是同步语义的直接证据。
|
|
208
|
+
- 方法名中的 "detached" 与其 system prompt 中的 "background analysis" 指的是**与主对话历史脱钩**(结果不写入父 history),属于**可见性**概念,**不是时间上的并发**。此命名易被误读,建议在实现 background 时一并澄清或重命名。
|
|
209
|
+
|
|
210
|
+
因此提案二需要新建的是**调度与结果回流通路**,而非在既有能力上做增强。
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 两者的依赖关系
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
提案一(emit 通用通道) ← 独立可用,先行
|
|
218
|
+
│
|
|
219
|
+
├── 后台任务进度可见(否则用户干等)
|
|
220
|
+
│
|
|
221
|
+
提案二(background 调用) ← 依赖提案一
|
|
222
|
+
│
|
|
223
|
+
└── 前置 bugfix:pending_subagent_transcript 单槽位 + find 误匹配
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
建议拆分为三个 PR:
|
|
227
|
+
|
|
228
|
+
1. **bugfix**:`pending_subagent_transcript` 改 map,修同轮多次 `invoke_skill` 的错配(当前串行即可复现,独立有价值)
|
|
229
|
+
2. **feature**:`UIInterface#emit` 通用事件通道(零破坏,不涉并发,易被接受)
|
|
230
|
+
3. **feature**:`invoke_skill(background:)` + 收割机制(依赖前两个)
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 风险与回滚
|
|
235
|
+
|
|
236
|
+
| 风险 | 评估 | 缓解 |
|
|
237
|
+
|---|---|---|
|
|
238
|
+
| 新增 `emit` 破坏现有 UI | 极低。默认 no-op,现有两处实现签名已一致 | 无需缓解 |
|
|
239
|
+
| 扩展事件类型与内置冲突 | 中 | 约定 `ext.` 前缀命名空间,写入文档 |
|
|
240
|
+
| 后台任务结果无人收割 | 中(方案 B 特有) | turn 结束时强制收割兜底 |
|
|
241
|
+
| 并行破坏消息顺序 | 高(若实现不当) | 铁律:子线程只返回结果,由父线程统一写入 history |
|
|
242
|
+
| 成本统计竞态 | 低 | `@total_cost` 累加加锁 |
|
|
243
|
+
|
|
244
|
+
三个 PR 均为增量改动,回滚即 revert,无数据迁移。
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 待决问题
|
|
249
|
+
|
|
250
|
+
1. 收割机制选方案 A(自动注入)还是 B(`wait_for` 工具)?
|
|
251
|
+
2. 后台任务的上下文策略:深拷贝父 history(保 prompt cache,成本乘 N)还是空上下文播种(省 token、强隔离)?
|
|
252
|
+
3. 并发上限与超时策略如何设定?
|
|
253
|
+
4. 三个 PR 是提交到上游,还是先在自有环境验证后再提?
|
data/docs/mcp-architecture.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# MCP Support — Design Notes
|
|
2
2
|
|
|
3
3
|
OpenClacky speaks the **Model Context Protocol** (MCP) so users can plug in
|
|
4
|
-
the same servers they already use with Claude Desktop, Cursor, etc.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
flat as users add more servers.
|
|
4
|
+
the same servers they already use with Claude Desktop, Cursor, etc. Both
|
|
5
|
+
local stdio servers and remote Streamable HTTP servers are supported. The
|
|
6
|
+
config uses an `mcpServers` map in `mcp.json`, while the internal architecture
|
|
7
|
+
keeps main-context tokens flat as users add more servers.
|
|
8
8
|
|
|
9
9
|
## The problem with naive MCP integration
|
|
10
10
|
|
|
@@ -63,11 +63,10 @@ Why a user message and not the system prompt:
|
|
|
63
63
|
|
|
64
64
|
### 4. Lazy startup, idle reaping
|
|
65
65
|
|
|
66
|
-
`Mcp::Registry` does **not**
|
|
66
|
+
`Mcp::Registry` does **not** start a connection at boot. The first
|
|
67
67
|
`call_tool` (or first time a subagent fetches the catalog) triggers
|
|
68
|
-
`ensure_started`. A background reaper
|
|
69
|
-
minutes of inactivity.
|
|
70
|
-
local processes the agent talks to over stdio.
|
|
68
|
+
`ensure_started`. A background reaper closes local processes and remote
|
|
69
|
+
sessions after five minutes of inactivity.
|
|
71
70
|
|
|
72
71
|
## Token-budget summary
|
|
73
72
|
|
|
@@ -82,7 +81,9 @@ naive integration: ~6 000 × 10 ≈ 60 000 tokens up front.
|
|
|
82
81
|
|
|
83
82
|
## Files
|
|
84
83
|
|
|
85
|
-
- `lib/clacky/mcp/client.rb` —
|
|
84
|
+
- `lib/clacky/mcp/client.rb` — JSON-RPC 2.0 client for stdio and HTTP
|
|
85
|
+
- `lib/clacky/mcp/http_transport.rb` — Streamable HTTP transport
|
|
86
|
+
- `lib/clacky/mcp/oauth/` — OAuth discovery, PKCE, refresh, and credential storage
|
|
86
87
|
- `lib/clacky/mcp/registry.rb` — config loading, lazy starts, idle reaping
|
|
87
88
|
- `lib/clacky/mcp/virtual_skill.rb` — synthesized Skill per server
|
|
88
89
|
- `lib/clacky/tools/mcp_call.rb` — the single bridge tool
|
|
@@ -112,3 +113,41 @@ Format matches Claude Desktop / Cursor:
|
|
|
112
113
|
|
|
113
114
|
`description` is OpenClacky-specific and recommended — it's what the main
|
|
114
115
|
agent sees when deciding whether to call into a given server.
|
|
116
|
+
|
|
117
|
+
### Remote HTTP with OAuth
|
|
118
|
+
|
|
119
|
+
OAuth-protected remote servers use the standard protected-resource metadata,
|
|
120
|
+
authorization-server metadata, dynamic client registration, PKCE S256, and
|
|
121
|
+
refresh-token flow:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"mcpServers": {
|
|
126
|
+
"video-editor": {
|
|
127
|
+
"type": "http",
|
|
128
|
+
"url": "https://mcp.example.com/mcp",
|
|
129
|
+
"auth": {
|
|
130
|
+
"type": "oauth",
|
|
131
|
+
"resource": "https://mcp.example.com/mcp"
|
|
132
|
+
},
|
|
133
|
+
"description": "Create and edit videos."
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Manage authorization from the CLI:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
clacky mcp capabilities --json
|
|
143
|
+
clacky mcp login video-editor
|
|
144
|
+
clacky mcp status video-editor --json
|
|
145
|
+
clacky mcp logout video-editor
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Credentials are stored outside `mcp.json` under
|
|
149
|
+
`~/.clacky/mcp/oauth/`, with owner-only permissions. OAuth endpoints
|
|
150
|
+
must use HTTPS; callback redirects bind to a random loopback port. Access and
|
|
151
|
+
refresh tokens are never interpolated into MCP error messages. HTTP 401 response
|
|
152
|
+
bodies are omitted; other HTTP error bodies retain at most 500 characters for
|
|
153
|
+
diagnostics, matching the existing HTTP transport behavior.
|
data/docs/mcp.example.json
CHANGED
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
"command": "uvx",
|
|
18
18
|
"args": ["mcp-server-sqlite", "--db-path", "/path/to/db.sqlite"],
|
|
19
19
|
"description": "Query a local SQLite database."
|
|
20
|
+
},
|
|
21
|
+
"remote-oauth": {
|
|
22
|
+
"type": "http",
|
|
23
|
+
"url": "https://mcp.example.com/mcp",
|
|
24
|
+
"auth": {
|
|
25
|
+
"type": "oauth",
|
|
26
|
+
"resource": "https://mcp.example.com/mcp"
|
|
27
|
+
},
|
|
28
|
+
"description": "Example OAuth-protected remote MCP server."
|
|
20
29
|
}
|
|
21
30
|
}
|
|
22
31
|
}
|