ltcai 10.0.0 → 10.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -32
- package/docs/CHANGELOG.md +204 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/HYBRID_CLOUD_KG_STREAMING.md +101 -0
- package/docs/MYPY_BACKLOG.md +98 -0
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +1 -1
- 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/archive.py +5 -4
- package/lattice_brain/conversations.py +14 -3
- package/lattice_brain/embeddings.py +12 -2
- package/lattice_brain/graph/_kg_common.py +5 -5
- package/lattice_brain/graph/_kg_fsutil.py +5 -4
- package/lattice_brain/graph/discovery.py +4 -3
- package/lattice_brain/graph/discovery_index.py +0 -1
- package/lattice_brain/graph/documents.py +3 -2
- package/lattice_brain/graph/fusion.py +4 -0
- package/lattice_brain/graph/ingest.py +12 -2
- package/lattice_brain/graph/projection.py +3 -2
- package/lattice_brain/graph/provenance.py +5 -3
- package/lattice_brain/graph/rerank.py +4 -1
- package/lattice_brain/graph/retrieval.py +0 -1
- package/lattice_brain/graph/retrieval_docgen.py +0 -1
- package/lattice_brain/graph/retrieval_reads.py +0 -1
- package/lattice_brain/graph/retrieval_vector.py +0 -1
- package/lattice_brain/graph/schema.py +4 -3
- package/lattice_brain/graph/store.py +18 -4
- package/lattice_brain/graph/write_master.py +46 -1
- package/lattice_brain/ingestion.py +4 -1
- package/lattice_brain/portability.py +5 -2
- package/lattice_brain/quality.py +12 -5
- package/lattice_brain/quiet.py +43 -0
- package/lattice_brain/runtime/agent_runtime.py +19 -10
- package/lattice_brain/runtime/hooks.py +3 -2
- package/lattice_brain/runtime/multi_agent.py +2 -3
- package/lattice_brain/sensitivity.py +94 -0
- package/lattice_brain/storage/base.py +30 -2
- package/lattice_brain/storage/migration.py +3 -2
- package/lattice_brain/storage/postgres.py +2 -2
- package/lattice_brain/storage/sqlite.py +6 -4
- package/lattice_brain/workflow.py +4 -2
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +1 -1
- package/latticeai/api/agents.py +4 -2
- package/latticeai/api/auth.py +5 -1
- package/latticeai/api/automation_intelligence.py +2 -1
- package/latticeai/api/browser.py +3 -2
- package/latticeai/api/chat.py +28 -17
- package/latticeai/api/chat_agent_http.py +22 -8
- package/latticeai/api/chat_contracts.py +4 -0
- package/latticeai/api/chat_documents.py +6 -2
- package/latticeai/api/chat_hybrid.py +82 -0
- package/latticeai/api/computer_use.py +8 -3
- package/latticeai/api/knowledge_graph.py +1 -1
- package/latticeai/api/mcp.py +4 -4
- package/latticeai/api/models.py +5 -2
- package/latticeai/api/network_boundary.py +220 -0
- package/latticeai/api/permissions.py +0 -1
- package/latticeai/api/realtime.py +1 -1
- package/latticeai/api/security_dashboard.py +1 -1
- package/latticeai/api/setup.py +16 -3
- package/latticeai/api/static_routes.py +2 -1
- package/latticeai/api/tools.py +12 -8
- package/latticeai/api/voice_capture.py +3 -1
- package/latticeai/api/workflow_designer.py +1 -1
- package/latticeai/api/workspace.py +1 -2
- package/latticeai/app_factory.py +174 -145
- package/latticeai/cli/entrypoint.py +6 -4
- package/latticeai/core/agent.py +55 -495
- package/latticeai/core/agent_eval.py +2 -2
- package/latticeai/core/agent_helpers.py +493 -0
- package/latticeai/core/agent_prompts.py +0 -1
- package/latticeai/core/agent_registry.py +3 -1
- package/latticeai/core/agent_state.py +41 -0
- package/latticeai/core/audit.py +1 -1
- package/latticeai/core/builtin_hooks.py +2 -1
- package/latticeai/core/config.py +0 -1
- package/latticeai/core/embedding_providers.py +12 -1
- package/latticeai/core/file_generation.py +3 -0
- package/latticeai/core/invitations.py +4 -1
- package/latticeai/core/io_utils.py +3 -1
- package/latticeai/core/legacy_compatibility.py +1 -2
- package/latticeai/core/local_embeddings.py +12 -1
- package/latticeai/core/marketplace.py +1 -2
- package/latticeai/core/mcp_registry.py +0 -1
- package/latticeai/core/model_compat.py +1 -1
- package/latticeai/core/model_resolution.py +1 -1
- package/latticeai/core/network_boundary.py +168 -0
- package/latticeai/core/oidc.py +3 -0
- package/latticeai/core/permission_mode.py +6 -6
- package/latticeai/core/plugins.py +3 -2
- package/latticeai/core/policy.py +0 -1
- package/latticeai/core/quiet.py +84 -0
- package/latticeai/core/realtime.py +3 -2
- package/latticeai/core/run_store.py +4 -2
- package/latticeai/core/security.py +4 -0
- package/latticeai/core/users.py +5 -3
- package/latticeai/core/workspace_graph_trace.py +3 -0
- package/latticeai/core/workspace_os.py +65 -273
- package/latticeai/core/workspace_os_constants.py +126 -0
- package/latticeai/core/workspace_os_state.py +180 -0
- package/latticeai/core/workspace_os_utils.py +3 -1
- package/latticeai/core/workspace_permissions.py +1 -0
- package/latticeai/core/workspace_snapshots.py +3 -1
- package/latticeai/core/workspace_timeline.py +3 -1
- package/latticeai/integrations/telegram_bot.py +25 -16
- package/latticeai/models/router.py +6 -3
- package/latticeai/runtime/access_runtime.py +3 -1
- package/latticeai/runtime/audit_runtime.py +3 -2
- package/latticeai/runtime/chat_wiring.py +4 -1
- package/latticeai/runtime/history_runtime.py +1 -1
- package/latticeai/runtime/history_writer.py +138 -0
- package/latticeai/runtime/lifespan_runtime.py +3 -1
- package/latticeai/runtime/network_boundary_wiring.py +124 -0
- package/latticeai/runtime/persistence_runtime.py +3 -1
- package/latticeai/runtime/router_registration.py +11 -1
- package/latticeai/services/architecture_readiness.py +1 -2
- package/latticeai/services/change_proposals.py +2 -1
- package/latticeai/services/cloud_egress_audit.py +85 -0
- package/latticeai/services/cloud_extraction.py +129 -0
- package/latticeai/services/cloud_streaming.py +266 -0
- package/latticeai/services/cloud_token_guard.py +84 -0
- package/latticeai/services/command_center.py +2 -1
- package/latticeai/services/folder_watch.py +3 -0
- package/latticeai/services/funnel_metrics.py +3 -1
- package/latticeai/services/hybrid_chat.py +265 -0
- package/latticeai/services/hybrid_context.py +228 -0
- package/latticeai/services/hybrid_policy.py +178 -0
- package/latticeai/services/memory_service.py +1 -1
- package/latticeai/services/model_catalog.py +10 -1
- package/latticeai/services/model_engines.py +35 -14
- package/latticeai/services/model_loading.py +3 -2
- package/latticeai/services/model_runtime.py +68 -17
- package/latticeai/services/multimodal_streaming.py +123 -0
- package/latticeai/services/network_boundary_service.py +154 -0
- package/latticeai/services/openai_compatible_adapter.py +100 -0
- package/latticeai/services/p_reinforce.py +4 -0
- package/latticeai/services/platform_runtime.py +9 -4
- package/latticeai/services/process_audit.py +0 -1
- package/latticeai/services/product_readiness.py +1 -1
- package/latticeai/services/run_executor.py +3 -2
- package/latticeai/services/search_service.py +1 -4
- package/latticeai/services/setup_detection.py +2 -1
- package/latticeai/services/tool_dispatch.py +7 -2
- package/latticeai/services/upload_service.py +2 -1
- package/latticeai/setup/auto_setup.py +10 -7
- package/latticeai/setup/wizard.py +15 -11
- package/latticeai/tools/__init__.py +3 -3
- package/latticeai/tools/commands.py +7 -7
- package/latticeai/tools/computer.py +3 -2
- package/latticeai/tools/documents.py +10 -9
- package/latticeai/tools/filesystem.py +7 -4
- package/latticeai/tools/knowledge.py +2 -0
- package/latticeai/tools/local_files.py +1 -1
- package/latticeai/tools/network.py +2 -1
- package/package.json +7 -3
- package/scripts/bench_agent_smoke.py +0 -1
- package/scripts/bench_models.py +0 -1
- package/scripts/brain_quality_eval.py +7 -2
- package/scripts/bump_version.py +2 -1
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/migrate_brain_storage.py +5 -1
- package/scripts/profile_kg.py +2 -7
- package/scripts/verify_hf_model_registry.py +2 -2
- 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 -37
- package/static/app/assets/{Act-BtCREeN1.js → Act-DrKIGZeJ.js} +1 -1
- package/static/app/assets/{AdminConsole-TPeeN18T.js → AdminConsole-CEV1VefA.js} +1 -1
- package/static/app/assets/{Brain-BKs6JAp0.js → Brain-BZSXnwWU.js} +1 -1
- package/static/app/assets/{BrainHome-BPGOvSd6.js → BrainHome-CEBd86k6.js} +1 -1
- package/static/app/assets/{BrainSignals-CtzQZ15J.js → BrainSignals-O4s6R7av.js} +1 -1
- package/static/app/assets/{Capture-1_NaHWqB.js → Capture-AGbOxsg-.js} +1 -1
- package/static/app/assets/{CommandPalette-pqvQOXe4.js → CommandPalette-CeqBjCn2.js} +1 -1
- package/static/app/assets/Library-CXnbNiQW.js +1 -0
- package/static/app/assets/{LivingBrain-DlQ20Q75.js → LivingBrain-D9Iei2ZJ.js} +1 -1
- package/static/app/assets/{ProductFlow-BZvGDRi_.js → ProductFlow-JlXiDfMM.js} +1 -1
- package/static/app/assets/{ReviewCard-BWgI0D2s.js → ReviewCard-QQly2bTV.js} +1 -1
- package/static/app/assets/System-Bp9ZY4ER.js +1 -0
- package/static/app/assets/{activity-Dlfk8YC7.js → activity-CxRPOQ2F.js} +1 -1
- package/static/app/assets/{bot-CDvUB76P.js → bot-1bcsWB2s.js} +1 -1
- package/static/app/assets/{brain-xczrohrt.js → brain-CxaeKCRM.js} +1 -1
- package/static/app/assets/{button-SOdH3Oyf.js → button-ZYFqGan7.js} +1 -1
- package/static/app/assets/{circle-pause-CG1ythH4.js → circle-pause-B5NOIqvk.js} +1 -1
- package/static/app/assets/{circle-play-HXwvjS6W.js → circle-play-BiJVFXf7.js} +1 -1
- package/static/app/assets/{cpu-B0d-rGyk.js → cpu-Vvq0OKTr.js} +1 -1
- package/static/app/assets/{download-BGIkTQL6.js → download-SEcXzkx1.js} +1 -1
- package/static/app/assets/{folder-open-Dst_Z0_K.js → folder-open-Dw1ff_Qa.js} +1 -1
- package/static/app/assets/{hard-drive-D53MsWkV.js → hard-drive-B00fR7_r.js} +1 -1
- package/static/app/assets/index-CEkL4_M-.css +2 -0
- package/static/app/assets/{index-C_IrlQMV.js → index-Dm9VXl1o.js} +3 -3
- package/static/app/assets/{input-C5m0riF6.js → input-DtdWYQEa.js} +1 -1
- package/static/app/assets/{network-C5a-E5iS.js → network-CpjBu0kI.js} +1 -1
- package/static/app/assets/{primitives-vNXYf58F.js → primitives-CwsudBwc.js} +1 -1
- package/static/app/assets/search-CwEJWBEA.js +1 -0
- package/static/app/assets/{shield-alert-Cc-WVXqN.js → shield-alert-CpjBc-gQ.js} +1 -1
- package/static/app/assets/{textarea-BkZ0EqVO.js → textarea-DUPyjHQs.js} +1 -1
- package/static/app/assets/{useFocusTrap-CTtKbAOU.js → useFocusTrap-BWcMtCWH.js} +1 -1
- package/static/app/assets/{useQuery-Dx1fi4Wu.js → useQuery-Dg5fPqTS.js} +1 -1
- package/static/app/assets/{users-BFpQXtEF.js → users-BgcdTQGM.js} +1 -1
- package/static/app/assets/{utils-BA_lmW3J.js → utils-a3YXFtEO.js} +2 -2
- package/static/app/assets/workspace-BMUEiXUm.js +1 -0
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/static/app/assets/Library-DhvoPvC7.js +0 -1
- package/static/app/assets/System-CSMdYLMy.js +0 -1
- package/static/app/assets/index-FxDusbr0.css +0 -2
- package/static/app/assets/search-DhbSgW6m.js +0 -1
- package/static/app/assets/workspace-DBPB0jkX.js +0 -1
|
@@ -30,12 +30,8 @@ from __future__ import annotations
|
|
|
30
30
|
|
|
31
31
|
from typing import Any, Callable, Dict, List, Optional
|
|
32
32
|
|
|
33
|
-
from
|
|
34
|
-
|
|
35
|
-
CORE_PIPELINE,
|
|
36
|
-
MULTI_AGENT_VERSION,
|
|
37
|
-
ROLE_AGENT_IDS,
|
|
38
|
-
)
|
|
33
|
+
from ..quiet import quiet
|
|
34
|
+
from ..utils import now_iso as _now
|
|
39
35
|
from .contracts import (
|
|
40
36
|
contract_view,
|
|
41
37
|
contract_views,
|
|
@@ -44,11 +40,18 @@ from .contracts import (
|
|
|
44
40
|
run_record_contract,
|
|
45
41
|
runtime_boundary_contract,
|
|
46
42
|
)
|
|
43
|
+
from .multi_agent import (
|
|
44
|
+
AGENT_ROLES,
|
|
45
|
+
CORE_PIPELINE,
|
|
46
|
+
MULTI_AGENT_VERSION,
|
|
47
|
+
ROLE_AGENT_IDS,
|
|
48
|
+
)
|
|
47
49
|
from .statuses import (
|
|
48
50
|
RUN_ACTIVE_STATUSES as _ACTIVE_STATUSES,
|
|
51
|
+
)
|
|
52
|
+
from .statuses import (
|
|
49
53
|
RUN_TERMINAL_STATUSES as _TERMINAL_STATUSES,
|
|
50
54
|
)
|
|
51
|
-
from ..utils import now_iso as _now
|
|
52
55
|
|
|
53
56
|
ROLE_DESCRIPTIONS = {
|
|
54
57
|
"researcher": "Gathers workspace context and memory for the goal.",
|
|
@@ -494,7 +497,7 @@ class AgentRuntime:
|
|
|
494
497
|
)
|
|
495
498
|
except Exception:
|
|
496
499
|
# Synthesis must never break the run record.
|
|
497
|
-
|
|
500
|
+
quiet()
|
|
498
501
|
|
|
499
502
|
@staticmethod
|
|
500
503
|
def _format_synthesis_section(title: str, items: List[str]) -> str:
|
|
@@ -515,8 +518,13 @@ class AgentRuntime:
|
|
|
515
518
|
if sentence.strip(" -•\t")
|
|
516
519
|
]
|
|
517
520
|
facts = [_compact_text(sentence, limit=180) for sentence in sentences[:4]]
|
|
518
|
-
|
|
519
|
-
|
|
521
|
+
# `or {}` rather than the isinstance ternary: the ternary could still
|
|
522
|
+
# yield None when the attribute was a dict-typed None, and the next
|
|
523
|
+
# line calls .get on both.
|
|
524
|
+
review = getattr(result, "review", None)
|
|
525
|
+
review = review if isinstance(review, dict) else {}
|
|
526
|
+
plan_review = getattr(result, "plan_review", None)
|
|
527
|
+
plan_review = plan_review if isinstance(plan_review, dict) else {}
|
|
520
528
|
decision_seed = review.get("decision") or review.get("status") or plan_review.get("decision") or getattr(result, "status", "")
|
|
521
529
|
decisions = [
|
|
522
530
|
_compact_text(f"Run finished with {decision_seed}", limit=160),
|
|
@@ -569,6 +577,7 @@ class AgentRuntime:
|
|
|
569
577
|
workspace_id=scope,
|
|
570
578
|
)
|
|
571
579
|
except Exception:
|
|
580
|
+
quiet()
|
|
572
581
|
continue
|
|
573
582
|
|
|
574
583
|
def _post_run_hooks(
|
|
@@ -36,9 +36,9 @@ from collections import deque
|
|
|
36
36
|
from pathlib import Path
|
|
37
37
|
from typing import Any, Callable, Dict, List, Optional
|
|
38
38
|
|
|
39
|
+
from ..quiet import quiet
|
|
39
40
|
from ..utils import now_iso as _now
|
|
40
41
|
|
|
41
|
-
|
|
42
42
|
LOGGER = logging.getLogger(__name__)
|
|
43
43
|
|
|
44
44
|
|
|
@@ -344,7 +344,7 @@ class HooksRegistry:
|
|
|
344
344
|
data.setdefault("overrides", {})
|
|
345
345
|
return data
|
|
346
346
|
except (OSError, json.JSONDecodeError, TypeError, ValueError) as exc:
|
|
347
|
-
LOGGER.warning("hook registry state is unreadable at %s: %s", self.
|
|
347
|
+
LOGGER.warning("hook registry state is unreadable at %s: %s", self.path, exc)
|
|
348
348
|
return {"custom": [], "overrides": {}}
|
|
349
349
|
|
|
350
350
|
def _save(self) -> None:
|
|
@@ -454,6 +454,7 @@ class HooksRegistry:
|
|
|
454
454
|
try:
|
|
455
455
|
self.set_order(hook_id, (idx + 1) * 10)
|
|
456
456
|
except KeyError:
|
|
457
|
+
quiet()
|
|
457
458
|
continue
|
|
458
459
|
return self.list(kind=kind)
|
|
459
460
|
|
|
@@ -44,11 +44,10 @@ from __future__ import annotations
|
|
|
44
44
|
from dataclasses import dataclass, field
|
|
45
45
|
from typing import Any, Callable, Dict, List, Optional
|
|
46
46
|
|
|
47
|
-
from .contracts import multi_agent_contract
|
|
48
47
|
from ..utils import now_iso as _now
|
|
48
|
+
from .contracts import multi_agent_contract
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
MULTI_AGENT_VERSION = "10.0.0"
|
|
50
|
+
MULTI_AGENT_VERSION = "10.3.0"
|
|
52
51
|
|
|
53
52
|
AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
|
|
54
53
|
CORE_PIPELINE = ("planner", "executor", "reviewer")
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Which local sources are never allowed to leave the machine.
|
|
2
|
+
|
|
3
|
+
This lives in Brain Core rather than in the app layer because it is a property
|
|
4
|
+
of the *data*, not of any transport: a node stamped never-leaves stays that way
|
|
5
|
+
whether it is read by the cloud path, an export, or a future surface nobody has
|
|
6
|
+
written yet. Brain Core cannot import from ``latticeai`` (see
|
|
7
|
+
``tests/unit/test_import_guard.py``), so the app-layer network boundary imports
|
|
8
|
+
this, not the other way round.
|
|
9
|
+
|
|
10
|
+
Deliberately narrow. These are locations whose *entire purpose* is to hold
|
|
11
|
+
secrets, so a match is a fact about the path rather than a guess about content.
|
|
12
|
+
A broader heuristic — scanning text for things that look like keys — would
|
|
13
|
+
produce false confidence in both directions: it would miss secrets in unusual
|
|
14
|
+
shapes and quarantine ordinary notes that merely discuss credentials.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Any, Optional
|
|
20
|
+
|
|
21
|
+
# Path fragments checked case-insensitively against the POSIX form of the path.
|
|
22
|
+
SENSITIVE_PATH_FRAGMENTS: tuple = (
|
|
23
|
+
"/.ssh/",
|
|
24
|
+
"/.gnupg/",
|
|
25
|
+
"/.aws/",
|
|
26
|
+
"/.kube/",
|
|
27
|
+
"/.docker/config.json",
|
|
28
|
+
"/.netrc",
|
|
29
|
+
"/.npmrc",
|
|
30
|
+
"/.pypirc",
|
|
31
|
+
"/.git-credentials",
|
|
32
|
+
"id_rsa",
|
|
33
|
+
"id_ed25519",
|
|
34
|
+
".pem",
|
|
35
|
+
".p12",
|
|
36
|
+
".pfx",
|
|
37
|
+
".keystore",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Exact filenames (in any directory) that are secret-bearing by convention.
|
|
41
|
+
SENSITIVE_FILENAMES: frozenset = frozenset({
|
|
42
|
+
".env",
|
|
43
|
+
".env.local",
|
|
44
|
+
".env.production",
|
|
45
|
+
".env.development",
|
|
46
|
+
"credentials",
|
|
47
|
+
"secrets.yaml",
|
|
48
|
+
"secrets.yml",
|
|
49
|
+
"secrets.json",
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
#: Metadata key stamped on a node that must never leave the machine.
|
|
53
|
+
LOCAL_ONLY_FLAG = "local_only"
|
|
54
|
+
#: Companion key holding the human-readable reason.
|
|
55
|
+
LOCAL_ONLY_REASON = "local_only_reason"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def sensitive_reason_for_path(path: Any) -> Optional[str]:
|
|
59
|
+
"""Return why ``path`` is never-leaves, or ``None`` if it is ordinary."""
|
|
60
|
+
if not path:
|
|
61
|
+
return None
|
|
62
|
+
lowered = str(path).replace("\\", "/").lower()
|
|
63
|
+
name = lowered.rsplit("/", 1)[-1]
|
|
64
|
+
if name in SENSITIVE_FILENAMES:
|
|
65
|
+
return f"{name!r} is a secret-bearing filename"
|
|
66
|
+
for fragment in SENSITIVE_PATH_FRAGMENTS:
|
|
67
|
+
if fragment in lowered:
|
|
68
|
+
return f"path contains {fragment!r}"
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def stamp_sensitivity(metadata: dict, path: Any) -> Optional[str]:
|
|
73
|
+
"""Stamp never-leaves onto ``metadata`` when ``path`` warrants it.
|
|
74
|
+
|
|
75
|
+
Returns the reason it stamped, or ``None``. Never clears an existing flag —
|
|
76
|
+
a user or another rule may have set it for a reason this function cannot
|
|
77
|
+
see, and downgrading a never-leaves marker is not a decision code should
|
|
78
|
+
make on its own.
|
|
79
|
+
"""
|
|
80
|
+
reason = sensitive_reason_for_path(path)
|
|
81
|
+
if reason:
|
|
82
|
+
metadata[LOCAL_ONLY_FLAG] = True
|
|
83
|
+
metadata.setdefault(LOCAL_ONLY_REASON, reason)
|
|
84
|
+
return reason
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
__all__ = [
|
|
88
|
+
"SENSITIVE_PATH_FRAGMENTS",
|
|
89
|
+
"SENSITIVE_FILENAMES",
|
|
90
|
+
"LOCAL_ONLY_FLAG",
|
|
91
|
+
"LOCAL_ONLY_REASON",
|
|
92
|
+
"sensitive_reason_for_path",
|
|
93
|
+
"stamp_sensitivity",
|
|
94
|
+
]
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
|
+
from contextlib import contextmanager
|
|
6
7
|
from dataclasses import dataclass, field
|
|
7
8
|
from pathlib import Path
|
|
8
|
-
from typing import Any, Dict, Optional
|
|
9
|
+
from typing import Any, Dict, Iterator, Optional
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class StorageUnavailable(RuntimeError):
|
|
@@ -58,7 +59,34 @@ class StorageEngine(ABC):
|
|
|
58
59
|
|
|
59
60
|
@abstractmethod
|
|
60
61
|
def connect(self) -> Any:
|
|
61
|
-
"""Return a DB-API-like connection for this engine.
|
|
62
|
+
"""Return a DB-API-like connection for this engine.
|
|
63
|
+
|
|
64
|
+
The caller owns the connection and must close it. Prefer
|
|
65
|
+
:meth:`session`, which does that for you — a bare ``connect()`` is only
|
|
66
|
+
correct when the statement cannot run inside a transaction (``VACUUM``)
|
|
67
|
+
or when the connection must outlive one block.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
@contextmanager
|
|
71
|
+
def session(self) -> Iterator[Any]:
|
|
72
|
+
"""Transactional connection that is always closed.
|
|
73
|
+
|
|
74
|
+
``with sqlite3.connect(...) as conn`` commits or rolls back — it does
|
|
75
|
+
**not** close. Every site that relied on that leaked a file descriptor
|
|
76
|
+
until the connection was garbage collected, which CPython usually did
|
|
77
|
+
promptly enough to hide it. Anything holding a frame alive (a coverage
|
|
78
|
+
tracer, a profiler, a logged traceback) delays that collection and the
|
|
79
|
+
descriptors accumulate until the process hits ``EMFILE``.
|
|
80
|
+
|
|
81
|
+
This yields inside ``with conn`` so commit/rollback semantics are
|
|
82
|
+
unchanged, then closes in ``finally``.
|
|
83
|
+
"""
|
|
84
|
+
conn = self.connect()
|
|
85
|
+
try:
|
|
86
|
+
with conn:
|
|
87
|
+
yield conn
|
|
88
|
+
finally:
|
|
89
|
+
conn.close()
|
|
62
90
|
|
|
63
91
|
@abstractmethod
|
|
64
92
|
def backup(self, destination: Path) -> Dict[str, Any]:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import sqlite3
|
|
6
|
+
from contextlib import closing
|
|
6
7
|
from dataclasses import dataclass
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any, Dict, List
|
|
@@ -52,7 +53,7 @@ class SQLiteToPostgresMigrator:
|
|
|
52
53
|
def plan(self) -> Dict[str, Any]:
|
|
53
54
|
if not self.sqlite_path.exists():
|
|
54
55
|
raise FileNotFoundError(f"SQLite brain database not found: {self.sqlite_path}")
|
|
55
|
-
with sqlite3.connect(str(self.sqlite_path)) as conn:
|
|
56
|
+
with closing(sqlite3.connect(str(self.sqlite_path))) as conn, conn:
|
|
56
57
|
conn.row_factory = sqlite3.Row
|
|
57
58
|
table_names = [
|
|
58
59
|
row["name"]
|
|
@@ -119,7 +120,7 @@ class SQLiteToPostgresMigrator:
|
|
|
119
120
|
schema = _quote_ident(self.target.config.schema)
|
|
120
121
|
copied: Dict[str, int] = {}
|
|
121
122
|
self.target.initialize()
|
|
122
|
-
with sqlite3.connect(str(self.sqlite_path)) as src, self.target.
|
|
123
|
+
with closing(sqlite3.connect(str(self.sqlite_path))) as src, self.target.session() as dst:
|
|
123
124
|
src.row_factory = sqlite3.Row
|
|
124
125
|
with dst.cursor() as cur:
|
|
125
126
|
for table in plan["tables"]:
|
|
@@ -45,7 +45,7 @@ class PostgresEngine(StorageEngine):
|
|
|
45
45
|
|
|
46
46
|
def initialize(self) -> Dict[str, Any]:
|
|
47
47
|
schema = _quote_ident(self.config.schema)
|
|
48
|
-
with self.
|
|
48
|
+
with self.session() as conn:
|
|
49
49
|
with conn.cursor() as cur:
|
|
50
50
|
cur.execute("CREATE EXTENSION IF NOT EXISTS vector")
|
|
51
51
|
cur.execute(f"CREATE SCHEMA IF NOT EXISTS {schema}")
|
|
@@ -85,7 +85,7 @@ class PostgresEngine(StorageEngine):
|
|
|
85
85
|
|
|
86
86
|
def capabilities(self) -> StorageCapabilities:
|
|
87
87
|
try:
|
|
88
|
-
with self.
|
|
88
|
+
with self.session() as conn:
|
|
89
89
|
with conn.cursor() as cur:
|
|
90
90
|
cur.execute("SELECT extname FROM pg_extension WHERE extname='vector'")
|
|
91
91
|
pgvector = cur.fetchone() is not None
|
|
@@ -9,9 +9,11 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import shutil
|
|
11
11
|
import sqlite3
|
|
12
|
+
from contextlib import closing
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
from typing import Any, Dict, Optional
|
|
14
15
|
|
|
16
|
+
from ..quiet import quiet
|
|
15
17
|
from .base import StorageCapabilities, StorageEngine
|
|
16
18
|
|
|
17
19
|
|
|
@@ -23,7 +25,7 @@ def _load_sqlite_vec(conn: sqlite3.Connection) -> tuple[bool, Optional[str]]:
|
|
|
23
25
|
try:
|
|
24
26
|
conn.enable_load_extension(True)
|
|
25
27
|
except Exception:
|
|
26
|
-
|
|
28
|
+
quiet()
|
|
27
29
|
try:
|
|
28
30
|
sqlite_vec.load(conn)
|
|
29
31
|
except Exception as exc:
|
|
@@ -53,7 +55,7 @@ class SQLiteEngine(StorageEngine):
|
|
|
53
55
|
return conn
|
|
54
56
|
|
|
55
57
|
def initialize(self) -> Dict[str, Any]:
|
|
56
|
-
with self.
|
|
58
|
+
with self.session() as conn:
|
|
57
59
|
conn.execute(
|
|
58
60
|
"""
|
|
59
61
|
CREATE TABLE IF NOT EXISTS storage_meta (
|
|
@@ -86,7 +88,7 @@ class SQLiteEngine(StorageEngine):
|
|
|
86
88
|
)
|
|
87
89
|
# Probe on demand so status is accurate even before the graph opens.
|
|
88
90
|
try:
|
|
89
|
-
with self.
|
|
91
|
+
with self.session():
|
|
90
92
|
pass
|
|
91
93
|
except Exception as exc:
|
|
92
94
|
return StorageCapabilities(
|
|
@@ -122,7 +124,7 @@ class SQLiteEngine(StorageEngine):
|
|
|
122
124
|
def backup(self, destination: Path) -> Dict[str, Any]:
|
|
123
125
|
dest = Path(destination)
|
|
124
126
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
125
|
-
with self.
|
|
127
|
+
with self.session() as src, closing(sqlite3.connect(str(dest))) as dst:
|
|
126
128
|
src.backup(dst)
|
|
127
129
|
return {"engine": self.name, "path": str(dest), "bytes": dest.stat().st_size}
|
|
128
130
|
|
|
@@ -26,10 +26,12 @@ from __future__ import annotations
|
|
|
26
26
|
from dataclasses import dataclass, field
|
|
27
27
|
from typing import Any, Callable, Dict, List, Optional
|
|
28
28
|
|
|
29
|
-
from lattice_brain.runtime.contracts import
|
|
29
|
+
from lattice_brain.runtime.contracts import (
|
|
30
|
+
runtime_boundary_contract,
|
|
31
|
+
workflow_run_contract,
|
|
32
|
+
)
|
|
30
33
|
from lattice_brain.utils import now_iso as _now
|
|
31
34
|
|
|
32
|
-
|
|
33
35
|
WORKFLOW_ENGINE_VERSION = "2.2.0"
|
|
34
36
|
|
|
35
37
|
# The node vocabulary a workflow can be built from. ``trigger`` and ``output``
|
package/latticeai/__init__.py
CHANGED
package/latticeai/api/admin.py
CHANGED
|
@@ -223,7 +223,7 @@ def create_admin_router(
|
|
|
223
223
|
async def admin_roles(request: Request):
|
|
224
224
|
_, users = require_admin(request)
|
|
225
225
|
counts: Dict[str, int] = defaultdict(int)
|
|
226
|
-
for email, user in users.items():
|
|
226
|
+
for email, user in users.items(): # noqa: B007 — the key is the payload; the loop var documents the shape
|
|
227
227
|
role = (get_user_role(email, users) or "user").lower()
|
|
228
228
|
counts[role] += 1
|
|
229
229
|
matrix = policy_matrix() if policy_matrix else [
|
package/latticeai/api/agents.py
CHANGED
|
@@ -16,7 +16,6 @@ from pydantic import BaseModel
|
|
|
16
16
|
|
|
17
17
|
from latticeai.api.ui_redirects import app_redirect
|
|
18
18
|
|
|
19
|
-
|
|
20
19
|
_CORE_EXECUTION_ROLES = ["planner", "executor", "reviewer"]
|
|
21
20
|
_MEMORY_GROUNDED_ROLES = ["researcher", *_CORE_EXECUTION_ROLES]
|
|
22
21
|
|
|
@@ -63,8 +62,11 @@ def create_agents_router(
|
|
|
63
62
|
agent_runtime: Any = None,
|
|
64
63
|
run_executor: Any = None,
|
|
65
64
|
) -> APIRouter:
|
|
65
|
+
from lattice_brain.runtime.agent_runtime import (
|
|
66
|
+
AgentRuntime,
|
|
67
|
+
AgentRuntimeUnavailable,
|
|
68
|
+
)
|
|
66
69
|
from lattice_brain.runtime.multi_agent import AGENT_ROLES, ROLE_AGENT_IDS
|
|
67
|
-
from lattice_brain.runtime.agent_runtime import AgentRuntime, AgentRuntimeUnavailable
|
|
68
70
|
|
|
69
71
|
# Single AgentRuntime boundary: the router (and via it, the frontend) talks
|
|
70
72
|
# to this façade instead of reaching into the orchestrator/store directly.
|
package/latticeai/api/auth.py
CHANGED
|
@@ -13,12 +13,16 @@ from fastapi import APIRouter, HTTPException, Request
|
|
|
13
13
|
from fastapi.responses import JSONResponse, RedirectResponse
|
|
14
14
|
from pydantic import BaseModel
|
|
15
15
|
|
|
16
|
-
from latticeai.core.users import normalize_email
|
|
17
16
|
from latticeai.core.oidc import (
|
|
18
17
|
OIDCValidationError,
|
|
18
|
+
)
|
|
19
|
+
from latticeai.core.oidc import (
|
|
19
20
|
fetch_jwks as _default_fetch_jwks,
|
|
21
|
+
)
|
|
22
|
+
from latticeai.core.oidc import (
|
|
20
23
|
verify_id_token as _default_verify_id_token,
|
|
21
24
|
)
|
|
25
|
+
from latticeai.core.users import normalize_email
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
class UserRegister(BaseModel):
|
|
@@ -20,6 +20,7 @@ from typing import Any, Callable, Optional
|
|
|
20
20
|
from fastapi import APIRouter, HTTPException, Request
|
|
21
21
|
from pydantic import BaseModel
|
|
22
22
|
|
|
23
|
+
from latticeai.core.quiet import quiet
|
|
23
24
|
from latticeai.services.automation_execution import (
|
|
24
25
|
build_last_execution,
|
|
25
26
|
dry_run_report,
|
|
@@ -162,7 +163,7 @@ def create_automation_intelligence_router(
|
|
|
162
163
|
workspace_id=scope,
|
|
163
164
|
)
|
|
164
165
|
except Exception: # noqa: BLE001 — surfacing must never undo the run
|
|
165
|
-
|
|
166
|
+
quiet()
|
|
166
167
|
|
|
167
168
|
@router.post("/api/automation/run-now")
|
|
168
169
|
async def automation_run_now(req: AutomationRunNowRequest, request: Request):
|
package/latticeai/api/browser.py
CHANGED
|
@@ -26,8 +26,9 @@ from urllib.parse import SplitResult, urljoin, urlsplit, urlunsplit
|
|
|
26
26
|
from fastapi import APIRouter, HTTPException, Request
|
|
27
27
|
from pydantic import BaseModel
|
|
28
28
|
|
|
29
|
-
from latticeai import __version__
|
|
30
29
|
from lattice_brain.ingestion import IngestionItem, capture_quality_verdict
|
|
30
|
+
from latticeai import __version__
|
|
31
|
+
from latticeai.core.quiet import quiet
|
|
31
32
|
|
|
32
33
|
MAX_TAB_BYTES = 4 * 1024 * 1024 # 4 MB per captured tab payload
|
|
33
34
|
MAX_URL_FETCH_BYTES = 4 * 1024 * 1024 # 4 MB cap on a fetched page
|
|
@@ -95,7 +96,7 @@ def extract_readable_text(html: str) -> Tuple[str, str]:
|
|
|
95
96
|
try:
|
|
96
97
|
parser.feed(html or "")
|
|
97
98
|
except Exception: # noqa: BLE001 — malformed HTML must still yield best-effort text
|
|
98
|
-
|
|
99
|
+
quiet()
|
|
99
100
|
return parser.title.strip(), parser.text()
|
|
100
101
|
|
|
101
102
|
|
package/latticeai/api/chat.py
CHANGED
|
@@ -16,7 +16,6 @@ from fastapi import APIRouter, HTTPException, Request
|
|
|
16
16
|
from fastapi.responses import JSONResponse, StreamingResponse
|
|
17
17
|
|
|
18
18
|
from latticeai.api.chat_agent_http import AgentHTTPController
|
|
19
|
-
from latticeai.core.project_sessions import ProjectSessionStore
|
|
20
19
|
from latticeai.api.chat_contracts import (
|
|
21
20
|
AgentEvalRequest,
|
|
22
21
|
AgentRequest,
|
|
@@ -46,9 +45,14 @@ from latticeai.api.chat_helpers import (
|
|
|
46
45
|
workspace_scope_from_request,
|
|
47
46
|
)
|
|
48
47
|
from latticeai.api.chat_history import HistoryRouteDependencies, register_history_routes
|
|
49
|
-
from latticeai.
|
|
48
|
+
from latticeai.api.chat_hybrid import (
|
|
49
|
+
maybe_hybrid_stream_response,
|
|
50
|
+
resolve_request_network_mode,
|
|
51
|
+
)
|
|
50
52
|
from latticeai.api.chat_intents import ChatIntentController
|
|
51
53
|
from latticeai.api.chat_stream import stream_chat
|
|
54
|
+
from latticeai.core.project_sessions import ProjectSessionStore
|
|
55
|
+
from latticeai.core.run_store import AgentRunStore
|
|
52
56
|
from latticeai.services.app_context import AppContext
|
|
53
57
|
from latticeai.services.chat_service import ChatService
|
|
54
58
|
from latticeai.services.tool_dispatch import build_agent_runtime, enforce_tool_policy
|
|
@@ -61,7 +65,6 @@ from latticeai.tools import (
|
|
|
61
65
|
network_status,
|
|
62
66
|
)
|
|
63
67
|
|
|
64
|
-
|
|
65
68
|
__all__ = [
|
|
66
69
|
"AgentEvalRequest",
|
|
67
70
|
"AgentRequest",
|
|
@@ -163,8 +166,6 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
163
166
|
|
|
164
167
|
agent_runtime = context.chat_agent_runtime
|
|
165
168
|
if agent_runtime is None:
|
|
166
|
-
# Keep construction here so the historical monkeypatch/import seam on
|
|
167
|
-
# latticeai.api.chat.build_agent_runtime remains valid.
|
|
168
169
|
agent_runtime = build_agent_runtime(
|
|
169
170
|
model_router=model_router,
|
|
170
171
|
execute_tool=execute_tool,
|
|
@@ -198,8 +199,6 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
198
199
|
if context.data_dir
|
|
199
200
|
else None
|
|
200
201
|
),
|
|
201
|
-
# Multi-turn project loop (v9.9.6): optional — without a data dir the
|
|
202
|
-
# agent runs exactly as before, one run at a time with no project state.
|
|
203
202
|
project_sessions=(
|
|
204
203
|
ProjectSessionStore(Path(context.data_dir) / "project_sessions")
|
|
205
204
|
if context.data_dir
|
|
@@ -293,8 +292,6 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
293
292
|
selected_model_id = request_model(req.model)
|
|
294
293
|
file_intent = is_file_action_request(req.message)
|
|
295
294
|
if file_intent and context.funnel_metrics is not None:
|
|
296
|
-
# UX funnel (backlog #16): one file-intent request counted once,
|
|
297
|
-
# regardless of which delivery path handles it below.
|
|
298
295
|
context.funnel_metrics.increment("file_requests")
|
|
299
296
|
if file_intent:
|
|
300
297
|
direct_response = await intent_controller.direct_file_action(
|
|
@@ -386,9 +383,6 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
386
383
|
if context_trace is not None and isinstance(trace_seed, dict):
|
|
387
384
|
trace_seed["context_assembly"] = context_trace
|
|
388
385
|
|
|
389
|
-
# v9.8.0 honest RAG signal: how well the graph grounded this answer.
|
|
390
|
-
# Rides the same channel as sources/evidence (the answer trace) and is
|
|
391
|
-
# additionally exposed top-level on both response shapes below.
|
|
392
386
|
context_quality = build_context_quality(
|
|
393
387
|
req.message,
|
|
394
388
|
knowledge_graph=context.knowledge_graph
|
|
@@ -401,8 +395,6 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
401
395
|
if context.funnel_metrics is not None and int(
|
|
402
396
|
context_quality.get("nodes") or 0
|
|
403
397
|
) > 0:
|
|
404
|
-
# UX funnel (backlog #16): a grounded answer is "first value" —
|
|
405
|
-
# the TTFV clock ends on the first successful recall.
|
|
406
398
|
context.funnel_metrics.record_recall_success()
|
|
407
399
|
|
|
408
400
|
history_message = (
|
|
@@ -428,6 +420,28 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
428
420
|
if document_response is not None:
|
|
429
421
|
return document_response
|
|
430
422
|
|
|
423
|
+
# Hybrid Phase 2: cloud_allowed → minimal KG context → cloud stream.
|
|
424
|
+
network_mode = resolve_request_network_mode(
|
|
425
|
+
request_mode=getattr(req, "network_mode", None),
|
|
426
|
+
user_email=effective_email,
|
|
427
|
+
workspace_id=workspace_id,
|
|
428
|
+
)
|
|
429
|
+
hybrid_response = maybe_hybrid_stream_response(
|
|
430
|
+
req=req,
|
|
431
|
+
mode=network_mode,
|
|
432
|
+
knowledge_graph=context.knowledge_graph,
|
|
433
|
+
enable_graph=context.enable_graph,
|
|
434
|
+
effective_email=effective_email,
|
|
435
|
+
workspace_id=workspace_id,
|
|
436
|
+
history_meta=history_meta,
|
|
437
|
+
history_user=history_user,
|
|
438
|
+
chat_service=chat_service,
|
|
439
|
+
notify=notify_chat_message,
|
|
440
|
+
model_id=selected_model_id,
|
|
441
|
+
)
|
|
442
|
+
if hybrid_response is not None:
|
|
443
|
+
return hybrid_response
|
|
444
|
+
|
|
431
445
|
if req.stream:
|
|
432
446
|
recent_context = recent_chat_context(
|
|
433
447
|
user_email=effective_email,
|
|
@@ -493,9 +507,6 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
493
507
|
req.image_data,
|
|
494
508
|
)
|
|
495
509
|
response_text = str(result)
|
|
496
|
-
# Answer-citation binding (backlog #11): annotate — never block — how
|
|
497
|
-
# the answer relates to the retrieved sources. Recorded on the trace
|
|
498
|
-
# so the Review/proof surfaces see the same verdict as the client.
|
|
499
510
|
grounding = assess_answer_grounding(
|
|
500
511
|
response_text,
|
|
501
512
|
trace=trace_seed if isinstance(trace_seed, dict) else None,
|
|
@@ -17,10 +17,19 @@ from fastapi import APIRouter, HTTPException, Request
|
|
|
17
17
|
from fastapi.responses import StreamingResponse
|
|
18
18
|
|
|
19
19
|
from lattice_brain.runtime.hooks import dispatch_tool
|
|
20
|
-
from latticeai.api.chat_contracts import
|
|
21
|
-
|
|
20
|
+
from latticeai.api.chat_contracts import (
|
|
21
|
+
AgentEvalRequest,
|
|
22
|
+
AgentRequest,
|
|
23
|
+
AgentResumeRequest,
|
|
24
|
+
)
|
|
25
|
+
from latticeai.api.chat_helpers import (
|
|
26
|
+
_LANG_HINT,
|
|
27
|
+
detect_language,
|
|
28
|
+
workspace_scope_from_request,
|
|
29
|
+
)
|
|
22
30
|
from latticeai.api.chat_stream import agent_live_stream
|
|
23
31
|
from latticeai.core.agent import AgentRunContext, AgentState, normalize_plan
|
|
32
|
+
from latticeai.core.quiet import quiet
|
|
24
33
|
from latticeai.core.run_explain import explain_run
|
|
25
34
|
from latticeai.core.run_store import (
|
|
26
35
|
AgentRunStore,
|
|
@@ -139,7 +148,7 @@ class AgentHTTPController:
|
|
|
139
148
|
try:
|
|
140
149
|
done.result()
|
|
141
150
|
except asyncio.CancelledError:
|
|
142
|
-
|
|
151
|
+
quiet()
|
|
143
152
|
except Exception as exc:
|
|
144
153
|
logging.warning("background chat task failed: %s", exc)
|
|
145
154
|
|
|
@@ -188,7 +197,12 @@ class AgentHTTPController:
|
|
|
188
197
|
self.hooks,
|
|
189
198
|
action_name,
|
|
190
199
|
case_input,
|
|
191
|
-
|
|
200
|
+
# Bound as defaults: the lambda is invoked inside this
|
|
201
|
+
# iteration today, so late binding is harmless — until
|
|
202
|
+
# someone defers it and every case runs the last input.
|
|
203
|
+
lambda action_name=action_name, case_input=case_input: self.execute_tool(
|
|
204
|
+
action_name, case_input
|
|
205
|
+
),
|
|
192
206
|
source="eval",
|
|
193
207
|
)
|
|
194
208
|
criteria = case.get("pass_criteria", "")
|
|
@@ -470,7 +484,7 @@ class AgentHTTPController:
|
|
|
470
484
|
try:
|
|
471
485
|
self.run_store.delete(run_id)
|
|
472
486
|
except Exception: # noqa: BLE001 — hygiene only
|
|
473
|
-
|
|
487
|
+
quiet()
|
|
474
488
|
|
|
475
489
|
def pending_approvals(self, request: Request) -> Dict[str, Any]:
|
|
476
490
|
"""Unexpired paused runs for the current user (memory ∪ disk).
|
|
@@ -525,7 +539,7 @@ class AgentHTTPController:
|
|
|
525
539
|
try:
|
|
526
540
|
self.run_store.delete(run_id)
|
|
527
541
|
except Exception: # noqa: BLE001
|
|
528
|
-
|
|
542
|
+
quiet()
|
|
529
543
|
raise HTTPException(
|
|
530
544
|
status_code=410,
|
|
531
545
|
detail={
|
|
@@ -724,7 +738,7 @@ class AgentHTTPController:
|
|
|
724
738
|
try:
|
|
725
739
|
self.run_store.delete(req.context_id)
|
|
726
740
|
except Exception: # noqa: BLE001
|
|
727
|
-
|
|
741
|
+
quiet()
|
|
728
742
|
raise HTTPException(
|
|
729
743
|
status_code=404,
|
|
730
744
|
detail="Agent context not found or expired. Start a new request.",
|
|
@@ -791,7 +805,7 @@ class AgentHTTPController:
|
|
|
791
805
|
try:
|
|
792
806
|
self.run_store.delete(req.run_id or "")
|
|
793
807
|
except Exception: # noqa: BLE001
|
|
794
|
-
|
|
808
|
+
quiet()
|
|
795
809
|
raise HTTPException(
|
|
796
810
|
status_code=410,
|
|
797
811
|
detail={
|
|
@@ -21,6 +21,10 @@ class ChatRequest(BaseModel):
|
|
|
21
21
|
user_nickname: Optional[str] = None
|
|
22
22
|
image_data: Optional[str] = None
|
|
23
23
|
allow_file_context: bool = False
|
|
24
|
+
# Hybrid Phase 1: optional per-request override of NetworkBoundaryMode.
|
|
25
|
+
# When omitted, the persisted dial (user/workspace/default) is used.
|
|
26
|
+
# Values: "local_only" | "cloud_allowed"
|
|
27
|
+
network_mode: Optional[str] = None
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
class AgentRequest(BaseModel):
|