groove-dev 0.27.135 → 0.27.136

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 (31) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/src/api.js +32 -0
  4. package/node_modules/@groove-dev/daemon/src/journalist.js +14 -12
  5. package/node_modules/@groove-dev/daemon/src/model-lab.js +50 -67
  6. package/node_modules/@groove-dev/daemon/src/rotator.js +1 -1
  7. package/node_modules/@groove-dev/gui/dist/assets/{index-D4JZyMWH.js → index-BrZHF7pK.js} +7 -7
  8. package/node_modules/@groove-dev/gui/dist/index.html +1 -1
  9. package/node_modules/@groove-dev/gui/package.json +1 -1
  10. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +5 -1
  11. package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +1 -1
  12. package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +8 -8
  13. package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +1 -1
  14. package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +3 -3
  15. package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
  16. package/package.json +1 -1
  17. package/packages/cli/package.json +1 -1
  18. package/packages/daemon/package.json +1 -1
  19. package/packages/daemon/src/api.js +32 -0
  20. package/packages/daemon/src/journalist.js +14 -12
  21. package/packages/daemon/src/model-lab.js +50 -67
  22. package/packages/daemon/src/rotator.js +1 -1
  23. package/packages/gui/dist/assets/{index-D4JZyMWH.js → index-BrZHF7pK.js} +7 -7
  24. package/packages/gui/dist/index.html +1 -1
  25. package/packages/gui/package.json +1 -1
  26. package/packages/gui/src/components/agents/agent-feed.jsx +5 -1
  27. package/packages/gui/src/components/agents/agent-file-tree.jsx +1 -1
  28. package/packages/gui/src/components/editor/code-editor.jsx +8 -8
  29. package/packages/gui/src/components/editor/file-tree.jsx +1 -1
  30. package/packages/gui/src/components/lab/system-prompt-editor.jsx +3 -3
  31. package/packages/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
@@ -6,7 +6,7 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <link rel="icon" type="image/png" href="/favicon.png" />
8
8
  <title>Groove GUI</title>
9
- <script type="module" crossorigin src="/assets/index-D4JZyMWH.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-BrZHF7pK.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-26L3JoZv.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/reactflow-DoBZjiHE.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/codemirror-DRQdprYi.js">
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.135",
3
+ "version": "0.27.136",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -575,7 +575,7 @@ export function AgentFeed({ agent }) {
575
575
  }
576
576
  });
577
577
  }
578
- }, [timeline.length]);
578
+ }, [timeline.length, sending, isThinking]);
579
579
 
580
580
  async function handleFileSelect(e) {
581
581
  const files = Array.from(e.target.files || []);
@@ -621,6 +621,10 @@ export function AgentFeed({ agent }) {
621
621
 
622
622
  setInput('');
623
623
  setSending(true);
624
+ isAtBottomRef.current = true;
625
+ requestAnimationFrame(() => {
626
+ if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
627
+ });
624
628
  try {
625
629
  if (mode === 'query') {
626
630
  await queryAgent(agent.id, text);
@@ -135,7 +135,7 @@ function TreeEntry({ entry, depth, onOpen, expandedDirs, onToggleDir, onContextM
135
135
  onDoubleClick={handleCtxMenu}
136
136
  onContextMenu={handleCtxMenu}
137
137
  className={cn(
138
- 'w-full flex items-center gap-1.5 py-[3px] text-[13px] font-sans cursor-pointer',
138
+ 'w-full flex items-center gap-1.5 py-[3px] text-xs font-sans cursor-pointer',
139
139
  'hover:bg-surface-4/50 transition-colors text-left',
140
140
  isDragging && 'opacity-50',
141
141
  isDragOver && 'bg-accent/15 ring-1 ring-accent/50 rounded',
@@ -27,10 +27,10 @@ const LANGS = {
27
27
  const grooveHighlightStyle = HighlightStyle.define([
28
28
  { tag: t.keyword, color: '#b07fd5' },
29
29
  { tag: [t.name, t.deleted, t.character, t.macroName], color: '#d4d8e0' },
30
- { tag: [t.function(t.variableName), t.labelName], color: '#d9a45c' },
31
- { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#c4956a' },
30
+ { tag: [t.function(t.variableName), t.labelName], color: '#dcc9a0' },
31
+ { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#d4a07a' },
32
32
  { tag: [t.definition(t.name), t.separator], color: '#bcc2cd' },
33
- { tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#cda869' },
33
+ { tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#e0c589' },
34
34
  { tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.special(t.string)], color: '#89b4c4' },
35
35
  { tag: [t.meta, t.comment], color: '#6e7681', fontStyle: 'italic' },
36
36
  { tag: t.strong, fontWeight: 'bold' },
@@ -38,13 +38,13 @@ const grooveHighlightStyle = HighlightStyle.define([
38
38
  { tag: t.strikethrough, textDecoration: 'line-through' },
39
39
  { tag: t.link, color: '#7ab0df', textDecoration: 'underline' },
40
40
  { tag: t.heading, fontWeight: '400', color: '#bcc2cd' },
41
- { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#c4956a' },
42
- { tag: [t.processingInstruction, t.string, t.inserted], color: '#a3b18a' },
41
+ { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d4a07a' },
42
+ { tag: [t.processingInstruction, t.string, t.inserted], color: '#95b2b8' },
43
43
  { tag: t.invalid, color: '#d4736e' },
44
- { tag: t.propertyName, color: '#d9a45c' },
44
+ { tag: t.propertyName, color: '#dcc9a0' },
45
45
  { tag: [t.tagName], color: '#d4736e' },
46
- { tag: t.attributeName, color: '#cda869' },
47
- { tag: t.attributeValue, color: '#a3b18a' },
46
+ { tag: t.attributeName, color: '#e0c589' },
47
+ { tag: t.attributeValue, color: '#95b2b8' },
48
48
  ]);
49
49
 
50
50
  // Custom theme overrides to match our design tokens
@@ -130,7 +130,7 @@ function TreeNode({ entry, depth = 0, activePath, onFileClick, onDirToggle, expa
130
130
  onDoubleClick={handleContextMenu}
131
131
  onContextMenu={handleContextMenu}
132
132
  className={cn(
133
- 'w-full flex items-center gap-1.5 py-[3px] text-[13px] font-sans cursor-pointer',
133
+ 'w-full flex items-center gap-1.5 py-[3px] text-xs font-sans cursor-pointer',
134
134
  'hover:bg-surface-5 transition-colors text-left select-none',
135
135
  isActive && 'bg-accent/10 text-text-0',
136
136
  !isActive && 'text-text-1',
@@ -23,14 +23,14 @@ const editorTheme = EditorView.theme({
23
23
  const promptHighlightStyle = HighlightStyle.define([
24
24
  { tag: t.keyword, color: '#b07fd5' },
25
25
  { tag: [t.name, t.deleted, t.character, t.macroName], color: '#d4d8e0' },
26
- { tag: [t.function(t.variableName), t.labelName], color: '#d9a45c' },
26
+ { tag: [t.function(t.variableName), t.labelName], color: '#dcc9a0' },
27
27
  { tag: [t.meta, t.comment], color: '#6e7681', fontStyle: 'italic' },
28
28
  { tag: t.strong, fontWeight: 'bold' },
29
29
  { tag: t.emphasis, fontStyle: 'italic' },
30
30
  { tag: t.link, color: '#7ab0df', textDecoration: 'underline' },
31
31
  { tag: t.heading, fontWeight: '400', color: '#bcc2cd' },
32
- { tag: [t.processingInstruction, t.string, t.inserted], color: '#a3b18a' },
33
- { tag: [t.atom, t.bool], color: '#c4956a' },
32
+ { tag: [t.processingInstruction, t.string, t.inserted], color: '#95b2b8' },
33
+ { tag: [t.atom, t.bool], color: '#d4a07a' },
34
34
  { tag: t.invalid, color: '#d4736e' },
35
35
  ]);
36
36
 
@@ -153,7 +153,7 @@ export function BreadcrumbBar({
153
153
  return (
154
154
  <header
155
155
  className={cn(
156
- 'h-11 flex-shrink-0 flex items-center gap-3 px-4 pt-0.5 bg-surface-3 border-b border-border relative',
156
+ 'h-12 flex-shrink-0 flex items-center gap-3 px-4 bg-surface-3 border-b border-border relative',
157
157
  darwinDrag && 'pl-24 electron-drag electron-no-drag-children',
158
158
  )}
159
159
  >
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.135",
3
+ "version": "0.27.136",
4
4
  "description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama, any local model.",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.135",
3
+ "version": "0.27.136",
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.135",
3
+ "version": "0.27.136",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -124,6 +124,38 @@ export function createApi(app, daemon) {
124
124
  res.json({ status: 'ok', uptime: process.uptime() });
125
125
  });
126
126
 
127
+ // Debug: test fetch to llama-server from daemon runtime
128
+ app.get('/api/lab/debug-fetch', async (req, res) => {
129
+ const target = req.query.url || 'http://localhost:8081/v1/chat/completions';
130
+ const log = [];
131
+ try {
132
+ log.push(`fetch → ${target}`);
133
+ log.push(`node ${process.version}, electron ${process.versions.electron || 'N/A'}`);
134
+ const start = Date.now();
135
+ const r = await fetch(target, {
136
+ method: 'POST',
137
+ headers: { 'Content-Type': 'application/json' },
138
+ body: JSON.stringify({ model: 'Qwen3-0.6B-Q8_0.gguf', messages: [{ role: 'user', content: 'Say ok' }], stream: true, max_tokens: 10 }),
139
+ signal: AbortSignal.timeout(10000),
140
+ });
141
+ log.push(`status=${r.status} in ${Date.now() - start}ms`);
142
+ const reader = r.body.getReader();
143
+ let chunks = 0;
144
+ while (chunks < 5) {
145
+ const { done, value } = await reader.read();
146
+ if (done) break;
147
+ chunks++;
148
+ log.push(`chunk ${chunks}: ${new TextDecoder().decode(value).slice(0, 120)}`);
149
+ }
150
+ reader.cancel();
151
+ log.push(`total chunks read: ${chunks}`);
152
+ res.json({ ok: true, log });
153
+ } catch (err) {
154
+ log.push(`ERROR: ${err.message}`);
155
+ res.json({ ok: false, log, error: err.message });
156
+ }
157
+ });
158
+
127
159
  // List all agents
128
160
  app.get('/api/agents', (req, res) => {
129
161
  res.json(daemon.registry.getAll());
@@ -853,9 +853,8 @@ export class Journalist {
853
853
  const agentLog = filteredLogs[agent.id];
854
854
  const entries = agentLog?.entries || [];
855
855
 
856
- // Layer 7 memory: discoveries (pointer only), constraints, specializations
857
- const hasDiscoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 1, 100);
858
- const discoveryPointer = hasDiscoveries ? 'See .groove/memory/agent-discoveries.jsonl for error→fix pairs.' : '';
856
+ // Layer 7 memory: discoveries (inline, not pointer — agents lose context with pointers), constraints, specializations
857
+ const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 10, 1500) || '';
859
858
  const constraints = this.daemon.memory?.getConstraintsMarkdown(2000) || '';
860
859
  const specialization = this.daemon.memory?.getSpecialization(agent.id);
861
860
  const specLine = specialization?.avgQualityScore != null
@@ -872,7 +871,7 @@ export class Journalist {
872
871
  const recentTools = entries
873
872
  .filter((e) => e.type === 'tool' || e.type === 'error')
874
873
  .slice(-5)
875
- .map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || e.text || '').slice(0, 80)}`)
874
+ .map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || e.text || '').slice(0, 200)}`)
876
875
  .join('\n');
877
876
 
878
877
  // Try AI-synthesized session summary
@@ -909,7 +908,7 @@ export class Journalist {
909
908
  const fallbackRecentTools = entries
910
909
  .filter((e) => e.type === 'tool' || e.type === 'error')
911
910
  .slice(-5)
912
- .map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || '').slice(0, 80)}`)
911
+ .map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || '').slice(0, 200)}`)
913
912
  .join('\n');
914
913
 
915
914
  const fallbackParts = [];
@@ -923,8 +922,8 @@ export class Journalist {
923
922
  // For quality_degradation rotations, drop user messages (already in session summary)
924
923
  const includeUserMessages = options.reason !== 'quality_degradation';
925
924
 
926
- // Cap Original Task to 500 chars
927
- const originalTask = agent.prompt ? agent.prompt.slice(0, 500) + (agent.prompt.length > 500 ? '…' : '') : '';
925
+ // Cap Original Task to 1000 chars — task descriptions for debugging can be long
926
+ const originalTask = agent.prompt ? agent.prompt.slice(0, 1000) + (agent.prompt.length > 1000 ? '…' : '') : '';
928
927
 
929
928
  let brief = [
930
929
  `# Handoff Brief — ${agent.name} (${agent.role})`,
@@ -934,10 +933,13 @@ export class Journalist {
934
933
  `Rotation: ${options.reason || 'manual'}${options.qualityScore ? ` (quality: ${options.qualityScore}/100)` : ''} | Tokens: ${agent.tokensUsed}`,
935
934
  specLine,
936
935
  ``,
937
- discoveryPointer ? `## Known Issues & Fixes\n\n${discoveryPointer}\n` : '',
936
+ // Priority order: session summary (contains unresolved errors) first,
937
+ // then constraints, then discoveries, then tools — so the most critical
938
+ // debugging context survives even if the brief hits the hard cap.
939
+ sessionSummary ? `## Session Summary\n\n${sessionSummary}\n` : '',
938
940
  constraints ? `## Project Constraints (must follow)\n\n${constraints}\n` : '',
941
+ discoveries ? `## Known Issues & Fixes\n\n${discoveries}\n` : '',
939
942
  recentTools ? `## Last 5 Tool Calls\n\n${recentTools}\n` : '',
940
- sessionSummary ? `## Session Summary\n\n${sessionSummary}\n` : '',
941
943
  includeUserMessages && conversationSummary ? `## Recent User Messages\n\n${conversationSummary}\n` : '',
942
944
  recentChain ? `## Rotation History\n\n${recentChain}\n` : '',
943
945
  originalTask ? `## Original Task\n\n${originalTask}\n` : '',
@@ -946,9 +948,9 @@ export class Journalist {
946
948
  `Continue seamlessly — finish the work and deliver the output.`,
947
949
  ].filter(Boolean).join('\n');
948
950
 
949
- // Hard cap: total brief must not exceed 5000 chars
950
- if (brief.length > 5000) {
951
- brief = brief.slice(0, 4950) + '\n\n[Brief truncated — see session logs for full context]';
951
+ // Hard cap: 8000 chars enough for debugging context without overwhelming the new agent
952
+ if (brief.length > 8000) {
953
+ brief = brief.slice(0, 7950) + '\n\n[Brief truncated — see session logs for full context]';
952
954
  }
953
955
 
954
956
  return brief;
@@ -4,6 +4,7 @@
4
4
  import { resolve } from 'path';
5
5
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from 'fs';
6
6
  import { randomUUID } from 'crypto';
7
+ import { Readable } from 'stream';
7
8
 
8
9
  const RUNTIME_TYPES = ['ollama', 'vllm', 'llama-cpp', 'tgi', 'openai-compatible'];
9
10
  const DEFAULT_OLLAMA_ENDPOINT = 'http://localhost:11434';
@@ -223,10 +224,9 @@ export class ModelLab {
223
224
  ...this._buildParameterBody(parameters || {}),
224
225
  };
225
226
 
226
- const endpoint = `${rt.endpoint}/v1/chat/completions`;
227
-
228
- const headers = { 'Content-Type': 'application/json' };
229
- if (rt.apiKey) headers['Authorization'] = `Bearer ${rt.apiKey}`;
227
+ const endpoint = rt.endpoint.replace('localhost', '127.0.0.1');
228
+ const reqHeaders = { 'Content-Type': 'application/json' };
229
+ if (rt.apiKey) reqHeaders['Authorization'] = `Bearer ${rt.apiKey}`;
230
230
 
231
231
  const requestStart = Date.now();
232
232
  let ttft = null;
@@ -236,85 +236,61 @@ export class ModelLab {
236
236
  let generationStart = null;
237
237
  let fullContent = '';
238
238
 
239
- const resp = await fetch(endpoint, {
239
+ const resp = await fetch(`${endpoint}/v1/chat/completions`, {
240
240
  method: 'POST',
241
- headers,
241
+ headers: reqHeaders,
242
242
  body: JSON.stringify(body),
243
243
  signal: AbortSignal.timeout(300000),
244
244
  });
245
245
 
246
246
  if (!resp.ok) {
247
- let errorMsg;
248
- try { errorMsg = (await resp.json()).error?.message || `HTTP ${resp.status}`; } catch { errorMsg = `HTTP ${resp.status}`; }
249
- throw new Error(errorMsg);
247
+ let errMsg = `HTTP ${resp.status}`;
248
+ try { const e = await resp.json(); errMsg = e.error?.message || errMsg; } catch { /* ignore */ }
249
+ throw new Error(errMsg);
250
250
  }
251
251
 
252
- const reader = resp.body.getReader();
253
- const decoder = new TextDecoder();
252
+ const nodeStream = Readable.fromWeb(resp.body);
254
253
  let buffer = '';
255
254
 
256
- try {
257
- while (true) {
258
- const { done, value } = await reader.read();
259
- if (done) break;
260
-
261
- buffer += decoder.decode(value, { stream: true });
262
- const lines = buffer.split('\n');
263
- buffer = lines.pop() || '';
264
-
265
- for (const line of lines) {
266
- const trimmed = line.trim();
267
- if (!trimmed || !trimmed.startsWith('data: ')) continue;
268
- const payload = trimmed.slice(6);
269
- if (payload === '[DONE]') continue;
270
-
271
- try {
272
- const chunk = JSON.parse(payload);
273
- const delta = chunk.choices?.[0]?.delta;
274
- if (delta?.reasoning_content) {
275
- if (ttft === null) {
276
- ttft = Date.now() - requestStart;
277
- generationStart = Date.now();
278
- }
279
- completionTokens++;
280
- onEvent({ type: 'reasoning', content: delta.reasoning_content });
281
- }
282
- if (delta?.content) {
283
- if (ttft === null) {
284
- ttft = Date.now() - requestStart;
285
- generationStart = Date.now();
286
- }
287
- fullContent += delta.content;
288
- completionTokens++;
289
- onEvent({ type: 'token', content: delta.content });
290
- }
291
- if (chunk.usage) {
292
- promptTokens = chunk.usage.prompt_tokens || 0;
293
- totalTokens = chunk.usage.total_tokens || 0;
294
- if (chunk.usage.completion_tokens) {
295
- completionTokens = chunk.usage.completion_tokens;
296
- }
297
- }
298
- } catch { /* skip malformed chunk */ }
299
- }
255
+ for await (const chunk of nodeStream) {
256
+ buffer += typeof chunk === 'string' ? chunk : chunk.toString('utf8');
257
+ const lines = buffer.split('\n');
258
+ buffer = lines.pop() || '';
259
+
260
+ for (const line of lines) {
261
+ const trimmed = line.trim();
262
+ if (!trimmed || !trimmed.startsWith('data: ')) continue;
263
+ const data = trimmed.slice(6);
264
+ if (data === '[DONE]') continue;
265
+
266
+ try {
267
+ const parsed = JSON.parse(data);
268
+ const delta = parsed.choices?.[0]?.delta;
269
+ if (delta?.reasoning_content) {
270
+ if (ttft === null) { ttft = Date.now() - requestStart; generationStart = Date.now(); }
271
+ completionTokens++;
272
+ onEvent({ type: 'reasoning', content: delta.reasoning_content });
273
+ }
274
+ if (delta?.content) {
275
+ if (ttft === null) { ttft = Date.now() - requestStart; generationStart = Date.now(); }
276
+ fullContent += delta.content;
277
+ completionTokens++;
278
+ onEvent({ type: 'token', content: delta.content });
279
+ }
280
+ if (parsed.usage) {
281
+ promptTokens = parsed.usage.prompt_tokens || 0;
282
+ totalTokens = parsed.usage.total_tokens || 0;
283
+ if (parsed.usage.completion_tokens) completionTokens = parsed.usage.completion_tokens;
284
+ }
285
+ } catch { /* skip malformed chunk */ }
300
286
  }
301
- } finally {
302
- reader.releaseLock();
303
287
  }
304
288
 
305
289
  const generationTime = generationStart ? Date.now() - generationStart : Date.now() - requestStart;
306
290
  const tokensPerSec = generationTime > 0 ? (completionTokens / (generationTime / 1000)) : 0;
307
291
 
308
- let memoryUsage = null;
309
- if (rt.type === 'ollama') {
310
- memoryUsage = await this.getOllamaMemoryUsage(rt.endpoint);
311
- }
312
-
313
292
  if (sessionId) {
314
- this._appendToSession(sessionId, messages, {
315
- role: 'assistant',
316
- content: fullContent,
317
- });
293
+ this._appendToSession(sessionId, messages, { role: 'assistant', content: fullContent });
318
294
  }
319
295
 
320
296
  onEvent({
@@ -326,9 +302,16 @@ export class ModelLab {
326
302
  promptTokens,
327
303
  completionTokens,
328
304
  generationTime,
329
- memoryUsage,
305
+ memoryUsage: null,
330
306
  },
331
307
  });
308
+
309
+ if (rt.type === 'ollama') {
310
+ try {
311
+ const mem = await this.getOllamaMemoryUsage(rt.endpoint);
312
+ if (mem) onEvent({ type: 'memory', usage: mem });
313
+ } catch { /* ignore */ }
314
+ }
332
315
  }
333
316
 
334
317
  _buildParameterBody(params) {
@@ -343,7 +343,7 @@ export class Rotator extends EventEmitter {
343
343
  reason: options.reason || 'manual',
344
344
  oldTokens: agent.tokensUsed,
345
345
  contextUsage: agent.contextUsage,
346
- brief: brief.slice(0, 4000),
346
+ brief: brief.slice(0, 6000),
347
347
  }, agent.workingDir, agent.teamId);
348
348
  }
349
349