ltcai 9.0.0 → 9.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.
- package/README.md +88 -46
- package/auto_setup.py +7 -853
- package/desktop/electron/README.md +9 -0
- package/desktop/electron/main.cjs +5 -3
- package/docs/CHANGELOG.md +178 -2
- package/docs/COMMUNITY_AND_PLUGINS.md +4 -2
- package/docs/DEVELOPMENT.md +53 -14
- package/docs/LEGACY_COMPATIBILITY.md +4 -4
- package/docs/ONBOARDING.md +10 -2
- package/docs/OPERATIONS.md +8 -4
- package/docs/PRODUCT_DIRECTION_REVIEW.md +4 -0
- package/docs/TRUST_MODEL.md +5 -2
- package/docs/WHY_LATTICE.md +15 -10
- package/docs/WORKFLOW_DESIGNER.md +22 -0
- package/docs/kg-schema.md +13 -2
- package/docs/mcp-tools.md +17 -6
- package/docs/privacy.md +19 -3
- package/docs/public-deploy.md +32 -3
- package/docs/security-model.md +46 -11
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/archive.py +1 -6
- package/lattice_brain/context.py +66 -9
- package/lattice_brain/graph/_kg_fsutil.py +1 -5
- package/lattice_brain/graph/discovery_index.py +174 -20
- package/lattice_brain/graph/documents.py +44 -9
- package/lattice_brain/graph/ingest.py +47 -20
- package/lattice_brain/graph/provenance.py +13 -6
- package/lattice_brain/graph/retrieval.py +140 -39
- package/lattice_brain/graph/retrieval_docgen.py +37 -4
- package/lattice_brain/ingestion.py +4 -7
- package/lattice_brain/portability.py +28 -14
- package/lattice_brain/runtime/agent_runtime.py +5 -9
- package/lattice_brain/runtime/hooks.py +30 -13
- package/lattice_brain/runtime/multi_agent.py +27 -8
- package/lattice_brain/runtime/statuses.py +10 -0
- package/lattice_brain/utils.py +20 -2
- package/lattice_brain/workflow.py +1 -5
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +1 -1
- package/latticeai/api/agent_registry.py +10 -8
- package/latticeai/api/agents.py +22 -3
- package/latticeai/api/auth.py +78 -16
- package/latticeai/api/browser.py +303 -34
- package/latticeai/api/chat.py +320 -850
- package/latticeai/api/chat_agent_http.py +356 -0
- package/latticeai/api/chat_contracts.py +54 -0
- package/latticeai/api/chat_documents.py +256 -0
- package/latticeai/api/chat_helpers.py +28 -1
- package/latticeai/api/chat_history.py +99 -0
- package/latticeai/api/chat_intents.py +316 -0
- package/latticeai/api/chat_stream.py +115 -0
- package/latticeai/api/computer_use.py +62 -9
- package/latticeai/api/health.py +9 -3
- package/latticeai/api/hooks.py +17 -6
- package/latticeai/api/knowledge_graph.py +78 -14
- package/latticeai/api/local_files.py +7 -2
- package/latticeai/api/mcp.py +102 -23
- package/latticeai/api/models.py +72 -33
- package/latticeai/api/network.py +5 -5
- package/latticeai/api/permissions.py +67 -26
- package/latticeai/api/plugins.py +21 -7
- package/latticeai/api/portability.py +5 -5
- package/latticeai/api/realtime.py +33 -5
- package/latticeai/api/setup.py +2 -2
- package/latticeai/api/static_routes.py +123 -24
- package/latticeai/api/tools.py +96 -14
- package/latticeai/api/workflow_designer.py +37 -0
- package/latticeai/api/workspace.py +2 -1
- package/latticeai/app_factory.py +110 -52
- package/latticeai/core/agent.py +50 -13
- package/latticeai/core/agent_prompts.py +5 -0
- package/latticeai/core/agent_registry.py +1 -5
- package/latticeai/core/config.py +23 -3
- package/latticeai/core/context_builder.py +21 -3
- package/latticeai/core/file_generation.py +451 -0
- package/latticeai/core/invitations.py +1 -4
- package/latticeai/core/io_utils.py +9 -1
- package/latticeai/core/legacy_compatibility.py +24 -3
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/plugins.py +15 -0
- package/latticeai/core/policy.py +1 -1
- package/latticeai/core/realtime.py +38 -15
- package/latticeai/core/sessions.py +7 -2
- package/latticeai/core/timeutil.py +10 -0
- package/latticeai/core/tool_registry.py +90 -18
- package/latticeai/core/users.py +1 -5
- package/latticeai/core/workspace_graph_trace.py +31 -5
- package/latticeai/core/workspace_memory.py +3 -1
- package/latticeai/core/workspace_os.py +18 -7
- package/latticeai/core/workspace_os_utils.py +0 -5
- package/latticeai/core/workspace_permissions.py +2 -1
- package/latticeai/core/workspace_plugins.py +1 -1
- package/latticeai/core/workspace_runs.py +14 -5
- package/latticeai/core/workspace_skills.py +1 -1
- package/latticeai/core/workspace_snapshots.py +2 -1
- package/latticeai/core/workspace_timeline.py +2 -1
- package/latticeai/integrations/telegram_bot.py +94 -43
- package/latticeai/models/router.py +130 -36
- package/latticeai/runtime/access_runtime.py +62 -4
- package/latticeai/runtime/automation_runtime.py +13 -7
- package/latticeai/runtime/brain_runtime.py +19 -7
- package/latticeai/runtime/chat_wiring.py +2 -0
- package/latticeai/runtime/config_runtime.py +58 -26
- package/latticeai/runtime/context_runtime.py +16 -4
- package/latticeai/runtime/hooks_runtime.py +1 -1
- package/latticeai/runtime/model_wiring.py +33 -5
- package/latticeai/runtime/namespace_runtime.py +62 -72
- package/latticeai/runtime/platform_runtime_wiring.py +4 -3
- package/latticeai/runtime/review_wiring.py +1 -1
- package/latticeai/runtime/router_registration.py +34 -1
- package/latticeai/runtime/security_runtime.py +110 -13
- package/latticeai/runtime/stages.py +27 -0
- package/latticeai/server_app.py +5 -1
- package/latticeai/services/architecture_readiness.py +74 -5
- package/latticeai/services/brain_automation.py +3 -26
- package/latticeai/services/chat_service.py +205 -15
- package/latticeai/services/local_knowledge.py +423 -0
- package/latticeai/services/memory_service.py +55 -21
- package/latticeai/services/model_engines.py +48 -33
- package/latticeai/services/model_errors.py +17 -0
- package/latticeai/services/model_loading.py +28 -25
- package/latticeai/services/model_runtime.py +228 -162
- package/latticeai/services/p_reinforce.py +22 -3
- package/latticeai/services/platform_runtime.py +83 -23
- package/latticeai/services/product_readiness.py +19 -17
- package/latticeai/services/review_queue.py +12 -0
- package/latticeai/services/router_context.py +1 -0
- package/latticeai/services/run_executor.py +4 -9
- package/latticeai/services/search_service.py +203 -28
- package/latticeai/services/tool_dispatch.py +28 -5
- package/latticeai/services/triggers.py +53 -4
- package/latticeai/services/upload_service.py +13 -2
- package/latticeai/setup/__init__.py +25 -0
- package/latticeai/setup/auto_setup.py +857 -0
- package/latticeai/setup/wizard.py +1264 -0
- package/latticeai/tools/__init__.py +278 -0
- package/{tools → latticeai/tools}/commands.py +67 -7
- package/{tools → latticeai/tools}/computer.py +1 -1
- package/{tools → latticeai/tools}/documents.py +1 -1
- package/{tools → latticeai/tools}/filesystem.py +3 -3
- package/latticeai/tools/knowledge.py +178 -0
- package/{tools → latticeai/tools}/local_files.py +1 -1
- package/{tools → latticeai/tools}/network.py +0 -1
- package/local_knowledge_api.py +4 -342
- package/package.json +22 -2
- package/scripts/brain_quality_eval.py +3 -1
- package/scripts/bump_version.py +3 -0
- package/scripts/capture_release_evidence.mjs +180 -0
- package/scripts/check_current_release_docs.mjs +142 -0
- package/scripts/check_i18n_literals.mjs +107 -31
- package/scripts/check_openapi_drift.mjs +56 -0
- package/scripts/export_openapi.py +67 -7
- package/scripts/i18n_literal_allowlist.json +22 -24
- package/scripts/run_integration_tests.mjs +72 -10
- package/scripts/validate_release_artifacts.py +49 -7
- package/scripts/wheel_smoke.py +5 -0
- package/setup_wizard.py +3 -1260
- 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 +11 -11
- package/static/app/assets/Act-C3dBrWE-.js +1 -0
- package/static/app/assets/Brain-DBYgdcjt.js +321 -0
- package/static/app/assets/Capture-Cf3hqRtN.js +1 -0
- package/static/app/assets/Library-CFfkNn3s.js +1 -0
- package/static/app/assets/System-BOurbT-v.js +1 -0
- package/static/app/assets/index-A3M9sElj.js +17 -0
- package/static/app/assets/index-Bmx9rzTc.css +2 -0
- package/static/app/assets/primitives-DcUUmhdC.js +1 -0
- package/static/app/assets/textarea-BklR6zN4.js +1 -0
- package/static/app/index.html +2 -2
- package/static/sw.js +1 -1
- package/tools/__init__.py +21 -269
- package/docs/CODE_REVIEW_2026-07-06.md +0 -764
- package/latticeai/runtime/app_context_runtime.py +0 -13
- package/latticeai/runtime/tail_wiring.py +0 -21
- package/scripts/com.pts.claudecode.discord.plist +0 -31
- package/scripts/pts-claudecode-discord-bridge.mjs +0 -207
- package/scripts/start-pts-claudecode-discord.sh +0 -51
- package/static/app/assets/Act-21lIXx2E.js +0 -1
- package/static/app/assets/Brain-BqUd5UJJ.js +0 -321
- package/static/app/assets/Capture-BA7Z2Q1u.js +0 -1
- package/static/app/assets/Library-bFMtyni3.js +0 -1
- package/static/app/assets/System-K6krGCqn.js +0 -1
- package/static/app/assets/index-C4R3ws30.js +0 -17
- package/static/app/assets/index-ChSeOB02.css +0 -2
- package/static/app/assets/primitives-sQU3it5I.js +0 -1
- package/static/app/assets/textarea-DK3Fd_lR.js +0 -1
- package/tools/knowledge.py +0 -95
package/latticeai/app_factory.py
CHANGED
|
@@ -18,7 +18,6 @@ import threading
|
|
|
18
18
|
from dataclasses import dataclass
|
|
19
19
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
20
20
|
|
|
21
|
-
from latticeai.runtime.app_context_runtime import build_app_context
|
|
22
21
|
from latticeai.runtime.access_runtime import build_access_runtime
|
|
23
22
|
from latticeai.runtime.bootstrap import build_session_runtime
|
|
24
23
|
from latticeai.runtime.brain_runtime import build_brain_runtime
|
|
@@ -52,6 +51,7 @@ from latticeai.runtime.sso_config_runtime import build_sso_config_runtime
|
|
|
52
51
|
from latticeai.runtime.audit_runtime import build_audit_runtime
|
|
53
52
|
from latticeai.runtime.router_registration import (
|
|
54
53
|
build_auth_admin_security_router_bundle,
|
|
54
|
+
build_router_bundle,
|
|
55
55
|
build_static_routes_bundle,
|
|
56
56
|
register_health_and_model_routers,
|
|
57
57
|
register_foundation_routers,
|
|
@@ -60,7 +60,6 @@ from latticeai.runtime.router_registration import (
|
|
|
60
60
|
register_review_and_brain_tail_routers,
|
|
61
61
|
)
|
|
62
62
|
from latticeai.runtime.security_runtime import build_security_runtime
|
|
63
|
-
from latticeai.runtime.tail_wiring import register_tail_runtime_routers
|
|
64
63
|
from latticeai.runtime.user_key_runtime import build_user_key_runtime
|
|
65
64
|
from latticeai.runtime.web_runtime import build_web_runtime
|
|
66
65
|
|
|
@@ -107,7 +106,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
107
106
|
enforce_rate_limit as _enforce_rate_limit,
|
|
108
107
|
)
|
|
109
108
|
from latticeai.core.audit import (
|
|
110
|
-
get_audit_log as _get_audit_log, # noqa: F401 - legacy server_app
|
|
109
|
+
get_audit_log as _get_audit_log, # noqa: F401 - explicit legacy server_app export
|
|
111
110
|
classify_sensitive_message as _classify_sensitive_message,
|
|
112
111
|
build_sensitivity_report as _build_sensitivity_report,
|
|
113
112
|
build_admin_audit_report as _build_admin_audit_report,
|
|
@@ -132,24 +131,19 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
132
131
|
user_id_for_email as _user_id_for_email,
|
|
133
132
|
)
|
|
134
133
|
from latticeai.services.chat_service import ChatService
|
|
134
|
+
from latticeai.services.app_context import AppContext
|
|
135
135
|
from latticeai.core.embedding_providers import resolve_embedder, resolve_embedding_profile
|
|
136
136
|
from latticeai.services.model_runtime import (
|
|
137
137
|
CLOUD_VERIFY_TTL_SECONDS,
|
|
138
138
|
ENGINE_MODEL_CATALOG,
|
|
139
139
|
LOCAL_SERVER_PROCESSES,
|
|
140
140
|
MODEL_ENGINE_ALIASES,
|
|
141
|
-
|
|
141
|
+
build_model_runtime,
|
|
142
142
|
download_hf_model,
|
|
143
|
-
engine_status,
|
|
144
143
|
filter_lower_family_versions,
|
|
145
|
-
install_engine,
|
|
146
144
|
local_binary,
|
|
147
145
|
normalize_local_model_request,
|
|
148
|
-
prepare_and_load_model,
|
|
149
|
-
prepare_and_load_model_stream,
|
|
150
|
-
runtime_features,
|
|
151
146
|
sse_event,
|
|
152
|
-
verify_cloud_models,
|
|
153
147
|
ensure_ollama_server,
|
|
154
148
|
)
|
|
155
149
|
from latticeai.api.workspace import create_workspace_router, _workspace_scope_from_request
|
|
@@ -177,10 +171,8 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
177
171
|
from lattice_brain.ingestion import IngestionItem
|
|
178
172
|
from lattice_brain.storage import storage_from_env
|
|
179
173
|
from latticeai.api.network import create_network_router
|
|
180
|
-
# The aliased names below
|
|
181
|
-
#
|
|
182
|
-
# ``dict(locals())`` and reached through ``server_app.__getattr__``
|
|
183
|
-
# (tests import _agent_risk, _LOCAL_WRITE_BLOCKED_PREFIXES, …).
|
|
174
|
+
# The aliased names below form the explicit, allowlisted compatibility
|
|
175
|
+
# surface consumed by historical ``server_app`` callers.
|
|
184
176
|
from latticeai.services.tool_dispatch import ( # noqa: F401
|
|
185
177
|
LOCAL_WRITE_BLOCKED_PREFIXES as _LOCAL_WRITE_BLOCKED_PREFIXES,
|
|
186
178
|
TOOL_GOVERNANCE,
|
|
@@ -201,8 +193,8 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
201
193
|
create_mcp_install_state,
|
|
202
194
|
)
|
|
203
195
|
from latticeai.services.p_reinforce import PReinforceGardener
|
|
204
|
-
from
|
|
205
|
-
from tools import ensure_agent_root, execute_tool, knowledge_save
|
|
196
|
+
from latticeai.setup.wizard import get_recommendations, scan_environment
|
|
197
|
+
from latticeai.tools import ensure_agent_root, execute_tool, knowledge_save
|
|
206
198
|
|
|
207
199
|
try:
|
|
208
200
|
import keyring
|
|
@@ -229,7 +221,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
229
221
|
def _host_is_loopback(host: str) -> bool:
|
|
230
222
|
return _host_is_loopback_impl(host)
|
|
231
223
|
|
|
232
|
-
NETWORK_EXPOSED = _config_runtime["NETWORK_EXPOSED"]
|
|
233
224
|
ENABLE_TELEGRAM = _config_runtime["ENABLE_TELEGRAM"]
|
|
234
225
|
ENABLE_GRAPH = _config_runtime["ENABLE_GRAPH"]
|
|
235
226
|
AUTOLOAD_MODELS = _config_runtime["AUTOLOAD_MODELS"]
|
|
@@ -253,6 +244,10 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
253
244
|
SSO_CLIENT_SECRET = _security_runtime["SSO_CLIENT_SECRET"]
|
|
254
245
|
SSO_REDIRECT_URI = _security_runtime["SSO_REDIRECT_URI"]
|
|
255
246
|
SSO_PROVIDER_NAME = _security_runtime["SSO_PROVIDER_NAME"]
|
|
247
|
+
INVITE_CODE = _security_runtime["INVITE_CODE"]
|
|
248
|
+
INVITE_COOKIE_SECRET = _security_runtime["INVITE_COOKIE_SECRET"]
|
|
249
|
+
INVITE_GATE_ENABLED = _security_runtime["INVITE_GATE_ENABLED"]
|
|
250
|
+
SECURE_COOKIES = _security_runtime["SECURE_COOKIES"]
|
|
256
251
|
|
|
257
252
|
# SSO config + discovery seam is built below, after SSO_FILE is known
|
|
258
253
|
# (latticeai.runtime.sso_config_runtime).
|
|
@@ -291,26 +286,27 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
291
286
|
_session_store = _session_runtime["_session_store"]
|
|
292
287
|
create_session = _session_runtime["create_session"]
|
|
293
288
|
get_session_email = _session_runtime["get_session_email"]
|
|
294
|
-
get_session_user_id = _session_runtime["get_session_user_id"]
|
|
295
289
|
invalidate_session = _session_runtime["invalidate_session"]
|
|
296
290
|
|
|
297
291
|
# ── User Management Logic ──────────────────────────────────────────────────
|
|
298
292
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
299
293
|
DATA_DIR = CONFIG.data_dir
|
|
300
|
-
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
294
|
+
DATA_DIR.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
295
|
+
try:
|
|
296
|
+
DATA_DIR.chmod(0o700)
|
|
297
|
+
except OSError:
|
|
298
|
+
pass
|
|
301
299
|
STATIC_DIR = CONFIG.static_dir
|
|
302
300
|
|
|
303
301
|
USERS_FILE = DATA_DIR / "users.json"
|
|
304
302
|
HISTORY_FILE = DATA_DIR / "chat_history.json"
|
|
305
303
|
VPC_FILE = DATA_DIR / "vpc_config.json"
|
|
306
|
-
MCP_FILE = DATA_DIR / "mcp_installs.json"
|
|
307
304
|
AUDIT_FILE = DATA_DIR / "audit_log.json"
|
|
308
305
|
SSO_FILE = DATA_DIR / "sso_config.json"
|
|
309
306
|
|
|
310
307
|
# MCP state extracted to mcp_registry.create_mcp_install_state (server decomp)
|
|
311
308
|
_mcp_state = create_mcp_install_state(DATA_DIR)
|
|
312
309
|
load_mcp_installs = _mcp_state["load_mcp_installs"]
|
|
313
|
-
save_mcp_installs = _mcp_state["save_mcp_installs"]
|
|
314
310
|
mcp_public_item = _mcp_state["mcp_public_item"]
|
|
315
311
|
recommend_mcps = _mcp_state["recommend_mcps"]
|
|
316
312
|
install_mcp = _mcp_state["install_mcp"]
|
|
@@ -350,7 +346,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
350
346
|
embedder=EMBEDDER,
|
|
351
347
|
storage_engine=STORAGE_ENGINE,
|
|
352
348
|
)
|
|
353
|
-
BRAIN_CORE = _brain_runtime["BRAIN_CORE"]
|
|
354
349
|
KNOWLEDGE_GRAPH = _brain_runtime["KNOWLEDGE_GRAPH"]
|
|
355
350
|
# ── v4 durable conversation store: unbounded episodic memory in the same
|
|
356
351
|
# SQLite file as the graph (kg_portability backup/restore covers it for
|
|
@@ -384,7 +379,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
384
379
|
WORKSPACE_OS = _persistence_runtime["WORKSPACE_OS"]
|
|
385
380
|
WORKSPACE_SERVICE = _persistence_runtime["WORKSPACE_SERVICE"]
|
|
386
381
|
INVITATION_STORE = _persistence_runtime["INVITATION_STORE"]
|
|
387
|
-
PLUGINS_DIR = _persistence_runtime["PLUGINS_DIR"]
|
|
388
382
|
PLUGIN_REGISTRY = _persistence_runtime["PLUGIN_REGISTRY"]
|
|
389
383
|
TEMPLATE_CATALOG = _persistence_runtime["TEMPLATE_CATALOG"]
|
|
390
384
|
AGENT_REGISTRY = _persistence_runtime["AGENT_REGISTRY"]
|
|
@@ -442,7 +436,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
442
436
|
logging=logging,
|
|
443
437
|
)
|
|
444
438
|
_sso_env_defaults = _sso_runtime["_sso_env_defaults"]
|
|
445
|
-
load_sso_config = _sso_runtime["load_sso_config"]
|
|
446
439
|
get_sso_settings = _sso_runtime["get_sso_settings"]
|
|
447
440
|
public_sso_config = _sso_runtime["public_sso_config"]
|
|
448
441
|
save_sso_config = _sso_runtime["save_sso_config"]
|
|
@@ -452,7 +445,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
452
445
|
# MCP/skill request models moved to latticeai.api.mcp (v1.3.0).
|
|
453
446
|
# VPC network-profile config → latticeai.runtime.network_config_runtime.
|
|
454
447
|
_vpc_runtime = build_vpc_runtime(vpc_file=VPC_FILE, logging=logging)
|
|
455
|
-
DEFAULT_VPC_CONFIG = _vpc_runtime["DEFAULT_VPC_CONFIG"]
|
|
456
448
|
load_vpc_config = _vpc_runtime["load_vpc_config"]
|
|
457
449
|
save_vpc_config = _vpc_runtime["save_vpc_config"]
|
|
458
450
|
|
|
@@ -579,10 +571,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
579
571
|
clear_history = _history_query_runtime["clear_history"]
|
|
580
572
|
clear_conversation = _history_query_runtime["clear_conversation"]
|
|
581
573
|
|
|
582
|
-
# Chat service seam: behaviour-preserving façade for history access and
|
|
583
|
-
# Workspace-OS answer-trace recording used by the (unchanged) streaming chat path.
|
|
584
|
-
CHAT_SERVICE = ChatService(store=WORKSPACE_OS, get_history=get_history)
|
|
585
|
-
|
|
586
574
|
_access_runtime = build_access_runtime(
|
|
587
575
|
config=CONFIG,
|
|
588
576
|
require_auth=REQUIRE_AUTH,
|
|
@@ -628,6 +616,15 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
628
616
|
get_user_api_key = _user_key_runtime["get_user_api_key"]
|
|
629
617
|
set_user_api_key = _user_key_runtime["set_user_api_key"]
|
|
630
618
|
|
|
619
|
+
# Chat service owns persistence and trace behavior after its user-key
|
|
620
|
+
# dependencies are available.
|
|
621
|
+
CHAT_SERVICE = ChatService(
|
|
622
|
+
store=WORKSPACE_OS,
|
|
623
|
+
get_history=get_history,
|
|
624
|
+
save_to_history=save_to_history,
|
|
625
|
+
get_history_user=get_history_user,
|
|
626
|
+
)
|
|
627
|
+
|
|
631
628
|
# ── Sensitivity analysis — delegated to latticeai.core.audit ──────────────────
|
|
632
629
|
def classify_sensitive_message(item: Dict, index: int) -> Dict:
|
|
633
630
|
return _classify_sensitive_message(item, index)
|
|
@@ -684,8 +681,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
684
681
|
local_server_processes=LOCAL_SERVER_PROCESSES,
|
|
685
682
|
logger=logging,
|
|
686
683
|
)
|
|
687
|
-
autoload_default_model = _lifespan_runtime["autoload_default_model"]
|
|
688
|
-
unload_idle_models_loop = _lifespan_runtime["unload_idle_models_loop"]
|
|
689
684
|
_spawn = _lifespan_runtime["_spawn"]
|
|
690
685
|
lifespan = _lifespan_runtime["lifespan"]
|
|
691
686
|
|
|
@@ -700,14 +695,11 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
700
695
|
static_dir=STATIC_DIR,
|
|
701
696
|
)
|
|
702
697
|
app = _web_runtime["app"]
|
|
703
|
-
CORS_ALLOWED_ORIGINS = _web_runtime["CORS_ALLOWED_ORIGINS"]
|
|
704
698
|
ensure_agent_root()
|
|
705
699
|
|
|
706
700
|
OPEN_REGISTRATION = CONFIG.open_registration
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
configure_model_runtime_from_context(
|
|
710
|
-
configure_model_runtime=configure_model_runtime,
|
|
701
|
+
_model_runtime_service = configure_model_runtime_from_context(
|
|
702
|
+
build_model_runtime=build_model_runtime,
|
|
711
703
|
router=router,
|
|
712
704
|
APP_MODE=APP_MODE,
|
|
713
705
|
DEFAULT_HOST=DEFAULT_HOST,
|
|
@@ -737,6 +729,8 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
737
729
|
static_dir=STATIC_DIR,
|
|
738
730
|
invite_gate_enabled=INVITE_GATE_ENABLED,
|
|
739
731
|
invite_code=INVITE_CODE,
|
|
732
|
+
invite_cookie_secret=INVITE_COOKIE_SECRET,
|
|
733
|
+
secure_cookies=SECURE_COOKIES,
|
|
740
734
|
app_mode=APP_MODE,
|
|
741
735
|
model_router=router,
|
|
742
736
|
require_user=require_user,
|
|
@@ -744,6 +738,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
744
738
|
STATIC_ROUTES = _static_routes_bundle["STATIC_ROUTES"]
|
|
745
739
|
ui_file_response = _static_routes_bundle["ui_file_response"]
|
|
746
740
|
local_sysinfo = _static_routes_bundle["local_sysinfo"]
|
|
741
|
+
invite_authorized = _static_routes_bundle["invite_authorized"]
|
|
747
742
|
|
|
748
743
|
# ── Auth & Admin routers (latticeai.api) ─────────────────────────────────────
|
|
749
744
|
_foundation_router_bundle = build_auth_admin_security_router_bundle(
|
|
@@ -766,6 +761,8 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
766
761
|
open_registration=OPEN_REGISTRATION,
|
|
767
762
|
session_ttl=_SESSION_TTL,
|
|
768
763
|
require_auth=REQUIRE_AUTH,
|
|
764
|
+
secure_cookies=SECURE_COOKIES,
|
|
765
|
+
invite_authorized=invite_authorized,
|
|
769
766
|
ensure_identity=ensure_user_identity,
|
|
770
767
|
create_admin_router=create_admin_router,
|
|
771
768
|
require_admin=require_admin,
|
|
@@ -866,6 +863,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
866
863
|
include_image_missing_replies: bool = True,
|
|
867
864
|
user_email: Optional[str] = None,
|
|
868
865
|
conversation_id: Optional[str] = None,
|
|
866
|
+
workspace_id: Optional[str] = None,
|
|
869
867
|
) -> str:
|
|
870
868
|
return build_recent_chat_context(
|
|
871
869
|
get_history=get_history,
|
|
@@ -873,6 +871,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
873
871
|
include_image_missing_replies=include_image_missing_replies,
|
|
874
872
|
user_email=user_email,
|
|
875
873
|
conversation_id=conversation_id,
|
|
874
|
+
workspace_id=workspace_id,
|
|
876
875
|
)
|
|
877
876
|
|
|
878
877
|
CHAT_AGENT_RUNTIME = build_chat_agent_runtime_from_context(
|
|
@@ -889,7 +888,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
889
888
|
|
|
890
889
|
# ── Typed dependency context (latticeai.services.app_context) ────────────────
|
|
891
890
|
# One context object replaces the historical 25-30-kwarg router wiring.
|
|
892
|
-
context =
|
|
891
|
+
context = AppContext(
|
|
893
892
|
config=CONFIG,
|
|
894
893
|
data_dir=DATA_DIR,
|
|
895
894
|
static_dir=STATIC_DIR,
|
|
@@ -971,6 +970,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
971
970
|
agent_registry=AGENT_REGISTRY,
|
|
972
971
|
data_dir=DATA_DIR,
|
|
973
972
|
append_audit_event=append_audit_event,
|
|
973
|
+
memory_service=MEMORY_SERVICE,
|
|
974
974
|
tz_name=getattr(CONFIG, "timezone", None),
|
|
975
975
|
)
|
|
976
976
|
_llm_generate_sync = _platform_automation_runtime["_llm_generate_sync"]
|
|
@@ -1025,28 +1025,30 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1025
1025
|
# ── Health / status / engine-summary router (latticeai.api.health, v1.2.0) ───
|
|
1026
1026
|
# /health, /mode, /runtime_features, /engines(GET) now live in the health router.
|
|
1027
1027
|
# Heavier engine mutation endpoints remain below in server_app.
|
|
1028
|
-
|
|
1028
|
+
_model_runtime = register_model_runtime_routers(
|
|
1029
1029
|
app=app,
|
|
1030
1030
|
create_health_router=create_health_router,
|
|
1031
1031
|
create_models_router=create_models_router,
|
|
1032
1032
|
register_health_and_model_routers=register_health_and_model_routers,
|
|
1033
1033
|
model_router=router,
|
|
1034
|
-
|
|
1034
|
+
runtime_service=_model_runtime_service,
|
|
1035
|
+
runtime_features=_model_runtime_service.runtime_features,
|
|
1035
1036
|
is_public_mode=IS_PUBLIC_MODE,
|
|
1036
|
-
engine_status=engine_status,
|
|
1037
|
+
engine_status=_model_runtime_service.engine_status,
|
|
1037
1038
|
get_current_user=get_current_user,
|
|
1038
1039
|
require_auth=REQUIRE_AUTH,
|
|
1039
1040
|
app_version=APP_VERSION,
|
|
1040
1041
|
app_mode=APP_MODE,
|
|
1041
1042
|
require_user=require_user,
|
|
1043
|
+
require_admin=require_admin,
|
|
1042
1044
|
load_users=load_users,
|
|
1043
1045
|
get_user_role=get_user_role,
|
|
1044
|
-
install_engine=install_engine,
|
|
1045
|
-
verify_cloud_models=verify_cloud_models,
|
|
1046
|
+
install_engine=_model_runtime_service.install_engine,
|
|
1047
|
+
verify_cloud_models=_model_runtime_service.verify_cloud_models,
|
|
1046
1048
|
normalize_local_model_request=normalize_local_model_request,
|
|
1047
1049
|
download_hf_model=download_hf_model,
|
|
1048
|
-
prepare_and_load_model=prepare_and_load_model,
|
|
1049
|
-
prepare_and_load_model_stream=prepare_and_load_model_stream,
|
|
1050
|
+
prepare_and_load_model=_model_runtime_service.prepare_and_load_model,
|
|
1051
|
+
prepare_and_load_model_stream=_model_runtime_service.prepare_and_load_model_stream,
|
|
1050
1052
|
sse_event=sse_event,
|
|
1051
1053
|
ensure_ollama_server=ensure_ollama_server,
|
|
1052
1054
|
local_binary=local_binary,
|
|
@@ -1073,7 +1075,8 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1073
1075
|
|
|
1074
1076
|
def _allowed_workspaces_for(user):
|
|
1075
1077
|
# No-auth local mode is single-user: no scoping. With auth, scope
|
|
1076
|
-
# reads to the caller's memberships (legacy-global rows
|
|
1078
|
+
# reads to the caller's memberships (legacy-global rows require an
|
|
1079
|
+
# explicit maintenance-only compatibility opt-in).
|
|
1077
1080
|
if not REQUIRE_AUTH or not user:
|
|
1078
1081
|
return None
|
|
1079
1082
|
return PLATFORM.allowed_scopes(user)
|
|
@@ -1102,6 +1105,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1102
1105
|
install_mcp=install_mcp,
|
|
1103
1106
|
mcp_public_item=mcp_public_item,
|
|
1104
1107
|
hooks=HOOKS_REGISTRY,
|
|
1108
|
+
workspace_service=WORKSPACE_SERVICE,
|
|
1105
1109
|
chat_context=context,
|
|
1106
1110
|
search_service=SEARCH_SERVICE,
|
|
1107
1111
|
allowed_workspaces_for=_allowed_workspaces_for,
|
|
@@ -1125,10 +1129,9 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1125
1129
|
from latticeai.api.review_queue import create_review_queue_router
|
|
1126
1130
|
run_review_item = build_review_run_now_runner(PLATFORM, HTTPException)
|
|
1127
1131
|
|
|
1128
|
-
|
|
1129
|
-
app
|
|
1132
|
+
register_review_and_brain_tail_routers(
|
|
1133
|
+
app,
|
|
1130
1134
|
create_review_queue_router=create_review_queue_router,
|
|
1131
|
-
register_review_and_brain_tail_routers=register_review_and_brain_tail_routers,
|
|
1132
1135
|
review_queue=REVIEW_QUEUE,
|
|
1133
1136
|
require_user=require_user,
|
|
1134
1137
|
gate_read=PLATFORM.gate_read,
|
|
@@ -1137,6 +1140,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1137
1140
|
append_audit_event=append_audit_event,
|
|
1138
1141
|
create_browser_router=create_browser_router,
|
|
1139
1142
|
ingestion_pipeline=INGESTION_PIPELINE,
|
|
1143
|
+
workspace_service=WORKSPACE_SERVICE,
|
|
1140
1144
|
create_portability_router=create_portability_router,
|
|
1141
1145
|
kg_portability=KG_PORTABILITY,
|
|
1142
1146
|
require_admin=require_admin,
|
|
@@ -1157,9 +1161,9 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1157
1161
|
uvicorn.run(app, host=DEFAULT_HOST, port=DEFAULT_PORT, log_level="info")
|
|
1158
1162
|
|
|
1159
1163
|
# ── Constructed-namespace export (consumed by AppRuntime) ────────────────
|
|
1160
|
-
#
|
|
1161
|
-
#
|
|
1162
|
-
|
|
1164
|
+
# The five stages are typed and the compatibility surface is enumerated;
|
|
1165
|
+
# assembly locals never escape the composition root.
|
|
1166
|
+
router_bundle = build_router_bundle(app, context)
|
|
1163
1167
|
runtime_bundle = RuntimeBundle(
|
|
1164
1168
|
app=app,
|
|
1165
1169
|
CONFIG=CONFIG,
|
|
@@ -1173,8 +1177,62 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1173
1177
|
build_runtime=build_runtime,
|
|
1174
1178
|
get_shared_runtime=get_shared_runtime,
|
|
1175
1179
|
create_app=create_app,
|
|
1180
|
+
config_runtime=_config_runtime,
|
|
1181
|
+
security_runtime=_security_runtime,
|
|
1182
|
+
brain_runtime=_brain_runtime,
|
|
1183
|
+
model_runtime=_model_runtime,
|
|
1184
|
+
router_bundle=router_bundle,
|
|
1185
|
+
)
|
|
1186
|
+
return build_runtime_namespace(
|
|
1187
|
+
runtime_bundle=runtime_bundle,
|
|
1188
|
+
legacy_exports={
|
|
1189
|
+
"ENGINE_MODEL_CATALOG": ENGINE_MODEL_CATALOG,
|
|
1190
|
+
"TOOL_GOVERNANCE": TOOL_GOVERNANCE,
|
|
1191
|
+
"enforce_rate_limit": enforce_rate_limit,
|
|
1192
|
+
"filter_lower_family_versions": filter_lower_family_versions,
|
|
1193
|
+
"hash_password": hash_password,
|
|
1194
|
+
"normalize_local_model_request": normalize_local_model_request,
|
|
1195
|
+
"verify_password": verify_password,
|
|
1196
|
+
"_LOCAL_WRITE_BLOCKED_PREFIXES": _LOCAL_WRITE_BLOCKED_PREFIXES,
|
|
1197
|
+
"_RATE_LIMIT_ENABLED": _RATE_LIMIT_ENABLED,
|
|
1198
|
+
"_SESSION_TTL": _SESSION_TTL,
|
|
1199
|
+
"_TOOL_CATALOG_BRIEF": _TOOL_CATALOG_BRIEF,
|
|
1200
|
+
"_TOOL_GOVERNANCE_DEFAULT": _TOOL_GOVERNANCE_DEFAULT,
|
|
1201
|
+
"_agent_risk": _agent_risk,
|
|
1202
|
+
"_allowed_workspaces_for": _allowed_workspaces_for,
|
|
1203
|
+
"_build_admin_audit_report": _build_admin_audit_report,
|
|
1204
|
+
"_build_sensitivity_report": _build_sensitivity_report,
|
|
1205
|
+
"_bytes_match_extension": _bytes_match_extension,
|
|
1206
|
+
"_check_ip_rate_limit": _check_ip_rate_limit,
|
|
1207
|
+
"_check_rate_limit": _check_rate_limit,
|
|
1208
|
+
"_check_tool_role": _check_tool_role,
|
|
1209
|
+
"_classify_sensitive_message": _classify_sensitive_message,
|
|
1210
|
+
"_client_ip": _client_ip,
|
|
1211
|
+
"_create_security_router": _create_security_router,
|
|
1212
|
+
"_embedding_info": _embedding_info,
|
|
1213
|
+
"_fetch_skills_marketplace": _fetch_skills_marketplace,
|
|
1214
|
+
"_get_audit_log": _get_audit_log,
|
|
1215
|
+
"_get_sso_discovery": _get_sso_discovery,
|
|
1216
|
+
"_graph_stats_safe": _graph_stats_safe,
|
|
1217
|
+
"_host_is_loopback": _host_is_loopback,
|
|
1218
|
+
"_list_compat_profiles": _list_compat_profiles,
|
|
1219
|
+
"_llm_generate_sync": _llm_generate_sync,
|
|
1220
|
+
"_product_hardening_status": _product_hardening_status,
|
|
1221
|
+
"_recent_chat_context": _recent_chat_context,
|
|
1222
|
+
"_redact_secret_text": _redact_secret_text,
|
|
1223
|
+
"_require_graph": _require_graph,
|
|
1224
|
+
"_scoped_hybrid_search": _scoped_hybrid_search,
|
|
1225
|
+
"_security_audit_events_safe": _security_audit_events_safe,
|
|
1226
|
+
"_security_list_uploaded_files": _security_list_uploaded_files,
|
|
1227
|
+
"_spawn": _spawn,
|
|
1228
|
+
"_tool_response": _tool_response,
|
|
1229
|
+
"_user_id_for_email": _user_id_for_email,
|
|
1230
|
+
"_workspace_graph": _workspace_graph,
|
|
1231
|
+
"_workspace_models_payload": _workspace_models_payload,
|
|
1232
|
+
"_workspace_scope_from_request": _workspace_scope_from_request,
|
|
1233
|
+
"_workspace_settings_payload": _workspace_settings_payload,
|
|
1234
|
+
},
|
|
1176
1235
|
)
|
|
1177
|
-
return build_runtime_namespace(locals(), runtime_bundle=runtime_bundle)
|
|
1178
1236
|
|
|
1179
1237
|
|
|
1180
1238
|
@dataclass(frozen=True)
|
package/latticeai/core/agent.py
CHANGED
|
@@ -30,7 +30,8 @@ from typing import Any, Awaitable, Callable, Dict, FrozenSet, List, Optional
|
|
|
30
30
|
|
|
31
31
|
from lattice_brain.runtime.hooks import dispatch_tool
|
|
32
32
|
from lattice_brain.runtime.contracts import runtime_boundary_contract, single_agent_contract
|
|
33
|
-
from
|
|
33
|
+
from latticeai.core.tool_registry import SCOPED_KNOWLEDGE_TOOLS
|
|
34
|
+
from latticeai.tools import ToolError
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
class AgentState(str, Enum):
|
|
@@ -68,9 +69,16 @@ class AgentRunContext:
|
|
|
68
69
|
self.approved_by_human: bool = False
|
|
69
70
|
|
|
70
71
|
|
|
72
|
+
_THINK_BLOCK_RE = re.compile(
|
|
73
|
+
r"<(think|thinking|reasoning)>.*?</\1>", flags=re.DOTALL | re.IGNORECASE
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
71
77
|
def extract_action(raw: str) -> Dict:
|
|
72
78
|
"""Parse one JSON action object out of an LLM response (tolerant of fences/prose)."""
|
|
73
|
-
|
|
79
|
+
# Small local models often prepend <think>...</think> reasoning that can
|
|
80
|
+
# itself contain braces — drop it before locating the action object.
|
|
81
|
+
text = _THINK_BLOCK_RE.sub("", raw).strip()
|
|
74
82
|
fenced = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, flags=re.DOTALL)
|
|
75
83
|
if fenced:
|
|
76
84
|
text = fenced.group(1).strip()
|
|
@@ -82,8 +90,14 @@ def extract_action(raw: str) -> Dict:
|
|
|
82
90
|
|
|
83
91
|
try:
|
|
84
92
|
action = json.loads(text)
|
|
85
|
-
except json.JSONDecodeError
|
|
86
|
-
|
|
93
|
+
except json.JSONDecodeError:
|
|
94
|
+
# Second chance for the most common small-model JSON slips: trailing
|
|
95
|
+
# commas before a closing brace/bracket.
|
|
96
|
+
repaired = re.sub(r",\s*([}\]])", r"\1", text)
|
|
97
|
+
try:
|
|
98
|
+
action = json.loads(repaired)
|
|
99
|
+
except json.JSONDecodeError as exc:
|
|
100
|
+
raise ValueError(f"Agent did not return valid JSON: {exc}") from exc
|
|
87
101
|
|
|
88
102
|
if not isinstance(action, dict) or "action" not in action:
|
|
89
103
|
raise ValueError("Agent JSON must include an action field.")
|
|
@@ -155,7 +169,7 @@ class SingleAgentRuntime:
|
|
|
155
169
|
entrypoint="latticeai.core.agent.SingleAgentRuntime",
|
|
156
170
|
surface="/agent",
|
|
157
171
|
owns="single-agent PLAN / EXECUTE / VERIFY state machine over injected ports",
|
|
158
|
-
compatibility_aliases=[
|
|
172
|
+
compatibility_aliases=[],
|
|
159
173
|
)
|
|
160
174
|
|
|
161
175
|
def config(self) -> Dict[str, Any]:
|
|
@@ -247,6 +261,7 @@ class SingleAgentRuntime:
|
|
|
247
261
|
d = self.deps
|
|
248
262
|
exec_count = sum(1 for s in ctx.transcript if s.get("state") == AgentState.EXECUTING.value)
|
|
249
263
|
budget = max(1, max_steps - exec_count)
|
|
264
|
+
parse_failures = 0
|
|
250
265
|
|
|
251
266
|
for _ in range(budget):
|
|
252
267
|
corrections_hint = (
|
|
@@ -254,12 +269,20 @@ class SingleAgentRuntime:
|
|
|
254
269
|
+ "\n".join(f"- {c}" for c in ctx.corrections)
|
|
255
270
|
) if ctx.corrections else ""
|
|
256
271
|
|
|
272
|
+
request_workspace = getattr(req, "workspace_id", None)
|
|
273
|
+
recent_kwargs = {
|
|
274
|
+
"conversation_id": req.conversation_id,
|
|
275
|
+
"user_email": current_user or None,
|
|
276
|
+
}
|
|
277
|
+
if request_workspace is not None:
|
|
278
|
+
recent_kwargs["workspace_id"] = request_workspace
|
|
279
|
+
recent_conversation = d.recent_chat_context(**recent_kwargs) or "(none)"
|
|
257
280
|
context = (
|
|
258
281
|
f"{d.executor_prompt}\n\n"
|
|
259
282
|
f"[LANGUAGE HINT: {lang_hint}]\n"
|
|
260
283
|
f"Workspace root: {d.agent_root}\n\n"
|
|
261
284
|
f"PLAN:\n{json.dumps(ctx.plan, ensure_ascii=False)}\n\n"
|
|
262
|
-
f"Recent conversation:\n{
|
|
285
|
+
f"Recent conversation:\n{recent_conversation}\n\n"
|
|
263
286
|
f"User request: {req.message}{corrections_hint}\n\n"
|
|
264
287
|
f"Execution transcript:\n{json.dumps(ctx.transcript, ensure_ascii=False, indent=2)}"
|
|
265
288
|
)
|
|
@@ -271,16 +294,36 @@ class SingleAgentRuntime:
|
|
|
271
294
|
try:
|
|
272
295
|
action = extract_action(str(raw))
|
|
273
296
|
except ValueError as exc:
|
|
297
|
+
parse_failures += 1
|
|
274
298
|
ctx.transcript.append({
|
|
275
299
|
"state": AgentState.EXECUTING.value, "action": "parse_error",
|
|
276
300
|
"raw": str(raw)[:400], "error": str(exc),
|
|
277
301
|
})
|
|
278
|
-
|
|
302
|
+
if parse_failures >= 3:
|
|
303
|
+
break
|
|
304
|
+
# Weak models often need one concrete reminder of the wire
|
|
305
|
+
# format; feed it through the corrections channel and retry
|
|
306
|
+
# instead of aborting the whole run on the first slip.
|
|
307
|
+
hint = (
|
|
308
|
+
'Your last reply was not a single JSON action object. Reply with '
|
|
309
|
+
'EXACTLY one JSON object like {"thoughts": "...", "action": '
|
|
310
|
+
'"tool_name", "args": {...}} and nothing else.'
|
|
311
|
+
)
|
|
312
|
+
if hint not in ctx.corrections:
|
|
313
|
+
ctx.corrections.append(hint)
|
|
314
|
+
continue
|
|
279
315
|
|
|
280
316
|
name = action.get("action")
|
|
281
317
|
thoughts = str(action.get("thoughts") or "")[:600]
|
|
282
318
|
args = action.get("args") or {}
|
|
283
319
|
|
|
320
|
+
if name in SCOPED_KNOWLEDGE_TOOLS:
|
|
321
|
+
# Scope is server-owned, never model-owned. Overwrite any
|
|
322
|
+
# claimed values before policy evaluation, audit, and dispatch.
|
|
323
|
+
args = dict(args)
|
|
324
|
+
args["workspace_id"] = request_workspace or "personal"
|
|
325
|
+
args["user_email"] = current_user or "local"
|
|
326
|
+
|
|
284
327
|
if name == "final":
|
|
285
328
|
ctx.final_message = action.get("message", "작업을 완료했습니다.")
|
|
286
329
|
ctx.transcript.append({
|
|
@@ -530,9 +573,3 @@ class SingleAgentRuntime:
|
|
|
530
573
|
ctx.state = AgentState.FAILED
|
|
531
574
|
|
|
532
575
|
ctx.state_history.append(ctx.state.value)
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
# Backward compatibility: external callers historically imported
|
|
536
|
-
# ``latticeai.core.agent.AgentRuntime`` for the single-agent state machine.
|
|
537
|
-
# The product/runtime facade lives at ``lattice_brain.runtime.agent_runtime``.
|
|
538
|
-
AgentRuntime = SingleAgentRuntime
|
|
@@ -43,6 +43,11 @@ You think and act like a senior software engineer:
|
|
|
43
43
|
Respond with exactly ONE JSON object per step:
|
|
44
44
|
{"thoughts": "what you learned / why this next action", "action": "tool_name", "args": {...}}
|
|
45
45
|
|
|
46
|
+
When writing a file (write_file), args.content must be the COMPLETE raw file
|
|
47
|
+
content: no Markdown fences, no commentary, valid for the file's extension
|
|
48
|
+
(an .html file starts with <!DOCTYPE html> and ends with </html>; a .json
|
|
49
|
+
file must parse as strict JSON).
|
|
50
|
+
|
|
46
51
|
When the task is fully done AND a tool result in this run confirms it:
|
|
47
52
|
{"thoughts": "verified", "action": "final", "message": "한국어로 무엇을 했고 어디서 검증했는지 요약"}
|
|
48
53
|
|
|
@@ -18,7 +18,6 @@ from __future__ import annotations
|
|
|
18
18
|
import json
|
|
19
19
|
import os
|
|
20
20
|
import tempfile
|
|
21
|
-
from datetime import datetime
|
|
22
21
|
from pathlib import Path
|
|
23
22
|
from typing import Any, Dict, List, Optional
|
|
24
23
|
|
|
@@ -28,14 +27,11 @@ from lattice_brain.runtime.multi_agent import (
|
|
|
28
27
|
MULTI_AGENT_VERSION,
|
|
29
28
|
ROLE_AGENT_IDS,
|
|
30
29
|
)
|
|
30
|
+
from .timeutil import now_iso as _now
|
|
31
31
|
|
|
32
32
|
AGENT_TYPES = ("planner", "researcher", "executor", "reviewer", "release", "custom")
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def _now() -> str:
|
|
36
|
-
return datetime.now().isoformat(timespec="seconds")
|
|
37
|
-
|
|
38
|
-
|
|
39
35
|
# Capabilities + descriptions for the built-in role agents. Kept here as the
|
|
40
36
|
# registry's metadata projection of the roles defined in multi_agent.py.
|
|
41
37
|
ROLE_META: Dict[str, Dict[str, Any]] = {
|
package/latticeai/core/config.py
CHANGED
|
@@ -99,6 +99,7 @@ class Config:
|
|
|
99
99
|
rate_limit_enabled: bool
|
|
100
100
|
open_registration: bool
|
|
101
101
|
invite_code: str
|
|
102
|
+
invite_cookie_secret: str
|
|
102
103
|
invite_gate_enabled: bool
|
|
103
104
|
admin_emails: List[str]
|
|
104
105
|
trusted_proxies: List[str]
|
|
@@ -160,6 +161,7 @@ class Config:
|
|
|
160
161
|
host = _value(env, "LATTICEAI_HOST", "127.0.0.1")
|
|
161
162
|
port = _port(env, "LATTICEAI_PORT", 4825)
|
|
162
163
|
network_exposed = not host_is_loopback(host)
|
|
164
|
+
externally_reachable = is_public or network_exposed
|
|
163
165
|
|
|
164
166
|
cors_extra = [item.strip() for item in _value(env, "LATTICEAI_CORS_ALLOWED_ORIGINS", "").split(",") if item.strip()]
|
|
165
167
|
admin_emails = [item.strip().lower() for item in _value(env, "LATTICEAI_ADMIN_EMAILS", "").split(",") if item.strip()]
|
|
@@ -189,13 +191,31 @@ class Config:
|
|
|
189
191
|
autoload_models=_bool(env, "LATTICEAI_AUTOLOAD_MODELS", default=is_public),
|
|
190
192
|
model_idle_unload_seconds=_int(env, "LATTICEAI_MODEL_IDLE_UNLOAD_SECONDS", 0),
|
|
191
193
|
allow_local_models=_bool(env, "LATTICEAI_ALLOW_LOCAL_MODELS", default=not is_public),
|
|
192
|
-
|
|
194
|
+
# Authentication is optional only for the local-first loopback
|
|
195
|
+
# profile. An explicit ``false`` must never turn a public/LAN
|
|
196
|
+
# binding into an unauthenticated service.
|
|
197
|
+
require_auth=(
|
|
198
|
+
True
|
|
199
|
+
if externally_reachable
|
|
200
|
+
else _bool(env, "LATTICEAI_REQUIRE_AUTH", default=False)
|
|
201
|
+
),
|
|
193
202
|
allow_plaintext_api_keys=_bool(env, "LATTICEAI_ALLOW_PLAINTEXT_API_KEYS", default=False),
|
|
194
203
|
cors_allow_network=_bool(env, "LATTICEAI_CORS_ALLOW_NETWORK", default=False),
|
|
195
204
|
cors_extra_origins=cors_extra,
|
|
196
205
|
rate_limit_enabled=_str(env, "LATTICEAI_RATE_LIMIT", "1") != "0",
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
# Public/LAN startup is closed-registration even if a stale or
|
|
207
|
+
# unsafe environment file attempts to opt back in.
|
|
208
|
+
open_registration=(
|
|
209
|
+
False
|
|
210
|
+
if externally_reachable
|
|
211
|
+
else _bool(env, "LATTICEAI_OPEN_REGISTRATION", default=True)
|
|
212
|
+
),
|
|
213
|
+
# There is deliberately no repository-wide/default invitation
|
|
214
|
+
# code. When the gate is enabled, the security runtime persists a
|
|
215
|
+
# cryptographically random code if the operator did not provide
|
|
216
|
+
# one explicitly.
|
|
217
|
+
invite_code=_value(env, "LATTICEAI_INVITE_CODE", ""),
|
|
218
|
+
invite_cookie_secret=_value(env, "LATTICEAI_INVITE_COOKIE_SECRET", ""),
|
|
199
219
|
invite_gate_enabled=_bool(env, "LATTICEAI_INVITE_GATE_ENABLED", default=False),
|
|
200
220
|
admin_emails=admin_emails,
|
|
201
221
|
trusted_proxies=trusted_proxies,
|