xtralab 0.15.0 → 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.
- package/README.md +10 -0
- 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 +28 -19
- package/lib/fileBrowser/commands.js +39 -98
- 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 +68 -216
- package/lib/fileBrowser/gitStatus.d.ts +4 -9
- package/lib/fileBrowser/gitStatus.js +7 -17
- 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 +126 -48
- package/lib/fileBrowser/widget.js +89 -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 +40 -5
- package/lib/git/commands.js +16 -10
- package/lib/git/diffModel.js +4 -0
- package/lib/git/diffSurface.d.ts +41 -55
- package/lib/git/diffSurface.js +42 -117
- package/lib/git/diffWidget.d.ts +81 -2
- package/lib/git/diffWidget.js +62 -12
- package/lib/git/imageDiff.d.ts +5 -6
- package/lib/git/imageDiff.js +18 -48
- package/lib/git/index.d.ts +0 -3
- package/lib/git/index.js +3 -8
- package/lib/git/notebookDiff.d.ts +110 -33
- package/lib/git/notebookDiff.js +52 -195
- 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 +2 -2
- 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 +57 -98
- package/src/fileBrowser/contents.ts +3 -0
- package/src/fileBrowser/dragAndDrop.ts +17 -7
- package/src/fileBrowser/fileBrowser.tsx +84 -223
- package/src/fileBrowser/gitStatus.ts +7 -17
- 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 +149 -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 +70 -10
- package/src/git/diffModel.ts +25 -0
- package/src/git/diffSurface.tsx +71 -159
- package/src/git/diffWidget.tsx +104 -14
- package/src/git/imageDiff.tsx +45 -51
- package/src/git/index.ts +3 -8
- package/src/git/notebookDiff.tsx +211 -238
- 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 +22 -184
- 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
|
@@ -11,34 +11,16 @@ const PLUGIN_ID = 'xtralab:search-replace';
|
|
|
11
11
|
const COMMAND_ID = 'xtralab:activate-search-replace';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Widget id of the
|
|
15
|
-
* extension
|
|
16
|
-
* so there is nothing to bind a shortcut to out of the box — this plugin adds
|
|
17
|
-
* the missing command.
|
|
14
|
+
* Widget id of the `jupyterlab-search-replace` left-sidebar panel; the
|
|
15
|
+
* extension registers no command of its own to bind a shortcut to.
|
|
18
16
|
*/
|
|
19
17
|
const SEARCH_REPLACE_WIDGET_ID = 'jp-search-replace';
|
|
20
18
|
|
|
21
19
|
/**
|
|
22
|
-
* Bind Accel+Shift+F
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* Two pieces are needed because that chord is already taken: JupyterLab core
|
|
27
|
-
* binds Accel+Shift+F to `filebrowser:toggle-main`.
|
|
28
|
-
*
|
|
29
|
-
* 1. The chord is freed declaratively. `SettingRegistry.reconcileShortcuts`
|
|
30
|
-
* keys collisions and disables on (keys, selector) — not on command — so the
|
|
31
|
-
* `@jupyterlab/shortcuts-extension:shortcuts` override in
|
|
32
|
-
* `default_setting_overrides.d/00-xtralab.json` disables the file-browser
|
|
33
|
-
* binding. The file browser stays reachable from its sidebar tab and the
|
|
34
|
-
* View menu; only the keyboard shortcut moves.
|
|
35
|
-
*
|
|
36
|
-
* 2. The new binding is added imperatively below rather than as another shipped
|
|
37
|
-
* default. A `disabled` default and a re-binding default land on the same
|
|
38
|
-
* (keys, selector) slot, and the disable suppresses every default on that
|
|
39
|
-
* slot — including the re-binding. `commands.addKeyBinding` runs outside the
|
|
40
|
-
* reconcile pass, so it is unaffected and wins cleanly once the file-browser
|
|
41
|
-
* binding is gone.
|
|
20
|
+
* Bind Accel+Shift+F to the Search and Replace panel (VS Code parity). Core
|
|
21
|
+
* binds the chord to `filebrowser:toggle-main`; a `default_setting_overrides.d`
|
|
22
|
+
* override disables it. The re-binding is imperative: `reconcileShortcuts` keys
|
|
23
|
+
* on (keys, selector), so the disable would suppress a shipped default too.
|
|
42
24
|
*/
|
|
43
25
|
const plugin: JupyterFrontEndPlugin<void> = {
|
|
44
26
|
id: PLUGIN_ID,
|
|
@@ -60,9 +42,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
60
42
|
label: trans.__('Search and Replace'),
|
|
61
43
|
caption: trans.__('Show the Search and Replace panel'),
|
|
62
44
|
execute: () => {
|
|
63
|
-
//
|
|
64
|
-
// tab, and the widget focuses its search input from `onAfterShow`, so
|
|
65
|
-
// the user can start typing a query immediately. It no-ops if
|
|
45
|
+
// The widget focuses its search input from `onAfterShow`; no-ops if
|
|
66
46
|
// `jupyterlab-search-replace` is unavailable.
|
|
67
47
|
labShell.activateById(SEARCH_REPLACE_WIDGET_ID);
|
|
68
48
|
}
|
package/src/showOutput/index.ts
CHANGED
|
@@ -12,24 +12,12 @@ import { coerceData } from '../mimeData';
|
|
|
12
12
|
|
|
13
13
|
const PLUGIN_ID = 'xtralab:show-output';
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* Render arbitrary rich content into a panel, with no notebook and no kernel.
|
|
17
|
-
*/
|
|
18
15
|
const SHOW_COMMAND = 'xtralab:show';
|
|
19
16
|
|
|
20
17
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* This is the kernel-free counterpart to running a notebook. A coding agent
|
|
25
|
-
* generates the content itself (a Vega-Lite spec, a Markdown explainer, an
|
|
26
|
-
* HTML fragment, an SVG, a base64 image) and hands it to this command, which
|
|
27
|
-
* renders it with the same renderers JupyterLab uses for cell output and docks
|
|
28
|
-
* the result beside the code. Nothing is written to disk and no kernel is
|
|
29
|
-
* required, so it works even where the kernel has no plotting libraries.
|
|
30
|
-
*
|
|
31
|
-
* Repeated calls with the same `id` reuse one panel, so a walkthrough can
|
|
32
|
-
* refresh the view in place instead of piling up tabs.
|
|
18
|
+
* Contributes `xtralab:show`: render a single MIME bundle into a panel with
|
|
19
|
+
* the same renderers JupyterLab uses for cell output — no kernel, no file on
|
|
20
|
+
* disk. Repeated calls with the same `id` reuse one panel.
|
|
33
21
|
*/
|
|
34
22
|
const plugin: JupyterFrontEndPlugin<void> = {
|
|
35
23
|
id: PLUGIN_ID,
|
|
@@ -48,7 +36,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
48
36
|
const { commands } = app;
|
|
49
37
|
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
50
38
|
|
|
51
|
-
// One reusable panel per `id`, so repeated shows refresh in place.
|
|
52
39
|
const panels = new Map<string, Panel>();
|
|
53
40
|
|
|
54
41
|
commands.addCommand(SHOW_COMMAND, {
|
|
@@ -120,9 +107,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
120
107
|
|
|
121
108
|
const model = rendermime.createModel({ data: bundle, trusted: true });
|
|
122
109
|
const renderer = rendermime.createRenderer(chosen);
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
// the content is taller than it (style/showOutput.css).
|
|
110
|
+
// Not a MainAreaWidget: it does not lay out its content in the side
|
|
111
|
+
// area; the panel scrolls via style/showOutput.css.
|
|
126
112
|
const widget = new Panel();
|
|
127
113
|
widget.addClass('jp-xtralab-ShowPanel');
|
|
128
114
|
widget.id = `xtralab-show-${id}`;
|
|
@@ -130,7 +116,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
130
116
|
widget.title.closable = true;
|
|
131
117
|
widget.addWidget(renderer);
|
|
132
118
|
|
|
133
|
-
// Replace any existing panel with this id so shows refresh in place.
|
|
134
119
|
const existing = panels.get(id);
|
|
135
120
|
if (existing && !existing.isDisposed) {
|
|
136
121
|
existing.dispose();
|
|
@@ -143,8 +128,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
143
128
|
});
|
|
144
129
|
|
|
145
130
|
// Attach before rendering so renderers that need layout (Vega) size
|
|
146
|
-
// correctly.
|
|
147
|
-
// only split the document area when `area` is "main".
|
|
131
|
+
// correctly.
|
|
148
132
|
if (area === 'main') {
|
|
149
133
|
labShell.add(widget, 'main', {
|
|
150
134
|
mode,
|
package/src/sidebar/index.ts
CHANGED
|
@@ -15,8 +15,17 @@ const PLUGIN_ID = 'xtralab:sidebar';
|
|
|
15
15
|
* per-widget options over the ones passed in.
|
|
16
16
|
*/
|
|
17
17
|
interface ITarget {
|
|
18
|
+
/**
|
|
19
|
+
* The shell widget id of the sidebar tab.
|
|
20
|
+
*/
|
|
18
21
|
id: string;
|
|
22
|
+
/**
|
|
23
|
+
* The sidebar rank used when re-adding the hidden tab.
|
|
24
|
+
*/
|
|
19
25
|
rank: number;
|
|
26
|
+
/**
|
|
27
|
+
* The boolean plugin setting controlling the tab's visibility.
|
|
28
|
+
*/
|
|
20
29
|
settingKey:
|
|
21
30
|
| 'showTerminals'
|
|
22
31
|
| 'showFileBrowser'
|
|
@@ -24,7 +33,13 @@ interface ITarget {
|
|
|
24
33
|
| 'showDefaultFileBrowser'
|
|
25
34
|
| 'showRunningSessions'
|
|
26
35
|
| 'showSearchReplace';
|
|
36
|
+
/**
|
|
37
|
+
* The id of the toggle command registered for the tab.
|
|
38
|
+
*/
|
|
27
39
|
command: string;
|
|
40
|
+
/**
|
|
41
|
+
* The localized label of the toggle command.
|
|
42
|
+
*/
|
|
28
43
|
label: (trans: ReturnType<ITranslator['load']>) => string;
|
|
29
44
|
}
|
|
30
45
|
|
|
@@ -74,13 +89,10 @@ const TARGETS: ITarget[] = [
|
|
|
74
89
|
];
|
|
75
90
|
|
|
76
91
|
/**
|
|
77
|
-
* Toggles individual sidebar tabs from View > Appearance > Sidebars
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* a hidden tab came from is only tracked in-memory, so after a reload a
|
|
82
|
-
* re-shown tab lands on the left. The menu placement is contributed
|
|
83
|
-
* declaratively in `schema/sidebar.json`.
|
|
92
|
+
* Toggles individual sidebar tabs from View > Appearance > Sidebars. Hiding
|
|
93
|
+
* detaches the widget while keeping the instance for a later re-add; which
|
|
94
|
+
* side it came from is only tracked in-memory, so after a reload a re-shown
|
|
95
|
+
* tab lands on the left. Menu placement is declared in schema/sidebar.json.
|
|
84
96
|
*/
|
|
85
97
|
const plugin: JupyterFrontEndPlugin<void> = {
|
|
86
98
|
id: PLUGIN_ID,
|
|
@@ -102,8 +114,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
102
114
|
// instances here so hidden tabs can be re-added.
|
|
103
115
|
const widgetCache = new Map<string, Widget>();
|
|
104
116
|
|
|
105
|
-
// Side each hidden widget was removed from, so re-showing restores it
|
|
106
|
-
// there.
|
|
107
117
|
const hiddenFrom = new Map<string, 'left' | 'right'>();
|
|
108
118
|
|
|
109
119
|
const findInSidebars = (
|
|
@@ -147,7 +157,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
147
157
|
const apply = (target: ITarget): void => {
|
|
148
158
|
const widget = captureWidget(target.id);
|
|
149
159
|
if (!widget) {
|
|
150
|
-
// Not seen yet (extension disabled or not added) — nothing to do.
|
|
151
160
|
return;
|
|
152
161
|
}
|
|
153
162
|
const area = locate(target.id);
|
|
@@ -202,8 +211,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
202
211
|
);
|
|
203
212
|
}
|
|
204
213
|
}
|
|
205
|
-
// No settings
|
|
206
|
-
// for the current session only.
|
|
214
|
+
// No settings (or the write failed): apply in-memory for this session.
|
|
207
215
|
apply(target);
|
|
208
216
|
commands.notifyCommandChanged(target.command);
|
|
209
217
|
}
|
|
@@ -22,16 +22,28 @@ const OSC_RXVT_NOTIFY = 777;
|
|
|
22
22
|
|
|
23
23
|
const MAX_TEXT_LENGTH = 256;
|
|
24
24
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
// JupyterLab's terminal keeps its xterm in a private `_term` field; structural
|
|
26
|
+
// types use its hooks without `@xterm/xterm`, going inert (warning) if renamed.
|
|
27
|
+
/**
|
|
28
|
+
* A handle returned by an xterm listener registration.
|
|
29
|
+
*/
|
|
30
30
|
interface IXtermDisposable {
|
|
31
|
+
/**
|
|
32
|
+
* Unregister the listener.
|
|
33
|
+
*/
|
|
31
34
|
dispose(): void;
|
|
32
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* The subset of the xterm.js API used to observe bells and OSC sequences.
|
|
38
|
+
*/
|
|
33
39
|
interface IXtermTerminal {
|
|
40
|
+
/**
|
|
41
|
+
* Register a handler invoked when the terminal bell rings.
|
|
42
|
+
*/
|
|
34
43
|
onBell(handler: () => void): IXtermDisposable;
|
|
44
|
+
/**
|
|
45
|
+
* The escape-sequence parser used to register OSC handlers.
|
|
46
|
+
*/
|
|
35
47
|
parser: {
|
|
36
48
|
registerOscHandler(
|
|
37
49
|
ident: number,
|
|
@@ -39,8 +51,17 @@ interface IXtermTerminal {
|
|
|
39
51
|
): IXtermDisposable;
|
|
40
52
|
};
|
|
41
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* A JupyterLab terminal widget content with its private xterm exposed.
|
|
56
|
+
*/
|
|
42
57
|
interface ITerminalContentInternals extends ITerminal.ITerminal {
|
|
58
|
+
/**
|
|
59
|
+
* A promise resolving once the underlying xterm exists.
|
|
60
|
+
*/
|
|
43
61
|
ready: Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* The private xterm instance; `undefined` if upstream renames the field.
|
|
64
|
+
*/
|
|
44
65
|
_term?: IXtermTerminal;
|
|
45
66
|
}
|
|
46
67
|
|
|
@@ -48,26 +69,30 @@ type TerminalWidget = MainAreaWidget<ITerminal.ITerminal>;
|
|
|
48
69
|
|
|
49
70
|
// The renderer→main bridge the desktop shell injects on the lab window. Absent
|
|
50
71
|
// for pip-install users in a browser, who fall back to web Notifications.
|
|
72
|
+
/**
|
|
73
|
+
* The desktop notification API exposed on `window.xtralab`.
|
|
74
|
+
*/
|
|
51
75
|
interface IDesktopBridge {
|
|
76
|
+
/**
|
|
77
|
+
* Show a native notification, tagged with the emitting terminal session.
|
|
78
|
+
*/
|
|
52
79
|
notify?: (
|
|
53
80
|
title: string,
|
|
54
81
|
body: string,
|
|
55
82
|
session?: string
|
|
56
83
|
) => Promise<void> | void;
|
|
84
|
+
/**
|
|
85
|
+
* Register a callback invoked with the tagged terminal session name when
|
|
86
|
+
* the user clicks a notification.
|
|
87
|
+
*/
|
|
57
88
|
onFocusTerminal?: (callback: (session: string) => void) => void;
|
|
58
89
|
}
|
|
59
90
|
|
|
60
91
|
/**
|
|
61
|
-
* Turns the
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* A notification is suppressed while its terminal is the focused, active tab,
|
|
67
|
-
* and throttled per terminal. The desktop shell advertises
|
|
68
|
-
* `TERM_PROGRAM=iTerm.app` so agents emit OSC 9 in the first place, and the
|
|
69
|
-
* session name is forwarded so clicking a notification focuses the terminal
|
|
70
|
-
* that fired it.
|
|
92
|
+
* Turns the OSC 9 / OSC 777 / bell sequences agents emit into desktop
|
|
93
|
+
* notifications via `window.xtralab.notify` (desktop) or the web Notifications
|
|
94
|
+
* API — JupyterLab's xterm renders them but never forwards them to the OS.
|
|
95
|
+
* The desktop shell advertises `TERM_PROGRAM=iTerm.app` so agents emit OSC 9.
|
|
71
96
|
*/
|
|
72
97
|
const plugin: JupyterFrontEndPlugin<void> = {
|
|
73
98
|
id: PLUGIN_ID,
|
|
@@ -87,13 +112,9 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
87
112
|
let enabled = true;
|
|
88
113
|
let notifyOnBell = true;
|
|
89
114
|
|
|
90
|
-
// Per-widget throttle timestamps and xterm hooks. WeakMaps let disposed
|
|
91
|
-
// widgets be collected; hooks are torn down when a tab closes.
|
|
92
115
|
const lastNotified = new WeakMap<TerminalWidget, number>();
|
|
93
116
|
const hooks = new WeakMap<TerminalWidget, IXtermDisposable[]>();
|
|
94
117
|
|
|
95
|
-
// True when the user is already looking at this terminal, so a banner would
|
|
96
|
-
// just be noise.
|
|
97
118
|
const isActivelyViewing = (widget: TerminalWidget): boolean =>
|
|
98
119
|
document.hasFocus() && app.shell.currentWidget === widget;
|
|
99
120
|
|
|
@@ -107,7 +128,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
107
128
|
);
|
|
108
129
|
return;
|
|
109
130
|
}
|
|
110
|
-
// Plain-browser fallback (pip install): the Web Notifications API.
|
|
111
131
|
if (typeof Notification === 'undefined') {
|
|
112
132
|
return;
|
|
113
133
|
}
|
|
@@ -146,9 +166,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
146
166
|
widget.title.label || trans.__('Terminal');
|
|
147
167
|
|
|
148
168
|
const onOsc9 = (widget: TerminalWidget, data: string): boolean => {
|
|
149
|
-
// OSC 9 is overloaded: ConEmu/Windows Terminal use
|
|
150
|
-
//
|
|
151
|
-
// payload and leave it unconsumed rather than treat it as a notification.
|
|
169
|
+
// OSC 9 is overloaded: ConEmu/Windows Terminal use numeric subcommands
|
|
170
|
+
// (e.g. `9 ; 4` progress); leave those unconsumed.
|
|
152
171
|
if (/^\d+(;|$)/.test(data)) {
|
|
153
172
|
return false;
|
|
154
173
|
}
|
|
@@ -197,8 +216,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
197
216
|
);
|
|
198
217
|
return;
|
|
199
218
|
}
|
|
200
|
-
// Handlers are wrapped so a parse slip cannot break xterm's parser; an
|
|
201
|
-
// OSC handler returns whether it consumed the sequence.
|
|
202
219
|
const disposables: IXtermDisposable[] = [
|
|
203
220
|
term.onBell(() => guard(() => onBell(widget))),
|
|
204
221
|
term.parser.registerOscHandler(OSC_ITERM2_GROWL, data =>
|
|
@@ -8,35 +8,23 @@ import type { SessionRegistry, TerminalWidget } from './model';
|
|
|
8
8
|
import type { IAgentTerminalSession, IAgentTerminals } from './tokens';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Pause between pasting the prompt and pressing Enter
|
|
12
|
-
*
|
|
13
|
-
* submitting. Sent back to back, the server can coalesce both websocket
|
|
14
|
-
* messages into one read and the TUI would have to split the paste from the
|
|
15
|
-
* trailing Enter inside a single chunk.
|
|
11
|
+
* Pause between pasting the prompt and pressing Enter so the server cannot
|
|
12
|
+
* coalesce both websocket messages into one PTY read.
|
|
16
13
|
*/
|
|
17
14
|
const PASTE_SUBMIT_DELAY_MS = 150;
|
|
18
15
|
|
|
19
16
|
/**
|
|
20
|
-
* Bracketed-paste markers (`CSI 200~` / `CSI 201~`)
|
|
21
|
-
*
|
|
22
|
-
* insert into its input box instead of submitting the message at each line
|
|
23
|
-
* break. Every send target is a terminal where detection just confirmed a
|
|
24
|
-
* running coding agent, and the agent TUIs keep bracketed-paste mode on.
|
|
17
|
+
* Bracketed-paste markers (`CSI 200~` / `CSI 201~`): a wrapped prompt arrives
|
|
18
|
+
* as one pasted block, so newlines insert instead of submitting at each break.
|
|
25
19
|
*/
|
|
26
20
|
const PASTE_OPEN = '\x1b[200~';
|
|
27
21
|
const PASTE_CLOSE = '\x1b[201~';
|
|
28
22
|
|
|
29
|
-
/**
|
|
30
|
-
* How long to wait for the session's websocket to reach `connected` before
|
|
31
|
-
* failing the send, so a server hiccup surfaces as an error notification
|
|
32
|
-
* rather than a silent hang.
|
|
33
|
-
*/
|
|
34
23
|
const CONNECT_TIMEOUT_MS = 10_000;
|
|
35
24
|
|
|
36
25
|
/**
|
|
37
26
|
* Resolve once `session` reports `connected`; reject with `timeoutMessage`
|
|
38
|
-
* after {@link CONNECT_TIMEOUT_MS}
|
|
39
|
-
* typing into a fresh terminal — writes sent while the websocket is still
|
|
27
|
+
* after {@link CONNECT_TIMEOUT_MS} — writes sent while the websocket is still
|
|
40
28
|
* connecting disappear silently.
|
|
41
29
|
*/
|
|
42
30
|
function waitUntilConnected(
|
|
@@ -67,11 +55,8 @@ function waitUntilConnected(
|
|
|
67
55
|
|
|
68
56
|
/**
|
|
69
57
|
* The {@link IAgentTerminals} implementation: a read-only view over the
|
|
70
|
-
*
|
|
71
|
-
* detection-confirmed coding agent, plus the "send a prompt into one"
|
|
72
|
-
* action. Lives in the `xtralab:terminals` plugin so the ask-agent popup
|
|
73
|
-
* (and any future caller) can target running agents without duplicating the
|
|
74
|
-
* detection plumbing.
|
|
58
|
+
* panel's {@link SessionRegistry} narrowed to sessions with a
|
|
59
|
+
* detection-confirmed coding agent, plus the "send a prompt into one" action.
|
|
75
60
|
*/
|
|
76
61
|
export class AgentTerminals implements IAgentTerminals {
|
|
77
62
|
constructor(options: AgentTerminals.IOptions) {
|
|
@@ -84,6 +69,9 @@ export class AgentTerminals implements IAgentTerminals {
|
|
|
84
69
|
this._registry.stateChanged.connect(this._onRegistryStateChanged, this);
|
|
85
70
|
}
|
|
86
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Snapshot of the sessions with a detection-confirmed running agent.
|
|
74
|
+
*/
|
|
87
75
|
sessions(): IAgentTerminalSession[] {
|
|
88
76
|
const result: IAgentTerminalSession[] = [];
|
|
89
77
|
for (const name of this._registry.sessionNames()) {
|
|
@@ -101,15 +89,20 @@ export class AgentTerminals implements IAgentTerminals {
|
|
|
101
89
|
return result;
|
|
102
90
|
}
|
|
103
91
|
|
|
92
|
+
/**
|
|
93
|
+
* A signal emitted whenever the {@link sessions} snapshot may have changed.
|
|
94
|
+
*/
|
|
104
95
|
get changed(): ISignal<IAgentTerminals, void> {
|
|
105
96
|
return this._changed;
|
|
106
97
|
}
|
|
107
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Paste the prompt into the named session and press Enter, re-validating
|
|
101
|
+
* with the server first that an agent still runs there.
|
|
102
|
+
*/
|
|
108
103
|
async sendPrompt(name: string, prompt: string): Promise<void> {
|
|
109
|
-
// Re-validate against the server
|
|
110
|
-
//
|
|
111
|
-
// into the shell prompt left behind by an exited agent would be
|
|
112
|
-
// *executed* on the Enter below.
|
|
104
|
+
// Re-validate against the server: prose pasted into the shell prompt an
|
|
105
|
+
// exited agent left behind would be *executed* on the Enter below.
|
|
113
106
|
await this._terminals.refreshRunning();
|
|
114
107
|
const alive = Array.from(this._terminals.running()).some(
|
|
115
108
|
model => model.name === name
|
|
@@ -119,9 +112,8 @@ export class AgentTerminals implements IAgentTerminals {
|
|
|
119
112
|
}
|
|
120
113
|
const detected = await fetchRunningAgents(this._detectCommands());
|
|
121
114
|
if (detected === null) {
|
|
122
|
-
// Detection
|
|
123
|
-
//
|
|
124
|
-
// invites the retry that would succeed.
|
|
115
|
+
// Detection unavailable (older server, transient failure) is not proof
|
|
116
|
+
// the agent is gone; refuse the write and invite a retry.
|
|
125
117
|
throw new Error(
|
|
126
118
|
this._trans.__('Could not verify the terminal — try sending again.')
|
|
127
119
|
);
|
|
@@ -133,11 +125,6 @@ export class AgentTerminals implements IAgentTerminals {
|
|
|
133
125
|
);
|
|
134
126
|
}
|
|
135
127
|
|
|
136
|
-
// Deliver in the background: write straight to the session over its
|
|
137
|
-
// websocket instead of revealing its tab, so focus stays wherever the
|
|
138
|
-
// user is asking from. An open tab's live connection is reused; a
|
|
139
|
-
// closed tab gets a short-lived client connection to the running server
|
|
140
|
-
// session, without creating any widget.
|
|
141
128
|
const widget = this._findWidget(name);
|
|
142
129
|
const session =
|
|
143
130
|
widget !== null
|
|
@@ -148,12 +135,8 @@ export class AgentTerminals implements IAgentTerminals {
|
|
|
148
135
|
session,
|
|
149
136
|
this._trans.__('Timed out connecting to the terminal.')
|
|
150
137
|
);
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
// tab's `Terminal.paste`) also sidesteps the one case paste() gets
|
|
154
|
-
// wrong: a freshly reconnected xterm whose replayed scrollback no
|
|
155
|
-
// longer holds the agent's original mode-setting escape, which makes
|
|
156
|
-
// paste() skip the wrapping the application still expects.
|
|
138
|
+
// Writing the markers directly (not `Terminal.paste`) sidesteps paste()
|
|
139
|
+
// skipping them on a reconnected xterm that lost the mode-setting escape.
|
|
157
140
|
session.send({
|
|
158
141
|
type: 'stdin',
|
|
159
142
|
content: [PASTE_OPEN + prompt.replace(/\r?\n/g, '\r') + PASTE_CLOSE]
|
|
@@ -162,16 +145,14 @@ export class AgentTerminals implements IAgentTerminals {
|
|
|
162
145
|
window.setTimeout(resolve, PASTE_SUBMIT_DELAY_MS)
|
|
163
146
|
);
|
|
164
147
|
if (session.isDisposed) {
|
|
165
|
-
// The tab closed during the pause; the pasted text already reached
|
|
166
|
-
// the agent's input box but Enter can no longer be delivered here.
|
|
167
148
|
throw new Error(
|
|
168
149
|
this._trans.__('The terminal closed before the prompt was submitted.')
|
|
169
150
|
);
|
|
170
151
|
}
|
|
171
152
|
session.send({ type: 'stdin', content: ['\r'] });
|
|
172
153
|
} finally {
|
|
173
|
-
//
|
|
174
|
-
//
|
|
154
|
+
// Disposing an ad-hoc client connection leaves the server session
|
|
155
|
+
// (and its agent) running.
|
|
175
156
|
if (widget === null && !session.isDisposed) {
|
|
176
157
|
session.dispose();
|
|
177
158
|
}
|
|
@@ -209,27 +190,22 @@ export namespace AgentTerminals {
|
|
|
209
190
|
* The panel's session registry — the source of the session snapshot.
|
|
210
191
|
*/
|
|
211
192
|
registry: SessionRegistry;
|
|
212
|
-
|
|
213
193
|
/**
|
|
214
194
|
* Tracker of open terminal widgets, to reuse an open tab's connection.
|
|
215
195
|
*/
|
|
216
196
|
tracker: ITerminalTracker;
|
|
217
|
-
|
|
218
197
|
/**
|
|
219
198
|
* The terminal session manager, for validation and ad-hoc connections.
|
|
220
199
|
*/
|
|
221
200
|
terminals: Terminal.IManager;
|
|
222
|
-
|
|
223
201
|
/**
|
|
224
202
|
* Names to detect — same list the registry polls with.
|
|
225
203
|
*/
|
|
226
204
|
detectCommands: () => string[];
|
|
227
|
-
|
|
228
205
|
/**
|
|
229
206
|
* Whether a detected command belongs to a coding agent (not an editor).
|
|
230
207
|
*/
|
|
231
208
|
isAgentCommand: (command: string) => boolean;
|
|
232
|
-
|
|
233
209
|
/**
|
|
234
210
|
* Translation bundle for the error messages thrown to callers.
|
|
235
211
|
*/
|
|
@@ -2,14 +2,10 @@ import { URLExt } from '@jupyterlab/coreutils';
|
|
|
2
2
|
import { ServerConnection } from '@jupyterlab/services';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Ask the xtralab server which of `commands`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* On any failure (endpoint missing on an older server, network error,
|
|
10
|
-
* malformed response) the whole result is `null` — callers should treat that
|
|
11
|
-
* as "detection unavailable" and fall back to the optimistic launch tags
|
|
12
|
-
* rather than clearing any badges.
|
|
5
|
+
* Ask the xtralab server which of `commands` runs in each open terminal
|
|
6
|
+
* session: a map from session name to matched command (`null` = idle). Any
|
|
7
|
+
* failure resolves the whole result to `null` — "detection unavailable",
|
|
8
|
+
* not "no agents"; callers must not clear badges on it.
|
|
13
9
|
*/
|
|
14
10
|
export async function fetchRunningAgents(
|
|
15
11
|
commands: string[]
|