ltcai 10.0.0 → 10.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.
Files changed (211) hide show
  1. package/README.md +48 -32
  2. package/docs/CHANGELOG.md +156 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/HYBRID_CLOUD_KG_STREAMING.md +101 -0
  6. package/docs/ONBOARDING.md +1 -1
  7. package/docs/OPERATIONS.md +1 -1
  8. package/docs/TRUST_MODEL.md +1 -1
  9. package/docs/WHY_LATTICE.md +1 -1
  10. package/docs/kg-schema.md +1 -1
  11. package/lattice_brain/__init__.py +1 -1
  12. package/lattice_brain/archive.py +5 -4
  13. package/lattice_brain/conversations.py +14 -3
  14. package/lattice_brain/embeddings.py +12 -2
  15. package/lattice_brain/graph/_kg_common.py +5 -5
  16. package/lattice_brain/graph/_kg_fsutil.py +4 -3
  17. package/lattice_brain/graph/discovery.py +4 -3
  18. package/lattice_brain/graph/discovery_index.py +0 -1
  19. package/lattice_brain/graph/documents.py +3 -2
  20. package/lattice_brain/graph/fusion.py +4 -0
  21. package/lattice_brain/graph/ingest.py +12 -2
  22. package/lattice_brain/graph/projection.py +3 -2
  23. package/lattice_brain/graph/provenance.py +5 -3
  24. package/lattice_brain/graph/rerank.py +4 -1
  25. package/lattice_brain/graph/retrieval.py +0 -1
  26. package/lattice_brain/graph/retrieval_docgen.py +0 -1
  27. package/lattice_brain/graph/retrieval_reads.py +0 -1
  28. package/lattice_brain/graph/retrieval_vector.py +0 -1
  29. package/lattice_brain/graph/schema.py +4 -3
  30. package/lattice_brain/graph/store.py +18 -4
  31. package/lattice_brain/graph/write_master.py +46 -1
  32. package/lattice_brain/ingestion.py +4 -1
  33. package/lattice_brain/portability.py +5 -2
  34. package/lattice_brain/quality.py +12 -5
  35. package/lattice_brain/quiet.py +43 -0
  36. package/lattice_brain/runtime/agent_runtime.py +12 -8
  37. package/lattice_brain/runtime/hooks.py +2 -1
  38. package/lattice_brain/runtime/multi_agent.py +2 -3
  39. package/lattice_brain/sensitivity.py +94 -0
  40. package/lattice_brain/storage/base.py +30 -2
  41. package/lattice_brain/storage/migration.py +3 -2
  42. package/lattice_brain/storage/postgres.py +2 -2
  43. package/lattice_brain/storage/sqlite.py +6 -4
  44. package/lattice_brain/workflow.py +4 -2
  45. package/latticeai/__init__.py +1 -1
  46. package/latticeai/api/admin.py +1 -1
  47. package/latticeai/api/agents.py +4 -2
  48. package/latticeai/api/auth.py +5 -1
  49. package/latticeai/api/automation_intelligence.py +2 -1
  50. package/latticeai/api/browser.py +3 -2
  51. package/latticeai/api/chat.py +28 -17
  52. package/latticeai/api/chat_agent_http.py +22 -8
  53. package/latticeai/api/chat_contracts.py +4 -0
  54. package/latticeai/api/chat_documents.py +6 -2
  55. package/latticeai/api/chat_hybrid.py +82 -0
  56. package/latticeai/api/computer_use.py +8 -3
  57. package/latticeai/api/knowledge_graph.py +1 -1
  58. package/latticeai/api/mcp.py +4 -4
  59. package/latticeai/api/models.py +5 -2
  60. package/latticeai/api/network_boundary.py +220 -0
  61. package/latticeai/api/permissions.py +0 -1
  62. package/latticeai/api/realtime.py +1 -1
  63. package/latticeai/api/security_dashboard.py +1 -1
  64. package/latticeai/api/setup.py +16 -3
  65. package/latticeai/api/static_routes.py +2 -1
  66. package/latticeai/api/tools.py +12 -8
  67. package/latticeai/api/voice_capture.py +3 -1
  68. package/latticeai/api/workflow_designer.py +1 -1
  69. package/latticeai/api/workspace.py +1 -2
  70. package/latticeai/app_factory.py +131 -78
  71. package/latticeai/cli/entrypoint.py +6 -4
  72. package/latticeai/core/agent.py +55 -495
  73. package/latticeai/core/agent_eval.py +2 -2
  74. package/latticeai/core/agent_helpers.py +493 -0
  75. package/latticeai/core/agent_prompts.py +0 -1
  76. package/latticeai/core/agent_registry.py +3 -1
  77. package/latticeai/core/agent_state.py +41 -0
  78. package/latticeai/core/audit.py +1 -1
  79. package/latticeai/core/builtin_hooks.py +2 -1
  80. package/latticeai/core/config.py +0 -1
  81. package/latticeai/core/embedding_providers.py +12 -1
  82. package/latticeai/core/file_generation.py +3 -0
  83. package/latticeai/core/invitations.py +4 -1
  84. package/latticeai/core/io_utils.py +3 -1
  85. package/latticeai/core/legacy_compatibility.py +1 -2
  86. package/latticeai/core/local_embeddings.py +12 -1
  87. package/latticeai/core/marketplace.py +1 -2
  88. package/latticeai/core/mcp_registry.py +0 -1
  89. package/latticeai/core/model_compat.py +1 -1
  90. package/latticeai/core/model_resolution.py +1 -1
  91. package/latticeai/core/network_boundary.py +168 -0
  92. package/latticeai/core/oidc.py +3 -0
  93. package/latticeai/core/permission_mode.py +6 -6
  94. package/latticeai/core/plugins.py +3 -2
  95. package/latticeai/core/policy.py +0 -1
  96. package/latticeai/core/quiet.py +84 -0
  97. package/latticeai/core/realtime.py +3 -2
  98. package/latticeai/core/run_store.py +4 -2
  99. package/latticeai/core/security.py +4 -0
  100. package/latticeai/core/users.py +5 -3
  101. package/latticeai/core/workspace_graph_trace.py +3 -0
  102. package/latticeai/core/workspace_os.py +65 -273
  103. package/latticeai/core/workspace_os_constants.py +126 -0
  104. package/latticeai/core/workspace_os_state.py +180 -0
  105. package/latticeai/core/workspace_os_utils.py +3 -1
  106. package/latticeai/core/workspace_permissions.py +1 -0
  107. package/latticeai/core/workspace_snapshots.py +3 -1
  108. package/latticeai/core/workspace_timeline.py +3 -1
  109. package/latticeai/integrations/telegram_bot.py +25 -16
  110. package/latticeai/models/router.py +6 -3
  111. package/latticeai/runtime/access_runtime.py +3 -1
  112. package/latticeai/runtime/audit_runtime.py +3 -2
  113. package/latticeai/runtime/chat_wiring.py +4 -1
  114. package/latticeai/runtime/history_runtime.py +1 -1
  115. package/latticeai/runtime/lifespan_runtime.py +3 -1
  116. package/latticeai/runtime/network_boundary_wiring.py +124 -0
  117. package/latticeai/runtime/persistence_runtime.py +3 -1
  118. package/latticeai/runtime/router_registration.py +11 -1
  119. package/latticeai/services/architecture_readiness.py +1 -2
  120. package/latticeai/services/change_proposals.py +2 -1
  121. package/latticeai/services/cloud_egress_audit.py +85 -0
  122. package/latticeai/services/cloud_extraction.py +129 -0
  123. package/latticeai/services/cloud_streaming.py +266 -0
  124. package/latticeai/services/cloud_token_guard.py +84 -0
  125. package/latticeai/services/command_center.py +2 -1
  126. package/latticeai/services/folder_watch.py +3 -0
  127. package/latticeai/services/funnel_metrics.py +3 -1
  128. package/latticeai/services/hybrid_chat.py +265 -0
  129. package/latticeai/services/hybrid_context.py +228 -0
  130. package/latticeai/services/hybrid_policy.py +178 -0
  131. package/latticeai/services/memory_service.py +1 -1
  132. package/latticeai/services/model_catalog.py +10 -1
  133. package/latticeai/services/model_engines.py +35 -14
  134. package/latticeai/services/model_loading.py +3 -2
  135. package/latticeai/services/model_runtime.py +68 -17
  136. package/latticeai/services/multimodal_streaming.py +123 -0
  137. package/latticeai/services/network_boundary_service.py +154 -0
  138. package/latticeai/services/openai_compatible_adapter.py +100 -0
  139. package/latticeai/services/p_reinforce.py +4 -0
  140. package/latticeai/services/platform_runtime.py +9 -4
  141. package/latticeai/services/process_audit.py +0 -1
  142. package/latticeai/services/product_readiness.py +1 -1
  143. package/latticeai/services/run_executor.py +3 -2
  144. package/latticeai/services/search_service.py +1 -4
  145. package/latticeai/services/setup_detection.py +2 -1
  146. package/latticeai/services/tool_dispatch.py +7 -2
  147. package/latticeai/services/upload_service.py +2 -1
  148. package/latticeai/setup/auto_setup.py +10 -7
  149. package/latticeai/setup/wizard.py +15 -11
  150. package/latticeai/tools/__init__.py +3 -3
  151. package/latticeai/tools/commands.py +7 -7
  152. package/latticeai/tools/computer.py +3 -2
  153. package/latticeai/tools/documents.py +10 -9
  154. package/latticeai/tools/filesystem.py +7 -4
  155. package/latticeai/tools/knowledge.py +2 -0
  156. package/latticeai/tools/local_files.py +1 -1
  157. package/latticeai/tools/network.py +2 -1
  158. package/package.json +5 -3
  159. package/scripts/bench_agent_smoke.py +0 -1
  160. package/scripts/bench_models.py +0 -1
  161. package/scripts/brain_quality_eval.py +7 -2
  162. package/scripts/bump_version.py +2 -1
  163. package/scripts/check_current_release_docs.mjs +1 -1
  164. package/scripts/migrate_brain_storage.py +5 -1
  165. package/scripts/profile_kg.py +2 -7
  166. package/scripts/verify_hf_model_registry.py +2 -2
  167. package/src-tauri/Cargo.lock +1 -1
  168. package/src-tauri/Cargo.toml +1 -1
  169. package/src-tauri/tauri.conf.json +1 -1
  170. package/static/app/asset-manifest.json +37 -37
  171. package/static/app/assets/{Act-BtCREeN1.js → Act-CbdGD-2i.js} +1 -1
  172. package/static/app/assets/{AdminConsole-TPeeN18T.js → AdminConsole-LgCkXpnf.js} +1 -1
  173. package/static/app/assets/{Brain-BKs6JAp0.js → Brain-CoPGJI1L.js} +1 -1
  174. package/static/app/assets/{BrainHome-BPGOvSd6.js → BrainHome-gVnaxSwp.js} +1 -1
  175. package/static/app/assets/{BrainSignals-CtzQZ15J.js → BrainSignals-ChxAYHtj.js} +1 -1
  176. package/static/app/assets/{Capture-1_NaHWqB.js → Capture-DBtgkHZg.js} +1 -1
  177. package/static/app/assets/{CommandPalette-pqvQOXe4.js → CommandPalette-Ovtv5I0Y.js} +1 -1
  178. package/static/app/assets/{Library-DhvoPvC7.js → Library-CBia2Fvm.js} +1 -1
  179. package/static/app/assets/{LivingBrain-DlQ20Q75.js → LivingBrain-CNz-6NSd.js} +1 -1
  180. package/static/app/assets/{ProductFlow-BZvGDRi_.js → ProductFlow-ePX-Y73J.js} +1 -1
  181. package/static/app/assets/{ReviewCard-BWgI0D2s.js → ReviewCard-C4mpvkwH.js} +1 -1
  182. package/static/app/assets/System-BvWNK1zc.js +1 -0
  183. package/static/app/assets/{activity-Dlfk8YC7.js → activity-CiauPV_3.js} +1 -1
  184. package/static/app/assets/{bot-CDvUB76P.js → bot-Dwct-Q1x.js} +1 -1
  185. package/static/app/assets/{brain-xczrohrt.js → brain-BKDW1F0T.js} +1 -1
  186. package/static/app/assets/{button-SOdH3Oyf.js → button-CCB9Kuw7.js} +1 -1
  187. package/static/app/assets/{circle-pause-CG1ythH4.js → circle-pause-DNa8WHC5.js} +1 -1
  188. package/static/app/assets/{circle-play-HXwvjS6W.js → circle-play-8jmxn5mf.js} +1 -1
  189. package/static/app/assets/{cpu-B0d-rGyk.js → cpu-mxvF3V1I.js} +1 -1
  190. package/static/app/assets/{download-BGIkTQL6.js → download--SmCcx0p.js} +1 -1
  191. package/static/app/assets/{folder-open-Dst_Z0_K.js → folder-open-D4Wy5roo.js} +1 -1
  192. package/static/app/assets/{hard-drive-D53MsWkV.js → hard-drive-BwQcSPlS.js} +1 -1
  193. package/static/app/assets/index-CQWdDU3z.css +2 -0
  194. package/static/app/assets/{index-C_IrlQMV.js → index-DDV2YZwM.js} +3 -3
  195. package/static/app/assets/{input-C5m0riF6.js → input-DFhSjmLS.js} +1 -1
  196. package/static/app/assets/{network-C5a-E5iS.js → network-Bts7VO94.js} +1 -1
  197. package/static/app/assets/{primitives-vNXYf58F.js → primitives-BuSMEJY8.js} +1 -1
  198. package/static/app/assets/search-DZzxhWaQ.js +1 -0
  199. package/static/app/assets/{shield-alert-Cc-WVXqN.js → shield-alert-BfTO6X_G.js} +1 -1
  200. package/static/app/assets/{textarea-BkZ0EqVO.js → textarea-BjctW1oh.js} +1 -1
  201. package/static/app/assets/{useFocusTrap-CTtKbAOU.js → useFocusTrap-CE43-LrA.js} +1 -1
  202. package/static/app/assets/{useQuery-Dx1fi4Wu.js → useQuery-Bv3ejLL5.js} +1 -1
  203. package/static/app/assets/{users-BFpQXtEF.js → users-CsNqLZAj.js} +1 -1
  204. package/static/app/assets/{utils-BA_lmW3J.js → utils-KFFdVG_t.js} +2 -2
  205. package/static/app/assets/workspace-wdCvdyPF.js +1 -0
  206. package/static/app/index.html +4 -4
  207. package/static/sw.js +1 -1
  208. package/static/app/assets/System-CSMdYLMy.js +0 -1
  209. package/static/app/assets/index-FxDusbr0.css +0 -2
  210. package/static/app/assets/search-DhbSgW6m.js +0 -1
  211. package/static/app/assets/workspace-DBPB0jkX.js +0 -1
@@ -0,0 +1,180 @@
1
+ """Workspace OS state: the shape of a new file, and upgrades to old ones.
2
+
3
+ Split out of ``workspace_os.py`` in 10.2.0. Neither concern belongs to the
4
+ store: ``default_state`` describes a brand-new workspace file, ``new_workspace_record``
5
+ builds one workspace entry, and ``migrate_workspaces`` upgrades a file written
6
+ by an older version. All three were already free of instance state — two were
7
+ ``@staticmethod`` in all but name — so they move without behaviour change.
8
+
9
+ ``WorkspaceOSStore`` keeps thin delegating methods, so its public surface and
10
+ every existing call site are unchanged.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ from .timeutil import now_iso as _now
18
+ from .workspace_os_constants import (
19
+ DEFAULT_AGENTS,
20
+ DEFAULT_WORKSPACE_ID,
21
+ ONBOARDING_STEPS,
22
+ ROLE_PERMISSIONS,
23
+ WORKSPACE_AREAS,
24
+ WORKSPACE_OS_VERSION,
25
+ WORKSPACE_TYPES,
26
+ )
27
+
28
+
29
+ def new_workspace_record(
30
+ *,
31
+ workspace_id: str,
32
+ name: str,
33
+ workspace_type: str,
34
+ owner_user_id: Optional[str],
35
+ settings: Optional[Dict[str, Any]] = None,
36
+ members: Optional[List[Dict[str, Any]]] = None,
37
+ ) -> Dict[str, Any]:
38
+ if workspace_type not in WORKSPACE_TYPES:
39
+ raise ValueError(f"unknown workspace type: {workspace_type}")
40
+ now = _now()
41
+ member_list = list(members or [])
42
+ if owner_user_id and not any(m.get("user_id") == owner_user_id for m in member_list):
43
+ member_list.insert(0, {"user_id": owner_user_id, "role": "owner", "added_at": now})
44
+ return {
45
+ "workspace_id": workspace_id,
46
+ "id": workspace_id,
47
+ "name": name,
48
+ "type": workspace_type,
49
+ "owner_user_id": owner_user_id,
50
+ "members": member_list,
51
+ "roles": {role: sorted(perms) for role, perms in ROLE_PERMISSIONS.items()},
52
+ "status": "active",
53
+ "areas": list(WORKSPACE_AREAS),
54
+ "settings": settings or {},
55
+ "created_at": now,
56
+ "updated_at": now,
57
+ }
58
+
59
+
60
+ def migrate_workspaces(state: Dict[str, Any]) -> Dict[str, Any]:
61
+ """Non-destructive upgrade of legacy workspace entries to the v1.1 model.
62
+
63
+ Existing 1.0.x state files stored minimal ``{id,name,type,areas}`` dicts.
64
+ This backfills membership/role/timestamp fields without dropping data and
65
+ guarantees the default Personal workspace always exists.
66
+ """
67
+ workspaces = state.get("workspaces")
68
+ if not isinstance(workspaces, dict):
69
+ workspaces = {}
70
+ migrated: Dict[str, Any] = {}
71
+ for ws_id, ws in workspaces.items():
72
+ if not isinstance(ws, dict):
73
+ continue
74
+ ws_type = ws.get("type") if ws.get("type") in WORKSPACE_TYPES else "organization"
75
+ if ws_id == DEFAULT_WORKSPACE_ID:
76
+ ws_type = "personal"
77
+ base = new_workspace_record(
78
+ workspace_id=ws_id,
79
+ name=ws.get("name") or ws_id,
80
+ workspace_type=ws_type,
81
+ owner_user_id=ws.get("owner_user_id"),
82
+ settings=ws.get("settings") or {},
83
+ members=ws.get("members") if isinstance(ws.get("members"), list) else None,
84
+ )
85
+ # Preserve any pre-existing timestamps / status from the loaded record.
86
+ base["created_at"] = ws.get("created_at") or base["created_at"]
87
+ base["updated_at"] = ws.get("updated_at") or base["updated_at"]
88
+ base["status"] = ws.get("status") or base["status"]
89
+ migrated[ws_id] = base
90
+ if DEFAULT_WORKSPACE_ID not in migrated:
91
+ migrated[DEFAULT_WORKSPACE_ID] = new_workspace_record(
92
+ workspace_id=DEFAULT_WORKSPACE_ID,
93
+ name="Personal Workspace",
94
+ workspace_type="personal",
95
+ owner_user_id=None,
96
+ )
97
+ state["workspaces"] = migrated
98
+ active = state.get("active_workspace")
99
+ if active not in migrated:
100
+ state["active_workspace"] = DEFAULT_WORKSPACE_ID
101
+ return state
102
+
103
+
104
+ def default_state() -> Dict[str, Any]:
105
+ return {
106
+ "version": WORKSPACE_OS_VERSION,
107
+ "identity": "AI Workspace OS",
108
+ "created_at": _now(),
109
+ "updated_at": _now(),
110
+ "active_workspace": DEFAULT_WORKSPACE_ID,
111
+ "workspaces": {
112
+ DEFAULT_WORKSPACE_ID: new_workspace_record(
113
+ workspace_id=DEFAULT_WORKSPACE_ID,
114
+ name="Personal Workspace",
115
+ workspace_type="personal",
116
+ owner_user_id=None,
117
+ ),
118
+ },
119
+ "feature_flags": {
120
+ "workspace_os": True,
121
+ "graph_trace": True,
122
+ "snapshots": True,
123
+ "personal_memory": True,
124
+ "multi_agent_graph": True,
125
+ "workflow_graph": True,
126
+ "skill_marketplace": True,
127
+ "local_computer_memory": False,
128
+ "organization_workspaces": True,
129
+ "enterprise_seam": True,
130
+ "plugin_sdk": True,
131
+ "workflow_designer": True,
132
+ "multi_agent_runtime": True,
133
+ "realtime_collaboration": True,
134
+ "agent_handoff": True,
135
+ "agent_context_packets": True,
136
+ "review_retry_loops": True,
137
+ "timeline_replay": True,
138
+ "agent_memory": True,
139
+ "agent_planning": True,
140
+ "marketplace_foundation": True,
141
+ "realtime_execution_observability": True,
142
+ },
143
+ "onboarding": {
144
+ "completed": False,
145
+ "current_step": "account",
146
+ "steps": {
147
+ step: {
148
+ "id": step,
149
+ "status": "pending",
150
+ "data": {},
151
+ "error": "",
152
+ "updated_at": None,
153
+ }
154
+ for step in ONBOARDING_STEPS
155
+ },
156
+ },
157
+ "snapshots": [],
158
+ "traces": [],
159
+ "memories": [],
160
+ "memory_snapshots": [],
161
+ "agents": list(DEFAULT_AGENTS),
162
+ "agent_runs": [],
163
+ "handoffs": [],
164
+ "workflows": [],
165
+ "workflow_runs": [],
166
+ "review_items": [],
167
+ "skill_registry": {},
168
+ "plugin_registry": {},
169
+ "template_registry": {},
170
+ "computer_memory": {
171
+ "enabled": False,
172
+ "approved": False,
173
+ "approved_at": None,
174
+ "approved_by": None,
175
+ "scopes": ["Downloads", "Documents", "Repositories"],
176
+ "activities": [],
177
+ "notice": "Local Computer Memory is OFF by default and requires explicit approval.",
178
+ },
179
+ "timeline": [],
180
+ }
@@ -11,7 +11,9 @@ import shutil
11
11
  from pathlib import Path
12
12
  from typing import Any, Dict, List, Optional
13
13
 
14
- from .io_utils import atomic_write_json as _atomic_write_json # noqa: F401 - legacy helper re-export
14
+ from .io_utils import (
15
+ atomic_write_json as _atomic_write_json, # noqa: F401 - legacy helper re-export
16
+ )
15
17
  from .io_utils import parse_iso as _parse_iso # noqa: F401 - legacy helper re-export
16
18
 
17
19
 
@@ -10,6 +10,7 @@ from typing import Any, Dict, Optional
10
10
  from .timeutil import now_iso as _now
11
11
  from .workspace_os_utils import _listify
12
12
 
13
+
13
14
  # Avoid circular at import time: pull constants lazily from parent module.
14
15
  def _get_role_permissions():
15
16
  from .workspace_os import ROLE_PERMISSIONS
@@ -10,6 +10,8 @@ from datetime import datetime
10
10
  from pathlib import Path
11
11
  from typing import Any, Dict, Iterable, Optional
12
12
 
13
+ from latticeai.core.quiet import quiet
14
+
13
15
  from .timeutil import now_iso as _now
14
16
  from .workspace_os_utils import _atomic_write_json, _json_hash, _listify, _safe_slug
15
17
 
@@ -137,7 +139,7 @@ class WorkspaceSnapshots:
137
139
  imported = graph.import_graph(snapshot.get("graph") or {}, mode="merge")
138
140
  result["imported"] = imported
139
141
  except Exception:
140
- pass
142
+ quiet()
141
143
  # Always set for test compatibility (additive restore)
142
144
  data = snapshot.get("graph") or {}
143
145
  if "counts" not in data:
@@ -6,6 +6,8 @@ from __future__ import annotations
6
6
 
7
7
  from typing import Any, Dict, Iterable, List, Optional
8
8
 
9
+ from latticeai.core.quiet import quiet
10
+
9
11
  from .timeutil import now_iso as _now
10
12
  from .workspace_os_utils import _listify, _parse_iso
11
13
 
@@ -54,7 +56,7 @@ class WorkspaceTimeline:
54
56
  try:
55
57
  self._store.event_sink({**entry, "type": "timeline"})
56
58
  except Exception:
57
- pass
59
+ quiet()
58
60
  return entry
59
61
 
60
62
  def filter_audit_timeline(
@@ -1,17 +1,19 @@
1
1
  import asyncio
2
- import httpx
3
- import logging
4
2
  import base64
3
+ import json
4
+ import logging
5
5
  import os
6
6
  import socket
7
7
  import tempfile
8
8
  import zipfile
9
- import json
10
9
  from pathlib import Path
11
10
 
11
+ import httpx
12
+
13
+ from latticeai.cli.runtime import _load_env_file
12
14
  from latticeai.core.io_utils import atomic_write_json
13
15
  from latticeai.core.logging_safety import install_sensitive_log_filter, safe_log_text
14
- from latticeai.cli.runtime import _load_env_file
16
+ from latticeai.core.quiet import quiet
15
17
 
16
18
  install_sensitive_log_filter()
17
19
 
@@ -182,14 +184,14 @@ async def send_chat_action(client, chat_id, action="typing"):
182
184
  try:
183
185
  await client.post(f"{API_URL}/sendChatAction", json={"chat_id": chat_id, "action": action})
184
186
  except Exception:
185
- pass
187
+ quiet()
186
188
 
187
189
  async def answer_callback(client, callback_query_id, text=""):
188
190
  try:
189
191
  await client.post(f"{API_URL}/answerCallbackQuery",
190
192
  json={"callback_query_id": callback_query_id, "text": text})
191
193
  except Exception:
192
- pass
194
+ quiet()
193
195
 
194
196
  async def edit_message(client, chat_id, message_id, text, reply_markup=None):
195
197
  try:
@@ -198,7 +200,7 @@ async def edit_message(client, chat_id, message_id, text, reply_markup=None):
198
200
  payload["reply_markup"] = reply_markup
199
201
  await client.post(f"{API_URL}/editMessageText", json=payload)
200
202
  except Exception:
201
- pass
203
+ quiet()
202
204
 
203
205
  # ── Network helpers ───────────────────────────────────────────────────────────
204
206
 
@@ -210,14 +212,14 @@ def get_lan_ip():
210
212
  if not ip.startswith("127."):
211
213
  return ip
212
214
  except OSError:
213
- pass
215
+ quiet()
214
216
  try:
215
217
  hostname = socket.gethostname()
216
218
  for ip in socket.gethostbyname_ex(hostname)[2]:
217
219
  if not ip.startswith("127."):
218
220
  return ip
219
221
  except OSError:
220
- pass
222
+ quiet()
221
223
  return "127.0.0.1"
222
224
 
223
225
  def get_web_url():
@@ -332,7 +334,7 @@ async def _mac_ram_used_gb() -> str:
332
334
  try:
333
335
  stats[k.strip()] = int(v.strip().rstrip(".")) * page_size
334
336
  except ValueError:
335
- pass
337
+ quiet()
336
338
 
337
339
  used = stats.get("Pages active", 0) + stats.get("Pages wired down", 0)
338
340
 
@@ -452,7 +454,12 @@ async def show_graph_stats(client, chat_id):
452
454
 
453
455
  async def take_screenshot(client, chat_id):
454
456
  await send_chat_action(client, chat_id, "upload_photo")
455
- tmp = Path(tempfile.mktemp(suffix=".jpg"))
457
+ # mkstemp, not mktemp: mktemp only predicts an unused name, leaving a
458
+ # window in which anything can create that path first. mkstemp creates
459
+ # the file atomically with 0600.
460
+ _fd, _name = tempfile.mkstemp(suffix=".jpg")
461
+ os.close(_fd)
462
+ tmp = Path(_name)
456
463
  try:
457
464
  proc = await asyncio.create_subprocess_exec(
458
465
  "screencapture", "-x", str(tmp),
@@ -474,7 +481,7 @@ async def take_screenshot(client, chat_id):
474
481
  try:
475
482
  tmp.unlink(missing_ok=True)
476
483
  except Exception:
477
- pass
484
+ quiet()
478
485
 
479
486
  # ── History ───────────────────────────────────────────────────────────────────
480
487
 
@@ -570,7 +577,9 @@ async def process_document_file(client, chat_id, file_id: str, filename: str, ca
570
577
  f"지원 형식: {', '.join(sorted(allowed))}")
571
578
  return
572
579
 
573
- tmp = Path(tempfile.mktemp(suffix=suffix))
580
+ _fd, _name = tempfile.mkstemp(suffix=suffix) # see take_screenshot
581
+ os.close(_fd)
582
+ tmp = Path(_name)
574
583
  try:
575
584
  tmp.write_bytes(raw)
576
585
  async with _server_client() as lc:
@@ -632,7 +641,7 @@ async def ask_ai(client, message, image_data=None, agent_mode=False,
632
641
  chunk = json.loads(line[5:].strip()).get("chunk", "")
633
642
  text += chunk
634
643
  except Exception:
635
- pass
644
+ quiet()
636
645
  return {"response": text.strip() or "⚠️ 빈 응답"}
637
646
  return res.json()
638
647
  try:
@@ -1050,7 +1059,7 @@ async def process_ai_request(client, chat_id, user_text, image_data=None):
1050
1059
  try:
1051
1060
  await send_message(client, chat_id, "⚠️ 처리 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.")
1052
1061
  except Exception:
1053
- pass
1062
+ quiet()
1054
1063
 
1055
1064
  # ── Command dispatch ──────────────────────────────────────────────────────────
1056
1065
 
@@ -1309,4 +1318,4 @@ if __name__ == "__main__":
1309
1318
  try:
1310
1319
  asyncio.run(run_bot())
1311
1320
  except KeyboardInterrupt:
1312
- pass
1321
+ quiet()
@@ -19,9 +19,12 @@ from pathlib import Path
19
19
  os.environ.setdefault("MLX_VLM_DRAFT_KIND", "mtp")
20
20
 
21
21
  from concurrent.futures import ThreadPoolExecutor
22
- from typing import AsyncIterator, Dict, Optional, Tuple, List
22
+ from typing import AsyncIterator, Dict, List, Optional, Tuple
23
+
23
24
  from PIL import Image
24
25
 
26
+ from latticeai.core.quiet import quiet
27
+
25
28
  # Cloud provider catalog data lives in .model_providers; re-exported here so
26
29
  # ``from latticeai.models.router import OPENAI_COMPATIBLE_PROVIDERS`` (and the
27
30
  # model_runtime re-export chain) resolve unchanged after the split.
@@ -265,7 +268,7 @@ def _mlx_sampler(temperature: float):
265
268
  package into the runtime contract.
266
269
  """
267
270
  _ = temperature
268
- return None
271
+ return
269
272
 
270
273
  class LLMRouter:
271
274
  def __init__(self):
@@ -525,7 +528,7 @@ class LLMRouter:
525
528
  msgs = [{"role": "system", "content": system}, {"role": "user", "content": message}]
526
529
  return tokenizer.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
527
530
  except Exception:
528
- pass
531
+ quiet()
529
532
  return f"<|im_start|>system\n{system}<|im_end|>\n<|im_start|>user\n{message}<|im_end|>\n<|im_start|>assistant\n"
530
533
 
531
534
  def _build_vlm_prompt(self, model, processor, message: str, context: Optional[str], num_images: int) -> str:
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Any, Callable, Dict, Optional
6
6
 
7
+ from latticeai.core.quiet import quiet
8
+
7
9
 
8
10
  def build_access_runtime(
9
11
  *,
@@ -129,7 +131,7 @@ def build_access_runtime(
129
131
  require_capability(role, "admin:users")
130
132
  return email, users
131
133
  except PermissionError:
132
- pass
134
+ quiet()
133
135
  raise http_exception(status_code=403, detail="관리자 권한이 필요합니다.")
134
136
 
135
137
  def public_user(email: str, user: Dict, users: Dict) -> Dict:
@@ -9,6 +9,7 @@ from pathlib import Path
9
9
  from typing import Any, Callable, Dict, List, Optional
10
10
 
11
11
  from latticeai.core import timezones
12
+ from latticeai.core.quiet import quiet
12
13
 
13
14
 
14
15
  def build_audit_runtime(
@@ -40,7 +41,7 @@ def build_audit_runtime(
40
41
  if isinstance(item, dict):
41
42
  events.append(item)
42
43
  except Exception:
43
- pass
44
+ quiet()
44
45
  return events[-5000:]
45
46
 
46
47
  def _append(event_type: str, **payload: Any) -> None:
@@ -53,7 +54,7 @@ def build_audit_runtime(
53
54
  if k in entry and isinstance(entry[k], str):
54
55
  entry[k] = redact_fn(entry[k])
55
56
  except Exception:
56
- pass
57
+ quiet()
57
58
  with _audit_lock:
58
59
  audit_file.parent.mkdir(parents=True, exist_ok=True)
59
60
  with audit_jsonl.open("a", encoding="utf-8") as fh:
@@ -8,7 +8,10 @@ from latticeai.runtime.permission_mode_wiring import (
8
8
  bind_dispatch_permission_mode,
9
9
  resolve_active_permission_mode,
10
10
  )
11
- from latticeai.services.router_context import InteractionRouterContext, ToolRouterContext
11
+ from latticeai.services.router_context import (
12
+ InteractionRouterContext,
13
+ ToolRouterContext,
14
+ )
12
15
 
13
16
 
14
17
  def build_chat_agent_runtime_from_context(
@@ -68,7 +68,7 @@ def build_history_query_runtime(
68
68
  grouped: Dict[str, Dict] = {}
69
69
  order: List[str] = []
70
70
 
71
- for index, item in enumerate(history):
71
+ for index, item in enumerate(history): # noqa: B007 — index documents the enumerate contract for readers
72
72
  conv_id = item.get("conversation_id")
73
73
  if not conv_id:
74
74
  conv_id = "legacy-previous-history"
@@ -5,6 +5,8 @@ from __future__ import annotations
5
5
  from contextlib import asynccontextmanager
6
6
  from typing import Any, Dict
7
7
 
8
+ from latticeai.core.quiet import quiet
9
+
8
10
 
9
11
  def build_lifespan_runtime(
10
12
  *,
@@ -128,7 +130,7 @@ def build_lifespan_runtime(
128
130
  proc.terminate()
129
131
  proc.wait(timeout=5)
130
132
  except Exception:
131
- pass
133
+ quiet()
132
134
 
133
135
  return {
134
136
  "autoload_default_model": autoload_default_model,
@@ -0,0 +1,124 @@
1
+ """Wire NetworkBoundaryMode + HybridPolicy into the running app (Phase 1–3)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import threading
7
+ from pathlib import Path
8
+ from typing import Any, Callable, Optional
9
+
10
+ from latticeai.core.network_boundary import (
11
+ DEFAULT_NETWORK_MODE,
12
+ normalize_network_mode,
13
+ )
14
+ from latticeai.services.hybrid_policy import HybridPolicyService
15
+ from latticeai.services.network_boundary_service import NetworkBoundaryService
16
+
17
+ _LOCK = threading.Lock()
18
+ _SHARED: Optional[NetworkBoundaryService] = None
19
+ _POLICY: Optional[HybridPolicyService] = None
20
+
21
+
22
+ def _default_data_dir() -> Path:
23
+ raw = os.environ.get("LATTICEAI_DATA_DIR", "").strip()
24
+ if raw:
25
+ return Path(raw)
26
+ return Path.home() / ".ltcai"
27
+
28
+
29
+ def _env_default_mode() -> Any:
30
+ return normalize_network_mode(
31
+ os.environ.get("LATTICEAI_NETWORK_MODE", DEFAULT_NETWORK_MODE.value)
32
+ )
33
+
34
+
35
+ def get_network_boundary_service(
36
+ *,
37
+ data_dir: Optional[Path] = None,
38
+ audit: Optional[Callable[..., None]] = None,
39
+ ) -> NetworkBoundaryService:
40
+ global _SHARED
41
+ with _LOCK:
42
+ if _SHARED is None:
43
+ _SHARED = NetworkBoundaryService(
44
+ data_dir=Path(data_dir) if data_dir is not None else _default_data_dir(),
45
+ default_mode=_env_default_mode(),
46
+ audit=audit,
47
+ )
48
+ return _SHARED
49
+ if data_dir is not None:
50
+ _SHARED.rebind_data_dir(Path(data_dir))
51
+ if audit is not None:
52
+ _SHARED.rebind_audit(audit)
53
+ return _SHARED
54
+
55
+
56
+ def get_hybrid_policy_service(
57
+ *,
58
+ data_dir: Optional[Path] = None,
59
+ audit: Optional[Callable[..., None]] = None,
60
+ ) -> HybridPolicyService:
61
+ global _POLICY
62
+ with _LOCK:
63
+ if _POLICY is None:
64
+ _POLICY = HybridPolicyService(
65
+ data_dir=Path(data_dir) if data_dir is not None else _default_data_dir(),
66
+ audit=audit,
67
+ )
68
+ return _POLICY
69
+ if data_dir is not None:
70
+ _POLICY.rebind_data_dir(Path(data_dir))
71
+ if audit is not None:
72
+ _POLICY.rebind_audit(audit)
73
+ return _POLICY
74
+
75
+
76
+ def resolve_active_network_mode(
77
+ *,
78
+ user_email: Optional[str] = None,
79
+ workspace_id: Optional[str] = None,
80
+ ) -> Any:
81
+ return get_network_boundary_service().resolve(
82
+ user_email=user_email, workspace_id=workspace_id,
83
+ )
84
+
85
+
86
+ def register_network_boundary_router(
87
+ app: Any,
88
+ *,
89
+ require_user: Callable[..., str],
90
+ data_dir: Optional[Path] = None,
91
+ append_audit_event: Optional[Callable[..., None]] = None,
92
+ knowledge_graph: Any = None,
93
+ ) -> Any:
94
+ from latticeai.api.network_boundary import create_network_boundary_router
95
+ from latticeai.services.cloud_egress_audit import bind_egress_audit
96
+
97
+ svc = get_network_boundary_service(data_dir=data_dir, audit=append_audit_event)
98
+ policy = get_hybrid_policy_service(data_dir=data_dir, audit=append_audit_event)
99
+ # The dial's own changes were audited from 10.1.0; the sends were not.
100
+ # Bind the same sink so egress lands in the same log.
101
+ bind_egress_audit(append_audit_event)
102
+ existing = {
103
+ getattr(route, "path", None)
104
+ for route in getattr(app, "routes", ())
105
+ }
106
+ if "/api/network-boundary" in existing:
107
+ return svc
108
+ app.include_router(
109
+ create_network_boundary_router(
110
+ service=svc,
111
+ require_user=require_user,
112
+ knowledge_graph=knowledge_graph,
113
+ policy_service=policy,
114
+ )
115
+ )
116
+ return svc
117
+
118
+
119
+ __all__ = [
120
+ "get_network_boundary_service",
121
+ "get_hybrid_policy_service",
122
+ "resolve_active_network_mode",
123
+ "register_network_boundary_router",
124
+ ]
@@ -9,6 +9,8 @@ from __future__ import annotations
9
9
 
10
10
  from typing import Any, Callable, Dict, Optional
11
11
 
12
+ from latticeai.core.quiet import quiet
13
+
12
14
 
13
15
  def build_persistence_runtime(
14
16
  *,
@@ -84,7 +86,7 @@ def build_persistence_runtime(
84
86
  duplicate=bool((detail or {}).get("duplicate"))
85
87
  )
86
88
  except Exception: # noqa: BLE001 — metrics must never break ingestion
87
- pass
89
+ quiet()
88
90
  audit(action, detail, user)
89
91
 
90
92
  ingestion_pipeline = IngestionPipeline(
@@ -653,7 +653,6 @@ def register_review_and_brain_tail_routers(
653
653
  workspace_service=workspace_service,
654
654
  ),
655
655
  )
656
- # Permission mode dial (v9.9.8): mount last so data_dir + audit are known.
657
656
  from latticeai.runtime.permission_mode_wiring import register_permission_mode_router
658
657
 
659
658
  register_permission_mode_router(
@@ -662,4 +661,15 @@ def register_review_and_brain_tail_routers(
662
661
  data_dir=data_dir,
663
662
  append_audit_event=append_audit_event,
664
663
  )
664
+ from latticeai.runtime.network_boundary_wiring import (
665
+ register_network_boundary_router,
666
+ )
667
+
668
+ register_network_boundary_router(
669
+ app,
670
+ require_user=require_user,
671
+ data_dir=data_dir,
672
+ append_audit_event=append_audit_event,
673
+ knowledge_graph=knowledge_graph,
674
+ )
665
675
  return brain_network
@@ -16,8 +16,7 @@ from typing import Any, Dict, List
16
16
 
17
17
  from latticeai.core.legacy_compatibility import legacy_shim_report
18
18
 
19
-
20
- ARCHITECTURE_VERSION_TARGET = "10.0.0"
19
+ ARCHITECTURE_VERSION_TARGET = "10.2.0"
21
20
 
22
21
  PREFERRED_REFACTORING_ORDER = [
23
22
  "agent-runtime",
@@ -29,6 +29,7 @@ import threading
29
29
  from pathlib import Path
30
30
  from typing import Any, Callable, Dict, List, Optional, Tuple
31
31
 
32
+ from latticeai.core.quiet import quiet
32
33
  from latticeai.core.tool_governor import classify_tool_call
33
34
 
34
35
  LOGGER = logging.getLogger(__name__)
@@ -435,7 +436,7 @@ class ChangeProposalService:
435
436
  try:
436
437
  os.unlink(tmp_name)
437
438
  except OSError:
438
- pass
439
+ quiet()
439
440
  raise
440
441
 
441
442
  def reject(