groove-dev 0.26.13 → 0.26.15

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.
@@ -891,6 +891,15 @@ Keep responses concise. Help them think, don't lecture them about the system the
891
891
  }
892
892
  });
893
893
 
894
+ app.post('/api/skills/:id/update', async (req, res) => {
895
+ try {
896
+ const result = await daemon.skills.update(req.params.id);
897
+ res.json(result);
898
+ } catch (err) {
899
+ res.status(400).json({ error: err.message });
900
+ }
901
+ });
902
+
894
903
  app.post('/api/skills/:id/rate', async (req, res) => {
895
904
  try {
896
905
  const rating = parseInt(req.body?.rating, 10);
@@ -296,8 +296,10 @@ IMPORTANT: No task has been assigned yet. You MUST wait for the user to tell you
296
296
 
297
297
  // Apply PM review instructions for Auto permission mode
298
298
  // Agents call the PM endpoint before risky operations for AI review
299
+ // Skip for sandboxed providers (Codex) — localhost is unreachable from their sandbox
299
300
  const permission = config.permission || 'full';
300
- if (permission === 'auto' || permission === 'supervised') {
301
+ const sandboxedProviders = ['codex'];
302
+ if ((permission === 'auto' || permission === 'supervised') && !sandboxedProviders.includes(providerName)) {
301
303
  const port = this.daemon.port || 31415;
302
304
  const pmPrompt = `## PM Review (Auto Mode)
303
305
 
@@ -64,7 +64,7 @@ export class SkillStore {
64
64
  });
65
65
  if (res.ok) {
66
66
  user = await res.json();
67
- console.log('[Groove:Auth] User object from API:', JSON.stringify(user));
67
+ console.log(`[Groove:Auth] Authenticated: ${user.username || user.id}`);
68
68
  } else {
69
69
  return null; // Invalid token
70
70
  }
@@ -329,6 +329,51 @@ export class SkillStore {
329
329
  return { id: skillId, installed: false };
330
330
  }
331
331
 
332
+ /**
333
+ * Update an installed skill — re-downloads latest content from the API.
334
+ */
335
+ async update(skillId) {
336
+ if (!this._isInstalled(skillId)) throw new Error(`Skill not installed: ${skillId}`);
337
+
338
+ let entry = this.registry.find((s) => s.id === skillId);
339
+ if (!entry) {
340
+ await this._refreshRegistry();
341
+ entry = this.registry.find((s) => s.id === skillId);
342
+ }
343
+
344
+ let content = null;
345
+
346
+ // Try live API content endpoint
347
+ try {
348
+ const res = await this._fetch(`${SKILLS_API}/skills/${skillId}/content`, { timeout: 10000 });
349
+ if (res.ok) {
350
+ const data = await res.json();
351
+ content = data.content;
352
+ }
353
+ } catch { /* fall through */ }
354
+
355
+ // Fall back to contentUrl
356
+ if (!content && entry?.contentUrl) {
357
+ try {
358
+ const res = await this._fetch(entry.contentUrl, { timeout: 10000 });
359
+ if (res.ok) content = await res.text();
360
+ } catch { /* fall through */ }
361
+ }
362
+
363
+ if (!content) {
364
+ throw new Error('Could not download latest version. Check your internet connection.');
365
+ }
366
+
367
+ // Overwrite local content
368
+ const skillDir = resolve(this.skillsDir, skillId);
369
+ mkdirSync(skillDir, { recursive: true });
370
+ writeFileSync(resolve(skillDir, 'SKILL.md'), content);
371
+
372
+ this.daemon.audit.log('skill.update', { id: skillId, name: entry?.name || skillId });
373
+
374
+ return { id: skillId, name: entry?.name || skillId, installed: true, updated: true };
375
+ }
376
+
332
377
  /**
333
378
  * Rate a skill. Proxies to the skills server.
334
379
  */
@@ -2,6 +2,7 @@
2
2
  // FSL-1.1-Apache-2.0 — see LICENSE
3
3
 
4
4
  import { spawn, execFileSync } from 'child_process';
5
+ import { randomUUID } from 'crypto';
5
6
  import { existsSync } from 'fs';
6
7
 
7
8
  // Python helper that creates a real PTY and relays I/O through stdin/stdout pipes.
@@ -81,12 +82,11 @@ export class TerminalManager {
81
82
  constructor(daemon) {
82
83
  this.daemon = daemon;
83
84
  this.sessions = new Map();
84
- this.counter = 0;
85
85
  this._python = this._findPython();
86
86
  }
87
87
 
88
88
  spawn(ws, options = {}) {
89
- const id = `term-${++this.counter}`;
89
+ const id = `term-${randomUUID()}`;
90
90
  const shell = this._detectShell();
91
91
  const cwd = options.cwd || this.daemon.projectDir;
92
92
  const cols = options.cols || 120;