xtralab 0.15.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -30
- package/lib/about/icons.d.ts +0 -5
- package/lib/about/icons.js +0 -5
- package/lib/about/index.d.ts +4 -8
- package/lib/about/index.js +6 -17
- package/lib/agentSessions.d.ts +9 -29
- package/lib/agentSessions.js +2 -7
- package/lib/askAgent/editorSelection.d.ts +20 -26
- package/lib/askAgent/editorSelection.js +11 -26
- package/lib/askAgent/icons.d.ts +2 -3
- package/lib/askAgent/icons.js +2 -3
- package/lib/askAgent/index.d.ts +4 -26
- package/lib/askAgent/index.js +54 -158
- package/lib/askAgent/popup.d.ts +12 -16
- package/lib/askAgent/popup.js +18 -37
- package/lib/askAgent/prompt.d.ts +3 -5
- package/lib/askAgent/prompt.js +6 -19
- package/lib/askAgent/queue.d.ts +11 -21
- package/lib/askAgent/queue.js +9 -19
- package/lib/askAgent/queuePanel.d.ts +13 -18
- package/lib/askAgent/queuePanel.js +15 -37
- package/lib/askAgent/targetPicker.d.ts +2 -9
- package/lib/askAgent/targetPicker.js +6 -11
- package/lib/askAgent/tokens.d.ts +32 -46
- package/lib/askAgent/tokens.js +4 -6
- package/lib/commandBar/index.d.ts +4 -13
- package/lib/commandBar/index.js +21 -39
- package/lib/customPanel/index.js +15 -0
- package/lib/editorBreadcrumbs/index.d.ts +3 -6
- package/lib/editorBreadcrumbs/index.js +10 -6
- package/lib/editorBreadcrumbs/widget.d.ts +17 -4
- package/lib/editorBreadcrumbs/widget.js +11 -17
- package/lib/editorIndent/index.d.ts +4 -12
- package/lib/editorIndent/index.js +8 -23
- package/lib/fileBrowser/commands.d.ts +29 -19
- package/lib/fileBrowser/commands.js +83 -97
- package/lib/fileBrowser/contents.d.ts +3 -0
- package/lib/fileBrowser/dragAndDrop.d.ts +17 -4
- package/lib/fileBrowser/dragAndDrop.js +2 -7
- package/lib/fileBrowser/fileBrowser.d.ts +19 -9
- package/lib/fileBrowser/fileBrowser.js +70 -222
- package/lib/fileBrowser/gitStatus.d.ts +16 -10
- package/lib/fileBrowser/gitStatus.js +31 -30
- package/lib/fileBrowser/gitignore.d.ts +4 -17
- package/lib/fileBrowser/gitignore.js +4 -25
- package/lib/fileBrowser/icons.d.ts +7 -17
- package/lib/fileBrowser/icons.js +25 -80
- package/lib/fileBrowser/index.js +2 -4
- package/lib/fileBrowser/widget.d.ts +141 -48
- package/lib/fileBrowser/widget.js +102 -4
- package/lib/fileTypeIcons/index.js +0 -10
- package/lib/git/api.d.ts +5 -13
- package/lib/git/api.js +10 -36
- package/lib/git/askRequest.d.ts +4 -8
- package/lib/git/askRequest.js +6 -20
- package/lib/git/commands.d.ts +45 -5
- package/lib/git/commands.js +20 -13
- package/lib/git/diffModel.js +4 -0
- package/lib/git/diffPreferences.d.ts +90 -0
- package/lib/git/diffPreferences.js +107 -0
- package/lib/git/diffProvider.js +10 -2
- package/lib/git/diffSurface.d.ts +64 -63
- package/lib/git/diffSurface.js +72 -213
- package/lib/git/diffTheme.d.ts +7 -0
- package/lib/git/diffTheme.js +23 -0
- package/lib/git/diffWidget.d.ts +70 -14
- package/lib/git/diffWidget.js +100 -94
- package/lib/git/imageDiff.d.ts +14 -6
- package/lib/git/imageDiff.js +19 -75
- package/lib/git/index.d.ts +0 -3
- package/lib/git/index.js +44 -10
- package/lib/git/notebookDiff.d.ts +110 -33
- package/lib/git/notebookDiff.js +55 -197
- package/lib/git/tokens.d.ts +73 -31
- package/lib/git/tokens.js +2 -3
- package/lib/highlight/index.d.ts +4 -10
- package/lib/highlight/index.js +10 -41
- package/lib/index.d.ts +0 -9
- package/lib/index.js +0 -9
- package/lib/launcher/agents.d.ts +55 -51
- package/lib/launcher/agents.js +15 -57
- package/lib/launcher/availability.d.ts +3 -9
- package/lib/launcher/availability.js +3 -9
- package/lib/launcher/commands.d.ts +7 -32
- package/lib/launcher/commands.js +14 -45
- package/lib/launcher/dashboard.d.ts +19 -24
- package/lib/launcher/dashboard.js +21 -67
- package/lib/launcher/editorRegistry.d.ts +10 -22
- package/lib/launcher/editorRegistry.js +14 -18
- package/lib/launcher/editors.d.ts +39 -45
- package/lib/launcher/editors.js +8 -34
- package/lib/launcher/icons.d.ts +0 -12
- package/lib/launcher/icons.js +5 -39
- package/lib/launcher/index.js +12 -55
- package/lib/launcher/invocation.d.ts +4 -10
- package/lib/launcher/invocation.js +7 -22
- package/lib/launcher/registry.d.ts +9 -7
- package/lib/launcher/registry.js +9 -7
- package/lib/launcher/tokens.d.ts +10 -22
- package/lib/launcher/tokens.js +5 -8
- package/lib/menuBar/index.d.ts +3 -10
- package/lib/menuBar/index.js +11 -28
- package/lib/menus/index.d.ts +3 -9
- package/lib/menus/index.js +14 -30
- package/lib/omnibox/files.d.ts +2 -4
- package/lib/omnibox/files.js +5 -11
- package/lib/omnibox/index.d.ts +4 -14
- package/lib/omnibox/index.js +9 -27
- package/lib/omnibox/model.d.ts +38 -10
- package/lib/omnibox/model.js +15 -33
- package/lib/omnibox/recents.d.ts +15 -17
- package/lib/omnibox/recents.js +12 -17
- package/lib/omnibox/tokens.d.ts +5 -10
- package/lib/omnibox/tokens.js +2 -6
- package/lib/omnibox/widget.d.ts +11 -7
- package/lib/omnibox/widget.js +8 -16
- package/lib/searchReplace/index.d.ts +4 -20
- package/lib/searchReplace/index.js +7 -27
- package/lib/showOutput/index.d.ts +3 -12
- package/lib/showOutput/index.js +6 -22
- package/lib/sidebar/index.d.ts +4 -7
- package/lib/sidebar/index.js +5 -12
- package/lib/terminalNotifications/index.d.ts +4 -10
- package/lib/terminalNotifications/index.js +6 -20
- package/lib/terminals/agentTerminals.d.ts +12 -5
- package/lib/terminals/agentTerminals.js +25 -44
- package/lib/terminals/detection.d.ts +4 -8
- package/lib/terminals/detection.js +4 -8
- package/lib/terminals/index.d.ts +0 -35
- package/lib/terminals/index.js +12 -101
- package/lib/terminals/model.d.ts +64 -139
- package/lib/terminals/model.js +80 -220
- package/lib/terminals/tokens.d.ts +11 -22
- package/lib/terminals/tokens.js +2 -2
- package/lib/terminals/widget.d.ts +43 -13
- package/lib/terminals/widget.js +33 -18
- package/lib/topBar/icons.d.ts +3 -8
- package/lib/topBar/icons.js +3 -8
- package/lib/topBar/index.d.ts +4 -11
- package/lib/topBar/index.js +10 -38
- package/lib/walkthrough/index.d.ts +4 -9
- package/lib/walkthrough/index.js +6 -20
- package/lib/walkthrough/panel.d.ts +40 -9
- package/lib/walkthrough/panel.js +4 -10
- package/package.json +5 -3
- package/schema/git-diff-preferences.json +42 -0
- package/src/about/icons.ts +0 -5
- package/src/about/index.tsx +6 -17
- package/src/agentSessions.ts +9 -29
- package/src/askAgent/editorSelection.ts +22 -29
- package/src/askAgent/icons.ts +2 -3
- package/src/askAgent/index.ts +54 -158
- package/src/askAgent/popup.tsx +23 -45
- package/src/askAgent/prompt.ts +6 -19
- package/src/askAgent/queue.ts +13 -24
- package/src/askAgent/queuePanel.tsx +21 -46
- package/src/askAgent/targetPicker.tsx +5 -15
- package/src/askAgent/tokens.ts +32 -46
- package/src/commandBar/index.ts +24 -39
- package/src/customPanel/index.ts +15 -0
- package/src/editorBreadcrumbs/index.ts +10 -6
- package/src/editorBreadcrumbs/widget.ts +23 -17
- package/src/editorIndent/index.ts +17 -23
- package/src/fileBrowser/commands.ts +119 -96
- package/src/fileBrowser/contents.ts +3 -0
- package/src/fileBrowser/dragAndDrop.ts +17 -7
- package/src/fileBrowser/fileBrowser.tsx +86 -230
- package/src/fileBrowser/gitStatus.ts +36 -33
- package/src/fileBrowser/gitignore.ts +4 -25
- package/src/fileBrowser/icons.ts +25 -80
- package/src/fileBrowser/index.ts +2 -4
- package/src/fileBrowser/widget.tsx +171 -50
- package/src/fileTypeIcons/index.ts +0 -10
- package/src/git/api.ts +10 -36
- package/src/git/askRequest.ts +6 -20
- package/src/git/commands.ts +83 -11
- package/src/git/diffModel.ts +25 -0
- package/src/git/diffPreferences.ts +192 -0
- package/src/git/diffProvider.tsx +11 -2
- package/src/git/diffSurface.tsx +165 -286
- package/src/git/diffTheme.ts +24 -0
- package/src/git/diffWidget.tsx +194 -134
- package/src/git/imageDiff.tsx +56 -84
- package/src/git/index.ts +54 -10
- package/src/git/notebookDiff.tsx +214 -240
- package/src/git/tokens.ts +73 -31
- package/src/highlight/index.ts +10 -41
- package/src/index.ts +0 -9
- package/src/launcher/agents.ts +64 -95
- package/src/launcher/availability.ts +3 -9
- package/src/launcher/commands.ts +14 -51
- package/src/launcher/dashboard.tsx +49 -88
- package/src/launcher/editorRegistry.ts +21 -29
- package/src/launcher/editors.ts +39 -58
- package/src/launcher/icons.ts +5 -39
- package/src/launcher/index.ts +12 -55
- package/src/launcher/invocation.ts +7 -22
- package/src/launcher/registry.ts +9 -7
- package/src/launcher/tokens.ts +10 -22
- package/src/menuBar/index.ts +11 -28
- package/src/menus/index.ts +14 -30
- package/src/omnibox/files.ts +10 -14
- package/src/omnibox/index.ts +9 -27
- package/src/omnibox/model.ts +50 -38
- package/src/omnibox/recents.ts +17 -22
- package/src/omnibox/tokens.ts +5 -10
- package/src/omnibox/widget.tsx +14 -19
- package/src/searchReplace/index.ts +7 -27
- package/src/showOutput/index.ts +6 -22
- package/src/sidebar/index.ts +20 -12
- package/src/terminalNotifications/index.ts +42 -25
- package/src/terminals/agentTerminals.ts +25 -49
- package/src/terminals/detection.ts +4 -8
- package/src/terminals/index.ts +12 -101
- package/src/terminals/model.ts +130 -246
- package/src/terminals/tokens.ts +11 -22
- package/src/terminals/widget.tsx +48 -31
- package/src/topBar/icons.ts +3 -8
- package/src/topBar/index.ts +21 -44
- package/src/walkthrough/index.ts +6 -20
- package/src/walkthrough/panel.ts +40 -10
- package/style/about.css +2 -7
- package/style/askAgent.css +8 -40
- package/style/base.css +9 -45
- package/style/chrome.css +108 -308
- package/style/commandBar.css +4 -20
- package/style/customPanel.css +1 -4
- package/style/git.css +26 -186
- package/style/highlight.css +1 -3
- package/style/launcher.css +6 -70
- package/style/omnibox.css +2 -14
- package/style/showOutput.css +0 -6
- package/style/sidebar.css +29 -9
- package/style/tabs.css +5 -13
- package/style/terminals.css +6 -53
- package/style/topBar.css +2 -22
- package/style/walkthrough.css +0 -8
package/lib/terminals/model.js
CHANGED
|
@@ -1,66 +1,19 @@
|
|
|
1
1
|
import { Poll } from '@lumino/polling';
|
|
2
2
|
import { Signal } from '@lumino/signaling';
|
|
3
3
|
import { fetchRunningAgents } from './detection';
|
|
4
|
-
/**
|
|
5
|
-
* How often to ask the server which agent (if any) is running in each
|
|
6
|
-
* terminal. Snappy enough that a manually-started agent's logo shows up
|
|
7
|
-
* within a few seconds, light enough that walking the shells' process trees
|
|
8
|
-
* stays negligible.
|
|
9
|
-
*/
|
|
10
4
|
const DETECT_POLL_INTERVAL_MS = 3000;
|
|
11
|
-
/**
|
|
12
|
-
* Upper bound on the exponential backoff when detection fails repeatedly
|
|
13
|
-
* (e.g. the endpoint is missing on an older server). Matches the rest of the
|
|
14
|
-
* plugin's polls.
|
|
15
|
-
*/
|
|
16
5
|
const DETECT_POLL_MAX_MS = 300000;
|
|
17
6
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* issuing an agent's command and its process actually being spawnable, so the
|
|
21
|
-
* freshly-launched logo doesn't blink off if a detection poll lands in that
|
|
22
|
-
* sliver. Comfortably longer than a cold agent start; short enough that a tag
|
|
23
|
-
* for an agent that never really started clears quickly.
|
|
7
|
+
* Window during which a fresh launch tag outranks a `null` detection result:
|
|
8
|
+
* covers the gap between launching an agent and its process becoming visible.
|
|
24
9
|
*/
|
|
25
10
|
const LAUNCH_GRACE_MS = 4000;
|
|
26
|
-
/**
|
|
27
|
-
* How often to re-read each open coding-agent terminal's output buffer to
|
|
28
|
-
* refresh the "latest activity" line shown under its row. Brisk enough to feel
|
|
29
|
-
* live while an agent works, cheap because it only walks the last screenful of
|
|
30
|
-
* a handful of terminals and emits only when a line actually changes.
|
|
31
|
-
*/
|
|
32
11
|
const ACTIVITY_POLL_INTERVAL_MS = 1500;
|
|
33
12
|
/**
|
|
34
|
-
* Source-of-truth model for the running-terminals panel
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* session continues running on the backend.
|
|
39
|
-
*
|
|
40
|
-
* For sessions running a coding agent it also surfaces a *latest activity*
|
|
41
|
-
* line — the freshest meaningful line of the terminal's output, read from the
|
|
42
|
-
* open tab's xterm buffer on a poll (see {@link activityFor}) — so each row
|
|
43
|
-
* shows what its agent is doing, not just its name.
|
|
44
|
-
*
|
|
45
|
-
* It also resolves *which agent is running* in each session, so the panel can
|
|
46
|
-
* badge rows with the agent's logo. Two inputs feed that, reconciled by
|
|
47
|
-
* {@link agentCommandFor}:
|
|
48
|
-
* - an optimistic launch tag ({@link IAgentSessions}) written when we start
|
|
49
|
-
* an agent ourselves — instant, but blind to the agent later exiting; and
|
|
50
|
-
* - authoritative server-side process detection, polled here, which works
|
|
51
|
-
* for hand-started agents too and clears once an agent exits.
|
|
52
|
-
*
|
|
53
|
-
* Upstream session signals:
|
|
54
|
-
* - `serviceManager.terminals.runningChanged` is the authoritative
|
|
55
|
-
* list of live sessions; everything not in it has been shut down
|
|
56
|
-
* server-side and we must drop it.
|
|
57
|
-
* - `tracker.widgetAdded` plus the per-widget `title.changed` /
|
|
58
|
-
* `disposed` signals keep our label cache in sync with whatever
|
|
59
|
-
* xterm/the launcher has set on the open tabs.
|
|
60
|
-
*
|
|
61
|
-
* `stateChanged` is emitted for every kind of update; the panel hooks it
|
|
62
|
-
* through a `UseSignal` so a single subscription re-renders the list on
|
|
63
|
-
* any change.
|
|
13
|
+
* Source-of-truth model for the running-terminals panel: live sessions,
|
|
14
|
+
* labels cached across tab closes, agent badges (launch tags reconciled with
|
|
15
|
+
* polled detection, see {@link agentCommandFor}), and per-row latest-activity
|
|
16
|
+
* lines. `stateChanged` fires on every kind of update.
|
|
64
17
|
*/
|
|
65
18
|
export class SessionRegistry {
|
|
66
19
|
constructor(options) {
|
|
@@ -86,17 +39,8 @@ export class SessionRegistry {
|
|
|
86
39
|
this._tracker.widgetAdded.connect(this._onWidgetAdded, this);
|
|
87
40
|
this._tracker.forEach(widget => this._trackWidget(widget));
|
|
88
41
|
this._refreshLive();
|
|
89
|
-
// Track which terminal is the current widget in the main area so the panel
|
|
90
|
-
// can highlight its row — and highlight nothing while a notebook or any
|
|
91
|
-
// other non-terminal tab is current instead. `currentChanged` is optional
|
|
92
|
-
// on the shell interface (not every shell can switch focus), so guard it;
|
|
93
|
-
// when it is absent the highlight stays off.
|
|
94
42
|
(_f = (_e = this._shell) === null || _e === void 0 ? void 0 : _e.currentChanged) === null || _f === void 0 ? void 0 : _f.connect(this._onShellCurrentChanged, this);
|
|
95
43
|
this._updateCurrent();
|
|
96
|
-
// A freshly written launch tag should re-render the panel immediately
|
|
97
|
-
// (show the logo) and again once the grace window closes (so a tag for
|
|
98
|
-
// an agent that never started, or that detection later contradicts,
|
|
99
|
-
// doesn't linger).
|
|
100
44
|
(_g = this._agentSessions) === null || _g === void 0 ? void 0 : _g.changed.connect(this._onTagChanged, this);
|
|
101
45
|
this._poll = new Poll({
|
|
102
46
|
name: '@xtralab/terminals:runningAgents',
|
|
@@ -108,10 +52,8 @@ export class SessionRegistry {
|
|
|
108
52
|
},
|
|
109
53
|
standby: 'when-hidden'
|
|
110
54
|
});
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
// separate from the detection poll so reading xterm buffers neither delays
|
|
114
|
-
// nor is delayed by the server round-trip that drives the agent badges.
|
|
55
|
+
// Separate from the detection poll so reading xterm buffers neither
|
|
56
|
+
// delays nor is delayed by the server round-trip.
|
|
115
57
|
this._activityPoll = new Poll({
|
|
116
58
|
name: '@xtralab/terminals:activity',
|
|
117
59
|
factory: () => this._refreshActivity(),
|
|
@@ -120,19 +62,19 @@ export class SessionRegistry {
|
|
|
120
62
|
});
|
|
121
63
|
}
|
|
122
64
|
/**
|
|
123
|
-
* Emitted
|
|
124
|
-
* running agent changes.
|
|
65
|
+
* Emitted on any change to the session list, labels, badges, or activity.
|
|
125
66
|
*/
|
|
126
67
|
get stateChanged() {
|
|
127
68
|
return this._stateChanged;
|
|
128
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Whether the registry has been disposed.
|
|
72
|
+
*/
|
|
129
73
|
get isDisposed() {
|
|
130
74
|
return this._isDisposed;
|
|
131
75
|
}
|
|
132
76
|
/**
|
|
133
|
-
* Names of all live sessions
|
|
134
|
-
* rendered list keeps a consistent order (roughly creation order) as
|
|
135
|
-
* new sessions are added and old ones shut down.
|
|
77
|
+
* Names of all live sessions in stable (roughly creation) order.
|
|
136
78
|
*/
|
|
137
79
|
sessionNames() {
|
|
138
80
|
const names = Array.from(this._live);
|
|
@@ -140,22 +82,16 @@ export class SessionRegistry {
|
|
|
140
82
|
return names;
|
|
141
83
|
}
|
|
142
84
|
/**
|
|
143
|
-
* True iff the named session is still on the server.
|
|
144
|
-
* to skip a row during the brief window between the session's shutdown
|
|
145
|
-
* and the next `runningChanged` arriving.
|
|
85
|
+
* True iff the named session is still on the server.
|
|
146
86
|
*/
|
|
147
87
|
has(name) {
|
|
148
88
|
return this._live.has(name);
|
|
149
89
|
}
|
|
150
90
|
/**
|
|
151
|
-
* Display name for the session. The cache wins
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* during reconnect from flickering into the panel. The live widget
|
|
156
|
-
* title is used when no cache exists, and the `Terminal {name}`
|
|
157
|
-
* fallback covers sessions we have never seen a widget for (e.g.
|
|
158
|
-
* surviving a lab reload).
|
|
91
|
+
* Display name for the session. The cache wins — it holds only "real"
|
|
92
|
+
* labels (see `_cacheLabel`), so the transient `Terminal {name}` an xterm
|
|
93
|
+
* widget shows during reconnect never flickers into the panel; the final
|
|
94
|
+
* fallback covers sessions never seen with a widget (e.g. a lab reload).
|
|
159
95
|
*/
|
|
160
96
|
labelFor(name) {
|
|
161
97
|
const cached = this._labels.get(name);
|
|
@@ -169,18 +105,10 @@ export class SessionRegistry {
|
|
|
169
105
|
return `Terminal ${name}`;
|
|
170
106
|
}
|
|
171
107
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* Server-side detection is authoritative whenever it reports a running
|
|
178
|
-
* agent. A launch tag fills two gaps: the startup grace window right after
|
|
179
|
-
* we launch an agent (so its logo shows before its process is detectable),
|
|
180
|
-
* and any time detection is unavailable (older server, transient error).
|
|
181
|
-
* Once the grace window has passed and detection has reported the session
|
|
182
|
-
* as idle, the tag is ignored — that is how the badge clears when an agent
|
|
183
|
-
* exits.
|
|
108
|
+
* Agent identifier for the session (configured command or canonical id), or
|
|
109
|
+
* `null`. Detection wins when it reports an agent; a launch tag fills the
|
|
110
|
+
* grace window and detection outages, and is ignored once detection says
|
|
111
|
+
* idle — that is how the badge clears when an agent exits.
|
|
184
112
|
*/
|
|
185
113
|
agentCommandFor(name) {
|
|
186
114
|
var _a, _b, _c;
|
|
@@ -203,26 +131,18 @@ export class SessionRegistry {
|
|
|
203
131
|
return null;
|
|
204
132
|
}
|
|
205
133
|
/**
|
|
206
|
-
* The agent command
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* {@link agentCommandFor} this never falls back to the optimistic launch
|
|
210
|
-
* tags: callers about to *write into* a session use it, and text must only
|
|
211
|
-
* ever be sent to a confirmed running agent — pasted into a shell prompt it
|
|
212
|
-
* would be executed.
|
|
134
|
+
* The agent command detection currently confirms for the session, or
|
|
135
|
+
* `null`. Never falls back to launch tags: callers about to *write into* a
|
|
136
|
+
* session use it, and text pasted into a shell prompt would be executed.
|
|
213
137
|
*/
|
|
214
138
|
detectedCommandFor(name) {
|
|
215
139
|
const detected = this._detected.get(name);
|
|
216
140
|
return typeof detected === 'string' ? detected : null;
|
|
217
141
|
}
|
|
218
142
|
/**
|
|
219
|
-
* The most recent meaningful line of
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* buffer holds only the agent's input box and chrome. Refreshed on a poll by
|
|
223
|
-
* {@link _refreshActivity}; shown as a smaller line under the row's title.
|
|
224
|
-
* Always `null` while the activity line is disabled (see
|
|
225
|
-
* {@link setActivityEnabled}).
|
|
143
|
+
* The most recent meaningful line of the session's output, or `null` when
|
|
144
|
+
* nothing qualifies (no running agent, closed tab, chrome-only buffer, or
|
|
145
|
+
* the activity line is disabled).
|
|
226
146
|
*/
|
|
227
147
|
activityFor(name) {
|
|
228
148
|
var _a;
|
|
@@ -232,11 +152,8 @@ export class SessionRegistry {
|
|
|
232
152
|
return (_a = this._activity.get(name)) !== null && _a !== void 0 ? _a : null;
|
|
233
153
|
}
|
|
234
154
|
/**
|
|
235
|
-
* Turn the per-row
|
|
236
|
-
*
|
|
237
|
-
* activity poll stops reading terminal buffers and any cached lines are
|
|
238
|
-
* dropped, so rows fall back to their title alone; when turned back on the
|
|
239
|
-
* lines are repopulated on the spot. A no-op when the value is unchanged.
|
|
155
|
+
* Turn the per-row activity line on or off (the `showAgentActivity`
|
|
156
|
+
* setting). Off drops cached lines; on repopulates them on the spot.
|
|
240
157
|
*/
|
|
241
158
|
setActivityEnabled(enabled) {
|
|
242
159
|
if (enabled === this._activityEnabled) {
|
|
@@ -244,8 +161,6 @@ export class SessionRegistry {
|
|
|
244
161
|
}
|
|
245
162
|
this._activityEnabled = enabled;
|
|
246
163
|
if (enabled) {
|
|
247
|
-
// Repopulate immediately rather than waiting for the next poll tick;
|
|
248
|
-
// `_refreshActivity` emits `stateChanged` if it finds anything to show.
|
|
249
164
|
void this._refreshActivity();
|
|
250
165
|
}
|
|
251
166
|
else {
|
|
@@ -254,30 +169,22 @@ export class SessionRegistry {
|
|
|
254
169
|
}
|
|
255
170
|
}
|
|
256
171
|
/**
|
|
257
|
-
*
|
|
258
|
-
* switch behavior between "activate existing tab" and "open a new tab
|
|
259
|
-
* connected to the live session".
|
|
172
|
+
* The open widget for a session, if any.
|
|
260
173
|
*/
|
|
261
174
|
widgetFor(name) {
|
|
262
175
|
var _a;
|
|
263
176
|
return ((_a = this._tracker.find(widget => widget.content.session.name === name)) !== null && _a !== void 0 ? _a : null);
|
|
264
177
|
}
|
|
265
178
|
/**
|
|
266
|
-
* Session name of the terminal that is
|
|
267
|
-
*
|
|
268
|
-
* example a notebook or text editor is current). The panel uses this to
|
|
269
|
-
* highlight the current terminal's row — mirroring how the file browser
|
|
270
|
-
* surfaces the open document — and to leave every row unhighlighted while a
|
|
271
|
-
* non-terminal tab is current.
|
|
179
|
+
* Session name of the terminal that is the shell's current main-area
|
|
180
|
+
* widget, or `null` when the current widget is not a terminal.
|
|
272
181
|
*/
|
|
273
182
|
currentSessionName() {
|
|
274
183
|
return this._currentName;
|
|
275
184
|
}
|
|
276
185
|
/**
|
|
277
|
-
* Stable per-session rank
|
|
278
|
-
*
|
|
279
|
-
* sessions come and go. Cleaned up when the session is shut down so the
|
|
280
|
-
* counter does not grow without bound across long sessions.
|
|
186
|
+
* Stable per-session rank in observation order, so rows don't reshuffle as
|
|
187
|
+
* sessions come and go.
|
|
281
188
|
*/
|
|
282
189
|
rankFor(name) {
|
|
283
190
|
let rank = this._ranks.get(name);
|
|
@@ -287,6 +194,9 @@ export class SessionRegistry {
|
|
|
287
194
|
}
|
|
288
195
|
return rank;
|
|
289
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Dispose of the registry's polls and signal connections.
|
|
199
|
+
*/
|
|
290
200
|
dispose() {
|
|
291
201
|
var _a, _b, _c;
|
|
292
202
|
if (this._isDisposed) {
|
|
@@ -314,17 +224,13 @@ export class SessionRegistry {
|
|
|
314
224
|
this._stateChanged.emit();
|
|
315
225
|
}
|
|
316
226
|
/**
|
|
317
|
-
* Adopt `next` as the live set and prune every per-session map
|
|
318
|
-
*
|
|
319
|
-
* detection results, and the shared launch tag. Without this the maps (and
|
|
320
|
-
* the rank counter) would grow without bound as terminals come and go.
|
|
227
|
+
* Adopt `next` as the live set and prune every per-session map, so the
|
|
228
|
+
* maps and rank counter don't grow without bound.
|
|
321
229
|
*/
|
|
322
230
|
_reconcileLive(next) {
|
|
323
231
|
var _a;
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
// not enumerable, so we drive the prune from the sessions we know about;
|
|
327
|
-
// `_firstSeen` has an entry for every session that has ever been live.)
|
|
232
|
+
// The tag map is not enumerable, so the tag prune below is driven from
|
|
233
|
+
// `_firstSeen`, which has an entry for every session ever live.
|
|
328
234
|
const known = Array.from(this._firstSeen.keys());
|
|
329
235
|
for (const name of next) {
|
|
330
236
|
if (!this._firstSeen.has(name)) {
|
|
@@ -357,10 +263,8 @@ export class SessionRegistry {
|
|
|
357
263
|
this._activity.delete(name);
|
|
358
264
|
}
|
|
359
265
|
}
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
// a stale tag could otherwise mislabel a brand-new terminal that happens
|
|
363
|
-
// to reuse a closed session's name.
|
|
266
|
+
// terminado reuses session names, so a stale tag could mislabel a
|
|
267
|
+
// brand-new terminal that reuses a closed session's name.
|
|
364
268
|
for (const name of known) {
|
|
365
269
|
if (!next.has(name)) {
|
|
366
270
|
(_a = this._agentSessions) === null || _a === void 0 ? void 0 : _a.delete(name);
|
|
@@ -368,9 +272,8 @@ export class SessionRegistry {
|
|
|
368
272
|
}
|
|
369
273
|
}
|
|
370
274
|
/**
|
|
371
|
-
* Poll body:
|
|
372
|
-
*
|
|
373
|
-
* clearing them, so a transient error doesn't strip every badge.
|
|
275
|
+
* Poll body: update the detection map from the server. On failure the
|
|
276
|
+
* previous results are kept, so a transient error doesn't strip every badge.
|
|
374
277
|
*/
|
|
375
278
|
async _refreshDetection() {
|
|
376
279
|
const commands = this._detectCommands();
|
|
@@ -397,15 +300,9 @@ export class SessionRegistry {
|
|
|
397
300
|
}
|
|
398
301
|
}
|
|
399
302
|
/**
|
|
400
|
-
* Poll body:
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
* buffer, editors run full-screen UIs that are not "activity", and plain
|
|
404
|
-
* shells would only echo their prompt. While a tab is reopening (its xterm
|
|
405
|
-
* is not ready yet) any existing line is kept; once the buffer is readable
|
|
406
|
-
* but has nothing worth showing the line is dropped, so a screen the agent
|
|
407
|
-
* has cleared doesn't leave a frozen line behind. Cached lines for sessions
|
|
408
|
-
* that no longer qualify are pruned. Emits only when something changed.
|
|
303
|
+
* Poll body: cache the latest meaningful output line of each open agent
|
|
304
|
+
* terminal (closed tabs have no live buffer; editors and shells don't
|
|
305
|
+
* qualify). Emits only when something changed.
|
|
409
306
|
*/
|
|
410
307
|
async _refreshActivity() {
|
|
411
308
|
if (!this._activityEnabled) {
|
|
@@ -425,8 +322,8 @@ export class SessionRegistry {
|
|
|
425
322
|
qualifying.add(name);
|
|
426
323
|
const term = widget.content._term;
|
|
427
324
|
if (!term) {
|
|
428
|
-
// The tab is reopening
|
|
429
|
-
//
|
|
325
|
+
// The tab is reopening; keep any existing line until its xterm buffer
|
|
326
|
+
// is readable again.
|
|
430
327
|
continue;
|
|
431
328
|
}
|
|
432
329
|
const line = Private.readActivity(term);
|
|
@@ -437,8 +334,8 @@ export class SessionRegistry {
|
|
|
437
334
|
}
|
|
438
335
|
}
|
|
439
336
|
else if (this._activity.delete(name)) {
|
|
440
|
-
// Readable buffer with nothing to surface (e.g.
|
|
441
|
-
//
|
|
337
|
+
// Readable buffer with nothing to surface (e.g. a cleared screen) —
|
|
338
|
+
// drop the stale line instead of freezing it.
|
|
442
339
|
changed = true;
|
|
443
340
|
}
|
|
444
341
|
}
|
|
@@ -454,8 +351,8 @@ export class SessionRegistry {
|
|
|
454
351
|
}
|
|
455
352
|
_onTagChanged() {
|
|
456
353
|
this._stateChanged.emit();
|
|
457
|
-
// Re-
|
|
458
|
-
//
|
|
354
|
+
// Re-emit once the grace window closes so a tag detection never confirmed
|
|
355
|
+
// (or has since contradicted) stops being shown.
|
|
459
356
|
setTimeout(() => {
|
|
460
357
|
if (!this._isDisposed) {
|
|
461
358
|
this._stateChanged.emit();
|
|
@@ -470,10 +367,8 @@ export class SessionRegistry {
|
|
|
470
367
|
this._updateCurrent();
|
|
471
368
|
}
|
|
472
369
|
/**
|
|
473
|
-
* Recompute which terminal (if any) is the active main-area widget
|
|
474
|
-
* only
|
|
475
|
-
* current widget is one of our terminals; anything else — a notebook, an
|
|
476
|
-
* editor, or nothing — clears the highlight.
|
|
370
|
+
* Recompute which terminal (if any) is the active main-area widget; emits
|
|
371
|
+
* only on change. Anything not a tracked terminal clears the highlight.
|
|
477
372
|
*/
|
|
478
373
|
_updateCurrent() {
|
|
479
374
|
var _a, _b;
|
|
@@ -506,16 +401,10 @@ export class SessionRegistry {
|
|
|
506
401
|
this._stateChanged.emit();
|
|
507
402
|
}
|
|
508
403
|
/**
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
* the
|
|
513
|
-
* `_initialConnection` fires). Without the filter, briefly
|
|
514
|
-
* reopening a tab while an agent has yet to re-emit its xterm
|
|
515
|
-
* title escape sequence would clobber the cached agent name with
|
|
516
|
-
* `Terminal 1`. Live widget titles still display whatever the widget
|
|
517
|
-
* currently holds because `labelFor` consults the widget first;
|
|
518
|
-
* the filter only affects what survives a tab close.
|
|
404
|
+
* Cache a label only when it is "real" — not `'...'` or `Terminal {name}`,
|
|
405
|
+
* the transient defaults xterm cycles through during reconnect. Without the
|
|
406
|
+
* filter, reopening a tab before the agent re-emits its title escape would
|
|
407
|
+
* clobber the cached agent name.
|
|
519
408
|
*/
|
|
520
409
|
_cacheLabel(name, label) {
|
|
521
410
|
if (!label) {
|
|
@@ -527,11 +416,8 @@ export class SessionRegistry {
|
|
|
527
416
|
this._labels.set(name, label);
|
|
528
417
|
}
|
|
529
418
|
_onWidgetDisposed(widget) {
|
|
530
|
-
// The
|
|
531
|
-
//
|
|
532
|
-
// when the user reopens the tab. We only clean up our subscriptions
|
|
533
|
-
// here; the cache is purged by `runningChanged` once the session
|
|
534
|
-
// itself goes away.
|
|
419
|
+
// The session may still be running on the server; the cached label stays
|
|
420
|
+
// until `runningChanged` purges it with the session itself.
|
|
535
421
|
this._untrackWidget(widget);
|
|
536
422
|
this._stateChanged.emit();
|
|
537
423
|
}
|
|
@@ -539,8 +425,7 @@ export class SessionRegistry {
|
|
|
539
425
|
var Private;
|
|
540
426
|
(function (Private) {
|
|
541
427
|
/**
|
|
542
|
-
*
|
|
543
|
-
* doesn't trigger a re-render.
|
|
428
|
+
* Whether two detection maps hold identical entries.
|
|
544
429
|
*/
|
|
545
430
|
function detectedEqual(a, b) {
|
|
546
431
|
if (a.size !== b.size) {
|
|
@@ -554,33 +439,21 @@ var Private;
|
|
|
554
439
|
return true;
|
|
555
440
|
}
|
|
556
441
|
Private.detectedEqual = detectedEqual;
|
|
557
|
-
/**
|
|
558
|
-
* Rows above the cursor to scan when looking for the latest output.
|
|
559
|
-
*/
|
|
560
442
|
const ACTIVITY_SCAN_ROWS = 64;
|
|
561
|
-
/**
|
|
562
|
-
* Clamp for the activity string so one runaway block can't bloat a row.
|
|
563
|
-
*/
|
|
564
443
|
const ACTIVITY_MAX_LENGTH = 160;
|
|
565
444
|
/**
|
|
566
|
-
* Horizontal
|
|
567
|
-
*
|
|
568
|
-
*
|
|
569
|
-
* deliberately excluded — {@link isMeaningfulActivity} relies on them to
|
|
570
|
-
* recognise (and skip) the contents of an input/quote box.
|
|
445
|
+
* Horizontal rule characters treated as blanks when sanitizing (status-line
|
|
446
|
+
* padding is chrome, not text). Vertical bars are excluded on purpose —
|
|
447
|
+
* {@link isMeaningfulActivity} uses them to skip input/quote boxes.
|
|
571
448
|
*/
|
|
572
449
|
const RULE_CHARS = new Set([
|
|
573
450
|
0x2500, 0x2501, 0x2504, 0x2505, 0x2508, 0x2509, 0x254c, 0x254d, 0x2550
|
|
574
451
|
]);
|
|
575
452
|
/**
|
|
576
|
-
* The most recent meaningful line of agent output, or `null
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
* of real output nearest the input. It then rewinds to that block's first row
|
|
581
|
-
* and stitches the block back together (see {@link joinBlock}), so a reply
|
|
582
|
-
* the agent hard-wrapped across several rows reads as its opening sentence
|
|
583
|
-
* rather than the trailing fragment left next to the cursor.
|
|
453
|
+
* The most recent meaningful line of agent output, or `null`. Agents park
|
|
454
|
+
* the cursor in a bottom input box, so the scan walks *up* past chrome to
|
|
455
|
+
* the nearest output block, then rewinds to that block's first row so a
|
|
456
|
+
* hard-wrapped reply reads as its opening sentence, not a trailing fragment.
|
|
584
457
|
*/
|
|
585
458
|
function readActivity(term) {
|
|
586
459
|
var _a;
|
|
@@ -594,8 +467,6 @@ var Private;
|
|
|
594
467
|
if (!isMeaningfulActivity(lineText(buffer, row))) {
|
|
595
468
|
continue;
|
|
596
469
|
}
|
|
597
|
-
// `row` is the bottom of the output block nearest the input; walk up
|
|
598
|
-
// while rows stay meaningful to find the block's first row.
|
|
599
470
|
let topRow = row;
|
|
600
471
|
while (topRow > limit &&
|
|
601
472
|
isMeaningfulActivity(lineText(buffer, topRow - 1))) {
|
|
@@ -607,7 +478,7 @@ var Private;
|
|
|
607
478
|
}
|
|
608
479
|
Private.readActivity = readActivity;
|
|
609
480
|
/**
|
|
610
|
-
*
|
|
481
|
+
* Read a buffer row as sanitized text; empty when the row is missing.
|
|
611
482
|
*/
|
|
612
483
|
function lineText(buffer, row) {
|
|
613
484
|
var _a, _b;
|
|
@@ -615,10 +486,9 @@ var Private;
|
|
|
615
486
|
}
|
|
616
487
|
Private.lineText = lineText;
|
|
617
488
|
/**
|
|
618
|
-
* Stitch rows `[topRow, lastRow]` of one output block into a single string
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
* newline is a word boundary, so it is joined with a space.
|
|
489
|
+
* Stitch rows `[topRow, lastRow]` of one output block into a single string.
|
|
490
|
+
* A soft-wrapped row continues mid-word (appended directly); a hard newline
|
|
491
|
+
* is a word boundary (joined with a space).
|
|
622
492
|
*/
|
|
623
493
|
function joinBlock(buffer, topRow, lastRow) {
|
|
624
494
|
var _a;
|
|
@@ -648,9 +518,7 @@ var Private;
|
|
|
648
518
|
}
|
|
649
519
|
Private.clampActivity = clampActivity;
|
|
650
520
|
/**
|
|
651
|
-
*
|
|
652
|
-
* and horizontal rule characters with spaces (so no escape sequence or drawn
|
|
653
|
-
* separator rides along), squeeze runs of whitespace, and trim.
|
|
521
|
+
* Replace control and rule characters with spaces and collapse whitespace.
|
|
654
522
|
*/
|
|
655
523
|
function sanitizeActivity(value) {
|
|
656
524
|
var _a;
|
|
@@ -664,18 +532,10 @@ var Private;
|
|
|
664
532
|
}
|
|
665
533
|
Private.sanitizeActivity = sanitizeActivity;
|
|
666
534
|
/**
|
|
667
|
-
* Whether a sanitized row is
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
* - begins with a vertical box-drawing bar (the contents of an input or
|
|
672
|
-
* quote box), a prompt chevron (the input row and its ghost suggestion),
|
|
673
|
-
* or Claude's tool-result / tip marker `⎿`; or
|
|
674
|
-
* - is a transient status footer — one carrying a `for <time>` / `(<time>`
|
|
675
|
-
* elapsed-time stamp, as agents print while and after they work
|
|
676
|
-
* ("✻ Churned for 17s", "Working… (8s · …)", "— Worked for 1m 06s"). This
|
|
677
|
-
* carries no real content, so skipping it lets the scan reach the reply
|
|
678
|
-
* the agent wrote just above it.
|
|
535
|
+
* Whether a sanitized row is real output rather than chrome; doubles as
|
|
536
|
+
* {@link readActivity}'s block-boundary test. Skips rows without a letter
|
|
537
|
+
* or digit, rows starting with a box bar / prompt chevron / `⎿`, and
|
|
538
|
+
* elapsed-time status footers ("✻ Churned for 17s", "Working… (8s · …)").
|
|
679
539
|
*/
|
|
680
540
|
function isMeaningfulActivity(text) {
|
|
681
541
|
if (!text) {
|
|
@@ -10,15 +10,12 @@ export interface IAgentTerminalSession {
|
|
|
10
10
|
*/
|
|
11
11
|
name: string;
|
|
12
12
|
/**
|
|
13
|
-
* The agent identifier
|
|
14
|
-
*
|
|
15
|
-
* is an alias (e.g. `ccm` spawning `claude`). Matches an `IAgent` by
|
|
16
|
-
* either field.
|
|
13
|
+
* The agent identifier detection reported: the configured `command`, or the
|
|
14
|
+
* canonical `id` when the command is an alias (e.g. `ccm` spawning `claude`).
|
|
17
15
|
*/
|
|
18
16
|
command: string;
|
|
19
17
|
/**
|
|
20
|
-
* Display label — the session's real
|
|
21
|
-
* whatever the running program published via an xterm escape sequence).
|
|
18
|
+
* Display label — the session's real (program-published) title.
|
|
22
19
|
*/
|
|
23
20
|
label: string;
|
|
24
21
|
/**
|
|
@@ -30,12 +27,9 @@ export interface IAgentTerminalSession {
|
|
|
30
27
|
/**
|
|
31
28
|
* Running agent terminals, and a way to send a prompt into one.
|
|
32
29
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* TUI can meaningfully receive pasted prompt text. `sendPrompt` re-validates
|
|
37
|
-
* against the server before writing, since pasting prose into a shell prompt
|
|
38
|
-
* would execute it.
|
|
30
|
+
* Only sessions with a detection-*confirmed* running agent are listed — never
|
|
31
|
+
* optimistic launch tags: pasted prompt text must land in an agent's TUI, not
|
|
32
|
+
* a shell prompt where it would be executed.
|
|
39
33
|
*/
|
|
40
34
|
export interface IAgentTerminals {
|
|
41
35
|
/**
|
|
@@ -47,20 +41,15 @@ export interface IAgentTerminals {
|
|
|
47
41
|
*/
|
|
48
42
|
readonly changed: ISignal<IAgentTerminals, void>;
|
|
49
43
|
/**
|
|
50
|
-
* Paste `prompt` into the named session
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* the session's websocket, bracketed-paste-wrapped so a multi-line prompt
|
|
54
|
-
* arrives as one block in the agent's input box, without revealing or
|
|
55
|
-
* focusing the terminal's tab — the user keeps working where they are,
|
|
56
|
-
* and the agent CLIs queue the prompt themselves when they are busy.
|
|
57
|
-
* Rejects when the session is gone or no agent is running in it anymore.
|
|
44
|
+
* Paste `prompt` (bracketed-paste-wrapped) into the named session and press
|
|
45
|
+
* Enter, writing straight to its websocket without revealing or focusing
|
|
46
|
+
* its tab. Rejects when the session is gone or no agent runs in it anymore.
|
|
58
47
|
*/
|
|
59
48
|
sendPrompt(name: string, prompt: string): Promise<void>;
|
|
60
49
|
}
|
|
61
50
|
/**
|
|
62
51
|
* DI token for {@link IAgentTerminals}. Provided by `xtralab:terminals`;
|
|
63
|
-
* consumed
|
|
64
|
-
*
|
|
52
|
+
* consumed optionally by `xtralab:ask-agent`, so each side works without
|
|
53
|
+
* the other.
|
|
65
54
|
*/
|
|
66
55
|
export declare const IAgentTerminals: Token<IAgentTerminals>;
|
package/lib/terminals/tokens.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Token } from '@lumino/coreutils';
|
|
2
2
|
/**
|
|
3
3
|
* DI token for {@link IAgentTerminals}. Provided by `xtralab:terminals`;
|
|
4
|
-
* consumed
|
|
5
|
-
*
|
|
4
|
+
* consumed optionally by `xtralab:ask-agent`, so each side works without
|
|
5
|
+
* the other.
|
|
6
6
|
*/
|
|
7
7
|
export const IAgentTerminals = new Token('xtralab:IAgentTerminals', 'The running terminal sessions with a detected coding agent, and a way to send a prompt into one.');
|