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.
Files changed (111) hide show
  1. stcode/__init__.py +10 -0
  2. stcode/cli/__init__.py +9 -0
  3. stcode/cli/bootstrap.py +70 -0
  4. stcode/cli/labels.py +621 -0
  5. stcode/cli/logic/__init__.py +0 -0
  6. stcode/cli/logic/commands.py +36 -0
  7. stcode/cli/logic/completion.py +43 -0
  8. stcode/cli/logic/connection.py +46 -0
  9. stcode/cli/logic/requests.py +18 -0
  10. stcode/cli/logic/sessions.py +67 -0
  11. stcode/cli/logic/settings.py +35 -0
  12. stcode/cli/logic/state.py +43 -0
  13. stcode/cli/logic/workspace.py +17 -0
  14. stcode/cli/main.py +290 -0
  15. stcode/cli/models.py +61 -0
  16. stcode/cli/services/__init__.py +0 -0
  17. stcode/cli/services/client.py +393 -0
  18. stcode/cli/services/launcher.py +159 -0
  19. stcode/cli/services/paths.py +18 -0
  20. stcode/cli/services/preferences.py +107 -0
  21. stcode/cli/ui/__init__.py +0 -0
  22. stcode/cli/ui/app.py +136 -0
  23. stcode/cli/ui/app.tcss +3 -0
  24. stcode/cli/ui/components/__init__.py +0 -0
  25. stcode/cli/ui/components/banner.py +78 -0
  26. stcode/cli/ui/components/cards.py +229 -0
  27. stcode/cli/ui/components/prompt.py +225 -0
  28. stcode/cli/ui/components/status.py +95 -0
  29. stcode/cli/ui/components/transcript/__init__.py +51 -0
  30. stcode/cli/ui/components/transcript/entries.py +232 -0
  31. stcode/cli/ui/components/transcript/formatting.py +188 -0
  32. stcode/cli/ui/components/transcript/view.py +101 -0
  33. stcode/cli/ui/screens/__init__.py +0 -0
  34. stcode/cli/ui/screens/chat/__init__.py +0 -0
  35. stcode/cli/ui/screens/chat/chat.tcss +12 -0
  36. stcode/cli/ui/screens/chat/presenter.py +187 -0
  37. stcode/cli/ui/screens/chat/screen.py +682 -0
  38. stcode/cli/ui/screens/connect.py +126 -0
  39. stcode/cli/ui/screens/connect.tcss +49 -0
  40. stcode/cli/ui/screens/dialog.tcss +40 -0
  41. stcode/cli/ui/screens/sessions.py +105 -0
  42. stcode/cli/ui/screens/settings.py +137 -0
  43. stcode/cli/ui/screens/settings.tcss +69 -0
  44. stcode/cli/ui/screens/trust.py +50 -0
  45. stcode/cli/ui/theme.py +221 -0
  46. stcode/core/__main__.py +7 -0
  47. stcode/core/agent/__init__.py +41 -0
  48. stcode/core/agent/agent.py +651 -0
  49. stcode/core/agent/events.py +97 -0
  50. stcode/core/agent/supervisor.py +226 -0
  51. stcode/core/agent/task.py +120 -0
  52. stcode/core/common/__init__.py +21 -0
  53. stcode/core/common/compat.py +27 -0
  54. stcode/core/common/ids.py +43 -0
  55. stcode/core/common/tools.py +50 -0
  56. stcode/core/common/trace.py +406 -0
  57. stcode/core/common/truncate.py +76 -0
  58. stcode/core/configs.py +490 -0
  59. stcode/core/daemon/__init__.py +32 -0
  60. stcode/core/daemon/autonomy.py +71 -0
  61. stcode/core/daemon/main.py +126 -0
  62. stcode/core/daemon/protocol.py +499 -0
  63. stcode/core/daemon/runner.py +359 -0
  64. stcode/core/daemon/server.py +682 -0
  65. stcode/core/daemon/workspace.py +88 -0
  66. stcode/core/harness/__init__.py +81 -0
  67. stcode/core/harness/approvals.py +85 -0
  68. stcode/core/harness/context.py +260 -0
  69. stcode/core/harness/errors.py +40 -0
  70. stcode/core/harness/harness.py +417 -0
  71. stcode/core/harness/mcp.py +530 -0
  72. stcode/core/harness/outputs.py +99 -0
  73. stcode/core/harness/prompts/__init__.py +138 -0
  74. stcode/core/harness/prompts/execute.py +51 -0
  75. stcode/core/harness/prompts/plan.py +57 -0
  76. stcode/core/harness/prompts/sections.py +238 -0
  77. stcode/core/harness/skills/__init__.py +30 -0
  78. stcode/core/harness/skills/loader.py +247 -0
  79. stcode/core/harness/tools/__init__.py +111 -0
  80. stcode/core/harness/tools/base.py +640 -0
  81. stcode/core/harness/tools/files.py +276 -0
  82. stcode/core/harness/tools/interact.py +72 -0
  83. stcode/core/harness/tools/registry.py +113 -0
  84. stcode/core/harness/tools/repl.py +97 -0
  85. stcode/core/harness/tools/schema.py +186 -0
  86. stcode/core/harness/tools/search.py +356 -0
  87. stcode/core/harness/tools/shell.py +372 -0
  88. stcode/core/harness/tools/skill.py +66 -0
  89. stcode/core/harness/tools/todo.py +62 -0
  90. stcode/core/harness/tools/web.py +163 -0
  91. stcode/core/providers/__init__.py +31 -0
  92. stcode/core/providers/anthropic_claude.py +243 -0
  93. stcode/core/providers/base.py +73 -0
  94. stcode/core/providers/gateway.py +267 -0
  95. stcode/core/providers/google_gemini.py +225 -0
  96. stcode/core/providers/openai_gpt.py +250 -0
  97. stcode/core/providers/registry.py +46 -0
  98. stcode/core/providers/types.py +148 -0
  99. stcode/core/repl/__init__.py +19 -0
  100. stcode/core/repl/_worker.py +179 -0
  101. stcode/core/repl/client.py +374 -0
  102. stcode/core/session/__init__.py +39 -0
  103. stcode/core/session/session.py +493 -0
  104. stcode/core/team/__init__.py +38 -0
  105. stcode/core/team/mailbox.py +249 -0
  106. stcode/core/team/tools.py +145 -0
  107. stcode-0.0.1a0.dist-info/METADATA +229 -0
  108. stcode-0.0.1a0.dist-info/RECORD +111 -0
  109. stcode-0.0.1a0.dist-info/WHEEL +4 -0
  110. stcode-0.0.1a0.dist-info/entry_points.txt +3 -0
  111. 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()