groove-dev 0.27.37 → 0.27.39

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 (35) hide show
  1. package/README.md +3 -3
  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 +78 -3
  5. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  6. package/node_modules/@groove-dev/daemon/src/lockmanager.js +44 -0
  7. package/node_modules/@groove-dev/daemon/src/memory.js +22 -5
  8. package/node_modules/@groove-dev/daemon/src/preview.js +243 -0
  9. package/node_modules/@groove-dev/daemon/src/process.js +145 -7
  10. package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +37 -1
  11. package/node_modules/@groove-dev/daemon/templates/knock-hook.cjs +44 -0
  12. package/node_modules/@groove-dev/gui/dist/assets/{index-Df4O6yJI.js → index-BRZ_leqO.js} +3 -3
  13. package/node_modules/@groove-dev/gui/dist/index.html +1 -1
  14. package/node_modules/@groove-dev/gui/package.json +1 -1
  15. package/node_modules/@groove-dev/gui/src/components/onboarding/setup-wizard.jsx +1 -1
  16. package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +12 -0
  17. package/node_modules/@groove-dev/gui/src/stores/groove.js +35 -2
  18. package/package.json +1 -1
  19. package/packages/cli/package.json +1 -1
  20. package/packages/daemon/package.json +1 -1
  21. package/packages/daemon/src/api.js +78 -3
  22. package/packages/daemon/src/index.js +3 -0
  23. package/packages/daemon/src/lockmanager.js +44 -0
  24. package/packages/daemon/src/memory.js +22 -5
  25. package/packages/daemon/src/preview.js +243 -0
  26. package/packages/daemon/src/process.js +145 -7
  27. package/packages/daemon/src/providers/claude-code.js +37 -1
  28. package/packages/daemon/templates/knock-hook.cjs +44 -0
  29. package/packages/gui/dist/assets/{index-Df4O6yJI.js → index-BRZ_leqO.js} +3 -3
  30. package/packages/gui/dist/index.html +1 -1
  31. package/packages/gui/package.json +1 -1
  32. package/packages/gui/src/components/onboarding/setup-wizard.jsx +1 -1
  33. package/packages/gui/src/components/ui/toast.jsx +12 -0
  34. package/packages/gui/src/stores/groove.js +35 -2
  35. package/plans/chat-persistence-refactor.md +0 -154
@@ -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-Df4O6yJI.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-BRZ_leqO.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.37",
3
+ "version": "0.27.39",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -19,7 +19,7 @@ const PROVIDERS = [
19
19
  id: 'claude-code',
20
20
  name: 'Claude Code',
21
21
  subtitle: 'by Anthropic',
22
- models: ['Opus 4.6', 'Sonnet 4.6', 'Haiku 4.5'],
22
+ models: ['Opus 4.7', 'Opus 4.6', 'Sonnet 4.6', 'Haiku 4.5'],
23
23
  authType: 'Subscription or API key',
24
24
  authModes: ['subscription', 'apikey'],
25
25
  recommended: true,
@@ -64,6 +64,18 @@ function ToastItem({ toast }) {
64
64
  <p className="text-xs text-text-3 font-sans mt-0.5">{toast.detail}</p>
65
65
  )}
66
66
  </div>
67
+ {toast.action?.url && (
68
+ <button
69
+ onClick={(e) => {
70
+ e.stopPropagation();
71
+ try { window.open(toast.action.url, '_blank', 'noopener'); } catch {}
72
+ removeToast(toast.id);
73
+ }}
74
+ className="text-xs font-medium text-accent hover:text-accent-hover bg-surface-5 hover:bg-surface-6 px-3 py-1.5 rounded transition-colors cursor-pointer flex-shrink-0 whitespace-nowrap"
75
+ >
76
+ {toast.action.label || 'Open'}
77
+ </button>
78
+ )}
67
79
  <button
68
80
  onClick={(e) => { e.stopPropagation(); removeToast(toast.id); }}
69
81
  className="p-1.5 text-text-4 hover:text-text-1 hover:bg-surface-5 rounded transition-colors cursor-pointer flex-shrink-0 z-10"
@@ -381,6 +381,39 @@ export const useGrooveStore = create((set, get) => ({
381
381
  get().addToast('info', `QC agent ${msg.name} auto-spawned`, 'Auditing phase 1 work');
382
382
  break;
383
383
 
384
+ case 'preview:ready':
385
+ get().addToast(
386
+ 'success',
387
+ 'Project ready to preview',
388
+ msg.url,
389
+ { label: 'View Site', url: msg.url },
390
+ );
391
+ break;
392
+
393
+ case 'preview:failed':
394
+ get().addToast(
395
+ 'warning',
396
+ 'Preview could not launch',
397
+ msg.reason ? String(msg.reason).slice(0, 200) : 'Unknown error',
398
+ );
399
+ break;
400
+
401
+ case 'preview:stopped':
402
+ break;
403
+
404
+ case 'agent:stalled': {
405
+ const name = msg.agentName || msg.agentId;
406
+ const secs = Math.round((msg.silentMs || 0) / 1000);
407
+ get().addToast('warning', `${name} may be stalled`, `No output for ${secs}s — API stream may be hung`);
408
+ break;
409
+ }
410
+
411
+ case 'knock:denied': {
412
+ const name = msg.agentName || msg.agentId;
413
+ get().addToast('warning', `${name} blocked`, `${msg.toolName} on ${msg.target} — ${msg.reason || 'scope conflict'}`);
414
+ break;
415
+ }
416
+
384
417
  case 'phase2:failed':
385
418
  get().addToast('error', `QC agent failed to spawn`, msg.error || 'Unknown error');
386
419
  break;
@@ -725,9 +758,9 @@ export const useGrooveStore = create((set, get) => ({
725
758
 
726
759
  // ── Toasts ────────────────────────────────────────────────
727
760
 
728
- addToast(type, message, detail) {
761
+ addToast(type, message, detail, action) {
729
762
  const id = ++toastCounter;
730
- set((s) => ({ toasts: [...s.toasts, { id, type, message, detail }] }));
763
+ set((s) => ({ toasts: [...s.toasts, { id, type, message, detail, action }] }));
731
764
  },
732
765
  removeToast(id) {
733
766
  set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) }));
@@ -1,154 +0,0 @@
1
- # Chat & Activity Persistence Refactor
2
-
3
- ## Context
4
-
5
- On 2026-04-14, node positions were failing to persist across team switches. Six
6
- attempted fixes in `packages/gui/src/views/agents.jsx` all failed because they
7
- targeted the wrong layer. On-screen debug logging (since removed) revealed the
8
- actual cause:
9
-
10
- ```
11
- SAVE.err: Failed to execute 'setItem' on 'Storage': Setting the value of
12
- 'groove:nodePositions:...' exceeded the quota.
13
- ```
14
-
15
- `localStorage` was full. `chatHistory` (100 msgs/agent) and `activityLog`
16
- (200 events/agent) were persisted on every agent stdout event and never pruned
17
- when agents were killed. Over time they consumed the browser's ~5–10 MB quota,
18
- so every new `setItem` silently failed — including node positions, panel
19
- widths, and anything else.
20
-
21
- ## What's already shipped
22
-
23
- Two stopgap fixes are in `main` so the app works today:
24
-
25
- - `packages/gui/src/stores/groove.js` — on every WebSocket `state` broadcast,
26
- prune `tokenTimeline`, `chatHistory`, and `activityLog` entries whose agent
27
- IDs are no longer in the live registry. Keeps localStorage bounded by live
28
- agents instead of all-agents-ever.
29
- - `packages/gui/src/views/agents.jsx` `savePositions()` — on quota error,
30
- drop `groove:chatHistory` + `groove:activityLog` and retry.
31
-
32
- These are sufficient for normal usage (5–20 concurrent agents with moderate
33
- output). They do not fix the underlying architectural problem.
34
-
35
- ## Remaining failure modes
36
-
37
- 1. **Single chatty agent hits quota alone.** Messages are count-capped (100)
38
- but not size-capped. One agent dumping 50 KB tool outputs × 100 = 5 MB,
39
- quota gone before any prune runs.
40
- 2. **Main-thread stalls.** Every stdout event does a synchronous
41
- `JSON.stringify` of the full `chatHistory` object + `setItem`. Scales O(n
42
- agents × m messages). Worse with more agents.
43
- 3. **Browser cache clears wipe history.** `localStorage` is per-origin,
44
- per-browser. Clearing the Electron webview or switching devices loses all
45
- conversation state.
46
- 4. **Multi-client divergence.** If/when we support web GUI + Electron hitting
47
- the same daemon, each has its own localStorage — they'll show different
48
- chat histories for the same agent.
49
- 5. **No querying.** Want "last message across all agents" or "search for
50
- 'error' in activity"? Not possible without deserializing everything.
51
-
52
- ## Proposed architecture
53
-
54
- Move chat, activity, and timeline to daemon-side disk persistence. Keep
55
- `localStorage` for tiny UI state only (node positions, viewport, panel widths,
56
- `activeTeamId`, `expandedNodes`).
57
-
58
- ### Daemon changes (`packages/daemon/src/`)
59
-
60
- New module `chatlog.js`:
61
- - Append-only JSONL files at `.groove/chat/<agentId>.jsonl` and
62
- `.groove/activity/<agentId>.jsonl`.
63
- - Each line: `{ t, from, text, type?, subtype? }` for chat;
64
- `{ t, text, type, subtype }` for activity.
65
- - Write on every message — fs.appendFile with a 0o600 perm. No in-memory
66
- buffer needed; kernel page cache handles batching.
67
- - Add rotation: if a file exceeds ~5 MB, rename to `.1.jsonl` and start fresh.
68
- Keep at most N=3 rotations. (Protects against a runaway agent eating disk.)
69
- - On agent kill/completion: leave files alone. GC cleans them later (existing
70
- `_gc()` in `index.js` already handles `GROOVE_AGENT_LOGS/` — extend to
71
- `.groove/chat/` and `.groove/activity/`.)
72
-
73
- New endpoints in `api.js`:
74
- - `GET /api/agents/:id/chat?cursor=<lineNumber>&limit=100` — returns
75
- `{ lines: [...], nextCursor, hasMore }`. Paginate from newest backwards
76
- (like tail -f) so the UI loads the last 100 first.
77
- - `GET /api/agents/:id/activity?cursor=...&limit=...` — same shape.
78
- - `DELETE /api/agents/:id/chat` — for future "clear history" UI.
79
-
80
- Integrate into `process.js` stdout handler:
81
- - When we currently emit `agent:stdout` / `agent:activity` over WebSocket,
82
- also call `chatlog.appendChat(agentId, entry)` or
83
- `chatlog.appendActivity(agentId, entry)`. WebSocket push stays for live
84
- streaming; JSONL is the durable tail.
85
-
86
- ### GUI changes (`packages/gui/src/`)
87
-
88
- In `stores/groove.js`:
89
- - Remove `chatHistory` and `activityLog` from the persisted keys. Kill the
90
- `persistJSON('groove:chatHistory', ...)` and `persistJSON('groove:activityLog', ...)`
91
- calls in the stdout handler.
92
- - Keep them as in-memory state only, scoped to agents the user has opened in
93
- this session. When user opens an agent panel, hydrate from
94
- `GET /api/agents/:id/chat` + `GET /api/agents/:id/activity`.
95
- - When user closes the panel or switches away, drop from memory after a grace
96
- period (e.g., 5 min LRU). WebSocket push still updates the in-memory copy
97
- for the active agent.
98
- - Optional: cap total in-memory messages (e.g., 1000 across all agents) with
99
- LRU eviction by agent.
100
-
101
- In `components/agents/agent-chat.jsx` (or wherever chat is rendered):
102
- - On mount: dispatch `fetchAgentHistory(agentId)` if not already loaded.
103
- - Show a "Load more" button or scroll-to-top infinite scroll for pagination.
104
-
105
- ### What stays in localStorage
106
-
107
- - `groove:nodePositions:<teamId>` — tiny, per-team, bounded by agent count.
108
- - `groove:teamViewports`, `groove:teamOrder`, `groove:activeTeamId`.
109
- - `groove:detailWidth`, `groove:terminalVisible`, `groove:terminalHeight`,
110
- `groove:editorSidebarWidth`, `groove:expandedNodes`.
111
- - `groove:onboardingComplete`, `groove:storeVersion`.
112
-
113
- None of these individually exceed a few KB. Total <100 KB in the worst case.
114
-
115
- ## Implementation order
116
-
117
- 1. Add `chatlog.js` + wire JSONL writes in `process.js`. Tests: spawn an
118
- agent, verify `.groove/chat/<id>.jsonl` grows; kill it, restart daemon,
119
- verify file still there.
120
- 2. Add GET endpoints in `api.js` + GUI fetch helpers. Test: hit endpoint with
121
- curl, verify cursor pagination.
122
- 3. Add in-memory + lazy-load path in `stores/groove.js`. Keep localStorage
123
- writes in parallel behind a feature flag (`groove:chatPersist=daemon`) so
124
- we can A/B before flipping default.
125
- 4. Flip default, remove localStorage writes for chatHistory/activityLog,
126
- remove the prune-stale-agents stopgap in the `state` handler.
127
- 5. Add GC for `.groove/chat/*.jsonl` and `.groove/activity/*.jsonl` in
128
- `index.js` `_gc()` (dead agents after N days).
129
-
130
- ## Estimated effort
131
-
132
- 2–3 hours focused work. Bulk is the GUI refactor — the daemon side is a thin
133
- append-only writer + two endpoints.
134
-
135
- ## Files touched (expected)
136
-
137
- - `packages/daemon/src/chatlog.js` (new)
138
- - `packages/daemon/src/process.js` (add appendChat/appendActivity calls)
139
- - `packages/daemon/src/api.js` (two new GET endpoints)
140
- - `packages/daemon/src/index.js` (import chatlog; extend `_gc()`)
141
- - `packages/gui/src/stores/groove.js` (remove persistJSON for chat/activity;
142
- add fetchAgentHistory action; drop prune-stale-agents stopgap from `state`
143
- handler)
144
- - `packages/gui/src/components/agents/agent-chat.jsx` (hydrate on mount)
145
- - `packages/gui/src/components/agents/agent-feed.jsx` (same for activity)
146
-
147
- ## When to do this
148
-
149
- Not urgent. The current prune+retry stopgap handles normal usage. Priority
150
- bumps if:
151
- - A user reports chat/activity loss that prune-on-broadcast didn't catch.
152
- - We add multi-client access (web GUI alongside Electron).
153
- - We add chat search or cross-agent analytics.
154
- - An agent with huge tool outputs single-handedly hits quota in production.