dot-agents 0.6.0 → 0.7.5

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 (80) hide show
  1. package/dist/cli/commands/channel.d.ts.map +1 -1
  2. package/dist/cli/commands/channel.js +70 -2
  3. package/dist/cli/commands/channel.js.map +1 -1
  4. package/dist/daemon/api/channels.d.ts +6 -0
  5. package/dist/daemon/api/channels.d.ts.map +1 -0
  6. package/dist/daemon/api/channels.js +143 -0
  7. package/dist/daemon/api/channels.js.map +1 -0
  8. package/dist/daemon/api/server.d.ts.map +1 -1
  9. package/dist/daemon/api/server.js +57 -0
  10. package/dist/daemon/api/server.js.map +1 -1
  11. package/dist/daemon/daemon.d.ts +28 -0
  12. package/dist/daemon/daemon.d.ts.map +1 -1
  13. package/dist/daemon/daemon.js +174 -6
  14. package/dist/daemon/daemon.js.map +1 -1
  15. package/dist/daemon/lib/index.d.ts +1 -0
  16. package/dist/daemon/lib/index.d.ts.map +1 -1
  17. package/dist/daemon/lib/index.js +1 -0
  18. package/dist/daemon/lib/index.js.map +1 -1
  19. package/dist/daemon/lib/safeguards.d.ts +135 -0
  20. package/dist/daemon/lib/safeguards.d.ts.map +1 -0
  21. package/dist/daemon/lib/safeguards.js +250 -0
  22. package/dist/daemon/lib/safeguards.js.map +1 -0
  23. package/dist/daemon/lib/watcher.d.ts.map +1 -1
  24. package/dist/daemon/lib/watcher.js +48 -8
  25. package/dist/daemon/lib/watcher.js.map +1 -1
  26. package/dist/daemon/web/app.js +433 -0
  27. package/dist/daemon/web/index.html +68 -0
  28. package/dist/daemon/web/styles.css +452 -0
  29. package/dist/lib/environment.d.ts +1 -0
  30. package/dist/lib/environment.d.ts.map +1 -1
  31. package/dist/lib/environment.js +14 -0
  32. package/dist/lib/environment.js.map +1 -1
  33. package/dist/lib/invoke.d.ts +8 -0
  34. package/dist/lib/invoke.d.ts.map +1 -1
  35. package/dist/lib/invoke.js +28 -16
  36. package/dist/lib/invoke.js.map +1 -1
  37. package/dist/lib/processor.d.ts +8 -0
  38. package/dist/lib/processor.d.ts.map +1 -1
  39. package/dist/lib/processor.js +16 -2
  40. package/dist/lib/processor.js.map +1 -1
  41. package/dist/lib/validation/persona.d.ts.map +1 -1
  42. package/dist/lib/validation/persona.js +41 -4
  43. package/dist/lib/validation/persona.js.map +1 -1
  44. package/package.json +15 -9
  45. package/dist/lib/channel.test.d.ts +0 -2
  46. package/dist/lib/channel.test.d.ts.map +0 -1
  47. package/dist/lib/channel.test.js +0 -33
  48. package/dist/lib/channel.test.js.map +0 -1
  49. package/dist/lib/frontmatter.test.d.ts +0 -2
  50. package/dist/lib/frontmatter.test.d.ts.map +0 -1
  51. package/dist/lib/frontmatter.test.js +0 -60
  52. package/dist/lib/frontmatter.test.js.map +0 -1
  53. package/dist/lib/integration.test.d.ts +0 -2
  54. package/dist/lib/integration.test.d.ts.map +0 -1
  55. package/dist/lib/integration.test.js +0 -445
  56. package/dist/lib/integration.test.js.map +0 -1
  57. package/dist/lib/invoke.test.d.ts +0 -2
  58. package/dist/lib/invoke.test.d.ts.map +0 -1
  59. package/dist/lib/invoke.test.js +0 -82
  60. package/dist/lib/invoke.test.js.map +0 -1
  61. package/dist/lib/persona.test.d.ts +0 -2
  62. package/dist/lib/persona.test.d.ts.map +0 -1
  63. package/dist/lib/persona.test.js +0 -324
  64. package/dist/lib/persona.test.js.map +0 -1
  65. package/dist/lib/processor.test.d.ts +0 -2
  66. package/dist/lib/processor.test.d.ts.map +0 -1
  67. package/dist/lib/processor.test.js +0 -134
  68. package/dist/lib/processor.test.js.map +0 -1
  69. package/dist/lib/registry.test.d.ts +0 -2
  70. package/dist/lib/registry.test.d.ts.map +0 -1
  71. package/dist/lib/registry.test.js +0 -236
  72. package/dist/lib/registry.test.js.map +0 -1
  73. package/dist/lib/session-thread.test.d.ts +0 -2
  74. package/dist/lib/session-thread.test.d.ts.map +0 -1
  75. package/dist/lib/session-thread.test.js +0 -235
  76. package/dist/lib/session-thread.test.js.map +0 -1
  77. package/dist/lib/session.test.d.ts +0 -2
  78. package/dist/lib/session.test.d.ts.map +0 -1
  79. package/dist/lib/session.test.js +0 -336
  80. package/dist/lib/session.test.js.map +0 -1
@@ -0,0 +1,433 @@
1
+ /**
2
+ * dot-agents Channels Web UI
3
+ */
4
+
5
+ const API_BASE = window.location.origin;
6
+
7
+ // State
8
+ let currentChannel = null;
9
+ let currentThread = null;
10
+ let channels = [];
11
+ let eventSource = null;
12
+
13
+ // DOM Elements
14
+ const channelListEl = document.getElementById("channel-list");
15
+ const channelNameEl = document.getElementById("channel-name");
16
+ const channelDescriptionEl = document.getElementById("channel-description");
17
+ const messagesEl = document.getElementById("messages");
18
+ const composerEl = document.getElementById("composer");
19
+ const messageFormEl = document.getElementById("message-form");
20
+ const messageInputEl = document.getElementById("message-input");
21
+ const threadPanelEl = document.getElementById("thread-panel");
22
+ const threadMessagesEl = document.getElementById("thread-messages");
23
+ const closeThreadBtn = document.getElementById("close-thread");
24
+ const replyFormEl = document.getElementById("reply-form");
25
+ const replyInputEl = document.getElementById("reply-input");
26
+ const connectionStatusEl = document.getElementById("connection-status");
27
+
28
+ /**
29
+ * Format ISO timestamp to readable time
30
+ */
31
+ function formatTime(isoString) {
32
+ const date = new Date(isoString);
33
+ const now = new Date();
34
+ const diffMs = now - date;
35
+ const diffMins = Math.floor(diffMs / 60000);
36
+ const diffHours = Math.floor(diffMs / 3600000);
37
+ const diffDays = Math.floor(diffMs / 86400000);
38
+
39
+ if (diffMins < 1) return "just now";
40
+ if (diffMins < 60) return `${diffMins}m ago`;
41
+ if (diffHours < 24) return `${diffHours}h ago`;
42
+ if (diffDays < 7) return `${diffDays}d ago`;
43
+
44
+ return date.toLocaleDateString("en-US", {
45
+ month: "short",
46
+ day: "numeric",
47
+ year: date.getFullYear() !== now.getFullYear() ? "numeric" : undefined,
48
+ });
49
+ }
50
+
51
+ /**
52
+ * Format message ID (ISO timestamp) to readable datetime
53
+ */
54
+ function formatDateTime(isoString) {
55
+ const date = new Date(isoString);
56
+ return date.toLocaleString("en-US", {
57
+ month: "short",
58
+ day: "numeric",
59
+ hour: "numeric",
60
+ minute: "2-digit",
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Simple markdown-like rendering (basic support)
66
+ */
67
+ function renderContent(content) {
68
+ // Escape HTML
69
+ let html = content
70
+ .replace(/&/g, "&amp;")
71
+ .replace(/</g, "&lt;")
72
+ .replace(/>/g, "&gt;");
73
+
74
+ // Code blocks
75
+ html = html.replace(/```([\s\S]*?)```/g, "<pre><code>$1</code></pre>");
76
+
77
+ // Inline code
78
+ html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
79
+
80
+ // Bold
81
+ html = html.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
82
+
83
+ // Italic
84
+ html = html.replace(/\*([^*]+)\*/g, "<em>$1</em>");
85
+
86
+ // Line breaks to paragraphs
87
+ html = html
88
+ .split(/\n\n+/)
89
+ .map((p) => `<p>${p.replace(/\n/g, "<br>")}</p>`)
90
+ .join("");
91
+
92
+ return html;
93
+ }
94
+
95
+ /**
96
+ * Render a single message
97
+ */
98
+ function renderMessage(message, isReply = false) {
99
+ const from = message.meta?.from || "unknown";
100
+ const host = message.meta?.host;
101
+ const tags = message.meta?.tags || [];
102
+ const replyCount = message.replies?.length || 0;
103
+
104
+ return `
105
+ <div class="message" data-id="${message.id}">
106
+ <div class="message-header">
107
+ <span class="message-from">${escapeHtml(from)}</span>
108
+ <span class="message-time">${formatDateTime(message.id)}</span>
109
+ ${host ? `<span class="message-host">${escapeHtml(host)}</span>` : ""}
110
+ </div>
111
+ <div class="message-content">
112
+ ${renderContent(message.content)}
113
+ </div>
114
+ <div class="message-footer">
115
+ ${tags.length ? `<div class="message-tags">${tags.map((t) => `<span class="tag">${escapeHtml(t)}</span>`).join("")}</div>` : ""}
116
+ ${!isReply && replyCount > 0 ? `<span class="reply-count" data-thread="${message.id}">${replyCount} ${replyCount === 1 ? "reply" : "replies"}</span>` : ""}
117
+ ${!isReply ? `<span class="reply-count" data-thread="${message.id}" style="cursor: pointer;">${replyCount === 0 ? "Reply" : ""}</span>` : ""}
118
+ </div>
119
+ </div>
120
+ `;
121
+ }
122
+
123
+ /**
124
+ * Escape HTML entities
125
+ */
126
+ function escapeHtml(str) {
127
+ const div = document.createElement("div");
128
+ div.textContent = str;
129
+ return div.innerHTML;
130
+ }
131
+
132
+ /**
133
+ * Fetch and render channel list
134
+ */
135
+ async function loadChannels() {
136
+ try {
137
+ const response = await fetch(`${API_BASE}/channels`);
138
+ const data = await response.json();
139
+ channels = data.channels;
140
+
141
+ // Separate public channels (#) and DMs (@)
142
+ const publicChannels = channels.filter((c) => c.name.startsWith("#"));
143
+ const dmChannels = channels.filter((c) => c.name.startsWith("@"));
144
+
145
+ let html = "";
146
+
147
+ if (publicChannels.length > 0) {
148
+ html += `
149
+ <div class="channel-section">
150
+ <div class="channel-section-title">Channels</div>
151
+ ${publicChannels
152
+ .map(
153
+ (c) => `
154
+ <div class="channel-item" data-channel="${c.name}">
155
+ <span class="channel-icon public">#</span>
156
+ <span class="channel-name">${escapeHtml(c.name.slice(1))}</span>
157
+ </div>
158
+ `
159
+ )
160
+ .join("")}
161
+ </div>
162
+ `;
163
+ }
164
+
165
+ if (dmChannels.length > 0) {
166
+ html += `
167
+ <div class="channel-section">
168
+ <div class="channel-section-title">Direct Messages</div>
169
+ ${dmChannels
170
+ .map(
171
+ (c) => `
172
+ <div class="channel-item" data-channel="${c.name}">
173
+ <span class="channel-icon dm">@</span>
174
+ <span class="channel-name">${escapeHtml(c.name.slice(1))}</span>
175
+ </div>
176
+ `
177
+ )
178
+ .join("")}
179
+ </div>
180
+ `;
181
+ }
182
+
183
+ if (publicChannels.length === 0 && dmChannels.length === 0) {
184
+ html = '<div class="loading">No channels found</div>';
185
+ }
186
+
187
+ channelListEl.innerHTML = html;
188
+
189
+ // Add click handlers
190
+ channelListEl.querySelectorAll(".channel-item").forEach((item) => {
191
+ item.addEventListener("click", () => {
192
+ const channelName = item.dataset.channel;
193
+ selectChannel(channelName);
194
+ });
195
+ });
196
+ } catch (error) {
197
+ console.error("Failed to load channels:", error);
198
+ channelListEl.innerHTML =
199
+ '<div class="loading">Failed to load channels</div>';
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Select and load a channel
205
+ */
206
+ async function selectChannel(channelName) {
207
+ currentChannel = channelName;
208
+ currentThread = null;
209
+
210
+ // Update sidebar
211
+ channelListEl.querySelectorAll(".channel-item").forEach((item) => {
212
+ item.classList.toggle("active", item.dataset.channel === channelName);
213
+ });
214
+
215
+ // Update header
216
+ const channelData = channels.find((c) => c.name === channelName);
217
+ channelNameEl.textContent = channelName;
218
+ channelDescriptionEl.textContent = channelData?.metadata?.description || "";
219
+
220
+ // Show composer
221
+ composerEl.style.display = "block";
222
+
223
+ // Close thread panel
224
+ closeThread();
225
+
226
+ // Load messages
227
+ await loadMessages(channelName);
228
+ }
229
+
230
+ /**
231
+ * Load messages for a channel
232
+ */
233
+ async function loadMessages(channelName) {
234
+ messagesEl.innerHTML =
235
+ '<div class="empty-state"><div class="loading-spinner"></div></div>';
236
+
237
+ try {
238
+ const response = await fetch(
239
+ `${API_BASE}/channels/${encodeURIComponent(channelName)}?limit=100`
240
+ );
241
+ const data = await response.json();
242
+
243
+ if (data.messages.length === 0) {
244
+ messagesEl.innerHTML =
245
+ '<div class="empty-state"><p>No messages in this channel</p></div>';
246
+ return;
247
+ }
248
+
249
+ // Reverse to show oldest first (chronological order)
250
+ const messages = [...data.messages].reverse();
251
+ messagesEl.innerHTML = messages.map((m) => renderMessage(m)).join("");
252
+
253
+ // Scroll to bottom
254
+ messagesEl.scrollTop = messagesEl.scrollHeight;
255
+
256
+ // Add thread click handlers
257
+ messagesEl.querySelectorAll(".reply-count").forEach((el) => {
258
+ el.addEventListener("click", () => {
259
+ const threadId = el.dataset.thread;
260
+ openThread(threadId);
261
+ });
262
+ });
263
+ } catch (error) {
264
+ console.error("Failed to load messages:", error);
265
+ messagesEl.innerHTML =
266
+ '<div class="empty-state"><p>Failed to load messages</p></div>';
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Open thread panel for a message
272
+ */
273
+ async function openThread(messageId) {
274
+ currentThread = messageId;
275
+ threadPanelEl.style.display = "flex";
276
+
277
+ // Find the message
278
+ const response = await fetch(
279
+ `${API_BASE}/channels/${encodeURIComponent(currentChannel)}/${encodeURIComponent(messageId)}`
280
+ );
281
+ const data = await response.json();
282
+
283
+ if (!data.message) {
284
+ threadMessagesEl.innerHTML = "<p>Message not found</p>";
285
+ return;
286
+ }
287
+
288
+ const message = data.message;
289
+
290
+ // Render original message + replies
291
+ let html = renderMessage(message, true);
292
+
293
+ if (message.replies && message.replies.length > 0) {
294
+ html += message.replies.map((r) => renderMessage(r, true)).join("");
295
+ }
296
+
297
+ threadMessagesEl.innerHTML = html;
298
+ threadMessagesEl.scrollTop = threadMessagesEl.scrollHeight;
299
+ }
300
+
301
+ /**
302
+ * Close thread panel
303
+ */
304
+ function closeThread() {
305
+ currentThread = null;
306
+ threadPanelEl.style.display = "none";
307
+ }
308
+
309
+ /**
310
+ * Send a message to current channel
311
+ */
312
+ async function sendMessage(content) {
313
+ if (!currentChannel || !content.trim()) return;
314
+
315
+ try {
316
+ const response = await fetch(
317
+ `${API_BASE}/channels/${encodeURIComponent(currentChannel)}`,
318
+ {
319
+ method: "POST",
320
+ headers: { "Content-Type": "application/json" },
321
+ body: JSON.stringify({
322
+ content: content.trim(),
323
+ from: "web-ui",
324
+ }),
325
+ }
326
+ );
327
+
328
+ if (response.ok) {
329
+ messageInputEl.value = "";
330
+ await loadMessages(currentChannel);
331
+ }
332
+ } catch (error) {
333
+ console.error("Failed to send message:", error);
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Reply to a thread
339
+ */
340
+ async function sendReply(content) {
341
+ if (!currentChannel || !currentThread || !content.trim()) return;
342
+
343
+ try {
344
+ const response = await fetch(
345
+ `${API_BASE}/channels/${encodeURIComponent(currentChannel)}/${encodeURIComponent(currentThread)}/reply`,
346
+ {
347
+ method: "POST",
348
+ headers: { "Content-Type": "application/json" },
349
+ body: JSON.stringify({
350
+ content: content.trim(),
351
+ from: "web-ui",
352
+ }),
353
+ }
354
+ );
355
+
356
+ if (response.ok) {
357
+ replyInputEl.value = "";
358
+ await openThread(currentThread);
359
+ await loadMessages(currentChannel);
360
+ }
361
+ } catch (error) {
362
+ console.error("Failed to send reply:", error);
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Connect to SSE stream for real-time updates
368
+ */
369
+ function connectSSE() {
370
+ if (eventSource) {
371
+ eventSource.close();
372
+ }
373
+
374
+ eventSource = new EventSource(`${API_BASE}/channels-stream`);
375
+
376
+ eventSource.onopen = () => {
377
+ connectionStatusEl.textContent = "Connected";
378
+ connectionStatusEl.className = "connection-status connected";
379
+ };
380
+
381
+ eventSource.onerror = () => {
382
+ connectionStatusEl.textContent = "Disconnected";
383
+ connectionStatusEl.className = "connection-status disconnected";
384
+
385
+ // Attempt reconnect after 5 seconds
386
+ setTimeout(() => {
387
+ if (eventSource.readyState === EventSource.CLOSED) {
388
+ connectSSE();
389
+ }
390
+ }, 5000);
391
+ };
392
+
393
+ eventSource.onmessage = (event) => {
394
+ try {
395
+ const data = JSON.parse(event.data);
396
+
397
+ if (data.type === "connected") {
398
+ console.log("SSE connected");
399
+ return;
400
+ }
401
+
402
+ // Reload channel list on any channel activity
403
+ if (data.type === "dm:received" || data.type === "channel:message") {
404
+ // Refresh channel list
405
+ loadChannels();
406
+
407
+ // If we're viewing the affected channel, reload messages
408
+ if (currentChannel === data.channel) {
409
+ loadMessages(currentChannel);
410
+ }
411
+ }
412
+ } catch (error) {
413
+ console.error("Failed to parse SSE message:", error);
414
+ }
415
+ };
416
+ }
417
+
418
+ // Event listeners
419
+ messageFormEl.addEventListener("submit", (e) => {
420
+ e.preventDefault();
421
+ sendMessage(messageInputEl.value);
422
+ });
423
+
424
+ replyFormEl.addEventListener("submit", (e) => {
425
+ e.preventDefault();
426
+ sendReply(replyInputEl.value);
427
+ });
428
+
429
+ closeThreadBtn.addEventListener("click", closeThread);
430
+
431
+ // Initialize
432
+ loadChannels();
433
+ connectSSE();
@@ -0,0 +1,68 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>dot-agents Channels</title>
7
+ <link rel="stylesheet" href="styles.css">
8
+ </head>
9
+ <body>
10
+ <div class="app">
11
+ <aside class="sidebar">
12
+ <header class="sidebar-header">
13
+ <h1>dot-agents</h1>
14
+ <span class="connection-status" id="connection-status">Connecting...</span>
15
+ </header>
16
+ <nav class="channel-list" id="channel-list">
17
+ <div class="loading">Loading channels...</div>
18
+ </nav>
19
+ </aside>
20
+
21
+ <main class="content">
22
+ <header class="content-header" id="content-header">
23
+ <h2 id="channel-name">Select a channel</h2>
24
+ <span class="channel-description" id="channel-description"></span>
25
+ </header>
26
+
27
+ <div class="messages" id="messages">
28
+ <div class="empty-state">
29
+ <p>Select a channel from the sidebar to view messages</p>
30
+ </div>
31
+ </div>
32
+
33
+ <footer class="composer" id="composer" style="display: none;">
34
+ <form id="message-form">
35
+ <input
36
+ type="text"
37
+ id="message-input"
38
+ placeholder="Type a message..."
39
+ autocomplete="off"
40
+ >
41
+ <button type="submit">Send</button>
42
+ </form>
43
+ </footer>
44
+ </main>
45
+
46
+ <aside class="thread-panel" id="thread-panel" style="display: none;">
47
+ <header class="thread-header">
48
+ <h3>Thread</h3>
49
+ <button class="close-thread" id="close-thread">&times;</button>
50
+ </header>
51
+ <div class="thread-messages" id="thread-messages"></div>
52
+ <footer class="thread-composer">
53
+ <form id="reply-form">
54
+ <input
55
+ type="text"
56
+ id="reply-input"
57
+ placeholder="Reply in thread..."
58
+ autocomplete="off"
59
+ >
60
+ <button type="submit">Reply</button>
61
+ </form>
62
+ </footer>
63
+ </aside>
64
+ </div>
65
+
66
+ <script src="app.js"></script>
67
+ </body>
68
+ </html>