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
|
@@ -58,32 +58,29 @@
|
|
|
58
58
|
// The DOM id "messages" is never swapped: external code, CSS, devtools and
|
|
59
59
|
// closures all see a stable identity.
|
|
60
60
|
const RenderTarget = (() => {
|
|
61
|
-
|
|
61
|
+
let pinned = null;
|
|
62
62
|
return {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
// Concurrent phases make "innermost open phase" meaningless, so events
|
|
64
|
+
// are routed by their own phase_id (pin) and anything untagged goes to
|
|
65
|
+
// the outer stream rather than whichever phase happens to be open.
|
|
66
|
+
pin(el) { pinned = el; },
|
|
67
|
+
unpin() { pinned = null; },
|
|
68
|
+
current(){ return pinned || document.getElementById("messages"); },
|
|
66
69
|
outer() { return document.getElementById("messages"); },
|
|
67
|
-
depth() { return stack.length; },
|
|
68
70
|
};
|
|
69
71
|
})();
|
|
70
72
|
window.RenderTarget = RenderTarget;
|
|
71
73
|
|
|
72
|
-
const
|
|
74
|
+
const _phases = new Map(); // phase_id -> { id, kind, card, body, summary, order }
|
|
75
|
+
let _phaseSeq = 0;
|
|
73
76
|
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
function _phaseLabel(kind) {
|
|
79
|
-
const map = {
|
|
80
|
-
skill_evolution: I18n.t ? (I18n.t("phase.skill_evolution") || "Skill evolution") : "Skill evolution",
|
|
81
|
-
};
|
|
82
|
-
return map[kind] || kind;
|
|
77
|
+
function _phaseIcon(kind) {
|
|
78
|
+
const map = { subagent: "🤖", fanout_subagent: "🤖", memory_update: "🧠" };
|
|
79
|
+
return map[kind] || "🧬";
|
|
83
80
|
}
|
|
84
81
|
|
|
85
82
|
function _beginPhase(ev) {
|
|
86
|
-
if (
|
|
83
|
+
if (_phases.has(ev.phase_id)) return;
|
|
87
84
|
|
|
88
85
|
const outer = RenderTarget.outer();
|
|
89
86
|
if (!outer) return;
|
|
@@ -92,11 +89,13 @@
|
|
|
92
89
|
card.className = "msg-phase";
|
|
93
90
|
card.dataset.phaseId = ev.phase_id;
|
|
94
91
|
card.dataset.phaseKind = ev.kind || "phase";
|
|
92
|
+
card.open = true;
|
|
95
93
|
|
|
96
94
|
const summary = document.createElement("summary");
|
|
97
95
|
summary.className = "msg-phase-summary";
|
|
98
|
-
const labelText = ev.label ||
|
|
99
|
-
|
|
96
|
+
const labelText = ev.label || ev.kind;
|
|
97
|
+
const icon = _phaseIcon(ev.kind);
|
|
98
|
+
summary.innerHTML = `<span class="msg-phase-icon">${icon}</span><span class="msg-phase-label">${escapeHtml(labelText)}</span><span class="msg-phase-status">…</span>`;
|
|
100
99
|
card.appendChild(summary);
|
|
101
100
|
|
|
102
101
|
const body = document.createElement("div");
|
|
@@ -104,33 +103,42 @@
|
|
|
104
103
|
card.appendChild(body);
|
|
105
104
|
|
|
106
105
|
outer.appendChild(card);
|
|
107
|
-
RenderTarget.push(body);
|
|
108
106
|
|
|
109
|
-
|
|
107
|
+
_phases.set(ev.phase_id, {
|
|
110
108
|
id: ev.phase_id,
|
|
111
109
|
kind: ev.kind,
|
|
112
110
|
card,
|
|
113
111
|
body,
|
|
114
112
|
summary,
|
|
113
|
+
order: _phaseSeq++,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const phase = _phases.get(ev.phase_id);
|
|
117
|
+
card.addEventListener("toggle", () => {
|
|
118
|
+
if (!phase.programmaticToggle) phase.userToggled = true;
|
|
115
119
|
});
|
|
116
120
|
|
|
117
121
|
outer.scrollTop = outer.scrollHeight;
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
function _endPhase(phaseId, summary) {
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
const phase = _phases.get(phaseId);
|
|
126
|
+
if (!phase) return;
|
|
127
|
+
_phases.delete(phaseId);
|
|
128
|
+
_finalizePhase(phase, { summary });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function _setPhaseOpen(phase, open) {
|
|
132
|
+
phase.programmaticToggle = true;
|
|
133
|
+
phase.card.open = open;
|
|
134
|
+
phase.programmaticToggle = false;
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
function _finalizePhase(phase, { summary, incomplete } = {}) {
|
|
131
138
|
const body = phase.body;
|
|
132
139
|
const isEmpty = !incomplete && body && body.children.length === 0;
|
|
133
140
|
if (isEmpty) phase.card.classList.add("msg-phase-empty");
|
|
141
|
+
if (!phase.userToggled) _setPhaseOpen(phase, false);
|
|
134
142
|
|
|
135
143
|
const statusEl = phase.summary && phase.summary.querySelector(".msg-phase-status");
|
|
136
144
|
if (statusEl) {
|
|
@@ -138,7 +146,7 @@
|
|
|
138
146
|
statusEl.textContent = " (interrupted)";
|
|
139
147
|
statusEl.classList.add("msg-phase-status-incomplete");
|
|
140
148
|
} else if (isEmpty) {
|
|
141
|
-
const noChange = I18n.t
|
|
149
|
+
const noChange = I18n.t("phase.no_changes");
|
|
142
150
|
statusEl.textContent = ` ✓ ${noChange}`;
|
|
143
151
|
} else if (summary) {
|
|
144
152
|
statusEl.textContent = ` ✓ ${summary}`;
|
|
@@ -149,11 +157,11 @@
|
|
|
149
157
|
}
|
|
150
158
|
|
|
151
159
|
function _closeAllPhases(reason) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
160
|
+
const phases = [..._phases.values()].sort((a, b) => b.order - a.order);
|
|
161
|
+
_phases.clear();
|
|
162
|
+
phases.forEach(phase => {
|
|
155
163
|
_finalizePhase(phase, { incomplete: reason === "incomplete" });
|
|
156
|
-
}
|
|
164
|
+
});
|
|
157
165
|
}
|
|
158
166
|
|
|
159
167
|
window._closeAllPhases = _closeAllPhases;
|
|
@@ -167,6 +175,20 @@ WS.onEvent(ev => {
|
|
|
167
175
|
_closeAllPhases("incomplete");
|
|
168
176
|
}
|
|
169
177
|
|
|
178
|
+
// Concurrent phases interleave, so stack order says nothing about where an
|
|
179
|
+
// event belongs — route by the phase_id the backend stamped on it.
|
|
180
|
+
const _taggedPhase = ev.phase_id && ev.type !== "phase_start" && ev.type !== "phase_end"
|
|
181
|
+
? _phases.get(ev.phase_id)
|
|
182
|
+
: null;
|
|
183
|
+
if (_taggedPhase) RenderTarget.pin(_taggedPhase.body);
|
|
184
|
+
try {
|
|
185
|
+
_dispatchEvent(ev);
|
|
186
|
+
} finally {
|
|
187
|
+
if (_taggedPhase) RenderTarget.unpin();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
function _dispatchEvent(ev) {
|
|
170
192
|
// Bridge before the switch: per-case active-session guards must not block
|
|
171
193
|
// the extension bus. All received events mirror onto Clacky.ext here.
|
|
172
194
|
if (window.Clacky && Clacky.ext) {
|
|
@@ -185,6 +207,11 @@ WS.onEvent(ev => {
|
|
|
185
207
|
}
|
|
186
208
|
const sid = payload.session_id || (ev.session && ev.session.id);
|
|
187
209
|
Clacky.ext.emit(extName, { sessionId: sid, ...payload, session_id: sid });
|
|
210
|
+
} else if (typeof ev.type === "string" && ev.type.startsWith("ext.")) {
|
|
211
|
+
// Custom events from UIInterface#emit. The host has no rendering for
|
|
212
|
+
// these; they exist purely so an extension can observe its own backend.
|
|
213
|
+
const sid = ev.session_id;
|
|
214
|
+
Clacky.ext.emit(ev.type, { sessionId: sid, ...ev });
|
|
188
215
|
}
|
|
189
216
|
}
|
|
190
217
|
|
|
@@ -237,7 +264,7 @@ WS.onEvent(ev => {
|
|
|
237
264
|
|
|
238
265
|
// ── Session list ───────────────────────────────────────────────────
|
|
239
266
|
case "session_list": {
|
|
240
|
-
Sessions.setAll(ev.sessions || [], !!ev.has_more, ev.
|
|
267
|
+
Sessions.setAll(ev.sessions || [], !!ev.has_more, ev.groups || {});
|
|
241
268
|
Projects.setAll(ev.projects || []);
|
|
242
269
|
Sessions.renderList();
|
|
243
270
|
Projects.renderSection();
|
|
@@ -519,7 +546,7 @@ WS.onEvent(ev => {
|
|
|
519
546
|
break;
|
|
520
547
|
|
|
521
548
|
}
|
|
522
|
-
}
|
|
549
|
+
}
|
|
523
550
|
|
|
524
551
|
// ── Ext-developer refresh hint ─────────────────────────────────────────────
|
|
525
552
|
// Appended after a completed ext-developer session so the user can reload
|
data/lib/clacky.rb
CHANGED
|
@@ -86,6 +86,7 @@ require_relative "clacky/agent/message_compressor"
|
|
|
86
86
|
require_relative "clacky/agent/hook_manager"
|
|
87
87
|
require_relative "clacky/shell_hook_loader"
|
|
88
88
|
require_relative "clacky/agent/tool_registry"
|
|
89
|
+
require_relative "clacky/fanout"
|
|
89
90
|
|
|
90
91
|
# UI modules
|
|
91
92
|
require_relative "clacky/ui2/thinking_verbs"
|
|
@@ -201,6 +202,7 @@ module Clacky
|
|
|
201
202
|
class UpstreamTruncatedError < RetryableError; end
|
|
202
203
|
class ToolCallError < AgentError; end # Raised when tool call fails due to invalid parameters
|
|
203
204
|
class BrowserNotReachableError < AgentError; end # Chrome/Edge not running or remote debugging disabled
|
|
205
|
+
class FanoutTimeoutError < AgentError; end # A fan-out job exceeded the batch wall-clock budget
|
|
204
206
|
# BrowserManager singleton: Clacky::BrowserManager.instance
|
|
205
207
|
end
|
|
206
208
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openclacky
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.5.
|
|
4
|
+
version: 1.5.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- windy
|
|
@@ -324,12 +324,16 @@ files:
|
|
|
324
324
|
- docs/agent-first-ui-design.md
|
|
325
325
|
- docs/billing-system.md
|
|
326
326
|
- docs/channel-architecture.md
|
|
327
|
+
- docs/design-emit-and-background-skills.md
|
|
327
328
|
- docs/engineering-article.md
|
|
328
329
|
- docs/mcp-architecture.md
|
|
329
330
|
- docs/mcp.example.json
|
|
330
331
|
- docs/rich_ui_guide.md
|
|
331
332
|
- docs/rich_ui_refactor_plan.md
|
|
332
333
|
- docs/session-skill-invocation.md
|
|
334
|
+
- docs/superpowers/plans/2026-08-09-chatcut-extension-bridge.md
|
|
335
|
+
- docs/superpowers/plans/2026-08-09-native-mcp-oauth.md
|
|
336
|
+
- docs/superpowers/specs/2026-08-09-mcp-oauth-extension-fallback-design.md
|
|
333
337
|
- docs/time_machine_design.md
|
|
334
338
|
- docs/ui2-architecture.md
|
|
335
339
|
- homebrew/README.md
|
|
@@ -463,14 +467,21 @@ files:
|
|
|
463
467
|
- lib/clacky/extension/scaffold/templates/hello/ext.yml.erb
|
|
464
468
|
- lib/clacky/extension/scaffold/templates/hello/panels/hello/view.js.erb
|
|
465
469
|
- lib/clacky/extension/verifier.rb
|
|
470
|
+
- lib/clacky/fanout.rb
|
|
466
471
|
- lib/clacky/identity.rb
|
|
467
472
|
- lib/clacky/idle_compression_timer.rb
|
|
468
473
|
- lib/clacky/json_ui_controller.rb
|
|
469
474
|
- lib/clacky/locales/en.rb
|
|
470
475
|
- lib/clacky/locales/i18n.rb
|
|
471
476
|
- lib/clacky/locales/zh.rb
|
|
477
|
+
- lib/clacky/mcp/cli_commands.rb
|
|
472
478
|
- lib/clacky/mcp/client.rb
|
|
479
|
+
- lib/clacky/mcp/connection_manager.rb
|
|
473
480
|
- lib/clacky/mcp/http_transport.rb
|
|
481
|
+
- lib/clacky/mcp/oauth/authorization_manager.rb
|
|
482
|
+
- lib/clacky/mcp/oauth/config.rb
|
|
483
|
+
- lib/clacky/mcp/oauth/credential_store.rb
|
|
484
|
+
- lib/clacky/mcp/oauth/session.rb
|
|
474
485
|
- lib/clacky/mcp/registry.rb
|
|
475
486
|
- lib/clacky/mcp/skill_provider.rb
|
|
476
487
|
- lib/clacky/mcp/stdio_transport.rb
|