xtralab 0.14.1 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.d.ts +7 -0
- package/lib/customPanel/index.js +89 -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 +51 -98
- package/lib/fileBrowser/contents.d.ts +14 -0
- package/lib/fileBrowser/contents.js +28 -0
- package/lib/fileBrowser/dragAndDrop.d.ts +79 -0
- package/lib/fileBrowser/dragAndDrop.js +110 -0
- package/lib/fileBrowser/fileBrowser.d.ts +21 -9
- package/lib/fileBrowser/fileBrowser.js +211 -405
- 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 +41 -10
- package/lib/fileBrowser/widget.d.ts +170 -53
- package/lib/fileBrowser/widget.js +242 -18
- 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 +2 -9
- package/lib/launcher/agents.d.ts +55 -51
- package/lib/launcher/agents.js +25 -54
- 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 +14 -37
- 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/schemaDefaults.d.ts +2 -9
- package/lib/launcher/schemaDefaults.js +37 -13
- 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 -13
- package/lib/sidebar/index.js +73 -46
- 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 -33
- package/lib/terminals/index.js +24 -102
- 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 +72 -38
- package/lib/terminals/widget.js +173 -73
- 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/schema/launcher.json +2 -2
- package/schema/sidebar.json +47 -7
- 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 +119 -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 +70 -98
- package/src/fileBrowser/contents.ts +33 -0
- package/src/fileBrowser/dragAndDrop.ts +188 -0
- package/src/fileBrowser/fileBrowser.tsx +259 -440
- 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 +43 -10
- package/src/fileBrowser/widget.tsx +344 -64
- 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 +2 -9
- package/src/launcher/agents.ts +74 -92
- 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 +15 -37
- package/src/launcher/index.ts +12 -55
- package/src/launcher/invocation.ts +7 -22
- package/src/launcher/registry.ts +9 -7
- package/src/launcher/schemaDefaults.ts +43 -13
- 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 +103 -52
- 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 +31 -102
- package/src/terminals/model.ts +130 -246
- package/src/terminals/tokens.ts +11 -22
- package/src/terminals/widget.tsx +229 -113
- 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 +25 -73
- package/style/chrome.css +108 -308
- package/style/commandBar.css +4 -20
- package/style/customPanel.css +6 -0
- package/style/git.css +22 -184
- package/style/highlight.css +1 -3
- package/style/index.js +1 -0
- 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 +18 -123
- package/style/topBar.css +2 -22
- package/style/walkthrough.css +0 -8
|
@@ -12,16 +12,10 @@ const OSC_ITERM2_GROWL = 9;
|
|
|
12
12
|
const OSC_RXVT_NOTIFY = 777;
|
|
13
13
|
const MAX_TEXT_LENGTH = 256;
|
|
14
14
|
/**
|
|
15
|
-
* Turns the
|
|
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,37 +1,4 @@
|
|
|
1
1
|
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
2
|
import { IAgentTerminals } from './tokens';
|
|
3
|
-
/**
|
|
4
|
-
* Adds a left-sidebar panel that lists every running terminal session.
|
|
5
|
-
*
|
|
6
|
-
* Each row is labelled with the *real* title the running program
|
|
7
|
-
* published — the launcher's agent name, or whatever title an xterm
|
|
8
|
-
* escape sequence set — which the registry caches so it survives the
|
|
9
|
-
* user closing the tab while the session keeps running on the server.
|
|
10
|
-
* JupyterLab's built-in "Running Terminals and Kernels" panel lists the
|
|
11
|
-
* same sessions but only by their `terminals/<n>` name, so this panel
|
|
12
|
-
* resolves and caches the published title to show something meaningful.
|
|
13
|
-
* Rows running a coding agent carry a smaller second line below the
|
|
14
|
-
* title with the agent's latest line of output, so the panel shows what
|
|
15
|
-
* each agent is doing at a glance — on by default, and switchable off
|
|
16
|
-
* through the `showAgentActivity` setting.
|
|
17
|
-
*
|
|
18
|
-
* Clicking a row activates the existing tab if one is open, or reopens
|
|
19
|
-
* the session in a fresh terminal widget (`terminal:open`). The inline
|
|
20
|
-
* `×` button shuts the session down on the server. The header carries a
|
|
21
|
-
* `+` button and a stop button (the latter shuts every session down at
|
|
22
|
-
* once, after a confirmation).
|
|
23
|
-
*
|
|
24
|
-
* The `+` button drops down a menu — built from the launcher's shared
|
|
25
|
-
* `IAgentRegistry` — listing each available agent (Claude, Codex, …)
|
|
26
|
-
* followed by a plain terminal, so starting an agent session is as
|
|
27
|
-
* consistent here as in the launcher and reuses the very same registered
|
|
28
|
-
* commands and icons. When the launcher is disabled or no agents are
|
|
29
|
-
* installed, the button falls back to opening a plain terminal directly.
|
|
30
|
-
*
|
|
31
|
-
* The plugin also provides {@link IAgentTerminals} — the sessions with a
|
|
32
|
-
* detection-confirmed running agent, plus a "paste a prompt into one"
|
|
33
|
-
* action — which the ask-agent popup uses to offer running agents as
|
|
34
|
-
* targets next to "new terminal".
|
|
35
|
-
*/
|
|
36
3
|
declare const plugin: JupyterFrontEndPlugin<IAgentTerminals>;
|
|
37
4
|
export default plugin;
|
package/lib/terminals/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ILayoutRestorer } from '@jupyterlab/application';
|
|
2
|
-
import { Dialog, showDialog } from '@jupyterlab/apputils';
|
|
2
|
+
import { Dialog, IMovableSectionRegistry, showDialog } from '@jupyterlab/apputils';
|
|
3
3
|
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
4
4
|
import { ITerminalTracker } from '@jupyterlab/terminal';
|
|
5
5
|
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
@@ -12,39 +12,6 @@ import { SessionRegistry } from './model';
|
|
|
12
12
|
import { IAgentTerminals } from './tokens';
|
|
13
13
|
import { RunningTerminals } from './widget';
|
|
14
14
|
const PLUGIN_ID = 'xtralab:terminals';
|
|
15
|
-
/**
|
|
16
|
-
* Adds a left-sidebar panel that lists every running terminal session.
|
|
17
|
-
*
|
|
18
|
-
* Each row is labelled with the *real* title the running program
|
|
19
|
-
* published — the launcher's agent name, or whatever title an xterm
|
|
20
|
-
* escape sequence set — which the registry caches so it survives the
|
|
21
|
-
* user closing the tab while the session keeps running on the server.
|
|
22
|
-
* JupyterLab's built-in "Running Terminals and Kernels" panel lists the
|
|
23
|
-
* same sessions but only by their `terminals/<n>` name, so this panel
|
|
24
|
-
* resolves and caches the published title to show something meaningful.
|
|
25
|
-
* Rows running a coding agent carry a smaller second line below the
|
|
26
|
-
* title with the agent's latest line of output, so the panel shows what
|
|
27
|
-
* each agent is doing at a glance — on by default, and switchable off
|
|
28
|
-
* through the `showAgentActivity` setting.
|
|
29
|
-
*
|
|
30
|
-
* Clicking a row activates the existing tab if one is open, or reopens
|
|
31
|
-
* the session in a fresh terminal widget (`terminal:open`). The inline
|
|
32
|
-
* `×` button shuts the session down on the server. The header carries a
|
|
33
|
-
* `+` button and a stop button (the latter shuts every session down at
|
|
34
|
-
* once, after a confirmation).
|
|
35
|
-
*
|
|
36
|
-
* The `+` button drops down a menu — built from the launcher's shared
|
|
37
|
-
* `IAgentRegistry` — listing each available agent (Claude, Codex, …)
|
|
38
|
-
* followed by a plain terminal, so starting an agent session is as
|
|
39
|
-
* consistent here as in the launcher and reuses the very same registered
|
|
40
|
-
* commands and icons. When the launcher is disabled or no agents are
|
|
41
|
-
* installed, the button falls back to opening a plain terminal directly.
|
|
42
|
-
*
|
|
43
|
-
* The plugin also provides {@link IAgentTerminals} — the sessions with a
|
|
44
|
-
* detection-confirmed running agent, plus a "paste a prompt into one"
|
|
45
|
-
* action — which the ask-agent popup uses to offer running agents as
|
|
46
|
-
* targets next to "new terminal".
|
|
47
|
-
*/
|
|
48
15
|
const plugin = {
|
|
49
16
|
id: PLUGIN_ID,
|
|
50
17
|
description: 'A left-sidebar panel listing every running terminal session by its real (program-published) title, with activate/reopen, shutdown and new-terminal actions.',
|
|
@@ -57,17 +24,13 @@ const plugin = {
|
|
|
57
24
|
IAgentRegistry,
|
|
58
25
|
IEditorRegistry,
|
|
59
26
|
IAgentSessions,
|
|
60
|
-
ISettingRegistry
|
|
27
|
+
ISettingRegistry,
|
|
28
|
+
IMovableSectionRegistry
|
|
61
29
|
],
|
|
62
|
-
activate: (app, tracker, restorer, translator, agentRegistry, editorRegistry, agentSessions, settingRegistry) => {
|
|
30
|
+
activate: (app, tracker, restorer, translator, agentRegistry, editorRegistry, agentSessions, settingRegistry, movableSections) => {
|
|
63
31
|
const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
// terminal icon when nothing recognised is running. Detection reports
|
|
67
|
-
// either the configured `command` or the canonical `id` (see
|
|
68
|
-
// `detectCommands`), so a row whose command is an alias — e.g. `ccm` for
|
|
69
|
-
// `claude` — still resolves to the right logo via its id. Reads the
|
|
70
|
-
// registries live so it tracks settings changes. Used to badge each row.
|
|
32
|
+
// Detection reports either the configured `command` or the canonical `id`,
|
|
33
|
+
// so an aliased command (e.g. `ccm` for `claude`) still maps to its logo.
|
|
71
34
|
const iconForCommand = (command) => {
|
|
72
35
|
var _a;
|
|
73
36
|
if (!command) {
|
|
@@ -77,18 +40,9 @@ const plugin = {
|
|
|
77
40
|
if (agent) {
|
|
78
41
|
return agent.icon;
|
|
79
42
|
}
|
|
80
|
-
// A terminal running an editor (Neovim/Vim, or a user-configured one) is
|
|
81
|
-
// badged with its logo, the same as an agent.
|
|
82
43
|
const editor = editorRegistry === null || editorRegistry === void 0 ? void 0 : editorRegistry.editors.find(e => e.command === command || e.id === command);
|
|
83
44
|
return (_a = editor === null || editor === void 0 ? void 0 : editor.icon) !== null && _a !== void 0 ? _a : terminalIcon;
|
|
84
45
|
};
|
|
85
|
-
// The names the server should look for in each terminal's process tree:
|
|
86
|
-
// for every agent and editor, its configured `command` plus its canonical
|
|
87
|
-
// `id`. The id is the real CLI name (e.g. `claude`, `nvim`), so an agent
|
|
88
|
-
// whose command points at an alias — e.g. `ccm` running `claude` — is
|
|
89
|
-
// still recognised by the process it actually spawns. Read live so the
|
|
90
|
-
// list tracks settings changes; deduplicated since command and id usually
|
|
91
|
-
// coincide.
|
|
92
46
|
const detectCommands = () => {
|
|
93
47
|
var _a, _b;
|
|
94
48
|
const names = new Set();
|
|
@@ -102,10 +56,6 @@ const plugin = {
|
|
|
102
56
|
}
|
|
103
57
|
return Array.from(names);
|
|
104
58
|
};
|
|
105
|
-
// Whether a detected command/id belongs to a coding agent rather than an
|
|
106
|
-
// editor. Only agent sessions get a latest-activity line (editor
|
|
107
|
-
// terminals stay badge-only), and only they are offered as prompt
|
|
108
|
-
// targets through `IAgentTerminals`.
|
|
109
59
|
const isAgentCommand = (command) => {
|
|
110
60
|
var _a;
|
|
111
61
|
return (_a = agentRegistry === null || agentRegistry === void 0 ? void 0 : agentRegistry.agents.some(a => a.command === command || a.id === command)) !== null && _a !== void 0 ? _a : false;
|
|
@@ -113,18 +63,13 @@ const plugin = {
|
|
|
113
63
|
const registry = new SessionRegistry({
|
|
114
64
|
serviceManager: app.serviceManager,
|
|
115
65
|
tracker,
|
|
116
|
-
// The shell tells the registry which widget is current, so the panel can
|
|
117
|
-
// highlight the terminal that is the current main-area widget (and
|
|
118
|
-
// nothing while a notebook or other widget is current instead).
|
|
119
66
|
shell: app.shell,
|
|
120
67
|
agentSessions,
|
|
121
68
|
detectCommands,
|
|
122
69
|
isAgentCommand
|
|
123
70
|
});
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
// `stateChanged` is safe: `Title.icon` ignores an unchanged assignment, so
|
|
127
|
-
// the writes neither loop nor churn.
|
|
71
|
+
// `Title.icon` ignores an unchanged assignment, so re-running per
|
|
72
|
+
// stateChanged is cheap.
|
|
128
73
|
const syncTabIcons = () => {
|
|
129
74
|
tracker.forEach(widget => {
|
|
130
75
|
const session = widget.content.session;
|
|
@@ -137,10 +82,8 @@ const plugin = {
|
|
|
137
82
|
registry.stateChanged.connect(syncTabIcons);
|
|
138
83
|
syncTabIcons();
|
|
139
84
|
const onActivate = (name) => {
|
|
140
|
-
// `terminal:open`
|
|
141
|
-
//
|
|
142
|
-
// session it activates that tab, otherwise it spins up a fresh
|
|
143
|
-
// widget connected to the live session.
|
|
85
|
+
// `terminal:open` activates an attached tab or spins up a fresh widget
|
|
86
|
+
// connected to the live session.
|
|
144
87
|
void app.commands.execute('terminal:open', { name });
|
|
145
88
|
};
|
|
146
89
|
const onShutdown = (name) => {
|
|
@@ -149,10 +92,6 @@ const plugin = {
|
|
|
149
92
|
});
|
|
150
93
|
};
|
|
151
94
|
const onShutdownAll = () => {
|
|
152
|
-
// Tearing down every session at once is hard to undo (each may be a
|
|
153
|
-
// running agent), so confirm before calling the manager's bulk
|
|
154
|
-
// shutdown. `runningChanged` then clears the list and the panel
|
|
155
|
-
// re-renders empty.
|
|
156
95
|
void showDialog({
|
|
157
96
|
title: trans.__('Shut Down All Terminals?'),
|
|
158
97
|
body: trans.__('Are you sure you want to permanently shut down all running terminals?'),
|
|
@@ -170,19 +109,8 @@ const plugin = {
|
|
|
170
109
|
console.error(`xtralab: failed to shut down all terminals: ${reason}`);
|
|
171
110
|
});
|
|
172
111
|
};
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
// `xtralab:start-agent:<id>` commands, so the icons and labels match the
|
|
176
|
-
// launcher exactly) followed by a separator and a plain terminal. Built
|
|
177
|
-
// lazily and repopulated on each open so it always reflects the current,
|
|
178
|
-
// settings-driven agent list. Without the registry (launcher disabled)
|
|
179
|
-
// or with no agents installed, there is nothing to choose between, so
|
|
180
|
-
// the button opens a plain terminal directly in one click.
|
|
181
|
-
//
|
|
182
|
-
// `MenuSvg` (not the bare Lumino `Menu`) is what every JupyterLab menu
|
|
183
|
-
// uses: its renderer applies the `menuItem` LabIcon stylesheet, which
|
|
184
|
-
// sizes the agent icons to 16px and centers them vertically — the bare
|
|
185
|
-
// `Menu` renders the raw, oversized, misaligned SVGs.
|
|
112
|
+
// `MenuSvg`, not the bare Lumino `Menu`: its renderer applies the `menuItem`
|
|
113
|
+
// LabIcon stylesheet — the bare menu renders raw, oversized SVGs.
|
|
186
114
|
let newMenu = null;
|
|
187
115
|
const onCreate = (anchor) => {
|
|
188
116
|
var _a;
|
|
@@ -211,22 +139,20 @@ const plugin = {
|
|
|
211
139
|
onShutdownAll,
|
|
212
140
|
onCreate
|
|
213
141
|
});
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
// this Terminals panel ranks first, so running agents sit one click
|
|
217
|
-
// away at the top. That setting assigns rank only and pins no area, so
|
|
218
|
-
// the "Move Widget" context menu can relocate the panel and the layout
|
|
219
|
-
// restorer remembers where the user puts it. The `rank` here is the
|
|
220
|
-
// in-code fallback used when the setting is absent. Sessions restored
|
|
221
|
-
// from a previous lab run land via `runningChanged` once the terminal
|
|
222
|
-
// manager is ready, which the panel's `UseSignal` picks up.
|
|
142
|
+
// The shipped labconfig `layout` setting ranks this panel first (rank only,
|
|
143
|
+
// no area pin, so "Move Widget" works); this rank is the fallback.
|
|
223
144
|
app.shell.add(panel, 'left', { rank: 1 });
|
|
224
145
|
if (restorer) {
|
|
225
146
|
restorer.add(panel, panel.id);
|
|
226
147
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
148
|
+
void app.restored.then(() => {
|
|
149
|
+
panel.expandOwnSection();
|
|
150
|
+
});
|
|
151
|
+
if (movableSections) {
|
|
152
|
+
movableSections.registerSource(PLUGIN_ID, trans.__('Terminals'), panel);
|
|
153
|
+
movableSections.registerTarget(PLUGIN_ID, trans.__('Terminals'), panel);
|
|
154
|
+
panel.announceSections();
|
|
155
|
+
}
|
|
230
156
|
if (settingRegistry) {
|
|
231
157
|
settingRegistry
|
|
232
158
|
.load(PLUGIN_ID)
|
|
@@ -235,9 +161,8 @@ const plugin = {
|
|
|
235
161
|
registry.setActivityEnabled(boolOption(settings.composite.showAgentActivity, true));
|
|
236
162
|
};
|
|
237
163
|
applyActivitySetting();
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
// then never reached on a later settings change.
|
|
164
|
+
// Bound to the panel so disposing it drops the connection — the
|
|
165
|
+
// disposed registry is never reached on a later settings change.
|
|
241
166
|
settings.changed.connect(applyActivitySetting, panel);
|
|
242
167
|
})
|
|
243
168
|
.catch(reason => {
|
|
@@ -254,9 +179,6 @@ const plugin = {
|
|
|
254
179
|
});
|
|
255
180
|
}
|
|
256
181
|
};
|
|
257
|
-
/**
|
|
258
|
-
* Read a boolean setting value, falling back when it is missing or invalid.
|
|
259
|
-
*/
|
|
260
182
|
function boolOption(value, fallback) {
|
|
261
183
|
return typeof value === 'boolean' ? value : fallback;
|
|
262
184
|
}
|