bloby-bot 0.51.4 → 0.52.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.
- package/dist-bloby/assets/{bloby-vi0Xitb-.js → bloby-CjvuL1QI.js} +77 -77
- package/dist-bloby/assets/{globals-DNO3ilRx.js → globals-UaNdQXf5.js} +3 -3
- package/dist-bloby/assets/globals-ZdFBsH0Q.css +2 -0
- package/dist-bloby/assets/{highlighted-body-OFNGDK62-DMeCY5Rc.js → highlighted-body-OFNGDK62-D0QQRXpE.js} +1 -1
- package/dist-bloby/assets/mermaid-GHXKKRXX-D9GshF2B.js +1 -0
- package/dist-bloby/assets/{onboard-D8sRPjz2.js → onboard-DU3OfA5h.js} +1 -1
- package/dist-bloby/bloby.html +3 -3
- package/dist-bloby/onboard.html +3 -3
- package/package.json +5 -3
- package/supervisor/chat/OnboardWizard.tsx +16 -16
- package/supervisor/chat/bloby-main.tsx +40 -7
- package/supervisor/chat/src/components/Chat/MessageBubble.tsx +17 -3
- package/supervisor/chat/src/components/Chat/MessageList.tsx +7 -1
- package/supervisor/chat/src/components/Chat/NotchCard.tsx +70 -0
- package/supervisor/chat/src/components/LoginScreen.tsx +3 -3
- package/supervisor/chat/src/styles/globals.css +18 -18
- package/supervisor/harnesses/claude.ts +31 -1
- package/supervisor/harnesses/codex.ts +15 -1
- package/supervisor/index.ts +48 -19
- package/worker/prompts/bloby-system-prompt.txt +1 -1
- package/workspace/client/src/components/Dashboard/DashboardPage.tsx +4 -4
- package/workspace/client/src/components/Layout/Sidebar.tsx +1 -1
- package/workspace/client/src/components/deleteme_onboarding/tour-theme.css +1 -1
- package/workspace/client/src/styles/globals.css +18 -18
- package/workspace/skills/mac/SKILL.md +302 -0
- package/workspace/skills/mac/frequentSnippets/calendar-today.html +22 -0
- package/workspace/skills/mac/frequentSnippets/info-card.html +27 -0
- package/workspace/skills/mac/frequentSnippets/single-stat.html +13 -0
- package/workspace/skills/mac/presets/PRESETS.md +226 -0
- package/workspace/skills/mac/skill.json +15 -0
- package/dist-bloby/assets/globals-D60b-8LY.css +0 -2
- package/dist-bloby/assets/mermaid-GHXKKRXX-BOqNyL14.js +0 -1
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
--color-accent-foreground: #EBEBEB;
|
|
23
23
|
|
|
24
24
|
/* ── Primary ── */
|
|
25
|
-
--color-primary: #
|
|
25
|
+
--color-primary: #0069FE;
|
|
26
26
|
--color-primary-foreground: #ffffff;
|
|
27
27
|
|
|
28
28
|
/* ── Danger ── */
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
/* ── Borders & inputs ── */
|
|
33
33
|
--color-border: #333333;
|
|
34
34
|
--color-input: #333333;
|
|
35
|
-
--color-ring: #
|
|
35
|
+
--color-ring: #0069FE;
|
|
36
36
|
|
|
37
37
|
/* ── Charts ── */
|
|
38
|
-
--color-chart-1: #
|
|
38
|
+
--color-chart-1: #0069FE;
|
|
39
39
|
--color-chart-2: #F04D68;
|
|
40
40
|
--color-chart-3: #F59E0B;
|
|
41
41
|
--color-chart-4: #8B5CF6;
|
|
@@ -44,12 +44,12 @@
|
|
|
44
44
|
/* ── Code blocks (Streamdown uses bg-sidebar / bg-background) ── */
|
|
45
45
|
--color-sidebar: #222222;
|
|
46
46
|
--color-sidebar-foreground: #EBEBEB;
|
|
47
|
-
--color-sidebar-primary: #
|
|
47
|
+
--color-sidebar-primary: #0069FE;
|
|
48
48
|
--color-sidebar-primary-foreground: #ffffff;
|
|
49
49
|
--color-sidebar-accent: #2A2A2A;
|
|
50
50
|
--color-sidebar-accent-foreground: #EBEBEB;
|
|
51
51
|
--color-sidebar-border: #333333;
|
|
52
|
-
--color-sidebar-ring: #
|
|
52
|
+
--color-sidebar-ring: #0069FE;
|
|
53
53
|
|
|
54
54
|
--radius: 0.75rem;
|
|
55
55
|
}
|
|
@@ -68,7 +68,7 @@ body {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
::selection {
|
|
71
|
-
background-color: rgba(
|
|
71
|
+
background-color: rgba(0, 105, 254, 0.25);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
::-webkit-scrollbar { width: 6px; }
|
|
@@ -81,16 +81,16 @@ body {
|
|
|
81
81
|
-webkit-background-clip: text;
|
|
82
82
|
color: transparent;
|
|
83
83
|
-webkit-text-fill-color: transparent;
|
|
84
|
-
background-image: linear-gradient(135deg, #
|
|
84
|
+
background-image: linear-gradient(135deg, #0166FF, #009AFE, #4AEEFF);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
.bg-gradient-brand {
|
|
88
|
-
background-image: linear-gradient(135deg, #
|
|
88
|
+
background-image: linear-gradient(135deg, #0166FF, #009AFE, #4AEEFF);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
.glow-border {
|
|
92
|
-
box-shadow: 0 0 0 1px rgba(
|
|
93
|
-
0 0 20px -5px rgba(
|
|
92
|
+
box-shadow: 0 0 0 1px rgba(0, 105, 254, 0.1),
|
|
93
|
+
0 0 20px -5px rgba(0, 105, 254, 0.15);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
.animated-border {
|
|
@@ -103,10 +103,10 @@ body {
|
|
|
103
103
|
inset: -150%;
|
|
104
104
|
background: conic-gradient(
|
|
105
105
|
from 0deg,
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
106
|
+
#0166FF,
|
|
107
|
+
#009AFE,
|
|
108
|
+
#4AEEFF,
|
|
109
|
+
#0166FF
|
|
110
110
|
);
|
|
111
111
|
animation: border-spin 3s linear infinite;
|
|
112
112
|
}
|
|
@@ -120,10 +120,10 @@ body {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
.input-glow:focus {
|
|
123
|
-
border-color: rgba(
|
|
124
|
-
box-shadow: 0 0 0 1px rgba(
|
|
125
|
-
0 0 20px -5px rgba(
|
|
126
|
-
0 0 4px -1px rgba(
|
|
123
|
+
border-color: rgba(0, 105, 254, 0.4);
|
|
124
|
+
box-shadow: 0 0 0 1px rgba(0, 105, 254, 0.15),
|
|
125
|
+
0 0 20px -5px rgba(0, 105, 254, 0.25),
|
|
126
|
+
0 0 4px -1px rgba(74, 238, 255, 0.1);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
@keyframes border-spin {
|
|
@@ -77,6 +77,9 @@ interface LiveConversation {
|
|
|
77
77
|
onMessage: (type: string, data: any) => void;
|
|
78
78
|
/** True while the model is actively processing (between message push and result) */
|
|
79
79
|
busy: boolean;
|
|
80
|
+
/** Messages pushed but not yet completed (1 result per message). Used to know when
|
|
81
|
+
* the session is truly idle — i.e. no queued message — so it's safe to recycle. */
|
|
82
|
+
pendingCount: number;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
const liveConversations = new Map<string, LiveConversation>();
|
|
@@ -217,6 +220,12 @@ async function buildConversationOptions(
|
|
|
217
220
|
mcpServers,
|
|
218
221
|
agents,
|
|
219
222
|
agentProgressSummaries: true,
|
|
223
|
+
// Auto-compaction: the live conversation is a single long-lived query() whose
|
|
224
|
+
// context grows every turn (messages + tool results + sub-agent transcripts).
|
|
225
|
+
// Enable it explicitly via inline settings so it does NOT depend on filesystem
|
|
226
|
+
// settings.json being present — when context fills, the SDK summarizes older
|
|
227
|
+
// history and continues instead of hitting the hard context wall.
|
|
228
|
+
settings: { autoCompactEnabled: true },
|
|
220
229
|
env: {
|
|
221
230
|
...process.env as Record<string, string>,
|
|
222
231
|
CLAUDE_CODE_OAUTH_TOKEN: oauthToken,
|
|
@@ -297,6 +306,7 @@ export async function startConversation(
|
|
|
297
306
|
queryHandle: null,
|
|
298
307
|
onMessage,
|
|
299
308
|
busy: false,
|
|
309
|
+
pendingCount: 0,
|
|
300
310
|
};
|
|
301
311
|
liveConversations.set(conversationId, conv);
|
|
302
312
|
|
|
@@ -369,7 +379,26 @@ export async function startConversation(
|
|
|
369
379
|
// Signal turn complete — backend restart + UI update
|
|
370
380
|
const FILE_TOOLS = ['Write', 'Edit'];
|
|
371
381
|
const usedFileTools = FILE_TOOLS.some((t) => usedTools.has(t));
|
|
372
|
-
|
|
382
|
+
|
|
383
|
+
// Context-size signal for the orchestrator's proactive session recycling.
|
|
384
|
+
// Prefer modelUsage (carries the per-model contextWindow); fall back to raw usage.
|
|
385
|
+
let contextTokens = 0;
|
|
386
|
+
let contextWindow = 0;
|
|
387
|
+
const modelUsage = (msg as any).modelUsage as Record<string, any> | undefined;
|
|
388
|
+
if (modelUsage) {
|
|
389
|
+
for (const mu of Object.values(modelUsage)) {
|
|
390
|
+
const used = (mu?.inputTokens || 0) + (mu?.cacheReadInputTokens || 0) + (mu?.cacheCreationInputTokens || 0);
|
|
391
|
+
if (used > contextTokens) { contextTokens = used; contextWindow = mu?.contextWindow || 0; }
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (!contextTokens) {
|
|
395
|
+
const u = (msg as any).usage || {};
|
|
396
|
+
contextTokens = (u.input_tokens || 0) + (u.cache_read_input_tokens || 0) + (u.cache_creation_input_tokens || 0);
|
|
397
|
+
}
|
|
398
|
+
// One result per pushed message → decrement; idle when nothing else is queued.
|
|
399
|
+
conv.pendingCount = Math.max(0, (conv.pendingCount || 0) - 1);
|
|
400
|
+
const idle = conv.pendingCount === 0;
|
|
401
|
+
onMessage('bot:turn-complete', { conversationId, usedFileTools, contextTokens, contextWindow, idle });
|
|
373
402
|
|
|
374
403
|
// Reset per-turn state
|
|
375
404
|
usedTools.clear();
|
|
@@ -482,6 +511,7 @@ export function pushMessage(
|
|
|
482
511
|
|
|
483
512
|
const userMessage = buildUserMessage(content, attachments, savedFiles);
|
|
484
513
|
conv.busy = true;
|
|
514
|
+
conv.pendingCount = (conv.pendingCount || 0) + 1;
|
|
485
515
|
conv.inputQueue.push(userMessage);
|
|
486
516
|
|
|
487
517
|
// Emit typing indicator
|
|
@@ -483,7 +483,16 @@ function handleNotification(conv: CodexConversation, n: { method: string; params
|
|
|
483
483
|
conv.onMessage('bot:done', { conversationId: conv.id, usedFileTools: conv.usedFileTools });
|
|
484
484
|
teardownConversation(conv.id);
|
|
485
485
|
} else {
|
|
486
|
-
|
|
486
|
+
// Context-size signal for the orchestrator's proactive session recycling.
|
|
487
|
+
// The codex app-server reports token usage on turn/completed; field names vary
|
|
488
|
+
// across versions, so probe defensively (0 if absent → falls back to codex's
|
|
489
|
+
// own built-in auto-compaction).
|
|
490
|
+
const tu: any = p.turn?.usage || p.usage || {};
|
|
491
|
+
const contextTokens = tu.input_tokens ?? tu.inputTokens ?? tu.total_tokens ?? tu.totalTokens ?? tu.tokens ?? 0;
|
|
492
|
+
const contextWindow = tu.context_window ?? tu.contextWindow ?? 0;
|
|
493
|
+
// idle = no message queued behind this turn (the drain happens just below).
|
|
494
|
+
const idle = conv.pendingInputs.length === 0;
|
|
495
|
+
conv.onMessage('bot:turn-complete', { conversationId: conv.id, usedFileTools: conv.usedFileTools, contextTokens, contextWindow, idle });
|
|
487
496
|
|
|
488
497
|
// Drain any messages that were submitted while we were busy.
|
|
489
498
|
const next = conv.pendingInputs.shift();
|
|
@@ -558,6 +567,11 @@ async function spawnAndInitialize(
|
|
|
558
567
|
log.info(`[codex] init conversation ${conversationId} (model=${modelId}${effort ? `, effort=${effort}` : ''})`);
|
|
559
568
|
await rpc.request('initialize', { clientInfo: CLIENT_INFO });
|
|
560
569
|
rpc.notify('initialized', {});
|
|
570
|
+
// Context auto-compaction is ON by default in the codex app-server: when the
|
|
571
|
+
// thread's token count crosses the model's threshold it compacts history in
|
|
572
|
+
// place (emitting a `contextCompaction` item) and continues — no flag needed
|
|
573
|
+
// here. A manual trigger also exists (`thread/compact/start`) if we ever want
|
|
574
|
+
// to force it from the UI.
|
|
561
575
|
const startResult = await rpc.request<{ thread: { id: string } }>('thread/start', {
|
|
562
576
|
cwd: WORKSPACE_DIR,
|
|
563
577
|
model: modelId,
|
package/supervisor/index.ts
CHANGED
|
@@ -31,6 +31,19 @@ import { ChannelManager } from './channels/manager.js';
|
|
|
31
31
|
const DIST_BLOBY = path.join(PKG_DIR, 'dist-bloby');
|
|
32
32
|
const SUPERVISOR_PUBLIC = path.join(PKG_DIR, 'supervisor', 'public');
|
|
33
33
|
|
|
34
|
+
// Proactive context recycling. The chat runs as one long-lived agent session per
|
|
35
|
+
// conversation (so the user can keep talking while the agent works). That session's
|
|
36
|
+
// context grows every turn and would eventually hit the wall. But continuity does NOT
|
|
37
|
+
// live in that session — every fresh session re-injects the recent messages + memory
|
|
38
|
+
// files — so when the context grows large AND the agent is idle, we end the session;
|
|
39
|
+
// the next message starts a clean one. This keeps heavy/long chats off the wall
|
|
40
|
+
// without lossy compaction, while preserving mid-turn responsiveness (we only recycle
|
|
41
|
+
// between turns). When the harness reports the model's context window we recycle at
|
|
42
|
+
// RECYCLE_FRACTION of it (adaptive to 200k vs 1M windows); otherwise we fall back to a
|
|
43
|
+
// fixed token budget. Auto-compaction stays on as the in-turn safety net.
|
|
44
|
+
const CONTEXT_RECYCLE_TOKENS = Number(process.env.BLOBY_CONTEXT_RECYCLE_TOKENS) || 140_000;
|
|
45
|
+
const CONTEXT_RECYCLE_FRACTION = Number(process.env.BLOBY_CONTEXT_RECYCLE_FRACTION) || 0.7;
|
|
46
|
+
|
|
34
47
|
// Platform assets that must survive workspace swaps — served directly by supervisor
|
|
35
48
|
const PLATFORM_ASSETS = new Set([
|
|
36
49
|
'/spritesheet.webp',
|
|
@@ -502,7 +515,7 @@ export async function startSupervisor() {
|
|
|
502
515
|
const connected = status?.connected || false;
|
|
503
516
|
res.writeHead(200);
|
|
504
517
|
const confettiHTML = Array.from({ length: 30 }, (_, i) => {
|
|
505
|
-
const colors = ['#
|
|
518
|
+
const colors = ['#0166FF', '#009AFE', '#4AEEFF', '#4ade80', '#facc15', '#818cf8'];
|
|
506
519
|
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
507
520
|
const left = Math.random() * 100;
|
|
508
521
|
const delay = i * 0.04;
|
|
@@ -521,7 +534,7 @@ export async function startSupervisor() {
|
|
|
521
534
|
body{background:#212121;color:#f5f5f5;font-family:'Inter',system-ui,-apple-system,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100dvh;margin:0;overflow-x:hidden}
|
|
522
535
|
.container{display:flex;flex-direction:column;align-items:center;max-width:360px;width:100%;padding:20px}
|
|
523
536
|
|
|
524
|
-
.qr-card{background:#2a2a2a;border:1px solid rgba(255,255,255,0.08);border-radius:20px;padding:28px;width:100%;box-shadow:0 0 0 1px rgba(
|
|
537
|
+
.qr-card{background:#2a2a2a;border:1px solid rgba(255,255,255,0.08);border-radius:20px;padding:28px;width:100%;box-shadow:0 0 0 1px rgba(0, 105, 254,0.1),0 0 20px -5px rgba(0, 105, 254,0.15);animation:fade-up .5s ease-out both}
|
|
525
538
|
.qr-inner{background:#fff;border-radius:12px;padding:16px}
|
|
526
539
|
.qr-inner svg{width:100%;height:auto;display:block}
|
|
527
540
|
|
|
@@ -533,22 +546,22 @@ export async function startSupervisor() {
|
|
|
533
546
|
|
|
534
547
|
.phone-section{width:100%;animation:fade-up .5s ease-out .4s both}
|
|
535
548
|
.phone-toggle{background:none;border:none;color:#888;font-size:13px;cursor:pointer;font-family:inherit;padding:4px 0;transition:color .2s;width:100%;text-align:center}
|
|
536
|
-
.phone-toggle:hover{color:#
|
|
549
|
+
.phone-toggle:hover{color:#0069FE}
|
|
537
550
|
|
|
538
551
|
.phone-form{display:none;width:100%;margin-top:16px;animation:fade-up .3s ease-out both}
|
|
539
552
|
.phone-form.visible{display:flex;flex-direction:column;align-items:center;gap:12px}
|
|
540
553
|
.phone-input-wrap{display:flex;gap:8px;width:100%}
|
|
541
554
|
.phone-input{flex:1;background:#2a2a2a;border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:12px 14px;color:#f5f5f5;font-size:15px;font-family:inherit;outline:none;transition:border-color .2s}
|
|
542
|
-
.phone-input:focus{border-color:#
|
|
555
|
+
.phone-input:focus{border-color:#0069FE}
|
|
543
556
|
.phone-input::placeholder{color:#555}
|
|
544
|
-
.phone-btn{background:linear-gradient(135deg
|
|
557
|
+
.phone-btn{background:linear-gradient(135deg, #0166FF, #009AFE);border:none;border-radius:10px;padding:12px 20px;color:#fff;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;white-space:nowrap;transition:opacity .2s}
|
|
545
558
|
.phone-btn:hover{opacity:.9}
|
|
546
559
|
.phone-btn:disabled{opacity:.5;cursor:not-allowed}
|
|
547
560
|
.phone-hint{font-size:12px;color:#555;text-align:center}
|
|
548
561
|
|
|
549
562
|
.code-display{display:none;width:100%;margin-top:16px;text-align:center;animation:fade-up .3s ease-out both}
|
|
550
563
|
.code-display.visible{display:block}
|
|
551
|
-
.code-value{font-family:'Space Grotesk',monospace;font-size:32px;font-weight:700;letter-spacing:6px;background:linear-gradient(135deg
|
|
564
|
+
.code-value{font-family:'Space Grotesk',monospace;font-size:32px;font-weight:700;letter-spacing:6px;background:linear-gradient(135deg, #0166FF, #009AFE);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin:12px 0}
|
|
552
565
|
.code-steps{font-size:12px;color:#666;line-height:1.8;text-align:left;margin-top:12px;padding:0 8px}
|
|
553
566
|
.code-steps li{margin-bottom:2px}
|
|
554
567
|
.code-error{color:#FB4072;font-size:13px;margin-top:8px}
|
|
@@ -562,7 +575,7 @@ export async function startSupervisor() {
|
|
|
562
575
|
@keyframes pop-in{0%{transform:scale(0);opacity:0}100%{transform:scale(1);opacity:1}}
|
|
563
576
|
|
|
564
577
|
.text-wrap{text-align:center;animation:fade-up .5s ease-out .3s both}
|
|
565
|
-
.title{font-family:'Space Grotesk',sans-serif;font-size:22px;font-weight:700;background:linear-gradient(135deg
|
|
578
|
+
.title{font-family:'Space Grotesk',sans-serif;font-size:22px;font-weight:700;background:linear-gradient(135deg, #0166FF, #009AFE, #4AEEFF);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-bottom:8px}
|
|
566
579
|
.subtitle{font-size:14px;color:#999;line-height:1.5}
|
|
567
580
|
|
|
568
581
|
.loading{font-size:14px;color:#999;animation:pulse 2s ease-in-out infinite}
|
|
@@ -574,7 +587,7 @@ ${connected
|
|
|
574
587
|
? `<div class="confetti-wrap">${confettiHTML}</div>
|
|
575
588
|
<div class="video-wrap"><video autoplay muted playsinline><source src="/bloby_happy_reappearing.mov" type='video/mp4; codecs="hvc1"'><source src="/bloby_happy_reappearing.webm" type="video/webm"></video></div>
|
|
576
589
|
<div class="text-wrap"><div class="title">Connected!</div><p class="subtitle">WhatsApp is linked. You can close this page.</p>
|
|
577
|
-
<button onclick="relink()" style="margin-top:20px;padding:10px 24px;background:#2a2a2a;border:1px solid rgba(255,255,255,0.15);border-radius:10px;color:#999;font-size:13px;cursor:pointer;font-family:inherit;transition:all .2s" onmouseover="this.style.borderColor='#
|
|
590
|
+
<button onclick="relink()" style="margin-top:20px;padding:10px 24px;background:#2a2a2a;border:1px solid rgba(255,255,255,0.15);border-radius:10px;color:#999;font-size:13px;cursor:pointer;font-family:inherit;transition:all .2s" onmouseover="this.style.borderColor='#0069FE';this.style.color='#f5f5f5'" onmouseout="this.style.borderColor='rgba(255,255,255,0.15)';this.style.color='#999'">Relink to a different number</button>
|
|
578
591
|
</div>
|
|
579
592
|
<script>async function relink(){await fetch('/api/channels/whatsapp/logout',{method:'POST'});await fetch('/api/channels/whatsapp/connect',{method:'POST'});setTimeout(()=>location.reload(),2000)}</script>`
|
|
580
593
|
: qr
|
|
@@ -980,7 +993,7 @@ ${!connected ? `<script>
|
|
|
980
993
|
const alexaStatus = channelManager.getStatus('alexa');
|
|
981
994
|
const alreadyLinked = !!(alexaStatus?.info as any)?.linked;
|
|
982
995
|
const confettiHTML = Array.from({ length: 30 }, (_, i) => {
|
|
983
|
-
const colors = ['#
|
|
996
|
+
const colors = ['#0166FF', '#009AFE', '#4AEEFF', '#4ade80', '#facc15', '#818cf8'];
|
|
984
997
|
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
985
998
|
const left = Math.random() * 100;
|
|
986
999
|
const delay = i * 0.04;
|
|
@@ -998,17 +1011,17 @@ ${!connected ? `<script>
|
|
|
998
1011
|
body{background:#212121;color:#f5f5f5;font-family:'Inter',system-ui,-apple-system,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100dvh;margin:0;overflow-x:hidden}
|
|
999
1012
|
.container{display:flex;flex-direction:column;align-items:center;max-width:380px;width:100%;padding:20px}
|
|
1000
1013
|
|
|
1001
|
-
.card{background:#2a2a2a;border:1px solid rgba(255,255,255,0.08);border-radius:20px;padding:28px 24px;width:100%;box-shadow:0 0 0 1px rgba(
|
|
1014
|
+
.card{background:#2a2a2a;border:1px solid rgba(255,255,255,0.08);border-radius:20px;padding:28px 24px;width:100%;box-shadow:0 0 0 1px rgba(0, 105, 254,0.1),0 0 20px -5px rgba(0, 105, 254,0.15);animation:fade-up .5s ease-out both;text-align:center}
|
|
1002
1015
|
|
|
1003
1016
|
.header{display:flex;flex-direction:column;align-items:center;gap:6px;margin-bottom:18px}
|
|
1004
1017
|
.badge-alexa{display:inline-flex;align-items:center;gap:6px;background:#1a1a1a;border:1px solid rgba(255,255,255,0.08);border-radius:9999px;padding:4px 10px;font-size:11px;color:#888;text-transform:uppercase;letter-spacing:0.6px}
|
|
1005
|
-
.badge-alexa::before{content:'';width:8px;height:8px;border-radius:50%;background:linear-gradient(135deg
|
|
1006
|
-
.title{font-family:'Space Grotesk',sans-serif;font-size:22px;font-weight:700;background:linear-gradient(135deg
|
|
1018
|
+
.badge-alexa::before{content:'';width:8px;height:8px;border-radius:50%;background:linear-gradient(135deg, #0166FF, #009AFE);box-shadow:0 0 8px rgba(74, 238, 255,0.5)}
|
|
1019
|
+
.title{font-family:'Space Grotesk',sans-serif;font-size:22px;font-weight:700;background:linear-gradient(135deg, #0166FF, #009AFE, #4AEEFF);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-top:6px}
|
|
1007
1020
|
.sub{font-size:13px;color:#999;line-height:1.6;margin-top:6px}
|
|
1008
1021
|
|
|
1009
1022
|
.code-block{margin:18px 0 6px;animation:fade-up .5s ease-out .15s both}
|
|
1010
1023
|
.code-label{font-size:11px;color:#666;text-transform:uppercase;letter-spacing:0.8px;margin-bottom:8px}
|
|
1011
|
-
.code-value{font-family:'Space Grotesk',monospace;font-size:36px;font-weight:700;letter-spacing:8px;background:linear-gradient(135deg
|
|
1024
|
+
.code-value{font-family:'Space Grotesk',monospace;font-size:36px;font-weight:700;letter-spacing:8px;background:linear-gradient(135deg, #0166FF, #009AFE);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;line-height:1.1}
|
|
1012
1025
|
.countdown{font-size:12px;color:#666;margin-top:10px;display:inline-flex;align-items:center;gap:6px}
|
|
1013
1026
|
.countdown.warn{color:#FB4072}
|
|
1014
1027
|
.countdown .dot{width:6px;height:6px;border-radius:50%;background:currentColor;animation:pulse 1.6s ease-in-out infinite;opacity:.6}
|
|
@@ -1016,8 +1029,8 @@ ${!connected ? `<script>
|
|
|
1016
1029
|
.quote-card{background:#1a1a1a;border:1px solid rgba(255,255,255,0.06);border-radius:12px;padding:14px 16px;margin:18px 0 6px;animation:fade-up .5s ease-out .25s both}
|
|
1017
1030
|
.quote-label{font-size:11px;color:#666;text-transform:uppercase;letter-spacing:0.6px;margin-bottom:6px}
|
|
1018
1031
|
.quote{font-size:15px;color:#f5f5f5;line-height:1.5;font-style:italic}
|
|
1019
|
-
.quote .invocation{color:#
|
|
1020
|
-
.quote .num{background:linear-gradient(135deg
|
|
1032
|
+
.quote .invocation{color:#0069FE;font-style:normal;font-weight:600}
|
|
1033
|
+
.quote .num{background:linear-gradient(135deg, #0166FF, #009AFE);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-style:normal;font-weight:700;letter-spacing:2px}
|
|
1021
1034
|
|
|
1022
1035
|
.steps{text-align:left;margin-top:20px;animation:fade-up .5s ease-out .35s both}
|
|
1023
1036
|
.steps-title{font-size:11px;color:#666;text-transform:uppercase;letter-spacing:0.6px;margin-bottom:10px;text-align:center}
|
|
@@ -1027,11 +1040,11 @@ ${!connected ? `<script>
|
|
|
1027
1040
|
|
|
1028
1041
|
.btn-row{display:flex;gap:10px;margin-top:20px;width:100%;animation:fade-up .5s ease-out .45s both}
|
|
1029
1042
|
.btn{flex:1;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:10px;padding:11px 16px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;transition:all .2s}
|
|
1030
|
-
.btn-primary{background:linear-gradient(135deg
|
|
1043
|
+
.btn-primary{background:linear-gradient(135deg, #0166FF, #009AFE);color:#fff}
|
|
1031
1044
|
.btn-primary:hover{opacity:.9}
|
|
1032
1045
|
.btn-primary:disabled{opacity:.5;cursor:not-allowed}
|
|
1033
1046
|
.btn-ghost{background:#1a1a1a;border:1px solid rgba(255,255,255,0.1);color:#999}
|
|
1034
|
-
.btn-ghost:hover{border-color:rgba(
|
|
1047
|
+
.btn-ghost:hover{border-color:rgba(0, 105, 254,0.4);color:#f5f5f5}
|
|
1035
1048
|
|
|
1036
1049
|
.err{color:#FB4072;font-size:13px;margin-top:14px;min-height:1.2em}
|
|
1037
1050
|
|
|
@@ -1429,7 +1442,7 @@ mint();
|
|
|
1429
1442
|
try {
|
|
1430
1443
|
const [status, recentRaw] = await Promise.all([
|
|
1431
1444
|
workerApi('/api/onboard/status'),
|
|
1432
|
-
workerApi(`/api/conversations/${convId}/messages/recent?limit=
|
|
1445
|
+
workerApi(`/api/conversations/${convId}/messages/recent?limit=30`),
|
|
1433
1446
|
]);
|
|
1434
1447
|
botName = status.agentName || 'Bloby';
|
|
1435
1448
|
humanName = status.userName || 'Human';
|
|
@@ -1894,6 +1907,22 @@ mint();
|
|
|
1894
1907
|
endConversation(convId);
|
|
1895
1908
|
runDeferredUpdate();
|
|
1896
1909
|
}
|
|
1910
|
+
|
|
1911
|
+
// Proactive session recycling (see CONTEXT_RECYCLE_TOKENS). Only when the
|
|
1912
|
+
// harness reports the session idle (no queued message) — and this handler runs
|
|
1913
|
+
// synchronously from the harness callback, so nothing can be pushed between the
|
|
1914
|
+
// idle check and endConversation. Recycling here therefore never drops a queued
|
|
1915
|
+
// message; the next user message re-injects recent history + memory.
|
|
1916
|
+
if (eventData.idle && hasConversation(convId)) {
|
|
1917
|
+
const used = typeof eventData.contextTokens === 'number' ? eventData.contextTokens : 0;
|
|
1918
|
+
const window = typeof eventData.contextWindow === 'number' ? eventData.contextWindow : 0;
|
|
1919
|
+
const recycleAt = window > 0 ? Math.floor(window * CONTEXT_RECYCLE_FRACTION) : CONTEXT_RECYCLE_TOKENS;
|
|
1920
|
+
if (used > recycleAt) {
|
|
1921
|
+
log.info(`[orchestrator] Context ~${used} tok > ${recycleAt} (window=${window || 'n/a'}) — recycling session ${convId}; next message re-injects recent + memory.`);
|
|
1922
|
+
endConversation(convId);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1897
1926
|
broadcastBloby('bot:idle', { conversationId: convId });
|
|
1898
1927
|
return;
|
|
1899
1928
|
}
|
|
@@ -2227,7 +2256,7 @@ mint();
|
|
|
2227
2256
|
try {
|
|
2228
2257
|
const [status, recentRaw] = await Promise.all([
|
|
2229
2258
|
workerApi('/api/onboard/status') as Promise<any>,
|
|
2230
|
-
workerApi(`/api/conversations/${convId}/messages/recent?limit=
|
|
2259
|
+
workerApi(`/api/conversations/${convId}/messages/recent?limit=30`) as Promise<any[]>,
|
|
2231
2260
|
]);
|
|
2232
2261
|
botName = status.agentName || 'Bloby';
|
|
2233
2262
|
humanName = status.userName || 'Human';
|
|
@@ -279,7 +279,7 @@ If your human asks you to update a skill's behavior, edit the files INSIDE `skil
|
|
|
279
279
|
You can communicate through several surfaces at once. The two built-in ones are:
|
|
280
280
|
|
|
281
281
|
- **`[PWA]`** — the chat bubble in the dashboard (web app / PWA). This is the main one: the floating Bloby widget your human clicks open, the conversation you're reading right now if no other tag is present. Treat it as your home base.
|
|
282
|
-
- **`[Mac]`** — the Morphy native Mac app living in the MacBook notch. Your human held a hotkey, spoke, and Morphy sent the transcript here. Screenshots of their screen may be attached. Keep replies concise — they'll be spoken aloud via TTS. No markdown, no bullet lists — plain spoken sentences only.
|
|
282
|
+
- **`[Mac]`** — the Morphy native Mac app living in the MacBook notch. Your human held a hotkey, spoke, and Morphy sent the transcript here. Screenshots of their screen may be attached. Keep replies concise — they'll be spoken aloud via TTS. No markdown, no bullet lists — plain spoken sentences only. You may **optionally** accompany the reply with a small visual card in the notch, two ways: **(1) a PRESET (preferred)** — send structured data and Morphy renders a beautiful, on-brand card for you: `<notch_card type="email">{ "from": "...", "subject": "...", "time": "...", "body": "..." }</notch_card>`. Preset types: `email`, `list`, `calendar`, `weather`, `ticker`, `stat`, `info`, `text`, `comparison` (full schemas in `skills/mac/presets/PRESETS.md`). The body is one JSON object; you never write CSS. **(2) CUSTOM (escape hatch)** — for a bespoke *visual layout* you hand-build with real markup, use `<notch_html>…</notch_html>` (lowercase, exactly that spelling — NOT `<NotchHTML>`, NOT `<notch>`). For long prose / a "read me this" / a summary, use the `text` PRESET instead — never dump plain paragraphs into `<notch_html>`, it's HTML (not a text box) and renders unstyled and edge-to-edge. Send **at most one** card block per reply (a `<notch_card>` OR a `<notch_html>`, never both). Both tags are stripped from TTS, so card contents are **never** spoken. Canvas is **fixed 383 × 147 pt, transparent over BLACK**; custom cards use light/white text, no external assets (no `<img src>`, no fonts, no JS network) and no interactive elements (clicks do nothing) — though long content IS scrollable, the user scrolls it with the trackpad, so letting content overflow is fine. **CRITICAL: never speak the contents of your card.** If the card carries a list / calendar / email / news / comparison / any structured answer, the spoken text MUST be a short lead-in ONLY ("Here are today's top five, Bruno.") and then stop — do not recite the items, the human is already looking at them. If the answer fits in one spoken sentence, send NO card. Voice and card are complementary, never redundant — pick which one carries the detail per reply, and let the other be brief or absent. When in doubt, **open `skills/mac/SKILL.md` and follow it** — it has the preset catalog, examples (including the canonical bad/good comparison of the speak-the-card duplication failure), custom templates under `skills/mac/frequentSnippets/`, and a pre-send checklist. **Mis-spelling a tag means the markup reaches TTS and gets spoken at the human** — that's a visible failure, not a silent one.
|
|
283
283
|
- **`[workspace]`** — a chat-shaped widget your human placed somewhere inside their dashboard *workspace*. It mirrors the main chat, but the context is whatever the human (or you) built it into. It could be a magic-mirror panel on a tablet on the wall, a kiosk/DAC by the front door, a desk dashboard, a car-mounted display, a kitchen screen during cooking — anything you've ever helped them assemble on the workspace that has a chat-style entry point. **Check `MEMORY.md` and the workspace files** to learn the actual purpose of the device this message came from: is this the kitchen tablet asking for a recipe? The hallway mirror asking what's on today's schedule? The garage panel asking about the car? Tailor tone, brevity, and content to that role. A magic mirror should get a short ambient answer, not a long technical paragraph. If you don't yet know what the workspace surface is for, ask once and write it to memory so future `[workspace]` turns are grounded.
|
|
284
284
|
|
|
285
285
|
Beyond those, your human can install additional channels (WhatsApp, Telegram, Discord, Alexa…) as **skills** from the Bloby Marketplace. Each channel skill teaches you the conventions for that surface.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Search, Mail, DollarSign, TrendingUp } from 'lucide-react';
|
|
2
2
|
import { AreaChart, Area, BarChart, Bar, ResponsiveContainer } from 'recharts';
|
|
3
3
|
|
|
4
|
-
const GRADIENT = 'linear-gradient(to right, #
|
|
4
|
+
const GRADIENT = 'linear-gradient(to right, #0166FF 10%, #009AFE 55%, #4AEEFF 100%)';
|
|
5
5
|
const CARD = 'relative rounded-xl overflow-hidden';
|
|
6
6
|
const BORDER = 'absolute inset-0 rounded-xl bg-gradient-to-b from-white/[0.08] via-white/[0.02] to-transparent pointer-events-none';
|
|
7
7
|
const INNER = 'relative rounded-xl bg-[#141414] m-px p-3.5 h-full';
|
|
@@ -52,8 +52,8 @@ export default function DashboardPage() {
|
|
|
52
52
|
<ResponsiveContainer width="100%" height={48}>
|
|
53
53
|
<AreaChart data={rev}>
|
|
54
54
|
<defs>
|
|
55
|
-
<linearGradient id="sg" x1="0" y1="0" x2="1" y2="0"><stop offset="0%" stopColor="#
|
|
56
|
-
<linearGradient id="sf" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="#
|
|
55
|
+
<linearGradient id="sg" x1="0" y1="0" x2="1" y2="0"><stop offset="0%" stopColor="#0166FF" /><stop offset="50%" stopColor="#009AFE" /><stop offset="100%" stopColor="#4AEEFF" /></linearGradient>
|
|
56
|
+
<linearGradient id="sf" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="#009AFE" stopOpacity={0.12} /><stop offset="100%" stopColor="#009AFE" stopOpacity={0} /></linearGradient>
|
|
57
57
|
</defs>
|
|
58
58
|
<Area type="monotone" dataKey="v" stroke="url(#sg)" strokeWidth={1.5} fill="url(#sf)" />
|
|
59
59
|
</AreaChart>
|
|
@@ -79,7 +79,7 @@ export default function DashboardPage() {
|
|
|
79
79
|
<ResponsiveContainer width="100%" height={40}>
|
|
80
80
|
<BarChart data={fol} barCategoryGap="25%">
|
|
81
81
|
<defs>
|
|
82
|
-
<linearGradient id="xg" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="#
|
|
82
|
+
<linearGradient id="xg" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="#009AFE" stopOpacity={0.3} /><stop offset="100%" stopColor="#0166FF" stopOpacity={0.05} /></linearGradient>
|
|
83
83
|
</defs>
|
|
84
84
|
<Bar dataKey="v" fill="url(#xg)" radius={[2, 2, 0, 0]} />
|
|
85
85
|
</BarChart>
|
|
@@ -40,7 +40,7 @@ export default function Sidebar({ userName, botName = 'Bloby', backendStatus = '
|
|
|
40
40
|
<h2
|
|
41
41
|
className="text-4xl font-bold mt-0.5 tracking-tight leading-[1.08] w-fit"
|
|
42
42
|
style={{
|
|
43
|
-
backgroundImage: 'linear-gradient(to right, #
|
|
43
|
+
backgroundImage: 'linear-gradient(to right, #0166FF, #009AFE, #4AEEFF)',
|
|
44
44
|
WebkitBackgroundClip: 'text',
|
|
45
45
|
WebkitTextFillColor: 'transparent',
|
|
46
46
|
backgroundClip: 'text',
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
.bloby-tour-popover .driver-popover-next-btn {
|
|
31
|
-
background: linear-gradient(to right, #
|
|
31
|
+
background: linear-gradient(to right, #0166FF, #009AFE, #4AEEFF) !important;
|
|
32
32
|
color: #fff !important;
|
|
33
33
|
border: none !important;
|
|
34
34
|
border-radius: 8px !important;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
--color-card-foreground: #f5f5f5;
|
|
11
11
|
--color-popover: #2a2a2a;
|
|
12
12
|
--color-popover-foreground: #f5f5f5;
|
|
13
|
-
--color-primary: #
|
|
13
|
+
--color-primary: #0069FE;
|
|
14
14
|
--color-primary-foreground: #ffffff;
|
|
15
15
|
--color-secondary: #333333;
|
|
16
16
|
--color-secondary-foreground: #f5f5f5;
|
|
@@ -22,20 +22,20 @@
|
|
|
22
22
|
--color-destructive-foreground: #ffffff;
|
|
23
23
|
--color-border: #3a3a3a;
|
|
24
24
|
--color-input: #3a3a3a;
|
|
25
|
-
--color-ring: #
|
|
26
|
-
--color-chart-1: #
|
|
25
|
+
--color-ring: #0069FE;
|
|
26
|
+
--color-chart-1: #0069FE;
|
|
27
27
|
--color-chart-2: #FD486B;
|
|
28
28
|
--color-chart-3: #F59E0B;
|
|
29
29
|
--color-chart-4: #8B5CF6;
|
|
30
30
|
--color-chart-5: #10B981;
|
|
31
31
|
--color-sidebar: #1c1c1c;
|
|
32
32
|
--color-sidebar-foreground: #f5f5f5;
|
|
33
|
-
--color-sidebar-primary: #
|
|
33
|
+
--color-sidebar-primary: #0069FE;
|
|
34
34
|
--color-sidebar-primary-foreground: #ffffff;
|
|
35
35
|
--color-sidebar-accent: #282828;
|
|
36
36
|
--color-sidebar-accent-foreground: #f5f5f5;
|
|
37
37
|
--color-sidebar-border: #3a3a3a;
|
|
38
|
-
--color-sidebar-ring: #
|
|
38
|
+
--color-sidebar-ring: #0069FE;
|
|
39
39
|
--radius: 0.75rem;
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -55,7 +55,7 @@ body {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
::selection {
|
|
58
|
-
background-color: rgba(
|
|
58
|
+
background-color: rgba(0, 105, 254, 0.25);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
::-webkit-scrollbar { width: 6px; }
|
|
@@ -68,16 +68,16 @@ body {
|
|
|
68
68
|
-webkit-background-clip: text;
|
|
69
69
|
color: transparent;
|
|
70
70
|
-webkit-text-fill-color: transparent;
|
|
71
|
-
background-image: linear-gradient(135deg, #
|
|
71
|
+
background-image: linear-gradient(135deg, #0166FF, #009AFE, #4AEEFF);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
.bg-gradient-brand {
|
|
75
|
-
background-image: linear-gradient(135deg, #
|
|
75
|
+
background-image: linear-gradient(135deg, #0166FF, #009AFE, #4AEEFF);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
.glow-border {
|
|
79
|
-
box-shadow: 0 0 0 1px rgba(
|
|
80
|
-
0 0 20px -5px rgba(
|
|
79
|
+
box-shadow: 0 0 0 1px rgba(0, 105, 254, 0.1),
|
|
80
|
+
0 0 20px -5px rgba(0, 105, 254, 0.15);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
.animated-border {
|
|
@@ -90,10 +90,10 @@ body {
|
|
|
90
90
|
inset: -150%;
|
|
91
91
|
background: conic-gradient(
|
|
92
92
|
from 0deg,
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
93
|
+
#0166FF,
|
|
94
|
+
#009AFE,
|
|
95
|
+
#4AEEFF,
|
|
96
|
+
#0166FF
|
|
97
97
|
);
|
|
98
98
|
animation: border-spin 3s linear infinite;
|
|
99
99
|
}
|
|
@@ -107,10 +107,10 @@ body {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
.input-glow:focus {
|
|
110
|
-
border-color: rgba(
|
|
111
|
-
box-shadow: 0 0 0 1px rgba(
|
|
112
|
-
0 0 20px -5px rgba(
|
|
113
|
-
0 0 4px -1px rgba(
|
|
110
|
+
border-color: rgba(0, 105, 254, 0.4);
|
|
111
|
+
box-shadow: 0 0 0 1px rgba(0, 105, 254, 0.15),
|
|
112
|
+
0 0 20px -5px rgba(0, 105, 254, 0.25),
|
|
113
|
+
0 0 4px -1px rgba(74, 238, 255, 0.1);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
@keyframes border-spin {
|