ltcai 7.1.0 → 7.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 (40) hide show
  1. package/README.md +21 -19
  2. package/docs/CHANGELOG.md +20 -0
  3. package/frontend/src/api/client.ts +3 -0
  4. package/frontend/src/features/admin/AdminConsole.tsx +41 -1
  5. package/frontend/src/i18n.ts +24 -0
  6. package/frontend/src/styles.css +45 -0
  7. package/lattice_brain/__init__.py +1 -1
  8. package/lattice_brain/runtime/agent_runtime.py +51 -0
  9. package/lattice_brain/runtime/multi_agent.py +1 -1
  10. package/latticeai/__init__.py +1 -1
  11. package/latticeai/api/agents.py +12 -0
  12. package/latticeai/api/tools.py +14 -0
  13. package/latticeai/core/marketplace.py +1 -1
  14. package/latticeai/core/tool_registry.py +47 -0
  15. package/latticeai/core/workspace_os.py +1 -1
  16. package/latticeai/services/tool_dispatch.py +14 -0
  17. package/package.json +1 -1
  18. package/src-tauri/Cargo.lock +1 -1
  19. package/src-tauri/Cargo.toml +1 -1
  20. package/src-tauri/tauri.conf.json +1 -1
  21. package/static/app/asset-manifest.json +28 -28
  22. package/static/app/assets/{Act-BHEb8bAM.js → Act-Di4tRFWY.js} +2 -2
  23. package/static/app/assets/{Act-BHEb8bAM.js.map → Act-Di4tRFWY.js.map} +1 -1
  24. package/static/app/assets/{Brain-yvR7xkrV.js → Brain-BZB3Gy9w.js} +2 -2
  25. package/static/app/assets/{Brain-yvR7xkrV.js.map → Brain-BZB3Gy9w.js.map} +1 -1
  26. package/static/app/assets/{Capture-BrAmsSEH.js → Capture-tNyYWxnh.js} +2 -2
  27. package/static/app/assets/{Capture-BrAmsSEH.js.map → Capture-tNyYWxnh.js.map} +1 -1
  28. package/static/app/assets/{Library-BOVzYfxI.js → Library-DAtDDLdg.js} +2 -2
  29. package/static/app/assets/{Library-BOVzYfxI.js.map → Library-DAtDDLdg.js.map} +1 -1
  30. package/static/app/assets/{System-D4WaN4kj.js → System-DEu0xNUc.js} +2 -2
  31. package/static/app/assets/{System-D4WaN4kj.js.map → System-DEu0xNUc.js.map} +1 -1
  32. package/static/app/assets/{index-DvLsGy-z.css → index-Bi_bpigM.css} +1 -1
  33. package/static/app/assets/{index-BOB-W1FZ.js → index-COuGp7_5.js} +3 -3
  34. package/static/app/assets/index-COuGp7_5.js.map +1 -0
  35. package/static/app/assets/{primitives-C3_BfUb8.js → primitives-CdwcE--L.js} +2 -2
  36. package/static/app/assets/{primitives-C3_BfUb8.js.map → primitives-CdwcE--L.js.map} +1 -1
  37. package/static/app/assets/{textarea-CbvhHvzg.js → textarea-CqOdBPL1.js} +2 -2
  38. package/static/app/assets/{textarea-CbvhHvzg.js.map → textarea-CqOdBPL1.js.map} +1 -1
  39. package/static/app/index.html +2 -2
  40. package/static/app/assets/index-BOB-W1FZ.js.map +0 -1
package/README.md CHANGED
@@ -200,25 +200,26 @@ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for developer workflow details.
200
200
 
201
201
  ## Current Release
202
202
 
203
- The current release is **7.1.0 Brain Usability Completion**:
204
-
205
- - First-run onboarding now explains detected hardware, recommendation timing,
206
- install progress, and the next action in plain language.
207
- - Brain Home shows ingestion progress for files, folders, notes, and web pages,
208
- including memory emergence after content lands.
209
- - Knowledge Graph exploration adds search suggestions, type filters, time
210
- exploration, focus movement, and neighbor highlighting.
211
- - Chat answers surface inline source citations and accessible proof payloads.
212
- - Workspace/profile/admin entry points, empty/error feedback, consent revoke
213
- feedback, and VS Code extension sync status are visible in the product shell.
214
-
215
- Expected artifacts for 7.1.0 release must use exact filenames:
216
-
217
- - `dist/ltcai-7.1.0-py3-none-any.whl`
218
- - `dist/ltcai-7.1.0.tar.gz`
219
- - `ltcai-7.1.0.tgz`
220
- - `dist/ltcai-7.1.0.vsix`
221
- - `src-tauri/target/release/bundle/dmg/Lattice AI_7.1.0_aarch64.dmg`
203
+ The current release is **7.2.0 Runtime Trust Baseline**:
204
+
205
+ - Agent runs can be previewed before execution, including selected roles,
206
+ clamped retry budget, readiness state, and blocking reasons.
207
+ - Product agent execution refuses simulation-mode success unless simulation is
208
+ explicitly enabled for a non-product runtime.
209
+ - ToolRegistry exposes a live manifest and diagnostics endpoint covering
210
+ dispatch handlers, governance policy, catalog descriptions, and permissions.
211
+ - Tool governance/catalog drift is covered by unit tests so tool additions
212
+ cannot silently bypass policy or disappear from operator views.
213
+ - 7.1.0 Brain usability work remains intact while the runtime gains clearer
214
+ contracts for admin surfaces and future AgentRuntime extraction.
215
+
216
+ Expected artifacts for 7.2.0 release must use exact filenames:
217
+
218
+ - `dist/ltcai-7.2.0-py3-none-any.whl`
219
+ - `dist/ltcai-7.2.0.tar.gz`
220
+ - `ltcai-7.2.0.tgz`
221
+ - `dist/ltcai-7.2.0.vsix`
222
+ - `src-tauri/target/release/bundle/dmg/Lattice AI_7.2.0_aarch64.dmg`
222
223
 
223
224
  Do not upload `dist/*`. Package registry publishing remains owner-run.
224
225
 
@@ -237,6 +238,7 @@ Do not upload `dist/*`. Package registry publishing remains owner-run.
237
238
 
238
239
  | Version | Theme |
239
240
  | --- | --- |
241
+ | 7.2.0 | Runtime Trust Baseline: agent run preview/readiness, simulation-mode guardrails, live ToolRegistry manifest/diagnostics, and tests for dispatch/governance/catalog drift |
240
242
  | 7.1.0 | Brain Usability Completion: clearer first-run onboarding, ingestion progress/emergence, richer graph controls, inline answer proof, workspace/profile/admin discovery, empty/error/consent feedback, and VS Code sync status |
241
243
  | 7.0.0 | Brain Productization Loop: first-screen ingestion for files/folders/notes/web, answer-level memory proof and source citations, model-continuity demo flow, five-minute first-run loop, and recall/KG quality eval in CI |
242
244
  | 6.7.0 | Brain IA Cleanup: reachable rich pages, separated product routes vs compatibility aliases, shared Brain shell navigation, and lazy-loaded rich pages |
package/docs/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ The top entry is either the current unreleased main-branch work or the current
4
4
  release line. Older entries are historical and may describe behavior as it
5
5
  existed at that release.
6
6
 
7
+ ## [7.2.0] - 2026-06-20
8
+
9
+ > Runtime Trust Baseline. Adds execution preview and registry diagnostics so
10
+ > agent runs and tool permissions become inspectable contracts before action.
11
+
12
+ ### Added
13
+ - `POST /agents/api/run/preview` for AgentRuntime readiness, role selection,
14
+ input keys, retry clamping, and blocking reasons without starting a run.
15
+ - `GET /tools/registry` for the live ToolRegistry manifest across dispatch
16
+ handlers, governance policy, catalog descriptions, and permissions.
17
+ - `GET /tools/registry/diagnostics` for a compact drift check suitable for CI,
18
+ admin views, and runtime health panels.
19
+ - Unit coverage for AgentRuntime preview and ToolRegistry manifest diagnostics.
20
+
21
+ ### Changed
22
+ - Tool governance now covers `read_document`, and the catalog describes
23
+ `create_web_project`, closing the current dispatch/governance/catalog drift.
24
+ - Package/runtime/static metadata is synchronized to 7.2.0; package publish and
25
+ deployment remain owner-run only.
26
+
7
27
  ## [7.1.0] - 2026-06-20
8
28
 
9
29
  > Brain Usability Completion. Completes the 7.1.0 first-run through editor-sync
@@ -481,7 +481,10 @@ export const latticeApi = {
481
481
  unloadModel: (model_id: string) => del(`/models/unload/${encodeURIComponent(model_id)}`, {}),
482
482
  embeddingsStatus: () => get("/api/embeddings/status", {}),
483
483
  agentRuntime: () => get("/agents/api/runtime/status", { runtime: {}, agents: [], runs: [] }),
484
+ agentRunPreview: (goal: string, roles: string[] = []) => post("/agents/api/run/preview", { goal, roles }, { ready: false, blocking_reasons: [] }),
484
485
  runAgent: (goal: string, roles: string[]) => post("/agents/api/run", { goal, roles }, {}),
486
+ toolRegistryDiagnostics: () => get("/tools/registry/diagnostics", { diagnostics: { ready: false } }),
487
+ toolRegistry: () => get("/tools/registry", { status: "unavailable", diagnostics: {}, tools: [] }),
485
488
  agentRun: (id: string) => get(`/agents/api/runs/${encodeURIComponent(id)}`, {}),
486
489
  stopAgentRun: (id: string) => post(`/agents/api/runs/${encodeURIComponent(id)}/stop`, {}, {}),
487
490
  agentRegistry: () => get("/agents/api/registry", { agents: [] }),
@@ -15,7 +15,7 @@ export function AdminConsole({ onBack }: { onBack: () => void }) {
15
15
  const qc = useQueryClient();
16
16
  const language = useAppStore((state) => state.language);
17
17
  const [filters, setFilters] = React.useState<AdminFilterState>({ q: "", actor: "", action: "", severity: "", limit: 50 });
18
- const { summaryQ, statsQ, usersQ, auditQ, securityQ, securityEventsQ, policiesQ, rolesQ, retentionQ, indexQ } = useAdminConsoleData(filters);
18
+ const { summaryQ, statsQ, usersQ, auditQ, securityQ, securityEventsQ, policiesQ, rolesQ, retentionQ, indexQ, agentRuntimeQ, toolRegistryQ } = useAdminConsoleData(filters);
19
19
  const rebuildIndex = useMutation({
20
20
  mutationFn: latticeApi.rebuildIndex,
21
21
  onSuccess: () => void qc.invalidateQueries({ queryKey: ["indexStatus"] }),
@@ -139,6 +139,10 @@ export function AdminConsole({ onBack }: { onBack: () => void }) {
139
139
  </div>
140
140
  </AdminPanel>
141
141
 
142
+ <AdminPanel title={t(language, "admin.panel.runtimeTrust")} eyebrow={t(language, "admin.panel.contracts")}>
143
+ <RuntimeTrustPanel runtime={agentRuntimeQ.data?.data as ApiRecord | undefined} registry={toolRegistryQ.data?.data as ApiRecord | undefined} language={language} />
144
+ </AdminPanel>
145
+
142
146
  </section>
143
147
  </main>
144
148
  );
@@ -164,9 +168,45 @@ function useAdminConsoleData(filters: AdminFilterState) {
164
168
  rolesQ: useQuery({ queryKey: ["adminRoles"], queryFn: latticeApi.adminRoles }),
165
169
  retentionQ: useQuery({ queryKey: ["adminLogRetention"], queryFn: latticeApi.adminLogRetention }),
166
170
  indexQ: useQuery({ queryKey: ["indexStatus"], queryFn: latticeApi.indexStatus }),
171
+ agentRuntimeQ: useQuery({ queryKey: ["agentRuntime"], queryFn: latticeApi.agentRuntime }),
172
+ toolRegistryQ: useQuery({ queryKey: ["toolRegistryDiagnostics"], queryFn: latticeApi.toolRegistryDiagnostics }),
167
173
  };
168
174
  }
169
175
 
176
+ function RuntimeTrustPanel({ runtime, registry, language }: { runtime?: ApiRecord; registry?: ApiRecord; language: "ko" | "en" }) {
177
+ const runtimeInfo = (runtime?.runtime || {}) as ApiRecord;
178
+ const health = (runtime?.health || {}) as ApiRecord;
179
+ const diagnostics = (registry?.diagnostics || {}) as ApiRecord;
180
+ const ready = Boolean(runtimeInfo.ready);
181
+ const registryReady = Boolean(diagnostics.ready);
182
+ const blocking = stringValue(runtimeInfo.unavailable_reason, ready ? t(language, "admin.runtime.readyDetail") : t(language, "admin.runtime.blockedFallback"));
183
+ return (
184
+ <div className="admin-runtime-trust">
185
+ <div className="admin-runtime-row">
186
+ <strong>{t(language, "admin.runtime.agent")}</strong>
187
+ <span className={ready ? "is-ok" : "is-warn"}>{ready ? t(language, "admin.status.ready") : t(language, "admin.status.unavailable")}</span>
188
+ <small>{blocking}</small>
189
+ </div>
190
+ <div className="admin-runtime-row">
191
+ <strong>{t(language, "admin.runtime.tools")}</strong>
192
+ <span className={registryReady ? "is-ok" : "is-warn"}>{registryReady ? t(language, "admin.runtime.aligned") : t(language, "admin.runtime.drift")}</span>
193
+ <small>
194
+ {t(language, "admin.runtime.toolCounts", {
195
+ registered: stringValue(diagnostics.registered_tools, "0"),
196
+ governed: stringValue(diagnostics.governed_tools, "0"),
197
+ described: stringValue(diagnostics.described_tools, "0"),
198
+ })}
199
+ </small>
200
+ </div>
201
+ <div className="admin-policy-strip">
202
+ <span>{t(language, "admin.runtime.mode", { mode: stringValue(runtimeInfo.mode, "unknown") })}</span>
203
+ <span>{t(language, "admin.runtime.execution", { mode: stringValue(runtimeInfo.execution_mode, "unknown") })}</span>
204
+ <span>{t(language, "admin.runtime.health", { status: stringValue(health.status, "unknown") })}</span>
205
+ </div>
206
+ </div>
207
+ );
208
+ }
209
+
170
210
  function AdminLogFilters({
171
211
  filters,
172
212
  matched,
@@ -348,6 +348,8 @@ export const COPY: Record<Language, TextMap> = {
348
348
  "admin.panel.protection": "보호",
349
349
  "admin.panel.brainOps": "Brain 운영",
350
350
  "admin.panel.maintenance": "유지보수",
351
+ "admin.panel.runtimeTrust": "런타임 신뢰도",
352
+ "admin.panel.contracts": "계약",
351
353
  "admin.empty.users": "관리 API가 보고한 사용자가 없습니다.",
352
354
  "admin.empty.roles": "보고된 역할 매트릭스가 없습니다.",
353
355
  "admin.empty.audit": "최근 감사 이벤트가 없습니다.",
@@ -369,6 +371,16 @@ export const COPY: Record<Language, TextMap> = {
369
371
  "admin.brain.summaryFallback": "로컬 Brain 서비스는 사용자 대화와 분리되어 있습니다.",
370
372
  "admin.brain.rebuilding": "재구성 중",
371
373
  "admin.brain.rebuild": "인덱스 재구성",
374
+ "admin.runtime.agent": "AgentRuntime",
375
+ "admin.runtime.tools": "ToolRegistry",
376
+ "admin.runtime.readyDetail": "실행 preview와 실제 실행이 준비되었습니다.",
377
+ "admin.runtime.blockedFallback": "실행 전 preview가 차단 사유를 반환합니다.",
378
+ "admin.runtime.aligned": "정렬됨",
379
+ "admin.runtime.drift": "점검 필요",
380
+ "admin.runtime.toolCounts": "등록 {registered} · 정책 {governed} · 설명 {described}",
381
+ "admin.runtime.mode": "mode {mode}",
382
+ "admin.runtime.execution": "exec {mode}",
383
+ "admin.runtime.health": "health {status}",
372
384
  "admin.policy.fallback": "정책",
373
385
  "admin.policy.quiet": "정책 API 대기 중",
374
386
  "admin.retention.days": "{days}일 보관",
@@ -894,6 +906,8 @@ export const COPY: Record<Language, TextMap> = {
894
906
  "admin.panel.protection": "Protection",
895
907
  "admin.panel.brainOps": "Brain Operations",
896
908
  "admin.panel.maintenance": "Maintenance",
909
+ "admin.panel.runtimeTrust": "Runtime Trust",
910
+ "admin.panel.contracts": "Contracts",
897
911
  "admin.empty.users": "No users reported by the admin API.",
898
912
  "admin.empty.roles": "No role matrix reported.",
899
913
  "admin.empty.audit": "No recent audit events.",
@@ -915,6 +929,16 @@ export const COPY: Record<Language, TextMap> = {
915
929
  "admin.brain.summaryFallback": "Local Brain services are separated from user chat.",
916
930
  "admin.brain.rebuilding": "Rebuilding",
917
931
  "admin.brain.rebuild": "Rebuild index",
932
+ "admin.runtime.agent": "AgentRuntime",
933
+ "admin.runtime.tools": "ToolRegistry",
934
+ "admin.runtime.readyDetail": "Run preview and execution are ready.",
935
+ "admin.runtime.blockedFallback": "Run preview returns the blocking reason before execution.",
936
+ "admin.runtime.aligned": "Aligned",
937
+ "admin.runtime.drift": "Needs review",
938
+ "admin.runtime.toolCounts": "Registered {registered} · governed {governed} · described {described}",
939
+ "admin.runtime.mode": "mode {mode}",
940
+ "admin.runtime.execution": "exec {mode}",
941
+ "admin.runtime.health": "health {status}",
918
942
  "admin.policy.fallback": "Policy",
919
943
  "admin.policy.quiet": "Policy API quiet",
920
944
  "admin.retention.days": "{days} day retention",
@@ -3472,6 +3472,51 @@ body {
3472
3472
  font-size: 0.88rem;
3473
3473
  }
3474
3474
 
3475
+ .admin-runtime-trust {
3476
+ display: grid;
3477
+ gap: 0.72rem;
3478
+ }
3479
+
3480
+ .admin-runtime-row {
3481
+ display: grid;
3482
+ grid-template-columns: minmax(8rem, 1fr) auto;
3483
+ gap: 0.2rem 0.75rem;
3484
+ border: 1px solid hsl(var(--border) / 0.48);
3485
+ border-radius: 8px;
3486
+ background: hsl(var(--bg) / 0.35);
3487
+ padding: 0.72rem;
3488
+ }
3489
+
3490
+ .admin-runtime-row strong {
3491
+ color: hsl(var(--fg));
3492
+ font-size: 0.88rem;
3493
+ }
3494
+
3495
+ .admin-runtime-row > span {
3496
+ align-self: start;
3497
+ border-radius: 999px;
3498
+ padding: 0.18rem 0.48rem;
3499
+ font-size: 0.72rem;
3500
+ font-weight: 700;
3501
+ }
3502
+
3503
+ .admin-runtime-row > small {
3504
+ grid-column: 1 / -1;
3505
+ color: hsl(var(--fg-muted));
3506
+ font-size: 0.78rem;
3507
+ line-height: 1.35;
3508
+ }
3509
+
3510
+ .admin-runtime-row .is-ok {
3511
+ background: hsl(151 65% 36% / 0.13);
3512
+ color: hsl(151 48% 32%);
3513
+ }
3514
+
3515
+ .admin-runtime-row .is-warn {
3516
+ background: hsl(36 88% 52% / 0.14);
3517
+ color: hsl(30 82% 35%);
3518
+ }
3519
+
3475
3520
  .brain-document-input,
3476
3521
  .brain-image-input {
3477
3522
  display: inline-flex;
@@ -26,7 +26,7 @@ from .storage import (
26
26
  storage_from_env,
27
27
  )
28
28
 
29
- __version__ = "7.1.0"
29
+ __version__ = "7.2.0"
30
30
 
31
31
  __all__ = [
32
32
  "AgentRuntime",
@@ -228,6 +228,57 @@ class AgentRuntime:
228
228
  "runs": runs[:25],
229
229
  }
230
230
 
231
+ def preview(
232
+ self,
233
+ goal: str,
234
+ *,
235
+ roles: Optional[List[str]] = None,
236
+ inputs: Optional[Dict[str, Any]] = None,
237
+ max_retries: int = 2,
238
+ scope: Optional[str] = None,
239
+ ) -> Dict[str, Any]:
240
+ """Return the execution contract without reserving or starting a run.
241
+
242
+ The preview is the product-facing readiness gate for agent execution:
243
+ clients can show whether a real LLM-backed run can start, which roles
244
+ will execute, how retry limits are clamped, and why the run is blocked.
245
+ """
246
+
247
+ requested_roles = list(roles or CORE_PIPELINE)
248
+ unknown_roles = [role for role in requested_roles if role not in AGENT_ROLES]
249
+ health = self.health()
250
+ goal_ready = bool(str(goal or "").strip())
251
+ retry_budget = max(0, min(int(max_retries or 0), self._max_retries_cap))
252
+ blocking_reasons: List[str] = []
253
+ if not goal_ready:
254
+ blocking_reasons.append("goal is required")
255
+ if unknown_roles:
256
+ blocking_reasons.append(f"unknown roles: {', '.join(unknown_roles)}")
257
+ if not health.get("ready"):
258
+ orchestrator = (health.get("checks") or {}).get("orchestrator") or {}
259
+ blocking_reasons.append(str(orchestrator.get("detail") or "agent runtime is unavailable"))
260
+ can_start = not blocking_reasons
261
+ return {
262
+ "ready": can_start,
263
+ "can_start": can_start,
264
+ "blocking_reasons": blocking_reasons,
265
+ "goal": str(goal or "").strip(),
266
+ "roles": requested_roles,
267
+ "unknown_roles": unknown_roles,
268
+ "inputs_keys": sorted((inputs or {}).keys()),
269
+ "max_retries": retry_budget,
270
+ "max_retries_requested": max_retries,
271
+ "scope": scope,
272
+ "execution_mode": self._execution_mode(),
273
+ "runtime": {
274
+ "version": MULTI_AGENT_VERSION,
275
+ "default_pipeline": list(CORE_PIPELINE),
276
+ "max_retries_cap": self._max_retries_cap,
277
+ "simulation_runs_allowed": self._allow_simulation_runs,
278
+ },
279
+ "health": health,
280
+ }
281
+
231
282
  # ── events / state ────────────────────────────────────────────────────
232
283
  def list_runs(self, *, scope: Optional[str] = None) -> Dict[str, Any]:
233
284
  return self._store.list_agents(workspace_id=scope)
@@ -19,7 +19,7 @@ from datetime import datetime
19
19
  from typing import Any, Callable, Dict, List, Optional
20
20
 
21
21
 
22
- MULTI_AGENT_VERSION = "7.1.0"
22
+ MULTI_AGENT_VERSION = "7.2.0"
23
23
 
24
24
  AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
25
25
  CORE_PIPELINE = ("planner", "executor", "reviewer")
@@ -1,3 +1,3 @@
1
1
  """Lattice AI - modular server package."""
2
2
 
3
- __version__ = "7.1.0"
3
+ __version__ = "7.2.0"
@@ -192,4 +192,16 @@ def create_agents_router(
192
192
  # A pre_run hook gated this run (e.g. a policy/permission hook).
193
193
  raise HTTPException(status_code=403, detail=str(exc)) from exc
194
194
 
195
+ @router.post("/agents/api/run/preview")
196
+ async def agent_run_preview(req: AgentRunRequest, request: Request):
197
+ require_user(request)
198
+ scope = gate_read(request)
199
+ return runtime.preview(
200
+ req.goal,
201
+ scope=scope,
202
+ roles=req.roles or None,
203
+ inputs=req.inputs,
204
+ max_retries=req.max_retries,
205
+ )
206
+
195
207
  return router
@@ -25,6 +25,8 @@ from latticeai.services.tool_dispatch import (
25
25
  check_tool_role as _check_tool_role,
26
26
  get_tool_permission,
27
27
  list_tool_permissions,
28
+ tool_registry_diagnostics,
29
+ tool_registry_manifest,
28
30
  )
29
31
  from latticeai.services.router_context import ToolRouterContext
30
32
  from latticeai.services.p_reinforce import BRAIN_DIR
@@ -602,6 +604,18 @@ def create_tools_router(
602
604
  """
603
605
  require_user(request)
604
606
  return {"status": "ok", "permissions": list_tool_permissions()}
607
+
608
+ @api_router.get("/tools/registry")
609
+ async def tools_registry(request: Request):
610
+ """Full ToolRegistry contract: handlers, governance, catalog, diagnostics."""
611
+ require_user(request)
612
+ return tool_registry_manifest()
613
+
614
+ @api_router.get("/tools/registry/diagnostics")
615
+ async def tools_registry_diagnostics(request: Request):
616
+ """Small drift check for CI/admin runtime readiness views."""
617
+ require_user(request)
618
+ return {"status": "ok", "diagnostics": tool_registry_diagnostics()}
605
619
 
606
620
 
607
621
  # ── MCP / skills / plugins router (latticeai.api.mcp, v1.3.0) ────────────────
@@ -11,7 +11,7 @@ from copy import deepcopy
11
11
  from typing import Any, Dict, List, Optional
12
12
 
13
13
 
14
- MARKETPLACE_VERSION = "7.1.0"
14
+ MARKETPLACE_VERSION = "7.2.0"
15
15
  TEMPLATE_KINDS = ("plugin", "workflow", "agent")
16
16
 
17
17
 
@@ -130,6 +130,7 @@ TOOL_GOVERNANCE: Dict[str, ToolPolicy] = {
130
130
  "todo_read": _r(),
131
131
  "local_list": _r(sandbox="home"),
132
132
  "local_read": _r(sandbox="home"),
133
+ "read_document": _r(sandbox="home"),
133
134
  "git_status": _rs(),
134
135
  "git_diff": _rs(),
135
136
  "git_log": _rs(),
@@ -189,6 +190,7 @@ MCP_TOOL_DESCRIPTIONS: Dict[str, str] = {
189
190
  "clear_history": "Clear chat history to reduce context and speed up responses.",
190
191
  "inspect_html": "Inspect local HTML structure and assets.",
191
192
  "preview_url": "Return a server URL for a workspace file.",
193
+ "create_web_project": "Create a web project scaffold inside the workspace.",
192
194
  "create_docx": "Create a Word DOCX document in the agent workspace.",
193
195
  "create_xlsx": "Create an XLSX spreadsheet in the agent workspace.",
194
196
  "create_pptx": "Create a PPTX presentation deck in the agent workspace.",
@@ -251,6 +253,51 @@ class ToolRegistry:
251
253
  def registered_tools(self) -> frozenset[str]:
252
254
  return frozenset(self.handlers)
253
255
 
256
+ def diagnostics(self) -> Dict[str, Any]:
257
+ """Return registry drift checks without executing any tool.
258
+
259
+ This is intentionally derived from the live registry instance, not from
260
+ duplicated constants, so admin/runtime surfaces can prove whether the
261
+ dispatch, governance, and catalog projections are still aligned.
262
+ """
263
+
264
+ registered = set(self.registered_tools())
265
+ governed = set(self.governance)
266
+ described = set(self.descriptions)
267
+ return {
268
+ "ready": not (governed - registered or registered - governed or registered - described),
269
+ "registered_tools": len(registered),
270
+ "governed_tools": len(governed),
271
+ "described_tools": len(described),
272
+ "governance_without_handler": sorted(governed - registered),
273
+ "handler_without_governance": sorted(registered - governed),
274
+ "handler_without_description": sorted(registered - described),
275
+ "description_without_handler": sorted(described - registered),
276
+ }
277
+
278
+ def manifest(self) -> Dict[str, Any]:
279
+ """Serializable tool registry contract for runtime/admin inspection."""
280
+
281
+ tools = []
282
+ for name in sorted(self.registered_tools() | set(self.governance) | set(self.descriptions)):
283
+ policy = self.policy_for(name, {})
284
+ tools.append({
285
+ "name": name,
286
+ "registered": name in self.handlers,
287
+ "governed": name in self.governance,
288
+ "described": name in self.descriptions,
289
+ "description": self.descriptions.get(name, ""),
290
+ "policy": dict(policy),
291
+ "permission": dict(self.permission(name, {})),
292
+ })
293
+ diagnostics = self.diagnostics()
294
+ return {
295
+ "status": "ok" if diagnostics["ready"] else "degraded",
296
+ "catalog_brief": self.catalog_brief.strip(),
297
+ "diagnostics": diagnostics,
298
+ "tools": tools,
299
+ }
300
+
254
301
  def execute(self, action: str, args: Dict[str, Any], *, error_cls: type[Exception]) -> Dict[str, Any]:
255
302
  handler = self.handlers.get(action)
256
303
  if handler is None:
@@ -19,7 +19,7 @@ from pathlib import Path
19
19
  from typing import Any, Callable, Dict, Iterable, List, Optional
20
20
 
21
21
 
22
- WORKSPACE_OS_VERSION = "7.1.0"
22
+ WORKSPACE_OS_VERSION = "7.2.0"
23
23
 
24
24
  # Workspace types separate single-user Personal workspaces from shared
25
25
  # Organization workspaces. Both keep the same local-first JSON store; the type
@@ -90,6 +90,12 @@ class ToolDispatchService:
90
90
  def permissions(self) -> list[ToolPermission]:
91
91
  return self.registry.permissions()
92
92
 
93
+ def diagnostics(self) -> Dict[str, Any]:
94
+ return self.registry.diagnostics()
95
+
96
+ def manifest(self) -> Dict[str, Any]:
97
+ return self.registry.manifest()
98
+
93
99
  def check_role(self, tool_name: str, current_user: str) -> None:
94
100
  if tool_name not in self.registry.admin_only_tools:
95
101
  return
@@ -131,6 +137,14 @@ def list_tool_permissions() -> list:
131
137
  return DEFAULT_TOOL_DISPATCH_SERVICE.permissions()
132
138
 
133
139
 
140
+ def tool_registry_diagnostics() -> Dict[str, Any]:
141
+ return DEFAULT_TOOL_DISPATCH_SERVICE.diagnostics()
142
+
143
+
144
+ def tool_registry_manifest() -> Dict[str, Any]:
145
+ return DEFAULT_TOOL_DISPATCH_SERVICE.manifest()
146
+
147
+
134
148
  def check_tool_role(tool_name: str, current_user: str) -> None:
135
149
  DEFAULT_TOOL_DISPATCH_SERVICE.check_role(tool_name, current_user)
136
150
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ltcai",
3
- "version": "7.1.0",
3
+ "version": "7.2.0",
4
4
  "description": "Lattice AI — local-first Digital Brain that keeps your knowledge durable across any AI model.",
5
5
  "homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
6
6
  "repository": {
@@ -1654,7 +1654,7 @@ dependencies = [
1654
1654
 
1655
1655
  [[package]]
1656
1656
  name = "lattice-ai-desktop"
1657
- version = "7.1.0"
1657
+ version = "7.2.0"
1658
1658
  dependencies = [
1659
1659
  "plist",
1660
1660
  "serde",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "lattice-ai-desktop"
3
- version = "7.1.0"
3
+ version = "7.2.0"
4
4
  description = "Lattice AI Digital Brain desktop shell"
5
5
  authors = ["TaeSoo Park"]
6
6
  edition = "2021"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://schema.tauri.app/config/2",
3
3
  "productName": "Lattice AI",
4
- "version": "7.1.0",
4
+ "version": "7.2.0",
5
5
  "identifier": "ai.lattice.desktop",
6
6
  "build": {
7
7
  "beforeDevCommand": "npm run frontend:dev",