ltcai 5.6.0 → 6.0.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 (40) hide show
  1. package/README.md +42 -25
  2. package/docs/CHANGELOG.md +38 -0
  3. package/frontend/openapi.json +39 -0
  4. package/frontend/src/api/client.ts +104 -23
  5. package/frontend/src/api/openapi.ts +48 -0
  6. package/frontend/src/components/FirstRunGuide.tsx +3 -3
  7. package/frontend/src/features/review/ReviewCard.tsx +91 -0
  8. package/frontend/src/features/review/ReviewInbox.tsx +112 -0
  9. package/frontend/src/features/review/reviewHelpers.ts +69 -0
  10. package/frontend/src/i18n.ts +8 -8
  11. package/frontend/src/pages/Act.tsx +5 -177
  12. package/frontend/src/routes.ts +1 -0
  13. package/lattice_brain/__init__.py +1 -1
  14. package/lattice_brain/runtime/multi_agent.py +1 -1
  15. package/latticeai/__init__.py +1 -1
  16. package/latticeai/api/review_queue.py +7 -3
  17. package/latticeai/app_factory.py +224 -473
  18. package/latticeai/core/marketplace.py +1 -1
  19. package/latticeai/core/workspace_os.py +1 -1
  20. package/latticeai/runtime/app_context_runtime.py +13 -0
  21. package/latticeai/runtime/automation_runtime.py +64 -0
  22. package/latticeai/runtime/bootstrap.py +48 -0
  23. package/latticeai/runtime/context_runtime.py +43 -0
  24. package/latticeai/runtime/hooks_runtime.py +77 -0
  25. package/latticeai/runtime/lifespan_runtime.py +138 -0
  26. package/latticeai/runtime/persistence_runtime.py +87 -0
  27. package/latticeai/runtime/platform_services_runtime.py +39 -0
  28. package/latticeai/runtime/router_registration.py +570 -0
  29. package/latticeai/runtime/web_runtime.py +65 -0
  30. package/latticeai/services/review_queue.py +20 -4
  31. package/package.json +1 -1
  32. package/src-tauri/Cargo.lock +1 -1
  33. package/src-tauri/Cargo.toml +1 -1
  34. package/src-tauri/tauri.conf.json +1 -1
  35. package/static/app/asset-manifest.json +3 -3
  36. package/static/app/assets/index-D2zafMYb.js +16 -0
  37. package/static/app/assets/index-D2zafMYb.js.map +1 -0
  38. package/static/app/index.html +1 -1
  39. package/static/app/assets/index-xMFu94cX.js +0 -16
  40. package/static/app/assets/index-xMFu94cX.js.map +0 -1
@@ -11,7 +11,7 @@ from copy import deepcopy
11
11
  from typing import Any, Dict, List, Optional
12
12
 
13
13
 
14
- MARKETPLACE_VERSION = "5.6.0"
14
+ MARKETPLACE_VERSION = "6.0.0"
15
15
  TEMPLATE_KINDS = ("plugin", "workflow", "agent")
16
16
 
17
17
 
@@ -19,7 +19,7 @@ from pathlib import Path
19
19
  from typing import Any, Callable, Dict, Iterable, List, Optional
20
20
 
21
21
 
22
- WORKSPACE_OS_VERSION = "5.6.0"
22
+ WORKSPACE_OS_VERSION = "6.0.0"
23
23
 
24
24
  # Workspace types separate single-user Personal workspaces from shared
25
25
  # Organization workspaces. Both keep the same local-first JSON store; the type
@@ -0,0 +1,13 @@
1
+ """Application dependency context assembly for app startup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def build_app_context(**deps: Any) -> Any:
9
+ """Construct the typed dependency context consumed by API routers."""
10
+
11
+ from latticeai.services.app_context import AppContext
12
+
13
+ return AppContext(**deps)
@@ -0,0 +1,64 @@
1
+ """Automation runtime assembly for review, trigger, agent, and run execution.
2
+
3
+ This module keeps the automation object graph behind one construction seam.
4
+ Router registration remains in ``app_factory`` so route order stays unchanged.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Callable, Dict
10
+
11
+
12
+ def build_automation_runtime(
13
+ *,
14
+ store: Any,
15
+ platform: Any,
16
+ data_dir: Any,
17
+ workspace_graph: Callable[..., Any],
18
+ append_audit_event: Callable[..., Any],
19
+ hooks: Any,
20
+ ) -> Dict[str, Any]:
21
+ """Construct automation services and attach the run executor boundary."""
22
+
23
+ from lattice_brain.runtime.agent_runtime import AgentRuntime
24
+ from latticeai.services.review_queue import ReviewQueueService
25
+ from latticeai.services.run_executor import RunExecutor
26
+ from latticeai.services.triggers import TriggerService
27
+
28
+ review_queue = ReviewQueueService(store=store)
29
+ trigger_service = TriggerService(
30
+ store=store,
31
+ run_workflow=lambda wf_id, inputs: platform.run_workflow_by_id(
32
+ wf_id,
33
+ None,
34
+ None,
35
+ with_agent=False,
36
+ inputs=inputs,
37
+ ),
38
+ data_dir=data_dir,
39
+ review_sink=review_queue,
40
+ )
41
+ agent_runtime = AgentRuntime(
42
+ store=store,
43
+ orchestrator_factory=platform.build_orchestrator,
44
+ workspace_graph=workspace_graph,
45
+ append_audit_event=append_audit_event,
46
+ hooks=hooks,
47
+ )
48
+ run_executor = RunExecutor(
49
+ store=store,
50
+ agent_runtime=agent_runtime,
51
+ build_workflow_runners=platform.build_workflow_runners,
52
+ workspace_graph=workspace_graph,
53
+ append_audit_event=append_audit_event,
54
+ hooks=hooks,
55
+ review_sink=review_queue,
56
+ )
57
+ agent_runtime.attach_executor(run_executor)
58
+
59
+ return {
60
+ "REVIEW_QUEUE": review_queue,
61
+ "TRIGGER_SERVICE": trigger_service,
62
+ "AGENT_RUNTIME": agent_runtime,
63
+ "RUN_EXECUTOR": run_executor,
64
+ }
@@ -0,0 +1,48 @@
1
+ """Session bootstrap runtime: session store and token helpers.
2
+
3
+ Extracted from ``app_factory._build`` as a composition seam. The session
4
+ token helpers stay closures over a single ``SessionStore`` so the factory
5
+ keeps one source of truth for token lifecycle. ``user_id_resolver`` is the
6
+ factory's ``user_id_for_email`` — injected so the store stays decoupled from
7
+ user persistence. Heavy imports stay inside the function so importing the
8
+ module has no side effects.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, Callable, Dict, Optional
14
+
15
+ _DEFAULT_SESSION_TTL = 60 * 60 * 24
16
+
17
+
18
+ def build_session_runtime(
19
+ *,
20
+ user_id_resolver: Callable[[Optional[str]], Optional[str]],
21
+ ttl_seconds: int = _DEFAULT_SESSION_TTL,
22
+ ) -> Dict[str, Any]:
23
+ """Construct the session store and its token helper closures."""
24
+
25
+ from latticeai.core.sessions import SessionStore
26
+
27
+ session_store = SessionStore()
28
+
29
+ def create_session(email: str) -> str:
30
+ return session_store.create(user_id_resolver(email) or email, email=email)
31
+
32
+ def get_session_email(token: str) -> Optional[str]:
33
+ return session_store.get_email(token)
34
+
35
+ def get_session_user_id(token: str) -> Optional[str]:
36
+ return session_store.get_subject(token)
37
+
38
+ def invalidate_session(token: str) -> None:
39
+ session_store.invalidate(token)
40
+
41
+ return {
42
+ "_SESSION_TTL": ttl_seconds,
43
+ "_session_store": session_store,
44
+ "create_session": create_session,
45
+ "get_session_email": get_session_email,
46
+ "get_session_user_id": get_session_user_id,
47
+ "invalidate_session": invalidate_session,
48
+ }
@@ -0,0 +1,43 @@
1
+ """Retrieval/context runtime assembly for app startup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Callable, Dict
6
+
7
+
8
+ def build_context_runtime(
9
+ *,
10
+ graph_store: Any,
11
+ ingestion_pipeline: Any,
12
+ memory_service: Any,
13
+ gardener: Any,
14
+ require_auth: bool,
15
+ allowed_scopes_for_user: Callable[[Any], Any],
16
+ ) -> Dict[str, Any]:
17
+ """Construct search, brain memory, and context assembly services."""
18
+
19
+ from lattice_brain.context import ContextAssembler
20
+ from lattice_brain.memory import BrainMemory
21
+ from latticeai.services.search_service import SearchService
22
+
23
+ search_service = SearchService(graph_store=graph_store)
24
+ brain_memory = BrainMemory(ingestion_pipeline)
25
+
26
+ def scoped_hybrid_search(q, user_email=None, **kw):
27
+ allowed = None
28
+ if require_auth and user_email:
29
+ allowed = allowed_scopes_for_user(user_email)
30
+ return search_service.hybrid_search(q, allowed_workspaces=allowed, **kw)
31
+
32
+ context_assembler = ContextAssembler(
33
+ memory_recall=memory_service.recall,
34
+ hybrid_search=scoped_hybrid_search,
35
+ notes_context=gardener.get_relevant_context,
36
+ )
37
+
38
+ return {
39
+ "SEARCH_SERVICE": search_service,
40
+ "BRAIN_MEMORY": brain_memory,
41
+ "CONTEXT_ASSEMBLER": context_assembler,
42
+ "_scoped_hybrid_search": scoped_hybrid_search,
43
+ }
@@ -0,0 +1,77 @@
1
+ """Hooks + local-knowledge watcher runtime assembly for app startup.
2
+
3
+ Extracted from ``app_factory._build`` as a composition seam: the hooks
4
+ registry must be constructed *ahead* of the local-knowledge watcher so
5
+ folder-watch reindexes can fire the ``pre_index``/``post_index`` lifecycle
6
+ hooks. Heavy imports stay inside the function so importing the module has no
7
+ side effects.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any, Callable, Dict
13
+
14
+
15
+ def build_hooks_runtime(
16
+ *,
17
+ data_dir: Any,
18
+ enable_graph: bool,
19
+ knowledge_graph_getter: Callable[[], Any],
20
+ ) -> Dict[str, Any]:
21
+ """Construct the hooks registry and local-knowledge watcher behind one seam."""
22
+
23
+ from lattice_brain.runtime.hooks import HooksRegistry
24
+ from local_knowledge_api import LocalKnowledgeWatcher
25
+
26
+ hooks_registry = HooksRegistry(data_dir / "hooks.json")
27
+ local_kg_watcher = (
28
+ LocalKnowledgeWatcher(knowledge_graph_getter, hooks=hooks_registry)
29
+ if enable_graph
30
+ else None
31
+ )
32
+ return {
33
+ "HOOKS_REGISTRY": hooks_registry,
34
+ "LOCAL_KG_WATCHER": local_kg_watcher,
35
+ }
36
+
37
+
38
+ def bind_trigger_hook_runner(*, registry: Any, trigger_service: Any) -> str:
39
+ """Ensure the brain-event trigger hook exists and bind its runtime runner."""
40
+
41
+ from latticeai.services.triggers import TRIGGER_HOOK_NAME
42
+
43
+ trigger_hook_id = next(
44
+ (
45
+ h.get("id")
46
+ for h in registry._state.get("custom", [])
47
+ if h.get("name") == TRIGGER_HOOK_NAME
48
+ ),
49
+ None,
50
+ )
51
+ if trigger_hook_id is None:
52
+ trigger_hook_id = registry.register(
53
+ name=TRIGGER_HOOK_NAME,
54
+ kind="post_tool",
55
+ description="Fires brain_event workflow triggers when knowledge enters the brain.",
56
+ )["id"]
57
+ registry.register_hook(trigger_hook_id, trigger_service.hook_runner())
58
+ return trigger_hook_id
59
+
60
+
61
+ def bind_builtin_hook_runners(
62
+ *,
63
+ registry: Any,
64
+ append_audit_event: Callable[..., Any],
65
+ get_tool_permission: Callable[..., Any],
66
+ classify_sensitive_message: Callable[..., Any],
67
+ ) -> None:
68
+ """Bind concrete platform runners for built-in hook definitions."""
69
+
70
+ from latticeai.core.builtin_hooks import register_builtin_hook_runners
71
+
72
+ register_builtin_hook_runners(
73
+ registry,
74
+ append_audit_event=append_audit_event,
75
+ get_tool_permission=get_tool_permission,
76
+ classify_sensitive_message=classify_sensitive_message,
77
+ )
@@ -0,0 +1,138 @@
1
+ """FastAPI lifespan assembly for app startup and shutdown tasks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextlib import asynccontextmanager
6
+ from typing import Any, Dict
7
+
8
+
9
+ def build_lifespan_runtime(
10
+ *,
11
+ app_mode: str,
12
+ enable_telegram: bool,
13
+ autoload_models: bool,
14
+ is_public_mode: bool,
15
+ public_model: str,
16
+ allow_local_models: bool,
17
+ local_model: str,
18
+ local_draft_model: str,
19
+ model_idle_unload_seconds: int,
20
+ model_router: Any,
21
+ local_kg_watcher: Any,
22
+ local_server_processes: Dict[str, Any],
23
+ logger: Any,
24
+ ) -> Dict[str, Any]:
25
+ """Create lifespan and background task helpers for the FastAPI app."""
26
+
27
+ import asyncio
28
+ import os
29
+
30
+ async def autoload_default_model() -> None:
31
+ if not autoload_models:
32
+ print("⏭️ Model autoload disabled by LATTICEAI_AUTOLOAD_MODELS=false.")
33
+ return
34
+
35
+ if is_public_mode:
36
+ model_id = public_model
37
+ provider = model_id.split(":", 1)[0] if ":" in model_id else "openai"
38
+ env_by_provider = {
39
+ "openai": "OPENAI_API_KEY",
40
+ "openrouter": "OPENROUTER_API_KEY",
41
+ "groq": "GROQ_API_KEY",
42
+ "together": "TOGETHER_API_KEY",
43
+ "ollama": "OLLAMA_API_KEY",
44
+ }
45
+ required_env = env_by_provider.get(provider)
46
+ if required_env and not os.getenv(required_env) and provider != "ollama":
47
+ print(f"🌐 Public mode ready. Set {required_env} to autoload {model_id}.")
48
+ return
49
+ print(f"🌐 Public mode autoload: {model_id}")
50
+ try:
51
+ msg = await model_router.load_model(model_id)
52
+ print(f"✅ {msg}")
53
+ except Exception as e: # pragma: no cover - startup diagnostics
54
+ print(f"⚠️ Public model autoload failed: {e}")
55
+ return
56
+
57
+ if not allow_local_models:
58
+ print("⏭️ Local model autoload skipped because LATTICEAI_ALLOW_LOCAL_MODELS=false.")
59
+ return
60
+
61
+ print("⏳ Auto-loading local model stack:")
62
+ print(f" - Target: {local_model}")
63
+ if local_draft_model:
64
+ print(f" - Draft: {local_draft_model}")
65
+ else:
66
+ print(" - Draft: disabled (set LATTICEAI_LOCAL_DRAFT_MODEL to enable)")
67
+ try:
68
+ await model_router.load_model(local_model, draft_model_id=local_draft_model or None)
69
+ except Exception as e: # pragma: no cover - startup diagnostics
70
+ print(f"⚠️ Local model autoload failed: {e}")
71
+
72
+ async def unload_idle_models_loop() -> None:
73
+ if model_idle_unload_seconds <= 0:
74
+ print("⏭️ Model idle unload disabled.")
75
+ return
76
+ while True:
77
+ await asyncio.sleep(min(60, model_idle_unload_seconds))
78
+ try:
79
+ unloaded = model_router.unload_idle_models(model_idle_unload_seconds)
80
+ if unloaded:
81
+ print(f"🧹 Idle model unload: {', '.join(unloaded)}")
82
+ except Exception as e: # pragma: no cover - background diagnostics
83
+ logger.warning("Idle model unload failed: %s", e)
84
+
85
+ def spawn(coro, *, name: str):
86
+ """Fire-and-forget asyncio task that logs exceptions instead of swallowing them."""
87
+
88
+ task = asyncio.create_task(coro, name=name)
89
+
90
+ def _on_done(t: asyncio.Task) -> None:
91
+ if t.cancelled():
92
+ return
93
+ exc = t.exception()
94
+ if exc is not None:
95
+ logger.warning("background task '%s' failed: %s", name, exc)
96
+
97
+ task.add_done_callback(_on_done)
98
+ return task
99
+
100
+ @asynccontextmanager
101
+ async def lifespan(_app):
102
+ try:
103
+ print(f"🧭 Lattice AI mode: {app_mode}")
104
+ if enable_telegram:
105
+ from telegram_bot import run_bot
106
+
107
+ spawn(run_bot(), name="telegram_bot")
108
+ print("🚀 Telegram Bot Bridge activated!")
109
+ else:
110
+ print("⏭️ Telegram Bot Bridge disabled for this mode.")
111
+ spawn(unload_idle_models_loop(), name="unload_idle_models")
112
+ spawn(autoload_default_model(), name="autoload_default_model")
113
+ if local_kg_watcher:
114
+ restored = local_kg_watcher.restore_enabled_sources()
115
+ if restored.get("restored"):
116
+ print(f"🕸️ Local knowledge watchers restored: {restored['restored']}")
117
+ except Exception as e: # pragma: no cover - startup diagnostics
118
+ print(f"⚠️ Startup sequence failed: {e}")
119
+ try:
120
+ yield
121
+ finally:
122
+ if local_kg_watcher:
123
+ local_kg_watcher.stop_all()
124
+ model_router.unload_all()
125
+ for proc in local_server_processes.values():
126
+ try:
127
+ if proc.poll() is None:
128
+ proc.terminate()
129
+ proc.wait(timeout=5)
130
+ except Exception:
131
+ pass
132
+
133
+ return {
134
+ "autoload_default_model": autoload_default_model,
135
+ "unload_idle_models_loop": unload_idle_models_loop,
136
+ "_spawn": spawn,
137
+ "lifespan": lifespan,
138
+ }
@@ -0,0 +1,87 @@
1
+ """Persistence/service assembly seams for app startup.
2
+
3
+ This module owns the durable local stores and services that sit between the
4
+ Brain runtime and API routers. Imports stay inside the function so importing
5
+ ``latticeai.app_factory`` remains side-effect free.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Callable, Dict, Optional
11
+
12
+
13
+ def build_persistence_runtime(
14
+ *,
15
+ data_dir: Any,
16
+ base_dir: Any,
17
+ enable_graph: bool,
18
+ knowledge_graph: Any,
19
+ hooks_registry: Any,
20
+ history_file: Any,
21
+ conversations: Any,
22
+ user_id_for_email: Callable[[Optional[str]], Optional[str]],
23
+ audit: Callable[[str, Dict[str, Any], Optional[str]], None],
24
+ ) -> Dict[str, Any]:
25
+ """Construct workspace, plugin, memory, ingestion, and portability services."""
26
+
27
+ import os
28
+ from pathlib import Path
29
+
30
+ from lattice_brain.identity import DeviceIdentity
31
+ from lattice_brain.ingestion import IngestionPipeline
32
+ from lattice_brain.portability import KGPortabilityService
33
+ from latticeai.core.agent_registry import AgentRegistry
34
+ from latticeai.core.invitations import InvitationStore
35
+ from latticeai.core.marketplace import TemplateCatalog
36
+ from latticeai.core.plugins import PluginRegistry
37
+ from latticeai.core.realtime import RealtimeBus
38
+ from latticeai.core.workspace_os import WorkspaceOSStore
39
+ from latticeai.services.memory_service import MemoryService
40
+ from latticeai.services.workspace_service import WorkspaceService
41
+
42
+ realtime_bus = RealtimeBus()
43
+ workspace_os = WorkspaceOSStore(data_dir, event_sink=realtime_bus)
44
+ workspace_service = WorkspaceService(workspace_os, resolve_user_id=user_id_for_email)
45
+ invitation_store = InvitationStore(data_dir / "invitations.json")
46
+
47
+ plugins_dir = Path(os.getenv("LATTICEAI_PLUGINS_DIR") or (base_dir / "plugins"))
48
+ plugin_registry = PluginRegistry(plugins_dir, store=workspace_os)
49
+ template_catalog = TemplateCatalog()
50
+ agent_registry = AgentRegistry(data_dir / "agent_registry.json")
51
+
52
+ memory_service = MemoryService(
53
+ store=workspace_os,
54
+ data_dir=data_dir,
55
+ knowledge_graph=knowledge_graph,
56
+ enable_graph=enable_graph,
57
+ history_file=history_file,
58
+ conversation_store=conversations,
59
+ )
60
+ ingestion_pipeline = IngestionPipeline(
61
+ knowledge_graph,
62
+ hooks=hooks_registry,
63
+ enable_graph=enable_graph,
64
+ audit=audit,
65
+ )
66
+ device_identity = DeviceIdentity(data_dir)
67
+ kg_portability = KGPortabilityService(
68
+ knowledge_graph=knowledge_graph,
69
+ data_dir=data_dir,
70
+ enable_graph=enable_graph,
71
+ device_identity=device_identity,
72
+ )
73
+
74
+ return {
75
+ "REALTIME_BUS": realtime_bus,
76
+ "WORKSPACE_OS": workspace_os,
77
+ "WORKSPACE_SERVICE": workspace_service,
78
+ "INVITATION_STORE": invitation_store,
79
+ "PLUGINS_DIR": plugins_dir,
80
+ "PLUGIN_REGISTRY": plugin_registry,
81
+ "TEMPLATE_CATALOG": template_catalog,
82
+ "AGENT_REGISTRY": agent_registry,
83
+ "MEMORY_SERVICE": memory_service,
84
+ "INGESTION_PIPELINE": ingestion_pipeline,
85
+ "DEVICE_IDENTITY": device_identity,
86
+ "KG_PORTABILITY": kg_portability,
87
+ }
@@ -0,0 +1,39 @@
1
+ """Small platform service construction seams for app startup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def build_model_service(
9
+ *,
10
+ model_router: Any,
11
+ runtime_features: Any,
12
+ is_public: bool,
13
+ ) -> Any:
14
+ """Construct the health/model summary service."""
15
+
16
+ from latticeai.services.model_service import ModelService
17
+
18
+ return ModelService(
19
+ model_router=model_router,
20
+ runtime_features=runtime_features,
21
+ is_public=is_public,
22
+ )
23
+
24
+
25
+ def build_brain_network(
26
+ *,
27
+ identity: Any,
28
+ portability: Any,
29
+ data_dir: Any,
30
+ ) -> Any:
31
+ """Construct peer sync/network service for brain portability routes."""
32
+
33
+ from lattice_brain.network import BrainNetwork
34
+
35
+ return BrainNetwork(
36
+ identity=identity,
37
+ portability=portability,
38
+ data_dir=data_dir,
39
+ )