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.
- package/ai-chat/CHAT_MASTER_PLAN.md +184 -0
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +169 -0
- package/node_modules/@groove-dev/daemon/src/conversations.js +423 -0
- package/node_modules/@groove-dev/daemon/src/index.js +2 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-C5WTeZO4.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-De-OWmBX.js → index-X58BAjGp.js} +1752 -1745
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/app.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/chat/chat-header.jsx +138 -0
- package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +112 -0
- package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +347 -0
- package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +165 -0
- package/node_modules/@groove-dev/gui/src/components/chat/conversation-list.jsx +154 -0
- package/node_modules/@groove-dev/gui/src/components/chat/model-picker.jsx +143 -0
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -1
- package/node_modules/@groove-dev/gui/src/stores/groove.js +220 -0
- package/node_modules/@groove-dev/gui/src/views/chat.jsx +6 -0
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +169 -0
- package/packages/daemon/src/conversations.js +423 -0
- package/packages/daemon/src/index.js +2 -0
- package/packages/gui/dist/assets/index-C5WTeZO4.css +1 -0
- package/packages/gui/dist/assets/{index-De-OWmBX.js → index-X58BAjGp.js} +1752 -1745
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.jsx +2 -0
- package/packages/gui/src/components/chat/chat-header.jsx +138 -0
- package/packages/gui/src/components/chat/chat-input.jsx +112 -0
- package/packages/gui/src/components/chat/chat-messages.jsx +347 -0
- package/packages/gui/src/components/chat/chat-view.jsx +165 -0
- package/packages/gui/src/components/chat/conversation-list.jsx +154 -0
- package/packages/gui/src/components/chat/model-picker.jsx +143 -0
- package/packages/gui/src/components/layout/activity-bar.jsx +2 -1
- package/packages/gui/src/stores/groove.js +220 -0
- package/packages/gui/src/views/chat.jsx +6 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CyVj0fHl.css +0 -1
- 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
|
|
@@ -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) => {
|