mycode-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.
- mycode/__init__.py +0 -0
- mycode/adapters/__init__.py +21 -0
- mycode/adapters/jsonl.py +692 -0
- mycode/agent/__init__.py +25 -0
- mycode/agent/events.py +111 -0
- mycode/agent/outcome.py +103 -0
- mycode/agent/progress.py +373 -0
- mycode/agent/runner.py +1481 -0
- mycode/application/__init__.py +38 -0
- mycode/application/agent_session.py +367 -0
- mycode/application/events.py +59 -0
- mycode/application/runtime.py +211 -0
- mycode/application/sessions.py +180 -0
- mycode/cli.py +840 -0
- mycode/config.py +355 -0
- mycode/context/__init__.py +1 -0
- mycode/context/artifacts.py +672 -0
- mycode/context/budget.py +752 -0
- mycode/context/builder.py +112 -0
- mycode/context/compact.py +795 -0
- mycode/context/tool_result_format.py +199 -0
- mycode/context/tool_result_retention.py +261 -0
- mycode/conversation.py +78 -0
- mycode/error_handling.py +481 -0
- mycode/event_format.py +147 -0
- mycode/instructions.py +285 -0
- mycode/llm.py +771 -0
- mycode/mcp/__init__.py +41 -0
- mycode/mcp/client.py +44 -0
- mycode/mcp/config.py +207 -0
- mycode/mcp/errors.py +302 -0
- mycode/mcp/manager.py +339 -0
- mycode/mcp/models.py +20 -0
- mycode/mcp/result_adapter.py +58 -0
- mycode/mcp/tool_adapter.py +145 -0
- mycode/mcp/trust.py +313 -0
- mycode/memory.py +570 -0
- mycode/memory_context.py +245 -0
- mycode/messages.py +63 -0
- mycode/observability.py +28 -0
- mycode/permissions.py +262 -0
- mycode/persistence/__init__.py +1 -0
- mycode/persistence/filesystem.py +291 -0
- mycode/persistence/project_storage.py +208 -0
- mycode/persistence/session_lock.py +138 -0
- mycode/persistence/session_store.py +503 -0
- mycode/presentation/__init__.py +1 -0
- mycode/presentation/cli/__init__.py +14 -0
- mycode/presentation/cli/confirmer.py +116 -0
- mycode/presentation/cli/mcp_trust.py +61 -0
- mycode/presentation/cli/presenter.py +320 -0
- mycode/presentation/cli/session_menu.py +146 -0
- mycode/presentation/cli/subagent_observer.py +124 -0
- mycode/presentation/command_format.py +90 -0
- mycode/presentation/commands.py +95 -0
- mycode/presentation/tui/__init__.py +6 -0
- mycode/presentation/tui/app.py +1351 -0
- mycode/presentation/tui/interactions.py +253 -0
- mycode/presentation/tui/presenter.py +266 -0
- mycode/presentation/tui/screens.py +305 -0
- mycode/presentation/tui/widgets.py +214 -0
- mycode/project.py +22 -0
- mycode/prompts.py +181 -0
- mycode/reasoning.py +40 -0
- mycode/session.py +86 -0
- mycode/skills/__init__.py +27 -0
- mycode/skills/builtin/database-recovery/SKILL.md +138 -0
- mycode/skills/builtin/database-recovery/references/sqlite.md +235 -0
- mycode/skills/registry.py +295 -0
- mycode/skills/state.py +68 -0
- mycode/subagents/__init__.py +1 -0
- mycode/subagents/audit.py +212 -0
- mycode/subagents/concurrency.py +124 -0
- mycode/subagents/contracts.py +421 -0
- mycode/subagents/delegate.py +80 -0
- mycode/subagents/delegation.py +128 -0
- mycode/subagents/lifecycle.py +86 -0
- mycode/subagents/limits.py +7 -0
- mycode/subagents/observability.py +150 -0
- mycode/subagents/persistence.py +152 -0
- mycode/subagents/profiles.py +184 -0
- mycode/subagents/prompts.py +67 -0
- mycode/subagents/results.py +178 -0
- mycode/subagents/runtime.py +528 -0
- mycode/subagents/snapshots.py +211 -0
- mycode/subagents/tool_batch.py +260 -0
- mycode/tools/__init__.py +81 -0
- mycode/tools/base.py +222 -0
- mycode/tools/bounds.py +14 -0
- mycode/tools/command_executor.py +167 -0
- mycode/tools/command_output.py +166 -0
- mycode/tools/command_risk.py +596 -0
- mycode/tools/defaults.py +59 -0
- mycode/tools/edit_file.py +524 -0
- mycode/tools/file_mutation.py +30 -0
- mycode/tools/glob.py +247 -0
- mycode/tools/grep.py +324 -0
- mycode/tools/ignore.py +122 -0
- mycode/tools/inspect_changes.py +269 -0
- mycode/tools/load_skill.py +92 -0
- mycode/tools/memory.py +264 -0
- mycode/tools/path_permissions.py +78 -0
- mycode/tools/patterns.py +48 -0
- mycode/tools/permission_metadata.py +27 -0
- mycode/tools/process_tree.py +166 -0
- mycode/tools/read_file.py +242 -0
- mycode/tools/read_skill_resource.py +93 -0
- mycode/tools/registry.py +279 -0
- mycode/tools/run_command.py +237 -0
- mycode/tools/run_skill_script.py +206 -0
- mycode/tools/run_validation.py +107 -0
- mycode/tools/submit_result.py +93 -0
- mycode/tools/text.py +15 -0
- mycode/tools/validation_command.py +377 -0
- mycode/tools/workspace.py +33 -0
- mycode/tools/write_file.py +169 -0
- mycode_coding_agent-0.1.0.dist-info/METADATA +244 -0
- mycode_coding_agent-0.1.0.dist-info/RECORD +121 -0
- mycode_coding_agent-0.1.0.dist-info/WHEEL +4 -0
- mycode_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
- mycode_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Application-layer runtime assembly and single-turn use cases."""
|
|
2
|
+
|
|
3
|
+
from mycode.application.agent_session import (
|
|
4
|
+
AgentApplicationSession,
|
|
5
|
+
CompactResult,
|
|
6
|
+
ContextStatus,
|
|
7
|
+
start_agent_application_session,
|
|
8
|
+
)
|
|
9
|
+
from mycode.application.events import RuntimeEvent, RuntimeEventType
|
|
10
|
+
from mycode.application.runtime import (
|
|
11
|
+
build_agent_runner,
|
|
12
|
+
context_budget_from_config,
|
|
13
|
+
run_agent_turn,
|
|
14
|
+
)
|
|
15
|
+
from mycode.application.sessions import (
|
|
16
|
+
ActiveProjectSession,
|
|
17
|
+
SessionStartRequest,
|
|
18
|
+
delete_project_session,
|
|
19
|
+
list_project_sessions,
|
|
20
|
+
start_project_session,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"ActiveProjectSession",
|
|
25
|
+
"AgentApplicationSession",
|
|
26
|
+
"CompactResult",
|
|
27
|
+
"ContextStatus",
|
|
28
|
+
"RuntimeEvent",
|
|
29
|
+
"RuntimeEventType",
|
|
30
|
+
"SessionStartRequest",
|
|
31
|
+
"build_agent_runner",
|
|
32
|
+
"context_budget_from_config",
|
|
33
|
+
"delete_project_session",
|
|
34
|
+
"list_project_sessions",
|
|
35
|
+
"run_agent_turn",
|
|
36
|
+
"start_agent_application_session",
|
|
37
|
+
"start_project_session",
|
|
38
|
+
]
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Iterator
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from mycode.agent.events import AgentEvent
|
|
8
|
+
from mycode.agent.outcome import AgentRunOutcome
|
|
9
|
+
from mycode.agent.runner import AgentRunner
|
|
10
|
+
from mycode.application.events import RuntimeEvent
|
|
11
|
+
from mycode.application.runtime import build_agent_runner, run_agent_turn
|
|
12
|
+
from mycode.context.budget import ModelContext
|
|
13
|
+
from mycode.error_handling import error_summary
|
|
14
|
+
from mycode.application.sessions import (
|
|
15
|
+
ActiveProjectSession,
|
|
16
|
+
SessionStartRequest,
|
|
17
|
+
start_project_session,
|
|
18
|
+
)
|
|
19
|
+
from mycode.config import LLMConfig
|
|
20
|
+
from mycode.mcp import MCPConfig, MCPManager
|
|
21
|
+
from mycode.observability import ObservationSink
|
|
22
|
+
from mycode.permissions import Confirmer
|
|
23
|
+
from mycode.persistence.session_store import SessionStore
|
|
24
|
+
from mycode.project import ProjectIdentity
|
|
25
|
+
from mycode.subagents.observability import CompositeSubAgentObserver, SubAgentObserver
|
|
26
|
+
from mycode.subagents.persistence import SessionSubAgentObserver
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
RuntimeEventHandler = Callable[[RuntimeEvent], None]
|
|
30
|
+
CompactResultStatus = Literal["compacted", "skipped", "failed"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ContextStatus:
|
|
35
|
+
"""Content-free, serializable inspection of the current context."""
|
|
36
|
+
|
|
37
|
+
estimated_input_tokens: int
|
|
38
|
+
context_window_tokens: int
|
|
39
|
+
max_input_tokens: int
|
|
40
|
+
reserved_output_tokens: int
|
|
41
|
+
safety_margin_tokens: int
|
|
42
|
+
estimate_source: str
|
|
43
|
+
last_provider_prompt_tokens: int | None
|
|
44
|
+
source_message_count: int
|
|
45
|
+
model_visible_message_count: int
|
|
46
|
+
memory_entry_count: int
|
|
47
|
+
memory_estimated_tokens: int
|
|
48
|
+
compact_status: str
|
|
49
|
+
compact_covered_message_count: int
|
|
50
|
+
compressed_tool_result_count: int
|
|
51
|
+
|
|
52
|
+
def to_dict(self) -> dict[str, object]:
|
|
53
|
+
return {
|
|
54
|
+
"estimated": True,
|
|
55
|
+
"estimated_input_tokens": self.estimated_input_tokens,
|
|
56
|
+
"context_window_tokens": self.context_window_tokens,
|
|
57
|
+
"max_input_tokens": self.max_input_tokens,
|
|
58
|
+
"reserved_output_tokens": self.reserved_output_tokens,
|
|
59
|
+
"safety_margin_tokens": self.safety_margin_tokens,
|
|
60
|
+
"estimate_source": self.estimate_source,
|
|
61
|
+
"last_provider_prompt_tokens": self.last_provider_prompt_tokens,
|
|
62
|
+
"source_message_count": self.source_message_count,
|
|
63
|
+
"model_visible_message_count": self.model_visible_message_count,
|
|
64
|
+
"memory_entry_count": self.memory_entry_count,
|
|
65
|
+
"memory_estimated_tokens": self.memory_estimated_tokens,
|
|
66
|
+
"compact_status": self.compact_status,
|
|
67
|
+
"compact_covered_message_count": self.compact_covered_message_count,
|
|
68
|
+
"compressed_tool_result_count": self.compressed_tool_result_count,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class CompactResult:
|
|
74
|
+
status: CompactResultStatus
|
|
75
|
+
reason: str | None
|
|
76
|
+
before: ContextStatus | None
|
|
77
|
+
after: ContextStatus | None
|
|
78
|
+
|
|
79
|
+
def to_dict(self) -> dict[str, object]:
|
|
80
|
+
return {
|
|
81
|
+
"status": self.status,
|
|
82
|
+
"reason": self.reason,
|
|
83
|
+
"before": None if self.before is None else self.before.to_dict(),
|
|
84
|
+
"after": None if self.after is None else self.after.to_dict(),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _context_status(
|
|
89
|
+
context: ModelContext,
|
|
90
|
+
*,
|
|
91
|
+
context_window_tokens: int,
|
|
92
|
+
reserved_output_tokens: int,
|
|
93
|
+
safety_margin_tokens: int,
|
|
94
|
+
last_provider_prompt_tokens: int | None,
|
|
95
|
+
) -> ContextStatus:
|
|
96
|
+
memory = context.memory_stats
|
|
97
|
+
compact = context.compact_stats
|
|
98
|
+
if compact is None or compact.boundary_id is None:
|
|
99
|
+
compact_status = "none"
|
|
100
|
+
compact_covered_message_count = 0
|
|
101
|
+
elif compact.summary_visible:
|
|
102
|
+
compact_status = "active"
|
|
103
|
+
compact_covered_message_count = compact.compacted_message_count
|
|
104
|
+
else:
|
|
105
|
+
compact_status = compact.status
|
|
106
|
+
compact_covered_message_count = compact.compacted_message_count
|
|
107
|
+
return ContextStatus(
|
|
108
|
+
estimated_input_tokens=context.estimate.estimated_input_tokens,
|
|
109
|
+
context_window_tokens=context_window_tokens,
|
|
110
|
+
max_input_tokens=context.estimate.max_input_tokens,
|
|
111
|
+
reserved_output_tokens=reserved_output_tokens,
|
|
112
|
+
safety_margin_tokens=safety_margin_tokens,
|
|
113
|
+
estimate_source=context.estimate.token_estimate_source,
|
|
114
|
+
last_provider_prompt_tokens=last_provider_prompt_tokens,
|
|
115
|
+
source_message_count=context.source_message_count,
|
|
116
|
+
model_visible_message_count=context.selected_message_count,
|
|
117
|
+
memory_entry_count=(0 if memory is None else memory.included_entry_count),
|
|
118
|
+
memory_estimated_tokens=(0 if memory is None else memory.estimated_tokens),
|
|
119
|
+
compact_status=compact_status,
|
|
120
|
+
compact_covered_message_count=compact_covered_message_count,
|
|
121
|
+
compressed_tool_result_count=context.compressed_tool_result_count,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class AgentApplicationSession:
|
|
127
|
+
runner: AgentRunner
|
|
128
|
+
active_project_session: ActiveProjectSession
|
|
129
|
+
mcp_manager: MCPManager
|
|
130
|
+
_cleaned_up: bool = field(default=False, init=False, repr=False)
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def compact_state_recovered(self) -> bool:
|
|
134
|
+
return self.active_project_session.compact_state_recovered
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def mcp_statuses(self):
|
|
138
|
+
return self.mcp_manager.statuses
|
|
139
|
+
|
|
140
|
+
def get_context_status(self) -> ContextStatus:
|
|
141
|
+
"""Recompute current Context statistics without invoking an LLM."""
|
|
142
|
+
self._ensure_active()
|
|
143
|
+
context = self.runner.inspect_context()
|
|
144
|
+
return _context_status(
|
|
145
|
+
context,
|
|
146
|
+
context_window_tokens=self.runner.context_budget.context_window_tokens,
|
|
147
|
+
reserved_output_tokens=self.runner.context_budget.reserved_output_tokens,
|
|
148
|
+
safety_margin_tokens=self.runner.context_budget.safety_margin_tokens,
|
|
149
|
+
last_provider_prompt_tokens=(
|
|
150
|
+
None
|
|
151
|
+
if self.runner.last_token_usage is None
|
|
152
|
+
else self.runner.last_token_usage.prompt_tokens
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def compact_context(self) -> CompactResult:
|
|
157
|
+
"""Run one manual Compact and return a safe structured outcome."""
|
|
158
|
+
self._ensure_active()
|
|
159
|
+
before: ContextStatus | None = None
|
|
160
|
+
try:
|
|
161
|
+
before = self.get_context_status()
|
|
162
|
+
context = self.runner.compact_context()
|
|
163
|
+
compact = context.compact_stats
|
|
164
|
+
if compact is None:
|
|
165
|
+
raise RuntimeError("Compact returned no statistics.")
|
|
166
|
+
operation_status = compact.status
|
|
167
|
+
if operation_status == "compacted":
|
|
168
|
+
result_status: CompactResultStatus = "compacted"
|
|
169
|
+
reason = None
|
|
170
|
+
elif operation_status in {
|
|
171
|
+
"insufficient_history",
|
|
172
|
+
"cooldown",
|
|
173
|
+
"circuit_open",
|
|
174
|
+
}:
|
|
175
|
+
result_status = "skipped"
|
|
176
|
+
reason = operation_status
|
|
177
|
+
elif operation_status in {"failed", "invalid_boundary"}:
|
|
178
|
+
result_status = "failed"
|
|
179
|
+
compactor = getattr(self.runner, "compactor", None)
|
|
180
|
+
reason = (
|
|
181
|
+
None
|
|
182
|
+
if compactor is None
|
|
183
|
+
else compactor.state.last_failure_reason
|
|
184
|
+
) or operation_status
|
|
185
|
+
else:
|
|
186
|
+
raise RuntimeError(
|
|
187
|
+
f"Unexpected manual Compact status: {operation_status}."
|
|
188
|
+
)
|
|
189
|
+
after = _context_status(
|
|
190
|
+
context,
|
|
191
|
+
context_window_tokens=self.runner.context_budget.context_window_tokens,
|
|
192
|
+
reserved_output_tokens=self.runner.context_budget.reserved_output_tokens,
|
|
193
|
+
safety_margin_tokens=self.runner.context_budget.safety_margin_tokens,
|
|
194
|
+
last_provider_prompt_tokens=(
|
|
195
|
+
None
|
|
196
|
+
if self.runner.last_token_usage is None
|
|
197
|
+
else self.runner.last_token_usage.prompt_tokens
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
return CompactResult(
|
|
201
|
+
status=result_status,
|
|
202
|
+
reason=reason,
|
|
203
|
+
before=before,
|
|
204
|
+
after=after,
|
|
205
|
+
)
|
|
206
|
+
except Exception as error: # noqa: BLE001 - application command boundary
|
|
207
|
+
return CompactResult(
|
|
208
|
+
status="failed",
|
|
209
|
+
reason=error_summary(error),
|
|
210
|
+
before=before,
|
|
211
|
+
after=(
|
|
212
|
+
self._safe_context_status(before)
|
|
213
|
+
if before is not None
|
|
214
|
+
else None
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def _ensure_active(self) -> None:
|
|
219
|
+
if self._cleaned_up:
|
|
220
|
+
raise RuntimeError(
|
|
221
|
+
"AgentApplicationSession is already closed or interrupted."
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def _safe_context_status(self, fallback: ContextStatus) -> ContextStatus:
|
|
225
|
+
try:
|
|
226
|
+
return self.get_context_status()
|
|
227
|
+
except Exception:
|
|
228
|
+
return fallback
|
|
229
|
+
|
|
230
|
+
def startup_events(self) -> Iterator[RuntimeEvent]:
|
|
231
|
+
yield RuntimeEvent(
|
|
232
|
+
type="runtime_ready",
|
|
233
|
+
session_id=self.active_project_session.record.id,
|
|
234
|
+
session_title=self.active_project_session.record.title,
|
|
235
|
+
session_created=self.active_project_session.created,
|
|
236
|
+
compact_state_recovered=self.compact_state_recovered,
|
|
237
|
+
instruction_sources=tuple(
|
|
238
|
+
getattr(self.runner, "instruction_sources", ())
|
|
239
|
+
),
|
|
240
|
+
instruction_warnings=tuple(
|
|
241
|
+
getattr(self.runner, "instruction_warnings", ())
|
|
242
|
+
),
|
|
243
|
+
skill_warnings=tuple(getattr(self.runner, "skill_warnings", ())),
|
|
244
|
+
)
|
|
245
|
+
for status in self.mcp_statuses:
|
|
246
|
+
yield RuntimeEvent(type="mcp_status", mcp_status=status)
|
|
247
|
+
|
|
248
|
+
def run_turn(
|
|
249
|
+
self,
|
|
250
|
+
content: str,
|
|
251
|
+
*,
|
|
252
|
+
turn_id: str | None = None,
|
|
253
|
+
event_handler: RuntimeEventHandler | None = None,
|
|
254
|
+
) -> AgentRunOutcome:
|
|
255
|
+
if self._cleaned_up:
|
|
256
|
+
raise RuntimeError(
|
|
257
|
+
"AgentApplicationSession is already closed or interrupted."
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def handle_agent_event(agent_event: AgentEvent) -> None:
|
|
261
|
+
if event_handler is not None:
|
|
262
|
+
event_handler(
|
|
263
|
+
RuntimeEvent(
|
|
264
|
+
type="agent",
|
|
265
|
+
turn_id=turn_id,
|
|
266
|
+
agent_event=agent_event,
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
outcome = run_agent_turn(
|
|
271
|
+
self.runner,
|
|
272
|
+
content,
|
|
273
|
+
event_handler=handle_agent_event,
|
|
274
|
+
)
|
|
275
|
+
if event_handler is not None:
|
|
276
|
+
event_handler(
|
|
277
|
+
RuntimeEvent(
|
|
278
|
+
type="turn_finished",
|
|
279
|
+
turn_id=turn_id,
|
|
280
|
+
outcome=outcome,
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
return outcome
|
|
284
|
+
|
|
285
|
+
def close(self) -> None:
|
|
286
|
+
self._cleanup("closed")
|
|
287
|
+
|
|
288
|
+
def interrupt(self) -> None:
|
|
289
|
+
"""Finalize this session as interrupted and release its resources.
|
|
290
|
+
|
|
291
|
+
This does not asynchronously cancel a currently running Agent turn.
|
|
292
|
+
"""
|
|
293
|
+
self._cleanup("interrupted")
|
|
294
|
+
|
|
295
|
+
def _cleanup(self, status: Literal["closed", "interrupted"]) -> None:
|
|
296
|
+
if self._cleaned_up:
|
|
297
|
+
return
|
|
298
|
+
try:
|
|
299
|
+
if status == "closed":
|
|
300
|
+
self.active_project_session.close()
|
|
301
|
+
else:
|
|
302
|
+
self.active_project_session.interrupt()
|
|
303
|
+
finally:
|
|
304
|
+
try:
|
|
305
|
+
self.mcp_manager.close()
|
|
306
|
+
finally:
|
|
307
|
+
self._cleaned_up = True
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def start_agent_application_session(
|
|
311
|
+
store: SessionStore,
|
|
312
|
+
project: ProjectIdentity,
|
|
313
|
+
*,
|
|
314
|
+
request: SessionStartRequest,
|
|
315
|
+
mcp_config: MCPConfig | None = None,
|
|
316
|
+
confirmer: Confirmer | None = None,
|
|
317
|
+
external_observer: SubAgentObserver | None = None,
|
|
318
|
+
llm_config: LLMConfig | None = None,
|
|
319
|
+
observability_sink: ObservationSink | None = None,
|
|
320
|
+
) -> AgentApplicationSession:
|
|
321
|
+
active_session = start_project_session(store, project, request=request)
|
|
322
|
+
mcp_manager: MCPManager | None = None
|
|
323
|
+
try:
|
|
324
|
+
conversation_history = active_session.load_history()
|
|
325
|
+
compact_state = active_session.load_compact_state()
|
|
326
|
+
mcp_manager = MCPManager(
|
|
327
|
+
MCPConfig() if mcp_config is None else mcp_config,
|
|
328
|
+
observability_sink=observability_sink,
|
|
329
|
+
)
|
|
330
|
+
mcp_manager.start()
|
|
331
|
+
session_observer = SessionSubAgentObserver(session=active_session.writer)
|
|
332
|
+
subagent_observer: SubAgentObserver = session_observer
|
|
333
|
+
if external_observer is not None:
|
|
334
|
+
subagent_observer = CompositeSubAgentObserver(
|
|
335
|
+
observers=(session_observer, external_observer),
|
|
336
|
+
)
|
|
337
|
+
runner = build_agent_runner(
|
|
338
|
+
workspace_path=project.workspace_root,
|
|
339
|
+
confirmer=confirmer,
|
|
340
|
+
conversation_history=conversation_history,
|
|
341
|
+
on_message_added=active_session.persist_message,
|
|
342
|
+
compact_state=compact_state,
|
|
343
|
+
on_compact_state_changed=active_session.persist_compact_state,
|
|
344
|
+
artifact_directory=active_session.artifact_directory,
|
|
345
|
+
subagent_observer=subagent_observer,
|
|
346
|
+
llm_config=llm_config,
|
|
347
|
+
llm_session_id=active_session.record.id,
|
|
348
|
+
observability_sink=observability_sink,
|
|
349
|
+
)
|
|
350
|
+
for tool in mcp_manager.tools:
|
|
351
|
+
runner.tool_registry.register(tool)
|
|
352
|
+
return AgentApplicationSession(
|
|
353
|
+
runner=runner,
|
|
354
|
+
active_project_session=active_session,
|
|
355
|
+
mcp_manager=mcp_manager,
|
|
356
|
+
)
|
|
357
|
+
except BaseException:
|
|
358
|
+
if mcp_manager is not None:
|
|
359
|
+
try:
|
|
360
|
+
mcp_manager.close()
|
|
361
|
+
except BaseException:
|
|
362
|
+
pass
|
|
363
|
+
try:
|
|
364
|
+
active_session.interrupt()
|
|
365
|
+
except BaseException:
|
|
366
|
+
pass
|
|
367
|
+
raise
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Application-facing runtime events."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from mycode.agent.events import AgentEvent
|
|
9
|
+
from mycode.agent.outcome import AgentRunOutcome
|
|
10
|
+
from mycode.mcp.models import MCPServerStatus
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
RuntimeEventType = Literal[
|
|
14
|
+
"runtime_ready",
|
|
15
|
+
"mcp_status",
|
|
16
|
+
"agent",
|
|
17
|
+
"turn_finished",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class RuntimeEvent:
|
|
23
|
+
"""Small application boundary wrapper around existing domain events."""
|
|
24
|
+
|
|
25
|
+
type: RuntimeEventType
|
|
26
|
+
turn_id: str | None = None
|
|
27
|
+
agent_event: AgentEvent | None = None
|
|
28
|
+
outcome: AgentRunOutcome | None = None
|
|
29
|
+
mcp_status: MCPServerStatus | None = None
|
|
30
|
+
session_id: str | None = None
|
|
31
|
+
session_title: str | None = None
|
|
32
|
+
session_created: bool | None = None
|
|
33
|
+
compact_state_recovered: bool | None = None
|
|
34
|
+
instruction_sources: tuple[str, ...] = ()
|
|
35
|
+
instruction_warnings: tuple[str, ...] = ()
|
|
36
|
+
skill_warnings: tuple[str, ...] = ()
|
|
37
|
+
|
|
38
|
+
def __post_init__(self) -> None:
|
|
39
|
+
if self.type not in {
|
|
40
|
+
"runtime_ready",
|
|
41
|
+
"mcp_status",
|
|
42
|
+
"agent",
|
|
43
|
+
"turn_finished",
|
|
44
|
+
}:
|
|
45
|
+
raise ValueError(f"Unsupported runtime event type: {self.type}")
|
|
46
|
+
object.__setattr__(self, "instruction_sources", tuple(self.instruction_sources))
|
|
47
|
+
object.__setattr__(self, "instruction_warnings", tuple(self.instruction_warnings))
|
|
48
|
+
object.__setattr__(self, "skill_warnings", tuple(self.skill_warnings))
|
|
49
|
+
|
|
50
|
+
if self.type == "agent" and not isinstance(self.agent_event, AgentEvent):
|
|
51
|
+
raise ValueError("agent runtime events require agent_event")
|
|
52
|
+
if self.type == "mcp_status" and not isinstance(
|
|
53
|
+
self.mcp_status, MCPServerStatus
|
|
54
|
+
):
|
|
55
|
+
raise ValueError("mcp_status runtime events require mcp_status")
|
|
56
|
+
if self.type == "turn_finished" and not isinstance(
|
|
57
|
+
self.outcome, AgentRunOutcome
|
|
58
|
+
):
|
|
59
|
+
raise ValueError("turn_finished runtime events require outcome")
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Application-layer use cases shared by interactive and automated frontends."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from uuid import uuid4
|
|
8
|
+
|
|
9
|
+
from mycode.agent.events import AgentEvent, AgentStopReason
|
|
10
|
+
from mycode.context.artifacts import (
|
|
11
|
+
ReadArtifactTool,
|
|
12
|
+
ToolResultArtifactStore,
|
|
13
|
+
)
|
|
14
|
+
from mycode.config import LLMConfig, load_llm_config
|
|
15
|
+
from mycode.context.budget import ContextBudget
|
|
16
|
+
from mycode.context.compact import CompactState, ConversationCompactor
|
|
17
|
+
from mycode.conversation import Conversation
|
|
18
|
+
from mycode.instructions import load_instruction_bundle
|
|
19
|
+
from mycode.llm import OpenAICompatibleLLMClient
|
|
20
|
+
from mycode.memory import MemoryStore
|
|
21
|
+
from mycode.memory_context import MemoryContextSelector, MemoryRecallPolicy
|
|
22
|
+
from mycode.messages import Message
|
|
23
|
+
from mycode.observability import ObservationSink
|
|
24
|
+
from mycode.permissions import Confirmer
|
|
25
|
+
from mycode.project import ProjectIdentity
|
|
26
|
+
from mycode.prompts import build_agent_system_prompt
|
|
27
|
+
from mycode.agent.outcome import AgentRunOutcome
|
|
28
|
+
from mycode.agent.runner import AgentRunner
|
|
29
|
+
from mycode.skills import ActiveSkillState, SkillRegistry
|
|
30
|
+
from mycode.subagents.delegate import DelegateTaskTool
|
|
31
|
+
from mycode.subagents.delegation import DelegationToolBatchHandler
|
|
32
|
+
from mycode.subagents.observability import SubAgentObserver
|
|
33
|
+
from mycode.subagents.runtime import SubAgentRuntime
|
|
34
|
+
from mycode.tools import (
|
|
35
|
+
LoadSkillTool,
|
|
36
|
+
ReadSkillResourceTool,
|
|
37
|
+
RunSkillScriptTool,
|
|
38
|
+
Workspace,
|
|
39
|
+
create_default_tool_registry,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
AgentEventHandler = Callable[[AgentEvent], None]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def build_agent_runner(
|
|
47
|
+
workspace_path: Path | None = None,
|
|
48
|
+
*,
|
|
49
|
+
confirmer: Confirmer | None = None,
|
|
50
|
+
conversation_history: Conversation | None = None,
|
|
51
|
+
on_message_added: Callable[[Message], None] | None = None,
|
|
52
|
+
compact_state: CompactState | None = None,
|
|
53
|
+
on_compact_state_changed: Callable[[CompactState], None] | None = None,
|
|
54
|
+
artifact_directory: Path | None = None,
|
|
55
|
+
memory_store: MemoryStore | None = None,
|
|
56
|
+
subagent_observer: SubAgentObserver | None = None,
|
|
57
|
+
llm_config: LLMConfig | None = None,
|
|
58
|
+
llm_session_id: str | None = None,
|
|
59
|
+
observability_sink: ObservationSink | None = None,
|
|
60
|
+
) -> AgentRunner:
|
|
61
|
+
"""Assemble one Agent runtime without coupling it to a presentation layer."""
|
|
62
|
+
workspace_root = Path.cwd() if workspace_path is None else workspace_path
|
|
63
|
+
workspace = Workspace(workspace_root)
|
|
64
|
+
project = ProjectIdentity.from_workspace(workspace.root)
|
|
65
|
+
effective_memory_store = (
|
|
66
|
+
MemoryStore(project) if memory_store is None else memory_store
|
|
67
|
+
)
|
|
68
|
+
instruction_bundle = load_instruction_bundle(workspace.root)
|
|
69
|
+
skill_registry = SkillRegistry.discover(workspace.root)
|
|
70
|
+
active_skill_state = ActiveSkillState()
|
|
71
|
+
config = (
|
|
72
|
+
load_llm_config(workspace_root=workspace.root)
|
|
73
|
+
if llm_config is None
|
|
74
|
+
else llm_config
|
|
75
|
+
)
|
|
76
|
+
effective_llm_session_id = llm_session_id or uuid4().hex
|
|
77
|
+
client = OpenAICompatibleLLMClient(
|
|
78
|
+
config=config,
|
|
79
|
+
session_id=effective_llm_session_id,
|
|
80
|
+
)
|
|
81
|
+
summary_client = OpenAICompatibleLLMClient(
|
|
82
|
+
config=config,
|
|
83
|
+
model=config.compact_model,
|
|
84
|
+
thinking_enabled=False,
|
|
85
|
+
session_id=effective_llm_session_id,
|
|
86
|
+
)
|
|
87
|
+
context_budget = context_budget_from_config(config)
|
|
88
|
+
memory_recall_policy = MemoryRecallPolicy(
|
|
89
|
+
max_tokens=config.memory_context_tokens,
|
|
90
|
+
)
|
|
91
|
+
subagent_runtime = SubAgentRuntime(
|
|
92
|
+
workspace=workspace,
|
|
93
|
+
llm_client_factory=lambda: OpenAICompatibleLLMClient(
|
|
94
|
+
config=config,
|
|
95
|
+
model=config.subagent_model,
|
|
96
|
+
session_id=effective_llm_session_id,
|
|
97
|
+
),
|
|
98
|
+
confirmer=confirmer,
|
|
99
|
+
memory_store=effective_memory_store,
|
|
100
|
+
memory_recall_policy=memory_recall_policy,
|
|
101
|
+
context_budget=context_budget,
|
|
102
|
+
observability_sink=observability_sink,
|
|
103
|
+
)
|
|
104
|
+
history_messages = (
|
|
105
|
+
[] if conversation_history is None else conversation_history.get_messages()
|
|
106
|
+
)
|
|
107
|
+
if any(message.role == "system" for message in history_messages):
|
|
108
|
+
raise ValueError("conversation_history must not contain system messages.")
|
|
109
|
+
conversation = Conversation.from_messages(
|
|
110
|
+
[
|
|
111
|
+
Message(
|
|
112
|
+
role="system",
|
|
113
|
+
content=build_agent_system_prompt(
|
|
114
|
+
instruction_bundle.to_prompt_text(),
|
|
115
|
+
memory_enabled=True,
|
|
116
|
+
delegation_enabled=True,
|
|
117
|
+
skill_catalog=skill_registry.get_catalog(),
|
|
118
|
+
),
|
|
119
|
+
),
|
|
120
|
+
*history_messages,
|
|
121
|
+
],
|
|
122
|
+
on_message_added=on_message_added,
|
|
123
|
+
)
|
|
124
|
+
artifact_store = (
|
|
125
|
+
None
|
|
126
|
+
if artifact_directory is None
|
|
127
|
+
else ToolResultArtifactStore(
|
|
128
|
+
root=artifact_directory,
|
|
129
|
+
threshold_chars=context_budget.tool_result_compression_threshold_chars,
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
extra_tools = []
|
|
133
|
+
if artifact_store is not None:
|
|
134
|
+
extra_tools.append(ReadArtifactTool(artifact_store.root))
|
|
135
|
+
extra_tools.append(
|
|
136
|
+
DelegateTaskTool(
|
|
137
|
+
subagent_runtime,
|
|
138
|
+
observer=subagent_observer,
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
if skill_registry.list_skills():
|
|
142
|
+
extra_tools.extend(
|
|
143
|
+
[
|
|
144
|
+
LoadSkillTool(skill_registry, active_skill_state),
|
|
145
|
+
ReadSkillResourceTool(skill_registry, active_skill_state),
|
|
146
|
+
RunSkillScriptTool(workspace, skill_registry, active_skill_state),
|
|
147
|
+
]
|
|
148
|
+
)
|
|
149
|
+
tool_registry = create_default_tool_registry(
|
|
150
|
+
workspace,
|
|
151
|
+
confirmer=confirmer,
|
|
152
|
+
memory_store=effective_memory_store,
|
|
153
|
+
extra_tools=extra_tools,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return AgentRunner(
|
|
157
|
+
llm_client=client,
|
|
158
|
+
tool_registry=tool_registry,
|
|
159
|
+
conversation=conversation,
|
|
160
|
+
context_budget=context_budget,
|
|
161
|
+
instruction_sources=tuple(
|
|
162
|
+
source.label for source in instruction_bundle.sources
|
|
163
|
+
),
|
|
164
|
+
instruction_warnings=tuple(
|
|
165
|
+
issue.display for issue in instruction_bundle.issues
|
|
166
|
+
),
|
|
167
|
+
skill_warnings=tuple(warning.display for warning in skill_registry.warnings),
|
|
168
|
+
active_skill_state=active_skill_state,
|
|
169
|
+
memory_context_selector=MemoryContextSelector(
|
|
170
|
+
effective_memory_store,
|
|
171
|
+
policy=memory_recall_policy,
|
|
172
|
+
),
|
|
173
|
+
tool_batch_handler=DelegationToolBatchHandler(),
|
|
174
|
+
compactor=ConversationCompactor(
|
|
175
|
+
llm_client=summary_client,
|
|
176
|
+
state=CompactState() if compact_state is None else compact_state,
|
|
177
|
+
on_state_changed=on_compact_state_changed,
|
|
178
|
+
observability_sink=observability_sink,
|
|
179
|
+
observability_scope="compact",
|
|
180
|
+
),
|
|
181
|
+
tool_result_artifact_store=artifact_store,
|
|
182
|
+
observability_sink=observability_sink,
|
|
183
|
+
observability_scope="main",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def run_agent_turn(
|
|
188
|
+
runner: AgentRunner,
|
|
189
|
+
content: str,
|
|
190
|
+
*,
|
|
191
|
+
event_handler: AgentEventHandler | None = None,
|
|
192
|
+
) -> AgentRunOutcome:
|
|
193
|
+
"""Run one user request, optionally forwarding events to its caller."""
|
|
194
|
+
stop_reasons: list[AgentStopReason] = []
|
|
195
|
+
for event in runner.run(content):
|
|
196
|
+
if event.type == "stop" and event.stop_reason is not None:
|
|
197
|
+
stop_reasons.append(event.stop_reason)
|
|
198
|
+
if event_handler is not None:
|
|
199
|
+
event_handler(event)
|
|
200
|
+
|
|
201
|
+
if len(stop_reasons) != 1:
|
|
202
|
+
return AgentRunOutcome.from_stop_reason(None)
|
|
203
|
+
return AgentRunOutcome.from_stop_reason(stop_reasons[0])
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def context_budget_from_config(config: LLMConfig) -> ContextBudget:
|
|
207
|
+
return ContextBudget(
|
|
208
|
+
context_window_tokens=config.context_window_tokens,
|
|
209
|
+
reserved_output_tokens=config.reserved_output_tokens,
|
|
210
|
+
safety_margin_tokens=config.context_safety_margin_tokens,
|
|
211
|
+
)
|