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
@@ -0,0 +1,570 @@
1
+ """Router registration helpers for app-factory decomposition.
2
+
3
+ The first router extraction step keeps router construction at the existing
4
+ call sites and centralizes only the registration operation. This preserves the
5
+ exact include order while creating a narrow seam for the later
6
+ ``register_routers(app, deps)`` extraction.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+
14
+ def build_static_routes_bundle(
15
+ *,
16
+ create_static_routes_router: Any,
17
+ static_dir: Any,
18
+ invite_gate_enabled: bool,
19
+ invite_code: str,
20
+ app_mode: str,
21
+ model_router: Any,
22
+ require_user: Any,
23
+ ) -> dict[str, Any]:
24
+ """Build static route support while preserving the legacy exported names."""
25
+
26
+ static_routes = create_static_routes_router(
27
+ static_dir=static_dir,
28
+ invite_gate_enabled=invite_gate_enabled,
29
+ invite_code=invite_code,
30
+ app_mode=app_mode,
31
+ model_router=model_router,
32
+ require_user=require_user,
33
+ )
34
+ return {
35
+ "STATIC_ROUTES": static_routes,
36
+ "ui_file_response": static_routes.ui_file_response,
37
+ "local_sysinfo": static_routes.local_sysinfo,
38
+ }
39
+
40
+
41
+ def build_auth_admin_security_router_bundle(
42
+ *,
43
+ create_auth_router: Any,
44
+ load_users: Any,
45
+ save_users: Any,
46
+ hash_password: Any,
47
+ verify_and_migrate_password: Any,
48
+ create_session: Any,
49
+ get_session_email: Any,
50
+ invalidate_session: Any,
51
+ extract_bearer_token: Any,
52
+ get_user_role: Any,
53
+ require_user: Any,
54
+ check_ip_rate_limit: Any,
55
+ client_ip: Any,
56
+ get_sso_settings: Any,
57
+ get_sso_discovery: Any,
58
+ public_sso_config: Any,
59
+ open_registration: bool,
60
+ session_ttl: int,
61
+ require_auth: bool,
62
+ ensure_identity: Any,
63
+ create_admin_router: Any,
64
+ require_admin: Any,
65
+ get_history: Any,
66
+ get_audit_log: Any,
67
+ audit_file: Any,
68
+ public_user: Any,
69
+ load_vpc_config: Any,
70
+ save_vpc_config: Any,
71
+ build_admin_audit_report: Any,
72
+ build_sensitivity_report: Any,
73
+ append_audit_event: Any,
74
+ save_sso_config: Any,
75
+ knowledge_graph: Any,
76
+ enable_graph: bool,
77
+ logger: Any,
78
+ invite_code: str,
79
+ invite_gate_enabled: bool,
80
+ default_port: int,
81
+ policy_matrix: Any,
82
+ build_product_hardening_status: Any,
83
+ config: Any,
84
+ kg_portability: Any,
85
+ device_identity: Any,
86
+ create_invitations_router: Any,
87
+ invitation_store: Any,
88
+ workspace_service: Any,
89
+ user_id_for_email: Any,
90
+ create_security_router: Any,
91
+ classify_sensitive_message: Any,
92
+ ) -> dict[str, Any]:
93
+ """Build auth/admin/security routers and their legacy helper closures."""
94
+
95
+ auth_router = create_auth_router(
96
+ load_users=load_users,
97
+ save_users=save_users,
98
+ hash_password=hash_password,
99
+ verify_and_migrate=verify_and_migrate_password,
100
+ create_session=create_session,
101
+ get_session_email=get_session_email,
102
+ invalidate_session=invalidate_session,
103
+ extract_bearer_token=extract_bearer_token,
104
+ get_user_role=get_user_role,
105
+ require_user=require_user,
106
+ check_ip_rate_limit=check_ip_rate_limit,
107
+ client_ip=client_ip,
108
+ get_sso_settings=get_sso_settings,
109
+ get_sso_discovery=get_sso_discovery,
110
+ public_sso_config=public_sso_config,
111
+ open_registration=open_registration,
112
+ session_ttl=session_ttl,
113
+ require_auth=require_auth,
114
+ ensure_identity=ensure_identity,
115
+ )
116
+
117
+ def graph_stats_safe() -> dict[str, Any]:
118
+ try:
119
+ return knowledge_graph.stats() if (enable_graph and knowledge_graph) else {"disabled": True}
120
+ except Exception as exc: # pragma: no cover - defensive legacy behavior
121
+ return {"error": str(exc)}
122
+
123
+ def product_hardening_status() -> Any:
124
+ return build_product_hardening_status(
125
+ config=config,
126
+ portability=kg_portability,
127
+ device_identity=device_identity,
128
+ )
129
+
130
+ admin_router = create_admin_router(
131
+ require_admin=require_admin,
132
+ require_user=require_user,
133
+ load_users=load_users,
134
+ save_users=save_users,
135
+ get_user_role=get_user_role,
136
+ get_history=get_history,
137
+ get_audit_log=get_audit_log,
138
+ public_user=public_user,
139
+ load_vpc_config=load_vpc_config,
140
+ save_vpc_config=save_vpc_config,
141
+ build_admin_audit_report=build_admin_audit_report,
142
+ build_sensitivity_report=build_sensitivity_report,
143
+ append_audit_event=append_audit_event,
144
+ public_sso_config=public_sso_config,
145
+ save_sso_config=save_sso_config,
146
+ get_graph_stats=graph_stats_safe,
147
+ enable_graph=enable_graph,
148
+ invite_code=invite_code,
149
+ invite_gate_enabled=invite_gate_enabled,
150
+ default_port=default_port,
151
+ policy_matrix=policy_matrix,
152
+ product_hardening_status=product_hardening_status,
153
+ )
154
+ invitations_router = create_invitations_router(
155
+ invitation_store=invitation_store,
156
+ workspace_service=workspace_service,
157
+ require_admin=require_admin,
158
+ require_user=require_user,
159
+ user_id_for_email=user_id_for_email,
160
+ append_audit_event=append_audit_event,
161
+ )
162
+
163
+ def security_audit_events_safe() -> list[dict[str, Any]]:
164
+ try:
165
+ return get_audit_log(audit_file)
166
+ except Exception as exc: # pragma: no cover - defensive legacy behavior
167
+ logger.warning("security audit events load failed: %s", exc)
168
+ return []
169
+
170
+ def security_list_uploaded_files() -> list[dict[str, Any]]:
171
+ files: list[dict[str, Any]] = []
172
+ for idx, event in enumerate(security_audit_events_safe()):
173
+ if event.get("event_type") != "document_upload":
174
+ continue
175
+ files.append({
176
+ "file_id": str(event.get("filename") or idx),
177
+ "filename": event.get("filename"),
178
+ "user_email": event.get("user_email"),
179
+ "user_nickname": event.get("user_nickname"),
180
+ "uploaded_at": event.get("timestamp"),
181
+ "ext": event.get("ext"),
182
+ "bytes": event.get("bytes"),
183
+ "sensitivity": event.get("sensitivity") or "none",
184
+ "sensitive_labels": event.get("sensitive_labels") or [],
185
+ "content_preview": event.get("content_preview"),
186
+ })
187
+ return files
188
+
189
+ security_router = create_security_router(
190
+ require_admin=require_admin,
191
+ get_history=get_history,
192
+ get_audit_events=security_audit_events_safe,
193
+ classify_sensitive_message=classify_sensitive_message,
194
+ build_sensitivity_report=build_sensitivity_report,
195
+ list_uploaded_files=security_list_uploaded_files,
196
+ append_audit_event=append_audit_event,
197
+ )
198
+
199
+ return {
200
+ "auth_router": auth_router,
201
+ "admin_router": admin_router,
202
+ "invitations_router": invitations_router,
203
+ "security_router": security_router,
204
+ "_graph_stats_safe": graph_stats_safe,
205
+ "_product_hardening_status": product_hardening_status,
206
+ "_security_audit_events_safe": security_audit_events_safe,
207
+ "_security_list_uploaded_files": security_list_uploaded_files,
208
+ }
209
+
210
+
211
+ def register_router(app: Any, router: Any) -> Any:
212
+ """Include one router and return it for optional caller-side bookkeeping."""
213
+
214
+ app.include_router(router)
215
+ return router
216
+
217
+
218
+ def register_routers(app: Any, *routers: Any) -> tuple[Any, ...]:
219
+ """Include routers in the given order and return them unchanged."""
220
+
221
+ for router in routers:
222
+ register_router(app, router)
223
+ return routers
224
+
225
+
226
+ def register_foundation_routers(
227
+ app: Any,
228
+ *,
229
+ static_router: Any,
230
+ auth_router: Any,
231
+ admin_router: Any,
232
+ invitations_router: Any,
233
+ security_router: Any,
234
+ create_workspace_router: Any,
235
+ context: Any,
236
+ ) -> tuple[Any, ...]:
237
+ """Register early static/auth/admin/security/workspace routers in order."""
238
+
239
+ workspace_router = create_workspace_router(context)
240
+ return register_routers(
241
+ app,
242
+ static_router,
243
+ auth_router,
244
+ admin_router,
245
+ invitations_router,
246
+ security_router,
247
+ workspace_router,
248
+ )
249
+
250
+
251
+ def register_platform_feature_routers(
252
+ app: Any,
253
+ *,
254
+ create_plugins_router: Any,
255
+ plugin_registry: Any,
256
+ require_user: Any,
257
+ require_admin: Any,
258
+ append_audit_event: Any,
259
+ platform: Any,
260
+ ui_file_response: Any,
261
+ static_dir: Any,
262
+ create_workflow_designer_router: Any,
263
+ store: Any,
264
+ get_current_user: Any,
265
+ workspace_graph: Any,
266
+ hooks: Any,
267
+ run_executor: Any,
268
+ trigger_service: Any,
269
+ create_agents_router: Any,
270
+ agent_runtime: Any,
271
+ create_marketplace_router: Any,
272
+ template_catalog: Any,
273
+ create_realtime_router: Any,
274
+ realtime_bus: Any,
275
+ ) -> tuple[Any, ...]:
276
+ """Register plugin/workflow/agent/marketplace/realtime routes in order."""
277
+
278
+ return register_routers(
279
+ app,
280
+ create_plugins_router(
281
+ registry=plugin_registry,
282
+ require_user=require_user,
283
+ require_admin=require_admin,
284
+ append_audit_event=append_audit_event,
285
+ register_skill=platform.register_plugin_skill,
286
+ plugin_runners_factory=lambda: platform.plugin_capability_runners(None, None),
287
+ ui_file_response=ui_file_response,
288
+ static_dir=static_dir,
289
+ ),
290
+ create_workflow_designer_router(
291
+ store=store,
292
+ require_user=require_user,
293
+ get_current_user=get_current_user,
294
+ gate_read=platform.gate_read,
295
+ gate_write=platform.gate_write,
296
+ workspace_graph=workspace_graph,
297
+ build_runners=platform.build_workflow_runners,
298
+ append_audit_event=append_audit_event,
299
+ ui_file_response=ui_file_response,
300
+ static_dir=static_dir,
301
+ hooks=hooks,
302
+ run_executor=run_executor,
303
+ trigger_service=trigger_service,
304
+ ),
305
+ create_agents_router(
306
+ store=store,
307
+ orchestrator_factory=platform.build_orchestrator,
308
+ require_user=require_user,
309
+ get_current_user=get_current_user,
310
+ gate_read=platform.gate_read,
311
+ gate_write=platform.gate_write,
312
+ workspace_graph=workspace_graph,
313
+ append_audit_event=append_audit_event,
314
+ ui_file_response=ui_file_response,
315
+ static_dir=static_dir,
316
+ agent_runtime=agent_runtime,
317
+ run_executor=run_executor,
318
+ ),
319
+ create_marketplace_router(
320
+ store=store,
321
+ catalog=template_catalog,
322
+ require_user=require_user,
323
+ gate_read=platform.gate_read,
324
+ gate_write=platform.gate_write,
325
+ workspace_graph=workspace_graph,
326
+ ),
327
+ create_realtime_router(
328
+ bus=realtime_bus,
329
+ require_user=require_user,
330
+ get_current_user=get_current_user,
331
+ allowed_scopes=platform.allowed_scopes,
332
+ ui_file_response=ui_file_response,
333
+ static_dir=static_dir,
334
+ ),
335
+ )
336
+
337
+
338
+ def register_health_and_model_routers(
339
+ app: Any,
340
+ *,
341
+ create_health_router: Any,
342
+ model_service: Any,
343
+ engine_status: Any,
344
+ get_current_user: Any,
345
+ require_auth: bool,
346
+ app_version: str,
347
+ app_mode: str,
348
+ create_models_router: Any,
349
+ model_router: Any,
350
+ require_user: Any,
351
+ load_users: Any,
352
+ get_user_role: Any,
353
+ install_engine: Any,
354
+ verify_cloud_models: Any,
355
+ normalize_local_model_request: Any,
356
+ download_hf_model: Any,
357
+ prepare_and_load_model: Any,
358
+ prepare_and_load_model_stream: Any,
359
+ sse_event: Any,
360
+ ensure_ollama_server: Any,
361
+ local_binary: Any,
362
+ filter_lower_family_versions: Any,
363
+ list_compat_profiles: Any,
364
+ set_user_api_key: Any,
365
+ engine_model_catalog: Any,
366
+ model_engine_aliases: Any,
367
+ cloud_verify_ttl_seconds: int,
368
+ is_public_mode: bool,
369
+ allow_local_models: bool,
370
+ ) -> tuple[Any, ...]:
371
+ """Register health and model management routes in legacy order."""
372
+
373
+ return register_routers(
374
+ app,
375
+ create_health_router(
376
+ model_service=model_service,
377
+ engine_status=engine_status,
378
+ get_current_user=get_current_user,
379
+ require_auth=require_auth,
380
+ app_version=app_version,
381
+ app_mode=app_mode,
382
+ ),
383
+ create_models_router(
384
+ model_router=model_router,
385
+ require_user=require_user,
386
+ get_current_user=get_current_user,
387
+ load_users=load_users,
388
+ get_user_role=get_user_role,
389
+ install_engine=install_engine,
390
+ verify_cloud_models=verify_cloud_models,
391
+ normalize_local_model_request=normalize_local_model_request,
392
+ download_hf_model=download_hf_model,
393
+ prepare_and_load_model=prepare_and_load_model,
394
+ prepare_and_load_model_stream=prepare_and_load_model_stream,
395
+ sse_event=sse_event,
396
+ ensure_ollama_server=ensure_ollama_server,
397
+ local_binary=local_binary,
398
+ engine_status=engine_status,
399
+ filter_lower_family_versions=filter_lower_family_versions,
400
+ list_compat_profiles=list_compat_profiles,
401
+ set_user_api_key=set_user_api_key,
402
+ engine_model_catalog=engine_model_catalog,
403
+ model_engine_aliases=model_engine_aliases,
404
+ cloud_verify_ttl_seconds=cloud_verify_ttl_seconds,
405
+ is_public_mode=is_public_mode,
406
+ allow_local_models=allow_local_models,
407
+ require_auth=require_auth,
408
+ ),
409
+ )
410
+
411
+
412
+ def register_interaction_routers(
413
+ app: Any,
414
+ *,
415
+ create_chat_router: Any,
416
+ context: Any,
417
+ create_search_router: Any,
418
+ search_service: Any,
419
+ allowed_workspaces_for: Any,
420
+ require_user: Any,
421
+ embedding_info: Any,
422
+ create_tools_router: Any,
423
+ ingestion_pipeline: Any,
424
+ config: Any,
425
+ data_dir: Any,
426
+ static_dir: Any,
427
+ model_router: Any,
428
+ require_admin: Any,
429
+ get_current_user: Any,
430
+ clear_history: Any,
431
+ append_audit_event: Any,
432
+ enforce_rate_limit: Any,
433
+ bytes_match_extension: Any,
434
+ classify_sensitive_message: Any,
435
+ save_to_history: Any,
436
+ enable_graph: bool,
437
+ knowledge_graph: Any,
438
+ require_graph: Any,
439
+ local_kg_watcher: Any,
440
+ load_mcp_installs: Any,
441
+ recommend_mcps: Any,
442
+ install_mcp: Any,
443
+ mcp_public_item: Any,
444
+ hooks: Any,
445
+ create_hooks_router: Any,
446
+ create_agent_registry_router: Any,
447
+ agent_registry: Any,
448
+ create_memory_router: Any,
449
+ memory_service: Any,
450
+ platform: Any,
451
+ ) -> tuple[Any, ...]:
452
+ """Register chat/search/tools/hooks/registry/memory routes in order."""
453
+
454
+ return register_routers(
455
+ app,
456
+ create_chat_router(context),
457
+ create_search_router(
458
+ service=search_service,
459
+ allowed_workspaces_for=allowed_workspaces_for,
460
+ require_user=require_user,
461
+ embedding_info=embedding_info,
462
+ ),
463
+ create_tools_router(
464
+ ingestion_pipeline=ingestion_pipeline,
465
+ config=config,
466
+ data_dir=data_dir,
467
+ static_dir=static_dir,
468
+ model_router=model_router,
469
+ require_user=require_user,
470
+ require_admin=require_admin,
471
+ get_current_user=get_current_user,
472
+ clear_history=clear_history,
473
+ append_audit_event=append_audit_event,
474
+ enforce_rate_limit=enforce_rate_limit,
475
+ bytes_match_extension=bytes_match_extension,
476
+ classify_sensitive_message=classify_sensitive_message,
477
+ save_to_history=save_to_history,
478
+ enable_graph=enable_graph,
479
+ knowledge_graph=knowledge_graph,
480
+ require_graph=require_graph,
481
+ local_kg_watcher=local_kg_watcher,
482
+ load_mcp_installs=load_mcp_installs,
483
+ recommend_mcps=recommend_mcps,
484
+ install_mcp=install_mcp,
485
+ mcp_public_item=mcp_public_item,
486
+ hooks=hooks,
487
+ ),
488
+ create_hooks_router(
489
+ registry=hooks,
490
+ require_user=require_user,
491
+ append_audit_event=append_audit_event,
492
+ ),
493
+ create_agent_registry_router(
494
+ registry=agent_registry,
495
+ require_user=require_user,
496
+ append_audit_event=append_audit_event,
497
+ ),
498
+ create_memory_router(
499
+ service=memory_service,
500
+ require_user=require_user,
501
+ get_current_user=get_current_user,
502
+ gate_read=platform.gate_read,
503
+ gate_write=platform.gate_write,
504
+ append_audit_event=append_audit_event,
505
+ ),
506
+ )
507
+
508
+
509
+ def register_review_and_brain_tail_routers(
510
+ app: Any,
511
+ *,
512
+ create_review_queue_router: Any,
513
+ review_queue: Any,
514
+ require_user: Any,
515
+ gate_read: Any,
516
+ gate_write: Any,
517
+ run_review_item: Any,
518
+ append_audit_event: Any,
519
+ create_browser_router: Any,
520
+ ingestion_pipeline: Any,
521
+ create_portability_router: Any,
522
+ kg_portability: Any,
523
+ require_admin: Any,
524
+ build_brain_network: Any,
525
+ device_identity: Any,
526
+ data_dir: Any,
527
+ create_network_router: Any,
528
+ create_garden_router: Any,
529
+ gardener: Any,
530
+ create_setup_router: Any,
531
+ model_router: Any,
532
+ ) -> Any:
533
+ """Register the final review/browser/brain tail routes in legacy order."""
534
+
535
+ register_routers(
536
+ app,
537
+ create_review_queue_router(
538
+ service=review_queue,
539
+ require_user=require_user,
540
+ gate_read=gate_read,
541
+ gate_write=gate_write,
542
+ run_review_item=run_review_item,
543
+ append_audit_event=append_audit_event,
544
+ ),
545
+ create_browser_router(
546
+ pipeline=ingestion_pipeline,
547
+ require_user=require_user,
548
+ ),
549
+ create_portability_router(
550
+ service=kg_portability,
551
+ require_user=require_user,
552
+ require_admin=require_admin,
553
+ ),
554
+ )
555
+ brain_network = build_brain_network(
556
+ identity=device_identity,
557
+ portability=kg_portability,
558
+ data_dir=data_dir,
559
+ )
560
+ register_routers(
561
+ app,
562
+ create_network_router(
563
+ network=brain_network,
564
+ identity=device_identity,
565
+ require_user=require_user,
566
+ ),
567
+ create_garden_router(gardener=gardener, require_user=require_user),
568
+ create_setup_router(model_router=model_router, require_user=require_user),
569
+ )
570
+ return brain_network
@@ -0,0 +1,65 @@
1
+ """FastAPI shell assembly for the application factory.
2
+
3
+ This seam owns only the outer web shell: app construction, CORS middleware,
4
+ and static asset mounts. Router registration stays in ``app_factory`` until
5
+ the route snapshot/reorder step is reviewed.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+ from typing import Any, Dict, Iterable
12
+
13
+
14
+ def build_web_runtime(
15
+ *,
16
+ app_mode: str,
17
+ app_version: str,
18
+ lifespan: Any,
19
+ default_host: str,
20
+ default_port: int,
21
+ cors_extra_origins: Iterable[str],
22
+ cors_allow_network: bool,
23
+ static_dir: Path,
24
+ ) -> Dict[str, Any]:
25
+ """Create the FastAPI shell and mount static assets in legacy order."""
26
+
27
+ from fastapi import FastAPI
28
+ from fastapi.middleware.cors import CORSMiddleware
29
+ from fastapi.staticfiles import StaticFiles
30
+
31
+ app = FastAPI(
32
+ title=f"Lattice AI Server ({app_mode})",
33
+ version=app_version,
34
+ lifespan=lifespan,
35
+ )
36
+
37
+ cors_allowed_origins = [
38
+ f"http://localhost:{default_port}",
39
+ f"http://127.0.0.1:{default_port}",
40
+ *cors_extra_origins,
41
+ ]
42
+ if cors_allow_network:
43
+ cors_allowed_origins = cors_allowed_origins + [
44
+ f"http://{default_host}:{default_port}",
45
+ f"https://{default_host}:{default_port}",
46
+ ]
47
+
48
+ app.add_middleware(
49
+ CORSMiddleware,
50
+ allow_origins=cors_allowed_origins,
51
+ allow_methods=["*"],
52
+ allow_headers=["*"],
53
+ allow_credentials=True,
54
+ )
55
+
56
+ static_dir.mkdir(parents=True, exist_ok=True)
57
+ app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
58
+ icons_dir = static_dir / "icons"
59
+ if icons_dir.exists():
60
+ app.mount("/icons", StaticFiles(directory=str(icons_dir)), name="icons")
61
+
62
+ return {
63
+ "app": app,
64
+ "CORS_ALLOWED_ORIGINS": cors_allowed_origins,
65
+ }
@@ -1,8 +1,8 @@
1
1
  """Brain review queue (5.6.0) — the suggestion inbox.
2
2
 
3
- Automation/trigger runs drop drafts here; the user approves, dismisses, or
4
- snoozes them. This service owns the *policy* (legal status transitions, snooze
5
- expiry semantics, run_now back-linking); the store owns persistence.
3
+ Automation/trigger runs drop drafts here; the user approves, dismisses, snoozes,
4
+ or unsnoozes them. This service owns the *policy* (legal status transitions,
5
+ snooze expiry semantics, run_now back-linking); the store owns persistence.
6
6
 
7
7
  Design decisions (agreed in #develop-with-openclaw):
8
8
 
@@ -19,7 +19,7 @@ Design decisions (agreed in #develop-with-openclaw):
19
19
  from __future__ import annotations
20
20
 
21
21
  from datetime import datetime
22
- from typing import Any, Callable, Dict, List, Optional
22
+ from typing import Any, Callable, Dict, Optional
23
23
 
24
24
  # status: terminal vs. open. Open items can still be acted on.
25
25
  OPEN_STATUSES = {"pending", "snoozed"}
@@ -33,6 +33,8 @@ _ALLOWED_FROM: Dict[str, set] = {
33
33
  "approve": {"pending", "snoozed"},
34
34
  "dismiss": {"pending", "snoozed"},
35
35
  "snooze": {"pending", "snoozed"},
36
+ # unsnooze only makes sense from a *stored* snoozed state (not pending).
37
+ "unsnooze": {"snoozed"},
36
38
  # run_now is a preview, not a transition — only while still open.
37
39
  "run_now": {"pending", "snoozed"},
38
40
  }
@@ -135,6 +137,20 @@ class ReviewQueueService:
135
137
  )
136
138
  return self._view(updated)
137
139
 
140
+ def unsnooze(self, item_id: str, *, workspace_id: Optional[str] = None) -> Dict[str, Any]:
141
+ """Return a snoozed item to the pending queue.
142
+
143
+ Only legal from a *stored* ``status == "snoozed"`` (an expired snooze is
144
+ still stored as snoozed, so it remains unsnoozable). Clears the timer and
145
+ sets ``status = "pending"``; any other source status raises 409.
146
+ """
147
+ item = self._store.get_review_item(item_id, workspace_id=workspace_id)
148
+ self._guard("unsnooze", item)
149
+ updated = self._store.update_review_item(
150
+ item_id, workspace_id=workspace_id, status="pending", snoozed_until=None,
151
+ )
152
+ return self._view(updated)
153
+
138
154
  # ── run_now: preview/regenerate, NOT a status change ─────────────────
139
155
  def run_now(
140
156
  self,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ltcai",
3
- "version": "5.6.0",
3
+ "version": "6.0.0",
4
4
  "description": "Lattice AI — local-first Digital Brain that keeps your knowledge durable across any AI model.",
5
5
  "homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
6
6
  "repository": {
@@ -1654,7 +1654,7 @@ dependencies = [
1654
1654
 
1655
1655
  [[package]]
1656
1656
  name = "lattice-ai-desktop"
1657
- version = "5.6.0"
1657
+ version = "6.0.0"
1658
1658
  dependencies = [
1659
1659
  "plist",
1660
1660
  "serde",