@geminixiang/mama 0.2.0-beta.1 → 0.2.0-beta.3

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 (149) hide show
  1. package/README.md +133 -78
  2. package/dist/adapter.d.ts +22 -10
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts +10 -7
  6. package/dist/adapters/discord/bot.d.ts.map +1 -1
  7. package/dist/adapters/discord/bot.js +228 -69
  8. package/dist/adapters/discord/bot.js.map +1 -1
  9. package/dist/adapters/discord/context.d.ts.map +1 -1
  10. package/dist/adapters/discord/context.js +92 -34
  11. package/dist/adapters/discord/context.js.map +1 -1
  12. package/dist/adapters/shared.d.ts +23 -0
  13. package/dist/adapters/shared.d.ts.map +1 -0
  14. package/dist/adapters/shared.js +57 -0
  15. package/dist/adapters/shared.js.map +1 -0
  16. package/dist/adapters/slack/bot.d.ts +19 -11
  17. package/dist/adapters/slack/bot.d.ts.map +1 -1
  18. package/dist/adapters/slack/bot.js +356 -96
  19. package/dist/adapters/slack/bot.js.map +1 -1
  20. package/dist/adapters/slack/branch-manager.d.ts +21 -0
  21. package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
  22. package/dist/adapters/slack/branch-manager.js +96 -0
  23. package/dist/adapters/slack/branch-manager.js.map +1 -0
  24. package/dist/adapters/slack/context.d.ts.map +1 -1
  25. package/dist/adapters/slack/context.js +100 -67
  26. package/dist/adapters/slack/context.js.map +1 -1
  27. package/dist/adapters/slack/session.d.ts +3 -0
  28. package/dist/adapters/slack/session.d.ts.map +1 -0
  29. package/dist/adapters/slack/session.js +16 -0
  30. package/dist/adapters/slack/session.js.map +1 -0
  31. package/dist/adapters/telegram/bot.d.ts +4 -2
  32. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  33. package/dist/adapters/telegram/bot.js +141 -74
  34. package/dist/adapters/telegram/bot.js.map +1 -1
  35. package/dist/adapters/telegram/context.d.ts.map +1 -1
  36. package/dist/adapters/telegram/context.js +49 -109
  37. package/dist/adapters/telegram/context.js.map +1 -1
  38. package/dist/adapters/telegram/html.d.ts +3 -0
  39. package/dist/adapters/telegram/html.d.ts.map +1 -0
  40. package/dist/adapters/telegram/html.js +98 -0
  41. package/dist/adapters/telegram/html.js.map +1 -0
  42. package/dist/agent.d.ts +4 -11
  43. package/dist/agent.d.ts.map +1 -1
  44. package/dist/agent.js +116 -196
  45. package/dist/agent.js.map +1 -1
  46. package/dist/bindings.d.ts +1 -20
  47. package/dist/bindings.d.ts.map +1 -1
  48. package/dist/bindings.js +1 -21
  49. package/dist/bindings.js.map +1 -1
  50. package/dist/config.d.ts +9 -27
  51. package/dist/config.d.ts.map +1 -1
  52. package/dist/config.js +89 -63
  53. package/dist/config.js.map +1 -1
  54. package/dist/context.d.ts +13 -3
  55. package/dist/context.d.ts.map +1 -1
  56. package/dist/context.js +102 -18
  57. package/dist/context.js.map +1 -1
  58. package/dist/events.d.ts +18 -6
  59. package/dist/events.d.ts.map +1 -1
  60. package/dist/events.js +86 -35
  61. package/dist/events.js.map +1 -1
  62. package/dist/execution-resolver.d.ts.map +1 -1
  63. package/dist/execution-resolver.js +1 -3
  64. package/dist/execution-resolver.js.map +1 -1
  65. package/dist/instrument.d.ts.map +1 -1
  66. package/dist/instrument.js +5 -11
  67. package/dist/instrument.js.map +1 -1
  68. package/dist/{login.d.ts → login/index.d.ts} +2 -2
  69. package/dist/login/index.d.ts.map +1 -0
  70. package/dist/{login.js → login/index.js} +2 -2
  71. package/dist/login/index.js.map +1 -0
  72. package/dist/{link-server.d.ts → login/portal.d.ts} +6 -4
  73. package/dist/login/portal.d.ts.map +1 -0
  74. package/dist/login/portal.js +1453 -0
  75. package/dist/login/portal.js.map +1 -0
  76. package/dist/{link-token.d.ts → login/session.d.ts} +1 -1
  77. package/dist/login/session.d.ts.map +1 -0
  78. package/dist/{link-token.js → login/session.js} +1 -1
  79. package/dist/login/session.js.map +1 -0
  80. package/dist/main.d.ts.map +1 -1
  81. package/dist/main.js +175 -119
  82. package/dist/main.js.map +1 -1
  83. package/dist/provisioner.d.ts +17 -43
  84. package/dist/provisioner.d.ts.map +1 -1
  85. package/dist/provisioner.js +84 -50
  86. package/dist/provisioner.js.map +1 -1
  87. package/dist/sandbox/host.d.ts +0 -2
  88. package/dist/sandbox/host.d.ts.map +1 -1
  89. package/dist/sandbox/host.js +1 -5
  90. package/dist/sandbox/host.js.map +1 -1
  91. package/dist/sentry.d.ts.map +1 -1
  92. package/dist/sentry.js +2 -0
  93. package/dist/sentry.js.map +1 -1
  94. package/dist/session-policy.d.ts +13 -0
  95. package/dist/session-policy.d.ts.map +1 -0
  96. package/dist/session-policy.js +23 -0
  97. package/dist/session-policy.js.map +1 -0
  98. package/dist/session-store.d.ts +27 -1
  99. package/dist/session-store.d.ts.map +1 -1
  100. package/dist/session-store.js +162 -9
  101. package/dist/session-store.js.map +1 -1
  102. package/dist/session-view/command.d.ts +5 -0
  103. package/dist/session-view/command.d.ts.map +1 -0
  104. package/dist/session-view/command.js +11 -0
  105. package/dist/session-view/command.js.map +1 -0
  106. package/dist/session-view/portal.d.ts +9 -0
  107. package/dist/session-view/portal.d.ts.map +1 -0
  108. package/dist/session-view/portal.js +766 -0
  109. package/dist/session-view/portal.js.map +1 -0
  110. package/dist/session-view/service.d.ts +34 -0
  111. package/dist/session-view/service.d.ts.map +1 -0
  112. package/dist/session-view/service.js +380 -0
  113. package/dist/session-view/service.js.map +1 -0
  114. package/dist/session-view/store.d.ts +16 -0
  115. package/dist/session-view/store.d.ts.map +1 -0
  116. package/dist/session-view/store.js +38 -0
  117. package/dist/session-view/store.js.map +1 -0
  118. package/dist/store.d.ts +3 -6
  119. package/dist/store.d.ts.map +1 -1
  120. package/dist/store.js +15 -35
  121. package/dist/store.js.map +1 -1
  122. package/dist/tools/event.d.ts +3 -0
  123. package/dist/tools/event.d.ts.map +1 -1
  124. package/dist/tools/event.js +27 -8
  125. package/dist/tools/event.js.map +1 -1
  126. package/dist/tools/index.d.ts +3 -0
  127. package/dist/tools/index.d.ts.map +1 -1
  128. package/dist/tools/index.js +2 -2
  129. package/dist/tools/index.js.map +1 -1
  130. package/dist/ui-copy.d.ts +1 -0
  131. package/dist/ui-copy.d.ts.map +1 -1
  132. package/dist/ui-copy.js +3 -0
  133. package/dist/ui-copy.js.map +1 -1
  134. package/dist/vault-routing.d.ts +1 -2
  135. package/dist/vault-routing.d.ts.map +1 -1
  136. package/dist/vault-routing.js +1 -7
  137. package/dist/vault-routing.js.map +1 -1
  138. package/package.json +1 -1
  139. package/dist/link-server.d.ts.map +0 -1
  140. package/dist/link-server.js +0 -839
  141. package/dist/link-server.js.map +0 -1
  142. package/dist/link-token.d.ts.map +0 -1
  143. package/dist/link-token.js.map +0 -1
  144. package/dist/login.d.ts.map +0 -1
  145. package/dist/login.js.map +0 -1
  146. package/dist/vault.test.d.ts +0 -2
  147. package/dist/vault.test.d.ts.map +0 -1
  148. package/dist/vault.test.js +0 -67
  149. package/dist/vault.test.js.map +0 -1
@@ -0,0 +1,766 @@
1
+ import * as log from "../log.js";
2
+ import { loadSessionViewModel, resolveRequestedSessionFile, } from "./service.js";
3
+ export async function handleSessionViewRequest(req, res, url, sessionViewTokenStore) {
4
+ if (req.method !== "GET" || url.pathname !== "/session") {
5
+ return false;
6
+ }
7
+ const token = url.searchParams.get("token")?.trim();
8
+ if (!token || !sessionViewTokenStore) {
9
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
10
+ res.end(renderStatusPage("Session unavailable", "This session link is invalid or has expired."));
11
+ return true;
12
+ }
13
+ const entry = sessionViewTokenStore.peek(token);
14
+ if (!entry) {
15
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
16
+ res.end(renderStatusPage("Session unavailable", "This session link is invalid or has expired."));
17
+ return true;
18
+ }
19
+ const requestedSession = url.searchParams.get("session");
20
+ const targetSessionFile = resolveRequestedSessionFile(entry.sessionFile, requestedSession);
21
+ if (!targetSessionFile) {
22
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
23
+ res.end(renderStatusPage("Session unavailable", "The selected session link is invalid."));
24
+ return true;
25
+ }
26
+ try {
27
+ const model = loadSessionViewModel(targetSessionFile);
28
+ res.writeHead(200, {
29
+ "Content-Type": "text/html; charset=utf-8",
30
+ "Cache-Control": "no-store",
31
+ });
32
+ res.end(renderSessionPage(model, entry.token, entry.expiresAt));
33
+ }
34
+ catch (error) {
35
+ log.logWarning(`[${entry.conversationId}] Failed to render session ${entry.sessionFile}`, error instanceof Error ? error.message : String(error));
36
+ res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
37
+ res.end(renderStatusPage("Session unavailable", "The session could not be loaded right now."));
38
+ }
39
+ return true;
40
+ }
41
+ function renderSessionPage(model, token, expiresAt) {
42
+ const items = model.items.length > 0
43
+ ? model.items.map((item) => renderItem(item, token)).join("\n")
44
+ : `<div class="system-event"><span class="event-dot"></span><span class="event-text">No messages yet — send one to the bot, then refresh.</span></div>`;
45
+ const relatedSections = model.parent
46
+ ? `<section class="related-card stack">
47
+ <p class="eyebrow">Forked from</p>
48
+ ${renderRelationCard(model.parent, token)}
49
+ </section>`
50
+ : "";
51
+ return renderHtmlDocument(`${model.title} · Session Viewer`, `<header class="hero-card">
52
+ <div class="hero-top">
53
+ <div class="hero-title-group">
54
+ <span class="hero-wordmark">mama</span>
55
+ <h1 class="hero-title">${esc(model.title)}</h1>
56
+ </div>
57
+ <button class="refresh-btn" onclick="window.location.reload()">
58
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M12.5 2.5A6 6 0 1 0 13 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M10 2.5h2.5V5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
59
+ Refresh
60
+ </button>
61
+ </div>
62
+ <div class="stat-row">
63
+ ${renderSummaryItem("ID", model.sessionId.slice(0, 8))}
64
+ ${renderSummaryItem("File", model.fileName)}
65
+ ${renderSummaryItem("Created", formatDate(model.createdAt))}
66
+ ${renderSummaryItem("Updated", formatDate(model.updatedAt))}
67
+ ${renderSummaryItem("Entries", String(model.entryCount))}
68
+ ${renderSummaryItem("Expires", formatDate(new Date(expiresAt).toISOString()))}
69
+ </div>
70
+ </header>
71
+
72
+ ${relatedSections}
73
+
74
+ <main class="timeline-shell">
75
+ <div class="timeline-list">
76
+ ${items}
77
+ </div>
78
+ </main>`);
79
+ }
80
+ function renderSummaryItem(label, value) {
81
+ return `<span class="stat-chip"><span class="stat-label">${esc(label)}</span><strong class="stat-value">${esc(value)}</strong></span>`;
82
+ }
83
+ function renderRelationCard(relation, token) {
84
+ const href = `/session?token=${encodeURIComponent(token)}&session=${encodeURIComponent(relation.fileName)}`;
85
+ const summary = relation.summary ? `<p class="related-summary">${esc(relation.summary)}</p>` : "";
86
+ return `<a class="related-link" href="${href}">
87
+ <span class="related-copy">
88
+ <strong class="related-title">${esc(relation.title)}</strong>
89
+ ${summary}
90
+ <span class="related-meta">${esc(formatDate(relation.updatedAt))} · ${esc(String(relation.entryCount))} entries · ${esc(relation.fileName)}</span>
91
+ </span>
92
+ <span class="related-arrow" aria-hidden="true">→</span>
93
+ </a>`;
94
+ }
95
+ function renderForkLinks(relations, token) {
96
+ if (!relations || relations.length === 0)
97
+ return "";
98
+ return `<div class="fork-links">${relations
99
+ .map((relation) => {
100
+ const href = `/session?token=${encodeURIComponent(token)}&session=${encodeURIComponent(relation.fileName)}`;
101
+ return `<a class="fork-link" href="${href}" title="Open ${esc(relation.title)}">
102
+ <span class="fork-dot" aria-hidden="true"></span>
103
+ <span class="fork-text">Thread</span>
104
+ </a>`;
105
+ })
106
+ .join("")}</div>`;
107
+ }
108
+ export function parseUserBody(raw) {
109
+ // [timestamp] [username] [in-thread:ts]: content
110
+ let m = raw.match(/^\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\]\s*\[([^\]]+)\](?:\s*\[in-thread:([^\]]+)\])?:\s*([\s\S]*)$/);
111
+ if (m) {
112
+ return { username: m[1], threadTs: m[2] ?? null, content: m[3] };
113
+ }
114
+ // [username] [in-thread:ts]: content
115
+ m = raw.match(/^\[([^\]]+)\](?:\s*\[in-thread:([^\]]+)\])?:\s*([\s\S]*)$/);
116
+ if (m) {
117
+ return { username: m[1], threadTs: m[2] ?? null, content: m[3] };
118
+ }
119
+ return { username: null, threadTs: null, content: raw };
120
+ }
121
+ function renderItem(item, token) {
122
+ if (item.kind === "system") {
123
+ const parts = [item.title, item.body].filter((x) => Boolean(x)).map(esc);
124
+ const time = item.meta
125
+ ? ` · <time class="event-time">${esc(formatDate(item.meta))}</time>`
126
+ : "";
127
+ return `<div class="system-event"><span class="event-dot"></span><span class="event-text">${parts.join(" — ")}</span>${time}</div>`;
128
+ }
129
+ if (item.kind === "tool") {
130
+ const toneClass = item.tone === "err" ? " tone-err" : item.tone === "ok" ? " tone-ok" : "";
131
+ const body = item.body ? `<pre class="tool-output${toneClass}">${esc(item.body)}</pre>` : "";
132
+ const time = item.meta ? `<time class="tool-time">${esc(formatDate(item.meta))}</time>` : "";
133
+ return `<div class="tool-block">
134
+ <div class="tool-header">
135
+ <span class="tool-icon"><svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M1.5 2L5 5.5 1.5 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 9h2.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></span>
136
+ <span class="tool-name">${esc(item.title)}</span>
137
+ ${time}
138
+ </div>
139
+ ${body}
140
+ </div>`;
141
+ }
142
+ const time = item.meta ? `<time class="msg-time">${esc(formatDate(item.meta))}</time>` : "";
143
+ if (item.kind === "user") {
144
+ const { username, threadTs, content } = item.body
145
+ ? parseUserBody(item.body)
146
+ : { username: null, threadTs: null, content: "" };
147
+ const initial = username ? esc(username.slice(0, 2).toUpperCase()) : "U";
148
+ const body = content ? `<pre class="msg-body">${esc(content)}</pre>` : "";
149
+ const threadBadge = threadTs
150
+ ? `<div class="thread-badge" title="Thread ${esc(threadTs)}">Thread · <code>${esc(threadTs)}</code></div>`
151
+ : "";
152
+ const forks = renderForkLinks(item.forks, token ?? "");
153
+ return `<div class="msg-row msg-user">
154
+ <div class="user-bubble">
155
+ ${threadBadge}
156
+ ${body}
157
+ ${forks}
158
+ ${time}
159
+ </div>
160
+ <div class="msg-avatar user-avatar" title="${username ? esc(username) : "User"}">${initial}</div>
161
+ </div>`;
162
+ }
163
+ // assistant
164
+ const body = item.body ? `<pre class="msg-body">${esc(item.body)}</pre>` : "";
165
+ const forks = renderForkLinks(item.forks, token ?? "");
166
+ return `<div class="msg-row msg-assistant">
167
+ <div class="msg-avatar asst-avatar" aria-hidden="true">A</div>
168
+ <div class="asst-card">
169
+ ${body}
170
+ ${forks}
171
+ ${time}
172
+ </div>
173
+ </div>`;
174
+ }
175
+ function renderStatusPage(title, message) {
176
+ return renderHtmlDocument(title, `<section class="card stack">
177
+ <p class="eyebrow">mama</p>
178
+ <h1>${esc(title)}</h1>
179
+ <div class="status err">${esc(message)}</div>
180
+ </section>`);
181
+ }
182
+ function renderHtmlDocument(title, shellContent) {
183
+ return `<!DOCTYPE html>
184
+ <html lang="en">
185
+ <head>
186
+ <meta charset="utf-8">
187
+ <meta name="viewport" content="width=device-width, initial-scale=1">
188
+ <title>${esc(title)}</title>
189
+ <style>${styles}</style>
190
+ </head>
191
+ <body>
192
+ <main class="shell">
193
+ ${shellContent}
194
+ </main>
195
+ </body>
196
+ </html>`;
197
+ }
198
+ function formatDate(value) {
199
+ const date = new Date(value);
200
+ if (Number.isNaN(date.getTime()))
201
+ return value;
202
+ return date.toLocaleString();
203
+ }
204
+ function esc(value) {
205
+ return value
206
+ .replaceAll("&", "&amp;")
207
+ .replaceAll("<", "&lt;")
208
+ .replaceAll(">", "&gt;")
209
+ .replaceAll('"', "&quot;")
210
+ .replaceAll("'", "&#39;");
211
+ }
212
+ const styles = `
213
+ @import url('https://fonts.googleapis.com/css2?family=Lora:wght@400;600&family=DM+Sans:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
214
+
215
+ :root {
216
+ --bg: #f0ece3;
217
+ --surface: #ffffff;
218
+ --border: rgba(0, 0, 0, 0.08);
219
+ --text: #18181b;
220
+ --muted: #71717a;
221
+ --subtle: #a1a1aa;
222
+
223
+ --user-bg: #18181b;
224
+ --user-text: #fafafa;
225
+ --user-time: rgba(250, 250, 250, 0.5);
226
+
227
+ --asst-border: #22c55e;
228
+ --asst-avatar-bg: #f0fdf4;
229
+ --asst-avatar-text: #16a34a;
230
+
231
+ --tool-bg: #0d1117;
232
+ --tool-header: #161b22;
233
+ --tool-text: #c9d1d9;
234
+ --tool-accent: #58a6ff;
235
+ --tool-ok: #3fb950;
236
+ --tool-err: #f85149;
237
+ --tool-time: #484f58;
238
+
239
+ --ok-bg: #f0fdf4;
240
+ --ok-text: #15803d;
241
+ --err-bg: #fef2f2;
242
+ --err-text: #b91c1c;
243
+ }
244
+
245
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
246
+
247
+ body {
248
+ min-height: 100vh;
249
+ padding: 40px 20px 80px;
250
+ display: flex;
251
+ flex-direction: column;
252
+ align-items: center;
253
+ background-color: var(--bg);
254
+ background-image:
255
+ radial-gradient(ellipse 80% 40% at 50% -10%, rgba(255,255,255,0.6) 0%, transparent 70%);
256
+ color: var(--text);
257
+ font-family: 'DM Sans', 'Segoe UI', system-ui, sans-serif;
258
+ font-size: 15px;
259
+ line-height: 1.5;
260
+ -webkit-font-smoothing: antialiased;
261
+ }
262
+
263
+ .shell {
264
+ width: 100%;
265
+ max-width: 780px;
266
+ display: flex;
267
+ flex-direction: column;
268
+ gap: 12px;
269
+ }
270
+
271
+ /* ── Hero ─────────────────────────────────────────────────────────────── */
272
+
273
+ .hero-card {
274
+ padding: 28px 32px 24px;
275
+ border: 1px solid var(--border);
276
+ border-radius: 20px;
277
+ background: var(--surface);
278
+ box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.06);
279
+ }
280
+
281
+ .hero-top {
282
+ display: flex;
283
+ align-items: flex-start;
284
+ justify-content: space-between;
285
+ gap: 16px;
286
+ margin-bottom: 20px;
287
+ }
288
+
289
+ .hero-wordmark {
290
+ display: block;
291
+ margin-bottom: 6px;
292
+ color: var(--subtle);
293
+ font-size: 0.72rem;
294
+ font-weight: 600;
295
+ letter-spacing: 0.12em;
296
+ text-transform: uppercase;
297
+ }
298
+
299
+ .hero-title {
300
+ font-family: 'Lora', Georgia, serif;
301
+ font-size: clamp(1.4rem, 2.5vw, 1.75rem);
302
+ font-weight: 600;
303
+ line-height: 1.2;
304
+ letter-spacing: -0.01em;
305
+ color: var(--text);
306
+ text-wrap: balance;
307
+ }
308
+
309
+ .refresh-btn {
310
+ display: inline-flex;
311
+ align-items: center;
312
+ gap: 6px;
313
+ flex-shrink: 0;
314
+ padding: 7px 14px;
315
+ border: 1px solid var(--border);
316
+ border-radius: 999px;
317
+ background: transparent;
318
+ color: var(--muted);
319
+ font: 500 0.8rem/1 'DM Sans', sans-serif;
320
+ cursor: pointer;
321
+ transition: color 120ms, border-color 120ms, background 120ms;
322
+ white-space: nowrap;
323
+ }
324
+
325
+ .refresh-btn:hover {
326
+ color: var(--text);
327
+ border-color: rgba(0,0,0,0.2);
328
+ background: rgba(0,0,0,0.03);
329
+ }
330
+
331
+ .refresh-btn:focus-visible {
332
+ outline: 2px solid var(--text);
333
+ outline-offset: 2px;
334
+ }
335
+
336
+ .stat-row {
337
+ display: flex;
338
+ flex-wrap: wrap;
339
+ gap: 6px;
340
+ }
341
+
342
+ .stat-chip {
343
+ display: inline-flex;
344
+ align-items: center;
345
+ gap: 5px;
346
+ padding: 4px 10px;
347
+ border: 1px solid var(--border);
348
+ border-radius: 999px;
349
+ background: #f4f4f5;
350
+ font-size: 0.775rem;
351
+ line-height: 1;
352
+ }
353
+
354
+ .stat-label {
355
+ color: var(--muted);
356
+ font-weight: 500;
357
+ }
358
+
359
+ .stat-value {
360
+ color: var(--text);
361
+ font-weight: 600;
362
+ }
363
+
364
+ /* ── Timeline shell ───────────────────────────────────────────────────── */
365
+
366
+ .fork-links {
367
+ display: flex;
368
+ flex-wrap: wrap;
369
+ gap: 6px;
370
+ margin-top: 10px;
371
+ }
372
+
373
+ .fork-link {
374
+ display: inline-flex;
375
+ align-items: center;
376
+ gap: 6px;
377
+ padding: 5px 10px;
378
+ border-radius: 999px;
379
+ border: 1px solid rgba(239, 68, 68, 0.18);
380
+ background: rgba(254, 242, 242, 0.95);
381
+ color: #b91c1c;
382
+ text-decoration: none;
383
+ font-size: 0.74rem;
384
+ font-weight: 600;
385
+ line-height: 1;
386
+ transition: transform 120ms, background 120ms, border-color 120ms;
387
+ }
388
+
389
+ .fork-link:hover {
390
+ transform: translateY(-1px);
391
+ background: #fff1f2;
392
+ border-color: rgba(239, 68, 68, 0.28);
393
+ }
394
+
395
+ .fork-dot {
396
+ width: 7px;
397
+ height: 7px;
398
+ border-radius: 50%;
399
+ background: #ef4444;
400
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.12);
401
+ flex-shrink: 0;
402
+ }
403
+
404
+ .fork-text {
405
+ white-space: nowrap;
406
+ }
407
+
408
+ .related-card {
409
+ padding: 18px 20px;
410
+ border: 1px solid var(--border);
411
+ border-radius: 18px;
412
+ background: rgba(255,255,255,0.78);
413
+ box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.04);
414
+ backdrop-filter: blur(12px);
415
+ }
416
+
417
+ .related-list {
418
+ display: flex;
419
+ flex-direction: column;
420
+ gap: 10px;
421
+ }
422
+
423
+ .related-link {
424
+ display: flex;
425
+ align-items: center;
426
+ justify-content: space-between;
427
+ gap: 12px;
428
+ padding: 12px 14px;
429
+ border-radius: 14px;
430
+ border: 1px solid var(--border);
431
+ background: rgba(255,255,255,0.82);
432
+ color: inherit;
433
+ text-decoration: none;
434
+ transition: transform 120ms, border-color 120ms, box-shadow 120ms, background 120ms;
435
+ }
436
+
437
+ .related-link:hover {
438
+ transform: translateY(-1px);
439
+ border-color: rgba(0,0,0,0.16);
440
+ background: #fff;
441
+ box-shadow: 0 8px 18px rgba(0,0,0,0.05);
442
+ }
443
+
444
+ .related-copy {
445
+ min-width: 0;
446
+ display: flex;
447
+ flex-direction: column;
448
+ gap: 4px;
449
+ }
450
+
451
+ .related-title {
452
+ color: var(--text);
453
+ font-size: 0.94rem;
454
+ line-height: 1.3;
455
+ }
456
+
457
+ .related-summary {
458
+ color: var(--muted);
459
+ font-size: 0.82rem;
460
+ line-height: 1.45;
461
+ }
462
+
463
+ .related-meta {
464
+ color: var(--subtle);
465
+ font-size: 0.74rem;
466
+ line-height: 1.4;
467
+ }
468
+
469
+ .related-arrow {
470
+ flex-shrink: 0;
471
+ color: var(--subtle);
472
+ font-size: 1rem;
473
+ }
474
+
475
+ .timeline-shell {
476
+ padding: 20px 0;
477
+ }
478
+
479
+ .timeline-list {
480
+ display: flex;
481
+ flex-direction: column;
482
+ gap: 4px;
483
+ }
484
+
485
+ /* ── Message rows ─────────────────────────────────────────────────────── */
486
+
487
+ .msg-row {
488
+ display: flex;
489
+ align-items: flex-end;
490
+ gap: 8px;
491
+ padding: 2px 0;
492
+ }
493
+
494
+ /* ── User messages ────────────────────────────────────────────────────── */
495
+
496
+ .msg-user {
497
+ justify-content: flex-end;
498
+ }
499
+
500
+ .user-bubble {
501
+ max-width: 85%;
502
+ padding: 12px 16px;
503
+ border-radius: 18px 18px 4px 18px;
504
+ background: var(--user-bg);
505
+ color: var(--user-text);
506
+ box-shadow: 0 1px 2px rgba(0,0,0,0.12);
507
+ }
508
+
509
+ .thread-badge {
510
+ display: inline-flex;
511
+ align-items: center;
512
+ gap: 6px;
513
+ margin-bottom: 8px;
514
+ padding: 4px 10px;
515
+ border-radius: 999px;
516
+ background: rgba(255,255,255,0.22);
517
+ color: var(--user-text);
518
+ font-size: 0.68rem;
519
+ font-weight: 700;
520
+ letter-spacing: 0.01em;
521
+ }
522
+
523
+ .thread-badge code {
524
+ font-family: 'JetBrains Mono', ui-monospace, monospace;
525
+ font-size: 0.66rem;
526
+ background: rgba(255,255,255,0.16);
527
+ padding: 1px 6px;
528
+ border-radius: 999px;
529
+ color: inherit;
530
+ }
531
+
532
+ .msg-user .msg-body {
533
+ font-family: 'DM Sans', system-ui, sans-serif;
534
+ font-size: 0.9rem;
535
+ line-height: 1.6;
536
+ white-space: pre-wrap;
537
+ word-break: break-word;
538
+ color: var(--user-text);
539
+ }
540
+
541
+ .msg-user .msg-time {
542
+ display: block;
543
+ margin-top: 6px;
544
+ font-size: 0.72rem;
545
+ color: var(--user-time);
546
+ text-align: right;
547
+ }
548
+
549
+ /* ── Avatars ──────────────────────────────────────────────────────────── */
550
+
551
+ .msg-avatar {
552
+ flex: 0 0 28px;
553
+ width: 28px;
554
+ height: 28px;
555
+ border-radius: 50%;
556
+ font-size: 0.68rem;
557
+ font-weight: 700;
558
+ display: flex;
559
+ align-items: center;
560
+ justify-content: center;
561
+ letter-spacing: 0;
562
+ flex-shrink: 0;
563
+ }
564
+
565
+ .user-avatar {
566
+ background: #eff6ff;
567
+ border: 1.5px solid #93c5fd;
568
+ color: #1d4ed8;
569
+ }
570
+
571
+ .asst-avatar {
572
+ background: var(--asst-avatar-bg);
573
+ border: 1.5px solid var(--asst-border);
574
+ color: var(--asst-avatar-text);
575
+ margin-bottom: 2px;
576
+ }
577
+
578
+ /* ── Assistant messages ───────────────────────────────────────────────── */
579
+
580
+ .msg-assistant {
581
+ align-items: flex-end;
582
+ gap: 8px;
583
+ max-width: 85%;
584
+ }
585
+
586
+ .asst-card {
587
+ min-width: 0;
588
+ padding: 14px 18px;
589
+ border: 1px solid var(--border);
590
+ border-radius: 18px 18px 18px 4px;
591
+ background: var(--surface);
592
+ box-shadow: 0 1px 3px rgba(0,0,0,0.04);
593
+ }
594
+
595
+ .msg-assistant .msg-body {
596
+ font-family: 'DM Sans', system-ui, sans-serif;
597
+ font-size: 0.9rem;
598
+ line-height: 1.65;
599
+ white-space: pre-wrap;
600
+ word-break: break-word;
601
+ color: var(--text);
602
+ }
603
+
604
+ .msg-assistant .msg-time {
605
+ display: block;
606
+ margin-top: 8px;
607
+ font-size: 0.72rem;
608
+ color: var(--subtle);
609
+ }
610
+
611
+ /* ── Tool blocks ──────────────────────────────────────────────────────── */
612
+
613
+ .tool-block {
614
+ max-width: 92%;
615
+ margin-left: 36px;
616
+ border-radius: 10px;
617
+ overflow: hidden;
618
+ border: 1px solid rgba(255,255,255,0.06);
619
+ box-shadow: 0 2px 8px rgba(0,0,0,0.16);
620
+ margin: 6px 0;
621
+ }
622
+
623
+ .tool-header {
624
+ display: flex;
625
+ align-items: center;
626
+ gap: 8px;
627
+ padding: 8px 14px;
628
+ background: var(--tool-header);
629
+ border-bottom: 1px solid rgba(255,255,255,0.06);
630
+ overflow: hidden;
631
+ }
632
+
633
+ .tool-icon {
634
+ color: var(--tool-accent);
635
+ flex-shrink: 0;
636
+ display: flex;
637
+ align-items: center;
638
+ }
639
+
640
+ .tool-name {
641
+ flex: 1;
642
+ font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
643
+ font-size: 0.75rem;
644
+ font-weight: 500;
645
+ color: var(--tool-accent);
646
+ white-space: nowrap;
647
+ overflow: hidden;
648
+ text-overflow: ellipsis;
649
+ }
650
+
651
+ .tool-time {
652
+ flex-shrink: 0;
653
+ font-family: 'JetBrains Mono', ui-monospace, monospace;
654
+ font-size: 0.7rem;
655
+ color: var(--tool-time);
656
+ }
657
+
658
+ .tool-output {
659
+ display: block;
660
+ padding: 12px 14px;
661
+ background: var(--tool-bg);
662
+ color: var(--tool-text);
663
+ font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
664
+ font-size: 0.78rem;
665
+ line-height: 1.6;
666
+ white-space: pre-wrap;
667
+ word-break: break-word;
668
+ overflow-x: auto;
669
+ max-height: 400px;
670
+ overflow-y: auto;
671
+ }
672
+
673
+ .tool-output.tone-ok { color: var(--tool-ok); }
674
+ .tool-output.tone-err { color: var(--tool-err); }
675
+
676
+ /* ── System events ────────────────────────────────────────────────────── */
677
+
678
+ .system-event {
679
+ display: flex;
680
+ align-items: center;
681
+ justify-content: center;
682
+ gap: 6px;
683
+ padding: 10px 0;
684
+ color: var(--subtle);
685
+ font-size: 0.775rem;
686
+ }
687
+
688
+ .event-dot {
689
+ width: 4px;
690
+ height: 4px;
691
+ border-radius: 50%;
692
+ background: var(--subtle);
693
+ flex-shrink: 0;
694
+ opacity: 0.6;
695
+ }
696
+
697
+ .event-text {
698
+ color: var(--muted);
699
+ }
700
+
701
+ .event-time {
702
+ color: var(--subtle);
703
+ font-style: normal;
704
+ }
705
+
706
+ /* ── Status page ──────────────────────────────────────────────────────── */
707
+
708
+ .card {
709
+ padding: 28px 32px;
710
+ border: 1px solid var(--border);
711
+ border-radius: 20px;
712
+ background: var(--surface);
713
+ box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.06);
714
+ }
715
+
716
+ .stack > * + * { margin-top: 14px; }
717
+
718
+ .eyebrow {
719
+ color: var(--subtle);
720
+ font-size: 0.72rem;
721
+ font-weight: 600;
722
+ letter-spacing: 0.12em;
723
+ text-transform: uppercase;
724
+ }
725
+
726
+ h1 {
727
+ font-family: 'Lora', Georgia, serif;
728
+ font-size: clamp(1.4rem, 2.5vw, 1.75rem);
729
+ font-weight: 600;
730
+ letter-spacing: -0.01em;
731
+ line-height: 1.2;
732
+ }
733
+
734
+ p { color: var(--muted); font-size: 0.9rem; line-height: 1.5; }
735
+
736
+ .status {
737
+ padding: 12px 16px;
738
+ border-radius: 10px;
739
+ font-size: 0.9rem;
740
+ }
741
+
742
+ .status.err {
743
+ background: var(--err-bg);
744
+ color: var(--err-text);
745
+ border: 1px solid rgba(185, 28, 28, 0.12);
746
+ }
747
+
748
+ /* ── Responsive ───────────────────────────────────────────────────────── */
749
+
750
+ @media (max-width: 600px) {
751
+ body { padding: 20px 12px 60px; }
752
+
753
+ .hero-card, .card { padding: 20px; border-radius: 16px; }
754
+
755
+ .hero-top { flex-direction: column; gap: 12px; }
756
+
757
+ .refresh-btn { align-self: flex-start; }
758
+
759
+ .user-bubble { max-width: 88%; }
760
+
761
+ .asst-avatar { display: none; }
762
+
763
+ .asst-card { border-radius: 4px 14px 14px 14px; }
764
+ }
765
+ `;
766
+ //# sourceMappingURL=portal.js.map