nexus-coding-agent 0.1.0__py3-none-any.whl
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.
- nexus/__init__.py +4 -0
- nexus/application/__init__.py +2 -0
- nexus/application/approval_service.py +130 -0
- nexus/application/diff_service.py +177 -0
- nexus/application/event_publisher.py +180 -0
- nexus/application/execution_context.py +29 -0
- nexus/application/execution_ledger.py +126 -0
- nexus/application/model_call_context.py +29 -0
- nexus/application/observed_model_gateway.py +194 -0
- nexus/application/plan_approval_service.py +250 -0
- nexus/application/planning.py +668 -0
- nexus/application/runtime.py +624 -0
- nexus/application/session_service.py +411 -0
- nexus/application/structured_output.py +96 -0
- nexus/application/telemetry.py +396 -0
- nexus/application/telemetry_subscriber.py +216 -0
- nexus/application/tool_runtime.py +496 -0
- nexus/application/tracing_dispatcher.py +283 -0
- nexus/application/validation.py +293 -0
- nexus/config/__init__.py +17 -0
- nexus/config/loader.py +253 -0
- nexus/config/models.py +310 -0
- nexus/context/__init__.py +6 -0
- nexus/context/budget.py +24 -0
- nexus/context/builder.py +92 -0
- nexus/context/chunking.py +65 -0
- nexus/context/explorer.py +246 -0
- nexus/context/integration.py +63 -0
- nexus/context/manager.py +328 -0
- nexus/context/retrieval.py +261 -0
- nexus/domain/__init__.py +2 -0
- nexus/domain/agent_decision.py +113 -0
- nexus/domain/agent_state.py +49 -0
- nexus/domain/approvals.py +64 -0
- nexus/domain/context.py +108 -0
- nexus/domain/exploration.py +86 -0
- nexus/domain/mcp.py +51 -0
- nexus/domain/model.py +173 -0
- nexus/domain/observability.py +326 -0
- nexus/domain/persistence.py +122 -0
- nexus/domain/planning.py +329 -0
- nexus/domain/ports/__init__.py +61 -0
- nexus/domain/ports/agent_decision.py +9 -0
- nexus/domain/ports/approval_unit_of_work.py +27 -0
- nexus/domain/ports/checkpoint_provider.py +11 -0
- nexus/domain/ports/context.py +71 -0
- nexus/domain/ports/events.py +21 -0
- nexus/domain/ports/graph_runtime.py +30 -0
- nexus/domain/ports/mcp.py +23 -0
- nexus/domain/ports/model_gateway.py +21 -0
- nexus/domain/ports/model_input_budget.py +15 -0
- nexus/domain/ports/planning.py +62 -0
- nexus/domain/ports/repositories.py +54 -0
- nexus/domain/ports/repository_context.py +18 -0
- nexus/domain/ports/session_unit_of_work.py +33 -0
- nexus/domain/ports/skills.py +41 -0
- nexus/domain/ports/tooling.py +39 -0
- nexus/domain/ports/tracer.py +13 -0
- nexus/domain/ports/validation.py +32 -0
- nexus/domain/runtime_events.py +445 -0
- nexus/domain/skills.py +129 -0
- nexus/domain/tooling.py +163 -0
- nexus/domain/validation.py +161 -0
- nexus/errors/__init__.py +82 -0
- nexus/evaluation/__init__.py +7 -0
- nexus/evaluation/assertions.py +4 -0
- nexus/evaluation/evaluator.py +263 -0
- nexus/evaluation/fixtures.py +36 -0
- nexus/evaluation/harness.py +606 -0
- nexus/evaluation/loader.py +415 -0
- nexus/evaluation/metrics.py +27 -0
- nexus/evaluation/models.py +362 -0
- nexus/evaluation/report.py +43 -0
- nexus/evaluation/runner.py +480 -0
- nexus/infrastructure/__init__.py +2 -0
- nexus/infrastructure/asyncio_compat.py +13 -0
- nexus/infrastructure/bootstrap/__init__.py +17 -0
- nexus/infrastructure/bootstrap/composition.py +663 -0
- nexus/infrastructure/checkpoint/__init__.py +5 -0
- nexus/infrastructure/checkpoint/postgres.py +62 -0
- nexus/infrastructure/database.py +39 -0
- nexus/infrastructure/embedding.py +61 -0
- nexus/infrastructure/graph/__init__.py +6 -0
- nexus/infrastructure/graph/day4_runtime.py +966 -0
- nexus/infrastructure/graph/langgraph_runtime.py +172 -0
- nexus/infrastructure/mcp/__init__.py +5 -0
- nexus/infrastructure/mcp/manager.py +380 -0
- nexus/infrastructure/model_gateway/__init__.py +6 -0
- nexus/infrastructure/model_gateway/openai_compatible.py +345 -0
- nexus/infrastructure/observability/__init__.py +7 -0
- nexus/infrastructure/observability/console.py +31 -0
- nexus/infrastructure/observability/langsmith.py +236 -0
- nexus/infrastructure/observability/structured_logger.py +70 -0
- nexus/infrastructure/persistence/__init__.py +13 -0
- nexus/infrastructure/persistence/approval_unit_of_work.py +74 -0
- nexus/infrastructure/persistence/models.py +146 -0
- nexus/infrastructure/persistence/repositories.py +326 -0
- nexus/infrastructure/persistence/unit_of_work.py +82 -0
- nexus/infrastructure/repository_files.py +117 -0
- nexus/infrastructure/sandbox/__init__.py +5 -0
- nexus/infrastructure/sandbox/local_process.py +353 -0
- nexus/infrastructure/semantic.py +276 -0
- nexus/infrastructure/semantic_models.py +56 -0
- nexus/interfaces/__init__.py +2 -0
- nexus/interfaces/cli/__init__.py +2 -0
- nexus/interfaces/cli/__main__.py +6 -0
- nexus/interfaces/cli/app.py +266 -0
- nexus/interfaces/cli/renderer.py +118 -0
- nexus/security/__init__.py +14 -0
- nexus/security/approval_policies.py +73 -0
- nexus/security/command_policy.py +117 -0
- nexus/security/executables.py +107 -0
- nexus/security/git_commands.py +65 -0
- nexus/security/workspace.py +93 -0
- nexus/skills/__init__.py +7 -0
- nexus/skills/builtin/__init__.py +1 -0
- nexus/skills/builtin/debug-python/SKILL.md +26 -0
- nexus/skills/builtin/review-repository/SKILL.md +26 -0
- nexus/skills/builtin/write-tests/SKILL.md +26 -0
- nexus/skills/loader.py +439 -0
- nexus/skills/registry.py +146 -0
- nexus/skills/selector.py +152 -0
- nexus/tools/__init__.py +6 -0
- nexus/tools/editing.py +312 -0
- nexus/tools/mcp.py +160 -0
- nexus/tools/native.py +654 -0
- nexus/tools/registry.py +27 -0
- nexus_coding_agent-0.1.0.dist-info/METADATA +142 -0
- nexus_coding_agent-0.1.0.dist-info/RECORD +131 -0
- nexus_coding_agent-0.1.0.dist-info/WHEEL +4 -0
- nexus_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
"""Assemble and dispose approved concrete application dependencies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from collections.abc import AsyncIterator, Callable
|
|
8
|
+
from contextlib import asynccontextmanager
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, cast
|
|
12
|
+
|
|
13
|
+
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
14
|
+
from langsmith import Client
|
|
15
|
+
|
|
16
|
+
from nexus.application.approval_service import ApprovalService
|
|
17
|
+
from nexus.application.diff_service import FinalDiffCollector
|
|
18
|
+
from nexus.application.event_publisher import (
|
|
19
|
+
InProcessEventPublisher,
|
|
20
|
+
LiveRuntimeEventBridge,
|
|
21
|
+
)
|
|
22
|
+
from nexus.application.execution_context import current_execution_context
|
|
23
|
+
from nexus.application.execution_ledger import RuntimeEventBuffer, ToolExecutionLedger
|
|
24
|
+
from nexus.application.observed_model_gateway import ObservedModelGateway
|
|
25
|
+
from nexus.application.plan_approval_service import PlanApprovalService
|
|
26
|
+
from nexus.application.planning import JsonAgentDecisionAdapter, ModelPlanner
|
|
27
|
+
from nexus.application.runtime import NexusRuntime
|
|
28
|
+
from nexus.application.session_service import SessionService
|
|
29
|
+
from nexus.application.telemetry import EventEnricher, TelemetryRedactor
|
|
30
|
+
from nexus.application.telemetry_subscriber import TelemetrySubscriber
|
|
31
|
+
from nexus.application.tool_runtime import RuntimeEventEmitter, ToolRuntime
|
|
32
|
+
from nexus.application.tracing_dispatcher import (
|
|
33
|
+
SafeTracerDispatcher,
|
|
34
|
+
TracerSink,
|
|
35
|
+
)
|
|
36
|
+
from nexus.application.validation import (
|
|
37
|
+
DeterministicValidationPlanner,
|
|
38
|
+
ToolValidationRunner,
|
|
39
|
+
)
|
|
40
|
+
from nexus.config.models import ApprovalMode, MCPServerConfig, RuntimeConfig
|
|
41
|
+
from nexus.context import SelectiveRepositoryExplorer
|
|
42
|
+
from nexus.context.budget import Day5ModelInputBudgetGuard
|
|
43
|
+
from nexus.context.chunking import LineWindowChunker
|
|
44
|
+
from nexus.context.integration import ManagedContextBuilder
|
|
45
|
+
from nexus.context.manager import BoundedContextManager
|
|
46
|
+
from nexus.context.retrieval import (
|
|
47
|
+
HybridContextProvider,
|
|
48
|
+
ToolLexicalSearchProvider,
|
|
49
|
+
ToolRepositoryAccess,
|
|
50
|
+
)
|
|
51
|
+
from nexus.domain.context import ContextBudget, IndexRequest, IndexResult
|
|
52
|
+
from nexus.domain.mcp import MCPToolDescriptor
|
|
53
|
+
from nexus.domain.persistence import SessionSummary
|
|
54
|
+
from nexus.domain.ports.checkpoint_provider import CheckpointProvider
|
|
55
|
+
from nexus.domain.ports.context import ContextProvider, EmbeddingGateway
|
|
56
|
+
from nexus.domain.ports.graph_runtime import GraphRuntime
|
|
57
|
+
from nexus.domain.ports.mcp import MCPManager
|
|
58
|
+
from nexus.domain.ports.model_gateway import ModelGateway
|
|
59
|
+
from nexus.domain.ports.tooling import ApprovalPolicy
|
|
60
|
+
from nexus.domain.runtime_events import (
|
|
61
|
+
ObservabilityWarning,
|
|
62
|
+
RuntimeEvent,
|
|
63
|
+
TraceFallback,
|
|
64
|
+
TraceOperation,
|
|
65
|
+
TraceSink,
|
|
66
|
+
)
|
|
67
|
+
from nexus.domain.tooling import JsonObject, RiskLevel
|
|
68
|
+
from nexus.errors import ConfigurationError, ContextError
|
|
69
|
+
from nexus.evaluation.models import EvalTraceCollector
|
|
70
|
+
from nexus.evaluation.runner import EvalRunner
|
|
71
|
+
from nexus.infrastructure.asyncio_compat import configure_asyncio_policy
|
|
72
|
+
from nexus.infrastructure.checkpoint import PostgresCheckpointProvider
|
|
73
|
+
from nexus.infrastructure.database import DatabaseBootstrap
|
|
74
|
+
from nexus.infrastructure.embedding import OpenAICompatibleEmbeddingGateway
|
|
75
|
+
from nexus.infrastructure.graph.day4_runtime import Day4LangGraphRuntime
|
|
76
|
+
from nexus.infrastructure.graph.langgraph_runtime import LangGraphRuntime
|
|
77
|
+
from nexus.infrastructure.mcp import SDKMCPManager
|
|
78
|
+
from nexus.infrastructure.model_gateway.openai_compatible import OpenAICompatibleModelGateway
|
|
79
|
+
from nexus.infrastructure.observability import (
|
|
80
|
+
ConsoleTracer,
|
|
81
|
+
LangSmithTracer,
|
|
82
|
+
StructuredLogger,
|
|
83
|
+
)
|
|
84
|
+
from nexus.infrastructure.observability.langsmith import LangSmithClient
|
|
85
|
+
from nexus.infrastructure.persistence import (
|
|
86
|
+
SqlAlchemyApprovalUnitOfWorkFactory,
|
|
87
|
+
SqlAlchemySessionUnitOfWorkFactory,
|
|
88
|
+
)
|
|
89
|
+
from nexus.infrastructure.repository_files import RepositoryFiles
|
|
90
|
+
from nexus.infrastructure.sandbox import LocalProcessSandbox
|
|
91
|
+
from nexus.infrastructure.semantic import PgVectorRepositoryIndexer, PgVectorSemanticSearchProvider
|
|
92
|
+
from nexus.security import (
|
|
93
|
+
AutoApprovalPolicy,
|
|
94
|
+
DefaultCommandPolicy,
|
|
95
|
+
TrustedExecutables,
|
|
96
|
+
WorkspaceGuard,
|
|
97
|
+
)
|
|
98
|
+
from nexus.skills import DefaultSkillRegistry, FileSkillLoader, ModelSkillSelector
|
|
99
|
+
from nexus.tools import MCPToolAdapter, ToolRegistry
|
|
100
|
+
from nexus.tools.native import build_native_tools
|
|
101
|
+
|
|
102
|
+
configure_asyncio_policy()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def build_evaluation_runner(config: RuntimeConfig, cases_root: Path) -> EvalRunner:
|
|
106
|
+
"""Assemble Day 9 evaluation without exposing Harness authority to the Agent."""
|
|
107
|
+
|
|
108
|
+
evaluation_config = config.model_copy(update={"approval_mode": ApprovalMode.AUTO})
|
|
109
|
+
|
|
110
|
+
@asynccontextmanager
|
|
111
|
+
async def runtime_factory(
|
|
112
|
+
workspace: Path, collector: EvalTraceCollector
|
|
113
|
+
) -> AsyncIterator[NexusRuntime]:
|
|
114
|
+
async with bootstrap_application(
|
|
115
|
+
evaluation_config,
|
|
116
|
+
workspace_path=workspace,
|
|
117
|
+
approval_policy=AutoApprovalPolicy(),
|
|
118
|
+
eval_trace_collector=collector,
|
|
119
|
+
) as application:
|
|
120
|
+
yield application.runtime
|
|
121
|
+
|
|
122
|
+
return EvalRunner(cases_root, runtime_factory)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True, slots=True)
|
|
126
|
+
class BootstrappedApplication:
|
|
127
|
+
"""Application dependencies exposed to entry-point adapters."""
|
|
128
|
+
|
|
129
|
+
runtime: NexusRuntime
|
|
130
|
+
session_service: SessionService
|
|
131
|
+
database: DatabaseBootstrap
|
|
132
|
+
tool_runtime: ToolRuntime
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass(frozen=True, slots=True)
|
|
136
|
+
class BootstrappedToolApplication:
|
|
137
|
+
"""Day 3 Tool Runtime dependencies without model/graph construction."""
|
|
138
|
+
|
|
139
|
+
tool_runtime: ToolRuntime
|
|
140
|
+
database: DatabaseBootstrap
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass(frozen=True, slots=True)
|
|
144
|
+
class _Day7ContextServices:
|
|
145
|
+
context_manager: BoundedContextManager
|
|
146
|
+
budget_guard: Day5ModelInputBudgetGuard
|
|
147
|
+
skill_registry: DefaultSkillRegistry
|
|
148
|
+
skill_selector: ModelSkillSelector
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _build_day7_context_services(
|
|
152
|
+
*,
|
|
153
|
+
config: RuntimeConfig,
|
|
154
|
+
gateway: ModelGateway,
|
|
155
|
+
workspace: Path,
|
|
156
|
+
provider: ContextProvider,
|
|
157
|
+
access: ToolRepositoryAccess,
|
|
158
|
+
chunker: LineWindowChunker,
|
|
159
|
+
) -> _Day7ContextServices:
|
|
160
|
+
context_manager = BoundedContextManager(
|
|
161
|
+
provider,
|
|
162
|
+
access,
|
|
163
|
+
chunker,
|
|
164
|
+
ContextBudget(
|
|
165
|
+
max_retrieved_chunks=config.max_retrieved_chunks,
|
|
166
|
+
max_exploration_seed_chunks=config.max_exploration_seed_chunks,
|
|
167
|
+
max_code_context_tokens=config.max_code_context_tokens,
|
|
168
|
+
max_recent_observations=config.max_recent_observations,
|
|
169
|
+
),
|
|
170
|
+
config.max_model_input_tokens,
|
|
171
|
+
)
|
|
172
|
+
repository_skill_root = workspace / ".nexus" / "skills"
|
|
173
|
+
user_skill_root = Path.home() / ".nexus" / "skills"
|
|
174
|
+
skill_loader = FileSkillLoader(repository_skill_root, user_skill_root)
|
|
175
|
+
skill_registry = DefaultSkillRegistry(
|
|
176
|
+
skill_loader,
|
|
177
|
+
repository_skill_root,
|
|
178
|
+
user_skill_root,
|
|
179
|
+
)
|
|
180
|
+
budget_guard = Day5ModelInputBudgetGuard(config.max_model_input_tokens)
|
|
181
|
+
skill_selector = ModelSkillSelector(
|
|
182
|
+
gateway,
|
|
183
|
+
config.max_selected_skills,
|
|
184
|
+
budget_guard,
|
|
185
|
+
)
|
|
186
|
+
return _Day7ContextServices(
|
|
187
|
+
context_manager,
|
|
188
|
+
budget_guard,
|
|
189
|
+
skill_registry,
|
|
190
|
+
skill_selector,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@asynccontextmanager
|
|
195
|
+
async def bootstrap_application(
|
|
196
|
+
config: RuntimeConfig,
|
|
197
|
+
*,
|
|
198
|
+
model_gateway: ModelGateway | None = None,
|
|
199
|
+
workspace_path: Path | None = None,
|
|
200
|
+
interrupt_before_model_response: bool = False,
|
|
201
|
+
approval_policy: ApprovalPolicy | None = None,
|
|
202
|
+
tool_event_emitter: RuntimeEventEmitter | None = None,
|
|
203
|
+
embedding_gateway: EmbeddingGateway | None = None,
|
|
204
|
+
eval_trace_collector: EvalTraceCollector | None = None,
|
|
205
|
+
) -> AsyncIterator[BootstrappedApplication]:
|
|
206
|
+
"""Build the approved object graph and reliably release infrastructure resources."""
|
|
207
|
+
|
|
208
|
+
database = DatabaseBootstrap(config.database_url)
|
|
209
|
+
checkpoint_provider: CheckpointProvider = PostgresCheckpointProvider(config.database_url)
|
|
210
|
+
mcp_manager: MCPManager | None = None
|
|
211
|
+
telemetry_subscriber: TelemetrySubscriber | None = None
|
|
212
|
+
telemetry_subscription: object | None = None
|
|
213
|
+
langsmith_tracer: LangSmithTracer | None = None
|
|
214
|
+
try:
|
|
215
|
+
await checkpoint_provider.setup()
|
|
216
|
+
raw_gateway = (
|
|
217
|
+
model_gateway if model_gateway is not None else OpenAICompatibleModelGateway(config)
|
|
218
|
+
)
|
|
219
|
+
unit_of_work_factory = SqlAlchemySessionUnitOfWorkFactory(database.session_factory)
|
|
220
|
+
workspace = workspace_path or Path.cwd()
|
|
221
|
+
session_service = SessionService(unit_of_work_factory, workspace)
|
|
222
|
+
event_publisher = InProcessEventPublisher()
|
|
223
|
+
event_buffer = LiveRuntimeEventBridge(event_publisher)
|
|
224
|
+
ledger = ToolExecutionLedger()
|
|
225
|
+
approval_factory = SqlAlchemyApprovalUnitOfWorkFactory(database.session_factory)
|
|
226
|
+
plan_approval_service = PlanApprovalService(approval_factory)
|
|
227
|
+
combined_emitter = _combined_emitter(event_buffer, tool_event_emitter)
|
|
228
|
+
tool_runtime, executables, mcp_manager, tool_metadata, tool_sources = (
|
|
229
|
+
await _build_tool_runtime(
|
|
230
|
+
config,
|
|
231
|
+
database,
|
|
232
|
+
workspace,
|
|
233
|
+
approval_policy or AutoApprovalPolicy(),
|
|
234
|
+
combined_emitter,
|
|
235
|
+
plan_approval_service=plan_approval_service,
|
|
236
|
+
ledger=ledger,
|
|
237
|
+
allow_lexical_path=RepositoryFiles(
|
|
238
|
+
workspace,
|
|
239
|
+
config.max_file_size_bytes,
|
|
240
|
+
).allowed,
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
sinks: list[TracerSink] = []
|
|
244
|
+
if config.console_tracing_enabled:
|
|
245
|
+
structured_logger = StructuredLogger()
|
|
246
|
+
sinks.append(
|
|
247
|
+
TracerSink(TraceSink.CONSOLE, ConsoleTracer(structured_logger))
|
|
248
|
+
)
|
|
249
|
+
if config.langsmith_tracing_enabled:
|
|
250
|
+
if config.langsmith_api_key is None:
|
|
251
|
+
raise ConfigurationError(
|
|
252
|
+
"Enabled LangSmith tracing requires a key.",
|
|
253
|
+
code="TRACE_CONFIGURATION_INVALID",
|
|
254
|
+
)
|
|
255
|
+
client = Client(
|
|
256
|
+
api_url=config.langsmith_endpoint,
|
|
257
|
+
api_key=config.langsmith_api_key.get_secret_value(),
|
|
258
|
+
workspace_id=config.langsmith_workspace_id,
|
|
259
|
+
hide_inputs=True,
|
|
260
|
+
hide_outputs=_safe_langsmith_mapping,
|
|
261
|
+
hide_metadata=_safe_langsmith_mapping,
|
|
262
|
+
auto_batch_tracing=True,
|
|
263
|
+
)
|
|
264
|
+
langsmith_tracer = LangSmithTracer(
|
|
265
|
+
cast(LangSmithClient, client), project=config.langsmith_project
|
|
266
|
+
)
|
|
267
|
+
sinks.append(TracerSink(TraceSink.LANGSMITH, langsmith_tracer))
|
|
268
|
+
|
|
269
|
+
async def emit_trace_warning(
|
|
270
|
+
code: str,
|
|
271
|
+
sink: TraceSink,
|
|
272
|
+
operation: TraceOperation,
|
|
273
|
+
fallback: TraceFallback,
|
|
274
|
+
) -> None:
|
|
275
|
+
context = current_execution_context()
|
|
276
|
+
warning = ObservabilityWarning(
|
|
277
|
+
run_id=context.run_id,
|
|
278
|
+
session_id=context.session_id,
|
|
279
|
+
code=code,
|
|
280
|
+
sink=sink,
|
|
281
|
+
operation=operation,
|
|
282
|
+
fallback=fallback,
|
|
283
|
+
)
|
|
284
|
+
try:
|
|
285
|
+
await event_publisher.publish(warning)
|
|
286
|
+
except RuntimeError:
|
|
287
|
+
print(
|
|
288
|
+
json.dumps(
|
|
289
|
+
{
|
|
290
|
+
"type": "ObservabilityWarning",
|
|
291
|
+
"code": code,
|
|
292
|
+
"sink": sink.value,
|
|
293
|
+
"operation": operation.value,
|
|
294
|
+
"fallback": fallback.value,
|
|
295
|
+
},
|
|
296
|
+
separators=(",", ":"),
|
|
297
|
+
),
|
|
298
|
+
file=sys.stderr,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
dispatcher = SafeTracerDispatcher(
|
|
302
|
+
tuple(sinks), warning_emitter=emit_trace_warning
|
|
303
|
+
)
|
|
304
|
+
telemetry_subscriber = TelemetrySubscriber(
|
|
305
|
+
EventEnricher(workspace, tool_sources),
|
|
306
|
+
dispatcher,
|
|
307
|
+
warning_emitter=emit_trace_warning,
|
|
308
|
+
ledger=ledger,
|
|
309
|
+
redactor=TelemetryRedactor(),
|
|
310
|
+
finish_observer=(
|
|
311
|
+
None if eval_trace_collector is None else eval_trace_collector.record_finish
|
|
312
|
+
),
|
|
313
|
+
)
|
|
314
|
+
telemetry_subscription = event_publisher.subscribe(telemetry_subscriber)
|
|
315
|
+
gateway = ObservedModelGateway(
|
|
316
|
+
raw_gateway,
|
|
317
|
+
emit=event_publisher.publish,
|
|
318
|
+
ledger=ledger,
|
|
319
|
+
provider=config.model_provider,
|
|
320
|
+
model=config.model_name,
|
|
321
|
+
)
|
|
322
|
+
checkpointer = cast(BaseCheckpointSaver[Any], checkpoint_provider.get_checkpointer())
|
|
323
|
+
legacy_runtime = LangGraphRuntime(
|
|
324
|
+
gateway,
|
|
325
|
+
checkpointer=checkpointer,
|
|
326
|
+
interrupt_before_model_response=interrupt_before_model_response,
|
|
327
|
+
)
|
|
328
|
+
graph_runtime: GraphRuntime
|
|
329
|
+
if interrupt_before_model_response:
|
|
330
|
+
graph_runtime = legacy_runtime
|
|
331
|
+
else:
|
|
332
|
+
access = ToolRepositoryAccess(tool_runtime)
|
|
333
|
+
chunker = LineWindowChunker()
|
|
334
|
+
semantic = None
|
|
335
|
+
if config.semantic_enabled:
|
|
336
|
+
embedding = embedding_gateway or OpenAICompatibleEmbeddingGateway(config)
|
|
337
|
+
semantic = PgVectorSemanticSearchProvider(database.session_factory, embedding)
|
|
338
|
+
context_services = _build_day7_context_services(
|
|
339
|
+
config=config,
|
|
340
|
+
gateway=gateway,
|
|
341
|
+
workspace=workspace,
|
|
342
|
+
provider=HybridContextProvider(
|
|
343
|
+
ToolLexicalSearchProvider(access, chunker), semantic
|
|
344
|
+
),
|
|
345
|
+
access=access,
|
|
346
|
+
chunker=chunker,
|
|
347
|
+
)
|
|
348
|
+
context_manager = context_services.context_manager
|
|
349
|
+
graph_runtime = Day4LangGraphRuntime(
|
|
350
|
+
explorer=SelectiveRepositoryExplorer(tool_runtime),
|
|
351
|
+
context_builder=ManagedContextBuilder(
|
|
352
|
+
context_manager,
|
|
353
|
+
session_service.context_repository_id,
|
|
354
|
+
str(workspace.resolve()),
|
|
355
|
+
session_service.context_turns,
|
|
356
|
+
context_services.skill_registry,
|
|
357
|
+
context_services.skill_selector,
|
|
358
|
+
),
|
|
359
|
+
planner=ModelPlanner(
|
|
360
|
+
gateway,
|
|
361
|
+
normalize_argv=executables.normalize_argv,
|
|
362
|
+
prepare_input=context_manager.fit_model_input,
|
|
363
|
+
tool_metadata=tool_metadata,
|
|
364
|
+
),
|
|
365
|
+
agent=JsonAgentDecisionAdapter(
|
|
366
|
+
gateway,
|
|
367
|
+
prepare_input=context_manager.fit_model_input,
|
|
368
|
+
tool_metadata=tool_metadata,
|
|
369
|
+
),
|
|
370
|
+
tool_runtime=tool_runtime,
|
|
371
|
+
validation_planner=DeterministicValidationPlanner(),
|
|
372
|
+
validation_runner=ToolValidationRunner(
|
|
373
|
+
tool_runtime,
|
|
374
|
+
emit=combined_emitter,
|
|
375
|
+
),
|
|
376
|
+
plan_approval_service=plan_approval_service,
|
|
377
|
+
diff_collector=FinalDiffCollector(tool_runtime),
|
|
378
|
+
event_buffer=event_buffer,
|
|
379
|
+
ledger=ledger,
|
|
380
|
+
approval_mode=config.approval_mode,
|
|
381
|
+
max_steps=config.max_steps,
|
|
382
|
+
max_repair_attempts=config.max_repair_attempts,
|
|
383
|
+
max_replans=config.max_replans,
|
|
384
|
+
checkpointer=checkpointer,
|
|
385
|
+
legacy_runtime=legacy_runtime,
|
|
386
|
+
context_manager=context_manager,
|
|
387
|
+
conversation_turns=session_service.context_turns,
|
|
388
|
+
model_call_id=gateway.last_model_call_id,
|
|
389
|
+
)
|
|
390
|
+
application = BootstrappedApplication(
|
|
391
|
+
runtime=NexusRuntime(
|
|
392
|
+
graph_runtime,
|
|
393
|
+
session_service=session_service,
|
|
394
|
+
model_metadata={
|
|
395
|
+
"provider": config.model_provider,
|
|
396
|
+
"model": config.model_name,
|
|
397
|
+
},
|
|
398
|
+
event_buffer=event_buffer,
|
|
399
|
+
event_publisher=event_publisher,
|
|
400
|
+
telemetry_subscriber=telemetry_subscriber,
|
|
401
|
+
ledger=ledger,
|
|
402
|
+
),
|
|
403
|
+
session_service=session_service,
|
|
404
|
+
database=database,
|
|
405
|
+
tool_runtime=tool_runtime,
|
|
406
|
+
)
|
|
407
|
+
yield application
|
|
408
|
+
finally:
|
|
409
|
+
if telemetry_subscription is not None:
|
|
410
|
+
await cast(Any, telemetry_subscription).aclose()
|
|
411
|
+
if telemetry_subscriber is not None:
|
|
412
|
+
await telemetry_subscriber.close()
|
|
413
|
+
if langsmith_tracer is not None:
|
|
414
|
+
try:
|
|
415
|
+
await langsmith_tracer.close()
|
|
416
|
+
except Exception:
|
|
417
|
+
pass
|
|
418
|
+
if mcp_manager is not None:
|
|
419
|
+
await mcp_manager.close()
|
|
420
|
+
await checkpoint_provider.close()
|
|
421
|
+
await database.close()
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
@asynccontextmanager
|
|
425
|
+
async def bootstrap_tool_application(
|
|
426
|
+
config: RuntimeConfig,
|
|
427
|
+
*,
|
|
428
|
+
workspace_path: Path | None = None,
|
|
429
|
+
approval_policy: ApprovalPolicy | None = None,
|
|
430
|
+
tool_event_emitter: RuntimeEventEmitter | None = None,
|
|
431
|
+
day5_file_filtering: bool = False,
|
|
432
|
+
) -> AsyncIterator[BootstrappedToolApplication]:
|
|
433
|
+
"""Build only the approved Day 3 Tool Runtime dependency graph."""
|
|
434
|
+
|
|
435
|
+
database = DatabaseBootstrap(config.database_url)
|
|
436
|
+
mcp_manager: MCPManager | None = None
|
|
437
|
+
try:
|
|
438
|
+
tool_runtime, _, mcp_manager, _, _ = await _build_tool_runtime(
|
|
439
|
+
config,
|
|
440
|
+
database,
|
|
441
|
+
workspace := workspace_path or Path.cwd(),
|
|
442
|
+
approval_policy or AutoApprovalPolicy(),
|
|
443
|
+
tool_event_emitter,
|
|
444
|
+
allow_lexical_path=(
|
|
445
|
+
RepositoryFiles(workspace, config.max_file_size_bytes).allowed
|
|
446
|
+
if day5_file_filtering
|
|
447
|
+
else None
|
|
448
|
+
),
|
|
449
|
+
)
|
|
450
|
+
yield BootstrappedToolApplication(tool_runtime=tool_runtime, database=database)
|
|
451
|
+
finally:
|
|
452
|
+
if mcp_manager is not None:
|
|
453
|
+
await mcp_manager.close()
|
|
454
|
+
await database.close()
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
async def _build_tool_runtime(
|
|
458
|
+
config: RuntimeConfig,
|
|
459
|
+
database: DatabaseBootstrap,
|
|
460
|
+
workspace: Path,
|
|
461
|
+
approval_policy: ApprovalPolicy,
|
|
462
|
+
event_emitter: RuntimeEventEmitter | None,
|
|
463
|
+
*,
|
|
464
|
+
plan_approval_service: PlanApprovalService | None = None,
|
|
465
|
+
ledger: ToolExecutionLedger | None = None,
|
|
466
|
+
allow_lexical_path: Callable[[str], bool] | None = None,
|
|
467
|
+
) -> tuple[
|
|
468
|
+
ToolRuntime,
|
|
469
|
+
TrustedExecutables,
|
|
470
|
+
MCPManager | None,
|
|
471
|
+
tuple[JsonObject, ...],
|
|
472
|
+
dict[str, str],
|
|
473
|
+
]:
|
|
474
|
+
guard = WorkspaceGuard(workspace)
|
|
475
|
+
executables = TrustedExecutables.resolve(guard.root)
|
|
476
|
+
manager: MCPManager | None = None
|
|
477
|
+
adapters: list[MCPToolAdapter] = []
|
|
478
|
+
mcp_risks: dict[str, RiskLevel] = {}
|
|
479
|
+
unsupported_writes: set[str] = set()
|
|
480
|
+
tool_metadata: list[JsonObject] = []
|
|
481
|
+
try:
|
|
482
|
+
if config.mcp_enabled:
|
|
483
|
+
manager = SDKMCPManager(config.mcp_servers)
|
|
484
|
+
await manager.connect()
|
|
485
|
+
descriptors = await manager.list_tools()
|
|
486
|
+
adapters, mcp_risks, unsupported_writes, tool_metadata = _adapt_mcp_tools(
|
|
487
|
+
descriptors,
|
|
488
|
+
config.mcp_servers,
|
|
489
|
+
manager,
|
|
490
|
+
)
|
|
491
|
+
command_policy = DefaultCommandPolicy(executables, mcp_risks)
|
|
492
|
+
sandbox = LocalProcessSandbox(guard, command_policy, executables)
|
|
493
|
+
tools = [
|
|
494
|
+
*build_native_tools(
|
|
495
|
+
guard,
|
|
496
|
+
sandbox,
|
|
497
|
+
executables,
|
|
498
|
+
allow_lexical_path=allow_lexical_path,
|
|
499
|
+
),
|
|
500
|
+
*adapters,
|
|
501
|
+
]
|
|
502
|
+
try:
|
|
503
|
+
registry = ToolRegistry(tools)
|
|
504
|
+
except ValueError as exc:
|
|
505
|
+
if not adapters:
|
|
506
|
+
raise
|
|
507
|
+
raise ConfigurationError(
|
|
508
|
+
"MCP/native Tool registration contains a duplicate name.",
|
|
509
|
+
code="MCP_TOOL_COLLISION",
|
|
510
|
+
) from exc
|
|
511
|
+
except Exception:
|
|
512
|
+
if manager is not None:
|
|
513
|
+
await manager.close()
|
|
514
|
+
raise
|
|
515
|
+
approval_factory = SqlAlchemyApprovalUnitOfWorkFactory(database.session_factory)
|
|
516
|
+
return (
|
|
517
|
+
ToolRuntime(
|
|
518
|
+
registry,
|
|
519
|
+
command_policy,
|
|
520
|
+
approval_policy,
|
|
521
|
+
ApprovalService(approval_factory),
|
|
522
|
+
emit=event_emitter,
|
|
523
|
+
plan_approval_service=plan_approval_service,
|
|
524
|
+
normalize_argv=executables.normalize_argv,
|
|
525
|
+
ledger=ledger,
|
|
526
|
+
unsupported_write_operations=frozenset(unsupported_writes),
|
|
527
|
+
),
|
|
528
|
+
executables,
|
|
529
|
+
manager,
|
|
530
|
+
tuple(tool_metadata),
|
|
531
|
+
{
|
|
532
|
+
tool.name: "MCP" if isinstance(tool, MCPToolAdapter) else "NATIVE"
|
|
533
|
+
for tool in tools
|
|
534
|
+
},
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _mcp_risk(server: MCPServerConfig, remote_name: str) -> RiskLevel:
|
|
539
|
+
return next(
|
|
540
|
+
(
|
|
541
|
+
configured.risk_level
|
|
542
|
+
for configured in server.tool_risks
|
|
543
|
+
if configured.tool_name == remote_name
|
|
544
|
+
),
|
|
545
|
+
RiskLevel.DANGEROUS,
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _adapt_mcp_tools(
|
|
550
|
+
descriptors: tuple[MCPToolDescriptor, ...],
|
|
551
|
+
server_configs: tuple[MCPServerConfig, ...],
|
|
552
|
+
manager: MCPManager,
|
|
553
|
+
) -> tuple[
|
|
554
|
+
list[MCPToolAdapter],
|
|
555
|
+
dict[str, RiskLevel],
|
|
556
|
+
set[str],
|
|
557
|
+
list[JsonObject],
|
|
558
|
+
]:
|
|
559
|
+
servers = {server.server_id: server for server in server_configs if server.enabled}
|
|
560
|
+
adapters: list[MCPToolAdapter] = []
|
|
561
|
+
risks: dict[str, RiskLevel] = {}
|
|
562
|
+
unsupported_writes: set[str] = set()
|
|
563
|
+
metadata: list[JsonObject] = []
|
|
564
|
+
for descriptor in descriptors:
|
|
565
|
+
server = servers.get(descriptor.server_id)
|
|
566
|
+
if server is None:
|
|
567
|
+
raise ConfigurationError(
|
|
568
|
+
"MCP discovery returned an unknown server identity.",
|
|
569
|
+
code="MCP_CONFIGURATION_INVALID",
|
|
570
|
+
)
|
|
571
|
+
risk = _mcp_risk(server, descriptor.remote_name)
|
|
572
|
+
adapter = MCPToolAdapter(
|
|
573
|
+
descriptor,
|
|
574
|
+
manager,
|
|
575
|
+
risk_level=risk,
|
|
576
|
+
timeout_seconds=server.tool_timeout_seconds,
|
|
577
|
+
)
|
|
578
|
+
adapters.append(adapter)
|
|
579
|
+
risks[adapter.name] = risk
|
|
580
|
+
if risk is RiskLevel.WRITE:
|
|
581
|
+
unsupported_writes.add(adapter.name)
|
|
582
|
+
if risk is RiskLevel.SAFE:
|
|
583
|
+
metadata.append(
|
|
584
|
+
{
|
|
585
|
+
"registry_name": descriptor.registry_name,
|
|
586
|
+
"description": descriptor.description,
|
|
587
|
+
"input_schema": descriptor.input_schema,
|
|
588
|
+
"risk_level": risk.value,
|
|
589
|
+
"source": "mcp",
|
|
590
|
+
"server_id": descriptor.server_id,
|
|
591
|
+
}
|
|
592
|
+
)
|
|
593
|
+
return adapters, risks, unsupported_writes, metadata
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
async def index_repository(
|
|
597
|
+
config: RuntimeConfig,
|
|
598
|
+
*,
|
|
599
|
+
rebuild: bool = False,
|
|
600
|
+
workspace_path: Path | None = None,
|
|
601
|
+
embedding_gateway: EmbeddingGateway | None = None,
|
|
602
|
+
) -> IndexResult:
|
|
603
|
+
"""Standalone index entry: no chat model, checkpointer, or synthetic coding Run."""
|
|
604
|
+
workspace = workspace_path or Path.cwd()
|
|
605
|
+
embedding = embedding_gateway or OpenAICompatibleEmbeddingGateway(config)
|
|
606
|
+
database = DatabaseBootstrap(config.database_url)
|
|
607
|
+
try:
|
|
608
|
+
service = SessionService(
|
|
609
|
+
SqlAlchemySessionUnitOfWorkFactory(database.session_factory),
|
|
610
|
+
workspace,
|
|
611
|
+
)
|
|
612
|
+
indexer = PgVectorRepositoryIndexer(
|
|
613
|
+
database.session_factory,
|
|
614
|
+
embedding,
|
|
615
|
+
LineWindowChunker(),
|
|
616
|
+
RepositoryFiles(workspace, config.max_file_size_bytes),
|
|
617
|
+
)
|
|
618
|
+
return await indexer.index(
|
|
619
|
+
IndexRequest(
|
|
620
|
+
await service.context_repository_id(),
|
|
621
|
+
str(workspace.resolve()),
|
|
622
|
+
rebuild,
|
|
623
|
+
)
|
|
624
|
+
)
|
|
625
|
+
except ContextError:
|
|
626
|
+
raise
|
|
627
|
+
except Exception as exc:
|
|
628
|
+
raise ContextError(
|
|
629
|
+
"Repository index initialization failed.", code="INDEX_BUILD_FAILED", retryable=True
|
|
630
|
+
) from exc
|
|
631
|
+
finally:
|
|
632
|
+
await database.close()
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
async def list_repository_sessions(config: RuntimeConfig) -> list[SessionSummary]:
|
|
636
|
+
"""Business history listing does not request a model or semantic capability."""
|
|
637
|
+
database = DatabaseBootstrap(config.database_url)
|
|
638
|
+
try:
|
|
639
|
+
service = SessionService(
|
|
640
|
+
SqlAlchemySessionUnitOfWorkFactory(database.session_factory),
|
|
641
|
+
Path.cwd(),
|
|
642
|
+
)
|
|
643
|
+
return await service.list_sessions()
|
|
644
|
+
finally:
|
|
645
|
+
await database.close()
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def _combined_emitter(
|
|
649
|
+
buffer: RuntimeEventBuffer | LiveRuntimeEventBridge,
|
|
650
|
+
external: RuntimeEventEmitter | None,
|
|
651
|
+
) -> RuntimeEventEmitter:
|
|
652
|
+
async def emit(event: RuntimeEvent) -> None:
|
|
653
|
+
await buffer.emit(event)
|
|
654
|
+
if external is not None:
|
|
655
|
+
await external(event)
|
|
656
|
+
|
|
657
|
+
return emit
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def _safe_langsmith_mapping(value: dict[str, Any]) -> dict[str, Any]:
|
|
661
|
+
"""Defense in depth: the adapter has already constructed the only safe mapping."""
|
|
662
|
+
|
|
663
|
+
return dict(value)
|