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.
- package/README.md +42 -25
- package/docs/CHANGELOG.md +38 -0
- package/frontend/openapi.json +39 -0
- package/frontend/src/api/client.ts +104 -23
- package/frontend/src/api/openapi.ts +48 -0
- package/frontend/src/components/FirstRunGuide.tsx +3 -3
- package/frontend/src/features/review/ReviewCard.tsx +91 -0
- package/frontend/src/features/review/ReviewInbox.tsx +112 -0
- package/frontend/src/features/review/reviewHelpers.ts +69 -0
- package/frontend/src/i18n.ts +8 -8
- package/frontend/src/pages/Act.tsx +5 -177
- package/frontend/src/routes.ts +1 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/review_queue.py +7 -3
- package/latticeai/app_factory.py +224 -473
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/runtime/app_context_runtime.py +13 -0
- package/latticeai/runtime/automation_runtime.py +64 -0
- package/latticeai/runtime/bootstrap.py +48 -0
- package/latticeai/runtime/context_runtime.py +43 -0
- package/latticeai/runtime/hooks_runtime.py +77 -0
- package/latticeai/runtime/lifespan_runtime.py +138 -0
- package/latticeai/runtime/persistence_runtime.py +87 -0
- package/latticeai/runtime/platform_services_runtime.py +39 -0
- package/latticeai/runtime/router_registration.py +570 -0
- package/latticeai/runtime/web_runtime.py +65 -0
- package/latticeai/services/review_queue.py +20 -4
- package/package.json +1 -1
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +3 -3
- package/static/app/assets/index-D2zafMYb.js +16 -0
- package/static/app/assets/index-D2zafMYb.js.map +1 -0
- package/static/app/index.html +1 -1
- package/static/app/assets/index-xMFu94cX.js +0 -16
- 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,
|
|
4
|
-
|
|
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,
|
|
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