ltcai 6.1.0 → 6.2.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 (44) hide show
  1. package/README.md +33 -38
  2. package/docs/CHANGELOG.md +33 -1
  3. package/frontend/src/App.tsx +3 -1286
  4. package/frontend/src/components/LanguageSwitcher.tsx +23 -0
  5. package/frontend/src/components/ProductFlow.tsx +32 -669
  6. package/frontend/src/components/onboarding/ProductFlowScreens.tsx +688 -0
  7. package/frontend/src/features/admin/AdminConsole.tsx +294 -0
  8. package/frontend/src/features/brain/BrainHome.tsx +999 -0
  9. package/frontend/src/features/brain/brainData.ts +98 -0
  10. package/frontend/src/features/brain/graphLayout.ts +26 -0
  11. package/frontend/src/features/brain/types.ts +44 -0
  12. package/frontend/src/i18n.ts +198 -0
  13. package/frontend/src/styles.css +220 -0
  14. package/lattice_brain/__init__.py +1 -1
  15. package/lattice_brain/runtime/multi_agent.py +1 -1
  16. package/latticeai/__init__.py +1 -1
  17. package/latticeai/api/tools.py +50 -23
  18. package/latticeai/app_factory.py +36 -45
  19. package/latticeai/cli/entrypoint.py +283 -0
  20. package/latticeai/core/marketplace.py +1 -1
  21. package/latticeai/core/workspace_os.py +1 -1
  22. package/latticeai/integrations/__init__.py +0 -0
  23. package/latticeai/integrations/telegram_bot.py +1009 -0
  24. package/latticeai/runtime/lifespan_runtime.py +1 -1
  25. package/latticeai/runtime/platform_runtime_wiring.py +87 -0
  26. package/latticeai/runtime/router_registration.py +49 -30
  27. package/latticeai/services/p_reinforce.py +258 -0
  28. package/latticeai/services/router_context.py +52 -0
  29. package/ltcai_cli.py +7 -279
  30. package/p_reinforce.py +4 -255
  31. package/package.json +2 -1
  32. package/scripts/release_smoke.py +133 -0
  33. package/scripts/wheel_smoke.py +4 -0
  34. package/src-tauri/Cargo.lock +1 -1
  35. package/src-tauri/Cargo.toml +1 -1
  36. package/src-tauri/tauri.conf.json +1 -1
  37. package/static/app/asset-manifest.json +5 -5
  38. package/static/app/assets/{index-B744yblP.css → index-B2-1Gm0q.css} +1 -1
  39. package/static/app/assets/index-D91Rz5--.js +16 -0
  40. package/static/app/assets/index-D91Rz5--.js.map +1 -0
  41. package/static/app/index.html +2 -2
  42. package/telegram_bot.py +9 -1002
  43. package/static/app/assets/index-DYaUKNfl.js +0 -16
  44. package/static/app/assets/index-DYaUKNfl.js.map +0 -1
@@ -0,0 +1,98 @@
1
+ import { asArray } from "@/lib/utils";
2
+ import type { ApiRecord, KnowledgeConcept, KnowledgeGraphModel, MemoryFragment, RelationshipThread } from "./types";
3
+ import { clamp } from "./graphLayout";
4
+
5
+ export function buildMemoryFragments(memoryData: unknown, historyData: unknown): MemoryFragment[] {
6
+ const memory = isRecord(memoryData) ? memoryData : {};
7
+ const sourceRows = asArray<ApiRecord>(memory.sources).length
8
+ ? asArray<ApiRecord>(memory.sources)
9
+ : asArray<ApiRecord>(memory.tiers);
10
+ const sourceFragments = sourceRows.map((item, index) => ({
11
+ id: textValue(item, ["id", "source", "label"], `memory-${index}`),
12
+ title: textValue(item, ["title", "label", "source", "path", "name"], "Workspace memory"),
13
+ kind: titleValue(item, ["type", "source_type", "kind", "health"], "Memory"),
14
+ }));
15
+ const conversationFragments = asArray<ApiRecord>(historyData).map((item, index) => ({
16
+ id: textValue(item, ["id", "conversation_id"], `conversation-${index}`),
17
+ title: textValue(item, ["title", "summary", "id"], "Conversation"),
18
+ kind: "Conversation",
19
+ }));
20
+
21
+ return uniqueById([...sourceFragments, ...conversationFragments]).slice(0, 10);
22
+ }
23
+
24
+ export function parseKnowledgeGraph(data: unknown): KnowledgeGraphModel {
25
+ const graph = isRecord(data) ? data : {};
26
+ const rawNodes = asArray<ApiRecord>(graph.nodes);
27
+ const rawEdges = asArray<ApiRecord>(graph.edges);
28
+ const nodes = rawNodes.flatMap((node): KnowledgeConcept[] => {
29
+ const id = textValue(node, ["id", "node_id", "title", "label"]);
30
+ if (!id) return [];
31
+ const metadata = isRecord(node.metadata) ? node.metadata : {};
32
+ const type = titleValue(node, ["type", "kind", "category"], "Concept");
33
+ const label = textValue(node, ["title", "label", "name"], id.replace(/^[^:]+:/, ""));
34
+ const summary = textValue(node, ["summary", "description", "snippet"]) || textValue(metadata, ["summary", "description", "relative_path", "filename"]);
35
+ const importance = clamp(numberValue(node, ["importance_norm", "importance", "score"]) || 0.5, 0.08, 1);
36
+ return [{ id, label, type, summary, importance }];
37
+ }).sort((left, right) => right.importance - left.importance);
38
+ const ids = new Set(nodes.map((node) => node.id));
39
+ const edges = rawEdges.flatMap((edge, index): RelationshipThread[] => {
40
+ const source = textValue(edge, ["from", "source", "source_id"]);
41
+ const target = textValue(edge, ["to", "target", "target_id"]);
42
+ if (!source || !target || !ids.has(source) || !ids.has(target)) return [];
43
+ return [{
44
+ id: textValue(edge, ["id"], `edge-${index}`),
45
+ source,
46
+ target,
47
+ label: titleValue(edge, ["type", "label", "relationship"], "Relates"),
48
+ weight: numberValue(edge, ["weight", "score", "confidence"]) || 1,
49
+ }];
50
+ });
51
+ return { nodes, edges };
52
+ }
53
+
54
+ export function currentModelName(data: unknown) {
55
+ const record = isRecord(data) ? data : {};
56
+ const current = textValue(record, ["current", "current_model", "local_model"]);
57
+ if (current) return current;
58
+ const loaded = asArray<ApiRecord>(record.loaded || record.loaded_models);
59
+ const firstLoaded = loaded.find((item) => item.id || item.name || item.model_id);
60
+ return firstLoaded ? textValue(firstLoaded, ["name", "id", "model_id"], "local mind") : "local mind";
61
+ }
62
+
63
+ function uniqueById<T extends { id: string }>(items: T[]) {
64
+ const seen = new Set<string>();
65
+ return items.filter((item) => {
66
+ if (seen.has(item.id)) return false;
67
+ seen.add(item.id);
68
+ return true;
69
+ });
70
+ }
71
+
72
+ export function isRecord(value: unknown): value is ApiRecord {
73
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
74
+ }
75
+
76
+ export function textValue(record: ApiRecord, keys: string[], fallback = "") {
77
+ for (const key of keys) {
78
+ const value = record[key];
79
+ if (typeof value === "string" && value.trim()) return value;
80
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
81
+ }
82
+ return fallback;
83
+ }
84
+
85
+ function titleValue(record: ApiRecord, keys: string[], fallback = "") {
86
+ const value = textValue(record, keys, fallback);
87
+ return value
88
+ .replace(/[_-]+/g, " ")
89
+ .replace(/\b\w/g, (character) => character.toUpperCase());
90
+ }
91
+
92
+ function numberValue(record: ApiRecord, keys: string[]) {
93
+ for (const key of keys) {
94
+ const value = Number(record[key]);
95
+ if (Number.isFinite(value)) return value;
96
+ }
97
+ return 0;
98
+ }
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import type { KnowledgeConcept } from "./types";
3
+
4
+ export function layoutGraphNodes(nodes: KnowledgeConcept[], radiusX: number, radiusY: number) {
5
+ return nodes.map((node, index) => {
6
+ const point = polarPoint(index, nodes.length, radiusX, radiusY, -88);
7
+ return { node, x: point.x, y: point.y };
8
+ });
9
+ }
10
+
11
+ export function polarPoint(index: number, total: number, radiusX: number, radiusY: number, offsetDegrees = -90) {
12
+ const count = Math.max(total, 1);
13
+ const angle = ((360 / count) * index + offsetDegrees) * Math.PI / 180;
14
+ return {
15
+ x: 50 + Math.cos(angle) * radiusX,
16
+ y: 50 + Math.sin(angle) * radiusY,
17
+ };
18
+ }
19
+
20
+ export function layerStyle(values: Record<string, string>) {
21
+ return values as React.CSSProperties;
22
+ }
23
+
24
+ export function clamp(value: number, min: number, max: number) {
25
+ return Math.max(min, Math.min(max, value));
26
+ }
@@ -0,0 +1,44 @@
1
+ import type { BrainState } from "@/components/LivingBrain";
2
+
3
+ export type ApiRecord = Record<string, unknown>;
4
+ export type BrainDepth = 1 | 2 | 3 | 4 | 5;
5
+
6
+ export type Message = {
7
+ role: "user" | "assistant";
8
+ content: string;
9
+ };
10
+
11
+ export type MemoryFragment = {
12
+ id: string;
13
+ title: string;
14
+ kind: string;
15
+ };
16
+
17
+ export type KnowledgeConcept = {
18
+ id: string;
19
+ label: string;
20
+ type: string;
21
+ summary: string;
22
+ importance: number;
23
+ };
24
+
25
+ export type RelationshipThread = {
26
+ id: string;
27
+ source: string;
28
+ target: string;
29
+ label: string;
30
+ weight: number;
31
+ };
32
+
33
+ export type KnowledgeGraphModel = {
34
+ nodes: KnowledgeConcept[];
35
+ edges: RelationshipThread[];
36
+ };
37
+
38
+ export const DEPTHS: Array<{ level: BrainDepth; label: string; state: BrainState }> = [
39
+ { level: 1, label: "Living Brain", state: "idle" },
40
+ { level: 2, label: "Memory Layer", state: "recalling" },
41
+ { level: 3, label: "Knowledge Layer", state: "synthesizing" },
42
+ { level: 4, label: "Relationship Layer", state: "planning" },
43
+ { level: 5, label: "Knowledge Graph", state: "synthesizing" },
44
+ ];
@@ -79,6 +79,96 @@ export const COPY: Record<Language, TextMap> = {
79
79
  "admin.kicker": "분리된 관리자 작업공간",
80
80
  "admin.title": "Admin Console",
81
81
  "admin.body": "사용자, 로그, 보안, Brain 상태는 일반 사용자 화면과 분리됩니다.",
82
+ "admin.overview": "관리 개요",
83
+ "admin.metric.users": "사용자",
84
+ "admin.metric.logs": "최근 로그",
85
+ "admin.metric.security": "보안",
86
+ "admin.metric.index": "Brain 인덱스",
87
+ "admin.status.ready": "준비됨",
88
+ "admin.status.unavailable": "사용 불가",
89
+ "admin.status.indexed": "인덱싱됨",
90
+ "admin.status.unknown": "알 수 없음",
91
+ "admin.panel.users": "사용자 디렉터리",
92
+ "admin.panel.people": "사람",
93
+ "admin.panel.roles": "역할 권한",
94
+ "admin.panel.access": "접근",
95
+ "admin.panel.logs": "활동 로그",
96
+ "admin.panel.audit": "감사",
97
+ "admin.panel.securityEvents": "보안 이벤트",
98
+ "admin.panel.protection": "보호",
99
+ "admin.panel.brainOps": "Brain 운영",
100
+ "admin.panel.maintenance": "유지보수",
101
+ "admin.empty.users": "관리 API가 보고한 사용자가 없습니다.",
102
+ "admin.empty.roles": "보고된 역할 매트릭스가 없습니다.",
103
+ "admin.empty.audit": "최근 감사 이벤트가 없습니다.",
104
+ "admin.empty.security": "보고된 보안 이벤트가 없습니다.",
105
+ "admin.fallback.localUser": "로컬 사용자",
106
+ "admin.fallback.member": "멤버",
107
+ "admin.fallback.role": "역할",
108
+ "admin.fallback.noCaps": "권한 없음",
109
+ "admin.filters.label": "감사 로그 필터",
110
+ "admin.filters.search": "로그 검색",
111
+ "admin.filters.searchAria": "감사 로그 검색",
112
+ "admin.filters.severityAria": "심각도별 필터",
113
+ "admin.filters.all": "모든 심각도",
114
+ "admin.filters.informational": "정보",
115
+ "admin.filters.notice": "알림",
116
+ "admin.filters.warning": "경고",
117
+ "admin.filters.high": "높음",
118
+ "admin.filters.matched": "{count}개 일치",
119
+ "admin.brain.summaryFallback": "로컬 Brain 서비스는 사용자 대화와 분리되어 있습니다.",
120
+ "admin.brain.rebuilding": "재구성 중",
121
+ "admin.brain.rebuild": "인덱스 재구성",
122
+ "admin.policy.fallback": "정책",
123
+ "admin.policy.quiet": "정책 API 대기 중",
124
+ "admin.retention.days": "{days}일 보관",
125
+ "admin.retention.detail": "{events}개 보관 · {candidates}개 내보내기/정리 검토 가능",
126
+ "admin.log.event": "이벤트",
127
+ "admin.log.system": "시스템",
128
+ "admin.log.recently": "최근",
129
+ "admin.source.loading": "불러오는 중",
130
+ "admin.source.live": "라이브",
131
+ "admin.index.ready": "인덱스 상태 준비됨",
132
+ "flow.analysis.fact.computer": "컴퓨터",
133
+ "flow.analysis.fact.memory": "메모리",
134
+ "flow.analysis.fact.graphics": "그래픽",
135
+ "flow.analysis.fact.support": "로컬 지원",
136
+ "flow.analysis.fact.models": "모델",
137
+ "flow.analysis.checking": "확인 중",
138
+ "flow.analysis.fact.computerDetail": "운영체제와 칩",
139
+ "flow.analysis.fact.memoryDetail": "로컬 사고를 위한 여유 공간",
140
+ "flow.analysis.fact.graphicsDetail": "로컬 가속 지원",
141
+ "flow.analysis.fact.supportDetail": "설치된 모델 도우미",
142
+ "flow.analysis.fact.modelsDetail": "설치된 로컬 Brain",
143
+ "flow.analysis.apple": "Apple Silicon Mac",
144
+ "flow.analysis.detected": "감지됨",
145
+ "flow.analysis.localReady": "로컬 가속 준비됨",
146
+ "flow.analysis.standardLocal": "표준 로컬 모드",
147
+ "flow.analysis.supportReady": "준비됨",
148
+ "flow.analysis.supportInstall": "준비 예정",
149
+ "flow.analysis.modelsInstalled": "{count}개 이미 설치됨",
150
+ "flow.analysis.noModels": "아직 설치된 모델 없음",
151
+ "flow.analysis.readyDetail": "로컬 Digital Brain 사용 준비 완료",
152
+ "flow.analysis.memoryReadyDetail": "추천 모델에 충분한 컨텍스트",
153
+ "flow.analysis.graphicsReadyDetail": "Lattice가 가장 좋은 경로를 선택합니다.",
154
+ "flow.analysis.supportReadyDetail": "설치된 모델 도우미가 감지되었습니다.",
155
+ "flow.analysis.supportInstallDetail": "Lattice가 필요한 항목을 추가합니다.",
156
+ "flow.analysis.modelsReadyDetail": "즉시 로드할 수 있는 모델이 있습니다.",
157
+ "flow.analysis.modelsInstallDetail": "Lattice가 첫 설치를 안내합니다.",
158
+ "flow.recommend.reason.best": "최적 경험",
159
+ "flow.recommend.reason.faster": "빠른 선택",
160
+ "flow.recommend.reason.advanced": "고급 선택",
161
+ "flow.recommend.reason.default": "추천",
162
+ "flow.recommend.sizeReady": "준비됨",
163
+ "flow.recommend.fallbackName": "추천 Brain",
164
+ "flow.install.stage.download": "모델 파일을 받는 중입니다.",
165
+ "flow.install.stage.validate": "Brain이 응답할 수 있는지 확인 중입니다.",
166
+ "flow.install.stage.load": "Brain을 불러오는 중입니다.",
167
+ "flow.install.stage.error": "확인이 필요한 일이 있습니다.",
168
+ "flow.install.step.install": "준비",
169
+ "flow.install.step.download": "다운로드",
170
+ "flow.install.step.validate": "확인",
171
+ "flow.install.step.load": "로드",
82
172
  "flow.shell": "내 로컬 AI 브레인 만들기",
83
173
  "flow.login.title": "내 AI 브레인의 주인을 정합니다.",
84
174
  "flow.login.body": "Lattice AI는 내 지식과 맥락을 이 컴퓨터에 보관하는 로컬 우선 AI 브레인입니다. 모델은 바꿀 수 있고, 외부 전송은 사용자가 선택할 때만 시작됩니다.",
@@ -124,7 +214,16 @@ export const COPY: Record<Language, TextMap> = {
124
214
  "flow.install.start": "다운로드하고 시작하기",
125
215
  "flow.install.busy": "모델 준비 중...",
126
216
  "flow.install.enter": "Brain으로 들어가기",
217
+ "flow.install.later": "나중에 하기",
127
218
  "flow.install.local": "모델 다운로드와 외부 통신은 사용자가 시작할 때만 진행됩니다.",
219
+ "flow.consent.title": "다운로드 전 확인",
220
+ "flow.consent.body": "시작하면 아래 모델 파일을 외부 저장소에서 받아 이 컴퓨터에 저장합니다.",
221
+ "flow.consent.ready": "이미 준비된 모델이거나, 선택하기 전에는 외부 접속을 시작하지 않습니다.",
222
+ "flow.consent.size": "다운로드 크기",
223
+ "flow.consent.sizeUnknown": "런타임 확인 후 표시",
224
+ "flow.consent.location": "저장 위치",
225
+ "flow.consent.external": "외부 접속 대상",
226
+ "flow.consent.externalNone": "선택 전 외부 접속 없음",
128
227
  },
129
228
  en: {
130
229
  "language.label": "Language",
@@ -197,6 +296,96 @@ export const COPY: Record<Language, TextMap> = {
197
296
  "admin.kicker": "Separate admin workspace",
198
297
  "admin.title": "Admin Console",
199
298
  "admin.body": "Users, logs, security, and Brain health stay out of the normal user experience.",
299
+ "admin.overview": "Admin overview",
300
+ "admin.metric.users": "Users",
301
+ "admin.metric.logs": "Recent logs",
302
+ "admin.metric.security": "Security",
303
+ "admin.metric.index": "Brain index",
304
+ "admin.status.ready": "Ready",
305
+ "admin.status.unavailable": "Unavailable",
306
+ "admin.status.indexed": "Indexed",
307
+ "admin.status.unknown": "Unknown",
308
+ "admin.panel.users": "User Directory",
309
+ "admin.panel.people": "People",
310
+ "admin.panel.roles": "Role Permissions",
311
+ "admin.panel.access": "Access",
312
+ "admin.panel.logs": "Activity Logs",
313
+ "admin.panel.audit": "Audit",
314
+ "admin.panel.securityEvents": "Security Events",
315
+ "admin.panel.protection": "Protection",
316
+ "admin.panel.brainOps": "Brain Operations",
317
+ "admin.panel.maintenance": "Maintenance",
318
+ "admin.empty.users": "No users reported by the admin API.",
319
+ "admin.empty.roles": "No role matrix reported.",
320
+ "admin.empty.audit": "No recent audit events.",
321
+ "admin.empty.security": "No security events reported.",
322
+ "admin.fallback.localUser": "Local user",
323
+ "admin.fallback.member": "member",
324
+ "admin.fallback.role": "role",
325
+ "admin.fallback.noCaps": "No caps",
326
+ "admin.filters.label": "Audit log filters",
327
+ "admin.filters.search": "Search logs",
328
+ "admin.filters.searchAria": "Search audit logs",
329
+ "admin.filters.severityAria": "Filter by severity",
330
+ "admin.filters.all": "All severities",
331
+ "admin.filters.informational": "Informational",
332
+ "admin.filters.notice": "Notice",
333
+ "admin.filters.warning": "Warning",
334
+ "admin.filters.high": "High",
335
+ "admin.filters.matched": "{count} matched",
336
+ "admin.brain.summaryFallback": "Local Brain services are separated from user chat.",
337
+ "admin.brain.rebuilding": "Rebuilding",
338
+ "admin.brain.rebuild": "Rebuild index",
339
+ "admin.policy.fallback": "Policy",
340
+ "admin.policy.quiet": "Policy API quiet",
341
+ "admin.retention.days": "{days} day retention",
342
+ "admin.retention.detail": "{events} retained · {candidates} ready for export/prune review",
343
+ "admin.log.event": "Event",
344
+ "admin.log.system": "system",
345
+ "admin.log.recently": "recently",
346
+ "admin.source.loading": "Loading",
347
+ "admin.source.live": "Live",
348
+ "admin.index.ready": "Index status ready",
349
+ "flow.analysis.fact.computer": "Computer",
350
+ "flow.analysis.fact.memory": "Memory",
351
+ "flow.analysis.fact.graphics": "Graphics",
352
+ "flow.analysis.fact.support": "Local Support",
353
+ "flow.analysis.fact.models": "Models",
354
+ "flow.analysis.checking": "Checking",
355
+ "flow.analysis.fact.computerDetail": "Operating system and chip",
356
+ "flow.analysis.fact.memoryDetail": "Available room for local thinking",
357
+ "flow.analysis.fact.graphicsDetail": "Local acceleration support",
358
+ "flow.analysis.fact.supportDetail": "Installed model helpers",
359
+ "flow.analysis.fact.modelsDetail": "Installed local Brains",
360
+ "flow.analysis.apple": "Apple Silicon Mac",
361
+ "flow.analysis.detected": "Detected",
362
+ "flow.analysis.localReady": "Local acceleration ready",
363
+ "flow.analysis.standardLocal": "Standard local mode",
364
+ "flow.analysis.supportReady": "Ready",
365
+ "flow.analysis.supportInstall": "Will be prepared",
366
+ "flow.analysis.modelsInstalled": "{count} already installed",
367
+ "flow.analysis.noModels": "None installed yet",
368
+ "flow.analysis.readyDetail": "Ready for local Digital Brain use",
369
+ "flow.analysis.memoryReadyDetail": "Enough context for the recommended model",
370
+ "flow.analysis.graphicsReadyDetail": "Lattice will choose the best available path",
371
+ "flow.analysis.supportReadyDetail": "Installed model helpers detected",
372
+ "flow.analysis.supportInstallDetail": "Lattice will add what is needed",
373
+ "flow.analysis.modelsReadyDetail": "One can be loaded immediately",
374
+ "flow.analysis.modelsInstallDetail": "Lattice will guide the first install",
375
+ "flow.recommend.reason.best": "Best Experience",
376
+ "flow.recommend.reason.faster": "Faster",
377
+ "flow.recommend.reason.advanced": "Advanced",
378
+ "flow.recommend.reason.default": "Recommended",
379
+ "flow.recommend.sizeReady": "ready",
380
+ "flow.recommend.fallbackName": "Recommended Brain",
381
+ "flow.install.stage.download": "Getting the model files.",
382
+ "flow.install.stage.validate": "Checking that the Brain can answer.",
383
+ "flow.install.stage.load": "Loading the Brain.",
384
+ "flow.install.stage.error": "Something needs attention.",
385
+ "flow.install.step.install": "Install",
386
+ "flow.install.step.download": "Download",
387
+ "flow.install.step.validate": "Validate",
388
+ "flow.install.step.load": "Load",
200
389
  "flow.shell": "Create your local AI Brain",
201
390
  "flow.login.title": "Choose the owner of your AI Brain.",
202
391
  "flow.login.body": "Lattice AI is a local-first Digital Brain that keeps your knowledge on this computer. Models can change; external transfer starts only when you choose it.",
@@ -242,7 +431,16 @@ export const COPY: Record<Language, TextMap> = {
242
431
  "flow.install.start": "Download and start",
243
432
  "flow.install.busy": "Preparing model...",
244
433
  "flow.install.enter": "Enter Brain",
434
+ "flow.install.later": "Do later",
245
435
  "flow.install.local": "Downloads and external communication start only when you choose them.",
436
+ "flow.consent.title": "Before downloading",
437
+ "flow.consent.body": "Starting will fetch these model files from an external repository and store them on this computer.",
438
+ "flow.consent.ready": "This model is already ready, or no external connection starts until you choose it.",
439
+ "flow.consent.size": "Download size",
440
+ "flow.consent.sizeUnknown": "Reported by runtime",
441
+ "flow.consent.location": "Storage location",
442
+ "flow.consent.external": "External target",
443
+ "flow.consent.externalNone": "No external connection before selection",
246
444
  },
247
445
  };
248
446
 
@@ -2982,6 +2982,58 @@ body {
2982
2982
  box-shadow: 0 0 0 1px hsl(var(--brain-core) / 0.25);
2983
2983
  }
2984
2984
 
2985
+ .ritual-form {
2986
+ max-width: 420px;
2987
+ margin: 0 auto;
2988
+ }
2989
+
2990
+ .ritual-field-stack {
2991
+ display: grid;
2992
+ gap: 0.85rem;
2993
+ }
2994
+
2995
+ .ritual-field-label {
2996
+ margin-bottom: 0.25rem;
2997
+ color: hsl(var(--fg-muted));
2998
+ font-size: 0.75rem;
2999
+ font-weight: 760;
3000
+ letter-spacing: 0;
3001
+ text-transform: uppercase;
3002
+ }
3003
+
3004
+ .ritual-error {
3005
+ margin-top: 0.85rem;
3006
+ padding: 0.6rem 0.85rem;
3007
+ border: 1px solid hsl(var(--destructive) / 0.4);
3008
+ border-radius: 8px;
3009
+ background: hsl(var(--destructive) / 0.12);
3010
+ font-size: 0.9rem;
3011
+ }
3012
+
3013
+ .ritual-full-button,
3014
+ .ritual-primary-model-button,
3015
+ .ritual-model-card {
3016
+ width: 100%;
3017
+ }
3018
+
3019
+ .ritual-full-button {
3020
+ margin-top: 1rem;
3021
+ }
3022
+
3023
+ .ritual-note,
3024
+ .ritual-local-note {
3025
+ color: hsl(var(--fg-muted));
3026
+ font-size: 0.75rem;
3027
+ }
3028
+
3029
+ .ritual-note {
3030
+ margin-top: 0.6rem;
3031
+ }
3032
+
3033
+ .ritual-local-note {
3034
+ margin-top: 0.9rem;
3035
+ }
3036
+
2985
3037
  .ritual-fact-grid {
2986
3038
  display: grid;
2987
3039
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
@@ -3011,6 +3063,59 @@ body {
3011
3063
  color: hsl(var(--fg));
3012
3064
  }
3013
3065
 
3066
+ .ritual-fact-detail,
3067
+ .ritual-muted-text,
3068
+ .ritual-muted-hint,
3069
+ .ritual-status-card {
3070
+ color: hsl(var(--fg-muted));
3071
+ }
3072
+
3073
+ .ritual-fact-detail {
3074
+ margin-top: 0.2rem;
3075
+ font-size: 0.8rem;
3076
+ }
3077
+
3078
+ .ritual-analysis-card,
3079
+ .ritual-centered-actions {
3080
+ margin-top: 1rem;
3081
+ }
3082
+
3083
+ .ritual-centered-actions {
3084
+ display: flex;
3085
+ justify-content: center;
3086
+ }
3087
+
3088
+ .ritual-inline-row {
3089
+ display: flex;
3090
+ align-items: center;
3091
+ gap: 0.6rem;
3092
+ }
3093
+
3094
+ .ritual-core-icon {
3095
+ color: hsl(var(--brain-core));
3096
+ }
3097
+
3098
+ .ritual-strong-text {
3099
+ font-weight: 620;
3100
+ }
3101
+
3102
+ .ritual-muted-text {
3103
+ font-size: 0.9rem;
3104
+ }
3105
+
3106
+ .ritual-wide-button {
3107
+ min-width: 260px;
3108
+ }
3109
+
3110
+ .ritual-model-list {
3111
+ max-width: 560px;
3112
+ margin: 0 auto;
3113
+ }
3114
+
3115
+ .ritual-primary-model-button {
3116
+ margin-bottom: 0.85rem;
3117
+ }
3118
+
3014
3119
  .ritual-model-card {
3015
3120
  text-align: left;
3016
3121
  padding: 1.15rem 1.35rem;
@@ -3046,6 +3151,80 @@ body {
3046
3151
  margin-top: 0.25rem;
3047
3152
  }
3048
3153
 
3154
+ .ritual-model-warning {
3155
+ margin-top: 0.35rem;
3156
+ color: hsl(var(--destructive));
3157
+ font-size: 0.85rem;
3158
+ }
3159
+
3160
+ .ritual-action-row,
3161
+ .ritual-button-row {
3162
+ display: flex;
3163
+ align-items: center;
3164
+ justify-content: center;
3165
+ gap: 0.75rem;
3166
+ flex-wrap: wrap;
3167
+ }
3168
+
3169
+ .ritual-action-row {
3170
+ margin-top: 1.1rem;
3171
+ }
3172
+
3173
+ .ritual-muted-hint {
3174
+ font-size: 0.82rem;
3175
+ }
3176
+
3177
+ .ritual-install-brain {
3178
+ margin: 0.6rem auto 1rem;
3179
+ }
3180
+
3181
+ .ritual-consent-panel {
3182
+ display: grid;
3183
+ grid-template-columns: minmax(0, 1fr) minmax(220px, 0.9fr);
3184
+ gap: 1rem;
3185
+ max-width: 620px;
3186
+ margin: 0 auto 1rem;
3187
+ padding: 1rem;
3188
+ border: 1px solid hsl(var(--border) / 0.6);
3189
+ border-radius: 8px;
3190
+ background: hsl(var(--bg) / 0.48);
3191
+ text-align: left;
3192
+ }
3193
+
3194
+ .ritual-consent-panel strong {
3195
+ display: block;
3196
+ margin-bottom: 0.25rem;
3197
+ }
3198
+
3199
+ .ritual-consent-panel p {
3200
+ margin: 0;
3201
+ color: hsl(var(--fg-muted));
3202
+ font-size: 0.9rem;
3203
+ }
3204
+
3205
+ .ritual-consent-panel dl {
3206
+ display: grid;
3207
+ gap: 0.45rem;
3208
+ margin: 0;
3209
+ }
3210
+
3211
+ .ritual-consent-panel dl div {
3212
+ display: grid;
3213
+ grid-template-columns: 110px minmax(0, 1fr);
3214
+ gap: 0.5rem;
3215
+ }
3216
+
3217
+ .ritual-consent-panel dt {
3218
+ color: hsl(var(--fg-muted));
3219
+ font-size: 0.78rem;
3220
+ }
3221
+
3222
+ .ritual-consent-panel dd {
3223
+ margin: 0;
3224
+ overflow-wrap: anywhere;
3225
+ font-size: 0.86rem;
3226
+ }
3227
+
3049
3228
  .ritual-progress {
3050
3229
  margin: 1.5rem 0;
3051
3230
  }
@@ -3077,6 +3256,11 @@ body {
3077
3256
  color: hsl(var(--fg));
3078
3257
  }
3079
3258
 
3259
+ .ritual-stage-icon {
3260
+ width: 15px;
3261
+ height: 15px;
3262
+ }
3263
+
3080
3264
  .ritual-bar {
3081
3265
  height: 6px;
3082
3266
  background: hsl(var(--border) / 0.6);
@@ -3092,12 +3276,48 @@ body {
3092
3276
  transition: width 280ms ease;
3093
3277
  }
3094
3278
 
3279
+ .ritual-bar-fill.progress-0 { width: 4%; }
3280
+ .ritual-bar-fill.progress-10 { width: 10%; }
3281
+ .ritual-bar-fill.progress-20 { width: 20%; }
3282
+ .ritual-bar-fill.progress-30 { width: 30%; }
3283
+ .ritual-bar-fill.progress-40 { width: 40%; }
3284
+ .ritual-bar-fill.progress-50 { width: 50%; }
3285
+ .ritual-bar-fill.progress-60 { width: 60%; }
3286
+ .ritual-bar-fill.progress-70 { width: 70%; }
3287
+ .ritual-bar-fill.progress-80 { width: 80%; }
3288
+ .ritual-bar-fill.progress-90 { width: 90%; }
3289
+ .ritual-bar-fill.progress-100 { width: 100%; }
3290
+
3095
3291
  .ritual-status {
3096
3292
  font-size: 0.95rem;
3097
3293
  color: hsl(var(--fg-muted));
3098
3294
  margin: 0.8rem 0;
3099
3295
  }
3100
3296
 
3297
+ .ritual-status-card {
3298
+ max-width: 540px;
3299
+ margin: 0.8rem auto 0;
3300
+ font-size: 0.86rem;
3301
+ }
3302
+
3303
+ .ritual-error-card {
3304
+ border-color: hsl(var(--destructive) / 0.45);
3305
+ background: hsl(var(--destructive) / 0.07);
3306
+ }
3307
+
3308
+ .ritual-install-error {
3309
+ margin-bottom: 1rem;
3310
+ }
3311
+
3312
+ .ritual-error-detail {
3313
+ margin-top: 0.5rem;
3314
+ font-size: 0.85rem;
3315
+ }
3316
+
3317
+ .ritual-button-row {
3318
+ margin-top: 1rem;
3319
+ }
3320
+
3101
3321
  .ritual-success {
3102
3322
  text-align: center;
3103
3323
  padding: 1.5rem;
@@ -26,7 +26,7 @@ from .storage import (
26
26
  storage_from_env,
27
27
  )
28
28
 
29
- __version__ = "6.1.0"
29
+ __version__ = "6.2.0"
30
30
 
31
31
  __all__ = [
32
32
  "AgentRuntime",
@@ -19,7 +19,7 @@ from datetime import datetime
19
19
  from typing import Any, Callable, Dict, List, Optional
20
20
 
21
21
 
22
- MULTI_AGENT_VERSION = "6.1.0"
22
+ MULTI_AGENT_VERSION = "6.2.0"
23
23
 
24
24
  AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
25
25
  CORE_PIPELINE = ("planner", "executor", "reviewer")
@@ -1,3 +1,3 @@
1
1
  """Lattice AI - modular server package."""
2
2
 
3
- __version__ = "6.1.0"
3
+ __version__ = "6.2.0"