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
@@ -12,16 +12,10 @@ const OSC_ITERM2_GROWL = 9;
12
12
  const OSC_RXVT_NOTIFY = 777;
13
13
  const MAX_TEXT_LENGTH = 256;
14
14
  /**
15
- * Turns the notifications coding agents already emit (OSC 9, OSC 777, the bell)
16
- * into desktop notifications. JupyterLab's xterm renders these sequences but
17
- * never forwards them to the OS, so this plugin hooks each terminal and bridges
18
- * them through `window.xtralab.notify` (desktop) or the web Notifications API.
19
- *
20
- * A notification is suppressed while its terminal is the focused, active tab,
21
- * and throttled per terminal. The desktop shell advertises
22
- * `TERM_PROGRAM=iTerm.app` so agents emit OSC 9 in the first place, and the
23
- * session name is forwarded so clicking a notification focuses the terminal
24
- * that fired it.
15
+ * Turns the OSC 9 / OSC 777 / bell sequences agents emit into desktop
16
+ * notifications via `window.xtralab.notify` (desktop) or the web Notifications
17
+ * API — JupyterLab's xterm renders them but never forwards them to the OS.
18
+ * The desktop shell advertises `TERM_PROGRAM=iTerm.app` so agents emit OSC 9.
25
19
  */
26
20
  const plugin = {
27
21
  id: PLUGIN_ID,
@@ -34,12 +28,8 @@ const plugin = {
34
28
  const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
35
29
  let enabled = true;
36
30
  let notifyOnBell = true;
37
- // Per-widget throttle timestamps and xterm hooks. WeakMaps let disposed
38
- // widgets be collected; hooks are torn down when a tab closes.
39
31
  const lastNotified = new WeakMap();
40
32
  const hooks = new WeakMap();
41
- // True when the user is already looking at this terminal, so a banner would
42
- // just be noise.
43
33
  const isActivelyViewing = (widget) => document.hasFocus() && app.shell.currentWidget === widget;
44
34
  const deliver = (title, body, session) => {
45
35
  const bridge = window.xtralab;
@@ -49,7 +39,6 @@ const plugin = {
49
39
  });
50
40
  return;
51
41
  }
52
- // Plain-browser fallback (pip install): the Web Notifications API.
53
42
  if (typeof Notification === 'undefined') {
54
43
  return;
55
44
  }
@@ -82,9 +71,8 @@ const plugin = {
82
71
  // it, so it reads well as the title for body-only sequences (OSC 9, bell).
83
72
  const labelOf = (widget) => widget.title.label || trans.__('Terminal');
84
73
  const onOsc9 = (widget, data) => {
85
- // OSC 9 is overloaded: ConEmu/Windows Terminal use `9 ; <n> ; …`
86
- // subcommands (e.g. `9 ; 4` progress), so skip a numeric-subcommand
87
- // payload and leave it unconsumed rather than treat it as a notification.
74
+ // OSC 9 is overloaded: ConEmu/Windows Terminal use numeric subcommands
75
+ // (e.g. `9 ; 4` progress); leave those unconsumed.
88
76
  if (/^\d+(;|$)/.test(data)) {
89
77
  return false;
90
78
  }
@@ -129,8 +117,6 @@ const plugin = {
129
117
  console.warn('xtralab: xterm internals not found; terminal notifications are disabled for this terminal');
130
118
  return;
131
119
  }
132
- // Handlers are wrapped so a parse slip cannot break xterm's parser; an
133
- // OSC handler returns whether it consumed the sequence.
134
120
  const disposables = [
135
121
  term.onBell(() => guard(() => onBell(widget))),
136
122
  term.parser.registerOscHandler(OSC_ITERM2_GROWL, data => guardOsc(() => onOsc9(widget, data))),
@@ -6,16 +6,23 @@ import type { SessionRegistry } from './model';
6
6
  import type { IAgentTerminalSession, IAgentTerminals } from './tokens';
7
7
  /**
8
8
  * The {@link IAgentTerminals} implementation: a read-only view over the
9
- * terminals panel's {@link SessionRegistry} narrowed to sessions with a
10
- * detection-confirmed coding agent, plus the "send a prompt into one"
11
- * action. Lives in the `xtralab:terminals` plugin so the ask-agent popup
12
- * (and any future caller) can target running agents without duplicating the
13
- * detection plumbing.
9
+ * panel's {@link SessionRegistry} narrowed to sessions with a
10
+ * detection-confirmed coding agent, plus the "send a prompt into one" action.
14
11
  */
15
12
  export declare class AgentTerminals implements IAgentTerminals {
16
13
  constructor(options: AgentTerminals.IOptions);
14
+ /**
15
+ * Snapshot of the sessions with a detection-confirmed running agent.
16
+ */
17
17
  sessions(): IAgentTerminalSession[];
18
+ /**
19
+ * A signal emitted whenever the {@link sessions} snapshot may have changed.
20
+ */
18
21
  get changed(): ISignal<IAgentTerminals, void>;
22
+ /**
23
+ * Paste the prompt into the named session and press Enter, re-validating
24
+ * with the server first that an agent still runs there.
25
+ */
19
26
  sendPrompt(name: string, prompt: string): Promise<void>;
20
27
  private _findWidget;
21
28
  private _onRegistryStateChanged;
@@ -1,32 +1,20 @@
1
1
  import { Signal } from '@lumino/signaling';
2
2
  import { fetchRunningAgents } from './detection';
3
3
  /**
4
- * Pause between pasting the prompt and pressing Enter, so the two reach the
5
- * agent as separate PTY reads — the same shape as a human pasting and then
6
- * submitting. Sent back to back, the server can coalesce both websocket
7
- * messages into one read and the TUI would have to split the paste from the
8
- * trailing Enter inside a single chunk.
4
+ * Pause between pasting the prompt and pressing Enter so the server cannot
5
+ * coalesce both websocket messages into one PTY read.
9
6
  */
10
7
  const PASTE_SUBMIT_DELAY_MS = 150;
11
8
  /**
12
- * Bracketed-paste markers (`CSI 200~` / `CSI 201~`). The prompt is wrapped
13
- * in these so the agent's TUI treats it as one pasted block — newlines
14
- * insert into its input box instead of submitting the message at each line
15
- * break. Every send target is a terminal where detection just confirmed a
16
- * running coding agent, and the agent TUIs keep bracketed-paste mode on.
9
+ * Bracketed-paste markers (`CSI 200~` / `CSI 201~`): a wrapped prompt arrives
10
+ * as one pasted block, so newlines insert instead of submitting at each break.
17
11
  */
18
12
  const PASTE_OPEN = '\x1b[200~';
19
13
  const PASTE_CLOSE = '\x1b[201~';
20
- /**
21
- * How long to wait for the session's websocket to reach `connected` before
22
- * failing the send, so a server hiccup surfaces as an error notification
23
- * rather than a silent hang.
24
- */
25
14
  const CONNECT_TIMEOUT_MS = 10000;
26
15
  /**
27
16
  * Resolve once `session` reports `connected`; reject with `timeoutMessage`
28
- * after {@link CONNECT_TIMEOUT_MS}. Matches how the launcher waits before
29
- * typing into a fresh terminal — writes sent while the websocket is still
17
+ * after {@link CONNECT_TIMEOUT_MS} — writes sent while the websocket is still
30
18
  * connecting disappear silently.
31
19
  */
32
20
  function waitUntilConnected(session, timeoutMessage) {
@@ -53,11 +41,8 @@ function waitUntilConnected(session, timeoutMessage) {
53
41
  }
54
42
  /**
55
43
  * The {@link IAgentTerminals} implementation: a read-only view over the
56
- * terminals panel's {@link SessionRegistry} narrowed to sessions with a
57
- * detection-confirmed coding agent, plus the "send a prompt into one"
58
- * action. Lives in the `xtralab:terminals` plugin so the ask-agent popup
59
- * (and any future caller) can target running agents without duplicating the
60
- * detection plumbing.
44
+ * panel's {@link SessionRegistry} narrowed to sessions with a
45
+ * detection-confirmed coding agent, plus the "send a prompt into one" action.
61
46
  */
62
47
  export class AgentTerminals {
63
48
  constructor(options) {
@@ -70,6 +55,9 @@ export class AgentTerminals {
70
55
  this._trans = options.trans;
71
56
  this._registry.stateChanged.connect(this._onRegistryStateChanged, this);
72
57
  }
58
+ /**
59
+ * Snapshot of the sessions with a detection-confirmed running agent.
60
+ */
73
61
  sessions() {
74
62
  const result = [];
75
63
  for (const name of this._registry.sessionNames()) {
@@ -86,14 +74,19 @@ export class AgentTerminals {
86
74
  }
87
75
  return result;
88
76
  }
77
+ /**
78
+ * A signal emitted whenever the {@link sessions} snapshot may have changed.
79
+ */
89
80
  get changed() {
90
81
  return this._changed;
91
82
  }
83
+ /**
84
+ * Paste the prompt into the named session and press Enter, re-validating
85
+ * with the server first that an agent still runs there.
86
+ */
92
87
  async sendPrompt(name, prompt) {
93
- // Re-validate against the server rather than trusting the caller's
94
- // (possibly seconds-old) snapshot: the prompt is prose, and prose pasted
95
- // into the shell prompt left behind by an exited agent would be
96
- // *executed* on the Enter below.
88
+ // Re-validate against the server: prose pasted into the shell prompt an
89
+ // exited agent left behind would be *executed* on the Enter below.
97
90
  await this._terminals.refreshRunning();
98
91
  const alive = Array.from(this._terminals.running()).some(model => model.name === name);
99
92
  if (!alive) {
@@ -101,47 +94,35 @@ export class AgentTerminals {
101
94
  }
102
95
  const detected = await fetchRunningAgents(this._detectCommands());
103
96
  if (detected === null) {
104
- // Detection being unavailable (older server, transient failure) is
105
- // not proof the agent is gone; refuse the write with a message that
106
- // invites the retry that would succeed.
97
+ // Detection unavailable (older server, transient failure) is not proof
98
+ // the agent is gone; refuse the write and invite a retry.
107
99
  throw new Error(this._trans.__('Could not verify the terminal — try sending again.'));
108
100
  }
109
101
  const command = detected[name];
110
102
  if (typeof command !== 'string' || !this._isAgentCommand(command)) {
111
103
  throw new Error(this._trans.__('No agent is running in that terminal anymore.'));
112
104
  }
113
- // Deliver in the background: write straight to the session over its
114
- // websocket instead of revealing its tab, so focus stays wherever the
115
- // user is asking from. An open tab's live connection is reused; a
116
- // closed tab gets a short-lived client connection to the running server
117
- // session, without creating any widget.
118
105
  const widget = this._findWidget(name);
119
106
  const session = widget !== null
120
107
  ? widget.content.session
121
108
  : this._terminals.connectTo({ model: { name } });
122
109
  try {
123
110
  await waitUntilConnected(session, this._trans.__('Timed out connecting to the terminal.'));
124
- // Wrapped and newline-normalised exactly like xterm's own paste
125
- // handling. Writing the markers directly (rather than through an open
126
- // tab's `Terminal.paste`) also sidesteps the one case paste() gets
127
- // wrong: a freshly reconnected xterm whose replayed scrollback no
128
- // longer holds the agent's original mode-setting escape, which makes
129
- // paste() skip the wrapping the application still expects.
111
+ // Writing the markers directly (not `Terminal.paste`) sidesteps paste()
112
+ // skipping them on a reconnected xterm that lost the mode-setting escape.
130
113
  session.send({
131
114
  type: 'stdin',
132
115
  content: [PASTE_OPEN + prompt.replace(/\r?\n/g, '\r') + PASTE_CLOSE]
133
116
  });
134
117
  await new Promise(resolve => window.setTimeout(resolve, PASTE_SUBMIT_DELAY_MS));
135
118
  if (session.isDisposed) {
136
- // The tab closed during the pause; the pasted text already reached
137
- // the agent's input box but Enter can no longer be delivered here.
138
119
  throw new Error(this._trans.__('The terminal closed before the prompt was submitted.'));
139
120
  }
140
121
  session.send({ type: 'stdin', content: ['\r'] });
141
122
  }
142
123
  finally {
143
- // Close a connection we opened just for this send. Disposing the
144
- // client connection leaves the server session (and its agent) running.
124
+ // Disposing an ad-hoc client connection leaves the server session
125
+ // (and its agent) running.
145
126
  if (widget === null && !session.isDisposed) {
146
127
  session.dispose();
147
128
  }
@@ -1,11 +1,7 @@
1
1
  /**
2
- * Ask the xtralab server which of `commands` is currently running inside each
3
- * open terminal session. Resolves to a map from session name to the matched
4
- * agent command, or `null` for a session sitting at its prompt.
5
- *
6
- * On any failure (endpoint missing on an older server, network error,
7
- * malformed response) the whole result is `null` — callers should treat that
8
- * as "detection unavailable" and fall back to the optimistic launch tags
9
- * rather than clearing any badges.
2
+ * Ask the xtralab server which of `commands` runs in each open terminal
3
+ * session: a map from session name to matched command (`null` = idle). Any
4
+ * failure resolves the whole result to `null` — "detection unavailable",
5
+ * not "no agents"; callers must not clear badges on it.
10
6
  */
11
7
  export declare function fetchRunningAgents(commands: string[]): Promise<Record<string, string | null> | null>;
@@ -1,14 +1,10 @@
1
1
  import { URLExt } from '@jupyterlab/coreutils';
2
2
  import { ServerConnection } from '@jupyterlab/services';
3
3
  /**
4
- * Ask the xtralab server which of `commands` is currently running inside each
5
- * open terminal session. Resolves to a map from session name to the matched
6
- * agent command, or `null` for a session sitting at its prompt.
7
- *
8
- * On any failure (endpoint missing on an older server, network error,
9
- * malformed response) the whole result is `null` — callers should treat that
10
- * as "detection unavailable" and fall back to the optimistic launch tags
11
- * rather than clearing any badges.
4
+ * Ask the xtralab server which of `commands` runs in each open terminal
5
+ * session: a map from session name to matched command (`null` = idle). Any
6
+ * failure resolves the whole result to `null` — "detection unavailable",
7
+ * not "no agents"; callers must not clear badges on it.
12
8
  */
13
9
  export async function fetchRunningAgents(commands) {
14
10
  if (commands.length === 0) {
@@ -1,37 +1,4 @@
1
1
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
2
  import { IAgentTerminals } from './tokens';
3
- /**
4
- * Adds a left-sidebar panel that lists every running terminal session.
5
- *
6
- * Each row is labelled with the *real* title the running program
7
- * published — the launcher's agent name, or whatever title an xterm
8
- * escape sequence set — which the registry caches so it survives the
9
- * user closing the tab while the session keeps running on the server.
10
- * JupyterLab's built-in "Running Terminals and Kernels" panel lists the
11
- * same sessions but only by their `terminals/<n>` name, so this panel
12
- * resolves and caches the published title to show something meaningful.
13
- * Rows running a coding agent carry a smaller second line below the
14
- * title with the agent's latest line of output, so the panel shows what
15
- * each agent is doing at a glance — on by default, and switchable off
16
- * through the `showAgentActivity` setting.
17
- *
18
- * Clicking a row activates the existing tab if one is open, or reopens
19
- * the session in a fresh terminal widget (`terminal:open`). The inline
20
- * `×` button shuts the session down on the server. The header carries a
21
- * `+` button and a stop button (the latter shuts every session down at
22
- * once, after a confirmation).
23
- *
24
- * The `+` button drops down a menu — built from the launcher's shared
25
- * `IAgentRegistry` — listing each available agent (Claude, Codex, …)
26
- * followed by a plain terminal, so starting an agent session is as
27
- * consistent here as in the launcher and reuses the very same registered
28
- * commands and icons. When the launcher is disabled or no agents are
29
- * installed, the button falls back to opening a plain terminal directly.
30
- *
31
- * The plugin also provides {@link IAgentTerminals} — the sessions with a
32
- * detection-confirmed running agent, plus a "paste a prompt into one"
33
- * action — which the ask-agent popup uses to offer running agents as
34
- * targets next to "new terminal".
35
- */
36
3
  declare const plugin: JupyterFrontEndPlugin<IAgentTerminals>;
37
4
  export default plugin;
@@ -1,5 +1,5 @@
1
1
  import { ILayoutRestorer } from '@jupyterlab/application';
2
- import { Dialog, showDialog } from '@jupyterlab/apputils';
2
+ import { Dialog, IMovableSectionRegistry, showDialog } from '@jupyterlab/apputils';
3
3
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
4
4
  import { ITerminalTracker } from '@jupyterlab/terminal';
5
5
  import { ITranslator, nullTranslator } from '@jupyterlab/translation';
@@ -12,39 +12,6 @@ import { SessionRegistry } from './model';
12
12
  import { IAgentTerminals } from './tokens';
13
13
  import { RunningTerminals } from './widget';
14
14
  const PLUGIN_ID = 'xtralab:terminals';
15
- /**
16
- * Adds a left-sidebar panel that lists every running terminal session.
17
- *
18
- * Each row is labelled with the *real* title the running program
19
- * published — the launcher's agent name, or whatever title an xterm
20
- * escape sequence set — which the registry caches so it survives the
21
- * user closing the tab while the session keeps running on the server.
22
- * JupyterLab's built-in "Running Terminals and Kernels" panel lists the
23
- * same sessions but only by their `terminals/<n>` name, so this panel
24
- * resolves and caches the published title to show something meaningful.
25
- * Rows running a coding agent carry a smaller second line below the
26
- * title with the agent's latest line of output, so the panel shows what
27
- * each agent is doing at a glance — on by default, and switchable off
28
- * through the `showAgentActivity` setting.
29
- *
30
- * Clicking a row activates the existing tab if one is open, or reopens
31
- * the session in a fresh terminal widget (`terminal:open`). The inline
32
- * `×` button shuts the session down on the server. The header carries a
33
- * `+` button and a stop button (the latter shuts every session down at
34
- * once, after a confirmation).
35
- *
36
- * The `+` button drops down a menu — built from the launcher's shared
37
- * `IAgentRegistry` — listing each available agent (Claude, Codex, …)
38
- * followed by a plain terminal, so starting an agent session is as
39
- * consistent here as in the launcher and reuses the very same registered
40
- * commands and icons. When the launcher is disabled or no agents are
41
- * installed, the button falls back to opening a plain terminal directly.
42
- *
43
- * The plugin also provides {@link IAgentTerminals} — the sessions with a
44
- * detection-confirmed running agent, plus a "paste a prompt into one"
45
- * action — which the ask-agent popup uses to offer running agents as
46
- * targets next to "new terminal".
47
- */
48
15
  const plugin = {
49
16
  id: PLUGIN_ID,
50
17
  description: 'A left-sidebar panel listing every running terminal session by its real (program-published) title, with activate/reopen, shutdown and new-terminal actions.',
@@ -57,17 +24,13 @@ const plugin = {
57
24
  IAgentRegistry,
58
25
  IEditorRegistry,
59
26
  IAgentSessions,
60
- ISettingRegistry
27
+ ISettingRegistry,
28
+ IMovableSectionRegistry
61
29
  ],
62
- activate: (app, tracker, restorer, translator, agentRegistry, editorRegistry, agentSessions, settingRegistry) => {
30
+ activate: (app, tracker, restorer, translator, agentRegistry, editorRegistry, agentSessions, settingRegistry, movableSections) => {
63
31
  const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
64
- // Resolve a running agent/editor identifier to an icon: the matching
65
- // agent's logo, the matching editor's logo (Neovim/Vim), or the plain
66
- // terminal icon when nothing recognised is running. Detection reports
67
- // either the configured `command` or the canonical `id` (see
68
- // `detectCommands`), so a row whose command is an alias — e.g. `ccm` for
69
- // `claude` — still resolves to the right logo via its id. Reads the
70
- // registries live so it tracks settings changes. Used to badge each row.
32
+ // Detection reports either the configured `command` or the canonical `id`,
33
+ // so an aliased command (e.g. `ccm` for `claude`) still maps to its logo.
71
34
  const iconForCommand = (command) => {
72
35
  var _a;
73
36
  if (!command) {
@@ -77,18 +40,9 @@ const plugin = {
77
40
  if (agent) {
78
41
  return agent.icon;
79
42
  }
80
- // A terminal running an editor (Neovim/Vim, or a user-configured one) is
81
- // badged with its logo, the same as an agent.
82
43
  const editor = editorRegistry === null || editorRegistry === void 0 ? void 0 : editorRegistry.editors.find(e => e.command === command || e.id === command);
83
44
  return (_a = editor === null || editor === void 0 ? void 0 : editor.icon) !== null && _a !== void 0 ? _a : terminalIcon;
84
45
  };
85
- // The names the server should look for in each terminal's process tree:
86
- // for every agent and editor, its configured `command` plus its canonical
87
- // `id`. The id is the real CLI name (e.g. `claude`, `nvim`), so an agent
88
- // whose command points at an alias — e.g. `ccm` running `claude` — is
89
- // still recognised by the process it actually spawns. Read live so the
90
- // list tracks settings changes; deduplicated since command and id usually
91
- // coincide.
92
46
  const detectCommands = () => {
93
47
  var _a, _b;
94
48
  const names = new Set();
@@ -102,10 +56,6 @@ const plugin = {
102
56
  }
103
57
  return Array.from(names);
104
58
  };
105
- // Whether a detected command/id belongs to a coding agent rather than an
106
- // editor. Only agent sessions get a latest-activity line (editor
107
- // terminals stay badge-only), and only they are offered as prompt
108
- // targets through `IAgentTerminals`.
109
59
  const isAgentCommand = (command) => {
110
60
  var _a;
111
61
  return (_a = agentRegistry === null || agentRegistry === void 0 ? void 0 : agentRegistry.agents.some(a => a.command === command || a.id === command)) !== null && _a !== void 0 ? _a : false;
@@ -113,18 +63,13 @@ const plugin = {
113
63
  const registry = new SessionRegistry({
114
64
  serviceManager: app.serviceManager,
115
65
  tracker,
116
- // The shell tells the registry which widget is current, so the panel can
117
- // highlight the terminal that is the current main-area widget (and
118
- // nothing while a notebook or other widget is current instead).
119
66
  shell: app.shell,
120
67
  agentSessions,
121
68
  detectCommands,
122
69
  isAgentCommand
123
70
  });
124
- // Mirror the agent/editor detected in each open terminal onto its main-area
125
- // tab icon, so the tab matches its sidebar row. Re-running on every
126
- // `stateChanged` is safe: `Title.icon` ignores an unchanged assignment, so
127
- // the writes neither loop nor churn.
71
+ // `Title.icon` ignores an unchanged assignment, so re-running per
72
+ // stateChanged is cheap.
128
73
  const syncTabIcons = () => {
129
74
  tracker.forEach(widget => {
130
75
  const session = widget.content.session;
@@ -137,10 +82,8 @@ const plugin = {
137
82
  registry.stateChanged.connect(syncTabIcons);
138
83
  syncTabIcons();
139
84
  const onActivate = (name) => {
140
- // `terminal:open` is the upstream entry point that handles both
141
- // sides of this: if a widget is already attached to the named
142
- // session it activates that tab, otherwise it spins up a fresh
143
- // widget connected to the live session.
85
+ // `terminal:open` activates an attached tab or spins up a fresh widget
86
+ // connected to the live session.
144
87
  void app.commands.execute('terminal:open', { name });
145
88
  };
146
89
  const onShutdown = (name) => {
@@ -149,10 +92,6 @@ const plugin = {
149
92
  });
150
93
  };
151
94
  const onShutdownAll = () => {
152
- // Tearing down every session at once is hard to undo (each may be a
153
- // running agent), so confirm before calling the manager's bulk
154
- // shutdown. `runningChanged` then clears the list and the panel
155
- // re-renders empty.
156
95
  void showDialog({
157
96
  title: trans.__('Shut Down All Terminals?'),
158
97
  body: trans.__('Are you sure you want to permanently shut down all running terminals?'),
@@ -170,19 +109,8 @@ const plugin = {
170
109
  console.error(`xtralab: failed to shut down all terminals: ${reason}`);
171
110
  });
172
111
  };
173
- // The header "+" button. With the launcher's agent registry available
174
- // it drops down a menu of the available agents (reusing their registered
175
- // `xtralab:start-agent:<id>` commands, so the icons and labels match the
176
- // launcher exactly) followed by a separator and a plain terminal. Built
177
- // lazily and repopulated on each open so it always reflects the current,
178
- // settings-driven agent list. Without the registry (launcher disabled)
179
- // or with no agents installed, there is nothing to choose between, so
180
- // the button opens a plain terminal directly in one click.
181
- //
182
- // `MenuSvg` (not the bare Lumino `Menu`) is what every JupyterLab menu
183
- // uses: its renderer applies the `menuItem` LabIcon stylesheet, which
184
- // sizes the agent icons to 16px and centers them vertically — the bare
185
- // `Menu` renders the raw, oversized, misaligned SVGs.
112
+ // `MenuSvg`, not the bare Lumino `Menu`: its renderer applies the `menuItem`
113
+ // LabIcon stylesheet — the bare menu renders raw, oversized SVGs.
186
114
  let newMenu = null;
187
115
  const onCreate = (anchor) => {
188
116
  var _a;
@@ -211,22 +139,20 @@ const plugin = {
211
139
  onShutdownAll,
212
140
  onCreate
213
141
  });
214
- // Added to the left sidebar. The shipped `layout` setting in
215
- // jupyter-config/labconfig gives each sidebar widget a default rank —
216
- // this Terminals panel ranks first, so running agents sit one click
217
- // away at the top. That setting assigns rank only and pins no area, so
218
- // the "Move Widget" context menu can relocate the panel and the layout
219
- // restorer remembers where the user puts it. The `rank` here is the
220
- // in-code fallback used when the setting is absent. Sessions restored
221
- // from a previous lab run land via `runningChanged` once the terminal
222
- // manager is ready, which the panel's `UseSignal` picks up.
142
+ // The shipped labconfig `layout` setting ranks this panel first (rank only,
143
+ // no area pin, so "Move Widget" works); this rank is the fallback.
223
144
  app.shell.add(panel, 'left', { rank: 1 });
224
145
  if (restorer) {
225
146
  restorer.add(panel, panel.id);
226
147
  }
227
- // The latest-activity line under each agent row is on by default; honour
228
- // the `showAgentActivity` setting so it can be turned off. Read on load and
229
- // re-applied on every change.
148
+ void app.restored.then(() => {
149
+ panel.expandOwnSection();
150
+ });
151
+ if (movableSections) {
152
+ movableSections.registerSource(PLUGIN_ID, trans.__('Terminals'), panel);
153
+ movableSections.registerTarget(PLUGIN_ID, trans.__('Terminals'), panel);
154
+ panel.announceSections();
155
+ }
230
156
  if (settingRegistry) {
231
157
  settingRegistry
232
158
  .load(PLUGIN_ID)
@@ -235,9 +161,8 @@ const plugin = {
235
161
  registry.setActivityEnabled(boolOption(settings.composite.showAgentActivity, true));
236
162
  };
237
163
  applyActivitySetting();
238
- // Bind the slot to the panel so disposing the panel (which clears
239
- // its signal data) drops the connection — the disposed registry is
240
- // then never reached on a later settings change.
164
+ // Bound to the panel so disposing it drops the connection — the
165
+ // disposed registry is never reached on a later settings change.
241
166
  settings.changed.connect(applyActivitySetting, panel);
242
167
  })
243
168
  .catch(reason => {
@@ -254,9 +179,6 @@ const plugin = {
254
179
  });
255
180
  }
256
181
  };
257
- /**
258
- * Read a boolean setting value, falling back when it is missing or invalid.
259
- */
260
182
  function boolOption(value, fallback) {
261
183
  return typeof value === 'boolean' ? value : fallback;
262
184
  }