clay-server 2.27.0-beta.8 → 2.27.0

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 (72) hide show
  1. package/README.md +10 -0
  2. package/lib/daemon-projects.js +164 -0
  3. package/lib/daemon.js +13 -126
  4. package/lib/mates-identity.js +132 -0
  5. package/lib/mates-knowledge.js +113 -0
  6. package/lib/mates-prompts.js +398 -0
  7. package/lib/mates.js +40 -599
  8. package/lib/project-connection.js +2 -0
  9. package/lib/project-debate.js +19 -12
  10. package/lib/project-http.js +4 -2
  11. package/lib/project-loop.js +110 -48
  12. package/lib/project-mate-interaction.js +4 -0
  13. package/lib/project-notifications.js +210 -0
  14. package/lib/project-sessions.js +5 -2
  15. package/lib/project-user-message.js +2 -1
  16. package/lib/project.js +26 -2
  17. package/lib/public/app.js +1193 -8521
  18. package/lib/public/css/command-palette.css +14 -0
  19. package/lib/public/css/loop.css +301 -0
  20. package/lib/public/css/notifications-center.css +190 -0
  21. package/lib/public/css/rewind.css +6 -0
  22. package/lib/public/index.html +89 -35
  23. package/lib/public/modules/app-connection.js +160 -0
  24. package/lib/public/modules/app-cursors.js +473 -0
  25. package/lib/public/modules/app-debate-ui.js +389 -0
  26. package/lib/public/modules/app-dm.js +627 -0
  27. package/lib/public/modules/app-favicon.js +212 -0
  28. package/lib/public/modules/app-header.js +229 -0
  29. package/lib/public/modules/app-home-hub.js +600 -0
  30. package/lib/public/modules/app-loop-ui.js +589 -0
  31. package/lib/public/modules/app-loop-wizard.js +439 -0
  32. package/lib/public/modules/app-messages.js +1560 -0
  33. package/lib/public/modules/app-misc.js +299 -0
  34. package/lib/public/modules/app-notifications.js +372 -0
  35. package/lib/public/modules/app-panels.js +888 -0
  36. package/lib/public/modules/app-projects.js +798 -0
  37. package/lib/public/modules/app-rate-limit.js +451 -0
  38. package/lib/public/modules/app-rendering.js +597 -0
  39. package/lib/public/modules/app-skills-install.js +234 -0
  40. package/lib/public/modules/command-palette.js +27 -4
  41. package/lib/public/modules/input.js +31 -20
  42. package/lib/public/modules/scheduler-config.js +1532 -0
  43. package/lib/public/modules/scheduler-history.js +79 -0
  44. package/lib/public/modules/scheduler.js +33 -1554
  45. package/lib/public/modules/session-search.js +13 -1
  46. package/lib/public/modules/sidebar-mates.js +812 -0
  47. package/lib/public/modules/sidebar-mobile.js +1269 -0
  48. package/lib/public/modules/sidebar-projects.js +1449 -0
  49. package/lib/public/modules/sidebar-sessions.js +986 -0
  50. package/lib/public/modules/sidebar.js +232 -4591
  51. package/lib/public/modules/store.js +27 -0
  52. package/lib/public/modules/ws-ref.js +7 -0
  53. package/lib/public/style.css +1 -0
  54. package/lib/sdk-bridge.js +96 -717
  55. package/lib/sdk-message-processor.js +587 -0
  56. package/lib/sdk-message-queue.js +42 -0
  57. package/lib/sdk-skill-discovery.js +131 -0
  58. package/lib/server-admin.js +712 -0
  59. package/lib/server-auth.js +737 -0
  60. package/lib/server-dm.js +221 -0
  61. package/lib/server-mates.js +281 -0
  62. package/lib/server-palette.js +110 -0
  63. package/lib/server-settings.js +479 -0
  64. package/lib/server-skills.js +280 -0
  65. package/lib/server.js +246 -2755
  66. package/lib/sessions.js +11 -4
  67. package/lib/users-auth.js +146 -0
  68. package/lib/users-permissions.js +118 -0
  69. package/lib/users-preferences.js +210 -0
  70. package/lib/users.js +48 -398
  71. package/lib/ws-schema.js +498 -0
  72. package/package.json +1 -1
@@ -0,0 +1,234 @@
1
+ // app-skills-install.js - Skill install dialog, requireSkills, requireClayMateInterview
2
+ // Extracted from app.js (PR-33)
3
+
4
+ import { refreshIcons, iconHtml } from './icons.js';
5
+ import { escapeHtml } from './utils.js';
6
+ import { store } from './store.js';
7
+
8
+ // --- Module-owned state (not in store) ---
9
+ var skillInstallModal = null;
10
+ var skillInstallTitle = null;
11
+ var skillInstallReason = null;
12
+ var skillInstallList = null;
13
+ var skillInstallOk = null;
14
+ var skillInstallCancel = null;
15
+ var skillInstallStatus = null;
16
+
17
+ var pendingSkillInstalls = [];
18
+ var skillInstallCallback = null;
19
+ var skillInstalling = false;
20
+ var skillInstallDone = false;
21
+
22
+ export function initSkillInstall() {
23
+ skillInstallModal = document.getElementById("skill-install-modal");
24
+ skillInstallTitle = document.getElementById("skill-install-title");
25
+ skillInstallReason = document.getElementById("skill-install-reason");
26
+ skillInstallList = document.getElementById("skill-install-list");
27
+ skillInstallOk = document.getElementById("skill-install-ok");
28
+ skillInstallCancel = document.getElementById("skill-install-cancel");
29
+ skillInstallStatus = document.getElementById("skill-install-status");
30
+
31
+ skillInstallCancel.addEventListener("click", hideSkillInstallModal);
32
+ skillInstallModal.querySelector(".confirm-backdrop").addEventListener("click", hideSkillInstallModal);
33
+
34
+ skillInstallOk.addEventListener("click", function () {
35
+ if (skillInstallDone) {
36
+ var proceedCb = skillInstallCallback;
37
+ skillInstallCallback = null;
38
+ hideSkillInstallModal();
39
+ if (proceedCb) proceedCb();
40
+ return;
41
+ }
42
+ if (skillInstalling) return;
43
+ skillInstalling = true;
44
+ skillInstallOk.disabled = true;
45
+ skillInstallOk.textContent = "Installing...";
46
+
47
+ var total = 0;
48
+ for (var i = 0; i < pendingSkillInstalls.length; i++) {
49
+ if (!pendingSkillInstalls[i].installed) total++;
50
+ }
51
+ skillInstallStatus.classList.remove("hidden");
52
+ updateSkillInstallProgress(0, total);
53
+
54
+ for (var j = 0; j < pendingSkillInstalls.length; j++) {
55
+ var s = pendingSkillInstalls[j];
56
+ if (s.installed) continue;
57
+ fetch(store.getState().basePath + "api/install-skill", {
58
+ method: "POST",
59
+ headers: { "Content-Type": "application/json" },
60
+ body: JSON.stringify({ url: s.url, skill: s.name, scope: s.scope || "global" }),
61
+ }).catch(function () {});
62
+ }
63
+ });
64
+ }
65
+
66
+ // --- Functions ---
67
+
68
+ function renderSkillInstallDialog(opts, missing) {
69
+ var hasOutdated = false;
70
+ var hasMissing = false;
71
+ for (var c = 0; c < missing.length; c++) {
72
+ if (missing[c].status === "outdated") hasOutdated = true;
73
+ else hasMissing = true;
74
+ }
75
+ var defaultTitle = hasMissing ? "Skill Installation Required" : "Skill Update Available";
76
+ var defaultReason = hasMissing
77
+ ? "This feature requires the following skill(s) to be installed."
78
+ : "Newer versions of the following skill(s) are available.";
79
+ if (hasMissing && hasOutdated) {
80
+ defaultTitle = "Skill Installation / Update Required";
81
+ defaultReason = "Some skills need to be installed or updated.";
82
+ }
83
+ skillInstallTitle.textContent = opts.title || defaultTitle;
84
+ skillInstallReason.textContent = opts.reason || defaultReason;
85
+ skillInstallList.innerHTML = "";
86
+ for (var i = 0; i < missing.length; i++) {
87
+ var s = missing[i];
88
+ var badge = s.status === "outdated"
89
+ ? '<span class="skill-badge skill-badge-update">Update ' + escapeHtml(s.installedVersion || "") + ' \u2192 ' + escapeHtml(s.remoteVersion || "") + '</span>'
90
+ : '<span class="skill-badge skill-badge-new">New</span>';
91
+ var item = document.createElement("div");
92
+ item.className = "skill-install-item";
93
+ item.setAttribute("data-skill", s.name);
94
+ item.innerHTML = '<span class="skill-icon">&#x1f9e9;</span>' +
95
+ '<div class="skill-info">' +
96
+ '<span class="skill-name">' + escapeHtml(s.name) + '</span>' +
97
+ badge +
98
+ '</div>' +
99
+ '<span class="skill-status"></span>';
100
+ skillInstallList.appendChild(item);
101
+ }
102
+ skillInstallStatus.classList.add("hidden");
103
+ skillInstallStatus.innerHTML = "";
104
+ skillInstallOk.disabled = false;
105
+ var btnLabel = hasMissing ? "Install" : "Update";
106
+ if (hasMissing && hasOutdated) btnLabel = "Install / Update";
107
+ skillInstallOk.textContent = btnLabel;
108
+ skillInstallOk.className = "confirm-btn confirm-delete";
109
+ skillInstallModal.classList.remove("hidden");
110
+ }
111
+
112
+ function hideSkillInstallModal() {
113
+ skillInstallModal.classList.add("hidden");
114
+ skillInstallCallback = null;
115
+ pendingSkillInstalls = [];
116
+ skillInstalling = false;
117
+ skillInstallDone = false;
118
+ }
119
+
120
+ function updateSkillInstallProgress(done, total) {
121
+ var hasUpdates = false;
122
+ for (var u = 0; u < pendingSkillInstalls.length; u++) {
123
+ if (pendingSkillInstalls[u].status === "outdated") { hasUpdates = true; break; }
124
+ }
125
+ var label = hasUpdates ? "Updating" : "Installing";
126
+ skillInstallStatus.innerHTML = '<div class="skills-spinner small"></div> ' + label + ' skills... (' + done + '/' + total + ')';
127
+ }
128
+
129
+ function updateSkillListItems() {
130
+ var items = skillInstallList.querySelectorAll(".skill-install-item");
131
+ for (var i = 0; i < items.length; i++) {
132
+ var name = items[i].getAttribute("data-skill");
133
+ for (var j = 0; j < pendingSkillInstalls.length; j++) {
134
+ if (pendingSkillInstalls[j].name === name) {
135
+ var statusEl = items[i].querySelector(".skill-status");
136
+ if (pendingSkillInstalls[j].installed) {
137
+ if (statusEl) {
138
+ statusEl.innerHTML = '<span class="skill-status-ok">' + iconHtml("circle-check") + '</span>';
139
+ refreshIcons();
140
+ }
141
+ }
142
+ break;
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ export function handleSkillInstallWs(msg) {
149
+ if (!skillInstalling || pendingSkillInstalls.length === 0) return;
150
+ for (var i = 0; i < pendingSkillInstalls.length; i++) {
151
+ if (pendingSkillInstalls[i].name === msg.skill) {
152
+ if (msg.success) {
153
+ pendingSkillInstalls[i].installed = true;
154
+ var _kis = Object.assign({}, store.getState().knownInstalledSkills);
155
+ _kis[msg.skill] = true;
156
+ store.setState({ knownInstalledSkills: _kis });
157
+ } else {
158
+ skillInstalling = false;
159
+ skillInstallOk.disabled = false;
160
+ skillInstallOk.textContent = "Install";
161
+ skillInstallStatus.innerHTML = "Failed to install " + escapeHtml(msg.skill) + ". Try again.";
162
+ updateSkillListItems();
163
+ return;
164
+ }
165
+ }
166
+ }
167
+
168
+ var doneCount = 0;
169
+ var totalCount = pendingSkillInstalls.length;
170
+ for (var k = 0; k < pendingSkillInstalls.length; k++) {
171
+ if (pendingSkillInstalls[k].installed) doneCount++;
172
+ }
173
+ updateSkillListItems();
174
+ updateSkillInstallProgress(doneCount, totalCount);
175
+
176
+ if (doneCount === totalCount) {
177
+ skillInstallDone = true;
178
+ var hasUpdates = false;
179
+ for (var u = 0; u < pendingSkillInstalls.length; u++) {
180
+ if (pendingSkillInstalls[u].status === "outdated") { hasUpdates = true; break; }
181
+ }
182
+ var doneMsg = hasUpdates ? "All skills updated successfully." : "All skills installed successfully.";
183
+ skillInstallStatus.innerHTML = '<span class="skill-status-ok">' + iconHtml("circle-check") + '</span> ' + doneMsg;
184
+ refreshIcons();
185
+ skillInstallOk.disabled = false;
186
+ skillInstallOk.textContent = "Proceed";
187
+ skillInstallOk.className = "confirm-btn confirm-proceed";
188
+ }
189
+ }
190
+
191
+ export function requireSkills(opts, cb) {
192
+ fetch(store.getState().basePath + "api/check-skill-updates", {
193
+ method: "POST",
194
+ headers: { "Content-Type": "application/json" },
195
+ body: JSON.stringify({ skills: opts.skills }),
196
+ })
197
+ .then(function (res) { return res.json(); })
198
+ .then(function (data) {
199
+ var results = data.results || [];
200
+ var actionable = [];
201
+ for (var i = 0; i < results.length; i++) {
202
+ var r = results[i];
203
+ if (r.status === "missing" || r.status === "outdated") {
204
+ var orig = null;
205
+ for (var j = 0; j < opts.skills.length; j++) {
206
+ if (opts.skills[j].name === r.name) { orig = opts.skills[j]; break; }
207
+ }
208
+ if (!orig) continue;
209
+ actionable.push({
210
+ name: r.name,
211
+ url: orig.url,
212
+ scope: orig.scope || "global",
213
+ installed: false,
214
+ status: r.status,
215
+ installedVersion: r.installedVersion,
216
+ remoteVersion: r.remoteVersion,
217
+ });
218
+ }
219
+ }
220
+ if (actionable.length === 0) { cb(); return; }
221
+ pendingSkillInstalls = actionable;
222
+ skillInstallCallback = cb;
223
+ renderSkillInstallDialog(opts, actionable);
224
+ })
225
+ .catch(function () { cb(); });
226
+ }
227
+
228
+ export function requireClayMateInterview(cb) {
229
+ requireSkills({
230
+ title: "Skill Installation Required",
231
+ reason: "The Mate Interview skill is required to create a new Mate.",
232
+ skills: [{ name: "clay-mate-interview", url: "https://github.com/chadbyte/clay-mate-interview", scope: "global" }]
233
+ }, cb);
234
+ }
@@ -3,6 +3,25 @@ import { escapeHtml } from './utils.js';
3
3
  import { refreshIcons } from './icons.js';
4
4
  import { openSearch as openSessionSearch } from './session-search.js';
5
5
 
6
+ function formatRelativeDate(ts) {
7
+ if (!ts) return "";
8
+ var now = Date.now();
9
+ var diff = now - ts;
10
+ if (diff < 0) diff = 0;
11
+ var sec = Math.floor(diff / 1000);
12
+ if (sec < 60) return "just now";
13
+ var min = Math.floor(sec / 60);
14
+ if (min < 60) return min + "m ago";
15
+ var hr = Math.floor(min / 60);
16
+ if (hr < 24) return hr + "h ago";
17
+ var days = Math.floor(hr / 24);
18
+ if (days < 7) return days + "d ago";
19
+ if (days < 30) return Math.floor(days / 7) + "w ago";
20
+ var d = new Date(ts);
21
+ var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
22
+ return months[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();
23
+ }
24
+
6
25
  var ctx;
7
26
  var paletteEl = null;
8
27
  var inputEl = null;
@@ -260,7 +279,7 @@ function renderHome(filter) {
260
279
  iconHtml = s.projectIcon || '<i data-lucide="message-square"></i>';
261
280
  }
262
281
  var projLabel = (s.projectIcon || "") + " " + escapeHtml(s.projectTitle || s.projectSlug);
263
- html += renderItem(flatIndex, iconHtml, escapeHtml(s.sessionTitle || "New Session"), projLabel.trim(), null);
282
+ html += renderItem(flatIndex, iconHtml, escapeHtml(s.sessionTitle || "New Session"), projLabel.trim(), null, s.lastActivity);
264
283
  flatIndex++;
265
284
  }
266
285
  }
@@ -418,7 +437,7 @@ function appendHomeSearchResults(results, query) {
418
437
  }
419
438
  var projLabel = (r.projectIcon || "") + " " + escapeHtml(r.projectTitle || r.projectSlug);
420
439
  var snippet = r.snippet ? escapeHtml(r.snippet) : "";
421
- html += renderItem(flatIndex, srIconHtml, escapeHtml(r.sessionTitle || "New Session"), projLabel.trim(), snippet);
440
+ html += renderItem(flatIndex, srIconHtml, escapeHtml(r.sessionTitle || "New Session"), projLabel.trim(), snippet, r.lastActivity);
422
441
  flatIndex++;
423
442
  }
424
443
 
@@ -436,11 +455,15 @@ function appendHomeSearchResults(results, query) {
436
455
  // Shared rendering / interaction helpers
437
456
  // ==========================================
438
457
 
439
- function renderItem(index, iconContent, title, desc, snippet) {
458
+ function renderItem(index, iconContent, title, desc, snippet, timestamp) {
459
+ var dateStr = formatRelativeDate(timestamp);
440
460
  return '<div class="cmd-palette-item" data-index="' + index + '">' +
441
461
  '<div class="cmd-palette-item-icon">' + iconContent + '</div>' +
442
462
  '<div class="cmd-palette-item-body">' +
443
- '<div class="cmd-palette-item-title">' + title + '</div>' +
463
+ '<div class="cmd-palette-item-title-row">' +
464
+ '<span class="cmd-palette-item-title">' + title + '</span>' +
465
+ (dateStr ? '<span class="cmd-palette-item-date">' + dateStr + '</span>' : '') +
466
+ '</div>' +
444
467
  (desc || snippet ?
445
468
  '<div class="cmd-palette-item-meta">' +
446
469
  (desc ? '<span class="cmd-palette-item-project">' + desc + '</span>' : '') +
@@ -150,29 +150,39 @@ export function sendMessage() {
150
150
  }
151
151
 
152
152
  // Check for @mention: if a mate was selected, route to mention handler
153
+ // Exception: if we're in a DM with the same mate, send as regular message instead
153
154
  var mention = parseMentionFromInput(text);
154
155
  if (mention) {
155
- hideMentionMenu();
156
- if (ctx.hideSuggestionChips) ctx.hideSuggestionChips();
157
- var mentionImages = pendingImages.slice();
158
- var mentionPastes = pendingPastes.map(function (p) { return p.text; });
159
- // Prepend file paths to mention text (same pattern as regular messages)
160
- var mentionFiles = pendingFiles.slice();
161
- var mentionText = mention.text;
162
- if (mentionFiles.length > 0) {
163
- var mFilePaths = mentionFiles.map(function (f) { return "[Uploaded file: " + f.path + "]"; }).join("\n");
164
- mentionText = mentionText ? mFilePaths + "\n\n" + mentionText : mFilePaths;
156
+ var dmMateId = ctx.getDmMateId ? ctx.getDmMateId() : null;
157
+ if (dmMateId && dmMateId === mention.mateId) {
158
+ // In DM with this mate — strip mention chip and send as normal message
159
+ hideMentionMenu();
160
+ removeMentionChip();
161
+ text = mention.text;
162
+ // Fall through to normal message send below
163
+ } else {
164
+ hideMentionMenu();
165
+ if (ctx.hideSuggestionChips) ctx.hideSuggestionChips();
166
+ var mentionImages = pendingImages.slice();
167
+ var mentionPastes = pendingPastes.map(function (p) { return p.text; });
168
+ // Prepend file paths to mention text (same pattern as regular messages)
169
+ var mentionFiles = pendingFiles.slice();
170
+ var mentionText = mention.text;
171
+ if (mentionFiles.length > 0) {
172
+ var mFilePaths = mentionFiles.map(function (f) { return "[Uploaded file: " + f.path + "]"; }).join("\n");
173
+ mentionText = mentionText ? mFilePaths + "\n\n" + mentionText : mFilePaths;
174
+ }
175
+ // Render user message with mention chip (same as history replay)
176
+ renderMentionUser({ mateName: mention.mateName, text: mentionText, images: mentionImages.length > 0 ? mentionImages : null, pastes: mentionPastes.length > 0 ? mentionPastes : null });
177
+ sendMention(mention.mateId, mentionText, mentionPastes, mentionImages);
178
+ ctx.inputEl.value = "";
179
+ stickyReapplyMention();
180
+ sendInputSync();
181
+ clearPendingImages();
182
+ autoResize();
183
+ ctx.inputEl.focus();
184
+ return;
165
185
  }
166
- // Render user message with mention chip (same as history replay)
167
- renderMentionUser({ mateName: mention.mateName, text: mentionText, images: mentionImages.length > 0 ? mentionImages : null, pastes: mentionPastes.length > 0 ? mentionPastes : null });
168
- sendMention(mention.mateId, mentionText, mentionPastes, mentionImages);
169
- ctx.inputEl.value = "";
170
- stickyReapplyMention();
171
- sendInputSync();
172
- clearPendingImages();
173
- autoResize();
174
- ctx.inputEl.focus();
175
- return;
176
186
  }
177
187
 
178
188
  // Prepend file paths to text
@@ -197,6 +207,7 @@ export function sendMessage() {
197
207
  return;
198
208
  }
199
209
 
210
+ ctx.currentMsgTs = Date.now();
200
211
  ctx.addUserMessage(text, images.length > 0 ? images : null, pastes.length > 0 ? pastes : null);
201
212
 
202
213
  var payload = { type: "message", text: text || "" };