ltcai 6.0.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 (54) hide show
  1. package/README.md +33 -35
  2. package/docs/CHANGELOG.md +68 -0
  3. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +1 -1
  4. package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +1 -1
  5. package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +1 -1
  6. package/docs/V4_7_1_ADMIN_OPERATIONS_REPORT.md +1 -1
  7. package/frontend/src/App.tsx +3 -1281
  8. package/frontend/src/components/LanguageSwitcher.tsx +23 -0
  9. package/frontend/src/components/ProductFlow.tsx +32 -662
  10. package/frontend/src/components/onboarding/ProductFlowScreens.tsx +688 -0
  11. package/frontend/src/features/admin/AdminConsole.tsx +294 -0
  12. package/frontend/src/features/brain/BrainHome.tsx +999 -0
  13. package/frontend/src/features/brain/brainData.ts +98 -0
  14. package/frontend/src/features/brain/graphLayout.ts +26 -0
  15. package/frontend/src/features/brain/types.ts +44 -0
  16. package/frontend/src/features/review/ReviewCard.tsx +15 -10
  17. package/frontend/src/i18n.ts +208 -0
  18. package/frontend/src/styles.css +240 -0
  19. package/lattice_brain/__init__.py +1 -1
  20. package/lattice_brain/runtime/multi_agent.py +1 -1
  21. package/latticeai/__init__.py +1 -1
  22. package/latticeai/api/chat.py +52 -33
  23. package/latticeai/api/tools.py +50 -23
  24. package/latticeai/app_factory.py +65 -47
  25. package/latticeai/cli/__init__.py +1 -0
  26. package/latticeai/cli/entrypoint.py +283 -0
  27. package/latticeai/cli/runtime.py +37 -0
  28. package/latticeai/core/marketplace.py +1 -1
  29. package/latticeai/core/workspace_os.py +1 -1
  30. package/latticeai/integrations/__init__.py +0 -0
  31. package/latticeai/integrations/telegram_bot.py +1009 -0
  32. package/latticeai/runtime/lifespan_runtime.py +1 -1
  33. package/latticeai/runtime/platform_runtime_wiring.py +87 -0
  34. package/latticeai/runtime/router_registration.py +49 -30
  35. package/latticeai/services/app_context.py +1 -0
  36. package/latticeai/services/p_reinforce.py +258 -0
  37. package/latticeai/services/router_context.py +52 -0
  38. package/latticeai/services/tool_dispatch.py +82 -25
  39. package/ltcai_cli.py +7 -305
  40. package/p_reinforce.py +4 -255
  41. package/package.json +2 -1
  42. package/scripts/release_smoke.py +133 -0
  43. package/scripts/wheel_smoke.py +4 -0
  44. package/src-tauri/Cargo.lock +1 -1
  45. package/src-tauri/Cargo.toml +1 -1
  46. package/src-tauri/tauri.conf.json +1 -1
  47. package/static/app/asset-manifest.json +5 -5
  48. package/static/app/assets/{index-xRn29gI8.css → index-B2-1Gm0q.css} +1 -1
  49. package/static/app/assets/index-D91Rz5--.js +16 -0
  50. package/static/app/assets/index-D91Rz5--.js.map +1 -0
  51. package/static/app/index.html +2 -2
  52. package/telegram_bot.py +9 -1008
  53. package/static/app/assets/index-D2zafMYb.js +0 -16
  54. package/static/app/assets/index-D2zafMYb.js.map +0 -1
@@ -2774,6 +2774,26 @@ body {
2774
2774
  transform: translateY(-1px);
2775
2775
  }
2776
2776
 
2777
+ .mind-empty-trail {
2778
+ display: flex;
2779
+ flex-wrap: wrap;
2780
+ justify-content: center;
2781
+ gap: 0.45rem;
2782
+ margin: 1rem auto 0;
2783
+ max-width: 40rem;
2784
+ }
2785
+
2786
+ .mind-empty-trail span {
2787
+ border: 1px solid hsl(var(--border) / 0.55);
2788
+ border-radius: 999px;
2789
+ background: hsl(var(--surface-glass));
2790
+ color: hsl(var(--fg-muted));
2791
+ padding: 0.34rem 0.62rem;
2792
+ font-size: 0.72rem;
2793
+ font-weight: 720;
2794
+ line-height: 1.15;
2795
+ }
2796
+
2777
2797
  .mind-empty .icon {
2778
2798
  opacity: 0.5;
2779
2799
  margin-bottom: 0.8rem;
@@ -2962,6 +2982,58 @@ body {
2962
2982
  box-shadow: 0 0 0 1px hsl(var(--brain-core) / 0.25);
2963
2983
  }
2964
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
+
2965
3037
  .ritual-fact-grid {
2966
3038
  display: grid;
2967
3039
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
@@ -2991,6 +3063,59 @@ body {
2991
3063
  color: hsl(var(--fg));
2992
3064
  }
2993
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
+
2994
3119
  .ritual-model-card {
2995
3120
  text-align: left;
2996
3121
  padding: 1.15rem 1.35rem;
@@ -3026,6 +3151,80 @@ body {
3026
3151
  margin-top: 0.25rem;
3027
3152
  }
3028
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
+
3029
3228
  .ritual-progress {
3030
3229
  margin: 1.5rem 0;
3031
3230
  }
@@ -3057,6 +3256,11 @@ body {
3057
3256
  color: hsl(var(--fg));
3058
3257
  }
3059
3258
 
3259
+ .ritual-stage-icon {
3260
+ width: 15px;
3261
+ height: 15px;
3262
+ }
3263
+
3060
3264
  .ritual-bar {
3061
3265
  height: 6px;
3062
3266
  background: hsl(var(--border) / 0.6);
@@ -3072,12 +3276,48 @@ body {
3072
3276
  transition: width 280ms ease;
3073
3277
  }
3074
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
+
3075
3291
  .ritual-status {
3076
3292
  font-size: 0.95rem;
3077
3293
  color: hsl(var(--fg-muted));
3078
3294
  margin: 0.8rem 0;
3079
3295
  }
3080
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
+
3081
3321
  .ritual-success {
3082
3322
  text-align: center;
3083
3323
  padding: 1.5rem;
@@ -26,7 +26,7 @@ from .storage import (
26
26
  storage_from_env,
27
27
  )
28
28
 
29
- __version__ = "6.0.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.0.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.0.0"
3
+ __version__ = "6.2.0"
@@ -150,6 +150,35 @@ async def single_text_stream(text: str, model: str = "system") -> AsyncIterator[
150
150
  yield "data: [DONE]\n\n"
151
151
 
152
152
 
153
+ def build_recent_chat_context(
154
+ *,
155
+ get_history,
156
+ limit: int = 10,
157
+ include_image_missing_replies: bool = True,
158
+ user_email: Optional[str] = None,
159
+ conversation_id: Optional[str] = None,
160
+ ) -> str:
161
+ history = get_history()
162
+ if conversation_id:
163
+ history = [item for item in history if item.get("conversation_id") == conversation_id]
164
+ if user_email:
165
+ history = pair_user_history(history, user_email)
166
+ history = history[-limit:]
167
+ lines = []
168
+ for item in history:
169
+ role = item.get("role", "user")
170
+ content = item.get("content", "")
171
+ if not include_image_missing_replies and role == "assistant":
172
+ if "이미지" in content and any(word in content for word in ["업로드", "제공", "올려"]):
173
+ continue
174
+ source = item.get("source")
175
+ label = role
176
+ if source:
177
+ label = f"{role} ({source})"
178
+ lines.append(f"{label}: {content}")
179
+ return "\n".join(lines)
180
+
181
+
153
182
  def create_chat_router(context: AppContext) -> APIRouter:
154
183
  """Build the chat/history/agent router from the typed application context.
155
184
 
@@ -201,31 +230,19 @@ def create_chat_router(context: AppContext) -> APIRouter:
201
230
  except Exception as exc:
202
231
  logging.warning("chat message bridge failed: %s", exc)
203
232
 
204
- def build_recent_chat_context(
233
+ def recent_chat_context(
205
234
  limit: int = 10,
206
235
  include_image_missing_replies: bool = True,
207
236
  user_email: Optional[str] = None,
208
237
  conversation_id: Optional[str] = None,
209
238
  ) -> str:
210
- history = get_history()
211
- if conversation_id:
212
- history = [item for item in history if item.get("conversation_id") == conversation_id]
213
- if user_email:
214
- history = pair_user_history(history, user_email)
215
- history = history[-limit:]
216
- lines = []
217
- for item in history:
218
- role = item.get("role", "user")
219
- content = item.get("content", "")
220
- if not include_image_missing_replies and role == "assistant":
221
- if "이미지" in content and any(word in content for word in ["업로드", "제공", "올려"]):
222
- continue
223
- source = item.get("source")
224
- label = role
225
- if source:
226
- label = f"{role} ({source})"
227
- lines.append(f"{label}: {content}")
228
- return "\n".join(lines)
239
+ return build_recent_chat_context(
240
+ get_history=get_history,
241
+ limit=limit,
242
+ include_image_missing_replies=include_image_missing_replies,
243
+ user_email=user_email,
244
+ conversation_id=conversation_id,
245
+ )
229
246
 
230
247
  def extract_screenshot_context(image_data: Optional[str]) -> str:
231
248
  if not image_data:
@@ -283,16 +300,18 @@ def create_chat_router(context: AppContext) -> APIRouter:
283
300
 
284
301
  return "\n".join(lines)
285
302
 
286
- _AGENT_RUNTIME = build_agent_runtime(
287
- model_router=router,
288
- execute_tool=execute_tool,
289
- recent_chat_context=build_recent_chat_context,
290
- clear_history=clear_history,
291
- knowledge_save=knowledge_save,
292
- audit=append_audit_event,
293
- hooks=hooks,
294
- brain_memory=context.brain_memory,
295
- )
303
+ _AGENT_RUNTIME = context.chat_agent_runtime
304
+ if _AGENT_RUNTIME is None:
305
+ _AGENT_RUNTIME = build_agent_runtime(
306
+ model_router=router,
307
+ execute_tool=execute_tool,
308
+ recent_chat_context=recent_chat_context,
309
+ clear_history=clear_history,
310
+ knowledge_save=knowledge_save,
311
+ audit=append_audit_event,
312
+ hooks=hooks,
313
+ brain_memory=context.brain_memory,
314
+ )
296
315
 
297
316
  @api_router.post("/chat")
298
317
  async def chat(req: ChatRequest, request: Request):
@@ -546,7 +565,7 @@ def create_chat_router(context: AppContext) -> APIRouter:
546
565
  return JSONResponse(content={"response": str(result), "trace_id": trace_record["id"], "trace": trace_record})
547
566
 
548
567
  if req.stream:
549
- recent_context = build_recent_chat_context(user_email=effective_email, conversation_id=req.conversation_id)
568
+ recent_context = recent_chat_context(user_email=effective_email, conversation_id=req.conversation_id)
550
569
  stream_context = context
551
570
  if recent_context:
552
571
  stream_context = f"[RECENT CONVERSATION]\n{recent_context}\n\n{context}".strip()
@@ -564,7 +583,7 @@ def create_chat_router(context: AppContext) -> APIRouter:
564
583
  )
565
584
  else:
566
585
  if req.image_data:
567
- recent_context = build_recent_chat_context(
586
+ recent_context = recent_chat_context(
568
587
  limit=6,
569
588
  include_image_missing_replies=False,
570
589
  user_email=effective_email,
@@ -572,7 +591,7 @@ def create_chat_router(context: AppContext) -> APIRouter:
572
591
  )
573
592
  full_context = f"[RECENT CONVERSATION]\n{recent_context}\n\n{context}".strip() if recent_context else context
574
593
  else:
575
- history_context = build_recent_chat_context(user_email=effective_email, conversation_id=req.conversation_id)
594
+ history_context = recent_chat_context(user_email=effective_email, conversation_id=req.conversation_id)
576
595
  full_context = f"{history_context}\n{context}" if context else history_context
577
596
 
578
597
  result = await router.generate(req.message, full_context, req.max_tokens, req.temperature, req.image_data)
@@ -26,7 +26,8 @@ from latticeai.services.tool_dispatch import (
26
26
  get_tool_permission,
27
27
  list_tool_permissions,
28
28
  )
29
- from p_reinforce import BRAIN_DIR
29
+ from latticeai.services.router_context import ToolRouterContext
30
+ from latticeai.services.p_reinforce import BRAIN_DIR
30
31
  from tools import (
31
32
  AGENT_ROOT,
32
33
  ToolError,
@@ -177,30 +178,56 @@ class ToolGitShowRequest(BaseModel):
177
178
 
178
179
  def create_tools_router(
179
180
  *,
180
- config,
181
- ingestion_pipeline,
182
- data_dir: Path,
183
- static_dir: Path,
184
- model_router,
185
- require_user,
186
- require_admin,
187
- get_current_user,
188
- clear_history,
189
- append_audit_event,
190
- enforce_rate_limit,
191
- bytes_match_extension,
192
- classify_sensitive_message,
193
- save_to_history,
194
- enable_graph: bool,
195
- knowledge_graph,
196
- require_graph,
197
- local_kg_watcher,
198
- load_mcp_installs,
199
- recommend_mcps,
200
- install_mcp,
201
- mcp_public_item,
181
+ tool_context: ToolRouterContext | None = None,
182
+ config=None,
183
+ ingestion_pipeline=None,
184
+ data_dir: Path | None = None,
185
+ static_dir: Path | None = None,
186
+ model_router=None,
187
+ require_user=None,
188
+ require_admin=None,
189
+ get_current_user=None,
190
+ clear_history=None,
191
+ append_audit_event=None,
192
+ enforce_rate_limit=None,
193
+ bytes_match_extension=None,
194
+ classify_sensitive_message=None,
195
+ save_to_history=None,
196
+ enable_graph: bool | None = None,
197
+ knowledge_graph=None,
198
+ require_graph=None,
199
+ local_kg_watcher=None,
200
+ load_mcp_installs=None,
201
+ recommend_mcps=None,
202
+ install_mcp=None,
203
+ mcp_public_item=None,
202
204
  hooks=None,
203
205
  ) -> APIRouter:
206
+ if tool_context is not None:
207
+ config = tool_context.config
208
+ ingestion_pipeline = tool_context.ingestion_pipeline
209
+ data_dir = tool_context.data_dir
210
+ static_dir = tool_context.static_dir
211
+ model_router = tool_context.model_router
212
+ require_user = tool_context.require_user
213
+ require_admin = tool_context.require_admin
214
+ get_current_user = tool_context.get_current_user
215
+ clear_history = tool_context.clear_history
216
+ append_audit_event = tool_context.append_audit_event
217
+ enforce_rate_limit = tool_context.enforce_rate_limit
218
+ bytes_match_extension = tool_context.bytes_match_extension
219
+ classify_sensitive_message = tool_context.classify_sensitive_message
220
+ save_to_history = tool_context.save_to_history
221
+ enable_graph = tool_context.enable_graph
222
+ knowledge_graph = tool_context.knowledge_graph
223
+ require_graph = tool_context.require_graph
224
+ local_kg_watcher = tool_context.local_kg_watcher
225
+ load_mcp_installs = tool_context.load_mcp_installs
226
+ recommend_mcps = tool_context.recommend_mcps
227
+ install_mcp = tool_context.install_mcp
228
+ mcp_public_item = tool_context.mcp_public_item
229
+ hooks = tool_context.hooks
230
+
204
231
  api_router = APIRouter()
205
232
  HOOKS = hooks
206
233
  CONFIG = config