ltcai 4.6.0 → 4.7.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.
Files changed (37) hide show
  1. package/README.md +145 -194
  2. package/docs/CHANGELOG.md +139 -1
  3. package/docs/PRODUCT_DIRECTION_REVIEW.md +88 -0
  4. package/docs/V4_6_0_LIVING_BRAIN_EXPERIENCE_REPORT.md +33 -19
  5. package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +42 -0
  6. package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +42 -0
  7. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +20 -18
  8. package/frontend/src/App.tsx +1098 -171
  9. package/frontend/src/api/client.ts +2 -0
  10. package/frontend/src/components/BrainConversation.tsx +10 -2
  11. package/frontend/src/components/LivingBrain.tsx +197 -106
  12. package/frontend/src/components/ProductFlow.tsx +210 -129
  13. package/frontend/src/styles.css +1946 -36
  14. package/lattice_brain/__init__.py +1 -1
  15. package/lattice_brain/archive.py +86 -13
  16. package/lattice_brain/portability.py +82 -14
  17. package/lattice_brain/runtime/multi_agent.py +1 -1
  18. package/latticeai/__init__.py +1 -1
  19. package/latticeai/api/admin.py +30 -4
  20. package/latticeai/api/chat.py +25 -11
  21. package/latticeai/app_factory.py +8 -2
  22. package/latticeai/core/audit.py +3 -2
  23. package/latticeai/core/marketplace.py +1 -1
  24. package/latticeai/core/workspace_os.py +1 -1
  25. package/package.json +1 -1
  26. package/scripts/launch-pts-grok.sh +56 -0
  27. package/src-tauri/Cargo.lock +1 -1
  28. package/src-tauri/Cargo.toml +1 -1
  29. package/src-tauri/tauri.conf.json +1 -1
  30. package/static/app/asset-manifest.json +5 -5
  31. package/static/app/assets/index-DFmuiJ6t.css +2 -0
  32. package/static/app/assets/index-DwX3rNfA.js +16 -0
  33. package/static/app/assets/index-DwX3rNfA.js.map +1 -0
  34. package/static/app/index.html +2 -2
  35. package/static/app/assets/index-By-G-Kay.css +0 -2
  36. package/static/app/assets/index-CJx6WuQH.js +0 -336
  37. package/static/app/assets/index-CJx6WuQH.js.map +0 -1
@@ -78,39 +78,58 @@ export function ProductFlow({ onComplete }: { onComplete: () => void }) {
78
78
  }, [analysis, step]);
79
79
 
80
80
  return (
81
- <main className="product-flow-shell" aria-label="Lattice first run">
82
- <div className="product-flow-orbit" aria-hidden="true" />
83
- {step === "login" ? (
84
- <LoginScreen onSuccess={() => setStep("analysis")} />
85
- ) : null}
86
- {step === "analysis" ? (
87
- <AnalysisScreen
88
- analysis={analysis}
89
- error={analysisError}
90
- onContinue={() => setStep("recommend")}
91
- />
92
- ) : null}
93
- {step === "recommend" ? (
94
- <RecommendationScreen
95
- recommendations={recommendations}
96
- onBack={() => setStep("analysis")}
97
- onSelect={(model) => {
98
- setSelected(model);
99
- setStep("install");
100
- }}
101
- />
102
- ) : null}
103
- {step === "install" ? (
104
- <InstallScreen
105
- model={selected || recommendations[0] || fallbackModel()}
106
- onBack={() => setStep("recommend")}
107
- onComplete={() => {
108
- try { localStorage.setItem(FLOW_COMPLETE_KEY, "true"); } catch {}
109
- onComplete();
110
- }}
111
- />
112
- ) : null}
113
- </main>
81
+ <div className="ritual-shell" aria-label="Awaken your Brain">
82
+ <div className="ritual-container">
83
+ {/* The living presence participates in the ritual at every step */}
84
+ <div className="ritual-brain">
85
+ <LivingBrain
86
+ state={
87
+ step === "login" ? "idle" :
88
+ step === "analysis" ? "listening" :
89
+ step === "recommend" ? "recalling" :
90
+ "thinking"
91
+ }
92
+ intensity={step === "install" ? 0.92 : 0.7}
93
+ size="large"
94
+ showLabel={false}
95
+ />
96
+ </div>
97
+
98
+ {step === "login" && (
99
+ <LoginScreen onSuccess={() => setStep("analysis")} />
100
+ )}
101
+
102
+ {step === "analysis" && (
103
+ <AnalysisScreen
104
+ analysis={analysis}
105
+ error={analysisError}
106
+ onContinue={() => setStep("recommend")}
107
+ />
108
+ )}
109
+
110
+ {step === "recommend" && (
111
+ <RecommendationScreen
112
+ recommendations={recommendations}
113
+ onBack={() => setStep("analysis")}
114
+ onSelect={(model) => {
115
+ setSelected(model);
116
+ setStep("install");
117
+ }}
118
+ />
119
+ )}
120
+
121
+ {step === "install" && (
122
+ <InstallScreen
123
+ model={selected || recommendations[0] || fallbackModel()}
124
+ onBack={() => setStep("recommend")}
125
+ onComplete={() => {
126
+ try { localStorage.setItem(FLOW_COMPLETE_KEY, "true"); } catch {}
127
+ onComplete();
128
+ }}
129
+ />
130
+ )}
131
+ </div>
132
+ </div>
114
133
  );
115
134
  }
116
135
 
@@ -157,34 +176,59 @@ function LoginScreen({ onSuccess }: { onSuccess: () => void }) {
157
176
  }
158
177
 
159
178
  return (
160
- <section className="login-screen" aria-label="Login">
161
- <div className="login-mark" aria-hidden="true"><LockKeyhole className="h-5 w-5" /></div>
162
- <div className="login-card">
163
- <div>
164
- <div className="login-kicker">Lattice AI</div>
165
- <h1>Enter your Brain.</h1>
166
- <p>Your private workspace starts with a local profile.</p>
179
+ <div>
180
+ <div className="ritual-title">Welcome to your mind.</div>
181
+ <div className="ritual-subtitle">
182
+ Models will change. Your knowledge should not. Lattice keeps your documents, conversations, decisions, and context together as a private Brain you own.
183
+ </div>
184
+
185
+ <ProductPromise />
186
+
187
+ <form onSubmit={submit} className="ritual-card" style={{ maxWidth: 420, margin: "0 auto" }}>
188
+ <div style={{ display: "grid", gap: "0.85rem" }}>
189
+ <div>
190
+ <div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}>Your name</div>
191
+ <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="You" />
192
+ </div>
193
+ <div>
194
+ <div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}>Email (local only)</div>
195
+ <Input value={email} onChange={(e) => setEmail(e.target.value)} type="email" placeholder="you@local" />
196
+ </div>
197
+ <div>
198
+ <div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}>Password</div>
199
+ <Input value={password} onChange={(e) => setPassword(e.target.value)} type="password" placeholder="Create a strong local password" />
200
+ </div>
167
201
  </div>
168
- <form className="login-form" onSubmit={submit}>
169
- <label>
170
- <span>Name</span>
171
- <Input value={name} onChange={(event) => setName(event.target.value)} autoComplete="name" />
172
- </label>
173
- <label>
174
- <span>Email</span>
175
- <Input value={email} onChange={(event) => setEmail(event.target.value)} type="email" autoComplete="email" />
176
- </label>
177
- <label>
178
- <span>Password</span>
179
- <Input value={password} onChange={(event) => setPassword(event.target.value)} type="password" autoComplete="current-password" placeholder="Use your local password" />
180
- </label>
181
- {error ? <div className="flow-error">{error}</div> : null}
182
- <Button className="login-submit" type="submit" disabled={busy || !email.trim()}>
183
- {busy ? "Opening" : "Continue"} <ChevronRight className="h-4 w-4" />
184
- </Button>
185
- </form>
202
+
203
+ {error && <div style={{ marginTop: "0.85rem", padding: "0.6rem 0.85rem", background: "hsl(var(--destructive)/0.12)", border: "1px solid hsl(var(--destructive)/0.4)", borderRadius: 10, fontSize: "0.9rem" }}>{error}</div>}
204
+
205
+ <Button type="submit" disabled={busy || !email.trim()} style={{ width: "100%", marginTop: "1rem" }}>
206
+ {busy ? "Opening the Brain..." : "Open my Brain"}
207
+ </Button>
208
+ <div style={{ fontSize: "0.75rem", color: "hsl(var(--fg-muted))", marginTop: "0.6rem" }}>
209
+ Local profile first. Model choice comes later.
210
+ </div>
211
+ </form>
212
+ </div>
213
+ );
214
+ }
215
+
216
+ function ProductPromise() {
217
+ return (
218
+ <div className="ritual-promise" aria-label="Lattice AI product promise">
219
+ <div>
220
+ <span>Durable knowledge</span>
221
+ <strong>Your work becomes long-lived context.</strong>
222
+ </div>
223
+ <div>
224
+ <span>Replaceable models</span>
225
+ <strong>The model is a voice, not the asset.</strong>
226
+ </div>
227
+ <div>
228
+ <span>User ownership</span>
229
+ <strong>Back up, restore, and move your Brain.</strong>
186
230
  </div>
187
- </section>
231
+ </div>
188
232
  );
189
233
  }
190
234
 
@@ -199,36 +243,42 @@ function AnalysisScreen({
199
243
  }) {
200
244
  const detected = buildDetectedFacts(analysis);
201
245
  return (
202
- <section className="flow-panel analysis-screen" aria-label="Environment Analysis">
203
- <div className="flow-panel-head">
204
- <div>
205
- <div className="flow-kicker"><MonitorCog className="h-4 w-4" /> Environment Analysis</div>
206
- <h1>Learning what your computer can do.</h1>
207
- <p>Lattice checks the essentials, then recommends the best local Brain for this machine.</p>
208
- </div>
209
- <Badge variant={analysis ? "success" : "muted"}>{analysis ? "complete" : "analyzing"}</Badge>
246
+ <div>
247
+ <div className="ritual-title">Understanding your home.</div>
248
+ <div className="ritual-subtitle">
249
+ Lattice is checking what this computer can support so your Brain can run locally instead of turning your memories into a cloud dependency.
210
250
  </div>
211
- <div className="analysis-grid">
212
- {detected.map((item) => (
213
- <div key={item.label} className="analysis-fact">
214
- <span>{item.label}</span>
215
- <strong>{item.value}</strong>
216
- <small>{item.detail}</small>
251
+
252
+ <div className="ritual-fact-grid">
253
+ {detected.map((item, idx) => (
254
+ <div key={idx} className="ritual-fact">
255
+ <div className="ritual-fact-label">{item.label}</div>
256
+ <div className="ritual-fact-value">{item.value}</div>
257
+ <div style={{ fontSize: "0.8rem", color: "hsl(var(--fg-muted))", marginTop: 3 }}>{item.detail}</div>
217
258
  </div>
218
259
  ))}
219
260
  </div>
220
- <div className="recommendation-callout">
221
- <Sparkles className="h-5 w-5" />
222
- <div>
223
- <strong>{analysis ? recommendedSummary(analysis) : "Recommendation is being prepared."}</strong>
224
- <span>{analysis ? "You will choose from a short, ranked list next." : "This usually takes a moment."}</span>
261
+
262
+ <div className="ritual-card" style={{ marginTop: "1rem" }}>
263
+ <div style={{ display: "flex", alignItems: "center", gap: "0.6rem" }}>
264
+ <Sparkles style={{ color: "hsl(var(--brain-core))" }} />
265
+ <div>
266
+ <div style={{ fontWeight: 620 }}>{analysis ? recommendedSummary(analysis) : "Preparing the best fit..."}</div>
267
+ <div style={{ fontSize: "0.9rem", color: "hsl(var(--fg-muted))" }}>
268
+ {analysis ? "A short, personal list of minds is ready for you to choose from." : "Reading your machine. This is gentle."}
269
+ </div>
270
+ </div>
225
271
  </div>
226
272
  </div>
227
- {error ? <div className="flow-error">{error}</div> : null}
228
- <div className="flow-actions">
229
- <Button onClick={onContinue} disabled={!analysis && !error}>See recommended models</Button>
273
+
274
+ {error && <div className="ritual-card" style={{ borderColor: "hsl(var(--destructive)/0.4)", background: "hsl(var(--destructive)/0.06)" }}>{error}</div>}
275
+
276
+ <div style={{ marginTop: "1.25rem" }}>
277
+ <Button onClick={onContinue} disabled={!analysis && !error} style={{ minWidth: 260 }}>
278
+ See how your Brain can think
279
+ </Button>
230
280
  </div>
231
- </section>
281
+ </div>
232
282
  );
233
283
  }
234
284
 
@@ -243,36 +293,34 @@ function RecommendationScreen({
243
293
  }) {
244
294
  const items = recommendations.length ? recommendations : [fallbackModel()];
245
295
  return (
246
- <section className="flow-panel recommendation-screen" aria-label="Recommended Models">
247
- <div className="flow-panel-head">
248
- <div>
249
- <div className="flow-kicker"><Cpu className="h-4 w-4" /> Recommended Models</div>
250
- <h1>Recommended for your computer.</h1>
251
- <p>A short list, ranked for this Mac. No catalog digging required.</p>
252
- </div>
296
+ <div>
297
+ <div className="ritual-title">How shall your mind think today?</div>
298
+ <div className="ritual-subtitle">
299
+ The model is the current voice of your Brain. You can replace it later; your knowledge stays.
253
300
  </div>
254
- <div className="model-recommendation-list">
301
+
302
+ <div style={{ maxWidth: 560, margin: "0 auto" }}>
255
303
  {items.slice(0, 3).map((model, index) => (
256
304
  <button
257
305
  key={`${model.role}-${model.id}`}
258
- className={cn("model-recommendation-card", model.role)}
259
- onClick={() => onSelect(model)}
306
+ className="ritual-model-card"
307
+ onClick={() => model.supported && onSelect(model)}
260
308
  disabled={!model.supported}
309
+ style={{ width: "100%" }}
261
310
  >
262
- <span className="model-rank">{rankLabel(model.role, index)}</span>
263
- <span>
264
- <strong>{model.shortName}</strong>
265
- <small>{model.reason}</small>
266
- </span>
267
- <Badge variant={model.supported ? "success" : "warning"}>{model.supported ? model.size || "ready" : "needs update"}</Badge>
311
+ <div className="role">{rankLabel(model.role, index)}</div>
312
+ <div className="name">{model.shortName}</div>
313
+ <div className="reason">{model.reason} · {model.size || "ready"}</div>
314
+ {!model.supported && <div style={{ color: "hsl(var(--destructive))", marginTop: 6, fontSize: "0.85rem" }}>Needs attention on this machine</div>}
268
315
  </button>
269
316
  ))}
270
317
  </div>
271
- <div className="flow-actions split">
318
+
319
+ <div style={{ marginTop: "1.1rem", display: "flex", justifyContent: "center", gap: "1rem", alignItems: "center" }}>
272
320
  <Button variant="ghost" onClick={onBack}>Back</Button>
273
- <span>Choose one recommendation to continue.</span>
321
+ <div style={{ fontSize: "0.82rem", color: "hsl(var(--fg-muted))" }}>Your choice becomes the current voice of your Brain.</div>
274
322
  </div>
275
- </section>
323
+ </div>
276
324
  );
277
325
  }
278
326
 
@@ -288,7 +336,7 @@ function InstallScreen({
288
336
  const [busy, setBusy] = React.useState(false);
289
337
  const [stage, setStage] = React.useState<InstallStage>("idle");
290
338
  const [percent, setPercent] = React.useState(0);
291
- const [message, setMessage] = React.useState("Ready when you are.");
339
+ const [message, setMessage] = React.useState("Your Brain is waiting for this mind.");
292
340
  const [error, setError] = React.useState<string | null>(null);
293
341
 
294
342
  async function start() {
@@ -329,39 +377,72 @@ function InstallScreen({
329
377
  }
330
378
  }
331
379
 
380
+ const brainStateForStage: any =
381
+ stage === "download" ? "thinking" :
382
+ stage === "validate" ? "recalling" :
383
+ stage === "load" ? "synthesizing" :
384
+ stage === "done" ? "idle" : "listening";
385
+
332
386
  return (
333
- <section className="flow-panel install-screen" aria-label="Install and Load">
334
- <div className="install-hero">
335
- <LivingBrain activity={busy ? "thinking" : stage === "done" ? "listening" : "idle"} compact />
336
- <div>
337
- <div className="flow-kicker"><Download className="h-4 w-4" /> Install & Load</div>
338
- <h1>{model.shortName}</h1>
339
- <p>Lattice will install, download, validate, and load the selected Brain.</p>
340
- </div>
387
+ <div>
388
+ <div className="ritual-title">Bring this mind home.</div>
389
+ <div className="ritual-subtitle">
390
+ <strong>{model.shortName}</strong> — {model.reason}.<br />
391
+ This gives your Brain a local voice. Download, validation, and loading happen only with your consent.
341
392
  </div>
342
- <div className="install-steps">
343
- {(["install", "download", "validate", "load"] as const).map((item) => (
344
- <div key={item} className={cn("install-step", installStepState(stage, item))}>
345
- <CheckCircle2 className="h-4 w-4" />
346
- <span>{installLabel(item)}</span>
347
- </div>
348
- ))}
393
+
394
+ {/* Living Brain reacts to the ceremony of installation */}
395
+ <div style={{ margin: "0.6rem auto 1rem" }}>
396
+ <LivingBrain
397
+ state={brainStateForStage}
398
+ intensity={stage === "download" || stage === "load" ? 0.96 : 0.82}
399
+ size="normal"
400
+ />
349
401
  </div>
350
- <div className="install-progress">
351
- <div>
352
- <strong>{message}</strong>
353
- <span>{stage === "error" ? "We will explain what to try next." : `${Math.round(percent)}%`}</span>
402
+
403
+ <div className="ritual-progress">
404
+ <div className="ritual-stage-list">
405
+ {(["install", "download", "validate", "load"] as const).map((item) => (
406
+ <div key={item} className={`ritual-stage ${installStepState(stage, item)}`}>
407
+ <CheckCircle2 style={{ width: 15, height: 15 }} />
408
+ <span>{installLabel(item)}</span>
409
+ </div>
410
+ ))}
411
+ </div>
412
+
413
+ <div className="ritual-bar">
414
+ <span style={{ width: `${Math.max(4, Math.min(100, percent))}%` }} />
354
415
  </div>
355
- <div className="install-bar"><span style={{ width: `${Math.max(0, Math.min(100, percent))}%` }} /></div>
356
416
  </div>
357
- {error ? <div className="flow-error">{error}</div> : null}
358
- <div className="flow-actions split">
359
- <Button variant="ghost" onClick={onBack} disabled={busy}>Back</Button>
360
- <Button onClick={stage === "done" ? onComplete : start} disabled={busy || !model.supported}>
361
- {stage === "done" ? "Enter Brain" : busy ? "Loading" : "Install & Load"}
362
- </Button>
417
+
418
+ <div className="ritual-status">{message}</div>
419
+
420
+ {error && (
421
+ <div className="ritual-card" style={{ borderColor: "hsl(var(--destructive)/0.45)", background: "hsl(var(--destructive)/0.07)", marginBottom: "1rem" }}>
422
+ {error}
423
+ <div style={{ marginTop: "0.5rem", fontSize: "0.85rem" }}>You can go back and choose a different mind, or try again.</div>
424
+ </div>
425
+ )}
426
+
427
+ <div style={{ display: "flex", gap: "0.75rem", justifyContent: "center", marginTop: "1rem" }}>
428
+ <Button variant="ghost" onClick={onBack} disabled={busy}>Choose differently</Button>
429
+
430
+ {stage !== "done" ? (
431
+ <Button
432
+ onClick={start}
433
+ disabled={busy || !model.supported}
434
+ >
435
+ {busy ? "Waking the mind..." : "Yes — make this my Brain"}
436
+ </Button>
437
+ ) : (
438
+ <Button onClick={onComplete}>Enter your Brain</Button>
439
+ )}
440
+ </div>
441
+
442
+ <div style={{ fontSize: "0.72rem", color: "hsl(var(--fg-muted))", marginTop: "0.9rem" }}>
443
+ Explicit consent only. All work happens locally on your machine.
363
444
  </div>
364
- </section>
445
+ </div>
365
446
  );
366
447
  }
367
448