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,115 @@
1
+ """Textual sink for harness events.
2
+
3
+ This is not a second control-plane product. It implements the harness
4
+ ``EventSink`` so the UI can observe the same events ``CoreHarness``
5
+ emits. Cancel is a TUI action: it dismisses questions and cancels the
6
+ run worker. The harness does not poll this object for cancel.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+ from typing import Any, Dict, Optional, Union
13
+
14
+ from textual.message import Message
15
+
16
+ from core_harness import EventSink, ControlPlaneEventType, HumanInteraction
17
+
18
+
19
+ class HarnessEvent(Message):
20
+ """Posted onto the Textual app when the harness emits an event."""
21
+
22
+ def __init__(
23
+ self,
24
+ event_type: Union[str, ControlPlaneEventType],
25
+ payload: Dict[str, Any],
26
+ ) -> None:
27
+ super().__init__()
28
+ if isinstance(event_type, ControlPlaneEventType):
29
+ self.event_type = event_type.value
30
+ else:
31
+ self.event_type = event_type
32
+ self.payload = payload
33
+
34
+
35
+ # Back-compat alias used by older tests / imports.
36
+ ControlPlaneEvent = HarnessEvent
37
+
38
+
39
+ class TextualInteraction(HumanInteraction):
40
+ """The TUI's question slot. An unbound sink has nobody to ask."""
41
+
42
+ sink: "TextualEventSink"
43
+
44
+ async def request_user_input(self, *, default: str = "", **question: Any) -> str:
45
+ if self.sink._app is None:
46
+ return default
47
+ return await super().request_user_input(default=default, **question)
48
+
49
+
50
+ class TextualEventSink(EventSink):
51
+ """TUI adapter: post events to the app and track the TUI's cancel state."""
52
+
53
+ interaction: TextualInteraction
54
+
55
+ def __init__(self, *, workspace: str | Path = ".") -> None:
56
+ super().__init__()
57
+ self._app: Any = None
58
+ self.workspace = Path(workspace).resolve()
59
+ self.interaction = TextualInteraction(self)
60
+ self._cancelled = False
61
+ self._cancel_reason = "cancelled"
62
+ self._cancel_parent: Optional[TextualEventSink] = None
63
+
64
+ def bind(self, app: Any) -> None:
65
+ self._app = app
66
+
67
+ def fork(self) -> "TextualEventSink":
68
+ """Child plane with the shared composer.
69
+
70
+ The TUI has one pending-question slot and answers on the parent plane,
71
+ so the child's interaction port is a fork of this one.
72
+ """
73
+ child = TextualEventSink(workspace=self.workspace)
74
+ child.bind(self._app)
75
+ child._cancel_parent = self
76
+ child.interaction = self.interaction.fork(child)
77
+ return child
78
+
79
+ async def deliver(self, event_type: str, payload: Dict[str, Any]) -> None:
80
+ if self._app is None:
81
+ return
82
+ # Register the waiter before posting the message. The Textual event
83
+ # may be handled immediately, while AskUserTool only calls
84
+ # ``ask_user`` after ``emit`` returns.
85
+ if event_type == "question_asked":
86
+ request_id = str(payload.get("request_id") or "")
87
+ if request_id:
88
+ self.interaction.expect(request_id)
89
+ self._app.post_message(HarnessEvent(event_type, payload))
90
+
91
+ def request_cancel(self, reason: str = "user_cancel") -> None:
92
+ """Dismiss pending questions. The TUI cancels the run worker."""
93
+ self._cancelled = True
94
+ self._cancel_reason = reason
95
+ self.interaction.dismiss("Deny")
96
+
97
+ @property
98
+ def cancelled(self) -> bool:
99
+ if self._cancelled:
100
+ return True
101
+ parent = self._cancel_parent
102
+ return bool(parent is not None and parent.cancelled)
103
+
104
+ @property
105
+ def cancel_reason(self) -> str:
106
+ if self._cancelled:
107
+ return self._cancel_reason
108
+ parent = self._cancel_parent
109
+ if parent is not None and parent.cancelled:
110
+ return parent.cancel_reason
111
+ return self._cancel_reason
112
+
113
+ def reset_cancel(self) -> None:
114
+ self._cancelled = False
115
+ self._cancel_reason = "cancelled"
@@ -0,0 +1,145 @@
1
+ """Mutable TUI run state derived from harness control-plane events."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Literal, Mapping, Optional
7
+
8
+ Phase = Literal["idle", "thinking", "streaming", "tool", "paused"]
9
+
10
+
11
+ @dataclass
12
+ class RunMetrics:
13
+ prompt_tokens: int = 0
14
+ completion_tokens: int = 0
15
+ reasoning_tokens: int = 0
16
+ total_tokens: int = 0
17
+ cumulative_tokens: int = 0
18
+ estimated: bool = False
19
+ context_limit: Optional[int] = None
20
+ context_left: Optional[int] = None
21
+ tokens_used: int = 0
22
+ utilization: Optional[float] = None
23
+
24
+
25
+ @dataclass
26
+ class UiRunState:
27
+ """Tracks live streaming + metrics for the status/live panes."""
28
+
29
+ phase: Phase = "idle"
30
+ model_id: str = ""
31
+ turn: Optional[int] = None
32
+ stream_text: str = ""
33
+ reasoning_text: str = ""
34
+ tool_args_preview: str = ""
35
+ metrics: RunMetrics = field(default_factory=RunMetrics)
36
+ detail: str = ""
37
+
38
+ def reset_for_run(self, *, model_id: str = "") -> None:
39
+ # Context usage describes the active session, not just the next turn.
40
+ # Preserve it while clearing per-run token accounting so the footer
41
+ # does not briefly fall back to an empty/unknown state.
42
+ context = self.metrics
43
+ self.phase = "thinking"
44
+ self.model_id = model_id or self.model_id
45
+ self.turn = None
46
+ self.stream_text = ""
47
+ self.reasoning_text = ""
48
+ self.tool_args_preview = ""
49
+ self.metrics = RunMetrics(
50
+ context_limit=context.context_limit,
51
+ context_left=context.context_left,
52
+ tokens_used=context.tokens_used,
53
+ utilization=context.utilization,
54
+ )
55
+ self.detail = "starting"
56
+
57
+ def begin_turn(self, turn: int) -> None:
58
+ self.turn = turn
59
+ self.phase = "thinking"
60
+ self.stream_text = ""
61
+ self.reasoning_text = ""
62
+ self.tool_args_preview = ""
63
+ self.detail = f"turn {turn}"
64
+
65
+ def append_text(self, delta: str) -> None:
66
+ self.phase = "streaming"
67
+ self.stream_text += delta
68
+ self.detail = "streaming"
69
+
70
+ def update_usage(self, payload: Mapping[str, object]) -> None:
71
+ m = self.metrics
72
+ m.prompt_tokens = int(payload.get("prompt_tokens") or 0)
73
+ m.completion_tokens = int(payload.get("completion_tokens") or 0)
74
+ m.reasoning_tokens = int(payload.get("reasoning_tokens") or 0)
75
+ m.total_tokens = int(payload.get("total_tokens") or 0)
76
+ m.cumulative_tokens = int(payload.get("cumulative_tokens") or m.total_tokens)
77
+ m.estimated = bool(payload.get("estimated", False))
78
+
79
+ def set_context_limit(self, limit: Optional[int]) -> None:
80
+ """Apply a model context limit and recalculate all derived metrics."""
81
+ m = self.metrics
82
+ m.context_limit = limit
83
+ if limit is None or limit <= 0:
84
+ m.context_left = None
85
+ m.utilization = None
86
+ return
87
+ m.context_left = max(limit - m.tokens_used, 0)
88
+ m.utilization = min(m.tokens_used / limit, 1.0)
89
+
90
+ def update_context(self, payload: Mapping[str, object]) -> None:
91
+ m = self.metrics
92
+ limit = payload.get("context_limit")
93
+ left = payload.get("context_left")
94
+ used = payload.get("tokens_used")
95
+ m.context_limit = int(limit) if limit is not None else None
96
+ m.context_left = int(left) if left is not None else None
97
+ m.tokens_used = int(used) if used is not None else m.tokens_used
98
+ util = payload.get("utilization")
99
+ m.utilization = float(util) if util is not None else None
100
+
101
+ def update_after_compaction(self, payload: Mapping[str, object]) -> bool:
102
+ """Re-derive context metrics from a ``compaction_completed`` payload.
103
+
104
+ The harness reports the post-compact prompt size as an estimate; the
105
+ footer should reflect it immediately instead of waiting for the next
106
+ ``context`` event. Returns False when the payload carries no estimate.
107
+ """
108
+ after = payload.get("estimated_tokens_after")
109
+ if after is None:
110
+ return False
111
+ m = self.metrics
112
+ limit = payload.get("context_limit")
113
+ if limit is not None:
114
+ m.context_limit = int(limit)
115
+ m.tokens_used = int(after)
116
+ if m.context_limit:
117
+ m.context_left = max(m.context_limit - m.tokens_used, 0)
118
+ m.utilization = min(m.tokens_used / m.context_limit, 1.0)
119
+ else:
120
+ m.context_left = None
121
+ m.utilization = None
122
+ return True
123
+
124
+ def status_line(self, *, workspace: str = "") -> str:
125
+ m = self.metrics
126
+ parts: list[str] = []
127
+ if self.model_id:
128
+ parts.append(f"model={self.model_id}")
129
+ if workspace:
130
+ parts.append(f"ws={workspace}")
131
+ parts.append(f"phase={self.phase}")
132
+ if self.detail:
133
+ parts.append(self.detail)
134
+
135
+ tokens = m.tokens_used or m.total_tokens
136
+ est = "~" if m.estimated and tokens else ""
137
+ parts.append(f"tokens={est}{tokens}")
138
+
139
+ if m.context_limit is not None:
140
+ left = m.context_left if m.context_left is not None else "?"
141
+ parts.append(f"context_left={left}/{m.context_limit}")
142
+ elif m.context_left is not None:
143
+ parts.append(f"context_left={m.context_left}")
144
+
145
+ return " · ".join(parts)