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.
- package/README.md +41 -36
- package/docs/CHANGELOG.md +193 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +3 -1
- package/docs/PERMISSION_MODE.md +107 -0
- package/docs/SURFACE_PARITY.md +38 -8
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/kg-schema.md +1 -1
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/context.py +39 -4
- package/lattice_brain/graph/_kg_common.py +189 -14
- package/lattice_brain/graph/curator.py +62 -0
- package/lattice_brain/graph/discovery.py +76 -0
- package/lattice_brain/graph/ingest.py +37 -7
- package/lattice_brain/graph/retrieval_vector.py +10 -0
- package/lattice_brain/ingestion.py +9 -106
- package/lattice_brain/ingestion_jobs.py +133 -0
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/brain_intelligence.py +8 -0
- package/latticeai/api/chat.py +9 -0
- package/latticeai/api/chat_agent_http.py +119 -2
- package/latticeai/api/chat_contracts.py +9 -0
- package/latticeai/api/chat_documents.py +16 -0
- package/latticeai/api/evidence_actions.py +48 -0
- package/latticeai/api/permission_mode.py +61 -0
- package/latticeai/api/project_sessions.py +112 -0
- package/latticeai/api/voice_capture.py +89 -0
- package/latticeai/app_factory.py +53 -0
- package/latticeai/core/agent.py +381 -43
- package/latticeai/core/agent_permission.py +160 -0
- package/latticeai/core/agent_profiles.py +110 -0
- package/latticeai/core/artifact_ledger.py +109 -0
- package/latticeai/core/context_builder.py +101 -2
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/permission_mode.py +296 -0
- package/latticeai/core/project_sessions.py +337 -0
- package/latticeai/core/run_explain.py +421 -0
- package/latticeai/core/run_store.py +5 -0
- package/latticeai/core/workspace_os.py +15 -80
- package/latticeai/core/workspace_review_items.py +107 -0
- package/latticeai/integrations/telegram_bot.py +188 -0
- package/latticeai/runtime/chat_wiring.py +8 -0
- package/latticeai/runtime/context_runtime.py +11 -0
- package/latticeai/runtime/permission_mode_wiring.py +116 -0
- package/latticeai/runtime/router_registration.py +9 -0
- package/latticeai/services/app_context.py +3 -0
- package/latticeai/services/architecture_readiness.py +1 -1
- package/latticeai/services/brain_intelligence.py +105 -0
- package/latticeai/services/evidence_actions.py +254 -0
- package/latticeai/services/funnel_metrics.py +111 -16
- package/latticeai/services/local_knowledge.py +26 -0
- package/latticeai/services/memory_service.py +12 -0
- package/latticeai/services/permission_mode_service.py +170 -0
- package/latticeai/services/product_readiness.py +1 -1
- package/latticeai/services/tool_dispatch.py +73 -4
- package/latticeai/services/voice_capture.py +189 -0
- package/package.json +5 -3
- package/scripts/check_bundle_budget.mjs +11 -0
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/check_i18n_namespace_coverage.mjs +148 -0
- package/skills/meeting_notes/SKILL.md +82 -0
- package/skills/meeting_notes/examples.md +41 -0
- package/skills/meeting_notes/risk.json +9 -0
- package/skills/meeting_notes/schema.json +55 -0
- package/skills/weekly_review/SKILL.md +75 -0
- package/skills/weekly_review/examples.md +39 -0
- package/skills/weekly_review/risk.json +9 -0
- package/skills/weekly_review/schema.json +55 -0
- 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 +37 -29
- package/static/app/assets/Act-BFbpl2_Y.js +1 -0
- package/static/app/assets/AdminConsole-DaZPha9Y.js +1 -0
- package/static/app/assets/Brain-DGrV0heZ.js +321 -0
- package/static/app/assets/BrainHome-Re6Zxh_W.js +2 -0
- package/static/app/assets/BrainSignals-BQ11jDpJ.js +1 -0
- package/static/app/assets/Capture-DRaf1zLH.js +1 -0
- package/static/app/assets/CommandPalette-DAKPm4uJ.js +1 -0
- package/static/app/assets/LanguageSwitcher-BgTwHoWv.js +1 -0
- package/static/app/assets/Library-BVtrSt12.js +1 -0
- package/static/app/assets/LivingBrain-7glOmK0R.js +1 -0
- package/static/app/assets/ProductFlow-cB3EFinZ.js +1 -0
- package/static/app/assets/ReviewCard-CShhHS-w.js +3 -0
- package/static/app/assets/System-B4nQDy8a.js +1 -0
- package/static/app/assets/{bot-VmP0kkeA.js → bot-DtKkLfXj.js} +1 -1
- package/static/app/assets/brain-wVV63_1z.js +1 -0
- package/static/app/assets/button-BTxZDMFJ.js +1 -0
- package/static/app/assets/{circle-pause-T9POo4qy.js → circle-pause--qxF9-EO.js} +1 -1
- package/static/app/assets/{circle-play-Bz5iTD0p.js → circle-play-89x3S6jm.js} +1 -1
- package/static/app/assets/{cpu-CJhjRtNq.js → cpu-DTUI63vo.js} +1 -1
- package/static/app/assets/download-EJS5_O0f.js +1 -0
- package/static/app/assets/{folder-open-B2K_22VI.js → folder-open-BL9zYTnc.js} +1 -1
- package/static/app/assets/{hard-drive-CjJtYqHf.js → hard-drive-BFnwi1Lu.js} +1 -1
- package/static/app/assets/index-BFtDYI6l.js +10 -0
- package/static/app/assets/index-BY9bpxyx.css +2 -0
- package/static/app/assets/input-DEz5UjBz.js +1 -0
- package/static/app/assets/{network-Ck1nqc-v.js → network-zKa3jAwl.js} +1 -1
- package/static/app/assets/primitives-jNrZm2O9.js +1 -0
- package/static/app/assets/search-ku6-_6wY.js +1 -0
- package/static/app/assets/shield-alert-CpHeCsaF.js +1 -0
- package/static/app/assets/textarea-DpcQkn75.js +1 -0
- package/static/app/assets/{useFocusTrap-CsnIhsDJ.js → useFocusTrap-58TNCONM.js} +1 -1
- package/static/app/assets/{navigation-BPQqEQwr.js → useQuery-CcVlXlBd.js} +1 -1
- package/static/app/assets/users-C0cFMj6o.js +1 -0
- package/static/app/assets/utils-DYf7pqrR.js +7 -0
- package/static/app/assets/workspace-CjthHsLN.js +1 -0
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/static/app/assets/Act-DmxfbqBp.js +0 -1
- package/static/app/assets/Brain-DK_zKCda.js +0 -321
- package/static/app/assets/BrainHome-CCE_0hni.js +0 -2
- package/static/app/assets/BrainSignals-r_J68_zh.js +0 -1
- package/static/app/assets/Capture-CvxwzpJZ.js +0 -1
- package/static/app/assets/CommandPalette-CVLiFIQC.js +0 -1
- package/static/app/assets/Library-4As9VUvy.js +0 -1
- package/static/app/assets/LivingBrain-BVWk58MW.js +0 -1
- package/static/app/assets/ProductFlow-B-Tz0Um4.js +0 -1
- package/static/app/assets/ReviewCard-Va3fPQuj.js +0 -3
- package/static/app/assets/System-DSR2zKWd.js +0 -1
- package/static/app/assets/index-7FAfYm4v.css +0 -2
- package/static/app/assets/index-CEu0Wqjl.js +0 -10
- package/static/app/assets/input-CSi6OBJ9.js +0 -1
- package/static/app/assets/primitives-B2f9N7Dh.js +0 -1
- package/static/app/assets/shield-alert-yA_Y8lOB.js +0 -1
- package/static/app/assets/textarea-C4wM0D-Z.js +0 -1
- 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"]
|
package/latticeai/app_factory.py
CHANGED
|
@@ -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,
|