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.
- coding_agent/__init__.py +20 -0
- coding_agent/_lazy.py +17 -0
- coding_agent/addons/__init__.py +35 -0
- coding_agent/addons/approvals.py +224 -0
- coding_agent/addons/compaction/__init__.py +36 -0
- coding_agent/addons/compaction/addon.py +88 -0
- coding_agent/addons/compaction/compactor.py +138 -0
- coding_agent/addons/compaction/prompts.py +20 -0
- coding_agent/addons/compaction/transcript.py +118 -0
- coding_agent/addons/evaluation/__init__.py +68 -0
- coding_agent/addons/evaluation/addon.py +199 -0
- coding_agent/addons/evaluation/evaluators.py +245 -0
- coding_agent/addons/evaluation/handlers.py +66 -0
- coding_agent/addons/evaluation/policy.py +277 -0
- coding_agent/addons/evaluation/protocol.py +192 -0
- coding_agent/addons/evaluation/state.py +284 -0
- coding_agent/addons/langfuse/__init__.py +24 -0
- coding_agent/addons/langfuse/addon.py +465 -0
- coding_agent/addons/langfuse/install.py +79 -0
- coding_agent/addons/langfuse/serialize.py +158 -0
- coding_agent/addons/learning/__init__.py +33 -0
- coding_agent/addons/learning/addon.py +181 -0
- coding_agent/addons/learning/loop.py +247 -0
- coding_agent/addons/learning/prompts.py +26 -0
- coding_agent/addons/learning/sanitize.py +64 -0
- coding_agent/addons/learning/store.py +522 -0
- coding_agent/addons/manager/__init__.py +18 -0
- coding_agent/addons/manager/bridge.py +74 -0
- coding_agent/addons/manager/discovery.py +34 -0
- coding_agent/addons/manager/report.py +33 -0
- coding_agent/addons/manager/runtime.py +76 -0
- coding_agent/addons/manager/tool_changes.py +16 -0
- coding_agent/addons/persistence/__init__.py +41 -0
- coding_agent/addons/persistence/active.py +132 -0
- coding_agent/addons/persistence/artifacts.py +110 -0
- coding_agent/addons/persistence/collection.py +166 -0
- coding_agent/addons/persistence/compatibility.py +30 -0
- coding_agent/addons/persistence/images.py +133 -0
- coding_agent/addons/persistence/jsonl.py +1195 -0
- coding_agent/addons/persistence/presentation.py +104 -0
- coding_agent/addons/plugins/__init__.py +19 -0
- coding_agent/addons/plugins/install.py +37 -0
- coding_agent/addons/plugins/manager.py +140 -0
- coding_agent/addons/plugins/models.py +50 -0
- coding_agent/addons/skills/__init__.py +28 -0
- coding_agent/addons/skills/addon.py +26 -0
- coding_agent/addons/skills/args.py +78 -0
- coding_agent/addons/skills/bundled_skills/addon-authoring/SKILL.md +77 -0
- coding_agent/addons/skills/bundled_skills/context-budget/SKILL.md +12 -0
- coding_agent/addons/skills/bundled_skills/orient-first/SKILL.md +16 -0
- coding_agent/addons/skills/bundled_skills/security-boundary/SKILL.md +16 -0
- coding_agent/addons/skills/bundled_skills/skill-discipline/SKILL.md +13 -0
- coding_agent/addons/skills/bundled_skills/surgical-edit/SKILL.md +11 -0
- coding_agent/addons/skills/bundled_skills/systematic-debug/SKILL.md +18 -0
- coding_agent/addons/skills/bundled_skills/verify-before-done/SKILL.md +11 -0
- coding_agent/addons/skills/frontmatter.py +57 -0
- coding_agent/addons/skills/models.py +57 -0
- coding_agent/addons/skills/registry.py +109 -0
- coding_agent/addons/skills/tool.py +54 -0
- coding_agent/addons/voice/__init__.py +5 -0
- coding_agent/addons/voice/addon.py +106 -0
- coding_agent/addons/voice/credentials.py +42 -0
- coding_agent/addons/voice/playback.py +42 -0
- coding_agent/addons/voice/speak.py +287 -0
- coding_agent/addons/voice/stream.py +651 -0
- coding_agent/addons/voice/summary.py +126 -0
- coding_agent/agent.py +523 -0
- coding_agent/bench/__init__.py +1 -0
- coding_agent/bench/cli.py +200 -0
- coding_agent/bench/patch.py +78 -0
- coding_agent/commands/__init__.py +5 -0
- coding_agent/commands/catalog.py +179 -0
- coding_agent/commands/jev_argument.py +40 -0
- coding_agent/config/__init__.py +51 -0
- coding_agent/config/config.py +376 -0
- coding_agent/config/credentials.py +153 -0
- coding_agent/config/personalities.json +34 -0
- coding_agent/config/personalities.py +170 -0
- coding_agent/config/prompts.py +67 -0
- coding_agent/protocols/__init__.py +5 -0
- coding_agent/protocols/acp/README.md +36 -0
- coding_agent/protocols/acp/__init__.py +27 -0
- coding_agent/protocols/acp/mapping.py +164 -0
- coding_agent/protocols/acp/server.py +261 -0
- coding_agent/protocols/acp/transport.py +72 -0
- coding_agent/protocols/stdio/README.md +36 -0
- coding_agent/protocols/stdio/__init__.py +37 -0
- coding_agent/protocols/stdio/catalog.py +49 -0
- coding_agent/protocols/stdio/commands.py +218 -0
- coding_agent/protocols/stdio/server.py +181 -0
- coding_agent/protocols/stdio/transport.py +71 -0
- coding_agent/py.typed +0 -0
- coding_agent/resources/__init__.py +5 -0
- coding_agent/resources/usage.py +264 -0
- coding_agent/run/__init__.py +1 -0
- coding_agent/run/cli.py +99 -0
- coding_agent/run/detach.py +37 -0
- coding_agent/run/interrupted.py +67 -0
- coding_agent/run/log_sink.py +46 -0
- coding_agent/tools/__init__.py +73 -0
- coding_agent/tools/ask_user.py +61 -0
- coding_agent/tools/base.py +136 -0
- coding_agent/tools/bash.py +288 -0
- coding_agent/tools/bash_jobs.py +116 -0
- coding_agent/tools/generate_image.py +123 -0
- coding_agent/tools/memory.py +100 -0
- coding_agent/tools/patch.py +218 -0
- coding_agent/tools/read_file.py +167 -0
- coding_agent/tools/search.py +433 -0
- coding_agent/tools/web_search.py +151 -0
- coding_agent/tools/write_file.py +40 -0
- coding_agent/tui/EFFICIENCY_PLAN.md +25 -0
- coding_agent/tui/__init__.py +22 -0
- coding_agent/tui/__main__.py +122 -0
- coding_agent/tui/app.py +456 -0
- coding_agent/tui/chrome/__init__.py +33 -0
- coding_agent/tui/chrome/footer.py +274 -0
- coding_agent/tui/chrome/topbar.py +259 -0
- coding_agent/tui/commands/__init__.py +45 -0
- coding_agent/tui/commands/catalog.py +35 -0
- coding_agent/tui/commands/manager.py +557 -0
- coding_agent/tui/composer/__init__.py +18 -0
- coding_agent/tui/composer/input.py +219 -0
- coding_agent/tui/composer/slash_menu.py +341 -0
- coding_agent/tui/composer/surface.py +330 -0
- coding_agent/tui/composer/voice.py +267 -0
- coding_agent/tui/composer/voice_mode.py +491 -0
- coding_agent/tui/runtime/__init__.py +23 -0
- coding_agent/tui/runtime/events.py +728 -0
- coding_agent/tui/runtime/sink.py +115 -0
- coding_agent/tui/runtime/state.py +145 -0
- coding_agent/tui/runtime/subagent.py +531 -0
- coding_agent/tui/runtime/turn.py +206 -0
- coding_agent/tui/screens/__init__.py +54 -0
- coding_agent/tui/screens/ask.py +69 -0
- coding_agent/tui/screens/context.py +195 -0
- coding_agent/tui/screens/dashboard.py +91 -0
- coding_agent/tui/screens/diff.py +302 -0
- coding_agent/tui/screens/extensions.py +68 -0
- coding_agent/tui/screens/file_selector.py +167 -0
- coding_agent/tui/screens/history.py +458 -0
- coding_agent/tui/screens/langfuse.py +142 -0
- coding_agent/tui/screens/learning.py +113 -0
- coding_agent/tui/screens/modal.py +131 -0
- coding_agent/tui/screens/onboard.py +455 -0
- coding_agent/tui/screens/resume.py +163 -0
- coding_agent/tui/theme/__init__.py +49 -0
- coding_agent/tui/theme/load.py +325 -0
- coding_agent/tui/theme/style.py +95 -0
- coding_agent/tui/theme/theme.toml +1687 -0
- coding_agent/tui/tools/__init__.py +53 -0
- coding_agent/tui/tools/activity.py +184 -0
- coding_agent/tui/tools/calls.py +560 -0
- coding_agent/tui/tools/code_view.py +52 -0
- coding_agent/tui/tools/images.py +250 -0
- coding_agent/tui/tools/labels.py +135 -0
- coding_agent/tui/tools/modal.py +137 -0
- coding_agent/tui/tools/partial_args.py +19 -0
- coding_agent/tui/tools/snapshots.py +382 -0
- coding_agent/tui/transcript/__init__.py +36 -0
- coding_agent/tui/transcript/messages.py +408 -0
- coding_agent/tui/transcript/process.py +504 -0
- coding_agent/tui/transcript/surface.py +345 -0
- coding_agent/tui/transcript/thinking_markdown.py +314 -0
- symphony_code-0.0.1.dist-info/METADATA +66 -0
- symphony_code-0.0.1.dist-info/RECORD +169 -0
- symphony_code-0.0.1.dist-info/WHEEL +4 -0
- symphony_code-0.0.1.dist-info/entry_points.txt +5 -0
- 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)
|