xtralab 0.15.0 → 0.15.2

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 (237) hide show
  1. package/README.md +65 -30
  2. package/lib/about/icons.d.ts +0 -5
  3. package/lib/about/icons.js +0 -5
  4. package/lib/about/index.d.ts +4 -8
  5. package/lib/about/index.js +6 -17
  6. package/lib/agentSessions.d.ts +9 -29
  7. package/lib/agentSessions.js +2 -7
  8. package/lib/askAgent/editorSelection.d.ts +20 -26
  9. package/lib/askAgent/editorSelection.js +11 -26
  10. package/lib/askAgent/icons.d.ts +2 -3
  11. package/lib/askAgent/icons.js +2 -3
  12. package/lib/askAgent/index.d.ts +4 -26
  13. package/lib/askAgent/index.js +54 -158
  14. package/lib/askAgent/popup.d.ts +12 -16
  15. package/lib/askAgent/popup.js +18 -37
  16. package/lib/askAgent/prompt.d.ts +3 -5
  17. package/lib/askAgent/prompt.js +6 -19
  18. package/lib/askAgent/queue.d.ts +11 -21
  19. package/lib/askAgent/queue.js +9 -19
  20. package/lib/askAgent/queuePanel.d.ts +13 -18
  21. package/lib/askAgent/queuePanel.js +15 -37
  22. package/lib/askAgent/targetPicker.d.ts +2 -9
  23. package/lib/askAgent/targetPicker.js +6 -11
  24. package/lib/askAgent/tokens.d.ts +32 -46
  25. package/lib/askAgent/tokens.js +4 -6
  26. package/lib/commandBar/index.d.ts +4 -13
  27. package/lib/commandBar/index.js +21 -39
  28. package/lib/customPanel/index.js +15 -0
  29. package/lib/editorBreadcrumbs/index.d.ts +3 -6
  30. package/lib/editorBreadcrumbs/index.js +10 -6
  31. package/lib/editorBreadcrumbs/widget.d.ts +17 -4
  32. package/lib/editorBreadcrumbs/widget.js +11 -17
  33. package/lib/editorIndent/index.d.ts +4 -12
  34. package/lib/editorIndent/index.js +8 -23
  35. package/lib/fileBrowser/commands.d.ts +29 -19
  36. package/lib/fileBrowser/commands.js +83 -97
  37. package/lib/fileBrowser/contents.d.ts +3 -0
  38. package/lib/fileBrowser/dragAndDrop.d.ts +17 -4
  39. package/lib/fileBrowser/dragAndDrop.js +2 -7
  40. package/lib/fileBrowser/fileBrowser.d.ts +19 -9
  41. package/lib/fileBrowser/fileBrowser.js +70 -222
  42. package/lib/fileBrowser/gitStatus.d.ts +16 -10
  43. package/lib/fileBrowser/gitStatus.js +31 -30
  44. package/lib/fileBrowser/gitignore.d.ts +4 -17
  45. package/lib/fileBrowser/gitignore.js +4 -25
  46. package/lib/fileBrowser/icons.d.ts +7 -17
  47. package/lib/fileBrowser/icons.js +25 -80
  48. package/lib/fileBrowser/index.js +2 -4
  49. package/lib/fileBrowser/widget.d.ts +141 -48
  50. package/lib/fileBrowser/widget.js +102 -4
  51. package/lib/fileTypeIcons/index.js +0 -10
  52. package/lib/git/api.d.ts +5 -13
  53. package/lib/git/api.js +10 -36
  54. package/lib/git/askRequest.d.ts +4 -8
  55. package/lib/git/askRequest.js +6 -20
  56. package/lib/git/commands.d.ts +45 -5
  57. package/lib/git/commands.js +20 -13
  58. package/lib/git/diffModel.js +4 -0
  59. package/lib/git/diffPreferences.d.ts +90 -0
  60. package/lib/git/diffPreferences.js +107 -0
  61. package/lib/git/diffProvider.js +10 -2
  62. package/lib/git/diffSurface.d.ts +64 -63
  63. package/lib/git/diffSurface.js +72 -213
  64. package/lib/git/diffTheme.d.ts +7 -0
  65. package/lib/git/diffTheme.js +23 -0
  66. package/lib/git/diffWidget.d.ts +70 -14
  67. package/lib/git/diffWidget.js +100 -94
  68. package/lib/git/imageDiff.d.ts +14 -6
  69. package/lib/git/imageDiff.js +19 -75
  70. package/lib/git/index.d.ts +0 -3
  71. package/lib/git/index.js +44 -10
  72. package/lib/git/notebookDiff.d.ts +110 -33
  73. package/lib/git/notebookDiff.js +55 -197
  74. package/lib/git/tokens.d.ts +73 -31
  75. package/lib/git/tokens.js +2 -3
  76. package/lib/highlight/index.d.ts +4 -10
  77. package/lib/highlight/index.js +10 -41
  78. package/lib/index.d.ts +0 -9
  79. package/lib/index.js +0 -9
  80. package/lib/launcher/agents.d.ts +55 -51
  81. package/lib/launcher/agents.js +15 -57
  82. package/lib/launcher/availability.d.ts +3 -9
  83. package/lib/launcher/availability.js +3 -9
  84. package/lib/launcher/commands.d.ts +7 -32
  85. package/lib/launcher/commands.js +14 -45
  86. package/lib/launcher/dashboard.d.ts +19 -24
  87. package/lib/launcher/dashboard.js +21 -67
  88. package/lib/launcher/editorRegistry.d.ts +10 -22
  89. package/lib/launcher/editorRegistry.js +14 -18
  90. package/lib/launcher/editors.d.ts +39 -45
  91. package/lib/launcher/editors.js +8 -34
  92. package/lib/launcher/icons.d.ts +0 -12
  93. package/lib/launcher/icons.js +5 -39
  94. package/lib/launcher/index.js +12 -55
  95. package/lib/launcher/invocation.d.ts +4 -10
  96. package/lib/launcher/invocation.js +7 -22
  97. package/lib/launcher/registry.d.ts +9 -7
  98. package/lib/launcher/registry.js +9 -7
  99. package/lib/launcher/tokens.d.ts +10 -22
  100. package/lib/launcher/tokens.js +5 -8
  101. package/lib/menuBar/index.d.ts +3 -10
  102. package/lib/menuBar/index.js +11 -28
  103. package/lib/menus/index.d.ts +3 -9
  104. package/lib/menus/index.js +14 -30
  105. package/lib/omnibox/files.d.ts +2 -4
  106. package/lib/omnibox/files.js +5 -11
  107. package/lib/omnibox/index.d.ts +4 -14
  108. package/lib/omnibox/index.js +9 -27
  109. package/lib/omnibox/model.d.ts +38 -10
  110. package/lib/omnibox/model.js +15 -33
  111. package/lib/omnibox/recents.d.ts +15 -17
  112. package/lib/omnibox/recents.js +12 -17
  113. package/lib/omnibox/tokens.d.ts +5 -10
  114. package/lib/omnibox/tokens.js +2 -6
  115. package/lib/omnibox/widget.d.ts +11 -7
  116. package/lib/omnibox/widget.js +8 -16
  117. package/lib/searchReplace/index.d.ts +4 -20
  118. package/lib/searchReplace/index.js +7 -27
  119. package/lib/showOutput/index.d.ts +3 -12
  120. package/lib/showOutput/index.js +6 -22
  121. package/lib/sidebar/index.d.ts +4 -7
  122. package/lib/sidebar/index.js +5 -12
  123. package/lib/terminalNotifications/index.d.ts +4 -10
  124. package/lib/terminalNotifications/index.js +6 -20
  125. package/lib/terminals/agentTerminals.d.ts +12 -5
  126. package/lib/terminals/agentTerminals.js +25 -44
  127. package/lib/terminals/detection.d.ts +4 -8
  128. package/lib/terminals/detection.js +4 -8
  129. package/lib/terminals/index.d.ts +0 -35
  130. package/lib/terminals/index.js +12 -101
  131. package/lib/terminals/model.d.ts +64 -139
  132. package/lib/terminals/model.js +80 -220
  133. package/lib/terminals/tokens.d.ts +11 -22
  134. package/lib/terminals/tokens.js +2 -2
  135. package/lib/terminals/widget.d.ts +43 -13
  136. package/lib/terminals/widget.js +33 -18
  137. package/lib/topBar/icons.d.ts +3 -8
  138. package/lib/topBar/icons.js +3 -8
  139. package/lib/topBar/index.d.ts +4 -11
  140. package/lib/topBar/index.js +10 -38
  141. package/lib/walkthrough/index.d.ts +4 -9
  142. package/lib/walkthrough/index.js +6 -20
  143. package/lib/walkthrough/panel.d.ts +40 -9
  144. package/lib/walkthrough/panel.js +4 -10
  145. package/package.json +5 -3
  146. package/schema/git-diff-preferences.json +42 -0
  147. package/src/about/icons.ts +0 -5
  148. package/src/about/index.tsx +6 -17
  149. package/src/agentSessions.ts +9 -29
  150. package/src/askAgent/editorSelection.ts +22 -29
  151. package/src/askAgent/icons.ts +2 -3
  152. package/src/askAgent/index.ts +54 -158
  153. package/src/askAgent/popup.tsx +23 -45
  154. package/src/askAgent/prompt.ts +6 -19
  155. package/src/askAgent/queue.ts +13 -24
  156. package/src/askAgent/queuePanel.tsx +21 -46
  157. package/src/askAgent/targetPicker.tsx +5 -15
  158. package/src/askAgent/tokens.ts +32 -46
  159. package/src/commandBar/index.ts +24 -39
  160. package/src/customPanel/index.ts +15 -0
  161. package/src/editorBreadcrumbs/index.ts +10 -6
  162. package/src/editorBreadcrumbs/widget.ts +23 -17
  163. package/src/editorIndent/index.ts +17 -23
  164. package/src/fileBrowser/commands.ts +119 -96
  165. package/src/fileBrowser/contents.ts +3 -0
  166. package/src/fileBrowser/dragAndDrop.ts +17 -7
  167. package/src/fileBrowser/fileBrowser.tsx +86 -230
  168. package/src/fileBrowser/gitStatus.ts +36 -33
  169. package/src/fileBrowser/gitignore.ts +4 -25
  170. package/src/fileBrowser/icons.ts +25 -80
  171. package/src/fileBrowser/index.ts +2 -4
  172. package/src/fileBrowser/widget.tsx +171 -50
  173. package/src/fileTypeIcons/index.ts +0 -10
  174. package/src/git/api.ts +10 -36
  175. package/src/git/askRequest.ts +6 -20
  176. package/src/git/commands.ts +83 -11
  177. package/src/git/diffModel.ts +25 -0
  178. package/src/git/diffPreferences.ts +192 -0
  179. package/src/git/diffProvider.tsx +11 -2
  180. package/src/git/diffSurface.tsx +165 -286
  181. package/src/git/diffTheme.ts +24 -0
  182. package/src/git/diffWidget.tsx +194 -134
  183. package/src/git/imageDiff.tsx +56 -84
  184. package/src/git/index.ts +54 -10
  185. package/src/git/notebookDiff.tsx +214 -240
  186. package/src/git/tokens.ts +73 -31
  187. package/src/highlight/index.ts +10 -41
  188. package/src/index.ts +0 -9
  189. package/src/launcher/agents.ts +64 -95
  190. package/src/launcher/availability.ts +3 -9
  191. package/src/launcher/commands.ts +14 -51
  192. package/src/launcher/dashboard.tsx +49 -88
  193. package/src/launcher/editorRegistry.ts +21 -29
  194. package/src/launcher/editors.ts +39 -58
  195. package/src/launcher/icons.ts +5 -39
  196. package/src/launcher/index.ts +12 -55
  197. package/src/launcher/invocation.ts +7 -22
  198. package/src/launcher/registry.ts +9 -7
  199. package/src/launcher/tokens.ts +10 -22
  200. package/src/menuBar/index.ts +11 -28
  201. package/src/menus/index.ts +14 -30
  202. package/src/omnibox/files.ts +10 -14
  203. package/src/omnibox/index.ts +9 -27
  204. package/src/omnibox/model.ts +50 -38
  205. package/src/omnibox/recents.ts +17 -22
  206. package/src/omnibox/tokens.ts +5 -10
  207. package/src/omnibox/widget.tsx +14 -19
  208. package/src/searchReplace/index.ts +7 -27
  209. package/src/showOutput/index.ts +6 -22
  210. package/src/sidebar/index.ts +20 -12
  211. package/src/terminalNotifications/index.ts +42 -25
  212. package/src/terminals/agentTerminals.ts +25 -49
  213. package/src/terminals/detection.ts +4 -8
  214. package/src/terminals/index.ts +12 -101
  215. package/src/terminals/model.ts +130 -246
  216. package/src/terminals/tokens.ts +11 -22
  217. package/src/terminals/widget.tsx +48 -31
  218. package/src/topBar/icons.ts +3 -8
  219. package/src/topBar/index.ts +21 -44
  220. package/src/walkthrough/index.ts +6 -20
  221. package/src/walkthrough/panel.ts +40 -10
  222. package/style/about.css +2 -7
  223. package/style/askAgent.css +8 -40
  224. package/style/base.css +9 -45
  225. package/style/chrome.css +108 -308
  226. package/style/commandBar.css +4 -20
  227. package/style/customPanel.css +1 -4
  228. package/style/git.css +26 -186
  229. package/style/highlight.css +1 -3
  230. package/style/launcher.css +6 -70
  231. package/style/omnibox.css +2 -14
  232. package/style/showOutput.css +0 -6
  233. package/style/sidebar.css +29 -9
  234. package/style/tabs.css +5 -13
  235. package/style/terminals.css +6 -53
  236. package/style/topBar.css +2 -22
  237. package/style/walkthrough.css +0 -8
@@ -10,101 +10,76 @@ import type { Title, Widget } from '@lumino/widgets';
10
10
  import type { IAgentSessions } from '../agentSessions';
11
11
  import { fetchRunningAgents } from './detection';
12
12
 
13
- /**
14
- * The widget shape every entry in `ITerminalTracker` takes — kept here
15
- * so the registry's call sites read cleanly.
16
- */
17
13
  export type TerminalWidget = MainAreaWidget<ITerminal.ITerminal>;
18
14
 
19
15
  /**
20
- * The slice of an xterm.js `Terminal` the registry reads to surface a
21
- * session's most recent line of output. JupyterLab keeps its xterm in the
22
- * terminal widget's private `_term` field, so a structural type reaches the
23
- * buffer without taking a dependency on `@xterm/xterm` — the same access the
24
- * terminal-notifications plugin already relies on. If the field is ever
25
- * renamed, `_term` reads back `undefined` and the activity line is simply
26
- * omitted: the row falls back to its title alone.
16
+ * Slice of xterm.js reached through the terminal widget's private `_term`
17
+ * field, so the buffer is readable without depending on `@xterm/xterm`.
18
+ * If upstream renames the field, the activity line is simply omitted.
27
19
  */
28
20
  interface IXtermBufferLine {
21
+ /**
22
+ * Whether this row is a soft-wrap continuation of the previous row.
23
+ */
29
24
  readonly isWrapped: boolean;
25
+ /**
26
+ * Translate the row to its text, trimming trailing whitespace on request.
27
+ */
30
28
  translateToString(trimRight?: boolean): string;
31
29
  }
30
+ /**
31
+ * An xterm.js buffer exposing the cursor position and row access.
32
+ */
32
33
  interface IXtermBuffer {
34
+ /**
35
+ * Buffer row of the viewport's top line when scrolled to the bottom.
36
+ */
33
37
  readonly baseY: number;
38
+ /**
39
+ * The cursor's row relative to the viewport top.
40
+ */
34
41
  readonly cursorY: number;
42
+ /**
43
+ * Get the line at the given buffer row, or `undefined` if out of range.
44
+ */
35
45
  getLine(index: number): IXtermBufferLine | undefined;
36
46
  }
47
+ /**
48
+ * An xterm.js terminal narrowed to its buffer namespace.
49
+ */
37
50
  interface IXtermTerminal {
51
+ /**
52
+ * The buffer namespace; `active` is the buffer currently on screen.
53
+ */
38
54
  readonly buffer: { readonly active: IXtermBuffer };
39
55
  }
56
+ /**
57
+ * The terminal widget content with its private xterm.js instance exposed.
58
+ */
40
59
  interface ITerminalContentInternals extends ITerminal.ITerminal {
60
+ /**
61
+ * The underlying xterm.js terminal; absent until the widget creates it.
62
+ */
41
63
  _term?: IXtermTerminal;
42
64
  }
43
65
 
44
- /**
45
- * How often to ask the server which agent (if any) is running in each
46
- * terminal. Snappy enough that a manually-started agent's logo shows up
47
- * within a few seconds, light enough that walking the shells' process trees
48
- * stays negligible.
49
- */
50
66
  const DETECT_POLL_INTERVAL_MS = 3000;
51
67
 
52
- /**
53
- * Upper bound on the exponential backoff when detection fails repeatedly
54
- * (e.g. the endpoint is missing on an older server). Matches the rest of the
55
- * plugin's polls.
56
- */
57
68
  const DETECT_POLL_MAX_MS = 300_000;
58
69
 
59
70
  /**
60
- * Grace window after a session first appears during which an optimistic
61
- * launch tag outranks a `null` detection result. It covers the gap between
62
- * issuing an agent's command and its process actually being spawnable, so the
63
- * freshly-launched logo doesn't blink off if a detection poll lands in that
64
- * sliver. Comfortably longer than a cold agent start; short enough that a tag
65
- * for an agent that never really started clears quickly.
71
+ * Window during which a fresh launch tag outranks a `null` detection result:
72
+ * covers the gap between launching an agent and its process becoming visible.
66
73
  */
67
74
  const LAUNCH_GRACE_MS = 4000;
68
75
 
69
- /**
70
- * How often to re-read each open coding-agent terminal's output buffer to
71
- * refresh the "latest activity" line shown under its row. Brisk enough to feel
72
- * live while an agent works, cheap because it only walks the last screenful of
73
- * a handful of terminals and emits only when a line actually changes.
74
- */
75
76
  const ACTIVITY_POLL_INTERVAL_MS = 1500;
76
77
 
77
78
  /**
78
- * Source-of-truth model for the running-terminals panel. Each running
79
- * terminal session known to the server is one entry; the registry caches
80
- * the last `widget.title.label` we observed so the agent name (or any
81
- * xterm-published title) survives the user closing the tab while the
82
- * session continues running on the backend.
83
- *
84
- * For sessions running a coding agent it also surfaces a *latest activity*
85
- * line — the freshest meaningful line of the terminal's output, read from the
86
- * open tab's xterm buffer on a poll (see {@link activityFor}) — so each row
87
- * shows what its agent is doing, not just its name.
88
- *
89
- * It also resolves *which agent is running* in each session, so the panel can
90
- * badge rows with the agent's logo. Two inputs feed that, reconciled by
91
- * {@link agentCommandFor}:
92
- * - an optimistic launch tag ({@link IAgentSessions}) written when we start
93
- * an agent ourselves — instant, but blind to the agent later exiting; and
94
- * - authoritative server-side process detection, polled here, which works
95
- * for hand-started agents too and clears once an agent exits.
96
- *
97
- * Upstream session signals:
98
- * - `serviceManager.terminals.runningChanged` is the authoritative
99
- * list of live sessions; everything not in it has been shut down
100
- * server-side and we must drop it.
101
- * - `tracker.widgetAdded` plus the per-widget `title.changed` /
102
- * `disposed` signals keep our label cache in sync with whatever
103
- * xterm/the launcher has set on the open tabs.
104
- *
105
- * `stateChanged` is emitted for every kind of update; the panel hooks it
106
- * through a `UseSignal` so a single subscription re-renders the list on
107
- * any change.
79
+ * Source-of-truth model for the running-terminals panel: live sessions,
80
+ * labels cached across tab closes, agent badges (launch tags reconciled with
81
+ * polled detection, see {@link agentCommandFor}), and per-row latest-activity
82
+ * lines. `stateChanged` fires on every kind of update.
108
83
  */
109
84
  export class SessionRegistry implements IDisposable {
110
85
  constructor(options: SessionRegistry.IOptions) {
@@ -120,18 +95,9 @@ export class SessionRegistry implements IDisposable {
120
95
  this._tracker.forEach(widget => this._trackWidget(widget));
121
96
  this._refreshLive();
122
97
 
123
- // Track which terminal is the current widget in the main area so the panel
124
- // can highlight its row — and highlight nothing while a notebook or any
125
- // other non-terminal tab is current instead. `currentChanged` is optional
126
- // on the shell interface (not every shell can switch focus), so guard it;
127
- // when it is absent the highlight stays off.
128
98
  this._shell?.currentChanged?.connect(this._onShellCurrentChanged, this);
129
99
  this._updateCurrent();
130
100
 
131
- // A freshly written launch tag should re-render the panel immediately
132
- // (show the logo) and again once the grace window closes (so a tag for
133
- // an agent that never started, or that detection later contradicts,
134
- // doesn't linger).
135
101
  this._agentSessions?.changed.connect(this._onTagChanged, this);
136
102
 
137
103
  this._poll = new Poll({
@@ -145,10 +111,8 @@ export class SessionRegistry implements IDisposable {
145
111
  standby: 'when-hidden'
146
112
  });
147
113
 
148
- // A second, faster poll reads the live output buffer of each open
149
- // coding-agent terminal to keep its "latest activity" line current. Kept
150
- // separate from the detection poll so reading xterm buffers neither delays
151
- // nor is delayed by the server round-trip that drives the agent badges.
114
+ // Separate from the detection poll so reading xterm buffers neither
115
+ // delays nor is delayed by the server round-trip.
152
116
  this._activityPoll = new Poll({
153
117
  name: '@xtralab/terminals:activity',
154
118
  factory: () => this._refreshActivity(),
@@ -158,21 +122,21 @@ export class SessionRegistry implements IDisposable {
158
122
  }
159
123
 
160
124
  /**
161
- * Emitted whenever the live session list, a cached label, or the detected
162
- * running agent changes.
125
+ * Emitted on any change to the session list, labels, badges, or activity.
163
126
  */
164
127
  get stateChanged(): ISignal<this, void> {
165
128
  return this._stateChanged;
166
129
  }
167
130
 
131
+ /**
132
+ * Whether the registry has been disposed.
133
+ */
168
134
  get isDisposed(): boolean {
169
135
  return this._isDisposed;
170
136
  }
171
137
 
172
138
  /**
173
- * Names of all live sessions, ordered by their stable rank so the
174
- * rendered list keeps a consistent order (roughly creation order) as
175
- * new sessions are added and old ones shut down.
139
+ * Names of all live sessions in stable (roughly creation) order.
176
140
  */
177
141
  sessionNames(): string[] {
178
142
  const names = Array.from(this._live);
@@ -181,23 +145,17 @@ export class SessionRegistry implements IDisposable {
181
145
  }
182
146
 
183
147
  /**
184
- * True iff the named session is still on the server. Used by the panel
185
- * to skip a row during the brief window between the session's shutdown
186
- * and the next `runningChanged` arriving.
148
+ * True iff the named session is still on the server.
187
149
  */
188
150
  has(name: string): boolean {
189
151
  return this._live.has(name);
190
152
  }
191
153
 
192
154
  /**
193
- * Display name for the session. The cache wins because it only
194
- * holds "real" labels (the launcher's agent name or an xterm escape
195
- * sequence — see `_cacheLabel` for the filter); reading it first
196
- * prevents the transient `Terminal {name}` an XTerm widget shows
197
- * during reconnect from flickering into the panel. The live widget
198
- * title is used when no cache exists, and the `Terminal {name}`
199
- * fallback covers sessions we have never seen a widget for (e.g.
200
- * surviving a lab reload).
155
+ * Display name for the session. The cache wins — it holds only "real"
156
+ * labels (see `_cacheLabel`), so the transient `Terminal {name}` an xterm
157
+ * widget shows during reconnect never flickers into the panel; the final
158
+ * fallback covers sessions never seen with a widget (e.g. a lab reload).
201
159
  */
202
160
  labelFor(name: string): string {
203
161
  const cached = this._labels.get(name);
@@ -212,18 +170,10 @@ export class SessionRegistry implements IDisposable {
212
170
  }
213
171
 
214
172
  /**
215
- * An identifier for the agent running in the session — its configured
216
- * command, or its canonical id when detection matched the spawned process by
217
- * id rather than by an aliased command — or `null` if none. Callers resolve
218
- * it to a logo through the plugin's `iconForCommand`, which accepts either.
219
- *
220
- * Server-side detection is authoritative whenever it reports a running
221
- * agent. A launch tag fills two gaps: the startup grace window right after
222
- * we launch an agent (so its logo shows before its process is detectable),
223
- * and any time detection is unavailable (older server, transient error).
224
- * Once the grace window has passed and detection has reported the session
225
- * as idle, the tag is ignored — that is how the badge clears when an agent
226
- * exits.
173
+ * Agent identifier for the session (configured command or canonical id), or
174
+ * `null`. Detection wins when it reports an agent; a launch tag fills the
175
+ * grace window and detection outages, and is ignored once detection says
176
+ * idle — that is how the badge clears when an agent exits.
227
177
  */
228
178
  agentCommandFor(name: string): string | null {
229
179
  // `string` = detected running agent, `null` = polled and idle,
@@ -246,13 +196,9 @@ export class SessionRegistry implements IDisposable {
246
196
  }
247
197
 
248
198
  /**
249
- * The agent command server-side detection currently reports for the
250
- * session, or `null` when it has not confirmed one (the session is idle,
251
- * not yet covered by a poll, or detection is unavailable). Unlike
252
- * {@link agentCommandFor} this never falls back to the optimistic launch
253
- * tags: callers about to *write into* a session use it, and text must only
254
- * ever be sent to a confirmed running agent — pasted into a shell prompt it
255
- * would be executed.
199
+ * The agent command detection currently confirms for the session, or
200
+ * `null`. Never falls back to launch tags: callers about to *write into* a
201
+ * session use it, and text pasted into a shell prompt would be executed.
256
202
  */
257
203
  detectedCommandFor(name: string): string | null {
258
204
  const detected = this._detected.get(name);
@@ -260,13 +206,9 @@ export class SessionRegistry implements IDisposable {
260
206
  }
261
207
 
262
208
  /**
263
- * The most recent meaningful line of output from the session's terminal, or
264
- * `null` when there is nothing to surface — no coding agent is running in the
265
- * session, its tab is closed (so there is no live buffer to read), or the
266
- * buffer holds only the agent's input box and chrome. Refreshed on a poll by
267
- * {@link _refreshActivity}; shown as a smaller line under the row's title.
268
- * Always `null` while the activity line is disabled (see
269
- * {@link setActivityEnabled}).
209
+ * The most recent meaningful line of the session's output, or `null` when
210
+ * nothing qualifies (no running agent, closed tab, chrome-only buffer, or
211
+ * the activity line is disabled).
270
212
  */
271
213
  activityFor(name: string): string | null {
272
214
  if (!this._activityEnabled) {
@@ -276,11 +218,8 @@ export class SessionRegistry implements IDisposable {
276
218
  }
277
219
 
278
220
  /**
279
- * Turn the per-row latest-activity line on or off, driven by the
280
- * `xtralab:terminals` `showAgentActivity` setting. When turned off the
281
- * activity poll stops reading terminal buffers and any cached lines are
282
- * dropped, so rows fall back to their title alone; when turned back on the
283
- * lines are repopulated on the spot. A no-op when the value is unchanged.
221
+ * Turn the per-row activity line on or off (the `showAgentActivity`
222
+ * setting). Off drops cached lines; on repopulates them on the spot.
284
223
  */
285
224
  setActivityEnabled(enabled: boolean): void {
286
225
  if (enabled === this._activityEnabled) {
@@ -288,8 +227,6 @@ export class SessionRegistry implements IDisposable {
288
227
  }
289
228
  this._activityEnabled = enabled;
290
229
  if (enabled) {
291
- // Repopulate immediately rather than waiting for the next poll tick;
292
- // `_refreshActivity` emits `stateChanged` if it finds anything to show.
293
230
  void this._refreshActivity();
294
231
  } else {
295
232
  this._activity.clear();
@@ -298,9 +235,7 @@ export class SessionRegistry implements IDisposable {
298
235
  }
299
236
 
300
237
  /**
301
- * Return the open widget for a session, if any. The panel uses this to
302
- * switch behavior between "activate existing tab" and "open a new tab
303
- * connected to the live session".
238
+ * The open widget for a session, if any.
304
239
  */
305
240
  widgetFor(name: string): TerminalWidget | null {
306
241
  return (
@@ -309,22 +244,16 @@ export class SessionRegistry implements IDisposable {
309
244
  }
310
245
 
311
246
  /**
312
- * Session name of the terminal that is currently the active widget in the
313
- * shell's main area, or `null` when that widget is not a terminal (for
314
- * example a notebook or text editor is current). The panel uses this to
315
- * highlight the current terminal's row — mirroring how the file browser
316
- * surfaces the open document — and to leave every row unhighlighted while a
317
- * non-terminal tab is current.
247
+ * Session name of the terminal that is the shell's current main-area
248
+ * widget, or `null` when the current widget is not a terminal.
318
249
  */
319
250
  currentSessionName(): string | null {
320
251
  return this._currentName;
321
252
  }
322
253
 
323
254
  /**
324
- * Stable per-session rank assigned in observation order. Used to keep
325
- * the rendered list in a steady order so rows don't reshuffle as
326
- * sessions come and go. Cleaned up when the session is shut down so the
327
- * counter does not grow without bound across long sessions.
255
+ * Stable per-session rank in observation order, so rows don't reshuffle as
256
+ * sessions come and go.
328
257
  */
329
258
  rankFor(name: string): number {
330
259
  let rank = this._ranks.get(name);
@@ -335,6 +264,9 @@ export class SessionRegistry implements IDisposable {
335
264
  return rank;
336
265
  }
337
266
 
267
+ /**
268
+ * Dispose of the registry's polls and signal connections.
269
+ */
338
270
  dispose(): void {
339
271
  if (this._isDisposed) {
340
272
  return;
@@ -364,16 +296,12 @@ export class SessionRegistry implements IDisposable {
364
296
  }
365
297
 
366
298
  /**
367
- * Adopt `next` as the live set and prune every per-session map for
368
- * sessions that have gone away — labels, ranks, first-seen timestamps,
369
- * detection results, and the shared launch tag. Without this the maps (and
370
- * the rank counter) would grow without bound as terminals come and go.
299
+ * Adopt `next` as the live set and prune every per-session map, so the
300
+ * maps and rank counter don't grow without bound.
371
301
  */
372
302
  private _reconcileLive(next: Set<string>): void {
373
- // Names we had already seen, captured before we prune below — the set of
374
- // candidates whose launch tag may now need forgetting. (The tag map is
375
- // not enumerable, so we drive the prune from the sessions we know about;
376
- // `_firstSeen` has an entry for every session that has ever been live.)
303
+ // The tag map is not enumerable, so the tag prune below is driven from
304
+ // `_firstSeen`, which has an entry for every session ever live.
377
305
  const known = Array.from(this._firstSeen.keys());
378
306
 
379
307
  for (const name of next) {
@@ -407,10 +335,8 @@ export class SessionRegistry implements IDisposable {
407
335
  this._activity.delete(name);
408
336
  }
409
337
  }
410
- // Forget launch tags for sessions that have gone away. This matters for
411
- // correctness as well as bookkeeping: terminado reuses session names, so
412
- // a stale tag could otherwise mislabel a brand-new terminal that happens
413
- // to reuse a closed session's name.
338
+ // terminado reuses session names, so a stale tag could mislabel a
339
+ // brand-new terminal that reuses a closed session's name.
414
340
  for (const name of known) {
415
341
  if (!next.has(name)) {
416
342
  this._agentSessions?.delete(name);
@@ -419,9 +345,8 @@ export class SessionRegistry implements IDisposable {
419
345
  }
420
346
 
421
347
  /**
422
- * Poll body: ask the server which agent runs in each terminal and update
423
- * the detection map. On failure we keep the previous results rather than
424
- * clearing them, so a transient error doesn't strip every badge.
348
+ * Poll body: update the detection map from the server. On failure the
349
+ * previous results are kept, so a transient error doesn't strip every badge.
425
350
  */
426
351
  private async _refreshDetection(): Promise<void> {
427
352
  const commands = this._detectCommands();
@@ -449,15 +374,9 @@ export class SessionRegistry implements IDisposable {
449
374
  }
450
375
 
451
376
  /**
452
- * Poll body: re-read the buffer of each open coding-agent terminal and cache
453
- * its latest meaningful line of output. Only sessions that have a running
454
- * *agent* (not an editor) and an open tab qualify — a closed tab has no live
455
- * buffer, editors run full-screen UIs that are not "activity", and plain
456
- * shells would only echo their prompt. While a tab is reopening (its xterm
457
- * is not ready yet) any existing line is kept; once the buffer is readable
458
- * but has nothing worth showing the line is dropped, so a screen the agent
459
- * has cleared doesn't leave a frozen line behind. Cached lines for sessions
460
- * that no longer qualify are pruned. Emits only when something changed.
377
+ * Poll body: cache the latest meaningful output line of each open agent
378
+ * terminal (closed tabs have no live buffer; editors and shells don't
379
+ * qualify). Emits only when something changed.
461
380
  */
462
381
  private async _refreshActivity(): Promise<void> {
463
382
  if (!this._activityEnabled) {
@@ -477,8 +396,8 @@ export class SessionRegistry implements IDisposable {
477
396
  qualifying.add(name);
478
397
  const term = (widget.content as ITerminalContentInternals)._term;
479
398
  if (!term) {
480
- // The tab is reopening and its xterm is not ready; keep any existing
481
- // line until the buffer can be read again.
399
+ // The tab is reopening; keep any existing line until its xterm buffer
400
+ // is readable again.
482
401
  continue;
483
402
  }
484
403
  const line = Private.readActivity(term);
@@ -488,8 +407,8 @@ export class SessionRegistry implements IDisposable {
488
407
  changed = true;
489
408
  }
490
409
  } else if (this._activity.delete(name)) {
491
- // Readable buffer with nothing to surface (e.g. the agent cleared its
492
- // screen) — drop the now-stale line instead of freezing it.
410
+ // Readable buffer with nothing to surface (e.g. a cleared screen) —
411
+ // drop the stale line instead of freezing it.
493
412
  changed = true;
494
413
  }
495
414
  }
@@ -506,8 +425,8 @@ export class SessionRegistry implements IDisposable {
506
425
 
507
426
  private _onTagChanged(): void {
508
427
  this._stateChanged.emit();
509
- // Re-render once the grace window closes so a tag that detection never
510
- // confirmed (or has since contradicted) stops being shown.
428
+ // Re-emit once the grace window closes so a tag detection never confirmed
429
+ // (or has since contradicted) stops being shown.
511
430
  setTimeout(() => {
512
431
  if (!this._isDisposed) {
513
432
  this._stateChanged.emit();
@@ -525,10 +444,8 @@ export class SessionRegistry implements IDisposable {
525
444
  }
526
445
 
527
446
  /**
528
- * Recompute which terminal (if any) is the active main-area widget and emit
529
- * only when it changes. The terminal tracker tells us whether the shell's
530
- * current widget is one of our terminals; anything else — a notebook, an
531
- * editor, or nothing — clears the highlight.
447
+ * Recompute which terminal (if any) is the active main-area widget; emits
448
+ * only on change. Anything not a tracked terminal clears the highlight.
532
449
  */
533
450
  private _updateCurrent(): void {
534
451
  const current = this._shell?.currentWidget ?? null;
@@ -565,16 +482,10 @@ export class SessionRegistry implements IDisposable {
565
482
  }
566
483
 
567
484
  /**
568
- * Update the cached label for a session, but only when the new
569
- * label is "real" — non-empty and not one of the transient defaults
570
- * the XTerm widget cycles through during reconnect (`'...'` while
571
- * the websocket is opening, `'Terminal {name}'` once
572
- * `_initialConnection` fires). Without the filter, briefly
573
- * reopening a tab while an agent has yet to re-emit its xterm
574
- * title escape sequence would clobber the cached agent name with
575
- * `Terminal 1`. Live widget titles still display whatever the widget
576
- * currently holds because `labelFor` consults the widget first;
577
- * the filter only affects what survives a tab close.
485
+ * Cache a label only when it is "real" — not `'...'` or `Terminal {name}`,
486
+ * the transient defaults xterm cycles through during reconnect. Without the
487
+ * filter, reopening a tab before the agent re-emits its title escape would
488
+ * clobber the cached agent name.
578
489
  */
579
490
  private _cacheLabel(name: string, label: string): void {
580
491
  if (!label) {
@@ -587,11 +498,8 @@ export class SessionRegistry implements IDisposable {
587
498
  }
588
499
 
589
500
  private _onWidgetDisposed(widget: Widget): void {
590
- // The widget is gone but the session may still be running on the
591
- // server — keep the cached label so the panel keeps the agent's name
592
- // when the user reopens the tab. We only clean up our subscriptions
593
- // here; the cache is purged by `runningChanged` once the session
594
- // itself goes away.
501
+ // The session may still be running on the server; the cached label stays
502
+ // until `runningChanged` purges it with the session itself.
595
503
  this._untrackWidget(widget as TerminalWidget);
596
504
  this._stateChanged.emit();
597
505
  }
@@ -621,15 +529,21 @@ export class SessionRegistry implements IDisposable {
621
529
  * Construction options for {@link SessionRegistry}.
622
530
  */
623
531
  export namespace SessionRegistry {
532
+ /**
533
+ * The instantiation options for a session registry.
534
+ */
624
535
  export interface IOptions {
536
+ /**
537
+ * The service manager whose terminal manager supplies the sessions.
538
+ */
625
539
  serviceManager: ServiceManager.IManager;
540
+ /**
541
+ * The tracker of open terminal widgets.
542
+ */
626
543
  tracker: ITerminalTracker;
627
544
  /**
628
- * The application shell, used to tell which widget is currently active so
629
- * the panel can highlight the terminal that is the current main-area
630
- * widget — and highlight nothing when that widget is not a terminal (a
631
- * notebook, an editor, …). Optional: without it, or on a shell that cannot
632
- * report `currentChanged`, the highlight stays off.
545
+ * Used to highlight the row of the current main-area terminal. Optional:
546
+ * without it (or its `currentChanged`) the highlight stays off.
633
547
  */
634
548
  shell?: JupyterFrontEnd.IShell | null;
635
549
  /**
@@ -638,19 +552,15 @@ export namespace SessionRegistry {
638
552
  */
639
553
  agentSessions?: IAgentSessions | null;
640
554
  /**
641
- * Returns the names the server should look for when detecting running
642
- * agents — each agent's command together with its canonical id, so a
643
- * command pointed at an alias (e.g. `ccm` running `claude`) is still
644
- * matched by the process it spawns. Read on every poll so it tracks the
645
- * live agent list.
555
+ * Names to detect: each agent's command plus its canonical id, so an
556
+ * aliased command (e.g. `ccm` running `claude`) is still matched by the
557
+ * process it spawns. Read on every poll.
646
558
  */
647
559
  detectCommands?: () => string[];
648
560
  /**
649
- * Whether a detected command (or id) belongs to a coding agent rather than
650
- * an editor. Only agent sessions get a latest-activity line — editors
651
- * (Neovim/Vim) are badged too, but run full-screen UIs whose buffer is not
652
- * meaningfully "activity". Defaults to treating every detected command as an
653
- * agent.
561
+ * Whether a detected command (or id) belongs to a coding agent rather
562
+ * than an editor; only agent sessions get a latest-activity line.
563
+ * Defaults to treating every detected command as an agent.
654
564
  */
655
565
  isAgentCommand?: (command: string) => boolean;
656
566
  }
@@ -658,8 +568,7 @@ export namespace SessionRegistry {
658
568
 
659
569
  namespace Private {
660
570
  /**
661
- * Value-equality for two detection maps, so a poll that changes nothing
662
- * doesn't trigger a re-render.
571
+ * Whether two detection maps hold identical entries.
663
572
  */
664
573
  export function detectedEqual(
665
574
  a: Map<string, string | null>,
@@ -676,36 +585,24 @@ namespace Private {
676
585
  return true;
677
586
  }
678
587
 
679
- /**
680
- * Rows above the cursor to scan when looking for the latest output.
681
- */
682
588
  const ACTIVITY_SCAN_ROWS = 64;
683
589
 
684
- /**
685
- * Clamp for the activity string so one runaway block can't bloat a row.
686
- */
687
590
  const ACTIVITY_MAX_LENGTH = 160;
688
591
 
689
592
  /**
690
- * Horizontal box-drawing / rule characters, treated as blanks when
691
- * sanitizing. Agents pad a status line or draw a separator with these (e.g.
692
- * `Worked for 1m 06s ────`), which is chrome, not text. Vertical bars are
693
- * deliberately excluded — {@link isMeaningfulActivity} relies on them to
694
- * recognise (and skip) the contents of an input/quote box.
593
+ * Horizontal rule characters treated as blanks when sanitizing (status-line
594
+ * padding is chrome, not text). Vertical bars are excluded on purpose —
595
+ * {@link isMeaningfulActivity} uses them to skip input/quote boxes.
695
596
  */
696
597
  const RULE_CHARS = new Set([
697
598
  0x2500, 0x2501, 0x2504, 0x2505, 0x2508, 0x2509, 0x254c, 0x254d, 0x2550
698
599
  ]);
699
600
 
700
601
  /**
701
- * The most recent meaningful line of agent output, or `null` if none is
702
- * found. Agents park the cursor in an input box at the bottom of the screen,
703
- * so the scan starts there and walks *up*, skipping that box, any hint or
704
- * footer beneath it, blank lines and separators, until it reaches the block
705
- * of real output nearest the input. It then rewinds to that block's first row
706
- * and stitches the block back together (see {@link joinBlock}), so a reply
707
- * the agent hard-wrapped across several rows reads as its opening sentence
708
- * rather than the trailing fragment left next to the cursor.
602
+ * The most recent meaningful line of agent output, or `null`. Agents park
603
+ * the cursor in a bottom input box, so the scan walks *up* past chrome to
604
+ * the nearest output block, then rewinds to that block's first row so a
605
+ * hard-wrapped reply reads as its opening sentence, not a trailing fragment.
709
606
  */
710
607
  export function readActivity(term: IXtermTerminal): string | null {
711
608
  const buffer = term.buffer?.active;
@@ -718,8 +615,6 @@ namespace Private {
718
615
  if (!isMeaningfulActivity(lineText(buffer, row))) {
719
616
  continue;
720
617
  }
721
- // `row` is the bottom of the output block nearest the input; walk up
722
- // while rows stay meaningful to find the block's first row.
723
618
  let topRow = row;
724
619
  while (
725
620
  topRow > limit &&
@@ -733,17 +628,16 @@ namespace Private {
733
628
  }
734
629
 
735
630
  /**
736
- * Sanitized text of one buffer row.
631
+ * Read a buffer row as sanitized text; empty when the row is missing.
737
632
  */
738
633
  export function lineText(buffer: IXtermBuffer, row: number): string {
739
634
  return sanitizeActivity(buffer.getLine(row)?.translateToString(true) ?? '');
740
635
  }
741
636
 
742
637
  /**
743
- * Stitch rows `[topRow, lastRow]` of one output block into a single string,
744
- * stopping once it is long enough to fill the row. A soft-wrapped row
745
- * continues the previous one mid-word, so it is appended directly; a hard
746
- * newline is a word boundary, so it is joined with a space.
638
+ * Stitch rows `[topRow, lastRow]` of one output block into a single string.
639
+ * A soft-wrapped row continues mid-word (appended directly); a hard newline
640
+ * is a word boundary (joined with a space).
747
641
  */
748
642
  export function joinBlock(
749
643
  buffer: IXtermBuffer,
@@ -776,9 +670,7 @@ namespace Private {
776
670
  }
777
671
 
778
672
  /**
779
- * Collapse a raw buffer row into displayable text: replace control characters
780
- * and horizontal rule characters with spaces (so no escape sequence or drawn
781
- * separator rides along), squeeze runs of whitespace, and trim.
673
+ * Replace control and rule characters with spaces and collapse whitespace.
782
674
  */
783
675
  export function sanitizeActivity(value: string): string {
784
676
  let result = '';
@@ -791,18 +683,10 @@ namespace Private {
791
683
  }
792
684
 
793
685
  /**
794
- * Whether a sanitized row is worth showing as activity, i.e. real output
795
- * rather than chrome. Doubles as the block-boundary test for
796
- * {@link readActivity}'s walk up. A row is skipped when it:
797
- * - is blank or pure box-drawing (carries no letter or digit);
798
- * - begins with a vertical box-drawing bar (the contents of an input or
799
- * quote box), a prompt chevron (the input row and its ghost suggestion),
800
- * or Claude's tool-result / tip marker `⎿`; or
801
- * - is a transient status footer — one carrying a `for <time>` / `(<time>`
802
- * elapsed-time stamp, as agents print while and after they work
803
- * ("✻ Churned for 17s", "Working… (8s · …)", "— Worked for 1m 06s"). This
804
- * carries no real content, so skipping it lets the scan reach the reply
805
- * the agent wrote just above it.
686
+ * Whether a sanitized row is real output rather than chrome; doubles as
687
+ * {@link readActivity}'s block-boundary test. Skips rows without a letter
688
+ * or digit, rows starting with a box bar / prompt chevron / `⎿`, and
689
+ * elapsed-time status footers ("✻ Churned for 17s", "Working… (8s · …)").
806
690
  */
807
691
  export function isMeaningfulActivity(text: string): boolean {
808
692
  if (!text) {