ltcai 9.9.5 → 9.9.9

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 (132) hide show
  1. package/README.md +41 -36
  2. package/docs/CHANGELOG.md +193 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/ONBOARDING.md +1 -1
  6. package/docs/OPERATIONS.md +3 -1
  7. package/docs/PERMISSION_MODE.md +107 -0
  8. package/docs/SURFACE_PARITY.md +38 -8
  9. package/docs/TRUST_MODEL.md +1 -1
  10. package/docs/WHY_LATTICE.md +1 -1
  11. package/docs/kg-schema.md +1 -1
  12. package/lattice_brain/__init__.py +1 -1
  13. package/lattice_brain/context.py +39 -4
  14. package/lattice_brain/graph/_kg_common.py +189 -14
  15. package/lattice_brain/graph/curator.py +62 -0
  16. package/lattice_brain/graph/discovery.py +76 -0
  17. package/lattice_brain/graph/ingest.py +37 -7
  18. package/lattice_brain/graph/retrieval_vector.py +10 -0
  19. package/lattice_brain/ingestion.py +9 -106
  20. package/lattice_brain/ingestion_jobs.py +133 -0
  21. package/lattice_brain/runtime/multi_agent.py +1 -1
  22. package/latticeai/__init__.py +1 -1
  23. package/latticeai/api/brain_intelligence.py +8 -0
  24. package/latticeai/api/chat.py +9 -0
  25. package/latticeai/api/chat_agent_http.py +119 -2
  26. package/latticeai/api/chat_contracts.py +9 -0
  27. package/latticeai/api/chat_documents.py +16 -0
  28. package/latticeai/api/evidence_actions.py +48 -0
  29. package/latticeai/api/permission_mode.py +61 -0
  30. package/latticeai/api/project_sessions.py +112 -0
  31. package/latticeai/api/voice_capture.py +89 -0
  32. package/latticeai/app_factory.py +53 -0
  33. package/latticeai/core/agent.py +381 -43
  34. package/latticeai/core/agent_permission.py +160 -0
  35. package/latticeai/core/agent_profiles.py +110 -0
  36. package/latticeai/core/artifact_ledger.py +109 -0
  37. package/latticeai/core/context_builder.py +101 -2
  38. package/latticeai/core/legacy_compatibility.py +1 -1
  39. package/latticeai/core/marketplace.py +1 -1
  40. package/latticeai/core/permission_mode.py +296 -0
  41. package/latticeai/core/project_sessions.py +337 -0
  42. package/latticeai/core/run_explain.py +421 -0
  43. package/latticeai/core/run_store.py +5 -0
  44. package/latticeai/core/workspace_os.py +15 -80
  45. package/latticeai/core/workspace_review_items.py +107 -0
  46. package/latticeai/integrations/telegram_bot.py +188 -0
  47. package/latticeai/runtime/chat_wiring.py +8 -0
  48. package/latticeai/runtime/context_runtime.py +11 -0
  49. package/latticeai/runtime/permission_mode_wiring.py +116 -0
  50. package/latticeai/runtime/router_registration.py +9 -0
  51. package/latticeai/services/app_context.py +3 -0
  52. package/latticeai/services/architecture_readiness.py +1 -1
  53. package/latticeai/services/brain_intelligence.py +105 -0
  54. package/latticeai/services/evidence_actions.py +254 -0
  55. package/latticeai/services/funnel_metrics.py +111 -16
  56. package/latticeai/services/local_knowledge.py +26 -0
  57. package/latticeai/services/memory_service.py +12 -0
  58. package/latticeai/services/permission_mode_service.py +170 -0
  59. package/latticeai/services/product_readiness.py +1 -1
  60. package/latticeai/services/tool_dispatch.py +73 -4
  61. package/latticeai/services/voice_capture.py +189 -0
  62. package/package.json +5 -3
  63. package/scripts/check_bundle_budget.mjs +11 -0
  64. package/scripts/check_current_release_docs.mjs +1 -1
  65. package/scripts/check_i18n_namespace_coverage.mjs +148 -0
  66. package/skills/meeting_notes/SKILL.md +82 -0
  67. package/skills/meeting_notes/examples.md +41 -0
  68. package/skills/meeting_notes/risk.json +9 -0
  69. package/skills/meeting_notes/schema.json +55 -0
  70. package/skills/weekly_review/SKILL.md +75 -0
  71. package/skills/weekly_review/examples.md +39 -0
  72. package/skills/weekly_review/risk.json +9 -0
  73. package/skills/weekly_review/schema.json +55 -0
  74. package/src-tauri/Cargo.lock +1 -1
  75. package/src-tauri/Cargo.toml +1 -1
  76. package/src-tauri/tauri.conf.json +1 -1
  77. package/static/app/asset-manifest.json +37 -29
  78. package/static/app/assets/Act-BFbpl2_Y.js +1 -0
  79. package/static/app/assets/AdminConsole-DaZPha9Y.js +1 -0
  80. package/static/app/assets/Brain-DGrV0heZ.js +321 -0
  81. package/static/app/assets/BrainHome-Re6Zxh_W.js +2 -0
  82. package/static/app/assets/BrainSignals-BQ11jDpJ.js +1 -0
  83. package/static/app/assets/Capture-DRaf1zLH.js +1 -0
  84. package/static/app/assets/CommandPalette-DAKPm4uJ.js +1 -0
  85. package/static/app/assets/LanguageSwitcher-BgTwHoWv.js +1 -0
  86. package/static/app/assets/Library-BVtrSt12.js +1 -0
  87. package/static/app/assets/LivingBrain-7glOmK0R.js +1 -0
  88. package/static/app/assets/ProductFlow-cB3EFinZ.js +1 -0
  89. package/static/app/assets/ReviewCard-CShhHS-w.js +3 -0
  90. package/static/app/assets/System-B4nQDy8a.js +1 -0
  91. package/static/app/assets/{bot-VmP0kkeA.js → bot-DtKkLfXj.js} +1 -1
  92. package/static/app/assets/brain-wVV63_1z.js +1 -0
  93. package/static/app/assets/button-BTxZDMFJ.js +1 -0
  94. package/static/app/assets/{circle-pause-T9POo4qy.js → circle-pause--qxF9-EO.js} +1 -1
  95. package/static/app/assets/{circle-play-Bz5iTD0p.js → circle-play-89x3S6jm.js} +1 -1
  96. package/static/app/assets/{cpu-CJhjRtNq.js → cpu-DTUI63vo.js} +1 -1
  97. package/static/app/assets/download-EJS5_O0f.js +1 -0
  98. package/static/app/assets/{folder-open-B2K_22VI.js → folder-open-BL9zYTnc.js} +1 -1
  99. package/static/app/assets/{hard-drive-CjJtYqHf.js → hard-drive-BFnwi1Lu.js} +1 -1
  100. package/static/app/assets/index-BFtDYI6l.js +10 -0
  101. package/static/app/assets/index-BY9bpxyx.css +2 -0
  102. package/static/app/assets/input-DEz5UjBz.js +1 -0
  103. package/static/app/assets/{network-Ck1nqc-v.js → network-zKa3jAwl.js} +1 -1
  104. package/static/app/assets/primitives-jNrZm2O9.js +1 -0
  105. package/static/app/assets/search-ku6-_6wY.js +1 -0
  106. package/static/app/assets/shield-alert-CpHeCsaF.js +1 -0
  107. package/static/app/assets/textarea-DpcQkn75.js +1 -0
  108. package/static/app/assets/{useFocusTrap-CsnIhsDJ.js → useFocusTrap-58TNCONM.js} +1 -1
  109. package/static/app/assets/{navigation-BPQqEQwr.js → useQuery-CcVlXlBd.js} +1 -1
  110. package/static/app/assets/users-C0cFMj6o.js +1 -0
  111. package/static/app/assets/utils-DYf7pqrR.js +7 -0
  112. package/static/app/assets/workspace-CjthHsLN.js +1 -0
  113. package/static/app/index.html +4 -4
  114. package/static/sw.js +1 -1
  115. package/static/app/assets/Act-DmxfbqBp.js +0 -1
  116. package/static/app/assets/Brain-DK_zKCda.js +0 -321
  117. package/static/app/assets/BrainHome-CCE_0hni.js +0 -2
  118. package/static/app/assets/BrainSignals-r_J68_zh.js +0 -1
  119. package/static/app/assets/Capture-CvxwzpJZ.js +0 -1
  120. package/static/app/assets/CommandPalette-CVLiFIQC.js +0 -1
  121. package/static/app/assets/Library-4As9VUvy.js +0 -1
  122. package/static/app/assets/LivingBrain-BVWk58MW.js +0 -1
  123. package/static/app/assets/ProductFlow-B-Tz0Um4.js +0 -1
  124. package/static/app/assets/ReviewCard-Va3fPQuj.js +0 -3
  125. package/static/app/assets/System-DSR2zKWd.js +0 -1
  126. package/static/app/assets/index-7FAfYm4v.css +0 -2
  127. package/static/app/assets/index-CEu0Wqjl.js +0 -10
  128. package/static/app/assets/input-CSi6OBJ9.js +0 -1
  129. package/static/app/assets/primitives-B2f9N7Dh.js +0 -1
  130. package/static/app/assets/shield-alert-yA_Y8lOB.js +0 -1
  131. package/static/app/assets/textarea-C4wM0D-Z.js +0 -1
  132. package/static/app/assets/utils-BqoznzGP.js +0 -7
@@ -0,0 +1,112 @@
1
+ """Project session router — the multi-turn project loop (v9.9.6).
2
+
3
+ CRUD over :class:`~latticeai.core.project_sessions.ProjectSessionStore`. A
4
+ project session carries what a single agent run cannot: the files the project
5
+ already produced, what is still open, and the last honest verification result.
6
+ Runs fold themselves in through the agent HTTP layer; this surface is what the
7
+ user drives.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any, Callable, List, Optional
13
+
14
+ from fastapi import APIRouter, HTTPException, Request
15
+ from pydantic import BaseModel, Field
16
+
17
+
18
+ class CreateProjectRequest(BaseModel):
19
+ title: str = Field(..., min_length=1, max_length=200)
20
+ goal: str = ""
21
+
22
+
23
+ class UpdateProjectRequest(BaseModel):
24
+ title: Optional[str] = None
25
+ goal: Optional[str] = None
26
+ status: Optional[str] = None
27
+
28
+
29
+ class TodosRequest(BaseModel):
30
+ todos: List[Any] = Field(default_factory=list)
31
+
32
+
33
+ def create_project_sessions_router(
34
+ *,
35
+ store: Any,
36
+ require_user: Callable[[Request], Any],
37
+ gate_read: Optional[Callable[[Request], Optional[str]]] = None,
38
+ gate_write: Optional[Callable[[Request], Optional[str]]] = None,
39
+ ) -> APIRouter:
40
+ router = APIRouter()
41
+
42
+ def _scope(request: Request, write: bool = False):
43
+ gate = gate_write if write else gate_read
44
+ return gate(request) if gate is not None else None
45
+
46
+ @router.get("/api/projects")
47
+ async def list_projects(request: Request, status: str = "active"):
48
+ user = require_user(request)
49
+ return store.list(
50
+ user_email=user, workspace_id=_scope(request), status=status
51
+ )
52
+
53
+ @router.post("/api/projects")
54
+ async def create_project(req: CreateProjectRequest, request: Request):
55
+ user = require_user(request)
56
+ return store.create(
57
+ title=req.title,
58
+ goal=req.goal,
59
+ user_email=user,
60
+ workspace_id=_scope(request, write=True),
61
+ )
62
+
63
+ @router.get("/api/projects/{session_id}")
64
+ async def get_project(session_id: str, request: Request):
65
+ user = require_user(request)
66
+ record = store.get(session_id, user_email=user, workspace_id=_scope(request))
67
+ if record is None:
68
+ raise HTTPException(status_code=404, detail="project not found")
69
+ return record
70
+
71
+ @router.patch("/api/projects/{session_id}")
72
+ async def update_project(session_id: str, req: UpdateProjectRequest, request: Request):
73
+ user = require_user(request)
74
+ record = store.update(
75
+ session_id,
76
+ title=req.title,
77
+ goal=req.goal,
78
+ status=req.status,
79
+ user_email=user,
80
+ workspace_id=_scope(request, write=True),
81
+ )
82
+ if record is None:
83
+ raise HTTPException(status_code=404, detail="project not found")
84
+ return record
85
+
86
+ @router.put("/api/projects/{session_id}/todos")
87
+ async def set_todos(session_id: str, req: TodosRequest, request: Request):
88
+ user = require_user(request)
89
+ record = store.set_todos(
90
+ session_id,
91
+ req.todos,
92
+ user_email=user,
93
+ workspace_id=_scope(request, write=True),
94
+ )
95
+ if record is None:
96
+ raise HTTPException(status_code=404, detail="project not found")
97
+ return record
98
+
99
+ @router.delete("/api/projects/{session_id}")
100
+ async def delete_project(session_id: str, request: Request):
101
+ user = require_user(request)
102
+ removed = store.delete(
103
+ session_id, user_email=user, workspace_id=_scope(request, write=True)
104
+ )
105
+ if not removed:
106
+ raise HTTPException(status_code=404, detail="project not found")
107
+ return {"status": "deleted", "id": session_id}
108
+
109
+ return router
110
+
111
+
112
+ __all__ = ["create_project_sessions_router"]
@@ -0,0 +1,89 @@
1
+ """Voice memo capture router (v9.9.7).
2
+
3
+ ``GET /api/capture/voice/status`` — what this install can actually do with a
4
+ voice memo (capture always; transcription only when a local transcriber
5
+ exists).
6
+
7
+ ``POST /api/capture/voice`` — ingest one memo (multipart upload) through the
8
+ unified ingestion pipeline. Local-only: audio is written to the workspace data
9
+ dir and transcribed on this machine or not at all.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import shutil
16
+ import tempfile
17
+ from pathlib import Path
18
+ from typing import Any, Callable, Optional
19
+
20
+ from fastapi import APIRouter, File, Form, HTTPException, Request, UploadFile
21
+
22
+ LOGGER = logging.getLogger(__name__)
23
+
24
+
25
+ def create_voice_capture_router(
26
+ *,
27
+ service: Any,
28
+ require_user: Callable[[Request], Any],
29
+ gate_write: Optional[Callable[[Request], Optional[str]]] = None,
30
+ append_audit_event: Optional[Callable[..., None]] = None,
31
+ ) -> APIRouter:
32
+ router = APIRouter()
33
+
34
+ @router.get("/api/capture/voice/status")
35
+ async def voice_status(request: Request):
36
+ require_user(request)
37
+ return service.status()
38
+
39
+ @router.post("/api/capture/voice")
40
+ async def voice_capture(
41
+ request: Request,
42
+ file: UploadFile = File(...),
43
+ title: str = Form(""),
44
+ transcript: str = Form(""),
45
+ conversation_id: str = Form(""),
46
+ ):
47
+ user = require_user(request)
48
+ scope = gate_write(request) if gate_write is not None else None
49
+ suffix = Path(file.filename or "memo.m4a").suffix or ".m4a"
50
+ tmp_path: Optional[Path] = None
51
+ try:
52
+ with tempfile.NamedTemporaryFile(
53
+ prefix="ltcai-voice-", suffix=suffix, delete=False
54
+ ) as handle:
55
+ shutil.copyfileobj(file.file, handle)
56
+ tmp_path = Path(handle.name)
57
+ result = service.capture(
58
+ str(tmp_path),
59
+ title=title or Path(file.filename or "").stem,
60
+ user_email=user,
61
+ workspace_id=scope,
62
+ conversation_id=conversation_id or None,
63
+ transcript=transcript or None,
64
+ )
65
+ except Exception as exc: # noqa: BLE001 — surface a clean error, not a 500 trace
66
+ LOGGER.exception("voice capture failed")
67
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
68
+ finally:
69
+ if tmp_path is not None:
70
+ try:
71
+ tmp_path.unlink(missing_ok=True)
72
+ except OSError:
73
+ pass
74
+ if append_audit_event is not None:
75
+ try:
76
+ append_audit_event(
77
+ "voice_capture",
78
+ user_email=user,
79
+ status=result.get("status"),
80
+ transcription=result.get("transcription"),
81
+ )
82
+ except Exception: # noqa: BLE001 — audit is advisory
83
+ LOGGER.debug("voice capture audit failed", exc_info=True)
84
+ return result
85
+
86
+ return router
87
+
88
+
89
+ __all__ = ["create_voice_capture_router"]
@@ -857,6 +857,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
857
857
  SEARCH_SERVICE = _context_runtime["SEARCH_SERVICE"]
858
858
  BRAIN_MEMORY = _context_runtime["BRAIN_MEMORY"]
859
859
  CONTEXT_ASSEMBLER = _context_runtime["CONTEXT_ASSEMBLER"]
860
+ ARTIFACT_LEDGER = _context_runtime["ARTIFACT_LEDGER"]
860
861
  _scoped_hybrid_search = _context_runtime["_scoped_hybrid_search"]
861
862
 
862
863
 
@@ -911,6 +912,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
911
912
  local_kg_watcher=LOCAL_KG_WATCHER,
912
913
  chat_service=CHAT_SERVICE,
913
914
  context_assembler=CONTEXT_ASSEMBLER,
915
+ artifact_ledger=ARTIFACT_LEDGER,
914
916
  brain_memory=BRAIN_MEMORY,
915
917
  ingestion_pipeline=INGESTION_PIPELINE if ENABLE_GRAPH else None,
916
918
  chat_agent_runtime=CHAT_AGENT_RUNTIME,
@@ -1149,6 +1151,57 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
1149
1151
  return None
1150
1152
  return PLATFORM.allowed_scopes(user)
1151
1153
 
1154
+ # Evidence → action (v9.9.6): an answer's citations become ready-to-send,
1155
+ # evidence-scoped follow-ups. Deterministic composition only — execution
1156
+ # stays on the chat/file-generation path.
1157
+ from latticeai.api.evidence_actions import create_evidence_actions_router
1158
+ from latticeai.services.evidence_actions import EvidenceActionService
1159
+
1160
+ EVIDENCE_ACTIONS_SERVICE = EvidenceActionService(
1161
+ node_reader=getattr(KNOWLEDGE_GRAPH, "get_node", None)
1162
+ )
1163
+ app.include_router(
1164
+ create_evidence_actions_router(
1165
+ service=EVIDENCE_ACTIONS_SERVICE,
1166
+ require_user=require_user,
1167
+ allowed_workspaces_for=_allowed_workspaces_for,
1168
+ )
1169
+ )
1170
+
1171
+ # Voice memo capture (v9.9.7): the shortest path from a thought to the
1172
+ # Brain. Transcription is an optional local port — absent, the memo is
1173
+ # still stored and the response says it is not searchable.
1174
+ from latticeai.api.voice_capture import create_voice_capture_router
1175
+ from latticeai.services.voice_capture import VoiceCaptureService
1176
+
1177
+ VOICE_CAPTURE = VoiceCaptureService(
1178
+ pipeline=INGESTION_PIPELINE if ENABLE_GRAPH else None,
1179
+ transcriber=None,
1180
+ )
1181
+ app.include_router(
1182
+ create_voice_capture_router(
1183
+ service=VOICE_CAPTURE,
1184
+ require_user=require_user,
1185
+ gate_write=PLATFORM.gate_write,
1186
+ append_audit_event=append_audit_event,
1187
+ )
1188
+ )
1189
+
1190
+ # Multi-turn project loop (v9.9.6): work that spans several runs keeps its
1191
+ # files, open TODOs, and last honest verification in one project session.
1192
+ from latticeai.api.project_sessions import create_project_sessions_router
1193
+ from latticeai.core.project_sessions import ProjectSessionStore
1194
+
1195
+ PROJECT_SESSIONS = ProjectSessionStore(DATA_DIR / "project_sessions")
1196
+ app.include_router(
1197
+ create_project_sessions_router(
1198
+ store=PROJECT_SESSIONS,
1199
+ require_user=require_user,
1200
+ gate_read=PLATFORM.gate_read,
1201
+ gate_write=PLATFORM.gate_write,
1202
+ )
1203
+ )
1204
+
1152
1205
  tool_router_context, interaction_router_context = build_interaction_contexts(
1153
1206
  config=CONFIG,
1154
1207
  ingestion_pipeline=INGESTION_PIPELINE,