groove-dev 0.27.55 → 0.27.57

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 (42) hide show
  1. package/ai-chat/CHAT_MASTER_PLAN.md +184 -0
  2. package/node_modules/@groove-dev/cli/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/src/api.js +169 -0
  5. package/node_modules/@groove-dev/daemon/src/conversations.js +423 -0
  6. package/node_modules/@groove-dev/daemon/src/index.js +2 -0
  7. package/node_modules/@groove-dev/gui/dist/assets/index-C5WTeZO4.css +1 -0
  8. package/node_modules/@groove-dev/gui/dist/assets/{index-De-OWmBX.js → index-X58BAjGp.js} +1752 -1745
  9. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  10. package/node_modules/@groove-dev/gui/package.json +1 -1
  11. package/node_modules/@groove-dev/gui/src/app.jsx +2 -0
  12. package/node_modules/@groove-dev/gui/src/components/chat/chat-header.jsx +138 -0
  13. package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +112 -0
  14. package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +347 -0
  15. package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +165 -0
  16. package/node_modules/@groove-dev/gui/src/components/chat/conversation-list.jsx +154 -0
  17. package/node_modules/@groove-dev/gui/src/components/chat/model-picker.jsx +143 -0
  18. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -1
  19. package/node_modules/@groove-dev/gui/src/stores/groove.js +220 -0
  20. package/node_modules/@groove-dev/gui/src/views/chat.jsx +6 -0
  21. package/package.json +1 -1
  22. package/packages/cli/package.json +1 -1
  23. package/packages/daemon/package.json +1 -1
  24. package/packages/daemon/src/api.js +169 -0
  25. package/packages/daemon/src/conversations.js +423 -0
  26. package/packages/daemon/src/index.js +2 -0
  27. package/packages/gui/dist/assets/index-C5WTeZO4.css +1 -0
  28. package/packages/gui/dist/assets/{index-De-OWmBX.js → index-X58BAjGp.js} +1752 -1745
  29. package/packages/gui/dist/index.html +2 -2
  30. package/packages/gui/package.json +1 -1
  31. package/packages/gui/src/app.jsx +2 -0
  32. package/packages/gui/src/components/chat/chat-header.jsx +138 -0
  33. package/packages/gui/src/components/chat/chat-input.jsx +112 -0
  34. package/packages/gui/src/components/chat/chat-messages.jsx +347 -0
  35. package/packages/gui/src/components/chat/chat-view.jsx +165 -0
  36. package/packages/gui/src/components/chat/conversation-list.jsx +154 -0
  37. package/packages/gui/src/components/chat/model-picker.jsx +143 -0
  38. package/packages/gui/src/components/layout/activity-bar.jsx +2 -1
  39. package/packages/gui/src/stores/groove.js +220 -0
  40. package/packages/gui/src/views/chat.jsx +6 -0
  41. package/node_modules/@groove-dev/gui/dist/assets/index-CyVj0fHl.css +0 -1
  42. package/packages/gui/dist/assets/index-CyVj0fHl.css +0 -1
@@ -0,0 +1,184 @@
1
+ # Groove Chat — Master Plan
2
+
3
+ > The smartest AI chat interface ever built, powered by a full orchestration layer.
4
+
5
+ ## North Star
6
+
7
+ Groove Chat is a first-class standalone chat experience that lives alongside Teams in the sidebar. It is not a wrapper around a chatbot — it is a command center disguised as a conversation. Every capability Groove has (multi-model routing, Layer 7 memory, MCP integrations, agent orchestration, project context) is accessible from a single chat input bar.
8
+
9
+ For Groove Network users, this is the primary interface — a decentralized LLM chat that feels as polished and intelligent as any frontier cloud product.
10
+
11
+ ## Why This Matters
12
+
13
+ Teams are powerful but heavy. Sometimes you just want to ask a question, research something, brainstorm, or get a quick code review. Chat fills that gap. It is the fast path — zero setup, instant AI access, full platform power.
14
+
15
+ ## Architecture
16
+
17
+ Chat supports two modes — the user picks which one fits the moment:
18
+
19
+ ### API Mode (default, lightweight)
20
+ - No agent process spawned — each message is a one-shot callHeadless() call
21
+ - Client sends conversation history with each request (context managed client-side)
22
+ - Cheap, fast, casual — like talking to ChatGPT or Claude.ai
23
+ - Uses the journalist's existing callHeadless() infrastructure (works across all providers)
24
+ - Response streamed back via a streaming HTTP response or WebSocket
25
+ - Perfect for: quick questions, brainstorming, research, casual chat
26
+ - Groove Network models are always API mode (inherently one-shot)
27
+
28
+ ### Agent Mode (heavyweight)
29
+ - Spawns a full chat agent with tools, file access, session resume
30
+ - Agent persists between messages — maintains its own context
31
+ - Can read/write files, run commands, search codebase
32
+ - Session resume means zero cold-start when returning
33
+ - More expensive but more powerful
34
+ - Perfect for: code reviews, implementation help, deep analysis
35
+
36
+ ### Mode Switching
37
+ - Toggle in the chat header next to the model picker: "Chat" (API) vs "Agent" (full)
38
+ - Default is API mode — lightweight until you need power
39
+ - Can switch mid-conversation — upgrading to Agent spawns an agent with the conversation history as context
40
+ - Downgrading to API kills the agent, continues with client-side history
41
+ - Conversations persist metadata (title, model, mode, timestamps, pinned status) separately from agent lifecycle
42
+ - Layer 7 memory gives continuity across conversations
43
+
44
+ ```
45
+ ┌─────────────────────────────────────────────────────────┐
46
+ │ Chat View │
47
+ │ ┌──────────────┬────────────────────────────────────────┐│
48
+ │ │ Conversation │ Chat Area ││
49
+ │ │ Sidebar │ ┌────────────────────────────────────┐ ││
50
+ │ │ │ │ Messages (streaming, markdown, │ ││
51
+ │ │ [+ New Chat] │ │ code blocks, tool calls) │ ││
52
+ │ │ │ │ │ ││
53
+ │ │ Today │ │ │ ││
54
+ │ │ ▸ Chat 1 │ │ │ ││
55
+ │ │ ▸ Chat 2 │ │ │ ││
56
+ │ │ │ │ │ ││
57
+ │ │ Yesterday │ │ │ ││
58
+ │ │ ▸ Chat 3 │ ├────────────────────────────────────┤ ││
59
+ │ │ │ │ [Model ▾] [Type a message...] [➤] │ ││
60
+ │ │ Pinned │ │ [Attach] [Stop] │ ││
61
+ │ │ ▸ Chat 4 │ └────────────────────────────────────┘ ││
62
+ │ └──────────────┴────────────────────────────────────────┘│
63
+ └─────────────────────────────────────────────────────────┘
64
+ ```
65
+
66
+ ## Phased Rollout
67
+
68
+ ### Phase 1 — Core Chat Experience (NOW)
69
+
70
+ The foundation. A beautiful, functional chat that works with every provider.
71
+
72
+ GUI (packages/gui/):
73
+ - ActivityBar: Add MessageCircle icon below Teams
74
+ - New view: src/views/chat.jsx
75
+ - New components: src/components/chat/
76
+ - chat-view.jsx — Main layout (conversation sidebar + chat area)
77
+ - conversation-list.jsx — Left sidebar: new chat button, grouped by date, pinned section, search
78
+ - chat-messages.jsx — Message rendering: full markdown, syntax-highlighted code blocks, streaming cursor
79
+ - chat-input.jsx — Rich input bar: model picker dropdown, file attachments, stop button
80
+ - chat-header.jsx — Active conversation header: title, model badge, token count, actions
81
+ - model-picker.jsx — Provider/model selector: shows all available providers, model tiers, cost indicators
82
+ - App router: Add chat case to ViewRouter in app.jsx
83
+ - Store: Add conversation state to stores/groove.js
84
+ - conversations: [] — metadata array
85
+ - activeConversationId: null
86
+ - conversationMessages: {} — keyed by conversation ID
87
+ - createConversation(), deleteConversation(), renameConversation()
88
+ - setActiveConversation(), sendChatMessage()
89
+
90
+ Daemon (packages/daemon/):
91
+ - New file: src/conversations.js — Conversation manager
92
+ - CRUD operations with .groove/conversations.json persistence
93
+ - Auto-title generation (first message summary)
94
+ - Pin/archive support
95
+ - Maps conversation ID to agent ID
96
+ - API endpoints in src/api.js:
97
+ - GET /api/conversations — list all conversations
98
+ - POST /api/conversations — create new conversation (spawns chat agent)
99
+ - GET /api/conversations/:id — get conversation with messages
100
+ - PATCH /api/conversations/:id — rename, pin, archive
101
+ - DELETE /api/conversations/:id — delete conversation (kills agent)
102
+ - POST /api/conversations/:id/message — send message (proxies to agent instruct)
103
+ - WebSocket: broadcast conversation events (created, updated, deleted)
104
+
105
+ Key behaviors:
106
+ - Creating a conversation spawns a chat agent with the selected provider/model
107
+ - Messages route through the existing instruct endpoint
108
+ - Streaming responses arrive via existing agent:output WebSocket events
109
+ - Model can be changed mid-conversation (hot-swap for supported providers, rotation for others)
110
+ - Conversations persist in .groove/conversations.json, messages in localStorage
111
+ - Auto-generated titles from first message content
112
+
113
+ ### Phase 2 — Intelligence Layer
114
+
115
+ What makes Groove Chat feel smarter than everything else.
116
+
117
+ - Research Mode: Toggle that enables deep codebase exploration, file reading, multi-step reasoning. Results rendered as structured cards with sources.
118
+ - Project Context: Auto-inject CLAUDE.md + project map. Chat knows your codebase without being told.
119
+ - Thinking/Reasoning Display: Collapsible sections showing the AI's chain of thought (for models that support it).
120
+ - Tool Call Visualization: When AI reads files, searches, or runs commands — show inline activity cards (not raw text).
121
+ - Conversation Search: Full-text search across all conversations.
122
+ - Keyboard Shortcuts: Cmd+Shift+N for new chat, Cmd+[ / ] to navigate conversations.
123
+
124
+ ### Phase 3 — Platform Integration
125
+
126
+ Leverage the full Groove ecosystem.
127
+
128
+ - MCP Integrations in Chat: Use installed integrations directly — "summarize my unread emails", "post to Slack", "check GitHub PRs".
129
+ - Escalate to Team: One-click button that takes the chat context and spawns a planner + team to build what was discussed.
130
+ - Agent Awareness: Ask about running agents — "what is frontend-3 working on?" queries the live agent.
131
+ - Approval Gates: Destructive actions in chat go through the same approval system as teams.
132
+ - Slash Commands: /search, /agent <id>, /escalate, /model <name>, /clear
133
+
134
+ ### Phase 4 — Advanced Features
135
+
136
+ - Multi-Model Comparison: Send one prompt to 2-3 models, see responses side by side. Perfect for research and model evaluation.
137
+ - Canvas/Artifacts: Code, documents, or diagrams generated in chat can be pinned to a side panel. Edit inline, iterate with the AI.
138
+ - Voice Input: Whisper-based voice-to-text for hands-free chat.
139
+ - Conversation Branching: Fork a conversation at any point, explore different directions.
140
+ - Shared Conversations: Export/share conversations with team members via federation.
141
+
142
+ ## File Map
143
+
144
+ ```
145
+ packages/gui/src/
146
+ ├── views/
147
+ │ └── chat.jsx (NEW — Chat view entry point)
148
+ ├── components/
149
+ │ └── chat/
150
+ │ ├── chat-view.jsx (NEW — Main layout container)
151
+ │ ├── conversation-list.jsx (NEW — Sidebar conversation list)
152
+ │ ├── chat-messages.jsx (NEW — Message rendering)
153
+ │ ├── chat-input.jsx (NEW — Input bar + model picker)
154
+ │ ├── chat-header.jsx (NEW — Conversation header)
155
+ │ └── model-picker.jsx (NEW — Provider/model selector)
156
+ ├── stores/
157
+ │ └── groove.js (MODIFY — Add conversation state)
158
+ ├── components/layout/
159
+ │ ├── activity-bar.jsx (MODIFY — Add Chat icon)
160
+ │ └── app-shell.jsx (MODIFY — Wire up Chat view)
161
+ └── app.jsx (MODIFY — Add Chat to ViewRouter)
162
+
163
+ packages/daemon/src/
164
+ ├── conversations.js (NEW — Conversation manager)
165
+ └── api.js (MODIFY — Add /api/conversations endpoints)
166
+ ```
167
+
168
+ ## Design Principles
169
+
170
+ 1. Zero friction — New Chat should be one click or one keystroke away
171
+ 2. Model-first — The model picker is prominent, not hidden. Users should always know what they're talking to
172
+ 3. Project-aware — Chat knows your codebase. You never have to explain your project
173
+ 4. Escalation path — Any chat can become a team. Research flows into action
174
+ 5. Provider-agnostic — Works with Claude, Gemini, Ollama, Groove Network, anything with a provider
175
+ 6. Beautiful defaults — Full markdown rendering, syntax highlighting, smooth streaming. No raw text dumps
176
+
177
+ ## Groove Network Integration
178
+
179
+ Chat is the primary interface for Groove Network (decentralized LLM). Key considerations:
180
+ - Groove Network is one-shot (stateless per message) — conversations track history client-side
181
+ - Model picker shows Groove Network models with a "decentralized" badge
182
+ - Cost display shows token savings vs cloud pricing
183
+ - Network status indicator in chat header (connected/latency/node count)
184
+ - Future: streaming support as network protocol evolves
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.55",
3
+ "version": "0.27.57",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.55",
3
+ "version": "0.27.57",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -810,6 +810,175 @@ export function createApi(app, daemon) {
810
810
  }
811
811
  });
812
812
 
813
+ // --- Conversations ---
814
+
815
+ app.get('/api/conversations', (req, res) => {
816
+ res.json({ conversations: daemon.conversations.list() });
817
+ });
818
+
819
+ app.post('/api/conversations', async (req, res) => {
820
+ try {
821
+ const { provider, model, title, mode } = req.body;
822
+ if (!provider || typeof provider !== 'string') {
823
+ return res.status(400).json({ error: 'provider is required' });
824
+ }
825
+ if (mode && mode !== 'api' && mode !== 'agent') {
826
+ return res.status(400).json({ error: 'mode must be "api" or "agent"' });
827
+ }
828
+ const conversation = await daemon.conversations.create(provider, model, title, mode || 'api');
829
+ daemon.audit.log('conversation.create', { id: conversation.id, provider, model, mode: conversation.mode });
830
+ res.status(201).json(conversation);
831
+ } catch (err) {
832
+ res.status(400).json({ error: err.message });
833
+ }
834
+ });
835
+
836
+ app.get('/api/conversations/:id', (req, res) => {
837
+ const conversation = daemon.conversations.get(req.params.id);
838
+ if (!conversation) return res.status(404).json({ error: 'Conversation not found' });
839
+ res.json(conversation);
840
+ });
841
+
842
+ app.patch('/api/conversations/:id', async (req, res) => {
843
+ try {
844
+ const conv = daemon.conversations.get(req.params.id);
845
+ if (!conv) return res.status(404).json({ error: 'Conversation not found' });
846
+ if (req.body.title !== undefined) daemon.conversations.rename(req.params.id, req.body.title);
847
+ if (req.body.pinned !== undefined) daemon.conversations.pin(req.params.id, req.body.pinned);
848
+ if (req.body.archived !== undefined) daemon.conversations.archive(req.params.id, req.body.archived);
849
+ if (req.body.mode !== undefined) {
850
+ if (req.body.mode !== 'api' && req.body.mode !== 'agent') {
851
+ return res.status(400).json({ error: 'mode must be "api" or "agent"' });
852
+ }
853
+ await daemon.conversations.setMode(req.params.id, req.body.mode);
854
+ }
855
+ daemon.audit.log('conversation.update', { id: req.params.id, mode: req.body.mode });
856
+ res.json(daemon.conversations.get(req.params.id));
857
+ } catch (err) {
858
+ res.status(400).json({ error: err.message });
859
+ }
860
+ });
861
+
862
+ app.delete('/api/conversations/:id', async (req, res) => {
863
+ try {
864
+ const conv = daemon.conversations.get(req.params.id);
865
+ if (!conv) return res.status(404).json({ error: 'Conversation not found' });
866
+ await daemon.conversations.delete(req.params.id);
867
+ daemon.audit.log('conversation.delete', { id: req.params.id });
868
+ res.json({ ok: true });
869
+ } catch (err) {
870
+ res.status(400).json({ error: err.message });
871
+ }
872
+ });
873
+
874
+ app.post('/api/conversations/:id/message', async (req, res) => {
875
+ try {
876
+ const { message, history } = req.body;
877
+ if (!message || typeof message !== 'string' || !message.trim()) {
878
+ return res.status(400).json({ error: 'message is required' });
879
+ }
880
+ const conv = daemon.conversations.get(req.params.id);
881
+ if (!conv) return res.status(404).json({ error: 'Conversation not found' });
882
+
883
+ daemon.conversations.autoTitle(req.params.id, message.trim());
884
+ daemon.conversations.touchUpdatedAt(req.params.id);
885
+
886
+ // API mode — lightweight headless streaming, no agent spawned
887
+ if (conv.mode === 'api' || !conv.agentId) {
888
+ await daemon.conversations.sendMessage(req.params.id, message.trim(), history || []);
889
+ daemon.audit.log('conversation.message', { id: req.params.id, mode: 'api' });
890
+ return res.json({ status: 'streaming', mode: 'api' });
891
+ }
892
+
893
+ // Agent mode — existing behavior
894
+ const agent = daemon.registry.get(conv.agentId);
895
+ if (!agent) return res.status(400).json({ error: 'Agent no longer exists' });
896
+
897
+ // Record user feedback for journalist context
898
+ if (daemon.journalist) daemon.journalist.recordUserFeedback(agent, message.trim());
899
+
900
+ // Agent loop path — send message directly to the running loop
901
+ if (daemon.processes.hasAgentLoop(conv.agentId)) {
902
+ const sent = await daemon.processes.sendMessage(conv.agentId, message.trim());
903
+ if (sent) {
904
+ daemon.audit.log('conversation.message', { id: req.params.id, agentId: conv.agentId });
905
+ return res.json({ id: conv.agentId, status: 'message_sent' });
906
+ }
907
+ }
908
+
909
+ // One-shot providers: kill and respawn with the message as prompt
910
+ const provider = getProvider(agent.provider);
911
+ if (provider?.constructor?.isOneShot) {
912
+ const oldConfig = { ...agent };
913
+ if (daemon.processes.isRunning(conv.agentId)) {
914
+ await daemon.processes.kill(conv.agentId);
915
+ }
916
+ daemon.registry.remove(conv.agentId);
917
+ daemon.locks.release(conv.agentId);
918
+
919
+ const newAgent = await daemon.processes.spawn({
920
+ role: 'chat',
921
+ scope: oldConfig.scope,
922
+ provider: oldConfig.provider,
923
+ model: oldConfig.model,
924
+ prompt: message.trim(),
925
+ permission: oldConfig.permission || 'full',
926
+ workingDir: oldConfig.workingDir,
927
+ name: oldConfig.name,
928
+ teamId: oldConfig.teamId,
929
+ });
930
+
931
+ // Update conversation to point to new agent
932
+ const convObj = daemon.conversations.conversations.get(req.params.id);
933
+ if (convObj) {
934
+ convObj.agentId = newAgent.id;
935
+ daemon.conversations._save();
936
+ }
937
+ daemon.audit.log('conversation.message', { id: req.params.id, agentId: newAgent.id, oneShot: true });
938
+ return res.json({ id: newAgent.id, status: 'respawned' });
939
+ }
940
+
941
+ // Running CLI agent — queue the message
942
+ if (daemon.processes.isRunning(conv.agentId)) {
943
+ daemon.processes.queueMessage(conv.agentId, message.trim());
944
+ daemon.audit.log('conversation.message', { id: req.params.id, agentId: conv.agentId, queued: true });
945
+ return res.json({ id: conv.agentId, status: 'message_queued' });
946
+ }
947
+
948
+ // CLI agent — session resume or rotation
949
+ const SESSION_RESUME_CEILING = 5_000_000;
950
+ const resumed = !!agent.sessionId && (agent.tokensUsed || 0) < SESSION_RESUME_CEILING;
951
+ const newAgent = resumed
952
+ ? await daemon.processes.resume(conv.agentId, message.trim())
953
+ : await daemon.rotator.rotate(conv.agentId, { additionalPrompt: message.trim() });
954
+
955
+ // Update conversation to point to new agent if rotated
956
+ if (newAgent.id !== conv.agentId) {
957
+ const convObj = daemon.conversations.conversations.get(req.params.id);
958
+ if (convObj) {
959
+ convObj.agentId = newAgent.id;
960
+ daemon.conversations._save();
961
+ }
962
+ }
963
+
964
+ daemon.audit.log('conversation.message', { id: req.params.id, agentId: newAgent.id, resumed });
965
+ res.json({ id: newAgent.id, status: resumed ? 'resumed' : 'rotated' });
966
+ } catch (err) {
967
+ res.status(400).json({ error: err.message });
968
+ }
969
+ });
970
+
971
+ app.post('/api/conversations/:id/stop', (req, res) => {
972
+ try {
973
+ const conv = daemon.conversations.get(req.params.id);
974
+ if (!conv) return res.status(404).json({ error: 'Conversation not found' });
975
+ daemon.conversations.stopStreaming(req.params.id);
976
+ res.json({ ok: true });
977
+ } catch (err) {
978
+ res.status(400).json({ error: err.message });
979
+ }
980
+ });
981
+
813
982
  // --- Approvals ---
814
983
 
815
984
  app.get('/api/approvals', (req, res) => {