xtralab 0.14.1 → 0.15.1

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 (236) hide show
  1. package/README.md +10 -0
  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.d.ts +7 -0
  29. package/lib/customPanel/index.js +89 -0
  30. package/lib/editorBreadcrumbs/index.d.ts +3 -6
  31. package/lib/editorBreadcrumbs/index.js +10 -6
  32. package/lib/editorBreadcrumbs/widget.d.ts +17 -4
  33. package/lib/editorBreadcrumbs/widget.js +11 -17
  34. package/lib/editorIndent/index.d.ts +4 -12
  35. package/lib/editorIndent/index.js +8 -23
  36. package/lib/fileBrowser/commands.d.ts +28 -19
  37. package/lib/fileBrowser/commands.js +51 -98
  38. package/lib/fileBrowser/contents.d.ts +14 -0
  39. package/lib/fileBrowser/contents.js +28 -0
  40. package/lib/fileBrowser/dragAndDrop.d.ts +79 -0
  41. package/lib/fileBrowser/dragAndDrop.js +110 -0
  42. package/lib/fileBrowser/fileBrowser.d.ts +21 -9
  43. package/lib/fileBrowser/fileBrowser.js +211 -405
  44. package/lib/fileBrowser/gitStatus.d.ts +4 -9
  45. package/lib/fileBrowser/gitStatus.js +7 -17
  46. package/lib/fileBrowser/gitignore.d.ts +4 -17
  47. package/lib/fileBrowser/gitignore.js +4 -25
  48. package/lib/fileBrowser/icons.d.ts +7 -17
  49. package/lib/fileBrowser/icons.js +25 -80
  50. package/lib/fileBrowser/index.js +41 -10
  51. package/lib/fileBrowser/widget.d.ts +170 -53
  52. package/lib/fileBrowser/widget.js +242 -18
  53. package/lib/fileTypeIcons/index.js +0 -10
  54. package/lib/git/api.d.ts +5 -13
  55. package/lib/git/api.js +10 -36
  56. package/lib/git/askRequest.d.ts +4 -8
  57. package/lib/git/askRequest.js +6 -20
  58. package/lib/git/commands.d.ts +40 -5
  59. package/lib/git/commands.js +16 -10
  60. package/lib/git/diffModel.js +4 -0
  61. package/lib/git/diffSurface.d.ts +41 -55
  62. package/lib/git/diffSurface.js +42 -117
  63. package/lib/git/diffWidget.d.ts +81 -2
  64. package/lib/git/diffWidget.js +62 -12
  65. package/lib/git/imageDiff.d.ts +5 -6
  66. package/lib/git/imageDiff.js +18 -48
  67. package/lib/git/index.d.ts +0 -3
  68. package/lib/git/index.js +3 -8
  69. package/lib/git/notebookDiff.d.ts +110 -33
  70. package/lib/git/notebookDiff.js +52 -195
  71. package/lib/git/tokens.d.ts +73 -31
  72. package/lib/git/tokens.js +2 -3
  73. package/lib/highlight/index.d.ts +4 -10
  74. package/lib/highlight/index.js +10 -41
  75. package/lib/index.d.ts +0 -9
  76. package/lib/index.js +2 -9
  77. package/lib/launcher/agents.d.ts +55 -51
  78. package/lib/launcher/agents.js +25 -54
  79. package/lib/launcher/availability.d.ts +3 -9
  80. package/lib/launcher/availability.js +3 -9
  81. package/lib/launcher/commands.d.ts +7 -32
  82. package/lib/launcher/commands.js +14 -45
  83. package/lib/launcher/dashboard.d.ts +19 -24
  84. package/lib/launcher/dashboard.js +21 -67
  85. package/lib/launcher/editorRegistry.d.ts +10 -22
  86. package/lib/launcher/editorRegistry.js +14 -18
  87. package/lib/launcher/editors.d.ts +39 -45
  88. package/lib/launcher/editors.js +8 -34
  89. package/lib/launcher/icons.d.ts +0 -12
  90. package/lib/launcher/icons.js +14 -37
  91. package/lib/launcher/index.js +12 -55
  92. package/lib/launcher/invocation.d.ts +4 -10
  93. package/lib/launcher/invocation.js +7 -22
  94. package/lib/launcher/registry.d.ts +9 -7
  95. package/lib/launcher/registry.js +9 -7
  96. package/lib/launcher/schemaDefaults.d.ts +2 -9
  97. package/lib/launcher/schemaDefaults.js +37 -13
  98. package/lib/launcher/tokens.d.ts +10 -22
  99. package/lib/launcher/tokens.js +5 -8
  100. package/lib/menuBar/index.d.ts +3 -10
  101. package/lib/menuBar/index.js +11 -28
  102. package/lib/menus/index.d.ts +3 -9
  103. package/lib/menus/index.js +14 -30
  104. package/lib/omnibox/files.d.ts +2 -4
  105. package/lib/omnibox/files.js +5 -11
  106. package/lib/omnibox/index.d.ts +4 -14
  107. package/lib/omnibox/index.js +9 -27
  108. package/lib/omnibox/model.d.ts +38 -10
  109. package/lib/omnibox/model.js +15 -33
  110. package/lib/omnibox/recents.d.ts +15 -17
  111. package/lib/omnibox/recents.js +12 -17
  112. package/lib/omnibox/tokens.d.ts +5 -10
  113. package/lib/omnibox/tokens.js +2 -6
  114. package/lib/omnibox/widget.d.ts +11 -7
  115. package/lib/omnibox/widget.js +8 -16
  116. package/lib/searchReplace/index.d.ts +4 -20
  117. package/lib/searchReplace/index.js +7 -27
  118. package/lib/showOutput/index.d.ts +3 -12
  119. package/lib/showOutput/index.js +6 -22
  120. package/lib/sidebar/index.d.ts +4 -13
  121. package/lib/sidebar/index.js +73 -46
  122. package/lib/terminalNotifications/index.d.ts +4 -10
  123. package/lib/terminalNotifications/index.js +6 -20
  124. package/lib/terminals/agentTerminals.d.ts +12 -5
  125. package/lib/terminals/agentTerminals.js +25 -44
  126. package/lib/terminals/detection.d.ts +4 -8
  127. package/lib/terminals/detection.js +4 -8
  128. package/lib/terminals/index.d.ts +0 -33
  129. package/lib/terminals/index.js +24 -102
  130. package/lib/terminals/model.d.ts +64 -139
  131. package/lib/terminals/model.js +80 -220
  132. package/lib/terminals/tokens.d.ts +11 -22
  133. package/lib/terminals/tokens.js +2 -2
  134. package/lib/terminals/widget.d.ts +72 -38
  135. package/lib/terminals/widget.js +173 -73
  136. package/lib/topBar/icons.d.ts +3 -8
  137. package/lib/topBar/icons.js +3 -8
  138. package/lib/topBar/index.d.ts +4 -11
  139. package/lib/topBar/index.js +10 -38
  140. package/lib/walkthrough/index.d.ts +4 -9
  141. package/lib/walkthrough/index.js +6 -20
  142. package/lib/walkthrough/panel.d.ts +40 -9
  143. package/lib/walkthrough/panel.js +4 -10
  144. package/package.json +2 -2
  145. package/schema/launcher.json +2 -2
  146. package/schema/sidebar.json +47 -7
  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 +119 -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 +70 -98
  165. package/src/fileBrowser/contents.ts +33 -0
  166. package/src/fileBrowser/dragAndDrop.ts +188 -0
  167. package/src/fileBrowser/fileBrowser.tsx +259 -440
  168. package/src/fileBrowser/gitStatus.ts +7 -17
  169. package/src/fileBrowser/gitignore.ts +4 -25
  170. package/src/fileBrowser/icons.ts +25 -80
  171. package/src/fileBrowser/index.ts +43 -10
  172. package/src/fileBrowser/widget.tsx +344 -64
  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 +70 -10
  177. package/src/git/diffModel.ts +25 -0
  178. package/src/git/diffSurface.tsx +71 -159
  179. package/src/git/diffWidget.tsx +104 -14
  180. package/src/git/imageDiff.tsx +45 -51
  181. package/src/git/index.ts +3 -8
  182. package/src/git/notebookDiff.tsx +211 -238
  183. package/src/git/tokens.ts +73 -31
  184. package/src/highlight/index.ts +10 -41
  185. package/src/index.ts +2 -9
  186. package/src/launcher/agents.ts +74 -92
  187. package/src/launcher/availability.ts +3 -9
  188. package/src/launcher/commands.ts +14 -51
  189. package/src/launcher/dashboard.tsx +49 -88
  190. package/src/launcher/editorRegistry.ts +21 -29
  191. package/src/launcher/editors.ts +39 -58
  192. package/src/launcher/icons.ts +15 -37
  193. package/src/launcher/index.ts +12 -55
  194. package/src/launcher/invocation.ts +7 -22
  195. package/src/launcher/registry.ts +9 -7
  196. package/src/launcher/schemaDefaults.ts +43 -13
  197. package/src/launcher/tokens.ts +10 -22
  198. package/src/menuBar/index.ts +11 -28
  199. package/src/menus/index.ts +14 -30
  200. package/src/omnibox/files.ts +10 -14
  201. package/src/omnibox/index.ts +9 -27
  202. package/src/omnibox/model.ts +50 -38
  203. package/src/omnibox/recents.ts +17 -22
  204. package/src/omnibox/tokens.ts +5 -10
  205. package/src/omnibox/widget.tsx +14 -19
  206. package/src/searchReplace/index.ts +7 -27
  207. package/src/showOutput/index.ts +6 -22
  208. package/src/sidebar/index.ts +103 -52
  209. package/src/terminalNotifications/index.ts +42 -25
  210. package/src/terminals/agentTerminals.ts +25 -49
  211. package/src/terminals/detection.ts +4 -8
  212. package/src/terminals/index.ts +31 -102
  213. package/src/terminals/model.ts +130 -246
  214. package/src/terminals/tokens.ts +11 -22
  215. package/src/terminals/widget.tsx +229 -113
  216. package/src/topBar/icons.ts +3 -8
  217. package/src/topBar/index.ts +21 -44
  218. package/src/walkthrough/index.ts +6 -20
  219. package/src/walkthrough/panel.ts +40 -10
  220. package/style/about.css +2 -7
  221. package/style/askAgent.css +8 -40
  222. package/style/base.css +25 -73
  223. package/style/chrome.css +108 -308
  224. package/style/commandBar.css +4 -20
  225. package/style/customPanel.css +6 -0
  226. package/style/git.css +22 -184
  227. package/style/highlight.css +1 -3
  228. package/style/index.js +1 -0
  229. package/style/launcher.css +6 -70
  230. package/style/omnibox.css +2 -14
  231. package/style/showOutput.css +0 -6
  232. package/style/sidebar.css +29 -9
  233. package/style/tabs.css +5 -13
  234. package/style/terminals.css +18 -123
  235. package/style/topBar.css +2 -22
  236. package/style/walkthrough.css +0 -8
@@ -8,35 +8,23 @@ import type { SessionRegistry, TerminalWidget } from './model';
8
8
  import type { IAgentTerminalSession, IAgentTerminals } from './tokens';
9
9
 
10
10
  /**
11
- * Pause between pasting the prompt and pressing Enter, so the two reach the
12
- * agent as separate PTY reads — the same shape as a human pasting and then
13
- * submitting. Sent back to back, the server can coalesce both websocket
14
- * messages into one read and the TUI would have to split the paste from the
15
- * trailing Enter inside a single chunk.
11
+ * Pause between pasting the prompt and pressing Enter so the server cannot
12
+ * coalesce both websocket messages into one PTY read.
16
13
  */
17
14
  const PASTE_SUBMIT_DELAY_MS = 150;
18
15
 
19
16
  /**
20
- * Bracketed-paste markers (`CSI 200~` / `CSI 201~`). The prompt is wrapped
21
- * in these so the agent's TUI treats it as one pasted block — newlines
22
- * insert into its input box instead of submitting the message at each line
23
- * break. Every send target is a terminal where detection just confirmed a
24
- * running coding agent, and the agent TUIs keep bracketed-paste mode on.
17
+ * Bracketed-paste markers (`CSI 200~` / `CSI 201~`): a wrapped prompt arrives
18
+ * as one pasted block, so newlines insert instead of submitting at each break.
25
19
  */
26
20
  const PASTE_OPEN = '\x1b[200~';
27
21
  const PASTE_CLOSE = '\x1b[201~';
28
22
 
29
- /**
30
- * How long to wait for the session's websocket to reach `connected` before
31
- * failing the send, so a server hiccup surfaces as an error notification
32
- * rather than a silent hang.
33
- */
34
23
  const CONNECT_TIMEOUT_MS = 10_000;
35
24
 
36
25
  /**
37
26
  * Resolve once `session` reports `connected`; reject with `timeoutMessage`
38
- * after {@link CONNECT_TIMEOUT_MS}. Matches how the launcher waits before
39
- * typing into a fresh terminal — writes sent while the websocket is still
27
+ * after {@link CONNECT_TIMEOUT_MS} — writes sent while the websocket is still
40
28
  * connecting disappear silently.
41
29
  */
42
30
  function waitUntilConnected(
@@ -67,11 +55,8 @@ function waitUntilConnected(
67
55
 
68
56
  /**
69
57
  * The {@link IAgentTerminals} implementation: a read-only view over the
70
- * terminals panel's {@link SessionRegistry} narrowed to sessions with a
71
- * detection-confirmed coding agent, plus the "send a prompt into one"
72
- * action. Lives in the `xtralab:terminals` plugin so the ask-agent popup
73
- * (and any future caller) can target running agents without duplicating the
74
- * detection plumbing.
58
+ * panel's {@link SessionRegistry} narrowed to sessions with a
59
+ * detection-confirmed coding agent, plus the "send a prompt into one" action.
75
60
  */
76
61
  export class AgentTerminals implements IAgentTerminals {
77
62
  constructor(options: AgentTerminals.IOptions) {
@@ -84,6 +69,9 @@ export class AgentTerminals implements IAgentTerminals {
84
69
  this._registry.stateChanged.connect(this._onRegistryStateChanged, this);
85
70
  }
86
71
 
72
+ /**
73
+ * Snapshot of the sessions with a detection-confirmed running agent.
74
+ */
87
75
  sessions(): IAgentTerminalSession[] {
88
76
  const result: IAgentTerminalSession[] = [];
89
77
  for (const name of this._registry.sessionNames()) {
@@ -101,15 +89,20 @@ export class AgentTerminals implements IAgentTerminals {
101
89
  return result;
102
90
  }
103
91
 
92
+ /**
93
+ * A signal emitted whenever the {@link sessions} snapshot may have changed.
94
+ */
104
95
  get changed(): ISignal<IAgentTerminals, void> {
105
96
  return this._changed;
106
97
  }
107
98
 
99
+ /**
100
+ * Paste the prompt into the named session and press Enter, re-validating
101
+ * with the server first that an agent still runs there.
102
+ */
108
103
  async sendPrompt(name: string, prompt: string): Promise<void> {
109
- // Re-validate against the server rather than trusting the caller's
110
- // (possibly seconds-old) snapshot: the prompt is prose, and prose pasted
111
- // into the shell prompt left behind by an exited agent would be
112
- // *executed* on the Enter below.
104
+ // Re-validate against the server: prose pasted into the shell prompt an
105
+ // exited agent left behind would be *executed* on the Enter below.
113
106
  await this._terminals.refreshRunning();
114
107
  const alive = Array.from(this._terminals.running()).some(
115
108
  model => model.name === name
@@ -119,9 +112,8 @@ export class AgentTerminals implements IAgentTerminals {
119
112
  }
120
113
  const detected = await fetchRunningAgents(this._detectCommands());
121
114
  if (detected === null) {
122
- // Detection being unavailable (older server, transient failure) is
123
- // not proof the agent is gone; refuse the write with a message that
124
- // invites the retry that would succeed.
115
+ // Detection unavailable (older server, transient failure) is not proof
116
+ // the agent is gone; refuse the write and invite a retry.
125
117
  throw new Error(
126
118
  this._trans.__('Could not verify the terminal — try sending again.')
127
119
  );
@@ -133,11 +125,6 @@ export class AgentTerminals implements IAgentTerminals {
133
125
  );
134
126
  }
135
127
 
136
- // Deliver in the background: write straight to the session over its
137
- // websocket instead of revealing its tab, so focus stays wherever the
138
- // user is asking from. An open tab's live connection is reused; a
139
- // closed tab gets a short-lived client connection to the running server
140
- // session, without creating any widget.
141
128
  const widget = this._findWidget(name);
142
129
  const session =
143
130
  widget !== null
@@ -148,12 +135,8 @@ export class AgentTerminals implements IAgentTerminals {
148
135
  session,
149
136
  this._trans.__('Timed out connecting to the terminal.')
150
137
  );
151
- // Wrapped and newline-normalised exactly like xterm's own paste
152
- // handling. Writing the markers directly (rather than through an open
153
- // tab's `Terminal.paste`) also sidesteps the one case paste() gets
154
- // wrong: a freshly reconnected xterm whose replayed scrollback no
155
- // longer holds the agent's original mode-setting escape, which makes
156
- // paste() skip the wrapping the application still expects.
138
+ // Writing the markers directly (not `Terminal.paste`) sidesteps paste()
139
+ // skipping them on a reconnected xterm that lost the mode-setting escape.
157
140
  session.send({
158
141
  type: 'stdin',
159
142
  content: [PASTE_OPEN + prompt.replace(/\r?\n/g, '\r') + PASTE_CLOSE]
@@ -162,16 +145,14 @@ export class AgentTerminals implements IAgentTerminals {
162
145
  window.setTimeout(resolve, PASTE_SUBMIT_DELAY_MS)
163
146
  );
164
147
  if (session.isDisposed) {
165
- // The tab closed during the pause; the pasted text already reached
166
- // the agent's input box but Enter can no longer be delivered here.
167
148
  throw new Error(
168
149
  this._trans.__('The terminal closed before the prompt was submitted.')
169
150
  );
170
151
  }
171
152
  session.send({ type: 'stdin', content: ['\r'] });
172
153
  } finally {
173
- // Close a connection we opened just for this send. Disposing the
174
- // client connection leaves the server session (and its agent) running.
154
+ // Disposing an ad-hoc client connection leaves the server session
155
+ // (and its agent) running.
175
156
  if (widget === null && !session.isDisposed) {
176
157
  session.dispose();
177
158
  }
@@ -209,27 +190,22 @@ export namespace AgentTerminals {
209
190
  * The panel's session registry — the source of the session snapshot.
210
191
  */
211
192
  registry: SessionRegistry;
212
-
213
193
  /**
214
194
  * Tracker of open terminal widgets, to reuse an open tab's connection.
215
195
  */
216
196
  tracker: ITerminalTracker;
217
-
218
197
  /**
219
198
  * The terminal session manager, for validation and ad-hoc connections.
220
199
  */
221
200
  terminals: Terminal.IManager;
222
-
223
201
  /**
224
202
  * Names to detect — same list the registry polls with.
225
203
  */
226
204
  detectCommands: () => string[];
227
-
228
205
  /**
229
206
  * Whether a detected command belongs to a coding agent (not an editor).
230
207
  */
231
208
  isAgentCommand: (command: string) => boolean;
232
-
233
209
  /**
234
210
  * Translation bundle for the error messages thrown to callers.
235
211
  */
@@ -2,14 +2,10 @@ import { URLExt } from '@jupyterlab/coreutils';
2
2
  import { ServerConnection } from '@jupyterlab/services';
3
3
 
4
4
  /**
5
- * Ask the xtralab server which of `commands` is currently running inside each
6
- * open terminal session. Resolves to a map from session name to the matched
7
- * agent command, or `null` for a session sitting at its prompt.
8
- *
9
- * On any failure (endpoint missing on an older server, network error,
10
- * malformed response) the whole result is `null` — callers should treat that
11
- * as "detection unavailable" and fall back to the optimistic launch tags
12
- * rather than clearing any badges.
5
+ * Ask the xtralab server which of `commands` runs in each open terminal
6
+ * session: a map from session name to matched command (`null` = idle). Any
7
+ * failure resolves the whole result to `null` — "detection unavailable",
8
+ * not "no agents"; callers must not clear badges on it.
13
9
  */
14
10
  export async function fetchRunningAgents(
15
11
  commands: string[]
@@ -3,7 +3,11 @@ import {
3
3
  JupyterFrontEnd,
4
4
  JupyterFrontEndPlugin
5
5
  } from '@jupyterlab/application';
6
- import { Dialog, showDialog } from '@jupyterlab/apputils';
6
+ import {
7
+ Dialog,
8
+ IMovableSectionRegistry,
9
+ showDialog
10
+ } from '@jupyterlab/apputils';
7
11
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
8
12
  import { ITerminalTracker } from '@jupyterlab/terminal';
9
13
  import { ITranslator, nullTranslator } from '@jupyterlab/translation';
@@ -19,39 +23,6 @@ import { RunningTerminals } from './widget';
19
23
 
20
24
  const PLUGIN_ID = 'xtralab:terminals';
21
25
 
22
- /**
23
- * Adds a left-sidebar panel that lists every running terminal session.
24
- *
25
- * Each row is labelled with the *real* title the running program
26
- * published — the launcher's agent name, or whatever title an xterm
27
- * escape sequence set — which the registry caches so it survives the
28
- * user closing the tab while the session keeps running on the server.
29
- * JupyterLab's built-in "Running Terminals and Kernels" panel lists the
30
- * same sessions but only by their `terminals/<n>` name, so this panel
31
- * resolves and caches the published title to show something meaningful.
32
- * Rows running a coding agent carry a smaller second line below the
33
- * title with the agent's latest line of output, so the panel shows what
34
- * each agent is doing at a glance — on by default, and switchable off
35
- * through the `showAgentActivity` setting.
36
- *
37
- * Clicking a row activates the existing tab if one is open, or reopens
38
- * the session in a fresh terminal widget (`terminal:open`). The inline
39
- * `×` button shuts the session down on the server. The header carries a
40
- * `+` button and a stop button (the latter shuts every session down at
41
- * once, after a confirmation).
42
- *
43
- * The `+` button drops down a menu — built from the launcher's shared
44
- * `IAgentRegistry` — listing each available agent (Claude, Codex, …)
45
- * followed by a plain terminal, so starting an agent session is as
46
- * consistent here as in the launcher and reuses the very same registered
47
- * commands and icons. When the launcher is disabled or no agents are
48
- * installed, the button falls back to opening a plain terminal directly.
49
- *
50
- * The plugin also provides {@link IAgentTerminals} — the sessions with a
51
- * detection-confirmed running agent, plus a "paste a prompt into one"
52
- * action — which the ask-agent popup uses to offer running agents as
53
- * targets next to "new terminal".
54
- */
55
26
  const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
56
27
  id: PLUGIN_ID,
57
28
  description:
@@ -65,7 +36,8 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
65
36
  IAgentRegistry,
66
37
  IEditorRegistry,
67
38
  IAgentSessions,
68
- ISettingRegistry
39
+ ISettingRegistry,
40
+ IMovableSectionRegistry
69
41
  ],
70
42
  activate: (
71
43
  app: JupyterFrontEnd,
@@ -75,17 +47,13 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
75
47
  agentRegistry: IAgentRegistry | null,
76
48
  editorRegistry: IEditorRegistry | null,
77
49
  agentSessions: IAgentSessions | null,
78
- settingRegistry: ISettingRegistry | null
50
+ settingRegistry: ISettingRegistry | null,
51
+ movableSections: IMovableSectionRegistry | null
79
52
  ): IAgentTerminals => {
80
53
  const trans = (translator ?? nullTranslator).load('jupyterlab');
81
54
 
82
- // Resolve a running agent/editor identifier to an icon: the matching
83
- // agent's logo, the matching editor's logo (Neovim/Vim), or the plain
84
- // terminal icon when nothing recognised is running. Detection reports
85
- // either the configured `command` or the canonical `id` (see
86
- // `detectCommands`), so a row whose command is an alias — e.g. `ccm` for
87
- // `claude` — still resolves to the right logo via its id. Reads the
88
- // registries live so it tracks settings changes. Used to badge each row.
55
+ // Detection reports either the configured `command` or the canonical `id`,
56
+ // so an aliased command (e.g. `ccm` for `claude`) still maps to its logo.
89
57
  const iconForCommand = (command: string | null): LabIcon => {
90
58
  if (!command) {
91
59
  return terminalIcon;
@@ -96,21 +64,12 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
96
64
  if (agent) {
97
65
  return agent.icon;
98
66
  }
99
- // A terminal running an editor (Neovim/Vim, or a user-configured one) is
100
- // badged with its logo, the same as an agent.
101
67
  const editor = editorRegistry?.editors.find(
102
68
  e => e.command === command || e.id === command
103
69
  );
104
70
  return editor?.icon ?? terminalIcon;
105
71
  };
106
72
 
107
- // The names the server should look for in each terminal's process tree:
108
- // for every agent and editor, its configured `command` plus its canonical
109
- // `id`. The id is the real CLI name (e.g. `claude`, `nvim`), so an agent
110
- // whose command points at an alias — e.g. `ccm` running `claude` — is
111
- // still recognised by the process it actually spawns. Read live so the
112
- // list tracks settings changes; deduplicated since command and id usually
113
- // coincide.
114
73
  const detectCommands = (): string[] => {
115
74
  const names = new Set<string>();
116
75
  for (const agent of agentRegistry?.agents ?? []) {
@@ -124,10 +83,6 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
124
83
  return Array.from(names);
125
84
  };
126
85
 
127
- // Whether a detected command/id belongs to a coding agent rather than an
128
- // editor. Only agent sessions get a latest-activity line (editor
129
- // terminals stay badge-only), and only they are offered as prompt
130
- // targets through `IAgentTerminals`.
131
86
  const isAgentCommand = (command: string): boolean =>
132
87
  agentRegistry?.agents.some(
133
88
  a => a.command === command || a.id === command
@@ -136,19 +91,14 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
136
91
  const registry = new SessionRegistry({
137
92
  serviceManager: app.serviceManager,
138
93
  tracker,
139
- // The shell tells the registry which widget is current, so the panel can
140
- // highlight the terminal that is the current main-area widget (and
141
- // nothing while a notebook or other widget is current instead).
142
94
  shell: app.shell,
143
95
  agentSessions,
144
96
  detectCommands,
145
97
  isAgentCommand
146
98
  });
147
99
 
148
- // Mirror the agent/editor detected in each open terminal onto its main-area
149
- // tab icon, so the tab matches its sidebar row. Re-running on every
150
- // `stateChanged` is safe: `Title.icon` ignores an unchanged assignment, so
151
- // the writes neither loop nor churn.
100
+ // `Title.icon` ignores an unchanged assignment, so re-running per
101
+ // stateChanged is cheap.
152
102
  const syncTabIcons = (): void => {
153
103
  tracker.forEach(widget => {
154
104
  const session = widget.content.session;
@@ -164,10 +114,8 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
164
114
  syncTabIcons();
165
115
 
166
116
  const onActivate = (name: string): void => {
167
- // `terminal:open` is the upstream entry point that handles both
168
- // sides of this: if a widget is already attached to the named
169
- // session it activates that tab, otherwise it spins up a fresh
170
- // widget connected to the live session.
117
+ // `terminal:open` activates an attached tab or spins up a fresh widget
118
+ // connected to the live session.
171
119
  void app.commands.execute('terminal:open', { name });
172
120
  };
173
121
 
@@ -180,10 +128,6 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
180
128
  };
181
129
 
182
130
  const onShutdownAll = (): void => {
183
- // Tearing down every session at once is hard to undo (each may be a
184
- // running agent), so confirm before calling the manager's bulk
185
- // shutdown. `runningChanged` then clears the list and the panel
186
- // re-renders empty.
187
131
  void showDialog({
188
132
  title: trans.__('Shut Down All Terminals?'),
189
133
  body: trans.__(
@@ -206,19 +150,8 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
206
150
  });
207
151
  };
208
152
 
209
- // The header "+" button. With the launcher's agent registry available
210
- // it drops down a menu of the available agents (reusing their registered
211
- // `xtralab:start-agent:<id>` commands, so the icons and labels match the
212
- // launcher exactly) followed by a separator and a plain terminal. Built
213
- // lazily and repopulated on each open so it always reflects the current,
214
- // settings-driven agent list. Without the registry (launcher disabled)
215
- // or with no agents installed, there is nothing to choose between, so
216
- // the button opens a plain terminal directly in one click.
217
- //
218
- // `MenuSvg` (not the bare Lumino `Menu`) is what every JupyterLab menu
219
- // uses: its renderer applies the `menuItem` LabIcon stylesheet, which
220
- // sizes the agent icons to 16px and centers them vertically — the bare
221
- // `Menu` renders the raw, oversized, misaligned SVGs.
153
+ // `MenuSvg`, not the bare Lumino `Menu`: its renderer applies the `menuItem`
154
+ // LabIcon stylesheet — the bare menu renders raw, oversized SVGs.
222
155
  let newMenu: MenuSvg | null = null;
223
156
  const onCreate = (anchor: { x: number; y: number }): void => {
224
157
  const agents = agentRegistry?.agents ?? [];
@@ -248,23 +181,23 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
248
181
  onCreate
249
182
  });
250
183
 
251
- // Added to the left sidebar. The shipped `layout` setting in
252
- // jupyter-config/labconfig gives each sidebar widget a default rank —
253
- // this Terminals panel ranks first, so running agents sit one click
254
- // away at the top. That setting assigns rank only and pins no area, so
255
- // the "Move Widget" context menu can relocate the panel and the layout
256
- // restorer remembers where the user puts it. The `rank` here is the
257
- // in-code fallback used when the setting is absent. Sessions restored
258
- // from a previous lab run land via `runningChanged` once the terminal
259
- // manager is ready, which the panel's `UseSignal` picks up.
184
+ // The shipped labconfig `layout` setting ranks this panel first (rank only,
185
+ // no area pin, so "Move Widget" works); this rank is the fallback.
260
186
  app.shell.add(panel, 'left', { rank: 1 });
261
187
  if (restorer) {
262
188
  restorer.add(panel, panel.id);
263
189
  }
264
190
 
265
- // The latest-activity line under each agent row is on by default; honour
266
- // the `showAgentActivity` setting so it can be turned off. Read on load and
267
- // re-applied on every change.
191
+ void app.restored.then(() => {
192
+ panel.expandOwnSection();
193
+ });
194
+
195
+ if (movableSections) {
196
+ movableSections.registerSource(PLUGIN_ID, trans.__('Terminals'), panel);
197
+ movableSections.registerTarget(PLUGIN_ID, trans.__('Terminals'), panel);
198
+ panel.announceSections();
199
+ }
200
+
268
201
  if (settingRegistry) {
269
202
  settingRegistry
270
203
  .load(PLUGIN_ID)
@@ -275,9 +208,8 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
275
208
  );
276
209
  };
277
210
  applyActivitySetting();
278
- // Bind the slot to the panel so disposing the panel (which clears
279
- // its signal data) drops the connection — the disposed registry is
280
- // then never reached on a later settings change.
211
+ // Bound to the panel so disposing it drops the connection — the
212
+ // disposed registry is never reached on a later settings change.
281
213
  settings.changed.connect(applyActivitySetting, panel);
282
214
  })
283
215
  .catch(reason => {
@@ -299,9 +231,6 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
299
231
  }
300
232
  };
301
233
 
302
- /**
303
- * Read a boolean setting value, falling back when it is missing or invalid.
304
- */
305
234
  function boolOption(value: unknown, fallback: boolean): boolean {
306
235
  return typeof value === 'boolean' ? value : fallback;
307
236
  }