symphony-code 0.0.1__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 (169) hide show
  1. coding_agent/__init__.py +20 -0
  2. coding_agent/_lazy.py +17 -0
  3. coding_agent/addons/__init__.py +35 -0
  4. coding_agent/addons/approvals.py +224 -0
  5. coding_agent/addons/compaction/__init__.py +36 -0
  6. coding_agent/addons/compaction/addon.py +88 -0
  7. coding_agent/addons/compaction/compactor.py +138 -0
  8. coding_agent/addons/compaction/prompts.py +20 -0
  9. coding_agent/addons/compaction/transcript.py +118 -0
  10. coding_agent/addons/evaluation/__init__.py +68 -0
  11. coding_agent/addons/evaluation/addon.py +199 -0
  12. coding_agent/addons/evaluation/evaluators.py +245 -0
  13. coding_agent/addons/evaluation/handlers.py +66 -0
  14. coding_agent/addons/evaluation/policy.py +277 -0
  15. coding_agent/addons/evaluation/protocol.py +192 -0
  16. coding_agent/addons/evaluation/state.py +284 -0
  17. coding_agent/addons/langfuse/__init__.py +24 -0
  18. coding_agent/addons/langfuse/addon.py +465 -0
  19. coding_agent/addons/langfuse/install.py +79 -0
  20. coding_agent/addons/langfuse/serialize.py +158 -0
  21. coding_agent/addons/learning/__init__.py +33 -0
  22. coding_agent/addons/learning/addon.py +181 -0
  23. coding_agent/addons/learning/loop.py +247 -0
  24. coding_agent/addons/learning/prompts.py +26 -0
  25. coding_agent/addons/learning/sanitize.py +64 -0
  26. coding_agent/addons/learning/store.py +522 -0
  27. coding_agent/addons/manager/__init__.py +18 -0
  28. coding_agent/addons/manager/bridge.py +74 -0
  29. coding_agent/addons/manager/discovery.py +34 -0
  30. coding_agent/addons/manager/report.py +33 -0
  31. coding_agent/addons/manager/runtime.py +76 -0
  32. coding_agent/addons/manager/tool_changes.py +16 -0
  33. coding_agent/addons/persistence/__init__.py +41 -0
  34. coding_agent/addons/persistence/active.py +132 -0
  35. coding_agent/addons/persistence/artifacts.py +110 -0
  36. coding_agent/addons/persistence/collection.py +166 -0
  37. coding_agent/addons/persistence/compatibility.py +30 -0
  38. coding_agent/addons/persistence/images.py +133 -0
  39. coding_agent/addons/persistence/jsonl.py +1195 -0
  40. coding_agent/addons/persistence/presentation.py +104 -0
  41. coding_agent/addons/plugins/__init__.py +19 -0
  42. coding_agent/addons/plugins/install.py +37 -0
  43. coding_agent/addons/plugins/manager.py +140 -0
  44. coding_agent/addons/plugins/models.py +50 -0
  45. coding_agent/addons/skills/__init__.py +28 -0
  46. coding_agent/addons/skills/addon.py +26 -0
  47. coding_agent/addons/skills/args.py +78 -0
  48. coding_agent/addons/skills/bundled_skills/addon-authoring/SKILL.md +77 -0
  49. coding_agent/addons/skills/bundled_skills/context-budget/SKILL.md +12 -0
  50. coding_agent/addons/skills/bundled_skills/orient-first/SKILL.md +16 -0
  51. coding_agent/addons/skills/bundled_skills/security-boundary/SKILL.md +16 -0
  52. coding_agent/addons/skills/bundled_skills/skill-discipline/SKILL.md +13 -0
  53. coding_agent/addons/skills/bundled_skills/surgical-edit/SKILL.md +11 -0
  54. coding_agent/addons/skills/bundled_skills/systematic-debug/SKILL.md +18 -0
  55. coding_agent/addons/skills/bundled_skills/verify-before-done/SKILL.md +11 -0
  56. coding_agent/addons/skills/frontmatter.py +57 -0
  57. coding_agent/addons/skills/models.py +57 -0
  58. coding_agent/addons/skills/registry.py +109 -0
  59. coding_agent/addons/skills/tool.py +54 -0
  60. coding_agent/addons/voice/__init__.py +5 -0
  61. coding_agent/addons/voice/addon.py +106 -0
  62. coding_agent/addons/voice/credentials.py +42 -0
  63. coding_agent/addons/voice/playback.py +42 -0
  64. coding_agent/addons/voice/speak.py +287 -0
  65. coding_agent/addons/voice/stream.py +651 -0
  66. coding_agent/addons/voice/summary.py +126 -0
  67. coding_agent/agent.py +523 -0
  68. coding_agent/bench/__init__.py +1 -0
  69. coding_agent/bench/cli.py +200 -0
  70. coding_agent/bench/patch.py +78 -0
  71. coding_agent/commands/__init__.py +5 -0
  72. coding_agent/commands/catalog.py +179 -0
  73. coding_agent/commands/jev_argument.py +40 -0
  74. coding_agent/config/__init__.py +51 -0
  75. coding_agent/config/config.py +376 -0
  76. coding_agent/config/credentials.py +153 -0
  77. coding_agent/config/personalities.json +34 -0
  78. coding_agent/config/personalities.py +170 -0
  79. coding_agent/config/prompts.py +67 -0
  80. coding_agent/protocols/__init__.py +5 -0
  81. coding_agent/protocols/acp/README.md +36 -0
  82. coding_agent/protocols/acp/__init__.py +27 -0
  83. coding_agent/protocols/acp/mapping.py +164 -0
  84. coding_agent/protocols/acp/server.py +261 -0
  85. coding_agent/protocols/acp/transport.py +72 -0
  86. coding_agent/protocols/stdio/README.md +36 -0
  87. coding_agent/protocols/stdio/__init__.py +37 -0
  88. coding_agent/protocols/stdio/catalog.py +49 -0
  89. coding_agent/protocols/stdio/commands.py +218 -0
  90. coding_agent/protocols/stdio/server.py +181 -0
  91. coding_agent/protocols/stdio/transport.py +71 -0
  92. coding_agent/py.typed +0 -0
  93. coding_agent/resources/__init__.py +5 -0
  94. coding_agent/resources/usage.py +264 -0
  95. coding_agent/run/__init__.py +1 -0
  96. coding_agent/run/cli.py +99 -0
  97. coding_agent/run/detach.py +37 -0
  98. coding_agent/run/interrupted.py +67 -0
  99. coding_agent/run/log_sink.py +46 -0
  100. coding_agent/tools/__init__.py +73 -0
  101. coding_agent/tools/ask_user.py +61 -0
  102. coding_agent/tools/base.py +136 -0
  103. coding_agent/tools/bash.py +288 -0
  104. coding_agent/tools/bash_jobs.py +116 -0
  105. coding_agent/tools/generate_image.py +123 -0
  106. coding_agent/tools/memory.py +100 -0
  107. coding_agent/tools/patch.py +218 -0
  108. coding_agent/tools/read_file.py +167 -0
  109. coding_agent/tools/search.py +433 -0
  110. coding_agent/tools/web_search.py +151 -0
  111. coding_agent/tools/write_file.py +40 -0
  112. coding_agent/tui/EFFICIENCY_PLAN.md +25 -0
  113. coding_agent/tui/__init__.py +22 -0
  114. coding_agent/tui/__main__.py +122 -0
  115. coding_agent/tui/app.py +456 -0
  116. coding_agent/tui/chrome/__init__.py +33 -0
  117. coding_agent/tui/chrome/footer.py +274 -0
  118. coding_agent/tui/chrome/topbar.py +259 -0
  119. coding_agent/tui/commands/__init__.py +45 -0
  120. coding_agent/tui/commands/catalog.py +35 -0
  121. coding_agent/tui/commands/manager.py +557 -0
  122. coding_agent/tui/composer/__init__.py +18 -0
  123. coding_agent/tui/composer/input.py +219 -0
  124. coding_agent/tui/composer/slash_menu.py +341 -0
  125. coding_agent/tui/composer/surface.py +330 -0
  126. coding_agent/tui/composer/voice.py +267 -0
  127. coding_agent/tui/composer/voice_mode.py +491 -0
  128. coding_agent/tui/runtime/__init__.py +23 -0
  129. coding_agent/tui/runtime/events.py +728 -0
  130. coding_agent/tui/runtime/sink.py +115 -0
  131. coding_agent/tui/runtime/state.py +145 -0
  132. coding_agent/tui/runtime/subagent.py +531 -0
  133. coding_agent/tui/runtime/turn.py +206 -0
  134. coding_agent/tui/screens/__init__.py +54 -0
  135. coding_agent/tui/screens/ask.py +69 -0
  136. coding_agent/tui/screens/context.py +195 -0
  137. coding_agent/tui/screens/dashboard.py +91 -0
  138. coding_agent/tui/screens/diff.py +302 -0
  139. coding_agent/tui/screens/extensions.py +68 -0
  140. coding_agent/tui/screens/file_selector.py +167 -0
  141. coding_agent/tui/screens/history.py +458 -0
  142. coding_agent/tui/screens/langfuse.py +142 -0
  143. coding_agent/tui/screens/learning.py +113 -0
  144. coding_agent/tui/screens/modal.py +131 -0
  145. coding_agent/tui/screens/onboard.py +455 -0
  146. coding_agent/tui/screens/resume.py +163 -0
  147. coding_agent/tui/theme/__init__.py +49 -0
  148. coding_agent/tui/theme/load.py +325 -0
  149. coding_agent/tui/theme/style.py +95 -0
  150. coding_agent/tui/theme/theme.toml +1687 -0
  151. coding_agent/tui/tools/__init__.py +53 -0
  152. coding_agent/tui/tools/activity.py +184 -0
  153. coding_agent/tui/tools/calls.py +560 -0
  154. coding_agent/tui/tools/code_view.py +52 -0
  155. coding_agent/tui/tools/images.py +250 -0
  156. coding_agent/tui/tools/labels.py +135 -0
  157. coding_agent/tui/tools/modal.py +137 -0
  158. coding_agent/tui/tools/partial_args.py +19 -0
  159. coding_agent/tui/tools/snapshots.py +382 -0
  160. coding_agent/tui/transcript/__init__.py +36 -0
  161. coding_agent/tui/transcript/messages.py +408 -0
  162. coding_agent/tui/transcript/process.py +504 -0
  163. coding_agent/tui/transcript/surface.py +345 -0
  164. coding_agent/tui/transcript/thinking_markdown.py +314 -0
  165. symphony_code-0.0.1.dist-info/METADATA +66 -0
  166. symphony_code-0.0.1.dist-info/RECORD +169 -0
  167. symphony_code-0.0.1.dist-info/WHEEL +4 -0
  168. symphony_code-0.0.1.dist-info/entry_points.txt +5 -0
  169. symphony_code-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,284 @@
1
+ """Capped semantic run state for the critic. Never a raw transcript or JSONL dump."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ from dataclasses import dataclass, field
8
+ from typing import Any, Iterable, Optional
9
+
10
+ from core_ai.content import text_from_content
11
+ from core_ai.types import Message
12
+
13
+ from coding_agent.config import EvaluationConfig
14
+ from coding_agent.addons.evaluation.protocol import EvaluationPhase
15
+ from coding_agent.addons.learning.sanitize import redact_secrets
16
+
17
+ WRITE_TOOLS = frozenset({"write_file", "patch"})
18
+ MAX_PLAN_ITEMS = 40
19
+ _CHECKBOX = re.compile(r"^\s*[-*]\s*\[([ xX])\]\s+(.*)$")
20
+
21
+
22
+ @dataclass
23
+ class PlanItem:
24
+ text: str
25
+ status: str
26
+
27
+
28
+ @dataclass
29
+ class RunState:
30
+ """Bounded snapshot sent to the evaluator."""
31
+
32
+ phase: EvaluationPhase
33
+ rule: str = ""
34
+ request: str = ""
35
+ brief: str = ""
36
+ plan: str = ""
37
+ plan_items: list[PlanItem] = field(default_factory=list)
38
+ observations: list[str] = field(default_factory=list)
39
+ files_modified: list[str] = field(default_factory=list)
40
+ last_tool_results: list[str] = field(default_factory=list)
41
+ final: str = ""
42
+
43
+ def as_eval_state(self) -> dict[str, Any]:
44
+ """Compact, redacted payload for the evaluation-model ``state`` field."""
45
+ return redact_eval_value(
46
+ {
47
+ "request": self.request,
48
+ "rule": self.rule,
49
+ "brief": self.brief,
50
+ "plan": self.plan,
51
+ "plan_items": [
52
+ {"text": item.text, "status": item.status} for item in self.plan_items
53
+ ],
54
+ "observations": self.observations,
55
+ "files_modified": self.files_modified,
56
+ "last_tool_results": self.last_tool_results,
57
+ "final": self.final,
58
+ }
59
+ )
60
+
61
+
62
+ def redact_field(text: str) -> str:
63
+ """Strip secrets from one outbound text field (same path as Langfuse)."""
64
+ return redact_secrets(text or "")
65
+
66
+
67
+ def redact_eval_value(value: Any) -> Any:
68
+ """Walk a state payload and redact every string leaf."""
69
+ if isinstance(value, str):
70
+ return redact_field(value)
71
+ if isinstance(value, dict):
72
+ return {str(key): redact_eval_value(item) for key, item in value.items()}
73
+ if isinstance(value, list):
74
+ return [redact_eval_value(item) for item in value]
75
+ if isinstance(value, tuple):
76
+ return [redact_eval_value(item) for item in value]
77
+ return value
78
+
79
+
80
+ def clip_text(text: str, limit: int) -> str:
81
+ value = text or ""
82
+ if limit <= 0 or len(value) <= limit:
83
+ return value
84
+ if limit == 1:
85
+ return "…"
86
+ return value[: limit - 1] + "…"
87
+
88
+
89
+ def bound_text(text: str, limit: int) -> str:
90
+ """Redact secrets, then apply the field's character cap."""
91
+ return clip_text(redact_field(text), limit)
92
+
93
+
94
+ def bound_lines(lines: Iterable[str], max_chars: int) -> list[str]:
95
+ return clip_lines((redact_field(line) for line in lines), max_chars)
96
+
97
+
98
+ def clip_lines(lines: Iterable[str], max_chars: int) -> list[str]:
99
+ out: list[str] = []
100
+ used = 0
101
+ for line in lines:
102
+ remaining = max_chars - used
103
+ if remaining <= 0:
104
+ break
105
+ clipped = clip_text(line, remaining)
106
+ if not clipped:
107
+ continue
108
+ out.append(clipped)
109
+ used += len(clipped)
110
+ return out
111
+
112
+
113
+ def first_line(text: str) -> str:
114
+ for line in (text or "").splitlines():
115
+ stripped = line.strip()
116
+ if stripped:
117
+ return stripped
118
+ return (text or "").strip()
119
+
120
+
121
+ def plan_items_from_markdown(text: str) -> list[PlanItem]:
122
+ items: list[PlanItem] = []
123
+ for line in (text or "").splitlines():
124
+ match = _CHECKBOX.match(line)
125
+ if match is None:
126
+ continue
127
+ status = "done" if match.group(1).strip() else "pending"
128
+ items.append(PlanItem(text=bound_text(match.group(2).strip(), 200), status=status))
129
+ return items
130
+
131
+
132
+ def request_from_messages(messages: Iterable[Message]) -> str:
133
+ for message in reversed(list(messages)):
134
+ if isinstance(message, Message) and message.role == "user":
135
+ return text_from_content(message.content).strip()
136
+ return ""
137
+
138
+
139
+ def _tool_name(call: object) -> str:
140
+ if isinstance(call, dict):
141
+ function = call.get("function")
142
+ if isinstance(function, dict):
143
+ return str(function.get("name") or "").strip()
144
+ return str(call.get("name") or "").strip()
145
+ return str(getattr(call, "name", "") or "").strip()
146
+
147
+
148
+ def _tool_args(call: object) -> dict[str, Any]:
149
+ raw: Any = None
150
+ if isinstance(call, dict):
151
+ function = call.get("function")
152
+ if isinstance(function, dict):
153
+ raw = function.get("arguments")
154
+ elif "arguments" in call:
155
+ raw = call.get("arguments")
156
+ else:
157
+ raw = getattr(call, "arguments", None)
158
+ if isinstance(raw, dict):
159
+ return raw
160
+ if isinstance(raw, str) and raw.strip():
161
+ try:
162
+ parsed = json.loads(raw)
163
+ except ValueError:
164
+ return {}
165
+ return parsed if isinstance(parsed, dict) else {}
166
+ return {}
167
+
168
+
169
+ def files_modified_from_calls(calls: Iterable[object], *, limit: int) -> list[str]:
170
+ paths: list[str] = []
171
+ seen: set[str] = set()
172
+ for call in calls:
173
+ if _tool_name(call) not in WRITE_TOOLS:
174
+ continue
175
+ path = str(_tool_args(call).get("path") or "").strip()
176
+ if not path or path in seen:
177
+ continue
178
+ seen.add(path)
179
+ paths.append(path)
180
+ if len(paths) >= limit:
181
+ break
182
+ return paths
183
+
184
+
185
+ def files_modified_from_messages(messages: Iterable[Message], *, limit: int) -> list[str]:
186
+ calls: list[object] = []
187
+ for message in messages:
188
+ if isinstance(message, Message) and message.tool_calls:
189
+ calls.extend(message.tool_calls)
190
+ return files_modified_from_calls(calls, limit=limit)
191
+
192
+
193
+ def observations_from_messages(messages: Iterable[Message]) -> list[str]:
194
+ lines: list[str] = []
195
+ pending: dict[str, str] = {}
196
+ for message in messages:
197
+ if not isinstance(message, Message):
198
+ continue
199
+ if message.role == "assistant" and message.tool_calls:
200
+ for call in message.tool_calls:
201
+ name = _tool_name(call) or "tool"
202
+ args = _tool_args(call)
203
+ detail = str(args.get("path") or args.get("command") or "").strip()
204
+ summary = f"{name} {detail}".strip()
205
+ call_id = ""
206
+ if isinstance(call, dict):
207
+ call_id = str(call.get("id") or "")
208
+ lines.append(summary)
209
+ if call_id:
210
+ pending[call_id] = summary
211
+ continue
212
+ if message.role == "tool":
213
+ body = text_from_content(message.content).strip()
214
+ failed = body.startswith("[tool:")
215
+ status = "error" if failed else "ok"
216
+ key = message.tool_call_id or ""
217
+ prefix = pending.get(key) or "tool"
218
+ lines.append(f"{prefix} → {status}")
219
+ return lines
220
+
221
+
222
+ def last_tool_results_from_messages(messages: Iterable[Message], *, max_items: int = 5) -> list[str]:
223
+ results: list[str] = []
224
+ for message in messages:
225
+ if isinstance(message, Message) and message.role == "tool":
226
+ results.append(text_from_content(message.content).strip())
227
+ return results[-max_items:]
228
+
229
+
230
+ def build_run_state(
231
+ *,
232
+ config: EvaluationConfig,
233
+ phase: EvaluationPhase,
234
+ request: str = "",
235
+ messages: Optional[Iterable[Message]] = None,
236
+ plan_text: str = "",
237
+ final: str = "",
238
+ tool_calls: Optional[Iterable[object]] = None,
239
+ ) -> RunState:
240
+ """Assemble a capped, secret-redacted semantic snapshot."""
241
+ message_list = list(messages or [])
242
+ request_text = request.strip() or request_from_messages(message_list)
243
+ items = plan_items_from_markdown(plan_text)
244
+ files = files_modified_from_calls(tool_calls or (), limit=config.files_modified_max)
245
+ if not files:
246
+ files = files_modified_from_messages(message_list, limit=config.files_modified_max)
247
+ return RunState(
248
+ phase=phase,
249
+ rule=bound_text(config.rule, config.request_max_chars),
250
+ request=bound_text(request_text, config.request_max_chars),
251
+ brief=bound_text(first_line(request_text), config.brief_max_chars),
252
+ plan=bound_text(plan_text, config.plan_max_chars),
253
+ plan_items=items[:MAX_PLAN_ITEMS],
254
+ observations=bound_lines(
255
+ observations_from_messages(message_list), config.observations_max_chars
256
+ ),
257
+ files_modified=[redact_field(path) for path in files],
258
+ last_tool_results=bound_lines(
259
+ last_tool_results_from_messages(message_list),
260
+ config.last_tool_results_max_chars,
261
+ ),
262
+ final=bound_text(final, config.final_max_chars),
263
+ )
264
+
265
+
266
+ __all__ = [
267
+ "PlanItem",
268
+ "RunState",
269
+ "WRITE_TOOLS",
270
+ "bound_lines",
271
+ "bound_text",
272
+ "build_run_state",
273
+ "clip_lines",
274
+ "clip_text",
275
+ "redact_eval_value",
276
+ "redact_field",
277
+ "files_modified_from_calls",
278
+ "files_modified_from_messages",
279
+ "first_line",
280
+ "last_tool_results_from_messages",
281
+ "observations_from_messages",
282
+ "plan_items_from_markdown",
283
+ "request_from_messages",
284
+ ]
@@ -0,0 +1,24 @@
1
+ """Optional Langfuse telemetry for coding-agent runs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from coding_agent._lazy import resolve
8
+ from coding_agent.addons.langfuse.install import ensure_langfuse_installed, langfuse_available
9
+
10
+ _ADDON_EXPORTS = {
11
+ "LangfuseAddon": ("coding_agent.addons.langfuse.addon", "LangfuseAddon"),
12
+ "langfuse_from_config": ("coding_agent.addons.langfuse.addon", "langfuse_from_config"),
13
+ }
14
+
15
+ __all__ = [
16
+ "LangfuseAddon",
17
+ "ensure_langfuse_installed",
18
+ "langfuse_available",
19
+ "langfuse_from_config",
20
+ ]
21
+
22
+
23
+ def __getattr__(name: str) -> Any:
24
+ return resolve(globals(), name, _ADDON_EXPORTS)