@cotal-ai/web 0.0.0 → 0.11.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.
@@ -0,0 +1,350 @@
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" />
6
+ <title>Cotal · observability</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@400;500;600;700&display=swap"
11
+ rel="stylesheet"
12
+ />
13
+ <style>
14
+ :root {
15
+ --bg: #0d1117; --panel: #161b22; --tile: #1c2230; --line: #21262d;
16
+ --fg: #e6edf3; --dim: #8b949e; --faint: #6e7681;
17
+ --blue: #58a6ff; --green: #3fb950; --amber: #d29922; --orange: #db6d28; --red: #f85149;
18
+ --tint-green: #1f2d24; --tint-amber: #2a2417; --peer-amber: #221e12; --tag-amber: #3a2e12;
19
+ --sel: #15233a; --intent: #10203a; --intent-badge: #16345c; --rollup: #12161d;
20
+ --font: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
21
+ }
22
+ * { box-sizing: border-box; }
23
+ html, body { height: 100%; }
24
+ body {
25
+ margin: 0; display: grid; grid-template-rows: 76px 1fr;
26
+ font-family: var(--font); font-size: 13px; line-height: 1.45;
27
+ background: var(--bg); color: var(--fg);
28
+ -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
29
+ }
30
+
31
+ /* ── Header ── */
32
+ header {
33
+ display: flex; align-items: center; gap: 20px; padding: 0 24px;
34
+ background: var(--panel); border-bottom: 1px solid var(--line);
35
+ }
36
+ .brand { display: flex; align-items: center; gap: 10px; }
37
+ .brand .mark { width: 10px; height: 10px; border-radius: 50%; background: var(--blue); }
38
+ .brand .title { font-size: 18px; font-weight: 700; color: var(--fg); }
39
+ .brand .space { font-size: 14px; color: var(--dim); }
40
+ .brand-graph {
41
+ font-size: 12px; font-weight: 600; color: var(--dim); text-decoration: none;
42
+ padding: 4px 10px; border: 1px solid var(--line); border-radius: 8px;
43
+ }
44
+ .brand-graph:hover { color: var(--fg); border-color: var(--blue); }
45
+ .pill {
46
+ display: inline-flex; align-items: center; gap: 6px; padding: 4px 9px;
47
+ border-radius: 20px; background: var(--tint-green);
48
+ }
49
+ .pill .d { width: 7px; height: 7px; border-radius: 50%; background: var(--green); }
50
+ .pill .t { font-size: 11px; font-weight: 600; color: var(--green); }
51
+ .pill.down { background: #2a1717; }
52
+ .pill.down .d { background: var(--red); } .pill.down .t { color: var(--red); }
53
+
54
+ /* HealthBand tiles */
55
+ .tiles { margin-left: auto; display: flex; gap: 10px; }
56
+ .tile {
57
+ display: flex; align-items: center; gap: 9px; padding: 8px 12px;
58
+ border-radius: 8px; background: var(--tile); border: 1px solid var(--line);
59
+ }
60
+ .tile .bar { width: 3px; height: 24px; border-radius: 2px; background: var(--faint); }
61
+ .tile .c { display: flex; flex-direction: column; gap: 1px; }
62
+ .tile .n { font-size: 17px; font-weight: 700; color: var(--fg); line-height: 1.1; }
63
+ .tile .lbl { font-size: 10.5px; color: var(--dim); letter-spacing: .3px; line-height: 1.1; }
64
+ .tile.working .bar { background: var(--green); }
65
+ .tile.waiting { background: var(--tint-amber); border-color: var(--amber); }
66
+ .tile.waiting .bar { background: var(--amber); } .tile.waiting .n { color: var(--amber); }
67
+ .tile.oldest .bar { background: var(--orange); }
68
+
69
+ /* ── Body grid ── */
70
+ main { display: grid; grid-template-columns: 300px 1fr 340px; min-height: 0; }
71
+ aside {
72
+ background: var(--panel); border-right: 1px solid var(--line);
73
+ overflow-y: auto; min-height: 0;
74
+ }
75
+ aside.rail { border-right: 0; border-left: 1px solid var(--line); }
76
+
77
+ .ghead {
78
+ display: flex; align-items: center; gap: 6px; padding: 12px 16px;
79
+ font-size: 11px; font-weight: 600; letter-spacing: .8px; color: var(--dim);
80
+ }
81
+ .ghead .c { margin-left: auto; font-size: 11px; font-weight: 400; color: var(--faint); }
82
+ .divider { height: 1px; background: var(--line); }
83
+
84
+ /* ── Roster ── */
85
+ .peer {
86
+ display: flex; gap: 9px; padding: 8px 16px; align-items: flex-start;
87
+ }
88
+ .peer.waiting { background: var(--peer-amber); border-left: 2px solid var(--amber); padding-left: 14px; }
89
+ .peer .dot { width: 7px; font-size: 11px; line-height: 19px; flex: none; text-align: center; }
90
+ .dot.working { color: var(--green); } .dot.waiting { color: var(--amber); }
91
+ .dot.idle, .dot.offline { color: var(--faint); }
92
+ .peer .c { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
93
+ .peer .l1 { display: flex; align-items: center; gap: 6px; min-width: 0; }
94
+ .peer .name { font-size: 13.5px; font-weight: 600; color: var(--fg); white-space: nowrap; }
95
+ .peer .role { font-size: 11px; color: var(--faint); white-space: nowrap;
96
+ overflow: hidden; text-overflow: ellipsis; }
97
+ .peer .act { font-size: 11.5px; color: var(--dim); white-space: nowrap;
98
+ overflow: hidden; text-overflow: ellipsis; }
99
+ .peer.waiting .act { color: var(--amber); }
100
+ .tag {
101
+ font-size: 9.5px; font-weight: 600; padding: 1px 6px; border-radius: 4px;
102
+ white-space: nowrap; background: var(--tag-amber); color: var(--amber);
103
+ }
104
+ /* A peer row is a click-through into its Agent Detail card. */
105
+ .peer.nav { cursor: pointer; }
106
+ .peer.nav:hover { background: #ffffff06; }
107
+ .peer.nav.sel { background: var(--sel); }
108
+ .peer.nav:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }
109
+
110
+ /* ── Channels ── */
111
+ .chan {
112
+ display: flex; align-items: center; gap: 8px; padding: 6px 16px;
113
+ cursor: pointer;
114
+ }
115
+ .chan:hover { background: #ffffff06; }
116
+ .chan.sel { background: var(--sel); border-left: 2px solid var(--blue); padding-left: 14px; }
117
+ .chan .l { display: flex; align-items: center; gap: 7px; min-width: 0; flex: 1; }
118
+ .chan .hash { font-size: 12px; color: var(--dim); flex: none; }
119
+ .chan .glyph { font-size: 12px; color: var(--blue); flex: none; }
120
+ .chan .name { font-size: 13px; color: var(--blue); overflow: hidden;
121
+ text-overflow: ellipsis; white-space: nowrap; }
122
+ .chan.sel .name { font-weight: 600; color: var(--fg); }
123
+ .chan.muted .hash, .chan.muted .name { color: var(--faint); }
124
+ .chan .count { font-size: 11px; color: var(--faint); flex: none; }
125
+ .chan.sel .count { color: var(--dim); }
126
+ .mention {
127
+ font-size: 10px; font-weight: 700; color: var(--bg); background: var(--blue);
128
+ padding: 1px 6px; border-radius: 9px; flex: none;
129
+ }
130
+
131
+ /* ── Feed ── */
132
+ section { display: flex; flex-direction: column; min-height: 0; background: var(--bg); }
133
+ .feed-head {
134
+ display: flex; align-items: center; gap: 10px; padding: 14px 20px;
135
+ border-bottom: 1px solid var(--line); flex: none;
136
+ }
137
+ .feed-head .h { font-size: 15px; font-weight: 700; color: var(--fg); }
138
+ .feed-head .sub { font-size: 12px; color: var(--dim); }
139
+ .feed-head .ctrls { margin-left: auto; display: flex; gap: 8px; align-items: center; }
140
+ .chip {
141
+ font-size: 11px; padding: 4px 10px; border-radius: 14px; cursor: pointer;
142
+ color: var(--faint); user-select: none; border: 1px solid transparent; white-space: nowrap;
143
+ }
144
+ .chip.mode.on { background: var(--tile); border-color: var(--line); color: var(--fg); }
145
+ .chip.pause.on { color: var(--amber); }
146
+ .chip.static { cursor: default; }
147
+ .chip.danger { color: var(--red); }
148
+ .chip.danger:hover { background: #2a1717; border-color: var(--red); }
149
+
150
+ .feed { overflow-y: auto; min-height: 0; flex: 1; display: flex; flex-direction: column;
151
+ gap: 2px; padding: 8px; }
152
+ .sys { text-align: center; padding: 5px 12px; font-size: 11px; color: var(--faint); }
153
+ .msg { display: flex; gap: 10px; padding: 7px 12px; align-items: flex-start; }
154
+ .msg .ts { font-size: 11px; color: var(--faint); flex: none; width: 40px; line-height: 19px; }
155
+ .msg .badge {
156
+ font-size: 9.5px; font-weight: 600; padding: 2px 7px; border-radius: 4px;
157
+ background: var(--line); flex: none; line-height: 1.4;
158
+ }
159
+ .badge.chat { color: var(--blue); } .badge.unicast { color: var(--amber); }
160
+ .badge.anycast { color: var(--green); } .badge.intent { color: var(--blue); background: var(--intent-badge); }
161
+ .msg .c { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
162
+ .msg .l1 { display: flex; align-items: center; gap: 6px; min-width: 0; flex-wrap: wrap; }
163
+ .msg .who { font-size: 13px; font-weight: 600; color: var(--fg); }
164
+ .msg .role { font-size: 10.5px; color: var(--faint); }
165
+ .msg .tgt { font-size: 11px; color: var(--dim); }
166
+ .msg .body { font-size: 12.5px; color: var(--fg); word-break: break-word; }
167
+ .msg.intent { background: var(--intent); border-left: 2px solid var(--blue); padding-left: 10px; }
168
+ .msg.intent .tgt { color: var(--blue); }
169
+ .subpill {
170
+ font-size: 9.5px; font-weight: 600; padding: 1px 6px; border-radius: 4px;
171
+ background: var(--tint-amber); color: var(--amber); flex: none;
172
+ }
173
+ .rollup {
174
+ display: flex; align-items: center; gap: 8px; margin: 0 4px; padding: 6px 12px;
175
+ border-radius: 6px; background: var(--rollup);
176
+ }
177
+ .rollup .ar { font-size: 13px; color: var(--faint); }
178
+ .rollup .t { font-size: 11.5px; color: var(--dim); }
179
+
180
+ /* ── NEEDS YOU rail ── */
181
+ aside.rail { padding: 16px; display: flex; flex-direction: column; gap: 12px; }
182
+ .rail-head { display: flex; align-items: center; justify-content: space-between; }
183
+ .rail-head .t { font-size: 12px; font-weight: 700; color: var(--fg); letter-spacing: .8px; }
184
+ .rail-head .n {
185
+ font-size: 11px; font-weight: 700; color: var(--bg); background: var(--red);
186
+ padding: 2px 8px; border-radius: 12px;
187
+ }
188
+ .card {
189
+ border-radius: 10px; background: var(--tile); border: 1px solid var(--ac, var(--line));
190
+ padding: 14px; display: flex; flex-direction: column; gap: 9px;
191
+ }
192
+ .card.nav { cursor: pointer; transition: border-color .12s, background .12s; }
193
+ .card.nav:hover { border-color: var(--ac); }
194
+ .card.nav.sel { border-color: var(--ac); background: var(--tint-amber, var(--tile)); }
195
+ .card .top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
196
+ .card .cat-l { display: flex; align-items: center; gap: 7px; }
197
+ .card .cdot { width: 8px; height: 8px; border-radius: 50%; background: var(--ac); }
198
+ .card .cat { font-size: 10.5px; font-weight: 700; color: var(--ac); letter-spacing: .4px; }
199
+ .card .age { font-size: 10px; color: var(--dim); background: var(--line);
200
+ padding: 1px 6px; border-radius: 4px; }
201
+ .card .title { font-size: 13.5px; font-weight: 600; color: var(--fg); }
202
+ .card .crole { font-size: 11px; font-weight: 500; color: var(--faint); margin-left: 7px; }
203
+ .card .desc { font-size: 12px; color: var(--dim); }
204
+ /* Live waiting cards: one-line preview (gist at a glance); full text lives in the click-through detail. */
205
+ .card.nav .desc { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
206
+ .card .btns { display: flex; gap: 8px; }
207
+ .btn {
208
+ font-size: 11.5px; padding: 6px 12px; border-radius: 6px; cursor: pointer;
209
+ border: 1px solid transparent; white-space: nowrap;
210
+ }
211
+ .btn.primary { background: var(--ac); color: var(--bg); font-weight: 700; }
212
+ .btn.secondary { border-color: var(--line); color: var(--fg); font-weight: 500; }
213
+ .tone-amber { --ac: var(--amber); } .tone-red { --ac: var(--red); }
214
+ .tone-orange { --ac: var(--orange); } .tone-blue { --ac: var(--blue); }
215
+ .rail-foot { font-size: 10.5px; color: var(--faint); }
216
+
217
+ /* ── Channel view (center) ── */
218
+ .ch-head {
219
+ display: flex; flex-direction: column; gap: 5px; padding: 14px 20px;
220
+ background: var(--panel); border-bottom: 1px solid var(--line); flex: none;
221
+ }
222
+ .ch-head .row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
223
+ .ch-head .title { display: flex; align-items: center; gap: 8px; min-width: 0; }
224
+ .ch-head .h { font-size: 17px; font-weight: 700; color: var(--fg); }
225
+ .ch-head .sub { font-size: 11px; color: var(--faint); white-space: nowrap; }
226
+ .ch-head .purpose { font-size: 12px; color: var(--dim); }
227
+ .clist { overflow-y: auto; min-height: 0; flex: 1; display: flex; flex-direction: column;
228
+ gap: 2px; padding: 10px; }
229
+ .cmsg { display: flex; gap: 10px; padding: 7px 12px; align-items: flex-start; }
230
+ .cmsg .ts { font-size: 11px; color: var(--faint); flex: none; width: 40px; line-height: 19px; }
231
+ .cmsg .dot { font-size: 11px; flex: none; width: 14px; text-align: center; line-height: 19px; }
232
+ .cmsg .c { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
233
+ .cmsg .l1 { display: flex; align-items: center; gap: 6px; }
234
+ .cmsg .who { font-size: 13px; font-weight: 600; color: var(--fg); }
235
+ .cmsg .role { font-size: 10.5px; color: var(--faint); }
236
+ .cmsg .body { font-size: 12.5px; color: var(--fg); word-break: break-word; }
237
+ .thread {
238
+ align-self: flex-start; margin-top: 2px; font-size: 10.5px; color: var(--blue);
239
+ background: var(--line); padding: 3px 8px; border-radius: 6px;
240
+ }
241
+ .unread-mark { display: flex; align-items: center; gap: 10px; padding: 6px 12px; }
242
+ .unread-mark .line { flex: 1; height: 1px; background: var(--red); }
243
+ .unread-mark .t { font-size: 10.5px; font-weight: 600; color: var(--red); white-space: nowrap; }
244
+
245
+ .ch-head .ctrls { display: flex; align-items: center; gap: 8px; }
246
+ .cmsg .dir { font-size: 11px; color: var(--dim); }
247
+ .dtag { font-size: 9.5px; font-weight: 600; padding: 2px 7px; border-radius: 4px;
248
+ background: var(--line); color: var(--amber); }
249
+
250
+ /* ── Direct messages (sidebar lens) ── */
251
+ .dm { display: flex; align-items: center; gap: 6px; padding: 7px 16px; cursor: pointer; }
252
+ .dm:hover { background: #ffffff06; }
253
+ .dm.sel { background: var(--sel); }
254
+ .dm .caret, .dm .ln { width: 10px; flex: none; text-align: center; font-size: 9px; color: var(--faint); }
255
+ .dm .l { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; }
256
+ .dm .dot { width: 12px; flex: none; text-align: center; font-size: 11px; }
257
+ .dm .nm { font-size: 13.5px; font-weight: 600; color: var(--fg); white-space: nowrap; }
258
+ .dm .role { font-size: 11px; color: var(--faint); white-space: nowrap;
259
+ overflow: hidden; text-overflow: ellipsis; }
260
+ .dm .threads { font-size: 11px; color: var(--faint); flex: none; }
261
+ .dm.sub { padding-left: 34px; }
262
+ .dm.sub .nm { font-size: 12.5px; font-weight: 400; }
263
+ .dm.sub .dot { width: 10px; font-size: 10px; }
264
+
265
+ .empty { color: var(--faint); padding: 22px 16px; font-size: 12px; }
266
+
267
+ /* ── Agent Detail (centre drill-down) ── */
268
+ .detail { padding: 22px 24px; display: flex; flex-direction: column; gap: 13px; overflow-y: auto; }
269
+ .detail .d-head { display: flex; align-items: center; gap: 9px; }
270
+ .detail .d-head .dot { font-size: 13px; }
271
+ .detail .d-status { font-size: 11px; font-weight: 700; letter-spacing: .5px; color: var(--dim); }
272
+ .detail.amber .d-status { color: var(--amber); }
273
+ .detail .d-age { margin-left: auto; font-size: 11px; color: var(--faint); }
274
+ .detail .d-who { font-size: 20px; font-weight: 700; color: var(--fg); display: flex; align-items: baseline; }
275
+ .detail .d-who .crole { font-size: 12px; font-weight: 500; color: var(--faint); margin-left: 8px; }
276
+ .detail .d-label { font-size: 10.5px; font-weight: 600; letter-spacing: .4px; color: var(--faint); text-transform: uppercase; }
277
+ .detail .d-block { font-size: 13px; color: var(--fg); line-height: 1.5; padding: 12px 14px; border-radius: 8px;
278
+ background: var(--tile); border: 1px solid var(--line); white-space: pre-wrap; word-break: break-word; }
279
+ .detail.amber .d-block { border-color: var(--amber); background: var(--tint-amber); }
280
+ .detail .d-block.muted { color: var(--dim); }
281
+ .detail .d-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: -6px; }
282
+ .detail .d-badge {
283
+ font-size: 10.5px; font-weight: 600; padding: 2px 8px; border-radius: 5px;
284
+ background: var(--tile); border: 1px solid var(--line); color: var(--dim);
285
+ }
286
+ .detail .d-badge.model { font-family: ui-monospace, monospace; color: var(--fg); letter-spacing: -.2px; }
287
+ /* Harness (host connector) badge — brand-coloured via --hc; used in the detail view (pill
288
+ with label) and the roster row (bare icon). */
289
+ .harness-badge {
290
+ display: inline-flex; align-items: center; gap: 6px;
291
+ font-size: 11px; font-weight: 600; padding: 3px 10px 3px 8px; border-radius: 999px;
292
+ color: var(--hc); border: 1px solid color-mix(in srgb, var(--hc) 45%, transparent);
293
+ background: color-mix(in srgb, var(--hc) 15%, transparent);
294
+ }
295
+ .harness-ico { display: inline-flex; align-items: center; flex: none; }
296
+ .harness-badge svg, .harness-ico svg { width: 13px; height: 13px; display: block; }
297
+ .detail .d-desc { font-size: 13px; color: var(--fg); line-height: 1.5; }
298
+ .detail .d-sec { display: flex; flex-direction: column; gap: 7px; }
299
+ .detail .d-chips { display: flex; flex-wrap: wrap; gap: 6px; }
300
+ .detail .d-chip {
301
+ font-size: 11px; padding: 2px 9px; border-radius: 12px;
302
+ background: var(--tile); border: 1px solid var(--line); color: var(--dim);
303
+ }
304
+ .detail .d-skills { display: flex; flex-direction: column; gap: 8px; }
305
+ .detail .d-skill {
306
+ display: flex; flex-direction: column; gap: 1px; padding: 8px 12px;
307
+ border-radius: 8px; background: var(--tile); border: 1px solid var(--line);
308
+ }
309
+ .detail .d-skill .nm { font-size: 12.5px; font-weight: 600; color: var(--fg); }
310
+ .detail .d-skill .dsc { font-size: 11.5px; color: var(--dim); }
311
+ .detail .d-kv { display: flex; flex-direction: column; gap: 4px; }
312
+ .detail .d-kv .row { display: flex; gap: 10px; font-size: 12px; }
313
+ .detail .d-kv .k { color: var(--faint); min-width: 90px; flex: none; }
314
+ .detail .d-kv .v { color: var(--fg); word-break: break-word; }
315
+ .detail .d-foot {
316
+ display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 4px;
317
+ padding-top: 11px; border-top: 1px solid var(--line);
318
+ }
319
+ .detail .d-foot-item { font-size: 10.5px; color: var(--faint); }
320
+ .detail .d-foot-item.id { font-family: ui-monospace, monospace; word-break: break-all; }
321
+ </style>
322
+ </head>
323
+ <body>
324
+ <header>
325
+ <span class="brand">
326
+ <span class="mark"></span>
327
+ <span class="title">Cotal</span>
328
+ <span class="space" id="space"></span>
329
+ <span class="pill down" id="conn"><span class="d"></span><span class="t">connecting</span></span>
330
+ <a class="brand-graph" href="/graph">Graph view →</a>
331
+ </span>
332
+ <div class="tiles" id="tiles"></div>
333
+ </header>
334
+ <main id="main">
335
+ <aside>
336
+ <div class="ghead">ONLINE <span class="c" id="online-c"></span></div>
337
+ <div id="roster"></div>
338
+ <div class="divider"></div>
339
+ <div class="ghead">CHANNELS</div>
340
+ <div id="channels"></div>
341
+ <div class="divider"></div>
342
+ <div class="ghead">DIRECT MESSAGES</div>
343
+ <div id="dms"></div>
344
+ </aside>
345
+ <section id="center"></section>
346
+ <aside class="rail" id="rail"></aside>
347
+ </main>
348
+ <script src="/app.js"></script>
349
+ </body>
350
+ </html>
package/dist/web.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { type ParsedArgs } from "@cotal-ai/core";
2
+ /** The dashboard's default port and its branded address. The server binds loopback
3
+ * (127.0.0.1) but serves any Host, so `cotal.localhost` — which Chrome/Firefox/Edge
4
+ * resolve to loopback with no DNS setup — just works. (Safari may not resolve
5
+ * `*.localhost`; plain http://127.0.0.1:7799 always does.) */
6
+ export declare const WEB_PORT = 7799;
7
+ export declare const WEB_URL = "http://cotal.localhost:7799/";
8
+ /** A live observability dashboard for a space, served over HTTP + SSE. A read-only
9
+ * observer endpoint (invisible to peers) feeds the page presence, channel history,
10
+ * and a live message stream — no manager required. Bound to loopback. */
11
+ export declare function web(args: ParsedArgs): Promise<void>;
12
+ //# sourceMappingURL=web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAKA,OAAO,EAQL,KAAK,UAAU,EAChB,MAAM,gBAAgB,CAAC;AAKxB;;;+DAG+D;AAC/D,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,OAAO,iCAAwC,CAAC;AAS7D;;0EAE0E;AAC1E,wBAAsB,GAAG,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAmMzD"}
package/dist/web.js ADDED
@@ -0,0 +1,258 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createServer } from "node:http";
3
+ import { readFileSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import { dirname, join } from "node:path";
6
+ import { CotalEndpoint, deliveryOf, parseSubject, spaceWildcard, mintCreds, newIdentity, clearChannel, } from "@cotal-ai/core";
7
+ import { c, connectOrExit, userViewAuth, userViewAuthOrExit } from "@cotal-ai/workspace";
8
+ const here = dirname(fileURLToPath(import.meta.url));
9
+ /** The dashboard's default port and its branded address. The server binds loopback
10
+ * (127.0.0.1) but serves any Host, so `cotal.localhost` — which Chrome/Firefox/Edge
11
+ * resolve to loopback with no DNS setup — just works. (Safari may not resolve
12
+ * `*.localhost`; plain http://127.0.0.1:7799 always does.) */
13
+ export const WEB_PORT = 7799;
14
+ export const WEB_URL = `http://cotal.localhost:${WEB_PORT}/`;
15
+ const PAGE = {
16
+ "/": { path: join(here, "web/index.html"), type: "text/html; charset=utf-8" },
17
+ "/app.js": { path: join(here, "web/app.js"), type: "text/javascript; charset=utf-8" },
18
+ "/graph": { path: join(here, "web/graph.html"), type: "text/html; charset=utf-8" },
19
+ "/graph.js": { path: join(here, "web/graph.js"), type: "text/javascript; charset=utf-8" },
20
+ };
21
+ /** A live observability dashboard for a space, served over HTTP + SSE. A read-only
22
+ * observer endpoint (invisible to peers) feeds the page presence, channel history,
23
+ * and a live message stream — no manager required. Bound to loopback. */
24
+ export async function web(args) {
25
+ const values = args.values;
26
+ // Resolve WHICH running mesh + creds (admin god-view: shows DMs + anycast), then DROP the account
27
+ // seed. The dashboard is a loopback HTTP process; holding the space signing seed (`auth` — it can
28
+ // mint ANY identity/role) for the whole session would make a dashboard compromise = full account
29
+ // control. Instead pre-mint ONE scoped `channel-purger` cred for the only write path (channel delete
30
+ // = filtered CHAT purge + a channel-registry key delete) and let the seed fall out of scope here, so
31
+ // it isn't reachable from the request handlers. `--creds` / open mode have no seed → the connection
32
+ // creds carry the purge rights.
33
+ //
34
+ // USER MODE: the god view rides an exchange-gated "admin" VIEW bearer (ledger scope "admin",
35
+ // fresh-checked at every mint and every connect) — standing via a bearer SOURCE so the tap
36
+ // survives the ≤5-minute token life. No pre-minted purge cred: channel delete mints a one-shot
37
+ // "channel-purger" view per action, so each destructive click is a fresh ledger check, and
38
+ // `cotal actor revoke` kills the dashboard live (eviction) while a scope edit bites at the next
39
+ // refresh.
40
+ const { conn, user } = await (async () => {
41
+ const conn = await connectOrExit(values, "admin");
42
+ return { conn, user: conn.bearer ? await userViewAuthOrExit(conn, "admin") : undefined };
43
+ })();
44
+ const { server, space } = conn;
45
+ const purgeCreds = !user && conn.auth ? await mintCreds(conn.auth, newIdentity(), "channel-purger") : conn.creds;
46
+ const port = values.port ? Number(values.port) : WEB_PORT;
47
+ // Observer: never registers presence, never consumes an inbox — invisible to peers.
48
+ const ep = new CotalEndpoint({
49
+ space,
50
+ servers: server,
51
+ ...(user
52
+ ? { bearer: user.source, sentinelCreds: user.sentinelCreds, card: { owner: user.owner, actor: user.actor, name: "web", kind: "endpoint" } }
53
+ : { creds: conn.creds, card: { name: "web", kind: "endpoint" } }),
54
+ channels: [],
55
+ consume: false, // observer: reads via tap + history + presence-watch, binds no durables
56
+ registerPresence: false,
57
+ watchPresence: true,
58
+ });
59
+ ep.on("error", (e) => console.error(c.red("! " + e.message)));
60
+ await ep.start();
61
+ const clients = new Set();
62
+ const send = (res, event, data) => res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
63
+ const broadcast = (event, data) => {
64
+ for (const res of clients)
65
+ if (!res.writableEnded)
66
+ send(res, event, data);
67
+ };
68
+ // Presence changes → push the whole roster; the client just re-renders it.
69
+ ep.on("presence", () => broadcast("roster", ep.getRoster()));
70
+ // Broker-sourced channel membership (the authoritative graph spokes): push a `membership` SSE event
71
+ // on every feed change (debounced; the client re-reads the snapshot). Best-effort — a space without the
72
+ // feed (no delivery daemon, or provisioned before this feature) simply never emits, and the graph
73
+ // degrades to traffic-only. The admin cred carries the read grant; agents never do.
74
+ let membershipWatch;
75
+ const pushMembership = debounce(() => {
76
+ void ep.readMembership().then((m) => broadcast("membership", m)).catch(() => { });
77
+ }, 150);
78
+ try {
79
+ membershipWatch = await ep.watchMembership(pushMembership);
80
+ }
81
+ catch (e) {
82
+ console.error(c.dim(`• membership feed unavailable - graph shows traffic only (${e.message})`));
83
+ }
84
+ // Every comm on the mesh (chat / unicast / anycast) → push to the live feed. The admin cred
85
+ // allows the whole space, so the observer taps everything — DMs + anycast included.
86
+ const tapSubject = spaceWildcard(space);
87
+ ep.tap((subject, msg) => {
88
+ const mode = deliveryOf(subject);
89
+ if (!mode || !msg)
90
+ return;
91
+ // senderId is the subject's sender token — the *verified* publisher (the server
92
+ // policed who could publish it), vs the advisory `from` in the payload.
93
+ const senderId = parseSubject(subject)?.sender;
94
+ broadcast("message", { mode, senderId, msg });
95
+ }, { subject: tapSubject });
96
+ const httpServer = createServer(async (req, res) => {
97
+ const path = (req.url ?? "/").split("?")[0];
98
+ const query = new URLSearchParams((req.url ?? "").split("?")[1] ?? "");
99
+ if (path === "/feed") {
100
+ res.writeHead(200, {
101
+ "content-type": "text/event-stream",
102
+ "cache-control": "no-cache",
103
+ connection: "keep-alive",
104
+ });
105
+ clients.add(res);
106
+ send(res, "roster", ep.getRoster());
107
+ // Seed this client's graph with the current membership snapshot (the live tap only carries
108
+ // post-connect traffic; membership is state, so a fresh client needs it explicitly).
109
+ void ep.readMembership().then((m) => { if (!res.writableEnded)
110
+ send(res, "membership", m); }).catch(() => { });
111
+ req.on("close", () => clients.delete(res));
112
+ return;
113
+ }
114
+ if (path === "/api/meta")
115
+ return json(res, { space });
116
+ if (path === "/api/roster")
117
+ return json(res, ep.getRoster());
118
+ if (path === "/api/membership") {
119
+ // Authoritative who-is-subscribed (broker-sourced); {asOf, members:[{id,live,durable,observedAt}]}.
120
+ // An unavailable feed returns an empty snapshot so the graph cleanly degrades to traffic-only.
121
+ try {
122
+ return json(res, await ep.readMembership());
123
+ }
124
+ catch {
125
+ return json(res, { asOf: undefined, members: [] });
126
+ }
127
+ }
128
+ if (path === "/api/channels")
129
+ return json(res, await ep.listChannels());
130
+ if (path === "/api/activity") {
131
+ // Backfill the all-activity feed: merge recent channel history with DM history (the live
132
+ // SSE tap only carries messages from after a client connects). Entries are mode-tagged
133
+ // ({mode, msg}) to match the live feed so DMs render as DMs.
134
+ const limit = query.get("limit") ? Number(query.get("limit")) : 200;
135
+ const chans = await ep.listChannels();
136
+ const chat = (await Promise.all(chans.map((ch) => ep.channelHistory(ch.channel, { limit }))))
137
+ .flat()
138
+ .map((msg) => ({ mode: "chat", msg }));
139
+ const dms = (await ep.dmHistory({ limit })).map((msg) => ({ mode: "unicast", msg }));
140
+ const all = [...chat, ...dms].sort((a, b) => a.msg.ts - b.msg.ts);
141
+ return json(res, all.slice(-limit));
142
+ }
143
+ if (path === "/api/dms") {
144
+ // DM history for the Direct-messages lens (god-view); the client groups it by peer/pair.
145
+ const limit = query.get("limit") ? Number(query.get("limit")) : 500;
146
+ return json(res, await ep.dmHistory({ limit }));
147
+ }
148
+ if (path.startsWith("/api/channels/") && path.endsWith("/history")) {
149
+ const name = decodeURIComponent(path.slice("/api/channels/".length, -"/history".length));
150
+ const limit = query.get("limit") ? Number(query.get("limit")) : 200;
151
+ return json(res, await ep.channelHistory(name, { limit }));
152
+ }
153
+ // Delete a channel and its content. The only write path on this otherwise read-only
154
+ // dashboard, so it's POST-gated and guarded by a confirm in the UI. Uses the manager cred
155
+ // pre-minted at startup (auth mode) or the connection creds (open / --creds), NOT the account
156
+ // seed (which we dropped). A wildcard / missing channel is a 400.
157
+ if (path === "/api/channel/delete" && req.method === "POST") {
158
+ const body = await readBody(req).catch(() => ({}));
159
+ const channel = typeof body.channel === "string" ? body.channel : "";
160
+ if (!channel) {
161
+ res.writeHead(400, { "content-type": "application/json" });
162
+ res.end(JSON.stringify({ error: "channel required" }));
163
+ return;
164
+ }
165
+ try {
166
+ // User mode mints a one-shot channel-purger VIEW per delete — the ledger is re-checked at
167
+ // this click, and a mid-session revoke becomes this handler's 400, never a dead dashboard.
168
+ const result = user
169
+ ? await userViewAuth(conn, "channel-purger").then((p) => clearChannel({ servers: server, space, channel, bearer: p.bearer, sentinelCreds: p.sentinelCreds }))
170
+ : await clearChannel({ servers: server, space, channel, creds: purgeCreds });
171
+ return json(res, { ok: true, ...result });
172
+ }
173
+ catch (e) {
174
+ res.writeHead(400, { "content-type": "application/json" });
175
+ res.end(JSON.stringify({ error: e.message }));
176
+ return;
177
+ }
178
+ }
179
+ const file = PAGE[path];
180
+ if (file) {
181
+ // no-cache: always revalidate so a `cotal` upgrade's new dashboard code is picked up on
182
+ // reload — a stale cached graph.js silently runs old behavior (e.g. pre-fix filters).
183
+ res.writeHead(200, { "content-type": file.type, "cache-control": "no-cache" });
184
+ res.end(readFileSync(file.path));
185
+ return;
186
+ }
187
+ res.writeHead(404).end("not found");
188
+ });
189
+ // Comment ping keeps idle SSE connections alive through proxies.
190
+ const ping = setInterval(() => {
191
+ for (const res of clients)
192
+ if (!res.writableEnded)
193
+ res.write(": ping\n\n");
194
+ }, 20_000);
195
+ httpServer.on("error", (e) => {
196
+ if (e.code === "EADDRINUSE")
197
+ console.error(c.red(`Port ${port} is in use. Pass --port <n>.`));
198
+ else
199
+ console.error(c.red("! " + e.message));
200
+ process.exit(1);
201
+ });
202
+ await new Promise((ready) => httpServer.listen(port, "127.0.0.1", ready));
203
+ // Branded URL only when on the default port; a custom --port keeps the plain loopback address.
204
+ const url = port === WEB_PORT ? WEB_URL : `http://127.0.0.1:${port}/`;
205
+ console.log(`${c.bold("Cotal web")} - observing space ${c.bold(space)}`);
206
+ console.log(c.dim(" god-view - DMs + anycast visible"));
207
+ console.log(` ${c.cyan(url)} ${c.dim("(Ctrl-C to stop)")}`);
208
+ if (!values["no-open"])
209
+ openBrowser(url);
210
+ const shutdown = async () => {
211
+ clearInterval(ping);
212
+ membershipWatch?.stop();
213
+ for (const res of clients)
214
+ res.end();
215
+ httpServer.close();
216
+ await ep.stop();
217
+ process.exit(0);
218
+ };
219
+ process.on("SIGINT", () => void shutdown());
220
+ process.on("SIGTERM", () => void shutdown());
221
+ await new Promise(() => { });
222
+ }
223
+ function json(res, data) {
224
+ res.writeHead(200, { "content-type": "application/json" });
225
+ res.end(JSON.stringify(data));
226
+ }
227
+ /** Trailing-edge debounce — coalesces a burst of membership-feed deltas into one push. */
228
+ function debounce(fn, ms) {
229
+ let t;
230
+ return () => {
231
+ if (t)
232
+ clearTimeout(t);
233
+ t = setTimeout(fn, ms);
234
+ };
235
+ }
236
+ async function readBody(req) {
237
+ const chunks = [];
238
+ for await (const chunk of req)
239
+ chunks.push(chunk);
240
+ const raw = Buffer.concat(chunks).toString("utf8");
241
+ return raw ? JSON.parse(raw) : {};
242
+ }
243
+ /** Best-effort open of the dashboard in the default browser. The URL is already
244
+ * printed, so a failure here is harmless — never block startup on it. */
245
+ function openBrowser(url) {
246
+ const [cmd, args] = process.platform === "darwin"
247
+ ? ["open", [url]]
248
+ : process.platform === "win32"
249
+ ? ["cmd", ["/c", "start", "", url]]
250
+ : ["xdg-open", [url]];
251
+ try {
252
+ spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
253
+ }
254
+ catch {
255
+ /* no opener on this platform — the printed URL is the fallback */
256
+ }
257
+ }
258
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"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"}