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,212 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import math
|
|
5
|
+
from typing import TypeAlias
|
|
6
|
+
|
|
7
|
+
from mycode.agent.events import AgentToolCall
|
|
8
|
+
from mycode.tools.base import ToolResult
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
MAX_AUDIT_REASON_CHARS = 100
|
|
12
|
+
KNOWN_SUBAGENT_TOOL_NAMES = frozenset(
|
|
13
|
+
{
|
|
14
|
+
"read_file",
|
|
15
|
+
"glob",
|
|
16
|
+
"grep",
|
|
17
|
+
"run_validation",
|
|
18
|
+
"inspect_changes",
|
|
19
|
+
"submit_result",
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
AuditScalar: TypeAlias = str | int | float | bool | None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class SubAgentToolAudit:
|
|
27
|
+
tool_name: str
|
|
28
|
+
arguments_sha256: str
|
|
29
|
+
argument_summary: dict[str, AuditScalar]
|
|
30
|
+
ok: bool
|
|
31
|
+
exit_code: int | None
|
|
32
|
+
duration_ms: int | None
|
|
33
|
+
output_chars: int
|
|
34
|
+
truncated: bool
|
|
35
|
+
reason: str | None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def build_tool_audit(
|
|
39
|
+
tool_call: AgentToolCall,
|
|
40
|
+
result: ToolResult,
|
|
41
|
+
) -> SubAgentToolAudit:
|
|
42
|
+
arguments_json = _canonical_json(tool_call.arguments)
|
|
43
|
+
body = result.content if result.ok else result.error or ""
|
|
44
|
+
metadata = result.metadata
|
|
45
|
+
tool_name = (
|
|
46
|
+
tool_call.name
|
|
47
|
+
if tool_call.name in KNOWN_SUBAGENT_TOOL_NAMES
|
|
48
|
+
else "unknown"
|
|
49
|
+
)
|
|
50
|
+
return SubAgentToolAudit(
|
|
51
|
+
tool_name=tool_name,
|
|
52
|
+
arguments_sha256=hashlib.sha256(arguments_json.encode("utf-8")).hexdigest(),
|
|
53
|
+
argument_summary=_argument_summary(
|
|
54
|
+
tool_name,
|
|
55
|
+
tool_call.arguments,
|
|
56
|
+
requested_tool_name=tool_call.name,
|
|
57
|
+
),
|
|
58
|
+
ok=result.ok,
|
|
59
|
+
exit_code=_optional_int(metadata.get("exit_code")),
|
|
60
|
+
duration_ms=_optional_non_negative_int(metadata.get("duration_ms")),
|
|
61
|
+
output_chars=len(body),
|
|
62
|
+
truncated=_optional_bool(metadata.get("truncated")) is True,
|
|
63
|
+
reason=_safe_reason(metadata.get("reason")),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _argument_summary(
|
|
68
|
+
name: str,
|
|
69
|
+
arguments: dict[str, object],
|
|
70
|
+
*,
|
|
71
|
+
requested_tool_name: str,
|
|
72
|
+
) -> dict[str, AuditScalar]:
|
|
73
|
+
if name == "unknown":
|
|
74
|
+
return {
|
|
75
|
+
"requested_tool_name_sha256": _value_sha256(requested_tool_name),
|
|
76
|
+
"requested_tool_name_chars": len(requested_tool_name),
|
|
77
|
+
"argument_key_count": len(arguments),
|
|
78
|
+
"argument_keys_sha256": _value_sha256(sorted(str(key) for key in arguments)),
|
|
79
|
+
"argument_chars": len(_canonical_json(arguments)),
|
|
80
|
+
}
|
|
81
|
+
if name == "read_file":
|
|
82
|
+
return {
|
|
83
|
+
"path_sha256": _value_sha256(arguments.get("path")),
|
|
84
|
+
"start_line": _optional_int(arguments.get("start_line")),
|
|
85
|
+
"max_lines": _optional_int(arguments.get("max_lines")),
|
|
86
|
+
}
|
|
87
|
+
if name == "glob":
|
|
88
|
+
return {
|
|
89
|
+
"pattern_sha256": _value_sha256(arguments.get("pattern")),
|
|
90
|
+
"pattern_chars": _string_chars(arguments.get("pattern")),
|
|
91
|
+
"max_results": _optional_int(arguments.get("max_results")),
|
|
92
|
+
}
|
|
93
|
+
if name == "grep":
|
|
94
|
+
return {
|
|
95
|
+
"query_sha256": _value_sha256(arguments.get("query")),
|
|
96
|
+
"query_chars": _string_chars(arguments.get("query")),
|
|
97
|
+
"path_pattern_sha256": _value_sha256(arguments.get("path_pattern")),
|
|
98
|
+
"case_sensitive": _optional_bool(arguments.get("case_sensitive")),
|
|
99
|
+
"max_results": _optional_int(arguments.get("max_results")),
|
|
100
|
+
}
|
|
101
|
+
if name == "run_validation":
|
|
102
|
+
command = arguments.get("command")
|
|
103
|
+
return {
|
|
104
|
+
"command_sha256": _value_sha256(command),
|
|
105
|
+
"command_parts": len(command) if isinstance(command, list) else None,
|
|
106
|
+
"cwd_sha256": _value_sha256(arguments.get("cwd")),
|
|
107
|
+
"timeout_seconds": _optional_number(arguments.get("timeout_seconds")),
|
|
108
|
+
"max_output_chars": _optional_int(arguments.get("max_output_chars")),
|
|
109
|
+
}
|
|
110
|
+
if name == "inspect_changes":
|
|
111
|
+
paths = arguments.get("paths")
|
|
112
|
+
return {
|
|
113
|
+
"action": _short_string(arguments.get("action")),
|
|
114
|
+
"path_count": len(paths) if isinstance(paths, list) else None,
|
|
115
|
+
"paths_sha256": _value_sha256(paths),
|
|
116
|
+
"staged": _optional_bool(arguments.get("staged")),
|
|
117
|
+
"base_ref_sha256": _value_sha256(arguments.get("base_ref")),
|
|
118
|
+
"max_output_chars": _optional_int(arguments.get("max_output_chars")),
|
|
119
|
+
}
|
|
120
|
+
if name == "submit_result":
|
|
121
|
+
findings = arguments.get("findings")
|
|
122
|
+
uncertainties = arguments.get("uncertainties")
|
|
123
|
+
return {
|
|
124
|
+
"status": _short_string(
|
|
125
|
+
arguments.get("status", arguments.get("recommendation"))
|
|
126
|
+
),
|
|
127
|
+
"summary_chars": _string_chars(arguments.get("summary")),
|
|
128
|
+
"finding_count": len(findings) if isinstance(findings, list) else None,
|
|
129
|
+
"uncertainty_count": (
|
|
130
|
+
len(uncertainties) if isinstance(uncertainties, list) else None
|
|
131
|
+
),
|
|
132
|
+
"argument_chars": len(_canonical_json(arguments)),
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
"argument_key_count": len(arguments),
|
|
136
|
+
"argument_keys_sha256": _value_sha256(sorted(str(key) for key in arguments)),
|
|
137
|
+
"argument_chars": len(_canonical_json(arguments)),
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _canonical_json(value: object) -> str:
|
|
142
|
+
return json.dumps(
|
|
143
|
+
_json_safe(value),
|
|
144
|
+
ensure_ascii=False,
|
|
145
|
+
sort_keys=True,
|
|
146
|
+
separators=(",", ":"),
|
|
147
|
+
allow_nan=False,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _json_safe(value: object) -> object:
|
|
152
|
+
if value is None or isinstance(value, (str, int, bool)):
|
|
153
|
+
return value
|
|
154
|
+
if isinstance(value, float):
|
|
155
|
+
return value if math.isfinite(value) else str(value)
|
|
156
|
+
if isinstance(value, list):
|
|
157
|
+
return [_json_safe(item) for item in value]
|
|
158
|
+
if isinstance(value, tuple):
|
|
159
|
+
return [_json_safe(item) for item in value]
|
|
160
|
+
if isinstance(value, dict):
|
|
161
|
+
return {
|
|
162
|
+
str(key): _json_safe(item)
|
|
163
|
+
for key, item in sorted(value.items(), key=lambda pair: str(pair[0]))
|
|
164
|
+
}
|
|
165
|
+
return {"type": type(value).__name__}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _value_sha256(value: object) -> str | None:
|
|
169
|
+
if value is None:
|
|
170
|
+
return None
|
|
171
|
+
return hashlib.sha256(_canonical_json(value).encode("utf-8")).hexdigest()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _string_chars(value: object) -> int | None:
|
|
175
|
+
return len(value) if isinstance(value, str) else None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _optional_int(value: object) -> int | None:
|
|
179
|
+
return value if isinstance(value, int) and not isinstance(value, bool) else None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _optional_non_negative_int(value: object) -> int | None:
|
|
183
|
+
parsed = _optional_int(value)
|
|
184
|
+
return parsed if parsed is not None and parsed >= 0 else None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _optional_number(value: object) -> int | float | None:
|
|
188
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
189
|
+
return None
|
|
190
|
+
return value if isinstance(value, int) or math.isfinite(value) else None
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _optional_bool(value: object) -> bool | None:
|
|
194
|
+
return value if isinstance(value, bool) else None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _short_string(value: object) -> str | None:
|
|
198
|
+
if not isinstance(value, str) or len(value) > MAX_AUDIT_REASON_CHARS:
|
|
199
|
+
return None
|
|
200
|
+
return value
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _safe_reason(value: object) -> str | None:
|
|
204
|
+
reason = _short_string(value)
|
|
205
|
+
if not reason:
|
|
206
|
+
return None
|
|
207
|
+
if not all(
|
|
208
|
+
character in "abcdefghijklmnopqrstuvwxyz0123456789_"
|
|
209
|
+
for character in reason
|
|
210
|
+
):
|
|
211
|
+
return None
|
|
212
|
+
return reason
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from concurrent.futures import Future, ThreadPoolExecutor
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from threading import RLock
|
|
7
|
+
from typing import TypeVar
|
|
8
|
+
|
|
9
|
+
from mycode.agent.events import AgentToolCall
|
|
10
|
+
from mycode.tools.base import ToolResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
ResultT = TypeVar("ResultT")
|
|
14
|
+
DelegationExecutor = Callable[[AgentToolCall], ToolResult]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class SubAgentInteractionGate:
|
|
19
|
+
"""Serialize process-local SubAgent confirmation and observer interactions."""
|
|
20
|
+
|
|
21
|
+
_lock: RLock = field(default_factory=RLock, init=False, repr=False)
|
|
22
|
+
|
|
23
|
+
def run(self, operation: Callable[[], ResultT]) -> ResultT:
|
|
24
|
+
with self._lock:
|
|
25
|
+
return operation()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class BoundedDelegationScheduler:
|
|
30
|
+
"""Run independent delegation calls in bounded fork-join chunks."""
|
|
31
|
+
|
|
32
|
+
max_concurrent: int
|
|
33
|
+
|
|
34
|
+
def __post_init__(self) -> None:
|
|
35
|
+
if self.max_concurrent < 1:
|
|
36
|
+
raise ValueError("max_concurrent must be at least 1.")
|
|
37
|
+
|
|
38
|
+
def execute(
|
|
39
|
+
self,
|
|
40
|
+
tool_calls: list[AgentToolCall],
|
|
41
|
+
executor: DelegationExecutor,
|
|
42
|
+
) -> list[ToolResult]:
|
|
43
|
+
results: list[ToolResult] = []
|
|
44
|
+
for start in range(0, len(tool_calls), self.max_concurrent):
|
|
45
|
+
chunk = tool_calls[start : start + self.max_concurrent]
|
|
46
|
+
results.extend(self._execute_chunk(chunk, executor))
|
|
47
|
+
return results
|
|
48
|
+
|
|
49
|
+
def _execute_chunk(
|
|
50
|
+
self,
|
|
51
|
+
tool_calls: list[AgentToolCall],
|
|
52
|
+
executor: DelegationExecutor,
|
|
53
|
+
) -> list[ToolResult]:
|
|
54
|
+
if not tool_calls:
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
pool = ThreadPoolExecutor(
|
|
59
|
+
max_workers=len(tool_calls),
|
|
60
|
+
thread_name_prefix="mycode-subagent",
|
|
61
|
+
)
|
|
62
|
+
except Exception as error:
|
|
63
|
+
return [
|
|
64
|
+
_worker_failure("delegation_worker_start_error", error)
|
|
65
|
+
for _ in tool_calls
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
futures: list[tuple[int, Future[ToolResult]]] = []
|
|
69
|
+
results: list[ToolResult | None] = [None] * len(tool_calls)
|
|
70
|
+
process_exception: BaseException | None = None
|
|
71
|
+
try:
|
|
72
|
+
for index, tool_call in enumerate(tool_calls):
|
|
73
|
+
try:
|
|
74
|
+
futures.append((index, pool.submit(executor, tool_call)))
|
|
75
|
+
except Exception as error:
|
|
76
|
+
results[index] = _worker_failure(
|
|
77
|
+
"delegation_worker_start_error",
|
|
78
|
+
error,
|
|
79
|
+
)
|
|
80
|
+
except BaseException as error:
|
|
81
|
+
process_exception = error
|
|
82
|
+
break
|
|
83
|
+
|
|
84
|
+
for index, future in futures:
|
|
85
|
+
try:
|
|
86
|
+
result = future.result()
|
|
87
|
+
except Exception as error:
|
|
88
|
+
result = _worker_failure(
|
|
89
|
+
"delegation_worker_error",
|
|
90
|
+
error,
|
|
91
|
+
)
|
|
92
|
+
except BaseException as error:
|
|
93
|
+
if process_exception is None:
|
|
94
|
+
process_exception = error
|
|
95
|
+
continue
|
|
96
|
+
if not isinstance(result, ToolResult):
|
|
97
|
+
result = ToolResult.failure(
|
|
98
|
+
error="Delegation worker returned an invalid result.",
|
|
99
|
+
metadata={"reason": "delegation_worker_contract"},
|
|
100
|
+
)
|
|
101
|
+
results[index] = result
|
|
102
|
+
finally:
|
|
103
|
+
pool.shutdown(wait=True, cancel_futures=False)
|
|
104
|
+
if process_exception is not None:
|
|
105
|
+
raise process_exception
|
|
106
|
+
return [
|
|
107
|
+
result
|
|
108
|
+
if result is not None
|
|
109
|
+
else ToolResult.failure(
|
|
110
|
+
error="Delegation worker did not produce a result.",
|
|
111
|
+
metadata={"reason": "delegation_worker_missing_result"},
|
|
112
|
+
)
|
|
113
|
+
for result in results
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _worker_failure(reason: str, error: Exception) -> ToolResult:
|
|
118
|
+
return ToolResult.failure(
|
|
119
|
+
error="Delegation execution failed unexpectedly.",
|
|
120
|
+
metadata={
|
|
121
|
+
"reason": reason,
|
|
122
|
+
"exception_type": type(error).__name__,
|
|
123
|
+
},
|
|
124
|
+
)
|