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.
- package/README.md +65 -30
- package/lib/about/icons.d.ts +0 -5
- package/lib/about/icons.js +0 -5
- package/lib/about/index.d.ts +4 -8
- package/lib/about/index.js +6 -17
- package/lib/agentSessions.d.ts +9 -29
- package/lib/agentSessions.js +2 -7
- package/lib/askAgent/editorSelection.d.ts +20 -26
- package/lib/askAgent/editorSelection.js +11 -26
- package/lib/askAgent/icons.d.ts +2 -3
- package/lib/askAgent/icons.js +2 -3
- package/lib/askAgent/index.d.ts +4 -26
- package/lib/askAgent/index.js +54 -158
- package/lib/askAgent/popup.d.ts +12 -16
- package/lib/askAgent/popup.js +18 -37
- package/lib/askAgent/prompt.d.ts +3 -5
- package/lib/askAgent/prompt.js +6 -19
- package/lib/askAgent/queue.d.ts +11 -21
- package/lib/askAgent/queue.js +9 -19
- package/lib/askAgent/queuePanel.d.ts +13 -18
- package/lib/askAgent/queuePanel.js +15 -37
- package/lib/askAgent/targetPicker.d.ts +2 -9
- package/lib/askAgent/targetPicker.js +6 -11
- package/lib/askAgent/tokens.d.ts +32 -46
- package/lib/askAgent/tokens.js +4 -6
- package/lib/commandBar/index.d.ts +4 -13
- package/lib/commandBar/index.js +21 -39
- package/lib/customPanel/index.js +15 -0
- package/lib/editorBreadcrumbs/index.d.ts +3 -6
- package/lib/editorBreadcrumbs/index.js +10 -6
- package/lib/editorBreadcrumbs/widget.d.ts +17 -4
- package/lib/editorBreadcrumbs/widget.js +11 -17
- package/lib/editorIndent/index.d.ts +4 -12
- package/lib/editorIndent/index.js +8 -23
- package/lib/fileBrowser/commands.d.ts +29 -19
- package/lib/fileBrowser/commands.js +83 -97
- package/lib/fileBrowser/contents.d.ts +3 -0
- package/lib/fileBrowser/dragAndDrop.d.ts +17 -4
- package/lib/fileBrowser/dragAndDrop.js +2 -7
- package/lib/fileBrowser/fileBrowser.d.ts +19 -9
- package/lib/fileBrowser/fileBrowser.js +70 -222
- package/lib/fileBrowser/gitStatus.d.ts +16 -10
- package/lib/fileBrowser/gitStatus.js +31 -30
- package/lib/fileBrowser/gitignore.d.ts +4 -17
- package/lib/fileBrowser/gitignore.js +4 -25
- package/lib/fileBrowser/icons.d.ts +7 -17
- package/lib/fileBrowser/icons.js +25 -80
- package/lib/fileBrowser/index.js +2 -4
- package/lib/fileBrowser/widget.d.ts +141 -48
- package/lib/fileBrowser/widget.js +102 -4
- package/lib/fileTypeIcons/index.js +0 -10
- package/lib/git/api.d.ts +5 -13
- package/lib/git/api.js +10 -36
- package/lib/git/askRequest.d.ts +4 -8
- package/lib/git/askRequest.js +6 -20
- package/lib/git/commands.d.ts +45 -5
- package/lib/git/commands.js +20 -13
- package/lib/git/diffModel.js +4 -0
- package/lib/git/diffPreferences.d.ts +90 -0
- package/lib/git/diffPreferences.js +107 -0
- package/lib/git/diffProvider.js +10 -2
- package/lib/git/diffSurface.d.ts +64 -63
- package/lib/git/diffSurface.js +72 -213
- package/lib/git/diffTheme.d.ts +7 -0
- package/lib/git/diffTheme.js +23 -0
- package/lib/git/diffWidget.d.ts +70 -14
- package/lib/git/diffWidget.js +100 -94
- package/lib/git/imageDiff.d.ts +14 -6
- package/lib/git/imageDiff.js +19 -75
- package/lib/git/index.d.ts +0 -3
- package/lib/git/index.js +44 -10
- package/lib/git/notebookDiff.d.ts +110 -33
- package/lib/git/notebookDiff.js +55 -197
- package/lib/git/tokens.d.ts +73 -31
- package/lib/git/tokens.js +2 -3
- package/lib/highlight/index.d.ts +4 -10
- package/lib/highlight/index.js +10 -41
- package/lib/index.d.ts +0 -9
- package/lib/index.js +0 -9
- package/lib/launcher/agents.d.ts +55 -51
- package/lib/launcher/agents.js +15 -57
- package/lib/launcher/availability.d.ts +3 -9
- package/lib/launcher/availability.js +3 -9
- package/lib/launcher/commands.d.ts +7 -32
- package/lib/launcher/commands.js +14 -45
- package/lib/launcher/dashboard.d.ts +19 -24
- package/lib/launcher/dashboard.js +21 -67
- package/lib/launcher/editorRegistry.d.ts +10 -22
- package/lib/launcher/editorRegistry.js +14 -18
- package/lib/launcher/editors.d.ts +39 -45
- package/lib/launcher/editors.js +8 -34
- package/lib/launcher/icons.d.ts +0 -12
- package/lib/launcher/icons.js +5 -39
- package/lib/launcher/index.js +12 -55
- package/lib/launcher/invocation.d.ts +4 -10
- package/lib/launcher/invocation.js +7 -22
- package/lib/launcher/registry.d.ts +9 -7
- package/lib/launcher/registry.js +9 -7
- package/lib/launcher/tokens.d.ts +10 -22
- package/lib/launcher/tokens.js +5 -8
- package/lib/menuBar/index.d.ts +3 -10
- package/lib/menuBar/index.js +11 -28
- package/lib/menus/index.d.ts +3 -9
- package/lib/menus/index.js +14 -30
- package/lib/omnibox/files.d.ts +2 -4
- package/lib/omnibox/files.js +5 -11
- package/lib/omnibox/index.d.ts +4 -14
- package/lib/omnibox/index.js +9 -27
- package/lib/omnibox/model.d.ts +38 -10
- package/lib/omnibox/model.js +15 -33
- package/lib/omnibox/recents.d.ts +15 -17
- package/lib/omnibox/recents.js +12 -17
- package/lib/omnibox/tokens.d.ts +5 -10
- package/lib/omnibox/tokens.js +2 -6
- package/lib/omnibox/widget.d.ts +11 -7
- package/lib/omnibox/widget.js +8 -16
- package/lib/searchReplace/index.d.ts +4 -20
- package/lib/searchReplace/index.js +7 -27
- package/lib/showOutput/index.d.ts +3 -12
- package/lib/showOutput/index.js +6 -22
- package/lib/sidebar/index.d.ts +4 -7
- package/lib/sidebar/index.js +5 -12
- package/lib/terminalNotifications/index.d.ts +4 -10
- package/lib/terminalNotifications/index.js +6 -20
- package/lib/terminals/agentTerminals.d.ts +12 -5
- package/lib/terminals/agentTerminals.js +25 -44
- package/lib/terminals/detection.d.ts +4 -8
- package/lib/terminals/detection.js +4 -8
- package/lib/terminals/index.d.ts +0 -35
- package/lib/terminals/index.js +12 -101
- package/lib/terminals/model.d.ts +64 -139
- package/lib/terminals/model.js +80 -220
- package/lib/terminals/tokens.d.ts +11 -22
- package/lib/terminals/tokens.js +2 -2
- package/lib/terminals/widget.d.ts +43 -13
- package/lib/terminals/widget.js +33 -18
- package/lib/topBar/icons.d.ts +3 -8
- package/lib/topBar/icons.js +3 -8
- package/lib/topBar/index.d.ts +4 -11
- package/lib/topBar/index.js +10 -38
- package/lib/walkthrough/index.d.ts +4 -9
- package/lib/walkthrough/index.js +6 -20
- package/lib/walkthrough/panel.d.ts +40 -9
- package/lib/walkthrough/panel.js +4 -10
- package/package.json +5 -3
- package/schema/git-diff-preferences.json +42 -0
- package/src/about/icons.ts +0 -5
- package/src/about/index.tsx +6 -17
- package/src/agentSessions.ts +9 -29
- package/src/askAgent/editorSelection.ts +22 -29
- package/src/askAgent/icons.ts +2 -3
- package/src/askAgent/index.ts +54 -158
- package/src/askAgent/popup.tsx +23 -45
- package/src/askAgent/prompt.ts +6 -19
- package/src/askAgent/queue.ts +13 -24
- package/src/askAgent/queuePanel.tsx +21 -46
- package/src/askAgent/targetPicker.tsx +5 -15
- package/src/askAgent/tokens.ts +32 -46
- package/src/commandBar/index.ts +24 -39
- package/src/customPanel/index.ts +15 -0
- package/src/editorBreadcrumbs/index.ts +10 -6
- package/src/editorBreadcrumbs/widget.ts +23 -17
- package/src/editorIndent/index.ts +17 -23
- package/src/fileBrowser/commands.ts +119 -96
- package/src/fileBrowser/contents.ts +3 -0
- package/src/fileBrowser/dragAndDrop.ts +17 -7
- package/src/fileBrowser/fileBrowser.tsx +86 -230
- package/src/fileBrowser/gitStatus.ts +36 -33
- package/src/fileBrowser/gitignore.ts +4 -25
- package/src/fileBrowser/icons.ts +25 -80
- package/src/fileBrowser/index.ts +2 -4
- package/src/fileBrowser/widget.tsx +171 -50
- package/src/fileTypeIcons/index.ts +0 -10
- package/src/git/api.ts +10 -36
- package/src/git/askRequest.ts +6 -20
- package/src/git/commands.ts +83 -11
- package/src/git/diffModel.ts +25 -0
- package/src/git/diffPreferences.ts +192 -0
- package/src/git/diffProvider.tsx +11 -2
- package/src/git/diffSurface.tsx +165 -286
- package/src/git/diffTheme.ts +24 -0
- package/src/git/diffWidget.tsx +194 -134
- package/src/git/imageDiff.tsx +56 -84
- package/src/git/index.ts +54 -10
- package/src/git/notebookDiff.tsx +214 -240
- package/src/git/tokens.ts +73 -31
- package/src/highlight/index.ts +10 -41
- package/src/index.ts +0 -9
- package/src/launcher/agents.ts +64 -95
- package/src/launcher/availability.ts +3 -9
- package/src/launcher/commands.ts +14 -51
- package/src/launcher/dashboard.tsx +49 -88
- package/src/launcher/editorRegistry.ts +21 -29
- package/src/launcher/editors.ts +39 -58
- package/src/launcher/icons.ts +5 -39
- package/src/launcher/index.ts +12 -55
- package/src/launcher/invocation.ts +7 -22
- package/src/launcher/registry.ts +9 -7
- package/src/launcher/tokens.ts +10 -22
- package/src/menuBar/index.ts +11 -28
- package/src/menus/index.ts +14 -30
- package/src/omnibox/files.ts +10 -14
- package/src/omnibox/index.ts +9 -27
- package/src/omnibox/model.ts +50 -38
- package/src/omnibox/recents.ts +17 -22
- package/src/omnibox/tokens.ts +5 -10
- package/src/omnibox/widget.tsx +14 -19
- package/src/searchReplace/index.ts +7 -27
- package/src/showOutput/index.ts +6 -22
- package/src/sidebar/index.ts +20 -12
- package/src/terminalNotifications/index.ts +42 -25
- package/src/terminals/agentTerminals.ts +25 -49
- package/src/terminals/detection.ts +4 -8
- package/src/terminals/index.ts +12 -101
- package/src/terminals/model.ts +130 -246
- package/src/terminals/tokens.ts +11 -22
- package/src/terminals/widget.tsx +48 -31
- package/src/topBar/icons.ts +3 -8
- package/src/topBar/index.ts +21 -44
- package/src/walkthrough/index.ts +6 -20
- package/src/walkthrough/panel.ts +40 -10
- package/style/about.css +2 -7
- package/style/askAgent.css +8 -40
- package/style/base.css +9 -45
- package/style/chrome.css +108 -308
- package/style/commandBar.css +4 -20
- package/style/customPanel.css +1 -4
- package/style/git.css +26 -186
- package/style/highlight.css +1 -3
- package/style/launcher.css +6 -70
- package/style/omnibox.css +2 -14
- package/style/showOutput.css +0 -6
- package/style/sidebar.css +29 -9
- package/style/tabs.css +5 -13
- package/style/terminals.css +6 -53
- package/style/topBar.css +2 -22
- package/style/walkthrough.css +0 -8
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
2
|
/**
|
|
3
|
-
* Bind Accel+Shift+F
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
8
|
-
* extension
|
|
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
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
//
|
|
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
|
-
*
|
|
4
|
-
*
|
|
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;
|
package/lib/showOutput/index.js
CHANGED
|
@@ -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
|
-
*
|
|
14
|
-
*
|
|
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
|
-
//
|
|
93
|
-
//
|
|
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.
|
|
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,
|
package/lib/sidebar/index.d.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
2
|
/**
|
|
3
|
-
* Toggles individual sidebar tabs from View > Appearance > Sidebars
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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;
|
package/lib/sidebar/index.js
CHANGED
|
@@ -47,13 +47,10 @@ const TARGETS = [
|
|
|
47
47
|
}
|
|
48
48
|
];
|
|
49
49
|
/**
|
|
50
|
-
* Toggles individual sidebar tabs from View > Appearance > Sidebars
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
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
|
|
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
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
|
86
|
-
//
|
|
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
|
-
*
|
|
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
|
|
5
|
-
*
|
|
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~`)
|
|
13
|
-
*
|
|
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}
|
|
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
|
-
*
|
|
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
|
|
94
|
-
//
|
|
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
|
|
105
|
-
//
|
|
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
|
-
//
|
|
125
|
-
//
|
|
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
|
-
//
|
|
144
|
-
//
|
|
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`
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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`
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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) {
|
package/lib/terminals/index.d.ts
CHANGED
|
@@ -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;
|