groove-dev 0.27.156 → 0.27.159

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 (91) 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/journalist.js +61 -16
  4. package/node_modules/@groove-dev/daemon/src/process.js +130 -2
  5. package/node_modules/@groove-dev/daemon/src/rotator.js +2 -1
  6. package/node_modules/@groove-dev/daemon/src/routes/files.js +28 -6
  7. package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +89 -71
  8. package/node_modules/@groove-dev/gui/dist/assets/index-Bij9o_dc.js +1020 -0
  9. package/node_modules/@groove-dev/gui/dist/assets/index-Dzofq3wS.css +1 -0
  10. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  11. package/node_modules/@groove-dev/gui/package.json +1 -2
  12. package/node_modules/@groove-dev/gui/src/app.css +2 -2
  13. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +8 -8
  14. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +2 -2
  15. package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +11 -2
  16. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +2 -2
  17. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +2 -2
  18. package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +1 -1
  19. package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +8 -1
  20. package/node_modules/@groove-dev/gui/src/components/network/activity-chart.jsx +4 -4
  21. package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +1 -1
  22. package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +18 -6
  23. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +122 -17
  24. package/node_modules/@groove-dev/gui/src/stores/groove.js +9 -1
  25. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +69 -38
  26. package/node_modules/@groove-dev/gui/src/views/memory.jsx +121 -49
  27. package/package.json +1 -1
  28. package/packages/cli/package.json +1 -1
  29. package/packages/daemon/package.json +1 -1
  30. package/packages/daemon/src/journalist.js +61 -16
  31. package/packages/daemon/src/process.js +130 -2
  32. package/packages/daemon/src/rotator.js +2 -1
  33. package/packages/daemon/src/routes/files.js +28 -6
  34. package/packages/daemon/src/tunnel-manager.js +89 -71
  35. package/packages/gui/dist/assets/index-Bij9o_dc.js +1020 -0
  36. package/packages/gui/dist/assets/index-Dzofq3wS.css +1 -0
  37. package/packages/gui/dist/index.html +2 -2
  38. package/packages/gui/package.json +1 -2
  39. package/packages/gui/src/app.css +2 -2
  40. package/packages/gui/src/components/agents/agent-feed.jsx +8 -8
  41. package/packages/gui/src/components/agents/diff-viewer.jsx +2 -2
  42. package/packages/gui/src/components/agents/workspace-mode.jsx +11 -2
  43. package/packages/gui/src/components/dashboard/cache-ring.jsx +2 -2
  44. package/packages/gui/src/components/dashboard/token-chart.jsx +2 -2
  45. package/packages/gui/src/components/editor/terminal.jsx +1 -1
  46. package/packages/gui/src/components/layout/welcome-splash.jsx +8 -1
  47. package/packages/gui/src/components/network/activity-chart.jsx +4 -4
  48. package/packages/gui/src/components/network/performance-dashboard.jsx +1 -1
  49. package/packages/gui/src/components/settings/quick-connect.jsx +18 -6
  50. package/packages/gui/src/components/settings/ssh-wizard.jsx +122 -17
  51. package/packages/gui/src/stores/groove.js +9 -1
  52. package/packages/gui/src/stores/slices/agents-slice.js +69 -38
  53. package/packages/gui/src/views/memory.jsx +121 -49
  54. package/ssh/error.png +0 -0
  55. package/node_modules/@fontsource-variable/jetbrains-mono/CHANGELOG.md +0 -2
  56. package/node_modules/@fontsource-variable/jetbrains-mono/LICENSE +0 -93
  57. package/node_modules/@fontsource-variable/jetbrains-mono/README.md +0 -48
  58. package/node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-cyrillic-ext-wght-italic.woff2 +0 -0
  59. package/node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-cyrillic-ext-wght-normal.woff2 +0 -0
  60. package/node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-cyrillic-wght-italic.woff2 +0 -0
  61. package/node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-cyrillic-wght-normal.woff2 +0 -0
  62. package/node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-greek-wght-italic.woff2 +0 -0
  63. package/node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-greek-wght-normal.woff2 +0 -0
  64. package/node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-latin-ext-wght-italic.woff2 +0 -0
  65. package/node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-latin-ext-wght-normal.woff2 +0 -0
  66. package/node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-latin-wght-italic.woff2 +0 -0
  67. package/node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-latin-wght-normal.woff2 +0 -0
  68. package/node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-vietnamese-wght-italic.woff2 +0 -0
  69. package/node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-vietnamese-wght-normal.woff2 +0 -0
  70. package/node_modules/@fontsource-variable/jetbrains-mono/index.css +0 -59
  71. package/node_modules/@fontsource-variable/jetbrains-mono/metadata.json +0 -29
  72. package/node_modules/@fontsource-variable/jetbrains-mono/package.json +0 -47
  73. package/node_modules/@fontsource-variable/jetbrains-mono/scss/metadata.scss +0 -46
  74. package/node_modules/@fontsource-variable/jetbrains-mono/scss/mixins.scss +0 -193
  75. package/node_modules/@fontsource-variable/jetbrains-mono/unicode.json +0 -8
  76. package/node_modules/@fontsource-variable/jetbrains-mono/wght-italic.css +0 -59
  77. package/node_modules/@fontsource-variable/jetbrains-mono/wght.css +0 -59
  78. package/node_modules/@groove-dev/gui/dist/assets/index-COQYX12F.js +0 -1015
  79. package/node_modules/@groove-dev/gui/dist/assets/index-Diw6wDPU.css +0 -1
  80. package/node_modules/@groove-dev/gui/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  81. package/node_modules/@groove-dev/gui/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  82. package/node_modules/@groove-dev/gui/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  83. package/node_modules/@groove-dev/gui/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  84. package/node_modules/@groove-dev/gui/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  85. package/packages/gui/dist/assets/index-COQYX12F.js +0 -1015
  86. package/packages/gui/dist/assets/index-Diw6wDPU.css +0 -1
  87. package/packages/gui/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  88. package/packages/gui/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  89. package/packages/gui/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  90. package/packages/gui/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  91. package/packages/gui/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
@@ -7,7 +7,7 @@ import { useGrooveStore } from '../../stores/groove';
7
7
  import { cn } from '../../lib/cn';
8
8
  import {
9
9
  FolderSearch, Check, X, AlertTriangle, Loader2,
10
- ExternalLink, Server, KeyRound, Settings, Plug,
10
+ ExternalLink, Server, KeyRound, Settings, Plug, Terminal, Copy, RefreshCw,
11
11
  } from 'lucide-react';
12
12
 
13
13
  const STEPS = [
@@ -110,6 +110,111 @@ function InfoCard({ icon: Icon, title, iconColor, children }) {
110
110
  );
111
111
  }
112
112
 
113
+ function CopyableCommand({ command }) {
114
+ const [copied, setCopied] = useState(false);
115
+ return (
116
+ <div className="flex items-center gap-1.5 group">
117
+ <code className="flex-1 text-2xs font-mono text-text-1 bg-surface-0 px-2.5 py-1.5 rounded-md border border-border-subtle truncate">
118
+ {command}
119
+ </code>
120
+ <button
121
+ onClick={() => { navigator.clipboard.writeText(command); setCopied(true); setTimeout(() => setCopied(false), 1500); }}
122
+ className="p-1.5 text-text-4 hover:text-text-1 cursor-pointer transition-colors flex-shrink-0"
123
+ title="Copy"
124
+ >
125
+ {copied ? <Check size={11} className="text-success" /> : <Copy size={11} />}
126
+ </button>
127
+ </div>
128
+ );
129
+ }
130
+
131
+ function SetupStatus({ testResult, user, host, sshPort, testLoading, onRetest }) {
132
+ const allGood = testResult.nodeInstalled && testResult.grooveInstalled && testResult.daemonRunning;
133
+ const needsNode = !testResult.nodeInstalled;
134
+ const needsGroove = testResult.nodeInstalled && !testResult.grooveInstalled;
135
+ const needsDaemon = testResult.grooveInstalled && !testResult.daemonRunning;
136
+ const sshTarget = `${user}@${host}${sshPort !== 22 ? ` -p ${sshPort}` : ''}`;
137
+
138
+ if (allGood) {
139
+ return (
140
+ <InfoCard icon={Check} title="Ready to Connect" iconColor="bg-success/10 text-success">
141
+ <div className="space-y-2.5">
142
+ <div className="flex items-center gap-2 text-2xs font-sans">
143
+ <StatusDot status="running" size="sm" />
144
+ <span className="text-text-1">Node.js {testResult.nodeVersion}</span>
145
+ </div>
146
+ <div className="flex items-center gap-2 text-2xs font-sans">
147
+ <StatusDot status="running" size="sm" />
148
+ <span className="text-text-1">Groove Installed{testResult.remoteVersion ? ` (v${testResult.remoteVersion})` : ''}</span>
149
+ </div>
150
+ <div className="flex items-center gap-2 text-2xs font-sans">
151
+ <StatusDot status="running" size="sm" />
152
+ <span className="text-text-1">Daemon Running</span>
153
+ </div>
154
+ </div>
155
+ </InfoCard>
156
+ );
157
+ }
158
+
159
+ return (
160
+ <div className="rounded-xl border border-warning/25 bg-gradient-to-br from-warning/[0.04] to-transparent px-5 py-4">
161
+ <div className="flex items-center justify-between mb-3">
162
+ <div className="flex items-center gap-2.5">
163
+ <div className="w-7 h-7 rounded-lg bg-warning/10 flex items-center justify-center flex-shrink-0">
164
+ <Terminal size={13} className="text-warning" />
165
+ </div>
166
+ <span className="text-sm font-semibold text-text-0 font-sans">Remote Setup Required</span>
167
+ </div>
168
+ <button
169
+ onClick={onRetest}
170
+ disabled={testLoading}
171
+ className="flex items-center gap-1 text-2xs text-text-3 hover:text-text-1 font-sans cursor-pointer transition-colors disabled:opacity-50"
172
+ >
173
+ <RefreshCw size={10} className={testLoading ? 'animate-spin' : ''} />
174
+ Re-test
175
+ </button>
176
+ </div>
177
+
178
+ <div className="space-y-2.5 mb-3">
179
+ <div className="flex items-center gap-2 text-2xs font-sans">
180
+ <StatusDot status={testResult.reachable ? 'running' : 'crashed'} size="sm" />
181
+ <span className="text-text-1">Reachable</span>
182
+ </div>
183
+ <div className="flex items-center gap-2 text-2xs font-sans">
184
+ <StatusDot status={testResult.nodeInstalled ? 'running' : 'stopped'} size="sm" />
185
+ <span className="text-text-1">Node.js{testResult.nodeVersion ? ` ${testResult.nodeVersion}` : ''}</span>
186
+ </div>
187
+ <div className="flex items-center gap-2 text-2xs font-sans">
188
+ <StatusDot status={testResult.grooveInstalled ? 'running' : 'stopped'} size="sm" />
189
+ <span className="text-text-1">Groove{testResult.remoteVersion ? ` v${testResult.remoteVersion}` : ''}</span>
190
+ </div>
191
+ <div className="flex items-center gap-2 text-2xs font-sans">
192
+ <StatusDot status={testResult.daemonRunning ? 'running' : 'stopped'} size="sm" />
193
+ <span className="text-text-1">Daemon</span>
194
+ </div>
195
+ </div>
196
+
197
+ <div className="border-t border-border-subtle pt-3 space-y-2">
198
+ <p className="text-2xs text-text-3 font-sans">SSH in and run{needsNode ? '' : needsGroove ? '' : ''}:</p>
199
+ <CopyableCommand command={`ssh ${sshTarget}`} />
200
+ {needsNode && (
201
+ <>
202
+ <CopyableCommand command='curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash' />
203
+ <CopyableCommand command="source ~/.bashrc && nvm install 20" />
204
+ </>
205
+ )}
206
+ {(needsNode || needsGroove) && (
207
+ <CopyableCommand command="npm i -g groove-dev" />
208
+ )}
209
+ {(needsNode || needsGroove || needsDaemon) && (
210
+ <CopyableCommand command="groove start" />
211
+ )}
212
+ <p className="text-2xs text-text-4 font-sans mt-1">Then click Re-test above.</p>
213
+ </div>
214
+ </div>
215
+ );
216
+ }
217
+
113
218
  export function SSHWizard({ server, onSave, onTest, onConnect, onCancel }) {
114
219
  const remoteHomedir = useGrooveStore((s) => s.remoteHomedir);
115
220
  const [step, setStep] = useState(0);
@@ -140,6 +245,13 @@ export function SSHWizard({ server, onSave, onTest, onConnect, onCancel }) {
140
245
  setAutoConnect(server.autoConnect || false);
141
246
  setCompletedSteps([0, 1]);
142
247
  setStep(2);
248
+ setTestResult(null);
249
+ setTestLoading(true);
250
+ onTest().then((result) => {
251
+ setTestResult(result);
252
+ }).catch((err) => {
253
+ setTestResult({ error: err.message || 'Test failed' });
254
+ }).finally(() => setTestLoading(false));
143
255
  } else {
144
256
  setName('');
145
257
  setHost('');
@@ -150,6 +262,7 @@ export function SSHWizard({ server, onSave, onTest, onConnect, onCancel }) {
150
262
  setAutoConnect(false);
151
263
  setCompletedSteps([]);
152
264
  setStep(0);
265
+ setTestResult(null);
153
266
  }
154
267
  }, [server]);
155
268
 
@@ -402,22 +515,14 @@ export function SSHWizard({ server, onSave, onTest, onConnect, onCancel }) {
402
515
  </FieldCard>
403
516
 
404
517
  {testResult && !testResult.error ? (
405
- <InfoCard icon={Check} title="Test Results" iconColor="bg-success/10 text-success">
406
- <div className="space-y-2.5">
407
- <div className="flex items-center gap-2 text-2xs font-sans">
408
- <StatusDot status={testResult.reachable ? 'running' : 'crashed'} size="sm" />
409
- <span className="text-text-1">Reachable</span>
410
- </div>
411
- <div className="flex items-center gap-2 text-2xs font-sans">
412
- <StatusDot status={testResult.grooveInstalled ? 'running' : 'stopped'} size="sm" />
413
- <span className="text-text-1">Groove Installed</span>
414
- </div>
415
- <div className="flex items-center gap-2 text-2xs font-sans">
416
- <StatusDot status={testResult.daemonRunning ? 'running' : 'stopped'} size="sm" />
417
- <span className="text-text-1">Daemon Running</span>
418
- </div>
419
- </div>
420
- </InfoCard>
518
+ <SetupStatus
519
+ testResult={testResult}
520
+ user={user}
521
+ host={host}
522
+ sshPort={sshPort}
523
+ testLoading={testLoading}
524
+ onRetest={handleTest}
525
+ />
421
526
  ) : (
422
527
  <InfoCard icon={Server} title={name || 'Server'}>
423
528
  <div className="space-y-2 text-2xs font-sans">
@@ -198,7 +198,11 @@ export const useGrooveStore = create((set, get) => ({
198
198
  }
199
199
  }
200
200
  for (const id of removed) delete timeline[id];
201
- set({ agents, tokenTimeline: timeline, hydrated: true });
201
+ const updates = { agents, tokenTimeline: timeline, hydrated: true };
202
+ if (removed.length > 0 && st.detailPanel?.type === 'agent' && removed.includes(st.detailPanel.agentId)) {
203
+ updates.detailPanel = null;
204
+ }
205
+ set(updates);
202
206
  break;
203
207
  }
204
208
 
@@ -366,6 +370,10 @@ export const useGrooveStore = create((set, get) => ({
366
370
  break;
367
371
  }
368
372
 
373
+ case 'recommended-team:ready':
374
+ if (!get().recommendedTeam) get().checkRecommendedTeam();
375
+ break;
376
+
369
377
  case 'phase2:spawned':
370
378
  get().addToast('info', `QC agent ${msg.name} auto-spawned`, 'Auditing phase 1 work');
371
379
  break;
@@ -169,10 +169,11 @@ export const createAgentsSlice = (set, get) => ({
169
169
  return data;
170
170
  }
171
171
 
172
- // CLI agent: was stopped + resumed/rotated — transfer state to new agent ID
172
+ // CLI agent: was stopped + resumed/rotated — transfer state to new agent ID.
173
+ // Only transfer if the rotation:complete WebSocket handler hasn't already done it.
173
174
  const newAgent = data;
174
175
  for (const key of ['chatHistory', 'activityLog', 'tokenTimeline']) {
175
- if (snapshot[key]?.length) {
176
+ if (snapshot[key]?.length && !get()[key]?.[newAgent.id]?.length) {
176
177
  set((s) => ({ [key]: { ...s[key], [newAgent.id]: [...snapshot[key]] } }));
177
178
  }
178
179
  }
@@ -380,6 +381,13 @@ export const createAgentsSlice = (set, get) => ({
380
381
  get().addChatMessage(agentId, 'system', text);
381
382
  };
382
383
 
384
+ const startThinking = () => {
385
+ set((s) => ({ thinkingAgents: new Set([...s.thinkingAgents, agentId]) }));
386
+ };
387
+ const stopThinking = () => {
388
+ set((s) => { const next = new Set(s.thinkingAgents); next.delete(agentId); return { thinkingAgents: next }; });
389
+ };
390
+
383
391
  try {
384
392
  switch (command) {
385
393
  case 'instruct': {
@@ -391,8 +399,11 @@ export const createAgentsSlice = (set, get) => ({
391
399
  if (tags.length === 0) { addSystemMsg('Usage: save #tag your message here'); return true; }
392
400
  const content = rest.replace(/#[\w/.-]+/g, '').trim();
393
401
  if (!content) { addSystemMsg('Usage: save #tag your message here'); return true; }
394
- await get().saveKeeperItem(tags[0], content);
395
- addSystemMsg(`Saved to #${tags[0]}`);
402
+ startThinking();
403
+ try {
404
+ await get().saveKeeperItem(tags[0], content);
405
+ addSystemMsg(`Saved to #${tags[0]}`);
406
+ } finally { stopThinking(); }
396
407
  return { passthrough: content };
397
408
  }
398
409
 
@@ -400,36 +411,48 @@ export const createAgentsSlice = (set, get) => ({
400
411
  if (tags.length === 0) { addSystemMsg('Usage: append #tag content to add'); return true; }
401
412
  const content = rest.replace(/#[\w/.-]+/g, '').trim();
402
413
  if (!content) { addSystemMsg('Usage: append #tag content to add'); return true; }
403
- await get().appendKeeperItem(tags[0], content);
404
- addSystemMsg(`Appended to #${tags[0]}`);
414
+ startThinking();
415
+ try {
416
+ await get().appendKeeperItem(tags[0], content);
417
+ addSystemMsg(`Appended to #${tags[0]}`);
418
+ } finally { stopThinking(); }
405
419
  return { passthrough: content };
406
420
  }
407
421
 
408
422
  case 'update': {
409
423
  if (tags.length === 0) { addSystemMsg('Usage: [update] #tag'); return true; }
410
424
  get().addChatMessage(agentId, 'user', message, false);
411
- const existing = await get().getKeeperItem(tags[0]);
412
- set({ keeperEditing: { tag: tags[0], content: existing?.content || '', isNew: !existing } });
425
+ startThinking();
426
+ try {
427
+ const existing = await get().getKeeperItem(tags[0]);
428
+ set({ keeperEditing: { tag: tags[0], content: existing?.content || '', isNew: !existing } });
429
+ } finally { stopThinking(); }
413
430
  return true;
414
431
  }
415
432
 
416
433
  case 'delete': {
417
434
  if (tags.length === 0) { addSystemMsg('Usage: [delete] #tag'); return true; }
418
435
  get().addChatMessage(agentId, 'user', message, false);
419
- await get().deleteKeeperItem(tags[0]);
420
- addSystemMsg(`Deleted #${tags[0]}`);
436
+ startThinking();
437
+ try {
438
+ await get().deleteKeeperItem(tags[0]);
439
+ addSystemMsg(`Deleted #${tags[0]}`);
440
+ } finally { stopThinking(); }
421
441
  return true;
422
442
  }
423
443
 
424
444
  case 'view': {
425
445
  if (tags.length === 0) { addSystemMsg('Usage: [view] #tag'); return true; }
426
446
  get().addChatMessage(agentId, 'user', message, false);
427
- const item = await get().getKeeperItem(tags[0]);
428
- if (item) {
429
- set({ keeperEditing: { tag: tags[0], content: item.content, isNew: false, readOnly: true } });
430
- } else {
431
- addSystemMsg(`#${tags[0]} not found`);
432
- }
447
+ startThinking();
448
+ try {
449
+ const item = await get().getKeeperItem(tags[0]);
450
+ if (item) {
451
+ set({ keeperEditing: { tag: tags[0], content: item.content, isNew: false, readOnly: true } });
452
+ } else {
453
+ addSystemMsg(`#${tags[0]} not found`);
454
+ }
455
+ } finally { stopThinking(); }
433
456
  return true;
434
457
  }
435
458
 
@@ -437,21 +460,22 @@ export const createAgentsSlice = (set, get) => ({
437
460
  if (tags.length === 0) { addSystemMsg('Usage: [read] #tag1 #tag2 ...'); return true; }
438
461
  const userText = rest.replace(/#[\w/.-]+/g, '').trim();
439
462
  get().addChatMessage(agentId, 'user', message, false);
440
- const readBrief = await api.post('/keeper/pull', { tags });
441
- if (readBrief?.brief) {
442
- const memoryBlock = `\n\n---\nContext from memories (${tags.map(t => '#' + t).join(', ')}):\n\n${readBrief.brief}`;
443
- set((s) => ({ thinkingAgents: new Set([...s.thinkingAgents, agentId]) }));
444
- await api.post(`/agents/${encodeURIComponent(agentId)}/instruct`, {
445
- message: userText ? `${userText}${memoryBlock}` : `Here is context from my tagged memories:\n\n${readBrief.brief}`,
446
- });
447
- addSystemMsg(`Sent ${tags.map(t => '#' + t).join(', ')} to agent`);
448
- } else {
449
- addSystemMsg(`No memories found for ${tags.map(t => '#' + t).join(', ')}`);
450
- if (userText) {
451
- set((s) => ({ thinkingAgents: new Set([...s.thinkingAgents, agentId]) }));
452
- await api.post(`/agents/${encodeURIComponent(agentId)}/instruct`, { message: userText });
463
+ startThinking();
464
+ try {
465
+ const readBrief = await api.post('/keeper/pull', { tags });
466
+ if (readBrief?.brief) {
467
+ const memoryBlock = `\n\n---\nContext from memories (${tags.map(t => '#' + t).join(', ')}):\n\n${readBrief.brief}`;
468
+ await api.post(`/agents/${encodeURIComponent(agentId)}/instruct`, {
469
+ message: userText ? `${userText}${memoryBlock}` : `Here is context from my tagged memories:\n\n${readBrief.brief}`,
470
+ });
471
+ addSystemMsg(`Sent ${tags.map(t => '#' + t).join(', ')} to agent`);
472
+ } else {
473
+ addSystemMsg(`No memories found for ${tags.map(t => '#' + t).join(', ')}`);
474
+ if (userText) {
475
+ await api.post(`/agents/${encodeURIComponent(agentId)}/instruct`, { message: userText });
476
+ }
453
477
  }
454
- }
478
+ } finally { stopThinking(); }
455
479
  return true;
456
480
  }
457
481
 
@@ -459,12 +483,15 @@ export const createAgentsSlice = (set, get) => ({
459
483
  if (tags.length === 0) { addSystemMsg('Usage: [doc] #tag'); return true; }
460
484
  get().addChatMessage(agentId, 'user', message, false);
461
485
  addSystemMsg(`Generating doc for #${tags[0]}...`);
462
- const history = get().chatHistory[agentId] || [];
463
- const result = await api.post('/keeper/doc', { tag: tags[0], chatHistory: history, agentId });
464
- if (result?.content) {
465
- addSystemMsg(`Doc #${tags[0]} generated (${result.size}B)`);
466
- set({ keeperEditing: { tag: tags[0], content: result.content, isNew: false } });
467
- }
486
+ startThinking();
487
+ try {
488
+ const history = get().chatHistory[agentId] || [];
489
+ const result = await api.post('/keeper/doc', { tag: tags[0], chatHistory: history, agentId });
490
+ if (result?.content) {
491
+ addSystemMsg(`Doc #${tags[0]} generated (${result.size}B)`);
492
+ set({ keeperEditing: { tag: tags[0], content: result.content, isNew: false } });
493
+ }
494
+ } finally { stopThinking(); }
468
495
  return true;
469
496
  }
470
497
 
@@ -473,12 +500,16 @@ export const createAgentsSlice = (set, get) => ({
473
500
  if (!linkMatch || tags.length === 0) { addSystemMsg('Usage: [link] #tag path/to/doc'); return true; }
474
501
  const docPath = linkMatch[2].trim();
475
502
  get().addChatMessage(agentId, 'user', message, false);
476
- await api.post('/keeper/link', { tag: tags[0], docPath });
477
- addSystemMsg(`Linked #${tags[0]} → ${docPath}`);
503
+ startThinking();
504
+ try {
505
+ await api.post('/keeper/link', { tag: tags[0], docPath });
506
+ addSystemMsg(`Linked #${tags[0]} → ${docPath}`);
507
+ } finally { stopThinking(); }
478
508
  return true;
479
509
  }
480
510
  }
481
511
  } catch (err) {
512
+ stopThinking();
482
513
  addSystemMsg(`Keeper error: ${err.message}`);
483
514
  return true;
484
515
  }