stcode 0.0.1a0__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.
- stcode/__init__.py +10 -0
- stcode/cli/__init__.py +9 -0
- stcode/cli/bootstrap.py +70 -0
- stcode/cli/labels.py +621 -0
- stcode/cli/logic/__init__.py +0 -0
- stcode/cli/logic/commands.py +36 -0
- stcode/cli/logic/completion.py +43 -0
- stcode/cli/logic/connection.py +46 -0
- stcode/cli/logic/requests.py +18 -0
- stcode/cli/logic/sessions.py +67 -0
- stcode/cli/logic/settings.py +35 -0
- stcode/cli/logic/state.py +43 -0
- stcode/cli/logic/workspace.py +17 -0
- stcode/cli/main.py +290 -0
- stcode/cli/models.py +61 -0
- stcode/cli/services/__init__.py +0 -0
- stcode/cli/services/client.py +393 -0
- stcode/cli/services/launcher.py +159 -0
- stcode/cli/services/paths.py +18 -0
- stcode/cli/services/preferences.py +107 -0
- stcode/cli/ui/__init__.py +0 -0
- stcode/cli/ui/app.py +136 -0
- stcode/cli/ui/app.tcss +3 -0
- stcode/cli/ui/components/__init__.py +0 -0
- stcode/cli/ui/components/banner.py +78 -0
- stcode/cli/ui/components/cards.py +229 -0
- stcode/cli/ui/components/prompt.py +225 -0
- stcode/cli/ui/components/status.py +95 -0
- stcode/cli/ui/components/transcript/__init__.py +51 -0
- stcode/cli/ui/components/transcript/entries.py +232 -0
- stcode/cli/ui/components/transcript/formatting.py +188 -0
- stcode/cli/ui/components/transcript/view.py +101 -0
- stcode/cli/ui/screens/__init__.py +0 -0
- stcode/cli/ui/screens/chat/__init__.py +0 -0
- stcode/cli/ui/screens/chat/chat.tcss +12 -0
- stcode/cli/ui/screens/chat/presenter.py +187 -0
- stcode/cli/ui/screens/chat/screen.py +682 -0
- stcode/cli/ui/screens/connect.py +126 -0
- stcode/cli/ui/screens/connect.tcss +49 -0
- stcode/cli/ui/screens/dialog.tcss +40 -0
- stcode/cli/ui/screens/sessions.py +105 -0
- stcode/cli/ui/screens/settings.py +137 -0
- stcode/cli/ui/screens/settings.tcss +69 -0
- stcode/cli/ui/screens/trust.py +50 -0
- stcode/cli/ui/theme.py +221 -0
- stcode/core/__main__.py +7 -0
- stcode/core/agent/__init__.py +41 -0
- stcode/core/agent/agent.py +651 -0
- stcode/core/agent/events.py +97 -0
- stcode/core/agent/supervisor.py +226 -0
- stcode/core/agent/task.py +120 -0
- stcode/core/common/__init__.py +21 -0
- stcode/core/common/compat.py +27 -0
- stcode/core/common/ids.py +43 -0
- stcode/core/common/tools.py +50 -0
- stcode/core/common/trace.py +406 -0
- stcode/core/common/truncate.py +76 -0
- stcode/core/configs.py +490 -0
- stcode/core/daemon/__init__.py +32 -0
- stcode/core/daemon/autonomy.py +71 -0
- stcode/core/daemon/main.py +126 -0
- stcode/core/daemon/protocol.py +499 -0
- stcode/core/daemon/runner.py +359 -0
- stcode/core/daemon/server.py +682 -0
- stcode/core/daemon/workspace.py +88 -0
- stcode/core/harness/__init__.py +81 -0
- stcode/core/harness/approvals.py +85 -0
- stcode/core/harness/context.py +260 -0
- stcode/core/harness/errors.py +40 -0
- stcode/core/harness/harness.py +417 -0
- stcode/core/harness/mcp.py +530 -0
- stcode/core/harness/outputs.py +99 -0
- stcode/core/harness/prompts/__init__.py +138 -0
- stcode/core/harness/prompts/execute.py +51 -0
- stcode/core/harness/prompts/plan.py +57 -0
- stcode/core/harness/prompts/sections.py +238 -0
- stcode/core/harness/skills/__init__.py +30 -0
- stcode/core/harness/skills/loader.py +247 -0
- stcode/core/harness/tools/__init__.py +111 -0
- stcode/core/harness/tools/base.py +640 -0
- stcode/core/harness/tools/files.py +276 -0
- stcode/core/harness/tools/interact.py +72 -0
- stcode/core/harness/tools/registry.py +113 -0
- stcode/core/harness/tools/repl.py +97 -0
- stcode/core/harness/tools/schema.py +186 -0
- stcode/core/harness/tools/search.py +356 -0
- stcode/core/harness/tools/shell.py +372 -0
- stcode/core/harness/tools/skill.py +66 -0
- stcode/core/harness/tools/todo.py +62 -0
- stcode/core/harness/tools/web.py +163 -0
- stcode/core/providers/__init__.py +31 -0
- stcode/core/providers/anthropic_claude.py +243 -0
- stcode/core/providers/base.py +73 -0
- stcode/core/providers/gateway.py +267 -0
- stcode/core/providers/google_gemini.py +225 -0
- stcode/core/providers/openai_gpt.py +250 -0
- stcode/core/providers/registry.py +46 -0
- stcode/core/providers/types.py +148 -0
- stcode/core/repl/__init__.py +19 -0
- stcode/core/repl/_worker.py +179 -0
- stcode/core/repl/client.py +374 -0
- stcode/core/session/__init__.py +39 -0
- stcode/core/session/session.py +493 -0
- stcode/core/team/__init__.py +38 -0
- stcode/core/team/mailbox.py +249 -0
- stcode/core/team/tools.py +145 -0
- stcode-0.0.1a0.dist-info/METADATA +229 -0
- stcode-0.0.1a0.dist-info/RECORD +111 -0
- stcode-0.0.1a0.dist-info/WHEEL +4 -0
- stcode-0.0.1a0.dist-info/entry_points.txt +3 -0
- stcode-0.0.1a0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Incremental transcript presentation; owns every handle into rendered entries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import contextlib
|
|
5
|
+
from typing import Any
|
|
6
|
+
from stcode.cli import labels
|
|
7
|
+
from stcode.cli.ui.components.banner import Banner
|
|
8
|
+
from stcode.cli.ui.components.transcript import (
|
|
9
|
+
Message,
|
|
10
|
+
Thinking,
|
|
11
|
+
Progress,
|
|
12
|
+
ToolCall,
|
|
13
|
+
Notice,
|
|
14
|
+
PlatformNote,
|
|
15
|
+
Transcript,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
STREAM_KINDS = ("assistant", "thinking", "progress")
|
|
19
|
+
_OPEN_STREAM = {
|
|
20
|
+
"assistant": lambda: Message("assistant"),
|
|
21
|
+
"thinking": Thinking,
|
|
22
|
+
"progress": Progress,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ChatPresenter:
|
|
27
|
+
def __init__(self, transcript: Transcript, banner: Banner) -> None:
|
|
28
|
+
self.transcript = transcript
|
|
29
|
+
self.banner = banner
|
|
30
|
+
self.streams: dict[tuple[str, str], Message | Thinking | Progress] = {}
|
|
31
|
+
self.tools: dict[tuple[str, str], ToolCall] = {}
|
|
32
|
+
|
|
33
|
+
def clear(self) -> None:
|
|
34
|
+
self.transcript.clear()
|
|
35
|
+
self.streams.clear()
|
|
36
|
+
self.tools.clear()
|
|
37
|
+
self.settle()
|
|
38
|
+
|
|
39
|
+
def render(self, frame: dict[str, Any]) -> None:
|
|
40
|
+
agent = str(frame.get("agent", ""))
|
|
41
|
+
match frame.get("type"):
|
|
42
|
+
case "text_delta":
|
|
43
|
+
self.stream_into("assistant", str(frame.get("text", "")), agent)
|
|
44
|
+
case "reasoning_delta":
|
|
45
|
+
self.stream_into("thinking", str(frame.get("text", "")), agent)
|
|
46
|
+
case "tool_started":
|
|
47
|
+
self.tool_started(frame, agent)
|
|
48
|
+
case "tool_finished":
|
|
49
|
+
self.tool_finished(frame, agent)
|
|
50
|
+
case "turn_finished":
|
|
51
|
+
self.end_streams(agent)
|
|
52
|
+
if not agent:
|
|
53
|
+
self.note(labels.turn_usage(dict(frame.get("usage", {}))))
|
|
54
|
+
case "agent_failed":
|
|
55
|
+
self.end_streams(agent)
|
|
56
|
+
self.error(str(frame.get("message", "")), agent=agent)
|
|
57
|
+
case "supervisor":
|
|
58
|
+
self.end_streams(agent)
|
|
59
|
+
self.note(labels.supervisor_nudge(str(frame.get("text", ""))))
|
|
60
|
+
case "progress":
|
|
61
|
+
self.stream_into("progress", str(frame.get("text", "")), agent)
|
|
62
|
+
case "history":
|
|
63
|
+
self.replay(frame)
|
|
64
|
+
case "error":
|
|
65
|
+
if frame.get("level") == "warning":
|
|
66
|
+
self.warn(str(frame.get("message", "")))
|
|
67
|
+
else:
|
|
68
|
+
self.error(str(frame.get("message", "")))
|
|
69
|
+
|
|
70
|
+
def replay(self, frame: dict[str, Any]) -> None:
|
|
71
|
+
"""Render a re-attached session's transcript before its live stream arrives.
|
|
72
|
+
|
|
73
|
+
Raw records, so this shows what happened rather than what the model was sent.
|
|
74
|
+
"""
|
|
75
|
+
for record in frame.get("records", []):
|
|
76
|
+
match record.get("type"):
|
|
77
|
+
case "user":
|
|
78
|
+
self.transcript.add(Message("user", str(record.get("content", ""))))
|
|
79
|
+
case "assistant" if record.get("content"):
|
|
80
|
+
self.transcript.add(
|
|
81
|
+
Message("assistant", str(record.get("content", "")))
|
|
82
|
+
)
|
|
83
|
+
case "tool_call":
|
|
84
|
+
call = ToolCall(
|
|
85
|
+
str(record.get("name", "")), dict(record.get("arguments", {}))
|
|
86
|
+
)
|
|
87
|
+
self.transcript.add(call)
|
|
88
|
+
self.tools[("", str(record.get("id", "")))] = call
|
|
89
|
+
case "tool_result":
|
|
90
|
+
call = self.tools.pop(("", str(record.get("id", ""))), None)
|
|
91
|
+
if call is not None:
|
|
92
|
+
call.finish(
|
|
93
|
+
ok=not record.get("is_error", False),
|
|
94
|
+
preview=str(record.get("content", "")),
|
|
95
|
+
)
|
|
96
|
+
case "supervisor":
|
|
97
|
+
self.note(labels.supervisor_nudge(str(record.get("content", ""))))
|
|
98
|
+
case "inbox":
|
|
99
|
+
self.note(
|
|
100
|
+
labels.inbox_message(
|
|
101
|
+
str(record.get("from", "")),
|
|
102
|
+
str(record.get("subject", "")),
|
|
103
|
+
[str(ref) for ref in record.get("refs", [])],
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
case "error":
|
|
107
|
+
self.error(str(record.get("message", "")))
|
|
108
|
+
self.settle()
|
|
109
|
+
|
|
110
|
+
def tool_started(self, frame: dict[str, Any], agent: str) -> None:
|
|
111
|
+
self.end_streams(agent)
|
|
112
|
+
call = ToolCall(str(frame.get("name", "")), dict(frame.get("arguments", {})))
|
|
113
|
+
self.transcript.add(call, agent=agent)
|
|
114
|
+
self.tools[(agent, str(frame.get("id", "")))] = call
|
|
115
|
+
self.settle()
|
|
116
|
+
|
|
117
|
+
def tool_finished(self, frame: dict[str, Any], agent: str) -> None:
|
|
118
|
+
call = self.tools.pop((agent, str(frame.get("id", ""))), None)
|
|
119
|
+
if call is None:
|
|
120
|
+
# A result with no box: the client attached mid-turn. One box saying what
|
|
121
|
+
# came back beats silently dropping it.
|
|
122
|
+
call = ToolCall(str(frame.get("name", "")), {})
|
|
123
|
+
self.transcript.add(call, agent=agent)
|
|
124
|
+
call.finish(
|
|
125
|
+
ok=bool(frame.get("ok", True)), preview=str(frame.get("preview", ""))
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def stream_into(self, kind: str, text: str, agent: str) -> None:
|
|
129
|
+
"""Append a delta, opening a new block when the kind of delta changes.
|
|
130
|
+
|
|
131
|
+
Keyed by `(agent, kind)`: deltas carry no boundaries, so a different kind
|
|
132
|
+
arriving is the only signal a block ended — and two sub-agents running in
|
|
133
|
+
parallel must not stream into each other's block.
|
|
134
|
+
|
|
135
|
+
`progress` is one of the kinds for the same reason the other two are: a REPL
|
|
136
|
+
cell streams a line at a time, and one entry per line is a screen of entries
|
|
137
|
+
for one tool call.
|
|
138
|
+
"""
|
|
139
|
+
key = (agent, kind)
|
|
140
|
+
block = self.streams.get(key)
|
|
141
|
+
if block is None:
|
|
142
|
+
for other in STREAM_KINDS:
|
|
143
|
+
if other != kind:
|
|
144
|
+
self.close_stream((agent, other))
|
|
145
|
+
block = _OPEN_STREAM[kind]()
|
|
146
|
+
self.transcript.add(block, agent=agent)
|
|
147
|
+
self.streams[key] = block
|
|
148
|
+
block.append(text)
|
|
149
|
+
self.transcript.scroll_end(animate=False)
|
|
150
|
+
self.settle()
|
|
151
|
+
|
|
152
|
+
def end_streams(self, agent: str = "") -> None:
|
|
153
|
+
for kind in STREAM_KINDS:
|
|
154
|
+
self.close_stream((agent, kind))
|
|
155
|
+
|
|
156
|
+
def close_stream(self, key: tuple[str, str]) -> None:
|
|
157
|
+
block = self.streams.pop(key, None)
|
|
158
|
+
if block is not None and not block.body.strip():
|
|
159
|
+
block.remove()
|
|
160
|
+
|
|
161
|
+
def settle(self) -> None:
|
|
162
|
+
"""Hide the banner once the transcript has outgrown the screen.
|
|
163
|
+
|
|
164
|
+
After the refresh, not during it: mounting is queued, so `max_scroll_y` read
|
|
165
|
+
immediately after adding an entry is the value from *before* that entry existed
|
|
166
|
+
— and the banner would sit there through a whole conversation.
|
|
167
|
+
"""
|
|
168
|
+
self.transcript.call_after_refresh(self.settle_now)
|
|
169
|
+
|
|
170
|
+
def settle_now(self) -> None:
|
|
171
|
+
with contextlib.suppress(Exception):
|
|
172
|
+
self.banner.follow(transcript_scrolls=self.transcript.scrolls)
|
|
173
|
+
|
|
174
|
+
def note(self, body: str) -> None:
|
|
175
|
+
"""What the platform did, as a rule across the screen."""
|
|
176
|
+
self.end_streams()
|
|
177
|
+
self.transcript.add(PlatformNote(body))
|
|
178
|
+
self.settle()
|
|
179
|
+
|
|
180
|
+
def warn(self, body: str) -> None:
|
|
181
|
+
self.transcript.add(Notice(body, level="warning"))
|
|
182
|
+
self.settle()
|
|
183
|
+
|
|
184
|
+
def error(self, body: str, *, agent: str = "") -> None:
|
|
185
|
+
self.end_streams(agent)
|
|
186
|
+
self.transcript.add(Notice(body, level="error"), agent=agent)
|
|
187
|
+
self.settle()
|