ltcai 5.6.0 → 6.1.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 (55) hide show
  1. package/README.md +45 -25
  2. package/docs/CHANGELOG.md +74 -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/openapi.json +39 -0
  8. package/frontend/src/App.tsx +5 -0
  9. package/frontend/src/api/client.ts +104 -23
  10. package/frontend/src/api/openapi.ts +48 -0
  11. package/frontend/src/components/FirstRunGuide.tsx +3 -3
  12. package/frontend/src/components/ProductFlow.tsx +7 -0
  13. package/frontend/src/features/review/ReviewCard.tsx +96 -0
  14. package/frontend/src/features/review/ReviewInbox.tsx +112 -0
  15. package/frontend/src/features/review/reviewHelpers.ts +69 -0
  16. package/frontend/src/i18n.ts +18 -8
  17. package/frontend/src/pages/Act.tsx +5 -177
  18. package/frontend/src/routes.ts +1 -0
  19. package/frontend/src/styles.css +20 -0
  20. package/lattice_brain/__init__.py +1 -1
  21. package/lattice_brain/runtime/multi_agent.py +1 -1
  22. package/latticeai/__init__.py +1 -1
  23. package/latticeai/api/chat.py +52 -33
  24. package/latticeai/api/review_queue.py +7 -3
  25. package/latticeai/app_factory.py +253 -475
  26. package/latticeai/cli/__init__.py +1 -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/runtime/app_context_runtime.py +13 -0
  31. package/latticeai/runtime/automation_runtime.py +64 -0
  32. package/latticeai/runtime/bootstrap.py +48 -0
  33. package/latticeai/runtime/context_runtime.py +43 -0
  34. package/latticeai/runtime/hooks_runtime.py +77 -0
  35. package/latticeai/runtime/lifespan_runtime.py +138 -0
  36. package/latticeai/runtime/persistence_runtime.py +87 -0
  37. package/latticeai/runtime/platform_services_runtime.py +39 -0
  38. package/latticeai/runtime/router_registration.py +570 -0
  39. package/latticeai/runtime/web_runtime.py +65 -0
  40. package/latticeai/services/app_context.py +1 -0
  41. package/latticeai/services/review_queue.py +20 -4
  42. package/latticeai/services/tool_dispatch.py +82 -25
  43. package/ltcai_cli.py +5 -31
  44. package/package.json +1 -1
  45. package/src-tauri/Cargo.lock +1 -1
  46. package/src-tauri/Cargo.toml +1 -1
  47. package/src-tauri/tauri.conf.json +1 -1
  48. package/static/app/asset-manifest.json +5 -5
  49. package/static/app/assets/{index-xRn29gI8.css → index-B744yblP.css} +1 -1
  50. package/static/app/assets/index-DYaUKNfl.js +16 -0
  51. package/static/app/assets/index-DYaUKNfl.js.map +1 -0
  52. package/static/app/index.html +2 -2
  53. package/telegram_bot.py +3 -9
  54. package/static/app/assets/index-xMFu94cX.js +0 -16
  55. package/static/app/assets/index-xMFu94cX.js.map +0 -1
@@ -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)
@@ -7,8 +7,8 @@ frontend without massaging.
7
7
 
8
8
  Action semantics live in :class:`~latticeai.services.review_queue.ReviewQueueService`:
9
9
 
10
- * ``approve`` / ``dismiss`` / ``snooze`` are status transitions; an illegal
11
- transition returns **409**.
10
+ * ``approve`` / ``dismiss`` / ``snooze`` / ``unsnooze`` are status transitions;
11
+ an illegal transition returns **409**.
12
12
  * ``run_now`` previews/regenerates without changing status (back-links the run).
13
13
  """
14
14
 
@@ -125,6 +125,10 @@ def create_review_queue_router(
125
125
  append_audit_event("review_item_snooze", user_email=user, item_id=item_id)
126
126
  return item
127
127
 
128
+ @router.post("/automation/reviews/{item_id}/unsnooze", response_model=ReviewItem)
129
+ async def unsnooze_item(item_id: str, request: Request):
130
+ return _act(request, item_id, "unsnooze")
131
+
128
132
  @router.post("/automation/reviews/{item_id}/run_now", response_model=ReviewItem)
129
133
  async def run_now_item(item_id: str, request: Request):
130
134
  user = require_user(request)
@@ -155,4 +159,4 @@ def create_review_queue_router(
155
159
  append_audit_event(f"review_item_{action}", user_email=user, item_id=item_id)
156
160
  return item
157
161
 
158
- return router
162
+ return router