arkaos 3.70.10 → 3.71.0

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.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.70.10
1
+ 3.71.0
@@ -46,6 +46,13 @@ def _default_cwd() -> str:
46
46
  return os.path.expanduser("~")
47
47
 
48
48
 
49
+ # v3.71.0 — in-memory scrollback so a reconnecting client (after the
50
+ # operator navigates away or reloads the dashboard) can replay recent
51
+ # output and find its session as it left it. Bounded, RAM-only, cleared
52
+ # on close, never written to disk and never sent to the audit log.
53
+ DEFAULT_SCROLLBACK_BYTES = 512 * 1024
54
+
55
+
49
56
  class TerminalSession:
50
57
  """A single forked PTY + the bookkeeping needed to drive it.
51
58
 
@@ -60,6 +67,7 @@ class TerminalSession:
60
67
  cwd: str,
61
68
  cols: int = 120,
62
69
  rows: int = 32,
70
+ scrollback_bytes: int = DEFAULT_SCROLLBACK_BYTES,
63
71
  ) -> None:
64
72
  self.session_id = session_id
65
73
  self.shell = shell
@@ -68,6 +76,8 @@ class TerminalSession:
68
76
  self.last_activity = time.monotonic()
69
77
  self.exit_code: Optional[int] = None
70
78
  self.title: str = ""
79
+ self.scrollback_max = max(0, int(scrollback_bytes))
80
+ self._scrollback = bytearray()
71
81
  self._closed = False
72
82
  self.pid, self.master_fd = pty.fork()
73
83
  if self.pid == 0:
@@ -104,8 +114,21 @@ class TerminalSession:
104
114
  raise
105
115
  if data:
106
116
  self.last_activity = time.monotonic()
117
+ self._record(data)
107
118
  return data
108
119
 
120
+ def _record(self, data: bytes) -> None:
121
+ """Append output to the bounded scrollback, evicting the oldest."""
122
+ if self.scrollback_max <= 0:
123
+ return
124
+ self._scrollback += data
125
+ if len(self._scrollback) > self.scrollback_max:
126
+ del self._scrollback[: -self.scrollback_max]
127
+
128
+ def scrollback(self) -> bytes:
129
+ """Snapshot of recent output, for replay on (re)connect."""
130
+ return bytes(self._scrollback)
131
+
109
132
  def write(self, data: bytes) -> int:
110
133
  if self._closed or self.master_fd < 0 or not data:
111
134
  return 0
@@ -172,6 +195,7 @@ class TerminalSession:
172
195
  except OSError:
173
196
  pass
174
197
  self.master_fd = -1
198
+ self._scrollback.clear()
175
199
  self._closed = True
176
200
 
177
201
  def to_dict(self) -> dict[str, Any]:
@@ -207,6 +231,9 @@ class TerminalSessionManager:
207
231
  self.idle_timeout_s = int(
208
232
  os.environ.get("ARKAOS_TERMINAL_IDLE_S", idle_default)
209
233
  )
234
+ self.scrollback_bytes = int(
235
+ os.environ.get("ARKAOS_TERMINAL_SCROLLBACK_BYTES", DEFAULT_SCROLLBACK_BYTES)
236
+ )
210
237
  self._sessions: dict[str, TerminalSession] = {}
211
238
 
212
239
  def create(
@@ -230,6 +257,7 @@ class TerminalSessionManager:
230
257
  cwd=chosen_cwd,
231
258
  cols=cols,
232
259
  rows=rows,
260
+ scrollback_bytes=self.scrollback_bytes,
233
261
  )
234
262
  self._sessions[sid] = session
235
263
  audit.log_start(sid, chosen_shell, chosen_cwd)