clay-server 3.2.1 → 3.2.2-beta.2

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.
@@ -6,12 +6,16 @@
6
6
 
7
7
  var fs = require("fs");
8
8
  var { scanWorktrees, isWorktree } = require("./worktree");
9
+ var { daemonSync } = require("./daemon-sync");
9
10
 
10
11
  // --- Worktree tracking state ---
11
12
  var worktreeRegistry = {}; // parentSlug -> [wtSlug, ...]
12
- var worktreeTimers = {}; // parentSlug -> intervalId
13
13
  var worktreeScanning = {}; // parentSlug -> boolean (mutex)
14
14
 
15
+ function getWorktreeSyncKey(parentSlug) {
16
+ return "worktrees:" + parentSlug;
17
+ }
18
+
15
19
  function isWorktreeSlug(slug) {
16
20
  return slug.indexOf("--") !== -1;
17
21
  }
@@ -42,11 +46,9 @@ function scanAndRegisterWorktrees(relay, parentPath, parentSlug, parentIcon, par
42
46
  worktreeRegistry[parentSlug].push(wtSlug);
43
47
  console.log("[daemon] Registered worktree:", wtSlug, "->", wt.path, wt.accessible ? "(accessible)" : "(inaccessible)");
44
48
  }
45
- if (!worktreeTimers[parentSlug]) {
46
- worktreeTimers[parentSlug] = setInterval(function () {
47
- rescanWorktrees(relay, parentPath, parentSlug, parentIcon, parentOwnerId);
48
- }, 10000);
49
- }
49
+ daemonSync.register(getWorktreeSyncKey(parentSlug), function () {
50
+ rescanWorktrees(relay, parentPath, parentSlug, parentIcon, parentOwnerId);
51
+ });
50
52
  }
51
53
 
52
54
  /**
@@ -114,10 +116,7 @@ function cleanupWorktreesForParent(relay, parentSlug) {
114
116
  console.log("[daemon] Cascade removed worktree:", wtSlugs[i]);
115
117
  }
116
118
  delete worktreeRegistry[parentSlug];
117
- if (worktreeTimers[parentSlug]) {
118
- clearInterval(worktreeTimers[parentSlug]);
119
- delete worktreeTimers[parentSlug];
120
- }
119
+ daemonSync.unregister(getWorktreeSyncKey(parentSlug));
121
120
  }
122
121
 
123
122
  /**
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Shared daemon synchronization loop.
3
+ *
4
+ * Periodic daemon work registers here so every project shares one timer.
5
+ */
6
+
7
+ function attachDaemonSync(ctx) {
8
+ var intervalMs = ctx.intervalMs || 10000;
9
+ var scheduleInterval = ctx.setInterval || setInterval;
10
+ var cancelInterval = ctx.clearInterval || clearInterval;
11
+ var onError = ctx.onError || function (key, err) {
12
+ console.error("[daemon] Sync task failed:", key, err);
13
+ };
14
+ var tasks = {};
15
+ var timer = null;
16
+
17
+ function finishTask(task) {
18
+ task.running = false;
19
+ }
20
+
21
+ function runTask(key, task) {
22
+ if (task.running) return;
23
+ task.running = true;
24
+ var result;
25
+ try {
26
+ result = task.run();
27
+ } catch (err) {
28
+ finishTask(task);
29
+ onError(key, err);
30
+ return;
31
+ }
32
+ if (result && typeof result.then === "function") {
33
+ Promise.resolve(result).then(function () {
34
+ finishTask(task);
35
+ }, function (err) {
36
+ finishTask(task);
37
+ onError(key, err);
38
+ });
39
+ return;
40
+ }
41
+ finishTask(task);
42
+ }
43
+
44
+ function tick() {
45
+ var keys = Object.keys(tasks);
46
+ for (var i = 0; i < keys.length; i++) {
47
+ var key = keys[i];
48
+ var task = tasks[key];
49
+ if (task) runTask(key, task);
50
+ }
51
+ }
52
+
53
+ function start() {
54
+ if (timer || Object.keys(tasks).length === 0) return;
55
+ timer = scheduleInterval(tick, intervalMs);
56
+ if (timer && typeof timer.unref === "function") timer.unref();
57
+ }
58
+
59
+ function register(key, run) {
60
+ var current = tasks[key];
61
+ if (current) {
62
+ current.run = run;
63
+ } else {
64
+ tasks[key] = { run: run, running: false };
65
+ }
66
+ start();
67
+ }
68
+
69
+ function unregister(key) {
70
+ delete tasks[key];
71
+ if (Object.keys(tasks).length === 0 && timer) {
72
+ cancelInterval(timer);
73
+ timer = null;
74
+ }
75
+ }
76
+
77
+ function stop() {
78
+ if (timer) cancelInterval(timer);
79
+ timer = null;
80
+ tasks = {};
81
+ }
82
+
83
+ function getTaskCount() {
84
+ return Object.keys(tasks).length;
85
+ }
86
+
87
+ function isRunning() {
88
+ return !!timer;
89
+ }
90
+
91
+ return {
92
+ register: register,
93
+ unregister: unregister,
94
+ tick: tick,
95
+ stop: stop,
96
+ getTaskCount: getTaskCount,
97
+ isRunning: isRunning,
98
+ };
99
+ }
100
+
101
+ var daemonSync = attachDaemonSync({});
102
+
103
+ module.exports = {
104
+ attachDaemonSync: attachDaemonSync,
105
+ daemonSync: daemonSync,
106
+ };
@@ -51,6 +51,7 @@ body.pane-mode #input-area {
51
51
 
52
52
  #split-host {
53
53
  display: none;
54
+ position: relative;
54
55
  flex: 1;
55
56
  min-width: 0;
56
57
  min-height: 0;
@@ -56,6 +56,11 @@ export function initAppNotifications() {
56
56
  bellBtn = document.getElementById("notif-center-btn");
57
57
  badgeEl = document.getElementById("notif-center-badge");
58
58
 
59
+ // Split panes are secondary clients inside the parent shell. They keep
60
+ // notification state in sync, but the parent owns the single visible
61
+ // banner surface for the whole split view.
62
+ if (store.get('paneMode')) return;
63
+
59
64
  // Create banner container
60
65
  bannerContainer = document.createElement("div");
61
66
  bannerContainer.className = "notif-banner-container";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "3.2.1",
3
+ "version": "3.2.2-beta.2",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",