minder-cli 0.4.2__tar.gz → 0.4.5__tar.gz
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.
- {minder_cli-0.4.2 → minder_cli-0.4.5}/PKG-INFO +2 -1
- {minder_cli-0.4.2 → minder_cli-0.4.5}/pyproject.toml +2 -1
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/application/admin/dto.py +25 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/application/admin/use_cases.py +114 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/bootstrap/providers.py +13 -1
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/bootstrap/transport.py +36 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/config.py +7 -6
- minder_cli-0.4.5/src/minder/context_compactor.py +197 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/executor.py +11 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/graph.py +9 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/nodes/__init__.py +4 -0
- minder_cli-0.4.5/src/minder/graph/nodes/clarification.py +164 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/nodes/planning.py +12 -1
- minder_cli-0.4.5/src/minder/graph/nodes/reflection.py +64 -0
- minder_cli-0.4.5/src/minder/learning/__init__.py +12 -0
- minder_cli-0.4.5/src/minder/learning/error_learner.py +70 -0
- minder_cli-0.4.5/src/minder/learning/pattern_extractor.py +44 -0
- minder_cli-0.4.5/src/minder/learning/quality_optimizer.py +105 -0
- minder_cli-0.4.5/src/minder/learning/skill_synthesizer.py +81 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/llm/litert.py +23 -16
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/skill.py +3 -1
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/observability/metrics.py +4 -1
- minder_cli-0.4.5/src/minder/presentation/cli/commands/agent.py +418 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/cli/main.py +2 -2
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/cli/utils/common.py +1 -1
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/http/admin/api.py +78 -14
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/http/admin/memories.py +1 -1
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/http/admin/runtime.py +60 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/http/admin/skills.py +1 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/server.py +28 -17
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/interfaces.py +1 -0
- minder_cli-0.4.5/src/minder/store/mongodb/graph_store.py +497 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/mongodb/operational_store.py +11 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/relational.py +23 -1
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/tools/memory.py +62 -8
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/tools/query.py +35 -18
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/tools/registry.py +33 -1
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/tools/session.py +48 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/tools/skills.py +43 -12
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/tools/workflow.py +19 -29
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/transport/base.py +14 -9
- minder_cli-0.4.2/src/minder/presentation/cli/commands/agent.py +0 -252
- {minder_cli-0.4.2 → minder_cli-0.4.5}/.gitignore +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/LICENSE +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/README-pypi.md +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/README.md +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/api/routers/prompts.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/application/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/application/admin/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/application/admin/jobs.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/auth/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/auth/context.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/auth/middleware.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/auth/principal.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/auth/rate_limiter.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/auth/rbac.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/auth/service.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/bootstrap/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/cache/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/cache/providers.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/chunking/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/chunking/code_splitter.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/chunking/splitter.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/cli.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/continuity.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/dev.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/embedding/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/embedding/base.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/embedding/local.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/embedding/openai.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/edges.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/nodes/evaluator.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/nodes/guard.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/nodes/llm.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/nodes/reasoning.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/nodes/reranker.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/nodes/retriever.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/nodes/verification.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/nodes/workflow_planner.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/runtime.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/graph/state.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/llm/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/llm/base.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/llm/factory.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/llm/openai.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/base.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/client.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/document.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/error.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/graph.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/history.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/job.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/prompt.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/repository.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/rule.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/session.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/user.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/models/workflow.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/observability/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/observability/audit.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/observability/logging.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/observability/tracing.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/cli/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/cli/commands/auth.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/cli/commands/ide.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/cli/commands/mcp.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/cli/commands/sync.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/cli/commands/update.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/cli/utils/config.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/cli/utils/git.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/cli/utils/version.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/http/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/http/admin/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/http/admin/context.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/http/admin/dashboard.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/http/admin/jobs.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/http/admin/prompts.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/http/admin/routes.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/presentation/http/admin/search.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/prompts/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/prompts/formatter.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/resources/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/retrieval/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/retrieval/hybrid.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/retrieval/mmr.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/retrieval/multi_hop.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/runtime.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/document.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/error.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/feedback.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/graph.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/history.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/milvus/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/milvus/client.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/milvus/collections.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/milvus/vector_store.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/mongodb/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/mongodb/client.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/mongodb/indexes.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/repo_state.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/rule.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/store/vector.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/tools/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/tools/auth.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/tools/graph.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/tools/ingest.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/tools/repo_scanner.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/tools/search.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/transport/__init__.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/transport/sse.py +0 -0
- {minder_cli-0.4.2 → minder_cli-0.4.5}/src/minder/transport/stdio.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: minder-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.5
|
|
4
4
|
Summary: Minder CLI is the command-line interface for the Minder self-hosted MCP platform.
|
|
5
5
|
Project-URL: Homepage, https://github.com/hiimtrung/minder
|
|
6
6
|
Project-URL: Repository, https://github.com/hiimtrung/minder
|
|
@@ -31,6 +31,7 @@ Requires-Dist: pydantic-settings[toml]>=2.13.1; extra == 'server'
|
|
|
31
31
|
Requires-Dist: pydantic>=2.12.5; extra == 'server'
|
|
32
32
|
Requires-Dist: pyjwt>=2.12.1; extra == 'server'
|
|
33
33
|
Requires-Dist: pymilvus>=2.6.12; extra == 'server'
|
|
34
|
+
Requires-Dist: redis>=4.2.0; extra == 'server'
|
|
34
35
|
Requires-Dist: sqlalchemy[asyncio]>=2.0.49; extra == 'server'
|
|
35
36
|
Requires-Dist: yarl>=1.16.0; extra == 'server'
|
|
36
37
|
Requires-Dist: zipp>=3.21.0; extra == 'server'
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "minder-cli"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.5"
|
|
8
8
|
description = "Minder CLI is the command-line interface for the Minder self-hosted MCP platform."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.14"
|
|
@@ -38,6 +38,7 @@ server = [
|
|
|
38
38
|
"pydantic-settings[toml]>=2.13.1",
|
|
39
39
|
"pyjwt>=2.12.1",
|
|
40
40
|
"pymilvus>=2.6.12",
|
|
41
|
+
"redis>=4.2.0",
|
|
41
42
|
"sqlalchemy[asyncio]>=2.0.49",
|
|
42
43
|
"yarl>=1.16.0",
|
|
43
44
|
"zipp>=3.21.0",
|
|
@@ -72,6 +72,7 @@ class AuditEventPayload(TypedDict):
|
|
|
72
72
|
resource_name: str | None # human-readable name for the resource
|
|
73
73
|
outcome: str
|
|
74
74
|
created_at: str | None
|
|
75
|
+
audit_metadata: dict | None
|
|
75
76
|
|
|
76
77
|
|
|
77
78
|
class ClientListPayload(TypedDict):
|
|
@@ -129,6 +130,28 @@ class AdminSessionPayload(TypedDict):
|
|
|
129
130
|
role: str
|
|
130
131
|
|
|
131
132
|
|
|
133
|
+
class SessionPayload(TypedDict):
|
|
134
|
+
id: str
|
|
135
|
+
user_id: str | None
|
|
136
|
+
client_id: str | None
|
|
137
|
+
name: str | None
|
|
138
|
+
repo_id: str | None
|
|
139
|
+
project_context: dict[str, Any]
|
|
140
|
+
active_skills: dict[str, Any]
|
|
141
|
+
state: dict[str, Any]
|
|
142
|
+
ttl: int
|
|
143
|
+
created_at: str
|
|
144
|
+
last_active: str
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class SessionListPayload(TypedDict):
|
|
148
|
+
sessions: list[SessionPayload]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class SessionDetailPayload(TypedDict):
|
|
152
|
+
session: SessionPayload
|
|
153
|
+
|
|
154
|
+
|
|
132
155
|
# ---------------------------------------------------------------------------
|
|
133
156
|
# User management
|
|
134
157
|
# ---------------------------------------------------------------------------
|
|
@@ -198,6 +221,7 @@ class RepositoryPayload(TypedDict):
|
|
|
198
221
|
remote_url: str | None
|
|
199
222
|
default_branch: str | None
|
|
200
223
|
tracked_branches: list[str]
|
|
224
|
+
workflow_id: str | None
|
|
201
225
|
workflow_name: str | None
|
|
202
226
|
workflow_state: str | None
|
|
203
227
|
current_step: str | None
|
|
@@ -260,6 +284,7 @@ class UpdateRepositoryPayload(TypedDict, total=False):
|
|
|
260
284
|
remote_url: str | None
|
|
261
285
|
default_branch: str | None
|
|
262
286
|
path: str
|
|
287
|
+
workflow_id: str | None
|
|
263
288
|
|
|
264
289
|
|
|
265
290
|
class DeleteRepositoryPayload(TypedDict):
|
|
@@ -6,6 +6,7 @@ from collections import Counter
|
|
|
6
6
|
from datetime import UTC, datetime
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any
|
|
9
|
+
|
|
9
10
|
from urllib.parse import urlsplit
|
|
10
11
|
|
|
11
12
|
from minder.application.admin.dto import (
|
|
@@ -49,13 +50,19 @@ from minder.application.admin.dto import (
|
|
|
49
50
|
WorkflowListPayload,
|
|
50
51
|
WorkflowPayload,
|
|
51
52
|
WorkflowStepPayload,
|
|
53
|
+
SessionPayload,
|
|
54
|
+
SessionListPayload,
|
|
55
|
+
SessionDetailPayload,
|
|
52
56
|
)
|
|
53
57
|
from minder.auth.service import AuthService
|
|
54
58
|
from minder.config import MinderConfig
|
|
59
|
+
from minder.observability.audit import AuditEmitter
|
|
55
60
|
from minder.store.interfaces import IGraphRepository, IOperationalStore
|
|
56
61
|
from minder.tools.graph import GraphTools
|
|
57
62
|
from minder.tools.registry import SCOPEABLE_TOOLS
|
|
58
63
|
|
|
64
|
+
_UNSET: Any = object() # sentinel for optional update fields
|
|
65
|
+
|
|
59
66
|
DASHBOARD_TOOL_SCOPE_OPTIONS = [tool.name for tool in SCOPEABLE_TOOLS]
|
|
60
67
|
|
|
61
68
|
DASHBOARD_TOOL_SCOPE_PRESETS: dict[str, list[str]] = {
|
|
@@ -96,6 +103,7 @@ class AdminConsoleUseCases:
|
|
|
96
103
|
self._config = config
|
|
97
104
|
self._graph_store = graph_store
|
|
98
105
|
self._graph_tools = GraphTools(graph_store, store)
|
|
106
|
+
self._audit = AuditEmitter(store)
|
|
99
107
|
|
|
100
108
|
async def has_admin_users(self) -> bool:
|
|
101
109
|
return await self._auth_service.has_admin_users()
|
|
@@ -413,6 +421,7 @@ class AdminConsoleUseCases:
|
|
|
413
421
|
"resource_name": None,
|
|
414
422
|
"outcome": event.outcome,
|
|
415
423
|
"created_at": event.created_at.isoformat() if event.created_at else None,
|
|
424
|
+
"audit_metadata": getattr(event, "audit_metadata", None),
|
|
416
425
|
}
|
|
417
426
|
|
|
418
427
|
async def serialize_audit_event_enriched(self, event: Any) -> AuditEventPayload:
|
|
@@ -481,6 +490,17 @@ class AdminConsoleUseCases:
|
|
|
481
490
|
role=role,
|
|
482
491
|
password=password,
|
|
483
492
|
)
|
|
493
|
+
await self._audit.emit(
|
|
494
|
+
actor_type="admin",
|
|
495
|
+
actor_id=str(user.id), # in this context, the new user IS the actor if it's initial setup?
|
|
496
|
+
# actually, if an admin creates a user, we should know WHO did it.
|
|
497
|
+
# but create_user use case doesn't take actor_id yet.
|
|
498
|
+
event_type="user.created",
|
|
499
|
+
resource_type="user",
|
|
500
|
+
resource_id=str(user.id),
|
|
501
|
+
outcome="success",
|
|
502
|
+
metadata={"username": username, "role": role},
|
|
503
|
+
)
|
|
484
504
|
return {"user": self.serialize_user(user), "api_key": api_key}
|
|
485
505
|
|
|
486
506
|
async def get_user_detail(self, user_id: uuid.UUID) -> UserDetailPayload:
|
|
@@ -514,6 +534,16 @@ class AdminConsoleUseCases:
|
|
|
514
534
|
updated = await self._store.update_user(user_id, **kwargs)
|
|
515
535
|
if updated is None:
|
|
516
536
|
raise LookupError(f"User {user_id} not found")
|
|
537
|
+
|
|
538
|
+
await self._audit.emit(
|
|
539
|
+
actor_type="admin",
|
|
540
|
+
actor_id=str(user_id), # same issue: no actor_id passed to use case
|
|
541
|
+
event_type="user.updated",
|
|
542
|
+
resource_type="user",
|
|
543
|
+
resource_id=str(user_id),
|
|
544
|
+
outcome="success",
|
|
545
|
+
metadata={"fields_changed": list(kwargs.keys())},
|
|
546
|
+
)
|
|
517
547
|
return await self.get_user_detail(user_id)
|
|
518
548
|
|
|
519
549
|
async def deactivate_user(self, user_id: uuid.UUID) -> UserDetailPayload:
|
|
@@ -623,6 +653,78 @@ class AdminConsoleUseCases:
|
|
|
623
653
|
),
|
|
624
654
|
}
|
|
625
655
|
|
|
656
|
+
# ------------------------------------------------------------------
|
|
657
|
+
# Session management
|
|
658
|
+
# ------------------------------------------------------------------
|
|
659
|
+
|
|
660
|
+
async def list_sessions(self) -> SessionListPayload:
|
|
661
|
+
sessions = await self._store.list_sessions()
|
|
662
|
+
return {"sessions": [self.serialize_session(s) for s in sessions]}
|
|
663
|
+
|
|
664
|
+
async def get_session_detail(self, session_id: uuid.UUID) -> SessionDetailPayload:
|
|
665
|
+
session = await self._store.get_session_by_id(session_id)
|
|
666
|
+
if session is None:
|
|
667
|
+
raise LookupError(f"Session {session_id} not found")
|
|
668
|
+
return {"session": self.serialize_session(session)}
|
|
669
|
+
|
|
670
|
+
async def update_session(
|
|
671
|
+
self,
|
|
672
|
+
session_id: uuid.UUID,
|
|
673
|
+
*,
|
|
674
|
+
state: dict[str, Any] | None = None,
|
|
675
|
+
active_skills: dict[str, Any] | None = None,
|
|
676
|
+
project_context: dict[str, Any] | None = None,
|
|
677
|
+
) -> SessionPayload:
|
|
678
|
+
existing = await self._store.get_session_by_id(session_id)
|
|
679
|
+
if existing is None:
|
|
680
|
+
raise LookupError(f"Session {session_id} not found")
|
|
681
|
+
updates: dict[str, Any] = {}
|
|
682
|
+
if state is not None:
|
|
683
|
+
updates["state"] = state
|
|
684
|
+
if active_skills is not None:
|
|
685
|
+
updates["active_skills"] = active_skills
|
|
686
|
+
if project_context is not None:
|
|
687
|
+
updates["project_context"] = project_context
|
|
688
|
+
if not updates:
|
|
689
|
+
return self.serialize_session(existing)
|
|
690
|
+
from datetime import UTC, datetime
|
|
691
|
+
updates["last_active"] = datetime.now(UTC)
|
|
692
|
+
updated = await self._store.update_session(session_id, **updates)
|
|
693
|
+
if updated is None:
|
|
694
|
+
raise LookupError(f"Session {session_id} not found")
|
|
695
|
+
return self.serialize_session(updated)
|
|
696
|
+
|
|
697
|
+
async def delete_session(self, session_id: uuid.UUID) -> dict[str, bool]:
|
|
698
|
+
existing = await self._store.get_session_by_id(session_id)
|
|
699
|
+
if existing is None:
|
|
700
|
+
raise LookupError(f"Session {session_id} not found")
|
|
701
|
+
await self._store.delete_session(session_id)
|
|
702
|
+
return {"deleted": True}
|
|
703
|
+
|
|
704
|
+
@staticmethod
|
|
705
|
+
def serialize_session(session: Any) -> SessionPayload:
|
|
706
|
+
return {
|
|
707
|
+
"id": str(session.id),
|
|
708
|
+
"user_id": str(session.user_id) if session.user_id else None,
|
|
709
|
+
"client_id": str(session.client_id) if session.client_id else None,
|
|
710
|
+
"name": session.name,
|
|
711
|
+
"repo_id": str(session.repo_id) if session.repo_id else None,
|
|
712
|
+
"project_context": dict(getattr(session, "project_context", {}) or {}),
|
|
713
|
+
"active_skills": dict(getattr(session, "active_skills", {}) or {}),
|
|
714
|
+
"state": dict(getattr(session, "state", {}) or {}),
|
|
715
|
+
"ttl": int(getattr(session, "ttl", 86400) or 86400),
|
|
716
|
+
"created_at": (
|
|
717
|
+
session.created_at.isoformat()
|
|
718
|
+
if getattr(session, "created_at", None)
|
|
719
|
+
else ""
|
|
720
|
+
),
|
|
721
|
+
"last_active": (
|
|
722
|
+
session.last_active.isoformat()
|
|
723
|
+
if getattr(session, "last_active", None)
|
|
724
|
+
else ""
|
|
725
|
+
),
|
|
726
|
+
}
|
|
727
|
+
|
|
626
728
|
# ------------------------------------------------------------------
|
|
627
729
|
# Repository management
|
|
628
730
|
# ------------------------------------------------------------------
|
|
@@ -661,6 +763,7 @@ class AdminConsoleUseCases:
|
|
|
661
763
|
remote_url: str | None = None,
|
|
662
764
|
default_branch: str | None = None,
|
|
663
765
|
path: str | None = None,
|
|
766
|
+
workflow_id: Any = _UNSET,
|
|
664
767
|
) -> RepositoryDetailPayload:
|
|
665
768
|
repository = await self._store.get_repository_by_id(repo_id)
|
|
666
769
|
if repository is None:
|
|
@@ -687,6 +790,15 @@ class AdminConsoleUseCases:
|
|
|
687
790
|
if not normalized_path:
|
|
688
791
|
raise ValueError("Repository path is required")
|
|
689
792
|
updates["state_path"] = normalized_path
|
|
793
|
+
if workflow_id is not _UNSET:
|
|
794
|
+
if workflow_id is None or str(workflow_id).strip() == "":
|
|
795
|
+
updates["workflow_id"] = None
|
|
796
|
+
else:
|
|
797
|
+
wf_id = uuid.UUID(str(workflow_id))
|
|
798
|
+
workflow = await self._store.get_workflow_by_id(wf_id)
|
|
799
|
+
if workflow is None:
|
|
800
|
+
raise LookupError(f"Workflow {wf_id} not found")
|
|
801
|
+
updates["workflow_id"] = str(wf_id)
|
|
690
802
|
|
|
691
803
|
updated = await self._store.update_repository(repo_id, **updates)
|
|
692
804
|
if updated is None:
|
|
@@ -772,6 +884,7 @@ class AdminConsoleUseCases:
|
|
|
772
884
|
tracked: list[str] = (
|
|
773
885
|
list(raw_branches) if isinstance(raw_branches, list) else []
|
|
774
886
|
)
|
|
887
|
+
raw_workflow_id = getattr(repo, "workflow_id", None)
|
|
775
888
|
return {
|
|
776
889
|
"id": str(repo.id),
|
|
777
890
|
"name": getattr(repo, "repo_name", getattr(repo, "name", "")),
|
|
@@ -779,6 +892,7 @@ class AdminConsoleUseCases:
|
|
|
779
892
|
"remote_url": _normalize_repository_remote(getattr(repo, "repo_url", None)),
|
|
780
893
|
"default_branch": getattr(repo, "default_branch", None),
|
|
781
894
|
"tracked_branches": tracked,
|
|
895
|
+
"workflow_id": str(raw_workflow_id) if raw_workflow_id else None,
|
|
782
896
|
"workflow_name": getattr(state, "workflow_name", None) if state else None,
|
|
783
897
|
"workflow_state": getattr(state, "state", None) if state else None,
|
|
784
898
|
"current_step": getattr(state, "current_step", None) if state else None,
|
|
@@ -87,6 +87,18 @@ def build_graph_store(config: MinderConfig) -> IGraphRepository | None:
|
|
|
87
87
|
if provider == "mongodb":
|
|
88
88
|
provider = "sqlite"
|
|
89
89
|
|
|
90
|
+
if provider == "mongodb":
|
|
91
|
+
from minder.store.mongodb.client import MongoClient
|
|
92
|
+
from minder.store.mongodb.graph_store import MongoGraphStore
|
|
93
|
+
|
|
94
|
+
client = MongoClient(
|
|
95
|
+
uri=config.mongodb.uri,
|
|
96
|
+
database=config.mongodb.database,
|
|
97
|
+
min_pool_size=config.mongodb.min_pool_size,
|
|
98
|
+
max_pool_size=config.mongodb.max_pool_size,
|
|
99
|
+
)
|
|
100
|
+
return MongoGraphStore(client) # type: ignore[return-value]
|
|
101
|
+
|
|
90
102
|
if provider in ("sqlite", "postgresql"):
|
|
91
103
|
from minder.store.graph import KnowledgeGraphStore
|
|
92
104
|
|
|
@@ -105,5 +117,5 @@ def build_graph_store(config: MinderConfig) -> IGraphRepository | None:
|
|
|
105
117
|
|
|
106
118
|
raise ValueError(
|
|
107
119
|
f"Unsupported graph_store.provider '{provider}'. "
|
|
108
|
-
"Supported: 'auto', 'sqlite', 'postgresql'."
|
|
120
|
+
"Supported: 'auto', 'mongodb', 'sqlite', 'postgresql'."
|
|
109
121
|
)
|
|
@@ -274,6 +274,12 @@ def build_transport(
|
|
|
274
274
|
open_files=open_files,
|
|
275
275
|
)
|
|
276
276
|
|
|
277
|
+
async def minder_session_summarize(
|
|
278
|
+
*, user=None, session_id: str
|
|
279
|
+
) -> dict[str, Any]: # noqa: ANN001
|
|
280
|
+
del user
|
|
281
|
+
return await session_tools.minder_session_summarize(uuid.UUID(session_id))
|
|
282
|
+
|
|
277
283
|
async def minder_session_cleanup(
|
|
278
284
|
*,
|
|
279
285
|
user=None,
|
|
@@ -368,6 +374,22 @@ def build_transport(
|
|
|
368
374
|
del user
|
|
369
375
|
return await memory_tools.minder_memory_list()
|
|
370
376
|
|
|
377
|
+
async def minder_memory_update(
|
|
378
|
+
*,
|
|
379
|
+
user=None,
|
|
380
|
+
memory_id: str,
|
|
381
|
+
title: str | None = None,
|
|
382
|
+
content: str | None = None,
|
|
383
|
+
tags: list[str] | None = None,
|
|
384
|
+
) -> dict[str, Any]: # noqa: ANN001
|
|
385
|
+
del user
|
|
386
|
+
return await memory_tools.minder_memory_update(
|
|
387
|
+
memory_id,
|
|
388
|
+
title=title,
|
|
389
|
+
content=content,
|
|
390
|
+
tags=tags,
|
|
391
|
+
)
|
|
392
|
+
|
|
371
393
|
async def minder_memory_delete(
|
|
372
394
|
*, user=None, skill_id: str
|
|
373
395
|
) -> dict[str, bool]: # noqa: ANN001
|
|
@@ -456,6 +478,7 @@ def build_transport(
|
|
|
456
478
|
artifact_types: list[str] | None = None,
|
|
457
479
|
provenance: str | None = None,
|
|
458
480
|
quality_score: float | None = None,
|
|
481
|
+
deprecated: bool | None = None,
|
|
459
482
|
) -> dict[str, Any]: # noqa: ANN001
|
|
460
483
|
del user
|
|
461
484
|
return await skill_tools.minder_skill_update(
|
|
@@ -468,6 +491,7 @@ def build_transport(
|
|
|
468
491
|
artifact_types=artifact_types,
|
|
469
492
|
provenance=provenance,
|
|
470
493
|
quality_score=quality_score,
|
|
494
|
+
deprecated=deprecated,
|
|
471
495
|
)
|
|
472
496
|
|
|
473
497
|
async def minder_skill_delete(
|
|
@@ -669,6 +693,12 @@ def build_transport(
|
|
|
669
693
|
require_auth=True,
|
|
670
694
|
description=TOOL_DESCRIPTIONS["minder_session_context"],
|
|
671
695
|
)
|
|
696
|
+
transport.register_tool(
|
|
697
|
+
"minder_session_summarize",
|
|
698
|
+
minder_session_summarize,
|
|
699
|
+
require_auth=True,
|
|
700
|
+
description=TOOL_DESCRIPTIONS["minder_session_summarize"],
|
|
701
|
+
)
|
|
672
702
|
transport.register_tool(
|
|
673
703
|
"minder_session_cleanup",
|
|
674
704
|
minder_session_cleanup,
|
|
@@ -717,6 +747,12 @@ def build_transport(
|
|
|
717
747
|
require_auth=True,
|
|
718
748
|
description=TOOL_DESCRIPTIONS["minder_memory_list"],
|
|
719
749
|
)
|
|
750
|
+
transport.register_tool(
|
|
751
|
+
"minder_memory_update",
|
|
752
|
+
minder_memory_update,
|
|
753
|
+
require_auth=True,
|
|
754
|
+
description=TOOL_DESCRIPTIONS["minder_memory_update"],
|
|
755
|
+
)
|
|
720
756
|
transport.register_tool(
|
|
721
757
|
"minder_memory_delete",
|
|
722
758
|
minder_memory_delete,
|
|
@@ -46,7 +46,7 @@ class LLMConfig(BaseModel):
|
|
|
46
46
|
litert_model_path: str = "~/.minder/models/gemma-4-E2B-it.litertlm"
|
|
47
47
|
litert_backend: str = "auto" # "auto" (GPU on Mac, CPU elsewhere) | "cpu" | "gpu"
|
|
48
48
|
litert_cache_dir: str = "~/.minder/cache/litert"
|
|
49
|
-
context_length: int =
|
|
49
|
+
context_length: int = 16384
|
|
50
50
|
temperature: float = 0.1
|
|
51
51
|
openai_api_key: Optional[str] = None
|
|
52
52
|
openai_model: str = "gpt-4o-mini"
|
|
@@ -67,16 +67,17 @@ class RelationalStoreConfig(BaseModel):
|
|
|
67
67
|
|
|
68
68
|
class GraphStoreConfig(BaseModel):
|
|
69
69
|
enabled: bool = True
|
|
70
|
-
provider: str = "auto" # "auto" | "sqlite" | "postgresql"
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
provider: str = "auto" # "auto" | "mongodb" | "sqlite" | "postgresql"
|
|
71
|
+
# auto: mirrors relational_store.provider (mongodb → mongodb, sqlite → sqlite, postgresql → postgresql)
|
|
72
|
+
db_path: str = "~/.minder/data/graph.db" # sqlite only
|
|
73
|
+
uri: str = "postgresql+asyncpg://localhost/minder_graph" # postgresql only
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
class MongoDBConfig(BaseModel):
|
|
76
77
|
uri: str = "mongodb://localhost:27017"
|
|
77
78
|
database: str = "minder"
|
|
78
|
-
min_pool_size: int =
|
|
79
|
-
max_pool_size: int =
|
|
79
|
+
min_pool_size: int = 2
|
|
80
|
+
max_pool_size: int = 10
|
|
80
81
|
|
|
81
82
|
|
|
82
83
|
class RedisConfig(BaseModel):
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Compacts chat history to fit within the LLM context window.
|
|
3
|
+
|
|
4
|
+
Strategy (HistoryCompactor):
|
|
5
|
+
- Always keep the `keep_recent` most recent messages verbatim (recency bias).
|
|
6
|
+
- If older messages still fit in the remaining budget, keep them all.
|
|
7
|
+
- Otherwise drop oldest messages and prepend a single notice message so the
|
|
8
|
+
LLM knows part of the history was omitted.
|
|
9
|
+
|
|
10
|
+
No LLM call is made here — this is a pure sliding-window approach so it adds
|
|
11
|
+
zero latency on every query.
|
|
12
|
+
|
|
13
|
+
P7-T06 — SummarizingCompactor extends the base with an optional LLM
|
|
14
|
+
summarization step: dropped messages are condensed into a single summary
|
|
15
|
+
message instead of a terse omission notice.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
from collections.abc import Callable
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
# Approximate chars-per-token for chat content (UTF-8 prose / code mix).
|
|
27
|
+
_CHARS_PER_TOKEN = 3
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class HistoryCompactor:
|
|
31
|
+
"""Trims chat_history to fit within a fraction of the LLM context window."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
*,
|
|
36
|
+
keep_recent: int = 6,
|
|
37
|
+
history_budget_ratio: float = 0.40,
|
|
38
|
+
) -> None:
|
|
39
|
+
# keep_recent: number of latest messages always preserved verbatim.
|
|
40
|
+
# history_budget_ratio: fraction of context_length tokens available for history.
|
|
41
|
+
self._keep_recent = keep_recent
|
|
42
|
+
self._history_budget_ratio = history_budget_ratio
|
|
43
|
+
|
|
44
|
+
def compact(
|
|
45
|
+
self,
|
|
46
|
+
history: list[dict[str, Any]],
|
|
47
|
+
*,
|
|
48
|
+
context_length: int,
|
|
49
|
+
) -> list[dict[str, Any]]:
|
|
50
|
+
"""Return a (possibly shorter) history list that fits within the budget.
|
|
51
|
+
|
|
52
|
+
If messages are dropped a synthetic notice is prepended so the model
|
|
53
|
+
knows earlier context exists but was truncated.
|
|
54
|
+
"""
|
|
55
|
+
if not history:
|
|
56
|
+
return []
|
|
57
|
+
|
|
58
|
+
budget_chars = int(context_length * self._history_budget_ratio) * _CHARS_PER_TOKEN
|
|
59
|
+
total_chars = sum(len(str(m.get("content", ""))) for m in history)
|
|
60
|
+
|
|
61
|
+
if total_chars <= budget_chars:
|
|
62
|
+
return history
|
|
63
|
+
|
|
64
|
+
# Split into recent (always kept) and older (candidates for dropping).
|
|
65
|
+
if len(history) > self._keep_recent:
|
|
66
|
+
recent = history[-self._keep_recent :]
|
|
67
|
+
older = history[: -self._keep_recent]
|
|
68
|
+
else:
|
|
69
|
+
recent = list(history)
|
|
70
|
+
older = []
|
|
71
|
+
|
|
72
|
+
recent_chars = sum(len(str(m.get("content", ""))) for m in recent)
|
|
73
|
+
|
|
74
|
+
# If even the recent slice alone exceeds the budget, truncate from the
|
|
75
|
+
# oldest message in that slice (keeping tail content within each message).
|
|
76
|
+
if recent_chars > budget_chars:
|
|
77
|
+
recent = self._fit_to_budget(recent, budget_chars)
|
|
78
|
+
dropped_count = len(history) - len(recent)
|
|
79
|
+
logger.warning(
|
|
80
|
+
"chat_history compacted: dropped %d messages (context_length=%d)",
|
|
81
|
+
dropped_count,
|
|
82
|
+
context_length,
|
|
83
|
+
)
|
|
84
|
+
if dropped_count > 0:
|
|
85
|
+
return [_notice(dropped_count)] + recent
|
|
86
|
+
return recent
|
|
87
|
+
|
|
88
|
+
# Fit as many older messages as possible within leftover budget.
|
|
89
|
+
older_budget = budget_chars - recent_chars
|
|
90
|
+
kept_older = self._fit_to_budget(older, older_budget)
|
|
91
|
+
dropped_count = len(older) - len(kept_older)
|
|
92
|
+
|
|
93
|
+
if dropped_count > 0:
|
|
94
|
+
logger.info(
|
|
95
|
+
"chat_history compacted: dropped %d older messages (context_length=%d)",
|
|
96
|
+
dropped_count,
|
|
97
|
+
context_length,
|
|
98
|
+
)
|
|
99
|
+
return [_notice(dropped_count)] + kept_older + recent
|
|
100
|
+
|
|
101
|
+
return kept_older + recent
|
|
102
|
+
|
|
103
|
+
# ------------------------------------------------------------------
|
|
104
|
+
# Internal helpers
|
|
105
|
+
# ------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _fit_to_budget(
|
|
109
|
+
messages: list[dict[str, Any]],
|
|
110
|
+
budget_chars: int,
|
|
111
|
+
) -> list[dict[str, Any]]:
|
|
112
|
+
"""Return the maximal tail of *messages* whose total chars ≤ budget_chars."""
|
|
113
|
+
kept: list[dict[str, Any]] = []
|
|
114
|
+
remaining = budget_chars
|
|
115
|
+
for msg in reversed(messages):
|
|
116
|
+
content = str(msg.get("content", ""))
|
|
117
|
+
if remaining <= 0:
|
|
118
|
+
break
|
|
119
|
+
if len(content) > remaining:
|
|
120
|
+
# Truncate the message to fit, keeping the tail (most recent content).
|
|
121
|
+
truncated = {**msg, "content": "…" + content[-(remaining - 1) :]}
|
|
122
|
+
kept.insert(0, truncated)
|
|
123
|
+
remaining = 0
|
|
124
|
+
else:
|
|
125
|
+
kept.insert(0, msg)
|
|
126
|
+
remaining -= len(content)
|
|
127
|
+
return kept
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _notice(dropped_count: int) -> dict[str, Any]:
|
|
131
|
+
return {
|
|
132
|
+
"role": "user",
|
|
133
|
+
"content": (
|
|
134
|
+
f"[{dropped_count} earlier message(s) omitted — context window limit reached]"
|
|
135
|
+
),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class SummarizingCompactor(HistoryCompactor):
|
|
140
|
+
"""P7-T06 — Compactor that replaces omission notices with LLM summaries.
|
|
141
|
+
|
|
142
|
+
The `summarizer` callable receives the list of dropped messages and
|
|
143
|
+
returns a short summary string. If the callable raises or returns an
|
|
144
|
+
empty string, the base-class omission notice is used as a fallback.
|
|
145
|
+
|
|
146
|
+
Because summarization adds latency, only activate this when an LLM is
|
|
147
|
+
available and the history budget is genuinely exceeded.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
def __init__(
|
|
151
|
+
self,
|
|
152
|
+
summarizer: Callable[[list[dict[str, Any]]], str],
|
|
153
|
+
*,
|
|
154
|
+
keep_recent: int = 6,
|
|
155
|
+
history_budget_ratio: float = 0.40,
|
|
156
|
+
) -> None:
|
|
157
|
+
super().__init__(
|
|
158
|
+
keep_recent=keep_recent,
|
|
159
|
+
history_budget_ratio=history_budget_ratio,
|
|
160
|
+
)
|
|
161
|
+
self._summarizer = summarizer
|
|
162
|
+
|
|
163
|
+
def compact(
|
|
164
|
+
self,
|
|
165
|
+
history: list[dict[str, Any]],
|
|
166
|
+
*,
|
|
167
|
+
context_length: int,
|
|
168
|
+
) -> list[dict[str, Any]]:
|
|
169
|
+
budget_chars = int(context_length * self._history_budget_ratio) * _CHARS_PER_TOKEN
|
|
170
|
+
total_chars = sum(len(str(m.get("content", ""))) for m in history)
|
|
171
|
+
|
|
172
|
+
if total_chars <= budget_chars:
|
|
173
|
+
return history
|
|
174
|
+
|
|
175
|
+
if len(history) > self._keep_recent:
|
|
176
|
+
recent = history[-self._keep_recent :]
|
|
177
|
+
dropped = history[: -self._keep_recent]
|
|
178
|
+
else:
|
|
179
|
+
return super().compact(history, context_length=context_length)
|
|
180
|
+
|
|
181
|
+
summary_message = self._summarize(dropped)
|
|
182
|
+
recent_chars = sum(len(str(m.get("content", ""))) for m in recent)
|
|
183
|
+
summary_chars = len(str(summary_message.get("content", "")))
|
|
184
|
+
|
|
185
|
+
if recent_chars + summary_chars <= budget_chars:
|
|
186
|
+
return [summary_message] + recent
|
|
187
|
+
|
|
188
|
+
return super().compact(history, context_length=context_length)
|
|
189
|
+
|
|
190
|
+
def _summarize(self, dropped: list[dict[str, Any]]) -> dict[str, Any]:
|
|
191
|
+
try:
|
|
192
|
+
text = self._summarizer(dropped)
|
|
193
|
+
if text and text.strip():
|
|
194
|
+
return {"role": "user", "content": f"[Earlier context summary: {text.strip()}]"}
|
|
195
|
+
except Exception as exc:
|
|
196
|
+
logger.debug("SummarizingCompactor summarizer failed: %s", exc)
|
|
197
|
+
return _notice(len(dropped))
|
|
@@ -4,12 +4,14 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
|
|
5
5
|
from minder.graph.edges import determine_next_edge
|
|
6
6
|
from minder.graph.nodes import (
|
|
7
|
+
ClarificationNode,
|
|
7
8
|
EvaluatorNode,
|
|
8
9
|
GuardNode,
|
|
9
10
|
LLMNode,
|
|
10
11
|
PlanningNode,
|
|
11
12
|
ReasoningNode,
|
|
12
13
|
RerankerNode,
|
|
14
|
+
ReflectionNode,
|
|
13
15
|
RetrieverNode,
|
|
14
16
|
VerificationNode,
|
|
15
17
|
WorkflowPlannerNode,
|
|
@@ -22,6 +24,7 @@ from minder.graph.state import GraphState
|
|
|
22
24
|
class GraphNodes:
|
|
23
25
|
workflow_planner: WorkflowPlannerNode
|
|
24
26
|
planning: PlanningNode
|
|
27
|
+
clarification: ClarificationNode
|
|
25
28
|
retriever: RetrieverNode
|
|
26
29
|
reasoning: ReasoningNode
|
|
27
30
|
llm: LLMNode
|
|
@@ -29,6 +32,7 @@ class GraphNodes:
|
|
|
29
32
|
verification: VerificationNode
|
|
30
33
|
evaluator: EvaluatorNode
|
|
31
34
|
reranker: RerankerNode | None = field(default=None)
|
|
35
|
+
reflection: ReflectionNode | None = field(default=None)
|
|
32
36
|
|
|
33
37
|
|
|
34
38
|
class InternalGraphExecutor:
|
|
@@ -41,6 +45,9 @@ class InternalGraphExecutor:
|
|
|
41
45
|
state.metadata["orchestration_runtime"] = "internal"
|
|
42
46
|
state = await self._nodes.workflow_planner.run(state)
|
|
43
47
|
state = self._nodes.planning.run(state)
|
|
48
|
+
state = self._nodes.clarification.run(state)
|
|
49
|
+
if state.metadata.get("needs_clarification"):
|
|
50
|
+
return state
|
|
44
51
|
state = await self._nodes.retriever.run(state)
|
|
45
52
|
if self._nodes.reranker is not None:
|
|
46
53
|
state = await self._nodes.reranker.run(state)
|
|
@@ -88,6 +95,10 @@ class InternalGraphExecutor:
|
|
|
88
95
|
|
|
89
96
|
state = self._nodes.evaluator.run(state)
|
|
90
97
|
state.metadata["edge"] = determine_next_edge(state)
|
|
98
|
+
|
|
99
|
+
if self._nodes.reflection is not None:
|
|
100
|
+
state = await self._nodes.reflection.run(state)
|
|
101
|
+
|
|
91
102
|
return state
|
|
92
103
|
|
|
93
104
|
|