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
|
@@ -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[]
|
package/src/terminals/index.ts
CHANGED
|
@@ -3,7 +3,11 @@ import {
|
|
|
3
3
|
JupyterFrontEnd,
|
|
4
4
|
JupyterFrontEndPlugin
|
|
5
5
|
} from '@jupyterlab/application';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
Dialog,
|
|
8
|
+
IMovableSectionRegistry,
|
|
9
|
+
showDialog
|
|
10
|
+
} from '@jupyterlab/apputils';
|
|
7
11
|
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
8
12
|
import { ITerminalTracker } from '@jupyterlab/terminal';
|
|
9
13
|
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
@@ -19,39 +23,6 @@ import { RunningTerminals } from './widget';
|
|
|
19
23
|
|
|
20
24
|
const PLUGIN_ID = 'xtralab:terminals';
|
|
21
25
|
|
|
22
|
-
/**
|
|
23
|
-
* Adds a left-sidebar panel that lists every running terminal session.
|
|
24
|
-
*
|
|
25
|
-
* Each row is labelled with the *real* title the running program
|
|
26
|
-
* published — the launcher's agent name, or whatever title an xterm
|
|
27
|
-
* escape sequence set — which the registry caches so it survives the
|
|
28
|
-
* user closing the tab while the session keeps running on the server.
|
|
29
|
-
* JupyterLab's built-in "Running Terminals and Kernels" panel lists the
|
|
30
|
-
* same sessions but only by their `terminals/<n>` name, so this panel
|
|
31
|
-
* resolves and caches the published title to show something meaningful.
|
|
32
|
-
* Rows running a coding agent carry a smaller second line below the
|
|
33
|
-
* title with the agent's latest line of output, so the panel shows what
|
|
34
|
-
* each agent is doing at a glance — on by default, and switchable off
|
|
35
|
-
* through the `showAgentActivity` setting.
|
|
36
|
-
*
|
|
37
|
-
* Clicking a row activates the existing tab if one is open, or reopens
|
|
38
|
-
* the session in a fresh terminal widget (`terminal:open`). The inline
|
|
39
|
-
* `×` button shuts the session down on the server. The header carries a
|
|
40
|
-
* `+` button and a stop button (the latter shuts every session down at
|
|
41
|
-
* once, after a confirmation).
|
|
42
|
-
*
|
|
43
|
-
* The `+` button drops down a menu — built from the launcher's shared
|
|
44
|
-
* `IAgentRegistry` — listing each available agent (Claude, Codex, …)
|
|
45
|
-
* followed by a plain terminal, so starting an agent session is as
|
|
46
|
-
* consistent here as in the launcher and reuses the very same registered
|
|
47
|
-
* commands and icons. When the launcher is disabled or no agents are
|
|
48
|
-
* installed, the button falls back to opening a plain terminal directly.
|
|
49
|
-
*
|
|
50
|
-
* The plugin also provides {@link IAgentTerminals} — the sessions with a
|
|
51
|
-
* detection-confirmed running agent, plus a "paste a prompt into one"
|
|
52
|
-
* action — which the ask-agent popup uses to offer running agents as
|
|
53
|
-
* targets next to "new terminal".
|
|
54
|
-
*/
|
|
55
26
|
const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
|
|
56
27
|
id: PLUGIN_ID,
|
|
57
28
|
description:
|
|
@@ -65,7 +36,8 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
|
|
|
65
36
|
IAgentRegistry,
|
|
66
37
|
IEditorRegistry,
|
|
67
38
|
IAgentSessions,
|
|
68
|
-
ISettingRegistry
|
|
39
|
+
ISettingRegistry,
|
|
40
|
+
IMovableSectionRegistry
|
|
69
41
|
],
|
|
70
42
|
activate: (
|
|
71
43
|
app: JupyterFrontEnd,
|
|
@@ -75,17 +47,13 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
|
|
|
75
47
|
agentRegistry: IAgentRegistry | null,
|
|
76
48
|
editorRegistry: IEditorRegistry | null,
|
|
77
49
|
agentSessions: IAgentSessions | null,
|
|
78
|
-
settingRegistry: ISettingRegistry | null
|
|
50
|
+
settingRegistry: ISettingRegistry | null,
|
|
51
|
+
movableSections: IMovableSectionRegistry | null
|
|
79
52
|
): IAgentTerminals => {
|
|
80
53
|
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
81
54
|
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
// terminal icon when nothing recognised is running. Detection reports
|
|
85
|
-
// either the configured `command` or the canonical `id` (see
|
|
86
|
-
// `detectCommands`), so a row whose command is an alias — e.g. `ccm` for
|
|
87
|
-
// `claude` — still resolves to the right logo via its id. Reads the
|
|
88
|
-
// registries live so it tracks settings changes. Used to badge each row.
|
|
55
|
+
// Detection reports either the configured `command` or the canonical `id`,
|
|
56
|
+
// so an aliased command (e.g. `ccm` for `claude`) still maps to its logo.
|
|
89
57
|
const iconForCommand = (command: string | null): LabIcon => {
|
|
90
58
|
if (!command) {
|
|
91
59
|
return terminalIcon;
|
|
@@ -96,21 +64,12 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
|
|
|
96
64
|
if (agent) {
|
|
97
65
|
return agent.icon;
|
|
98
66
|
}
|
|
99
|
-
// A terminal running an editor (Neovim/Vim, or a user-configured one) is
|
|
100
|
-
// badged with its logo, the same as an agent.
|
|
101
67
|
const editor = editorRegistry?.editors.find(
|
|
102
68
|
e => e.command === command || e.id === command
|
|
103
69
|
);
|
|
104
70
|
return editor?.icon ?? terminalIcon;
|
|
105
71
|
};
|
|
106
72
|
|
|
107
|
-
// The names the server should look for in each terminal's process tree:
|
|
108
|
-
// for every agent and editor, its configured `command` plus its canonical
|
|
109
|
-
// `id`. The id is the real CLI name (e.g. `claude`, `nvim`), so an agent
|
|
110
|
-
// whose command points at an alias — e.g. `ccm` running `claude` — is
|
|
111
|
-
// still recognised by the process it actually spawns. Read live so the
|
|
112
|
-
// list tracks settings changes; deduplicated since command and id usually
|
|
113
|
-
// coincide.
|
|
114
73
|
const detectCommands = (): string[] => {
|
|
115
74
|
const names = new Set<string>();
|
|
116
75
|
for (const agent of agentRegistry?.agents ?? []) {
|
|
@@ -124,10 +83,6 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
|
|
|
124
83
|
return Array.from(names);
|
|
125
84
|
};
|
|
126
85
|
|
|
127
|
-
// Whether a detected command/id belongs to a coding agent rather than an
|
|
128
|
-
// editor. Only agent sessions get a latest-activity line (editor
|
|
129
|
-
// terminals stay badge-only), and only they are offered as prompt
|
|
130
|
-
// targets through `IAgentTerminals`.
|
|
131
86
|
const isAgentCommand = (command: string): boolean =>
|
|
132
87
|
agentRegistry?.agents.some(
|
|
133
88
|
a => a.command === command || a.id === command
|
|
@@ -136,19 +91,14 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
|
|
|
136
91
|
const registry = new SessionRegistry({
|
|
137
92
|
serviceManager: app.serviceManager,
|
|
138
93
|
tracker,
|
|
139
|
-
// The shell tells the registry which widget is current, so the panel can
|
|
140
|
-
// highlight the terminal that is the current main-area widget (and
|
|
141
|
-
// nothing while a notebook or other widget is current instead).
|
|
142
94
|
shell: app.shell,
|
|
143
95
|
agentSessions,
|
|
144
96
|
detectCommands,
|
|
145
97
|
isAgentCommand
|
|
146
98
|
});
|
|
147
99
|
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// `stateChanged` is safe: `Title.icon` ignores an unchanged assignment, so
|
|
151
|
-
// the writes neither loop nor churn.
|
|
100
|
+
// `Title.icon` ignores an unchanged assignment, so re-running per
|
|
101
|
+
// stateChanged is cheap.
|
|
152
102
|
const syncTabIcons = (): void => {
|
|
153
103
|
tracker.forEach(widget => {
|
|
154
104
|
const session = widget.content.session;
|
|
@@ -164,10 +114,8 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
|
|
|
164
114
|
syncTabIcons();
|
|
165
115
|
|
|
166
116
|
const onActivate = (name: string): void => {
|
|
167
|
-
// `terminal:open`
|
|
168
|
-
//
|
|
169
|
-
// session it activates that tab, otherwise it spins up a fresh
|
|
170
|
-
// widget connected to the live session.
|
|
117
|
+
// `terminal:open` activates an attached tab or spins up a fresh widget
|
|
118
|
+
// connected to the live session.
|
|
171
119
|
void app.commands.execute('terminal:open', { name });
|
|
172
120
|
};
|
|
173
121
|
|
|
@@ -180,10 +128,6 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
|
|
|
180
128
|
};
|
|
181
129
|
|
|
182
130
|
const onShutdownAll = (): void => {
|
|
183
|
-
// Tearing down every session at once is hard to undo (each may be a
|
|
184
|
-
// running agent), so confirm before calling the manager's bulk
|
|
185
|
-
// shutdown. `runningChanged` then clears the list and the panel
|
|
186
|
-
// re-renders empty.
|
|
187
131
|
void showDialog({
|
|
188
132
|
title: trans.__('Shut Down All Terminals?'),
|
|
189
133
|
body: trans.__(
|
|
@@ -206,19 +150,8 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
|
|
|
206
150
|
});
|
|
207
151
|
};
|
|
208
152
|
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
// `xtralab:start-agent:<id>` commands, so the icons and labels match the
|
|
212
|
-
// launcher exactly) followed by a separator and a plain terminal. Built
|
|
213
|
-
// lazily and repopulated on each open so it always reflects the current,
|
|
214
|
-
// settings-driven agent list. Without the registry (launcher disabled)
|
|
215
|
-
// or with no agents installed, there is nothing to choose between, so
|
|
216
|
-
// the button opens a plain terminal directly in one click.
|
|
217
|
-
//
|
|
218
|
-
// `MenuSvg` (not the bare Lumino `Menu`) is what every JupyterLab menu
|
|
219
|
-
// uses: its renderer applies the `menuItem` LabIcon stylesheet, which
|
|
220
|
-
// sizes the agent icons to 16px and centers them vertically — the bare
|
|
221
|
-
// `Menu` renders the raw, oversized, misaligned SVGs.
|
|
153
|
+
// `MenuSvg`, not the bare Lumino `Menu`: its renderer applies the `menuItem`
|
|
154
|
+
// LabIcon stylesheet — the bare menu renders raw, oversized SVGs.
|
|
222
155
|
let newMenu: MenuSvg | null = null;
|
|
223
156
|
const onCreate = (anchor: { x: number; y: number }): void => {
|
|
224
157
|
const agents = agentRegistry?.agents ?? [];
|
|
@@ -248,23 +181,23 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
|
|
|
248
181
|
onCreate
|
|
249
182
|
});
|
|
250
183
|
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
// this Terminals panel ranks first, so running agents sit one click
|
|
254
|
-
// away at the top. That setting assigns rank only and pins no area, so
|
|
255
|
-
// the "Move Widget" context menu can relocate the panel and the layout
|
|
256
|
-
// restorer remembers where the user puts it. The `rank` here is the
|
|
257
|
-
// in-code fallback used when the setting is absent. Sessions restored
|
|
258
|
-
// from a previous lab run land via `runningChanged` once the terminal
|
|
259
|
-
// manager is ready, which the panel's `UseSignal` picks up.
|
|
184
|
+
// The shipped labconfig `layout` setting ranks this panel first (rank only,
|
|
185
|
+
// no area pin, so "Move Widget" works); this rank is the fallback.
|
|
260
186
|
app.shell.add(panel, 'left', { rank: 1 });
|
|
261
187
|
if (restorer) {
|
|
262
188
|
restorer.add(panel, panel.id);
|
|
263
189
|
}
|
|
264
190
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
191
|
+
void app.restored.then(() => {
|
|
192
|
+
panel.expandOwnSection();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (movableSections) {
|
|
196
|
+
movableSections.registerSource(PLUGIN_ID, trans.__('Terminals'), panel);
|
|
197
|
+
movableSections.registerTarget(PLUGIN_ID, trans.__('Terminals'), panel);
|
|
198
|
+
panel.announceSections();
|
|
199
|
+
}
|
|
200
|
+
|
|
268
201
|
if (settingRegistry) {
|
|
269
202
|
settingRegistry
|
|
270
203
|
.load(PLUGIN_ID)
|
|
@@ -275,9 +208,8 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
|
|
|
275
208
|
);
|
|
276
209
|
};
|
|
277
210
|
applyActivitySetting();
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
// then never reached on a later settings change.
|
|
211
|
+
// Bound to the panel so disposing it drops the connection — the
|
|
212
|
+
// disposed registry is never reached on a later settings change.
|
|
281
213
|
settings.changed.connect(applyActivitySetting, panel);
|
|
282
214
|
})
|
|
283
215
|
.catch(reason => {
|
|
@@ -299,9 +231,6 @@ const plugin: JupyterFrontEndPlugin<IAgentTerminals> = {
|
|
|
299
231
|
}
|
|
300
232
|
};
|
|
301
233
|
|
|
302
|
-
/**
|
|
303
|
-
* Read a boolean setting value, falling back when it is missing or invalid.
|
|
304
|
-
*/
|
|
305
234
|
function boolOption(value: unknown, fallback: boolean): boolean {
|
|
306
235
|
return typeof value === 'boolean' ? value : fallback;
|
|
307
236
|
}
|