bloby-bot 0.47.7 → 0.47.9

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.
@@ -1 +1 @@
1
- import{c as e,r as t,t as n}from"./jsx-runtime-C0W9Wf2W.js";import{n as r,r as i,t as a}from"./bloby-E-QLmQDW.js";var o=e(t(),1),s=n(),c=({code:e,language:t,raw:n,className:c,startLine:l,lineNumbers:u,...d})=>{let{shikiTheme:f}=(0,o.useContext)(i),p=r(),[m,h]=(0,o.useState)(n);return(0,o.useEffect)(()=>{if(!p){h(n);return}let r=p.highlight({code:e,language:t,themes:f},e=>{h(e)});r&&h(r)},[e,t,f,p,n]),(0,s.jsx)(a,{className:c,language:t,lineNumbers:u,result:m,startLine:l,...d})};export{c as HighlightedCodeBlockBody};
1
+ import{c as e,r as t,t as n}from"./jsx-runtime-C0W9Wf2W.js";import{n as r,r as i,t as a}from"./bloby-BOSem5eq.js";var o=e(t(),1),s=n(),c=({code:e,language:t,raw:n,className:c,startLine:l,lineNumbers:u,...d})=>{let{shikiTheme:f}=(0,o.useContext)(i),p=r(),[m,h]=(0,o.useState)(n);return(0,o.useEffect)(()=>{if(!p){h(n);return}let r=p.highlight({code:e,language:t,themes:f},e=>{h(e)});r&&h(r)},[e,t,f,p,n]),(0,s.jsx)(a,{className:c,language:t,lineNumbers:u,result:m,startLine:l,...d})};export{c as HighlightedCodeBlockBody};
@@ -0,0 +1 @@
1
+ import{i as e}from"./bloby-BOSem5eq.js";export{e as Mermaid};
@@ -1 +1 @@
1
- import{c as e,r as t,t as n}from"./jsx-runtime-C0W9Wf2W.js";import{p as r,t as i}from"./globals-Ci0CEj1X.js";var a=e(t(),1),o=e(r(),1),s=n();function c(){return(0,s.jsx)(i,{onComplete:()=>{window.parent?.postMessage({type:`bloby:onboard-complete`},`*`)},isInitialSetup:!0})}o.createRoot(document.getElementById(`root`)).render((0,s.jsx)(a.StrictMode,{children:(0,s.jsx)(c,{})}));
1
+ import{c as e,r as t,t as n}from"./jsx-runtime-C0W9Wf2W.js";import{p as r,t as i}from"./globals-CgIIDNVG.js";var a=e(t(),1),o=e(r(),1),s=n();function c(){return(0,s.jsx)(i,{onComplete:()=>{window.parent?.postMessage({type:`bloby:onboard-complete`},`*`)},isInitialSetup:!0})}o.createRoot(document.getElementById(`root`)).render((0,s.jsx)(a.StrictMode,{children:(0,s.jsx)(c,{})}));
@@ -4,10 +4,10 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
6
6
  <title>Bloby Chat</title>
7
- <script type="module" crossorigin src="/bloby/assets/bloby-E-QLmQDW.js"></script>
7
+ <script type="module" crossorigin src="/bloby/assets/bloby-BOSem5eq.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="/bloby/assets/jsx-runtime-C0W9Wf2W.js">
9
- <link rel="modulepreload" crossorigin href="/bloby/assets/globals-Ci0CEj1X.js">
10
- <link rel="stylesheet" crossorigin href="/bloby/assets/globals-DriF_8Q_.css">
9
+ <link rel="modulepreload" crossorigin href="/bloby/assets/globals-CgIIDNVG.js">
10
+ <link rel="stylesheet" crossorigin href="/bloby/assets/globals-CZdsyZot.css">
11
11
  <link rel="stylesheet" crossorigin href="/bloby/assets/bloby-DkK0ymA2.css">
12
12
  </head>
13
13
  <body class="bg-background text-foreground">
@@ -4,10 +4,10 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
6
6
  <title>Bloby Setup</title>
7
- <script type="module" crossorigin src="/bloby/assets/onboard-C1uMxuk2.js"></script>
7
+ <script type="module" crossorigin src="/bloby/assets/onboard-8AOYU7vY.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="/bloby/assets/jsx-runtime-C0W9Wf2W.js">
9
- <link rel="modulepreload" crossorigin href="/bloby/assets/globals-Ci0CEj1X.js">
10
- <link rel="stylesheet" crossorigin href="/bloby/assets/globals-DriF_8Q_.css">
9
+ <link rel="modulepreload" crossorigin href="/bloby/assets/globals-CgIIDNVG.js">
10
+ <link rel="stylesheet" crossorigin href="/bloby/assets/globals-CZdsyZot.css">
11
11
  </head>
12
12
  <body class="bg-background text-foreground">
13
13
  <div id="root"></div>
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "bloby-bot",
3
- "version": "0.47.7",
3
+ "version": "0.47.9",
4
4
  "releaseNotes": [
5
- "1. # voice note (PTT bubble)",
6
- "2. # audio file + caption",
7
- "3. # PDF",
5
+ "1. Something great..",
6
+ "2. ",
7
+ "3. ",
8
8
  "4. "
9
9
  ],
10
10
  "description": "Self-hosted, self-evolving AI agent with its own dashboard.",
@@ -46,9 +46,10 @@ const ACCESS_LABELS: Record<AccessMethod, string> = {
46
46
  /* ── Provider config ── */
47
47
 
48
48
  const PROVIDERS = [
49
- { id: 'pi', name: 'Bloby', subtitle: 'Built on top of Pi — bring your own model', icon: '/bloby-icon-192.png' },
50
- { id: 'anthropic', name: 'Claude', subtitle: 'by Anthropic', icon: '/icons/claude.png' },
51
- { id: 'openai', name: 'OpenAI Codex', subtitle: 'ChatGPT Plus / Pro', icon: '/icons/codex.png' },
49
+ { id: 'pi', name: 'Pi', subtitle: 'Bring your own model', icon: '/icons/pi.svg', comingSoon: false },
50
+ { id: 'bloby', name: 'Bloby', subtitle: 'Coming Soon..', icon: '/bloby-icon-192.png', comingSoon: true },
51
+ { id: 'anthropic', name: 'Claude', subtitle: 'by Anthropic', icon: '/icons/claude.png', comingSoon: false },
52
+ { id: 'openai', name: 'OpenAI Codex', subtitle: 'ChatGPT Plus / Pro', icon: '/icons/codex.png', comingSoon: false },
52
53
  ] as const;
53
54
 
54
55
  const MODELS: Record<string, { id: string; label: string }[]> = {
@@ -196,8 +197,6 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
196
197
  const [piShowKey, setPiShowKey] = useState(false);
197
198
  const [piConnecting, setPiConnecting] = useState(false);
198
199
  const [piError, setPiError] = useState<string | undefined>();
199
- const [piTestRunning, setPiTestRunning] = useState(false);
200
- const [piTestResult, setPiTestResult] = useState<{ ok: boolean; text?: string; error?: string } | null>(null);
201
200
  const [piSavedStatus, setPiSavedStatus] = useState<{ subProvider?: string; modelId?: string; baseUrl?: string } | null>(null);
202
201
 
203
202
  // Anthropic/Claude-specific
@@ -601,7 +600,6 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
601
600
  setOpenaiError(undefined);
602
601
  // Pi flow cleanup is per-sub-provider; the load effect handles re-hydration.
603
602
  setPiError(undefined);
604
- setPiTestResult(null);
605
603
  };
606
604
 
607
605
  /* ── Bloby (pi) handlers ── */
@@ -648,7 +646,6 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
648
646
  setPiBaseUrl(next?.baseUrl || '');
649
647
  setPiModelId(next?.defaultModel || '');
650
648
  setPiError(undefined);
651
- setPiTestResult(null);
652
649
  // Picking a new sub-provider invalidates the previously saved auth.
653
650
  if (authState.pi === 'connected') {
654
651
  setAuthState((s) => ({ ...s, pi: 'idle' }));
@@ -659,7 +656,6 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
659
656
  const handlePiConnect = async () => {
660
657
  if (!piSubProvider) return;
661
658
  setPiError(undefined);
662
- setPiTestResult(null);
663
659
  setPiConnecting(true);
664
660
  try {
665
661
  const payload = {
@@ -703,28 +699,9 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
703
699
  try { await fetch('/api/auth/pi', { method: 'DELETE' }); } catch {}
704
700
  setAuthState((s) => ({ ...s, pi: 'idle' }));
705
701
  setPiSavedStatus(null);
706
- setPiTestResult(null);
707
702
  setModel('');
708
703
  };
709
704
 
710
- const handlePiTestCompletion = async () => {
711
- setPiTestRunning(true);
712
- setPiTestResult(null);
713
- try {
714
- const res = await fetch('/api/auth/pi/completion', {
715
- method: 'POST',
716
- headers: { 'Content-Type': 'application/json' },
717
- body: JSON.stringify({}),
718
- });
719
- const data = await res.json();
720
- setPiTestResult({ ok: !!data?.ok, text: data?.text, error: data?.error });
721
- } catch (err: any) {
722
- setPiTestResult({ ok: false, error: err?.message || 'Request failed' });
723
- } finally {
724
- setPiTestRunning(false);
725
- }
726
- };
727
-
728
705
  /* ── Auth handlers: Anthropic/Claude ── */
729
706
 
730
707
  const openExternal = (url: string) => {
@@ -2241,180 +2218,146 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
2241
2218
 
2242
2219
  <div className="border-t border-white/[0.06] mt-4 mb-3" />
2243
2220
 
2244
- {/* ── Auth flow: Bloby (pi) ── */}
2221
+ {/* ── Auth flow: Pi (bring your own model) ── */}
2245
2222
  {provider === 'pi' && (
2246
- <div className="space-y-3">
2223
+ <div className="space-y-2">
2247
2224
  {authState.pi === 'connected' && piSavedStatus ? (
2248
- <>
2249
- <div className="bg-emerald-500/8 border border-emerald-500/15 rounded-lg px-3.5 py-2.5">
2250
- <p className="text-emerald-400/90 text-[12px]">
2251
- Connected — {piSubProviders.find((p) => p.id === piSavedStatus.subProvider)?.name || piSavedStatus.subProvider}
2252
- {piSavedStatus.modelId ? <> · <span className="font-mono">{piSavedStatus.modelId}</span></> : null}
2253
- </p>
2254
- </div>
2255
-
2256
- <div className="bg-white/[0.03] border border-white/[0.06] rounded-xl p-3.5 space-y-2.5">
2257
- <p className="text-[12px] text-white/55">
2258
- Send a quick test request to confirm the model is reachable.
2259
- </p>
2260
- <button
2261
- onClick={handlePiTestCompletion}
2262
- disabled={piTestRunning}
2263
- className="w-full py-2 px-4 bg-white/[0.06] hover:bg-white/[0.1] text-white text-[12px] font-medium rounded-xl transition-colors flex items-center justify-center gap-2 disabled:opacity-60"
2264
- >
2265
- {piTestRunning ? (
2266
- <><LoaderCircle className="h-3.5 w-3.5 animate-spin" />Sending test request…</>
2267
- ) : (
2268
- <>Send test request<ArrowRight className="h-3.5 w-3.5 opacity-60" /></>
2269
- )}
2270
- </button>
2271
- {piTestResult && piTestResult.ok && (
2272
- <div className="bg-emerald-500/5 border border-emerald-500/15 rounded-lg px-3 py-2">
2273
- <p className="text-[11px] text-emerald-400/70 mb-1">Model reply</p>
2274
- <p className="text-[12px] text-white/85 whitespace-pre-wrap">{piTestResult.text}</p>
2275
- </div>
2276
- )}
2277
- {piTestResult && !piTestResult.ok && (
2278
- <div className="bg-red-500/8 border border-red-500/15 rounded-lg px-3 py-2">
2279
- <p className="text-[12px] text-red-400/90">{piTestResult.error}</p>
2280
- </div>
2281
- )}
2282
- </div>
2283
-
2225
+ <div className="flex items-center justify-between bg-emerald-500/8 border border-emerald-500/15 rounded-lg px-3 py-2">
2226
+ <p className="text-emerald-400/90 text-[12px] truncate">
2227
+ Connected — {piSubProviders.find((p) => p.id === piSavedStatus.subProvider)?.name || piSavedStatus.subProvider}
2228
+ {piSavedStatus.modelId ? <> · <span className="font-mono">{piSavedStatus.modelId}</span></> : null}
2229
+ </p>
2284
2230
  <button
2285
2231
  onClick={handlePiDisconnect}
2286
- className="w-full py-1.5 text-white/25 text-[11px] hover:text-white/40 transition-colors flex items-center justify-center gap-1.5"
2232
+ className="text-white/30 hover:text-white/60 text-[11px] flex items-center gap-1 shrink-0 ml-2"
2287
2233
  >
2288
2234
  <RefreshCw className="h-3 w-3" />
2289
- Use a different model
2235
+ Change
2290
2236
  </button>
2291
- </>
2237
+ </div>
2292
2238
  ) : (
2293
2239
  <>
2294
- <div>
2295
- <p className="text-[12px] text-white/40 mb-2">Pick an LLM provider — Bloby brings your own model.</p>
2296
- <div className="grid grid-cols-2 gap-2 max-h-[320px] overflow-y-auto pr-1">
2297
- {piSubProviders.map((sp) => (
2298
- <button
2299
- key={sp.id}
2300
- type="button"
2301
- onClick={() => choosePiSubProvider(sp.id)}
2302
- className={`relative text-left rounded-xl border px-3 py-2.5 transition-colors ${
2303
- piSubProvider === sp.id
2304
- ? 'border-[#AF27E3]/50 bg-[#AF27E3]/10'
2305
- : 'border-white/[0.08] bg-white/[0.02] hover:bg-white/[0.04]'
2306
- }`}
2307
- >
2308
- <p className="text-[12.5px] text-white font-medium leading-tight">{sp.name}</p>
2309
- <p className="text-[10.5px] text-white/40 mt-0.5 leading-tight">{sp.subtitle}</p>
2310
- {piSubProvider === sp.id && (
2311
- <div className="absolute top-1.5 right-1.5 w-1.5 h-1.5 rounded-full bg-gradient-brand" />
2312
- )}
2313
- </button>
2314
- ))}
2240
+ {/* Two-column compact row: provider dropdown + model picker */}
2241
+ <div className="grid grid-cols-2 gap-2">
2242
+ <div>
2243
+ <label className="text-[11px] text-white/40 font-medium mb-1 block">Provider</label>
2244
+ <ModelDropdown
2245
+ models={piSubProviders.map((sp) => ({ id: sp.id, label: sp.name }))}
2246
+ value={piSubProvider}
2247
+ onChange={choosePiSubProvider}
2248
+ />
2315
2249
  </div>
2316
- </div>
2317
-
2318
- {selectedPiSub && (
2319
- <div className="space-y-2 pt-1">
2320
- {(selectedPiSub.needsBaseUrl || selectedPiSub.flavor === 'openai-completions') && (
2321
- <div>
2322
- <label className="text-[11px] text-white/40 font-medium mb-1 block">
2323
- Base URL{selectedPiSub.needsBaseUrl ? '' : ' (override — optional)'}
2324
- </label>
2325
- <input
2326
- type="text"
2327
- value={piBaseUrl}
2328
- onChange={(e) => setPiBaseUrl(e.target.value)}
2329
- placeholder={selectedPiSub.baseUrl || 'https://example.com/v1'}
2330
- className={inputSmCls + ' font-mono'}
2331
- />
2332
- </div>
2250
+ <div>
2251
+ <label className="text-[11px] text-white/40 font-medium mb-1 block">Model</label>
2252
+ {selectedPiSub && Array.isArray(selectedPiSub.models) ? (
2253
+ <ModelDropdown
2254
+ models={selectedPiSub.models}
2255
+ value={piModelId}
2256
+ onChange={setPiModelId}
2257
+ />
2258
+ ) : (
2259
+ <input
2260
+ type="text"
2261
+ value={piModelId}
2262
+ onChange={(e) => setPiModelId(e.target.value)}
2263
+ placeholder={selectedPiSub?.defaultModel || 'model-id'}
2264
+ className={inputSmCls + ' font-mono'}
2265
+ />
2333
2266
  )}
2267
+ </div>
2268
+ </div>
2334
2269
 
2335
- {selectedPiSub.needsApiKey && (
2336
- <div>
2337
- <label className="text-[11px] text-white/40 font-medium mb-1 block flex items-center justify-between">
2338
- <span>API key</span>
2339
- {selectedPiSub.apiKeyUrl && (
2340
- <button
2341
- type="button"
2342
- onClick={() => openExternal(selectedPiSub.apiKeyUrl!)}
2343
- className="text-white/30 hover:text-white/60 text-[10.5px] flex items-center gap-1"
2344
- >
2345
- Get a key <ExternalLink className="h-3 w-3" />
2346
- </button>
2347
- )}
2348
- </label>
2349
- <div className="relative">
2350
- <input
2351
- type={piShowKey ? 'text' : 'password'}
2352
- value={piApiKey}
2353
- onChange={(e) => setPiApiKey(e.target.value)}
2354
- onKeyDown={(e) => e.key === 'Enter' && handlePiConnect()}
2355
- placeholder="sk-..."
2356
- className={inputSmCls + ' pr-10 font-mono'}
2357
- />
2358
- <button
2359
- type="button"
2360
- onClick={() => setPiShowKey((v) => !v)}
2361
- className="absolute right-3 top-1/2 -translate-y-1/2 text-white/20 hover:text-white/50 transition-colors"
2362
- >
2363
- {piShowKey ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
2364
- </button>
2365
- </div>
2366
- </div>
2367
- )}
2270
+ {selectedPiSub?.needsBaseUrl && (
2271
+ <input
2272
+ type="text"
2273
+ value={piBaseUrl}
2274
+ onChange={(e) => setPiBaseUrl(e.target.value)}
2275
+ placeholder={selectedPiSub.baseUrl || 'https://example.com/v1'}
2276
+ className={inputSmCls + ' font-mono'}
2277
+ />
2278
+ )}
2368
2279
 
2369
- <div>
2370
- <label className="text-[11px] text-white/40 font-medium mb-1 block">Model</label>
2371
- {Array.isArray(selectedPiSub.models) ? (
2372
- <ModelDropdown
2373
- models={selectedPiSub.models}
2374
- value={piModelId}
2375
- onChange={setPiModelId}
2376
- />
2377
- ) : (
2378
- <input
2379
- type="text"
2380
- value={piModelId}
2381
- onChange={(e) => setPiModelId(e.target.value)}
2382
- placeholder={selectedPiSub.defaultModel || 'model-id'}
2383
- className={inputSmCls + ' font-mono'}
2384
- />
2280
+ {selectedPiSub?.needsApiKey && (
2281
+ <div className="relative">
2282
+ <input
2283
+ type={piShowKey ? 'text' : 'password'}
2284
+ value={piApiKey}
2285
+ onChange={(e) => setPiApiKey(e.target.value)}
2286
+ onKeyDown={(e) => e.key === 'Enter' && handlePiConnect()}
2287
+ placeholder="API key…"
2288
+ className={inputSmCls + ' pr-16 font-mono'}
2289
+ />
2290
+ <div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1.5">
2291
+ {selectedPiSub.apiKeyUrl && (
2292
+ <button
2293
+ type="button"
2294
+ onClick={() => openExternal(selectedPiSub.apiKeyUrl!)}
2295
+ className="text-white/25 hover:text-white/55 transition-colors"
2296
+ title="Get a key"
2297
+ >
2298
+ <ExternalLink className="h-3.5 w-3.5" />
2299
+ </button>
2385
2300
  )}
2301
+ <button
2302
+ type="button"
2303
+ onClick={() => setPiShowKey((v) => !v)}
2304
+ className="text-white/25 hover:text-white/55 transition-colors"
2305
+ >
2306
+ {piShowKey ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
2307
+ </button>
2386
2308
  </div>
2309
+ </div>
2310
+ )}
2387
2311
 
2388
- {piError && (
2389
- <div className="bg-red-500/8 border border-red-500/15 rounded-lg px-3 py-2">
2390
- <p className="text-[12px] text-red-400/90 break-words">{piError}</p>
2391
- </div>
2392
- )}
2393
-
2394
- <button
2395
- onClick={handlePiConnect}
2396
- disabled={
2397
- piConnecting ||
2398
- !piSubProvider ||
2399
- (selectedPiSub.needsApiKey && !piApiKey.trim()) ||
2400
- (selectedPiSub.needsBaseUrl && !piBaseUrl.trim()) ||
2401
- !piModelId.trim()
2402
- }
2403
- className="w-full py-2.5 px-4 bg-gradient-brand hover:opacity-90 text-white text-[13px] font-medium rounded-xl transition-colors flex items-center justify-center gap-2 disabled:opacity-40"
2404
- >
2405
- {piConnecting ? (
2406
- <><LoaderCircle className="h-3.5 w-3.5 animate-spin" />Connecting…</>
2407
- ) : (
2408
- <>Test &amp; connect<ArrowRight className="h-3.5 w-3.5 opacity-60" /></>
2409
- )}
2410
- </button>
2312
+ {piError && (
2313
+ <div className="bg-red-500/8 border border-red-500/15 rounded-lg px-3 py-2">
2314
+ <p className="text-[12px] text-red-400/90 break-words">{piError}</p>
2411
2315
  </div>
2412
2316
  )}
2317
+
2318
+ <button
2319
+ onClick={handlePiConnect}
2320
+ disabled={
2321
+ piConnecting ||
2322
+ !piSubProvider ||
2323
+ (!!selectedPiSub?.needsApiKey && !piApiKey.trim()) ||
2324
+ (!!selectedPiSub?.needsBaseUrl && !piBaseUrl.trim()) ||
2325
+ !piModelId.trim()
2326
+ }
2327
+ className="w-full py-2.5 px-4 bg-gradient-brand hover:opacity-90 text-white text-[13px] font-medium rounded-xl transition-colors flex items-center justify-center gap-2 disabled:opacity-40"
2328
+ >
2329
+ {piConnecting ? (
2330
+ <><LoaderCircle className="h-3.5 w-3.5 animate-spin" />Connecting…</>
2331
+ ) : (
2332
+ <>Test &amp; connect<ArrowRight className="h-3.5 w-3.5 opacity-60" /></>
2333
+ )}
2334
+ </button>
2413
2335
  </>
2414
2336
  )}
2415
2337
  </div>
2416
2338
  )}
2417
2339
 
2340
+ {/* ── Auth flow: Bloby (coming soon placeholder) ── */}
2341
+ {provider === 'bloby' && (
2342
+ <div className="space-y-3">
2343
+ <p className="text-[12px] text-white/40 leading-relaxed">
2344
+ Bloby (managed) is on the way. Sign in with Google once it ships and your bot will be hosted, updated, and billed by us — no API keys to manage.
2345
+ </p>
2346
+ <button
2347
+ type="button"
2348
+ disabled
2349
+ title="Coming soon"
2350
+ className="w-full py-2.5 px-4 bg-white/[0.04] border border-white/[0.06] text-white/35 text-[13px] font-medium rounded-xl flex items-center justify-center gap-2 cursor-not-allowed"
2351
+ >
2352
+ <svg viewBox="0 0 24 24" className="h-4 w-4 opacity-50" fill="currentColor">
2353
+ <path d="M21.35 11.1h-9.17v2.96h5.27c-.23 1.39-1.62 4.08-5.27 4.08-3.17 0-5.76-2.62-5.76-5.85s2.59-5.85 5.76-5.85c1.81 0 3.02.77 3.71 1.43l2.53-2.43C16.85 3.91 14.74 3 12.18 3 7.03 3 2.86 7.17 2.86 12.29s4.17 9.29 9.32 9.29c5.38 0 8.94-3.79 8.94-9.12 0-.61-.07-1.08-.17-1.55Z"/>
2354
+ </svg>
2355
+ Login with Google
2356
+ </button>
2357
+ <p className="text-[10.5px] text-white/25 text-center">Not available yet.</p>
2358
+ </div>
2359
+ )}
2360
+
2418
2361
  {/* ── Auth flow: Anthropic ── */}
2419
2362
  {provider === 'anthropic' && (
2420
2363
  <div className="space-y-2.5">
@@ -9,6 +9,12 @@
9
9
  export interface AsyncQueue<T> extends AsyncIterable<T> {
10
10
  push(item: T): void;
11
11
  end(): void;
12
+ /**
13
+ * Non-blocking drain: return every item currently buffered without waiting
14
+ * for a new one. Used by the session to fold mid-turn user messages into
15
+ * the model's history without breaking the outer `for await` consumer.
16
+ */
17
+ drainPending(): T[];
12
18
  }
13
19
 
14
20
  export function createAsyncQueue<T>(): AsyncQueue<T> {
@@ -30,6 +36,10 @@ export function createAsyncQueue<T>(): AsyncQueue<T> {
30
36
  done = true;
31
37
  if (resolve) resolve({ value: undefined as any, done: true });
32
38
  },
39
+ drainPending() {
40
+ if (pending.length === 0) return [];
41
+ return pending.splice(0, pending.length);
42
+ },
33
43
  [Symbol.asyncIterator]() {
34
44
  return {
35
45
  next(): Promise<IteratorResult<T>> {
@@ -82,6 +82,26 @@ function formatConversationHistory(messages: RecentMessage[]): string {
82
82
  return messages.map((m) => `${m.role}: ${m.content}`).join('\n\n');
83
83
  }
84
84
 
85
+ /**
86
+ * Live-conversation pacing hint. The Claude Agent SDK trains its model to do
87
+ * this natively; non-Anthropic models (Gemini especially) tend to go silent
88
+ * during tool loops and never report progress. This nudge makes the
89
+ * conversation feel alive — quick acknowledgement before long tasks, short
90
+ * status notes between tool calls, and inline answers if the user types
91
+ * something while the agent is mid-task.
92
+ */
93
+ const LIVE_CONVERSATION_HINT = `
94
+
95
+ ---
96
+ # Live-conversation pacing
97
+
98
+ You are running in a streaming chat where the user can keep typing while you work. Make the conversation feel alive:
99
+
100
+ - Before kicking off a multi-step task, say one short line acknowledging it ("On it, looking at the widget now.").
101
+ - Between tool calls on long tasks, drop a brief progress note ("Found the file, checking the layout next.") so the user knows you're still working.
102
+ - If a new user message arrives while you're mid-task, you'll see it as a fresh user-role message in the conversation history. Answer it briefly inline, mention you're still working on the main task, then continue.
103
+ - Final answers should be concise and concrete.`;
104
+
85
105
  async function buildSystemPrompt(
86
106
  names?: { botName: string; humanName: string },
87
107
  recentMessages?: RecentMessage[],
@@ -89,6 +109,7 @@ async function buildSystemPrompt(
89
109
  const memoryFiles = readMemoryFiles();
90
110
  const basePrompt = await assembleSystemPrompt(names?.botName, names?.humanName);
91
111
  let systemPrompt = basePrompt;
112
+ systemPrompt += LIVE_CONVERSATION_HINT;
92
113
  systemPrompt += `\n\n---\n# Your Memory Files\n\n## MYSELF.md\n${memoryFiles.myself}\n\n## MYHUMAN.md\n${memoryFiles.myhuman}\n\n## MEMORY.md\n${memoryFiles.memory}\n\n---\n# Your Config Files\n\n## PULSE.json\n${memoryFiles.pulse}\n\n## CRONS.json\n${memoryFiles.crons}`;
93
114
 
94
115
  try {