@ian2018cs/agenthub 0.1.77 → 0.1.79

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.
package/dist/index.html CHANGED
@@ -25,7 +25,7 @@
25
25
 
26
26
  <!-- Prevent zoom on iOS -->
27
27
  <meta name="format-detection" content="telephone=no" />
28
- <script type="module" crossorigin src="/assets/index-C9030Hra.js"></script>
28
+ <script type="module" crossorigin src="/assets/index-NaMmXkCt.js"></script>
29
29
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-Bv0Nkan8.js">
30
30
  <link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-sVRjxPVQ.js">
31
31
  <link rel="modulepreload" crossorigin href="/assets/vendor-utils-00TdZexr.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ian2018cs/agenthub",
3
- "version": "0.1.77",
3
+ "version": "0.1.79",
4
4
  "description": "A web-based UI for AI Agents",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
package/server/index.js CHANGED
@@ -447,6 +447,17 @@ app.use(express.static(path.join(__dirname, '../dist'), {
447
447
  // /api/config endpoint removed - no longer needed
448
448
  // Frontend now uses window.location for WebSocket URLs
449
449
 
450
+ // Public version endpoint - reads from package.json at runtime to avoid build-time lag
451
+ app.get('/api/version', (req, res) => {
452
+ try {
453
+ const pkgPath = path.join(__dirname, '..', 'package.json');
454
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
455
+ res.json({ version: pkg.version });
456
+ } catch (e) {
457
+ res.json({ version: 'unknown' });
458
+ }
459
+ });
460
+
450
461
  app.get('/api/projects', authenticateToken, async (req, res) => {
451
462
  try {
452
463
  const projects = await getProjects(req.user.uuid);
@@ -347,6 +347,47 @@ async function ensureSharedGitRepo(repoUrl, branch) {
347
347
  return targetPath;
348
348
  }
349
349
 
350
+ /**
351
+ * Try to move an existing local git clone directly into the shared location,
352
+ * avoiding a fresh network clone. Only succeeds when:
353
+ * - localPath is a real directory (not a symlink) with a .git folder
354
+ * - The shared target path does not yet exist
355
+ * Returns the shared path on success, or null if promotion is not possible.
356
+ */
357
+ async function tryPromoteLocalRepoToShared(localPath, repoUrl, branch) {
358
+ const parsed = parseGitUrl(repoUrl);
359
+ if (!parsed) return null;
360
+
361
+ const publicPaths = getPublicPaths();
362
+ const targetPath = path.join(publicPaths.gitRepoDir, parsed.owner, parsed.repo, branch);
363
+
364
+ // Shared repo already exists — nothing to promote
365
+ try {
366
+ await fs.access(targetPath);
367
+ return null;
368
+ } catch {}
369
+
370
+ // Source must be a real git directory (not a symlink, must have .git)
371
+ try {
372
+ const stat = await fs.lstat(localPath);
373
+ if (!stat.isDirectory() || stat.isSymbolicLink()) return null;
374
+ await fs.access(path.join(localPath, '.git'));
375
+ } catch {
376
+ return null;
377
+ }
378
+
379
+ try {
380
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
381
+ await fs.rename(localPath, targetPath);
382
+ console.log(`[GitRepo] Promoted local repo to shared: ${localPath} → ${targetPath}`);
383
+ return targetPath;
384
+ } catch (e) {
385
+ // rename may fail on cross-device links — fall back to normal clone
386
+ console.warn(`[GitRepo] Could not promote local repo (will clone instead): ${e.message}`);
387
+ return null;
388
+ }
389
+ }
390
+
350
391
  /**
351
392
  * Create a symlink from a project subdirectory to a shared git repo.
352
393
  * Removes existing directory or symlink at the target location.
@@ -513,6 +554,7 @@ async function scanClaudeMdRefs(projectPath) {
513
554
  if (!ref) continue;
514
555
  if (/^https?:\/\/|^mailto:|^\/\//.test(ref)) continue; // skip absolute URLs
515
556
  if (path.isAbsolute(ref)) continue; // skip absolute paths
557
+ if (ref.endsWith('/')) continue; // skip directory references like [src/utils/]
516
558
  if (seen.has(ref)) continue;
517
559
  seen.add(ref);
518
560
 
@@ -1530,8 +1572,22 @@ router.post('/submissions/:id/approve', async (req, res) => {
1530
1572
  for (const gitRepo of yamlGitRepos) {
1531
1573
  if (!gitRepo.name || !gitRepo.repo) continue;
1532
1574
  try {
1533
- // Ensure shared git repo exists
1534
- const sharedPath = await ensureSharedGitRepo(gitRepo.repo, gitRepo.branch || 'main');
1575
+ // Try to promote the submitter's existing local clone to shared location first
1576
+ // (avoids a full network re-clone if the user already has the repo cloned)
1577
+ let sharedPath = null;
1578
+ if (submitterUuid) {
1579
+ const submitterProjectDir = path.join(getUserPaths(submitterUuid).projectsDir, submission.agent_name);
1580
+ const localRepoPath = path.join(submitterProjectDir, gitRepo.name);
1581
+ sharedPath = await tryPromoteLocalRepoToShared(localRepoPath, gitRepo.repo, gitRepo.branch || 'main');
1582
+ if (sharedPath) {
1583
+ console.log(`[AgentApprove] Promoted local repo "${gitRepo.name}" to shared (no re-clone needed)`);
1584
+ }
1585
+ }
1586
+
1587
+ // Fall back to normal clone/update if promotion was not possible
1588
+ if (!sharedPath) {
1589
+ sharedPath = await ensureSharedGitRepo(gitRepo.repo, gitRepo.branch || 'main');
1590
+ }
1535
1591
  console.log(`[AgentApprove] Ensured shared git repo "${gitRepo.name}" at ${sharedPath}`);
1536
1592
 
1537
1593
  // Replace submitter's original subdirectory with symlink to shared repo