openclacky 0.7.8 → 0.7.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9b2a72212b8b9e8072acb09a055df1afdd8225ee81de41f11dce460a68ff1781
4
- data.tar.gz: e8048bb38dc4a8ae8c85937165e10a855f8642d6759c8a2a94aa336b084e4188
3
+ metadata.gz: 93b025580a849df456c7d9d20089b31f392446c2d46d842117563fe349740c92
4
+ data.tar.gz: 5a61ab30b81751176a2f612d3db2822bef54f99b3f907f73e7e7bba0c12bc231
5
5
  SHA512:
6
- metadata.gz: '095710e060b7c29882eb128be1a4008281435c13af07e7da01ec9ad1af27c30e252eb19e77f557882628409058d504f29024ea0dbb396ed20d326b26cfe1925c'
7
- data.tar.gz: 2875ac5911322341be5fe396ad44a98e4cbcea51c95ed7d832e3978d5790542ca94ae0f065f6170484d9900be2f1ca190f6db0d25c14a33366f924f9d13a147d
6
+ metadata.gz: c5a5aefd0945a2296783df6f9b8f6779d4bd36bc35cae587dc743a19e71e59c7714731e742dc8b1ecdba2535896c8f4be434bea1d2adac2dc1bd4ad56762a11e
7
+ data.tar.gz: 639d2f4e3d72250ac70dc9e27ad3157e91878bac076a3b13edaa8de00bae7f8342b87fe42357475092b2aecaca5425720e124d222e398cbe623207167ef21b2c
data/CHANGELOG.md CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.9] - 2026-03-07
11
+
12
+ ### Added
13
+ - Cursor-paginated message history in web UI for large session navigation
14
+ - `confirm_all` permission mode for WebUI human sessions
15
+ - Re-run onboard entry in settings panel
16
+
17
+ ### Fixed
18
+ - Expand `~` in file system tools path arguments (file_reader, glob, grep, write, edit)
19
+ - Sort sessions newest-first with scheduled sessions at bottom
20
+ - Tasks and skills sidebar items now static — no longer disappear on scroll
21
+ - Delete task now also removes associated schedules
22
+
23
+ ### More
24
+ - Add frontmatter (`name`, `description`, `disable-model-invocation`, `user-invocable`) to onboard skill
25
+
10
26
  ## [0.7.8] - 2026-03-06
11
27
 
12
28
  ### Added
@@ -0,0 +1,77 @@
1
+ # Agent-First UI Design Philosophy
2
+
3
+ > Guiding principle for all OpenClacky UI and feature design.
4
+
5
+ ---
6
+
7
+ ## Core Principle
8
+
9
+ **Conversation first, interactive cards when needed.**
10
+
11
+ Users interact with the Agent through natural language to accomplish everything. When conversation is inconvenient for structured input (e.g. dropdowns, multi-select, precise time picking), the Agent triggers an **interactive card** via the `request_user_feedback` tool — rendered by the frontend as a structured UI component. Cards are a complement to conversation, not a replacement.
12
+
13
+ ---
14
+
15
+ ## Two Interaction Modes
16
+
17
+ ### 1. Conversation (default)
18
+ User expresses intent in natural language, Agent understands and executes.
19
+
20
+ ```
21
+ User: Send me a daily standup summary every morning at 9
22
+ Agent: Done! Task created, runs Mon–Fri at 09:00 ✅
23
+ ```
24
+
25
+ ### 2. Interactive Cards (when conversation falls short)
26
+ When the Agent needs structured input that's hard to express in free text, it calls `request_user_feedback`. The frontend renders this as an interactive card (dropdowns, radio buttons, time pickers, etc.).
27
+
28
+ ```
29
+ Agent calls request_user_feedback → frontend renders a card:
30
+
31
+ ┌─────────────────────────────┐
32
+ │ 📋 Confirm task settings │
33
+ │ Frequency: [Daily ▼] │
34
+ │ Time: [09:00 ] │
35
+ │ [✅ Confirm] [Cancel] │
36
+ └─────────────────────────────┘
37
+
38
+ User fills card → structured data sent back to Agent → execution continues
39
+ ```
40
+
41
+ ---
42
+
43
+ ## When to Use Cards
44
+
45
+ | Situation | Reason |
46
+ |-----------|--------|
47
+ | Choosing from a list of options | Easier than enumerating in chat |
48
+ | Date / time selection | Precise value, error-prone in free text |
49
+ | Sensitive input like API keys | Should not appear in conversation history |
50
+ | Collecting multiple fields at once | One card beats several back-and-forth questions |
51
+
52
+ Everything else: use conversation.
53
+
54
+ ---
55
+
56
+ ## What Should NOT Exist
57
+
58
+ - ❌ Persistent configuration form pages
59
+ - ❌ Fields that require users to understand technical details (cron expressions, agent IDs, etc.)
60
+ - ❌ More than 3 action buttons per list row
61
+ - ❌ Standalone "Create" form modals
62
+
63
+ ---
64
+
65
+ ## Role of UI Pages
66
+
67
+ UI pages are for **displaying state**, not for configuring things:
68
+
69
+ - ✅ Show task lists, run history, current status
70
+ - ✅ Minimal action set per row: ▶ Run / ✎ Edit (opens conversation) / ✕ Delete
71
+ - ❌ No inline create/edit forms inside list pages
72
+
73
+ Clicking "Edit" opens an Agent conversation with context pre-filled. The Agent drives the modification flow from there.
74
+
75
+ ---
76
+
77
+ *Applies to all OpenClacky Web UI and feature design.*
@@ -0,0 +1,69 @@
1
+ # Session + Skill Invocation Pattern
2
+
3
+ > Design pattern for launching an Agent session that immediately runs a skill.
4
+ > Follow this whenever a UI action needs to "open a session and do something automatically."
5
+
6
+ ---
7
+
8
+ ## The Pattern
9
+
10
+ ```
11
+ 1. POST /api/sessions → create a named session
12
+ 2. Sessions.add(session) → register locally
13
+ 3. Sessions.renderList() → update sidebar
14
+ 4. _bootUI() if needed → connect WS (only on first boot)
15
+ 5. Sessions.select(session.id) → navigate to session (triggers WS subscribe)
16
+ 6. WS.send({ type: "message", session_id, content: "/skill-name" })
17
+ → agent runs the skill immediately
18
+ ```
19
+
20
+ The slash command (`/skill-name`) is handled by `Agent#parse_skill_command` on the
21
+ server side — no special API endpoint or pending-state machinery needed.
22
+
23
+ ---
24
+
25
+ ## Real Usages
26
+
27
+ ### Create Task (`tasks.js → createInSession`)
28
+ ```js
29
+ Sessions.select(session.id);
30
+ WS.send({ type: "message", session_id: session.id, content: "/create-task" });
31
+ ```
32
+
33
+ ### Onboard (`onboard.js → _startSoulSession`)
34
+ ```js
35
+ _bootUI(); // WS.connect() + Tasks/Skills load
36
+ Sessions.add(session);
37
+ Sessions.renderList();
38
+ Sessions.select(session.id);
39
+ WS.send({ type: "message", session_id: session.id, content: "/onboard" });
40
+ ```
41
+
42
+ ---
43
+
44
+ ## When to Use `pending_task` Instead
45
+
46
+ Use the `pending_task` registry field (and the `run_task` WS message) **only** when
47
+ the prompt is a large block of text read from a file (e.g. `POST /api/tasks/run`).
48
+
49
+ For slash commands, always prefer the direct `WS.send` approach above — simpler and
50
+ no server-side state to manage.
51
+
52
+ ---
53
+
54
+ ## Anti-patterns Avoided
55
+
56
+ | Anti-pattern | Why it was wrong |
57
+ |---|---|
58
+ | Store `_pendingSessionId` in module state, resolve on `session_list` | Race condition between WS connect and session_list arrival; unnecessary complexity |
59
+ | Custom `takePendingSession()` hook in app.js `session_list` handler | Spread logic across files; hard to trace |
60
+ | Send prompt via `setTimeout` after boot | Fragile timing; breaks if WS is slow |
61
+
62
+ ---
63
+
64
+ ## Key Insight
65
+
66
+ `Sessions.select(id)` triggers a WS `subscribe` message. Once the server confirms
67
+ with `subscribed`, the client is guaranteed to receive all subsequent broadcasts for
68
+ that session. Sending `WS.send({ type: "message" })` right after `select` is safe
69
+ because the WebSocket driver queues messages until the connection is open.
@@ -131,6 +131,82 @@ module Clacky
131
131
  end
132
132
  end
133
133
 
134
+ # Replay conversation history by calling ui.show_* methods for each message.
135
+ # Supports cursor-based pagination using created_at timestamps on user messages.
136
+ # Each "round" starts at a user message and includes all subsequent assistant/tool messages.
137
+ #
138
+ # @param ui [Object] UI interface that responds to show_user_message, show_assistant_message, etc.
139
+ # @param limit [Integer] Maximum number of rounds (user turns) to replay
140
+ # @param before [Float, nil] Unix timestamp cursor — only replay rounds where the user message
141
+ # created_at < before. Pass nil to get the most recent rounds.
142
+ # @return [Hash] { has_more: Boolean } — whether older rounds exist beyond this page
143
+ def replay_history(ui, limit: 20, before: nil)
144
+ # Split @messages into rounds, each starting at a real user message
145
+ rounds = []
146
+ current_round = nil
147
+
148
+ @messages.each do |msg|
149
+ role = msg[:role].to_s
150
+
151
+ if role == "user" && !msg[:system_injected] && msg[:content].is_a?(String) &&
152
+ !msg[:content].to_s.start_with?("[SYSTEM]")
153
+ # Start a new round at each real user message
154
+ current_round = { user_msg: msg, events: [] }
155
+ rounds << current_round
156
+ elsif current_round
157
+ current_round[:events] << msg
158
+ end
159
+ end
160
+
161
+ # Apply before-cursor filter: only rounds whose user message created_at < before
162
+ if before
163
+ rounds = rounds.select { |r| r[:user_msg][:created_at] && r[:user_msg][:created_at] < before }
164
+ end
165
+
166
+ has_more = rounds.size > limit
167
+ # Take the most recent `limit` rounds
168
+ page = rounds.last(limit)
169
+
170
+ page.each do |round|
171
+ msg = round[:user_msg]
172
+ # Emit user message with its timestamp for dedup on the frontend
173
+ ui.show_user_message(extract_text_from_content(msg[:content]), created_at: msg[:created_at])
174
+
175
+ round[:events].each do |ev|
176
+ case ev[:role].to_s
177
+ when "assistant"
178
+ # Text content
179
+ text = extract_text_from_content(ev[:content]).to_s.strip
180
+ ui.show_assistant_message(text) unless text.empty?
181
+
182
+ # Tool calls embedded in assistant message
183
+ Array(ev[:tool_calls]).each do |tc|
184
+ name = tc[:name] || tc.dig(:function, :name) || ""
185
+ args_raw = tc[:arguments] || tc.dig(:function, :arguments) || {}
186
+ args = args_raw.is_a?(String) ? (JSON.parse(args_raw) rescue args_raw) : args_raw
187
+ ui.show_tool_call(name, args)
188
+ end
189
+
190
+ when "user"
191
+ # Anthropic-format tool results (role: user, content: array of tool_result blocks)
192
+ next unless ev[:content].is_a?(Array)
193
+
194
+ ev[:content].each do |blk|
195
+ next unless blk.is_a?(Hash) && blk[:type] == "tool_result"
196
+
197
+ ui.show_tool_result(blk[:content].to_s)
198
+ end
199
+
200
+ when "tool"
201
+ # OpenAI-format tool result
202
+ ui.show_tool_result(ev[:content].to_s)
203
+ end
204
+ end
205
+ end
206
+
207
+ { has_more: has_more }
208
+ end
209
+
134
210
  private
135
211
 
136
212
  # Extract text from message content (handles string and array formats)
@@ -86,6 +86,25 @@ module Clacky
86
86
  prompt += "=" * 80
87
87
  end
88
88
 
89
+ # Load agent soul and user profile from ~/.clacky/agents/
90
+ agents_dir = File.expand_path("~/.clacky/agents")
91
+ [
92
+ { file: "SOUL.md", label: "AGENT SOUL" },
93
+ { file: "USER.md", label: "USER PROFILE" }
94
+ ].each do |entry|
95
+ path = File.join(agents_dir, entry[:file])
96
+ next unless File.exist?(path)
97
+
98
+ content = File.read(path).strip
99
+ next if content.empty?
100
+
101
+ prompt += "\n\n" + "=" * 80 + "\n"
102
+ prompt += "#{entry[:label]} (from ~/.clacky/agents/#{entry[:file]}):\n"
103
+ prompt += "=" * 80 + "\n"
104
+ prompt += content
105
+ prompt += "\n" + "=" * 80
106
+ end
107
+
89
108
  # Add all loaded skills to system prompt
90
109
  skill_context = build_skill_context
91
110
  prompt += skill_context if skill_context && !skill_context.empty?
@@ -11,7 +11,11 @@ module Clacky
11
11
  # @return [Boolean] true if should auto-execute
12
12
  def should_auto_execute?(tool_name, tool_params = {})
13
13
  case @config.permission_mode
14
- when :auto_approve
14
+ when :auto_approve, :confirm_all
15
+ # Both modes auto-execute all file/shell tools without confirmation.
16
+ # The difference is only in request_user_feedback handling:
17
+ # auto_approve → no human present, inject auto_reply
18
+ # confirm_all → human present, truly wait for user input
15
19
  true
16
20
  when :confirm_safes
17
21
  # Use SafeShell integration for safety check
data/lib/clacky/agent.rb CHANGED
@@ -163,7 +163,7 @@ module Clacky
163
163
 
164
164
  # Format user message with images if provided
165
165
  user_content = format_user_content(user_input, images)
166
- @messages << { role: "user", content: user_content, task_id: task_id }
166
+ @messages << { role: "user", content: user_content, task_id: task_id, created_at: Time.now.to_f }
167
167
  @total_tasks += 1
168
168
 
169
169
  @hooks.trigger(:on_start, user_input)
@@ -502,12 +502,13 @@ module Clacky
502
502
  end
503
503
 
504
504
  if @config.permission_mode == :auto_approve
505
- # Auto-approve mode means no human is watching inject a reply so the LLM
506
- # knows it should make a reasonable decision and keep going
505
+ # auto_approve means no human is watching (unattended/scheduled tasks).
506
+ # Inject an auto_reply so the LLM makes a reasonable decision and keeps going.
507
507
  result = result.merge(
508
508
  auto_reply: "No user is available. Please make a reasonable decision based on the context and continue."
509
509
  )
510
510
  else
511
+ # confirm_all / confirm_safes — a human is present, truly wait for user input.
511
512
  awaiting_feedback = true
512
513
  end
513
514
  else
@@ -148,7 +148,7 @@ module Clacky
148
148
  # Default model for ClaudeCode environment
149
149
  CLAUDE_DEFAULT_MODEL = "claude-sonnet-4-5"
150
150
 
151
- PERMISSION_MODES = [:auto_approve, :confirm_safes].freeze
151
+ PERMISSION_MODES = [:auto_approve, :confirm_safes, :confirm_all].freeze
152
152
 
153
153
  attr_accessor :permission_mode, :max_tokens, :verbose,
154
154
  :enable_compression, :enable_prompt_caching,
data/lib/clacky/cli.rb CHANGED
@@ -25,8 +25,9 @@ module Clacky
25
25
  After completing a task, the agent waits for your next instruction.
26
26
 
27
27
  Permission modes:
28
- auto_approve - Automatically execute all tools (use with caution)
28
+ auto_approve - Automatically execute all tools, no human interaction (use with caution)
29
29
  confirm_safes - Auto-approve safe operations, confirm risky ones (default)
30
+ confirm_all - Auto-approve all file/shell tools, but wait for human on interactive prompts
30
31
 
31
32
  UI themes:
32
33
  hacker - Matrix/hacker-style with bracket symbols (default)
@@ -41,7 +42,7 @@ module Clacky
41
42
  $ clacky agent --mode=auto_approve --path /path/to/project
42
43
  LONGDESC
43
44
  option :mode, type: :string, default: "confirm_safes",
44
- desc: "Permission mode: auto_approve, confirm_safes"
45
+ desc: "Permission mode: auto_approve, confirm_safes, confirm_all"
45
46
  option :theme, type: :string, default: "hacker",
46
47
  desc: "UI theme: hacker, minimal (default: hacker)"
47
48
  option :verbose, type: :boolean, aliases: "-v", default: false, desc: "Show detailed output"
@@ -0,0 +1,146 @@
1
+ ---
2
+ name: onboard
3
+ description: Onboard a new user by collecting AI personality preferences and user profile, then writing SOUL.md and USER.md.
4
+ disable-model-invocation: true
5
+ user-invocable: true
6
+ ---
7
+
8
+ # Skill: onboard
9
+
10
+ ## Purpose
11
+ Guide a new user through personalizing their Clacky experience via interactive cards.
12
+ Collect AI personality preferences and user profile, then write `SOUL.md` and `USER.md`.
13
+ All structured input is gathered through `request_user_feedback` cards — no free-form interrogation.
14
+
15
+ ## Steps
16
+
17
+ ### 1. Greet the user
18
+
19
+ Send a short, warm welcome message (2–3 sentences). Detect the user's language from any
20
+ text they've already typed; default to English. Do NOT ask any questions yet.
21
+
22
+ Example (English):
23
+ > Hi! I'm Clacky, your AI coding assistant ⚡
24
+ > Let's take 30 seconds to personalize your experience — I'll ask just a couple of quick things.
25
+
26
+ ### 2. Collect AI personality (card)
27
+
28
+ Call `request_user_feedback` with a card to set the assistant's name and personality:
29
+
30
+ ```json
31
+ {
32
+ "question": "First, let's set up your assistant.",
33
+ "options": [
34
+ "🎯 Professional — Precise, structured, minimal filler",
35
+ "😊 Friendly — Warm, encouraging, like a knowledgeable friend",
36
+ "🎨 Creative — Imaginative, uses metaphors, enthusiastic",
37
+ "⚡ Concise — Ultra-brief, bullet points, maximum signal"
38
+ ]
39
+ }
40
+ ```
41
+
42
+ Also ask for a custom name in the same message if the platform supports a text field;
43
+ otherwise follow up with: "What should I call myself? (leave blank to keep 'Clacky')"
44
+
45
+ Map the chosen option to a personality key:
46
+ - Option 1 → `professional`
47
+ - Option 2 → `friendly`
48
+ - Option 3 → `creative`
49
+ - Option 4 → `concise`
50
+
51
+ Store: `ai.name` (default `"Clacky"`), `ai.personality`.
52
+
53
+ ### 3. Collect user profile (card)
54
+
55
+ Call `request_user_feedback` again:
56
+
57
+ ```json
58
+ {
59
+ "question": "Now a bit about you — all optional, skip anything you like.",
60
+ "options": []
61
+ }
62
+ ```
63
+
64
+ Ask for the following in the question text (as labeled fields description, since options is empty):
65
+ - Name / nickname
66
+ - Occupation
67
+ - What you want to use AI for most
68
+ - Social / portfolio links (GitHub, Twitter/X, personal site…) — AI will read them to learn about you
69
+
70
+ Parse the user's reply as free text; extract whatever they provide.
71
+
72
+ ### 4. Learn from links (if any)
73
+
74
+ For each URL the user provided, use the `web_search` tool or fetch the page to read
75
+ publicly available info: bio, projects, tech stack, interests, writing style, etc.
76
+ Note key facts for the USER.md. Skip silently if a URL is unreachable.
77
+
78
+ ### 5. Write SOUL.md
79
+
80
+ Write to `~/.clacky/agents/SOUL.md`.
81
+
82
+ Use `ai.name` and `ai.personality` to shape the content.
83
+ If the user's language appears to be non-English (detected from their replies), write in that language.
84
+
85
+ **Personality style guide:**
86
+
87
+ | Key | Tone |
88
+ |-----|------|
89
+ | `professional` | Concise, precise, structured. Gets to the point. Minimal filler. |
90
+ | `friendly` | Warm, uses light humor, feels like a knowledgeable friend. |
91
+ | `creative` | Imaginative, uses metaphors, thinks outside the box, enthusiastic. |
92
+ | `concise` | Ultra-brief. Bullet points. Maximum signal-to-noise ratio. |
93
+
94
+ Template:
95
+
96
+ ```markdown
97
+ # [AI Name] — Soul
98
+
99
+ ## Identity
100
+ I am [AI Name], an AI coding assistant and technical co-founder.
101
+ [1–2 sentences reflecting the chosen personality.]
102
+
103
+ ## Personality & Tone
104
+ [3–5 bullet points describing communication style.]
105
+
106
+ ## Core Strengths
107
+ - Translating ideas into working code quickly
108
+ - Breaking down complex problems into clear steps
109
+ - Spotting issues before they become problems
110
+ - Adapting explanation depth to the user's background
111
+
112
+ ## Working Style
113
+ [2–3 sentences about how I approach tasks, matching the personality.]
114
+ ```
115
+
116
+ ### 6. Write USER.md
117
+
118
+ Write to `~/.clacky/agents/USER.md`.
119
+
120
+ ```markdown
121
+ # User Profile
122
+
123
+ ## About
124
+ - **Name**: [nickname or "Not provided"]
125
+ - **Occupation**: [or "Not provided"]
126
+ - **Primary Goal**: [or "Not provided"]
127
+
128
+ ## Background & Interests
129
+ [If links were fetched: 3–5 bullet points from what was learned.
130
+ Otherwise: omit section or write "No additional context."]
131
+
132
+ ## How to Help Best
133
+ [1–2 sentences tailored to the user's goal and background.]
134
+ ```
135
+
136
+ ### 7. Confirm and close
137
+
138
+ Reply with a single short message, e.g.:
139
+ > All set! I've saved your preferences. Feel free to close this tab and start a fresh session — enjoy! 🚀
140
+
141
+ Do NOT open a new session — the UI handles navigation after the skill finishes.
142
+
143
+ ## Notes
144
+ - Keep both files under 300 words each.
145
+ - Do not ask follow-up questions beyond the two cards above.
146
+ - Work with whatever the user provides; fill in sensible defaults for anything omitted.