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.
Files changed (121) hide show
  1. mycode/__init__.py +0 -0
  2. mycode/adapters/__init__.py +21 -0
  3. mycode/adapters/jsonl.py +692 -0
  4. mycode/agent/__init__.py +25 -0
  5. mycode/agent/events.py +111 -0
  6. mycode/agent/outcome.py +103 -0
  7. mycode/agent/progress.py +373 -0
  8. mycode/agent/runner.py +1481 -0
  9. mycode/application/__init__.py +38 -0
  10. mycode/application/agent_session.py +367 -0
  11. mycode/application/events.py +59 -0
  12. mycode/application/runtime.py +211 -0
  13. mycode/application/sessions.py +180 -0
  14. mycode/cli.py +840 -0
  15. mycode/config.py +355 -0
  16. mycode/context/__init__.py +1 -0
  17. mycode/context/artifacts.py +672 -0
  18. mycode/context/budget.py +752 -0
  19. mycode/context/builder.py +112 -0
  20. mycode/context/compact.py +795 -0
  21. mycode/context/tool_result_format.py +199 -0
  22. mycode/context/tool_result_retention.py +261 -0
  23. mycode/conversation.py +78 -0
  24. mycode/error_handling.py +481 -0
  25. mycode/event_format.py +147 -0
  26. mycode/instructions.py +285 -0
  27. mycode/llm.py +771 -0
  28. mycode/mcp/__init__.py +41 -0
  29. mycode/mcp/client.py +44 -0
  30. mycode/mcp/config.py +207 -0
  31. mycode/mcp/errors.py +302 -0
  32. mycode/mcp/manager.py +339 -0
  33. mycode/mcp/models.py +20 -0
  34. mycode/mcp/result_adapter.py +58 -0
  35. mycode/mcp/tool_adapter.py +145 -0
  36. mycode/mcp/trust.py +313 -0
  37. mycode/memory.py +570 -0
  38. mycode/memory_context.py +245 -0
  39. mycode/messages.py +63 -0
  40. mycode/observability.py +28 -0
  41. mycode/permissions.py +262 -0
  42. mycode/persistence/__init__.py +1 -0
  43. mycode/persistence/filesystem.py +291 -0
  44. mycode/persistence/project_storage.py +208 -0
  45. mycode/persistence/session_lock.py +138 -0
  46. mycode/persistence/session_store.py +503 -0
  47. mycode/presentation/__init__.py +1 -0
  48. mycode/presentation/cli/__init__.py +14 -0
  49. mycode/presentation/cli/confirmer.py +116 -0
  50. mycode/presentation/cli/mcp_trust.py +61 -0
  51. mycode/presentation/cli/presenter.py +320 -0
  52. mycode/presentation/cli/session_menu.py +146 -0
  53. mycode/presentation/cli/subagent_observer.py +124 -0
  54. mycode/presentation/command_format.py +90 -0
  55. mycode/presentation/commands.py +95 -0
  56. mycode/presentation/tui/__init__.py +6 -0
  57. mycode/presentation/tui/app.py +1351 -0
  58. mycode/presentation/tui/interactions.py +253 -0
  59. mycode/presentation/tui/presenter.py +266 -0
  60. mycode/presentation/tui/screens.py +305 -0
  61. mycode/presentation/tui/widgets.py +214 -0
  62. mycode/project.py +22 -0
  63. mycode/prompts.py +181 -0
  64. mycode/reasoning.py +40 -0
  65. mycode/session.py +86 -0
  66. mycode/skills/__init__.py +27 -0
  67. mycode/skills/builtin/database-recovery/SKILL.md +138 -0
  68. mycode/skills/builtin/database-recovery/references/sqlite.md +235 -0
  69. mycode/skills/registry.py +295 -0
  70. mycode/skills/state.py +68 -0
  71. mycode/subagents/__init__.py +1 -0
  72. mycode/subagents/audit.py +212 -0
  73. mycode/subagents/concurrency.py +124 -0
  74. mycode/subagents/contracts.py +421 -0
  75. mycode/subagents/delegate.py +80 -0
  76. mycode/subagents/delegation.py +128 -0
  77. mycode/subagents/lifecycle.py +86 -0
  78. mycode/subagents/limits.py +7 -0
  79. mycode/subagents/observability.py +150 -0
  80. mycode/subagents/persistence.py +152 -0
  81. mycode/subagents/profiles.py +184 -0
  82. mycode/subagents/prompts.py +67 -0
  83. mycode/subagents/results.py +178 -0
  84. mycode/subagents/runtime.py +528 -0
  85. mycode/subagents/snapshots.py +211 -0
  86. mycode/subagents/tool_batch.py +260 -0
  87. mycode/tools/__init__.py +81 -0
  88. mycode/tools/base.py +222 -0
  89. mycode/tools/bounds.py +14 -0
  90. mycode/tools/command_executor.py +167 -0
  91. mycode/tools/command_output.py +166 -0
  92. mycode/tools/command_risk.py +596 -0
  93. mycode/tools/defaults.py +59 -0
  94. mycode/tools/edit_file.py +524 -0
  95. mycode/tools/file_mutation.py +30 -0
  96. mycode/tools/glob.py +247 -0
  97. mycode/tools/grep.py +324 -0
  98. mycode/tools/ignore.py +122 -0
  99. mycode/tools/inspect_changes.py +269 -0
  100. mycode/tools/load_skill.py +92 -0
  101. mycode/tools/memory.py +264 -0
  102. mycode/tools/path_permissions.py +78 -0
  103. mycode/tools/patterns.py +48 -0
  104. mycode/tools/permission_metadata.py +27 -0
  105. mycode/tools/process_tree.py +166 -0
  106. mycode/tools/read_file.py +242 -0
  107. mycode/tools/read_skill_resource.py +93 -0
  108. mycode/tools/registry.py +279 -0
  109. mycode/tools/run_command.py +237 -0
  110. mycode/tools/run_skill_script.py +206 -0
  111. mycode/tools/run_validation.py +107 -0
  112. mycode/tools/submit_result.py +93 -0
  113. mycode/tools/text.py +15 -0
  114. mycode/tools/validation_command.py +377 -0
  115. mycode/tools/workspace.py +33 -0
  116. mycode/tools/write_file.py +169 -0
  117. mycode_coding_agent-0.1.0.dist-info/METADATA +244 -0
  118. mycode_coding_agent-0.1.0.dist-info/RECORD +121 -0
  119. mycode_coding_agent-0.1.0.dist-info/WHEEL +4 -0
  120. mycode_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
  121. 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
+ )