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
@@ -1,25 +1,9 @@
1
1
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
2
  /**
3
- * Bind Accel+Shift+F (Cmd+Shift+F on macOS, Ctrl+Shift+F elsewhere) to the
4
- * project-wide Search and Replace panel, matching the equivalent shortcut in
5
- * editors like VS Code.
6
- *
7
- * Two pieces are needed because that chord is already taken: JupyterLab core
8
- * binds Accel+Shift+F to `filebrowser:toggle-main`.
9
- *
10
- * 1. The chord is freed declaratively. `SettingRegistry.reconcileShortcuts`
11
- * keys collisions and disables on (keys, selector) — not on command — so the
12
- * `@jupyterlab/shortcuts-extension:shortcuts` override in
13
- * `default_setting_overrides.d/00-xtralab.json` disables the file-browser
14
- * binding. The file browser stays reachable from its sidebar tab and the
15
- * View menu; only the keyboard shortcut moves.
16
- *
17
- * 2. The new binding is added imperatively below rather than as another shipped
18
- * default. A `disabled` default and a re-binding default land on the same
19
- * (keys, selector) slot, and the disable suppresses every default on that
20
- * slot — including the re-binding. `commands.addKeyBinding` runs outside the
21
- * reconcile pass, so it is unaffected and wins cleanly once the file-browser
22
- * binding is gone.
3
+ * Bind Accel+Shift+F to the Search and Replace panel (VS Code parity). Core
4
+ * binds the chord to `filebrowser:toggle-main`; a `default_setting_overrides.d`
5
+ * override disables it. The re-binding is imperative: `reconcileShortcuts` keys
6
+ * on (keys, selector), so the disable would suppress a shipped default too.
23
7
  */
24
8
  declare const plugin: JupyterFrontEndPlugin<void>;
25
9
  export default plugin;
@@ -4,33 +4,15 @@ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
4
4
  const PLUGIN_ID = 'xtralab:search-replace';
5
5
  const COMMAND_ID = 'xtralab:activate-search-replace';
6
6
  /**
7
- * Widget id of the panel contributed by `jupyterlab-search-replace`. The
8
- * extension adds it to the left sidebar but registers no command of its own,
9
- * so there is nothing to bind a shortcut to out of the box — this plugin adds
10
- * the missing command.
7
+ * Widget id of the `jupyterlab-search-replace` left-sidebar panel; the
8
+ * extension registers no command of its own to bind a shortcut to.
11
9
  */
12
10
  const SEARCH_REPLACE_WIDGET_ID = 'jp-search-replace';
13
11
  /**
14
- * Bind Accel+Shift+F (Cmd+Shift+F on macOS, Ctrl+Shift+F elsewhere) to the
15
- * project-wide Search and Replace panel, matching the equivalent shortcut in
16
- * editors like VS Code.
17
- *
18
- * Two pieces are needed because that chord is already taken: JupyterLab core
19
- * binds Accel+Shift+F to `filebrowser:toggle-main`.
20
- *
21
- * 1. The chord is freed declaratively. `SettingRegistry.reconcileShortcuts`
22
- * keys collisions and disables on (keys, selector) — not on command — so the
23
- * `@jupyterlab/shortcuts-extension:shortcuts` override in
24
- * `default_setting_overrides.d/00-xtralab.json` disables the file-browser
25
- * binding. The file browser stays reachable from its sidebar tab and the
26
- * View menu; only the keyboard shortcut moves.
27
- *
28
- * 2. The new binding is added imperatively below rather than as another shipped
29
- * default. A `disabled` default and a re-binding default land on the same
30
- * (keys, selector) slot, and the disable suppresses every default on that
31
- * slot — including the re-binding. `commands.addKeyBinding` runs outside the
32
- * reconcile pass, so it is unaffected and wins cleanly once the file-browser
33
- * binding is gone.
12
+ * Bind Accel+Shift+F to the Search and Replace panel (VS Code parity). Core
13
+ * binds the chord to `filebrowser:toggle-main`; a `default_setting_overrides.d`
14
+ * override disables it. The re-binding is imperative: `reconcileShortcuts` keys
15
+ * on (keys, selector), so the disable would suppress a shipped default too.
34
16
  */
35
17
  const plugin = {
36
18
  id: PLUGIN_ID,
@@ -45,9 +27,7 @@ const plugin = {
45
27
  label: trans.__('Search and Replace'),
46
28
  caption: trans.__('Show the Search and Replace panel'),
47
29
  execute: () => {
48
- // `activateById` expands the left sidebar when collapsed, selects the
49
- // tab, and the widget focuses its search input from `onAfterShow`, so
50
- // the user can start typing a query immediately. It no-ops if
30
+ // The widget focuses its search input from `onAfterShow`; no-ops if
51
31
  // `jupyterlab-search-replace` is unavailable.
52
32
  labShell.activateById(SEARCH_REPLACE_WIDGET_ID);
53
33
  }
@@ -1,17 +1,8 @@
1
1
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
2
  /**
3
- * Contribute `xtralab:show`: render a single MIME bundle into a main-area
4
- * panel using the application's render-mime registry.
5
- *
6
- * This is the kernel-free counterpart to running a notebook. A coding agent
7
- * generates the content itself (a Vega-Lite spec, a Markdown explainer, an
8
- * HTML fragment, an SVG, a base64 image) and hands it to this command, which
9
- * renders it with the same renderers JupyterLab uses for cell output and docks
10
- * the result beside the code. Nothing is written to disk and no kernel is
11
- * required, so it works even where the kernel has no plotting libraries.
12
- *
13
- * Repeated calls with the same `id` reuse one panel, so a walkthrough can
14
- * refresh the view in place instead of piling up tabs.
3
+ * Contributes `xtralab:show`: render a single MIME bundle into a panel with
4
+ * the same renderers JupyterLab uses for cell output — no kernel, no file on
5
+ * disk. Repeated calls with the same `id` reuse one panel.
15
6
  */
16
7
  declare const plugin: JupyterFrontEndPlugin<void>;
17
8
  export default plugin;
@@ -5,23 +5,11 @@ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
5
5
  import { Panel } from '@lumino/widgets';
6
6
  import { coerceData } from '../mimeData';
7
7
  const PLUGIN_ID = 'xtralab:show-output';
8
- /**
9
- * Render arbitrary rich content into a panel, with no notebook and no kernel.
10
- */
11
8
  const SHOW_COMMAND = 'xtralab:show';
12
9
  /**
13
- * Contribute `xtralab:show`: render a single MIME bundle into a main-area
14
- * panel using the application's render-mime registry.
15
- *
16
- * This is the kernel-free counterpart to running a notebook. A coding agent
17
- * generates the content itself (a Vega-Lite spec, a Markdown explainer, an
18
- * HTML fragment, an SVG, a base64 image) and hands it to this command, which
19
- * renders it with the same renderers JupyterLab uses for cell output and docks
20
- * the result beside the code. Nothing is written to disk and no kernel is
21
- * required, so it works even where the kernel has no plotting libraries.
22
- *
23
- * Repeated calls with the same `id` reuse one panel, so a walkthrough can
24
- * refresh the view in place instead of piling up tabs.
10
+ * Contributes `xtralab:show`: render a single MIME bundle into a panel with
11
+ * the same renderers JupyterLab uses for cell output — no kernel, no file on
12
+ * disk. Repeated calls with the same `id` reuse one panel.
25
13
  */
26
14
  const plugin = {
27
15
  id: PLUGIN_ID,
@@ -32,7 +20,6 @@ const plugin = {
32
20
  activate: (app, rendermime, labShell, palette, translator) => {
33
21
  const { commands } = app;
34
22
  const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
35
- // One reusable panel per `id`, so repeated shows refresh in place.
36
23
  const panels = new Map();
37
24
  commands.addCommand(SHOW_COMMAND, {
38
25
  label: trans.__('Show Rich Output'),
@@ -89,16 +76,14 @@ const plugin = {
89
76
  }
90
77
  const model = rendermime.createModel({ data: bundle, trusted: true });
91
78
  const renderer = rendermime.createRenderer(chosen);
92
- // A scrollable Panel, not a MainAreaWidget: MainAreaWidget does not lay
93
- // out its content in the side area, and the panel needs to scroll when
94
- // the content is taller than it (style/showOutput.css).
79
+ // Not a MainAreaWidget: it does not lay out its content in the side
80
+ // area; the panel scrolls via style/showOutput.css.
95
81
  const widget = new Panel();
96
82
  widget.addClass('jp-xtralab-ShowPanel');
97
83
  widget.id = `xtralab-show-${id}`;
98
84
  widget.title.label = label;
99
85
  widget.title.closable = true;
100
86
  widget.addWidget(renderer);
101
- // Replace any existing panel with this id so shows refresh in place.
102
87
  const existing = panels.get(id);
103
88
  if (existing && !existing.isDisposed) {
104
89
  existing.dispose();
@@ -110,8 +95,7 @@ const plugin = {
110
95
  }
111
96
  });
112
97
  // Attach before rendering so renderers that need layout (Vega) size
113
- // correctly. Default to the side area so the editor keeps full width;
114
- // only split the document area when `area` is "main".
98
+ // correctly.
115
99
  if (area === 'main') {
116
100
  labShell.add(widget, 'main', {
117
101
  mode,
@@ -1,12 +1,9 @@
1
1
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
2
  /**
3
- * Toggles individual sidebar tabs from View > Appearance > Sidebars,
4
- * whichever side a tab currently lives on. Hiding detaches the widget
5
- * (`widget.parent = null`) while keeping the instance for a later
6
- * re-add; the preference persists in this plugin's settings. Which side
7
- * a hidden tab came from is only tracked in-memory, so after a reload a
8
- * re-shown tab lands on the left. The menu placement is contributed
9
- * declaratively in `schema/sidebar.json`.
3
+ * Toggles individual sidebar tabs from View > Appearance > Sidebars. Hiding
4
+ * detaches the widget while keeping the instance for a later re-add; which
5
+ * side it came from is only tracked in-memory, so after a reload a re-shown
6
+ * tab lands on the left. Menu placement is declared in schema/sidebar.json.
10
7
  */
11
8
  declare const plugin: JupyterFrontEndPlugin<void>;
12
9
  export default plugin;
@@ -47,13 +47,10 @@ const TARGETS = [
47
47
  }
48
48
  ];
49
49
  /**
50
- * Toggles individual sidebar tabs from View > Appearance > Sidebars,
51
- * whichever side a tab currently lives on. Hiding detaches the widget
52
- * (`widget.parent = null`) while keeping the instance for a later
53
- * re-add; the preference persists in this plugin's settings. Which side
54
- * a hidden tab came from is only tracked in-memory, so after a reload a
55
- * re-shown tab lands on the left. The menu placement is contributed
56
- * declaratively in `schema/sidebar.json`.
50
+ * Toggles individual sidebar tabs from View > Appearance > Sidebars. Hiding
51
+ * detaches the widget while keeping the instance for a later re-add; which
52
+ * side it came from is only tracked in-memory, so after a reload a re-shown
53
+ * tab lands on the left. Menu placement is declared in schema/sidebar.json.
57
54
  */
58
55
  const plugin = {
59
56
  id: PLUGIN_ID,
@@ -67,8 +64,6 @@ const plugin = {
67
64
  // Detached widgets are unreachable via `labShell.widgets(...)`; keep
68
65
  // instances here so hidden tabs can be re-added.
69
66
  const widgetCache = new Map();
70
- // Side each hidden widget was removed from, so re-showing restores it
71
- // there.
72
67
  const hiddenFrom = new Map();
73
68
  const findInSidebars = (id) => {
74
69
  for (const area of ['left', 'right']) {
@@ -105,7 +100,6 @@ const plugin = {
105
100
  var _a;
106
101
  const widget = captureWidget(target.id);
107
102
  if (!widget) {
108
- // Not seen yet (extension disabled or not added) — nothing to do.
109
103
  return;
110
104
  }
111
105
  const area = locate(target.id);
@@ -155,8 +149,7 @@ const plugin = {
155
149
  console.error(`xtralab: failed to persist ${target.settingKey}`, reason);
156
150
  }
157
151
  }
158
- // No settings registry (or the write failed): apply in-memory
159
- // for the current session only.
152
+ // No settings (or the write failed): apply in-memory for this session.
160
153
  apply(target);
161
154
  commands.notifyCommandChanged(target.command);
162
155
  }
@@ -1,15 +1,9 @@
1
1
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
2
  /**
3
- * Turns the notifications coding agents already emit (OSC 9, OSC 777, the bell)
4
- * into desktop notifications. JupyterLab's xterm renders these sequences but
5
- * never forwards them to the OS, so this plugin hooks each terminal and bridges
6
- * them through `window.xtralab.notify` (desktop) or the web Notifications API.
7
- *
8
- * A notification is suppressed while its terminal is the focused, active tab,
9
- * and throttled per terminal. The desktop shell advertises
10
- * `TERM_PROGRAM=iTerm.app` so agents emit OSC 9 in the first place, and the
11
- * session name is forwarded so clicking a notification focuses the terminal
12
- * that fired it.
3
+ * Turns the OSC 9 / OSC 777 / bell sequences agents emit into desktop
4
+ * notifications via `window.xtralab.notify` (desktop) or the web Notifications
5
+ * API — JupyterLab's xterm renders them but never forwards them to the OS.
6
+ * The desktop shell advertises `TERM_PROGRAM=iTerm.app` so agents emit OSC 9.
13
7
  */
14
8
  declare const plugin: JupyterFrontEndPlugin<void>;
15
9
  export default plugin;
@@ -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,39 +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 Terminals
21
- * section's title toolbar carries a `+` button and a stop button (the
22
- * latter shuts every session down at once, after a confirmation).
23
- *
24
- * The list is one movable section, and other panels' sections can move in.
25
- *
26
- * The `+` button drops down a menu — built from the launcher's shared
27
- * `IAgentRegistry` — listing each available agent (Claude, Codex, …)
28
- * followed by a plain terminal, so starting an agent session is as
29
- * consistent here as in the launcher and reuses the very same registered
30
- * commands and icons. When the launcher is disabled or no agents are
31
- * installed, the button falls back to opening a plain terminal directly.
32
- *
33
- * The plugin also provides {@link IAgentTerminals} — the sessions with a
34
- * detection-confirmed running agent, plus a "paste a prompt into one"
35
- * action — which the ask-agent popup uses to offer running agents as
36
- * targets next to "new terminal".
37
- */
38
3
  declare const plugin: JupyterFrontEndPlugin<IAgentTerminals>;
39
4
  export default plugin;