openclacky 1.5.13 → 1.5.14

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -0
  3. data/lib/clacky/agent/chunk_index.rb +83 -0
  4. data/lib/clacky/agent/history_navigation.rb +239 -0
  5. data/lib/clacky/agent/session_serializer.rb +38 -97
  6. data/lib/clacky/agent.rb +281 -276
  7. data/lib/clacky/agent_config.rb +1 -1
  8. data/lib/clacky/billing/billing_store.rb +2 -2
  9. data/lib/clacky/billing/platform_billing.rb +6 -0
  10. data/lib/clacky/brand_config.rb +13 -2
  11. data/lib/clacky/cli.rb +27 -0
  12. data/lib/clacky/client.rb +2 -2
  13. data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +70 -134
  14. data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +91 -5
  15. data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +179 -577
  16. data/lib/clacky/default_extensions/git/panels/git/view.js +34 -14
  17. data/lib/clacky/default_extensions/preview/ext.yml +20 -0
  18. data/lib/clacky/default_extensions/preview/panels/preview/view.js +475 -0
  19. data/lib/clacky/default_extensions/time_machine/panels/time_machine/view.js +1 -10
  20. data/lib/clacky/extension/api_extension.rb +1 -1
  21. data/lib/clacky/extension/verifier.rb +1 -1
  22. data/lib/clacky/media/openai_compat.rb +70 -30
  23. data/lib/clacky/message_format/bedrock.rb +6 -1
  24. data/lib/clacky/prompts/base.md +1 -1
  25. data/lib/clacky/providers.rb +38 -3
  26. data/lib/clacky/rich_ui/rich_ui_controller.rb +1 -1
  27. data/lib/clacky/search_config.rb +3 -3
  28. data/lib/clacky/server/channel/channel_manager.rb +8 -5
  29. data/lib/clacky/server/git_panel.rb +10 -2
  30. data/lib/clacky/server/http_server.rb +161 -14
  31. data/lib/clacky/server/preview.rb +351 -0
  32. data/lib/clacky/server/web_ui_controller.rb +1 -1
  33. data/lib/clacky/skill.rb +48 -0
  34. data/lib/clacky/tools/browser.rb +176 -19
  35. data/lib/clacky/tools/web_search.rb +79 -8
  36. data/lib/clacky/ui2/components/command_suggestions.rb +2 -1
  37. data/lib/clacky/ui2/components/input_area.rb +22 -2
  38. data/lib/clacky/ui2/components/modal_component.rb +35 -6
  39. data/lib/clacky/ui2/ui_controller.rb +44 -7
  40. data/lib/clacky/utils/file_processor.rb +19 -7
  41. data/lib/clacky/utils/mac_app_detector.rb +186 -0
  42. data/lib/clacky/utils/model_pricing.rb +114 -66
  43. data/lib/clacky/utils/windows_app_detector.rb +334 -0
  44. data/lib/clacky/version.rb +1 -1
  45. data/lib/clacky/web/app.css +596 -130
  46. data/lib/clacky/web/components/chat-navigator.js +489 -202
  47. data/lib/clacky/web/components/code-editor.js +191 -5
  48. data/lib/clacky/web/components/model-picker.js +2 -1
  49. data/lib/clacky/web/components/quote-select.js +235 -0
  50. data/lib/clacky/web/core/aside.js +157 -8
  51. data/lib/clacky/web/core/ext.js +16 -0
  52. data/lib/clacky/web/features/billing/view.js +75 -21
  53. data/lib/clacky/web/features/skills/store.js +18 -1
  54. data/lib/clacky/web/features/skills/view.js +128 -7
  55. data/lib/clacky/web/features/workspace/store.js +80 -7
  56. data/lib/clacky/web/features/workspace/view.js +392 -40
  57. data/lib/clacky/web/i18n.js +90 -5
  58. data/lib/clacky/web/index.html +15 -6
  59. data/lib/clacky/web/sessions.js +193 -38
  60. data/lib/clacky/web/utils.js +34 -0
  61. data/lib/clacky/web/vendor/codemirror/codemirror.min.js +24 -19
  62. data/lib/clacky/web/vendor/codemirror/entry.js +130 -0
  63. data/lib/clacky/web/vendor/codemirror/package.json +29 -0
  64. data/lib/clacky/web/ws-dispatcher.js +20 -0
  65. data/lib/clacky.rb +2 -0
  66. metadata +11 -1
@@ -1,4 +1,7 @@
1
1
  ---
2
+ # Maintenance note: Do not relax the installed-gem source restriction.
3
+ # After context compression, previously inspected host files may be
4
+ # mistaken for extension edit targets.
2
5
  name: ext-develop
3
6
  description: Build, debug, or publish an OpenClacky extension — scaffold a new one from an idea, fix a broken/invisible panel/api/skill/agent, or ship it to the marketplace. Trigger on create/start extension, plugin, panel, ext verify error, "won't load", "not showing up", publish/ship/unpublish an extension.
4
7
  agent: ext-developer
@@ -6,586 +9,185 @@ agent: ext-developer
6
9
 
7
10
  # Extension Development
8
11
 
9
- Build an OpenClacky extension end to end — scaffold, edit, verify, hot-reload, and
10
- (only when asked) publish. Prefer editing real files and verifying over describing.
11
-
12
- ## The extension model (ground truth)
13
-
14
- An extension is one directory with a single `ext.yml` manifest declaring
15
- `contributes:`. Nothing is nested — units reference each other by id. It survives
16
- `gem update` and never requires editing gem source.
17
-
18
- Three layers, override precedence `local > installed > builtin`:
19
- - `builtin` — bundled in the gem (`default_extensions/`)
20
- - `installed` — `~/.clacky/ext/installed/<id>/` (from `ext install`)
21
- - `local` — `~/.clacky/ext/local/<id>/` (where users develop; `ext new` lands here)
22
-
23
- Eight `contributes:` types (use one, several, or all):
24
- - `panels` — WebUI panels (a `view.js`, no build step, no React, no iframe)
25
- - `api` — one backend file `api/handler.rb`, mounted at `/api/ext/<id>/`
26
- - `skills` — a `SKILL.md` under `skills/<id>/` (prompt-only capability)
27
- - `agents` — a `system_prompt.md`; can reference `panels: [id]` and `skills: [id]`; `hidden: true` hides from the new-session picker
28
- - `channels` — an IM adapter
29
- - `patches` — monkey-patch a real class (advanced, supply-chain risk)
30
- - `hooks` — lifecycle hooks like `before_tool_use` (advanced)
31
- - `tools` — a custom agent tool (a `Clacky::Tools::Base` subclass in `tools/<id>.rb`)
32
-
33
- Hot reload is per-request: after editing `view.js`, `handler.rb`, or a `SKILL.md`,
34
- the user just reloads the WebUI page — no server restart. Editing `ext.yml` also
35
- applies on the next load.
36
-
37
- ## Hard rules — never break these
38
-
39
- - ❌ **Never edit the gem source.** Do NOT `bundle show openclacky` and change files
40
- in there. Everything lives in `~/.clacky/ext/local/<id>/` and survives `gem update`.
41
- - ❌ **Never `restart the server` to apply a change.** Hot reload is per-request —
42
- the user just reloads the WebUI page. If you're telling them to restart, you're wrong.
43
- - ❌ **Never declare success on "it should work."** A task is done only when
44
- `clacky ext verify` is clean AND the user reloaded and saw it work. Run verify —
45
- don't imagine its output.
46
- - ❌ **Never add `patches:` or `hooks:` unless the user explicitly asks.** They run
47
- arbitrary Ruby and carry supply-chain risk. Default to `panels`/`api`/`skills`/`agents`.
48
- - ❌ **Never publish on your own initiative.** Publishing is opt-in — see **Publish**.
49
- - ❌ **Never write `window.Sessions` / `"Sessions" in window` in `view.js`.** Host
50
- services are `const` bindings, not `window` properties — such checks return
51
- `undefined`/`false` even when loaded. Always use `Clacky.Sessions.*` etc.
52
- - ✅ **Always work in the `local` layer** (`~/.clacky/ext/local/<id>/`). `ext new` lands
53
- there; that's the only layer you edit.
54
-
55
- ## Which section do I need?
56
-
57
- Pick exactly ONE and follow it top to bottom. Don't blend the three.
58
-
59
- - Starting a new extension from an idea → **Scaffold**.
60
- - Something is broken, `verify` errors, or a change didn't show up → **Debug & verify**.
61
- - The user explicitly wants to share/ship it to others → **Publish** (optional; skip
62
- it entirely for extensions the user only runs themselves).
63
-
64
- **Reference: the contracts** is not a path — it's the field/slot/event/API ground truth
65
- you consult from whichever path you're on.
66
-
67
- ---
68
-
69
- ## Reference: the contracts
70
-
71
- Read the relevant reference doc with `web_fetch` before writing code — don't guess field
72
- names, hook events, adapter methods, or the `Clacky.ext` WebUI contract. These docs are
73
- long (well over the default cap); pass `max_length: 20000` so you get the whole page in one
74
- fetch instead of a truncated head full of nav chrome.
75
-
76
- ### Authoritative documentation
77
-
78
- - Extension system overview → https://www.openclacky.com/docs/extension-system
79
- - **ext.yml manifest — every field (names, avatar, title_zh, order, …)** → https://www.openclacky.com/docs/ext-manifest
80
- - Panels (WebUI) → https://www.openclacky.com/docs/extend-webui
81
- - API backends → https://www.openclacky.com/docs/extend-api
82
- - **Calling the host's native APIs from a panel (sessions, trash/file-recovery, skills, memories, cron, billing, media)** → https://www.openclacky.com/docs/extend-host-api
83
- - Agents (prompt, avatar, panels/skills wiring) → https://www.openclacky.com/docs/agent-config
84
- - Channel adapters → https://www.openclacky.com/docs/extend-channel-adapter
85
- - Patches → https://www.openclacky.com/docs/extend-patches
86
- - Shell hooks → https://www.openclacky.com/docs/extend-shell-hooks
87
-
88
- ### WebUI panels: the `Clacky.ext` contract
89
-
90
- A panel is a plain `view.js` (no build step, no React, no iframe). It reaches the host
91
- **only** through `window.Clacky` — everything else on the page is off-limits. There are
92
- exactly three capabilities:
93
-
94
- ```js
95
- Clacky.ext.ui.mount(slot, spec, opts) // inject UI into a named slot
96
- Clacky.ext.subscribe(event, handler) // observe store events + live session events (read-only)
97
- Clacky.ext.api.register(name, fn) // expose a named data source; api.resolve(name)
98
- ```
99
-
100
- **`subscribe(event, handler)`** - two event families: (1) host **store** events (`skills:changed`, `tasks:changed`, `profile:changed`, …) and (2) **live session events** mirrored from the WebSocket stream — conversation (`session:assistant-message`, `session:tool-call`, `session:tool-result`), status/errors (`session:error`, `session:warning`, `session:update`), lifecycle (`session:renamed`, `session:deleted`, …). Payload is `{ sessionId, ...wsFields }`. Full list in the "Core Events" section of the extend-webui docs. Handlers are read-only.
101
-
102
- **`ui.mount(slot, spec, opts)`** — `spec` is either `(container, ctx, runtime) => …` or
103
- `{ create?, render }`. The render function:
104
- - gets a host-owned `container` DOM element — append into it, **or** return a Node / HTML
105
- string and the host appends for you;
106
- - returning a **function** registers it as a teardown callback;
107
- - returning **`null`/`undefined` renders nothing** (safe) — a red "crashed" placeholder
108
- means the render **threw**; the #1 cause is a wrong signature shifting every argument
109
- (e.g. `(ctx) => …` instead of `(container, ctx) => …`). Match the signature exactly.
110
-
111
- `ctx` carries `{ sessionId, agentProfile }`. `opts`: `order` (lower renders first,
112
- default 100), `tab: { id, label, badge? }` (**required** for tabbed slots — `session.aside`
113
- is tabbed), `agents: [profile]` (override auto scope), `workspace: id` (for nav items).
114
-
115
- **Valid slot names** (mounting into any other name silently renders nothing, warned once):
116
-
117
- ```
118
- header.left header.right
119
- sidebar.nav.top sidebar.nav sidebar.nav.bottom sidebar.footer
120
- main.workspace
121
- session.banner session.composer session.aside (session.aside is tabbed)
122
- settings.tabs settings.body
123
- ```
124
-
125
- Agent scope is automatic: mounts into `session.*` / `settings.*` slots only show for the
126
- panel's owning agent(s); all other slots (`sidebar.*`, `header.*`, `main.workspace`) are
127
- global chrome. You rarely set `agents:` by hand.
128
-
129
- **Per-session state** — for `session.aside/banner/composer`, pass `{ create(ctx), render }`:
130
- `create` runs once per session and returns a runtime (put timers/recorders/subscriptions
131
- there), `render(container, ctx, runtime)` runs on each show, and `runtime.dispose()` runs
132
- when the session leaves. State survives tab switches; use this instead of module globals.
133
-
134
- **Full-page workspace** - `Clacky.ext.ui.registerWorkspace(id, { title, render })` takes
135
- over the main area with its own `#ext/<id>` URL; open it with `Clacky.ext.ui.openWorkspace(id)`,
136
- typically from a `sidebar.nav` item mounted with `opts.workspace: id`.
137
-
138
- **Sidebar nav items** - render the host nav-item structure (`div.task-item.task-item-summary >
139
- div.task-row > svg.task-icon + div.task-info > span.task-name` - copy `navRow()` from the
140
- `full` scaffold template) with `opts.workspace: id` so the item inherits theme styling
141
- and the Router's active highlight.
142
-
143
- **Safe mode** - `?pure=true` makes the whole registry a no-op; never rely on side effects
144
- outside these calls.
145
-
146
- ### Other host services under `Clacky.*`
147
-
148
- Beyond `Clacky.ext`, the host exposes stores as properties on `window.Clacky`. Use them
149
- instead of bare globals:
150
-
151
- ```js
152
- Clacky.Sessions.on("switched", handler); // active session store
153
- Clacky.Router.go("session"); // top-level view routing
154
- Clacky.Router.navigate("session", { id }); // navigate with params
155
- Clacky.I18n.t("some.key"); // translations
156
- Clacky.Modal.confirm("Delete?"); // dialogs
157
- Clacky.Modal.toast("Saved", "success"); // toasts (not window.alert)
158
- Clacky.Auth.passed; // auth state
159
- Clacky.Workspace.list(dir); // working-directory files
160
- Clacky.Skills.list(); // skill catalog
161
- Clacky.WS.send({ type: "..." }); // send a WebSocket message to the agent
162
- ```
163
-
164
- - Prefer `Clacky.Xxx.method(...)` — the recommended, forward-stable form. Never test with
165
- `window.Sessions` / `"Sessions" in window` (see Hard rules).
166
-
167
- A panel can also `fetch("/api/...")` the host's own REST endpoints directly (same origin,
168
- auth is automatic) — sessions, trash/**file-recovery**, skills, memories, cron, billing,
169
- media, and more each have a ready-made endpoint. Before telling a user a feature "can't be
170
- done" (e.g. "delete a file but keep it recoverable"), check whether the host already
171
- exposes it — `web_fetch` https://www.openclacky.com/docs/extend-host-api for the callable
172
- list. Don't rebuild what the host already provides.
173
-
174
- ### API backend: the `Clacky::ApiExtension` contract
175
-
176
- `api/handler.rb` subclasses `Clacky::ApiExtension`. Routes mount under
177
- `/api/ext/<ext_id>/`. This base class already wires up auth, JSON envelopes, timeouts, and
178
- path params — you only write business logic. Full surface:
179
-
180
- ```ruby
181
- class MyExt < Clacky::ApiExtension
182
- timeout 30 # class-wide default (max 600s)
183
-
184
- get "/summary" do
185
- json(count: session_manager.list.size) # json(key: val) → 200 JSON
186
- end
187
-
188
- post "/items/:id" do # :id → params["id"]
189
- body = json_body # parsed request JSON (Hash)
190
- q = query["page"] # query string params
191
- File.write(data_path("items", "#{params['id']}.json"), body.to_json) # persistence
192
- json({ ok: true }, status: 201)
193
- end
194
-
195
- get "/export", timeout: 60 do
196
- send_data(bytes, content_type: "text/csv", filename: "out.csv")
197
- end
198
- end
199
- ```
200
-
201
- Response helpers: `json` / `text(str)` / `send_data(bytes, content_type:, filename:)` /
202
- `error!(msg, status:)`. Request: `params` (path), `query`, `json_body`, `req`.
203
-
204
- - **`data_path(*parts)`** is the **official way to persist user data** — it returns a path
205
- under `~/.clacky/ext-data/<id>/`, **outside** the package tree, so it survives reloads,
206
- `gem update`, and even uninstall/reinstall (uninstall keeps it by default; the user opts
207
- in to deleting it via a checkbox). **Never** write user data into the extension's code
208
- dir (`ext_dir` / `File.join(ext_dir, ...)`) — uninstall deletes the whole package, so
209
- anything there is lost. Package-internal writes are only for disposable caches.
210
- - Host context (white-listed): `session_manager`, `registry`, `agent_config`, `config`
211
- (from ext.yml), `logger`, `ext_id`, `ext_dir`, `project_manager`.
212
- - Drive sessions from the backend: `create_session(prompt:, profile:, …)`,
213
- `submit_task(session_id, prompt)`, `dispatch_to_session(session_id, prompt)` (runs a
214
- side task on a fork and returns its reply without touching the conversation).
215
- - **Projects**: `project_manager.all` lists projects, `find(id)` returns one (or
216
- `nil`), `create(name:, working_dir:, …)` / `update(id, …)` / `delete(id)` mutate.
217
- Pass `project_id:` to `create_session` to bind a session to a project - its
218
- `working_dir` is inherited (unless overridden) and `agent.project_id` is persisted.
219
- A panel can also `fetch("/api/projects")` directly (same-origin, no-auth; see
220
- [Host API](/docs/extend-host-api)).
221
- - **Session source grouping** (⚠️ opt-in only): `create_session` accepts `source: "manual"`
222
- (default) or `source: "ext"`; anything else is rejected with 400. Use `"ext"` **only**
223
- when the extension creates sessions the user did not individually ask for (bookkeeping,
224
- background workers, one per webhook) and would otherwise flood the list — they collapse
225
- under a single "Extensions" sidebar entry with their own 200-session cleanup pool.
226
- **Default to `"manual"`**: a session the user asked for is one they should be able to
227
- find, and folded sessions are easy to overlook while the separate pool silently consumes
228
- storage until evicted. Sessions bound to a project always show in that project's area
229
- regardless of `source`, and count toward the regular pool rather than the ext one.
230
- - Public (no-auth) endpoints: call `public_endpoint("/path")` in the class **and** set
231
- `public: true` at ext.yml top level — both are required.
232
-
233
- ### Patches & hooks (advanced — only when asked)
234
-
235
- - **Patch** (`contributes.patches: [{ target, file, fingerprint?, on_mismatch }]`):
236
- overrides a method via `Module#prepend` without editing gem source. `target` is
237
- `"Clacky::Tools::WebSearch#execute"` (`#` = instance, `.` = class). `fingerprint` is a
238
- SHA of the original method source; on drift the patch is disabled (`on_mismatch: disable`,
239
- default) or warned (`warn`).
240
- - **Hook** (`contributes.hooks: [{ event, file }]`): registers a lifecycle callback. Valid
241
- `event` values (exactly these): `before_tool_use after_tool_use on_tool_error on_start
242
- on_complete on_iteration session_rollback`. A `before_tool_use` hook returning
243
- `{ action: :deny, reason: "…" }` **blocks** the tool call — this is how you audit or
244
- gate dangerous commands. Every event passes the **agent as its last block arg**
245
- (`|call, result, agent|` for `after_tool_use`); call `agent.emit_event("ext.<id>.<event>",
246
- persist: true, **data)` to push structured events to the frontend. `persist: false`
247
- (default) vanishes on refresh — emit a `persist: true` terminal event at the end.
248
-
249
- ### Custom tools
250
-
251
- **Tool** (`contributes.tools: [{ id, file }]`): adds a brand-new tool to an agent's
252
- schema. The tool is injected **only into agents that declare it** via `tools: [<id>]`
253
- in the same container's agent spec — an agent can only reference tools from its own
254
- container. `file` points at a Ruby file in the container (`tools/<id>.rb`) defining
255
- `Clacky::Tools::<Camelized id>` — the file name IS the class-name mapping
256
- (`tools/hello.rb` → `Clacky::Tools::Hello`). The class contract is identical to a
257
- built-in tool:
258
-
259
- ```ruby
260
- module Clacky
261
- module Tools
262
- class Weather < Base
263
- self.tool_name = "weather" # the name the model calls
264
- self.tool_description = "Get current weather for a city."
265
- self.tool_category = "general"
266
- self.tool_parameters = {
267
- type: "object",
268
- properties: { city: { type: "string", description: "City name" } },
269
- required: %w[city]
270
- }
271
-
272
- def execute(city:, **)
273
- { temperature: 22, city: city }
274
- end
275
- end
276
- end
277
- end
278
- ```
279
-
280
- Rules:
281
- - **Instance methods only** — `execute(**args)` takes keyword args; `working_dir: nil`
282
- is passed when the agent runs inside a project directory.
283
- - A broken tool file is logged and skipped at startup — it never blocks the agent.
284
- - Name collisions with built-ins: the extension tool **wins** (registered last, same as
285
- the `local > installed > builtin` layer precedence).
286
- - Prefer a `skill` when the capability can be composed from existing tools — a tool is
287
- only needed for something no built-in tool can do (new API integration, new
288
- system-level operation).
289
-
290
- ## Scaffold
291
-
292
- Turn a plain-language idea into a working skeleton.
293
-
294
- ### 1 — Understand the idea
295
-
296
- Figure out what it should DO and which contributes types it needs. Ask one clarifying
297
- question only if genuinely ambiguous. Common mappings:
298
-
299
- | User wants to… | contributes: field |
12
+ Use this skill to guide the workflow, not as a second API manual. The online
13
+ references below own the detailed contracts, examples, and reload behavior. The local
14
+ engineering checks below remain mandatory even when those references cannot be fetched.
15
+
16
+ ## Before designing or editing
17
+
18
+ 1. Identify what the user wants and the smallest capability set that can provide it.
19
+ Check existing host services and shared panels before building a replacement.
20
+ 2. For behavior-only discussion, apply **Engineering checks** without enumerating APIs.
21
+ Before specifying or implementing an interface, choose its one matching capability
22
+ reference below; read the overview when container/scaffold/reload behavior is relevant.
23
+ Reuse verified text still in context. For a named interface, obey the system prompt's
24
+ strict lookup budget and stop as unverified when it is exhausted. Use the article's
25
+ contract/examples, not the documentation site's navigation, styles, or scripts. If the
26
+ user declined a fallback, request only the missing source and never ask again.
27
+ 3. Confirm the proposed behavior, affected files, and visible result with the user
28
+ before scaffolding or editing. Do not publish, restart, or add privileged behavior
29
+ merely because implementation was approved.
30
+
31
+ ### Documentation fallback
32
+
33
+ 1. Fetch the relevant official URL directly; a web search is not required. For truncated
34
+ output, read the returned `temp_file` for the needed section. For HTML pages, `web_fetch`
35
+ saves extracted text with whitespace collapsed, not original HTML: line-based reading
36
+ may still return one long line. One targeted text extraction is enough to check a
37
+ detail. Do not write helper scripts or try to reconstruct code formatting from that
38
+ text; lost newlines can change the meaning of code comments and examples.
39
+ A no-file-change task includes temporary files such as `/tmp/*.py`: only read the
40
+ tool-returned cache, do not create a script or reformatted copy elsewhere.
41
+ 2. If the needed contract is still missing, truncated, or ambiguous, stop this lookup.
42
+ Report it as unverified; no match on a page does not prove the method or its namespace
43
+ absent, private, or unsupported. Do not inventory other APIs to infer an answer.
44
+ Do not retry extraction or probe other extensions/the running host to fill the gap.
45
+ Ask permission to open the official documentation in a new browser tab. If already
46
+ authorized for this task's documentation lookup, do not ask again for each page.
47
+ 3. After consent, use the available `browser` tool to read only the relevant official
48
+ documentation. Do not navigate, inspect, or close unrelated tabs. Read beyond truncated
49
+ snapshots as needed; opening a page alone does not verify its contents. If browser setup
50
+ is required, ask whether the user wants to use `browser-setup`; never enable it silently
51
+ or bypass a disabled browser. If the tool is unavailable, do not invent another browser
52
+ control mechanism or install one without approval.
53
+ 4. If the user declines or the browser cannot retrieve the reference, ask the user to
54
+ supply the documentation or designate a separate source checkout matching the running
55
+ version and permit read-only inspection. Do not search installed OpenClacky gem source.
56
+ Reading a checkout does not authorize modifying it to make an extension work.
57
+ 5. If still blocked, end with only **Unverified detail**, **Reference checked**, and
58
+ **Evidence or permission needed next**. For example: "I could not verify this method
59
+ from this page; please provide its source." Do not turn that into "the object does
60
+ not exist" followed by a disclaimer. Clarifying the user's goal may continue, but
61
+ implementation against that contract must wait. Resolve version differences before
62
+ relying on a contract; version metadata is allowed, installed implementation source is not.
63
+
64
+ ## Capability → reference
65
+
66
+ | Need | Read |
300
67
  |---|---|
301
- | Show X in a side panel / add a button / dashboard | `panels:` (+ `api:` if it needs a backend or an external service) |
302
- | A capability the AI can invoke (summarize, translate, format) | `skills:` |
303
- | A specialized assistant with its own personality/tools | `agents:` (usually bundling its own panels/skills) |
304
- | Connect to Slack / an in-house IM | `channels:` |
305
- | Change behavior of a built-in method | `patches:` |
306
- | Audit / block / observe tool calls | `hooks:` |
307
- | A brand-new tool the model can call (new API, new system op) | `tools:` (+ agent-side `tools:` declaration) |
308
-
309
- Keep it minimal — most useful extensions are one panel + one handler, or one skill.
310
- Do NOT add `patches` or `hooks` unless the user explicitly asks; they run arbitrary
311
- Ruby and carry supply-chain risk.
312
-
313
- **Appearance & naming are manifest fields, not separate features.** When the user wants a
314
- custom logo/avatar for an agent, a Chinese (or other-language) display name, a panel tab
315
- label, or ordering, those are optional keys in `ext.yml` — e.g. agent `avatar:` (image
316
- path), `title` / `title_zh`, `description` / `description_zh`, `order`. Never say it can't
317
- be done; set the field and check the full list in the ext.yml manifest doc.
318
-
319
- ### 2 — Generate the skeleton
68
+ | Container model, source layers, scaffold, reload, verification, publishing | [Extension System Overview](https://www.openclacky.com/docs/extension-system) |
69
+ | Manifest metadata, panels, agents, tools, skill restrictions, field validation | [ext.yml Manifest](https://www.openclacky.com/docs/ext-manifest) |
70
+ | Slots, agent scope, tab lifecycle/badges, subscriptions, replay, Composer, Aside, Modal, Workspace | [Web UI Extensions](https://www.openclacky.com/docs/extend-webui) |
71
+ | Extension-owned backend, route parameters, persistent data, session/project helpers, public endpoints | [HTTP API Extensions](https://www.openclacky.com/docs/extend-api) |
72
+ | Host sessions/history, files, recovery, billing, media, development UI helpers | [Calling Host APIs](https://www.openclacky.com/docs/extend-host-api) |
73
+ | Agent persona and configuration | [Agent Configuration](https://www.openclacky.com/docs/agent-config) |
74
+ | Skill authoring and invocation | [Skills](https://www.openclacky.com/docs/how-to-use-a-skill) |
75
+ | IM adapters, optional file delivery and buffered output | [Channel Adapters](https://www.openclacky.com/docs/extend-channel-adapter) |
76
+ | Tool-call/lifecycle hooks and persistent custom events | [Hooks](https://www.openclacky.com/docs/extend-shell-hooks) |
77
+ | Explicitly requested runtime patches | [Runtime Patches](https://www.openclacky.com/docs/extend-patches) |
78
+
79
+ Read the pages needed for this task, not every reference indiscriminately. This index
80
+ routes lookups; field lists, signatures, and code examples belong in those pages.
81
+
82
+ ## Working boundaries
83
+
84
+ - Develop in `~/.clacky/ext/local/<id>/`. Never edit installed/builtin packages or
85
+ gem source to customize a user's extension. Do not search or read installed OpenClacky
86
+ gem implementation source, even to resolve missing documentation; version metadata is
87
+ allowed. Confirm intentional id overrides.
88
+ - Do not add hooks or patches without an explicit request; they execute arbitrary Ruby.
89
+ - Reuse the documented host APIs and theme conventions. Do not guess methods from
90
+ similarly named libraries or manipulate host internals.
91
+ - Keep data outside the extension package using the documented persistence facility.
92
+ Validate input and get consent for destructive or paid operations.
93
+ - Choose the reload procedure from the overview's contribution-specific table.
94
+ Do not claim that every change hot-reloads, or restart the server without consent.
95
+ - Verification is evidence, not a prediction: distinguish manifest checks, tests,
96
+ actual UI/API behavior, and anything still awaiting user verification.
97
+ - Local development does not imply marketplace publication.
98
+
99
+ ## Engineering checks
100
+
101
+ Apply these when proposing the design and again against the actual code before handoff.
102
+ Do not count a promise to follow a rule as evidence that the implementation follows it.
103
+
104
+ 1. **Access and side effects:** identify files, sessions, credentials, and external
105
+ destinations involved. Refuse unauthorized access or disclosure; never bypass host
106
+ authentication. Keep secrets out of frontend code and logs. Validate inputs and paths,
107
+ keep persistent data outside the package, and ensure destructive changes or private-data
108
+ transfers require specific approval rather than general permission to build.
109
+ 2. **Request budget:** check every fetch, loop, timer, retry, and worker. Prefer events and
110
+ cached reads; do not repeatedly fetch full histories. Default to no added threads;
111
+ necessary concurrency must be bounded. No tight loops or sub-second polling. Necessary
112
+ polling must use a coarse interval, prevent overlap, pause when hidden or finished, and
113
+ have bounded retries and request duration. Check the failure path as well as success.
114
+ 3. **Lifecycle and cost:** verify rerenders and session switches do not multiply listeners,
115
+ timers, or requests; disposal releases them and stale responses cannot affect a new
116
+ session. Hidden cached panels must pause unnecessary work without waiting for disposal.
117
+ A tab/session switch does not prove disposal occurred; verify unsubscribe/visibility
118
+ contracts instead of using assumed disposal as a substitute for cleanup.
119
+ Model/media calls need explicit approval, including recurring scope; mounting, refresh,
120
+ and history replay must not initiate paid or destructive actions or external sends.
121
+ Read-only billing queries are not model invocations, but still need request limits.
122
+ 4. **Host reuse and evidence:** reuse host capabilities and `btn-*` / `form-*`,
123
+ `Clacky.Modal`, and `var(--color-*)`; use scoped, prefixed custom styles where needed.
124
+ For basic inputs use `form-input` / `form-textarea`. Confirm custom property names in
125
+ the relevant reference or generated scaffold, not by their plausible spelling; do not
126
+ invent fallback token names. If a token is unverified, omit the color override and
127
+ inherit host styling, rather than adding a guessed token with hardcoded fallback colors.
128
+ Prefix tab ids with the extension id to avoid collisions.
129
+ Check light/dark appearance for UI work. Do not add unrequested hooks or patches or
130
+ modify installed packages. Test the applicable boundaries above with controlled data;
131
+ do not use real destructive, disclosure, or paid operations just to prove a safeguard.
132
+ Report which checks were exercised, which were code review only, and which remain open.
320
133
 
321
- Pick a lowercase, hyphenated id derived from the idea (e.g. `weather-panel`).
322
-
323
- ```
324
- clacky ext new <id>
325
- ```
326
-
327
- This creates `~/.clacky/ext/local/<id>/` with a working hello panel + handler:
328
- - `ext.yml` — the manifest
329
- - `panels/hello/view.js` — a panel that pings the backend
330
- - `api/handler.rb` — a `Clacky::ApiExtension` subclass mounted at `/api/ext/<id>/`
331
- - `test/handler_test.rb` — a runnable minitest example (`ruby test/handler_test.rb`)
332
-
333
- Use `--full` only when the user needs the kitchen-sink reference exercising all eight
334
- contributes types — it's a lot to read, so prefer the plain scaffold otherwise.
335
-
336
- ### 3 — Reshape to the idea
337
-
338
- Below is a concrete before → after for a tiny "add a note" panel — use it as
339
- the shape to copy, not the literal content.
340
-
341
- **`ext.yml`** — rename the panel id/view to the feature; add `skills:`/`agents:` only if needed:
342
-
343
- ```yaml
344
- contributes:
345
- api: api/handler.rb
346
- panels:
347
- - id: notes # was: hello
348
- view: panels/notes/view.js # was: panels/hello/view.js
349
- attach: ["*"]
350
- ```
351
-
352
- **`panels/notes/view.js`** — keep the `Clacky.ext.ui.mount(...)` wrapper and host CSS
353
- classes; swap the body for the real UI, POST to your own route:
354
-
355
- ```js
356
- Clacky.ext.ui.mount("session.aside", function (container, ctx) {
357
- var el = document.createElement("div");
358
- el.style.padding = "16px";
359
- var input = document.createElement("input");
360
- input.className = "form-input"; // reuse host theme
361
- var btn = document.createElement("button");
362
- btn.className = "btn-primary";
363
- btn.textContent = "Save note";
364
- btn.addEventListener("click", async function () {
365
- await fetch("/api/ext/<id>/notes", { // relative to your mount
366
- method: "POST",
367
- headers: { "Content-Type": "application/json" },
368
- body: JSON.stringify({ text: input.value }),
369
- });
370
- Clacky.Modal.toast("Saved", "success"); // host toast, not window.alert
371
- });
372
- el.append(input, btn);
373
- return el;
374
- }, { tab: { id: "notes", label: () => "Notes" }, order: 500 });
375
- ```
376
-
377
- **`api/handler.rb`** — stay a `Clacky::ApiExtension` subclass; add the route the panel
378
- calls. Persist user data with `data_path`, never into the code dir:
379
-
380
- ```ruby
381
- class <Prefix>Ext < Clacky::ApiExtension
382
- post "/notes" do # matches /api/ext/<id>/notes
383
- text = json_body["text"].to_s
384
- File.write(data_path("notes.txt"), "#{text}\n", mode: "a")
385
- json(saved: true)
386
- end
387
- end
388
- ```
389
-
390
- Rules while reshaping:
391
- - Keep the panel `view:` path and the on-disk `view.js` path in sync — mismatched paths
392
- are the #1 cause of a `loader.error`.
393
- - Routes in `handler.rb` are **relative** to `/api/ext/<id>/`; the `view.js` `fetch` must
394
- match. A mismatch is a silent 404, not a verify error.
395
- - **UI styling: default to host classes; anything the host has no class for, build
396
- freely.** Colors: only `var(--color-*)` — raw hex breaks the dark theme. For the
397
- full class/variable list grep the host stylesheet `lib/clacky/web/app.css`.
398
-
399
- ### 4 — Write tests
400
-
401
- Test the backend before you call it done. Rewrite the scaffolded `test/handler_test.rb`
402
- to exercise your real routes (parse the JSON response, assert its fields), then run it
403
- green:
404
-
405
- ```
406
- ruby test/handler_test.rb
407
- ```
408
-
409
- - If `test/` is missing from the scaffold, create `test/handler_test.rb` yourself
410
- (`mkdir -p test` then write it) — do NOT skip tests just because the file is absent.
411
- - A handler that only returns a static string may keep the scaffold's hello test;
412
- anything that fetches, transforms, caches, or computes MUST have a real test.
413
-
414
- ### 5 — Confirm it loads
415
-
416
- Run `clacky ext verify` and confirm the new units resolve with no errors, then have the
417
- user reload the WebUI page. If verify reports problems, go to **Debug & verify**.
418
-
419
- ## When NOT to build an extension
420
-
421
- - The user is building features in their own app that just *use* openclacky — that's
422
- normal coding, no extension container needed.
423
- - The user wants a skill for *their own* project — use `.clacky/skills/` in their
424
- project, not a gem-level container.
425
- - The user wants a custom agent **tool** — contribute it from a local extension
426
- (`contributes.tools`), which is the supported way to add a tool name to the agent's
427
- schema. Don't patch a built-in tool to fake new behavior.
428
- - The change can be made via `clacky config set ...` — prefer config over patches.
134
+ ## Scaffold
429
135
 
430
- ---
136
+ 1. Agree on the smallest implementation. A panel may reuse a host API without a new
137
+ backend; a capability composed from existing tools may only need a skill.
138
+ A user's ordinary app work does not need an extension container, and a project-only
139
+ skill can live in that project's `.clacky/skills/`.
140
+ 2. Run `clacky ext new <id>` for a runnable skeleton. Use `--full` only when the task
141
+ needs the broader examples. Read generated files before changing them.
142
+ 3. Implement the approved behavior against the referenced contracts. Keep manifest
143
+ paths, unit ids, and actual files aligned; do not retain unused scaffold features.
144
+ 4. Adapt the scaffold's tests to the real behavior and run them. If a backend has no
145
+ tests, add them instead of treating missing tests as permission to skip verification.
146
+ Cover invalid input and failures as well as the successful response; apply
147
+ **Engineering checks** to the implementation.
148
+ 5. Run `clacky ext verify`; resolve errors and review warnings using its actual
149
+ `code`, `file`, and `hint`. Verify the visible feature or endpoint afterward.
150
+ 6. Follow **Handoff** below.
431
151
 
432
152
  ## Debug & verify
433
153
 
434
- Your primary instrument is `clacky ext verify` — a compiler for extensions: every issue
435
- is structured with a `code`, `message`, the offending `file`, and a `hint`.
436
-
437
- **Top 5 things that break — check these first:**
438
-
439
- | Symptom | Almost always | Fix |
440
- |---|---|---|
441
- | Red error box where the panel should be | `ui.mount` render threw — usually a wrong signature | signature is `(container, ctx, runtime)` — not `(ctx)` |
442
- | Panel doesn't appear at all | `slot` name typo (silent) or no `attach:` | use a valid slot; set `attach: ["*"]` or an agent id |
443
- | Frontend `fetch` gets 404 | route in `handler.rb` ≠ path in `view.js` fetch | routes are relative to `/api/ext/<id>/` |
444
- | `loader.error` on verify | `ext.yml` `view:` path ≠ the on-disk `view.js` path | make the two match exactly |
445
- | Edited a file, nothing changed | page not reloaded (or edited `ext.yml`) | reload the WebUI page — hot reload is per-request |
446
-
447
-
448
- ### 1 — Run verify
449
-
450
- ```
451
- clacky ext verify
452
- ```
453
-
454
- Read the output line by line. `[OK]` confirms a resolved unit; `[ERR]` blocks a load;
455
- `[WARN]` is advisory. Each issue looks like:
456
-
457
- ```
458
- [ERR] <ext> <unit> (<code>) — <message> [<file>]
459
- hint: <how to fix>
460
- ```
461
-
462
- **Always trust the `hint` first.** The line below tells you the fix per code; do the
463
- smallest change, re-run verify, repeat until clean — fix ONE issue at a time.
464
-
465
- ### 2 — Fix by error code
466
-
467
- - **`loader.error`** → a file the manifest points at is missing, or `ext.yml` isn't valid
468
- YAML. **Do:** open the `file` path in the error; make sure it exists and the path in
469
- `ext.yml` matches it exactly. (skill → `SKILL.md` under `skills/<id>/`; agent → its
470
- `prompt` file; panel → its `view` file; api → `api/handler.rb`.)
471
- - **`schema.unknown_contributes`** → a top-level key under `contributes:` is misspelled.
472
- **Do:** fix the spelling to one of `panels api skills agents channels patches hooks`.
473
- - **`schema.unknown_key`** → an unknown **top-level** key in `ext.yml`. **Do:** fix the
474
- spelling. Allowed top-level keys: `id name name_zh display_name display_name_zh title
475
- description description_zh emoji version origin author homepage license public
476
- license_required keywords contributes`.
477
- - **`schema.unknown_field`** → a unit has a field not allowed for its type. **Do:** delete
478
- or rename that field. Allowed fields per type (this is the authoritative list — do not
479
- invent others):
480
- - panel: `id title title_zh description description_zh view order attach entry_points`
481
- - api: `id handler`
482
- - skill: `id dir protected`
483
- - agent: `id title title_zh description description_zh order prompt panels skills avatar`
484
- - channel: `id platform adapter`
485
- - patch: `target file fingerprint on_mismatch`
486
- - hook: `event file`
487
- - **`schema.bad_attach`** → a panel `attach:` entry isn't a valid token. **Do:** set it to
488
- an agent id or `"*"` (all).
489
- - **`ref.missing_panel`** → an agent's `panels: [id]` names a panel that doesn't exist.
490
- **Do:** fix the id, or use `<ext_id>/<panel_id>` to point at another extension's panel.
491
- - **`ref.missing_skill`** → an agent's `skills: [id]` names a skill that doesn't exist.
492
- **Do:** fix the id, or add the `SKILL.md`.
493
- - **`ref.missing_attach_agent`** → a panel's `attach:` names a nonexistent agent.
494
- **Do:** fix the agent id.
495
- - **`override`** (warning) → a higher layer is shadowing a lower one
496
- (`local > installed > builtin`). **Do:** usually intentional — leave it; confirm with the
497
- user only if the shadowing is a surprise.
498
-
499
- Fix one issue, re-run verify, repeat until clean.
500
-
501
- ### 3 — "It verifies but doesn't show up"
502
-
503
- If verify is clean but a change isn't visible:
504
- - **Hot reload is per-request.** After editing `view.js`, `handler.rb`, or a `SKILL.md`,
505
- the user must **reload the WebUI page** — no restart, but a stale tab won't update on
506
- its own. Editing `ext.yml` also applies on the next load.
507
- - **Panel not appearing?** In order: (1) the `slot` name in `ui.mount` must be one of the
508
- valid slots — a typo like `session.aisde` silently renders nothing (check the browser
509
- console for a "unknown slot" warning); (2) check the panel's `attach:` (or the agent
510
- that references it via `panels: [id]`) — a panel with no `attach` and no referencing
511
- agent has nothing to mount onto; (3) a red error box means the render function threw
512
- (usually a wrong signature) — open the console for the stack.
513
- - **API 404?** Routes are relative to `/api/ext/<ext_id>/`. Confirm the handler subclasses
514
- `Clacky::ApiExtension` and the route pattern matches what `view.js` fetches.
515
- - **Skill not triggering?** The AI selects skills by their `description`. Make the
516
- description concrete about WHEN to use it.
517
-
518
- ### 4 — Confirm the fix
519
-
520
- End with a clean `clacky ext verify` and have the user reload to confirm the behavior
521
- actually works — don't declare success on "should work."
522
-
523
- ---
524
-
525
- ## Publish (optional)
526
-
527
- Publishing is **not** a required step. Many extensions are built for the user's own use —
528
- scaffold, verify, and reload is the whole job. Only publish when the user explicitly asks
529
- to share, ship, or list the extension for others. Never publish on your own initiative or
530
- as a "wrap up" of the build.
531
-
532
- The **Creator Center panel** has a Publish button — prefer it for a
533
- guided flow. Use the CLI below for scripted/CI publishing.
534
-
535
- ### Before publishing
536
-
537
- - The extension must live in the **local** layer (`~/.clacky/ext/local/<id>/`). Only local
538
- containers can be packed; encrypted (`SKILL.md.enc`) containers are rejected.
539
- - Publishing requires the device to be **bound to a platform account** (it attributes the
540
- extension to that account). If it isn't bound, tell the user to authorize the device
541
- first — don't try to work around it.
542
- - Run `clacky ext verify` one last time and confirm no errors.
543
- - **README check:** If `~/.clacky/ext/local/<id>/README.md` does not exist, ask the user
544
- before proceeding: "No README.md found — would you like me to write usage instructions
545
- first?" If yes, read the source files and write a concise README, then publish. If the
546
- user asks to write a README / usage instructions at any point, do the same.
547
-
548
- ### Publish (first time)
549
-
550
- ```
551
- clacky ext publish <id>
552
- ```
553
-
554
- Packs the local container into a zip and uploads it. On success: `Published <id>
555
- v<version> → status=<status>`. Options:
556
- - `--status draft` — publish as a draft (not visible on the public marketplace). Omit or
557
- use `--status published` to go live.
558
- - `--changelog "..."` — release notes for this version.
559
-
560
- ### Publish a new version
561
-
562
- If already published, a plain `publish` fails with `Error: <id> already published. Re-run
563
- with --force to publish a new version.` Re-run with `--force` (and ideally a `--changelog`);
564
- the patch version auto-increments on the platform side.
565
-
566
- ```
567
- clacky ext publish <id> --force --changelog "Fixed the weather refresh bug"
568
- ```
569
-
570
- ### List your published extensions
571
-
572
- ```
573
- clacky ext published
574
- ```
575
-
576
- Shows each extension with its latest version, status, and unit summary.
577
-
578
- ### Unpublish
579
-
580
- ```
581
- clacky ext unpublish <id>
582
- ```
583
-
584
- Soft-deletes (takes down) one of your published extensions. Confirm with the user first —
585
- it removes it from the marketplace.
586
-
587
- ### Wrap up
588
-
589
- After a successful publish, tell the user the version and status in plain terms, and
590
- mention they can run `clacky ext published` to see it, or bump a new version anytime with
591
- `--force`.
154
+ 1. Reproduce the reported symptom; inspect the actual local container and relevant
155
+ reference before proposing a fix.
156
+ 2. Run `clacky ext verify` and read its structured findings. Use `clacky ext list`
157
+ to identify the resolved layer and accidental shadowing. Do not maintain a second
158
+ hardcoded list of accepted keys in this skill.
159
+ 3. For invisible panels, check documented slots, tab options, manifest associations,
160
+ and the browser error stack. For API failures, check the request method/path,
161
+ handler contract, status, and logs. For stale behavior, check the reload matrix.
162
+ 4. Fix the root cause within the agreed scope, rerun relevant tests and verification,
163
+ then check the reported behavior and the affected **Engineering checks**. Do not widen
164
+ scope to unrelated warnings without discussing them with the user.
165
+ 5. Follow **Handoff** below.
166
+
167
+ ## Publish (only on explicit request)
168
+
169
+ 1. Read the overview's current publishing requirements and command options.
170
+ 2. Confirm the target local container, intended marketplace status, device binding,
171
+ and version. Do not bypass authentication, ownership, or encrypted-content limits.
172
+ 3. Run tests and verification. If `README.md` is missing, ask whether to write usage
173
+ instructions before proceeding; derive them from the actual implemented behavior.
174
+ 4. Publish only the approved target/version/status. Updating an existing extension
175
+ requires manually choosing a greater version in `ext.yml`; `--force` does not
176
+ increment it. Explain failures instead of guessing alternate publish behavior.
177
+ 5. Report the actual version and status returned. Unpublishing also needs explicit
178
+ approval; it is not routine cleanup.
179
+
180
+ ## Handoff
181
+
182
+ - State what changed, what passed, and what the user still needs to check.
183
+ - After extension edits, use the development UI helpers documented in **Calling Host
184
+ APIs** to show the refresh button once; also open the aside for a session-aside panel.
185
+ Use the injected service/session context, not a guessed host, port, or session id.
186
+ - These HTTP helpers are not browser control. If the user will test the browser, keep
187
+ that boundary and report UI checks as pending; do not treat it as permission to inspect
188
+ their tabs. Honor a separate request not to send UI-helper broadcasts.
189
+ - A successful UI-helper response only acknowledges a broadcast. If no matching UI
190
+ is connected, ask for manual refresh. Verify required Ruby reloads separately;
191
+ neither UI helper restarts the server.
192
+ - Stop at the approved outcome. Do not publish or perform other release actions as
193
+ an automatic wrap-up.