groove-dev 0.27.131 → 0.27.134

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 (70) hide show
  1. package/AGENT_ORCHESTRATION.md +375 -0
  2. package/moe-training/shared/envelope-schema.js +1 -1
  3. package/node_modules/@groove-dev/cli/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/package.json +1 -1
  5. package/node_modules/@groove-dev/daemon/src/index.js +3 -1
  6. package/node_modules/@groove-dev/daemon/src/introducer.js +48 -4
  7. package/node_modules/@groove-dev/daemon/src/llama-server.js +4 -4
  8. package/node_modules/@groove-dev/daemon/src/model-lab.js +8 -0
  9. package/node_modules/@groove-dev/daemon/src/preview.js +85 -58
  10. package/node_modules/@groove-dev/daemon/src/process.js +9 -0
  11. package/node_modules/@groove-dev/daemon/src/terminal-pty.js +24 -14
  12. package/node_modules/@groove-dev/daemon/src/validate.js +0 -4
  13. package/{packages/gui/dist/assets/codemirror-CFF1Lrnz.js → node_modules/@groove-dev/gui/dist/assets/codemirror-DRQdprYi.js} +11 -11
  14. package/node_modules/@groove-dev/gui/dist/assets/index-BgQL4bNl.css +1 -0
  15. package/{packages/gui/dist/assets/index-BiB9oY9U.js → node_modules/@groove-dev/gui/dist/assets/index-Dozp69tK.js} +1721 -1721
  16. package/node_modules/@groove-dev/gui/dist/index.html +3 -3
  17. package/node_modules/@groove-dev/gui/package.json +1 -1
  18. package/node_modules/@groove-dev/gui/src/app.css +6 -6
  19. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +12 -1
  20. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +15 -5
  21. package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +6 -6
  22. package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +11 -9
  23. package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +26 -3
  24. package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +6 -6
  25. package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +20 -8
  26. package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +10 -1
  27. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +4 -4
  28. package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +17 -3
  29. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +2 -4
  30. package/node_modules/@groove-dev/gui/src/components/preview/preview-toolbar.jsx +8 -6
  31. package/node_modules/@groove-dev/gui/src/stores/groove.js +82 -15
  32. package/node_modules/@groove-dev/gui/src/views/agents.jsx +82 -74
  33. package/node_modules/@groove-dev/gui/src/views/editor.jsx +11 -9
  34. package/node_modules/moe-training/shared/envelope-schema.js +1 -1
  35. package/package.json +1 -1
  36. package/packages/cli/package.json +1 -1
  37. package/packages/daemon/package.json +1 -1
  38. package/packages/daemon/src/index.js +3 -1
  39. package/packages/daemon/src/introducer.js +48 -4
  40. package/packages/daemon/src/llama-server.js +4 -4
  41. package/packages/daemon/src/model-lab.js +8 -0
  42. package/packages/daemon/src/preview.js +85 -58
  43. package/packages/daemon/src/process.js +9 -0
  44. package/packages/daemon/src/terminal-pty.js +24 -14
  45. package/packages/daemon/src/validate.js +0 -4
  46. package/{node_modules/@groove-dev/gui/dist/assets/codemirror-CFF1Lrnz.js → packages/gui/dist/assets/codemirror-DRQdprYi.js} +11 -11
  47. package/packages/gui/dist/assets/index-BgQL4bNl.css +1 -0
  48. package/{node_modules/@groove-dev/gui/dist/assets/index-BiB9oY9U.js → packages/gui/dist/assets/index-Dozp69tK.js} +1721 -1721
  49. package/packages/gui/dist/index.html +3 -3
  50. package/packages/gui/package.json +1 -1
  51. package/packages/gui/src/app.css +6 -6
  52. package/packages/gui/src/components/agents/agent-chat.jsx +12 -1
  53. package/packages/gui/src/components/agents/agent-feed.jsx +15 -5
  54. package/packages/gui/src/components/agents/agent-file-tree.jsx +6 -6
  55. package/packages/gui/src/components/agents/workspace-mode.jsx +11 -9
  56. package/packages/gui/src/components/editor/code-editor.jsx +26 -3
  57. package/packages/gui/src/components/editor/file-tree.jsx +6 -6
  58. package/packages/gui/src/components/editor/terminal.jsx +20 -8
  59. package/packages/gui/src/components/lab/chat-playground.jsx +10 -1
  60. package/packages/gui/src/components/lab/lab-assistant.jsx +4 -4
  61. package/packages/gui/src/components/lab/system-prompt-editor.jsx +17 -3
  62. package/packages/gui/src/components/layout/terminal-panel.jsx +2 -4
  63. package/packages/gui/src/components/preview/preview-toolbar.jsx +8 -6
  64. package/packages/gui/src/stores/groove.js +82 -15
  65. package/packages/gui/src/views/agents.jsx +82 -74
  66. package/packages/gui/src/views/editor.jsx +11 -9
  67. package/CENTRAL_COMMAND_REBUILD.md +0 -689
  68. package/MERKLE_TREE_ARCHITECTURE.md +0 -354
  69. package/node_modules/@groove-dev/gui/dist/assets/index-CeyDFVub.css +0 -1
  70. package/packages/gui/dist/assets/index-CeyDFVub.css +0 -1
@@ -116,67 +116,94 @@ export class PreviewService {
116
116
  return { launched: false, reason: installResult.reason };
117
117
  }
118
118
 
119
- let result;
120
- if (preview.kind === 'static-html') {
121
- if (this._needsBuild(baseDir, preview)) {
122
- const buildResult = this._runBuild(teamId, baseDir);
123
- if (buildResult?.failed) {
124
- this.daemon.audit?.log('preview.failed', { teamId, reason: buildResult.reason });
125
- return { launched: false, reason: buildResult.reason };
126
- }
127
- const distDir = resolve(baseDir, 'dist');
128
- if (existsSync(distDir)) {
129
- result = await this._launchStatic(teamId, distDir, { ...preview, openPath: preview.openPath || 'index.html' });
130
- } else {
131
- result = await this._launchStatic(teamId, baseDir, preview);
132
- }
133
- } else {
134
- const distDir = resolve(baseDir, 'dist');
135
- const openFile = (preview.openPath || 'index.html').replace(/^\/+/, '');
136
- if (existsSync(resolve(distDir, openFile))) {
137
- result = await this._launchStatic(teamId, distDir, { ...preview, openPath: openFile });
138
- } else {
139
- result = await this._launchStatic(teamId, baseDir, preview);
119
+ // Static-first strategy: always try build+static before dev-server.
120
+ // Static serving is near-100% reliable — no port conflicts, no proxy
121
+ // issues, no HMR quirks, no URL rewriting. Dev servers fail constantly
122
+ // in AI-generated projects (wrong ports, missing scripts, relative path
123
+ // breakage through the proxy). Only fall back to dev-server when there
124
+ // is no build output at all.
125
+
126
+ const openFile = (preview.openPath || 'index.html').replace(/^\/+/, '');
127
+ const pkgPath = resolve(baseDir, 'package.json');
128
+ let hasBuildScript = false;
129
+ let isFrontendProject = false;
130
+ try {
131
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
132
+ hasBuildScript = !!pkg.scripts?.build;
133
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
134
+ isFrontendProject = !!(allDeps?.react || allDeps?.vue || allDeps?.svelte || allDeps?.vite
135
+ || allDeps?.['@angular/core'] || allDeps?.next || allDeps?.nuxt || allDeps?.astro);
136
+ } catch { /* no package.json or malformed */ }
137
+
138
+ // Also detect frontend by config files
139
+ if (!isFrontendProject) {
140
+ const frontendConfigs = ['vite.config', 'next.config', 'webpack.config', 'svelte.config', 'astro.config', 'nuxt.config', 'angular.json'];
141
+ for (const cfg of frontendConfigs) {
142
+ if (['.js', '.ts', '.mjs', '.cjs', ''].some((ext) => existsSync(resolve(baseDir, cfg + ext)))) {
143
+ isFrontendProject = true;
144
+ break;
140
145
  }
141
146
  }
142
- } else if (preview.kind === 'dev-server') {
143
- if (this._needsPreBuild(baseDir)) {
144
- const preBuild = this._runBuild(teamId, baseDir);
145
- if (preBuild?.failed) {
146
- this.daemon.audit?.log('preview.prebuild-failed', { teamId, reason: preBuild.reason });
147
+ }
148
+
149
+ // Common build output directories
150
+ const OUTPUT_DIRS = ['dist', 'build', 'out', '.next/standalone', 'public'];
151
+
152
+ let result;
153
+
154
+ // Strategy 1: Build and serve static (only for frontend projects — skip for backends)
155
+ if (hasBuildScript && (isFrontendProject || preview.kind === 'static-html')) {
156
+ const buildResult = this._runBuild(teamId, baseDir);
157
+ if (!buildResult?.failed) {
158
+ result = this._findAndServeStatic(teamId, baseDir, OUTPUT_DIRS, openFile, preview);
159
+ if (result && (await result).launched) {
160
+ result = await result;
161
+ this.daemon.audit?.log('preview.launched', { teamId, url: result.url, kind: result.kind, baseDir, strategy: 'build-static' });
162
+ return result;
147
163
  }
148
164
  }
165
+ }
166
+
167
+ // Strategy 2: Serve existing static files (pre-built, plain HTML, no-build projects)
168
+ result = this._findAndServeStatic(teamId, baseDir, OUTPUT_DIRS, openFile, preview);
169
+ if (result && (await result).launched) {
170
+ result = await result;
171
+ this.daemon.audit?.log('preview.launched', { teamId, url: result.url, kind: result.kind, baseDir, strategy: 'serve-existing' });
172
+ return result;
173
+ }
174
+
175
+ // Strategy 3: Dev server (backends, SSR frameworks, projects with no static output)
176
+ if (preview.kind === 'dev-server') {
149
177
  result = await this._launchDevServer(teamId, baseDir, preview);
150
- // Fallback: if dev-server failed, try building and serving statically
151
- if (!result.launched) {
152
- const pkgPath = resolve(baseDir, 'package.json');
153
- if (existsSync(pkgPath)) {
154
- try {
155
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
156
- if (pkg.scripts?.build) {
157
- const buildResult = this._runBuild(teamId, baseDir);
158
- if (!buildResult?.failed) {
159
- const distDir = resolve(baseDir, 'dist');
160
- if (existsSync(resolve(distDir, 'index.html'))) {
161
- result = await this._launchStatic(teamId, distDir, { ...preview, openPath: 'index.html' });
162
- }
163
- }
164
- }
165
- } catch { /* fallback failed, keep original error */ }
166
- }
178
+ if (result?.launched) {
179
+ this.daemon.audit?.log('preview.launched', { teamId, url: result.url, kind: result.kind, baseDir, strategy: 'dev-server' });
180
+ return result;
167
181
  }
168
- } else {
169
- result = { launched: false, reason: `unknown_kind: ${preview.kind}` };
170
182
  }
171
183
 
172
- if (result.launched) {
173
- this.daemon.audit?.log('preview.launched', { teamId, url: result.url, kind: result.kind, baseDir });
174
- } else {
175
- this.daemon.audit?.log('preview.failed', { teamId, reason: result.reason, baseDir });
176
- }
184
+ result = result || { launched: false, reason: 'all_strategies_failed' };
185
+ this.daemon.audit?.log('preview.failed', { teamId, reason: result.reason, baseDir });
177
186
  return result;
178
187
  }
179
188
 
189
+ _findAndServeStatic(teamId, baseDir, outputDirs, openFile, preview) {
190
+ // Check output directories first, then baseDir itself
191
+ for (const dir of outputDirs) {
192
+ const fullDir = resolve(baseDir, dir);
193
+ if (existsSync(resolve(fullDir, openFile))) {
194
+ return this._launchStatic(teamId, fullDir, { ...preview, openPath: openFile });
195
+ }
196
+ if (openFile !== 'index.html' && existsSync(resolve(fullDir, 'index.html'))) {
197
+ return this._launchStatic(teamId, fullDir, { ...preview, openPath: 'index.html' });
198
+ }
199
+ }
200
+ // Finally check baseDir itself (plain HTML projects)
201
+ if (existsSync(resolve(baseDir, openFile))) {
202
+ return this._launchStatic(teamId, baseDir, { ...preview, openPath: openFile });
203
+ }
204
+ return Promise.resolve({ launched: false, reason: 'no_static_entry' });
205
+ }
206
+
180
207
  _ensureDependencies(teamId, baseDir) {
181
208
  const pkgPath = resolve(baseDir, 'package.json');
182
209
  const nodeModules = resolve(baseDir, 'node_modules');
@@ -262,18 +289,18 @@ export class PreviewService {
262
289
  if (!pkg.scripts?.build) return { failed: true, reason: 'no build script' };
263
290
  } catch { return { failed: true, reason: 'malformed package.json' }; }
264
291
 
265
- const isVite = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']
292
+ const isVite = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs', 'vite.config.cjs']
266
293
  .some((f) => existsSync(resolve(baseDir, f)));
267
294
 
268
295
  let command = 'npm run build';
269
- const buildScript = (pkg.scripts.build || '').trim();
270
-
271
- if (isVite && /^(tsc\s*&&\s*)?vite\s+build\s*$/.test(buildScript)) {
272
- command = `npm run build -- --base=./`;
273
- }
274
-
275
296
  const env = { ...process.env };
276
- if (isVite && command === 'npm run build') {
297
+
298
+ // Force relative base paths for Vite — absolute paths break in iframe
299
+ if (isVite) {
300
+ const buildScript = (pkg.scripts.build || '').trim();
301
+ if (/\bvite\s+build\b/.test(buildScript)) {
302
+ command = `npm run build -- --base=./`;
303
+ }
277
304
  env.VITE_BASE = './';
278
305
  }
279
306
 
@@ -266,6 +266,13 @@ For NEW projects (team creation only):
266
266
  Include "projectDir" with a short kebab-case directory name. All agents spawn inside it.
267
267
  For EXISTING codebases: Do NOT include "projectDir".
268
268
 
269
+ DIRECTORY RULES:
270
+ Your team directory (where you are spawned) is for ORCHESTRATION ONLY — .groove/, AGENTS_REGISTRY.md, coordination files. Do NOT instruct agents to create source code, projects, or build artifacts inside the team directory.
271
+
272
+ All source code, features, and projects MUST be created in the PARENT directory (one level up from the team dir). Look at the parent directory structure provided in your context to understand existing directories, naming conventions, and paths. Reference files and directories relative to the parent using "../" prefix in prompts (e.g., "../demo/", "../training/src/").
273
+
274
+ Scope patterns CAN use "../" to reference directories outside the team dir. For multi-directory projects, use patterns like "../demo/**", "../training/**". If the project spans many directories or you are unsure of boundaries, use empty scope ([]) to give agents unrestricted access.
275
+
269
276
  MANDATORY RULES:
270
277
 
271
278
  1. For team creation: the LAST entry MUST be { "role": "fullstack", "phase": 2 } — the QC agent.
@@ -284,6 +291,8 @@ MANDATORY RULES:
284
291
 
285
292
  7. In MODE 2, be FAST. Read only the files needed to understand the specific task. Do not re-analyze the full codebase.
286
293
 
294
+ 8. NEVER build source code or project files inside the team directory. The team dir is ephemeral — the user may delete it at any time. All persistent work goes in the parent directory.
295
+
287
296
  IMPORTANT: Do not use markdown formatting like ** or ### in your output. Write in plain text with clean formatting. Use line breaks, dashes, and indentation for structure.
288
297
 
289
298
  `,
@@ -37,6 +37,10 @@ signal.signal(signal.SIGWINCH, resize)
37
37
  flags = fcntl.fcntl(0, fcntl.F_GETFL)
38
38
  fcntl.fcntl(0, fcntl.F_SETFL, flags | os.O_NONBLOCK)
39
39
 
40
+ ESC_START = b'\\x1b]7;'
41
+ ESC_END = b'\\x07'
42
+ stdin_buf = b''
43
+
40
44
  try:
41
45
  while True:
42
46
  rlist = select.select([0, master], [], [], 0.05)[0]
@@ -44,22 +48,28 @@ try:
44
48
  try:
45
49
  data = os.read(0, 4096)
46
50
  if not data: break
47
- # Resize command: ESC ] 7 ; <rows> ; <cols> BEL
48
- if b'\\x1b]7;' in data:
49
- idx = data.index(b'\\x1b]7;')
50
- end = data.index(b'\\x07', idx)
51
- params = data[idx+4:end].decode().split(';')
52
- if len(params) == 2:
53
- r, c = int(params[0]), int(params[1])
54
- fcntl.ioctl(master, termios.TIOCSWINSZ, struct.pack('HHHH', r, c, 0, 0))
55
- os.kill(pid, signal.SIGWINCH)
56
- rest = data[:idx] + data[end+1:]
57
- if rest:
58
- os.write(master, rest)
59
- else:
60
- os.write(master, data)
51
+ stdin_buf += data
61
52
  except OSError as e:
62
53
  if e.errno != errno.EAGAIN: break
54
+ # Process buffered stdin — extract resize commands, forward the rest
55
+ while stdin_buf:
56
+ esc_pos = stdin_buf.find(ESC_START)
57
+ if esc_pos == -1:
58
+ os.write(master, stdin_buf)
59
+ stdin_buf = b''
60
+ break
61
+ if esc_pos > 0:
62
+ os.write(master, stdin_buf[:esc_pos])
63
+ stdin_buf = stdin_buf[esc_pos:]
64
+ bel_pos = stdin_buf.find(ESC_END)
65
+ if bel_pos == -1:
66
+ break
67
+ params = stdin_buf[4:bel_pos].decode().split(';')
68
+ if len(params) == 2:
69
+ r, c = int(params[0]), int(params[1])
70
+ fcntl.ioctl(master, termios.TIOCSWINSZ, struct.pack('HHHH', r, c, 0, 0))
71
+ os.kill(pid, signal.SIGWINCH)
72
+ stdin_buf = stdin_buf[bel_pos+1:]
63
73
  if master in rlist:
64
74
  try:
65
75
  data = os.read(master, 4096)
@@ -146,10 +146,6 @@ export function validateScopePattern(pattern) {
146
146
  if (pattern.startsWith('/')) {
147
147
  throw new Error('Scope patterns cannot be absolute paths');
148
148
  }
149
- // No path traversal
150
- if (pattern.includes('..')) {
151
- throw new Error('Scope patterns cannot contain path traversal (..)');
152
- }
153
149
  // No null bytes
154
150
  if (pattern.includes('\0')) {
155
151
  throw new Error('Scope pattern contains invalid characters');